CLI reference
Every command, option, limit and exit code of kabu 2.5.1, as the CLI’s contract defines them. Scripts and agents may rely on all of it: options are only ever added, and changing or removing one is a breaking change. For a walkthrough, start with the kabu CLI.
Commands
| Command | API calls | stdout |
|---|---|---|
kabu send [TITLE] MESSAGE |
POST /v2/messages; each --file first: POST /v2/media/uploads, then PUT the bytes |
{"ok", "id", "status", "attachments", "project", "project_source", "session"} |
kabu ask QUESTION [MESSAGE] |
POST /v2/messages, then GET /v2/messages/{id}?wait=25 until it settles |
the answer, below |
kabu ask … --no-wait |
POST /v2/messages |
{"ok", "id", "status", "outcome": "pending", "project", "project_source", "session"}; then kabu status ID --wait 50 |
kabu send or ask with --dry-run |
POST /v2/messages/preview; nothing is uploaded or sent |
{"ok", "dry_run", "request", "files", "preview", "project_source"} |
kabu status MSG_ID [--wait N] |
GET /v2/messages/{id} |
the message; exit 0 whatever the answer, so read status and resolution.decision |
kabu whoami |
GET /v2, and GET /v2/org when the key may read it |
{"ok", "capabilities", "org", "org_name", "project", "project_source", "agent", "base_url", "config_path"}; project is null when no rule names one |
kabu session start --title T |
POST /v2/sessions with a Live Activity; then PATCH for --headline or --progress N/M |
the session; exit 3 session_ended if this --external-id’s session has ended |
kabu session update [SES_ID] |
PATCH /v2/sessions/{id} |
the session |
kabu session end [SES_ID] |
POST /v2/sessions/{id}/end |
the session |
kabu login --api-key KEY |
none | {"ok", "config_path"} |
kabu login --device |
POST /v2/device_authorizations, then POST /v2/device_authorizations/poll until approved |
{"ok", "config_path"}, plus org_name and project when known |
kabu guide (or kabu help agents) |
none | a plain-text cheat sheet for coding agents |
kabu mcp |
POST /mcp for each JSON-RPC message read on stdin |
each reply, one JSON-RPC message per line; see MCP bridge |
kabu --help lists the commands, exit codes and environment. kabu COMMAND --help (or kabu help COMMAND) lists every
option with its limit, and examples. kabu --version prints kabu X.Y.Z. kabu alone is a usage error (exit 2).
kabu login with neither flag asks for the key only when stdin is a terminal; --api-key - reads it from stdin. A
key is never shown in an error. login --device prints on stderr, first, In the Kabu app, open Connect a computer and enter the code: WXYZ-1234, then a line with the link that approves the same code in a browser, and a line saying that
an admin or owner of the workspace approves it.
Text
One argument is the message; two are TITLE MESSAGE. --title (or --ask, --ask-text) and --message set them by
flag. Without a title, the title is the message’s first line, cut to 120 characters; without a message, the message is
the title. - as the message, --code, --diff or --quote reads stdin as UTF-8 (invalid bytes become U+FFFD), for
one of them at most. The title takes 120 characters and the message 8192, counted as the API counts (code points).
Longer text is refused, never cut. The request goes as UTF-8 JSON, and one over the API’s 64 KiB (65,536 bytes) is
refused before any call.
Options of send and ask
| Option (aliases) | Request body | Limit |
|---|---|---|
--project, -p |
project |
64; default: project resolution, below. kabu.py’s -p -1, 0, 1 or 2 (its priority) is refused: use --priority |
--agent, -a |
agent |
64; default: agent resolution, below |
--source, -s |
source |
120; default KABU_SOURCE, the saved source, then the hostname |
--session |
session |
a ses_… id; default KABU_SESSION |
--priority |
priority |
low, normal, high, urgent; kabu.py’s -1, 0, 1, 2 are low, normal, high, high |
--expires-in, --ask-timeout |
expires_in |
60 to 2592000 seconds; an ask defaults to 300 |
--sensitive |
sensitive: true |
|
--collapse-key |
collapse_key |
64 |
--accent |
appearance.accent |
one of the contract’s 13 accents |
--icon |
appearance.icon |
64; an SF Symbol name, which the app draws for this message (lowercase letters and digits in dot-separated words, such as checkmark.seal.fill), or a med_… id, which it does not |
--field KEY=VALUE |
fields[]: {label, value} |
12; key 40, value 200 |
--link LABEL=URL, --url URL |
links[]: {label, url} |
4; label 40 (--url-title, else Open, for a bare URL); an absolute URL up to 2048 |
--code, --language |
blocks[]: {type: code, text, language} |
8000; 30 |
--diff, --diff-path |
blocks[]: {type: diff, text, path} |
16000; 300 (a label: nothing is read) |
--quote |
blocks[]: {type: quote, text} |
2000 |
--action, --args JSON, --action-description |
action: {name, args, description} |
100; a JSON object up to 16 KiB; 2000 |
--as-audio, --voice (--tts-voice), --speed (--tts-speed) |
voice: {speak: true, voice, speed} |
voice 60, speed 0.5 to 2.0; --voice and --speed imply --as-audio |
--file, --file-path, --attach, --attachment, --image |
attachments[]: {media}, after the upload, declared by extension as a type players open (UPLOAD_TYPES: .m4a is audio/mp4, never mimetypes’ audio/mp4a-latm), else mimetypes’, else application/octet-stream |
10 readable files (9 on a spoken notify), 1 byte to 2 GiB each |
--api-key, --base-url, --timeout |
none | see Configuration; --timeout 1 to 600 seconds, default 30 |
--dry-run |
none | |
--json |
none | accepted and ignored on every command but login: the output is always JSON (kabu.py’s flag) |
Asks only (anywhere else they are refused):
| Option (aliases) | Request body | Limit |
|---|---|---|
--ask QUESTION, --ask-text QUESTION |
title; --ask-text makes ask.type text |
120 |
--type |
ask.type |
confirm, choice, multi, text; default choice with -c, else confirm (a call with no command: below) |
--choice, -c, --option |
ask.options[]: {id, label} |
2 to 12, label 40; a confirm takes none, or two: the approve label, then the reject label. ID:LABEL sets the id when ID is a letter then up to 63 letters, digits, _, . or -, and no space follows the colon (prod:Production); otherwise the whole text is the label (10:00, Retry: once) and the id is the label in lower case, with _ for every character but an ASCII letter or digit and none at either end (Roll back gives roll_back, Retry: once retry__once, 10:00 10_00) |
--allow-reply, --allow-free-form |
ask.allow_reply: true |
|
--irreversible |
ask.irreversible: true |
|
--no-wait |
none |
Every option is validated before any HTTP call: limits, enums, ids, mutually exclusive and dependent flags, files
that exist. The request body is the contract’s MessageCreate; the CLI sends body, never its alias message.
Exit codes
| Code | Meaning |
|---|---|
0 |
Done: sent; an ask approved, or a choice, multi or text ask answered; any other command succeeded |
2 |
Usage or validation error; nothing was sent (zero HTTP calls). Also local_error: a local failure, such as a config file kabu cannot write |
3 |
Network or API error, after retries; an ask kabu stopped waiting for, which is still open; an answer kabu does not know, never taken as approval |
10 |
Ask rejected (from the app, Lock Screen, Live Activity, web or API), or a confirm answered with a typed reply instead of approve; login --device: the code was denied |
20 |
Ask expired before an answer; login --device: the code expired |
21 |
Ask cancelled before an answer, or deleted while kabu waited for it (ask_gone) |
130 |
Interrupted |
Output
stdout carries one JSON object and nothing else.
-
send:{"ok": true, "id": "msg_…", "status": "sent", "attachments": N, "project": "…", "project_source": {…}, "session": null}. -
ask, keys sorted:{"choices", "decision", "id", "option", "outcome", "status", "text", "values", "via"}, whatever the exit code.outcomeisanswered,expiredorcancelled;decisionis the API’s, as given (the legacydenyreadsreject), or null;optionis the chosen option’s id andtexta typed answer. Expired and cancelled asks print{"decision", "id", "outcome", "status"}. The exit code says what to do, and only an explicit approve or the answer to a choice, multi or text ask is0:The answer decisionExit Approved approve0A choice, multi or text ask answered: option,choices,textorvaluesrespond0, whatever was chosen: branch onoptionRejected reject(ordeny)10A confirm answered with a typed reply ( --allow-reply): readtextrespond10Expired; cancelled as given, or null 20;21Anything else ( edit,ignore, a decision kabu does not know, or none)as given, or null 3 -
Every send and ask,
--no-waitand the answer included, also printsproject, the one sent (null when no rule named one),project_source(Project resolution) andsession, the message’s session as the API has it (null for none). -
status,session: the API’s object as returned. -
An ask kabu stops waiting for is still open: the error adds its
id, and the hint iskabu status ID --wait 50, never to ask again. -
An ask the API no longer has while kabu waits for it (a 404: it was deleted, as when its project is) can never be answered: kabu stops at once, with no retry, and exits 21 with
{"error": {"code": "ask_gone", "message", "hint", "status": 404, "id"}}, whose hint is to ask again only if the answer is still needed. -
When the API answers
session: nullfor the session kabu named, that session no longer exists (it was deleted with its project) and the message went without one: the output addssession_gone, its id, and kabu never names it again (Session, under Configuration). -
kabu statuson a message the API does not have (404) keeps the API’s error, exit 3, with a hint that it was deleted (as when its project is) or never existed, so it will never be answered.kabu session updateorendwith a session the API reported gone exits 3 withsession_goneand makes no call; a 404 from either gets the same hint asstatus.
An error is one JSON line on stderr: {"error": {"code": "…", "message": "…", "hint": "…"}}. The message names the
flag and the allowed values or limit; the hint is the next step, usually a corrected command. API errors add status
(HTTP), and param and request_id when the API gives them; their code is the API’s problem code, or
network_error. Nothing is ever coloured.
MCP bridge
kabu mcp serves the API’s MCP tools (/mcp, streamable HTTP) to a harness that starts it as a stdio server, such as
claude mcp add kabu -- kabu mcp or the Kabu plugin’s .mcp.json. It finds the key and base URL as every command does
(Configuration, below), so the harness’s config holds no key.
- Each line on stdin is one JSON-RPC message, POSTed to
{base URL}/mcpwith the key as a bearer token, unchanged but for one addition: atools/callwith noprojectargument, of a tool whose input schema in the server’stools/listhas aprojectproperty, gets the project kabu resolves for that call (Project resolution) when rule 1 to 5 names one, never a<folder>@<host>name, and only when the key is bound to no project:kabu mcpasksGET /v2once, when it starts, and adds nothing if the key has aprojector the answer does not come. Requests run side by side, so a waiting ask never holds up another call; notifications and replies keep their order. - Presence (ADR 021, 022). Nothing before its first
tools/call. Then, when a harness started it (its parent is one, as for an MCP server; never akabu mcpthat a script in an agent session runs) and presence is on,kabu mcpgets or creates that harness’s automatic session once, on the hooks’ HMAC ladder (Presence: the sameexternal_id, so oneses_per run, shared with the hooks andkabu send), adds it assessionto the calls of the tools that take a project, unless a call names one, writes anonlinerecord from sourcemcpand startskabu presence runwhen none runs. The hooks’workingandidlebeat thatonline. At EOF (or SIGTERM) it writes anofflinerecord, which the reporter sends within 5 s; it never ends the session. - Each reply is printed on stdout as one line: the JSON body, or each event of a
text/event-streambody as it arrives, so the harness sees a long wait’s progress notifications before its answer. An empty reply, such as a notification’s202, prints nothing. - The
Mcp-Session-Idthe server issues is sent on every later request, and replaced when the server sends a new one. - Nothing is retried, so a tool call is never sent twice. A request with no answer from the API (a network error, an
HTTP error status, a redirect, a body that is not JSON, or an empty body or event stream with no reply for its id)
gets a JSON-RPC error with its id:
{"code": -32000, "message": "…", "data": {"code", "status"}}, wheremessageends with the hint. The same error is logged on stderr as one JSON line. An id is answered once: when the API’s answer came before the failure, such as a stream that breaks after its reply, the failure is only logged. - stdout carries only JSON-RPC; anything else goes to stderr. Exit
0once stdin closes and the requests in flight are answered,2with no key (missing_api_key) or a bad option, before reading stdin, and130when interrupted.
Reliability
- One
Idempotency-Key(a uuid4) per invocation onPOST /v2/messages, the same on every retry, so a message is never sent twice. Uploads and session calls use keys derived from it. - Up to 3 retries, on network errors, 5xx, 429 with
Retry-Afterand 409idempotency_key_in_use. Each waitsRetry-After(up to 30 s; a longer one fails at once) or 0.5, 1 and 2 s. Any other 4xx fails at once. - Each response may take
--timeoutseconds (default 30), plus thewaitthe API was asked to hold for. - While an ask is pending, network errors, 5xx and 429 only delay the wait: kabu polls again every 5 s, up to
expires_in+ 120 s. Any other refusal, or that deadline, ends the wait with exit 3 and the ask’sid, but for a 404, which ends it withask_gone, exit 21 (Output). - A redirect is never followed, so the key never goes to another URL: a 3xx is an API error, exit 3 with its
status(inkabu mcp, a JSON-RPC error). SetKABU_BASE_URLto the final URL instead.
Configuration
| Setting | Order |
|---|---|
| API key | --api-key, KABU_API_KEY (or AGENT_NOTIFIER_API_KEY), the saved api_key; spaces and line breaks around it are dropped, and one with a space, line break or other character outside printable ASCII inside it is refused (exit 2, invalid_api_key), without showing it |
| Base URL | --base-url, KABU_BASE_URL (or KABU_API_BASE_URL), the saved base_url, https://usekabu.com; an http:// or https:// URL, else exit 2; a trailing /v2 is dropped |
| Session | --session, KABU_SESSION; in an agent session that reports presence, its session (Presence); else KABU_AUTO_SESSION=1 gets or creates one automatic session per host, project and agent (automatic: true, never live: it claims no run, so it stays idle with no Live Activity of its own, ADR 022; each ask in it has its own; kabu session start is the explicit, live run), in one try of up to 3 s. An ended or expired session is not used: the message goes without one, as does any message whose session call fails. A session the API reported gone (Output) is never sent again: kabu gets or creates, in its place, the auto session of this agent session (per host, project, agent and harness process), whatever KABU_AUTO_SESSION, and the output names both, in session and session_gone. An auto session names the message’s project, or none when the message names none |
| Config file | KABU_CONFIG_PATH, else $XDG_CONFIG_HOME/kabu/config.json, else ~/.config/kabu/config.json; written by kabu login with mode 600 |
| Presence | On unless KABU_PRESENCE=0 (that agent’s environment) or kabu presence off (every machine that shares this home); its records in a machine-local folder (KABU_RUNTIME_DIR), its identity in KABU_HOME, else ~/.kabu (Presence) |
| Cache | $XDG_CACHE_HOME/kabu/, else ~/.cache/kabu/: sessions/ keeps each agent session’s project (Project resolution), and gone-<hash of the base URL> the last 100 session ids that API reported gone. It holds no key; deleting it only makes kabu resolve again |
Presence
Whether an agent is live (ADR 021, docs/adr/021-agent-presence.md): working, idle, waiting at the computer or gone.
kabu --help names these commands in a paragraph of their own, after Commands.
| Command | API calls | stdout |
|---|---|---|
kabu presence status |
none | {"ok", "enabled", "off", "opted_out", "running", "pid", "instance", "generation", "dir", "lanes", "last_beat", "ack"} |
kabu presence on, kabu presence off |
none | the status |
kabu presence run |
POST /v2/sessions once per agent session, PUT /v2/presence |
nothing |
kabu presence install, kabu presence uninstall |
none; --harness claude or copilot, --settings PATH |
{"ok", "harness", "settings", "hook", "installed", "events", "changed", "backup"} |
- What leaves the machine: state enums (runtime, attention, source), ids made by HMAC with the installation secret
(the session’s
external_id, theln_lane), the harness’s name as the session’s agent (claude-code) and the project the session runs in, as kabu send sends it. Never a prompt, path, tool name, host name, the client’s title or its raw session id; the session’s title is the constant<harness> session. - The hook.
PRESENCE_HOOK, written out as the plugin’shooks/presence.shand bykabu presence installas~/.kabu/presence-hook.sh(~/.kabuisKABU_HOMEwhen set), mode 0600. A harness runs it askabu_event=EVENT; (. FILE) || true: a subshell of the hook’s own shell sources it, so it starts no program (butkabu presence run, below), never reads its input (a harness may hold it open), has no unbounded loop, never calls the network and always exits 0. Events, in Claude Code’s names or Copilot CLI’s:SessionStart(sessionStart) idle,UserPromptSubmit(userPromptSubmitted),PreToolUse(preToolUse) andPostToolUse(postToolUse) working,Notification(notification) attentionterminaluntil the next event (Claude Code’s matcher passes only the types that need the user:permission_prompt,elicitation_dialog,elicitation_url_dialog,worker_permission_prompt,agent_needs_input),Stop(agentStop) idle,SessionEnd(sessionEnd) offline.kabu mcpwrites records too, withsource: mcpandruntimeonline, thenoffline(MCP bridge); a lane’s state is its hooks’ latest record when it has one, else kabu mcp’s. - Records. Each event is a new file,
l<harness pid>-<seq>.json(0600), created no-clobber at the next free seq, in this user’s machine-local folderkabu-presence-$USER(0700) ofKABU_RUNTIME_DIR, elseXDG_RUNTIME_DIR, elseTMPDIR, else/tmp(on a Mac also the per-user temp folder):{v, event, runtime, attention, seq, pid, sid, pane}, wherepidis the hook’s parent (the harness),sidClaude Code’sCLAUDE_CODE_SESSION_IDwhen that Claude is the parent, andpaneHERDR_PANE_ID. A folder that is a link or not this user’s is never used; no record is written through a link or over another file, so concurrent hooks lose no event and the state is the highest seq’s. The hook startskabu presence runatSessionStart, and at a turn’s other events when none runs (run.pidthere). kabu presence run, one per machine: an exclusive lock onrun.lockin that folder, which is machine-local, so machines that share a home each run theirs. Every 2 s it folds each harness’s records (keeping only the latest) and checks its process: one that exited reads offline at once, and one that is a zombie or whose id another process took within 10 s. A record older than its harness’s process (another process had that id) is never adopted. It sendsPUT /v2/presencewith every live agent session (at most 20; the API takes one left out offline), once a minute ornext_beat_in, and at once on a change, but working and idle wait 5 s to settle, never more than 10 a minute. A turn with no event for 15 minutes and no tool running reads idle. Each agent session’s Kabu session is got or created once, automatic (automatic: true: idle, no Live Activity of its own, ADR 022):external_idkp_and HMAC(installation secret, harness ‖ session id, else its process id and start time), agent the harness, title<harness> session, project the one the harness’s folder names (never a temp folder’s or<folder>@<host>); it is kept beside the records, per API and key. The reporter isinstance(pin_…) andgeneration, raised at every start, with the secret in~/.kabu/presence/reporter-<machine>.json(0600; one per machine:KABU_MACHINE_ID, else Linux’s machine-id, the Mac’s hardware UUID or the host name, hashed). Astalebatch resumes above the API’s generation; a session the API reports ended or gone is replaced by a new one. It exits after 10 minutes with no agent session, once presence is off (its sessions sent offline first), or when its folder orKABU_HOMEis gone. Every file it writes is new (0600) and renamed into place; it opens none through a link.- kabu send and ask join it. In an agent session whose records exist and are newer than its harness’s process (the
harness is kabu’s nearest harness ancestor), with no
--sessionorKABU_SESSION, a message goes in that agent session’s Kabu session, found or created as above, in one try of up to 3 s;KABU_AUTO_SESSIONapplies only without one. If the API answerssession_ended, the message is sent again without it. kabu presence installwrites the hook and adds it for the seven events to Claude Code’s settings (CLAUDE_CONFIG_DIR, else~/.claude,settings.json: a group per event,matcherthe types above forNotification) or Copilot CLI’s (COPILOT_HOME, else~/.copilot: its native keyssessionStart,userPromptSubmitted,preToolUse,postToolUse,notification,agentStop,sessionEnd, each a flat{type, bash, timeoutSec}), or--settings. Entries that runpresence-hook.share replaced, others kept; running it again changes nothing. Each change first saves a backup under a name no other has (settings.json.bak-kabu-presence-<time>-<ns>-<id>, 0600), and the file is written at once, never looser than 0600; a file that is not JSON is left alone (exit 2,invalid_settings).uninstallputs back the exact bytes from before the install when the file is as kabu left it (or removes the file kabu made), else takes out only kabu’s entries; the hook file goes once no settings use it (~/.kabu/presence/installs.jsonkeeps what was installed where). The Kabu plugin needs neither.
Agent resolution
--agent, then KABU_AGENT (or AGENT_NOTIFIER_AGENT), then the agent saved by kabu login --agent, then the
harness that runs kabu:
- The nearest ancestor process named
claude,copilot,codex,cursor-agent,gemini,opencode,aider,goose,amporpi. Process names only: never their arguments, which can hold secrets. kabu reads its ancestors from/procon Linux and with onepscall elsewhere; apsthat cannot show start times still gives their names. AI_AGENT, as a slug.- Markers harnesses set for the commands they run:
CLAUDECODEorCLAUDE_CODE_ENTRYPOINT(claude-code),CURSOR_AGENTorCURSOR_TRACE_ID(cursor),GEMINI_CLI(gemini-cli),CODEX_THREAD_IDorCODEX_SANDBOX(codex),PI_CODING_AGENT(pi). - Otherwise
kabu-cli.
Project resolution
Without --project, kabu sends the project of the first rule below that names one. Every send, ask, dry run and
kabu whoami says which in project_source: {"rule"}, plus, where they apply, the harness process, the dir the
rule read, via (dotenv or git), the environment variable (only with session_root), and cached: true when
the project was read back from the agent session’s cache. With --project it is {"rule": "flag"}.
| # | rule |
The project |
|---|---|---|
| 1 | env |
KABU_PROJECT, then the legacy AGENT_NOTIFIER_PROJECT, from the process environment |
| 2 | session_root |
The one the agent session’s root names |
| 3 | cwd, with cached: true |
When the root names none: the one this session’s first call got from rule 4 |
| 4 | cwd |
The one the current folder names |
| 5 | login |
The project saved by kabu login --project |
| 6 | folder |
<folder>@<short-hostname>, such as scratch@yshah-mn3: the session root’s folder, else the current one |
| 7 | default |
None: the request names no project, so the API uses the key’s project, else default |
- Agent session. The nearest ancestor process named as a harness (Agent resolution, step 1), such as
claudeorcopilot. Its root isCLAUDE_PROJECT_DIRwhen set (Claude Code sets it for hooks and MCP servers, Copilot CLI for plugin hooks), else the harness process’s working folder: the one it started in, or the one the user moved the session to (Copilot CLI’s/cd). Socdin a command, which moves only that command’s shell, never changes the project. With no harness among kabu’s ancestors, rule 3 does not apply, and rule 2 only withCLAUDE_PROJECT_DIR. - What a folder names.
KABU_PROJECT(elseAGENT_NOTIFIER_PROJECT) in.env.local, checked first, or.env, from the folder up to its git repository’s top, then in the main worktree’s root, as.envis usually untracked and so absent from linked worktrees; files outside the repository are never read (via: dotenv). Otherwise the git repository’s name: theoriginremote’s, else the main worktree’s folder name, the same from any nested folder or linked worktree (via: git). Otherwise nothing. A folder that no longer exists names nothing. - Temp folders. A folder in
/tmp,/var/tmp,/var/foldersor$TMPDIR, symlinks resolved (so/private/tmptoo), names nothing, but for a worktree of a repository whose main worktree is outside them, which names that repository’s project.cd /tmp/x && kabu send …keeps the session’s project, else the saved one, else none. - No
<folder>of rule 6 is a temp folder,/, your home folder or a folder that no longer exists. - Kept per agent session. Rules 2 and 3 keep what they named in the cache’s
sessions/(Configuration), one file per harness process, named by its process id and start time, with the session root it was named under. A later process that reuses the id never finds another session’s project, so a file is only read while its process runs, and only while the session’s root is the folder it was kept under; when the root moves, the session resolves again. A project is kept as a name, never a project id: a project deleted in Kabu is created again by the next message that names it. Files unused for 7 days are removed. Where a harness’s start time cannot be read, nothing is kept. kabu mcpresolves the project for each tool call, as kabu send would (MCP bridge).
Calls with no command (kabu.py compatibility)
A call whose first word is not a command is send, or ask when --ask or --ask-text is given, so ~/bin/kabu.py
can become a one-line shim:
| kabu.py call | Same as |
|---|---|
kabu.py --project P --agent A --title T "message" --file-path X |
kabu send T "message" --project P --agent A --file X |
kabu.py --ask "Q?" -c Yes -c No |
kabu ask "Q?" --type confirm -c Yes -c No: Yes approves (exit 0), No rejects (exit 10) |
kabu.py --ask "Which env?" -c staging -c prod |
kabu ask "Which env?" -c staging -c prod: a choice, exit 0, option names it |
kabu.py --ask-text "Name?" |
kabu ask "Name?" --type text |
kabu.py "Build finished" |
kabu send "Build finished" |
Only a yes or no pair makes a confirm: yes then no, y then n, or approve then reject, in any case, with no
--type and no --allow-free-form. Its first choice exits 0 and its second 10, as kabu.py’s 0 and 1. Any other
choices, and any with --allow-free-form, are a choice, which exits 0 and names what was picked in option (or the
typed answer in text). The rest is kabu’s, not kabu.py’s: an ask exits 0, 3, 10, 20 or 21 (kabu.py exited
with the chosen index, 1 on reject and 124 on timeout), -p is --project (kabu.py: priority), and an unknown flag is
refused rather than ignored, with kabu’s way when it has one (--quiet, --id-only, --fyi, --category,
--metadata, --max-retries, --client-event-id, --check-status).
A first word is never sent as a message when it is one of kabu.py’s other commands, daemon, listen, service,
steer, identity or project: steering and project settings are not in kabu, so it exits 2. Neither is a word that
looks like a mistyped command, such as kabu sned "hi", which is refused with a suggestion. Send such a word with
kabu send.