incur 0.3.2 → 0.4.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 +22 -26
- package/SKILL.md +17 -20
- package/dist/Cli.d.ts +17 -15
- package/dist/Cli.d.ts.map +1 -1
- package/dist/Cli.js +144 -24
- package/dist/Cli.js.map +1 -1
- package/dist/Fetch.d.ts.map +1 -1
- package/dist/Fetch.js.map +1 -1
- package/dist/Filter.js.map +1 -1
- package/dist/Help.js +4 -4
- package/dist/Help.js.map +1 -1
- package/dist/Openapi.d.ts.map +1 -1
- package/dist/Openapi.js +10 -2
- package/dist/Openapi.js.map +1 -1
- package/dist/Skill.d.ts.map +1 -1
- package/dist/Skill.js.map +1 -1
- package/dist/bin.d.ts +1 -1
- package/dist/bin.d.ts.map +1 -1
- package/dist/middleware.d.ts +8 -4
- package/dist/middleware.d.ts.map +1 -1
- package/dist/middleware.js +1 -1
- package/dist/middleware.js.map +1 -1
- package/package.json +1 -1
- package/src/Cli.test-d.ts +37 -0
- package/src/Cli.test.ts +31 -24
- package/src/Cli.ts +260 -76
- package/src/Fetch.test.ts +3 -13
- package/src/Fetch.ts +1 -2
- package/src/Filter.ts +7 -3
- package/src/Help.test.ts +2 -2
- package/src/Help.ts +2 -2
- package/src/Openapi.test.ts +89 -32
- package/src/Openapi.ts +15 -6
- package/src/Skill.test.ts +1 -3
- package/src/Skill.ts +9 -2
- package/src/e2e.test.ts +74 -34
- package/src/middleware.ts +12 -3
package/src/Cli.ts
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
|
+
import { estimateTokenCount, sliceByTokens } from 'tokenx'
|
|
1
2
|
import type { z } from 'zod'
|
|
2
3
|
|
|
3
|
-
import { estimateTokenCount, sliceByTokens } from 'tokenx'
|
|
4
4
|
import * as Completions from './Completions.js'
|
|
5
|
-
import * as Filter from './Filter.js'
|
|
6
5
|
import type { FieldError } from './Errors.js'
|
|
7
6
|
import { IncurError, ValidationError } from './Errors.js'
|
|
8
7
|
import * as Fetch from './Fetch.js'
|
|
9
|
-
import * as
|
|
8
|
+
import * as Filter from './Filter.js'
|
|
10
9
|
import * as Formatter from './Formatter.js'
|
|
11
10
|
import * as Help from './Help.js'
|
|
12
11
|
import { detectRunner } from './internal/pm.js'
|
|
13
12
|
import type { OneOf } from './internal/types.js'
|
|
14
13
|
import * as Mcp from './Mcp.js'
|
|
15
14
|
import type { Context as MiddlewareContext, Handler as MiddlewareHandler } from './middleware.js'
|
|
15
|
+
import * as Openapi from './Openapi.js'
|
|
16
16
|
export type { MiddlewareHandler }
|
|
17
17
|
import * as Parser from './Parser.js'
|
|
18
18
|
import type { Register } from './Register.js'
|
|
@@ -26,6 +26,7 @@ export type Cli<
|
|
|
26
26
|
commands extends CommandsMap = {},
|
|
27
27
|
vars extends z.ZodObject<any> | undefined = undefined,
|
|
28
28
|
env extends z.ZodObject<any> | undefined = undefined,
|
|
29
|
+
opts extends z.ZodObject<any> | undefined = undefined,
|
|
29
30
|
> = {
|
|
30
31
|
/** Registers a root command or mounts a sub-CLI as a command group. */
|
|
31
32
|
command: {
|
|
@@ -42,29 +43,42 @@ export type Cli<
|
|
|
42
43
|
): Cli<
|
|
43
44
|
commands & { [key in name]: { args: InferOutput<args>; options: InferOutput<options> } },
|
|
44
45
|
vars,
|
|
45
|
-
env
|
|
46
|
+
env,
|
|
47
|
+
opts
|
|
46
48
|
>
|
|
47
49
|
/** Mounts a sub-CLI as a command group. */
|
|
48
50
|
<const name extends string, const sub extends CommandsMap>(
|
|
49
|
-
cli: Cli<sub, any, any> & { name: name },
|
|
50
|
-
): Cli<
|
|
51
|
+
cli: Cli<sub, any, any, any> & { name: name },
|
|
52
|
+
): Cli<
|
|
53
|
+
commands & { [key in keyof sub & string as `${name} ${key}`]: sub[key] },
|
|
54
|
+
vars,
|
|
55
|
+
env,
|
|
56
|
+
opts
|
|
57
|
+
>
|
|
51
58
|
/** Mounts a root CLI as a single command. */
|
|
52
59
|
<
|
|
53
60
|
const name extends string,
|
|
54
61
|
const args extends z.ZodObject<any> | undefined,
|
|
55
|
-
const
|
|
62
|
+
const cmdOpts extends z.ZodObject<any> | undefined,
|
|
56
63
|
>(
|
|
57
|
-
cli: Root<args,
|
|
64
|
+
cli: Root<args, cmdOpts> & { name: name },
|
|
58
65
|
): Cli<
|
|
59
|
-
commands & { [key in name]: { args: InferOutput<args>; options: InferOutput<
|
|
66
|
+
commands & { [key in name]: { args: InferOutput<args>; options: InferOutput<cmdOpts> } },
|
|
60
67
|
vars,
|
|
61
|
-
env
|
|
68
|
+
env,
|
|
69
|
+
opts
|
|
62
70
|
>
|
|
63
71
|
/** Mounts a fetch handler as a command, optionally with OpenAPI spec for typed subcommands. */
|
|
64
72
|
<const name extends string>(
|
|
65
73
|
name: name,
|
|
66
|
-
definition: {
|
|
67
|
-
|
|
74
|
+
definition: {
|
|
75
|
+
basePath?: string | undefined
|
|
76
|
+
description?: string | undefined
|
|
77
|
+
fetch: FetchHandler
|
|
78
|
+
openapi?: Openapi.OpenAPISpec | undefined
|
|
79
|
+
outputPolicy?: OutputPolicy | undefined
|
|
80
|
+
},
|
|
81
|
+
): Cli<commands, vars, env, opts>
|
|
68
82
|
}
|
|
69
83
|
/** A short description of the CLI. */
|
|
70
84
|
description?: string | undefined
|
|
@@ -76,8 +90,10 @@ export type Cli<
|
|
|
76
90
|
fetch(req: Request): Promise<Response>
|
|
77
91
|
/** Parses argv, runs the matched command, and writes the output envelope to stdout. */
|
|
78
92
|
serve(argv?: string[], options?: serve.Options): Promise<void>
|
|
93
|
+
/** The options schema, if declared. Use `typeof cli.options` with `middleware<vars, env, options>()` for typed middleware. */
|
|
94
|
+
options: opts
|
|
79
95
|
/** Registers middleware that runs around every command. */
|
|
80
|
-
use(handler: MiddlewareHandler<vars, env>): Cli<commands, vars, env>
|
|
96
|
+
use(handler: MiddlewareHandler<vars, env, opts>): Cli<commands, vars, env, opts>
|
|
81
97
|
/** The vars schema, if declared. Use `typeof cli.vars` with `middleware<vars, env>()` for typed middleware. */
|
|
82
98
|
vars: vars
|
|
83
99
|
}
|
|
@@ -145,7 +161,12 @@ export function create<
|
|
|
145
161
|
>(
|
|
146
162
|
name: string,
|
|
147
163
|
definition: create.Options<args, env, opts, output, vars> & { run: Function },
|
|
148
|
-
): Cli<
|
|
164
|
+
): Cli<
|
|
165
|
+
{ [key in typeof name]: { args: InferOutput<args>; options: InferOutput<opts> } },
|
|
166
|
+
vars,
|
|
167
|
+
env,
|
|
168
|
+
opts
|
|
169
|
+
>
|
|
149
170
|
/** Creates a router CLI that registers subcommands. */
|
|
150
171
|
export function create<
|
|
151
172
|
const args extends z.ZodObject<any> | undefined = undefined,
|
|
@@ -153,7 +174,10 @@ export function create<
|
|
|
153
174
|
const opts extends z.ZodObject<any> | undefined = undefined,
|
|
154
175
|
const output extends z.ZodType | undefined = undefined,
|
|
155
176
|
const vars extends z.ZodObject<any> | undefined = undefined,
|
|
156
|
-
>(
|
|
177
|
+
>(
|
|
178
|
+
name: string,
|
|
179
|
+
definition?: create.Options<args, env, opts, output, vars>,
|
|
180
|
+
): Cli<{}, vars, env, opts>
|
|
157
181
|
/** Creates a CLI with a root handler from a single options object. Can still register subcommands. */
|
|
158
182
|
export function create<
|
|
159
183
|
const args extends z.ZodObject<any> | undefined = undefined,
|
|
@@ -168,7 +192,8 @@ export function create<
|
|
|
168
192
|
[key in (typeof definition)['name']]: { args: InferOutput<args>; options: InferOutput<opts> }
|
|
169
193
|
},
|
|
170
194
|
vars,
|
|
171
|
-
env
|
|
195
|
+
env,
|
|
196
|
+
opts
|
|
172
197
|
>
|
|
173
198
|
/** Creates a router CLI from a single options object (e.g. package.json). */
|
|
174
199
|
export function create<
|
|
@@ -177,7 +202,9 @@ export function create<
|
|
|
177
202
|
const opts extends z.ZodObject<any> | undefined = undefined,
|
|
178
203
|
const output extends z.ZodType | undefined = undefined,
|
|
179
204
|
const vars extends z.ZodObject<any> | undefined = undefined,
|
|
180
|
-
>(
|
|
205
|
+
>(
|
|
206
|
+
definition: create.Options<args, env, opts, output, vars> & { name: string },
|
|
207
|
+
): Cli<{}, vars, env, opts>
|
|
181
208
|
export function create(
|
|
182
209
|
nameOrDefinition: string | (any & { name: string }),
|
|
183
210
|
definition?: any,
|
|
@@ -196,6 +223,7 @@ export function create(
|
|
|
196
223
|
name,
|
|
197
224
|
description: def.description,
|
|
198
225
|
env: def.env,
|
|
226
|
+
options: def.options,
|
|
199
227
|
vars: def.vars,
|
|
200
228
|
|
|
201
229
|
command(nameOrCli: any, def?: any): any {
|
|
@@ -204,14 +232,16 @@ export function create(
|
|
|
204
232
|
// OpenAPI + fetch → generate typed command group (async, resolved before serve)
|
|
205
233
|
if (def.openapi) {
|
|
206
234
|
pending.push(
|
|
207
|
-
Openapi.generateCommands(def.openapi, def.fetch, { basePath: def.basePath }).then(
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
235
|
+
Openapi.generateCommands(def.openapi, def.fetch, { basePath: def.basePath }).then(
|
|
236
|
+
(generated) => {
|
|
237
|
+
commands.set(nameOrCli, {
|
|
238
|
+
_group: true,
|
|
239
|
+
description: def.description,
|
|
240
|
+
commands: generated as Map<string, CommandEntry>,
|
|
241
|
+
...(def.outputPolicy ? { outputPolicy: def.outputPolicy } : undefined),
|
|
242
|
+
} as InternalGroup)
|
|
243
|
+
},
|
|
244
|
+
),
|
|
215
245
|
)
|
|
216
246
|
return cli
|
|
217
247
|
}
|
|
@@ -266,6 +296,7 @@ export function create(
|
|
|
266
296
|
format: def.format,
|
|
267
297
|
mcp: def.mcp,
|
|
268
298
|
middlewares,
|
|
299
|
+
optionsSchema: def.options,
|
|
269
300
|
outputPolicy: def.outputPolicy,
|
|
270
301
|
rootCommand: rootDef,
|
|
271
302
|
rootFetch,
|
|
@@ -786,7 +817,16 @@ async function serveImpl(
|
|
|
786
817
|
filtered.length === 0 && options.rootCommand
|
|
787
818
|
? { command: options.rootCommand, path: name, rest: [] as string[] }
|
|
788
819
|
: filtered.length === 0 && options.rootFetch
|
|
789
|
-
? {
|
|
820
|
+
? {
|
|
821
|
+
fetchGateway: {
|
|
822
|
+
_fetch: true as const,
|
|
823
|
+
fetch: options.rootFetch,
|
|
824
|
+
description: options.description,
|
|
825
|
+
},
|
|
826
|
+
middlewares: [] as MiddlewareHandler[],
|
|
827
|
+
path: name,
|
|
828
|
+
rest: [] as string[],
|
|
829
|
+
}
|
|
790
830
|
: resolveCommand(commands, filtered)
|
|
791
831
|
|
|
792
832
|
// --help on a fetch gateway → show fetch-specific help
|
|
@@ -909,6 +949,14 @@ async function serveImpl(
|
|
|
909
949
|
|
|
910
950
|
const start = performance.now()
|
|
911
951
|
|
|
952
|
+
// Parse root CLI-level options (available to middleware via c.options)
|
|
953
|
+
const rootOptions = options.optionsSchema
|
|
954
|
+
? Parser.parse(filtered, {
|
|
955
|
+
alias: options.rootCommand?.alias as Record<string, string> | undefined,
|
|
956
|
+
options: options.optionsSchema,
|
|
957
|
+
}).options
|
|
958
|
+
: {}
|
|
959
|
+
|
|
912
960
|
// Resolve effective format: explicit --format/--json → command default → CLI default → toon
|
|
913
961
|
const resolvedFormat = 'command' in resolved && (resolved as any).command.format
|
|
914
962
|
const format = formatExplicit ? formatFlag : resolvedFormat || options.format || 'toon'
|
|
@@ -916,7 +964,16 @@ async function serveImpl(
|
|
|
916
964
|
// Fall back to root fetch when no subcommand matches
|
|
917
965
|
const effective =
|
|
918
966
|
'error' in resolved && options.rootFetch && !resolved.path
|
|
919
|
-
? {
|
|
967
|
+
? {
|
|
968
|
+
fetchGateway: {
|
|
969
|
+
_fetch: true as const,
|
|
970
|
+
fetch: options.rootFetch,
|
|
971
|
+
description: options.description,
|
|
972
|
+
},
|
|
973
|
+
middlewares: [] as MiddlewareHandler[],
|
|
974
|
+
path: name,
|
|
975
|
+
rest: filtered,
|
|
976
|
+
}
|
|
920
977
|
: 'error' in resolved && options.rootCommand && !resolved.path
|
|
921
978
|
? { command: options.rootCommand, path: name, rest: filtered }
|
|
922
979
|
: resolved
|
|
@@ -928,7 +985,11 @@ async function serveImpl(
|
|
|
928
985
|
|
|
929
986
|
const filterPaths = filterOutput ? Filter.parse(filterOutput) : undefined
|
|
930
987
|
|
|
931
|
-
function truncate(s: string): {
|
|
988
|
+
function truncate(s: string): {
|
|
989
|
+
text: string
|
|
990
|
+
truncated: boolean
|
|
991
|
+
nextOffset?: number | undefined
|
|
992
|
+
} {
|
|
932
993
|
if (tokenLimit == null && tokenOffset == null) return { text: s, truncated: false }
|
|
933
994
|
const total = estimateTokenCount(s)
|
|
934
995
|
const offset = tokenOffset ?? 0
|
|
@@ -964,11 +1025,12 @@ async function serveImpl(
|
|
|
964
1025
|
if (verbose) {
|
|
965
1026
|
if (tokenLimit != null || tokenOffset != null) {
|
|
966
1027
|
// Truncate data separately so meta (including nextOffset) is always visible
|
|
967
|
-
const dataFormatted =
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
1028
|
+
const dataFormatted =
|
|
1029
|
+
output.ok && output.data != null
|
|
1030
|
+
? Formatter.format(output.data, format)
|
|
1031
|
+
: !output.ok
|
|
1032
|
+
? Formatter.format(output.error, format)
|
|
1033
|
+
: ''
|
|
972
1034
|
const t = truncate(dataFormatted)
|
|
973
1035
|
if (t.truncated) {
|
|
974
1036
|
const envelope: Record<string, unknown> = output.ok
|
|
@@ -1070,9 +1132,12 @@ async function serveImpl(
|
|
|
1070
1132
|
ok: false,
|
|
1071
1133
|
error: {
|
|
1072
1134
|
code: `HTTP_${output.status}`,
|
|
1073
|
-
message:
|
|
1074
|
-
|
|
1075
|
-
|
|
1135
|
+
message:
|
|
1136
|
+
typeof output.data === 'object' && output.data !== null && 'message' in output.data
|
|
1137
|
+
? String((output.data as any).message)
|
|
1138
|
+
: typeof output.data === 'string'
|
|
1139
|
+
? output.data
|
|
1140
|
+
: `HTTP ${output.status}`,
|
|
1076
1141
|
},
|
|
1077
1142
|
meta: {
|
|
1078
1143
|
command: path,
|
|
@@ -1084,11 +1149,18 @@ async function serveImpl(
|
|
|
1084
1149
|
}
|
|
1085
1150
|
|
|
1086
1151
|
try {
|
|
1087
|
-
const cliEnv = options.envSchema
|
|
1152
|
+
const cliEnv = options.envSchema
|
|
1153
|
+
? Parser.parseEnv(options.envSchema, options.env ?? process.env)
|
|
1154
|
+
: {}
|
|
1088
1155
|
if (fetchMiddleware.length > 0) {
|
|
1089
1156
|
const varsMap: Record<string, unknown> = options.vars ? options.vars.parse({}) : {}
|
|
1090
|
-
const errorFn = (opts: {
|
|
1091
|
-
|
|
1157
|
+
const errorFn = (opts: {
|
|
1158
|
+
code: string
|
|
1159
|
+
exitCode?: number | undefined
|
|
1160
|
+
message: string
|
|
1161
|
+
retryable?: boolean | undefined
|
|
1162
|
+
cta?: CtaBlock | undefined
|
|
1163
|
+
}): never => ({ [sentinel]: 'error', ...opts }) as never
|
|
1092
1164
|
const mwCtx: MiddlewareContext = {
|
|
1093
1165
|
agent: !human,
|
|
1094
1166
|
command: path,
|
|
@@ -1097,7 +1169,10 @@ async function serveImpl(
|
|
|
1097
1169
|
format,
|
|
1098
1170
|
formatExplicit,
|
|
1099
1171
|
name,
|
|
1100
|
-
|
|
1172
|
+
options: rootOptions,
|
|
1173
|
+
set(key: string, value: unknown) {
|
|
1174
|
+
varsMap[key] = value
|
|
1175
|
+
},
|
|
1101
1176
|
var: varsMap,
|
|
1102
1177
|
version: options.version,
|
|
1103
1178
|
}
|
|
@@ -1107,13 +1182,23 @@ async function serveImpl(
|
|
|
1107
1182
|
const cta = formatCtaBlock(name, err.cta)
|
|
1108
1183
|
write({
|
|
1109
1184
|
ok: false,
|
|
1110
|
-
error: {
|
|
1111
|
-
|
|
1185
|
+
error: {
|
|
1186
|
+
code: err.code,
|
|
1187
|
+
message: err.message,
|
|
1188
|
+
...(err.retryable !== undefined ? { retryable: err.retryable } : undefined),
|
|
1189
|
+
},
|
|
1190
|
+
meta: {
|
|
1191
|
+
command: path,
|
|
1192
|
+
duration: `${Math.round(performance.now() - start)}ms`,
|
|
1193
|
+
...(cta ? { cta } : undefined),
|
|
1194
|
+
},
|
|
1112
1195
|
})
|
|
1113
1196
|
exit(err.exitCode ?? 1)
|
|
1114
1197
|
}
|
|
1115
1198
|
const composed = fetchMiddleware.reduceRight(
|
|
1116
|
-
(next: () => Promise<void>, mw) => async () => {
|
|
1199
|
+
(next: () => Promise<void>, mw) => async () => {
|
|
1200
|
+
handleMwSentinel(await mw(mwCtx, next))
|
|
1201
|
+
},
|
|
1117
1202
|
runFetch,
|
|
1118
1203
|
)
|
|
1119
1204
|
await composed()
|
|
@@ -1275,6 +1360,7 @@ async function serveImpl(
|
|
|
1275
1360
|
format,
|
|
1276
1361
|
formatExplicit,
|
|
1277
1362
|
name,
|
|
1363
|
+
options: rootOptions,
|
|
1278
1364
|
set(key: string, value: unknown) {
|
|
1279
1365
|
varsMap[key] = value
|
|
1280
1366
|
},
|
|
@@ -1344,7 +1430,9 @@ async function serveImpl(
|
|
|
1344
1430
|
/** @internal Options for fetchImpl. */
|
|
1345
1431
|
declare namespace fetchImpl {
|
|
1346
1432
|
type Options = {
|
|
1347
|
-
mcpHandler?:
|
|
1433
|
+
mcpHandler?:
|
|
1434
|
+
| ((req: Request, commands: Map<string, CommandEntry>) => Promise<Response>)
|
|
1435
|
+
| undefined
|
|
1348
1436
|
middlewares?: MiddlewareHandler[] | undefined
|
|
1349
1437
|
rootCommand?: CommandDefinition<any, any, any> | undefined
|
|
1350
1438
|
vars?: z.ZodObject<any> | undefined
|
|
@@ -1358,9 +1446,8 @@ function createMcpHttpHandler(name: string, version: string) {
|
|
|
1358
1446
|
return async (req: Request, commands: Map<string, CommandEntry>): Promise<Response> => {
|
|
1359
1447
|
if (!transport) {
|
|
1360
1448
|
const { McpServer } = await import('@modelcontextprotocol/sdk/server/mcp.js')
|
|
1361
|
-
const { WebStandardStreamableHTTPServerTransport } =
|
|
1362
|
-
'@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js'
|
|
1363
|
-
)
|
|
1449
|
+
const { WebStandardStreamableHTTPServerTransport } =
|
|
1450
|
+
await import('@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js')
|
|
1364
1451
|
|
|
1365
1452
|
const server = new McpServer({ name, version })
|
|
1366
1453
|
|
|
@@ -1455,12 +1542,12 @@ async function fetchImpl(
|
|
|
1455
1542
|
|
|
1456
1543
|
// Parse options from search params (GET) or body (non-GET)
|
|
1457
1544
|
let inputOptions: Record<string, unknown> = {}
|
|
1458
|
-
if (req.method === 'GET')
|
|
1459
|
-
for (const [key, value] of url.searchParams) inputOptions[key] = value
|
|
1545
|
+
if (req.method === 'GET') for (const [key, value] of url.searchParams) inputOptions[key] = value
|
|
1460
1546
|
else {
|
|
1461
1547
|
try {
|
|
1462
1548
|
const contentType = req.headers.get('content-type') ?? ''
|
|
1463
|
-
if (contentType.includes('application/json'))
|
|
1549
|
+
if (contentType.includes('application/json'))
|
|
1550
|
+
inputOptions = (await req.json()) as Record<string, unknown>
|
|
1464
1551
|
} catch {}
|
|
1465
1552
|
}
|
|
1466
1553
|
|
|
@@ -1477,7 +1564,11 @@ async function fetchImpl(
|
|
|
1477
1564
|
if (options.rootCommand)
|
|
1478
1565
|
return executeCommand(name, options.rootCommand, [], inputOptions, start, options)
|
|
1479
1566
|
return jsonResponse(
|
|
1480
|
-
{
|
|
1567
|
+
{
|
|
1568
|
+
ok: false,
|
|
1569
|
+
error: { code: 'COMMAND_NOT_FOUND', message: 'No root command defined.' },
|
|
1570
|
+
meta: { command: '/', duration: `${Math.round(performance.now() - start)}ms` },
|
|
1571
|
+
},
|
|
1481
1572
|
404,
|
|
1482
1573
|
)
|
|
1483
1574
|
}
|
|
@@ -1486,18 +1577,31 @@ async function fetchImpl(
|
|
|
1486
1577
|
|
|
1487
1578
|
if ('error' in resolved)
|
|
1488
1579
|
return jsonResponse(
|
|
1489
|
-
{
|
|
1580
|
+
{
|
|
1581
|
+
ok: false,
|
|
1582
|
+
error: {
|
|
1583
|
+
code: 'COMMAND_NOT_FOUND',
|
|
1584
|
+
message: `'${resolved.error}' is not a command for '${resolved.path ? `${name} ${resolved.path}` : name}'.`,
|
|
1585
|
+
},
|
|
1586
|
+
meta: { command: resolved.error, duration: `${Math.round(performance.now() - start)}ms` },
|
|
1587
|
+
},
|
|
1490
1588
|
404,
|
|
1491
1589
|
)
|
|
1492
1590
|
|
|
1493
1591
|
if ('help' in resolved)
|
|
1494
1592
|
return jsonResponse(
|
|
1495
|
-
{
|
|
1593
|
+
{
|
|
1594
|
+
ok: false,
|
|
1595
|
+
error: {
|
|
1596
|
+
code: 'COMMAND_NOT_FOUND',
|
|
1597
|
+
message: `'${resolved.path}' is a command group. Specify a subcommand.`,
|
|
1598
|
+
},
|
|
1599
|
+
meta: { command: resolved.path, duration: `${Math.round(performance.now() - start)}ms` },
|
|
1600
|
+
},
|
|
1496
1601
|
404,
|
|
1497
1602
|
)
|
|
1498
1603
|
|
|
1499
|
-
if ('fetchGateway' in resolved)
|
|
1500
|
-
return resolved.fetchGateway.fetch(req)
|
|
1604
|
+
if ('fetchGateway' in resolved) return resolved.fetchGateway.fetch(req)
|
|
1501
1605
|
|
|
1502
1606
|
const { command, path, rest } = resolved
|
|
1503
1607
|
return executeCommand(path, command, rest, inputOptions, start, options)
|
|
@@ -1528,8 +1632,11 @@ async function executeCommand(
|
|
|
1528
1632
|
const parsedOptions = command.options ? command.options.parse(inputOptions) : {}
|
|
1529
1633
|
|
|
1530
1634
|
const okFn = (data: unknown): never => ({ [sentinel_]: 'ok', data }) as never
|
|
1531
|
-
const errorFn = (opts: {
|
|
1532
|
-
|
|
1635
|
+
const errorFn = (opts: {
|
|
1636
|
+
code: string
|
|
1637
|
+
message: string
|
|
1638
|
+
exitCode?: number | undefined
|
|
1639
|
+
}): never => ({ [sentinel_]: 'error', ...opts }) as never
|
|
1533
1640
|
|
|
1534
1641
|
const result = command.run({
|
|
1535
1642
|
agent: true,
|
|
@@ -1560,26 +1667,60 @@ async function executeCommand(
|
|
|
1560
1667
|
}
|
|
1561
1668
|
if (isSentinel(value) && (value as any)[sentinel] === 'error') {
|
|
1562
1669
|
const tagged = value as any
|
|
1563
|
-
controller.enqueue(
|
|
1670
|
+
controller.enqueue(
|
|
1671
|
+
encoder.encode(
|
|
1672
|
+
JSON.stringify({
|
|
1673
|
+
type: 'error',
|
|
1674
|
+
ok: false,
|
|
1675
|
+
error: { code: tagged.code, message: tagged.message },
|
|
1676
|
+
}) + '\n',
|
|
1677
|
+
),
|
|
1678
|
+
)
|
|
1564
1679
|
controller.close()
|
|
1565
1680
|
return
|
|
1566
1681
|
}
|
|
1567
|
-
controller.enqueue(
|
|
1682
|
+
controller.enqueue(
|
|
1683
|
+
encoder.encode(JSON.stringify({ type: 'chunk', data: value }) + '\n'),
|
|
1684
|
+
)
|
|
1568
1685
|
}
|
|
1569
1686
|
const meta: Record<string, unknown> = { command: path }
|
|
1570
1687
|
if (isSentinel(returnValue) && (returnValue as any)[sentinel] === 'error') {
|
|
1571
1688
|
const tagged = returnValue as any
|
|
1572
|
-
controller.enqueue(
|
|
1689
|
+
controller.enqueue(
|
|
1690
|
+
encoder.encode(
|
|
1691
|
+
JSON.stringify({
|
|
1692
|
+
type: 'error',
|
|
1693
|
+
ok: false,
|
|
1694
|
+
error: { code: tagged.code, message: tagged.message },
|
|
1695
|
+
}) + '\n',
|
|
1696
|
+
),
|
|
1697
|
+
)
|
|
1573
1698
|
} else {
|
|
1574
|
-
controller.enqueue(
|
|
1699
|
+
controller.enqueue(
|
|
1700
|
+
encoder.encode(JSON.stringify({ type: 'done', ok: true, meta }) + '\n'),
|
|
1701
|
+
)
|
|
1575
1702
|
}
|
|
1576
1703
|
} catch (error) {
|
|
1577
|
-
controller.enqueue(
|
|
1704
|
+
controller.enqueue(
|
|
1705
|
+
encoder.encode(
|
|
1706
|
+
JSON.stringify({
|
|
1707
|
+
type: 'error',
|
|
1708
|
+
ok: false,
|
|
1709
|
+
error: {
|
|
1710
|
+
code: 'UNKNOWN',
|
|
1711
|
+
message: error instanceof Error ? error.message : String(error),
|
|
1712
|
+
},
|
|
1713
|
+
}) + '\n',
|
|
1714
|
+
),
|
|
1715
|
+
)
|
|
1578
1716
|
}
|
|
1579
1717
|
controller.close()
|
|
1580
1718
|
},
|
|
1581
1719
|
})
|
|
1582
|
-
response = new Response(stream, {
|
|
1720
|
+
response = new Response(stream, {
|
|
1721
|
+
status: 200,
|
|
1722
|
+
headers: { 'content-type': 'application/x-ndjson' },
|
|
1723
|
+
})
|
|
1583
1724
|
return
|
|
1584
1725
|
}
|
|
1585
1726
|
|
|
@@ -1590,7 +1731,11 @@ async function executeCommand(
|
|
|
1590
1731
|
const tagged = awaited as any
|
|
1591
1732
|
if (tagged[sentinel_] === 'error')
|
|
1592
1733
|
response = jsonResponse(
|
|
1593
|
-
{
|
|
1734
|
+
{
|
|
1735
|
+
ok: false,
|
|
1736
|
+
error: { code: tagged.code, message: tagged.message },
|
|
1737
|
+
meta: { command: path, duration },
|
|
1738
|
+
},
|
|
1594
1739
|
500,
|
|
1595
1740
|
)
|
|
1596
1741
|
else
|
|
@@ -1601,19 +1746,24 @@ async function executeCommand(
|
|
|
1601
1746
|
return
|
|
1602
1747
|
}
|
|
1603
1748
|
|
|
1604
|
-
response = jsonResponse(
|
|
1605
|
-
{ ok: true, data: awaited, meta: { command: path, duration } },
|
|
1606
|
-
200,
|
|
1607
|
-
)
|
|
1749
|
+
response = jsonResponse({ ok: true, data: awaited, meta: { command: path, duration } }, 200)
|
|
1608
1750
|
}
|
|
1609
1751
|
|
|
1610
1752
|
try {
|
|
1611
1753
|
const allMiddleware = options.middlewares ?? []
|
|
1612
1754
|
if (allMiddleware.length > 0) {
|
|
1613
|
-
const errorFn = (opts: {
|
|
1755
|
+
const errorFn = (opts: {
|
|
1756
|
+
code: string
|
|
1757
|
+
message: string
|
|
1758
|
+
exitCode?: number | undefined
|
|
1759
|
+
}): never => {
|
|
1614
1760
|
const duration = `${Math.round(performance.now() - start)}ms`
|
|
1615
1761
|
response = jsonResponse(
|
|
1616
|
-
{
|
|
1762
|
+
{
|
|
1763
|
+
ok: false,
|
|
1764
|
+
error: { code: opts.code, message: opts.message },
|
|
1765
|
+
meta: { command: path, duration },
|
|
1766
|
+
},
|
|
1617
1767
|
500,
|
|
1618
1768
|
)
|
|
1619
1769
|
return undefined as never
|
|
@@ -1626,12 +1776,17 @@ async function executeCommand(
|
|
|
1626
1776
|
format: 'json',
|
|
1627
1777
|
formatExplicit: true,
|
|
1628
1778
|
name: path,
|
|
1629
|
-
|
|
1779
|
+
options: {},
|
|
1780
|
+
set(key: string, value: unknown) {
|
|
1781
|
+
varsMap[key] = value
|
|
1782
|
+
},
|
|
1630
1783
|
var: varsMap,
|
|
1631
1784
|
version: undefined,
|
|
1632
1785
|
}
|
|
1633
1786
|
const composed = allMiddleware.reduceRight(
|
|
1634
|
-
(next: () => Promise<void>, mw) => async () => {
|
|
1787
|
+
(next: () => Promise<void>, mw) => async () => {
|
|
1788
|
+
await mw(mwCtx, next)
|
|
1789
|
+
},
|
|
1635
1790
|
runCommand,
|
|
1636
1791
|
)
|
|
1637
1792
|
await composed()
|
|
@@ -1642,11 +1797,22 @@ async function executeCommand(
|
|
|
1642
1797
|
const duration = `${Math.round(performance.now() - start)}ms`
|
|
1643
1798
|
if (error instanceof ValidationError)
|
|
1644
1799
|
return jsonResponse(
|
|
1645
|
-
{
|
|
1800
|
+
{
|
|
1801
|
+
ok: false,
|
|
1802
|
+
error: { code: 'VALIDATION_ERROR', message: error.message },
|
|
1803
|
+
meta: { command: path, duration },
|
|
1804
|
+
},
|
|
1646
1805
|
400,
|
|
1647
1806
|
)
|
|
1648
1807
|
return jsonResponse(
|
|
1649
|
-
{
|
|
1808
|
+
{
|
|
1809
|
+
ok: false,
|
|
1810
|
+
error: {
|
|
1811
|
+
code: error instanceof IncurError ? error.code : 'UNKNOWN',
|
|
1812
|
+
message: error instanceof Error ? error.message : String(error),
|
|
1813
|
+
},
|
|
1814
|
+
meta: { command: path, duration },
|
|
1815
|
+
},
|
|
1650
1816
|
500,
|
|
1651
1817
|
)
|
|
1652
1818
|
}
|
|
@@ -1785,6 +1951,8 @@ declare namespace serveImpl {
|
|
|
1785
1951
|
format?: Formatter.Format | undefined
|
|
1786
1952
|
/** Middleware handlers registered on the root CLI. */
|
|
1787
1953
|
middlewares?: MiddlewareHandler[] | undefined
|
|
1954
|
+
/** CLI-level options schema. Parsed before middleware runs. */
|
|
1955
|
+
optionsSchema?: z.ZodObject<any> | undefined
|
|
1788
1956
|
/** CLI-level default output policy. */
|
|
1789
1957
|
outputPolicy?: OutputPolicy | undefined
|
|
1790
1958
|
mcp?:
|
|
@@ -1857,7 +2025,22 @@ function extractBuiltinFlags(argv: string[]) {
|
|
|
1857
2025
|
else rest.push(token)
|
|
1858
2026
|
}
|
|
1859
2027
|
|
|
1860
|
-
return {
|
|
2028
|
+
return {
|
|
2029
|
+
verbose,
|
|
2030
|
+
format,
|
|
2031
|
+
formatExplicit,
|
|
2032
|
+
filterOutput,
|
|
2033
|
+
tokenLimit,
|
|
2034
|
+
tokenOffset,
|
|
2035
|
+
tokenCount,
|
|
2036
|
+
llms,
|
|
2037
|
+
llmsFull,
|
|
2038
|
+
mcp,
|
|
2039
|
+
help,
|
|
2040
|
+
version,
|
|
2041
|
+
schema,
|
|
2042
|
+
rest,
|
|
2043
|
+
}
|
|
1861
2044
|
}
|
|
1862
2045
|
|
|
1863
2046
|
/** @internal Collects immediate child commands/groups for help output. */
|
|
@@ -2073,7 +2256,8 @@ async function handleStreaming(
|
|
|
2073
2256
|
}
|
|
2074
2257
|
}
|
|
2075
2258
|
if (useJsonl) ctx.writeln(JSON.stringify({ type: 'chunk', data: value }))
|
|
2076
|
-
else if (ctx.renderOutput)
|
|
2259
|
+
else if (ctx.renderOutput)
|
|
2260
|
+
ctx.writeln(ctx.truncate(Formatter.format(value, ctx.format)).text)
|
|
2077
2261
|
}
|
|
2078
2262
|
|
|
2079
2263
|
// Handle return value — error() or ok() sentinel
|
package/src/Fetch.test.ts
CHANGED
|
@@ -66,13 +66,7 @@ describe('parseArgv', () => {
|
|
|
66
66
|
})
|
|
67
67
|
|
|
68
68
|
test('multiple headers', () => {
|
|
69
|
-
const input = Fetch.parseArgv([
|
|
70
|
-
'users',
|
|
71
|
-
'-H',
|
|
72
|
-
'X-A: 1',
|
|
73
|
-
'-H',
|
|
74
|
-
'X-B: 2',
|
|
75
|
-
])
|
|
69
|
+
const input = Fetch.parseArgv(['users', '-H', 'X-A: 1', '-H', 'X-B: 2'])
|
|
76
70
|
expect(input.headers.get('X-A')).toBe('1')
|
|
77
71
|
expect(input.headers.get('X-B')).toBe('2')
|
|
78
72
|
})
|
|
@@ -123,16 +117,12 @@ describe('buildRequest', () => {
|
|
|
123
117
|
})
|
|
124
118
|
|
|
125
119
|
test('builds POST request with body', () => {
|
|
126
|
-
const req = Fetch.buildRequest(
|
|
127
|
-
Fetch.parseArgv(['users', '-X', 'POST', '-d', '{"name":"Bob"}']),
|
|
128
|
-
)
|
|
120
|
+
const req = Fetch.buildRequest(Fetch.parseArgv(['users', '-X', 'POST', '-d', '{"name":"Bob"}']))
|
|
129
121
|
expect(req.method).toBe('POST')
|
|
130
122
|
})
|
|
131
123
|
|
|
132
124
|
test('builds request with headers', () => {
|
|
133
|
-
const req = Fetch.buildRequest(
|
|
134
|
-
Fetch.parseArgv(['users', '-H', 'X-Api-Key: secret']),
|
|
135
|
-
)
|
|
125
|
+
const req = Fetch.buildRequest(Fetch.parseArgv(['users', '-H', 'X-Api-Key: secret']))
|
|
136
126
|
expect(req.headers.get('X-Api-Key')).toBe('secret')
|
|
137
127
|
})
|
|
138
128
|
})
|
package/src/Fetch.ts
CHANGED
|
@@ -102,8 +102,7 @@ export function buildRequest(input: FetchInput): Request {
|
|
|
102
102
|
|
|
103
103
|
if (input.body !== undefined) {
|
|
104
104
|
init.body = input.body
|
|
105
|
-
if (!input.headers.has('content-type'))
|
|
106
|
-
input.headers.set('content-type', 'application/json')
|
|
105
|
+
if (!input.headers.has('content-type')) input.headers.set('content-type', 'application/json')
|
|
107
106
|
}
|
|
108
107
|
|
|
109
108
|
return new Request(url.toString(), init)
|