@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.
Files changed (79) hide show
  1. package/README.md +51 -5
  2. package/dist/auth.d.ts +30 -4
  3. package/dist/auth.js +94 -13
  4. package/dist/auth.js.map +1 -1
  5. package/dist/capability-proxy.d.ts +7 -0
  6. package/dist/capability-proxy.js +8 -2
  7. package/dist/capability-proxy.js.map +1 -1
  8. package/dist/cli.d.ts +9 -1
  9. package/dist/cli.js +426 -100
  10. package/dist/cli.js.map +1 -1
  11. package/dist/dev-command.d.ts +93 -0
  12. package/dist/dev-command.js +369 -0
  13. package/dist/dev-command.js.map +1 -0
  14. package/dist/discover-org.d.ts +10 -0
  15. package/dist/discover-org.js +62 -0
  16. package/dist/discover-org.js.map +1 -0
  17. package/dist/help.d.ts +13 -1
  18. package/dist/help.js +112 -15
  19. package/dist/help.js.map +1 -1
  20. package/dist/local-execute-auth.d.ts +1 -0
  21. package/dist/local-execute-auth.js +7 -1
  22. package/dist/local-execute-auth.js.map +1 -1
  23. package/dist/local-execute.d.ts +2 -0
  24. package/dist/local-execute.js +6 -1
  25. package/dist/local-execute.js.map +1 -1
  26. package/dist/local-package-graph.js +2 -0
  27. package/dist/local-package-graph.js.map +1 -1
  28. package/dist/local-package-run.d.ts +28 -0
  29. package/dist/local-package-run.js +299 -0
  30. package/dist/local-package-run.js.map +1 -0
  31. package/dist/mcp.d.ts +4 -0
  32. package/dist/mcp.js +3 -0
  33. package/dist/mcp.js.map +1 -1
  34. package/dist/mock/mock-server.d.ts +42 -0
  35. package/dist/mock/mock-server.js +373 -0
  36. package/dist/mock/mock-server.js.map +1 -0
  37. package/dist/mock/mock-storage.d.ts +46 -0
  38. package/dist/mock/mock-storage.js +96 -0
  39. package/dist/mock/mock-storage.js.map +1 -0
  40. package/dist/mock/mock-supported.d.ts +18 -0
  41. package/dist/mock/mock-supported.js +43 -0
  42. package/dist/mock/mock-supported.js.map +1 -0
  43. package/dist/oauth-provider.d.ts +1 -2
  44. package/dist/oauth-provider.js +2 -8
  45. package/dist/oauth-provider.js.map +1 -1
  46. package/dist/open-api-client.d.ts +5 -0
  47. package/dist/open-api-client.js +4 -0
  48. package/dist/open-api-client.js.map +1 -1
  49. package/dist/org-resolve.d.ts +9 -0
  50. package/dist/org-resolve.js +16 -0
  51. package/dist/org-resolve.js.map +1 -0
  52. package/dist/org-slug.d.ts +4 -0
  53. package/dist/org-slug.js +11 -0
  54. package/dist/org-slug.js.map +1 -0
  55. package/dist/org.d.ts +22 -0
  56. package/dist/org.js +100 -0
  57. package/dist/org.js.map +1 -0
  58. package/dist/package-app-dev.d.ts +43 -0
  59. package/dist/package-app-dev.js +356 -0
  60. package/dist/package-app-dev.js.map +1 -0
  61. package/dist/package-local-runtime-source.d.ts +35 -0
  62. package/dist/package-local-runtime-source.js +317 -0
  63. package/dist/package-local-runtime-source.js.map +1 -0
  64. package/dist/package-manifest.d.ts +43 -0
  65. package/dist/package-manifest.js +264 -0
  66. package/dist/package-manifest.js.map +1 -0
  67. package/dist/remote-execute.d.ts +2 -0
  68. package/dist/remote-execute.js +6 -1
  69. package/dist/remote-execute.js.map +1 -1
  70. package/dist/store.d.ts +93 -3
  71. package/dist/store.js +454 -23
  72. package/dist/store.js.map +1 -1
  73. package/dist/structured-error.d.ts +28 -0
  74. package/dist/structured-error.js +57 -0
  75. package/dist/structured-error.js.map +1 -0
  76. package/dist/target.d.ts +25 -0
  77. package/dist/target.js +45 -0
  78. package/dist/target.js.map +1 -0
  79. 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 { usage } from './help.js';
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(usage);
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.org !== undefined && parsed.command !== 'login') {
136
- throw new Error('`--org` can only be used with `kody login`.');
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(usage);
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 [{ login }, { orgSlugFromFlag }] = await Promise.all([
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
- ...(org ? { org } : {}),
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 ? ` as org ${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
- const apiToken = deleteStoredApiToken(apiUrl);
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('No stored credentials.\n');
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('Logged out of kody login.');
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.expiresAt
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.mcpUrl}`, `logged in: yes`, `access token expires: ${expires}`, `refresh token: ${credentials.refreshToken ? 'yes' : 'no'}`, `scope: ${credentials.scope ?? 'unknown'}`);
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
- write(`Connected to ${credentials.mcpUrl} (${modernMcpProtocolVersion})\nTools: ${tools.map((tool) => tool.name).join(', ') || '(none)'}\n`);
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
- const apiUrl = apiUrlFrom({
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
- if (shouldUseApiToken({
391
- tokenValues,
392
- mcpUrl,
393
- apiUrl,
394
- allowEnvWithoutLogin: true,
395
- })) {
396
- const { searchWithApiToken } = await import('./open-api-client.js');
397
- const result = await searchWithApiToken({
398
- token: requireApiToken(tokenValues, process.env, 'search', { apiUrl }),
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
- query: query || undefined,
401
- entity: typeof parsed.values.entity === 'string' ? parsed.values.entity : undefined,
402
- domain: typeof parsed.values.domain === 'string' ? parsed.values.domain : undefined,
403
- limit: typeof parsed.values.limit === 'string'
404
- ? Number(parsed.values.limit)
405
- : undefined,
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
- const args = {};
411
- if (query)
412
- args.query = query;
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
- const local = parsed.values.local === true;
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
- if (local && invoke !== undefined) {
432
- throw new Error('--invoke runs a saved package export in Kody; --local runs a module you provide (--code, --file, or a module string).');
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 = !hasCodeFlag && fileArg !== undefined && fileArg !== '-' ? fileArg : undefined;
442
- const code = invoke !== undefined
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
- : fileArg === '-'
449
- ? await readStdin()
450
- : positionalModule || undefined;
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
- const apiUrl = apiUrlFrom({
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
- const useToken = local ||
471
- shouldUseApiToken({
472
- tokenValues,
473
- mcpUrl,
474
- apiUrl,
475
- allowEnvWithoutLogin: true,
476
- });
477
- const result = local
478
- ? await (await import('./local-execute.js')).runLocalExecute({
479
- code: args.code,
480
- ...(modulePath !== undefined ? { modulePath } : {}),
481
- params: args.params,
482
- conversationId: args.conversationId,
483
- ...(credentials !== undefined ? { credentials } : {}),
484
- token: await resolveLocalExecuteBearer({
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
- purpose: 'execute --local',
489
- }),
490
- apiUrl,
491
- allowPrivateNetwork: parsed.values['allow-private-network'] === true,
492
- onStatus: (message) => writeErr(`${message}\n`),
493
- })
494
- : useToken
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: requireApiToken(tokenValues, process.env, 'execute with an API token', { apiUrl }),
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
- : await (await import('./mcp.js')).callKodyTool({ name: 'execute', args, mcpUrl });
505
- write(formatToolResult(result, json));
506
- return result.isError ? 1 : 0;
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) != null;
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 ?? loadCredentials(input.mcpUrl) != null;
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