@oh-hai/cli
v0.2.3
Published
OH HAI — the `oh-hai` CLI: reach a human from your agent through an MA2H Hub (notify / ask / task). Placement + machine contract; command bodies land in #106–#108.
Readme
@oh-hai/cli — the oh-hai command-line tool
One universal, runtime-agnostic way for an autonomous agent to reach a human through an
MA2H Hub: oh-hai notify | ask | task. Shell-out is the lowest common denominator (Claude
skills are Claude-only, MCP is partial); a small, well-behaved binary works everywhere.
The design contract is docs/specs/cli.md (the command surface,
auth/token model, config resolution, exit codes, and the --json envelope). This package
implements that contract.
The one invariant: the agent bearer token never passes through the LLM.
oh-hai loginputs the token in the OS keychain; the agent invokesoh-haiby reference and never sees, prints, or reasons over the secret (spec §5).
Status
#105 shipped package placement + the machine contract + the publish pipeline + docs; #106 wired the auth commands (token storage + login/logout/whoami); #107 wired the messaging commands (notify/ask/task). The remaining command bodies land in downstream issues:
| Works today | Lands in |
|---|---|
| --help, --version (and --version --json) | — |
| Global-flag parsing, command dispatch | — |
| Exit-code table (§7), --json envelope (§8), config resolution (§6) | — |
| login --token-stdin / logout / whoami (OS keychain + 0600 file fallback) | ✅ #106 |
| oh-hai login device-code acquisition (server device-authz + web approval) | #166 |
| notify / ask submit\|await / task submit\|await (account-aware; --json; pull-await) | ✅ #107 |
| agents list\|create\|revoke | later |
| doctor | #108 |
Every not-yet-implemented command exits 1 with a stable not_implemented error and a
pointer to its owning issue — it never silently no-ops or fakes success.
Auth (login / logout / whoami)
The agent bearer token is stored in the OS keychain (macOS Keychain, Linux Secret
Service) or, where no keychain is available, a 0600 file at ~/.config/oh-hai/credentials
(with a loud one-time warning). It is keyed per Hub (<origin>|<agent id>), never printed,
and never accepted as a flag value (spec §5).
# Store a token minted in the web app / via `oh-hai agents create` (never typed, never logged):
printf %s "$AGENT_TOKEN" | oh-hai login --account my-agent --token-stdin
oh-hai whoami # agent id + base URL + token presence (…last4) + source
oh-hai whoami --check # also probe the Hub for token validity
oh-hai logout # remove this identity's token (--all clears every identity)Bare oh-hai login (the browser device-code flow) is not available yet — it needs a
server device-authorization grant + a web approval page (tracked in #166); until then it
declines with a pointer to --token-stdin.
Per-project identity (use)
Running multiple agents on one machine? Bind a directory to a specific agent so sessions in
that tree target it automatically — no --account on every command. The simple path stays
simple: one agent, zero config, no use needed.
oh-hai use my-project-agent # bind this directory (and its subtree) to my-project-agent
oh-hai whoami # → agent: my-project-agent · via: binding (<dir>)
oh-hai use --list # show all directory bindings
oh-hai use --unset # remove this directory's bindingBindings are user-side (~/.config/oh-hai/bindings.json), scoped per Hub, and never
settable from a repo — a cloned .oh-hai/config.toml can't re-route your identity
(spec §6). Precedence: --account/MA2H_AGENT_ID > directory
binding > user default_account. Offline — the agent need not be logged in yet.
Messaging (notify / ask / task)
The productized counterparts of the @oh-hai/scripts wrappers (spec §4.4):
the bearer is resolved account-aware (keychain / 0600 file / MA2H_AGENT_TOKEN env — not
raw env), output follows the --json envelope, and Hub statuses map to the §7 exit codes.
oh-hai notify --title "Build finished" --body "All green" --priority normal
oh-hai ask submit --mode select --option ship:Ship --option hold:Hold \
--resolver human:owner --title "Ship v1?"
oh-hai ask await --id msg_02A… # poll until the human answers; prints the outcome
oh-hai task submit --instructions "Rotate the signing key" --title "Rotate key" \
--resolver human:owner # terminal by default (--callback opts into a return leg)
oh-hai <ask|task> submit --dry-run … # compose + print the envelope, POST nothing
oh-hai ask submit --envelope @captured.json # idempotent replay of a captured envelope (§4.7)
--state(sealed resume) is not yet available in the CLI. The AES-256-GCM state seal is vendored crypto that the self-contained published bin can't import; sealing needs the shared MA2H core extracted first (follow-up). Passing--stateis rejected with a pointer; use the@oh-hai/scriptswrappers for sealed state until then.
Install
New to OH HAI? For a top-to-bottom walkthrough — install → onboard → your first
notify/ask/task— see the Connect your agent guide. This README is the implementer-facing package reference.
The headline path is the standalone binary — no Node required. The installer detects your
OS + arch, downloads the matching binary from the ohhai.app distribution mirror, verifies
its SHA-256 checksum, and puts oh-hai on your PATH:
# macOS / Linux
curl -fsSL https://ohhai.autonomy.com.ai/install | sh
# Windows (PowerShell)
irm https://ohhai.autonomy.com.ai/install.ps1 | iex
ohhai.autonomy.com.aiis the live Pages domain today; the same scripts are served athttps://ohhai.app/install.shandhttps://ohhai.app/install.ps1once the dist Worker is live.The repo is private, so binaries are served from the public ohhai.app mirror (
dist-worker/+ an R2 bucket CI fills on each release — issue #357). Until the coordinator provisions the bucket/Worker/secrets (seedist-worker/README.md), the binary download 404s and an unauthenticated install falls back to npm. Use the npm install below until then.
npm is the secondary channel for node devs (and the automatic fallback when a host has no prebuilt binary, e.g. windows-arm64):
npm install -g @oh-hai/cliOH_HAI_VERSION pins a version on either channel. (Homebrew was dropped in favor of the
standalone binaries — epic #277.)
Command surface (see spec §4)
oh-hai [global flags] <command> [subcommand] [flags]
Auth login | logout | whoami | use
Messaging notify | ask | task
Agents agents list | create | revoke
Health doctor
Global flags: --base-url --account --json --quiet --verbose --no-color --timeoutUsage examples
$ oh-hai --version
0.0.0
$ oh-hai --version --json
{"ok":true,"command":"version","data":{"version":"0.0.0","wire_version":"v0.3"},"error":null}
$ oh-hai notify --title "Build finished" --body "All green" --priority normal
$ oh-hai ask submit --mode select --option ship:Ship --option hold:Hold \
--resolver human:owner --title "Ship v1?" --json
$ oh-hai ask await --id msg_02A… --jsonMachine contract
- Exit codes (§7):
0success ·1generic ·2usage ·3auth ·4not-found ·5network ·6server ·7timeout ·8conflict ·9invalid-request. (The scaffold adds anot_implementederror string that maps to exit1.) --jsonenvelope (§8): every command emits one uniform shape:
Absent optional fields are omitted from{ "ok": true, "command": "ask.submit", "data": { "id": "…" }, "error": null }data, not set tonull. On failureokisfalse,dataisnull, anderroris{ code, message }. The token is never in the envelope (spec §5.4).- Config precedence (§6):
flag > env > config file > default, resolved per value. The token is special-cased (keychain /MA2H_AGENT_TOKENenv — never a config value), and per-project config is credential-safe: it may set onlyoutput/timeout/color, neverbase_url/account.
Develop
Node ≥ 24, run from the repo root:
pnpm --filter @oh-hai/cli start --help # run the CLI from source via tsx (or: pnpm oh-hai --help)
pnpm --filter @oh-hai/cli typecheck # tsc --noEmit
pnpm --filter @oh-hai/cli test # node:test unit suite
pnpm --filter @oh-hai/cli build # publish-time compile → dist/ (gitignored)Dev + test run the .ts sources directly via tsx (repo convention — no build step). The
published artifact is compiled to dist/ because end users have node, not tsx.
Release
Tag-driven: push cli-vX.Y.Z (matching package.json) and the
cli-release workflow builds + publishes to npm.
See the runbook: docs/runbooks/cli-release.md.
