@linxiraos/pi-utils 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/CHANGELOG.md +449 -0
- package/README.md +36 -0
- package/dist/types/abortable.d.ts +32 -0
- package/dist/types/acp/connection.d.ts +118 -0
- package/dist/types/acp/protocol.d.ts +526 -0
- package/dist/types/acp/schema.d.ts +41 -0
- package/dist/types/acp/stream.d.ts +8 -0
- package/dist/types/acp/transport.d.ts +81 -0
- package/dist/types/acp.d.ts +6 -0
- package/dist/types/async.d.ts +20 -0
- package/dist/types/binary.d.ts +17 -0
- package/dist/types/browsers.d.ts +68 -0
- package/dist/types/chalk.d.ts +125 -0
- package/dist/types/cli.d.ts +134 -0
- package/dist/types/color.d.ts +102 -0
- package/dist/types/dates.d.ts +7 -0
- package/dist/types/dirs.d.ts +282 -0
- package/dist/types/docx/converter.d.ts +46 -0
- package/dist/types/docx/xml.d.ts +26 -0
- package/dist/types/docx/zip.d.ts +6 -0
- package/dist/types/docx.d.ts +11 -0
- package/dist/types/dom/core.d.ts +431 -0
- package/dist/types/dom/parser.d.ts +7 -0
- package/dist/types/dom/selector.d.ts +5 -0
- package/dist/types/dom.d.ts +5 -0
- package/dist/types/env.d.ts +124 -0
- package/dist/types/fetch-retry.d.ts +96 -0
- package/dist/types/file-lock.d.ts +23 -0
- package/dist/types/format.d.ts +37 -0
- package/dist/types/frontmatter.d.ts +46 -0
- package/dist/types/fs-error.d.ts +31 -0
- package/dist/types/glob.d.ts +28 -0
- package/dist/types/headers.d.ts +34 -0
- package/dist/types/index.d.ts +39 -0
- package/dist/types/json-parse.d.ts +74 -0
- package/dist/types/json.d.ts +14 -0
- package/dist/types/logger/rotating-file.d.ts +18 -0
- package/dist/types/logger.d.ts +96 -0
- package/dist/types/loop-phase.d.ts +10 -0
- package/dist/types/lru.d.ts +46 -0
- package/dist/types/marked/core.d.ts +445 -0
- package/dist/types/marked.d.ts +2 -0
- package/dist/types/mermaid-ascii.d.ts +11 -0
- package/dist/types/mime.d.ts +29 -0
- package/dist/types/module-timer.d.ts +1 -0
- package/dist/types/path-tree.d.ts +76 -0
- package/dist/types/path.d.ts +2 -0
- package/dist/types/peek-file.d.ts +29 -0
- package/dist/types/postmortem.d.ts +104 -0
- package/dist/types/process-name.d.ts +7 -0
- package/dist/types/procmgr.d.ts +74 -0
- package/dist/types/prompt.d.ts +18 -0
- package/dist/types/ptree.d.ts +110 -0
- package/dist/types/readability/readability.d.ts +9 -0
- package/dist/types/readability/readerable.d.ts +10 -0
- package/dist/types/readability/types.d.ts +70 -0
- package/dist/types/readability.d.ts +4 -0
- package/dist/types/ring.d.ts +93 -0
- package/dist/types/runtime-install.d.ts +78 -0
- package/dist/types/sanitize-text.d.ts +29 -0
- package/dist/types/snowflake.d.ts +25 -0
- package/dist/types/stderr-guard.d.ts +22 -0
- package/dist/types/stream.d.ts +67 -0
- package/dist/types/tab-spacing.d.ts +24 -0
- package/dist/types/temp.d.ts +17 -0
- package/dist/types/template.d.ts +62 -0
- package/dist/types/timing-buffer.d.ts +22 -0
- package/dist/types/tls-fetch.d.ts +37 -0
- package/dist/types/turndown/gfm.d.ts +11 -0
- package/dist/types/turndown/html.d.ts +5 -0
- package/dist/types/turndown/service.d.ts +21 -0
- package/dist/types/turndown/types.d.ts +70 -0
- package/dist/types/turndown.d.ts +4 -0
- package/dist/types/type-guards.d.ts +5 -0
- package/dist/types/vendor/mermaid-ascii/ascii/ansi.d.ts +41 -0
- package/dist/types/vendor/mermaid-ascii/ascii/canvas.d.ts +89 -0
- package/dist/types/vendor/mermaid-ascii/ascii/class-diagram.d.ts +7 -0
- package/dist/types/vendor/mermaid-ascii/ascii/converter.d.ts +12 -0
- package/dist/types/vendor/mermaid-ascii/ascii/draw.d.ts +66 -0
- package/dist/types/vendor/mermaid-ascii/ascii/edge-bundling.d.ts +48 -0
- package/dist/types/vendor/mermaid-ascii/ascii/edge-routing.d.ts +43 -0
- package/dist/types/vendor/mermaid-ascii/ascii/er-diagram.d.ts +7 -0
- package/dist/types/vendor/mermaid-ascii/ascii/grid.d.ts +56 -0
- package/dist/types/vendor/mermaid-ascii/ascii/index.d.ts +65 -0
- package/dist/types/vendor/mermaid-ascii/ascii/multiline-utils.d.ts +27 -0
- package/dist/types/vendor/mermaid-ascii/ascii/pathfinder.d.ts +17 -0
- package/dist/types/vendor/mermaid-ascii/ascii/sequence.d.ts +7 -0
- package/dist/types/vendor/mermaid-ascii/ascii/shapes/circle.d.ts +11 -0
- package/dist/types/vendor/mermaid-ascii/ascii/shapes/corners.d.ts +34 -0
- package/dist/types/vendor/mermaid-ascii/ascii/shapes/diamond.d.ts +11 -0
- package/dist/types/vendor/mermaid-ascii/ascii/shapes/hexagon.d.ts +11 -0
- package/dist/types/vendor/mermaid-ascii/ascii/shapes/index.d.ts +26 -0
- package/dist/types/vendor/mermaid-ascii/ascii/shapes/rectangle.d.ts +31 -0
- package/dist/types/vendor/mermaid-ascii/ascii/shapes/rounded.d.ts +11 -0
- package/dist/types/vendor/mermaid-ascii/ascii/shapes/special.d.ts +59 -0
- package/dist/types/vendor/mermaid-ascii/ascii/shapes/stadium.d.ts +17 -0
- package/dist/types/vendor/mermaid-ascii/ascii/shapes/state.d.ts +30 -0
- package/dist/types/vendor/mermaid-ascii/ascii/shapes/types.d.ts +55 -0
- package/dist/types/vendor/mermaid-ascii/ascii/types.d.ts +206 -0
- package/dist/types/vendor/mermaid-ascii/ascii/validate.d.ts +51 -0
- package/dist/types/vendor/mermaid-ascii/ascii/xychart.d.ts +2 -0
- package/dist/types/vendor/mermaid-ascii/class/parser.d.ts +6 -0
- package/dist/types/vendor/mermaid-ascii/class/types.d.ts +102 -0
- package/dist/types/vendor/mermaid-ascii/er/parser.d.ts +6 -0
- package/dist/types/vendor/mermaid-ascii/er/types.d.ts +76 -0
- package/dist/types/vendor/mermaid-ascii/index.d.ts +1 -0
- package/dist/types/vendor/mermaid-ascii/multiline-utils.d.ts +9 -0
- package/dist/types/vendor/mermaid-ascii/parser.d.ts +7 -0
- package/dist/types/vendor/mermaid-ascii/sequence/parser.d.ts +6 -0
- package/dist/types/vendor/mermaid-ascii/sequence/types.d.ts +130 -0
- package/dist/types/vendor/mermaid-ascii/text-metrics.d.ts +21 -0
- package/dist/types/vendor/mermaid-ascii/types.d.ts +114 -0
- package/dist/types/vendor/mermaid-ascii/xychart/colors.d.ts +25 -0
- package/dist/types/vendor/mermaid-ascii/xychart/parser.d.ts +6 -0
- package/dist/types/vendor/mermaid-ascii/xychart/types.d.ts +145 -0
- package/dist/types/version.d.ts +18 -0
- package/dist/types/vterm/buffer.d.ts +99 -0
- package/dist/types/vterm/terminal.d.ts +44 -0
- package/dist/types/vterm.d.ts +8 -0
- package/dist/types/which.d.ts +37 -0
- package/dist/types/worker-host.d.ts +51 -0
- package/dist/types/xml.d.ts +31 -0
- package/package.json +59 -0
- package/src/abortable.ts +109 -0
- package/src/acp/connection.ts +344 -0
- package/src/acp/protocol.ts +466 -0
- package/src/acp/schema.ts +160 -0
- package/src/acp/stream.ts +82 -0
- package/src/acp/transport.ts +213 -0
- package/src/acp.ts +6 -0
- package/src/async.ts +91 -0
- package/src/binary.ts +50 -0
- package/src/browsers.ts +501 -0
- package/src/chalk.ts +312 -0
- package/src/cli.ts +529 -0
- package/src/color.ts +302 -0
- package/src/dates.ts +194 -0
- package/src/dirs.ts +1026 -0
- package/src/docx/converter.ts +681 -0
- package/src/docx/xml.ts +166 -0
- package/src/docx/zip.ts +87 -0
- package/src/docx.ts +20 -0
- package/src/dom/core.ts +1254 -0
- package/src/dom/parser.ts +370 -0
- package/src/dom/selector.ts +290 -0
- package/src/dom.ts +33 -0
- package/src/env.ts +400 -0
- package/src/fetch-retry.ts +379 -0
- package/src/file-lock.ts +69 -0
- package/src/format.ts +113 -0
- package/src/frontmatter.ts +205 -0
- package/src/fs-error.ts +56 -0
- package/src/glob.ts +189 -0
- package/src/headers.ts +167 -0
- package/src/index.ts +59 -0
- package/src/json-parse.ts +821 -0
- package/src/json.ts +23 -0
- package/src/logger/rotating-file.ts +149 -0
- package/src/logger.ts +798 -0
- package/src/loop-phase.ts +49 -0
- package/src/lru.ts +185 -0
- package/src/marked/core.ts +1576 -0
- package/src/marked.ts +2 -0
- package/src/mermaid-ascii.ts +31 -0
- package/src/mime.ts +159 -0
- package/src/module-timer.ts +148 -0
- package/src/path-tree.ts +147 -0
- package/src/path.ts +28 -0
- package/src/peek-file.ts +188 -0
- package/src/postmortem.ts +433 -0
- package/src/process-name.ts +57 -0
- package/src/procmgr.ts +247 -0
- package/src/prompt.ts +545 -0
- package/src/ptree.ts +412 -0
- package/src/readability/readability.ts +533 -0
- package/src/readability/readerable.ts +51 -0
- package/src/readability/types.ts +72 -0
- package/src/readability.ts +11 -0
- package/src/ring.ts +169 -0
- package/src/runtime-install.ts +393 -0
- package/src/sanitize-text.ts +96 -0
- package/src/snowflake.ts +121 -0
- package/src/stderr-guard.ts +155 -0
- package/src/stream.ts +454 -0
- package/src/tab-spacing.ts +336 -0
- package/src/temp.ts +134 -0
- package/src/template.ts +586 -0
- package/src/timing-buffer.ts +47 -0
- package/src/tls-fetch.ts +178 -0
- package/src/turndown/gfm.ts +106 -0
- package/src/turndown/html.ts +257 -0
- package/src/turndown/service.ts +334 -0
- package/src/turndown/types.ts +81 -0
- package/src/turndown.ts +5 -0
- package/src/type-guards.ts +17 -0
- package/src/vendor/mermaid-ascii/NOTICE +33 -0
- package/src/vendor/mermaid-ascii/ascii/ansi.ts +409 -0
- package/src/vendor/mermaid-ascii/ascii/canvas.ts +476 -0
- package/src/vendor/mermaid-ascii/ascii/class-diagram.ts +699 -0
- package/src/vendor/mermaid-ascii/ascii/converter.ts +271 -0
- package/src/vendor/mermaid-ascii/ascii/draw.ts +1382 -0
- package/src/vendor/mermaid-ascii/ascii/edge-bundling.ts +328 -0
- package/src/vendor/mermaid-ascii/ascii/edge-routing.ts +297 -0
- package/src/vendor/mermaid-ascii/ascii/er-diagram.ts +441 -0
- package/src/vendor/mermaid-ascii/ascii/grid.ts +578 -0
- package/src/vendor/mermaid-ascii/ascii/index.ts +187 -0
- package/src/vendor/mermaid-ascii/ascii/multiline-utils.ts +78 -0
- package/src/vendor/mermaid-ascii/ascii/pathfinder.ts +277 -0
- package/src/vendor/mermaid-ascii/ascii/sequence.ts +460 -0
- package/src/vendor/mermaid-ascii/ascii/shapes/circle.ts +27 -0
- package/src/vendor/mermaid-ascii/ascii/shapes/corners.ts +127 -0
- package/src/vendor/mermaid-ascii/ascii/shapes/diamond.ts +27 -0
- package/src/vendor/mermaid-ascii/ascii/shapes/hexagon.ts +27 -0
- package/src/vendor/mermaid-ascii/ascii/shapes/index.ts +101 -0
- package/src/vendor/mermaid-ascii/ascii/shapes/rectangle.ts +175 -0
- package/src/vendor/mermaid-ascii/ascii/shapes/rounded.ts +27 -0
- package/src/vendor/mermaid-ascii/ascii/shapes/special.ts +296 -0
- package/src/vendor/mermaid-ascii/ascii/shapes/stadium.ts +114 -0
- package/src/vendor/mermaid-ascii/ascii/shapes/state.ts +192 -0
- package/src/vendor/mermaid-ascii/ascii/shapes/types.ts +73 -0
- package/src/vendor/mermaid-ascii/ascii/types.ts +273 -0
- package/src/vendor/mermaid-ascii/ascii/validate.ts +120 -0
- package/src/vendor/mermaid-ascii/ascii/xychart.ts +875 -0
- package/src/vendor/mermaid-ascii/class/parser.ts +290 -0
- package/src/vendor/mermaid-ascii/class/types.ts +121 -0
- package/src/vendor/mermaid-ascii/er/parser.ts +181 -0
- package/src/vendor/mermaid-ascii/er/types.ts +91 -0
- package/src/vendor/mermaid-ascii/index.ts +14 -0
- package/src/vendor/mermaid-ascii/multiline-utils.ts +30 -0
- package/src/vendor/mermaid-ascii/parser.ts +645 -0
- package/src/vendor/mermaid-ascii/sequence/parser.ts +207 -0
- package/src/vendor/mermaid-ascii/sequence/types.ts +146 -0
- package/src/vendor/mermaid-ascii/text-metrics.ts +71 -0
- package/src/vendor/mermaid-ascii/types.ts +164 -0
- package/src/vendor/mermaid-ascii/xychart/colors.ts +140 -0
- package/src/vendor/mermaid-ascii/xychart/parser.ts +115 -0
- package/src/vendor/mermaid-ascii/xychart/types.ts +150 -0
- package/src/version.ts +99 -0
- package/src/vterm/buffer.ts +218 -0
- package/src/vterm/terminal.ts +773 -0
- package/src/vterm.ts +8 -0
- package/src/which.ts +234 -0
- package/src/worker-host.ts +100 -0
- package/src/xml.ts +313 -0
package/src/cli.ts
ADDED
|
@@ -0,0 +1,529 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal CLI framework — drop-in replacement for the subset of @oclif/core
|
|
3
|
+
* actually used by the coding agent. Provides `Command`, `Args`, `Flags`,
|
|
4
|
+
* and a `run()` entry point with explicit command registration.
|
|
5
|
+
*
|
|
6
|
+
* Design goals:
|
|
7
|
+
* - Zero dependencies beyond node builtins
|
|
8
|
+
* - No filesystem scanning, no manifest files, no plugin loading
|
|
9
|
+
* - Lazy command imports (only the invoked command is loaded)
|
|
10
|
+
* - Typed `this.parse()` output matching oclif's API shape
|
|
11
|
+
*/
|
|
12
|
+
import * as fs from "node:fs";
|
|
13
|
+
import { parseArgs as nodeParseArgs } from "node:util";
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Streaming startup marker, enabled by `PI_DEBUG_STARTUP`. Local copy of
|
|
17
|
+
* `logger.startupMarker` so the minimal `--version`/bootstrap import graph
|
|
18
|
+
* stays free of the winston-backed logger module. Synchronous on purpose:
|
|
19
|
+
* a command module whose import hangs (dlopen, fs on a dead mount) must
|
|
20
|
+
* still leave its `:start` marker behind.
|
|
21
|
+
*/
|
|
22
|
+
function startupMarker(text: string): void {
|
|
23
|
+
if (!process.env.PI_DEBUG_STARTUP) return;
|
|
24
|
+
try {
|
|
25
|
+
fs.writeSync(2, `[startup] ${text}\n`);
|
|
26
|
+
} catch {
|
|
27
|
+
// stderr unavailable; markers are best-effort
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* A user-facing argument/flag validation failure. Thrown by {@link Command.parse}
|
|
33
|
+
* for missing/invalid positionals and flags. The top-level {@link run} handler
|
|
34
|
+
* prints its message plus the command usage line to stderr and exits 1, instead
|
|
35
|
+
* of letting it bubble to the process-level catch — which would dump a minified
|
|
36
|
+
* `dist/cli.js` code frame over a plain argument mistake (issue #5369).
|
|
37
|
+
*/
|
|
38
|
+
export class CliUsageError extends Error {
|
|
39
|
+
constructor(message: string) {
|
|
40
|
+
super(message);
|
|
41
|
+
this.name = "CliUsageError";
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// ---------------------------------------------------------------------------
|
|
46
|
+
// Flag & Arg descriptors
|
|
47
|
+
// ---------------------------------------------------------------------------
|
|
48
|
+
|
|
49
|
+
export interface FlagDescriptor<K extends "string" | "boolean" | "integer" = "string" | "boolean" | "integer"> {
|
|
50
|
+
kind: K;
|
|
51
|
+
description?: string;
|
|
52
|
+
char?: string;
|
|
53
|
+
default?: unknown;
|
|
54
|
+
multiple?: boolean;
|
|
55
|
+
options?: readonly string[];
|
|
56
|
+
required?: boolean;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export interface ArgDescriptor {
|
|
60
|
+
kind: "string";
|
|
61
|
+
description?: string;
|
|
62
|
+
required?: boolean;
|
|
63
|
+
multiple?: boolean;
|
|
64
|
+
options?: readonly string[];
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
interface FlagInput {
|
|
68
|
+
description?: string;
|
|
69
|
+
char?: string;
|
|
70
|
+
default?: unknown;
|
|
71
|
+
multiple?: boolean;
|
|
72
|
+
options?: readonly string[];
|
|
73
|
+
required?: boolean;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
interface ArgInput {
|
|
77
|
+
description?: string;
|
|
78
|
+
required?: boolean;
|
|
79
|
+
multiple?: boolean;
|
|
80
|
+
options?: readonly string[];
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** Builders that match the `Flags.*()` / `Args.*()` API from oclif. */
|
|
84
|
+
export const Flags = {
|
|
85
|
+
string<T extends FlagInput>(opts?: T): FlagDescriptor<"string"> & T {
|
|
86
|
+
return { kind: "string" as const, ...opts } as FlagDescriptor<"string"> & T;
|
|
87
|
+
},
|
|
88
|
+
boolean<T extends FlagInput>(opts?: T): FlagDescriptor<"boolean"> & T {
|
|
89
|
+
return { kind: "boolean" as const, ...opts } as FlagDescriptor<"boolean"> & T;
|
|
90
|
+
},
|
|
91
|
+
integer<T extends FlagInput & { default?: number }>(opts?: T): FlagDescriptor<"integer"> & T {
|
|
92
|
+
return { kind: "integer" as const, ...opts } as FlagDescriptor<"integer"> & T;
|
|
93
|
+
},
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
export const Args = {
|
|
97
|
+
string<T extends ArgInput>(opts?: T): ArgDescriptor & T {
|
|
98
|
+
return { kind: "string" as const, ...opts } as ArgDescriptor & T;
|
|
99
|
+
},
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
// ---------------------------------------------------------------------------
|
|
103
|
+
// Parse result types — mirrors oclif's typed output from this.parse()
|
|
104
|
+
// ---------------------------------------------------------------------------
|
|
105
|
+
|
|
106
|
+
type FlagValue<D extends FlagDescriptor> = D["kind"] extends "boolean"
|
|
107
|
+
? D extends { default: boolean }
|
|
108
|
+
? boolean
|
|
109
|
+
: boolean | undefined
|
|
110
|
+
: D["kind"] extends "integer"
|
|
111
|
+
? D extends { default: number }
|
|
112
|
+
? number
|
|
113
|
+
: number | undefined
|
|
114
|
+
: D extends { multiple: true }
|
|
115
|
+
? string[] | undefined
|
|
116
|
+
: string | undefined;
|
|
117
|
+
|
|
118
|
+
type ArgValue<D extends ArgDescriptor> = D extends { multiple: true } ? string[] | undefined : string | undefined;
|
|
119
|
+
|
|
120
|
+
type FlagValues<T extends Record<string, FlagDescriptor>> = { [K in keyof T]: FlagValue<T[K]> };
|
|
121
|
+
type ArgValues<T extends Record<string, ArgDescriptor>> = { [K in keyof T]: ArgValue<T[K]> };
|
|
122
|
+
|
|
123
|
+
export interface ParseOutput<
|
|
124
|
+
F extends Record<string, FlagDescriptor> = Record<string, FlagDescriptor>,
|
|
125
|
+
A extends Record<string, ArgDescriptor> = Record<string, ArgDescriptor>,
|
|
126
|
+
> {
|
|
127
|
+
flags: FlagValues<F>;
|
|
128
|
+
args: ArgValues<A>;
|
|
129
|
+
argv: string[];
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// ---------------------------------------------------------------------------
|
|
133
|
+
// Command base class
|
|
134
|
+
// ---------------------------------------------------------------------------
|
|
135
|
+
|
|
136
|
+
export interface CommandMetadata {
|
|
137
|
+
description?: string;
|
|
138
|
+
hidden?: boolean;
|
|
139
|
+
flags?: Record<string, FlagDescriptor>;
|
|
140
|
+
args?: Record<string, ArgDescriptor>;
|
|
141
|
+
examples?: string[];
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export interface CommandCtor extends CommandMetadata {
|
|
145
|
+
new (argv: string[], config: CliConfig): Command;
|
|
146
|
+
strict?: boolean;
|
|
147
|
+
aliases?: string[];
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/** Configuration passed to every command instance and help renderers. */
|
|
151
|
+
export interface CliConfig<TCommand extends CommandMetadata = CommandCtor> {
|
|
152
|
+
bin: string;
|
|
153
|
+
version: string;
|
|
154
|
+
/** All registered commands keyed by their canonical name. */
|
|
155
|
+
commands: Map<string, TCommand>;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/** Minimal Command base matching the oclif surface we use. */
|
|
159
|
+
export abstract class Command {
|
|
160
|
+
argv: string[];
|
|
161
|
+
config: CliConfig;
|
|
162
|
+
|
|
163
|
+
constructor(argv: string[], config: CliConfig) {
|
|
164
|
+
this.argv = argv;
|
|
165
|
+
this.config = config;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
abstract run(): Promise<void>;
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Parse argv against the static `flags` and `args` declared on the
|
|
172
|
+
* concrete command class. Returns a typed `{ flags, args, argv }` object.
|
|
173
|
+
*/
|
|
174
|
+
async parse<C extends CommandCtor>(
|
|
175
|
+
_Cmd: C,
|
|
176
|
+
): Promise<
|
|
177
|
+
ParseOutput<
|
|
178
|
+
NonNullable<C["flags"]> extends Record<string, FlagDescriptor>
|
|
179
|
+
? NonNullable<C["flags"]>
|
|
180
|
+
: Record<string, FlagDescriptor>,
|
|
181
|
+
NonNullable<C["args"]> extends Record<string, ArgDescriptor>
|
|
182
|
+
? NonNullable<C["args"]>
|
|
183
|
+
: Record<string, ArgDescriptor>
|
|
184
|
+
>
|
|
185
|
+
> {
|
|
186
|
+
const Cmd = _Cmd as CommandCtor;
|
|
187
|
+
const flagDefs = (Cmd.flags ?? {}) as Record<string, FlagDescriptor>;
|
|
188
|
+
const argDefs = (Cmd.args ?? {}) as Record<string, ArgDescriptor>;
|
|
189
|
+
const strict = Cmd.strict !== false;
|
|
190
|
+
|
|
191
|
+
// Build node:util parseArgs options from flag descriptors
|
|
192
|
+
const options: Record<
|
|
193
|
+
string,
|
|
194
|
+
{ type: "string" | "boolean"; short?: string; multiple?: boolean; default?: string | boolean }
|
|
195
|
+
> = {};
|
|
196
|
+
for (const [name, desc] of Object.entries(flagDefs)) {
|
|
197
|
+
const opt: (typeof options)[string] = {
|
|
198
|
+
type: desc.kind === "boolean" ? "boolean" : "string",
|
|
199
|
+
};
|
|
200
|
+
if (desc.char) opt.short = desc.char;
|
|
201
|
+
if (desc.multiple) opt.multiple = true;
|
|
202
|
+
if (desc.default !== undefined) {
|
|
203
|
+
opt.default = desc.kind === "boolean" ? Boolean(desc.default) : String(desc.default);
|
|
204
|
+
}
|
|
205
|
+
options[name] = opt;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// strict=false when command declares args (positionals must pass through)
|
|
209
|
+
// or when the command itself opts out
|
|
210
|
+
const { values: rawValues, positionals } = (() => {
|
|
211
|
+
try {
|
|
212
|
+
return nodeParseArgs({
|
|
213
|
+
args: this.argv,
|
|
214
|
+
options,
|
|
215
|
+
allowPositionals: true,
|
|
216
|
+
strict,
|
|
217
|
+
});
|
|
218
|
+
} catch (error) {
|
|
219
|
+
throw new CliUsageError(error instanceof Error ? error.message : String(error));
|
|
220
|
+
}
|
|
221
|
+
})();
|
|
222
|
+
|
|
223
|
+
// Convert raw values to proper types and validate
|
|
224
|
+
const flags: Record<string, unknown> = {};
|
|
225
|
+
for (const [name, desc] of Object.entries(flagDefs)) {
|
|
226
|
+
const raw = rawValues[name];
|
|
227
|
+
if (desc.kind === "integer") {
|
|
228
|
+
if (raw === undefined || typeof raw === "boolean") {
|
|
229
|
+
flags[name] = desc.default ?? undefined;
|
|
230
|
+
} else {
|
|
231
|
+
const n = Number.parseInt(raw as string, 10);
|
|
232
|
+
if (Number.isNaN(n)) {
|
|
233
|
+
throw new CliUsageError(`Expected integer for --${name}, got "${raw}"`);
|
|
234
|
+
}
|
|
235
|
+
flags[name] = n;
|
|
236
|
+
}
|
|
237
|
+
} else if (desc.kind === "boolean") {
|
|
238
|
+
flags[name] =
|
|
239
|
+
raw !== undefined ? Boolean(raw) : desc.default !== undefined ? Boolean(desc.default) : undefined;
|
|
240
|
+
} else {
|
|
241
|
+
// string
|
|
242
|
+
const val = raw !== undefined && typeof raw !== "boolean" ? raw : (desc.default ?? undefined);
|
|
243
|
+
// Validate options constraint
|
|
244
|
+
if (val !== undefined && desc.options && !Array.isArray(val)) {
|
|
245
|
+
if (!desc.options.includes(val as string)) {
|
|
246
|
+
throw new CliUsageError(
|
|
247
|
+
`Expected --${name} to be one of: ${[...desc.options].join(", ")}; got "${val}"`,
|
|
248
|
+
);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
flags[name] = val;
|
|
252
|
+
}
|
|
253
|
+
// Validate required
|
|
254
|
+
if (desc.required && flags[name] === undefined) {
|
|
255
|
+
throw new CliUsageError(`Missing required flag: --${name}`);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// Map positionals to named args in declaration order and validate
|
|
260
|
+
const args: Record<string, unknown> = {};
|
|
261
|
+
let posIdx = 0;
|
|
262
|
+
for (const [argName, desc] of Object.entries(argDefs)) {
|
|
263
|
+
if (desc.multiple) {
|
|
264
|
+
const val = positionals.slice(posIdx);
|
|
265
|
+
args[argName] = val.length > 0 ? val : undefined;
|
|
266
|
+
posIdx = positionals.length;
|
|
267
|
+
} else {
|
|
268
|
+
const val = positionals[posIdx];
|
|
269
|
+
args[argName] = val;
|
|
270
|
+
posIdx++;
|
|
271
|
+
}
|
|
272
|
+
// Validate required
|
|
273
|
+
if (desc.required && args[argName] === undefined) {
|
|
274
|
+
throw new CliUsageError(`Missing required argument: ${argName}`);
|
|
275
|
+
}
|
|
276
|
+
// Validate options constraint
|
|
277
|
+
const argVal = args[argName];
|
|
278
|
+
if (argVal !== undefined && desc.options && typeof argVal === "string") {
|
|
279
|
+
if (!desc.options.includes(argVal)) {
|
|
280
|
+
throw new CliUsageError(
|
|
281
|
+
`Expected ${argName} to be one of: ${[...desc.options].join(", ")}; got "${argVal}"`,
|
|
282
|
+
);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
return { flags, args, argv: positionals } as never;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// ---------------------------------------------------------------------------
|
|
292
|
+
// Help rendering
|
|
293
|
+
// ---------------------------------------------------------------------------
|
|
294
|
+
|
|
295
|
+
/** Render full root help: header, default command details, subcommand list. */
|
|
296
|
+
export function renderRootHelp(config: CliConfig<CommandMetadata>): void {
|
|
297
|
+
const { bin, version, commands } = config;
|
|
298
|
+
const lines: string[] = [];
|
|
299
|
+
lines.push(`${bin} v${version}\n`);
|
|
300
|
+
lines.push("USAGE");
|
|
301
|
+
lines.push(` $ ${bin} [COMMAND]\n`);
|
|
302
|
+
|
|
303
|
+
// Show the default command's flags/args/examples inline.
|
|
304
|
+
// The default command is the one marked hidden (it's the implicit entry point).
|
|
305
|
+
const defaultCmd = [...commands.values()].find(command => command.hidden);
|
|
306
|
+
if (defaultCmd) {
|
|
307
|
+
renderCommandBody(lines, defaultCmd);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// List visible subcommands
|
|
311
|
+
const visible = [...commands.entries()].filter(([, C]) => !C.hidden);
|
|
312
|
+
if (visible.length > 0) {
|
|
313
|
+
lines.push("COMMANDS");
|
|
314
|
+
const maxLen = Math.max(...visible.map(([n]) => n.length));
|
|
315
|
+
for (const [name, command] of visible.sort((a, b) => a[0].localeCompare(b[0]))) {
|
|
316
|
+
lines.push(` ${name.padEnd(maxLen + 2)}${command.description ?? ""}`);
|
|
317
|
+
}
|
|
318
|
+
lines.push("");
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
process.stdout.write(lines.join("\n"));
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* Format a command's positional args for a USAGE line. Required args render
|
|
326
|
+
* bare (`MODELS`), optional args wrapped in brackets (`[MODELS]`), and
|
|
327
|
+
* `multiple` args get a trailing ellipsis (`MODELS...`) so a required
|
|
328
|
+
* variadic reads as `MODELS...`, not the misleading optional `[MODELS]`.
|
|
329
|
+
*/
|
|
330
|
+
function formatUsageArgs(Cmd: CommandCtor): string {
|
|
331
|
+
const entries = Object.entries(Cmd.args ?? {});
|
|
332
|
+
if (entries.length === 0) return "";
|
|
333
|
+
const parts = entries.map(([name, desc]) => {
|
|
334
|
+
const label = `${name.toUpperCase()}${desc.multiple ? "..." : ""}`;
|
|
335
|
+
return desc.required ? label : `[${label}]`;
|
|
336
|
+
});
|
|
337
|
+
return ` ${parts.join(" ")}`;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/** Build the single USAGE line for a command (without the leading label). */
|
|
341
|
+
export function commandUsageLine(bin: string, id: string, Cmd: CommandCtor): string {
|
|
342
|
+
const hasFlags = Object.keys(Cmd.flags ?? {}).length > 0;
|
|
343
|
+
return `$ ${bin} ${id}${formatUsageArgs(Cmd)}${hasFlags ? " [FLAGS]" : ""}`;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/** Render help for a single command. */
|
|
347
|
+
export function renderCommandHelp(bin: string, id: string, Cmd: CommandCtor): void {
|
|
348
|
+
const lines: string[] = [];
|
|
349
|
+
if (Cmd.description) lines.push(`${Cmd.description}\n`);
|
|
350
|
+
lines.push("USAGE");
|
|
351
|
+
lines.push(` ${commandUsageLine(bin, id, Cmd)}\n`);
|
|
352
|
+
renderCommandBody(lines, Cmd);
|
|
353
|
+
process.stdout.write(lines.join("\n"));
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
function renderCommandBody(lines: string[], command: CommandMetadata): void {
|
|
357
|
+
const argDefs = command.args ?? {};
|
|
358
|
+
const flagDefs = command.flags ?? {};
|
|
359
|
+
|
|
360
|
+
// Arguments
|
|
361
|
+
const argEntries = Object.entries(argDefs);
|
|
362
|
+
if (argEntries.length > 0) {
|
|
363
|
+
lines.push("ARGUMENTS");
|
|
364
|
+
const maxLen = Math.max(...argEntries.map(([n]) => n.length));
|
|
365
|
+
for (const [name, desc] of argEntries) {
|
|
366
|
+
const parts = [name.toUpperCase().padEnd(maxLen + 2)];
|
|
367
|
+
if (desc.description) parts.push(desc.description);
|
|
368
|
+
if (desc.options) parts.push(`(${[...desc.options].join("|")})`);
|
|
369
|
+
lines.push(` ${parts.join(" ")}`);
|
|
370
|
+
}
|
|
371
|
+
lines.push("");
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
// Flags
|
|
375
|
+
const flagEntries = Object.entries(flagDefs);
|
|
376
|
+
if (flagEntries.length > 0) {
|
|
377
|
+
lines.push("FLAGS");
|
|
378
|
+
const formatted: [string, string][] = [];
|
|
379
|
+
for (const [name, desc] of flagEntries) {
|
|
380
|
+
const charPart = desc.char ? `-${desc.char}, ` : " ";
|
|
381
|
+
const namePart = `--${name}`;
|
|
382
|
+
const typePart = desc.kind === "boolean" ? "" : desc.kind === "integer" ? "=<int>" : "=<value>";
|
|
383
|
+
formatted.push([` ${charPart}${namePart}${typePart}`, desc.description ?? ""]);
|
|
384
|
+
}
|
|
385
|
+
const maxLeft = Math.max(...formatted.map(([l]) => l.length));
|
|
386
|
+
for (const [left, right] of formatted) {
|
|
387
|
+
lines.push(`${left.padEnd(maxLeft + 2)}${right}`);
|
|
388
|
+
}
|
|
389
|
+
lines.push("");
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// Examples
|
|
393
|
+
if (command.examples && command.examples.length > 0) {
|
|
394
|
+
lines.push("EXAMPLES");
|
|
395
|
+
for (const ex of command.examples) {
|
|
396
|
+
for (const line of ex.split("\n")) {
|
|
397
|
+
lines.push(` ${line}`);
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
lines.push("");
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
// ---------------------------------------------------------------------------
|
|
405
|
+
// CLI entry point
|
|
406
|
+
// ---------------------------------------------------------------------------
|
|
407
|
+
|
|
408
|
+
/** A lazily-loaded command: canonical name, loader, and optional aliases. */
|
|
409
|
+
export interface CommandEntry {
|
|
410
|
+
name: string;
|
|
411
|
+
load: () => Promise<CommandCtor>;
|
|
412
|
+
help?: CommandMetadata;
|
|
413
|
+
aliases?: string[];
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
export interface RunOptions {
|
|
417
|
+
bin: string;
|
|
418
|
+
version: string;
|
|
419
|
+
argv: string[];
|
|
420
|
+
commands: CommandEntry[];
|
|
421
|
+
/** Custom help renderer with the fully loaded command constructors. */
|
|
422
|
+
help?: (config: CliConfig) => Promise<void> | void;
|
|
423
|
+
/** Lightweight help renderer backed by static command metadata. */
|
|
424
|
+
metadataHelp?: (config: CliConfig<CommandMetadata>) => Promise<void> | void;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
/** Find a command entry by exact name or alias. */
|
|
428
|
+
function findEntry(commands: CommandEntry[], id: string): CommandEntry | undefined {
|
|
429
|
+
return commands.find(e => e.name === id) ?? commands.find(e => e.aliases?.includes(id));
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
/**
|
|
433
|
+
* Main entry point — replaces `run()` from @oclif/core.
|
|
434
|
+
*
|
|
435
|
+
* Each command is explicitly registered with a lazy loader.
|
|
436
|
+
* No filesystem scanning, no plugin system, no package.json reading.
|
|
437
|
+
*/
|
|
438
|
+
export async function run(opts: RunOptions): Promise<void> {
|
|
439
|
+
const { bin, version, argv } = opts;
|
|
440
|
+
|
|
441
|
+
const commandId = argv[0] ?? "";
|
|
442
|
+
const commandArgv = argv.slice(1);
|
|
443
|
+
|
|
444
|
+
// Top-level help
|
|
445
|
+
if (commandId === "--help" || commandId === "-h" || commandId === "help" || commandId === "") {
|
|
446
|
+
if (opts.help) {
|
|
447
|
+
await opts.help(await loadAllCommands(opts));
|
|
448
|
+
} else {
|
|
449
|
+
const config = await loadAllCommandMetadata(opts);
|
|
450
|
+
if (opts.metadataHelp) {
|
|
451
|
+
await opts.metadataHelp(config);
|
|
452
|
+
} else {
|
|
453
|
+
renderRootHelp(config);
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
return;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
// Version
|
|
460
|
+
if (commandId === "--version" || commandId === "-v") {
|
|
461
|
+
process.stdout.write(`${bin}/${version}\n`);
|
|
462
|
+
return;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
// Per-command help: load only the requested command. Loading the full
|
|
466
|
+
// command table here would make `omp <cmd> --help` hang or crash whenever
|
|
467
|
+
// any *unrelated* command module misbehaves at import time.
|
|
468
|
+
if (commandArgv.includes("--help") || commandArgv.includes("-h")) {
|
|
469
|
+
const entry = findEntry(opts.commands, commandId);
|
|
470
|
+
if (entry) {
|
|
471
|
+
const Cmd = await loadEntry(entry);
|
|
472
|
+
renderCommandHelp(bin, entry.name, Cmd);
|
|
473
|
+
} else {
|
|
474
|
+
process.stderr.write(`Unknown command: ${commandId}\n`);
|
|
475
|
+
}
|
|
476
|
+
return;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
// Find command by name or alias
|
|
480
|
+
const entry = findEntry(opts.commands, commandId);
|
|
481
|
+
|
|
482
|
+
if (!entry) {
|
|
483
|
+
process.stderr.write(`Error: command ${commandId} not found\n`);
|
|
484
|
+
process.exitCode = 1;
|
|
485
|
+
return;
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
const Cmd = await loadEntry(entry);
|
|
489
|
+
const config: CliConfig = { bin, version, commands: new Map([[entry.name, Cmd]]) };
|
|
490
|
+
const instance = new Cmd(commandArgv, config);
|
|
491
|
+
try {
|
|
492
|
+
await instance.run();
|
|
493
|
+
} catch (error) {
|
|
494
|
+
// A usage mistake (missing/invalid arg or flag) is not a crash: print the
|
|
495
|
+
// message and the command's usage line, then exit 1. Letting it reach the
|
|
496
|
+
// process-level catch would dump a minified `dist/cli.js` code frame over a
|
|
497
|
+
// plain argument error (issue #5369).
|
|
498
|
+
if (error instanceof CliUsageError) {
|
|
499
|
+
process.stderr.write(`error: ${error.message}\n\n`);
|
|
500
|
+
process.stderr.write(`USAGE\n ${commandUsageLine(bin, entry.name, Cmd)}\n`);
|
|
501
|
+
process.stderr.write(`\nRun \`${bin} ${entry.name} --help\` for details.\n`);
|
|
502
|
+
process.exitCode = 1;
|
|
503
|
+
return;
|
|
504
|
+
}
|
|
505
|
+
throw error;
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
/** Load one command module, leaving streaming markers around the import. */
|
|
510
|
+
async function loadEntry(entry: CommandEntry): Promise<CommandCtor> {
|
|
511
|
+
startupMarker(`cli:load:${entry.name}:start`);
|
|
512
|
+
const Cmd = await entry.load();
|
|
513
|
+
startupMarker(`cli:load:${entry.name}:done`);
|
|
514
|
+
return Cmd;
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
/** Load every command constructor for backward-compatible custom help callbacks. */
|
|
518
|
+
async function loadAllCommands(opts: RunOptions): Promise<CliConfig> {
|
|
519
|
+
const loaded = await Promise.all(opts.commands.map(async entry => [entry.name, await loadEntry(entry)] as const));
|
|
520
|
+
return { bin: opts.bin, version: opts.version, commands: new Map(loaded) };
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
/** Resolve static command metadata for lightweight root help. */
|
|
524
|
+
async function loadAllCommandMetadata(opts: RunOptions): Promise<CliConfig<CommandMetadata>> {
|
|
525
|
+
const loaded = await Promise.all(
|
|
526
|
+
opts.commands.map(async entry => [entry.name, entry.help ?? (await loadEntry(entry))] as const),
|
|
527
|
+
);
|
|
528
|
+
return { bin: opts.bin, version: opts.version, commands: new Map(loaded) };
|
|
529
|
+
}
|