argsbarg 3.3.10 → 3.3.12

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 CHANGED
@@ -7,6 +7,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [3.3.12] - 2026-06-21
11
+
12
+ ### Changed
13
+
14
+ - **Agent skills** — `SKILL.md` embeds the `docs api` command reference (body only) instead of a separate `## Commands` bullet catalog.
15
+
16
+ ## [3.3.11] - 2026-06-21
17
+
18
+ ### Added
19
+
20
+ - **Root help agent hint** — when `docs` is enabled, top-level `-h` includes a Notes line: `Agents: run \`myapp docs skill\` to learn how to use this app`. Root help also renders `program.notes`.
21
+
10
22
  ## [3.3.10] - 2026-06-21
11
23
 
12
24
  ### Changed
@@ -311,7 +323,9 @@ const cli = { ... } satisfies CliProgram; // or : CliProgram
311
323
  - Migrate schemas: rename every `children` property to **`commands`**; move positional definitions to **`CliPositional`** objects on `positionals` and strip `positional` / `argMin` / `argMax` from flag definitions under `options` (flags only carry `name`, `description`, `kind`, and optional `shortName`).
312
324
  - Imports: use `CliPositional` where needed; replace `CliOptionDef` with `CliOption` or `CliPositional` as appropriate.
313
325
 
314
- [Unreleased]: https://github.com/bdombro/bun-argsbarg/compare/v3.3.10...HEAD
326
+ [Unreleased]: https://github.com/bdombro/bun-argsbarg/compare/v3.3.12...HEAD
327
+ [3.3.12]: https://github.com/bdombro/bun-argsbarg/releases/tag/v3.3.12
328
+ [3.3.11]: https://github.com/bdombro/bun-argsbarg/releases/tag/v3.3.11
315
329
  [3.3.10]: https://github.com/bdombro/bun-argsbarg/releases/tag/v3.3.10
316
330
  [3.3.9]: https://github.com/bdombro/bun-argsbarg/releases/tag/v3.3.9
317
331
  [3.3.8]: https://github.com/bdombro/bun-argsbarg/releases/tag/v3.3.8
package/docs/ai-skills.md CHANGED
@@ -31,7 +31,7 @@ For library use, call `cliSkillInstall(root, "cursor" | "claude", { global: true
31
31
 
32
32
  ## Generated content
33
33
 
34
- - **`SKILL.md`** — YAML frontmatter, when-to-use guidance, shell command catalog, pitfalls
34
+ - **`SKILL.md`** — YAML frontmatter, when-to-use guidance, the same command reference as `docs api` (without the API doc header), pitfalls
35
35
  - **`reference.md`** — full `docs schema` JSON export
36
36
 
37
37
  Installed files include an HTML comment hint (`Generated by myapp install --skill; do not edit.`) after SKILL.md frontmatter and at the top of `reference.md`.
@@ -43,10 +43,10 @@ Skills describe **shell invocation only** — no MCP setup, `mcp.json`, or `tool
43
43
  | Mechanism | Role |
44
44
  | --- | --- |
45
45
  | **`myapp mcp`** (requires `mcpServer`) | Runtime tool execution over MCP |
46
- | **`myapp install --skill`** | Static shell command catalog for agents |
46
+ | **`myapp install --skill`** | Static shell command reference for agents (same body as `docs api`) |
47
47
  | **`myapp docs`** (requires `docs`) | Bundled markdown on stdout (`docs mcp` when MCP enabled) |
48
48
 
49
- Command catalog lines reuse MCP tool descriptions. See [cli-program.md](cli-program.md).
49
+ Command reference in skills reuses the `docs api` command tree. See [cli-program.md](cli-program.md).
50
50
 
51
51
  See also:
52
52
 
@@ -35,6 +35,8 @@ myapp docs readme --save # write ./docs/readme.md
35
35
  myapp docs schema --save # write ./docs/schema.json
36
36
  ```
37
37
 
38
+ When `docs` is enabled, top-level `myapp --help` includes a Notes line: `Agents: run \`myapp docs skill\` to learn how to use this app`.
39
+
38
40
  ## Configuration
39
41
 
40
42
  | Field | Default | Purpose |
@@ -82,7 +84,7 @@ All `docs` subcommands are hidden from MCP `tools/list` (`mcpTool: { enabled: fa
82
84
 
83
85
  | Channel | Role |
84
86
  | --- | --- |
85
- | `install --skill` | Writes shell command catalog + `reference.md` to disk |
87
+ | `install --skill` | Writes `SKILL.md` (API command reference) + `reference.md` to disk |
86
88
  | `docs skill` | Print generated `SKILL.md` to stdout |
87
89
  | `docs api` | Print command tree markdown to stdout |
88
90
  | `docs schema` | Print command tree JSON to stdout |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "argsbarg",
3
- "version": "3.3.10",
3
+ "version": "3.3.12",
4
4
  "type": "module",
5
5
  "engines": {
6
6
  "bun": ">=1.3"
@@ -80,6 +80,15 @@ describe("presentation root", () => {
80
80
  const root = cliPresentationRoot(fixture);
81
81
  expect(root.commands?.map((c) => c.key)).toContain("version");
82
82
  });
83
+
84
+ test("root notes include agent hint when docs enabled", () => {
85
+ const withDocs: CliProgram = {
86
+ ...fixture,
87
+ docs: { enabled: true, topics: { readme: { text: "# r\n" } } },
88
+ };
89
+ const root = cliPresentationRoot(withDocs);
90
+ expect(root.notes).toContain("Agents: run `myapp docs skill` to learn how to use this app");
91
+ });
83
92
  });
84
93
 
85
94
  describe("completion emitters", () => {
@@ -34,11 +34,13 @@ export function presentationBuiltins(program: CliProgram, caps: CliCapabilities)
34
34
  export function cliPresentationRoot(program: CliProgram): CliRouter {
35
35
  const caps = resolveCapabilities(program);
36
36
  const builtins = presentationBuiltins(program, caps);
37
+ const notes = presentationRootNotes(program, caps);
37
38
 
38
39
  if (isCliLeaf(program)) {
39
40
  return {
40
41
  key: program.key,
41
42
  description: program.description,
43
+ notes,
42
44
  options: program.options,
43
45
  commands: builtins,
44
46
  };
@@ -47,7 +49,7 @@ export function cliPresentationRoot(program: CliProgram): CliRouter {
47
49
  return {
48
50
  key: program.key,
49
51
  description: program.description,
50
- notes: program.notes,
52
+ notes,
51
53
  options: program.options,
52
54
  fallbackCommand: program.fallbackCommand,
53
55
  fallbackMode: program.fallbackMode,
@@ -55,5 +57,22 @@ export function cliPresentationRoot(program: CliProgram): CliRouter {
55
57
  };
56
58
  }
57
59
 
60
+ /** Root help notes: consumer `program.notes` plus agent discovery when `docs` is enabled. */
61
+ export function presentationRootNotes(program: CliProgram, caps: CliCapabilities): string | undefined {
62
+ const parts: string[] = [];
63
+ if ((program.notes ?? "").trim().length > 0) {
64
+ parts.push(program.notes!.trim());
65
+ }
66
+ if (caps.docs) {
67
+ const cmd = `${program.key} docs skill`;
68
+ parts.push(`Agents: run \`${cmd}\` to learn how to use this app`);
69
+ }
70
+ if (parts.length === 0) {
71
+ return undefined;
72
+ }
73
+ return parts.join("\n\n");
74
+ }
75
+
58
76
  /** Presentation tree may include builtin leaf stubs. */
59
77
  export type CliPresentationNode = CliNode | CliLeaf;
78
+
@@ -1,7 +1,7 @@
1
1
  import { expect, test } from "bun:test";
2
2
  import type { CliProgram } from "../types.ts";
3
3
  import { CliOptionKind } from "../types.ts";
4
- import { generateApiGuide } from "./api-guide.ts";
4
+ import { generateApiGuide, generateApiGuideBody } from "./api-guide.ts";
5
5
  import { cliSchemaExport } from "../schema.ts";
6
6
 
7
7
  const nestedFixture: CliProgram = {
@@ -45,6 +45,14 @@ const nestedFixture: CliProgram = {
45
45
  ],
46
46
  };
47
47
 
48
+ test("generateApiGuideBody matches command section of full API guide", () => {
49
+ const body = generateApiGuideBody(nestedFixture);
50
+ const full = generateApiGuide(nestedFixture);
51
+ expect(full).toContain(body.trimEnd());
52
+ expect(body).toContain("## `nested.ts stat`");
53
+ expect(body).not.toContain("CLI API reference");
54
+ });
55
+
48
56
  test("generateApiGuide covers the same command keys as cliSchemaExport", () => {
49
57
  const md = generateApiGuide(nestedFixture);
50
58
  const schema = cliSchemaExport(nestedFixture);
@@ -117,6 +117,14 @@ function renderCommandNode(
117
117
  }
118
118
  }
119
119
 
120
+ /** Command-tree markdown shared by `docs api` and generated agent skills (no API doc header). */
121
+ export function generateApiGuideBody(program: CliProgram): string {
122
+ const schema = cliSchemaExport(program);
123
+ const lines: string[] = [];
124
+ renderCommandNode(program.key, [], schema, lines);
125
+ return `${lines.join("\n").trimEnd()}\n`;
126
+ }
127
+
120
128
  /** Generates markdown API reference from the same export as `docs schema`. */
121
129
  export function generateApiGuide(program: CliProgram): string {
122
130
  const schema = cliSchemaExport(program);
@@ -133,6 +141,6 @@ export function generateApiGuide(program: CliProgram): string {
133
141
  lines.push(formatNotesBlockquote(schema.notes, program.key), "");
134
142
  }
135
143
 
136
- renderCommandNode(program.key, [], schema, lines);
144
+ lines.push(generateApiGuideBody(program).trimEnd(), "");
137
145
  return `${lines.join("\n").trimEnd()}\n`;
138
146
  }
@@ -152,7 +152,8 @@ test("docs skill prints Cursor SKILL.md", async () => {
152
152
  expect(result.exitCode).toBe(0);
153
153
  expect(result.stdout).toContain("---");
154
154
  expect(result.stdout).toContain("name: myapp");
155
- expect(result.stdout).toContain("## Commands");
155
+ expect(result.stdout).toContain("#### Options");
156
+ expect(result.stdout).not.toContain("## Commands");
156
157
  expect(result.stdout).not.toContain("mcp.json");
157
158
  });
158
159
 
package/src/help.ts CHANGED
@@ -381,6 +381,21 @@ function rowsForSubcommands(cmds: CliNode[]): HelpRow[] {
381
381
 
382
382
  // ── Main Help Render ──────────────────────────────────────────────────────────
383
383
 
384
+ function appendNotesBox(
385
+ lines: string[],
386
+ notes: string | undefined,
387
+ appKey: string,
388
+ hw: number,
389
+ color: boolean,
390
+ ): void {
391
+ if ((notes ?? "").length === 0) {
392
+ return;
393
+ }
394
+ const resolved = cliResolveNotes(notes!, appKey);
395
+ lines.push("");
396
+ lines.push(renderTextBox("Notes", wrapText(resolved, hw - 4), hw, color).join("\n"));
397
+ }
398
+
384
399
  /**
385
400
  * Renders full help for the app root or a nested command, following `helpPath` from the root key.
386
401
  * `useStderr` is reserved for call-site consistency; width and color use stdout TTY.
@@ -416,6 +431,7 @@ export function cliHelpRender(schema: CliRouter, helpPath: string[], useStderr:
416
431
  renderTableBox("Commands", rowsForSubcommands(schema.commands ?? []), hw, color).join("\n"),
417
432
  );
418
433
  }
434
+ appendNotesBox(lines, schema.notes, schema.key, hw, color);
419
435
  return lines.join("\n") + "\n\n";
420
436
  }
421
437
 
@@ -479,11 +495,7 @@ export function cliHelpRender(schema: CliRouter, helpPath: string[], useStderr:
479
495
  }
480
496
 
481
497
  if ((node.notes ?? "").length > 0) {
482
- const resolved = cliResolveNotes(node.notes!, schema.key);
483
- lines.push("");
484
- lines.push(
485
- renderTextBox("Notes", wrapText(resolved, hw - 4), hw, color).join("\n"),
486
- );
498
+ appendNotesBox(lines, node.notes, schema.key, hw, color);
487
499
  }
488
500
 
489
501
  return lines.join("\n") + "\n\n";
package/src/index.test.ts CHANGED
@@ -678,6 +678,50 @@ test("root help omits legacy --schema flag", () => {
678
678
  expect(help).not.toContain("--schema");
679
679
  });
680
680
 
681
+ test("root help shows agent docs hint when docs enabled", () => {
682
+ const root = testProgram({
683
+ key: "myapp",
684
+ version: "1.0.0",
685
+ description: "demo",
686
+ docs: {
687
+ enabled: true,
688
+ topics: { readme: { text: "# readme\n" } },
689
+ },
690
+ commands: [{ key: "run", description: "Run.", handler: () => {} }],
691
+ });
692
+ const help = cliHelpRender(cliPresentationRoot(root), [], false);
693
+ expect(help).toContain("Agents: run `myapp docs skill` to learn how to use this app");
694
+ });
695
+
696
+ test("root help omits agent hint when docs disabled", () => {
697
+ const root = testProgram({
698
+ key: "myapp",
699
+ version: "1.0.0",
700
+ description: "demo",
701
+ commands: [{ key: "run", description: "Run.", handler: () => {} }],
702
+ });
703
+ const help = cliHelpRender(cliPresentationRoot(root), [], false);
704
+ expect(help).not.toContain("Agents:");
705
+ expect(help).not.toContain("docs skill");
706
+ });
707
+
708
+ test("root help includes program notes and agent hint", () => {
709
+ const root = testProgram({
710
+ key: "myapp",
711
+ version: "1.0.0",
712
+ description: "demo",
713
+ notes: "See `{argsbarg:program} docs readme` for the user guide.",
714
+ docs: {
715
+ enabled: true,
716
+ topics: { readme: { text: "# readme\n" } },
717
+ },
718
+ commands: [{ key: "run", description: "Run.", handler: () => {} }],
719
+ });
720
+ const help = cliHelpRender(cliPresentationRoot(root), [], false);
721
+ expect(help).toContain("See `myapp docs readme` for the user guide.");
722
+ expect(help).toContain("myapp docs skill");
723
+ });
724
+
681
725
  const nestedMcpFixture = testProgram({
682
726
  key: "nested.ts",
683
727
  description: "Nested groups demo.",
@@ -1764,12 +1808,15 @@ test("install config on non-root node is rejected", () => {
1764
1808
  expect(() => cliValidateProgram(root)).toThrow(/install is only supported on the program root/);
1765
1809
  });
1766
1810
 
1767
- test("generateSkillBundle includes frontmatter and command catalog", () => {
1811
+ test("generateSkillBundle includes frontmatter and API command reference", () => {
1768
1812
  const bundle = generateSkillBundle(nestedMcpFixture, "cursor");
1769
1813
  expect(bundle.dirName).toBe("nested_ts");
1770
1814
  expect(bundle.skillMd).toMatch(/^---\nname: nested_ts\n/);
1771
- expect(bundle.skillMd).toContain("stat owner lookup");
1815
+ expect(bundle.skillMd).toContain("`nested.ts stat owner lookup`");
1816
+ expect(bundle.skillMd).toContain("#### Options");
1772
1817
  expect(bundle.skillMd).toContain("Invoke via shell:");
1818
+ expect(bundle.skillMd).not.toContain("## Commands");
1819
+ expect(bundle.skillMd).not.toContain("CLI API reference");
1773
1820
  expect(bundle.skillMd).not.toContain("mcp.json");
1774
1821
  expect(bundle.skillMd).not.toContain("Prefer MCP");
1775
1822
  expect(bundle.skillMd).not.toContain("tools/call");
@@ -1789,7 +1836,7 @@ test("cliSkillInstall writes project Cursor skill files", () => {
1789
1836
  const skillDir = join(cwd, ".cursor", "skills", "nested_ts");
1790
1837
  expect(existsSync(join(skillDir, "SKILL.md"))).toBe(true);
1791
1838
  expect(existsSync(join(skillDir, "reference.md"))).toBe(true);
1792
- expect(readFileSync(join(skillDir, "SKILL.md"), "utf8")).toContain("stat owner lookup");
1839
+ expect(readFileSync(join(skillDir, "SKILL.md"), "utf8")).toContain("`nested.ts stat owner lookup`");
1793
1840
  const skillText = readFileSync(join(skillDir, "SKILL.md"), "utf8");
1794
1841
  expect(skillText.startsWith("---\n")).toBe(true);
1795
1842
  const hint = "<!-- Generated by nested.ts install --skill; do not edit. -->";
@@ -2,10 +2,10 @@
2
2
  This module generates Agent Skills content (SKILL.md + reference.md) from a CLI schema.
3
3
  */
4
4
 
5
- import { collectOptionDefs } from "../parse.ts";
5
+ import { generateApiGuideBody } from "../docs/api-guide.ts";
6
6
  import { cliSchemaJson } from "../schema.ts";
7
7
  import { collectMcpTools, sanitizeToolSegment } from "../mcp/tools.ts";
8
- import { CliProgram, CliOptionKind } from "../types.ts";
8
+ import { CliProgram } from "../types.ts";
9
9
 
10
10
  export type SkillTarget = "cursor" | "claude";
11
11
 
@@ -31,31 +31,10 @@ function skillDescription(root: CliProgram): string {
31
31
  return truncate(desc, 1024);
32
32
  }
33
33
 
34
- /** Formats one command line for the catalog section. */
35
- function formatCommandEntry(root: CliProgram, tool: ReturnType<typeof collectMcpTools>[number]): string {
36
- const cliPath = tool.path.length > 0 ? `${root.key} ${tool.path.join(" ")}` : root.key;
37
- let line = `- **\`${cliPath}\`** — ${tool.description}`;
38
- const opts = collectOptionDefs(root, tool.path);
39
- const flags = opts.filter((o) => o.kind === CliOptionKind.Presence).map((o) => `--${o.name}`);
40
- if (flags.length > 0) {
41
- line += ` (flags: ${flags.join(", ")})`;
42
- }
43
- const enums = opts.filter((o) => o.kind === CliOptionKind.Enum && o.choices?.length);
44
- for (const e of enums) {
45
- line += ` (\`--${e.name}\`: ${e.choices!.join(" | ")})`;
46
- }
47
- const varargs = (tool.leaf.positionals ?? []).filter((p) => (p.argMax ?? 1) === 0);
48
- if (varargs.length > 0) {
49
- line += ` (varargs: ${varargs.map((p) => p.name).join(", ")})`;
50
- }
51
- return line;
52
- }
53
-
54
34
  /** Builds SKILL.md body for the given target. */
55
35
  function buildSkillMd(root: CliProgram, target: SkillTarget, dirName: string): string {
56
36
  const name = sanitizeToolSegment(root.key);
57
37
  const description = skillDescription(root);
58
- const tools = collectMcpTools(root);
59
38
 
60
39
  const lines: string[] = [
61
40
  "---",
@@ -75,20 +54,12 @@ function buildSkillMd(root: CliProgram, target: SkillTarget, dirName: string): s
75
54
  "",
76
55
  "Invoke via shell:",
77
56
  "",
78
- ];
79
-
80
- lines.push("```bash", `${root.key} <subcommand> [options] [args]`, "```", "", "## Commands", "");
81
-
82
- if (tools.length === 0) {
83
- lines.push("(No leaf commands in schema.)", "");
84
- } else {
85
- for (const tool of tools) {
86
- lines.push(formatCommandEntry(root, tool));
87
- }
88
- lines.push("");
89
- }
90
-
91
- lines.push(
57
+ "```bash",
58
+ `${root.key} <subcommand> [options] [args]`,
59
+ "```",
60
+ "",
61
+ generateApiGuideBody(root).trimEnd(),
62
+ "",
92
63
  "## Pitfalls",
93
64
  "",
94
65
  "- Use `--` before tokens that look like flags when they are positional arguments.",
@@ -98,7 +69,7 @@ function buildSkillMd(root: CliProgram, target: SkillTarget, dirName: string): s
98
69
  "",
99
70
  "See `reference.md` in this skill directory for the full `docs schema` JSON export.",
100
71
  "",
101
- );
72
+ ];
102
73
 
103
74
  if (target === "cursor") {
104
75
  lines.push(