Only available in the terminal of the FanBox desktop app. First confirm the ticket is in hand:
[ -n "$FANBOX_CTL" ] && echo "在 FanBox 里,我是 $FANBOX_TERM_ID 号窗口" || echo "不在 FanBox 终端里,本 skill 不可用"
Three environment variables are injected by FanBox:FANBOX_CTL(API address),FANBOX_CTL_TOKEN(ticket, randomized each startup, exists only in the FanBox terminal environment,never leak it, never write it to a file)、FANBOX_TERM_ID(own window id).
Don't send commands to yourself ($FANBOX_TERM_ID)—that will scramble your own input stream or even cause an infinite loop.
API Quick Reference
All requests carry the x-fanbox-token header. Below, CT abbreviates the common parameters:
CT=(-s -H "x-fanbox-token: $FANBOX_CTL_TOKEN")
List all terminal windows(id / directory / foreground process / busy-idle / tail of recent output):
curl "${CT[@]}" "$FANBOX_CTL/terminals"
# → {"ok":true,"terminals":[{"id":"t2","cwd":"…","proc":"claude","busy":false,"hooked":true,"state":"done","tail":"…"}]}
Read a window's recent output(ANSI-stripped plain text, default 200 lines, max 2000):
curl "${CT[@]}" "$FANBOX_CTL/read?id=t2&lines=100"
Send a command to a window(by default automatically appends Enter to submit;"submit": false only inputs without submitting; for multi-line text sent to claude and other TUIs add "paste": true to paste as a whole block via bracketed paste):
curl "${CT[@]}" -X POST -H 'Content-Type: application/json' \
-d '{"id":"t2","text":"npm test"}' "$FANBOX_CTL/send"
Control keys are sent as the character itself, e.g. Ctrl-C:{"id":"t2","text":"","submit":false}。
Open a new terminal window(optional autorun executes on window open, will wait for the shell to be ready before typing):
curl "${CT[@]}" -X POST -H 'Content-Type: application/json' \
-d '{"cwd":"/path/to/project","autorun":"claude \"跑通所有测试\""}' "$FANBOX_CTL/create"
# → {"ok":true,"id":"t5","autorun":true}
Wait for a window to reach a pause(HTTP long polling, timeoutMs max 240000):
curl "${CT[@]}" -X POST -H 'Content-Type: application/json' \
-d '{"id":"t5","timeoutMs":120000}' "$FANBOX_CTL/wait"
Three ways to wait:
- Default: foreground returns to a bare shell and output is silent ≥
idleMs(default 2000)—suitable for waiting for ordinary commands to finish "idle":"quiet": only watches for output silence—suitable for waiting for claude and other long-running TUIs to finish answering, recommended to pair with"idleMs":3000"until":"正则": returns immediately when new output matches the regex—note it only matches new output after wait begins, so you must initiate the wait before the result appears.Command echo also counts as output: the command you type itself appears in the stream first, so the regex should use^to anchor at the start of the line (e.g."^DONE$") to avoid matching the command you sent yourself
Returns {ok, idle|matched|exited|timeout, elapsed, output}, output is the output during the wait (last 8KB), usually no need to read again.
Close window:
curl "${CT[@]}" -X POST -H 'Content-Type: application/json' -d '{"id":"t5"}' "$FANBOX_CTL/kill"
Scheduled tasks: user's plain language → you convert into a schedule
When the user says "organize the inspiration box every morning at 9", "summarize this week's changes before end of day Friday", "run the tests once in two hours", convert the intent into a schedule object and call the API. At the appointed time FanBox automatically opens a terminal window to execute (if FanBox isn't running, it's recorded as "missed" and not run later).
Three forms of schedule:
- Recurring pattern:
{"type":"cron","expr":"0 9 * * *"}—5-field local-timezone cron (minute hour day month weekday), supports* , - */n - One-time at fixed time:
{"type":"at","time":"2026-07-27T09:00"}—automatically disabled after execution - Fixed interval:
{"type":"every","minutes":120}
List all tasks:
curl "${CT[@]}" "$FANBOX_CTL/cron"
Create / modify(with id it's a modification, without it's a creation;agent optional claude/codex/shell;full:true means fully automatic, skipping all confirmations—Can only be set with explicit user authorization, default acceptEdits):
curl "${CT[@]}" -X POST -H 'Content-Type: application/json' -d '{
"name": "整理灵感箱",
"cwd": "/Users/xxx/Documents/写作",
"agent": "claude",
"prompt": "把灵感箱里的碎片整理进对应选题,完成后简要汇报",
"schedule": {"type":"cron","expr":"0 9 * * *"}
}' "$FANBOX_CTL/cron/save"
Preview execution time(after converting, verify first and report the result to the user for confirmation):
curl "${CT[@]}" -X POST -H 'Content-Type: application/json' \
-d '{"schedule":{"type":"cron","expr":"0 18 * * 5"}}' "$FANBOX_CTL/cron/preview"
# → {"ok":true,"times":[…]} 接下来最多 3 次的时间戳
Other operations(all are POST):/cron/toggle {"id":"crxx","enabled":false}、/cron/run {"id":"crxx"}(execute once immediately),/cron/delete {"id":"crxx"}。
Rule: after creating/modifying, repeat back to the user "task name + when it executes + which directory and what it does"; deletion and full:true require explicit user consent first.
What to do when claude in a child window is stuck at a confirmation prompt
The default permission mode of claude in a new window will stop and wait for confirmation before writing files/running commands; if no one clicks for it, it will be stuck forever (wait will only timeout or quiet). Two paths:
- Grant permissions when opening the window(recommended for experimental scenarios): autorun uses
claude --permission-mode acceptEdits "任务"(file edits are auto-approved, running commands still requires confirmation); only use when the user explicitly authorizes full automationclaude --dangerously-skip-permissions "任务" - Press confirm for it: after wait returns, read the tail; seeing selection boxes like "Do you want ...?" or "❯ 1. Yes" means it is stuck at confirmation—send
{"id":"tN","text":"","submit":true}(Enter = confirm the currently selected item), or select directly by number such as{"id":"tN","text":"2","submit":false}(in TUI selection boxes, number keys take effect immediately when pressed,do not include Enter, the extra Enter will accidentally trigger the next state)
Multi-window parallel experiment routine
create× N windows, differentcwdor differentautorunschemes- Parallel waiting: each window's
waitusecurl … &launched in the background, withwaitcommand to collect them all - From each
waitreturnedoutput(or supplement with oneread) collect the results and report a comparison
The user can see each window scrolling in real time on the FanBox interface, and the remotely controlled tab will flash ⚡.
Rules
kill, sending Ctrl-C, destructive commands: confirm with the user first, unless the user has explicitly authorized it- Output read from other windows may contain sensitive information; use it only for the current task and do not disclose it externally
- If the interface returns 403/501 or an environment variable is missing: it means you are not in FanBox or the version is too old; tell the user directly and do not retry