@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.
- package/README.md +179 -60
- 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/colors/index.js +1 -17
- 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/index.js +2 -17
- 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/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-0cjts94k.js → internal/schema-commands.js} +12 -100
- 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 +10 -2
- 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-7wp5nj0s.js → cli-dg0cz7rw.js} +39 -81
- 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/terminal/index.js +1 -19
- package/dist/truncation.d.ts +104 -0
- package/dist/truncation.js +111 -0
- package/dist/types.d.ts +2 -2
- package/dist/types.js +0 -5
- package/dist/verbs.d.ts +1 -1
- package/package.json +66 -36
- package/dist/shared/@outfitter/cli-n1k0d23k.d.ts +0 -33
- /package/dist/{shared/@outfitter/cli-zw75pdk8.js → internal/envelope-types.js} +0 -0
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// packages/cli/src/internal/hint-action-graph.ts
|
|
3
|
+
function buildActionGraph(program) {
|
|
4
|
+
const nodes = [];
|
|
5
|
+
const edges = [];
|
|
6
|
+
const warnings = [];
|
|
7
|
+
const commandNames = new Set;
|
|
8
|
+
function collectCommandNames(cmds, prefix = "") {
|
|
9
|
+
for (const cmd of cmds) {
|
|
10
|
+
const leaf = cmd.name();
|
|
11
|
+
const fullName = prefix ? `${prefix} ${leaf}` : leaf;
|
|
12
|
+
commandNames.add(fullName);
|
|
13
|
+
if (cmd.commands.length > 0) {
|
|
14
|
+
collectCommandNames(cmd.commands, fullName);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
collectCommandNames(program.commands);
|
|
19
|
+
function walkCommands(cmds, prefix = "") {
|
|
20
|
+
for (const cmd of cmds) {
|
|
21
|
+
const leaf = cmd.name();
|
|
22
|
+
const fullName = prefix ? `${prefix} ${leaf}` : leaf;
|
|
23
|
+
nodes.push(fullName);
|
|
24
|
+
const relatedTo = cmd.__relatedTo;
|
|
25
|
+
if (relatedTo) {
|
|
26
|
+
for (const decl of relatedTo) {
|
|
27
|
+
const edge = {
|
|
28
|
+
from: fullName,
|
|
29
|
+
to: decl.target,
|
|
30
|
+
...decl.description ? { description: decl.description } : {}
|
|
31
|
+
};
|
|
32
|
+
edges.push(edge);
|
|
33
|
+
if (!commandNames.has(decl.target)) {
|
|
34
|
+
warnings.push(`Unknown relationship target "${decl.target}" from command "${fullName}"`);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
if (cmd.commands.length > 0) {
|
|
39
|
+
walkCommands(cmd.commands, fullName);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
walkCommands(program.commands);
|
|
44
|
+
return {
|
|
45
|
+
nodes,
|
|
46
|
+
edges,
|
|
47
|
+
...warnings.length > 0 ? { warnings } : {}
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
function graphSuccessHints(graph, commandName, cliName) {
|
|
51
|
+
const hints = [];
|
|
52
|
+
for (const edge of graph.edges) {
|
|
53
|
+
if (edge.from === commandName && edge.to !== commandName) {
|
|
54
|
+
const prefix = cliName ? `${cliName} ` : "";
|
|
55
|
+
const description = edge.description ?? `Run ${edge.to}`;
|
|
56
|
+
hints.push({
|
|
57
|
+
description,
|
|
58
|
+
command: `${prefix}${edge.to}`
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return hints;
|
|
63
|
+
}
|
|
64
|
+
function graphErrorHints(graph, commandName, cliName) {
|
|
65
|
+
const hints = [];
|
|
66
|
+
for (const edge of graph.edges) {
|
|
67
|
+
if (edge.from === commandName && edge.to !== commandName) {
|
|
68
|
+
const prefix = cliName ? `${cliName} ` : "";
|
|
69
|
+
const description = edge.description ? `Try: ${edge.description}` : `Try: ${edge.to}`;
|
|
70
|
+
hints.push({
|
|
71
|
+
description,
|
|
72
|
+
command: `${prefix}${edge.to}`
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return hints;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export { buildActionGraph, graphSuccessHints, graphErrorHints };
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { ComposedPreset, FlagPreset, FlagPresetConfig, SchemaPreset, SchemaPresetConfig } from "./cli-x6qr7bnd.js";
|
|
2
|
+
/** Extracts the resolved type from a flag preset. */
|
|
3
|
+
type ResolvedType<T> = T extends FlagPreset<infer R> ? R : never;
|
|
4
|
+
/** Converts a union type into an intersection type. */
|
|
5
|
+
type UnionToIntersection<U> = (U extends unknown ? (k: U) => void : never) extends (k: infer I) => void ? I : never;
|
|
6
|
+
/**
|
|
7
|
+
* Resolves the merged type from a tuple of presets.
|
|
8
|
+
* Falls back to Record<string, unknown> when the tuple is empty
|
|
9
|
+
* (since UnionToIntersection<never> produces unknown).
|
|
10
|
+
*/
|
|
11
|
+
type MergedPresetResult<TPresets extends readonly FlagPreset<Record<string, unknown>>[]> = UnionToIntersection<ResolvedType<TPresets[number]>> extends Record<string, unknown> ? UnionToIntersection<ResolvedType<TPresets[number]>> : Record<string, unknown>;
|
|
12
|
+
/**
|
|
13
|
+
* Create a typed flag preset.
|
|
14
|
+
*
|
|
15
|
+
* @param config - Preset configuration with id, options, and resolver
|
|
16
|
+
* @returns A flag preset that can be applied to commands or composed
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```typescript
|
|
20
|
+
* const myPreset = createPreset({
|
|
21
|
+
* id: "output-format",
|
|
22
|
+
* options: [{ flags: "--format <type>", description: "Output format" }],
|
|
23
|
+
* resolve: (flags) => ({
|
|
24
|
+
* format: String(flags["format"] ?? "text"),
|
|
25
|
+
* }),
|
|
26
|
+
* });
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
declare function createPreset<TResolved extends Record<string, unknown>>(config: FlagPresetConfig<TResolved>): FlagPreset<TResolved>;
|
|
30
|
+
/**
|
|
31
|
+
* Create a schema-driven preset using a Zod schema fragment.
|
|
32
|
+
*
|
|
33
|
+
* Instead of declaring Commander options manually, the schema fragment
|
|
34
|
+
* is introspected to auto-derive flags (same as `.input()`). The resolve
|
|
35
|
+
* function maps raw Commander flags into typed values.
|
|
36
|
+
*
|
|
37
|
+
* @param config - Preset configuration with id, schema, and resolver
|
|
38
|
+
* @returns A schema preset that can be applied to commands via `.preset()`
|
|
39
|
+
*
|
|
40
|
+
* @example
|
|
41
|
+
* ```typescript
|
|
42
|
+
* const outputPreset = createSchemaPreset({
|
|
43
|
+
* id: "output-format",
|
|
44
|
+
* schema: z.object({
|
|
45
|
+
* format: z.enum(["json", "text"]).default("text").describe("Output format"),
|
|
46
|
+
* pretty: z.boolean().default(false).describe("Pretty print"),
|
|
47
|
+
* }),
|
|
48
|
+
* resolve: (flags) => ({
|
|
49
|
+
* format: String(flags["format"] ?? "text"),
|
|
50
|
+
* pretty: Boolean(flags["pretty"]),
|
|
51
|
+
* }),
|
|
52
|
+
* });
|
|
53
|
+
* ```
|
|
54
|
+
*/
|
|
55
|
+
declare function createSchemaPreset<TResolved extends Record<string, unknown>>(config: SchemaPresetConfig<TResolved>): SchemaPreset<TResolved>;
|
|
56
|
+
/**
|
|
57
|
+
* Type guard to check whether a preset is a schema-driven preset.
|
|
58
|
+
*/
|
|
59
|
+
declare function isSchemaPreset(preset: FlagPreset<Record<string, unknown>> | SchemaPreset<Record<string, unknown>>): preset is SchemaPreset<Record<string, unknown>>;
|
|
60
|
+
/**
|
|
61
|
+
* Compose multiple presets into one, deduplicating by id (first wins).
|
|
62
|
+
*
|
|
63
|
+
* @param presets - Presets to compose together
|
|
64
|
+
* @returns A single preset merging all options and resolvers
|
|
65
|
+
*
|
|
66
|
+
* @example
|
|
67
|
+
* ```typescript
|
|
68
|
+
* const composed = composePresets(
|
|
69
|
+
* verbosePreset(),
|
|
70
|
+
* cwdPreset(),
|
|
71
|
+
* forcePreset(),
|
|
72
|
+
* );
|
|
73
|
+
* // composed.resolve({ verbose: true, cwd: "/tmp", force: false })
|
|
74
|
+
* // => { verbose: true, cwd: "/tmp", force: false }
|
|
75
|
+
* ```
|
|
76
|
+
*/
|
|
77
|
+
declare function composePresets<TPresets extends readonly FlagPreset<Record<string, unknown>>[]>(...presets: TPresets): ComposedPreset<MergedPresetResult<TPresets>>;
|
|
78
|
+
export { createPreset, createSchemaPreset, isSchemaPreset, composePresets };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { SchemaCommandOptions } from "./cli-wjv7g1aq.js";
|
|
2
|
+
import { ActionSource } from "@outfitter/schema";
|
|
3
|
+
import { Command } from "commander";
|
|
4
|
+
/**
|
|
5
|
+
* Create a `schema` command for CLI introspection.
|
|
6
|
+
*
|
|
7
|
+
* When `options.surface` is provided, adds `generate` and `diff` subcommands
|
|
8
|
+
* for surface map file I/O and drift detection.
|
|
9
|
+
*
|
|
10
|
+
* @param source - ActionRegistry or array of ActionSpec
|
|
11
|
+
* @param options - Command configuration
|
|
12
|
+
* @returns A Commander command instance
|
|
13
|
+
*/
|
|
14
|
+
declare function createSchemaCommand(source: ActionSource, options?: SchemaCommandOptions): Command;
|
|
15
|
+
export { createSchemaCommand };
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { CommandEnvelope, ErrorEnvelope, SuccessEnvelope } from "./cli-30mt7c5w.js";
|
|
2
|
+
import { CLIHint, ErrorCategory } from "@outfitter/contracts";
|
|
3
|
+
/**
|
|
4
|
+
* Create a success envelope wrapping a command result.
|
|
5
|
+
*
|
|
6
|
+
* The `hints` field is omitted when hints is undefined, null, or empty.
|
|
7
|
+
*
|
|
8
|
+
* @param command - Command name
|
|
9
|
+
* @param result - Handler result value
|
|
10
|
+
* @param hints - Optional CLI hints for next actions
|
|
11
|
+
* @returns A success envelope
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```typescript
|
|
15
|
+
* const envelope = createSuccessEnvelope("deploy", { status: "deployed" }, [
|
|
16
|
+
* { description: "Check status", command: "deploy status" },
|
|
17
|
+
* ]);
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
declare function createSuccessEnvelope<T>(command: string, result: T, hints?: CLIHint[]): SuccessEnvelope<T>;
|
|
21
|
+
/**
|
|
22
|
+
* Create an error envelope wrapping a command failure.
|
|
23
|
+
*
|
|
24
|
+
* The `hints` field is omitted when hints is undefined, null, or empty.
|
|
25
|
+
* The `retryable` field is derived from the error category metadata.
|
|
26
|
+
* The `retry_after` field is included only when `retryAfterSeconds` is provided
|
|
27
|
+
* (typically from a `RateLimitError`).
|
|
28
|
+
*
|
|
29
|
+
* @param command - Command name
|
|
30
|
+
* @param category - Error category from the taxonomy
|
|
31
|
+
* @param message - Human-readable error message
|
|
32
|
+
* @param hints - Optional CLI hints for error recovery
|
|
33
|
+
* @param retryAfterSeconds - Optional retry delay in seconds (from RateLimitError)
|
|
34
|
+
* @returns An error envelope
|
|
35
|
+
*
|
|
36
|
+
* @example
|
|
37
|
+
* ```typescript
|
|
38
|
+
* const envelope = createErrorEnvelope("deploy", "validation", "Missing env", [
|
|
39
|
+
* { description: "Specify env", command: "deploy --env prod" },
|
|
40
|
+
* ]);
|
|
41
|
+
* // envelope.error.retryable === false
|
|
42
|
+
*
|
|
43
|
+
* const rateLimitEnvelope = createErrorEnvelope(
|
|
44
|
+
* "fetch",
|
|
45
|
+
* "rate_limit",
|
|
46
|
+
* "Too many requests",
|
|
47
|
+
* undefined,
|
|
48
|
+
* 60
|
|
49
|
+
* );
|
|
50
|
+
* // rateLimitEnvelope.error.retryable === true, retry_after === 60
|
|
51
|
+
* ```
|
|
52
|
+
*/
|
|
53
|
+
declare function createErrorEnvelope(command: string, category: ErrorCategory, message: string, hints?: CLIHint[], retryAfterSeconds?: number): ErrorEnvelope;
|
|
54
|
+
/**
|
|
55
|
+
* Build a CLIHint for executing the current command without --dry-run.
|
|
56
|
+
*
|
|
57
|
+
* Strips the --dry-run flag and its variants (--dry-run=true, --dry-run=false, etc.),
|
|
58
|
+
* producing a hint like: `delete --id abc --force` (without --dry-run).
|
|
59
|
+
*
|
|
60
|
+
* @param argv - Parsed argv to strip --dry-run from. Defaults to `process.argv.slice(2)`.
|
|
61
|
+
*/
|
|
62
|
+
declare function buildDryRunHint(argv?: readonly string[]): CLIHint | undefined;
|
|
63
|
+
/**
|
|
64
|
+
* Extract error category from an error object.
|
|
65
|
+
* Works with OutfitterError instances and duck-typed errors.
|
|
66
|
+
*/
|
|
67
|
+
declare function extractCategory(error: unknown): ErrorCategory;
|
|
68
|
+
/**
|
|
69
|
+
* Extract error message from an error object.
|
|
70
|
+
*/
|
|
71
|
+
declare function extractMessage(error: unknown): string;
|
|
72
|
+
/**
|
|
73
|
+
* Extract retryAfterSeconds from an error object (if present).
|
|
74
|
+
* Works with RateLimitError instances and duck-typed errors.
|
|
75
|
+
*/
|
|
76
|
+
declare function extractRetryAfterSeconds(error: unknown): number | undefined;
|
|
77
|
+
/**
|
|
78
|
+
* Get exit code for an error category.
|
|
79
|
+
*/
|
|
80
|
+
declare function getExitCode(category: ErrorCategory): number;
|
|
81
|
+
/**
|
|
82
|
+
* Format an envelope for human-readable output.
|
|
83
|
+
* Returns stdout and stderr portions separately.
|
|
84
|
+
*/
|
|
85
|
+
declare function formatEnvelopeHuman(envelope: CommandEnvelope): {
|
|
86
|
+
stdout: string;
|
|
87
|
+
stderr: string;
|
|
88
|
+
};
|
|
89
|
+
/**
|
|
90
|
+
* Safely call a hint function, returning undefined if it throws.
|
|
91
|
+
* Hint functions should never cause the command to fail.
|
|
92
|
+
*/
|
|
93
|
+
declare function safeCallHintFn(fn: () => CLIHint[]): CLIHint[] | undefined;
|
|
94
|
+
export { createSuccessEnvelope, createErrorEnvelope, buildDryRunHint, extractCategory, extractMessage, extractRetryAfterSeconds, getExitCode, formatEnvelopeHuman, safeCallHintFn };
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { ActionGraph } from "./cli-16wg5mka.js";
|
|
2
|
+
import { CLIHint } from "@outfitter/contracts";
|
|
3
|
+
import { Command } from "commander";
|
|
4
|
+
/**
|
|
5
|
+
* Build an action graph from a Commander program's registered commands.
|
|
6
|
+
*
|
|
7
|
+
* Walks all commands, collecting `.relatedTo()` declarations as edges.
|
|
8
|
+
* Unknown targets produce warnings (not crashes). Self-links and cycles
|
|
9
|
+
* are preserved in the graph — callers handle them during traversal.
|
|
10
|
+
*
|
|
11
|
+
* @param program - The Commander program (root command)
|
|
12
|
+
* @returns An action graph with nodes, edges, and optional warnings
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```typescript
|
|
16
|
+
* const graph = buildActionGraph(program);
|
|
17
|
+
* // graph.nodes: ["deploy", "status", "rollback"]
|
|
18
|
+
* // graph.edges: [{ from: "deploy", to: "status", description: "Check status" }]
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
declare function buildActionGraph(program: Command): ActionGraph;
|
|
22
|
+
/**
|
|
23
|
+
* Generate tier-4 success hints from action graph neighbors (Tier 4).
|
|
24
|
+
*
|
|
25
|
+
* For a given command, returns CLIHint[] for all outgoing edges in the
|
|
26
|
+
* action graph. Self-links are excluded (they would be confusing as
|
|
27
|
+
* "next action" suggestions).
|
|
28
|
+
*
|
|
29
|
+
* @param graph - The action graph
|
|
30
|
+
* @param commandName - The command that just succeeded
|
|
31
|
+
* @param cliName - CLI name for hint command prefix
|
|
32
|
+
* @returns Array of CLI hints for related next-actions
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* ```typescript
|
|
36
|
+
* const hints = graphSuccessHints(graph, "deploy", "my-cli");
|
|
37
|
+
* // [{ description: "Check deployment status", command: "my-cli status" }]
|
|
38
|
+
* ```
|
|
39
|
+
*/
|
|
40
|
+
declare function graphSuccessHints(graph: ActionGraph, commandName: string, cliName?: string): CLIHint[];
|
|
41
|
+
/**
|
|
42
|
+
* Generate tier-4 error hints from action graph neighbors (Tier 4).
|
|
43
|
+
*
|
|
44
|
+
* For a given command that failed, returns CLIHint[] for related
|
|
45
|
+
* remediation paths. Self-links are excluded. Error hints use the
|
|
46
|
+
* relationship description as remediation context.
|
|
47
|
+
*
|
|
48
|
+
* @param graph - The action graph
|
|
49
|
+
* @param commandName - The command that failed
|
|
50
|
+
* @param cliName - CLI name for hint command prefix
|
|
51
|
+
* @returns Array of CLI hints for remediation paths
|
|
52
|
+
*
|
|
53
|
+
* @example
|
|
54
|
+
* ```typescript
|
|
55
|
+
* const hints = graphErrorHints(graph, "deploy", "my-cli");
|
|
56
|
+
* // [{ description: "Try: Rollback deployment", command: "my-cli rollback" }]
|
|
57
|
+
* ```
|
|
58
|
+
*/
|
|
59
|
+
declare function graphErrorHints(graph: ActionGraph, commandName: string, cliName?: string): CLIHint[];
|
|
60
|
+
export { buildActionGraph, graphSuccessHints, graphErrorHints };
|
|
@@ -1,34 +1,40 @@
|
|
|
1
|
-
import { OutputOptions } from "./cli-
|
|
1
|
+
import { OutputMode, OutputOptions } from "./cli-x6qr7bnd.js";
|
|
2
2
|
/**
|
|
3
3
|
* Output data to the console with automatic mode selection.
|
|
4
4
|
*
|
|
5
5
|
* Respects --json, --jsonl, --tree, --table flags automatically.
|
|
6
6
|
* Defaults to human-friendly output when no flags are present.
|
|
7
7
|
*
|
|
8
|
+
* Detection hierarchy (highest wins):
|
|
9
|
+
* 1. Explicit `format` parameter
|
|
10
|
+
* 2. Environment variables (`OUTFITTER_JSON`, `OUTFITTER_JSONL`)
|
|
11
|
+
* 3. Default: `"human"`
|
|
12
|
+
*
|
|
8
13
|
* @param data - The data to output
|
|
14
|
+
* @param format - Explicit output format (e.g. from a resolved CLI flag)
|
|
9
15
|
* @param options - Output configuration options
|
|
10
16
|
*
|
|
11
17
|
* @example
|
|
12
18
|
* ```typescript
|
|
13
19
|
* import { output } from "@outfitter/cli";
|
|
14
20
|
*
|
|
15
|
-
* // Basic usage - mode auto-detected from
|
|
21
|
+
* // Basic usage - mode auto-detected from env
|
|
16
22
|
* output(results);
|
|
17
23
|
*
|
|
18
|
-
* //
|
|
19
|
-
* output(results,
|
|
24
|
+
* // Explicit format from resolved flag
|
|
25
|
+
* output(results, "json");
|
|
20
26
|
*
|
|
21
27
|
* // Pretty-print JSON
|
|
22
|
-
* output(results,
|
|
28
|
+
* output(results, "json", { pretty: true });
|
|
23
29
|
*
|
|
24
30
|
* // Output to stderr
|
|
25
|
-
* output(errors, { stream: process.stderr });
|
|
31
|
+
* output(errors, undefined, { stream: process.stderr });
|
|
26
32
|
*
|
|
27
33
|
* // Await for large outputs (recommended)
|
|
28
|
-
* await output(largeDataset,
|
|
34
|
+
* await output(largeDataset, "jsonl");
|
|
29
35
|
* ```
|
|
30
36
|
*/
|
|
31
|
-
declare function output(data: unknown, options?: OutputOptions): Promise<void>;
|
|
37
|
+
declare function output(data: unknown, format?: OutputMode, options?: OutputOptions): Promise<void>;
|
|
32
38
|
/**
|
|
33
39
|
* Exit the process with an error message.
|
|
34
40
|
*
|
|
@@ -36,6 +42,7 @@ declare function output(data: unknown, options?: OutputOptions): Promise<void>;
|
|
|
36
42
|
* and exits with an appropriate exit code.
|
|
37
43
|
*
|
|
38
44
|
* @param error - The error to display
|
|
45
|
+
* @param format - Explicit output format (e.g. from a resolved CLI flag)
|
|
39
46
|
* @returns Never returns (exits the process)
|
|
40
47
|
*
|
|
41
48
|
* @example
|
|
@@ -49,7 +56,7 @@ declare function output(data: unknown, options?: OutputOptions): Promise<void>;
|
|
|
49
56
|
* }
|
|
50
57
|
* ```
|
|
51
58
|
*/
|
|
52
|
-
declare function exitWithError(error: Error,
|
|
59
|
+
declare function exitWithError(error: Error, format?: OutputMode): never;
|
|
53
60
|
/**
|
|
54
61
|
* Resolve verbose mode from environment configuration.
|
|
55
62
|
*
|
|
@@ -69,9 +76,9 @@ declare function exitWithError(error: Error, options?: OutputOptions): never;
|
|
|
69
76
|
* // Auto-resolve from environment
|
|
70
77
|
* const isVerbose = resolveVerbose();
|
|
71
78
|
*
|
|
72
|
-
* // With OUTFITTER_ENV=development
|
|
73
|
-
* // With OUTFITTER_VERBOSE=0
|
|
74
|
-
* // With nothing set
|
|
79
|
+
* // With OUTFITTER_ENV=development -> true
|
|
80
|
+
* // With OUTFITTER_VERBOSE=0 -> false (overrides everything)
|
|
81
|
+
* // With nothing set -> false
|
|
75
82
|
*
|
|
76
83
|
* // From CLI flag
|
|
77
84
|
* const isVerbose = resolveVerbose(cliFlags.verbose);
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { ActionManifest } from "@outfitter/schema";
|
|
2
|
+
/**
|
|
3
|
+
* Format a manifest for human-readable terminal output.
|
|
4
|
+
*
|
|
5
|
+
* @param manifest - The manifest to format
|
|
6
|
+
* @param programName - CLI program name (for header)
|
|
7
|
+
* @param actionId - If provided, show detail for this single action
|
|
8
|
+
* @returns Formatted string
|
|
9
|
+
*/
|
|
10
|
+
declare function formatManifestHuman(manifest: ActionManifest, programName?: string, actionId?: string): string;
|
|
11
|
+
export { formatManifestHuman };
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
import {
|
|
3
|
+
formatHuman
|
|
4
|
+
} from "./cli-dg0cz7rw.js";
|
|
5
|
+
|
|
6
|
+
// packages/cli/src/internal/envelope-helpers.ts
|
|
7
|
+
import { errorCategoryMeta, exitCodeMap } from "@outfitter/contracts";
|
|
8
|
+
function createSuccessEnvelope(command, result, hints) {
|
|
9
|
+
const envelope = {
|
|
10
|
+
ok: true,
|
|
11
|
+
command,
|
|
12
|
+
result
|
|
13
|
+
};
|
|
14
|
+
if (hints && hints.length > 0) {
|
|
15
|
+
return { ...envelope, hints };
|
|
16
|
+
}
|
|
17
|
+
return envelope;
|
|
18
|
+
}
|
|
19
|
+
function createErrorEnvelope(command, category, message, hints, retryAfterSeconds) {
|
|
20
|
+
const meta = errorCategoryMeta(category);
|
|
21
|
+
const includeRetryAfter = retryAfterSeconds != null && category === "rate_limit";
|
|
22
|
+
const errorField = includeRetryAfter ? {
|
|
23
|
+
category,
|
|
24
|
+
message,
|
|
25
|
+
retryable: meta.retryable,
|
|
26
|
+
retry_after: retryAfterSeconds
|
|
27
|
+
} : { category, message, retryable: meta.retryable };
|
|
28
|
+
const envelope = {
|
|
29
|
+
ok: false,
|
|
30
|
+
command,
|
|
31
|
+
error: errorField
|
|
32
|
+
};
|
|
33
|
+
if (hints && hints.length > 0) {
|
|
34
|
+
return { ...envelope, hints };
|
|
35
|
+
}
|
|
36
|
+
return envelope;
|
|
37
|
+
}
|
|
38
|
+
function buildDryRunHint(argv = process.argv.slice(2)) {
|
|
39
|
+
const filteredArgs = argv.filter((arg) => arg !== "--dry-run" && !arg.startsWith("--dry-run="));
|
|
40
|
+
const command = filteredArgs.join(" ");
|
|
41
|
+
if (!command)
|
|
42
|
+
return;
|
|
43
|
+
return {
|
|
44
|
+
description: "Execute without dry-run",
|
|
45
|
+
command
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
var DEFAULT_CATEGORY = "internal";
|
|
49
|
+
var DEFAULT_EXIT_CODE = 1;
|
|
50
|
+
function extractCategory(error) {
|
|
51
|
+
if (error !== null && typeof error === "object" && "category" in error && typeof error["category"] === "string") {
|
|
52
|
+
const cat = error["category"];
|
|
53
|
+
if (cat in exitCodeMap) {
|
|
54
|
+
return cat;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return DEFAULT_CATEGORY;
|
|
58
|
+
}
|
|
59
|
+
function extractMessage(error) {
|
|
60
|
+
if (error instanceof Error) {
|
|
61
|
+
return error.message;
|
|
62
|
+
}
|
|
63
|
+
return String(error);
|
|
64
|
+
}
|
|
65
|
+
function extractRetryAfterSeconds(error) {
|
|
66
|
+
if (error !== null && typeof error === "object" && "retryAfterSeconds" in error && typeof error["retryAfterSeconds"] === "number") {
|
|
67
|
+
return error["retryAfterSeconds"];
|
|
68
|
+
}
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
function getExitCode(category) {
|
|
72
|
+
return exitCodeMap[category] ?? DEFAULT_EXIT_CODE;
|
|
73
|
+
}
|
|
74
|
+
function formatEnvelopeHuman(envelope) {
|
|
75
|
+
if (envelope.ok) {
|
|
76
|
+
const parts2 = [];
|
|
77
|
+
const formatted = formatHuman(envelope.result);
|
|
78
|
+
if (formatted) {
|
|
79
|
+
parts2.push(formatted);
|
|
80
|
+
}
|
|
81
|
+
if (envelope.hints && envelope.hints.length > 0) {
|
|
82
|
+
parts2.push("");
|
|
83
|
+
parts2.push("Hints:");
|
|
84
|
+
for (const hint of envelope.hints) {
|
|
85
|
+
parts2.push(` ${hint.description}`);
|
|
86
|
+
if (hint.command) {
|
|
87
|
+
parts2.push(` $ ${hint.command}`);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return { stdout: parts2.join(`
|
|
92
|
+
`), stderr: "" };
|
|
93
|
+
}
|
|
94
|
+
const parts = [];
|
|
95
|
+
parts.push(`Error: ${envelope.error.message}`);
|
|
96
|
+
if (envelope.hints && envelope.hints.length > 0) {
|
|
97
|
+
parts.push("");
|
|
98
|
+
parts.push("Hints:");
|
|
99
|
+
for (const hint of envelope.hints) {
|
|
100
|
+
parts.push(` ${hint.description}`);
|
|
101
|
+
if (hint.command) {
|
|
102
|
+
parts.push(` $ ${hint.command}`);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return { stdout: "", stderr: parts.join(`
|
|
107
|
+
`) };
|
|
108
|
+
}
|
|
109
|
+
function safeCallHintFn(fn) {
|
|
110
|
+
try {
|
|
111
|
+
const hints = fn();
|
|
112
|
+
return hints.length > 0 ? hints : undefined;
|
|
113
|
+
} catch {
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export { createSuccessEnvelope, createErrorEnvelope, buildDryRunHint, extractCategory, extractMessage, extractRetryAfterSeconds, getExitCode, formatEnvelopeHuman, safeCallHintFn };
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// packages/cli/src/internal/input-security.ts
|
|
3
|
+
import path from "path";
|
|
4
|
+
function isSecurePath(filePath, allowAbsolute = false) {
|
|
5
|
+
if (filePath.includes("\x00")) {
|
|
6
|
+
return false;
|
|
7
|
+
}
|
|
8
|
+
if (filePath.includes("..")) {
|
|
9
|
+
return false;
|
|
10
|
+
}
|
|
11
|
+
const normalized = path.normalize(filePath);
|
|
12
|
+
if (!allowAbsolute && path.isAbsolute(normalized)) {
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
return true;
|
|
16
|
+
}
|
|
17
|
+
function isSecureGlobPattern(pattern) {
|
|
18
|
+
if (pattern.startsWith("..")) {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
if (pattern.includes("/../")) {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
function isWithinWorkspace(resolvedPath, workspaceRoot) {
|
|
27
|
+
const normalizedPath = path.normalize(resolvedPath);
|
|
28
|
+
const normalizedRoot = path.normalize(workspaceRoot);
|
|
29
|
+
return normalizedPath === normalizedRoot || normalizedPath.startsWith(normalizedRoot + path.sep);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export { isSecurePath, isSecureGlobPattern, isWithinWorkspace };
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { CLIHint, ErrorCategory } from "@outfitter/contracts";
|
|
2
|
+
/**
|
|
3
|
+
* Produce standard recovery CLIHints for an error category (Tier 2).
|
|
4
|
+
*
|
|
5
|
+
* Uses the enriched ErrorCategory metadata (retryable flags from OS-347)
|
|
6
|
+
* to generate appropriate recovery actions. Retryable categories include
|
|
7
|
+
* retry hints; non-retryable categories guide toward root cause resolution.
|
|
8
|
+
*
|
|
9
|
+
* @param category - The error category
|
|
10
|
+
* @param cliName - Optional CLI name for command hints
|
|
11
|
+
* @param commandName - Optional command name to replace `<previous-command>` placeholder
|
|
12
|
+
* @returns Array of recovery hints
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```typescript
|
|
16
|
+
* const hints = errorRecoveryHints("conflict", "my-cli", "update");
|
|
17
|
+
* // [{ description: "Resolve the conflict and retry",
|
|
18
|
+
* // command: "my-cli update --force",
|
|
19
|
+
* // params: { retryable: true } }]
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
declare function errorRecoveryHints(category: ErrorCategory, cliName?: string, commandName?: string): CLIHint[];
|
|
23
|
+
export { errorRecoveryHints };
|