Archived.
@kodycodes/cli1.27.0 and later are published from kentcdodds/kody (packages/cli). Versions through 1.26.3 published from this repository keep working and still talk to Kody over MCP. New CLI work belongs in the monorepo.
Install Kody as a remote MCP server in local agents.
Talks MCP 2026-07-28 (Kody's stateless /mcp lane) and logs in with Client
ID Metadata Documents (SEP-991).
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.
npx @kodycodes/cli install
npx @kodycodes/cli skill installCopyright © 2026 Kent C. Dodds. MIT licensed.
npx @kodycodes/cli installkody 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. It does not list web clients.
For ChatGPT, Claude.ai, and Grok, use 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.
npx @kodycodes/cli skill installcopies the getting-started skill into Claude Code / Cursor / Agents. That skill also tells the agent to prefer the MCP server over the CLI.
--mcp-url or KODY_MCP_URL overrides the default https://kody.codes/mcp.
Optional. Use when you need a scripted or headless client instead of a host MCP connection.
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). Re-logging
into an org that is already stored replaces only that org's credential. Org-scoped
commands (execute, search, whoami, api, …) take --org <slug> or
KODY_ORG (flag wins, then env, then the active org). kody logout removes the
active org; kody logout --org <slug> 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).
| Command | Purpose |
|---|---|
kody install |
Detect running local MCP clients, write their config, and start host OAuth. Recommended long-term path. |
kody skill install |
Copies the getting-started skill into Claude Code / Cursor / Agents. |
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. |
kody org list |
Lists logged-in orgs and marks the active one. --json for agents. |
kody org use [<slug>] |
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 <slug> 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 |
Opt-in extra scopes: 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. |
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. |
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 |
Confirms the CLI MCP connection and lists tools. With a scoped API token (and no login), shows token identity via the Open API. |
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 <operationId> |
Thin Open API wrapper matching the MCP api tool: operationId + flat --params JSON. Auth: --token / KODY_API_TOKEN / stored auth bootstrap token. 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. |
kody package collaborators / grant / revoke |
Who can use one package, and add or remove teams and members of the org (--org). <package> is @owner/leaf, the kody id, or the package id. grant <package> --team <slug>|--user <username> [--preset use|contribute|manage] (default use) adds or changes a collaborator via packageCollaboratorSet; people outside the org are refused. revoke <package> --team|--user|--grant <grant_id> 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 -). 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. |
--json prints structured MCP results.
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. The CLI uses that
token path when you pass --token / KODY_API_TOKEN and are not logged in
(or when you pass --token explicitly):
# Preferred for agents on MCP (no tokenCreate, no second OAuth):
# 1. MCP api / kody.cliCredentialBootstrap → { bootstrap_code, cli_command }
npx @kodycodes/cli auth bootstrap --code 'kody_bc_…'
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 → the error preferscliCredentialBootstrap→auth bootstrap, thenkody login, thentokenCreatefor CI/headless (--token/KODY_API_TOKEN). - For
execute --localspecifically:--token/KODY_API_TOKENwins when set; else a stored bootstrap/API token fromauth bootstrap; else the storedkody loginOAuth access token as Bearer only when the API and MCP URLs are paired (no under-the-hoodtokenCreate). The default pair ishttps://api.kody.codesandhttps://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, useauth bootstrap --api-url <origin>or setKODY_API_TOKEN. The Open API must accept that OAuth bearer on CapabilityProxy / package-graph (kentcdodds/kody#2812); until then use bootstrap or a scopedkody_at_…token. - Wrong/expired token → 401 with a mint-fresh-token message (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). 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_disabledand thelocal-executefeature 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).
Bootstrap tokens default to least-privilege local-execute scopes (no
package:write, for example). Opt in to more when a task needs them:
# 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--scopesreplaces the default list, so include every scope you need.- The CLI passes the list straight to
cliCredentialBootstrap. It mints with--tokenwhen given, else thekody loginsession, elseKODY_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
--codewith--scopesis an error that prints the matchingcliCredentialBootstrapcall. insufficient_scopeerrors print both commands with the full scope list (your current scopes plus the missing one), so you can copy one and retry.
After the server ships persistent kody_at_… git auth (see
auth-setup-git), install a credential
helper once:
kody login # or: auth bootstrap --code … --lifetime long
kody auth setup-git
git clone https://kody.codes/@owner/name.gitsetup-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@<exact-version> … — 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.
git config --global credential.https://kody.codes.helper '!… git-credential'
git config --global credential.https://kody.codes.useHttpPath trueThe 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.
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).
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 # bundle + light publish validations
# 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-
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
503or 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: messagewith a code frame. Until the next good build, every request gets a500error page with the same text (HTML for browsers, plain text otherwise, headerx-kody-dev-error: build). The last good build does not keep answering200. -
Runtime errors: an uncaught throw from the fetch handler returns a dev-only
500page with the stack mapped back to your.tssources (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.warnanderrorgo to stderr. -
Live reload (on by default;
--no-live-reloadturns it off): HTML responses get a small<script>that listens on/__kody/dev/events(server-sent events). The page reloads after each swap to a new build. Build errors, and runtime errors from the page's ownfetchcalls, show an overlay; the page reloads on the next good build. There is no HMR. Only requests that accepttext/html(browser page loads) get the script; compressed HTML, agent requests, andkody dev fetchresponses are left untouched. -
Fast rebuilds. File events are debounced for 30ms, and esbuild reuses an incremental context, so a save usually serves the new code in about 60ms on a small app.
-
Ports. Each package gets a stable default port in 4100–4999 (derived from
kody.id), so URLs survive restarts. If it is taken,kody devmoves to the next free port. An explicit--port <n>is used exactly and fails with a fix when the port is busy (--port 0picks any free port). Starting a secondkody devfor the same package and target without--portfails and names the running server's URL and pid. -
Client bundle and assets, same URLs as a published app:
kody.app.clientis bundled for the browser and served at<appBasePath>/_assets/client.<hash>.js(inline source map;client.externalsstay bare imports).packageContext.clientModuleUrlpoints at it, and/_assets/__version.jsonreports it. Files inkody.app.assetsare served under/_assets/. Both areno-storeand rebuild on save; editing an asset reloads open pages. -
Prebuild:
kody.dev.prebuildruns a shell command from the package root before every build (withKODY_DEV=1), for generated CSS, icons, or data files. Its output is tagged[prebuild]. A non-zero exit is shown as a build error. File changes made while it runs do not trigger another build.kody.dev.ignorelists root-relative path prefixes the watcher skips:{ "kody": { "app": { "entry": "./app/router.ts", "client": "./app/client.ts", "assets": "./public" }, "dev": { "prebuild": "node scripts/build-css.mjs", "ignore": ["public/generated"] } } }kody.devonly affectskody dev; publish ignores it. -
Cleanup: Ctrl-C,
SIGTERM, andSIGHUPstop workerd and remove thekody-dev-*temp dir. If the CLI is killed outright, a small reaper process does the same. Each start also sweeps dirs and orphaned workerd processes left behind by dead CLI runs, including older CLI versions.
kody dev --json --ready-file .kody-dev.json & # NDJSON events on stdout
curl -s http://127.0.0.1:<port>/__kody/dev/status
kody dev fetch /scores # reuses the running server-
--jsonprints one JSON object per line on stdout, each with atypeand an ISOts:ready:url,port,basePath,pid,statusUrl,buildIdrebuild_start:filesrebuild_ok:ms,buildId,restartedbuild_error:file,line,col,message,diagnosticsruntime_error:message,stack,requestlog:source,level,message,request
Status lines stay on stderr.
-
GET /__kody/dev/statusreturns{ ok, state, buildId, url, port, basePath, target, package, pid, lastRebuild, lastError }.stateis one ofstarting,building,ready, orbuild_error.lastErrorholds the current build error or the most recent runtime error. -
--ready-file <path>writes{ url, port, basePath, pid, statusUrl }once the first build settles, and deletes the file on exit. -
kody dev fetchattaches to a runningkody devfor the same package and--target(found through the user cache dir and confirmed via the status endpoint) instead of booting a second workerd. The request waits for any in-progress rebuild, so fetching right after a save sees the new code. Pass--no-attachto force a one-shot server.
kody execute --local runs the same execute module (default export called
with --params) on this machine instead of in Kody's cloud sandbox. Local CPU
is free; every kody:runtime call (kody.*, kody.mcp.*, workflows.create)
is proxied to Kody's CapabilityProxy and metered like a cloud hop. Static
kody:@scope/package/export imports are resolved into the local workerd
bundle via POST /v1/local-execute/package-graph (same Bearer as
CapabilityProxy — never hosted MCP execute, and never a whole-module
CapabilityProxy → kody.execute defer). There is no author-facing
packages.invoke.
# Prefer login when already signed in (no temporary API token to paste):
npx @kodycodes/cli login
npx @kodycodes/cli execute --local --file ./task.js --params '{"to":"me@example.com"}'
# Agents on MCP: bootstrap code → store (no tokenCreate, no second OAuth):
npx @kodycodes/cli auth bootstrap --code 'kody_bc_…'
npx @kodycodes/cli execute --local --file ./task.js --params '{"to":"me@example.com"}'
# Or a scoped API token (still wins over login / bootstrap store when set):
export KODY_API_TOKEN=… # minted through the Kody `api` tool
npx @kodycodes/cli execute --local --file ./task.js --params '{"to":"me@example.com"}'
# ADR 0068: pick credentials for credential(key) / credentials(key)
npx @kodycodes/cli execute --local --credential github=work --credential resend=personal:prod \
--code 'import { credential } from "kody:runtime"; export default async () => (await credential("github")).name'
# Call a published package export without a --code import wrapper:
npx @kodycodes/cli execute --local --invoke kody:@scope/pkg/export --params '{"name":"kent"}'- Auth:
--token/KODY_API_TOKENwhen set; else storedauth bootstrapAPI token; else a validkody loginsession (OAuth access token as Bearer — never printed, never exchanged viatokenCreate). Prefer the env var for API tokens so they stay out of shell history andps. The bearer stays in the CLI process; the sandbox only talks to a loopback bridge. Host MCP OAuth from other clients is never read. Until kentcdodds/kody#2812 ships, login-only Bearer is rejected by the Open API — use bootstrap or akody_at_…token in that case. - Node.js: 22 or newer (
package.jsonenginesis>=22). Older Node fails immediately with that requirement, before workerd is downloaded or started. - Bundling:
--fileentries are bundled with esbuild before workerd loadsmain.js(relative local imports and bare npm packages fromnode_modules;kody:*stays external for package-graph / runtime). That matches hosted execute and publish. Unresolvable imports fail with a clear bundle error before any network call.--codeand stdin (--file -) are left as written (no filesystem graph to follow). - Runtime: a pinned workerd
release, downloaded once from GitHub, sha256-verified, and cached under
~/.cache/kody(Linux, or$XDG_CACHE_HOME/kody) or~/Library/Caches/kody(macOS). Override withKODY_CACHE_DIR, or pointKODY_WORKERD_PATHat your own binary. No Docker required. Linux and macOS (x64/arm64) only for now. - API:
https://api.kody.codesby default (--api-urlorKODY_API_URLto override). The CLI callsPOST /v1/local-execute/package-graphbefore starting workerd (it authenticates the bearer and, when the module importskody:@…, downloads stamped package modules for local embedding, platform issue) andPOST /v1/capability-proxy/callwith{ path, args, conversationId? }for each runtime call. CapabilityProxy requests go through Node's ownnode:http(s)client with one keep-alive connection and a 7-minute idle limit for headers and body chunks. Origin's 5-minute deadline for afetchwith{{secret:…}}placeholders ends that call first, with its own error. Node's default 300-second fetch limit no longer cuts it short. WithNODE_USE_ENV_PROXY=1or--use-env-proxy, these requests honorHTTP_PROXY/HTTPS_PROXY/NO_PROXYlike Node's own fetch (Node 22.21 / 24.5 or later; older Node fails with a clear error instead of silently bypassing the proxy). A PR preview's API host ishttps://kody-pr-<n>-api.<subdomain>.workers.dev; the app origin (kody-pr-<n>) answers/v1/*with an HTML 404. - Errors: an expired/revoked token or an account without the
local-executeflag fails fast with a clear message before any code runs. The token is only sent over https (plain http is allowed for localhost). A non-JSON error body, such as an HTML page from the wrong--api-url, is shortened to its title or a 200-character excerpt plus its size. Missing package-graph API, unresolved imports, or missing artifacts fail clearly — there is no silent fallback to cloudkody.execute. - Lifetime: no execution time limit locally; Ctrl-C (or SIGTERM) stops workerd and removes the temporary module.
- Saved packages:
import { … } from 'kody:@owner/name/export'under--localkeeps the user module + package graph in local workerd. Local package CPU is not billed as a full cloud execute of the user module; per-call CapabilityProxy hops (and whatever the package-graph prep call meters once shipped) may still meter. Prefer keeping--localin agent workflows — do not switch to hosted MCPexecute.--invokewithout--localis the thin passthrough for a single export (still cloud).packageStorage(),packageSecrets,email, andeventsstay unbound on the ad hoc entry like cloud execute unless the downloaded package modules carry stamps. Outboundfetchis public-network-only by default. Pass--allow-private-networkwithexecute --localto also allow private and local addresses; the loopback bridge used for CapabilityProxy calls remains available either way.
CLI credentials are stored in the OS secret store:
- macOS: Keychain
- Windows: Credential Manager
- Linux: Secret Service / libsecret (not the in-memory kernel keyring)
If the keychain is unavailable (common on headless Linux), the CLI writes a
0600 file under $XDG_CONFIG_HOME/kody (or %APPDATA%\kody on Windows,
~/Library/Application Support/kody on macOS). Tokens are never printed.
Access tokens refresh automatically on expiry or HTTP 401.
execute --local prefers --token / KODY_API_TOKEN when set; otherwise it
uses stored CLI credentials from kody login. Token-only cloud execute still
only reads --token / KODY_API_TOKEN.
npm run build && npm run bench times the built CLI (startup, status, and
execute --local against a loopback stand-in for api.kody.codes) over 20 runs
and prints p50/p95. The bench workflow runs it on every pull request and
weekly, failing when a scenario's p50 exceeds the budget in
scripts/bench-budgets.json (milliseconds over the bare Node startup of the
same run).
This repo uses semantic-release
the same way match-sorter does:
conventional commits on main publish @kodycodes/cli with npm provenance
(id-token: write). Version in package.json stays 0.0.0-semantically-released.
Trusted publishing for @kodycodes/cli is attached to this GitHub repository.
MIT © Kent C. Dodds