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.
Files changed (98) hide show
  1. package/CHANGELOG.md +28 -2
  2. package/README.md +21 -9
  3. package/docs/README.md +12 -8
  4. package/docs/bundled-docs.md +1 -1
  5. package/docs/cli-program.md +62 -2
  6. package/docs/config-schema.md +192 -0
  7. package/docs/developing.md +13 -0
  8. package/docs/install.md +38 -1
  9. package/docs/mcp.md +43 -19
  10. package/docs/output-schema.md +74 -52
  11. package/docs/templates/cursor/rules/cli-program.mdc +10 -5
  12. package/examples/config-app/main.ts +20 -0
  13. package/examples/config-app/program.ts +81 -0
  14. package/examples/config-app/schema.ts +37 -0
  15. package/examples/config-app/types.ts +19 -0
  16. package/examples/consumer-app/README.md +56 -0
  17. package/examples/consumer-app/bun.lock +75 -0
  18. package/examples/consumer-app/capabilities.test.ts +69 -0
  19. package/examples/consumer-app/package.json +17 -0
  20. package/examples/consumer-app/schemas/configSchemas.ts +6 -0
  21. package/examples/consumer-app/schemas/generated/app-config.json +40 -0
  22. package/examples/consumer-app/schemas/generated/status.json +28 -0
  23. package/examples/consumer-app/schemas/outputSchemas.ts +6 -0
  24. package/examples/consumer-app/scripts/schemagen/discover-schema-roots.test.ts +25 -0
  25. package/examples/consumer-app/scripts/schemagen/discover-schema-roots.ts +93 -0
  26. package/examples/consumer-app/scripts/schemagen/naming.ts +82 -0
  27. package/examples/consumer-app/scripts/schemagen.ts +76 -0
  28. package/examples/consumer-app/src/commands/status/types.ts +11 -0
  29. package/examples/consumer-app/src/main.ts +15 -0
  30. package/examples/consumer-app/src/program.ts +116 -0
  31. package/examples/consumer-app/src/types.ts +23 -0
  32. package/examples/consumer-app/tsconfig.json +14 -0
  33. package/examples/formats.ts +10 -3
  34. package/examples/mcp-test.ts +27 -8
  35. package/examples/minimal.ts +4 -3
  36. package/examples/nested.ts +5 -4
  37. package/examples/option-required.ts +8 -4
  38. package/index.d.ts +152 -75
  39. package/package.json +1 -1
  40. package/src/builtins/builtins.test.ts +13 -0
  41. package/src/builtins/config.test.ts +82 -0
  42. package/src/builtins/config.ts +220 -0
  43. package/src/builtins/dispatch.ts +18 -9
  44. package/src/builtins/export.ts +8 -33
  45. package/src/builtins/index.ts +1 -0
  46. package/src/builtins/install.ts +13 -0
  47. package/src/builtins/mcp.ts +1 -1
  48. package/src/builtins/presentation.ts +2 -17
  49. package/src/builtins/registry.ts +40 -0
  50. package/src/capabilities.ts +46 -0
  51. package/src/cli-errors.ts +15 -0
  52. package/src/cli.ts +389 -0
  53. package/src/config/bootstrap.ts +265 -0
  54. package/src/config/context.test.ts +61 -0
  55. package/src/config/context.ts +98 -0
  56. package/src/config/entry.ts +81 -0
  57. package/src/config/file.test.ts +112 -0
  58. package/src/config/file.ts +120 -0
  59. package/src/config/manifest.ts +62 -0
  60. package/src/config/resolve.test.ts +88 -0
  61. package/src/config/resolve.ts +167 -0
  62. package/src/config/schema.ts +101 -0
  63. package/src/config/validate.test.ts +63 -0
  64. package/src/config/validate.ts +292 -0
  65. package/src/config.integration.test.ts +100 -0
  66. package/src/context.ts +5 -0
  67. package/src/docs/docs.test.ts +16 -16
  68. package/src/docs/mcp-guide.ts +35 -11
  69. package/src/hidden-mcpb.test.ts +40 -2
  70. package/src/index.ts +4 -3
  71. package/src/install/index.ts +46 -3
  72. package/src/install/paths.ts +5 -20
  73. package/src/install/plan.ts +6 -0
  74. package/src/install/status.ts +12 -0
  75. package/src/install/uninstall.ts +11 -0
  76. package/src/install/update.test.ts +5 -5
  77. package/src/invoke.test.ts +207 -0
  78. package/src/mcp/bundle.ts +9 -116
  79. package/src/mcp/claude.test.ts +73 -0
  80. package/src/mcp/claude.ts +168 -0
  81. package/src/mcp/env.ts +3 -37
  82. package/src/mcp/server.ts +18 -10
  83. package/src/mcp/tools.ts +3 -7
  84. package/src/mcp/zip.ts +82 -0
  85. package/src/mcp.integration.test.ts +502 -0
  86. package/src/{index.test.ts → parse.test.ts} +24 -935
  87. package/src/paths/host.ts +40 -0
  88. package/src/schema.ts +1 -1
  89. package/src/skill/generate.ts +18 -5
  90. package/src/skill/hint.ts +18 -0
  91. package/src/skill/install.ts +1 -5
  92. package/src/test-fixtures.ts +192 -0
  93. package/src/types.ts +39 -13
  94. package/src/validate.ts +70 -0
  95. package/src/completion.ts +0 -13
  96. package/src/invoke.ts +0 -217
  97. package/src/mcp.ts +0 -28
  98. package/src/runtime.ts +0 -134
@@ -0,0 +1,40 @@
1
+ /*
2
+ Shared host path primitives for install, config, and skill modules.
3
+ */
4
+
5
+ import { homedir } from "node:os";
6
+ import { join } from "node:path";
7
+
8
+ /** Resolves the user home directory (`$HOME` when set). */
9
+ export function userHome(): string {
10
+ return process.env.HOME ?? homedir();
11
+ }
12
+
13
+ /** Expands a leading `~` or `~/` in a path using {@link userHome}. */
14
+ export function expandTilde(path: string): string {
15
+ if (path.startsWith("~/")) {
16
+ return join(userHome(), path.slice(2));
17
+ }
18
+ if (path === "~") {
19
+ return userHome();
20
+ }
21
+ return path;
22
+ }
23
+
24
+ /** XDG config base directory (`$XDG_CONFIG_HOME` or `~/.config`). */
25
+ export function xdgConfigHome(home = userHome()): string {
26
+ return process.env.XDG_CONFIG_HOME ?? join(home, ".config");
27
+ }
28
+
29
+ /** Windows `%APPDATA%` (or `~/AppData/Roaming`). */
30
+ export function appDataHome(home = userHome()): string {
31
+ return process.env.APPDATA ?? join(home, "AppData", "Roaming");
32
+ }
33
+
34
+ /** OS-appropriate base directory for app config files. */
35
+ export function appConfigHome(home = userHome()): string {
36
+ if (process.platform === "win32") {
37
+ return appDataHome(home);
38
+ }
39
+ return xdgConfigHome(home);
40
+ }
package/src/schema.ts CHANGED
@@ -13,7 +13,7 @@ import {
13
13
  leafOutputSchema,
14
14
  } from "./types.ts";
15
15
 
16
- const RESERVED = new Set(["completion", "install", "docs", "mcp", "version"]);
16
+ const RESERVED = new Set(["completion", "install", "docs", "mcp", "version", "config"]);
17
17
 
18
18
  function exportCommand(cmd: CliNode, root: CliProgram): CliSchemaExport | null {
19
19
  if (cmd.hidden) {
@@ -2,6 +2,7 @@
2
2
  This module generates Agent Skills content (SKILL.md + reference.md) from a CLI schema.
3
3
  */
4
4
 
5
+ import { defaultConfigEntryTitle } from "../config/entry.ts";
5
6
  import { generateApiGuide } from "../docs/api-guide.ts";
6
7
  import { collectMcpTools, type McpToolDef, sanitizeToolSegment } from "../mcp/tools.ts";
7
8
  import { collectOptionDefs } from "../parse.ts";
@@ -60,13 +61,24 @@ function formatCommandEntry(root: CliProgram, tool: McpToolDef): string {
60
61
  if (varargs.length > 0) {
61
62
  line += ` (varargs: ${varargs.map((p) => p.name).join(", ")})`;
62
63
  }
63
- const env = tool.leaf.mcpTool?.requiresEnv;
64
- if (env && env.length > 0) {
65
- line += ` [requires env: ${env.join(", ")}]`;
66
- }
67
64
  return line;
68
65
  }
69
66
 
67
+ function buildConfigurationSection(root: CliProgram): string[] {
68
+ const schema = root.appConfig?.entries;
69
+ if (!schema || Object.keys(schema).length === 0) {
70
+ return [];
71
+ }
72
+ const lines = ["## Configuration", ""];
73
+ for (const [key, entry] of Object.entries(schema)) {
74
+ const label = entry.title ?? defaultConfigEntryTitle(key);
75
+ const envNote = entry.env ? ` (env: \`${entry.env}\`)` : "";
76
+ lines.push(`- **${label}** (\`${key}\`${envNote}) — ${entry.description}`);
77
+ }
78
+ lines.push("");
79
+ return lines;
80
+ }
81
+
70
82
  /** Builds SKILL.md body for the given target. */
71
83
  function buildSkillMd(root: CliProgram, target: SkillTarget, dirName: string): string {
72
84
  const name = sanitizeToolSegment(root.key);
@@ -104,11 +116,12 @@ function buildSkillMd(root: CliProgram, target: SkillTarget, dirName: string): s
104
116
  lines.push("");
105
117
  }
106
118
 
119
+ lines.push(...buildConfigurationSection(root));
120
+
107
121
  lines.push(
108
122
  "## Pitfalls",
109
123
  "",
110
124
  "- Pass `--` before arguments that look like flags.",
111
- "- Commands marked `[requires env: ...]` need those variables set in the shell.",
112
125
  "",
113
126
  "## Reference",
114
127
  "",
package/src/skill/hint.ts CHANGED
@@ -40,3 +40,21 @@ export function applySkillInstallHints(
40
40
  referenceMd: insertGeneratedHint(referenceMd, hint),
41
41
  };
42
42
  }
43
+
44
+ /** Hint for `mcp bundle` plugin skill output. */
45
+ export function skillBundleHint(program: CliProgram): string {
46
+ return generatedFileHtmlComment(`${program.key} mcp bundle`);
47
+ }
48
+
49
+ /** Applies bundle hints to SKILL.md (after frontmatter) and reference.md. */
50
+ export function applySkillBundleHints(
51
+ program: CliProgram,
52
+ skillMd: string,
53
+ referenceMd: string,
54
+ ): { skillMd: string; referenceMd: string } {
55
+ const hint = skillBundleHint(program);
56
+ return {
57
+ skillMd: insertGeneratedHint(skillMd, hint, { afterFrontmatter: true }),
58
+ referenceMd: insertGeneratedHint(referenceMd, hint),
59
+ };
60
+ }
@@ -1,6 +1,6 @@
1
1
  import { existsSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
2
- import { homedir } from "node:os";
3
2
  import { join } from "node:path";
3
+ import { userHome } from "../paths/host.ts";
4
4
  import type { CliProgram } from "../types.ts";
5
5
  import { generateSkillBundle, type SkillTarget } from "./generate.ts";
6
6
  import { applySkillInstallHints } from "./hint.ts";
@@ -13,10 +13,6 @@ export interface SkillInstallOpts {
13
13
  dry?: boolean;
14
14
  }
15
15
 
16
- function userHome(): string {
17
- return process.env.HOME ?? homedir();
18
- }
19
-
20
16
  function resolveSkillDir(target: SkillTarget, dirName: string, global: boolean): string {
21
17
  const base = global
22
18
  ? join(userHome(), target === "cursor" ? ".cursor" : ".claude", "skills")
@@ -0,0 +1,192 @@
1
+ import type { CliProgram } from "./types.ts";
2
+ import { CliFallbackMode, CliOptionKind } from "./types.ts";
3
+
4
+ export function testProgram(
5
+ prog: Record<string, unknown> & { key: string; description: string },
6
+ ): CliProgram {
7
+ return { version: "0.0.0", ...prog } as CliProgram;
8
+ }
9
+
10
+ export const nestedMcpFixture = testProgram({
11
+ key: "nested.ts",
12
+ description: "Nested groups demo.",
13
+ version: "1.0.0",
14
+ mcpServer: { enabled: true },
15
+ commands: [
16
+ {
17
+ key: "stat",
18
+ description: "File metadata.",
19
+ options: [
20
+ {
21
+ name: "json",
22
+ description: "Emit handler output as JSON.",
23
+ kind: CliOptionKind.Presence,
24
+ },
25
+ ],
26
+ commands: [
27
+ {
28
+ key: "owner",
29
+ description: "Ownership helpers.",
30
+ commands: [
31
+ {
32
+ key: "lookup",
33
+ description: "Resolve owner info.",
34
+ options: [
35
+ {
36
+ name: "user-name",
37
+ description: "User to look up.",
38
+ kind: CliOptionKind.String,
39
+ shortName: "u",
40
+ },
41
+ ],
42
+ positionals: [
43
+ {
44
+ name: "path",
45
+ description: "File or directory.",
46
+ kind: CliOptionKind.String,
47
+ },
48
+ ],
49
+ handler: () => {},
50
+ },
51
+ ],
52
+ },
53
+ ],
54
+ },
55
+ {
56
+ key: "read",
57
+ description: "Print the first line of each file.",
58
+ positionals: [
59
+ {
60
+ name: "files",
61
+ description: "Paths to read.",
62
+ kind: CliOptionKind.String,
63
+ argMax: 0,
64
+ },
65
+ ],
66
+ handler: () => {},
67
+ },
68
+ {
69
+ key: "hidden",
70
+ description: "Internal debug.",
71
+ mcpTool: { enabled: false },
72
+ handler: () => {},
73
+ },
74
+ ],
75
+ fallbackCommand: "read",
76
+ fallbackMode: CliFallbackMode.MissingOrUnknown,
77
+ });
78
+
79
+ /** Sends NDJSON MCP requests to a subprocess and collects responses by id. */
80
+ export async function mcpRequest(
81
+ requests: object[],
82
+ opts?: { script?: string; env?: Record<string, string> },
83
+ ): Promise<Map<string | number, object>> {
84
+ const script = opts?.script ?? "examples/nested.ts";
85
+ const proc = Bun.spawn(["bun", "run", script, "mcp"], {
86
+ stdin: "pipe",
87
+ stdout: "pipe",
88
+ stderr: "pipe",
89
+ env: opts?.env ? { ...process.env, ...opts.env } : process.env,
90
+ });
91
+
92
+ const input = requests.map((r) => `${JSON.stringify(r)}\n`).join("");
93
+ proc.stdin.write(input);
94
+ proc.stdin.end();
95
+
96
+ const timeout = setTimeout(() => proc.kill(), 10_000);
97
+ const stdout = await new Response(proc.stdout).text();
98
+ await proc.exited;
99
+ clearTimeout(timeout);
100
+
101
+ const byId = new Map<string | number, object>();
102
+ for (const line of stdout.split("\n")) {
103
+ const trimmed = line.trim();
104
+ if (!trimmed) {
105
+ continue;
106
+ }
107
+ const msg = JSON.parse(trimmed) as { id?: string | number };
108
+ if (msg.id !== undefined) {
109
+ byId.set(msg.id, msg);
110
+ }
111
+ }
112
+ return byId;
113
+ }
114
+
115
+ export const enumMcpFixture = testProgram({
116
+ key: "app",
117
+ description: "",
118
+ mcpServer: { enabled: true },
119
+ commands: [
120
+ {
121
+ key: "run",
122
+ description: "Run with mode.",
123
+ options: [
124
+ {
125
+ name: "mode",
126
+ description: "Mode.",
127
+ kind: CliOptionKind.Enum,
128
+ choices: ["dev", "prod"],
129
+ required: true,
130
+ },
131
+ ],
132
+ handler: () => {},
133
+ },
134
+ ],
135
+ });
136
+
137
+ export function varargsReadFixture(): CliProgram {
138
+ return testProgram({
139
+ key: "app",
140
+ description: "",
141
+ commands: [
142
+ {
143
+ key: "read",
144
+ description: "Read files.",
145
+ options: [
146
+ {
147
+ name: "json",
148
+ description: "",
149
+ kind: CliOptionKind.Presence,
150
+ },
151
+ ],
152
+ positionals: [
153
+ {
154
+ name: "files",
155
+ description: "",
156
+ kind: CliOptionKind.String,
157
+ argMin: 0,
158
+ argMax: 0,
159
+ },
160
+ ],
161
+ handler: () => {},
162
+ },
163
+ ],
164
+ });
165
+ }
166
+
167
+ export function nestedDocsFallbackFixture(): CliProgram {
168
+ return testProgram({
169
+ key: "app",
170
+ description: "",
171
+ commands: [
172
+ {
173
+ key: "docs",
174
+ description: "Documentation commands.",
175
+ fallbackCommand: "guide",
176
+ fallbackMode: CliFallbackMode.MissingOnly,
177
+ commands: [
178
+ {
179
+ key: "guide",
180
+ description: "User guide.",
181
+ handler: () => {},
182
+ },
183
+ {
184
+ key: "api",
185
+ description: "API reference.",
186
+ handler: () => {},
187
+ },
188
+ ],
189
+ },
190
+ ],
191
+ });
192
+ }
package/src/types.ts CHANGED
@@ -141,12 +141,6 @@ export interface CliMcpServerConfig {
141
141
  * and shell exports that MCP hosts (e.g. Cursor) don't inherit.
142
142
  */
143
143
  shellEnv?: boolean | string;
144
- /**
145
- * Path to a .env file loaded into process.env at MCP server start, after shellEnv.
146
- * Supports `~` expansion. Warns on stderr if the file does not exist.
147
- * Always overwrites — envFile is authoritative for its keys.
148
- */
149
- envFile?: string;
150
144
  /**
151
145
  * Custom MCP resources exposed alongside the built-in schema resource.
152
146
  * URIs must be unique and must not equal schemaResourceUri.
@@ -183,12 +177,6 @@ export interface CliMcpToolConfig {
183
177
  * Default: auto-generated from command path and description.
184
178
  */
185
179
  description?: string;
186
- /**
187
- * Environment variable names required at runtime.
188
- * Appended to auto-generated MCP tool descriptions; enforced at tools/call time.
189
- * Empty string counts as absent.
190
- */
191
- requiresEnv?: string[];
192
180
  /**
193
181
  * @deprecated Set `outputSchema` on the leaf command instead.
194
182
  */
@@ -210,6 +198,42 @@ export interface CliUpdateArtifact {
210
198
  /** Fetches the latest release binary for `install --update`. */
211
199
  export type CliUpdateGetLatest = (ctx: { version: string }) => Promise<CliUpdateArtifact>;
212
200
 
201
+ /**
202
+ * Metadata overlay for one key in {@link CliAppConfig.entries}.
203
+ * Types and validation come from {@link CliAppConfig.jsonSchema} when set; otherwise all values are strings.
204
+ */
205
+ export interface CliAppConfigEntry {
206
+ /** Help text for prompts, MCP manifests, and generated docs. */
207
+ description: string;
208
+ /** Short label in host UIs and CLI prompts. Default: the config key. */
209
+ title?: string;
210
+ /** Default when `jsonSchema` is omitted (all-string mode). */
211
+ default?: string;
212
+ /** When `false`, optional for bootstrap and MCP enforcement. Default: `true`. */
213
+ required?: boolean;
214
+ /**
215
+ * Mask stdin during prompts and redact on `config get`.
216
+ * Default: `/key|token|secret|password/i.test(name)`.
217
+ */
218
+ sensitive?: boolean;
219
+ /** When set: non-empty `process.env[env]` overrides file; value exported after resolve. */
220
+ env?: string;
221
+ }
222
+
223
+ /**
224
+ * App configuration block on the program root ({@link CliProgram.appConfig}).
225
+ */
226
+ export interface CliAppConfig {
227
+ /** Default: `~/.config/<sanitized-key>/config` (or `%APPDATA%/<key>/config` on Windows). */
228
+ path?: string;
229
+ /** Built-in `config get` / `config set`. Default: enabled when `appConfig` is set. */
230
+ commands?: boolean | { enabled?: boolean; mcpSet?: boolean };
231
+ /** Block JSON Schema (draft-07). When omitted, synthesize all-string schema from `entries`. */
232
+ jsonSchema?: Record<string, unknown>;
233
+ /** Per-key metadata; keys must match `jsonSchema.properties` when `jsonSchema` is set. */
234
+ entries: Record<string, CliAppConfigEntry>;
235
+ }
236
+
213
237
  export interface CliInstallConfig {
214
238
  /** When `false`, hide/disable `install` (default: enabled). */
215
239
  enabled?: boolean;
@@ -301,12 +325,14 @@ export type CliRouter = CliNodeBase & {
301
325
  export type CliNode = CliLeaf | CliRouter;
302
326
 
303
327
  /**
304
- * Program root passed to `cliRun` / `cliInvoke`.
328
+ * Program root passed to {@link Cli}.
305
329
  * May be a leaf or router, plus optional program-level MCP and install config.
306
330
  */
307
331
  export type CliProgram = CliNode & {
308
332
  /** Program version (printed by the `version` built-in and MCP serverInfo). */
309
333
  version: string;
334
+ /** Schema-driven app config file, bootstrap, and MCP metadata. */
335
+ appConfig?: CliAppConfig;
310
336
  /** When set with `enabled: true`, enables the `mcp` built-in subcommand. */
311
337
  mcpServer?: CliMcpServerConfig;
312
338
  /** Opt-out and defaults for `install`. */
package/src/validate.ts CHANGED
@@ -43,6 +43,67 @@ function validateDocsConfig(docs: import("./types.ts").CliDocsConfig): void {
43
43
  }
44
44
  }
45
45
 
46
+ /** Validates `program.appConfig` on the program root. */
47
+ function validateConfigBlock(appConfigBlock: import("./types.ts").CliAppConfig): void {
48
+ const entries = appConfigBlock.entries;
49
+ if (typeof entries !== "object" || entries === null || Array.isArray(entries)) {
50
+ throw new CliSchemaValidationError("program.appConfig.entries must be an object");
51
+ }
52
+
53
+ const envNames = new Set<string>();
54
+ for (const [key, entry] of Object.entries(entries)) {
55
+ if (key.length === 0) {
56
+ throw new CliSchemaValidationError(
57
+ "program.appConfig.entries keys must be non-empty strings",
58
+ );
59
+ }
60
+ if (entry === undefined || typeof entry !== "object") {
61
+ throw new CliSchemaValidationError(`program.appConfig.entries['${key}'] must be an object`);
62
+ }
63
+ const description = entry.description;
64
+ if (typeof description !== "string" || description.trim().length === 0) {
65
+ throw new CliSchemaValidationError(
66
+ `program.appConfig.entries['${key}'].description must be a non-empty string`,
67
+ );
68
+ }
69
+ if (entry.env !== undefined) {
70
+ if (typeof entry.env !== "string" || entry.env.length === 0) {
71
+ throw new CliSchemaValidationError(
72
+ `program.appConfig.entries['${key}'].env must be a non-empty string when set`,
73
+ );
74
+ }
75
+ if (envNames.has(entry.env)) {
76
+ throw new CliSchemaValidationError(`Duplicate program.appConfig env mapping: ${entry.env}`);
77
+ }
78
+ envNames.add(entry.env);
79
+ }
80
+ }
81
+
82
+ const jsonSchema = appConfigBlock.jsonSchema;
83
+ if (jsonSchema !== undefined) {
84
+ if (typeof jsonSchema !== "object" || jsonSchema === null || Array.isArray(jsonSchema)) {
85
+ throw new CliSchemaValidationError(
86
+ "program.appConfig.jsonSchema must be a JSON Schema object (not null or an array)",
87
+ );
88
+ }
89
+ const properties = jsonSchema.properties;
90
+ if (properties !== undefined) {
91
+ if (typeof properties !== "object" || properties === null || Array.isArray(properties)) {
92
+ throw new CliSchemaValidationError(
93
+ "program.appConfig.jsonSchema.properties must be an object when set",
94
+ );
95
+ }
96
+ for (const key of Object.keys(entries)) {
97
+ if (!(key in properties)) {
98
+ throw new CliSchemaValidationError(
99
+ `program.appConfig.entries key '${key}' is missing from jsonSchema.properties`,
100
+ );
101
+ }
102
+ }
103
+ }
104
+ }
105
+ }
106
+
46
107
  /** Validates a program schema. */
47
108
  export function cliValidateProgram(program: CliProgram): void {
48
109
  if (!program.version || program.version.trim().length === 0) {
@@ -65,6 +126,10 @@ export function cliValidateProgram(program: CliProgram): void {
65
126
  validateDocsConfig(program.docs);
66
127
  }
67
128
 
129
+ if (program.appConfig !== undefined) {
130
+ validateConfigBlock(program.appConfig);
131
+ }
132
+
68
133
  if (program.install?.updateGetLatest !== undefined) {
69
134
  if (program.install.enabled === false) {
70
135
  throw new CliSchemaValidationError(
@@ -108,6 +173,11 @@ function walkNode(node: CliNode, program: CliProgram, isRoot: boolean): void {
108
173
  `docs is only supported on the program root (not on ${node.key})`,
109
174
  );
110
175
  }
176
+ if (rogue.appConfig !== undefined) {
177
+ throw new CliSchemaValidationError(
178
+ `appConfig is only supported on the program root (not on ${node.key})`,
179
+ );
180
+ }
111
181
  }
112
182
 
113
183
  if (isCliLeaf(node)) {
package/src/completion.ts DELETED
@@ -1,13 +0,0 @@
1
- /*
2
- Re-export shim — completion emitters and built-in trees live in ./builtins/.
3
- */
4
-
5
- export {
6
- cliBuiltinCompletionGroup,
7
- cliBuiltinInstallCommand,
8
- cliBuiltinMcpCommand,
9
- cliPresentationRoot,
10
- completionBashScript,
11
- completionFishScript,
12
- completionZshScript,
13
- } from "./builtins/index.ts";