@kodycodes/cli 1.18.0 → 1.19.1
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 +34 -1
- package/dist/api-token.d.ts +23 -4
- package/dist/api-token.js +48 -10
- package/dist/api-token.js.map +1 -1
- package/dist/capability-proxy.js +5 -2
- package/dist/capability-proxy.js.map +1 -1
- package/dist/cli.d.ts +16 -1
- package/dist/cli.js +407 -91
- package/dist/cli.js.map +1 -1
- package/dist/dev-command.d.ts +96 -0
- package/dist/dev-command.js +385 -0
- package/dist/dev-command.js.map +1 -0
- package/dist/git-credential.d.ts +5 -4
- package/dist/git-credential.js +5 -4
- package/dist/git-credential.js.map +1 -1
- package/dist/help.js +49 -7
- package/dist/help.js.map +1 -1
- package/dist/local-package-graph.js +5 -2
- package/dist/local-package-graph.js.map +1 -1
- package/dist/local-package-run.d.ts +30 -0
- package/dist/local-package-run.js +304 -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/open-api-client.js +5 -2
- package/dist/open-api-client.js.map +1 -1
- package/dist/package-app-dev.d.ts +45 -0
- package/dist/package-app-dev.js +357 -0
- package/dist/package-app-dev.js.map +1 -0
- package/dist/package-info.d.ts +5 -0
- package/dist/package-info.js +14 -2
- package/dist/package-info.js.map +1 -1
- 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/setup-git.d.ts +17 -4
- package/dist/setup-git.js +24 -5
- package/dist/setup-git.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 +27 -0
- package/dist/target.js +48 -0
- package/dist/target.js.map +1 -0
- package/package.json +1 -1
- package/skills/kody/SKILL.md +7 -3
package/dist/cli.js
CHANGED
|
@@ -23,7 +23,9 @@ const orgScopedCommands = new Set([
|
|
|
23
23
|
'whoami',
|
|
24
24
|
'search',
|
|
25
25
|
'api',
|
|
26
|
+
'package',
|
|
26
27
|
'execute',
|
|
28
|
+
'dev',
|
|
27
29
|
'auth',
|
|
28
30
|
]);
|
|
29
31
|
function mcpUrlFrom(values) {
|
|
@@ -62,6 +64,12 @@ function parseKnown(args) {
|
|
|
62
64
|
project: { type: 'boolean' },
|
|
63
65
|
'no-browser': { type: 'boolean' },
|
|
64
66
|
org: { type: 'string' },
|
|
67
|
+
target: { type: 'string' },
|
|
68
|
+
'reset-storage': { type: 'boolean' },
|
|
69
|
+
port: { type: 'string' },
|
|
70
|
+
'app-base-path': { type: 'string' },
|
|
71
|
+
method: { type: 'string' },
|
|
72
|
+
body: { type: 'string' },
|
|
65
73
|
clients: { type: 'string' },
|
|
66
74
|
yes: { type: 'boolean', short: 'y' },
|
|
67
75
|
},
|
|
@@ -88,7 +96,9 @@ export function resolveCommand(argv) {
|
|
|
88
96
|
case 'whoami':
|
|
89
97
|
case 'search':
|
|
90
98
|
case 'api':
|
|
99
|
+
case 'package':
|
|
91
100
|
case 'execute':
|
|
101
|
+
case 'dev':
|
|
92
102
|
case 'install':
|
|
93
103
|
case 'skill':
|
|
94
104
|
case 'help':
|
|
@@ -106,12 +116,23 @@ export async function runCli(argv = process.argv.slice(2), io = {}) {
|
|
|
106
116
|
const write = io.stdout ?? ((text) => process.stdout.write(text));
|
|
107
117
|
const writeErr = io.stderr ?? ((text) => process.stderr.write(text));
|
|
108
118
|
// Best-effort: upgrade stale git helper pins (major-only / ephemeral /
|
|
109
|
-
// outdated exact) before
|
|
110
|
-
//
|
|
119
|
+
// outdated exact) before most commands. Must not write to stdout — git
|
|
120
|
+
// reads the helper protocol from stdout only.
|
|
111
121
|
// Skip under node:test so unit tests never rewrite the developer's
|
|
112
|
-
// global git config;
|
|
113
|
-
|
|
114
|
-
|
|
122
|
+
// global git config; skip git-credential (helper must stay fast / offline
|
|
123
|
+
// while the stored token is valid); skip version/help (no git touch).
|
|
124
|
+
const head = argv[0];
|
|
125
|
+
const skipHelperRefresh = process.env.NODE_TEST === '1' ||
|
|
126
|
+
process.env.KODY_SKIP_GIT_HELPER_REFRESH === '1' ||
|
|
127
|
+
head === 'git-credential' ||
|
|
128
|
+
head === 'version' ||
|
|
129
|
+
head === '--version' ||
|
|
130
|
+
head === '-v' ||
|
|
131
|
+
head === 'help' ||
|
|
132
|
+
head === '--help' ||
|
|
133
|
+
head === '-h' ||
|
|
134
|
+
head === undefined;
|
|
135
|
+
if (!skipHelperRefresh) {
|
|
115
136
|
try {
|
|
116
137
|
refreshGitCredentialHelperPinIfNeeded({
|
|
117
138
|
mcpUrl: process.env.KODY_MCP_URL || undefined,
|
|
@@ -144,13 +165,28 @@ async function dispatch(parsed, write, writeErr) {
|
|
|
144
165
|
mcpUrl: typeof parsed.values['mcp-url'] === 'string' ? parsed.values['mcp-url'] : undefined,
|
|
145
166
|
});
|
|
146
167
|
const json = parsed.values.json === true;
|
|
147
|
-
if (parsed.values['allow-private-network'] === true
|
|
148
|
-
|
|
149
|
-
|
|
168
|
+
if (parsed.values['allow-private-network'] === true) {
|
|
169
|
+
const executeAllowsPrivateNetwork = parsed.command === 'execute' &&
|
|
170
|
+
(parsed.values.local === true ||
|
|
171
|
+
(await import('./target.js')).resolveTarget({
|
|
172
|
+
command: 'execute',
|
|
173
|
+
flag: typeof parsed.values.target === 'string'
|
|
174
|
+
? parsed.values.target
|
|
175
|
+
: undefined,
|
|
176
|
+
}) === 'mock');
|
|
177
|
+
if (parsed.command !== 'dev' && !executeAllowsPrivateNetwork) {
|
|
178
|
+
throw new Error('--allow-private-network can only be used with execute --local, execute --target mock, or kody dev.');
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
if (parsed.values['reset-storage'] === true &&
|
|
182
|
+
parsed.command !== 'dev' &&
|
|
183
|
+
parsed.command !== 'execute' &&
|
|
184
|
+
parsed.command !== 'search') {
|
|
185
|
+
throw new Error('`--reset-storage` is only used with --target mock on execute, search, or dev.');
|
|
150
186
|
}
|
|
151
187
|
const orgFlag = typeof parsed.values.org === 'string' ? parsed.values.org : undefined;
|
|
152
188
|
if (orgFlag !== undefined && !orgScopedCommands.has(parsed.command)) {
|
|
153
|
-
throw new Error(`\`--org\` is not used with \`kody ${parsed.command}\`. Use it with login, logout, whoami, search, api, execute, or auth.`);
|
|
189
|
+
throw new Error(`\`--org\` is not used with \`kody ${parsed.command}\`. Use it with login, logout, whoami, search, api, package, execute, or auth.`);
|
|
154
190
|
}
|
|
155
191
|
// Login/logout honor `--org` only — not KODY_ORG (that env is for one-shot
|
|
156
192
|
// org-scoped commands and must not bind browser login or change logout).
|
|
@@ -441,63 +477,110 @@ async function dispatch(parsed, write, writeErr) {
|
|
|
441
477
|
return 0;
|
|
442
478
|
}
|
|
443
479
|
case 'search': {
|
|
480
|
+
const { resolveTarget } = await import('./target.js');
|
|
481
|
+
const target = resolveTarget({
|
|
482
|
+
command: 'search',
|
|
483
|
+
flag: typeof parsed.values.target === 'string' ? parsed.values.target : undefined,
|
|
484
|
+
});
|
|
444
485
|
const query = parsed.positionals.join(' ').trim();
|
|
445
486
|
const tokenValues = tokenFlagValues(parsed.values);
|
|
446
|
-
|
|
487
|
+
let apiUrl = apiUrlFrom({
|
|
447
488
|
apiUrl: typeof parsed.values['api-url'] === 'string'
|
|
448
489
|
? parsed.values['api-url']
|
|
449
490
|
: undefined,
|
|
450
491
|
});
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
492
|
+
let closeMock;
|
|
493
|
+
try {
|
|
494
|
+
if (target === 'mock') {
|
|
495
|
+
const { ensureMockTarget } = await import('./mock/mock-server.js');
|
|
496
|
+
const mock = await ensureMockTarget({
|
|
497
|
+
resetStorage: parsed.values['reset-storage'] === true,
|
|
498
|
+
});
|
|
499
|
+
apiUrl = mock.apiUrl;
|
|
500
|
+
closeMock = mock.started ? () => mock.server.close() : undefined;
|
|
501
|
+
const { searchWithApiToken } = await import('./open-api-client.js');
|
|
502
|
+
const result = await searchWithApiToken({
|
|
503
|
+
token: mock.token,
|
|
504
|
+
apiUrl,
|
|
505
|
+
query: query || undefined,
|
|
506
|
+
entity: typeof parsed.values.entity === 'string'
|
|
507
|
+
? parsed.values.entity
|
|
508
|
+
: undefined,
|
|
509
|
+
domain: typeof parsed.values.domain === 'string'
|
|
510
|
+
? parsed.values.domain
|
|
511
|
+
: undefined,
|
|
512
|
+
limit: typeof parsed.values.limit === 'string'
|
|
513
|
+
? Number(parsed.values.limit)
|
|
514
|
+
: undefined,
|
|
515
|
+
});
|
|
516
|
+
write(formatToolResult(result, json));
|
|
517
|
+
return result.isError ? 1 : 0;
|
|
518
|
+
}
|
|
519
|
+
if (shouldUseApiToken({
|
|
520
|
+
tokenValues,
|
|
521
|
+
mcpUrl,
|
|
461
522
|
apiUrl,
|
|
523
|
+
allowEnvWithoutLogin: true,
|
|
524
|
+
...(org ? { org } : {}),
|
|
525
|
+
})) {
|
|
526
|
+
const { searchWithApiToken } = await import('./open-api-client.js');
|
|
527
|
+
const result = await searchWithApiToken({
|
|
528
|
+
token: requireApiToken(tokenValues, process.env, 'search', { apiUrl }),
|
|
529
|
+
apiUrl,
|
|
530
|
+
...(org ? { org } : {}),
|
|
531
|
+
query: query || undefined,
|
|
532
|
+
entity: typeof parsed.values.entity === 'string'
|
|
533
|
+
? parsed.values.entity
|
|
534
|
+
: undefined,
|
|
535
|
+
domain: typeof parsed.values.domain === 'string'
|
|
536
|
+
? parsed.values.domain
|
|
537
|
+
: undefined,
|
|
538
|
+
limit: typeof parsed.values.limit === 'string'
|
|
539
|
+
? Number(parsed.values.limit)
|
|
540
|
+
: undefined,
|
|
541
|
+
});
|
|
542
|
+
write(formatToolResult(result, json));
|
|
543
|
+
return result.isError ? 1 : 0;
|
|
544
|
+
}
|
|
545
|
+
const args = {};
|
|
546
|
+
if (query)
|
|
547
|
+
args.query = query;
|
|
548
|
+
if (typeof parsed.values.entity === 'string')
|
|
549
|
+
args.entity = parsed.values.entity;
|
|
550
|
+
if (typeof parsed.values.domain === 'string')
|
|
551
|
+
args.domain = parsed.values.domain;
|
|
552
|
+
if (typeof parsed.values.limit === 'string')
|
|
553
|
+
args.limit = Number(parsed.values.limit);
|
|
554
|
+
const { callKodyTool } = await import('./mcp.js');
|
|
555
|
+
const result = await callKodyTool({
|
|
556
|
+
name: 'search',
|
|
557
|
+
args,
|
|
558
|
+
mcpUrl,
|
|
462
559
|
...(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
560
|
});
|
|
470
561
|
write(formatToolResult(result, json));
|
|
471
562
|
return result.isError ? 1 : 0;
|
|
472
563
|
}
|
|
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;
|
|
564
|
+
finally {
|
|
565
|
+
await closeMock?.();
|
|
566
|
+
}
|
|
491
567
|
}
|
|
492
568
|
case 'execute': {
|
|
569
|
+
const { resolveTarget } = await import('./target.js');
|
|
570
|
+
const target = resolveTarget({
|
|
571
|
+
command: 'execute',
|
|
572
|
+
flag: typeof parsed.values.target === 'string' ? parsed.values.target : undefined,
|
|
573
|
+
});
|
|
493
574
|
const invoke = typeof parsed.values.invoke === 'string' ? parsed.values.invoke : undefined;
|
|
494
575
|
const hasCodeFlag = typeof parsed.values.code === 'string';
|
|
495
576
|
const hasFileFlag = typeof parsed.values.file === 'string';
|
|
496
577
|
const positionalModule = parsed.positionals.join('\n').trim();
|
|
497
|
-
|
|
578
|
+
// --target mock always runs locally against the CLI mock (no cloud sandbox).
|
|
579
|
+
const local = parsed.values.local === true || target === 'mock';
|
|
498
580
|
const tokenValues = tokenFlagValues(parsed.values);
|
|
499
|
-
|
|
500
|
-
|
|
581
|
+
const localPackageInvoke = invoke !== undefined ? await tryResolveLocalPackageInvoke(invoke) : null;
|
|
582
|
+
if (local && invoke !== undefined && !localPackageInvoke) {
|
|
583
|
+
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
584
|
}
|
|
502
585
|
if (executeSourcesConflict({ invoke, hasCodeFlag, hasFileFlag, positionalModule })) {
|
|
503
586
|
throw new Error('--invoke cannot be combined with --code, --file, or a module string.');
|
|
@@ -506,23 +589,26 @@ async function dispatch(parsed, write, writeErr) {
|
|
|
506
589
|
assertLocalExecuteNodeEngine();
|
|
507
590
|
const fileArg = hasFileFlag ? parsed.values.file : undefined;
|
|
508
591
|
// `--code` wins over `--file`. Only attribute the path when the file is the source.
|
|
509
|
-
const modulePath =
|
|
510
|
-
|
|
592
|
+
const modulePath = localPackageInvoke?.modulePath ??
|
|
593
|
+
(!hasCodeFlag && fileArg !== undefined && fileArg !== '-' ? fileArg : undefined);
|
|
594
|
+
const code = invoke !== undefined && !localPackageInvoke
|
|
511
595
|
? undefined
|
|
512
596
|
: hasCodeFlag
|
|
513
597
|
? parsed.values.code
|
|
514
|
-
: modulePath !== undefined
|
|
598
|
+
: modulePath !== undefined && !localPackageInvoke
|
|
515
599
|
? await readFile(modulePath, 'utf8')
|
|
516
|
-
:
|
|
517
|
-
? await
|
|
518
|
-
:
|
|
600
|
+
: localPackageInvoke
|
|
601
|
+
? await readFile(localPackageInvoke.modulePath, 'utf8')
|
|
602
|
+
: fileArg === '-'
|
|
603
|
+
? await readStdin()
|
|
604
|
+
: positionalModule || undefined;
|
|
519
605
|
const credentials = parseCredentialFlags(Array.isArray(parsed.values.credential)
|
|
520
606
|
? parsed.values.credential.filter((value) => typeof value === 'string')
|
|
521
607
|
: typeof parsed.values.credential === 'string'
|
|
522
608
|
? [parsed.values.credential]
|
|
523
609
|
: undefined);
|
|
524
610
|
const args = buildExecuteToolArgs({
|
|
525
|
-
invoke,
|
|
611
|
+
invoke: localPackageInvoke ? undefined : invoke,
|
|
526
612
|
code: typeof code === 'string' ? code : undefined,
|
|
527
613
|
paramsJson: typeof parsed.values.params === 'string' ? parsed.values.params : undefined,
|
|
528
614
|
conversationId: typeof parsed.values['conversation-id'] === 'string'
|
|
@@ -530,57 +616,225 @@ async function dispatch(parsed, write, writeErr) {
|
|
|
530
616
|
: undefined,
|
|
531
617
|
credentials,
|
|
532
618
|
});
|
|
533
|
-
|
|
619
|
+
let apiUrl = apiUrlFrom({
|
|
534
620
|
apiUrl: typeof parsed.values['api-url'] === 'string'
|
|
535
621
|
? parsed.values['api-url']
|
|
536
622
|
: undefined,
|
|
537
623
|
});
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
624
|
+
let closeMock;
|
|
625
|
+
let mockToken;
|
|
626
|
+
try {
|
|
627
|
+
if (target === 'mock') {
|
|
628
|
+
const { ensureMockTarget } = await import('./mock/mock-server.js');
|
|
629
|
+
const mock = await ensureMockTarget({
|
|
630
|
+
resetStorage: parsed.values['reset-storage'] === true,
|
|
631
|
+
packageId: localPackageInvoke
|
|
632
|
+
? `local:${localPackageInvoke.kodyId}`
|
|
633
|
+
: undefined,
|
|
634
|
+
});
|
|
635
|
+
apiUrl = mock.apiUrl;
|
|
636
|
+
mockToken = mock.token;
|
|
637
|
+
closeMock = mock.started ? () => mock.server.close() : undefined;
|
|
638
|
+
}
|
|
639
|
+
if (localPackageInvoke) {
|
|
640
|
+
const { buildLocalPackageContext, runLocalPackageModule } = await import('./local-package-run.js');
|
|
641
|
+
const result = await runLocalPackageModule({
|
|
642
|
+
modulePath: localPackageInvoke.modulePath,
|
|
643
|
+
params: args.params,
|
|
644
|
+
token: mockToken ??
|
|
645
|
+
(await resolveLocalExecuteBearer({
|
|
646
|
+
tokenValues,
|
|
647
|
+
mcpUrl,
|
|
648
|
+
apiUrl,
|
|
649
|
+
purpose: 'execute local package export',
|
|
650
|
+
...(org ? { org } : {}),
|
|
651
|
+
})),
|
|
652
|
+
apiUrl,
|
|
653
|
+
target,
|
|
654
|
+
packageContext: buildLocalPackageContext({
|
|
655
|
+
kodyId: localPackageInvoke.kodyId,
|
|
656
|
+
hostedUrl: 'http://127.0.0.1:0',
|
|
657
|
+
}),
|
|
658
|
+
...(org ? { org } : {}),
|
|
659
|
+
allowPrivateNetwork: parsed.values['allow-private-network'] === true,
|
|
660
|
+
onStatus: (message) => writeErr(`${message}\n`),
|
|
661
|
+
});
|
|
662
|
+
write(formatToolResult(result, json));
|
|
663
|
+
return result.isError ? 1 : 0;
|
|
664
|
+
}
|
|
665
|
+
const useToken = local ||
|
|
666
|
+
shouldUseApiToken({
|
|
554
667
|
tokenValues,
|
|
555
668
|
mcpUrl,
|
|
556
669
|
apiUrl,
|
|
557
|
-
|
|
670
|
+
allowEnvWithoutLogin: true,
|
|
558
671
|
...(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,
|
|
672
|
+
});
|
|
673
|
+
const result = local
|
|
674
|
+
? await (await import('./local-execute.js')).runLocalExecute({
|
|
675
|
+
code: args.code,
|
|
676
|
+
...(modulePath !== undefined ? { modulePath } : {}),
|
|
569
677
|
params: args.params,
|
|
570
678
|
conversationId: args.conversationId,
|
|
571
679
|
...(credentials !== undefined ? { credentials } : {}),
|
|
572
|
-
token:
|
|
680
|
+
token: mockToken ??
|
|
681
|
+
(await resolveLocalExecuteBearer({
|
|
682
|
+
tokenValues,
|
|
683
|
+
mcpUrl,
|
|
684
|
+
apiUrl,
|
|
685
|
+
purpose: 'execute --local',
|
|
686
|
+
...(org ? { org } : {}),
|
|
687
|
+
})),
|
|
573
688
|
apiUrl,
|
|
574
689
|
...(org ? { org } : {}),
|
|
690
|
+
allowPrivateNetwork: parsed.values['allow-private-network'] === true,
|
|
691
|
+
onStatus: (message) => writeErr(`${message}\n`),
|
|
575
692
|
})
|
|
576
|
-
:
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
693
|
+
: useToken
|
|
694
|
+
? await (await import('./remote-execute.js')).runRemoteExecuteWithToken({
|
|
695
|
+
code: typeof args.code === 'string' ? args.code : undefined,
|
|
696
|
+
invoke,
|
|
697
|
+
params: args.params,
|
|
698
|
+
conversationId: args.conversationId,
|
|
699
|
+
...(credentials !== undefined ? { credentials } : {}),
|
|
700
|
+
token: requireApiToken(tokenValues, process.env, 'execute with an API token', { apiUrl }),
|
|
701
|
+
apiUrl,
|
|
702
|
+
...(org ? { org } : {}),
|
|
703
|
+
})
|
|
704
|
+
: await (await import('./mcp.js')).callKodyTool({
|
|
705
|
+
name: 'execute',
|
|
706
|
+
args,
|
|
707
|
+
mcpUrl,
|
|
708
|
+
...(org ? { org } : {}),
|
|
709
|
+
});
|
|
710
|
+
write(formatToolResult(result, json));
|
|
711
|
+
return result.isError ? 1 : 0;
|
|
712
|
+
}
|
|
713
|
+
finally {
|
|
714
|
+
await closeMock?.();
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
case 'dev': {
|
|
718
|
+
const { resolveTarget } = await import('./target.js');
|
|
719
|
+
const { parseDevSubcommand, resolveDevAuth, runDevCheck, runDevEmit, runDevFetch, runDevJob, runDevServe, runDevWebhook, } = await import('./dev-command.js');
|
|
720
|
+
const target = resolveTarget({
|
|
721
|
+
command: 'dev',
|
|
722
|
+
flag: typeof parsed.values.target === 'string' ? parsed.values.target : undefined,
|
|
723
|
+
});
|
|
724
|
+
const { subcommand, rest } = parseDevSubcommand(parsed.positionals);
|
|
725
|
+
const tokenValues = tokenFlagValues(parsed.values);
|
|
726
|
+
const prodApiUrl = apiUrlFrom({
|
|
727
|
+
apiUrl: typeof parsed.values['api-url'] === 'string'
|
|
728
|
+
? parsed.values['api-url']
|
|
729
|
+
: undefined,
|
|
730
|
+
});
|
|
731
|
+
const io = { stdout: write, stderr: writeErr };
|
|
732
|
+
const bodyPath = typeof parsed.values.body === 'string' ? parsed.values.body : undefined;
|
|
733
|
+
// `check` only bundles + validates the package tree; it never talks to
|
|
734
|
+
// mock/prod, so skip auth (and the mock server) to avoid leaking a listen
|
|
735
|
+
// handle that would keep the process alive after printing results.
|
|
736
|
+
if (subcommand === 'check') {
|
|
737
|
+
return await runDevCheck({ target, json, io });
|
|
738
|
+
}
|
|
739
|
+
const token = target === 'prod'
|
|
740
|
+
? await resolveLocalExecuteBearer({
|
|
741
|
+
tokenValues,
|
|
742
|
+
mcpUrl,
|
|
743
|
+
apiUrl: prodApiUrl,
|
|
744
|
+
purpose: 'kody dev',
|
|
745
|
+
...(org ? { org } : {}),
|
|
746
|
+
})
|
|
747
|
+
: 'mock';
|
|
748
|
+
const auth = await resolveDevAuth({
|
|
749
|
+
target,
|
|
750
|
+
token,
|
|
751
|
+
apiUrl: prodApiUrl,
|
|
752
|
+
...(org ? { org } : {}),
|
|
753
|
+
resetStorage: parsed.values['reset-storage'] === true,
|
|
754
|
+
});
|
|
755
|
+
const port = typeof parsed.values.port === 'string' ? Number(parsed.values.port) : undefined;
|
|
756
|
+
const appBasePath = typeof parsed.values['app-base-path'] === 'string'
|
|
757
|
+
? parsed.values['app-base-path']
|
|
758
|
+
: undefined;
|
|
759
|
+
try {
|
|
760
|
+
switch (subcommand) {
|
|
761
|
+
case 'serve':
|
|
762
|
+
return await runDevServe({
|
|
763
|
+
target,
|
|
764
|
+
auth,
|
|
765
|
+
port,
|
|
766
|
+
appBasePath,
|
|
767
|
+
allowPrivateNetwork: parsed.values['allow-private-network'] === true,
|
|
768
|
+
io,
|
|
769
|
+
});
|
|
770
|
+
case 'fetch': {
|
|
771
|
+
const path = rest[0];
|
|
772
|
+
if (!path) {
|
|
773
|
+
throw new Error('Usage: kody dev fetch <path> [--method GET] [--body file.json]');
|
|
774
|
+
}
|
|
775
|
+
let body;
|
|
776
|
+
if (bodyPath)
|
|
777
|
+
body = await readFile(bodyPath, 'utf8');
|
|
778
|
+
return await runDevFetch({
|
|
779
|
+
target,
|
|
780
|
+
auth,
|
|
781
|
+
path,
|
|
782
|
+
method: typeof parsed.values.method === 'string'
|
|
783
|
+
? parsed.values.method
|
|
784
|
+
: undefined,
|
|
785
|
+
body,
|
|
786
|
+
port,
|
|
787
|
+
appBasePath,
|
|
788
|
+
json,
|
|
789
|
+
io,
|
|
790
|
+
});
|
|
791
|
+
}
|
|
792
|
+
case 'job': {
|
|
793
|
+
const jobName = rest[0];
|
|
794
|
+
if (!jobName)
|
|
795
|
+
throw new Error('Usage: kody dev job <name>');
|
|
796
|
+
return await runDevJob({ target, auth, jobName, json, io });
|
|
797
|
+
}
|
|
798
|
+
case 'webhook': {
|
|
799
|
+
const webhookName = rest[0];
|
|
800
|
+
if (!webhookName || !bodyPath) {
|
|
801
|
+
throw new Error('Usage: kody dev webhook <name> --body file.json');
|
|
802
|
+
}
|
|
803
|
+
return await runDevWebhook({
|
|
804
|
+
target,
|
|
805
|
+
auth,
|
|
806
|
+
webhookName,
|
|
807
|
+
bodyPath,
|
|
808
|
+
json,
|
|
809
|
+
io,
|
|
810
|
+
});
|
|
811
|
+
}
|
|
812
|
+
case 'emit': {
|
|
813
|
+
const event = rest[0];
|
|
814
|
+
if (!event || !bodyPath) {
|
|
815
|
+
throw new Error('Usage: kody dev emit <event> --body event.json');
|
|
816
|
+
}
|
|
817
|
+
return await runDevEmit({
|
|
818
|
+
target,
|
|
819
|
+
auth,
|
|
820
|
+
event,
|
|
821
|
+
bodyPath,
|
|
822
|
+
json,
|
|
823
|
+
io,
|
|
824
|
+
});
|
|
825
|
+
}
|
|
826
|
+
default: {
|
|
827
|
+
const exhaustive = subcommand;
|
|
828
|
+
throw new Error(`Unhandled kody dev subcommand: ${String(exhaustive)}`);
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
catch (error) {
|
|
833
|
+
// Usage / surface errors after the mock started must still close it,
|
|
834
|
+
// or the listen handle keeps the process alive (same hang as check).
|
|
835
|
+
await auth.closeMock?.();
|
|
836
|
+
throw error;
|
|
837
|
+
}
|
|
584
838
|
}
|
|
585
839
|
case 'api': {
|
|
586
840
|
const operationId = parsed.positionals[0]?.trim() ?? '';
|
|
@@ -606,6 +860,34 @@ async function dispatch(parsed, write, writeErr) {
|
|
|
606
860
|
write(`${JSON.stringify(result, null, 2)}\n`);
|
|
607
861
|
return 0;
|
|
608
862
|
}
|
|
863
|
+
case 'package': {
|
|
864
|
+
const action = parsed.positionals[0];
|
|
865
|
+
if (action !== 'publish') {
|
|
866
|
+
throw new Error('Usage: kody package publish [<@owner/leaf|package_id>] [--params <json>] [--token <token>] [--api-url <url>] [--org <slug>]');
|
|
867
|
+
}
|
|
868
|
+
const apiUrl = apiUrlFrom({
|
|
869
|
+
apiUrl: typeof parsed.values['api-url'] === 'string'
|
|
870
|
+
? parsed.values['api-url']
|
|
871
|
+
: undefined,
|
|
872
|
+
});
|
|
873
|
+
const tokenValues = tokenFlagValues(parsed.values);
|
|
874
|
+
const params = packagePublishParams({
|
|
875
|
+
positional: parsed.positionals[1],
|
|
876
|
+
paramsJson: typeof parsed.values.params === 'string' ? parsed.values.params : undefined,
|
|
877
|
+
});
|
|
878
|
+
const { callOpenApiOperation } = await import('./open-api-client.js');
|
|
879
|
+
const result = await callOpenApiOperation({
|
|
880
|
+
operationId: 'packagePublishExternalPush',
|
|
881
|
+
params,
|
|
882
|
+
token: requireApiToken(tokenValues, process.env, 'package publish', {
|
|
883
|
+
apiUrl,
|
|
884
|
+
}),
|
|
885
|
+
apiUrl,
|
|
886
|
+
...(org ? { org } : {}),
|
|
887
|
+
});
|
|
888
|
+
write(`${JSON.stringify(result, null, 2)}\n`);
|
|
889
|
+
return 0;
|
|
890
|
+
}
|
|
609
891
|
case 'install': {
|
|
610
892
|
const { runInstall } = await import('./install.js');
|
|
611
893
|
const result = await runInstall({
|
|
@@ -640,6 +922,20 @@ async function readStdin() {
|
|
|
640
922
|
}
|
|
641
923
|
return Buffer.concat(chunks).toString('utf8');
|
|
642
924
|
}
|
|
925
|
+
async function tryResolveLocalPackageInvoke(invoke) {
|
|
926
|
+
const trimmed = invoke.trim();
|
|
927
|
+
if (!trimmed || trimmed.startsWith('kody:@'))
|
|
928
|
+
return null;
|
|
929
|
+
try {
|
|
930
|
+
const { loadPackageManifest, resolveExportModulePath } = await import('./package-manifest.js');
|
|
931
|
+
const manifest = await loadPackageManifest(process.cwd());
|
|
932
|
+
const modulePath = resolveExportModulePath(manifest, trimmed);
|
|
933
|
+
return { modulePath, kodyId: manifest.kodyId };
|
|
934
|
+
}
|
|
935
|
+
catch {
|
|
936
|
+
return null;
|
|
937
|
+
}
|
|
938
|
+
}
|
|
643
939
|
/** True when --invoke is set alongside any code source (--code, --file, or positional). */
|
|
644
940
|
export function executeSourcesConflict(input) {
|
|
645
941
|
return (input.invoke !== undefined &&
|
|
@@ -822,6 +1118,26 @@ export function parseApiParamsJson(paramsJson) {
|
|
|
822
1118
|
}
|
|
823
1119
|
return parsed;
|
|
824
1120
|
}
|
|
1121
|
+
const packageIdUuidPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
1122
|
+
/**
|
|
1123
|
+
* Build `packagePublishExternalPush` params for `kody package publish`.
|
|
1124
|
+
* Optional positional is `package_id` when UUID-shaped, otherwise `kody_id`.
|
|
1125
|
+
* Explicit `--params` keys win over the positional.
|
|
1126
|
+
*/
|
|
1127
|
+
export function packagePublishParams(input) {
|
|
1128
|
+
const params = parseApiParamsJson(input.paramsJson);
|
|
1129
|
+
const ref = input.positional?.trim();
|
|
1130
|
+
if (!ref)
|
|
1131
|
+
return params;
|
|
1132
|
+
if (packageIdUuidPattern.test(ref)) {
|
|
1133
|
+
if (params.package_id === undefined)
|
|
1134
|
+
params.package_id = ref;
|
|
1135
|
+
}
|
|
1136
|
+
else if (params.kody_id === undefined) {
|
|
1137
|
+
params.kody_id = ref;
|
|
1138
|
+
}
|
|
1139
|
+
return params;
|
|
1140
|
+
}
|
|
825
1141
|
/**
|
|
826
1142
|
* Prefer a scoped API token when the user passed `--token`, or when
|
|
827
1143
|
* `KODY_API_TOKEN` / a stored bootstrap token is available and there is no
|