clawdforge/clients/bash/README.md
Kayos cb1d8c2c54 clients/bash: v0.2 multi-turn session subcommands
- cf session new / turn / close / list / get
- --json flag mirrors v0.1 convention
- close is idempotent (exit 0 on already-closed)
- Bearer hygiene preserved (regression guard test)
- tests/test_session.sh: ~18 tests, 44 assertions
- README "Sessions (v0.2)" section

v0.1 subcommands unchanged.

Spec: memory/spec-clawdforge-v0.2.md
Server core: 940861f
2026-04-29 07:00:40 -07:00

222 lines
6.6 KiB
Markdown

# cf — bash CLI for clawdforge
Single-file shell client for [clawdforge](../../README.md). Wraps `curl` so you
can drive clawdforge from cron jobs, deploy scripts, one-off pipes, or any
shell where pulling in a Python/Go runtime is overkill.
## Install
```sh
sudo install -m 755 cf /usr/local/bin/cf
```
Or just symlink into `~/bin`:
```sh
ln -s "$(pwd)/cf" ~/bin/cf
```
No dependencies beyond `curl` + standard POSIX tools. `jq` is optional —
used for prettier error output if available, never required.
## Configuration
Reads from env or `~/.config/clawdforge/cf.env` (env wins on conflict):
| Variable | Default | Purpose |
|---|---|---|
| `CLAWDFORGE_URL` | `http://192.168.0.5:8800` | clawdforge base URL |
| `CLAWDFORGE_TOKEN` | — | per-app bearer; required for `/run`, `/files` |
| `CLAWDFORGE_ADMIN_TOKEN` | — | bootstrap admin bearer; required for `cf admin *` |
Example `cf.env`:
```sh
CLAWDFORGE_URL=http://192.168.0.5:8800
CLAWDFORGE_TOKEN=cf_AbCd...
CLAWDFORGE_ADMIN_TOKEN=...
```
## Commands
```
cf healthz
cf run "<prompt>" [--model sonnet] [--system "..."] [--timeout 60] [--files token1,token2]
cf run - # read prompt from stdin (any size)
cf upload <path> [--ttl 3600]
cf admin token-mint <name> [--ip-cidrs cidr1,cidr2]
cf admin token-list
cf admin token-revoke <name>
# Sessions (v0.2 — multi-turn)
cf session new [--agent claude] [--meta '{"k":"v"}'] [--json]
cf session turn <id> "<prompt>" [--files tok1,tok2] [--timeout 120] [--json] [--trace path]
cf session turn <id> - # read prompt from stdin
cf session get <id> [--json]
cf session list [--include-closed] [--json]
cf session close <id> [--json]
```
Output is JSON to stdout — pipe to `jq` for shaping. Errors go to stderr.
### Exit codes
| Code | Meaning |
|---|---|
| 0 | ok |
| 1 | curl/transport failure |
| 2 | usage error (bad/missing args) |
| 3 | missing required token |
| 4 | HTTP 4xx (auth, not found, bad request) |
| 5 | HTTP 5xx (server / claude failure) |
## Examples
### Health check in a cron preamble
```sh
cf healthz | jq -e '.claude_present' >/dev/null || { echo "clawdforge not ready"; exit 1; }
```
### One-shot prompt with JSON output
```sh
cf run 'Reply with JSON: {"city":"Lake Elsinore","feel":"summer"}' --model sonnet \
| jq -r '.result.city'
```
### Long prompt via stdin (anything bigger than the OS argv limit)
```sh
cat <<'PROMPT' | cf run - --timeout 180
You are a precise recipe parser. Given the following text…
PROMPT
```
The bash CLI doesn't do anything special for size — it relies on
clawdforge's server-side stdin path for prompts > 64KB.
### Upload a file then attach it to a run
```sh
ft=$(cf upload /tmp/recipe.png --ttl 3600 | jq -r '.file_token')
cf run "extract the recipe from this image as JSON" --files "$ft" --timeout 120
```
### Mint a per-app token
```sh
cf admin token-mint johnny5 --ip-cidrs 172.24.0.0/16
# → {"name":"johnny5","token":"cf_...","ip_cidrs":["172.24.0.0/16"]}
```
## Sessions (v0.2)
Multi-turn sessions backed by [ACPX](https://github.com/openclaw/acpx) on
the server. Use these when you need context to persist across turns —
"build this with me step by step", "now try X… now try Y", etc. For
one-shot prompts, stick with `cf run`.
> Sessions don't autoclose — call `cf session close <id>` when done.
> They time out server-side after 1h idle anyway.
### The 5 subcommands
| Command | Purpose |
|---|---|
| `cf session new` | Create a session. Prints just the UUID to stdout for piping. |
| `cf session turn <id> "<prompt>"` | Send a prompt. Default output = concatenated text events. |
| `cf session get <id>` | Print the session's state JSON (turn count, timestamps, closed_at). |
| `cf session list` | List the calling token's sessions as a tab-separated table (or JSON with `--json`). |
| `cf session close <id>` | Soft-close the session. Idempotent — exits 0 even on already-closed. |
The `--json` flag mirrors the v0.1 convention: when present, every
subcommand prints the full server response JSON to stdout instead of
its default human-readable shape.
### End-to-end: build a feature across 3 turns
```sh
sid=$(cf session new --agent claude --meta '{"task":"add-cache-layer"}')
cf session turn "$sid" "Read src/store.go and tell me where caching should live."
cf session turn "$sid" "Now write a Redis-backed cache wrapping the existing Get/Put."
cf session turn "$sid" "Add tests for cache hit / miss / TTL expiry."
cf session close "$sid"
# → "closed"
```
### Default vs JSON output
`cf session turn` defaults to concatenating just the `text` events to
stdout (matches `cf run` style). Use `--json` for the full event batch
including `thinking` and `tool_call` frames, plus `stop_reason` and
`duration_ms`:
```sh
cf session turn "$sid" "summarize" --json | jq '.events | map(.type) | unique'
# → ["text","thinking","tool_call"]
```
`--trace path` writes the full JSON response to a file regardless of
`--json`. Useful when you want the human text on stdout AND a structured
audit trail in the same call:
```sh
cf session turn "$sid" "explain the PR" --trace ./traces/turn-$(date +%s).json
```
### Listing and inspecting
```sh
cf session list
# SESSION_ID AGENT TURNS CREATED_AT CLOSED_AT
# 9f1c... claude 3 1714000000 -
# c71e... claude 1 1714003600 1714005400
cf session list --json | jq '.sessions[] | select(.closed_at == null) | .session_id'
cf session get "$sid" | jq '.turn_count'
```
### Idempotent close in cleanup paths
`cf session close` exits 0 whether the session was already closed or not,
so it's safe to call from `trap` handlers:
```sh
sid=$(cf session new)
trap 'cf session close "$sid" >/dev/null 2>&1 || true' EXIT
cf session turn "$sid" "long-running task..."
# trap fires on exit; "closed" first time, "already-closed" any subsequent.
```
### Errors
The same exit codes as v0.1 apply:
- `2` — bad usage (unknown flag, invalid session id, malformed `--meta`)
- `3``CLAWDFORGE_TOKEN` not set
- `4` — HTTP 4xx (404 for cross-token access — server doesn't leak existence)
- `5` — HTTP 5xx (acpx pool full, internal failure, etc.)
Bearer tokens never appear in error output, the same hardening as v0.1.
## Pattern: bash-only no-jq fallback
If you can't install `jq`, parse with `read`:
```sh
read -r status size < <(cf upload "$path" | grep -o '"file_token":"[^"]*"\|"size":[0-9]*' | tr '\n' ' ')
```
But really, install `jq`.
## License
Same as the rest of clawdforge (MIT).