@kodycodes/cli 1.18.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 +33 -1
- package/dist/cli.d.ts +7 -1
- package/dist/cli.js +319 -82
- 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/help.js +38 -4
- package/dist/help.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/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/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/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/README.md
CHANGED
|
@@ -85,7 +85,8 @@ keychain credentials migrate silently into the multi-org store as the active org
|
|
|
85
85
|
| `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. |
|
|
86
86
|
| `kody search [query]` | Calls Kody `search` from the CLI (prefer the host MCP tool). Token-only auth uses Open API `GET /v1/search`. |
|
|
87
87
|
| `kody api <operationId>` | Thin Open API wrapper matching the MCP `api` tool: `operationId` + flat `--params` JSON. Auth: `--token` / `KODY_API_TOKEN` / stored `auth bootstrap` token. The parsed `/openapi.json` route map is cached under the Kody cache dir keyed by the document's `ETag` and revalidated with `If-None-Match` on each run. |
|
|
88
|
-
| `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. `--credential key=name` (repeatable; `org:name` / `personal:name` / empty `=` for none) picks ADR 0068 credentials for `--local` or a `kody login` MCP session. `--allow-private-network` opts local execution into private and local network access. |
|
|
88
|
+
| `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. `--credential key=name` (repeatable; `org:name` / `personal:name` / empty `=` for none) picks ADR 0068 credentials for `--local` or a `kody login` MCP session. `--allow-private-network` opts local execution into private and local network access. `--target prod\|mock` (default `prod`) sends CapabilityProxy / search hops to prod or the built-in CLI mock; `--target mock` implies local workerd. |
|
|
89
|
+
| `kody dev` | Local testing for unpublished packages. Serves `kody.app.entry` on localhost via workerd with hot reload. Subcommands: `fetch`, `job`, `webhook`, `emit`, `check`. Default `--target mock` (scratch storage, fake credentials). Covers exports, webhooks, schedules (run-once), and subscriptions. See [Local package testing](#local-package-testing-kody-dev). |
|
|
89
90
|
|
|
90
91
|
`--json` prints structured MCP results.
|
|
91
92
|
|
|
@@ -204,6 +205,37 @@ config helpers, revokes and deletes the stored API token; leaves login alone).
|
|
|
204
205
|
Push moves live HEAD only (packages do not auto-publish). Existing `kody login`
|
|
205
206
|
/ `auth bootstrap` flows are unchanged.
|
|
206
207
|
|
|
208
|
+
## Local package testing (`kody dev`)
|
|
209
|
+
|
|
210
|
+
Test an unpublished package (app, exports, jobs, webhooks, subscriptions)
|
|
211
|
+
before publish. Bundling reuses the same esbuild path as `execute --file` /
|
|
212
|
+
publish (`local-bundle.ts`). One shared flag:
|
|
213
|
+
|
|
214
|
+
| Command | `--target prod` (default for execute/search) | `--target mock` (default for `kody dev`) |
|
|
215
|
+
| --- | --- | --- |
|
|
216
|
+
| `kody dev` | Local app against real Kody | Local app against the CLI mock |
|
|
217
|
+
| `execute` / `search` | Real capabilities, storage, secrets | Mock catalog / scratch storage / fake credentials |
|
|
218
|
+
|
|
219
|
+
The mock is built into the CLI (no kody-celld). It refuses unimplemented routes
|
|
220
|
+
with `not_implemented_in_mock` rather than faking success. `--reset-storage`
|
|
221
|
+
wipes the mock session's scratch store. Share previews are out of scope;
|
|
222
|
+
`execute --local` behavior is unchanged when `--target` is omitted (prod).
|
|
223
|
+
|
|
224
|
+
```bash
|
|
225
|
+
cd ./my-package
|
|
226
|
+
kody dev # serve kody.app.entry; hot reload; --target mock
|
|
227
|
+
kody dev fetch /health # agent-friendly HTTP probe
|
|
228
|
+
kody dev job heartbeat # run a declared job once (never schedules locally)
|
|
229
|
+
kody dev webhook ping --body ./fixture.json
|
|
230
|
+
kody dev emit demo.event --body ./event.json # subscription delivery shape
|
|
231
|
+
kody dev check # bundle + light publish validations
|
|
232
|
+
|
|
233
|
+
# Four surfaces against mock then prod:
|
|
234
|
+
kody execute --target mock --invoke ./greet --params '{"name":"kent"}'
|
|
235
|
+
kody execute --target prod --invoke ./greet --params '{"name":"kent"}'
|
|
236
|
+
kody search --target mock local
|
|
237
|
+
```
|
|
238
|
+
|
|
207
239
|
## Local execute
|
|
208
240
|
|
|
209
241
|
`kody execute --local` runs the same execute module (default export called
|
package/dist/cli.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { type CredentialCallMapInput } from './credential-call-map.js';
|
|
|
3
3
|
export { readApiToken, requireApiToken as resolveApiToken, resolveScopedApiToken, } from './api-token.js';
|
|
4
4
|
export { resolveLocalExecuteBearer } from './local-execute-auth.js';
|
|
5
5
|
export { authBootstrap, authBootstrapWithScopes, bootstrapRedeemRequestBody, cliTokenLifetimeAliases, cliTokenLifetimeMissingError, cliTokenLifetimePolicy, parseCliLifetimeSecondsFlag, parseCliScopesFlag, redeemBootstrapCode, resolveCliTokenLifetime, } from './auth-bootstrap.js';
|
|
6
|
-
export type CommandName = 'login' | 'logout' | 'org' | 'status' | 'auth' | 'git-credential' | 'whoami' | 'search' | 'api' | 'execute' | 'install' | 'skill' | 'help' | 'version';
|
|
6
|
+
export type CommandName = 'login' | 'logout' | 'org' | 'status' | 'auth' | 'git-credential' | 'whoami' | 'search' | 'api' | 'execute' | 'dev' | 'install' | 'skill' | 'help' | 'version';
|
|
7
7
|
export declare function apiUrlFrom(values: {
|
|
8
8
|
apiUrl?: string;
|
|
9
9
|
}, env?: NodeJS.ProcessEnv): string;
|
|
@@ -35,6 +35,12 @@ declare function parseKnown(args: Array<string>): {
|
|
|
35
35
|
project?: string | boolean | undefined;
|
|
36
36
|
'no-browser'?: string | boolean | undefined;
|
|
37
37
|
org?: string | boolean | undefined;
|
|
38
|
+
target?: string | boolean | undefined;
|
|
39
|
+
'reset-storage'?: string | boolean | undefined;
|
|
40
|
+
port?: string | boolean | undefined;
|
|
41
|
+
'app-base-path'?: string | boolean | undefined;
|
|
42
|
+
method?: string | boolean | undefined;
|
|
43
|
+
body?: string | boolean | undefined;
|
|
38
44
|
clients?: string | boolean | undefined;
|
|
39
45
|
yes?: string | boolean | undefined;
|
|
40
46
|
};
|
package/dist/cli.js
CHANGED
|
@@ -24,6 +24,7 @@ const orgScopedCommands = new Set([
|
|
|
24
24
|
'search',
|
|
25
25
|
'api',
|
|
26
26
|
'execute',
|
|
27
|
+
'dev',
|
|
27
28
|
'auth',
|
|
28
29
|
]);
|
|
29
30
|
function mcpUrlFrom(values) {
|
|
@@ -62,6 +63,12 @@ function parseKnown(args) {
|
|
|
62
63
|
project: { type: 'boolean' },
|
|
63
64
|
'no-browser': { type: 'boolean' },
|
|
64
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' },
|
|
65
72
|
clients: { type: 'string' },
|
|
66
73
|
yes: { type: 'boolean', short: 'y' },
|
|
67
74
|
},
|
|
@@ -89,6 +96,7 @@ export function resolveCommand(argv) {
|
|
|
89
96
|
case 'search':
|
|
90
97
|
case 'api':
|
|
91
98
|
case 'execute':
|
|
99
|
+
case 'dev':
|
|
92
100
|
case 'install':
|
|
93
101
|
case 'skill':
|
|
94
102
|
case 'help':
|
|
@@ -145,8 +153,15 @@ async function dispatch(parsed, write, writeErr) {
|
|
|
145
153
|
});
|
|
146
154
|
const json = parsed.values.json === true;
|
|
147
155
|
if (parsed.values['allow-private-network'] === true &&
|
|
156
|
+
parsed.command !== 'dev' &&
|
|
148
157
|
(parsed.command !== 'execute' || parsed.values.local !== true)) {
|
|
149
|
-
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.');
|
|
159
|
+
}
|
|
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.');
|
|
150
165
|
}
|
|
151
166
|
const orgFlag = typeof parsed.values.org === 'string' ? parsed.values.org : undefined;
|
|
152
167
|
if (orgFlag !== undefined && !orgScopedCommands.has(parsed.command)) {
|
|
@@ -441,63 +456,110 @@ async function dispatch(parsed, write, writeErr) {
|
|
|
441
456
|
return 0;
|
|
442
457
|
}
|
|
443
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
|
+
});
|
|
444
464
|
const query = parsed.positionals.join(' ').trim();
|
|
445
465
|
const tokenValues = tokenFlagValues(parsed.values);
|
|
446
|
-
|
|
466
|
+
let apiUrl = apiUrlFrom({
|
|
447
467
|
apiUrl: typeof parsed.values['api-url'] === 'string'
|
|
448
468
|
? parsed.values['api-url']
|
|
449
469
|
: undefined,
|
|
450
470
|
});
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
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,
|
|
461
501
|
apiUrl,
|
|
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,
|
|
462
538
|
...(org ? { org } : {}),
|
|
463
|
-
query: query || undefined,
|
|
464
|
-
entity: typeof parsed.values.entity === 'string' ? parsed.values.entity : undefined,
|
|
465
|
-
domain: typeof parsed.values.domain === 'string' ? parsed.values.domain : undefined,
|
|
466
|
-
limit: typeof parsed.values.limit === 'string'
|
|
467
|
-
? Number(parsed.values.limit)
|
|
468
|
-
: undefined,
|
|
469
539
|
});
|
|
470
540
|
write(formatToolResult(result, json));
|
|
471
541
|
return result.isError ? 1 : 0;
|
|
472
542
|
}
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
if (typeof parsed.values.entity === 'string')
|
|
477
|
-
args.entity = parsed.values.entity;
|
|
478
|
-
if (typeof parsed.values.domain === 'string')
|
|
479
|
-
args.domain = parsed.values.domain;
|
|
480
|
-
if (typeof parsed.values.limit === 'string')
|
|
481
|
-
args.limit = Number(parsed.values.limit);
|
|
482
|
-
const { callKodyTool } = await import('./mcp.js');
|
|
483
|
-
const result = await callKodyTool({
|
|
484
|
-
name: 'search',
|
|
485
|
-
args,
|
|
486
|
-
mcpUrl,
|
|
487
|
-
...(org ? { org } : {}),
|
|
488
|
-
});
|
|
489
|
-
write(formatToolResult(result, json));
|
|
490
|
-
return result.isError ? 1 : 0;
|
|
543
|
+
finally {
|
|
544
|
+
await closeMock?.();
|
|
545
|
+
}
|
|
491
546
|
}
|
|
492
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
|
+
});
|
|
493
553
|
const invoke = typeof parsed.values.invoke === 'string' ? parsed.values.invoke : undefined;
|
|
494
554
|
const hasCodeFlag = typeof parsed.values.code === 'string';
|
|
495
555
|
const hasFileFlag = typeof parsed.values.file === 'string';
|
|
496
556
|
const positionalModule = parsed.positionals.join('\n').trim();
|
|
497
|
-
|
|
557
|
+
// --target mock always runs locally against the CLI mock (no cloud sandbox).
|
|
558
|
+
const local = parsed.values.local === true || target === 'mock';
|
|
498
559
|
const tokenValues = tokenFlagValues(parsed.values);
|
|
499
|
-
|
|
500
|
-
|
|
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.');
|
|
501
563
|
}
|
|
502
564
|
if (executeSourcesConflict({ invoke, hasCodeFlag, hasFileFlag, positionalModule })) {
|
|
503
565
|
throw new Error('--invoke cannot be combined with --code, --file, or a module string.');
|
|
@@ -506,23 +568,26 @@ async function dispatch(parsed, write, writeErr) {
|
|
|
506
568
|
assertLocalExecuteNodeEngine();
|
|
507
569
|
const fileArg = hasFileFlag ? parsed.values.file : undefined;
|
|
508
570
|
// `--code` wins over `--file`. Only attribute the path when the file is the source.
|
|
509
|
-
const modulePath =
|
|
510
|
-
|
|
571
|
+
const modulePath = localPackageInvoke?.modulePath ??
|
|
572
|
+
(!hasCodeFlag && fileArg !== undefined && fileArg !== '-' ? fileArg : undefined);
|
|
573
|
+
const code = invoke !== undefined && !localPackageInvoke
|
|
511
574
|
? undefined
|
|
512
575
|
: hasCodeFlag
|
|
513
576
|
? parsed.values.code
|
|
514
|
-
: modulePath !== undefined
|
|
577
|
+
: modulePath !== undefined && !localPackageInvoke
|
|
515
578
|
? await readFile(modulePath, 'utf8')
|
|
516
|
-
:
|
|
517
|
-
? await
|
|
518
|
-
:
|
|
579
|
+
: localPackageInvoke
|
|
580
|
+
? await readFile(localPackageInvoke.modulePath, 'utf8')
|
|
581
|
+
: fileArg === '-'
|
|
582
|
+
? await readStdin()
|
|
583
|
+
: positionalModule || undefined;
|
|
519
584
|
const credentials = parseCredentialFlags(Array.isArray(parsed.values.credential)
|
|
520
585
|
? parsed.values.credential.filter((value) => typeof value === 'string')
|
|
521
586
|
: typeof parsed.values.credential === 'string'
|
|
522
587
|
? [parsed.values.credential]
|
|
523
588
|
: undefined);
|
|
524
589
|
const args = buildExecuteToolArgs({
|
|
525
|
-
invoke,
|
|
590
|
+
invoke: localPackageInvoke ? undefined : invoke,
|
|
526
591
|
code: typeof code === 'string' ? code : undefined,
|
|
527
592
|
paramsJson: typeof parsed.values.params === 'string' ? parsed.values.params : undefined,
|
|
528
593
|
conversationId: typeof parsed.values['conversation-id'] === 'string'
|
|
@@ -530,57 +595,215 @@ async function dispatch(parsed, write, writeErr) {
|
|
|
530
595
|
: undefined,
|
|
531
596
|
credentials,
|
|
532
597
|
});
|
|
533
|
-
|
|
598
|
+
let apiUrl = apiUrlFrom({
|
|
534
599
|
apiUrl: typeof parsed.values['api-url'] === 'string'
|
|
535
600
|
? parsed.values['api-url']
|
|
536
601
|
: undefined,
|
|
537
602
|
});
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
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({
|
|
554
645
|
tokenValues,
|
|
555
646
|
mcpUrl,
|
|
556
647
|
apiUrl,
|
|
557
|
-
|
|
648
|
+
allowEnvWithoutLogin: true,
|
|
558
649
|
...(org ? { org } : {}),
|
|
559
|
-
})
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
})
|
|
565
|
-
: useToken
|
|
566
|
-
? await (await import('./remote-execute.js')).runRemoteExecuteWithToken({
|
|
567
|
-
code: typeof args.code === 'string' ? args.code : undefined,
|
|
568
|
-
invoke,
|
|
650
|
+
});
|
|
651
|
+
const result = local
|
|
652
|
+
? await (await import('./local-execute.js')).runLocalExecute({
|
|
653
|
+
code: args.code,
|
|
654
|
+
...(modulePath !== undefined ? { modulePath } : {}),
|
|
569
655
|
params: args.params,
|
|
570
656
|
conversationId: args.conversationId,
|
|
571
657
|
...(credentials !== undefined ? { credentials } : {}),
|
|
572
|
-
token:
|
|
658
|
+
token: mockToken ??
|
|
659
|
+
(await resolveLocalExecuteBearer({
|
|
660
|
+
tokenValues,
|
|
661
|
+
mcpUrl,
|
|
662
|
+
apiUrl,
|
|
663
|
+
purpose: 'execute --local',
|
|
664
|
+
...(org ? { org } : {}),
|
|
665
|
+
})),
|
|
573
666
|
apiUrl,
|
|
574
667
|
...(org ? { org } : {}),
|
|
668
|
+
allowPrivateNetwork: parsed.values['allow-private-network'] === true,
|
|
669
|
+
onStatus: (message) => writeErr(`${message}\n`),
|
|
575
670
|
})
|
|
576
|
-
:
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
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,
|
|
581
786
|
});
|
|
582
|
-
|
|
583
|
-
|
|
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
|
+
}
|
|
584
807
|
}
|
|
585
808
|
case 'api': {
|
|
586
809
|
const operationId = parsed.positionals[0]?.trim() ?? '';
|
|
@@ -640,6 +863,20 @@ async function readStdin() {
|
|
|
640
863
|
}
|
|
641
864
|
return Buffer.concat(chunks).toString('utf8');
|
|
642
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
|
+
}
|
|
643
880
|
/** True when --invoke is set alongside any code source (--code, --file, or positional). */
|
|
644
881
|
export function executeSourcesConflict(input) {
|
|
645
882
|
return (input.invoke !== undefined &&
|