Skip to content
DocsLocal CLI execute

Concepts

Local CLI execute

Official Kody doc

When Node ≥22 and @kodycodes/cli are available, prefer npx @kodycodes/cli execute --local for one-off modules, smoke tests, and composition. Local execute is orthogonal to Open API: the CLI runs your module in a local workerd and forwards each kody:runtime call through the CapabilityProxy. Do not use hosted MCP execute for agent work when a local or Open API path exists.

Auth: --token / KODY_API_TOKEN (kody_at_… with org:execute; add package:execute when the module imports kody:@…), or when no API token is set, the access token from kody login (MCP OAuth) as Bearer on CapabilityProxy and package-graph routes.

Setup

Agents already on MCP (do this yourself; never ask a human for a token): call cliCredentialBootstrap with a required lifetime (single-task agents use short). Prefer MCP api with operationId: "cliCredentialBootstrap" (or the capability via search when you need the schema). It returns a one-shot kody_bc_… code and a cli_command that already includes lifetime flags (not a kody_at_…). Bootstrap tokens are person-owned (org_binding: user); pass ?org=<slug> on API calls to act in a team org (default: personal). Run the returned cli_command (no second OAuth, no tokenCreate):

{
	"operationId": "cliCredentialBootstrap",
	"params": { "lifetime": "short" }
}
# Or run the cli_command returned by cliCredentialBootstrap:
npx @kodycodes/cli auth bootstrap --code 'kody_bc_…' --lifetime short

short is 1 hour idle / 24 hours max. long is 14 days idle / 3 months max. Or pass both --idle-ttl-seconds and --max-lifetime-seconds. Lifetime is required; missing it fails with the exact flag syntax.

Default scopes: bootstrap tokens include local-execute use scopes (org:execute, org:read, and package/integration/secret/email/job/app/memory use scopes), plus package:write (plain-repo / package pushes), package:create, and token:delete (self-revoke). They do not include search:read. When a task needs a different set, pass scopes when minting. The list replaces the defaults, so include every scope you need:

# Agents on MCP, with the api tool (execute is refused): cliCredentialBootstrap({ lifetime: "short", scopes: ["org:execute", "org:read", "package:execute", "package:read", "package:write"] })
npx @kodycodes/cli auth bootstrap --code 'kody_bc_…' --lifetime short

# After `kody login`, the CLI mints the code itself and stores the token:
npx @kodycodes/cli auth bootstrap --lifetime short \
  --scopes org:execute,org:read,package:execute,package:read,package:write

--scopes mints with --token when given, else the kody login session (any scope your org role grants), else KODY_API_TOKEN or the stored token (only scopes that token already holds). --code with --scopes is an error because a code's scopes are fixed when it is minted.

Interactive humans: kody login once per machine (beside the agent cliCredentialBootstrap path above), then run --local without KODY_API_TOKEN.

npx @kodycodes/cli login   # once

On a remote VM, NAS, or SSH session, use npx @kodycodes/cli login --device and approve the printed code from any browser. See Remote or headless machines (raw).

CI / headless only: scoped KODY_API_TOKEN (kody_at_… with org:execute, plus package:execute for saved-package imports), usually from MCP api tokenCreate with a required lifetime. Put the value in the environment; never paste it into chat (agents on MCP must use cliCredentialBootstrap instead). Prefer the env var over --token. When set, KODY_API_TOKEN wins over kody login / bootstrap store. Minting details: Open API (guide:open_api).

{
	"operationId": "tokenCreate",
	"params": {
		"name": "kody-cli-local",
		"scopes": [
			"org:execute",
			"org:read",
			"package:execute",
			"package:read",
			"integration:read",
			"integration:use",
			"secret:use"
		],
		"lifetime": "short"
	}
}
export KODY_API_TOKEN='kody_at_…'

Usage

npx @kodycodes/cli execute --local --code 'import { kody } from "kody:runtime"; export default async function main() { return await kody.metaGetCurrentUser({}) }'

Use --file path.ts the same way when the module lives on disk. Keep --local; static kody:@owner/name imports still run locally (package-graph download + CapabilityProxy hops). The CLI posts the kody:@ imports that still need published modules to POST /v1/local-execute/package-graph (with a tiny synthetic entry), never the whole module. Inline code on that route may be up to just under the Open API JSON body limit (~5 MiB); larger callers pass imports alone so origin synthesizes a tiny entry. If inline code still exceeds that ceiling, the API (and CLI) name the actual UTF-8 byte length and the maximum — not a bare Invalid parameters. Package-graph always returns the gateway-fetch shim, so ad-hoc scripts with no kody:@ imports still expand {{secret:…}} placeholders on origin (or fail closed naming the secret) instead of sending them raw. import { credential } from 'kody:runtime' for ad hoc and unpublished package source needs a CLI whose host kody:runtime exports credential / credentials per the CLI local credential runtime contract (CapabilityProxy opens a handle; gateway placement only — same as hosted). Older CLIs fail at import time; upgrade @kodycodes/cli.

A fetch that carries a placeholder runs on origin with the same 5-minute overall deadline as cloud fetches. The deadline starts when origin sends the request upstream, after it checks your quota and fills placeholders, and runs until the response body ends. The response is also aborted after 60s with no bytes. --local has no run cap, so a long streamed response, such as an AI completion, can finish.

Small buffered placeholder-bearing bodies use the JSON CapabilityProxy hop (kody.gatewayFetch): origin buffers the whole response before returning it, so your code receives nothing until the body ends, and a body over 4 MiB fails. Streamed bodies, multipart bodies, and bodies that would exceed 4 MiB use the streaming hop (POST /v1/capability-proxy/gateway-fetch via kody.gatewayFetchStream): there is no 4 MiB hop cap. That path still respects the Cloudflare Worker upload limit (~100 MiB on Free/Pro). A Content-Length over that limit returns JSON 413 payload_too_large. See the CLI streaming gateway-fetch contract.

The CLI CapabilityProxy client waits up to 7 minutes for origin's reply, so the gateway's 5-minute deadline ends a slow fetch, with its own error. @kodycodes/cli below 1.13.1 hits Node's default 300-second wait (UND_ERR_HEADERS_TIMEOUT) instead. With NODE_USE_ENV_PROXY=1 or --use-env-proxy, these requests still go through HTTP_PROXY / HTTPS_PROXY. A fetch without placeholders, and a multipart upload that does not need fill, goes straight from local workerd and is not subject to this deadline.

Running from a package checkout

Run --file from inside a package directory and the CLI resolves that package's own kody:@… exports (and .kody/deps.json mappings) from the working tree. Their remaining kody:@ dependencies come from published package-graph. When a successful package-graph response omits required modules, the module never ran: that error (incomplete_package_graph in --json output) is a package resolution problem, not an auth problem. HTTP 401 or 403 from package-graph is still auth or permission — use the Failure section below. Two workarounds for incomplete_package_graph:

  • Inside the checkout, import your own package's files with relative imports (./src/helper.ts) instead of kody:@scope/pkg/helper.
  • To test the published exports, run the entry from a directory outside the checkout so every kody:@ import resolves from package-graph.

Failure

  • Node is below 22, the CLI is missing, or the host cannot run workerd. Use Open API / MCP api, or fix the environment. Hosted MCP execute stays banned for that failure case.
  • Not authenticated (missing or invalid credentials). Agents on MCP: call cliCredentialBootstrap and run the returned cli_command; never ask a human to paste a token. Humans: kody login. CI/headless: tokenCreate + KODY_API_TOKEN.
  • Bootstrap or redeem rejects a missing lifetime. Pass --lifetime short|long (or both idle/max flags). Call cliCredentialBootstrap again with lifetime: "short" if the code expired.
  • insufficient_scope on a token means it lacks a scope. The error names the scope and prints both mint commands with the full list (the token's scopes plus the missing one); run one and retry. --token and KODY_API_TOKEN still win over the stored token, so replace or unset them before retrying. Open API errors carry the same list as details.mint_scopes. When your org role lacks the permission (denial: missing_permission), a new token cannot help; an org Owner has to grant it.
  • Package-scoped secretSet / secretSetMany / secretDelete are not available under --local. Local CapabilityProxy does not attach package secret authority (a client-supplied package id is not trusted as a hosted run). Use hosted package execute, or set the secret from the account UI (/connect/secret-set) or Open API secretSet with a user scope / outside local execute. See Secrets.
  • The module imports kody:@…. Keep --local.
  • Auth priority for --local: --token / KODY_API_TOKEN, then the stored bootstrap token, then kody login.

Working with an agent? This page is also plain markdown at /docs/local-execute.md, or load it over MCP with search({ entity: 'guide:local_execute' }).