incur 0.1.17 → 0.2.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 +86 -0
- package/SKILL.md +42 -0
- package/dist/Cli.d.ts +23 -2
- package/dist/Cli.d.ts.map +1 -1
- package/dist/Cli.js +222 -22
- package/dist/Cli.js.map +1 -1
- package/dist/Fetch.d.ts +26 -0
- package/dist/Fetch.d.ts.map +1 -0
- package/dist/Fetch.js +150 -0
- package/dist/Fetch.js.map +1 -0
- package/dist/Openapi.d.ts +20 -0
- package/dist/Openapi.d.ts.map +1 -0
- package/dist/Openapi.js +136 -0
- package/dist/Openapi.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/package.json +4 -1
- package/src/Cli.test-d.ts +2 -2
- package/src/Cli.test.ts +205 -0
- package/src/Cli.ts +258 -23
- package/src/Fetch.test.ts +274 -0
- package/src/Fetch.ts +170 -0
- package/src/Openapi.test.ts +320 -0
- package/src/Openapi.ts +196 -0
- package/src/e2e.test.ts +175 -0
- package/src/index.ts +2 -0
package/src/Cli.ts
CHANGED
|
@@ -3,6 +3,8 @@ import type { z } from 'zod'
|
|
|
3
3
|
import * as Completions from './Completions.js'
|
|
4
4
|
import type { FieldError } from './Errors.js'
|
|
5
5
|
import { IncurError, ValidationError } from './Errors.js'
|
|
6
|
+
import * as Fetch from './Fetch.js'
|
|
7
|
+
import * as Openapi from './Openapi.js'
|
|
6
8
|
import * as Formatter from './Formatter.js'
|
|
7
9
|
import * as Help from './Help.js'
|
|
8
10
|
import { detectRunner } from './internal/pm.js'
|
|
@@ -56,6 +58,11 @@ export type Cli<
|
|
|
56
58
|
vars,
|
|
57
59
|
env
|
|
58
60
|
>
|
|
61
|
+
/** Mounts a fetch handler as a command, optionally with OpenAPI spec for typed subcommands. */
|
|
62
|
+
<const name extends string>(
|
|
63
|
+
name: name,
|
|
64
|
+
definition: { basePath?: string | undefined; description?: string | undefined; fetch: FetchHandler; openapi?: Openapi.OpenAPISpec | undefined; outputPolicy?: OutputPolicy | undefined },
|
|
65
|
+
): Cli<commands, vars, env>
|
|
59
66
|
}
|
|
60
67
|
/** A short description of the CLI. */
|
|
61
68
|
description?: string | undefined
|
|
@@ -174,9 +181,11 @@ export function create(
|
|
|
174
181
|
const name = typeof nameOrDefinition === 'string' ? nameOrDefinition : nameOrDefinition.name
|
|
175
182
|
const def = typeof nameOrDefinition === 'string' ? (definition ?? {}) : nameOrDefinition
|
|
176
183
|
const rootDef = 'run' in def ? (def as CommandDefinition<any, any, any>) : undefined
|
|
184
|
+
const rootFetch = 'fetch' in def ? (def.fetch as FetchHandler) : undefined
|
|
177
185
|
|
|
178
186
|
const commands = new Map<string, CommandEntry>()
|
|
179
187
|
const middlewares: MiddlewareHandler[] = []
|
|
188
|
+
const pending: Promise<void>[] = []
|
|
180
189
|
|
|
181
190
|
const cli: Cli = {
|
|
182
191
|
name,
|
|
@@ -186,6 +195,30 @@ export function create(
|
|
|
186
195
|
|
|
187
196
|
command(nameOrCli: any, def?: any): any {
|
|
188
197
|
if (typeof nameOrCli === 'string') {
|
|
198
|
+
if (def && 'fetch' in def && typeof def.fetch === 'function') {
|
|
199
|
+
// OpenAPI + fetch → generate typed command group (async, resolved before serve)
|
|
200
|
+
if (def.openapi) {
|
|
201
|
+
pending.push(
|
|
202
|
+
Openapi.generateCommands(def.openapi, def.fetch, { basePath: def.basePath }).then((generated) => {
|
|
203
|
+
commands.set(nameOrCli, {
|
|
204
|
+
_group: true,
|
|
205
|
+
description: def.description,
|
|
206
|
+
commands: generated as Map<string, CommandEntry>,
|
|
207
|
+
...(def.outputPolicy ? { outputPolicy: def.outputPolicy } : undefined),
|
|
208
|
+
} as InternalGroup)
|
|
209
|
+
}),
|
|
210
|
+
)
|
|
211
|
+
return cli
|
|
212
|
+
}
|
|
213
|
+
commands.set(nameOrCli, {
|
|
214
|
+
_fetch: true,
|
|
215
|
+
basePath: def.basePath,
|
|
216
|
+
description: def.description,
|
|
217
|
+
fetch: def.fetch,
|
|
218
|
+
...(def.outputPolicy ? { outputPolicy: def.outputPolicy } : undefined),
|
|
219
|
+
} as InternalFetchGateway)
|
|
220
|
+
return cli
|
|
221
|
+
}
|
|
189
222
|
commands.set(nameOrCli, def)
|
|
190
223
|
return cli
|
|
191
224
|
}
|
|
@@ -209,6 +242,7 @@ export function create(
|
|
|
209
242
|
},
|
|
210
243
|
|
|
211
244
|
async serve(argv = process.argv.slice(2), serveOptions: serve.Options = {}) {
|
|
245
|
+
if (pending.length > 0) await Promise.all(pending)
|
|
212
246
|
return serveImpl(name, commands, argv, {
|
|
213
247
|
...serveOptions,
|
|
214
248
|
aliases: def.aliases,
|
|
@@ -219,6 +253,7 @@ export function create(
|
|
|
219
253
|
middlewares,
|
|
220
254
|
outputPolicy: def.outputPolicy,
|
|
221
255
|
rootCommand: rootDef,
|
|
256
|
+
rootFetch,
|
|
222
257
|
sync: def.sync,
|
|
223
258
|
vars: def.vars,
|
|
224
259
|
version: def.version,
|
|
@@ -261,6 +296,8 @@ export declare namespace create {
|
|
|
261
296
|
env?: env | undefined
|
|
262
297
|
/** Usage examples for this command. */
|
|
263
298
|
examples?: Example<args, options>[] | undefined
|
|
299
|
+
/** A fetch handler to use as the root command. All argv tokens are interpreted as path segments and curl-style flags. */
|
|
300
|
+
fetch?: FetchHandler | undefined
|
|
264
301
|
/** Default output format. Overridden by `--format` or `--json`. */
|
|
265
302
|
format?: Formatter.Format | undefined
|
|
266
303
|
/** Zod schema for named options/flags. */
|
|
@@ -689,8 +726,8 @@ async function serveImpl(
|
|
|
689
726
|
)
|
|
690
727
|
return
|
|
691
728
|
}
|
|
692
|
-
if (options.rootCommand) {
|
|
693
|
-
// Root command with no args — treat as root invocation
|
|
729
|
+
if (options.rootCommand || options.rootFetch) {
|
|
730
|
+
// Root command/fetch with no args — treat as root invocation
|
|
694
731
|
} else {
|
|
695
732
|
writeln(
|
|
696
733
|
Help.formatRoot(name, {
|
|
@@ -708,7 +745,16 @@ async function serveImpl(
|
|
|
708
745
|
const resolved =
|
|
709
746
|
filtered.length === 0 && options.rootCommand
|
|
710
747
|
? { command: options.rootCommand, path: name, rest: [] as string[] }
|
|
711
|
-
:
|
|
748
|
+
: filtered.length === 0 && options.rootFetch
|
|
749
|
+
? { fetchGateway: { _fetch: true as const, fetch: options.rootFetch, description: options.description }, middlewares: [] as MiddlewareHandler[], path: name, rest: [] as string[] }
|
|
750
|
+
: resolveCommand(commands, filtered)
|
|
751
|
+
|
|
752
|
+
// --help on a fetch gateway → show fetch-specific help
|
|
753
|
+
if (help && 'fetchGateway' in resolved) {
|
|
754
|
+
const commandName = resolved.path === name ? name : `${name} ${resolved.path}`
|
|
755
|
+
writeln(formatFetchHelp(commandName, resolved.fetchGateway.description))
|
|
756
|
+
return
|
|
757
|
+
}
|
|
712
758
|
|
|
713
759
|
// --help after a command → show help for that command
|
|
714
760
|
if (help) {
|
|
@@ -749,7 +795,8 @@ async function serveImpl(
|
|
|
749
795
|
}),
|
|
750
796
|
)
|
|
751
797
|
}
|
|
752
|
-
} else {
|
|
798
|
+
} else if ('command' in resolved) {
|
|
799
|
+
const cmd = resolved.command
|
|
753
800
|
const isRootCmd = resolved.path === name
|
|
754
801
|
const commandName = isRootCmd ? name : `${name} ${resolved.path}`
|
|
755
802
|
const helpSubcommands =
|
|
@@ -758,17 +805,17 @@ async function serveImpl(
|
|
|
758
805
|
: undefined
|
|
759
806
|
writeln(
|
|
760
807
|
Help.formatCommand(commandName, {
|
|
761
|
-
alias:
|
|
808
|
+
alias: cmd.alias as Record<string, string> | undefined,
|
|
762
809
|
aliases: isRootCmd ? options.aliases : undefined,
|
|
763
|
-
description:
|
|
810
|
+
description: cmd.description,
|
|
764
811
|
version: isRootCmd ? options.version : undefined,
|
|
765
|
-
args:
|
|
766
|
-
env:
|
|
812
|
+
args: cmd.args,
|
|
813
|
+
env: cmd.env,
|
|
767
814
|
envSource: options.env,
|
|
768
|
-
hint:
|
|
769
|
-
options:
|
|
770
|
-
examples: formatExamples(
|
|
771
|
-
usage:
|
|
815
|
+
hint: cmd.hint,
|
|
816
|
+
options: cmd.options,
|
|
817
|
+
examples: formatExamples(cmd.examples),
|
|
818
|
+
usage: cmd.usage,
|
|
772
819
|
commands: helpSubcommands,
|
|
773
820
|
root: isRootCmd,
|
|
774
821
|
}),
|
|
@@ -790,14 +837,16 @@ async function serveImpl(
|
|
|
790
837
|
const start = performance.now()
|
|
791
838
|
|
|
792
839
|
// Resolve effective format: explicit --format/--json → command default → CLI default → toon
|
|
793
|
-
const resolvedFormat = 'command' in resolved && resolved.command.format
|
|
840
|
+
const resolvedFormat = 'command' in resolved && (resolved as any).command.format
|
|
794
841
|
const format = formatExplicit ? formatFlag : resolvedFormat || options.format || 'toon'
|
|
795
842
|
|
|
796
|
-
// Fall back to root
|
|
843
|
+
// Fall back to root fetch when no subcommand matches
|
|
797
844
|
const effective =
|
|
798
|
-
'error' in resolved && options.
|
|
799
|
-
? {
|
|
800
|
-
: resolved
|
|
845
|
+
'error' in resolved && options.rootFetch && !resolved.path
|
|
846
|
+
? { fetchGateway: { _fetch: true as const, fetch: options.rootFetch, description: options.description }, middlewares: [] as MiddlewareHandler[], path: name, rest: filtered }
|
|
847
|
+
: 'error' in resolved && options.rootCommand && !resolved.path
|
|
848
|
+
? { command: options.rootCommand, path: name, rest: filtered }
|
|
849
|
+
: resolved
|
|
801
850
|
|
|
802
851
|
// Resolve outputPolicy: command/group → CLI-level → default ('all')
|
|
803
852
|
const effectiveOutputPolicy =
|
|
@@ -852,6 +901,116 @@ async function serveImpl(
|
|
|
852
901
|
return
|
|
853
902
|
}
|
|
854
903
|
|
|
904
|
+
// Fetch gateway execution path
|
|
905
|
+
if ('fetchGateway' in effective) {
|
|
906
|
+
const { fetchGateway, path, rest: fetchRest } = effective
|
|
907
|
+
const fetchMiddleware = [
|
|
908
|
+
...(options.middlewares ?? []),
|
|
909
|
+
...((effective as any).middlewares ?? []),
|
|
910
|
+
]
|
|
911
|
+
|
|
912
|
+
const runFetch = async () => {
|
|
913
|
+
const input = Fetch.parseArgv(fetchRest)
|
|
914
|
+
if (fetchGateway.basePath) input.path = fetchGateway.basePath + input.path
|
|
915
|
+
const request = Fetch.buildRequest(input)
|
|
916
|
+
const response = await fetchGateway.fetch(request)
|
|
917
|
+
|
|
918
|
+
// Streaming path — NDJSON responses pipe through handleStreaming
|
|
919
|
+
if (Fetch.isStreamingResponse(response)) {
|
|
920
|
+
const generator = Fetch.parseStreamingResponse(response)
|
|
921
|
+
await handleStreaming(generator, {
|
|
922
|
+
name,
|
|
923
|
+
path,
|
|
924
|
+
start,
|
|
925
|
+
format,
|
|
926
|
+
formatExplicit,
|
|
927
|
+
human,
|
|
928
|
+
renderOutput,
|
|
929
|
+
verbose,
|
|
930
|
+
write,
|
|
931
|
+
writeln,
|
|
932
|
+
exit,
|
|
933
|
+
})
|
|
934
|
+
return
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
const output = await Fetch.parseResponse(response)
|
|
938
|
+
|
|
939
|
+
if (output.ok) {
|
|
940
|
+
write({
|
|
941
|
+
ok: true,
|
|
942
|
+
data: output.data,
|
|
943
|
+
meta: {
|
|
944
|
+
command: path,
|
|
945
|
+
duration: `${Math.round(performance.now() - start)}ms`,
|
|
946
|
+
},
|
|
947
|
+
})
|
|
948
|
+
} else {
|
|
949
|
+
write({
|
|
950
|
+
ok: false,
|
|
951
|
+
error: {
|
|
952
|
+
code: `HTTP_${output.status}`,
|
|
953
|
+
message: typeof output.data === 'object' && output.data !== null && 'message' in output.data
|
|
954
|
+
? String((output.data as any).message)
|
|
955
|
+
: typeof output.data === 'string' ? output.data : `HTTP ${output.status}`,
|
|
956
|
+
},
|
|
957
|
+
meta: {
|
|
958
|
+
command: path,
|
|
959
|
+
duration: `${Math.round(performance.now() - start)}ms`,
|
|
960
|
+
},
|
|
961
|
+
})
|
|
962
|
+
exit(1)
|
|
963
|
+
}
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
try {
|
|
967
|
+
const cliEnv = options.envSchema ? Parser.parseEnv(options.envSchema, options.env ?? process.env) : {}
|
|
968
|
+
if (fetchMiddleware.length > 0) {
|
|
969
|
+
const varsMap: Record<string, unknown> = options.vars ? options.vars.parse({}) : {}
|
|
970
|
+
const errorFn = (opts: { code: string; message: string; retryable?: boolean | undefined; cta?: CtaBlock | undefined }): never =>
|
|
971
|
+
({ [sentinel]: 'error', ...opts }) as never
|
|
972
|
+
const mwCtx: MiddlewareContext = {
|
|
973
|
+
agent: !human,
|
|
974
|
+
command: path,
|
|
975
|
+
env: cliEnv,
|
|
976
|
+
error: errorFn,
|
|
977
|
+
name,
|
|
978
|
+
set(key: string, value: unknown) { varsMap[key] = value },
|
|
979
|
+
var: varsMap,
|
|
980
|
+
version: options.version,
|
|
981
|
+
}
|
|
982
|
+
const handleMwSentinel = (result: unknown) => {
|
|
983
|
+
if (!isSentinel(result) || result[sentinel] !== 'error') return
|
|
984
|
+
const cta = formatCtaBlock(name, result.cta)
|
|
985
|
+
write({
|
|
986
|
+
ok: false,
|
|
987
|
+
error: { code: result.code, message: result.message, ...(result.retryable !== undefined ? { retryable: result.retryable } : undefined) },
|
|
988
|
+
meta: { command: path, duration: `${Math.round(performance.now() - start)}ms`, ...(cta ? { cta } : undefined) },
|
|
989
|
+
})
|
|
990
|
+
exit(1)
|
|
991
|
+
}
|
|
992
|
+
const composed = fetchMiddleware.reduceRight(
|
|
993
|
+
(next: () => Promise<void>, mw) => async () => { handleMwSentinel(await mw(mwCtx, next)) },
|
|
994
|
+
runFetch,
|
|
995
|
+
)
|
|
996
|
+
await composed()
|
|
997
|
+
} else {
|
|
998
|
+
await runFetch()
|
|
999
|
+
}
|
|
1000
|
+
} catch (error) {
|
|
1001
|
+
write({
|
|
1002
|
+
ok: false,
|
|
1003
|
+
error: {
|
|
1004
|
+
code: error instanceof IncurError ? error.code : 'UNKNOWN',
|
|
1005
|
+
message: error instanceof Error ? error.message : String(error),
|
|
1006
|
+
},
|
|
1007
|
+
meta: { command: path, duration: `${Math.round(performance.now() - start)}ms` },
|
|
1008
|
+
})
|
|
1009
|
+
exit(1)
|
|
1010
|
+
}
|
|
1011
|
+
return
|
|
1012
|
+
}
|
|
1013
|
+
|
|
855
1014
|
const { command, path, rest } = effective
|
|
856
1015
|
|
|
857
1016
|
// Collect middleware: root CLI + groups traversed + per-command
|
|
@@ -1090,6 +1249,13 @@ function resolveCommand(
|
|
|
1090
1249
|
path: string
|
|
1091
1250
|
rest: string[]
|
|
1092
1251
|
}
|
|
1252
|
+
| {
|
|
1253
|
+
fetchGateway: InternalFetchGateway
|
|
1254
|
+
middlewares: MiddlewareHandler[]
|
|
1255
|
+
outputPolicy?: OutputPolicy | undefined
|
|
1256
|
+
path: string
|
|
1257
|
+
rest: string[]
|
|
1258
|
+
}
|
|
1093
1259
|
| {
|
|
1094
1260
|
help: true
|
|
1095
1261
|
path: string
|
|
@@ -1107,6 +1273,18 @@ function resolveCommand(
|
|
|
1107
1273
|
let inheritedOutputPolicy: OutputPolicy | undefined
|
|
1108
1274
|
const collectedMiddlewares: MiddlewareHandler[] = []
|
|
1109
1275
|
|
|
1276
|
+
// Fetch gateway — all remaining tokens go to the fetch handler
|
|
1277
|
+
if (isFetchGateway(entry)) {
|
|
1278
|
+
const outputPolicy = entry.outputPolicy ?? inheritedOutputPolicy
|
|
1279
|
+
return {
|
|
1280
|
+
fetchGateway: entry,
|
|
1281
|
+
middlewares: collectedMiddlewares,
|
|
1282
|
+
path: path.join(' '),
|
|
1283
|
+
rest: remaining,
|
|
1284
|
+
...(outputPolicy ? { outputPolicy } : undefined),
|
|
1285
|
+
}
|
|
1286
|
+
}
|
|
1287
|
+
|
|
1110
1288
|
while (isGroup(entry)) {
|
|
1111
1289
|
if (entry.outputPolicy) inheritedOutputPolicy = entry.outputPolicy
|
|
1112
1290
|
if (entry.middlewares) collectedMiddlewares.push(...entry.middlewares)
|
|
@@ -1127,6 +1305,17 @@ function resolveCommand(
|
|
|
1127
1305
|
path.push(next)
|
|
1128
1306
|
remaining = remaining.slice(1)
|
|
1129
1307
|
entry = child
|
|
1308
|
+
|
|
1309
|
+
if (isFetchGateway(entry)) {
|
|
1310
|
+
const outputPolicy = entry.outputPolicy ?? inheritedOutputPolicy
|
|
1311
|
+
return {
|
|
1312
|
+
fetchGateway: entry,
|
|
1313
|
+
middlewares: collectedMiddlewares,
|
|
1314
|
+
path: path.join(' '),
|
|
1315
|
+
rest: remaining,
|
|
1316
|
+
...(outputPolicy ? { outputPolicy } : undefined),
|
|
1317
|
+
}
|
|
1318
|
+
}
|
|
1130
1319
|
}
|
|
1131
1320
|
|
|
1132
1321
|
const outputPolicy = entry.outputPolicy ?? inheritedOutputPolicy
|
|
@@ -1161,6 +1350,8 @@ declare namespace serveImpl {
|
|
|
1161
1350
|
| undefined
|
|
1162
1351
|
/** Root command handler, invoked when no subcommand matches. */
|
|
1163
1352
|
rootCommand?: CommandDefinition<any, any, any> | undefined
|
|
1353
|
+
/** Root fetch handler, invoked when no subcommand matches and no rootCommand is set. */
|
|
1354
|
+
rootFetch?: FetchHandler | undefined
|
|
1164
1355
|
sync?:
|
|
1165
1356
|
| {
|
|
1166
1357
|
cwd?: string | undefined
|
|
@@ -1212,24 +1403,45 @@ function collectHelpCommands(
|
|
|
1212
1403
|
): { name: string; description?: string | undefined }[] {
|
|
1213
1404
|
const result: { name: string; description?: string | undefined }[] = []
|
|
1214
1405
|
for (const [name, entry] of commands) {
|
|
1215
|
-
|
|
1216
|
-
else result.push({ name, description: entry.description })
|
|
1406
|
+
result.push({ name, description: entry.description })
|
|
1217
1407
|
}
|
|
1218
1408
|
return result.sort((a, b) => a.name.localeCompare(b.name))
|
|
1219
1409
|
}
|
|
1220
1410
|
|
|
1411
|
+
/** @internal Formats help text for a fetch gateway command. */
|
|
1412
|
+
function formatFetchHelp(name: string, description?: string): string {
|
|
1413
|
+
const lines: string[] = []
|
|
1414
|
+
if (description) lines.push(`${name} — ${description}`)
|
|
1415
|
+
else lines.push(name)
|
|
1416
|
+
lines.push('')
|
|
1417
|
+
lines.push(`Usage: ${name} <path> [options]`)
|
|
1418
|
+
lines.push('')
|
|
1419
|
+
lines.push('Path segments are joined into the request URL path.')
|
|
1420
|
+
lines.push('')
|
|
1421
|
+
lines.push('Options:')
|
|
1422
|
+
lines.push(' -X, --method <METHOD> HTTP method (default: GET, POST if body present)')
|
|
1423
|
+
lines.push(' -H, --header "Key: Val" Set a request header (repeatable)')
|
|
1424
|
+
lines.push(' -d, --data <json> Request body (implies POST)')
|
|
1425
|
+
lines.push(' --body <json> Request body (implies POST)')
|
|
1426
|
+
lines.push(' --<key> <value> Query string parameter')
|
|
1427
|
+
return lines.join('\n')
|
|
1428
|
+
}
|
|
1429
|
+
|
|
1221
1430
|
/** Shape of the commands map accumulated through `.command()` chains. */
|
|
1222
1431
|
export type CommandsMap = Record<
|
|
1223
1432
|
string,
|
|
1224
1433
|
{ args: Record<string, unknown>; options: Record<string, unknown> }
|
|
1225
1434
|
>
|
|
1226
1435
|
|
|
1227
|
-
/** @internal Entry stored in a command map — either a leaf definition or a
|
|
1228
|
-
type CommandEntry = CommandDefinition<any, any, any> | InternalGroup
|
|
1436
|
+
/** @internal Entry stored in a command map — either a leaf definition, a group, or a fetch gateway. */
|
|
1437
|
+
type CommandEntry = CommandDefinition<any, any, any> | InternalGroup | InternalFetchGateway
|
|
1229
1438
|
|
|
1230
1439
|
/** Controls when output data is displayed. `'all'` displays to both humans and agents. `'agent-only'` suppresses data output in human/TTY mode. */
|
|
1231
1440
|
export type OutputPolicy = 'agent-only' | 'all'
|
|
1232
1441
|
|
|
1442
|
+
/** A standard Fetch API handler. */
|
|
1443
|
+
type FetchHandler = (req: Request) => Response | Promise<Response>
|
|
1444
|
+
|
|
1233
1445
|
/** @internal A command group's internal storage. */
|
|
1234
1446
|
type InternalGroup = {
|
|
1235
1447
|
_group: true
|
|
@@ -1239,11 +1451,25 @@ type InternalGroup = {
|
|
|
1239
1451
|
commands: Map<string, CommandEntry>
|
|
1240
1452
|
}
|
|
1241
1453
|
|
|
1454
|
+
/** @internal A fetch gateway entry. */
|
|
1455
|
+
type InternalFetchGateway = {
|
|
1456
|
+
_fetch: true
|
|
1457
|
+
basePath?: string | undefined
|
|
1458
|
+
description?: string | undefined
|
|
1459
|
+
fetch: FetchHandler
|
|
1460
|
+
outputPolicy?: OutputPolicy | undefined
|
|
1461
|
+
}
|
|
1462
|
+
|
|
1242
1463
|
/** @internal Type guard for command groups. */
|
|
1243
1464
|
function isGroup(entry: CommandEntry): entry is InternalGroup {
|
|
1244
1465
|
return '_group' in entry
|
|
1245
1466
|
}
|
|
1246
1467
|
|
|
1468
|
+
/** @internal Type guard for fetch gateways. */
|
|
1469
|
+
function isFetchGateway(entry: CommandEntry): entry is InternalFetchGateway {
|
|
1470
|
+
return '_fetch' in entry
|
|
1471
|
+
}
|
|
1472
|
+
|
|
1247
1473
|
/** @internal Maps CLI instances to their command maps. */
|
|
1248
1474
|
export const toCommands = new WeakMap<Cli, Map<string, CommandEntry>>()
|
|
1249
1475
|
|
|
@@ -1570,7 +1796,11 @@ function collectCommands(
|
|
|
1570
1796
|
const result: ReturnType<typeof collectCommands> = []
|
|
1571
1797
|
for (const [name, entry] of commands) {
|
|
1572
1798
|
const path = [...prefix, name]
|
|
1573
|
-
if (
|
|
1799
|
+
if (isFetchGateway(entry)) {
|
|
1800
|
+
const cmd: (typeof result)[number] = { name: path.join(' ') }
|
|
1801
|
+
if (entry.description) cmd.description = entry.description
|
|
1802
|
+
result.push(cmd)
|
|
1803
|
+
} else if (isGroup(entry)) {
|
|
1574
1804
|
result.push(...collectCommands(entry.commands, path))
|
|
1575
1805
|
} else {
|
|
1576
1806
|
const cmd: (typeof result)[number] = { name: path.join(' ') }
|
|
@@ -1609,7 +1839,12 @@ function collectSkillCommands(
|
|
|
1609
1839
|
const result: Skill.CommandInfo[] = []
|
|
1610
1840
|
for (const [name, entry] of commands) {
|
|
1611
1841
|
const path = [...prefix, name]
|
|
1612
|
-
if (
|
|
1842
|
+
if (isFetchGateway(entry)) {
|
|
1843
|
+
const cmd: Skill.CommandInfo = { name: path.join(' ') }
|
|
1844
|
+
if (entry.description) cmd.description = entry.description
|
|
1845
|
+
cmd.hint = 'Fetch gateway. Pass path segments and curl-style flags (-X, -H, -d, --key value).'
|
|
1846
|
+
result.push(cmd)
|
|
1847
|
+
} else if (isGroup(entry)) {
|
|
1613
1848
|
if (entry.description) groups.set(path.join(' '), entry.description)
|
|
1614
1849
|
result.push(...collectSkillCommands(entry.commands, path, groups))
|
|
1615
1850
|
} else {
|