@outfitter/cli 0.5.3 → 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.
- package/README.md +105 -2
- package/dist/actions.d.ts +5 -2
- package/dist/actions.js +2 -2
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +8 -1
- package/dist/command.d.ts +3 -43
- package/dist/command.js +241 -13
- package/dist/envelope.d.ts +5 -0
- package/dist/envelope.js +160 -0
- package/dist/flags.d.ts +5 -189
- package/dist/flags.js +5 -1
- package/dist/hints.d.ts +34 -0
- package/dist/hints.js +26 -0
- package/dist/index.d.ts +3 -2
- package/dist/input.d.ts +3 -124
- package/dist/input.js +14 -359
- package/dist/internal/envelope-helpers.d.ts +4 -0
- package/dist/internal/envelope-helpers.js +24 -0
- package/dist/internal/envelope-types.d.ts +3 -0
- package/dist/internal/envelope-types.js +1 -0
- package/dist/internal/flag-builders.d.ts +3 -0
- package/dist/internal/flag-builders.js +155 -0
- package/dist/internal/flag-types.d.ts +3 -0
- package/dist/internal/flag-types.js +13 -0
- package/dist/internal/hint-action-graph.d.ts +5 -0
- package/dist/internal/hint-action-graph.js +11 -0
- package/dist/internal/hint-command-tree.d.ts +5 -0
- package/dist/internal/hint-command-tree.js +9 -0
- package/dist/internal/hint-error-recovery.d.ts +2 -0
- package/dist/internal/hint-error-recovery.js +7 -0
- package/dist/internal/hint-types.d.ts +4 -0
- package/dist/internal/hint-types.js +1 -0
- package/dist/internal/input-helpers.d.ts +18 -0
- package/dist/internal/input-helpers.js +11 -0
- package/dist/internal/input-normalization.d.ts +3 -0
- package/dist/internal/input-normalization.js +9 -0
- package/dist/internal/input-parsers.d.ts +3 -0
- package/dist/internal/input-parsers.js +19 -0
- package/dist/internal/input-security.d.ts +22 -0
- package/dist/internal/input-security.js +11 -0
- package/dist/internal/output-formatting.d.ts +3 -0
- package/dist/internal/output-formatting.js +21 -0
- package/dist/internal/presets.d.ts +3 -0
- package/dist/{shared/@outfitter/cli-pdb7znbq.js → internal/presets.js} +49 -223
- package/dist/internal/schema-commands.d.ts +3 -0
- package/dist/{shared/@outfitter/cli-5vtr4bdt.js → internal/schema-commands.js} +8 -99
- package/dist/internal/schema-formatting.d.ts +2 -0
- package/dist/internal/schema-formatting.js +7 -0
- package/dist/internal/schema-types.d.ts +2 -0
- package/dist/internal/schema-types.js +1 -0
- package/dist/output.d.ts +4 -3
- package/dist/output.js +13 -166
- package/dist/pagination.d.ts +1 -1
- package/dist/query.d.ts +84 -2
- package/dist/query.js +8 -45
- package/dist/schema-input.d.ts +80 -0
- package/dist/schema-input.js +15 -0
- package/dist/schema.d.ts +4 -1
- package/dist/schema.js +1 -1
- package/dist/shared/@outfitter/cli-10wxfc78.d.ts +45 -0
- package/dist/shared/@outfitter/cli-16wg5mka.d.ts +71 -0
- package/dist/shared/@outfitter/cli-1q5redaj.js +267 -0
- package/dist/shared/@outfitter/cli-2dfxs239.js +98 -0
- package/dist/shared/@outfitter/cli-30mt7c5w.d.ts +112 -0
- package/dist/shared/@outfitter/cli-3jta1h1h.js +134 -0
- package/dist/shared/@outfitter/cli-4h85mpth.js +76 -0
- package/dist/shared/@outfitter/cli-6shkwxdc.js +28 -0
- package/dist/shared/@outfitter/cli-89335n9a.js +16 -0
- package/dist/shared/@outfitter/cli-8999qjdd.js +3 -0
- package/dist/shared/@outfitter/cli-8cfxdady.js +60 -0
- package/dist/shared/@outfitter/cli-bcajqy33.d.ts +25 -0
- package/dist/shared/@outfitter/cli-c09332vm.d.ts +39 -0
- package/dist/shared/@outfitter/cli-cgha038c.d.ts +3 -0
- package/dist/shared/@outfitter/{cli-zahqsaby.js → cli-d40m2x1d.js} +19 -3
- package/dist/shared/@outfitter/cli-dg0cz7rw.js +127 -0
- package/dist/shared/@outfitter/cli-dv8kk4jw.d.ts +24 -0
- package/dist/shared/@outfitter/cli-g43887b7.js +20 -0
- package/dist/shared/@outfitter/cli-gqtkhgw4.js +52 -0
- package/dist/shared/@outfitter/cli-h4ejpmjs.d.ts +104 -0
- package/dist/shared/@outfitter/cli-htzez8v2.js +70 -0
- package/dist/shared/@outfitter/cli-hvg2m5gf.js +79 -0
- package/dist/shared/@outfitter/cli-n54zs151.d.ts +78 -0
- package/dist/shared/@outfitter/cli-nbpgw7z7.d.ts +15 -0
- package/dist/shared/@outfitter/cli-nkt399zf.d.ts +94 -0
- package/dist/shared/@outfitter/cli-pmd04gtv.d.ts +60 -0
- package/dist/shared/@outfitter/{cli-xy3gs50c.d.ts → cli-q6csxmeh.d.ts} +19 -12
- package/dist/shared/@outfitter/cli-qcskd96y.d.ts +11 -0
- package/dist/shared/@outfitter/cli-ry7btmy4.js +118 -0
- package/dist/shared/@outfitter/cli-sy99pjyj.js +32 -0
- package/dist/shared/@outfitter/cli-tm2fzngs.d.ts +23 -0
- package/dist/shared/@outfitter/cli-vvvhjwks.js +106 -0
- package/dist/shared/@outfitter/cli-wjv7g1aq.d.ts +16 -0
- package/dist/shared/@outfitter/{cli-98aa9104.d.ts → cli-x6qr7bnd.d.ts} +338 -16
- package/dist/shared/@outfitter/cli-xde45xcc.d.ts +53 -0
- package/dist/shared/@outfitter/cli-xw8ys1je.d.ts +123 -0
- package/dist/shared/@outfitter/cli-yfewnyc2.d.ts +43 -0
- package/dist/shared/@outfitter/cli-zkzj0q4q.js +99 -0
- package/dist/shared/@outfitter/cli-zv3ah6f0.js +3 -0
- package/dist/streaming.d.ts +47 -0
- package/dist/streaming.js +13 -0
- package/dist/truncation.d.ts +104 -0
- package/dist/truncation.js +111 -0
- package/dist/types.d.ts +2 -2
- package/dist/verbs.d.ts +1 -1
- package/package.json +55 -25
- package/dist/shared/@outfitter/cli-n1k0d23k.d.ts +0 -33
package/README.md
CHANGED
|
@@ -361,6 +361,105 @@ if (flags.reset) {
|
|
|
361
361
|
}
|
|
362
362
|
```
|
|
363
363
|
|
|
364
|
+
## NDJSON Streaming (`@outfitter/cli/streaming`)
|
|
365
|
+
|
|
366
|
+
Real-time progress reporting via newline-delimited JSON. Handlers emit progress events through an optional `ctx.progress` callback; the CLI adapter writes them as NDJSON lines to stdout.
|
|
367
|
+
|
|
368
|
+
```typescript
|
|
369
|
+
import { streamPreset } from "@outfitter/cli/query";
|
|
370
|
+
import { runHandler } from "@outfitter/cli/envelope";
|
|
371
|
+
|
|
372
|
+
command("deploy")
|
|
373
|
+
.preset(streamPreset())
|
|
374
|
+
.action(async ({ flags, input }) => {
|
|
375
|
+
await runHandler({
|
|
376
|
+
command: "deploy",
|
|
377
|
+
handler: async (input, ctx) => {
|
|
378
|
+
ctx.progress?.({ type: "progress", current: 1, total: 3 });
|
|
379
|
+
// ... work ...
|
|
380
|
+
return Result.ok({ status: "deployed" });
|
|
381
|
+
},
|
|
382
|
+
input,
|
|
383
|
+
stream: Boolean(flags.stream),
|
|
384
|
+
});
|
|
385
|
+
});
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
```
|
|
389
|
+
$ mycli deploy --stream
|
|
390
|
+
{"type":"start","command":"deploy","ts":"2025-01-01T00:00:00.000Z"}
|
|
391
|
+
{"type":"progress","current":1,"total":3}
|
|
392
|
+
{"ok":true,"command":"deploy","result":{"status":"deployed"}}
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
`--stream` is orthogonal to output mode — it controls delivery (streaming vs batch), not serialization. Types live in `@outfitter/contracts/stream`.
|
|
396
|
+
|
|
397
|
+
Low-level utilities (`writeNdjsonLine`, `writeStreamEnvelope`, `createNdjsonProgress`) are exported from `@outfitter/cli/streaming` for custom setups.
|
|
398
|
+
|
|
399
|
+
## Output Truncation (`@outfitter/cli/truncation`)
|
|
400
|
+
|
|
401
|
+
Truncate array output with pagination hints and optional file pointers for very large results.
|
|
402
|
+
|
|
403
|
+
```typescript
|
|
404
|
+
import { truncateOutput } from "@outfitter/cli/truncation";
|
|
405
|
+
|
|
406
|
+
const result = truncateOutput(items, {
|
|
407
|
+
limit: 20,
|
|
408
|
+
offset: 0,
|
|
409
|
+
commandName: "list",
|
|
410
|
+
});
|
|
411
|
+
// result.data — sliced items (length <= 20)
|
|
412
|
+
// result.metadata — { showing: 20, total: 500, truncated: true }
|
|
413
|
+
// result.hints — [{ description: "Show next 20 of 480...", command: "list --offset 20 --limit 20" }]
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
When output exceeds `filePointerThreshold` (default: 1000 items), the full result is written to a temp file and `metadata.full_output` contains the file path. File write failures degrade gracefully with a warning hint instead of crashing.
|
|
417
|
+
|
|
418
|
+
**Exports:** `truncateOutput`, `TruncationOptions`, `TruncationResult`, `TruncationMetadata`, `DEFAULT_FILE_POINTER_THRESHOLD`
|
|
419
|
+
|
|
420
|
+
## CommandBuilder
|
|
421
|
+
|
|
422
|
+
The `command()` builder provides a fluent API for defining CLI commands with typed input schemas, safety metadata, and hint generation:
|
|
423
|
+
|
|
424
|
+
```typescript
|
|
425
|
+
import { command } from "@outfitter/cli/command";
|
|
426
|
+
import { runHandler } from "@outfitter/cli/envelope";
|
|
427
|
+
|
|
428
|
+
command("delete <id>")
|
|
429
|
+
.description("Delete a resource")
|
|
430
|
+
.input(z.object({ id: z.string(), force: z.boolean().default(false) }))
|
|
431
|
+
.destructive(true) // auto-adds --dry-run flag
|
|
432
|
+
.relatedTo("list", { description: "List remaining resources" })
|
|
433
|
+
.hints((result, input) => [
|
|
434
|
+
{ description: "Verify", command: `show ${input.id}` },
|
|
435
|
+
])
|
|
436
|
+
.onError((error, input) => [
|
|
437
|
+
{ description: "Check exists", command: `show ${input.id}` },
|
|
438
|
+
])
|
|
439
|
+
.action(async ({ input, flags }) => {
|
|
440
|
+
await runHandler({
|
|
441
|
+
command: "delete",
|
|
442
|
+
handler: deleteHandler,
|
|
443
|
+
input,
|
|
444
|
+
dryRun: Boolean(flags.dryRun),
|
|
445
|
+
});
|
|
446
|
+
})
|
|
447
|
+
.build();
|
|
448
|
+
```
|
|
449
|
+
|
|
450
|
+
**Safety metadata methods:**
|
|
451
|
+
|
|
452
|
+
| Method | Effect |
|
|
453
|
+
| ------------------- | ----------------------------------------------------------------------------- |
|
|
454
|
+
| `.destructive()` | Auto-adds `--dry-run` flag; `runHandler({ dryRun })` generates execution hint |
|
|
455
|
+
| `.readOnly()` | Surfaces in command tree JSON and maps to MCP `readOnlyHint` |
|
|
456
|
+
| `.idempotent()` | Surfaces in command tree JSON and maps to MCP `idempotentHint` |
|
|
457
|
+
| `.relatedTo()` | Declares action graph edges for tier-4 hint generation |
|
|
458
|
+
| `.input(schema)` | Zod schema for auto-derived flags and validation |
|
|
459
|
+
| `.context(factory)` | Async factory for handler context construction |
|
|
460
|
+
| `.hints(fn)` | Success hint function called with `(result, input)` |
|
|
461
|
+
| `.onError(fn)` | Error hint function called with `(error, input)` |
|
|
462
|
+
|
|
364
463
|
## Conventions
|
|
365
464
|
|
|
366
465
|
Composable flag presets provide typed, reusable CLI flag definitions. See the [full conventions guide](../../docs/cli/conventions.md) for the complete catalog.
|
|
@@ -384,13 +483,17 @@ command("deploy")
|
|
|
384
483
|
});
|
|
385
484
|
```
|
|
386
485
|
|
|
387
|
-
**Available presets:** `verbosePreset`, `cwdPreset`, `dryRunPreset`, `forcePreset`, `interactionPreset`, `strictPreset`, `colorPreset`, `projectionPreset`, `paginationPreset`, `timeWindowPreset`, `executionPreset`, `outputModePreset`, `jqPreset`
|
|
486
|
+
**Available presets:** `verbosePreset`, `cwdPreset`, `dryRunPreset`, `forcePreset`, `interactionPreset`, `strictPreset`, `colorPreset`, `projectionPreset`, `paginationPreset`, `timeWindowPreset`, `executionPreset`, `outputModePreset`, `jqPreset`, `streamPreset`
|
|
388
487
|
|
|
389
488
|
**Additional modules:**
|
|
390
489
|
|
|
391
490
|
- `@outfitter/cli/verbs` — Standard verb families (`create`, `modify`, `remove`, `list`, `show`)
|
|
392
|
-
- `@outfitter/cli/query` — Output mode
|
|
491
|
+
- `@outfitter/cli/query` — Output mode, jq expression, and streaming presets
|
|
393
492
|
- `@outfitter/cli/completion` — Shell completion script generation
|
|
493
|
+
- `@outfitter/cli/streaming` — NDJSON streaming primitives
|
|
494
|
+
- `@outfitter/cli/truncation` — Output truncation with pagination hints
|
|
495
|
+
- `@outfitter/cli/envelope` — Response envelope construction and `runHandler()` lifecycle
|
|
496
|
+
- `@outfitter/cli/hints` — Hint generation tiers (command tree, error recovery, schema params, action graph)
|
|
394
497
|
|
|
395
498
|
## Configuration
|
|
396
499
|
|
package/dist/actions.d.ts
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
1
|
+
import "./shared/@outfitter/cli-cgha038c.js";
|
|
2
|
+
import "./shared/@outfitter/cli-nbpgw7z7.js";
|
|
3
|
+
import { SchemaCommandOptions } from "./shared/@outfitter/cli-wjv7g1aq.js";
|
|
4
|
+
import "./shared/@outfitter/cli-qcskd96y.js";
|
|
5
|
+
import { FlagPreset } from "./shared/@outfitter/cli-x6qr7bnd.js";
|
|
3
6
|
import { ActionCliInputContext, ActionCliOption, ActionRegistry, ActionSurface, AnyActionSpec, HandlerContext } from "@outfitter/contracts";
|
|
4
7
|
import { Command } from "commander";
|
|
5
8
|
interface BuildCliCommandsOptions {
|
package/dist/actions.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
// @bun
|
|
2
2
|
import {
|
|
3
3
|
createSchemaCommand
|
|
4
|
-
} from "./shared/@outfitter/cli-
|
|
4
|
+
} from "./shared/@outfitter/cli-zv3ah6f0.js";
|
|
5
5
|
import {
|
|
6
6
|
composePresets
|
|
7
|
-
} from "./shared/@outfitter/cli-
|
|
7
|
+
} from "./shared/@outfitter/cli-8999qjdd.js";
|
|
8
8
|
|
|
9
9
|
// packages/cli/src/actions.ts
|
|
10
10
|
import {
|
package/dist/cli.d.ts
CHANGED
package/dist/cli.js
CHANGED
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
// @bun
|
|
2
2
|
import {
|
|
3
3
|
createCLI
|
|
4
|
-
} from "./shared/@outfitter/cli-
|
|
4
|
+
} from "./shared/@outfitter/cli-d40m2x1d.js";
|
|
5
|
+
import"./shared/@outfitter/cli-89335n9a.js";
|
|
6
|
+
import"./shared/@outfitter/cli-htzez8v2.js";
|
|
7
|
+
import"./shared/@outfitter/cli-hvg2m5gf.js";
|
|
8
|
+
import"./shared/@outfitter/cli-2dfxs239.js";
|
|
9
|
+
import"./shared/@outfitter/cli-4h85mpth.js";
|
|
10
|
+
import"./shared/@outfitter/cli-dg0cz7rw.js";
|
|
11
|
+
import"./shared/@outfitter/cli-3jta1h1h.js";
|
|
5
12
|
export {
|
|
6
13
|
createCLI
|
|
7
14
|
};
|
package/dist/command.d.ts
CHANGED
|
@@ -1,43 +1,3 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
*/
|
|
5
|
-
declare function createCLI(config: CLIConfig): CLI;
|
|
6
|
-
/**
|
|
7
|
-
* Create a new command builder with the given name.
|
|
8
|
-
*
|
|
9
|
-
* The command builder provides a fluent API for defining CLI commands
|
|
10
|
-
* with typed flags, arguments, and actions.
|
|
11
|
-
*
|
|
12
|
-
* @param name - Command name and optional argument syntax (e.g., "list" or "get <id>")
|
|
13
|
-
* @returns A CommandBuilder instance for fluent configuration
|
|
14
|
-
*
|
|
15
|
-
* @example
|
|
16
|
-
* ```typescript
|
|
17
|
-
* import { command, output } from "@outfitter/cli";
|
|
18
|
-
*
|
|
19
|
-
* const list = command("list")
|
|
20
|
-
* .description("List all notes")
|
|
21
|
-
* .option("--limit <n>", "Max results", "20")
|
|
22
|
-
* .option("--json", "Output as JSON")
|
|
23
|
-
* .option("--next", "Continue from last position")
|
|
24
|
-
* .action(async ({ flags }) => {
|
|
25
|
-
* const results = await listNotes(flags);
|
|
26
|
-
* output(results);
|
|
27
|
-
* });
|
|
28
|
-
* ```
|
|
29
|
-
*
|
|
30
|
-
* @example
|
|
31
|
-
* ```typescript
|
|
32
|
-
* // Command with required argument
|
|
33
|
-
* const get = command("get <id>")
|
|
34
|
-
* .description("Get a note by ID")
|
|
35
|
-
* .action(async ({ args }) => {
|
|
36
|
-
* const [id] = args;
|
|
37
|
-
* const note = await getNote(id);
|
|
38
|
-
* output(note);
|
|
39
|
-
* });
|
|
40
|
-
* ```
|
|
41
|
-
*/
|
|
42
|
-
declare function command(name: string): CommandBuilder;
|
|
43
|
-
export { createCLI, command, FlagPreset, CommandFlags, CommandConfig, CommandBuilder, CommandAction, CLIConfig, CLI };
|
|
1
|
+
import { CommandMetadata, command, createCLI } from "./shared/@outfitter/cli-xde45xcc.js";
|
|
2
|
+
import { AnyPreset, CLI, CLIConfig, CommandAction, CommandBuilder, CommandConfig, CommandFlags, ContextFactory, ErrorHintFn, FlagPreset, RelatedToDeclaration, RelatedToOptions, SchemaPreset, SuccessHintFn, ZodObjectLike } from "./shared/@outfitter/cli-x6qr7bnd.js";
|
|
3
|
+
export { createCLI, command, ZodObjectLike, SuccessHintFn, SchemaPreset, RelatedToOptions, RelatedToDeclaration, FlagPreset, ErrorHintFn, ContextFactory, CommandMetadata, CommandFlags, CommandConfig, CommandBuilder, CommandAction, CLIConfig, CLI, AnyPreset };
|
package/dist/command.js
CHANGED
|
@@ -1,10 +1,87 @@
|
|
|
1
1
|
// @bun
|
|
2
2
|
import {
|
|
3
3
|
createCLI
|
|
4
|
-
} from "./shared/@outfitter/cli-
|
|
4
|
+
} from "./shared/@outfitter/cli-d40m2x1d.js";
|
|
5
|
+
import {
|
|
6
|
+
resolveOutputMode
|
|
7
|
+
} from "./shared/@outfitter/cli-vvvhjwks.js";
|
|
8
|
+
import {
|
|
9
|
+
isSchemaPreset
|
|
10
|
+
} from "./shared/@outfitter/cli-8999qjdd.js";
|
|
11
|
+
import"./shared/@outfitter/cli-89335n9a.js";
|
|
12
|
+
import"./shared/@outfitter/cli-htzez8v2.js";
|
|
13
|
+
import"./shared/@outfitter/cli-hvg2m5gf.js";
|
|
14
|
+
import"./shared/@outfitter/cli-2dfxs239.js";
|
|
15
|
+
import {
|
|
16
|
+
exitWithError
|
|
17
|
+
} from "./shared/@outfitter/cli-4h85mpth.js";
|
|
18
|
+
import"./shared/@outfitter/cli-dg0cz7rw.js";
|
|
19
|
+
import {
|
|
20
|
+
createCommanderOption,
|
|
21
|
+
deriveFlags,
|
|
22
|
+
validateInput
|
|
23
|
+
} from "./shared/@outfitter/cli-3jta1h1h.js";
|
|
5
24
|
|
|
6
25
|
// packages/cli/src/command.ts
|
|
7
26
|
import { Command } from "commander";
|
|
27
|
+
import { z } from "zod";
|
|
28
|
+
function buildMergedSchema(inputSchema, schemaPresets) {
|
|
29
|
+
if (!inputSchema && schemaPresets.length === 0)
|
|
30
|
+
return;
|
|
31
|
+
if (schemaPresets.length === 0)
|
|
32
|
+
return inputSchema;
|
|
33
|
+
if (!inputSchema && schemaPresets.length === 1) {
|
|
34
|
+
return schemaPresets[0].schema;
|
|
35
|
+
}
|
|
36
|
+
const inputKeys = new Set(Object.keys(inputSchema?.shape ?? {}));
|
|
37
|
+
const omitOverriddenPresetFields = (schema) => {
|
|
38
|
+
if (inputKeys.size === 0) {
|
|
39
|
+
return schema;
|
|
40
|
+
}
|
|
41
|
+
const filteredEntries = Object.entries(schema.shape).filter(([key]) => !inputKeys.has(key));
|
|
42
|
+
if (filteredEntries.length === 0) {
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
const filteredShape = Object.fromEntries(filteredEntries);
|
|
46
|
+
return z.object(filteredShape);
|
|
47
|
+
};
|
|
48
|
+
const mergedShape = {};
|
|
49
|
+
const allSchemas = [];
|
|
50
|
+
for (const preset of schemaPresets) {
|
|
51
|
+
const filteredPresetSchema = omitOverriddenPresetFields(preset.schema);
|
|
52
|
+
if (!filteredPresetSchema) {
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
Object.assign(mergedShape, filteredPresetSchema.shape);
|
|
56
|
+
allSchemas.push(filteredPresetSchema);
|
|
57
|
+
}
|
|
58
|
+
if (inputSchema) {
|
|
59
|
+
Object.assign(mergedShape, inputSchema.shape);
|
|
60
|
+
allSchemas.push(inputSchema);
|
|
61
|
+
}
|
|
62
|
+
return {
|
|
63
|
+
shape: mergedShape,
|
|
64
|
+
safeParse(data) {
|
|
65
|
+
let merged = {};
|
|
66
|
+
for (const schema of allSchemas) {
|
|
67
|
+
const schemaData = {};
|
|
68
|
+
if (data && typeof data === "object") {
|
|
69
|
+
for (const key of Object.keys(schema.shape)) {
|
|
70
|
+
if (key in data) {
|
|
71
|
+
schemaData[key] = data[key];
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
const result = schema.safeParse(schemaData);
|
|
76
|
+
if (!result.success) {
|
|
77
|
+
return result;
|
|
78
|
+
}
|
|
79
|
+
merged = { ...merged, ...result.data };
|
|
80
|
+
}
|
|
81
|
+
return { success: true, data: merged };
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
}
|
|
8
85
|
function parseCommandSignature(signature) {
|
|
9
86
|
const trimmed = signature.trim();
|
|
10
87
|
const firstWhitespace = trimmed.search(/\s/);
|
|
@@ -23,51 +100,202 @@ function createCLI2(config) {
|
|
|
23
100
|
}
|
|
24
101
|
|
|
25
102
|
class CommandBuilderImpl {
|
|
26
|
-
|
|
103
|
+
cmd;
|
|
104
|
+
inputSchema;
|
|
105
|
+
ctxFactory;
|
|
106
|
+
successHintFn;
|
|
107
|
+
errorHintsFn;
|
|
108
|
+
explicitLongFlags = new Set;
|
|
109
|
+
schemaPresets = [];
|
|
110
|
+
schemaFlagsApplied = false;
|
|
111
|
+
_destructive = false;
|
|
112
|
+
_readOnly = false;
|
|
113
|
+
_idempotent = false;
|
|
114
|
+
_relatedTo = [];
|
|
27
115
|
constructor(signature) {
|
|
28
116
|
const { name, argumentsSpec } = parseCommandSignature(signature);
|
|
29
|
-
this.
|
|
117
|
+
this.cmd = new Command(name);
|
|
30
118
|
if (argumentsSpec) {
|
|
31
|
-
this.
|
|
119
|
+
this.cmd.arguments(argumentsSpec);
|
|
32
120
|
}
|
|
33
121
|
}
|
|
34
122
|
description(text) {
|
|
35
|
-
this.
|
|
123
|
+
this.cmd.description(text);
|
|
36
124
|
return this;
|
|
37
125
|
}
|
|
38
126
|
option(flags, description, defaultValue) {
|
|
39
|
-
|
|
127
|
+
const longMatch = flags.match(/--([a-z][a-z0-9-]*)/i);
|
|
128
|
+
if (longMatch) {
|
|
129
|
+
this.explicitLongFlags.add(`--${longMatch[1]}`);
|
|
130
|
+
}
|
|
131
|
+
this.cmd.option(flags, description, defaultValue);
|
|
40
132
|
return this;
|
|
41
133
|
}
|
|
42
134
|
requiredOption(flags, description, defaultValue) {
|
|
43
|
-
|
|
135
|
+
const longMatch = flags.match(/--([a-z][a-z0-9-]*)/i);
|
|
136
|
+
if (longMatch) {
|
|
137
|
+
this.explicitLongFlags.add(`--${longMatch[1]}`);
|
|
138
|
+
}
|
|
139
|
+
this.cmd.requiredOption(flags, description, defaultValue);
|
|
44
140
|
return this;
|
|
45
141
|
}
|
|
46
142
|
alias(alias) {
|
|
47
|
-
this.
|
|
143
|
+
this.cmd.alias(alias);
|
|
144
|
+
return this;
|
|
145
|
+
}
|
|
146
|
+
input(schema) {
|
|
147
|
+
this.inputSchema = schema;
|
|
148
|
+
return this;
|
|
149
|
+
}
|
|
150
|
+
context(factory) {
|
|
151
|
+
this.ctxFactory = factory;
|
|
152
|
+
return this;
|
|
153
|
+
}
|
|
154
|
+
hints(fn) {
|
|
155
|
+
this.successHintFn = fn;
|
|
156
|
+
return this;
|
|
157
|
+
}
|
|
158
|
+
onError(fn) {
|
|
159
|
+
this.errorHintsFn = fn;
|
|
160
|
+
return this;
|
|
161
|
+
}
|
|
162
|
+
destructive(isDest) {
|
|
163
|
+
this._destructive = isDest;
|
|
164
|
+
return this;
|
|
165
|
+
}
|
|
166
|
+
readOnly(isReadOnly) {
|
|
167
|
+
this._readOnly = isReadOnly;
|
|
168
|
+
return this;
|
|
169
|
+
}
|
|
170
|
+
idempotent(isIdempotent) {
|
|
171
|
+
this._idempotent = isIdempotent;
|
|
172
|
+
return this;
|
|
173
|
+
}
|
|
174
|
+
relatedTo(target, options) {
|
|
175
|
+
this._relatedTo.push({
|
|
176
|
+
target,
|
|
177
|
+
...options?.description ? { description: options.description } : {}
|
|
178
|
+
});
|
|
48
179
|
return this;
|
|
49
180
|
}
|
|
50
181
|
preset(preset) {
|
|
182
|
+
if (isSchemaPreset(preset)) {
|
|
183
|
+
this.schemaPresets.push(preset);
|
|
184
|
+
return this;
|
|
185
|
+
}
|
|
51
186
|
for (const opt of preset.options) {
|
|
187
|
+
const longMatch = opt.flags.match(/--([a-z][a-z0-9-]*)/i);
|
|
188
|
+
if (longMatch) {
|
|
189
|
+
this.explicitLongFlags.add(`--${longMatch[1]}`);
|
|
190
|
+
}
|
|
52
191
|
if (opt.required) {
|
|
53
|
-
this.
|
|
192
|
+
this.cmd.requiredOption(opt.flags, opt.description, opt.defaultValue);
|
|
54
193
|
} else {
|
|
55
|
-
this.
|
|
194
|
+
this.cmd.option(opt.flags, opt.description, opt.defaultValue);
|
|
56
195
|
}
|
|
57
196
|
}
|
|
58
197
|
return this;
|
|
59
198
|
}
|
|
60
199
|
action(handler) {
|
|
61
|
-
|
|
200
|
+
const schema = this.inputSchema;
|
|
201
|
+
const contextFactory = this.ctxFactory;
|
|
202
|
+
const presets = [...this.schemaPresets];
|
|
203
|
+
this.applySchemaFlags();
|
|
204
|
+
const mergedSchema = buildMergedSchema(schema, presets);
|
|
205
|
+
this.cmd.action(async (...args) => {
|
|
62
206
|
const command = args.at(-1);
|
|
63
207
|
const flags = command.optsWithGlobals?.() ?? command.opts();
|
|
64
208
|
const positional = command.args;
|
|
65
|
-
|
|
209
|
+
let input;
|
|
210
|
+
let ctx;
|
|
211
|
+
try {
|
|
212
|
+
input = mergedSchema ? validateInput(flags, mergedSchema) : undefined;
|
|
213
|
+
if (input !== undefined && presets.length > 0) {
|
|
214
|
+
for (const preset of presets) {
|
|
215
|
+
const resolved = preset.resolve(flags);
|
|
216
|
+
Object.assign(input, resolved);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
if (contextFactory) {
|
|
220
|
+
const factoryArg = input !== undefined ? input : flags;
|
|
221
|
+
ctx = await contextFactory(factoryArg);
|
|
222
|
+
}
|
|
223
|
+
} catch (err) {
|
|
224
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
225
|
+
const { mode } = resolveOutputMode(flags);
|
|
226
|
+
exitWithError(error, mode);
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
await handler({
|
|
230
|
+
args: positional,
|
|
231
|
+
flags,
|
|
232
|
+
command,
|
|
233
|
+
input,
|
|
234
|
+
ctx
|
|
235
|
+
});
|
|
66
236
|
});
|
|
67
237
|
return this;
|
|
68
238
|
}
|
|
69
239
|
build() {
|
|
70
|
-
|
|
240
|
+
this.applySchemaFlags();
|
|
241
|
+
this.applyDestructiveFlag();
|
|
242
|
+
if (this.successHintFn) {
|
|
243
|
+
this.cmd.__successHintFn = this.successHintFn;
|
|
244
|
+
}
|
|
245
|
+
if (this.errorHintsFn) {
|
|
246
|
+
this.cmd.__errorHintFn = this.errorHintsFn;
|
|
247
|
+
}
|
|
248
|
+
const metadata = {};
|
|
249
|
+
if (this._readOnly) {
|
|
250
|
+
metadata.readOnly = true;
|
|
251
|
+
}
|
|
252
|
+
if (this._idempotent) {
|
|
253
|
+
metadata.idempotent = true;
|
|
254
|
+
}
|
|
255
|
+
if (metadata.readOnly !== undefined || metadata.idempotent !== undefined) {
|
|
256
|
+
this.cmd.__metadata = metadata;
|
|
257
|
+
}
|
|
258
|
+
if (this._relatedTo.length > 0) {
|
|
259
|
+
this.cmd.__relatedTo = [...this._relatedTo];
|
|
260
|
+
}
|
|
261
|
+
return this.cmd;
|
|
262
|
+
}
|
|
263
|
+
applySchemaFlags() {
|
|
264
|
+
if (this.schemaFlagsApplied)
|
|
265
|
+
return;
|
|
266
|
+
if (!this.inputSchema && this.schemaPresets.length === 0)
|
|
267
|
+
return;
|
|
268
|
+
this.schemaFlagsApplied = true;
|
|
269
|
+
const existingLongs = new Set(this.explicitLongFlags);
|
|
270
|
+
for (const opt of this.cmd.options) {
|
|
271
|
+
if (opt.long) {
|
|
272
|
+
existingLongs.add(opt.long);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
if (this.inputSchema) {
|
|
276
|
+
const derived = deriveFlags(this.inputSchema, existingLongs);
|
|
277
|
+
for (const flag of derived) {
|
|
278
|
+
const option = createCommanderOption(flag, this.inputSchema);
|
|
279
|
+
this.cmd.addOption(option);
|
|
280
|
+
existingLongs.add(flag.longFlag);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
for (const schemaPreset of this.schemaPresets) {
|
|
284
|
+
const derived = deriveFlags(schemaPreset.schema, existingLongs);
|
|
285
|
+
for (const flag of derived) {
|
|
286
|
+
const option = createCommanderOption(flag, schemaPreset.schema);
|
|
287
|
+
this.cmd.addOption(option);
|
|
288
|
+
existingLongs.add(flag.longFlag);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
applyDestructiveFlag() {
|
|
293
|
+
if (!this._destructive)
|
|
294
|
+
return;
|
|
295
|
+
const hasDryRun = this.cmd.options.some((o) => o.long === "--dry-run");
|
|
296
|
+
if (hasDryRun)
|
|
297
|
+
return;
|
|
298
|
+
this.cmd.option("--dry-run", "Preview changes without applying (destructive command safety)", false);
|
|
71
299
|
}
|
|
72
300
|
}
|
|
73
301
|
function command(name) {
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { runHandler } from "./shared/@outfitter/cli-c09332vm.js";
|
|
2
|
+
import { createErrorEnvelope, createSuccessEnvelope } from "./shared/@outfitter/cli-nkt399zf.js";
|
|
3
|
+
import { CommandEnvelope, ErrorEnvelope, RunHandlerOptions, SuccessEnvelope } from "./shared/@outfitter/cli-30mt7c5w.js";
|
|
4
|
+
import "./shared/@outfitter/cli-x6qr7bnd.js";
|
|
5
|
+
export { runHandler, createSuccessEnvelope, createErrorEnvelope, SuccessEnvelope, RunHandlerOptions, ErrorEnvelope, CommandEnvelope };
|
package/dist/envelope.js
ADDED
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
import {
|
|
3
|
+
buildDryRunHint,
|
|
4
|
+
createErrorEnvelope,
|
|
5
|
+
createSuccessEnvelope,
|
|
6
|
+
extractCategory,
|
|
7
|
+
extractMessage,
|
|
8
|
+
extractRetryAfterSeconds,
|
|
9
|
+
formatEnvelopeHuman,
|
|
10
|
+
getExitCode,
|
|
11
|
+
safeCallHintFn
|
|
12
|
+
} from "./shared/@outfitter/cli-ry7btmy4.js";
|
|
13
|
+
import {
|
|
14
|
+
createNdjsonProgress,
|
|
15
|
+
writeNdjsonLine,
|
|
16
|
+
writeStreamEnvelope
|
|
17
|
+
} from "./shared/@outfitter/cli-g43887b7.js";
|
|
18
|
+
import {
|
|
19
|
+
output
|
|
20
|
+
} from "./shared/@outfitter/cli-4h85mpth.js";
|
|
21
|
+
import {
|
|
22
|
+
detectMode
|
|
23
|
+
} from "./shared/@outfitter/cli-dg0cz7rw.js";
|
|
24
|
+
|
|
25
|
+
// packages/cli/src/envelope.ts
|
|
26
|
+
import { safeStringify } from "@outfitter/contracts";
|
|
27
|
+
async function runHandler(options) {
|
|
28
|
+
const {
|
|
29
|
+
command: commandName,
|
|
30
|
+
handler,
|
|
31
|
+
input,
|
|
32
|
+
format,
|
|
33
|
+
contextFactory,
|
|
34
|
+
hints: hintsFn,
|
|
35
|
+
onError: onErrorFn,
|
|
36
|
+
stream: isStreaming = false,
|
|
37
|
+
dryRun: isDryRun = false
|
|
38
|
+
} = options;
|
|
39
|
+
const inputValue = input;
|
|
40
|
+
const resolvedFormat = format ?? detectMode();
|
|
41
|
+
const isJsonMode = resolvedFormat === "json" || resolvedFormat === "jsonl";
|
|
42
|
+
let progressCallback;
|
|
43
|
+
if (isStreaming) {
|
|
44
|
+
const startEvent = {
|
|
45
|
+
type: "start",
|
|
46
|
+
command: commandName,
|
|
47
|
+
ts: new Date().toISOString()
|
|
48
|
+
};
|
|
49
|
+
writeNdjsonLine(startEvent);
|
|
50
|
+
const writeProgress = createNdjsonProgress(commandName);
|
|
51
|
+
progressCallback = (event) => {
|
|
52
|
+
if (event.type === "start") {
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
writeProgress(event);
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
let context;
|
|
59
|
+
if (contextFactory) {
|
|
60
|
+
try {
|
|
61
|
+
const rawContext = await contextFactory(inputValue);
|
|
62
|
+
if (isStreaming && rawContext && typeof rawContext === "object") {
|
|
63
|
+
context = Object.assign(Object.create(Object.getPrototypeOf(rawContext)), rawContext, { progress: progressCallback });
|
|
64
|
+
} else {
|
|
65
|
+
context = rawContext;
|
|
66
|
+
}
|
|
67
|
+
} catch (err) {
|
|
68
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
69
|
+
const category = extractCategory(error);
|
|
70
|
+
const message = extractMessage(error);
|
|
71
|
+
const retryAfter = extractRetryAfterSeconds(error);
|
|
72
|
+
const errorHints = onErrorFn ? safeCallHintFn(() => onErrorFn(error, inputValue)) : undefined;
|
|
73
|
+
const envelope = createErrorEnvelope(commandName, category, message, errorHints, retryAfter);
|
|
74
|
+
if (isStreaming) {
|
|
75
|
+
writeStreamEnvelope(envelope);
|
|
76
|
+
const exitCode = getExitCode(category);
|
|
77
|
+
process.exit(exitCode);
|
|
78
|
+
}
|
|
79
|
+
outputErrorEnvelope(envelope, isJsonMode);
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
} else if (isStreaming) {
|
|
83
|
+
context = { progress: progressCallback };
|
|
84
|
+
} else {
|
|
85
|
+
context = undefined;
|
|
86
|
+
}
|
|
87
|
+
let result;
|
|
88
|
+
try {
|
|
89
|
+
result = await handler(inputValue, context);
|
|
90
|
+
} catch (err) {
|
|
91
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
92
|
+
const category = extractCategory(error);
|
|
93
|
+
const message = extractMessage(error);
|
|
94
|
+
const retryAfter = extractRetryAfterSeconds(error);
|
|
95
|
+
const errorHints = onErrorFn ? safeCallHintFn(() => onErrorFn(error, inputValue)) : undefined;
|
|
96
|
+
const envelope = createErrorEnvelope(commandName, category, message, errorHints, retryAfter);
|
|
97
|
+
if (isStreaming) {
|
|
98
|
+
writeStreamEnvelope(envelope);
|
|
99
|
+
const exitCode = getExitCode(category);
|
|
100
|
+
process.exit(exitCode);
|
|
101
|
+
}
|
|
102
|
+
outputErrorEnvelope(envelope, isJsonMode);
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
if (result.isOk()) {
|
|
106
|
+
let successHints = hintsFn ? safeCallHintFn(() => hintsFn(result.value, inputValue)) : undefined;
|
|
107
|
+
if (isDryRun) {
|
|
108
|
+
const dryRunHint = buildDryRunHint(options.argv);
|
|
109
|
+
if (dryRunHint) {
|
|
110
|
+
successHints = successHints ? [...successHints, dryRunHint] : [dryRunHint];
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
const envelope = createSuccessEnvelope(commandName, result.value, successHints);
|
|
114
|
+
if (isStreaming) {
|
|
115
|
+
writeStreamEnvelope(envelope);
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
if (isJsonMode) {
|
|
119
|
+
await output(envelope, resolvedFormat);
|
|
120
|
+
} else {
|
|
121
|
+
const formatted = formatEnvelopeHuman(envelope);
|
|
122
|
+
if (formatted.stdout) {
|
|
123
|
+
process.stdout.write(`${formatted.stdout}
|
|
124
|
+
`);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
} else {
|
|
128
|
+
const error = result.error;
|
|
129
|
+
const category = extractCategory(error);
|
|
130
|
+
const message = extractMessage(error);
|
|
131
|
+
const retryAfter = extractRetryAfterSeconds(error);
|
|
132
|
+
const errorHints = onErrorFn ? safeCallHintFn(() => onErrorFn(error, inputValue)) : undefined;
|
|
133
|
+
const envelope = createErrorEnvelope(commandName, category, message, errorHints, retryAfter);
|
|
134
|
+
if (isStreaming) {
|
|
135
|
+
writeStreamEnvelope(envelope);
|
|
136
|
+
const exitCode = getExitCode(category);
|
|
137
|
+
process.exit(exitCode);
|
|
138
|
+
}
|
|
139
|
+
outputErrorEnvelope(envelope, isJsonMode);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
function outputErrorEnvelope(envelope, isJsonMode) {
|
|
143
|
+
const exitCode = getExitCode(envelope.error.category);
|
|
144
|
+
if (isJsonMode) {
|
|
145
|
+
process.stderr.write(`${safeStringify(envelope)}
|
|
146
|
+
`);
|
|
147
|
+
} else {
|
|
148
|
+
const formatted = formatEnvelopeHuman(envelope);
|
|
149
|
+
if (formatted.stderr) {
|
|
150
|
+
process.stderr.write(`${formatted.stderr}
|
|
151
|
+
`);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
process.exit(exitCode);
|
|
155
|
+
}
|
|
156
|
+
export {
|
|
157
|
+
runHandler,
|
|
158
|
+
createSuccessEnvelope,
|
|
159
|
+
createErrorEnvelope
|
|
160
|
+
};
|