@kodycodes/cli 1.17.0 → 1.19.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 +51 -5
- package/dist/auth.d.ts +30 -4
- package/dist/auth.js +94 -13
- package/dist/auth.js.map +1 -1
- package/dist/capability-proxy.d.ts +7 -0
- package/dist/capability-proxy.js +8 -2
- package/dist/capability-proxy.js.map +1 -1
- package/dist/cli.d.ts +9 -1
- package/dist/cli.js +426 -100
- package/dist/cli.js.map +1 -1
- package/dist/dev-command.d.ts +93 -0
- package/dist/dev-command.js +369 -0
- package/dist/dev-command.js.map +1 -0
- package/dist/discover-org.d.ts +10 -0
- package/dist/discover-org.js +62 -0
- package/dist/discover-org.js.map +1 -0
- package/dist/help.d.ts +13 -1
- package/dist/help.js +112 -15
- package/dist/help.js.map +1 -1
- package/dist/local-execute-auth.d.ts +1 -0
- package/dist/local-execute-auth.js +7 -1
- package/dist/local-execute-auth.js.map +1 -1
- package/dist/local-execute.d.ts +2 -0
- package/dist/local-execute.js +6 -1
- package/dist/local-execute.js.map +1 -1
- package/dist/local-package-graph.js +2 -0
- package/dist/local-package-graph.js.map +1 -1
- package/dist/local-package-run.d.ts +28 -0
- package/dist/local-package-run.js +299 -0
- package/dist/local-package-run.js.map +1 -0
- package/dist/mcp.d.ts +4 -0
- package/dist/mcp.js +3 -0
- package/dist/mcp.js.map +1 -1
- package/dist/mock/mock-server.d.ts +42 -0
- package/dist/mock/mock-server.js +373 -0
- package/dist/mock/mock-server.js.map +1 -0
- package/dist/mock/mock-storage.d.ts +46 -0
- package/dist/mock/mock-storage.js +96 -0
- package/dist/mock/mock-storage.js.map +1 -0
- package/dist/mock/mock-supported.d.ts +18 -0
- package/dist/mock/mock-supported.js +43 -0
- package/dist/mock/mock-supported.js.map +1 -0
- package/dist/oauth-provider.d.ts +1 -2
- package/dist/oauth-provider.js +2 -8
- package/dist/oauth-provider.js.map +1 -1
- package/dist/open-api-client.d.ts +5 -0
- package/dist/open-api-client.js +4 -0
- package/dist/open-api-client.js.map +1 -1
- package/dist/org-resolve.d.ts +9 -0
- package/dist/org-resolve.js +16 -0
- package/dist/org-resolve.js.map +1 -0
- package/dist/org-slug.d.ts +4 -0
- package/dist/org-slug.js +11 -0
- package/dist/org-slug.js.map +1 -0
- package/dist/org.d.ts +22 -0
- package/dist/org.js +100 -0
- package/dist/org.js.map +1 -0
- package/dist/package-app-dev.d.ts +43 -0
- package/dist/package-app-dev.js +356 -0
- package/dist/package-app-dev.js.map +1 -0
- package/dist/package-local-runtime-source.d.ts +35 -0
- package/dist/package-local-runtime-source.js +317 -0
- package/dist/package-local-runtime-source.js.map +1 -0
- package/dist/package-manifest.d.ts +43 -0
- package/dist/package-manifest.js +264 -0
- package/dist/package-manifest.js.map +1 -0
- package/dist/remote-execute.d.ts +2 -0
- package/dist/remote-execute.js +6 -1
- package/dist/remote-execute.js.map +1 -1
- package/dist/store.d.ts +93 -3
- package/dist/store.js +454 -23
- package/dist/store.js.map +1 -1
- package/dist/structured-error.d.ts +28 -0
- package/dist/structured-error.js +57 -0
- package/dist/structured-error.js.map +1 -0
- package/dist/target.d.ts +25 -0
- package/dist/target.js +45 -0
- package/dist/target.js.map +1 -0
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -5,9 +5,10 @@ import { deleteStoredApiToken, loadStoredApiToken, } from './api-token-store.js'
|
|
|
5
5
|
import { authBootstrap, authBootstrapWithScopes, bootstrapMintLifetimeFields, cliCredentialBootstrapOperationId, parseCliLifetimeSecondsFlag, parseCliScopesFlag, resolveCliTokenLifetime, } from './auth-bootstrap.js';
|
|
6
6
|
import { parseCredentialFlags, } from './credential-call-map.js';
|
|
7
7
|
import { defaultApiUrl, defaultMcpUrl, modernMcpProtocolVersion } from './defaults.js';
|
|
8
|
-
import {
|
|
8
|
+
import { buildUsage } from './help.js';
|
|
9
|
+
import { orgSlugFromFlag, resolveOrgSlug } from './org-resolve.js';
|
|
9
10
|
import { refreshGitCredentialHelperPinIfNeeded } from './setup-git.js';
|
|
10
|
-
import { deleteCredentials, loadCredentials } from './store.js';
|
|
11
|
+
import { deleteCredentials, listStoredOrgs, loadCredentials } from './store.js';
|
|
11
12
|
import { formatToolResult } from './tool-result.js';
|
|
12
13
|
import { resolveLocalExecuteBearer } from './local-execute-auth.js';
|
|
13
14
|
import { assertLocalExecuteNodeEngine } from './node-engine.js';
|
|
@@ -16,6 +17,16 @@ import { redactError } from './redact.js';
|
|
|
16
17
|
export { readApiToken, requireApiToken as resolveApiToken, resolveScopedApiToken, } from './api-token.js';
|
|
17
18
|
export { resolveLocalExecuteBearer } from './local-execute-auth.js';
|
|
18
19
|
export { authBootstrap, authBootstrapWithScopes, bootstrapRedeemRequestBody, cliTokenLifetimeAliases, cliTokenLifetimeMissingError, cliTokenLifetimePolicy, parseCliLifetimeSecondsFlag, parseCliScopesFlag, redeemBootstrapCode, resolveCliTokenLifetime, } from './auth-bootstrap.js';
|
|
20
|
+
const orgScopedCommands = new Set([
|
|
21
|
+
'login',
|
|
22
|
+
'logout',
|
|
23
|
+
'whoami',
|
|
24
|
+
'search',
|
|
25
|
+
'api',
|
|
26
|
+
'execute',
|
|
27
|
+
'dev',
|
|
28
|
+
'auth',
|
|
29
|
+
]);
|
|
19
30
|
function mcpUrlFrom(values) {
|
|
20
31
|
return values.mcpUrl || process.env.KODY_MCP_URL || defaultMcpUrl;
|
|
21
32
|
}
|
|
@@ -52,6 +63,12 @@ function parseKnown(args) {
|
|
|
52
63
|
project: { type: 'boolean' },
|
|
53
64
|
'no-browser': { type: 'boolean' },
|
|
54
65
|
org: { type: 'string' },
|
|
66
|
+
target: { type: 'string' },
|
|
67
|
+
'reset-storage': { type: 'boolean' },
|
|
68
|
+
port: { type: 'string' },
|
|
69
|
+
'app-base-path': { type: 'string' },
|
|
70
|
+
method: { type: 'string' },
|
|
71
|
+
body: { type: 'string' },
|
|
55
72
|
clients: { type: 'string' },
|
|
56
73
|
yes: { type: 'boolean', short: 'y' },
|
|
57
74
|
},
|
|
@@ -71,6 +88,7 @@ export function resolveCommand(argv) {
|
|
|
71
88
|
switch (raw) {
|
|
72
89
|
case 'login':
|
|
73
90
|
case 'logout':
|
|
91
|
+
case 'org':
|
|
74
92
|
case 'status':
|
|
75
93
|
case 'auth':
|
|
76
94
|
case 'git-credential':
|
|
@@ -78,6 +96,7 @@ export function resolveCommand(argv) {
|
|
|
78
96
|
case 'search':
|
|
79
97
|
case 'api':
|
|
80
98
|
case 'execute':
|
|
99
|
+
case 'dev':
|
|
81
100
|
case 'install':
|
|
82
101
|
case 'skill':
|
|
83
102
|
case 'help':
|
|
@@ -112,8 +131,13 @@ export async function runCli(argv = process.argv.slice(2), io = {}) {
|
|
|
112
131
|
}
|
|
113
132
|
try {
|
|
114
133
|
const parsed = resolveCommand(argv);
|
|
134
|
+
const helpMcpUrl = mcpUrlFrom({
|
|
135
|
+
mcpUrl: typeof parsed.values['mcp-url'] === 'string'
|
|
136
|
+
? parsed.values['mcp-url']
|
|
137
|
+
: undefined,
|
|
138
|
+
});
|
|
115
139
|
if (parsed.values.help && parsed.command !== 'help') {
|
|
116
|
-
write(
|
|
140
|
+
write(buildUsage({ mcpUrl: helpMcpUrl }));
|
|
117
141
|
return 0;
|
|
118
142
|
}
|
|
119
143
|
return await dispatch(parsed, write, writeErr);
|
|
@@ -129,37 +153,47 @@ async function dispatch(parsed, write, writeErr) {
|
|
|
129
153
|
});
|
|
130
154
|
const json = parsed.values.json === true;
|
|
131
155
|
if (parsed.values['allow-private-network'] === true &&
|
|
156
|
+
parsed.command !== 'dev' &&
|
|
132
157
|
(parsed.command !== 'execute' || parsed.values.local !== true)) {
|
|
133
|
-
throw new Error('--allow-private-network can only be used with execute --local.');
|
|
158
|
+
throw new Error('--allow-private-network can only be used with execute --local or kody dev.');
|
|
134
159
|
}
|
|
135
|
-
if (parsed.values
|
|
136
|
-
|
|
160
|
+
if (parsed.values['reset-storage'] === true &&
|
|
161
|
+
parsed.command !== 'dev' &&
|
|
162
|
+
parsed.command !== 'execute' &&
|
|
163
|
+
parsed.command !== 'search') {
|
|
164
|
+
throw new Error('`--reset-storage` is only used with --target mock on execute, search, or dev.');
|
|
137
165
|
}
|
|
166
|
+
const orgFlag = typeof parsed.values.org === 'string' ? parsed.values.org : undefined;
|
|
167
|
+
if (orgFlag !== undefined && !orgScopedCommands.has(parsed.command)) {
|
|
168
|
+
throw new Error(`\`--org\` is not used with \`kody ${parsed.command}\`. Use it with login, logout, whoami, search, api, execute, or auth.`);
|
|
169
|
+
}
|
|
170
|
+
// Login/logout honor `--org` only — not KODY_ORG (that env is for one-shot
|
|
171
|
+
// org-scoped commands and must not bind browser login or change logout).
|
|
172
|
+
const orgFlagOnly = orgFlag !== undefined ? orgSlugFromFlag(orgFlag) : undefined;
|
|
173
|
+
const org = resolveOrgSlug({ flag: orgFlag });
|
|
138
174
|
switch (parsed.command) {
|
|
139
175
|
case 'help':
|
|
140
|
-
write(
|
|
176
|
+
write(buildUsage({ mcpUrl }));
|
|
141
177
|
return 0;
|
|
142
178
|
case 'version':
|
|
143
179
|
write(`${readPackageVersion()}\n`);
|
|
144
180
|
return 0;
|
|
145
181
|
case 'login': {
|
|
146
|
-
const
|
|
147
|
-
import('./auth.js'),
|
|
148
|
-
import('./oauth-provider.js'),
|
|
149
|
-
]);
|
|
150
|
-
const org = typeof parsed.values.org === 'string'
|
|
151
|
-
? orgSlugFromFlag(parsed.values.org)
|
|
152
|
-
: undefined;
|
|
182
|
+
const { login } = await import('./auth.js');
|
|
153
183
|
write('Opening the Kody login page in your browser…\n');
|
|
154
184
|
const result = await login({
|
|
155
185
|
mcpUrl,
|
|
156
|
-
...(
|
|
186
|
+
...(orgFlagOnly ? { org: orgFlagOnly } : {}),
|
|
157
187
|
openBrowser: parsed.values['no-browser'] !== true,
|
|
158
188
|
onAuthorizationUrl: (url) => {
|
|
159
189
|
write(`If the browser does not open, visit:\n${url.href}\n`);
|
|
160
190
|
},
|
|
161
191
|
});
|
|
162
|
-
const orgLabel = result.org
|
|
192
|
+
const orgLabel = result.org
|
|
193
|
+
? ` as org ${result.org}`
|
|
194
|
+
: result.credentials.org
|
|
195
|
+
? ` as org ${result.credentials.org}`
|
|
196
|
+
: '';
|
|
163
197
|
const accountLabel = result.account?.email
|
|
164
198
|
? ` (${result.account.email})`
|
|
165
199
|
: result.account?.displayName
|
|
@@ -180,38 +214,74 @@ async function dispatch(parsed, write, writeErr) {
|
|
|
180
214
|
? parsed.values['api-url']
|
|
181
215
|
: undefined,
|
|
182
216
|
});
|
|
183
|
-
const oauth = deleteCredentials(mcpUrl);
|
|
184
|
-
|
|
217
|
+
const oauth = deleteCredentials(mcpUrl, undefined, undefined, orgFlagOnly ? { org: orgFlagOnly } : undefined);
|
|
218
|
+
// Clear the bootstrap/API token only when removing the last org login
|
|
219
|
+
// or when not targeting a specific remaining multi-org set.
|
|
220
|
+
const clearApiToken = !orgFlagOnly || oauth.remaining === 0;
|
|
221
|
+
const apiToken = clearApiToken
|
|
222
|
+
? deleteStoredApiToken(apiUrl)
|
|
223
|
+
: { deleted: false };
|
|
185
224
|
if (!oauth.deleted && !apiToken.deleted) {
|
|
186
|
-
write(
|
|
225
|
+
write(orgFlagOnly
|
|
226
|
+
? `No stored login for org "${orgFlagOnly}".\n`
|
|
227
|
+
: 'No stored credentials.\n');
|
|
187
228
|
return 0;
|
|
188
229
|
}
|
|
189
230
|
const parts = [];
|
|
190
|
-
if (oauth.deleted)
|
|
191
|
-
parts.push(
|
|
231
|
+
if (oauth.deleted) {
|
|
232
|
+
parts.push(orgFlagOnly
|
|
233
|
+
? `Logged out of org ${orgFlagOnly}.`
|
|
234
|
+
: 'Logged out of the active org (kody login).');
|
|
235
|
+
}
|
|
192
236
|
if (apiToken.deleted)
|
|
193
237
|
parts.push('Cleared stored bootstrap/API token.');
|
|
194
238
|
write(`${parts.join(' ')}\n`);
|
|
195
239
|
return 0;
|
|
196
240
|
}
|
|
241
|
+
case 'org': {
|
|
242
|
+
const { chooseOrgInteractively, formatOrgListHuman, formatOrgListJson, useOrg, } = await import('./org.js');
|
|
243
|
+
const { listStoredOrgs } = await import('./store.js');
|
|
244
|
+
const action = parsed.positionals[0];
|
|
245
|
+
if (action === 'list') {
|
|
246
|
+
const orgs = listStoredOrgs(mcpUrl);
|
|
247
|
+
write(json ? formatOrgListJson(orgs) : formatOrgListHuman(orgs));
|
|
248
|
+
return orgs.length === 0 ? 1 : 0;
|
|
249
|
+
}
|
|
250
|
+
if (action === 'use') {
|
|
251
|
+
const slugArg = parsed.positionals[1];
|
|
252
|
+
if (slugArg) {
|
|
253
|
+
const next = useOrg(slugArg, mcpUrl);
|
|
254
|
+
write(`Active org is now ${next.activeOrg}.\n`);
|
|
255
|
+
return 0;
|
|
256
|
+
}
|
|
257
|
+
const orgs = listStoredOrgs(mcpUrl);
|
|
258
|
+
const chosen = await chooseOrgInteractively({ orgs });
|
|
259
|
+
const next = useOrg(chosen, mcpUrl);
|
|
260
|
+
write(`Active org is now ${next.activeOrg}.\n`);
|
|
261
|
+
return 0;
|
|
262
|
+
}
|
|
263
|
+
throw new Error('Usage: kody org list [--json] | kody org use [<slug>]');
|
|
264
|
+
}
|
|
197
265
|
case 'status': {
|
|
198
266
|
const apiUrl = apiUrlFrom({
|
|
199
267
|
apiUrl: typeof parsed.values['api-url'] === 'string'
|
|
200
268
|
? parsed.values['api-url']
|
|
201
269
|
: undefined,
|
|
202
270
|
});
|
|
271
|
+
// Status always reports the active (or pending) login — not KODY_ORG.
|
|
203
272
|
const credentials = loadCredentials(mcpUrl);
|
|
273
|
+
const orgs = listStoredOrgs(mcpUrl);
|
|
204
274
|
const storedApi = loadStoredApiToken(apiUrl);
|
|
205
|
-
if (!credentials && !storedApi) {
|
|
275
|
+
if (!credentials && orgs.length === 0 && !storedApi) {
|
|
206
276
|
write('Not logged in.\n');
|
|
207
277
|
return 1;
|
|
208
278
|
}
|
|
209
279
|
const lines = [];
|
|
210
|
-
if (credentials) {
|
|
211
|
-
const expires = credentials
|
|
280
|
+
if (credentials || orgs.length > 0) {
|
|
281
|
+
const expires = credentials?.expiresAt
|
|
212
282
|
? new Date(credentials.expiresAt).toISOString()
|
|
213
283
|
: 'unknown';
|
|
214
|
-
lines.push(`mcp: ${credentials
|
|
284
|
+
lines.push(`mcp: ${credentials?.mcpUrl ?? mcpUrl}`, `logged in: yes`, `active org: ${credentials?.org ?? orgs.find((row) => row.active)?.slug ?? '(pending)'}`, `orgs: ${orgs.map((row) => (row.active ? `${row.slug}*` : row.slug)).join(', ') || '(pending)'}`, `access token expires: ${expires}`, `refresh token: ${credentials?.refreshToken ? 'yes' : 'no'}`, `scope: ${credentials?.scope ?? 'unknown'}`);
|
|
215
285
|
}
|
|
216
286
|
else {
|
|
217
287
|
lines.push(`mcp: ${mcpUrl}`, `logged in: no`);
|
|
@@ -234,6 +304,7 @@ async function dispatch(parsed, write, writeErr) {
|
|
|
234
304
|
values: parsed.values,
|
|
235
305
|
mcpUrl,
|
|
236
306
|
write,
|
|
307
|
+
...(org ? { org } : {}),
|
|
237
308
|
});
|
|
238
309
|
}
|
|
239
310
|
if (action === 'remove-git') {
|
|
@@ -281,6 +352,7 @@ async function dispatch(parsed, write, writeErr) {
|
|
|
281
352
|
mcpUrl,
|
|
282
353
|
apiUrl,
|
|
283
354
|
tokenValues: tokenFlagValues(parsed.values),
|
|
355
|
+
...(org ? { org } : {}),
|
|
284
356
|
}),
|
|
285
357
|
})
|
|
286
358
|
: await authBootstrap({
|
|
@@ -337,11 +409,13 @@ async function dispatch(parsed, write, writeErr) {
|
|
|
337
409
|
mcpUrl,
|
|
338
410
|
apiUrl,
|
|
339
411
|
allowEnvWithoutLogin: true,
|
|
412
|
+
...(org ? { org } : {}),
|
|
340
413
|
})) {
|
|
341
414
|
const { whoamiWithApiToken } = await import('./open-api-client.js');
|
|
342
415
|
const identity = await whoamiWithApiToken({
|
|
343
416
|
token: requireApiToken(tokenValues, process.env, 'whoami', { apiUrl }),
|
|
344
417
|
apiUrl,
|
|
418
|
+
...(org ? { org } : {}),
|
|
345
419
|
});
|
|
346
420
|
if (json) {
|
|
347
421
|
write(`${JSON.stringify(identity, null, 2)}\n`);
|
|
@@ -365,71 +439,127 @@ async function dispatch(parsed, write, writeErr) {
|
|
|
365
439
|
import('./auth.js'),
|
|
366
440
|
import('./mcp.js'),
|
|
367
441
|
]);
|
|
368
|
-
const credentials = await ensureFreshCredentials({ mcpUrl });
|
|
369
|
-
const tools = await listKodyTools({ mcpUrl });
|
|
442
|
+
const credentials = await ensureFreshCredentials({ mcpUrl, org });
|
|
443
|
+
const tools = await listKodyTools({ mcpUrl, org });
|
|
370
444
|
if (json) {
|
|
371
445
|
write(`${JSON.stringify({
|
|
372
446
|
mcpUrl: credentials.mcpUrl,
|
|
447
|
+
org: credentials.org ?? null,
|
|
373
448
|
protocol: modernMcpProtocolVersion,
|
|
374
449
|
scope: credentials.scope ?? null,
|
|
375
450
|
tools: tools.map((tool) => tool.name),
|
|
376
451
|
}, null, 2)}\n`);
|
|
377
452
|
return 0;
|
|
378
453
|
}
|
|
379
|
-
|
|
454
|
+
const orgLine = credentials.org ? `\norg: ${credentials.org}` : '';
|
|
455
|
+
write(`Connected to ${credentials.mcpUrl} (${modernMcpProtocolVersion})${orgLine}\nTools: ${tools.map((tool) => tool.name).join(', ') || '(none)'}\n`);
|
|
380
456
|
return 0;
|
|
381
457
|
}
|
|
382
458
|
case 'search': {
|
|
459
|
+
const { resolveTarget } = await import('./target.js');
|
|
460
|
+
const target = resolveTarget({
|
|
461
|
+
command: 'search',
|
|
462
|
+
flag: typeof parsed.values.target === 'string' ? parsed.values.target : undefined,
|
|
463
|
+
});
|
|
383
464
|
const query = parsed.positionals.join(' ').trim();
|
|
384
465
|
const tokenValues = tokenFlagValues(parsed.values);
|
|
385
|
-
|
|
466
|
+
let apiUrl = apiUrlFrom({
|
|
386
467
|
apiUrl: typeof parsed.values['api-url'] === 'string'
|
|
387
468
|
? parsed.values['api-url']
|
|
388
469
|
: undefined,
|
|
389
470
|
});
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
471
|
+
let closeMock;
|
|
472
|
+
try {
|
|
473
|
+
if (target === 'mock') {
|
|
474
|
+
const { ensureMockTarget } = await import('./mock/mock-server.js');
|
|
475
|
+
const mock = await ensureMockTarget({
|
|
476
|
+
resetStorage: parsed.values['reset-storage'] === true,
|
|
477
|
+
});
|
|
478
|
+
apiUrl = mock.apiUrl;
|
|
479
|
+
closeMock = mock.started ? () => mock.server.close() : undefined;
|
|
480
|
+
const { searchWithApiToken } = await import('./open-api-client.js');
|
|
481
|
+
const result = await searchWithApiToken({
|
|
482
|
+
token: mock.token,
|
|
483
|
+
apiUrl,
|
|
484
|
+
query: query || undefined,
|
|
485
|
+
entity: typeof parsed.values.entity === 'string'
|
|
486
|
+
? parsed.values.entity
|
|
487
|
+
: undefined,
|
|
488
|
+
domain: typeof parsed.values.domain === 'string'
|
|
489
|
+
? parsed.values.domain
|
|
490
|
+
: undefined,
|
|
491
|
+
limit: typeof parsed.values.limit === 'string'
|
|
492
|
+
? Number(parsed.values.limit)
|
|
493
|
+
: undefined,
|
|
494
|
+
});
|
|
495
|
+
write(formatToolResult(result, json));
|
|
496
|
+
return result.isError ? 1 : 0;
|
|
497
|
+
}
|
|
498
|
+
if (shouldUseApiToken({
|
|
499
|
+
tokenValues,
|
|
500
|
+
mcpUrl,
|
|
399
501
|
apiUrl,
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
:
|
|
502
|
+
allowEnvWithoutLogin: true,
|
|
503
|
+
...(org ? { org } : {}),
|
|
504
|
+
})) {
|
|
505
|
+
const { searchWithApiToken } = await import('./open-api-client.js');
|
|
506
|
+
const result = await searchWithApiToken({
|
|
507
|
+
token: requireApiToken(tokenValues, process.env, 'search', { apiUrl }),
|
|
508
|
+
apiUrl,
|
|
509
|
+
...(org ? { org } : {}),
|
|
510
|
+
query: query || undefined,
|
|
511
|
+
entity: typeof parsed.values.entity === 'string'
|
|
512
|
+
? parsed.values.entity
|
|
513
|
+
: undefined,
|
|
514
|
+
domain: typeof parsed.values.domain === 'string'
|
|
515
|
+
? parsed.values.domain
|
|
516
|
+
: undefined,
|
|
517
|
+
limit: typeof parsed.values.limit === 'string'
|
|
518
|
+
? Number(parsed.values.limit)
|
|
519
|
+
: undefined,
|
|
520
|
+
});
|
|
521
|
+
write(formatToolResult(result, json));
|
|
522
|
+
return result.isError ? 1 : 0;
|
|
523
|
+
}
|
|
524
|
+
const args = {};
|
|
525
|
+
if (query)
|
|
526
|
+
args.query = query;
|
|
527
|
+
if (typeof parsed.values.entity === 'string')
|
|
528
|
+
args.entity = parsed.values.entity;
|
|
529
|
+
if (typeof parsed.values.domain === 'string')
|
|
530
|
+
args.domain = parsed.values.domain;
|
|
531
|
+
if (typeof parsed.values.limit === 'string')
|
|
532
|
+
args.limit = Number(parsed.values.limit);
|
|
533
|
+
const { callKodyTool } = await import('./mcp.js');
|
|
534
|
+
const result = await callKodyTool({
|
|
535
|
+
name: 'search',
|
|
536
|
+
args,
|
|
537
|
+
mcpUrl,
|
|
538
|
+
...(org ? { org } : {}),
|
|
406
539
|
});
|
|
407
540
|
write(formatToolResult(result, json));
|
|
408
541
|
return result.isError ? 1 : 0;
|
|
409
542
|
}
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
if (typeof parsed.values.entity === 'string')
|
|
414
|
-
args.entity = parsed.values.entity;
|
|
415
|
-
if (typeof parsed.values.domain === 'string')
|
|
416
|
-
args.domain = parsed.values.domain;
|
|
417
|
-
if (typeof parsed.values.limit === 'string')
|
|
418
|
-
args.limit = Number(parsed.values.limit);
|
|
419
|
-
const { callKodyTool } = await import('./mcp.js');
|
|
420
|
-
const result = await callKodyTool({ name: 'search', args, mcpUrl });
|
|
421
|
-
write(formatToolResult(result, json));
|
|
422
|
-
return result.isError ? 1 : 0;
|
|
543
|
+
finally {
|
|
544
|
+
await closeMock?.();
|
|
545
|
+
}
|
|
423
546
|
}
|
|
424
547
|
case 'execute': {
|
|
548
|
+
const { resolveTarget } = await import('./target.js');
|
|
549
|
+
const target = resolveTarget({
|
|
550
|
+
command: 'execute',
|
|
551
|
+
flag: typeof parsed.values.target === 'string' ? parsed.values.target : undefined,
|
|
552
|
+
});
|
|
425
553
|
const invoke = typeof parsed.values.invoke === 'string' ? parsed.values.invoke : undefined;
|
|
426
554
|
const hasCodeFlag = typeof parsed.values.code === 'string';
|
|
427
555
|
const hasFileFlag = typeof parsed.values.file === 'string';
|
|
428
556
|
const positionalModule = parsed.positionals.join('\n').trim();
|
|
429
|
-
|
|
557
|
+
// --target mock always runs locally against the CLI mock (no cloud sandbox).
|
|
558
|
+
const local = parsed.values.local === true || target === 'mock';
|
|
430
559
|
const tokenValues = tokenFlagValues(parsed.values);
|
|
431
|
-
|
|
432
|
-
|
|
560
|
+
const localPackageInvoke = invoke !== undefined ? await tryResolveLocalPackageInvoke(invoke) : null;
|
|
561
|
+
if (local && invoke !== undefined && !localPackageInvoke) {
|
|
562
|
+
throw new Error('--invoke runs a saved package export in Kody; --local runs a module you provide (--code, --file, or a module string). To test an unpublished package export, pass an export key from package.json#exports (e.g. `--invoke ./on-user-deleted`) from the package directory, or use --file.');
|
|
433
563
|
}
|
|
434
564
|
if (executeSourcesConflict({ invoke, hasCodeFlag, hasFileFlag, positionalModule })) {
|
|
435
565
|
throw new Error('--invoke cannot be combined with --code, --file, or a module string.');
|
|
@@ -438,23 +568,26 @@ async function dispatch(parsed, write, writeErr) {
|
|
|
438
568
|
assertLocalExecuteNodeEngine();
|
|
439
569
|
const fileArg = hasFileFlag ? parsed.values.file : undefined;
|
|
440
570
|
// `--code` wins over `--file`. Only attribute the path when the file is the source.
|
|
441
|
-
const modulePath =
|
|
442
|
-
|
|
571
|
+
const modulePath = localPackageInvoke?.modulePath ??
|
|
572
|
+
(!hasCodeFlag && fileArg !== undefined && fileArg !== '-' ? fileArg : undefined);
|
|
573
|
+
const code = invoke !== undefined && !localPackageInvoke
|
|
443
574
|
? undefined
|
|
444
575
|
: hasCodeFlag
|
|
445
576
|
? parsed.values.code
|
|
446
|
-
: modulePath !== undefined
|
|
577
|
+
: modulePath !== undefined && !localPackageInvoke
|
|
447
578
|
? await readFile(modulePath, 'utf8')
|
|
448
|
-
:
|
|
449
|
-
? await
|
|
450
|
-
:
|
|
579
|
+
: localPackageInvoke
|
|
580
|
+
? await readFile(localPackageInvoke.modulePath, 'utf8')
|
|
581
|
+
: fileArg === '-'
|
|
582
|
+
? await readStdin()
|
|
583
|
+
: positionalModule || undefined;
|
|
451
584
|
const credentials = parseCredentialFlags(Array.isArray(parsed.values.credential)
|
|
452
585
|
? parsed.values.credential.filter((value) => typeof value === 'string')
|
|
453
586
|
: typeof parsed.values.credential === 'string'
|
|
454
587
|
? [parsed.values.credential]
|
|
455
588
|
: undefined);
|
|
456
589
|
const args = buildExecuteToolArgs({
|
|
457
|
-
invoke,
|
|
590
|
+
invoke: localPackageInvoke ? undefined : invoke,
|
|
458
591
|
code: typeof code === 'string' ? code : undefined,
|
|
459
592
|
paramsJson: typeof parsed.values.params === 'string' ? parsed.values.params : undefined,
|
|
460
593
|
conversationId: typeof parsed.values['conversation-id'] === 'string'
|
|
@@ -462,48 +595,215 @@ async function dispatch(parsed, write, writeErr) {
|
|
|
462
595
|
: undefined,
|
|
463
596
|
credentials,
|
|
464
597
|
});
|
|
465
|
-
|
|
598
|
+
let apiUrl = apiUrlFrom({
|
|
466
599
|
apiUrl: typeof parsed.values['api-url'] === 'string'
|
|
467
600
|
? parsed.values['api-url']
|
|
468
601
|
: undefined,
|
|
469
602
|
});
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
603
|
+
let closeMock;
|
|
604
|
+
let mockToken;
|
|
605
|
+
try {
|
|
606
|
+
if (target === 'mock') {
|
|
607
|
+
const { ensureMockTarget } = await import('./mock/mock-server.js');
|
|
608
|
+
const mock = await ensureMockTarget({
|
|
609
|
+
resetStorage: parsed.values['reset-storage'] === true,
|
|
610
|
+
packageId: localPackageInvoke
|
|
611
|
+
? `local:${localPackageInvoke.kodyId}`
|
|
612
|
+
: undefined,
|
|
613
|
+
});
|
|
614
|
+
apiUrl = mock.apiUrl;
|
|
615
|
+
mockToken = mock.token;
|
|
616
|
+
closeMock = mock.started ? () => mock.server.close() : undefined;
|
|
617
|
+
}
|
|
618
|
+
if (localPackageInvoke) {
|
|
619
|
+
const { buildLocalPackageContext, runLocalPackageModule } = await import('./local-package-run.js');
|
|
620
|
+
const result = await runLocalPackageModule({
|
|
621
|
+
modulePath: localPackageInvoke.modulePath,
|
|
622
|
+
params: args.params,
|
|
623
|
+
token: mockToken ??
|
|
624
|
+
(await resolveLocalExecuteBearer({
|
|
625
|
+
tokenValues,
|
|
626
|
+
mcpUrl,
|
|
627
|
+
apiUrl,
|
|
628
|
+
purpose: 'execute local package export',
|
|
629
|
+
...(org ? { org } : {}),
|
|
630
|
+
})),
|
|
631
|
+
apiUrl,
|
|
632
|
+
target,
|
|
633
|
+
packageContext: buildLocalPackageContext({
|
|
634
|
+
kodyId: localPackageInvoke.kodyId,
|
|
635
|
+
hostedUrl: 'http://127.0.0.1:0',
|
|
636
|
+
}),
|
|
637
|
+
allowPrivateNetwork: parsed.values['allow-private-network'] === true,
|
|
638
|
+
onStatus: (message) => writeErr(`${message}\n`),
|
|
639
|
+
});
|
|
640
|
+
write(formatToolResult(result, json));
|
|
641
|
+
return result.isError ? 1 : 0;
|
|
642
|
+
}
|
|
643
|
+
const useToken = local ||
|
|
644
|
+
shouldUseApiToken({
|
|
485
645
|
tokenValues,
|
|
486
646
|
mcpUrl,
|
|
487
647
|
apiUrl,
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
? await (await import('./remote-execute.js')).runRemoteExecuteWithToken({
|
|
496
|
-
code: typeof args.code === 'string' ? args.code : undefined,
|
|
497
|
-
invoke,
|
|
648
|
+
allowEnvWithoutLogin: true,
|
|
649
|
+
...(org ? { org } : {}),
|
|
650
|
+
});
|
|
651
|
+
const result = local
|
|
652
|
+
? await (await import('./local-execute.js')).runLocalExecute({
|
|
653
|
+
code: args.code,
|
|
654
|
+
...(modulePath !== undefined ? { modulePath } : {}),
|
|
498
655
|
params: args.params,
|
|
499
656
|
conversationId: args.conversationId,
|
|
500
657
|
...(credentials !== undefined ? { credentials } : {}),
|
|
501
|
-
token:
|
|
658
|
+
token: mockToken ??
|
|
659
|
+
(await resolveLocalExecuteBearer({
|
|
660
|
+
tokenValues,
|
|
661
|
+
mcpUrl,
|
|
662
|
+
apiUrl,
|
|
663
|
+
purpose: 'execute --local',
|
|
664
|
+
...(org ? { org } : {}),
|
|
665
|
+
})),
|
|
502
666
|
apiUrl,
|
|
667
|
+
...(org ? { org } : {}),
|
|
668
|
+
allowPrivateNetwork: parsed.values['allow-private-network'] === true,
|
|
669
|
+
onStatus: (message) => writeErr(`${message}\n`),
|
|
503
670
|
})
|
|
504
|
-
:
|
|
505
|
-
|
|
506
|
-
|
|
671
|
+
: useToken
|
|
672
|
+
? await (await import('./remote-execute.js')).runRemoteExecuteWithToken({
|
|
673
|
+
code: typeof args.code === 'string' ? args.code : undefined,
|
|
674
|
+
invoke,
|
|
675
|
+
params: args.params,
|
|
676
|
+
conversationId: args.conversationId,
|
|
677
|
+
...(credentials !== undefined ? { credentials } : {}),
|
|
678
|
+
token: requireApiToken(tokenValues, process.env, 'execute with an API token', { apiUrl }),
|
|
679
|
+
apiUrl,
|
|
680
|
+
...(org ? { org } : {}),
|
|
681
|
+
})
|
|
682
|
+
: await (await import('./mcp.js')).callKodyTool({
|
|
683
|
+
name: 'execute',
|
|
684
|
+
args,
|
|
685
|
+
mcpUrl,
|
|
686
|
+
...(org ? { org } : {}),
|
|
687
|
+
});
|
|
688
|
+
write(formatToolResult(result, json));
|
|
689
|
+
return result.isError ? 1 : 0;
|
|
690
|
+
}
|
|
691
|
+
finally {
|
|
692
|
+
await closeMock?.();
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
case 'dev': {
|
|
696
|
+
const { resolveTarget } = await import('./target.js');
|
|
697
|
+
const { parseDevSubcommand, resolveDevAuth, runDevCheck, runDevEmit, runDevFetch, runDevJob, runDevServe, runDevWebhook, } = await import('./dev-command.js');
|
|
698
|
+
const target = resolveTarget({
|
|
699
|
+
command: 'dev',
|
|
700
|
+
flag: typeof parsed.values.target === 'string' ? parsed.values.target : undefined,
|
|
701
|
+
});
|
|
702
|
+
const { subcommand, rest } = parseDevSubcommand(parsed.positionals);
|
|
703
|
+
const tokenValues = tokenFlagValues(parsed.values);
|
|
704
|
+
const prodApiUrl = apiUrlFrom({
|
|
705
|
+
apiUrl: typeof parsed.values['api-url'] === 'string'
|
|
706
|
+
? parsed.values['api-url']
|
|
707
|
+
: undefined,
|
|
708
|
+
});
|
|
709
|
+
const io = { stdout: write, stderr: writeErr };
|
|
710
|
+
const bodyPath = typeof parsed.values.body === 'string' ? parsed.values.body : undefined;
|
|
711
|
+
// `check` only bundles + validates the package tree; it never talks to
|
|
712
|
+
// mock/prod, so skip auth (and the mock server) to avoid leaking a listen
|
|
713
|
+
// handle that would keep the process alive after printing results.
|
|
714
|
+
if (subcommand === 'check') {
|
|
715
|
+
return await runDevCheck({ target, json, io });
|
|
716
|
+
}
|
|
717
|
+
const token = target === 'prod'
|
|
718
|
+
? await resolveLocalExecuteBearer({
|
|
719
|
+
tokenValues,
|
|
720
|
+
mcpUrl,
|
|
721
|
+
apiUrl: prodApiUrl,
|
|
722
|
+
purpose: 'kody dev',
|
|
723
|
+
...(org ? { org } : {}),
|
|
724
|
+
})
|
|
725
|
+
: 'mock';
|
|
726
|
+
const auth = await resolveDevAuth({
|
|
727
|
+
target,
|
|
728
|
+
token,
|
|
729
|
+
apiUrl: prodApiUrl,
|
|
730
|
+
resetStorage: parsed.values['reset-storage'] === true,
|
|
731
|
+
});
|
|
732
|
+
const port = typeof parsed.values.port === 'string' ? Number(parsed.values.port) : undefined;
|
|
733
|
+
const appBasePath = typeof parsed.values['app-base-path'] === 'string'
|
|
734
|
+
? parsed.values['app-base-path']
|
|
735
|
+
: undefined;
|
|
736
|
+
switch (subcommand) {
|
|
737
|
+
case 'serve':
|
|
738
|
+
return await runDevServe({
|
|
739
|
+
target,
|
|
740
|
+
auth,
|
|
741
|
+
port,
|
|
742
|
+
appBasePath,
|
|
743
|
+
allowPrivateNetwork: parsed.values['allow-private-network'] === true,
|
|
744
|
+
io,
|
|
745
|
+
});
|
|
746
|
+
case 'fetch': {
|
|
747
|
+
const path = rest[0];
|
|
748
|
+
if (!path) {
|
|
749
|
+
throw new Error('Usage: kody dev fetch <path> [--method GET] [--body file.json]');
|
|
750
|
+
}
|
|
751
|
+
let body;
|
|
752
|
+
if (bodyPath)
|
|
753
|
+
body = await readFile(bodyPath, 'utf8');
|
|
754
|
+
return await runDevFetch({
|
|
755
|
+
target,
|
|
756
|
+
auth,
|
|
757
|
+
path,
|
|
758
|
+
method: typeof parsed.values.method === 'string'
|
|
759
|
+
? parsed.values.method
|
|
760
|
+
: undefined,
|
|
761
|
+
body,
|
|
762
|
+
port,
|
|
763
|
+
appBasePath,
|
|
764
|
+
json,
|
|
765
|
+
io,
|
|
766
|
+
});
|
|
767
|
+
}
|
|
768
|
+
case 'job': {
|
|
769
|
+
const jobName = rest[0];
|
|
770
|
+
if (!jobName)
|
|
771
|
+
throw new Error('Usage: kody dev job <name>');
|
|
772
|
+
return await runDevJob({ target, auth, jobName, json, io });
|
|
773
|
+
}
|
|
774
|
+
case 'webhook': {
|
|
775
|
+
const webhookName = rest[0];
|
|
776
|
+
if (!webhookName || !bodyPath) {
|
|
777
|
+
throw new Error('Usage: kody dev webhook <name> --body file.json');
|
|
778
|
+
}
|
|
779
|
+
return await runDevWebhook({
|
|
780
|
+
target,
|
|
781
|
+
auth,
|
|
782
|
+
webhookName,
|
|
783
|
+
bodyPath,
|
|
784
|
+
json,
|
|
785
|
+
io,
|
|
786
|
+
});
|
|
787
|
+
}
|
|
788
|
+
case 'emit': {
|
|
789
|
+
const event = rest[0];
|
|
790
|
+
if (!event || !bodyPath) {
|
|
791
|
+
throw new Error('Usage: kody dev emit <event> --body event.json');
|
|
792
|
+
}
|
|
793
|
+
return await runDevEmit({
|
|
794
|
+
target,
|
|
795
|
+
auth,
|
|
796
|
+
event,
|
|
797
|
+
bodyPath,
|
|
798
|
+
json,
|
|
799
|
+
io,
|
|
800
|
+
});
|
|
801
|
+
}
|
|
802
|
+
default: {
|
|
803
|
+
const exhaustive = subcommand;
|
|
804
|
+
throw new Error(`Unhandled kody dev subcommand: ${String(exhaustive)}`);
|
|
805
|
+
}
|
|
806
|
+
}
|
|
507
807
|
}
|
|
508
808
|
case 'api': {
|
|
509
809
|
const operationId = parsed.positionals[0]?.trim() ?? '';
|
|
@@ -523,6 +823,7 @@ async function dispatch(parsed, write, writeErr) {
|
|
|
523
823
|
params,
|
|
524
824
|
token: requireApiToken(tokenValues, process.env, 'api', { apiUrl }),
|
|
525
825
|
apiUrl,
|
|
826
|
+
...(org ? { org } : {}),
|
|
526
827
|
});
|
|
527
828
|
// Always JSON: mirrors MCP `api` structured results for agents/scripts.
|
|
528
829
|
write(`${JSON.stringify(result, null, 2)}\n`);
|
|
@@ -562,6 +863,20 @@ async function readStdin() {
|
|
|
562
863
|
}
|
|
563
864
|
return Buffer.concat(chunks).toString('utf8');
|
|
564
865
|
}
|
|
866
|
+
async function tryResolveLocalPackageInvoke(invoke) {
|
|
867
|
+
const trimmed = invoke.trim();
|
|
868
|
+
if (!trimmed || trimmed.startsWith('kody:@'))
|
|
869
|
+
return null;
|
|
870
|
+
try {
|
|
871
|
+
const { loadPackageManifest, resolveExportModulePath } = await import('./package-manifest.js');
|
|
872
|
+
const manifest = await loadPackageManifest(process.cwd());
|
|
873
|
+
const modulePath = resolveExportModulePath(manifest, trimmed);
|
|
874
|
+
return { modulePath, kodyId: manifest.kodyId };
|
|
875
|
+
}
|
|
876
|
+
catch {
|
|
877
|
+
return null;
|
|
878
|
+
}
|
|
879
|
+
}
|
|
565
880
|
/** True when --invoke is set alongside any code source (--code, --file, or positional). */
|
|
566
881
|
export function executeSourcesConflict(input) {
|
|
567
882
|
return (input.invoke !== undefined &&
|
|
@@ -621,7 +936,9 @@ async function runAuthSetupGit(input) {
|
|
|
621
936
|
});
|
|
622
937
|
const tokenValues = tokenFlagValues(input.values);
|
|
623
938
|
const mintScopes = scopes ?? setupGitDefaultScopes;
|
|
624
|
-
const hasLoginSession = loadCredentials(input.mcpUrl
|
|
939
|
+
const hasLoginSession = loadCredentials(input.mcpUrl, undefined, undefined, {
|
|
940
|
+
org: input.org,
|
|
941
|
+
}) != null;
|
|
625
942
|
const result = await authSetupGit({
|
|
626
943
|
apiUrl,
|
|
627
944
|
mcpUrl: input.mcpUrl,
|
|
@@ -635,6 +952,7 @@ async function runAuthSetupGit(input) {
|
|
|
635
952
|
mcpUrl: input.mcpUrl,
|
|
636
953
|
apiUrl,
|
|
637
954
|
tokenValues,
|
|
955
|
+
...(input.org ? { org: input.org } : {}),
|
|
638
956
|
}),
|
|
639
957
|
});
|
|
640
958
|
input.write([
|
|
@@ -685,6 +1003,7 @@ function bootstrapMinter(input) {
|
|
|
685
1003
|
mcpUrl: input.mcpUrl,
|
|
686
1004
|
apiUrl: input.apiUrl,
|
|
687
1005
|
allowEnvWithoutLogin: true,
|
|
1006
|
+
...(input.org ? { org: input.org } : {}),
|
|
688
1007
|
})) {
|
|
689
1008
|
const token = requireApiToken(input.tokenValues, process.env, 'auth bootstrap --scopes', {
|
|
690
1009
|
apiUrl: input.apiUrl,
|
|
@@ -696,6 +1015,7 @@ function bootstrapMinter(input) {
|
|
|
696
1015
|
apiUrl: input.apiUrl,
|
|
697
1016
|
operationId: cliCredentialBootstrapOperationId,
|
|
698
1017
|
params,
|
|
1018
|
+
...(input.org ? { org: input.org } : {}),
|
|
699
1019
|
});
|
|
700
1020
|
}
|
|
701
1021
|
catch (error) {
|
|
@@ -705,11 +1025,14 @@ function bootstrapMinter(input) {
|
|
|
705
1025
|
throw error;
|
|
706
1026
|
}
|
|
707
1027
|
}
|
|
708
|
-
if (loadCredentials(input.mcpUrl
|
|
1028
|
+
if (loadCredentials(input.mcpUrl, undefined, undefined, {
|
|
1029
|
+
org: input.org,
|
|
1030
|
+
})) {
|
|
709
1031
|
return (await import('./mcp.js')).callKodyApiOperation({
|
|
710
1032
|
operationId: cliCredentialBootstrapOperationId,
|
|
711
1033
|
params,
|
|
712
1034
|
mcpUrl: input.mcpUrl,
|
|
1035
|
+
...(input.org ? { org: input.org } : {}),
|
|
713
1036
|
});
|
|
714
1037
|
}
|
|
715
1038
|
throw new Error(`auth bootstrap --scopes mints with your current Kody credential, and none was found. Run \`kody login\` and retry, or from Kody MCP call \`${cliBootstrapMintCall(input.scopes, bootstrapMintLifetimeFields(input.lifetime))}\` and run the returned cli_command.`);
|
|
@@ -756,7 +1079,10 @@ export function shouldUseApiToken(input) {
|
|
|
756
1079
|
})) {
|
|
757
1080
|
return false;
|
|
758
1081
|
}
|
|
759
|
-
const loggedIn = input.hasSession ??
|
|
1082
|
+
const loggedIn = input.hasSession ??
|
|
1083
|
+
loadCredentials(input.mcpUrl, undefined, undefined, {
|
|
1084
|
+
org: input.org,
|
|
1085
|
+
}) != null;
|
|
760
1086
|
return !loggedIn;
|
|
761
1087
|
}
|
|
762
1088
|
//# sourceMappingURL=cli.js.map
|