The kabu CLI
kabu is how a script, a shell or a coding agent reaches you: one Python 3.9+ file, standard library only. Every
command prints one JSON object on stdout, and every answer has an exit code, so a script or an agent can act on it
without parsing text. The full list of commands, options and limits is the CLI reference.
Install
mkdir -p ~/.local/bin && curl -fsSL https://usekabu.com/kabu -o ~/.local/bin/kabu && chmod +x ~/.local/bin/kabuKabu serves the current version at https://usekabu.com/kabu; run the same line to update. kabu --version prints
the version you have.
Log in
kabu login --devicekabu prints a code: in the Kabu app, open Connect a computer and enter it, or open the link it prints in a browser.
Once an admin or owner of the workspace approves it, this computer gets its own key. It is saved in
~/.config/kabu/config.json with mode 600, and every command, kabu mcp and the plugin’s hooks use it. Denied codes
exit 10, expired ones 20.
To use a key you already have instead, kabu login --api-key - reads it from stdin, or set KABU_API_KEY. kabu whoami shows which key is in use, what it may do, and the project and agent kabu would send as.
Send
kabu send "Tests passed" "412 passed, 0 failed"
kabu send "Nightly report" - < report.md
kabu send "Build failed" "3 tests failed" --priority high --file ./junit.xml --link "CI=https://ci.example.com/run/77"One argument is the message; two are a title and a message. - reads the message from stdin. A message can carry up
to 10 files (--file), 4 links (--link LABEL=URL), facts (--field KEY=VALUE), a code block (--code), a diff
(--diff) or a quote (--quote). --as-audio has Kabu speak it: the API turns the message into an audio file, which the app plays. --dry-run has the API check the
message and show what it would send, without sending anything.
Ask
kabu ask "Deploy v1.4.2 to prod?" # approve: exit 0, reject: exit 10
kabu ask "Which environment?" -c staging -c production # a choice: read .option
kabu ask "What should the branch be called?" --type text # a typed answer: read .textkabu ask waits for the answer, however long it takes, until the ask expires (--expires-in, 5 minutes by default,
up to 30 days). Then it prints the answer as JSON and exits:
| Exit | When |
|---|---|
0 |
Approved, or a choice or typed answer given: branch on option or text |
10 |
Rejected |
20 |
Expired before anyone answered |
21 |
Cancelled |
3 |
A network or API error, or an answer kabu does not know; never taken as approval |
A choice exits 0 whatever is picked, so branch on .option, never on the exit code. --irreversible makes the
answer need your phone unlocked, and an irreversible ask is never decided by its expiry.
If whatever runs kabu may stop waiting first, such as an agent’s tool call with a time limit, ask without waiting
and wait separately. The ask stays open either way:
kabu ask "Run the migration?" --no-wait # prints the ask's id
kabu status msg_01J9Z --wait 50 # waits up to 50 s; run it again until it settlesSessions
A session is one run of work, such as a build, a migration or an agent’s task, with a headline and progress you update as it goes.
export KABU_SESSION=$(kabu session start --title "Migrate billing" --headline "Copying tables" --progress 1/4 --progress-label tables | jq -r .id)
kabu session update --headline "Copying tables" --progress 3/4 --progress-label tables
kabu session update --headline "Verifying"
kabu session end --outcome success --summary "PR opened"Report progress only when it is measurable (items done of a known total) or for known steps; set --progress-label to
what is counted; never estimate a percentage; otherwise send only a --headline. The phone shows exactly what you
send: --progress 54/172 --progress-label projects reads “54 of 172 projects”.
With KABU_SESSION set, every kabu send and kabu ask belongs to the session. Ending a session cancels its open
asks. --external-id lets a restarted job find its own session again. More in Sessions.
Use it from an agent
kabu guide prints a short cheat sheet written for coding agents: paste it into your agent’s instructions (for example
AGENTS.md), and the agent knows when and how to send and ask. Claude Code and GitHub Copilot CLI can install the
plugin instead, which carries the same guidance as a skill. kabu mcp serves Kabu’s tools to any agent
that speaks MCP over stdio; see MCP.
Configuration
| Setting | Comes from, first match wins |
|---|---|
| API key | --api-key, KABU_API_KEY, the saved login |
| Base URL | --base-url, KABU_BASE_URL, the saved login, https://usekabu.com |
| Project | --project, KABU_PROJECT, the folder your coding agent started in, the current folder, the saved default, <folder>@<host>: see Which project |
| Agent | --agent, KABU_AGENT, the saved default, the agent that runs kabu (Claude Code, Copilot CLI, Codex, Cursor, Gemini CLI and others), kabu-cli |
| Session | --session, KABU_SESSION; KABU_AUTO_SESSION=1 keeps one per computer, project and agent |
| Config file | KABU_CONFIG_PATH, $XDG_CONFIG_HOME/kabu/config.json, ~/.config/kabu/config.json |
A key is never printed, in output or in errors. An error is one JSON line on stderr, {"error": {"code", "message", "hint"}}, and the hint is the next step, usually a corrected command.
Which project
Without --project, kabu names the project after the folder your coding agent started in, not the folder a command
runs in, so cd /tmp/x && kabu send … still reaches the right project: that folder’s KABU_PROJECT in .env.local or
.env, else its git repository’s name, the same from any worktree. When that folder names nothing, the first project
the session’s commands name is kept for it. Outside an agent, the current folder decides the same way. A scratch
folder in /tmp never names a project, and when nothing does, a message names none and goes to your key’s project,
else default. Every send and ask prints project_source, the rule that chose, and kabu whoami shows it too. The
CLI reference lists every rule.
Coming from kabu.py
kabu accepts kabu.py’s calls: kabu "message" sends, and kabu --ask "Deploy?" -c Yes -c No asks. Most flags
work as before, but an ask’s exit codes are kabu’s: a rejection exits 10, not 1, and a choice names the pick in
its JSON instead of its exit code. The CLI reference
lists every difference.