A five-minute build runs. The agent waits. You wait. Nothing else happens — and you are billed for all of it twice: your hour, and the tokens burning in a session doing nothing.
No single wait is worth stopping for. It is their sum that costs, and the sum is invisible until something counts it.
$ jbx gain
jbx gain — last 30d
commands wrapped 142
of those, detached 11 (7.7% of them)
they took 3h04m
you stood still 35m12s
given back 2h29m ███████████████████░░░░░ 81.0%
project calls detached elapsed waited saved impact
acme 142 11 3h04m 35m12s 2h29m (81%) ████████████████████████
api 96 7 2h11m 18m03s 1h53m (86%) ██████████████████░░░░░░
front 34 4 39m 3m17s 36m (92%) ██████░░░░░░░░░░░░░░░░░░
*self 12 0 14m 13m52s 8.0s (1%) ░░░░░░░░░░░░░░░░░░░░░░░░
last hour 6 calls · 1 detached · 8m12s saved (73%)
last day 38 calls · 4 detached · 40m05s saved (77%)
last week 142 calls · 11 detached · 2h29m saved (81%)
last month 142 calls · 11 detached · 2h29m saved (81%)That is one week. Put your own rate on it.
JobBox wraps every command your agent runs. The quick ones come back untouched — output as written, exit code unchanged, as though nothing were there. The slow ones detach themselves, say so, and tell whoever needs to know when they end.
Nobody judges in advance which is which. That judgement is the thing everybody gets wrong, so JobBox does not make it: it runs the line and finds out.
JobBox is the project; jbx is the command. One binary — Rust,
serde_json and yaml-rust2, nothing else. Linux, macOS, Windows.
$ jbx hook --list
claude Bash PreToolUse ~/.claude/settings.json
gemini run_shell_command BeforeTool ~/.gemini/settings.json
droid Execute PreToolUse declare by hand
cursor Shell preToolUse declare by hand (no unasked endings)
copilot bash preToolUse declare by handThey agree on almost nothing — not the name of the shell tool, not the
event, not the shape of the answer, not the file it is declared in.
jbx init handles the first two; the rest take one block of JSON, and
CLI-AI.md has the exact block for each, along with the
two it deliberately does not support and why.
$ curl -fsSL https://raw.githubusercontent.com/quazardous/jobbox/main/install.sh | shIt checks the download against the sums published with the release, puts
it on your PATH, and asks before declaring its hooks — they go in a
settings file other tools share. On Windows, irm https://raw.githubusercontent.com/quazardous/jobbox/main/install.ps1 | iex, and WINDOWS.md has the rest. No script at all:
cargo install --git https://github.com/quazardous/jobbox, then
jbx init — that builds main, and --tag builds a release. On Claude
Code, jbx init also places the skills /jbx, /jbx-after and
/jbx-slots.
Or, on Claude Code, the plugin — one or the other, not both, since each brings the same skills under its own names. It declares the announcing hooks as well — the ones that make an ending report itself unasked — along with the background watch:
$ claude plugin marketplace add quazardous/jobbox
$ claude plugin install jbx@jobboxThe plugin does not carry the binary, and the order above is the
reason. A marketplace installs from the repository, and binaries do not
live in a repository; the release archives carry them, a git checkout
does not. So the plugin uses the jbx you already have, and says so
plainly if there is none.
Run jbx init as well if you have rtk.
A plugin declares hooks; it cannot displace somebody else's, and two
hooks rewriting one field is a race no harness documents. init settles
that by calling rtk itself.
Nothing changes — until something is slow:
$ npm run build
> building…
jbx: this passed 30s, so it is in the BACKGROUND as j7f3a91c — nothing lost.
DO NOT WAIT FOR IT, DO SOMETHING ELSE.
jbx help j7f3a91cThe build output arrived as it was written, not replayed at the end.
The ending reaches you two ways, and one is better. Left alone, it is
announced on the next turn — free, and it costs that delay. Handed to
Monitor, it arrives the moment it happens: jbx wait <id> ends exactly
when the job does, so whatever is watching is woken then. jbx watch
does that for every job at once, one line each.
The message does not hand over that command, on purpose. Saying "do
not wait" and then offering the line that waits is a contradiction, and
an agent resolves it the easy way. jbx help <id> lists it, one step
further away — and allow_wait: false takes the foreground use away
entirely, for a project where the habit has already set in.
That is the difference between waiting and being told. Polling is neither — it is waiting with extra steps.
The old answer to "when should this go to the background?" was a document telling an agent to estimate how long a command would take. Agents get that wrong, and so do people. jbx removes the question and leaves a smaller one:
Do you need this result before you can do anything else?
Almost always, no. When the answer is yes, say so — jbx fg -- '<line>'
runs without ever letting go, and jbx gain counts what that cost.
- USAGE.md — every verb, every setting, how
savedis counted and why it is a ceiling, and what was deliberately left out. - CLI-AI.md — every agent CLI jbx answers, what to paste where, and the two it leaves alone.
- MACOS.md — why the install command never trips Gatekeeper, and what to do if you downloaded by hand.
- WINDOWS.md — Smart App Control, which shell runs your commands, and the two things that differ there.
- TROUBLESHOOTING.md — why nothing is detaching, which is usually on purpose.
- MIGRATING.md — what each
jobboxcommand became, and the one rename that goes wrong without a word. - CONTRIBUTING.md — what belongs in a test, and what has already been ruled out.
- CODE-SIGNING-POLICY.md — who may release this, what it never sends anywhere, and how to undo everything it does.
- CHANGELOG.md — what changed, and why it mattered.
Made with simai-cli, from demo/detach.txt.
MIT — see LICENSE.
