Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ Or run via `npx @kodycodes/cli` without a global install.
| `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 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. |
| `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. |

`--json` prints structured MCP results.
Expand Down Expand Up @@ -93,6 +94,7 @@ npx @kodycodes/cli execute --code 'export default async () => ({ ok: true })'
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 prefers
Expand Down
52 changes: 50 additions & 2 deletions src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,11 @@ import { runInstall } from './install.js'
import { resolveLocalExecuteBearer } from './local-execute-auth.js'
import { runLocalExecute } from './local-execute.js'
import { assertLocalExecuteNodeEngine } from './node-engine.js'
import { searchWithApiToken, whoamiWithApiToken } from './open-api-client.js'
import {
callOpenApiOperation,
searchWithApiToken,
whoamiWithApiToken,
} from './open-api-client.js'
import { runRemoteExecuteWithToken } from './remote-execute.js'
import { installSkill } from './skill.js'
import { readPackageVersion } from './package-info.js'
Expand All @@ -40,6 +44,7 @@ export type CommandName =
| 'auth'
| 'whoami'
| 'search'
| 'api'
| 'execute'
| 'install'
| 'skill'
Expand Down Expand Up @@ -107,6 +112,7 @@ export function resolveCommand(argv: Array<string>): {
case 'auth':
case 'whoami':
case 'search':
case 'api':
case 'execute':
case 'install':
case 'skill':
Expand Down Expand Up @@ -260,7 +266,7 @@ async function dispatch(
const scopes = result.stored.scopes?.join(', ') || '(none)'
write(
[
`Bootstrap API token stored for execute --local, search, whoami, and token-auth cloud execute.`,
`Bootstrap API token stored for execute --local, search, whoami, api, and token-auth cloud execute.`,
`api: ${result.stored.apiUrl}`,
`token id: ${result.stored.tokenId}`,
`scopes: ${scopes}`,
Expand Down Expand Up @@ -462,6 +468,33 @@ async function dispatch(
write(formatToolResult(result, json))
return result.isError ? 1 : 0
}
case 'api': {
const operationId = parsed.positionals[0]?.trim() ?? ''
if (!operationId || parsed.positionals.length > 1) {
throw new Error(
'Usage: kody api <operationId> [--params <json>] [--token <token>] [--api-url <url>] [--json]',
)
}
const apiUrl = apiUrlFrom({
apiUrl:
typeof parsed.values['api-url'] === 'string'
? parsed.values['api-url']
: undefined,
})
const tokenValues = tokenFlagValues(parsed.values)
const params = parseApiParamsJson(
typeof parsed.values.params === 'string' ? parsed.values.params : undefined,
)
const result = await callOpenApiOperation({
operationId,
params,
token: requireApiToken(tokenValues, process.env, 'api', { apiUrl }),
apiUrl,
})
// Always JSON: mirrors MCP `api` structured results for agents/scripts.
write(`${JSON.stringify(result, null, 2)}\n`)
return 0
}
case 'install': {
const result = await runInstall(
{
Expand Down Expand Up @@ -563,6 +596,21 @@ function tokenFlagValues(values: ReturnType<typeof parseKnown>['values']): {
}
}

/** Parse `--params` JSON for `kody api` (flat object, same as MCP `api`). */
export function parseApiParamsJson(paramsJson?: string): Record<string, unknown> {
if (paramsJson === undefined) return {}
let parsed: unknown
try {
parsed = JSON.parse(paramsJson)
} catch {
throw new Error('--params must be valid JSON (a flat object).')
}
if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) {
throw new Error('--params must be a JSON object (e.g. \'{"query":"email"}\').')
}
return parsed as Record<string, unknown>
}

/**
* Prefer a scoped API token when the user passed `--token`, or when
* `KODY_API_TOKEN` / a stored bootstrap token is available and there is no
Expand Down
22 changes: 17 additions & 5 deletions src/help.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ Usage:
kody auth bootstrap --code <kody_bc_…> [--api-url <url>]
kody whoami [--mcp-url <url>] [--token <token>] [--api-url <url>] [--json]
kody search [query] [--entity <ref>] [--domain <id>] [--limit <n>] [--token <token>] [--api-url <url>] [--json]
kody api <operationId> [--params <json>] [--token <token>] [--api-url <url>] [--json]
kody execute [--invoke <ref> | --code <esm> | --file <path>] [--params <json>] [--conversation-id <id>] [--json]
[--token <token>] [--api-url <url>] [--local]
kody install [--mcp-url <url>] [--clients <ids>] [--yes] [--project] [--json]
Expand All @@ -28,13 +29,24 @@ Usage:
Redeem a one-shot \`kody_bc_…\` from MCP \`cliCredentialBootstrap\`
(POST /v1/tokens/bootstrap/redeem, no Authorization header).
Stores the resulting \`kody_at_…\` for \`execute --local\`,
search, whoami, and token-auth cloud execute without printing
the token. Prefer this over tokenCreate for agents already on
MCP. Interactive humans can use \`kody login\` instead.
search, whoami, api, and token-auth cloud execute without
printing the token. Prefer this over tokenCreate for agents
already on MCP. Interactive humans can use \`kody login\`
instead. Do not call \`kody api cliCredentialBootstrapRedeem\`
— that would print the token.

api Call one Open API operation by operationId + flat --params
JSON (same shape as the MCP \`api\` tool). Uses scoped API
auth only (\`--token\` / ${apiTokenEnvVar} / stored bootstrap).
Prints JSON. Example: \`kody api usageGet --params '{}'\`.
Unknown operationIds error clearly; see
${defaultApiUrl}/openapi.json. tokenCreate / tokenRotate
responses include a one-time token value — prefer env storage
over pasting into chat.

--token / ${apiTokenEnvVar}
Scoped API token (preferred via env). With no \`kody login\`
session, search / whoami / execute use the Open API and
session, search / whoami / api / execute use the Open API and
CapabilityProxy — including cloud execute without --local.
Auth priority matches \`execute --local\`: \`--token\` /
${apiTokenEnvVar}; stored bootstrap/API token from
Expand All @@ -58,7 +70,7 @@ Usage:

Environment:
KODY_MCP_URL Override the default MCP URL (${defaultMcpUrl})
${apiTokenEnvVar} Scoped API token for token-auth search / whoami / execute
${apiTokenEnvVar} Scoped API token for token-auth search / whoami / api / execute
KODY_API_URL Override the Kody API URL (${defaultApiUrl})
KODY_CACHE_DIR Where execute --local caches workerd (default: user cache dir)
KODY_WORKERD_PATH Use this workerd binary instead of the pinned download
Expand Down
Loading
Loading