# The kabu CLI

> Send updates and ask questions from any script or coding agent with kabu, one standard-library Python file. Install, log in, send, ask and sessions.

`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](/docs/cli/reference).

## Install

```sh
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

```sh
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

```sh
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

```sh
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:

```sh
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.

```sh
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](/docs/concepts/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](/docs/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](/docs/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](#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](/docs/cli/reference#project-resolution) 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](/docs/cli/reference#calls-with-no-command-kabupy-compatibility)
lists every difference.
