- 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
222 lines
6.6 KiB
Markdown
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).
|