argsbarg 3.6.4 → 4.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 +28 -2
- package/README.md +21 -9
- package/docs/README.md +12 -8
- package/docs/bundled-docs.md +1 -1
- package/docs/cli-program.md +62 -2
- package/docs/config-schema.md +192 -0
- package/docs/developing.md +13 -0
- package/docs/install.md +38 -1
- package/docs/mcp.md +43 -19
- package/docs/output-schema.md +74 -52
- package/docs/templates/cursor/rules/cli-program.mdc +10 -5
- package/examples/config-app/main.ts +20 -0
- package/examples/config-app/program.ts +81 -0
- package/examples/config-app/schema.ts +37 -0
- package/examples/config-app/types.ts +19 -0
- package/examples/consumer-app/README.md +56 -0
- package/examples/consumer-app/bun.lock +75 -0
- package/examples/consumer-app/capabilities.test.ts +69 -0
- package/examples/consumer-app/package.json +17 -0
- package/examples/consumer-app/schemas/configSchemas.ts +6 -0
- package/examples/consumer-app/schemas/generated/app-config.json +40 -0
- package/examples/consumer-app/schemas/generated/status.json +28 -0
- package/examples/consumer-app/schemas/outputSchemas.ts +6 -0
- package/examples/consumer-app/scripts/schemagen/discover-schema-roots.test.ts +25 -0
- package/examples/consumer-app/scripts/schemagen/discover-schema-roots.ts +93 -0
- package/examples/consumer-app/scripts/schemagen/naming.ts +82 -0
- package/examples/consumer-app/scripts/schemagen.ts +76 -0
- package/examples/consumer-app/src/commands/status/types.ts +11 -0
- package/examples/consumer-app/src/main.ts +15 -0
- package/examples/consumer-app/src/program.ts +116 -0
- package/examples/consumer-app/src/types.ts +23 -0
- package/examples/consumer-app/tsconfig.json +14 -0
- package/examples/formats.ts +10 -3
- package/examples/mcp-test.ts +27 -8
- package/examples/minimal.ts +4 -3
- package/examples/nested.ts +5 -4
- package/examples/option-required.ts +8 -4
- package/index.d.ts +152 -75
- package/package.json +1 -1
- package/src/builtins/builtins.test.ts +13 -0
- package/src/builtins/config.test.ts +82 -0
- package/src/builtins/config.ts +220 -0
- package/src/builtins/dispatch.ts +18 -9
- package/src/builtins/export.ts +8 -33
- package/src/builtins/index.ts +1 -0
- package/src/builtins/install.ts +13 -0
- package/src/builtins/mcp.ts +1 -1
- package/src/builtins/presentation.ts +2 -17
- package/src/builtins/registry.ts +40 -0
- package/src/capabilities.ts +46 -0
- package/src/cli-errors.ts +15 -0
- package/src/cli.ts +389 -0
- package/src/config/bootstrap.ts +265 -0
- package/src/config/context.test.ts +61 -0
- package/src/config/context.ts +98 -0
- package/src/config/entry.ts +81 -0
- package/src/config/file.test.ts +112 -0
- package/src/config/file.ts +120 -0
- package/src/config/manifest.ts +62 -0
- package/src/config/resolve.test.ts +88 -0
- package/src/config/resolve.ts +167 -0
- package/src/config/schema.ts +101 -0
- package/src/config/validate.test.ts +63 -0
- package/src/config/validate.ts +292 -0
- package/src/config.integration.test.ts +100 -0
- package/src/context.ts +5 -0
- package/src/docs/docs.test.ts +16 -16
- package/src/docs/mcp-guide.ts +35 -11
- package/src/hidden-mcpb.test.ts +40 -2
- package/src/index.ts +4 -3
- package/src/install/index.ts +46 -3
- package/src/install/paths.ts +5 -20
- package/src/install/plan.ts +6 -0
- package/src/install/status.ts +12 -0
- package/src/install/uninstall.ts +11 -0
- package/src/install/update.test.ts +5 -5
- package/src/invoke.test.ts +207 -0
- package/src/mcp/bundle.ts +9 -116
- package/src/mcp/claude.test.ts +73 -0
- package/src/mcp/claude.ts +168 -0
- package/src/mcp/env.ts +3 -37
- package/src/mcp/server.ts +18 -10
- package/src/mcp/tools.ts +3 -7
- package/src/mcp/zip.ts +82 -0
- package/src/mcp.integration.test.ts +502 -0
- package/src/{index.test.ts → parse.test.ts} +24 -935
- package/src/paths/host.ts +40 -0
- package/src/schema.ts +1 -1
- package/src/skill/generate.ts +18 -5
- package/src/skill/hint.ts +18 -0
- package/src/skill/install.ts +1 -5
- package/src/test-fixtures.ts +192 -0
- package/src/types.ts +39 -13
- package/src/validate.ts +70 -0
- package/src/completion.ts +0 -13
- package/src/invoke.ts +0 -217
- package/src/mcp.ts +0 -28
- package/src/runtime.ts +0 -134
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Config schema
|
|
3
|
+
*
|
|
4
|
+
* Application settings for `consumer-app` (`program.appConfig`).
|
|
5
|
+
*/
|
|
6
|
+
export interface AppConfig {
|
|
7
|
+
/** API token from the provider dashboard. */
|
|
8
|
+
apiToken: string;
|
|
9
|
+
/**
|
|
10
|
+
* AWS region (default us-east-1).
|
|
11
|
+
* @default us-east-1
|
|
12
|
+
*/
|
|
13
|
+
defaultRegion?: string;
|
|
14
|
+
/**
|
|
15
|
+
* HTTP retry count (default 3).
|
|
16
|
+
* @default 3
|
|
17
|
+
*/
|
|
18
|
+
maxRetries: number;
|
|
19
|
+
/** Local preferences (file-only; not mapped to process.env). */
|
|
20
|
+
prefs?: {
|
|
21
|
+
ttl: number;
|
|
22
|
+
};
|
|
23
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ESNext",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"moduleResolution": "bundler",
|
|
6
|
+
"allowImportingTsExtensions": true,
|
|
7
|
+
"resolveJsonModule": true,
|
|
8
|
+
"noEmit": true,
|
|
9
|
+
"strict": true,
|
|
10
|
+
"skipLibCheck": true,
|
|
11
|
+
"types": ["bun"]
|
|
12
|
+
},
|
|
13
|
+
"include": ["src/**/*.ts", "scripts/**/*.ts"]
|
|
14
|
+
}
|
package/examples/formats.ts
CHANGED
|
@@ -6,9 +6,15 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import pkg from "../package.json" with { type: "json" };
|
|
9
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
Cli,
|
|
11
|
+
CliFallbackMode,
|
|
12
|
+
CliOptionKind,
|
|
13
|
+
type CliProgram,
|
|
14
|
+
CliValueFormat,
|
|
15
|
+
} from "../src/index.ts";
|
|
10
16
|
|
|
11
|
-
const
|
|
17
|
+
const program = {
|
|
12
18
|
key: "formats.ts",
|
|
13
19
|
version: pkg.version,
|
|
14
20
|
description: "Value formats and readLeafInputs demo.",
|
|
@@ -63,4 +69,5 @@ const cli = {
|
|
|
63
69
|
],
|
|
64
70
|
} satisfies CliProgram;
|
|
65
71
|
|
|
66
|
-
|
|
72
|
+
const cli = new Cli(program);
|
|
73
|
+
await cli.run();
|
package/examples/mcp-test.ts
CHANGED
|
@@ -3,17 +3,38 @@
|
|
|
3
3
|
MCP test fixture for subprocess integration tests only.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import {
|
|
6
|
+
import { Cli, CliOptionKind, type CliProgram } from "../src/index.ts";
|
|
7
7
|
|
|
8
|
-
const
|
|
8
|
+
const configPath = process.env.ARGS_TEST_CONFIG_FILE;
|
|
9
9
|
|
|
10
|
-
const
|
|
10
|
+
const program = {
|
|
11
11
|
key: "mcp-test",
|
|
12
12
|
version: "0.0.0-test",
|
|
13
13
|
description: "MCP integration test fixture.",
|
|
14
|
+
...(configPath
|
|
15
|
+
? {
|
|
16
|
+
appConfig: {
|
|
17
|
+
path: configPath,
|
|
18
|
+
entries: {
|
|
19
|
+
argsTestSecret: {
|
|
20
|
+
description: "Test secret for integration tests.",
|
|
21
|
+
env: "ARGS_TEST_SECRET",
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
}
|
|
26
|
+
: {
|
|
27
|
+
appConfig: {
|
|
28
|
+
entries: {
|
|
29
|
+
argsTestSecret: {
|
|
30
|
+
description: "Test secret for integration tests.",
|
|
31
|
+
env: "ARGS_TEST_SECRET",
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
}),
|
|
14
36
|
mcpServer: {
|
|
15
37
|
enabled: true,
|
|
16
|
-
...(envFilePath ? { envFile: envFilePath } : {}),
|
|
17
38
|
resources: [
|
|
18
39
|
{
|
|
19
40
|
uri: "test://hello",
|
|
@@ -28,9 +49,6 @@ const cli = {
|
|
|
28
49
|
{
|
|
29
50
|
key: "echo-env",
|
|
30
51
|
description: "Echo an env var.",
|
|
31
|
-
mcpTool: {
|
|
32
|
-
requiresEnv: ["ARGS_TEST_SECRET"],
|
|
33
|
-
},
|
|
34
52
|
options: [
|
|
35
53
|
{
|
|
36
54
|
name: "name",
|
|
@@ -63,4 +81,5 @@ const cli = {
|
|
|
63
81
|
],
|
|
64
82
|
} satisfies CliProgram;
|
|
65
83
|
|
|
66
|
-
|
|
84
|
+
const cli = new Cli(program);
|
|
85
|
+
await cli.run();
|
package/examples/minimal.ts
CHANGED
|
@@ -8,9 +8,9 @@ It demonstrates the minimal Bun integration path.
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import pkg from "../package.json" with { type: "json" };
|
|
11
|
-
import {
|
|
11
|
+
import { Cli, CliOptionKind, type CliProgram } from "../src/index.ts";
|
|
12
12
|
|
|
13
|
-
const
|
|
13
|
+
const program = {
|
|
14
14
|
key: "minimal.ts",
|
|
15
15
|
version: pkg.version,
|
|
16
16
|
description: "Tiny demo.",
|
|
@@ -46,4 +46,5 @@ const cli = {
|
|
|
46
46
|
},
|
|
47
47
|
} satisfies CliProgram;
|
|
48
48
|
|
|
49
|
-
|
|
49
|
+
const cli = new Cli(program);
|
|
50
|
+
await cli.run();
|
package/examples/nested.ts
CHANGED
|
@@ -8,9 +8,9 @@ It demonstrates how the schema scales beyond one command.
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import pkg from "../package.json" with { type: "json" };
|
|
11
|
-
import {
|
|
11
|
+
import { Cli, CliFallbackMode, CliOptionKind, type CliProgram } from "../src/index.ts";
|
|
12
12
|
|
|
13
|
-
const
|
|
13
|
+
const program = {
|
|
14
14
|
key: "nested.ts",
|
|
15
15
|
version: pkg.version,
|
|
16
16
|
description: "Nested groups demo.",
|
|
@@ -99,7 +99,7 @@ const cli = {
|
|
|
99
99
|
const text = await file.text();
|
|
100
100
|
const firstLine = text.split("\n")[0];
|
|
101
101
|
console.log(`${path}: ${firstLine}`);
|
|
102
|
-
} catch
|
|
102
|
+
} catch {
|
|
103
103
|
console.error(`Cannot open: ${path}`);
|
|
104
104
|
}
|
|
105
105
|
}
|
|
@@ -110,4 +110,5 @@ const cli = {
|
|
|
110
110
|
fallbackMode: CliFallbackMode.MissingOrUnknown,
|
|
111
111
|
} satisfies CliProgram;
|
|
112
112
|
|
|
113
|
-
|
|
113
|
+
const cli = new Cli(program);
|
|
114
|
+
await cli.run();
|
|
@@ -8,9 +8,9 @@ It demonstrates the minimal Bun integration path.
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import pkg from "../package.json" with { type: "json" };
|
|
11
|
-
import {
|
|
11
|
+
import { Cli, CliOptionKind, type CliProgram, isInteractiveTty } from "../src/index.ts";
|
|
12
12
|
|
|
13
|
-
const
|
|
13
|
+
const program = {
|
|
14
14
|
key: "option-required.ts",
|
|
15
15
|
version: pkg.version,
|
|
16
16
|
description: "Demo of a required option.",
|
|
@@ -37,7 +37,10 @@ const cli = {
|
|
|
37
37
|
},
|
|
38
38
|
],
|
|
39
39
|
handler: (ctx) => {
|
|
40
|
-
const requiredAlways = ctx.stringOpt("requiredAlways")
|
|
40
|
+
const requiredAlways = ctx.stringOpt("requiredAlways");
|
|
41
|
+
if (requiredAlways === undefined) {
|
|
42
|
+
throw new Error("requiredAlways missing after validation");
|
|
43
|
+
}
|
|
41
44
|
const requiredNonTty = ctx.stringOpt("requiredNonTty") ?? "valueWhenOmitted";
|
|
42
45
|
const optional = ctx.stringOpt("optional") ?? "valueWhenOmitted";
|
|
43
46
|
console.log(`requiredAlways: ${requiredAlways}`);
|
|
@@ -46,4 +49,5 @@ const cli = {
|
|
|
46
49
|
},
|
|
47
50
|
} satisfies CliProgram;
|
|
48
51
|
|
|
49
|
-
|
|
52
|
+
const cli = new Cli(program);
|
|
53
|
+
await cli.run();
|
package/index.d.ts
CHANGED
|
@@ -1,5 +1,69 @@
|
|
|
1
1
|
// Generated by dts-bundle-generator v9.5.1
|
|
2
2
|
|
|
3
|
+
export type ResolvedConfig = Record<string, unknown>;
|
|
4
|
+
declare class EmptyAppConfigSnapshot {
|
|
5
|
+
get(_key: string): undefined;
|
|
6
|
+
require(key: string): never;
|
|
7
|
+
set(_key: string, _value: unknown): void;
|
|
8
|
+
read(): ResolvedConfig;
|
|
9
|
+
}
|
|
10
|
+
declare class AppConfigSnapshot {
|
|
11
|
+
private readonly program;
|
|
12
|
+
private snapshot;
|
|
13
|
+
private fileData;
|
|
14
|
+
constructor(program: CliProgram, fileData: Record<string, unknown>, resolved: ResolvedConfig);
|
|
15
|
+
get(key: string): unknown;
|
|
16
|
+
require(key: string): unknown;
|
|
17
|
+
set(key: string, value: unknown): void;
|
|
18
|
+
read(): ResolvedConfig;
|
|
19
|
+
/** Replace snapshot after external bootstrap (internal). */
|
|
20
|
+
refresh(fileData: Record<string, unknown>, resolved: ResolvedConfig): void;
|
|
21
|
+
private assertEntryKey;
|
|
22
|
+
}
|
|
23
|
+
export type AnyAppConfigSnapshot = AppConfigSnapshot | EmptyAppConfigSnapshot;
|
|
24
|
+
/** Coerced leaf inputs keyed by option and positional names. */
|
|
25
|
+
export type CliLeafInputs = Record<string, boolean | number | string | string[] | undefined>;
|
|
26
|
+
/**
|
|
27
|
+
* Values passed to a leaf command handler after parsing: app name, routed path, args, and merged options.
|
|
28
|
+
*/
|
|
29
|
+
export declare class CliContext {
|
|
30
|
+
readonly appName: string;
|
|
31
|
+
readonly commandPath: string[];
|
|
32
|
+
readonly args: string[];
|
|
33
|
+
readonly program: CliProgram;
|
|
34
|
+
readonly opts: Record<string, string>;
|
|
35
|
+
readonly invocation: CliInvocation;
|
|
36
|
+
readonly appConfig: AnyAppConfigSnapshot;
|
|
37
|
+
/** Captures the program root, routed path, positional words, and option map for a leaf handler. */
|
|
38
|
+
constructor(appName: string, commandPath: string[], args: string[], opts: Record<string, string>, program: CliProgram, invocation?: CliInvocation, appConfig?: AnyAppConfigSnapshot);
|
|
39
|
+
/** Returns whether a presence flag was set (including implicit "1" for boolean options). */
|
|
40
|
+
hasFlag(name: string): boolean;
|
|
41
|
+
/** Returns the string value for a string-valued option, if present. */
|
|
42
|
+
stringOpt(name: string): string | undefined;
|
|
43
|
+
/** Parses a stored string as a number; returns null if missing or not a strict double string. */
|
|
44
|
+
numberOpt(name: string): number | null;
|
|
45
|
+
/**
|
|
46
|
+
* Generic typed accessor: parses a stored string using the provided parse function.
|
|
47
|
+
* This is the TypeScript-native advantage over the Swift version.
|
|
48
|
+
*/
|
|
49
|
+
typedOpt<T>(name: string, parse: (s: string) => T): T | null;
|
|
50
|
+
/** Duration option in milliseconds (post-parse validated). */
|
|
51
|
+
durationOpt(name: string): number | undefined;
|
|
52
|
+
/** Comma-list option as a string array (post-parse validated). */
|
|
53
|
+
commaListOpt(name: string): string[] | undefined;
|
|
54
|
+
/** Date option as canonical YYYY-MM-DD (post-parse validated). */
|
|
55
|
+
dateOpt(name: string): string | undefined;
|
|
56
|
+
/** Date-time option as normalized ISO 8601 UTC (post-parse validated). */
|
|
57
|
+
dateTimeOpt(name: string): string | undefined;
|
|
58
|
+
/** Returns the value(s) for a named positional slot. Varargs slots return string[]; single slots return string | undefined. */
|
|
59
|
+
positional(name: string): string | string[] | undefined;
|
|
60
|
+
/** Reads coerced option and positional values for the current leaf from schema metadata. */
|
|
61
|
+
readLeafInputs(): CliLeafInputs;
|
|
62
|
+
private _readOptionValue;
|
|
63
|
+
private _leafNode;
|
|
64
|
+
private _posMap;
|
|
65
|
+
private _positionalMap;
|
|
66
|
+
}
|
|
3
67
|
/**
|
|
4
68
|
* How a leaf handler was dispatched.
|
|
5
69
|
*/
|
|
@@ -128,12 +192,6 @@ export interface CliMcpServerConfig {
|
|
|
128
192
|
* and shell exports that MCP hosts (e.g. Cursor) don't inherit.
|
|
129
193
|
*/
|
|
130
194
|
shellEnv?: boolean | string;
|
|
131
|
-
/**
|
|
132
|
-
* Path to a .env file loaded into process.env at MCP server start, after shellEnv.
|
|
133
|
-
* Supports `~` expansion. Warns on stderr if the file does not exist.
|
|
134
|
-
* Always overwrites — envFile is authoritative for its keys.
|
|
135
|
-
*/
|
|
136
|
-
envFile?: string;
|
|
137
195
|
/**
|
|
138
196
|
* Custom MCP resources exposed alongside the built-in schema resource.
|
|
139
197
|
* URIs must be unique and must not equal schemaResourceUri.
|
|
@@ -168,12 +226,6 @@ export interface CliMcpToolConfig {
|
|
|
168
226
|
* Default: auto-generated from command path and description.
|
|
169
227
|
*/
|
|
170
228
|
description?: string;
|
|
171
|
-
/**
|
|
172
|
-
* Environment variable names required at runtime.
|
|
173
|
-
* Appended to auto-generated MCP tool descriptions; enforced at tools/call time.
|
|
174
|
-
* Empty string counts as absent.
|
|
175
|
-
*/
|
|
176
|
-
requiresEnv?: string[];
|
|
177
229
|
/**
|
|
178
230
|
* @deprecated Set `outputSchema` on the leaf command instead.
|
|
179
231
|
*/
|
|
@@ -194,6 +246,43 @@ export interface CliUpdateArtifact {
|
|
|
194
246
|
export type CliUpdateGetLatest = (ctx: {
|
|
195
247
|
version: string;
|
|
196
248
|
}) => Promise<CliUpdateArtifact>;
|
|
249
|
+
/**
|
|
250
|
+
* Metadata overlay for one key in {@link CliAppConfig.entries}.
|
|
251
|
+
* Types and validation come from {@link CliAppConfig.jsonSchema} when set; otherwise all values are strings.
|
|
252
|
+
*/
|
|
253
|
+
export interface CliAppConfigEntry {
|
|
254
|
+
/** Help text for prompts, MCP manifests, and generated docs. */
|
|
255
|
+
description: string;
|
|
256
|
+
/** Short label in host UIs and CLI prompts. Default: the config key. */
|
|
257
|
+
title?: string;
|
|
258
|
+
/** Default when `jsonSchema` is omitted (all-string mode). */
|
|
259
|
+
default?: string;
|
|
260
|
+
/** When `false`, optional for bootstrap and MCP enforcement. Default: `true`. */
|
|
261
|
+
required?: boolean;
|
|
262
|
+
/**
|
|
263
|
+
* Mask stdin during prompts and redact on `config get`.
|
|
264
|
+
* Default: `/key|token|secret|password/i.test(name)`.
|
|
265
|
+
*/
|
|
266
|
+
sensitive?: boolean;
|
|
267
|
+
/** When set: non-empty `process.env[env]` overrides file; value exported after resolve. */
|
|
268
|
+
env?: string;
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* App configuration block on the program root ({@link CliProgram.appConfig}).
|
|
272
|
+
*/
|
|
273
|
+
export interface CliAppConfig {
|
|
274
|
+
/** Default: `~/.config/<sanitized-key>/config` (or `%APPDATA%/<key>/config` on Windows). */
|
|
275
|
+
path?: string;
|
|
276
|
+
/** Built-in `config get` / `config set`. Default: enabled when `appConfig` is set. */
|
|
277
|
+
commands?: boolean | {
|
|
278
|
+
enabled?: boolean;
|
|
279
|
+
mcpSet?: boolean;
|
|
280
|
+
};
|
|
281
|
+
/** Block JSON Schema (draft-07). When omitted, synthesize all-string schema from `entries`. */
|
|
282
|
+
jsonSchema?: Record<string, unknown>;
|
|
283
|
+
/** Per-key metadata; keys must match `jsonSchema.properties` when `jsonSchema` is set. */
|
|
284
|
+
entries: Record<string, CliAppConfigEntry>;
|
|
285
|
+
}
|
|
197
286
|
export interface CliInstallConfig {
|
|
198
287
|
/** When `false`, hide/disable `install` (default: enabled). */
|
|
199
288
|
enabled?: boolean;
|
|
@@ -278,12 +367,14 @@ export type CliRouter = CliNodeBase & {
|
|
|
278
367
|
*/
|
|
279
368
|
export type CliNode = CliLeaf | CliRouter;
|
|
280
369
|
/**
|
|
281
|
-
* Program root passed to
|
|
370
|
+
* Program root passed to {@link Cli}.
|
|
282
371
|
* May be a leaf or router, plus optional program-level MCP and install config.
|
|
283
372
|
*/
|
|
284
373
|
export type CliProgram = CliNode & {
|
|
285
374
|
/** Program version (printed by the `version` built-in and MCP serverInfo). */
|
|
286
375
|
version: string;
|
|
376
|
+
/** Schema-driven app config file, bootstrap, and MCP metadata. */
|
|
377
|
+
appConfig?: CliAppConfig;
|
|
287
378
|
/** When set with `enabled: true`, enables the `mcp` built-in subcommand. */
|
|
288
379
|
mcpServer?: CliMcpServerConfig;
|
|
289
380
|
/** Opt-out and defaults for `install`. */
|
|
@@ -303,48 +394,56 @@ export declare class CliSchemaValidationError extends Error {
|
|
|
303
394
|
/** Creates a schema validation error with a human-readable rule violation. */
|
|
304
395
|
constructor(message: string);
|
|
305
396
|
}
|
|
306
|
-
/**
|
|
307
|
-
export
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
397
|
+
/** Platform builtins derived from program config and runtime. */
|
|
398
|
+
export interface CliCapabilities {
|
|
399
|
+
completion: true;
|
|
400
|
+
mcp: boolean;
|
|
401
|
+
install: boolean;
|
|
402
|
+
docs: boolean;
|
|
403
|
+
update: boolean;
|
|
404
|
+
configCommands: boolean;
|
|
405
|
+
}
|
|
406
|
+
/** JSON-safe command node (no handlers). */
|
|
407
|
+
export interface CliSchemaExport {
|
|
408
|
+
key: string;
|
|
409
|
+
description: string;
|
|
410
|
+
notes?: string;
|
|
411
|
+
/** JSON Schema for structured stdout when set on the leaf. */
|
|
412
|
+
outputSchema?: Record<string, unknown>;
|
|
413
|
+
options?: CliOption[];
|
|
414
|
+
fallbackCommand?: string;
|
|
415
|
+
fallbackMode?: CliFallbackMode;
|
|
416
|
+
commands?: CliSchemaExport[];
|
|
417
|
+
positionals?: CliPositional[];
|
|
418
|
+
}
|
|
419
|
+
/** Outcome of a non-exiting CLI invocation. */
|
|
420
|
+
export type CliInvokeKind = "ok" | "help" | "error";
|
|
421
|
+
/** Result of Cli.invoke: captured output and exit metadata without process.exit. */
|
|
422
|
+
export interface CliInvokeResult {
|
|
423
|
+
kind: CliInvokeKind;
|
|
424
|
+
exitCode: number;
|
|
425
|
+
stdout: string;
|
|
426
|
+
stderr: string;
|
|
427
|
+
errorMsg?: string;
|
|
428
|
+
}
|
|
429
|
+
/** Argsbarg runtime for a validated, frozen {@link CliProgram}. */
|
|
430
|
+
export declare class Cli {
|
|
315
431
|
readonly program: CliProgram;
|
|
316
|
-
readonly
|
|
317
|
-
readonly
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
*/
|
|
330
|
-
typedOpt<T>(name: string, parse: (s: string) => T): T | null;
|
|
331
|
-
/** Duration option in milliseconds (post-parse validated). */
|
|
332
|
-
durationOpt(name: string): number | undefined;
|
|
333
|
-
/** Comma-list option as a string array (post-parse validated). */
|
|
334
|
-
commaListOpt(name: string): string[] | undefined;
|
|
335
|
-
/** Date option as canonical YYYY-MM-DD (post-parse validated). */
|
|
336
|
-
dateOpt(name: string): string | undefined;
|
|
337
|
-
/** Date-time option as normalized ISO 8601 UTC (post-parse validated). */
|
|
338
|
-
dateTimeOpt(name: string): string | undefined;
|
|
339
|
-
/** Returns the value(s) for a named positional slot. Varargs slots return string[]; single slots return string | undefined. */
|
|
340
|
-
positional(name: string): string | string[] | undefined;
|
|
341
|
-
/** Reads coerced option and positional values for the current leaf from schema metadata. */
|
|
342
|
-
readLeafInputs(): CliLeafInputs;
|
|
343
|
-
private _readOptionValue;
|
|
344
|
-
private _leafNode;
|
|
345
|
-
private _posMap;
|
|
346
|
-
private _positionalMap;
|
|
432
|
+
readonly caps: CliCapabilities;
|
|
433
|
+
private readonly parseRootMerged;
|
|
434
|
+
private readonly presentationRoot;
|
|
435
|
+
private _appConfig?;
|
|
436
|
+
constructor(program: CliProgram);
|
|
437
|
+
get appConfig(): AnyAppConfigSnapshot;
|
|
438
|
+
exportCommandSchema(): CliSchemaExport;
|
|
439
|
+
exportAppConfigSchema(): Record<string, unknown> | undefined;
|
|
440
|
+
run(argv?: string[]): Promise<never>;
|
|
441
|
+
invoke(argv: string[]): Promise<CliInvokeResult>;
|
|
442
|
+
serveMcp(): Promise<never>;
|
|
443
|
+
private prepareDispatch;
|
|
444
|
+
private buildAppConfigSnapshot;
|
|
347
445
|
}
|
|
446
|
+
export declare function cliErrWithHelp(ctx: CliContext, msg: string): never;
|
|
348
447
|
/** Parses a duration string (e.g. 20m, 1h, 30s) into milliseconds. */
|
|
349
448
|
export declare function parseDurationMs(durationStr: string): number;
|
|
350
449
|
/** Splits a comma-separated string into trimmed non-empty tokens. */
|
|
@@ -426,26 +525,6 @@ export declare function createGhVersionCheck(config: GhVersionCheckConfig): {
|
|
|
426
525
|
};
|
|
427
526
|
/** Shared `gh release view` fetcher for hooks and version-check refresh. */
|
|
428
527
|
export declare function createGhFetchLatest(config: Pick<GhReleaseUpdateConfig, "repo" | "repoEnvHint">): () => Promise<string>;
|
|
429
|
-
/** Outcome of a non-exiting CLI invocation. */
|
|
430
|
-
export type CliInvokeKind = "ok" | "help" | "error";
|
|
431
|
-
/** Result of cliInvoke: captured output and exit metadata without process.exit. */
|
|
432
|
-
export interface CliInvokeResult {
|
|
433
|
-
/** Invocation outcome. */
|
|
434
|
-
kind: CliInvokeKind;
|
|
435
|
-
/** Simulated exit code. */
|
|
436
|
-
exitCode: number;
|
|
437
|
-
/** Captured stdout during handler execution. */
|
|
438
|
-
stdout: string;
|
|
439
|
-
/** Captured stderr during handler execution. */
|
|
440
|
-
stderr: string;
|
|
441
|
-
/** Set when kind === "error" (parse/validation message). */
|
|
442
|
-
errorMsg?: string;
|
|
443
|
-
}
|
|
444
|
-
/**
|
|
445
|
-
* Parses argv against the user root, runs the leaf handler, and returns captured output.
|
|
446
|
-
* Never calls process.exit.
|
|
447
|
-
*/
|
|
448
|
-
export declare function cliInvoke(root: CliProgram, argv: string[]): Promise<CliInvokeResult>;
|
|
449
528
|
/** Resolved paths for `mcp bundle`. */
|
|
450
529
|
export interface McpBundlePaths {
|
|
451
530
|
binaryPath: string;
|
|
@@ -466,8 +545,6 @@ export interface PackMcpBundleOpts {
|
|
|
466
545
|
* Requires the compiled binary to exist.
|
|
467
546
|
*/
|
|
468
547
|
export declare function packMcpBundle(program: CliProgram, opts?: PackMcpBundleOpts): string;
|
|
469
|
-
export declare function cliRun(program: CliProgram, argv?: string[]): Promise<never>;
|
|
470
|
-
export declare function cliErrWithHelp(ctx: CliContext, msg: string): never;
|
|
471
548
|
/** True when stdin is a TTY. */
|
|
472
549
|
export declare const isInteractiveTty: boolean;
|
|
473
550
|
|
package/package.json
CHANGED
|
@@ -127,6 +127,19 @@ describe("completion emitters", () => {
|
|
|
127
127
|
});
|
|
128
128
|
|
|
129
129
|
describe("schema export builtins", () => {
|
|
130
|
+
test("exportPresentationBuiltins includes config when appConfig set", () => {
|
|
131
|
+
const withConfig: CliProgram = {
|
|
132
|
+
...fixture,
|
|
133
|
+
appConfig: {
|
|
134
|
+
entries: {
|
|
135
|
+
apiToken: { description: "Token.", env: "API_TOKEN" },
|
|
136
|
+
},
|
|
137
|
+
},
|
|
138
|
+
};
|
|
139
|
+
const builtins = exportPresentationBuiltins(withConfig);
|
|
140
|
+
expect(builtins.map((b) => b.key)).toContain("config");
|
|
141
|
+
});
|
|
142
|
+
|
|
130
143
|
test("exportPresentationBuiltins includes install options", () => {
|
|
131
144
|
const builtins = exportPresentationBuiltins(fixture);
|
|
132
145
|
const install = builtins.find((b) => b.key === "install");
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { Cli, type CliProgram } from "../index.ts";
|
|
6
|
+
|
|
7
|
+
function configFixture(configPath: string): CliProgram {
|
|
8
|
+
return {
|
|
9
|
+
key: "cfg-app",
|
|
10
|
+
version: "1.0.0",
|
|
11
|
+
description: "Config builtin test.",
|
|
12
|
+
appConfig: {
|
|
13
|
+
path: configPath,
|
|
14
|
+
entries: {
|
|
15
|
+
apiToken: { description: "Token.", env: "API_TOKEN", sensitive: true },
|
|
16
|
+
port: { description: "Port.", required: false },
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
commands: [{ key: "run", description: "Run.", handler: () => {} }],
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
describe("builtins/config", () => {
|
|
24
|
+
test("config get redacts sensitive values", async () => {
|
|
25
|
+
const dir = mkdtempSync(join(tmpdir(), "cfg-builtin-"));
|
|
26
|
+
const configPath = join(dir, "config");
|
|
27
|
+
writeFileSync(configPath, `${JSON.stringify({ apiToken: "secret" })}\n`);
|
|
28
|
+
const prev = process.env.API_TOKEN;
|
|
29
|
+
delete process.env.API_TOKEN;
|
|
30
|
+
try {
|
|
31
|
+
const result = await new Cli(configFixture(configPath)).invoke(["config", "get", "apiToken"]);
|
|
32
|
+
expect(result.exitCode).toBe(0);
|
|
33
|
+
expect(result.stdout.trim()).toBe("REDACTED");
|
|
34
|
+
} finally {
|
|
35
|
+
if (prev !== undefined) process.env.API_TOKEN = prev;
|
|
36
|
+
rmSync(dir, { recursive: true, force: true });
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
test("config get --json redacts sensitive as { set: true }", async () => {
|
|
41
|
+
const dir = mkdtempSync(join(tmpdir(), "cfg-builtin-"));
|
|
42
|
+
const configPath = join(dir, "config");
|
|
43
|
+
writeFileSync(configPath, `${JSON.stringify({ apiToken: "secret" })}\n`);
|
|
44
|
+
const prev = process.env.API_TOKEN;
|
|
45
|
+
delete process.env.API_TOKEN;
|
|
46
|
+
try {
|
|
47
|
+
const result = await new Cli(configFixture(configPath)).invoke([
|
|
48
|
+
"config",
|
|
49
|
+
"get",
|
|
50
|
+
"apiToken",
|
|
51
|
+
"--json",
|
|
52
|
+
]);
|
|
53
|
+
expect(result.exitCode).toBe(0);
|
|
54
|
+
expect(JSON.parse(result.stdout.trim())).toEqual({ set: true });
|
|
55
|
+
} finally {
|
|
56
|
+
if (prev !== undefined) process.env.API_TOKEN = prev;
|
|
57
|
+
rmSync(dir, { recursive: true, force: true });
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test("config set writes and resolves without required exit", async () => {
|
|
62
|
+
const dir = mkdtempSync(join(tmpdir(), "cfg-builtin-"));
|
|
63
|
+
const configPath = join(dir, "config");
|
|
64
|
+
writeFileSync(configPath, `${JSON.stringify({ apiToken: "present" })}\n`);
|
|
65
|
+
const prev = process.env.API_TOKEN;
|
|
66
|
+
delete process.env.API_TOKEN;
|
|
67
|
+
try {
|
|
68
|
+
const result = await new Cli(configFixture(configPath)).invoke([
|
|
69
|
+
"config",
|
|
70
|
+
"set",
|
|
71
|
+
"port",
|
|
72
|
+
"9090",
|
|
73
|
+
]);
|
|
74
|
+
expect(result.exitCode).toBe(0);
|
|
75
|
+
const get = await new Cli(configFixture(configPath)).invoke(["config", "get", "port"]);
|
|
76
|
+
expect(get.stdout.trim()).toBe("9090");
|
|
77
|
+
} finally {
|
|
78
|
+
if (prev !== undefined) process.env.API_TOKEN = prev;
|
|
79
|
+
rmSync(dir, { recursive: true, force: true });
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
});
|