View Markdown

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/kabu

Kabu 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 --device

kabu 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 .text

kabu 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 settles

Sessions

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.