Rewrite README to reflect current state
Covers keygen workflow, auth model, all three binaries, env vars, action registry, path resolution, and security notes. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
182
README.md
182
README.md
@@ -1,11 +1,25 @@
|
||||
# claude-code-conduit
|
||||
|
||||
A supervised action bridge between Claude Code and the host system.
|
||||
A supervised action bridge between Claude Code and the host system. Claude requests structured actions; the server applies per-action policies and optionally holds them for human approval before executing.
|
||||
|
||||
Claude requests structured actions. The server applies per-action policies:
|
||||
- **auto-accept** — executed immediately (e.g. open a file in editor)
|
||||
- **auto-deny** — rejected immediately
|
||||
- **queue** — held for user approval (e.g. open a browser URL)
|
||||
## Concepts
|
||||
|
||||
**Actions** are typed verbs with named parameters — not shell commands. The server defines what actions exist and what happens when they are called. Example:
|
||||
|
||||
```json
|
||||
{ "action": "edit-file", "filename": "/workspace/foo.mjs" }
|
||||
```
|
||||
|
||||
**Policies** control what happens when an action is requested:
|
||||
- `auto-accept` — executed immediately (e.g. open a file in the editor)
|
||||
- `auto-deny` — rejected immediately
|
||||
- `queue` — held for human approval (e.g. open a browser URL)
|
||||
|
||||
**Authentication** uses HMAC-SHA256. Every request is signed with the caller's secret. Secrets live in a JSON file — never in environment variables.
|
||||
|
||||
**Users** each have a secret and a `canApprove` list controlling whose queued actions they may approve.
|
||||
|
||||
---
|
||||
|
||||
## Setup
|
||||
|
||||
@@ -13,54 +27,136 @@ Claude requests structured actions. The server applies per-action policies:
|
||||
npm install
|
||||
```
|
||||
|
||||
## Running the server
|
||||
### Generate secrets
|
||||
|
||||
```bash
|
||||
node server/index.js
|
||||
# or
|
||||
CONDUIT_PORT=3333 CONDUIT_ROOT=/workspace node server/index.js
|
||||
# Create a secrets file with random secrets for each user
|
||||
ccc-keygen --create user,agent
|
||||
|
||||
# Edit secrets.json to configure who can approve whom:
|
||||
# set user.canApprove = ["agent"]
|
||||
|
||||
# Produce a filtered file for the agent (e.g. to copy into a Docker container)
|
||||
ccc-keygen --filter agent --output agent-secrets.json
|
||||
```
|
||||
|
||||
## Using the CLI client
|
||||
The full `secrets.json` stays on the host. `agent-secrets.json` goes into the container.
|
||||
|
||||
---
|
||||
|
||||
## Running
|
||||
|
||||
### Server (host)
|
||||
|
||||
```bash
|
||||
# List available actions
|
||||
node client/index.js list-actions
|
||||
|
||||
# Open a file in the editor (auto-accepted)
|
||||
node client/index.js edit-file filename=/workspace/myfile.js
|
||||
|
||||
# Open a URL (queued for user approval)
|
||||
node client/index.js open-browser url=https://example.com
|
||||
ccc-server --secrets secrets.json
|
||||
```
|
||||
|
||||
When a queued action is submitted, the server prints the approve/deny URLs to stdout:
|
||||
|
||||
```
|
||||
[QUEUE] New request #a1b2c3d4
|
||||
Action: open-browser
|
||||
Params: {"url":"https://example.com"}
|
||||
Approve: POST /queue/a1b2c3d4.../approve
|
||||
Deny: POST /queue/a1b2c3d4.../deny
|
||||
```
|
||||
|
||||
User approves via:
|
||||
```bash
|
||||
curl -X POST http://localhost:3333/queue/<id>/approve
|
||||
```
|
||||
|
||||
## Environment variables
|
||||
|
||||
Environment variables:
|
||||
| Variable | Default | Description |
|
||||
|----------|---------|-------------|
|
||||
| `CONDUIT_PORT` | `3333` | Server port |
|
||||
| `CONDUIT_PORT` | `3015` | Port to listen on |
|
||||
| `CONDUIT_ROOT` | `/workspace` | Workspace root for path resolution |
|
||||
| `CONDUIT_URL` | `http://localhost:3333` | Server URL (client-side) |
|
||||
|
||||
## Adding actions
|
||||
### Client (container / agent)
|
||||
|
||||
Edit `server/actions.js`. Each action needs:
|
||||
- `description` — shown in list-actions
|
||||
- `params` — array of `{ name, required, type }`
|
||||
- `policy` — `"auto-accept"` | `"auto-deny"` | `"queue"`
|
||||
- `handler(params, helpers)` — async function that performs the action
|
||||
```bash
|
||||
ccc-client --secrets agent-secrets.json --user agent '{"action": "list-actions"}'
|
||||
ccc-client --secrets agent-secrets.json --user agent '{"action": "edit-file", "filename": "/workspace/foo.mjs"}'
|
||||
```
|
||||
|
||||
`--secrets` and `--user` can also be set via environment variables:
|
||||
|
||||
```bash
|
||||
export CCC_SECRETS=/path/to/agent-secrets.json
|
||||
export CCC_USER=agent
|
||||
ccc-client '{"action": "list-actions"}'
|
||||
```
|
||||
|
||||
The JSON payload can be spread across multiple arguments — they are space-joined before parsing:
|
||||
|
||||
```bash
|
||||
ccc-client '{"action": "edit-file",' '"filename": "/workspace/foo.mjs"}'
|
||||
```
|
||||
|
||||
### Queue manager (host)
|
||||
|
||||
```bash
|
||||
ccc-queue --secrets secrets.json --user user
|
||||
```
|
||||
|
||||
Opens an interactive TUI showing pending actions:
|
||||
|
||||
```
|
||||
┌─ Pending Actions ──────────┐ ┌─ Details ───────────────────────────┐
|
||||
│ │ │ │
|
||||
│ > [a1b2c3] open-browser │ │ Action: open-browser │
|
||||
│ [d4e5f6] open-terminal │ │ ID: a1b2c3d4-... │
|
||||
│ │ │ Submitted by: agent │
|
||||
│ │ │ Created: 2026-03-07T12:00:00Z │
|
||||
│ │ │ │
|
||||
│ │ │ Params: │
|
||||
│ │ │ url: https://example.com │
|
||||
└─────────────────────────────┘ └──────────────────────────────────────┘
|
||||
[y] approve [n] deny [r] refresh [q] quit
|
||||
```
|
||||
|
||||
Supports `CCC_SECRETS` and `CCC_USER` env vars the same as the client.
|
||||
|
||||
---
|
||||
|
||||
## Actions
|
||||
|
||||
Query available actions at runtime:
|
||||
|
||||
```bash
|
||||
ccc-client '{"action": "list-actions"}'
|
||||
```
|
||||
|
||||
Built-in actions:
|
||||
|
||||
| Action | Policy | Params |
|
||||
|--------|--------|--------|
|
||||
| `list-actions` | auto-accept | — |
|
||||
| `edit-file` | auto-accept | `filename` (path) |
|
||||
| `open-browser` | queue | `url` (http/https only) |
|
||||
| `open-terminal` | queue | `path` (optional) |
|
||||
|
||||
### Adding actions
|
||||
|
||||
Edit `server/actions.mjs`. Each entry needs:
|
||||
|
||||
```js
|
||||
'my-action': {
|
||||
description: 'What this does',
|
||||
params: [{ name: 'foo', required: true, type: 'string' }],
|
||||
policy: 'auto-accept', // or 'auto-deny' | 'queue'
|
||||
handler: ({ foo }) => {
|
||||
// do something
|
||||
return { result: foo };
|
||||
},
|
||||
},
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Path resolution
|
||||
|
||||
The server translates container-side paths to host-side paths using the volume map in `server/helpers.mjs`. By default this matches the `docker-compose.yml` layout:
|
||||
|
||||
| Container path | Host path |
|
||||
|----------------|-----------|
|
||||
| `/workspace` | `<CONTAINER_PATH>/workspace` |
|
||||
| `/home/claude` | `<CONTAINER_PATH>/claude-home` |
|
||||
|
||||
Paths outside known volumes are rejected. Edit `CONTAINER_PATH` and `VOLUME_MAPPING` in `server/helpers.mjs` to match your setup.
|
||||
|
||||
---
|
||||
|
||||
## Security notes
|
||||
|
||||
- Secrets are never passed via environment variables or command line arguments — only via a file
|
||||
- HMAC signatures include a timestamp; requests older than 30 seconds are rejected
|
||||
- `canApprove` is empty by default — permissions must be explicitly granted
|
||||
- Browser URLs are validated to `http`/`https` only before being passed to `xdg-open`
|
||||
- All path arguments are resolved against the volume map; traversal outside known volumes is rejected
|
||||
|
||||
Reference in New Issue
Block a user