argsbarg 3.4.2 → 3.5.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 (66) hide show
  1. package/CHANGELOG.md +14 -1
  2. package/biome.json +29 -6
  3. package/bun.lock +22 -0
  4. package/docs/install.md +1 -1
  5. package/docs/mcp.md +45 -1
  6. package/index.d.ts +50 -50
  7. package/justfile +27 -6
  8. package/package.json +4 -2
  9. package/scripts/release.ts +26 -9
  10. package/src/builtins/builtins.test.ts +9 -4
  11. package/src/builtins/completion-bash.ts +74 -50
  12. package/src/builtins/completion-fish.ts +3 -8
  13. package/src/builtins/completion-group.ts +1 -1
  14. package/src/builtins/completion-zsh.ts +80 -42
  15. package/src/builtins/dispatch.ts +20 -16
  16. package/src/builtins/export.ts +19 -10
  17. package/src/builtins/index.ts +9 -4
  18. package/src/builtins/install.ts +10 -10
  19. package/src/builtins/mcp.ts +1 -1
  20. package/src/builtins/presentation.ts +8 -8
  21. package/src/builtins/scopes.ts +1 -1
  22. package/src/builtins/version.ts +1 -1
  23. package/src/completion.ts +4 -4
  24. package/src/docs/api-guide.test.ts +2 -2
  25. package/src/docs/api-guide.ts +2 -2
  26. package/src/docs/builtin.ts +27 -8
  27. package/src/docs/docs.test.ts +18 -11
  28. package/src/docs/mcp-guide.ts +105 -24
  29. package/src/docs/resolve.ts +10 -3
  30. package/src/docs/save.ts +11 -3
  31. package/src/headless.test.ts +8 -16
  32. package/src/help.ts +73 -43
  33. package/src/hidden-mcpb.test.ts +7 -6
  34. package/src/hidden.ts +2 -2
  35. package/src/index.test.ts +113 -89
  36. package/src/index.ts +24 -24
  37. package/src/install/binary.ts +12 -5
  38. package/src/install/completions.ts +7 -3
  39. package/src/install/detect-installed.ts +29 -4
  40. package/src/install/gh-release-update.ts +31 -23
  41. package/src/install/index.ts +69 -19
  42. package/src/install/install.test.ts +31 -8
  43. package/src/install/mcp-codex.test.ts +57 -0
  44. package/src/install/mcp-codex.ts +125 -0
  45. package/src/install/mcp-config.ts +12 -5
  46. package/src/install/mcp-opencode.test.ts +98 -0
  47. package/src/install/mcp-opencode.ts +149 -0
  48. package/src/install/paths.ts +29 -3
  49. package/src/install/plan.ts +73 -6
  50. package/src/install/shell.ts +1 -4
  51. package/src/install/status.ts +12 -6
  52. package/src/install/uninstall.ts +38 -4
  53. package/src/install/update.test.ts +2 -2
  54. package/src/install/update.ts +3 -1
  55. package/src/invoke.ts +12 -9
  56. package/src/mcp/bundle.ts +36 -8
  57. package/src/mcp/env.ts +7 -13
  58. package/src/mcp/server.ts +12 -6
  59. package/src/mcp/tools.ts +20 -4
  60. package/src/mcp.ts +3 -3
  61. package/src/parse.ts +96 -24
  62. package/src/runtime.ts +22 -12
  63. package/src/schema.ts +11 -5
  64. package/src/skill/generate.ts +4 -4
  65. package/src/skill/install.ts +6 -2
  66. package/src/validate.ts +21 -16
package/src/runtime.ts CHANGED
@@ -2,26 +2,29 @@
2
2
  This module runs parsed commands, help, errors, completion, and leaf handlers.
3
3
  */
4
4
 
5
- import { resolveCapabilities } from "./capabilities.ts";
6
5
  import { builtinInterceptRoot, dispatchBuiltin } from "./builtins/dispatch.ts";
7
6
  import { cliParseRoot, cliPresentationRoot } from "./builtins/presentation.ts";
8
- import type { CliRouter } from "./types.ts";
9
- import { type CliNode, type CliProgram, isCliLeaf, isCliRouter } from "./types.ts";
7
+ import { resolveCapabilities } from "./capabilities.ts";
10
8
  import { CliContext } from "./context.ts";
11
9
  import { cliHelpRender } from "./help.ts";
12
- import { parse, postParseValidate, ParseKind } from "./parse.ts";
10
+ import { ParseKind, parse, postParseValidate } from "./parse.ts";
11
+ import type { CliRouter } from "./types.ts";
12
+ import { type CliNode, type CliProgram, isCliLeaf, isCliRouter } from "./types.ts";
13
13
  import { cliValidateProgram } from "./validate.ts";
14
14
 
15
15
  function cliRootMergedWithBuiltins(program: CliProgram): CliRouter {
16
16
  return cliParseRoot(program);
17
17
  }
18
18
 
19
- export async function cliRun(program: CliProgram, argv: string[] = process.argv.slice(2)): Promise<never> {
19
+ export async function cliRun(
20
+ program: CliProgram,
21
+ argv: string[] = process.argv.slice(2),
22
+ ): Promise<never> {
20
23
  try {
21
24
  cliValidateProgram(program);
22
25
  } catch (err) {
23
26
  if (err instanceof Error) {
24
- process.stderr.write(err.message + "\n");
27
+ process.stderr.write(`${err.message}\n`);
25
28
  } else {
26
29
  process.stderr.write("Invalid CLI definition.\n");
27
30
  }
@@ -31,12 +34,16 @@ export async function cliRun(program: CliProgram, argv: string[] = process.argv.
31
34
  const caps = resolveCapabilities(program);
32
35
 
33
36
  if (argv.length >= 1 && argv[0] === "mcp" && !caps.mcp) {
34
- process.stderr.write("MCP is not enabled. Set mcpServer: { enabled: true } on the program root.\n");
37
+ process.stderr.write(
38
+ "MCP is not enabled. Set mcpServer: { enabled: true } on the program root.\n",
39
+ );
35
40
  process.exit(1);
36
41
  }
37
42
 
38
43
  if (argv.length >= 1 && argv[0] === "install" && !caps.install) {
39
- process.stderr.write("install is disabled. Remove install.enabled: false from the program root.\n");
44
+ process.stderr.write(
45
+ "install is disabled. Remove install.enabled: false from the program root.\n",
46
+ );
40
47
  process.exit(1);
41
48
  }
42
49
 
@@ -75,13 +82,16 @@ export async function cliRun(program: CliProgram, argv: string[] = process.argv.
75
82
  if (pr.kind === "error") {
76
83
  const color = process.stderr.isTTY;
77
84
  const msg = color ? `\u001B[31m${pr.errorMsg}\u001B[0m` : pr.errorMsg;
78
- process.stderr.write(msg + "\n");
85
+ process.stderr.write(`${msg}\n`);
79
86
  process.stderr.write(cliHelpRender(cliPresentationRoot(program), pr.errorHelpPath, true));
80
87
  process.exit(1);
81
88
  }
82
89
 
83
90
  if (pr.kind === ParseKind.Ok) {
84
- await dispatchBuiltin(program, pr, { isLeafCompletionIntercept, parseRoot: completionParseRoot });
91
+ await dispatchBuiltin(program, pr, {
92
+ isLeafCompletionIntercept,
93
+ parseRoot: completionParseRoot,
94
+ });
85
95
  }
86
96
 
87
97
  let current: CliNode = parseRoot;
@@ -109,7 +119,7 @@ export async function cliRun(program: CliProgram, argv: string[] = process.argv.
109
119
  process.exit(0);
110
120
  } catch (err) {
111
121
  if (err instanceof Error) {
112
- process.stderr.write(err.message + "\n");
122
+ process.stderr.write(`${err.message}\n`);
113
123
  }
114
124
  process.exit(1);
115
125
  }
@@ -118,7 +128,7 @@ export async function cliRun(program: CliProgram, argv: string[] = process.argv.
118
128
  export function cliErrWithHelp(ctx: CliContext, msg: string): never {
119
129
  const color = process.stderr.isTTY;
120
130
  const line = color ? `\u001B[31m${msg}\u001B[0m` : msg;
121
- process.stderr.write(line + "\n");
131
+ process.stderr.write(`${line}\n`);
122
132
  process.stderr.write(cliHelpRender(cliPresentationRoot(ctx.program), ctx.commandPath, true));
123
133
  process.exit(1);
124
134
  }
package/src/schema.ts CHANGED
@@ -2,10 +2,16 @@
2
2
  This module serializes the CLI schema tree to JSON for machine-readable introspection.
3
3
  */
4
4
 
5
- import { type CliNode, type CliProgram, isCliLeaf, isCliRouter, leafOutputSchema } from "./types.ts";
6
- import { exportPresentationBuiltins, type CliSchemaExport } from "./builtins/export.ts";
5
+ import { type CliSchemaExport, exportPresentationBuiltins } from "./builtins/export.ts";
7
6
  import { cliResolveNotes } from "./help.ts";
8
7
  import { visibleOptions } from "./hidden.ts";
8
+ import {
9
+ type CliNode,
10
+ type CliProgram,
11
+ isCliLeaf,
12
+ isCliRouter,
13
+ leafOutputSchema,
14
+ } from "./types.ts";
9
15
 
10
16
  const RESERVED = new Set(["completion", "install", "docs", "mcp", "version"]);
11
17
 
@@ -60,8 +66,8 @@ function exportCommand(cmd: CliNode, root: CliProgram): CliSchemaExport | null {
60
66
  /** Resolves `{argsbarg:program}` in exported notes using the root program key. */
61
67
  function resolveSchemaNotes(node: CliSchemaExport, appKey: string): CliSchemaExport {
62
68
  const out: CliSchemaExport = { ...node };
63
- if ((out.notes ?? "").length > 0) {
64
- out.notes = cliResolveNotes(out.notes!, appKey);
69
+ if ((out.notes ?? "").length > 0 && out.notes !== undefined) {
70
+ out.notes = cliResolveNotes(out.notes, appKey);
65
71
  }
66
72
  if (out.commands) {
67
73
  out.commands = out.commands.map((ch) => resolveSchemaNotes(ch, appKey));
@@ -83,7 +89,7 @@ export function cliSchemaExport(root: CliProgram): CliSchemaExport {
83
89
  }
84
90
 
85
91
  export function cliSchemaJson(root: CliProgram): string {
86
- return JSON.stringify(cliSchemaExport(root), null, 2) + "\n";
92
+ return `${JSON.stringify(cliSchemaExport(root), null, 2)}\n`;
87
93
  }
88
94
 
89
95
  export type { CliSchemaExport };
@@ -3,9 +3,9 @@ This module generates Agent Skills content (SKILL.md + reference.md) from a CLI
3
3
  */
4
4
 
5
5
  import { generateApiGuide } from "../docs/api-guide.ts";
6
+ import { collectMcpTools, type McpToolDef, sanitizeToolSegment } from "../mcp/tools.ts";
6
7
  import { collectOptionDefs } from "../parse.ts";
7
- import { collectMcpTools, sanitizeToolSegment, type McpToolDef } from "../mcp/tools.ts";
8
- import { CliProgram, CliOptionKind } from "../types.ts";
8
+ import { CliOptionKind, type CliProgram } from "../types.ts";
9
9
 
10
10
  export type SkillTarget = "cursor" | "claude";
11
11
 
@@ -18,7 +18,7 @@ export interface SkillBundle {
18
18
  /** Truncates text to maxLen with ellipsis. */
19
19
  function truncate(text: string, maxLen: number): string {
20
20
  if (text.length <= maxLen) return text;
21
- return text.slice(0, maxLen - 1) + "…";
21
+ return `${text.slice(0, maxLen - 1)}…`;
22
22
  }
23
23
 
24
24
  /** Builds third-person skill description for YAML frontmatter. */
@@ -54,7 +54,7 @@ function formatCommandEntry(root: CliProgram, tool: McpToolDef): string {
54
54
  }
55
55
  const enums = opts.filter((o) => o.kind === CliOptionKind.Enum && o.choices?.length);
56
56
  for (const e of enums) {
57
- line += ` (\`--${e.name}\`: ${e.choices!.join(" | ")})`;
57
+ line += ` (\`--${e.name}\`: ${e.choices?.join(" | ")})`;
58
58
  }
59
59
  const varargs = (tool.leaf.positionals ?? []).filter((p) => (p.argMax ?? 1) === 0);
60
60
  if (varargs.length > 0) {
@@ -1,7 +1,7 @@
1
1
  import { existsSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
2
2
  import { homedir } from "node:os";
3
3
  import { join } from "node:path";
4
- import { CliProgram } from "../types.ts";
4
+ import type { CliProgram } from "../types.ts";
5
5
  import { generateSkillBundle, type SkillTarget } from "./generate.ts";
6
6
  import { applySkillInstallHints } from "./hint.ts";
7
7
 
@@ -25,7 +25,11 @@ function resolveSkillDir(target: SkillTarget, dirName: string, global: boolean):
25
25
  }
26
26
 
27
27
  /** Writes SKILL.md and reference.md; returns changed file paths. */
28
- export function cliSkillInstall(root: CliProgram, target: SkillTarget, opts: SkillInstallOpts): string[] {
28
+ export function cliSkillInstall(
29
+ root: CliProgram,
30
+ target: SkillTarget,
31
+ opts: SkillInstallOpts,
32
+ ): string[] {
29
33
  const bundle = generateSkillBundle(root, target);
30
34
  const { skillMd, referenceMd } = applySkillInstallHints(root, bundle.skillMd, bundle.referenceMd);
31
35
  const dir = resolveSkillDir(target, bundle.dirName, opts.global ?? false);
package/src/validate.ts CHANGED
@@ -3,17 +3,17 @@ This module validates CLI schemas before execution.
3
3
  */
4
4
 
5
5
  import { reservedCommandNames, resolveCapabilities } from "./capabilities.ts";
6
+ import { DOCS_BUILTIN_TOPIC_KEYS } from "./docs/resolve.ts";
7
+ import { resolveMcpSchemaUri } from "./mcp/tools.ts";
6
8
  import {
7
9
  type CliLeaf,
8
10
  type CliNode,
9
- type CliProgram,
10
11
  CliOptionKind,
12
+ type CliProgram,
11
13
  CliSchemaValidationError,
12
14
  isCliLeaf,
13
15
  isCliRouter,
14
16
  } from "./types.ts";
15
- import { resolveMcpSchemaUri } from "./mcp/tools.ts";
16
- import { DOCS_BUILTIN_TOPIC_KEYS } from "./docs/resolve.ts";
17
17
 
18
18
  /** Validates `docs` configuration on the program root. */
19
19
  function validateDocsConfig(docs: import("./types.ts").CliDocsConfig): void {
@@ -93,17 +93,17 @@ function walkNode(node: CliNode, program: CliProgram, isRoot: boolean): void {
93
93
  const rogue = node as CliProgram;
94
94
  if (rogue.mcpServer !== undefined) {
95
95
  throw new CliSchemaValidationError(
96
- "mcpServer is only supported on the program root (not on " + node.key + ")",
96
+ `mcpServer is only supported on the program root (not on ${node.key})`,
97
97
  );
98
98
  }
99
99
  if (rogue.install !== undefined) {
100
100
  throw new CliSchemaValidationError(
101
- "install is only supported on the program root (not on " + node.key + ")",
101
+ `install is only supported on the program root (not on ${node.key})`,
102
102
  );
103
103
  }
104
104
  if (rogue.docs !== undefined) {
105
105
  throw new CliSchemaValidationError(
106
- "docs is only supported on the program root (not on " + node.key + ")",
106
+ `docs is only supported on the program root (not on ${node.key})`,
107
107
  );
108
108
  }
109
109
  }
@@ -115,9 +115,7 @@ function walkNode(node: CliNode, program: CliProgram, isRoot: boolean): void {
115
115
  const outputSchema = node.outputSchema;
116
116
  const legacyOutputSchema = node.mcpTool?.outputSchema;
117
117
  if (outputSchema !== undefined && legacyOutputSchema !== undefined) {
118
- throw new CliSchemaValidationError(
119
- "Set outputSchema on the leaf only, not under mcpTool",
120
- );
118
+ throw new CliSchemaValidationError("Set outputSchema on the leaf only, not under mcpTool");
121
119
  }
122
120
  const resolved = outputSchema ?? legacyOutputSchema;
123
121
  if (
@@ -132,7 +130,7 @@ function walkNode(node: CliNode, program: CliProgram, isRoot: boolean): void {
132
130
  const rogue = node as unknown as CliLeaf;
133
131
  if (rogue.mcpTool !== undefined) {
134
132
  throw new CliSchemaValidationError(
135
- "mcpTool is only supported on leaf commands (not on " + node.key + ")",
133
+ `mcpTool is only supported on leaf commands (not on ${node.key})`,
136
134
  );
137
135
  }
138
136
  }
@@ -160,9 +158,7 @@ function walkNode(node: CliNode, program: CliProgram, isRoot: boolean): void {
160
158
  }
161
159
 
162
160
  if (node.fallbackMode !== undefined && node.fallbackCommand === undefined) {
163
- throw new CliSchemaValidationError(
164
- `fallbackMode requires fallbackCommand on '${node.key}'`,
165
- );
161
+ throw new CliSchemaValidationError(`fallbackMode requires fallbackCommand on '${node.key}'`);
166
162
  }
167
163
 
168
164
  if (node.fallbackCommand !== undefined) {
@@ -233,10 +229,15 @@ function validateOptions(scopeKey: string, options: import("./types.ts").CliOpti
233
229
  }
234
230
  }
235
231
 
236
- function validatePositionals(scopeKey: string, positionals: import("./types.ts").CliPositional[]): void {
232
+ function validatePositionals(
233
+ scopeKey: string,
234
+ positionals: import("./types.ts").CliPositional[],
235
+ ): void {
237
236
  for (const p of positionals) {
238
237
  if (p.argMin !== undefined && p.argMin < 0) {
239
- throw new CliSchemaValidationError(`argMin must be >= 0 for positional ${scopeKey}/${p.name}`);
238
+ throw new CliSchemaValidationError(
239
+ `argMin must be >= 0 for positional ${scopeKey}/${p.name}`,
240
+ );
240
241
  }
241
242
  if (p.argMax !== undefined && p.argMax < 0) {
242
243
  throw new CliSchemaValidationError(
@@ -262,7 +263,11 @@ function validatePositionals(scopeKey: string, positionals: import("./types.ts")
262
263
  }
263
264
 
264
265
  for (let idx = 0; idx < positionals.length; idx++) {
265
- const { argMax = 1 } = positionals[idx]!;
266
+ const positional = positionals[idx];
267
+ if (!positional) {
268
+ continue;
269
+ }
270
+ const { argMax = 1 } = positional;
266
271
  if (argMax === 0 && idx + 1 < positionals.length) {
267
272
  throw new CliSchemaValidationError(
268
273
  `Unlimited positional (argMax == 0) must be last in scope ${scopeKey}`,