# @kodycodes/cli Install [Kody](https://kody.codes) as a remote MCP server in local agents, and call Kody's Open API from the terminal. `kody login` uses Client ID Metadata Documents (SEP-991) once, then stores a `kody_at_…` device token. **Long term, use Kody through the host MCP connection** — `search` and `execute` in Cursor, Claude Code, or another client. This CLI writes that config. Keep `kody login` / `kody search` / `kody execute` for bootstrap, scripts, or hosts that cannot run MCP. ```bash npx @kodycodes/cli install npx @kodycodes/cli skill install ``` Copyright © 2026 [Kent C. Dodds](https://kentcdodds.com). MIT licensed. ## Install the MCP server ```bash npx @kodycodes/cli install ``` `kody install` lists **running local** agents (Cursor, Claude Desktop, VS Code, Goose, Claude Code, Codex, Windsurf, Zed, and similar) and writes each host's remote MCP entry for `https://kody.codes/mcp`. Common host formats go through [`add-mcp`](https://www.npmjs.com/package/add-mcp). It does not list web clients. For ChatGPT, Claude.ai, and Grok, use [kody.codes/onboarding](https://kody.codes/onboarding). After install, the CLI prints a prompt you can paste into the configured agent to continue onboarding. Host OAuth stays in that client — `kody login` is only for the CLI itself. ```bash npx @kodycodes/cli skill install ``` installs the Kody CLI skill into Claude Code / Cursor / Codex / `.agents` folders. It prefers the always-current hosted `/.well-known/agent-skills/kody-cli/SKILL.md` (and refreshes stale local copies on later CLI runs). That skill tells shell-capable agents to prefer `execute --local`. `--mcp-url` or `KODY_MCP_URL` overrides the default `https://kody.codes/mcp`. ## CLI as a local client Optional. Use when you need a scripted or headless client instead of a host MCP connection. ```bash npm install -g @kodycodes/cli kody login # browser picks the org; adds that org login kody login --org acme # bind / replace the acme login; other orgs stay kody org list # show logged-in orgs (* = active) kody org use beta # switch active org (agents); bare `org use` opens a TTY chooser kody execute --org acme … # one-shot org override (also: KODY_ORG=acme) ``` Or run via `npx @kodycodes/cli` without a global install. Each `kody login` adds an org (chosen in the browser, or via `--org`). If you do not have an account yet, create one on that page; login resumes the same authorization after signup and email verification. Re-logging into an org that is already stored replaces only that org's credential. Org-scoped commands (`execute`, `search`, `whoami`, `api`, …) take `--org ` or `KODY_ORG` (flag wins, then env, then the active org). `kody logout` removes the active org; `kody logout --org ` removes one org. Git auth stays user-wide (`kody auth setup-git` once covers every repo across orgs). Existing single-org keychain credentials migrate silently into the multi-org store as the active org (no re-login). ## Remote or headless machines `kody login` opens a browser and waits for it on `http://127.0.0.1:43742`, so the browser has to run on the same machine as the CLI. On a remote VM, a NAS, or over SSH, use the device flow instead: ```bash kody login --device # prints a URL and a code; approve on any device kody login --device --org acme # same, bound to the acme org ``` The CLI prints `https://kody.codes/device` and an 8-letter code such as `BCDF-GHJK`, plus a link that fills the code in for you. Open either one on your phone or laptop, sign in, check that the code matches, and choose **Approve**. The CLI polls in the background and stores the same device API token `kody login` stores. Codes expire after 10 minutes. Only approve a code you started yourself on a device you control. `kody login` suggests `--device` when it detects an SSH session or no display, when it cannot open a browser, or when the callback port is busy. The browser flow stays the default. ## Commands | Command | Purpose | | ------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `kody install` | Detect running local MCP clients and write their remote MCP config. The host client starts OAuth when the user connects. **Recommended long-term path.** | | `kody skill install` | Installs the hosted `kody-cli` skill (or the bundled stub offline) into Claude Code / Cursor / Codex / `.agents`. Later CLI runs refresh stale copies by digest. | | `kody login` | Browser OAuth (CIMD + PKCE) for the CLI itself. Adds/replaces one org login (browser picker, or `kody login --org acme`) and keeps other org logins. `--device` signs in from another device instead ([Remote or headless machines](#remote-or-headless-machines)). | | `kody org list` | Lists logged-in orgs and marks the active one. `--json` for agents. | | `kody org use []` | Switches the active org. With a slug (agents) switches directly; with no slug on a TTY opens a chooser; without a TTY pass a slug. | | `kody logout` | Removes the active org login (or `--org ` for one org) and clears a stored bootstrap/API token when appropriate. | | `kody status` | Shows CLI login / stored API token state without printing secrets. | | `kody auth bootstrap --code` | Redeems a one-shot `kody_bc_…` from MCP `cliCredentialBootstrap` and stores the resulting `kody_at_…` for `execute --local` (never prints the token). | | `kody auth bootstrap --scopes` | Replaces the default bootstrap scope list (include every scope you need): mints the code itself by passing `scopes` to `cliCredentialBootstrap` (with `--token`, else the `kody login` session, else a stored token), then redeems and stores it. See [Opt-in scopes](#opt-in-scopes). | | `kody auth setup-git` | Installs a Git credential helper for `https://kody.codes/@owner/name.git` (persistent `kody_at_…`, user-owned token with `package:read`/`package:write`). See [Git HTTPS](#git-https-setup-git). | | `kody auth remove-git` | Inverse of `setup-git`: clears Kody HTTPS git helpers, revokes the stored API token, deletes it from the keychain. Leaves `kody login` alone. | | `kody git-credential` | Credential helper invoked by Git after `setup-git` (`get` / `store` / `erase`). | | `kody whoami` | Shows the credential commands will use (`--token`, `KODY_API_TOKEN`, stored API token, or `kody login`), its token id and scopes, and any other stored credential it outranks. See [Which credential a command uses](#which-credential-a-command-uses). | | `kody search [query]` | Calls Kody `search` from the CLI (prefer the host MCP tool). Token-only auth uses Open API `GET /v1/search`. | | `kody api ` | Thin Open API wrapper matching the MCP `api` tool: `operationId` + flat `--params` JSON. Auth: `--token`, `KODY_API_TOKEN`, the stored API token, then `kody login` (same credential `kody whoami` reports). The parsed `/openapi.json` route map is cached under the Kody cache dir keyed by the document's `ETag` and revalidated with `If-None-Match` on each run. | | `kody package publish` | Thin wrapper for `packagePublishExternalPush`. Optional positional `@owner/leaf`, bare leaf name, or package UUID (names are resolved to the package UUID via `packageList`); extra fields via `--params`. Same auth as `kody api`. Prints the API JSON. Exit **0** for `published`, `already_published` (including `artifacts_rebuilt: true`), and `dispatched` (poll `workflowRunList({ workflow_id })`). Exit **1** for refusal/rejection statuses (`checks_failed`, `locked`, `approval_required`, `not_fast_forward`) with the API `message` (or a checks summary) on stderr, like npm. Auth / permission / network failures also exit 1; `--json` adds `{ error, message, sent }` on stdout for network errors (`sent: false` = not sent; `sent: "unknown"` = maybe sent — prefer `kody api workflowRunList` with `workflow_id` when known, otherwise a recent list, before retry). Re-publishing the same commit is safe (durable dedupe / `already_published`). | | `kody package collaborators` / `grant` / `revoke` | Who can use one package, and add or remove teams and members of the org (`--org`). `` is `@owner/leaf`, the name leaf, or the package id. `grant --team \|--user [--preset use\|contribute\|manage]` (default `use`) adds or changes a collaborator via `packageCollaboratorSet`; people outside the org are refused. `revoke --team\|--user\|--grant ` removes one via `packageCollaboratorRemove`. Needs Manage on the package and a token with `package:manage_access` (`auth bootstrap --scopes …,package:manage_access`). Prints a summary, or JSON with `--json`. | | `kody execute` | Calls Kody `execute` from the CLI (`--invoke`, `--code`, `--file`, or stdin via `--file -`). `--invoke` is client-side sugar: the CLI writes the one-export import passthrough and sends it as `code`, the only module field hosted execute accepts. With a scoped API token and no login (or with `--token`), cloud execute goes through CapabilityProxy → `kody.execute` — no `kody login`. Add `--local` to run the module (and static `kody:@…` package modules) on this machine instead. `--credential key=name` (repeatable; `org:name` / `personal:name` / empty `=` for none) picks ADR 0068 credentials for `--local` or a `kody login` MCP session. `--allow-private-network` opts local execution into private and local network access. `--target prod\|mock` (default `prod`) sends CapabilityProxy / search hops to prod or the built-in CLI mock; `--target mock` implies local workerd. | | `kody dev` | Local testing for unpublished packages. Serves `kody.app.entry` on localhost via workerd with hot reload. Subcommands: `fetch`, `job`, `webhook`, `emit`, `check`. Default `--target mock` (scratch storage, fake credentials). Covers exports, webhooks, schedules (run-once), and subscriptions. See [Local package testing](#local-package-testing-kody-dev). | `--json` prints structured MCP results. On command failures it also prints a JSON error object on stdout (`error`, `message`, and when a network hop failed before an HTTP response, additive `sent: false | "unknown"`). ## Token-authenticated execute (no `kody login`) Agents already on Kody MCP should prefer `cliCredentialBootstrap` → `kody auth bootstrap --code …` (ADR 0056) so a one-shot `kody_bc_…` seeds the CLI store without pasting `kody_at_…` into chat. Interactive humans can use `kody login`. Scoped API tokens (`kody_at_…`, from bootstrap redeem, `tokenCreate`, or `POST /v1/tokens`) authenticate the Open API and CapabilityProxy. They never replace MCP OAuth on `/mcp`. See [Which credential a command uses](#which-credential-a-command-uses) for the order the CLI picks them in: ```bash # Preferred for agents on MCP (no tokenCreate, no second OAuth): # 1. MCP api tool, operationId cliCredentialBootstrap → { bootstrap_code, cli_command } npx @kodycodes/cli auth bootstrap --code 'kody_bc_…' --lifetime short npx @kodycodes/cli execute --local --file ./task.js export KODY_API_TOKEN=… # scopes: local-execute (+ search:read for search) # Cloud execute — module runs in Kody's sandbox via CapabilityProxy → kody.execute npx @kodycodes/cli execute --code 'export default async () => ({ ok: true })' # Same token, module runs on this machine npx @kodycodes/cli execute --local --file ./task.js --params '{"to":"me@example.com"}' npx @kodycodes/cli search "what can you do" npx @kodycodes/cli whoami npx @kodycodes/cli api usageGet --params '{}' ``` - Neither bootstrap store, `kody login`, nor a token → one shared not-authenticated error: agents on MCP call `cliCredentialBootstrap` and run the returned `cli_command` (never ask a human to paste a token); humans run `kody login`; CI/headless uses `tokenCreate` + `KODY_API_TOKEN`. - For `execute --local` specifically: `--token` / `KODY_API_TOKEN` wins when set; else a stored bootstrap/API token from `auth bootstrap`; else the stored `kody login` OAuth access token as Bearer only when the API and MCP URLs are paired (no under-the-hood `tokenCreate`). The default pair is `https://api.kody.codes` and `https://kody.codes/mcp`; preview workers pair when the API worker name adds `-api`, and loopback hosts pair at any port. For a different API origin, use `auth bootstrap --api-url ` or set `KODY_API_TOKEN`. Paired `kody login` OAuth is accepted as Bearer on CapabilityProxy and package-graph. Unpaired API origins need bootstrap or a scoped `kody_at_…` token. - Wrong/expired token → 401 with the same agent-first not-authenticated guidance (or the OAuth platform-gap message when the bearer is login OAuth). - Wrong scopes → the error names the missing scope and the exact command to mint a token that adds it (see [Opt-in scopes](#opt-in-scopes)). When your org role lacks the permission, it says so instead: a new token cannot add it, and an org Owner has to grant it. - Account flag off → the error includes `feature_disabled` and the `local-execute` feature flag. Another token does not bypass that flag. - Prefer the env var so the token stays out of shell history and `ps`. - Never scavenge host MCP OAuth tokens from disk (ADR 0053). ## Which credential a command uses One resolver picks the credential for every command, and `kody whoami` reports the same pick: | Commands | Order | | ------------------------------------------------------------------------- | ---------------------------------------------------------------------- | | `whoami`, `api`, `package`, `execute --local`, `context`, `daemon`, `dev` | `--token`, `KODY_API_TOKEN`, stored API token, `kody login` | | `search`, cloud `execute` (no `--local`) | `--token`, `kody login` (over MCP), `KODY_API_TOKEN`, stored API token | | `auth bootstrap --scopes` (minting) | `--token`, `kody login`, `KODY_API_TOKEN`, stored API token | - The stored API token comes from `kody auth bootstrap` or `kody auth setup-git`. `kody login` stores a login session next to it and does not replace it, so a stored token keeps winning for `api` and the other first-row commands after you log in again. `kody login` prints a note when that happens. - To change the stored token's scopes, run `npx @kodycodes/cli auth bootstrap --lifetime short --scopes ` (mints with your login and replaces the stored token). `kody logout` clears both. - `kody whoami` prints `credential:` (which one is in use, with its token id), its `scopes:`, and `also stored:` lines for credentials it outranks. With `--json` the same data is under `credential` and `otherCredentials`. - `insufficient_scope` errors end with `Credential used: …` and, for a stored token, the exact `auth bootstrap` command that replaces it. ## Opt-in scopes Bootstrap tokens default to local-execute use scopes plus `package:write`, `package:create`, and `token:delete`. They do not include `search:read`. Pass `--scopes` when a task needs a different set (the list replaces the defaults): ```bash # After `kody login` (mints any scope your org role grants): npx @kodycodes/cli auth bootstrap --lifetime short \ --scopes org:execute,org:read,package:execute,package:read,package:write # Agents on MCP without `kody login`: pass scopes when minting the code # cliCredentialBootstrap({ lifetime: "short", scopes: ["org:execute", "org:read", "package:execute", "package:read", "package:write"] }) npx @kodycodes/cli auth bootstrap --code 'kody_bc_…' --lifetime short ``` - `--scopes` replaces the default list, so include every scope you need. - The CLI passes the list straight to `cliCredentialBootstrap`. It mints with `--token` when given, else the `kody login` session, else `KODY_API_TOKEN` / the stored token. A token can only mint scopes it already holds. - A bootstrap code's scopes are fixed when it is minted, so `--code` with `--scopes` is an error that prints the matching `cliCredentialBootstrap` call. - `insufficient_scope` errors print both commands with the full scope list (your current scopes plus the missing one), so you can copy one and retry. ## Git HTTPS (`setup-git`) After the server ships persistent `kody_at_…` git auth (see [auth-setup-git](https://kody.codes/docs/auth-setup-git)), install a credential helper once: ```bash kody login # or: auth bootstrap --code … --lifetime long kody auth setup-git git clone https://kody.codes/@owner/name.git ``` `setup-git` uses your `kody login` session (or a scoped API token) to mint a user-owned `kody_at_…` with `package:read` and `package:write` (default `--lifetime long`; reuses a suitable stored token). That one credential authorizes every `@owner/name.git` you can access across all orgs — login `--org` only scopes the MCP mint session, not git. When an API token is already stored it probes `org_binding` on `GET /v1/tokens/current`; after a fresh login with no API token yet, it mints via the login session and checks `org_binding` on the minted token. Configures a **stable** credential helper (global `kody` only when that install's version supports `git-credential`, otherwise `npx -y @kodycodes/cli@ …` — never a major-only `@1` range that can reuse a stale npx cache, and never an ephemeral `~/.npm/_npx/…` path). Re-running `setup-git` rewrites stale helper entries; every CLI command also refreshes an outdated pin silently. ```bash git config --global credential.https://kody.codes.helper '!… git-credential' git config --global credential.https://kody.codes.useHttpPath true ``` The helper silently remints via your stored `kody login` when the API token is expired or within an hour of expiry, so you should not need to re-run `setup-git` for token lifetime. Undo with `kody auth remove-git` (clears git config helpers, revokes and deletes the stored API token; leaves login alone). Push moves live HEAD only (packages do not auto-publish). Existing `kody login` / `auth bootstrap` flows are unchanged. ## Local package testing (`kody dev`) Test an unpublished package (app, exports, jobs, webhooks, subscriptions) before publish. Bundling reuses the same esbuild path as `execute --file` / publish (`local-bundle.ts`). One shared flag: | Command | `--target prod` (default for execute/search) | `--target mock` (default for `kody dev`) | | -------------------- | -------------------------------------------- | ------------------------------------------------- | | `kody dev` | Local app against real Kody | Local app against the CLI mock | | `execute` / `search` | Real capabilities, storage, secrets | Mock catalog / scratch storage / fake credentials | The mock is built into the CLI (no kody-celld). It refuses unimplemented routes with `not_implemented_in_mock` rather than faking success. `--reset-storage` wipes the mock session's scratch store. Share previews are out of scope; `execute --local` behavior is unchanged when `--target` is omitted (prod). ```bash cd ./my-package kody dev # serve kody.app.entry; live reload; --target mock kody dev fetch /health # agent-friendly HTTP probe kody dev job heartbeat # run a declared job once (never schedules locally) kody dev webhook ping --body ./fixture.json kody dev emit demo.event --body ./event.json # subscription delivery shape kody dev check # local bundles + snapshot membership + publish checks (needs login) kody dev check --offline # local bundles + snapshot membership only; publish can still refuse kody dev check --debug # also print the publish checks call: HTTP status, error code, request id # Four surfaces against mock then prod: kody execute --target mock --invoke ./greet --params '{"name":"kent"}' kody execute --target prod --invoke ./greet --params '{"name":"kent"}' # Published export locally (no --code import wrapper): kody execute --local --invoke kody:@scope/pkg/export --params '{}' kody search --target mock local ``` ### What `kody dev` does while it runs - **Rebuilds never drop requests.** A save starts a new workerd, and the proxy switches to it only once it is listening. The old one finishes its in-flight requests and then stops. Requests that arrive mid-rebuild wait for it, so you never get a `503` or the previous bundle after a save. - **Watching survives editors and scripts.** chokidar watches the package (skipping `node_modules`, dotfiles, and `*.map`). A 1s stat poll over the files in the current bundle graph backs it up, so replaced files (`sed -i`, `cp`, atomic saves) are always picked up. - **Build errors** print `file:line:col: message` with a code frame. Until the next good build, every request gets a `500` error page with the same text (HTML for browsers, plain text otherwise, header `x-kody-dev-error: build`). The last good build does not keep answering `200`. - **Runtime errors**: an uncaught throw from the fetch handler returns a dev-only `500` page with the stack mapped back to your `.ts` sources (`x-kody-dev-error: runtime`). Runtime wrapper and native frames are dropped. The terminal prints the same stack. - **Logs**: app `console.*` output streams to the terminal tagged with the request, e.g. `[GET /scores #12] saved 3 rows`. `warn` and `error` go to stderr. - **Live reload** (on by default; `--no-live-reload` turns it off): HTML responses get a small `