Security: - S1: bearer via tmpfile/--config, not cmdline arg (no /proc/<pid>/cmdline leak) - S2/S3: JSON-escape user input in --files, --ip-cidrs, token name - S4: URL-encode token name in revoke - S5: refuse to source cf.env unless 0600/0400 + owner-matched - S6: reject ; in upload paths to defeat curl @ filename injection Correctness: - B1: refuse cf run - on TTY stdin - B2: replace fragile files splice with proper JSON-array composer (raw: passthrough in _json_obj_from_assoc) - B3: disable glob on comma-split (set -f around loop) - B4: only create stdin tmpfile when actually used - B5: EXIT trap (was RETURN; missed _die exit) - B6/B7: --max-time + stderr capture on uploads - B8: drop bare Bearer header on healthz when no token - B9: validate admin subcommand before token - B10: wire _extract_error into HTTP-error path - U3: dedicated '# --- end help ---' sentinel for cmd_help New: clients/bash/test/test_cf.sh (curl wrapper mock + 23 assertions covering all of the above; fully shellcheck-clean). Audit: memory/clawdforge-audits/bash-347fdde.md |
||
|---|---|---|
| .. | ||
| test | ||
| cf | ||
| README.md | ||
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>
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"]}
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).