@outfitter/cli 0.5.2 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (110) hide show
  1. package/README.md +179 -60
  2. package/dist/actions.d.ts +5 -2
  3. package/dist/actions.js +2 -2
  4. package/dist/cli.d.ts +1 -1
  5. package/dist/cli.js +8 -1
  6. package/dist/colors/index.js +1 -17
  7. package/dist/command.d.ts +3 -43
  8. package/dist/command.js +241 -13
  9. package/dist/envelope.d.ts +5 -0
  10. package/dist/envelope.js +160 -0
  11. package/dist/flags.d.ts +5 -189
  12. package/dist/flags.js +5 -1
  13. package/dist/hints.d.ts +34 -0
  14. package/dist/hints.js +26 -0
  15. package/dist/index.d.ts +3 -2
  16. package/dist/index.js +2 -17
  17. package/dist/input.d.ts +3 -124
  18. package/dist/input.js +14 -359
  19. package/dist/internal/envelope-helpers.d.ts +4 -0
  20. package/dist/internal/envelope-helpers.js +24 -0
  21. package/dist/internal/envelope-types.d.ts +3 -0
  22. package/dist/internal/flag-builders.d.ts +3 -0
  23. package/dist/internal/flag-builders.js +155 -0
  24. package/dist/internal/flag-types.d.ts +3 -0
  25. package/dist/internal/flag-types.js +13 -0
  26. package/dist/internal/hint-action-graph.d.ts +5 -0
  27. package/dist/internal/hint-action-graph.js +11 -0
  28. package/dist/internal/hint-command-tree.d.ts +5 -0
  29. package/dist/internal/hint-command-tree.js +9 -0
  30. package/dist/internal/hint-error-recovery.d.ts +2 -0
  31. package/dist/internal/hint-error-recovery.js +7 -0
  32. package/dist/internal/hint-types.d.ts +4 -0
  33. package/dist/internal/hint-types.js +1 -0
  34. package/dist/internal/input-helpers.d.ts +18 -0
  35. package/dist/internal/input-helpers.js +11 -0
  36. package/dist/internal/input-normalization.d.ts +3 -0
  37. package/dist/internal/input-normalization.js +9 -0
  38. package/dist/internal/input-parsers.d.ts +3 -0
  39. package/dist/internal/input-parsers.js +19 -0
  40. package/dist/internal/input-security.d.ts +22 -0
  41. package/dist/internal/input-security.js +11 -0
  42. package/dist/internal/output-formatting.d.ts +3 -0
  43. package/dist/internal/output-formatting.js +21 -0
  44. package/dist/internal/presets.d.ts +3 -0
  45. package/dist/{shared/@outfitter/cli-pdb7znbq.js → internal/presets.js} +49 -223
  46. package/dist/internal/schema-commands.d.ts +3 -0
  47. package/dist/{shared/@outfitter/cli-0cjts94k.js → internal/schema-commands.js} +12 -100
  48. package/dist/internal/schema-formatting.d.ts +2 -0
  49. package/dist/internal/schema-formatting.js +7 -0
  50. package/dist/internal/schema-types.d.ts +2 -0
  51. package/dist/internal/schema-types.js +1 -0
  52. package/dist/output.d.ts +4 -3
  53. package/dist/output.js +10 -2
  54. package/dist/pagination.d.ts +1 -1
  55. package/dist/query.d.ts +84 -2
  56. package/dist/query.js +8 -45
  57. package/dist/schema-input.d.ts +80 -0
  58. package/dist/schema-input.js +15 -0
  59. package/dist/schema.d.ts +4 -1
  60. package/dist/schema.js +1 -1
  61. package/dist/shared/@outfitter/cli-10wxfc78.d.ts +45 -0
  62. package/dist/shared/@outfitter/cli-16wg5mka.d.ts +71 -0
  63. package/dist/shared/@outfitter/cli-1q5redaj.js +267 -0
  64. package/dist/shared/@outfitter/cli-2dfxs239.js +98 -0
  65. package/dist/shared/@outfitter/cli-30mt7c5w.d.ts +112 -0
  66. package/dist/shared/@outfitter/cli-3jta1h1h.js +134 -0
  67. package/dist/shared/@outfitter/cli-4h85mpth.js +76 -0
  68. package/dist/shared/@outfitter/cli-6shkwxdc.js +28 -0
  69. package/dist/shared/@outfitter/cli-89335n9a.js +16 -0
  70. package/dist/shared/@outfitter/cli-8999qjdd.js +3 -0
  71. package/dist/shared/@outfitter/cli-8cfxdady.js +60 -0
  72. package/dist/shared/@outfitter/cli-bcajqy33.d.ts +25 -0
  73. package/dist/shared/@outfitter/cli-c09332vm.d.ts +39 -0
  74. package/dist/shared/@outfitter/cli-cgha038c.d.ts +3 -0
  75. package/dist/shared/@outfitter/{cli-zahqsaby.js → cli-d40m2x1d.js} +19 -3
  76. package/dist/shared/@outfitter/{cli-7wp5nj0s.js → cli-dg0cz7rw.js} +39 -81
  77. package/dist/shared/@outfitter/cli-dv8kk4jw.d.ts +24 -0
  78. package/dist/shared/@outfitter/cli-g43887b7.js +20 -0
  79. package/dist/shared/@outfitter/cli-gqtkhgw4.js +52 -0
  80. package/dist/shared/@outfitter/cli-h4ejpmjs.d.ts +104 -0
  81. package/dist/shared/@outfitter/cli-htzez8v2.js +70 -0
  82. package/dist/shared/@outfitter/cli-hvg2m5gf.js +79 -0
  83. package/dist/shared/@outfitter/cli-n54zs151.d.ts +78 -0
  84. package/dist/shared/@outfitter/cli-nbpgw7z7.d.ts +15 -0
  85. package/dist/shared/@outfitter/cli-nkt399zf.d.ts +94 -0
  86. package/dist/shared/@outfitter/cli-pmd04gtv.d.ts +60 -0
  87. package/dist/shared/@outfitter/{cli-xy3gs50c.d.ts → cli-q6csxmeh.d.ts} +19 -12
  88. package/dist/shared/@outfitter/cli-qcskd96y.d.ts +11 -0
  89. package/dist/shared/@outfitter/cli-ry7btmy4.js +118 -0
  90. package/dist/shared/@outfitter/cli-sy99pjyj.js +32 -0
  91. package/dist/shared/@outfitter/cli-tm2fzngs.d.ts +23 -0
  92. package/dist/shared/@outfitter/cli-vvvhjwks.js +106 -0
  93. package/dist/shared/@outfitter/cli-wjv7g1aq.d.ts +16 -0
  94. package/dist/shared/@outfitter/{cli-98aa9104.d.ts → cli-x6qr7bnd.d.ts} +338 -16
  95. package/dist/shared/@outfitter/cli-xde45xcc.d.ts +53 -0
  96. package/dist/shared/@outfitter/cli-xw8ys1je.d.ts +123 -0
  97. package/dist/shared/@outfitter/cli-yfewnyc2.d.ts +43 -0
  98. package/dist/shared/@outfitter/cli-zkzj0q4q.js +99 -0
  99. package/dist/shared/@outfitter/cli-zv3ah6f0.js +3 -0
  100. package/dist/streaming.d.ts +47 -0
  101. package/dist/streaming.js +13 -0
  102. package/dist/terminal/index.js +1 -19
  103. package/dist/truncation.d.ts +104 -0
  104. package/dist/truncation.js +111 -0
  105. package/dist/types.d.ts +2 -2
  106. package/dist/types.js +0 -5
  107. package/dist/verbs.d.ts +1 -1
  108. package/package.json +66 -36
  109. package/dist/shared/@outfitter/cli-n1k0d23k.d.ts +0 -33
  110. /package/dist/{shared/@outfitter/cli-zw75pdk8.js → internal/envelope-types.js} +0 -0
@@ -0,0 +1,106 @@
1
+ // @bun
2
+ import {
3
+ createPreset
4
+ } from "./cli-8999qjdd.js";
5
+
6
+ // packages/cli/src/query.ts
7
+ function argvContainsOutputFlag(argv) {
8
+ for (const arg of argv) {
9
+ if (!arg)
10
+ continue;
11
+ if (arg === "-o" || arg === "--output")
12
+ return true;
13
+ if (arg.startsWith("--output=") || arg.startsWith("-o="))
14
+ return true;
15
+ }
16
+ return false;
17
+ }
18
+ function hasExplicitOutputFlag(flags, config) {
19
+ const mode = flags["output"];
20
+ if (typeof mode !== "string")
21
+ return false;
22
+ const defaultMode = config?.defaultMode ?? "human";
23
+ if (mode !== defaultMode)
24
+ return true;
25
+ return argvContainsOutputFlag(config?.argv ?? process.argv.slice(2));
26
+ }
27
+ function resolveOutputMode(flags, config) {
28
+ const defaultMode = config?.defaultMode ?? "human";
29
+ if (hasExplicitOutputFlag(flags, config)) {
30
+ const raw = flags["output"];
31
+ const mode = raw === "json" || raw === "jsonl" || raw === "human" ? raw : defaultMode;
32
+ return { mode, source: "flag" };
33
+ }
34
+ if (flags["json"] === true)
35
+ return { mode: "json", source: "flag" };
36
+ if (flags["jsonl"] === true)
37
+ return { mode: "jsonl", source: "flag" };
38
+ if (config?.forceHumanWhenImplicit) {
39
+ return { mode: defaultMode, source: "default" };
40
+ }
41
+ if (process.env["OUTFITTER_JSONL"] === "1") {
42
+ return { mode: "jsonl", source: "env" };
43
+ }
44
+ if (process.env["OUTFITTER_JSON"] === "1") {
45
+ return { mode: "json", source: "env" };
46
+ }
47
+ return { mode: defaultMode, source: "default" };
48
+ }
49
+ function outputModePreset(config) {
50
+ const defaultMode = config?.defaultMode ?? "human";
51
+ const baseModes = config?.modes ?? ["human", "json"];
52
+ const modes = new Set(config?.includeJsonl ? [...baseModes, "jsonl"] : baseModes);
53
+ modes.add(defaultMode);
54
+ return createPreset({
55
+ id: "outputMode",
56
+ options: [
57
+ {
58
+ flags: "-o, --output <mode>",
59
+ description: `Output mode (${[...modes].join(", ")})`,
60
+ defaultValue: defaultMode
61
+ }
62
+ ],
63
+ resolve: (flags) => {
64
+ const raw = flags["output"];
65
+ if (typeof raw === "string" && modes.has(raw)) {
66
+ return { outputMode: raw };
67
+ }
68
+ return { outputMode: defaultMode };
69
+ }
70
+ });
71
+ }
72
+ function streamPreset() {
73
+ return createPreset({
74
+ id: "stream",
75
+ options: [
76
+ {
77
+ flags: "--stream",
78
+ description: "Stream progress events as NDJSON to stdout",
79
+ defaultValue: false
80
+ }
81
+ ],
82
+ resolve: (flags) => ({
83
+ stream: Boolean(flags["stream"])
84
+ })
85
+ });
86
+ }
87
+ function jqPreset() {
88
+ return createPreset({
89
+ id: "jq",
90
+ options: [
91
+ {
92
+ flags: "--jq <expr>",
93
+ description: "Filter JSON output with a jq expression"
94
+ }
95
+ ],
96
+ resolve: (flags) => {
97
+ const raw = flags["jq"];
98
+ if (typeof raw === "string" && raw.length > 0) {
99
+ return { jq: raw };
100
+ }
101
+ return { jq: undefined };
102
+ }
103
+ });
104
+ }
105
+
106
+ export { resolveOutputMode, outputModePreset, streamPreset, jqPreset };
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Types for schema introspection commands.
3
+ *
4
+ * @internal
5
+ */
6
+ /** Options for surface map subcommands (generate, diff). */
7
+ interface SurfaceCommandOptions {
8
+ readonly cwd?: string;
9
+ readonly outputDir?: string;
10
+ }
11
+ /** Options for the schema Commander command. */
12
+ interface SchemaCommandOptions {
13
+ readonly programName?: string;
14
+ readonly surface?: SurfaceCommandOptions;
15
+ }
16
+ export { SurfaceCommandOptions, SchemaCommandOptions };
@@ -1,4 +1,4 @@
1
- import { ActionCliOption } from "@outfitter/contracts";
1
+ import { ActionCliOption, CLIHint } from "@outfitter/contracts";
2
2
  import { Command } from "commander";
3
3
  import { CancelledError, ErrorCategory, ValidationError } from "@outfitter/contracts";
4
4
  import { Result } from "better-result";
@@ -51,17 +51,59 @@ interface CommandConfig {
51
51
  readonly name: string;
52
52
  }
53
53
  /**
54
+ * Factory function for constructing command context.
55
+ *
56
+ * Called after schema validation (if `.input()` is used), before the handler.
57
+ * When `.input()` is used, receives the validated typed input.
58
+ * When `.input()` is not used, receives the raw parsed flags.
59
+ *
60
+ * @typeParam TInput - Type of validated input (from .input() schema)
61
+ * @typeParam TContext - Type of the constructed context object
62
+ */
63
+ type ContextFactory<
64
+ TInput,
65
+ TContext
66
+ > = (input: TInput extends undefined ? Record<string, unknown> : TInput) => Promise<TContext> | TContext;
67
+ /**
68
+ * Success hint function for transport-local hint generation.
69
+ *
70
+ * Called at output time (not during handler execution) with the handler's
71
+ * result and the validated input. Returns CLI-specific hints for the response.
72
+ *
73
+ * @typeParam TInput - Type of validated input (from .input() schema)
74
+ */
75
+ type SuccessHintFn<TInput = undefined> = (result: unknown, input: TInput extends undefined ? Record<string, unknown> : TInput) => CLIHint[];
76
+ /**
77
+ * Error hint function for transport-local error hint generation.
78
+ *
79
+ * Called at output time (not during handler execution) with the error
80
+ * and the validated input. Returns CLI-specific hints for error recovery.
81
+ *
82
+ * @typeParam TInput - Type of validated input (from .input() schema)
83
+ */
84
+ type ErrorHintFn<TInput = undefined> = (error: unknown, input: TInput extends undefined ? Record<string, unknown> : TInput) => CLIHint[];
85
+ /**
54
86
  * Action function executed when a command is invoked.
55
87
  *
56
88
  * @typeParam TFlags - Type of parsed command flags
57
- */
58
- type CommandAction<TFlags extends CommandFlags = CommandFlags> = (context: {
89
+ * @typeParam TInput - Type of validated input (from .input() schema)
90
+ * @typeParam TContext - Type of context object (from .context() factory)
91
+ */
92
+ type CommandAction<
93
+ TFlags extends CommandFlags = CommandFlags,
94
+ TInput = undefined,
95
+ TContext = undefined
96
+ > = (context: {
59
97
  /** Parsed command-line arguments */
60
98
  readonly args: readonly string[];
61
- /** Parsed command flags */
62
- readonly flags: TFlags;
63
99
  /** Raw Commander command instance */
64
100
  readonly command: Command;
101
+ /** Context object constructed by .context() factory (undefined when .context() is not used) */
102
+ readonly ctx: TContext;
103
+ /** Parsed command flags */
104
+ readonly flags: TFlags;
105
+ /** Validated input from Zod schema (present when .input() is used) */
106
+ readonly input: TInput;
65
107
  }) => Promise<void> | void;
66
108
  /**
67
109
  * Base type for command flags.
@@ -70,24 +112,250 @@ type CommandAction<TFlags extends CommandFlags = CommandFlags> = (context: {
70
112
  type CommandFlags = Record<string, unknown>;
71
113
  /**
72
114
  * Builder interface for constructing commands fluently.
115
+ *
116
+ * @typeParam TInput - Type of validated input when .input() is used
117
+ * @typeParam TContext - Type of context object when .context() is used
73
118
  */
74
- interface CommandBuilder {
119
+ interface CommandBuilder<
120
+ TInput = undefined,
121
+ TContext = undefined
122
+ > {
75
123
  /** Set the action handler */
76
- action<TFlags extends CommandFlags = CommandFlags>(handler: CommandAction<TFlags>): this;
124
+ action<TFlags extends CommandFlags = CommandFlags>(handler: CommandAction<TFlags, TInput, TContext>): this;
77
125
  /** Add command aliases */
78
126
  alias(alias: string): this;
79
127
  /** Build the underlying Commander command */
80
128
  build(): Command;
129
+ /**
130
+ * Set an async context factory.
131
+ *
132
+ * The factory is called after schema validation (if `.input()` is used),
133
+ * before the handler. It receives the validated typed input (or raw parsed
134
+ * flags when `.input()` is not used) and returns a typed context object.
135
+ *
136
+ * Context factory errors are caught and produce proper exit codes.
137
+ *
138
+ * @typeParam T - Type of the context object returned by the factory
139
+ *
140
+ * @example
141
+ * ```typescript
142
+ * command("deploy")
143
+ * .input(z.object({ env: z.string() }))
144
+ * .context(async (input) => ({
145
+ * config: await loadConfig(input.env),
146
+ * client: createClient(input.env),
147
+ * }))
148
+ * .action(async ({ input, ctx }) => {
149
+ * await ctx.client.deploy(ctx.config);
150
+ * });
151
+ * ```
152
+ */
153
+ context<T>(factory: ContextFactory<TInput, T>): CommandBuilder<TInput, T>;
81
154
  /** Set command description */
82
155
  description(text: string): this;
156
+ /**
157
+ * Mark the command as destructive.
158
+ *
159
+ * When `isDest` is `true`, a `--dry-run` flag is auto-added to the command
160
+ * (deduplicated if already present from `.option()` or `.preset()`).
161
+ * The handler is responsible for checking the dry-run flag and performing
162
+ * preview-only logic when active.
163
+ *
164
+ * When used with `runHandler({ dryRun: true })`, the response envelope
165
+ * includes a CLIHint with the command to execute without `--dry-run`
166
+ * (preview-then-commit pattern).
167
+ *
168
+ * Default (no `.destructive()` call) is non-destructive.
169
+ *
170
+ * @param isDest - Whether the command is destructive
171
+ *
172
+ * @example
173
+ * ```typescript
174
+ * command("delete")
175
+ * .description("Delete resources")
176
+ * .destructive(true)
177
+ * .action(async ({ flags }) => {
178
+ * const isDryRun = Boolean(flags.dryRun);
179
+ * await runHandler({
180
+ * command: "delete",
181
+ * handler: async (input) => isDryRun
182
+ * ? Result.ok({ preview: true, count: 5 })
183
+ * : deleteResources(input),
184
+ * dryRun: isDryRun,
185
+ * });
186
+ * });
187
+ * ```
188
+ */
189
+ destructive(isDest: boolean): this;
190
+ /**
191
+ * Mark the command as idempotent.
192
+ *
193
+ * When `true`, indicates that calling this command multiple times with the
194
+ * same input produces the same effect (PUT-like semantics). This metadata
195
+ * is included in the self-documenting command tree (JSON mode) and maps to
196
+ * `idempotentHint` in MCP tool annotations.
197
+ *
198
+ * Default (no `.idempotent()` call) is non-idempotent.
199
+ *
200
+ * @param isIdempotent - Whether the command is idempotent
201
+ *
202
+ * @example
203
+ * ```typescript
204
+ * command("set")
205
+ * .description("Set a configuration value")
206
+ * .idempotent(true)
207
+ * .action(async ({ flags }) => { ... });
208
+ * ```
209
+ */
210
+ idempotent(isIdempotent: boolean): this;
211
+ /**
212
+ * Set a success hint function.
213
+ *
214
+ * The hint function is stored on the builder and invoked at output time
215
+ * (not during handler execution). It receives the handler's result and the
216
+ * validated input (or raw flags when `.input()` is not used), and returns
217
+ * CLI-specific hints for the response.
218
+ *
219
+ * Hint functions are transport-local — handlers remain transport-agnostic.
220
+ *
221
+ * @example
222
+ * ```typescript
223
+ * command("deploy")
224
+ * .input(z.object({ env: z.string() }))
225
+ * .hints((result, input) => [
226
+ * {
227
+ * description: `Check status for ${input.env}`,
228
+ * command: `deploy status --env ${input.env}`,
229
+ * },
230
+ * ])
231
+ * .action(async ({ input }) => { ... });
232
+ * ```
233
+ */
234
+ hints(fn: SuccessHintFn<TInput>): this;
235
+ /**
236
+ * Set a Zod object schema for auto-deriving Commander flags.
237
+ *
238
+ * Handles the 80% case automatically:
239
+ * - `z.string()` → string option
240
+ * - `z.number()` → number option with coercion
241
+ * - `z.boolean()` → boolean flag
242
+ * - `z.enum()` → choices option
243
+ *
244
+ * `.describe()` text becomes the option description.
245
+ * `.default()` values become option defaults.
246
+ *
247
+ * Explicit `.option()` / `.requiredOption()` / `.argument()` calls
248
+ * compose alongside `.input()` — they override or supplement auto-derived flags.
249
+ */
250
+ input<T extends Record<string, unknown>>(schema: ZodObjectLike<T>): CommandBuilder<T, TContext>;
251
+ /**
252
+ * Set an error hint function.
253
+ *
254
+ * The hint function is stored on the builder and invoked at output time
255
+ * (not during handler execution). It receives the error and the validated
256
+ * input (or raw flags when `.input()` is not used), and returns CLI-specific
257
+ * hints for error recovery.
258
+ *
259
+ * Hint functions are transport-local — handlers remain transport-agnostic.
260
+ *
261
+ * @example
262
+ * ```typescript
263
+ * command("deploy")
264
+ * .input(z.object({ env: z.string() }))
265
+ * .onError((error, input) => [
266
+ * {
267
+ * description: "Retry with --force",
268
+ * command: `deploy --env ${input.env} --force`,
269
+ * },
270
+ * ])
271
+ * .action(async ({ input }) => { ... });
272
+ * ```
273
+ */
274
+ onError(fn: ErrorHintFn<TInput>): this;
275
+ /**
276
+ * Declare a relationship to another command for action graph navigation.
277
+ *
278
+ * Relationships build a navigable action graph (tier-4 hints) that agents
279
+ * use to discover workflows. Success envelopes include related next-actions
280
+ * from graph neighbors; error envelopes include remediation paths.
281
+ *
282
+ * Multiple `.relatedTo()` calls accumulate — each declares a separate edge.
283
+ *
284
+ * @param target - Name of the related command
285
+ * @param options - Optional relationship metadata (description)
286
+ *
287
+ * @example
288
+ * ```typescript
289
+ * command("deploy")
290
+ * .description("Deploy application")
291
+ * .relatedTo("status", { description: "Check deployment status" })
292
+ * .relatedTo("rollback", { description: "Rollback if needed" })
293
+ * .action(async ({ flags }) => { ... });
294
+ * ```
295
+ */
296
+ relatedTo(target: string, options?: RelatedToOptions): this;
83
297
  /** Add a command option/flag */
84
298
  option(flags: string, description: string, defaultValue?: unknown): this;
85
- /** Apply a flag preset (adds its options to the command) */
86
- preset(preset: FlagPreset<Record<string, unknown>>): this;
299
+ /**
300
+ * Mark the command as read-only.
301
+ *
302
+ * When `true`, indicates that this command does not modify any state.
303
+ * This metadata is included in the self-documenting command tree (JSON mode)
304
+ * and maps to `readOnlyHint` in MCP tool annotations.
305
+ *
306
+ * Default (no `.readOnly()` call) is non-read-only.
307
+ *
308
+ * @param isReadOnly - Whether the command is read-only
309
+ *
310
+ * @example
311
+ * ```typescript
312
+ * command("list")
313
+ * .description("List all resources")
314
+ * .readOnly(true)
315
+ * .action(async ({ flags }) => { ... });
316
+ * ```
317
+ */
318
+ readOnly(isReadOnly: boolean): this;
319
+ /**
320
+ * Apply a preset to the command.
321
+ *
322
+ * Accepts either a `FlagPreset` (Commander option definitions with resolver)
323
+ * or a `SchemaPreset` (Zod schema fragment with resolver). Schema presets
324
+ * auto-derive Commander flags from the schema, composing with `.input()`.
325
+ */
326
+ preset(preset: AnyPreset<Record<string, unknown>>): this;
87
327
  /** Add a required option */
88
328
  requiredOption(flags: string, description: string, defaultValue?: unknown): this;
89
329
  }
90
330
  /**
331
+ * Options for declaring a command relationship via `.relatedTo()`.
332
+ */
333
+ interface RelatedToOptions {
334
+ /** Description of the relationship (used in hints) */
335
+ readonly description?: string;
336
+ }
337
+ /**
338
+ * A stored relationship declaration from `.relatedTo()`.
339
+ */
340
+ interface RelatedToDeclaration {
341
+ /** Target command name */
342
+ readonly target: string;
343
+ /** Description of the relationship */
344
+ readonly description?: string;
345
+ }
346
+ /**
347
+ * Minimal interface for a Zod-like object schema.
348
+ * Avoids tight coupling to a specific Zod version.
349
+ */
350
+ interface ZodObjectLike<T extends Record<string, unknown> = Record<string, unknown>> {
351
+ readonly shape: Record<string, unknown>;
352
+ safeParse(data: unknown): {
353
+ success: boolean;
354
+ data?: T;
355
+ error?: unknown;
356
+ };
357
+ }
358
+ /**
91
359
  * A family of related command verbs with a primary name and aliases.
92
360
  */
93
361
  interface VerbFamily {
@@ -137,6 +405,39 @@ interface FlagPresetConfig<TResolved extends Record<string, unknown>> {
137
405
  readonly resolve: (flags: Record<string, unknown>) => TResolved;
138
406
  }
139
407
  /**
408
+ * A schema-driven preset that uses a Zod schema fragment for flag derivation.
409
+ *
410
+ * Instead of declaring Commander options manually, the schema fragment is
411
+ * introspected to auto-derive flags (same as `.input()`). The schema merges
412
+ * with `.input()` schema automatically when both are used.
413
+ *
414
+ * Eliminates the need for separate `.resolve()` / `preAction` hooks.
415
+ */
416
+ interface SchemaPreset<TResolved extends Record<string, unknown>> {
417
+ /** Unique identifier for deduplication */
418
+ readonly id: string;
419
+ /** Resolve raw Commander flags into typed values */
420
+ readonly resolve: (flags: Record<string, unknown>) => TResolved;
421
+ /** Zod schema fragment defining the preset's flags */
422
+ readonly schema: ZodObjectLike;
423
+ }
424
+ /**
425
+ * Configuration for creating a schema-driven preset.
426
+ */
427
+ interface SchemaPresetConfig<TResolved extends Record<string, unknown>> {
428
+ /** Unique identifier for deduplication */
429
+ readonly id: string;
430
+ /** Resolve raw Commander flags into typed values */
431
+ readonly resolve: (flags: Record<string, unknown>) => TResolved;
432
+ /** Zod schema fragment defining the preset's flags */
433
+ readonly schema: ZodObjectLike;
434
+ }
435
+ /**
436
+ * Union type for any preset accepted by `.preset()`.
437
+ * Includes both flag-based presets and schema-driven presets.
438
+ */
439
+ type AnyPreset<TResolved extends Record<string, unknown> = Record<string, unknown>> = FlagPreset<TResolved> | SchemaPreset<TResolved>;
440
+ /**
140
441
  * Configuration for creating a custom boolean flag preset.
141
442
  */
142
443
  interface BooleanFlagPresetConfig<TKey extends string> {
@@ -340,12 +641,37 @@ type OutputMode = "human" | "json" | "jsonl" | "tree" | "table";
340
641
  interface OutputOptions {
341
642
  /** Exit code to use after output (undefined = don't exit) */
342
643
  readonly exitCode?: number;
343
- /** Force a specific output mode (overrides flag detection) */
344
- readonly mode?: OutputMode;
345
644
  /** Whether to pretty-print JSON output */
346
645
  readonly pretty?: boolean;
347
646
  /** Stream to write to (defaults to stdout) */
348
647
  readonly stream?: NodeJS.WritableStream;
648
+ /**
649
+ * Optional truncation configuration metadata.
650
+ *
651
+ * `output()` applies array slicing when this option is provided.
652
+ * For array data, it uses `offset` + `limit` before rendering.
653
+ *
654
+ * If you need pagination hints or file-pointer metadata, call
655
+ * `truncateOutput()` first and pass `truncated.hints` / `truncated.metadata`
656
+ * through your envelope separately.
657
+ *
658
+ * @example
659
+ * ```typescript
660
+ * await output(items, "json", {
661
+ * truncation: { limit: 50, offset: 0 },
662
+ * });
663
+ * ```
664
+ */
665
+ readonly truncation?: {
666
+ /** Command name for pagination hints (used by `truncateOutput()`, not `output()`). */
667
+ readonly commandName?: string;
668
+ /** File-pointer threshold (used by `truncateOutput()`, not `output()`). */
669
+ readonly filePointerThreshold?: number;
670
+ /** Maximum items to show. */
671
+ readonly limit: number;
672
+ /** Starting offset (0-based). */
673
+ readonly offset?: number;
674
+ };
349
675
  }
350
676
  /**
351
677
  * Options for collectIds() input utility.
@@ -361,12 +687,8 @@ interface OutputOptions {
361
687
  interface CollectIdsOptions {
362
688
  /** Allow @file expansion (reads IDs from file) */
363
689
  readonly allowFile?: boolean;
364
- /** Allow glob patterns */
365
- readonly allowGlob?: boolean;
366
690
  /** Allow reading from stdin with "-" */
367
691
  readonly allowStdin?: boolean;
368
- /** Separator for comma-separated values */
369
- readonly separator?: string;
370
692
  }
371
693
  /**
372
694
  * Options for expandFileArg() input utility.
@@ -485,4 +807,4 @@ interface CursorOptions {
485
807
  /** Total count of results (if known) */
486
808
  readonly total?: number;
487
809
  }
488
- export { CLIConfig, CLI, CommandConfig, CommandAction, CommandFlags, CommandBuilder, VerbFamily, VerbConfig, FlagPreset, FlagPresetConfig, BooleanFlagPresetConfig, EnumFlagPresetConfig, NumberFlagPresetConfig, StringListFlagPresetConfig, ComposedPreset, PaginationPresetConfig, InteractionFlags, StrictFlags, TimeWindowFlags, TimeWindowPresetConfig, ExecutionFlags, ExecutionPresetConfig, ProjectionFlags, ColorMode, ColorFlags, PaginationFlags, OutputMode, OutputOptions, CollectIdsOptions, ExpandFileOptions, ParseGlobOptions, NormalizeIdOptions, Range, NumericRange, DateRange, FilterExpression, SortCriteria, KeyValuePair, PaginationState, CursorOptions, CancelledError, ErrorCategory, ValidationError, Result };
810
+ export { CLIConfig, CLI, CommandConfig, ContextFactory, SuccessHintFn, ErrorHintFn, CommandAction, CommandFlags, CommandBuilder, RelatedToOptions, RelatedToDeclaration, ZodObjectLike, VerbFamily, VerbConfig, FlagPreset, FlagPresetConfig, SchemaPreset, SchemaPresetConfig, AnyPreset, BooleanFlagPresetConfig, EnumFlagPresetConfig, NumberFlagPresetConfig, StringListFlagPresetConfig, ComposedPreset, PaginationPresetConfig, InteractionFlags, StrictFlags, TimeWindowFlags, TimeWindowPresetConfig, ExecutionFlags, ExecutionPresetConfig, ProjectionFlags, ColorMode, ColorFlags, PaginationFlags, OutputMode, OutputOptions, CollectIdsOptions, ExpandFileOptions, ParseGlobOptions, NormalizeIdOptions, Range, NumericRange, DateRange, FilterExpression, SortCriteria, KeyValuePair, PaginationState, CursorOptions, CancelledError, ErrorCategory, ValidationError, Result };
@@ -0,0 +1,53 @@
1
+ import { CLI, CLIConfig, CommandBuilder } from "./cli-x6qr7bnd.js";
2
+ /**
3
+ * Command metadata for safety signals (readOnly, idempotent).
4
+ * Stored on Commander commands and surfaced in the command tree.
5
+ */
6
+ interface CommandMetadata {
7
+ /** When true, the command does not modify any state */
8
+ readonly readOnly?: boolean;
9
+ /** When true, calling the command multiple times with the same input has the same effect */
10
+ readonly idempotent?: boolean;
11
+ }
12
+ /**
13
+ * Create a CLI instance with a portable return type from this module.
14
+ */
15
+ declare function createCLI(config: CLIConfig): CLI;
16
+ /**
17
+ * Create a new command builder with the given name.
18
+ *
19
+ * The command builder provides a fluent API for defining CLI commands
20
+ * with typed flags, arguments, and actions.
21
+ *
22
+ * @param name - Command name and optional argument syntax (e.g., "list" or "get <id>")
23
+ * @returns A CommandBuilder instance for fluent configuration
24
+ *
25
+ * @example
26
+ * ```typescript
27
+ * import { command, output } from "@outfitter/cli";
28
+ *
29
+ * const list = command("list")
30
+ * .description("List all notes")
31
+ * .option("--limit <n>", "Max results", "20")
32
+ * .option("--json", "Output as JSON")
33
+ * .option("--next", "Continue from last position")
34
+ * .action(async ({ flags }) => {
35
+ * const results = await listNotes(flags);
36
+ * output(results);
37
+ * });
38
+ * ```
39
+ *
40
+ * @example
41
+ * ```typescript
42
+ * // Command with required argument
43
+ * const get = command("get <id>")
44
+ * .description("Get a note by ID")
45
+ * .action(async ({ args }) => {
46
+ * const [id] = args;
47
+ * const note = await getNote(id);
48
+ * output(note);
49
+ * });
50
+ * ```
51
+ */
52
+ declare function command(name: string): CommandBuilder;
53
+ export { CommandMetadata, createCLI, command };