- 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
6.6 KiB
cf — bash CLI for clawdforge
Single-file shell client for clawdforge. 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
sudo install -m 755 cf /usr/local/bin/cf
Or just symlink into ~/bin:
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:
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
cf healthz | jq -e '.claude_present' >/dev/null || { echo "clawdforge not ready"; exit 1; }
One-shot prompt with JSON output
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)
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
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
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 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
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:
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:
cf session turn "$sid" "explain the PR" --trace ./traces/turn-$(date +%s).json
Listing and inspecting
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:
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_TOKENnot set4— 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:
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).