@kodycodes/cli 1.6.0 → 1.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +55 -23
- package/dist/api-token-store.d.ts +28 -0
- package/dist/api-token-store.js +120 -0
- package/dist/api-token-store.js.map +1 -0
- package/dist/api-token.d.ts +16 -3
- package/dist/api-token.js +26 -5
- package/dist/api-token.js.map +1 -1
- package/dist/auth-bootstrap.d.ts +51 -0
- package/dist/auth-bootstrap.js +157 -0
- package/dist/auth-bootstrap.js.map +1 -0
- package/dist/capability-proxy.d.ts +4 -3
- package/dist/capability-proxy.js +12 -8
- package/dist/capability-proxy.js.map +1 -1
- package/dist/cli.d.ts +3 -1
- package/dist/cli.js +81 -12
- package/dist/cli.js.map +1 -1
- package/dist/help.js +23 -11
- package/dist/help.js.map +1 -1
- package/dist/local-execute-auth.d.ts +32 -0
- package/dist/local-execute-auth.js +39 -0
- package/dist/local-execute-auth.js.map +1 -0
- package/dist/local-package-graph.js +7 -3
- package/dist/local-package-graph.js.map +1 -1
- package/dist/redact.js +5 -1
- package/dist/redact.js.map +1 -1
- package/package.json +1 -1
- package/skills/kody/SKILL.md +24 -8
package/README.md
CHANGED
|
@@ -60,8 +60,9 @@ Or run via `npx @kodycodes/cli` without a global install.
|
|
|
60
60
|
| `kody install` | Detect running local MCP clients, write their config, and start host OAuth. **Recommended long-term path.** |
|
|
61
61
|
| `kody skill install` | Copies the getting-started skill into Claude Code / Cursor / Agents. |
|
|
62
62
|
| `kody login` | Browser OAuth (CIMD + PKCE) for the CLI itself. Stores access and refresh tokens. |
|
|
63
|
-
| `kody logout` | Deletes stored CLI credentials. |
|
|
64
|
-
| `kody status` | Shows CLI login state without printing secrets. |
|
|
63
|
+
| `kody logout` | Deletes stored CLI OAuth credentials and any stored bootstrap/API token. |
|
|
64
|
+
| `kody status` | Shows CLI login / stored API token state without printing secrets. |
|
|
65
|
+
| `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). |
|
|
65
66
|
| `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. |
|
|
66
67
|
| `kody search [query]` | Calls Kody `search` from the CLI (prefer the host MCP tool). Token-only auth uses Open API `GET /v1/search`. |
|
|
67
68
|
| `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. |
|
|
@@ -70,13 +71,21 @@ Or run via `npx @kodycodes/cli` without a global install.
|
|
|
70
71
|
|
|
71
72
|
## Token-authenticated execute (no `kody login`)
|
|
72
73
|
|
|
73
|
-
|
|
74
|
-
`
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
74
|
+
Agents already on Kody MCP should prefer `cliCredentialBootstrap` →
|
|
75
|
+
`kody auth bootstrap --code …` (ADR 0056) so a one-shot `kody_bc_…` seeds the
|
|
76
|
+
CLI store without pasting `kody_at_…` into chat. Interactive humans can use
|
|
77
|
+
`kody login`. Scoped API tokens (`kody_at_…`, from bootstrap redeem,
|
|
78
|
+
`tokenCreate`, or `POST /v1/tokens`) authenticate the Open API and
|
|
79
|
+
CapabilityProxy. They never replace MCP OAuth on `/mcp`. The CLI uses that
|
|
80
|
+
token path when you pass `--token` / `KODY_API_TOKEN` and are not logged in
|
|
81
|
+
(or when you pass `--token` explicitly):
|
|
78
82
|
|
|
79
83
|
```bash
|
|
84
|
+
# Preferred for agents on MCP (no tokenCreate, no second OAuth):
|
|
85
|
+
# 1. MCP api / kody.cliCredentialBootstrap → { bootstrap_code, cli_command }
|
|
86
|
+
npx @kodycodes/cli auth bootstrap --code 'kody_bc_…'
|
|
87
|
+
npx @kodycodes/cli execute --local --file ./task.js
|
|
88
|
+
|
|
80
89
|
export KODY_API_TOKEN=… # scopes: local-execute (+ search:read for search)
|
|
81
90
|
# Cloud execute — module runs in Kody's sandbox via CapabilityProxy → kody.execute
|
|
82
91
|
npx @kodycodes/cli execute --code 'export default async () => ({ ok: true })'
|
|
@@ -86,17 +95,24 @@ npx @kodycodes/cli search "what can you do"
|
|
|
86
95
|
npx @kodycodes/cli whoami
|
|
87
96
|
```
|
|
88
97
|
|
|
89
|
-
- Neither `kody login
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
98
|
+
- Neither bootstrap store, `kody login`, nor a token → the error prefers
|
|
99
|
+
`cliCredentialBootstrap` → `auth bootstrap`, then `kody login`, then
|
|
100
|
+
`tokenCreate` for CI/headless (`--token` / `KODY_API_TOKEN`).
|
|
101
|
+
- For `execute --local` specifically: `--token` / `KODY_API_TOKEN` wins when
|
|
102
|
+
set; else a stored bootstrap/API token from `auth bootstrap`; else the
|
|
103
|
+
stored `kody login` OAuth access token as Bearer (no under-the-hood
|
|
104
|
+
`tokenCreate`). The Open API must accept that OAuth bearer on
|
|
105
|
+
CapabilityProxy / package-graph
|
|
106
|
+
([kentcdodds/kody#2812](https://github.com/kentcdodds/kody/issues/2812));
|
|
107
|
+
until then use bootstrap or a scoped `kody_at_…` token.
|
|
108
|
+
- Wrong/expired token → 401 with a mint-fresh-token message (or the OAuth
|
|
109
|
+
platform-gap message when the bearer is login OAuth).
|
|
95
110
|
- Wrong scopes → the error includes `insufficient_scope` and the required
|
|
96
111
|
scope when Kody sends one.
|
|
97
112
|
- Account flag off → the error includes `feature_disabled` and the
|
|
98
113
|
`local-execute` feature flag. Another token does not bypass that flag.
|
|
99
114
|
- Prefer the env var so the token stays out of shell history and `ps`.
|
|
115
|
+
- Never scavenge host MCP OAuth tokens from disk (ADR 0053).
|
|
100
116
|
|
|
101
117
|
## Local execute
|
|
102
118
|
|
|
@@ -105,19 +121,34 @@ with `--params`) on this machine instead of in Kody's cloud sandbox. Local CPU
|
|
|
105
121
|
is free; every `kody:runtime` call (`kody.*`, `kody.mcp.*`, `workflows.create`)
|
|
106
122
|
is proxied to Kody's CapabilityProxy and metered like a cloud hop. Static
|
|
107
123
|
`kody:@scope/package/export` imports are **resolved into the local workerd
|
|
108
|
-
bundle** via `POST /v1/local-execute/package-graph` (same
|
|
109
|
-
never hosted MCP `execute`, and never a whole-module
|
|
110
|
-
`kody.execute` defer). There is no author-facing
|
|
124
|
+
bundle** via `POST /v1/local-execute/package-graph` (same Bearer as
|
|
125
|
+
CapabilityProxy — never hosted MCP `execute`, and never a whole-module
|
|
126
|
+
CapabilityProxy → `kody.execute` defer). There is no author-facing
|
|
127
|
+
`packages.invoke`.
|
|
111
128
|
|
|
112
129
|
```bash
|
|
113
|
-
|
|
130
|
+
# Prefer login when already signed in (no temporary API token to paste):
|
|
131
|
+
npx @kodycodes/cli login
|
|
132
|
+
npx @kodycodes/cli execute --local --file ./task.js --params '{"to":"me@example.com"}'
|
|
133
|
+
|
|
134
|
+
# Agents on MCP: bootstrap code → store (no tokenCreate, no second OAuth):
|
|
135
|
+
npx @kodycodes/cli auth bootstrap --code 'kody_bc_…'
|
|
136
|
+
npx @kodycodes/cli execute --local --file ./task.js --params '{"to":"me@example.com"}'
|
|
137
|
+
|
|
138
|
+
# Or a scoped API token (still wins over login / bootstrap store when set):
|
|
139
|
+
export KODY_API_TOKEN=… # minted through the Kody `api` tool
|
|
114
140
|
npx @kodycodes/cli execute --local --file ./task.js --params '{"to":"me@example.com"}'
|
|
115
141
|
```
|
|
116
142
|
|
|
117
|
-
- **Auth:**
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
143
|
+
- **Auth:** `--token` / `KODY_API_TOKEN` when set; else stored `auth bootstrap`
|
|
144
|
+
API token; else a valid `kody login` session (OAuth access token as Bearer —
|
|
145
|
+
never printed, never exchanged via `tokenCreate`). Prefer the env var for
|
|
146
|
+
API tokens so they stay out of shell history and `ps`. The bearer stays in
|
|
147
|
+
the CLI process; the sandbox only talks to a loopback bridge. Host MCP OAuth
|
|
148
|
+
from other clients is never read. Until
|
|
149
|
+
[kentcdodds/kody#2812](https://github.com/kentcdodds/kody/issues/2812)
|
|
150
|
+
ships, login-only Bearer is rejected by the Open API — use bootstrap or a
|
|
151
|
+
`kody_at_…` token in that case.
|
|
121
152
|
- **Node.js:** 22 or newer (`package.json` `engines` is `>=22`). Older Node
|
|
122
153
|
fails immediately with that requirement, before workerd is downloaded or
|
|
123
154
|
started.
|
|
@@ -167,8 +198,9 @@ If the keychain is unavailable (common on headless Linux), the CLI writes a
|
|
|
167
198
|
|
|
168
199
|
Access tokens refresh automatically on expiry or HTTP 401.
|
|
169
200
|
|
|
170
|
-
`execute --local`
|
|
171
|
-
credentials
|
|
201
|
+
`execute --local` prefers `--token` / `KODY_API_TOKEN` when set; otherwise it
|
|
202
|
+
uses stored CLI credentials from `kody login`. Token-only cloud execute still
|
|
203
|
+
only reads `--token` / `KODY_API_TOKEN`.
|
|
172
204
|
|
|
173
205
|
## Releases
|
|
174
206
|
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { type SecretBackend, type StoreResolution } from './store.js';
|
|
2
|
+
/**
|
|
3
|
+
* Scoped API token stored by `auth bootstrap` (ADR 0056) for
|
|
4
|
+
* `execute --local`. Separate from `kody login` OAuth credentials.
|
|
5
|
+
*/
|
|
6
|
+
export type StoredApiToken = {
|
|
7
|
+
version: 1;
|
|
8
|
+
apiUrl: string;
|
|
9
|
+
token: string;
|
|
10
|
+
tokenId: string;
|
|
11
|
+
name?: string;
|
|
12
|
+
scopes?: Array<string>;
|
|
13
|
+
expiresAt?: string | null;
|
|
14
|
+
maxExpiresAt?: string | null;
|
|
15
|
+
createdVia?: string;
|
|
16
|
+
};
|
|
17
|
+
export declare function accountForApiUrl(apiUrl: string): string;
|
|
18
|
+
export declare function apiTokenFileStorePath(apiUrl: string, home?: string): string;
|
|
19
|
+
export declare function createApiTokenKeyringBackend(apiUrl: string): SecretBackend;
|
|
20
|
+
export declare function parseStoredApiToken(raw: string): StoredApiToken;
|
|
21
|
+
export declare function loadStoredApiToken(apiUrl?: string, backend?: SecretBackend, resolution?: StoreResolution): StoredApiToken | null;
|
|
22
|
+
export declare function saveStoredApiToken(credentials: StoredApiToken, backend?: SecretBackend, resolution?: StoreResolution): {
|
|
23
|
+
backend: SecretBackend;
|
|
24
|
+
};
|
|
25
|
+
export declare function deleteStoredApiToken(apiUrl?: string, backend?: SecretBackend, resolution?: StoreResolution): {
|
|
26
|
+
deleted: boolean;
|
|
27
|
+
backend: SecretBackend;
|
|
28
|
+
};
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { Entry } from '@napi-rs/keyring';
|
|
2
|
+
import { homedir } from 'node:os';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { defaultApiUrl, keyringService } from './defaults.js';
|
|
5
|
+
import { createFileBackend, secretServiceKeyringOptions, } from './store.js';
|
|
6
|
+
export function accountForApiUrl(apiUrl) {
|
|
7
|
+
return `cli-api-token:${new URL(apiUrl).origin}`;
|
|
8
|
+
}
|
|
9
|
+
export function apiTokenFileStorePath(apiUrl, home = homedir()) {
|
|
10
|
+
const origin = new URL(apiUrl).host.replace(/[^a-zA-Z0-9.-]/g, '_');
|
|
11
|
+
const base = process.platform === 'win32'
|
|
12
|
+
? join(process.env.APPDATA || join(home, 'AppData', 'Roaming'), 'kody')
|
|
13
|
+
: process.platform === 'darwin'
|
|
14
|
+
? join(home, 'Library', 'Application Support', 'kody')
|
|
15
|
+
: join(process.env.XDG_CONFIG_HOME || join(home, '.config'), 'kody');
|
|
16
|
+
return join(base, `api-token-${origin}.json`);
|
|
17
|
+
}
|
|
18
|
+
export function createApiTokenKeyringBackend(apiUrl) {
|
|
19
|
+
const entry = new Entry(keyringService, accountForApiUrl(apiUrl), secretServiceKeyringOptions);
|
|
20
|
+
return {
|
|
21
|
+
kind: 'keyring',
|
|
22
|
+
get() {
|
|
23
|
+
try {
|
|
24
|
+
return entry.getPassword();
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
set(value) {
|
|
31
|
+
entry.setPassword(value);
|
|
32
|
+
},
|
|
33
|
+
delete() {
|
|
34
|
+
try {
|
|
35
|
+
return entry.deleteCredential();
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
function fileBackendFor(apiUrl, resolution) {
|
|
44
|
+
const path = resolution?.fileStorePath?.(apiUrl) ?? apiTokenFileStorePath(apiUrl);
|
|
45
|
+
return createFileBackend(path);
|
|
46
|
+
}
|
|
47
|
+
function resolveApiTokenBackend(apiUrl, preferred, resolution) {
|
|
48
|
+
if (preferred)
|
|
49
|
+
return preferred;
|
|
50
|
+
try {
|
|
51
|
+
return (resolution?.createKeyring ?? createApiTokenKeyringBackend)(apiUrl);
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
return fileBackendFor(apiUrl, resolution);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
export function parseStoredApiToken(raw) {
|
|
58
|
+
const parsed = JSON.parse(raw);
|
|
59
|
+
if (parsed.version !== 1 ||
|
|
60
|
+
typeof parsed.token !== 'string' ||
|
|
61
|
+
!parsed.token.startsWith('kody_at_') ||
|
|
62
|
+
typeof parsed.apiUrl !== 'string' ||
|
|
63
|
+
typeof parsed.tokenId !== 'string') {
|
|
64
|
+
throw new Error('Stored Kody API token is invalid. Run `kody auth bootstrap --code …` again.');
|
|
65
|
+
}
|
|
66
|
+
return parsed;
|
|
67
|
+
}
|
|
68
|
+
function readParsed(store) {
|
|
69
|
+
const raw = store.get();
|
|
70
|
+
if (!raw)
|
|
71
|
+
return null;
|
|
72
|
+
return parseStoredApiToken(raw);
|
|
73
|
+
}
|
|
74
|
+
export function loadStoredApiToken(apiUrl = defaultApiUrl, backend, resolution) {
|
|
75
|
+
const store = resolveApiTokenBackend(apiUrl, backend, resolution);
|
|
76
|
+
try {
|
|
77
|
+
const loaded = readParsed(store);
|
|
78
|
+
if (loaded)
|
|
79
|
+
return loaded;
|
|
80
|
+
}
|
|
81
|
+
catch (error) {
|
|
82
|
+
if (!(store.kind === 'keyring' && !backend))
|
|
83
|
+
throw error;
|
|
84
|
+
}
|
|
85
|
+
if (store.kind === 'keyring' && !backend) {
|
|
86
|
+
return readParsed(fileBackendFor(apiUrl, resolution));
|
|
87
|
+
}
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
export function saveStoredApiToken(credentials, backend, resolution) {
|
|
91
|
+
const store = resolveApiTokenBackend(credentials.apiUrl, backend, resolution);
|
|
92
|
+
try {
|
|
93
|
+
store.set(JSON.stringify(credentials));
|
|
94
|
+
return { backend: store };
|
|
95
|
+
}
|
|
96
|
+
catch (error) {
|
|
97
|
+
if (store.kind === 'keyring' && !backend) {
|
|
98
|
+
const fallback = fileBackendFor(credentials.apiUrl, resolution);
|
|
99
|
+
fallback.set(JSON.stringify(credentials));
|
|
100
|
+
return { backend: fallback };
|
|
101
|
+
}
|
|
102
|
+
throw error;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
export function deleteStoredApiToken(apiUrl = defaultApiUrl, backend, resolution) {
|
|
106
|
+
const store = resolveApiTokenBackend(apiUrl, backend, resolution);
|
|
107
|
+
let deleted = false;
|
|
108
|
+
try {
|
|
109
|
+
deleted = store.delete();
|
|
110
|
+
}
|
|
111
|
+
catch {
|
|
112
|
+
deleted = false;
|
|
113
|
+
}
|
|
114
|
+
if (store.kind === 'keyring' && !backend) {
|
|
115
|
+
const file = fileBackendFor(apiUrl, resolution);
|
|
116
|
+
deleted = file.delete() || deleted;
|
|
117
|
+
}
|
|
118
|
+
return { deleted, backend: store };
|
|
119
|
+
}
|
|
120
|
+
//# sourceMappingURL=api-token-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api-token-store.js","sourceRoot":"","sources":["../src/api-token-store.ts"],"names":[],"mappings":"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"}
|
package/dist/api-token.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
/** Platform tracking for login OAuth as CapabilityProxy / package-graph Bearer. */
|
|
2
|
+
export declare const localExecuteOauthPlatformIssueUrl = "https://github.com/kentcdodds/kody/issues/2812";
|
|
1
3
|
/**
|
|
2
4
|
* Scoped Open API / CapabilityProxy token (`kody_at_…`). Same source for
|
|
3
5
|
* `execute --local`, token-only cloud execute, and Open API search/whoami.
|
|
@@ -5,15 +7,26 @@
|
|
|
5
7
|
export declare function readApiToken(values?: {
|
|
6
8
|
token?: string;
|
|
7
9
|
}, env?: NodeJS.ProcessEnv): string | null;
|
|
10
|
+
/** True when the bearer looks like a minted Open API token (not MCP OAuth). */
|
|
11
|
+
export declare function isScopedApiToken(token: string): boolean;
|
|
8
12
|
/**
|
|
9
|
-
* How to mint
|
|
10
|
-
*
|
|
13
|
+
* How to mint a scoped token for CI/headless. Interactive agents on MCP should
|
|
14
|
+
* prefer `cliCredentialBootstrap` → `auth bootstrap` instead.
|
|
11
15
|
*/
|
|
12
16
|
export declare function apiTokenMintInstructions(): string;
|
|
13
|
-
/**
|
|
17
|
+
/** Preferred interactive path for agents already on Kody MCP (ADR 0056). */
|
|
18
|
+
export declare function cliBootstrapInstructions(): string;
|
|
19
|
+
/** Token-only Open API paths (search / whoami / cloud token execute) with no token. */
|
|
14
20
|
export declare function missingApiTokenMessage(purpose: string): string;
|
|
21
|
+
/**
|
|
22
|
+
* `execute --local` with neither env/`--token`, stored bootstrap token, nor
|
|
23
|
+
* `kody login`. Prefer bootstrap (MCP) → login → tokenCreate (CI).
|
|
24
|
+
*/
|
|
25
|
+
export declare function missingLocalExecuteAuthMessage(purpose?: string): string;
|
|
15
26
|
/** Cloud search / whoami / execute when the process has neither a session nor a token. */
|
|
16
27
|
export declare function missingCliAuthMessage(): string;
|
|
28
|
+
/** 401 when CapabilityProxy rejected a non-`kody_at_` bearer (typically CLI OAuth). */
|
|
29
|
+
export declare function rejectedOauthBearerMessage(): string;
|
|
17
30
|
export declare function insufficientScopeMessage(input: {
|
|
18
31
|
requiredScope?: string | null;
|
|
19
32
|
/** CapabilityProxy execute needs `local-execute` plus the capability scopes. */
|
package/dist/api-token.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { apiTokenEnvVar } from './defaults.js';
|
|
2
|
+
/** Platform tracking for login OAuth as CapabilityProxy / package-graph Bearer. */
|
|
3
|
+
export const localExecuteOauthPlatformIssueUrl = 'https://github.com/kentcdodds/kody/issues/2812';
|
|
2
4
|
/**
|
|
3
5
|
* Scoped Open API / CapabilityProxy token (`kody_at_…`). Same source for
|
|
4
6
|
* `execute --local`, token-only cloud execute, and Open API search/whoami.
|
|
@@ -7,20 +9,39 @@ export function readApiToken(values = {}, env = process.env) {
|
|
|
7
9
|
const token = (values.token ?? env[apiTokenEnvVar] ?? '').trim();
|
|
8
10
|
return token.length > 0 ? token : null;
|
|
9
11
|
}
|
|
12
|
+
/** True when the bearer looks like a minted Open API token (not MCP OAuth). */
|
|
13
|
+
export function isScopedApiToken(token) {
|
|
14
|
+
return token.startsWith('kody_at_');
|
|
15
|
+
}
|
|
10
16
|
/**
|
|
11
|
-
* How to mint
|
|
12
|
-
*
|
|
17
|
+
* How to mint a scoped token for CI/headless. Interactive agents on MCP should
|
|
18
|
+
* prefer `cliCredentialBootstrap` → `auth bootstrap` instead.
|
|
13
19
|
*/
|
|
14
20
|
export function apiTokenMintInstructions() {
|
|
15
21
|
return `Mint one with the Kody MCP \`api\` tool \`tokenCreate\` (include the \`local-execute\` scope plus the capability scopes this command needs) and pass --token or set ${apiTokenEnvVar}.`;
|
|
16
22
|
}
|
|
17
|
-
/**
|
|
23
|
+
/** Preferred interactive path for agents already on Kody MCP (ADR 0056). */
|
|
24
|
+
export function cliBootstrapInstructions() {
|
|
25
|
+
return `From MCP, call \`cliCredentialBootstrap\` (MCP \`api\` / \`kody.cliCredentialBootstrap\`), then run \`npx @kodycodes/cli auth bootstrap --code <kody_bc_…>\``;
|
|
26
|
+
}
|
|
27
|
+
/** Token-only Open API paths (search / whoami / cloud token execute) with no token. */
|
|
18
28
|
export function missingApiTokenMessage(purpose) {
|
|
19
|
-
return `${purpose} needs a scoped Kody API token. ${
|
|
29
|
+
return `${purpose} needs a scoped Kody API token. ${cliBootstrapInstructions()}, or ${apiTokenMintInstructions()}`;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* `execute --local` with neither env/`--token`, stored bootstrap token, nor
|
|
33
|
+
* `kody login`. Prefer bootstrap (MCP) → login → tokenCreate (CI).
|
|
34
|
+
*/
|
|
35
|
+
export function missingLocalExecuteAuthMessage(purpose = 'execute --local') {
|
|
36
|
+
return `${purpose} needs auth. ${cliBootstrapInstructions()}; or run \`kody login\`; or for CI/headless, ${apiTokenMintInstructions()}`;
|
|
20
37
|
}
|
|
21
38
|
/** Cloud search / whoami / execute when the process has neither a session nor a token. */
|
|
22
39
|
export function missingCliAuthMessage() {
|
|
23
|
-
return `Not logged in, and no API token is set. ${
|
|
40
|
+
return `Not logged in, and no API token is set. ${cliBootstrapInstructions()}; or run \`kody login\` for browser OAuth (search, whoami, cloud execute, and login-backed \`execute --local\`); or for CI/headless, ${apiTokenMintInstructions()}`;
|
|
41
|
+
}
|
|
42
|
+
/** 401 when CapabilityProxy rejected a non-`kody_at_` bearer (typically CLI OAuth). */
|
|
43
|
+
export function rejectedOauthBearerMessage() {
|
|
44
|
+
return `Kody rejected the bearer from \`kody login\`: the Open API still accepts only scoped \`kody_at_…\` API tokens on CapabilityProxy / package-graph (not MCP OAuth). See ${localExecuteOauthPlatformIssueUrl}. Until that lands, ${apiTokenMintInstructions()}`;
|
|
24
45
|
}
|
|
25
46
|
export function insufficientScopeMessage(input) {
|
|
26
47
|
const required = input.requiredScope ? ` The server requires "${input.requiredScope}".` : '';
|
package/dist/api-token.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api-token.js","sourceRoot":"","sources":["../src/api-token.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAA;AAE9C;;;GAGG;AACH,MAAM,UAAU,YAAY,CAC3B,SAA6B,EAAE,EAC/B,MAAyB,OAAO,CAAC,GAAG;IAEpC,MAAM,KAAK,GAAG,CAAC,MAAM,CAAC,KAAK,IAAI,GAAG,CAAC,cAAc,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAA;IAChE,OAAO,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAA;AACvC,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,wBAAwB;IACvC,OAAO,uKAAuK,cAAc,GAAG,CAAA;AAChM,CAAC;AAED,
|
|
1
|
+
{"version":3,"file":"api-token.js","sourceRoot":"","sources":["../src/api-token.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAA;AAE9C,mFAAmF;AACnF,MAAM,CAAC,MAAM,iCAAiC,GAC7C,gDAAgD,CAAA;AAEjD;;;GAGG;AACH,MAAM,UAAU,YAAY,CAC3B,SAA6B,EAAE,EAC/B,MAAyB,OAAO,CAAC,GAAG;IAEpC,MAAM,KAAK,GAAG,CAAC,MAAM,CAAC,KAAK,IAAI,GAAG,CAAC,cAAc,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAA;IAChE,OAAO,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAA;AACvC,CAAC;AAED,+EAA+E;AAC/E,MAAM,UAAU,gBAAgB,CAAC,KAAa;IAC7C,OAAO,KAAK,CAAC,UAAU,CAAC,UAAU,CAAC,CAAA;AACpC,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,wBAAwB;IACvC,OAAO,uKAAuK,cAAc,GAAG,CAAA;AAChM,CAAC;AAED,4EAA4E;AAC5E,MAAM,UAAU,wBAAwB;IACvC,OAAO,8JAA8J,CAAA;AACtK,CAAC;AAED,uFAAuF;AACvF,MAAM,UAAU,sBAAsB,CAAC,OAAe;IACrD,OAAO,GAAG,OAAO,mCAAmC,wBAAwB,EAAE,QAAQ,wBAAwB,EAAE,EAAE,CAAA;AACnH,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,8BAA8B,CAC7C,UAAkB,iBAAiB;IAEnC,OAAO,GAAG,OAAO,gBAAgB,wBAAwB,EAAE,gDAAgD,wBAAwB,EAAE,EAAE,CAAA;AACxI,CAAC;AAED,0FAA0F;AAC1F,MAAM,UAAU,qBAAqB;IACpC,OAAO,2CAA2C,wBAAwB,EAAE,wIAAwI,wBAAwB,EAAE,EAAE,CAAA;AACjP,CAAC;AAED,uFAAuF;AACvF,MAAM,UAAU,0BAA0B;IACzC,OAAO,yKAAyK,iCAAiC,uBAAuB,wBAAwB,EAAE,EAAE,CAAA;AACrQ,CAAC;AAED,MAAM,UAAU,wBAAwB,CAAC,KAIxC;IACA,MAAM,QAAQ,GAAG,KAAK,CAAC,aAAa,CAAC,CAAC,CAAC,yBAAyB,KAAK,CAAC,aAAa,IAAI,CAAC,CAAC,CAAC,EAAE,CAAA;IAC5F,MAAM,IAAI,GAAG,KAAK,CAAC,mBAAmB;QACrC,CAAC,CAAC,wBAAwB,EAAE;QAC5B,CAAC,CAAC,8DACA,KAAK,CAAC,aAAa;YAClB,CAAC,CAAC,mBAAmB,KAAK,CAAC,aAAa,GAAG;YAC3C,CAAC,CAAC,mCACJ,8BAA8B,cAAc,GAAG,CAAA;IACjD,OAAO,oCAAoC,QAAQ,IAAI,IAAI,EAAE,CAAA;AAC9D,CAAC;AAED,MAAM,UAAU,sBAAsB;IACrC,OAAO,gSAAgS,CAAA;AACxS,CAAC;AAED,MAAM,UAAU,eAAe,CAC9B,SAA6B,EAAE,EAC/B,MAAyB,OAAO,CAAC,GAAG,EACpC,UAAkB,cAAc;IAEhC,MAAM,KAAK,GAAG,YAAY,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IACvC,IAAI,CAAC,KAAK;QAAE,MAAM,IAAI,KAAK,CAAC,sBAAsB,CAAC,OAAO,CAAC,CAAC,CAAA;IAC5D,OAAO,KAAK,CAAA;AACb,CAAC;AAED,kEAAkE;AAClE,MAAM,UAAU,oBAAoB,CAAC,MAA0B;IAC9D,OAAO,OAAO,MAAM,CAAC,KAAK,KAAK,QAAQ,IAAI,MAAM,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,CAAA;AAC1E,CAAC"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { type StoredApiToken } from './api-token-store.js';
|
|
2
|
+
import type { SecretBackend, StoreResolution } from './store.js';
|
|
3
|
+
/** ADR 0056 one-shot bootstrap code prefix (`kody_bc_…`). */
|
|
4
|
+
export declare const cliBootstrapCodePrefix = "kody_bc_";
|
|
5
|
+
export declare const bootstrapRedeemPath = "v1/tokens/bootstrap/redeem";
|
|
6
|
+
export type BootstrapRedeemResponse = {
|
|
7
|
+
token: string;
|
|
8
|
+
token_type?: string;
|
|
9
|
+
id: string;
|
|
10
|
+
name?: string | null;
|
|
11
|
+
scopes?: Array<string>;
|
|
12
|
+
status?: string;
|
|
13
|
+
idle_ttl_seconds?: number;
|
|
14
|
+
expires_at?: string | null;
|
|
15
|
+
max_expires_at?: string | null;
|
|
16
|
+
created_via?: string;
|
|
17
|
+
};
|
|
18
|
+
export declare function parseCliBootstrapCode(value: string): {
|
|
19
|
+
codeId: string;
|
|
20
|
+
secret: string;
|
|
21
|
+
} | null;
|
|
22
|
+
export declare function assertCliBootstrapCode(code: string): string;
|
|
23
|
+
/**
|
|
24
|
+
* POST /v1/tokens/bootstrap/redeem with JSON `{ code }` and **no** Authorization
|
|
25
|
+
* header (ADR 0056). Returns the minted `kody_at_…` once.
|
|
26
|
+
*/
|
|
27
|
+
export declare function redeemBootstrapCode(input: {
|
|
28
|
+
code: string;
|
|
29
|
+
apiUrl?: string;
|
|
30
|
+
fetchFn?: typeof fetch;
|
|
31
|
+
}): Promise<BootstrapRedeemResponse>;
|
|
32
|
+
export declare function storedApiTokenFromRedeem(input: {
|
|
33
|
+
apiUrl: string;
|
|
34
|
+
redeemed: BootstrapRedeemResponse;
|
|
35
|
+
}): StoredApiToken;
|
|
36
|
+
/**
|
|
37
|
+
* Redeem a one-shot `kody_bc_…` and persist the resulting API token for
|
|
38
|
+
* `execute --local`. Never returns the token string to callers that print
|
|
39
|
+
* success output — use `stored.tokenId` / backend kind only.
|
|
40
|
+
*/
|
|
41
|
+
export declare function authBootstrap(input: {
|
|
42
|
+
code: string;
|
|
43
|
+
apiUrl?: string;
|
|
44
|
+
fetchFn?: typeof fetch;
|
|
45
|
+
backend?: SecretBackend;
|
|
46
|
+
resolution?: StoreResolution;
|
|
47
|
+
}): Promise<{
|
|
48
|
+
stored: StoredApiToken;
|
|
49
|
+
backendKind: SecretBackend['kind'];
|
|
50
|
+
backendPath?: string;
|
|
51
|
+
}>;
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { assertTokenSafeApiUrl, capabilityProxyUrl } from './capability-proxy.js';
|
|
2
|
+
import { cliName, defaultApiUrl } from './defaults.js';
|
|
3
|
+
import { describeNetworkError } from './network-error.js';
|
|
4
|
+
import { readPackageVersion } from './package-info.js';
|
|
5
|
+
import { saveStoredApiToken, } from './api-token-store.js';
|
|
6
|
+
/** ADR 0056 one-shot bootstrap code prefix (`kody_bc_…`). */
|
|
7
|
+
export const cliBootstrapCodePrefix = 'kody_bc_';
|
|
8
|
+
/** Platform shape: `kody_bc_<16 alnum>_<32 base64url>`. */
|
|
9
|
+
const bootstrapCodePattern = /^kody_bc_([a-z0-9]{16})_([A-Za-z0-9_-]{32})$/;
|
|
10
|
+
export const bootstrapRedeemPath = 'v1/tokens/bootstrap/redeem';
|
|
11
|
+
export function parseCliBootstrapCode(value) {
|
|
12
|
+
const match = bootstrapCodePattern.exec(value.trim());
|
|
13
|
+
if (!match)
|
|
14
|
+
return null;
|
|
15
|
+
const [, codeId, secret] = match;
|
|
16
|
+
if (!codeId || !secret)
|
|
17
|
+
return null;
|
|
18
|
+
return { codeId, secret };
|
|
19
|
+
}
|
|
20
|
+
export function assertCliBootstrapCode(code) {
|
|
21
|
+
const trimmed = code.trim();
|
|
22
|
+
if (!parseCliBootstrapCode(trimmed)) {
|
|
23
|
+
throw new Error(`Invalid bootstrap code. Expected a one-shot ${cliBootstrapCodePrefix}… from cliCredentialBootstrap (MCP api / kody.cliCredentialBootstrap).`);
|
|
24
|
+
}
|
|
25
|
+
return trimmed;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* POST /v1/tokens/bootstrap/redeem with JSON `{ code }` and **no** Authorization
|
|
29
|
+
* header (ADR 0056). Returns the minted `kody_at_…` once.
|
|
30
|
+
*/
|
|
31
|
+
export async function redeemBootstrapCode(input) {
|
|
32
|
+
const code = assertCliBootstrapCode(input.code);
|
|
33
|
+
const apiUrl = input.apiUrl || defaultApiUrl;
|
|
34
|
+
assertTokenSafeApiUrl(apiUrl);
|
|
35
|
+
const url = capabilityProxyUrl(apiUrl, bootstrapRedeemPath);
|
|
36
|
+
const fetchFn = input.fetchFn ?? fetch;
|
|
37
|
+
let response;
|
|
38
|
+
try {
|
|
39
|
+
response = await fetchFn(url, {
|
|
40
|
+
method: 'POST',
|
|
41
|
+
headers: {
|
|
42
|
+
accept: 'application/json',
|
|
43
|
+
'content-type': 'application/json',
|
|
44
|
+
'user-agent': `${cliName}/${readPackageVersion()}`,
|
|
45
|
+
},
|
|
46
|
+
body: JSON.stringify({ code }),
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
catch (error) {
|
|
50
|
+
const reason = describeNetworkError(error);
|
|
51
|
+
throw new Error(`Could not reach the Kody API at ${url.origin} (${reason}). Check --api-url / KODY_API_URL and your network.`);
|
|
52
|
+
}
|
|
53
|
+
const body = await readJson(response);
|
|
54
|
+
if (!response.ok) {
|
|
55
|
+
throw describeRedeemFailure(response.status, body, url);
|
|
56
|
+
}
|
|
57
|
+
return parseRedeemResponse(body);
|
|
58
|
+
}
|
|
59
|
+
export function storedApiTokenFromRedeem(input) {
|
|
60
|
+
const token = input.redeemed.token.trim();
|
|
61
|
+
if (!token.startsWith('kody_at_')) {
|
|
62
|
+
throw new Error('Bootstrap redeem did not return a scoped kody_at_… API token.');
|
|
63
|
+
}
|
|
64
|
+
return {
|
|
65
|
+
version: 1,
|
|
66
|
+
apiUrl: input.apiUrl,
|
|
67
|
+
token,
|
|
68
|
+
tokenId: input.redeemed.id,
|
|
69
|
+
...(input.redeemed.name ? { name: input.redeemed.name } : {}),
|
|
70
|
+
...(Array.isArray(input.redeemed.scopes) ? { scopes: input.redeemed.scopes } : {}),
|
|
71
|
+
expiresAt: input.redeemed.expires_at ?? null,
|
|
72
|
+
maxExpiresAt: input.redeemed.max_expires_at ?? null,
|
|
73
|
+
createdVia: input.redeemed.created_via ?? 'cli-bootstrap',
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Redeem a one-shot `kody_bc_…` and persist the resulting API token for
|
|
78
|
+
* `execute --local`. Never returns the token string to callers that print
|
|
79
|
+
* success output — use `stored.tokenId` / backend kind only.
|
|
80
|
+
*/
|
|
81
|
+
export async function authBootstrap(input) {
|
|
82
|
+
const apiUrl = input.apiUrl || defaultApiUrl;
|
|
83
|
+
const redeemed = await redeemBootstrapCode({
|
|
84
|
+
code: input.code,
|
|
85
|
+
apiUrl,
|
|
86
|
+
fetchFn: input.fetchFn,
|
|
87
|
+
});
|
|
88
|
+
const stored = storedApiTokenFromRedeem({ apiUrl, redeemed });
|
|
89
|
+
const saved = saveStoredApiToken(stored, input.backend, input.resolution);
|
|
90
|
+
return {
|
|
91
|
+
stored,
|
|
92
|
+
backendKind: saved.backend.kind,
|
|
93
|
+
...(saved.backend.path ? { backendPath: saved.backend.path } : {}),
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
function parseRedeemResponse(body) {
|
|
97
|
+
if (!isRecord(body) || typeof body.token !== 'string' || typeof body.id !== 'string') {
|
|
98
|
+
throw new Error('Bootstrap redeem returned an unexpected response.');
|
|
99
|
+
}
|
|
100
|
+
return {
|
|
101
|
+
token: body.token,
|
|
102
|
+
token_type: typeof body.token_type === 'string' ? body.token_type : undefined,
|
|
103
|
+
id: body.id,
|
|
104
|
+
name: typeof body.name === 'string' ? body.name : null,
|
|
105
|
+
scopes: Array.isArray(body.scopes)
|
|
106
|
+
? body.scopes.filter((scope) => typeof scope === 'string')
|
|
107
|
+
: undefined,
|
|
108
|
+
status: typeof body.status === 'string' ? body.status : undefined,
|
|
109
|
+
idle_ttl_seconds: typeof body.idle_ttl_seconds === 'number' ? body.idle_ttl_seconds : undefined,
|
|
110
|
+
expires_at: typeof body.expires_at === 'string' ? body.expires_at : null,
|
|
111
|
+
max_expires_at: typeof body.max_expires_at === 'string' ? body.max_expires_at : null,
|
|
112
|
+
created_via: typeof body.created_via === 'string' ? body.created_via : undefined,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
function describeRedeemFailure(status, body, url) {
|
|
116
|
+
const failure = readErrorBody(body);
|
|
117
|
+
const detail = failure?.message ? ` Server said: ${failure.message}` : '';
|
|
118
|
+
if (status === 400 || status === 401 || status === 403 || status === 404) {
|
|
119
|
+
return new Error(`Bootstrap redeem failed (HTTP ${status}). The code may be invalid, expired, or already used. Call cliCredentialBootstrap again for a fresh code.${detail}`);
|
|
120
|
+
}
|
|
121
|
+
return new Error(failure?.message ??
|
|
122
|
+
`Bootstrap redeem failed with HTTP ${status} (${url.pathname}).${detail}`);
|
|
123
|
+
}
|
|
124
|
+
async function readJson(response) {
|
|
125
|
+
const text = await response.text();
|
|
126
|
+
if (!text)
|
|
127
|
+
return null;
|
|
128
|
+
try {
|
|
129
|
+
return JSON.parse(text);
|
|
130
|
+
}
|
|
131
|
+
catch {
|
|
132
|
+
return text;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
function readErrorBody(body) {
|
|
136
|
+
if (!isRecord(body) || body.error == null) {
|
|
137
|
+
if (typeof body === 'string' && body.trim())
|
|
138
|
+
return { code: null, message: body };
|
|
139
|
+
if (isRecord(body) && typeof body.message === 'string') {
|
|
140
|
+
return { code: null, message: body.message };
|
|
141
|
+
}
|
|
142
|
+
return null;
|
|
143
|
+
}
|
|
144
|
+
const { error } = body;
|
|
145
|
+
if (typeof error === 'string')
|
|
146
|
+
return { code: null, message: error };
|
|
147
|
+
if (isRecord(error)) {
|
|
148
|
+
const code = typeof error.code === 'string' ? error.code : null;
|
|
149
|
+
const message = typeof error.message === 'string' ? error.message : code ?? JSON.stringify(error);
|
|
150
|
+
return { code, message };
|
|
151
|
+
}
|
|
152
|
+
return { code: null, message: String(error) };
|
|
153
|
+
}
|
|
154
|
+
function isRecord(value) {
|
|
155
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
156
|
+
}
|
|
157
|
+
//# sourceMappingURL=auth-bootstrap.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth-bootstrap.js","sourceRoot":"","sources":["../src/auth-bootstrap.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* HTTP contract for Kody's CapabilityProxy (Open API `/v1`). Local execute
|
|
3
3
|
* runs user code in workerd and forwards every `kody:runtime` call here with
|
|
4
|
-
* a
|
|
4
|
+
* a Bearer from `--token` / `KODY_API_TOKEN` or (when unset) `kody login`
|
|
5
|
+
* OAuth access token.
|
|
5
6
|
*
|
|
6
7
|
* - `GET /v1/capability-proxy/session` validates the token before workerd
|
|
7
8
|
* starts. 200 → `{ scopes?: string[], expiresAt?: string }`.
|
|
@@ -11,8 +12,8 @@
|
|
|
11
12
|
* is the positional argument list. 200 → `{ result }`.
|
|
12
13
|
*
|
|
13
14
|
* Errors use `{ error: { code, message } }` (a bare string is accepted too).
|
|
14
|
-
* 401 means the token is expired/revoked
|
|
15
|
-
* `local-execute` flag is off
|
|
15
|
+
* 401 means the token is expired/revoked (or MCP OAuth until the platform
|
|
16
|
+
* accepts it); 403 `feature_disabled` means the `local-execute` flag is off.
|
|
16
17
|
*/
|
|
17
18
|
export declare const capabilityProxySessionPath = "v1/capability-proxy/session";
|
|
18
19
|
export declare const capabilityProxyCallPath = "v1/capability-proxy/call";
|