argsbarg 3.3.2 → 3.3.4
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 +14 -3
- package/docs/bundled-docs.md +16 -2
- package/package.json +1 -1
- package/src/docs/builtin.ts +20 -4
- package/src/docs/docs.test.ts +72 -10
- package/src/docs/resolve.ts +12 -43
- package/src/docs/save.ts +66 -0
package/CHANGELOG.md
CHANGED
|
@@ -7,8 +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.
|
|
10
|
+
## [3.3.4] - 2026-06-21
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
## [3.3.3] - 2026-06-21
|
|
14
|
+
|
|
15
|
+
### Added
|
|
11
16
|
|
|
17
|
+
- **`docs --save`** — write one docs subcommand to `./docs/`; argsbarg-generated markdown (`mcp`, `api`, `skill`) is prefixed with a `Generated by … docs … --save` HTML comment.
|
|
18
|
+
|
|
19
|
+
### Removed
|
|
20
|
+
|
|
21
|
+
- **`docs all`** — use individual subcommands (`docs readme`, `docs schema`, `docs api`, …) or `--save` per topic.
|
|
12
22
|
|
|
13
23
|
## [3.3.2] - 2026-06-21
|
|
14
24
|
|
|
@@ -252,8 +262,9 @@ const cli = { ... } satisfies CliProgram; // or : CliProgram
|
|
|
252
262
|
- 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`).
|
|
253
263
|
- Imports: use `CliPositional` where needed; replace `CliOptionDef` with `CliOption` or `CliPositional` as appropriate.
|
|
254
264
|
|
|
255
|
-
[Unreleased]: https://github.com/bdombro/bun-argsbarg/compare/v3.3.
|
|
256
|
-
[3.3.
|
|
265
|
+
[Unreleased]: https://github.com/bdombro/bun-argsbarg/compare/v3.3.4...HEAD
|
|
266
|
+
[3.3.4]: https://github.com/bdombro/bun-argsbarg/releases/tag/v3.3.4
|
|
267
|
+
[3.3.3]: https://github.com/bdombro/bun-argsbarg/releases/tag/v3.3.3
|
|
257
268
|
[3.3.2]: https://github.com/bdombro/bun-argsbarg/releases/tag/v3.3.2
|
|
258
269
|
[3.3.1]: https://github.com/bdombro/bun-argsbarg/releases/tag/v3.3.1
|
|
259
270
|
[3.3.0]: https://github.com/bdombro/bun-argsbarg/releases/tag/v3.3.0
|
package/docs/bundled-docs.md
CHANGED
|
@@ -30,8 +30,9 @@ myapp docs architecture
|
|
|
30
30
|
myapp docs schema # full command tree as JSON
|
|
31
31
|
myapp docs api # command tree as markdown
|
|
32
32
|
myapp docs skill # generated Cursor SKILL.md
|
|
33
|
-
myapp docs all # all user topics; includes auto mcp when MCP enabled
|
|
34
33
|
myapp docs mcp # auto-generated when mcpServer.enabled
|
|
34
|
+
myapp docs readme --save # write ./docs/readme.md
|
|
35
|
+
myapp docs schema --save # write ./docs/schema.json
|
|
35
36
|
```
|
|
36
37
|
|
|
37
38
|
## Configuration
|
|
@@ -43,7 +44,7 @@ myapp docs mcp # auto-generated when mcpServer.enabled
|
|
|
43
44
|
| `defaultTopic` | first key in `topics` | `fallbackCommand` for bare `myapp docs` |
|
|
44
45
|
| `topics` | *(required)* | Topic key → `{ text, description? }` |
|
|
45
46
|
|
|
46
|
-
Reserved topic keys in `topics`: **`mcp`**, **`all`**, **`schema`**, **`api`**, **`skill`** (
|
|
47
|
+
Reserved topic keys in `topics`: **`mcp`**, **`all`**, **`schema`**, **`api`**, **`skill`** (reserved — use the matching `docs <name>` subcommand instead).
|
|
47
48
|
|
|
48
49
|
When `description` is omitted on a topic, ArgsBarg generates leaf help (`readme` → "Print README (user guide).").
|
|
49
50
|
|
|
@@ -89,3 +90,16 @@ All `docs` subcommands are hidden from MCP `tools/list` (`mcpTool: { enabled: fa
|
|
|
89
90
|
| `mcp` | Callable tools + schema resource |
|
|
90
91
|
|
|
91
92
|
Do not declare a top-level command named **`docs`** when `docs.enabled` is `true` — it is reserved.
|
|
93
|
+
|
|
94
|
+
## Save to disk (`--save`)
|
|
95
|
+
|
|
96
|
+
Pass **`--save`** on `docs` or any docs subcommand to write files under **`./docs/`** (relative to the current working directory). Each saved path is printed on stdout.
|
|
97
|
+
|
|
98
|
+
| Command | Output |
|
|
99
|
+
| --- | --- |
|
|
100
|
+
| `docs readme --save` | `./docs/readme.md` |
|
|
101
|
+
| `docs schema --save` | `./docs/schema.json` |
|
|
102
|
+
| `docs api --save` | `./docs/api.md` |
|
|
103
|
+
| `docs skill --save` | `./docs/skill.md` |
|
|
104
|
+
|
|
105
|
+
Argsbarg-generated markdown (`mcp`, `api`, `skill`) includes a `Generated by … docs … --save` HTML comment (`skill` places it after YAML frontmatter so parsers still work). Consumer topic files and `schema.json` are written as-is.
|
package/package.json
CHANGED
package/src/docs/builtin.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { CliFallbackMode, type CliLeaf, type CliProgram, type CliRouter } from "../types.ts";
|
|
1
|
+
import { CliFallbackMode, CliOptionKind, type CliLeaf, type CliOption, type CliProgram, type CliRouter } from "../types.ts";
|
|
2
2
|
import {
|
|
3
3
|
DOCS_ROUTER_DESCRIPTION,
|
|
4
4
|
docsEffectiveDefaultTopic,
|
|
@@ -8,14 +8,30 @@ import {
|
|
|
8
8
|
docsUserTopicKeys,
|
|
9
9
|
printDocsTopic,
|
|
10
10
|
} from "./resolve.ts";
|
|
11
|
+
import { saveDocsTopic } from "./save.ts";
|
|
12
|
+
|
|
13
|
+
const DOCS_SAVE_OPTION: CliOption = {
|
|
14
|
+
name: "save",
|
|
15
|
+
description: "Write documentation to ./docs/.",
|
|
16
|
+
kind: CliOptionKind.Presence,
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
function runDocsTopic(program: CliProgram, topic: string, ctx: { hasFlag(name: string): boolean }): void {
|
|
20
|
+
if (ctx.hasFlag("save")) {
|
|
21
|
+
process.stdout.write(`${saveDocsTopic(program, topic)}\n`);
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
printDocsTopic(program, topic);
|
|
25
|
+
}
|
|
11
26
|
|
|
12
27
|
function docsLeaf(program: CliProgram, key: string, description: string): CliLeaf {
|
|
13
28
|
return {
|
|
14
29
|
key,
|
|
15
30
|
description,
|
|
31
|
+
options: [DOCS_SAVE_OPTION],
|
|
16
32
|
mcpTool: { enabled: false },
|
|
17
|
-
handler: () => {
|
|
18
|
-
|
|
33
|
+
handler: (ctx) => {
|
|
34
|
+
runDocsTopic(program, key, ctx);
|
|
19
35
|
},
|
|
20
36
|
};
|
|
21
37
|
}
|
|
@@ -40,12 +56,12 @@ export function cliBuiltinDocsGroup(program: CliProgram): CliRouter {
|
|
|
40
56
|
docsLeaf(program, "schema", "Print the full command tree as JSON."),
|
|
41
57
|
docsLeaf(program, "api", "Print the command tree as markdown."),
|
|
42
58
|
docsLeaf(program, "skill", "Print generated Cursor SKILL.md content."),
|
|
43
|
-
docsLeaf(program, "all", "Print all bundled documentation combined."),
|
|
44
59
|
);
|
|
45
60
|
|
|
46
61
|
return {
|
|
47
62
|
key: "docs",
|
|
48
63
|
description: docs.description ?? DOCS_ROUTER_DESCRIPTION,
|
|
64
|
+
options: [DOCS_SAVE_OPTION],
|
|
49
65
|
fallbackCommand: docsEffectiveDefaultTopic(docs),
|
|
50
66
|
fallbackMode: CliFallbackMode.MissingOnly,
|
|
51
67
|
commands: leaves,
|
package/src/docs/docs.test.ts
CHANGED
|
@@ -1,11 +1,29 @@
|
|
|
1
|
-
import { expect, test } from "bun:test";
|
|
1
|
+
import { describe, expect, test, beforeEach, afterEach } from "bun:test";
|
|
2
|
+
import { mkdtempSync, readFileSync, rmSync } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { tmpdir } from "node:os";
|
|
2
5
|
import { cliPresentationRoot } from "../builtins/presentation.ts";
|
|
3
6
|
import { completionBashScript } from "../completion.ts";
|
|
4
7
|
import { cliInvoke } from "../index.ts";
|
|
5
8
|
import type { CliProgram } from "../types.ts";
|
|
6
9
|
import { cliValidateProgram } from "../validate.ts";
|
|
7
|
-
import {
|
|
10
|
+
import { docsEffectiveDefaultTopic } from "./resolve.ts";
|
|
8
11
|
import { generateMcpGuide } from "./mcp-guide.ts";
|
|
12
|
+
import { saveDocsTopic } from "./save.ts";
|
|
13
|
+
|
|
14
|
+
let workDir: string;
|
|
15
|
+
let prevCwd: string;
|
|
16
|
+
|
|
17
|
+
beforeEach(() => {
|
|
18
|
+
workDir = mkdtempSync(join(tmpdir(), "argsbarg-docs-save-"));
|
|
19
|
+
prevCwd = process.cwd();
|
|
20
|
+
process.chdir(workDir);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
afterEach(() => {
|
|
24
|
+
process.chdir(prevCwd);
|
|
25
|
+
rmSync(workDir, { recursive: true, force: true });
|
|
26
|
+
});
|
|
9
27
|
|
|
10
28
|
function docsFixture(mcp = true): CliProgram {
|
|
11
29
|
return {
|
|
@@ -86,6 +104,11 @@ test("docs mcp when MCP enabled", async () => {
|
|
|
86
104
|
expect(result.stdout).toContain("myapp mcp");
|
|
87
105
|
});
|
|
88
106
|
|
|
107
|
+
test("docs rejects unknown subcommand", async () => {
|
|
108
|
+
const result = await cliInvoke(docsFixture(), ["docs", "all"]);
|
|
109
|
+
expect(result.exitCode).not.toBe(0);
|
|
110
|
+
});
|
|
111
|
+
|
|
89
112
|
test("docs mcp absent from router when MCP disabled", async () => {
|
|
90
113
|
const root = docsFixture(false);
|
|
91
114
|
const presentation = cliPresentationRoot(root);
|
|
@@ -98,14 +121,6 @@ test("docs mcp absent from router when MCP disabled", async () => {
|
|
|
98
121
|
expect(result.exitCode).not.toBe(0);
|
|
99
122
|
});
|
|
100
123
|
|
|
101
|
-
test("docs all concatenates user topics and mcp", () => {
|
|
102
|
-
const program = docsFixture(true);
|
|
103
|
-
const combined = combineAllDocs(program);
|
|
104
|
-
expect(combined).toContain("Hello README");
|
|
105
|
-
expect(combined).toContain("Architecture");
|
|
106
|
-
expect(combined).toContain("MCP server (myapp)");
|
|
107
|
-
});
|
|
108
|
-
|
|
109
124
|
test("presentation includes docs subtree", () => {
|
|
110
125
|
const presentation = cliPresentationRoot(docsFixture());
|
|
111
126
|
const docsNode = presentation.commands.find((c) => c.key === "docs");
|
|
@@ -165,3 +180,50 @@ test("generateMcpGuide includes schema URI", () => {
|
|
|
165
180
|
const guide = generateMcpGuide(docsFixture(true));
|
|
166
181
|
expect(guide).toContain("myapp://schema");
|
|
167
182
|
});
|
|
183
|
+
|
|
184
|
+
test("docs --save writes topic file", async () => {
|
|
185
|
+
const result = await cliInvoke(docsFixture(), ["docs", "readme", "--save"]);
|
|
186
|
+
expect(result.exitCode).toBe(0);
|
|
187
|
+
expect(result.stdout.trim()).toBe("docs/readme.md");
|
|
188
|
+
const text = readFileSync(join(workDir, "docs/readme.md"), "utf8");
|
|
189
|
+
expect(text).toContain("Hello README");
|
|
190
|
+
expect(text).not.toContain("Generated by");
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
test("docs api --save prepends generated hint", async () => {
|
|
194
|
+
const result = await cliInvoke(docsFixture(), ["docs", "api", "--save"]);
|
|
195
|
+
expect(result.exitCode).toBe(0);
|
|
196
|
+
const text = readFileSync(join(workDir, "docs/api.md"), "utf8");
|
|
197
|
+
expect(text.startsWith("<!-- Generated by myapp docs api --save; do not edit. -->\n\n")).toBe(
|
|
198
|
+
true,
|
|
199
|
+
);
|
|
200
|
+
expect(text).toContain("CLI API reference");
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
test("docs skill --save keeps frontmatter first", async () => {
|
|
204
|
+
const result = await cliInvoke(docsFixture(), ["docs", "skill", "--save"]);
|
|
205
|
+
expect(result.exitCode).toBe(0);
|
|
206
|
+
const text = readFileSync(join(workDir, "docs/skill.md"), "utf8");
|
|
207
|
+
expect(text.startsWith("---\n")).toBe(true);
|
|
208
|
+
expect(text).toContain("name: myapp");
|
|
209
|
+
const hint = "<!-- Generated by myapp docs skill --save; do not edit. -->";
|
|
210
|
+
expect(text.indexOf(hint)).toBeGreaterThan(text.indexOf("---\n", 4));
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
test("docs schema --save writes JSON file", async () => {
|
|
214
|
+
const result = await cliInvoke(docsFixture(), ["docs", "schema", "--save"]);
|
|
215
|
+
expect(result.exitCode).toBe(0);
|
|
216
|
+
expect(result.stdout.trim()).toBe("docs/schema.json");
|
|
217
|
+
const text = readFileSync(join(workDir, "docs/schema.json"), "utf8");
|
|
218
|
+
expect(text).not.toContain("Generated by");
|
|
219
|
+
const schema = JSON.parse(text);
|
|
220
|
+
expect(schema.key).toBe("myapp");
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
test("saveDocsTopic returns relative path", () => {
|
|
224
|
+
const path = saveDocsTopic(docsFixture(), "api");
|
|
225
|
+
expect(path).toBe("docs/api.md");
|
|
226
|
+
const text = readFileSync(join(workDir, "docs/api.md"), "utf8");
|
|
227
|
+
expect(text).toContain("<!-- Generated by myapp docs api --save; do not edit. -->");
|
|
228
|
+
expect(text).toContain("CLI API reference");
|
|
229
|
+
});
|
package/src/docs/resolve.ts
CHANGED
|
@@ -51,16 +51,6 @@ export function docsTopicDescription(key: string, custom?: string): string {
|
|
|
51
51
|
return `Print ${label} documentation.`;
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
-
/** Ordered keys for `docs all` (user topics, then auto `mcp` when enabled). */
|
|
55
|
-
export function docsPrintOrder(program: CliProgram): string[] {
|
|
56
|
-
const docs = program.docs!;
|
|
57
|
-
const order = docsUserTopicKeys(docs);
|
|
58
|
-
if (docsIncludesMcpTopic(program)) {
|
|
59
|
-
order.push("mcp");
|
|
60
|
-
}
|
|
61
|
-
return order;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
54
|
/** Markdown body for one docs topic key. */
|
|
65
55
|
export function docsTopicText(program: CliProgram, topic: string): string {
|
|
66
56
|
const docs = program.docs!;
|
|
@@ -77,43 +67,22 @@ export function docsTopicText(program: CliProgram, topic: string): string {
|
|
|
77
67
|
return entry.text;
|
|
78
68
|
}
|
|
79
69
|
|
|
80
|
-
/**
|
|
81
|
-
export function
|
|
82
|
-
return docsPrintOrder(program)
|
|
83
|
-
.map((key) => docsTopicText(program, key).trim())
|
|
84
|
-
.join("\n\n---\n\n");
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
/** Writes CLI schema JSON to stdout (`docs schema`). */
|
|
88
|
-
export function printDocsSchema(program: CliProgram): void {
|
|
89
|
-
process.stdout.write(cliSchemaJson(program));
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
/** Writes markdown API reference to stdout (`docs api`). */
|
|
93
|
-
export function printDocsApi(program: CliProgram): void {
|
|
94
|
-
process.stdout.write(generateApiGuide(program));
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
/** Writes generated Cursor SKILL.md to stdout (`docs skill`). */
|
|
98
|
-
export function printDocsSkill(program: CliProgram): void {
|
|
99
|
-
const bundle = generateSkillBundle(program, "cursor");
|
|
100
|
-
process.stdout.write(`${bundle.skillMd}\n`);
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
/** Writes one docs topic (or `all`) to stdout. */
|
|
104
|
-
export function printDocsTopic(program: CliProgram, topic: string): void {
|
|
70
|
+
/** Full file body for a docs topic (stdout or `--save`). */
|
|
71
|
+
export function docsTopicContent(program: CliProgram, topic: string): string {
|
|
105
72
|
if (topic === "schema") {
|
|
106
|
-
|
|
107
|
-
return;
|
|
73
|
+
return cliSchemaJson(program);
|
|
108
74
|
}
|
|
109
75
|
if (topic === "api") {
|
|
110
|
-
|
|
111
|
-
return;
|
|
76
|
+
return generateApiGuide(program);
|
|
112
77
|
}
|
|
113
78
|
if (topic === "skill") {
|
|
114
|
-
|
|
115
|
-
return;
|
|
79
|
+
return `${generateSkillBundle(program, "cursor").skillMd}\n`;
|
|
116
80
|
}
|
|
117
|
-
const
|
|
118
|
-
|
|
81
|
+
const text = docsTopicText(program, topic);
|
|
82
|
+
return text.endsWith("\n") ? text : `${text}\n`;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** Writes one docs topic to stdout. */
|
|
86
|
+
export function printDocsTopic(program: CliProgram, topic: string): void {
|
|
87
|
+
process.stdout.write(docsTopicContent(program, topic));
|
|
119
88
|
}
|
package/src/docs/save.ts
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { mkdirSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import type { CliProgram } from "../types.ts";
|
|
4
|
+
import { docsTopicContent } from "./resolve.ts";
|
|
5
|
+
|
|
6
|
+
/** Relative output directory for `docs --save`. */
|
|
7
|
+
export const DOCS_SAVE_DIR = "docs";
|
|
8
|
+
|
|
9
|
+
/** Builtin docs topics generated by argsbarg (not consumer `docs.topics`). */
|
|
10
|
+
export const DOCS_GENERATED_SAVE_TOPICS = ["mcp", "api", "skill"] as const;
|
|
11
|
+
|
|
12
|
+
/** Whether `--save` should prepend a generated-file hint (argsbarg writers only). */
|
|
13
|
+
export function docsTopicIsGeneratedByArgsbarg(topic: string): boolean {
|
|
14
|
+
return (DOCS_GENERATED_SAVE_TOPICS as readonly string[]).includes(topic);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** HTML comment for generated markdown saved with `--save`. */
|
|
18
|
+
export function docsSaveGeneratedHint(program: CliProgram, topic: string): string {
|
|
19
|
+
return `<!-- Generated by ${program.key} docs ${topic} --save; do not edit. -->\n\n`;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const SKILL_FRONTMATTER_RE = /^---\r?\n[\s\S]*?\r?\n---\r?\n/;
|
|
23
|
+
|
|
24
|
+
/** Inserts save hint without breaking YAML frontmatter (`docs skill`). */
|
|
25
|
+
export function applySaveGeneratedHint(program: CliProgram, topic: string, content: string): string {
|
|
26
|
+
if (!docsTopicIsGeneratedByArgsbarg(topic)) {
|
|
27
|
+
return content;
|
|
28
|
+
}
|
|
29
|
+
const hint = docsSaveGeneratedHint(program, topic);
|
|
30
|
+
if (topic === "skill") {
|
|
31
|
+
const match = content.match(SKILL_FRONTMATTER_RE);
|
|
32
|
+
if (match) {
|
|
33
|
+
return `${match[0]}${hint}${content.slice(match[0].length)}`;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return `${hint}${content}`;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** File body for `--save` (hint on argsbarg-generated markdown only). */
|
|
40
|
+
export function docsTopicContentForSave(program: CliProgram, topic: string): string {
|
|
41
|
+
return applySaveGeneratedHint(program, topic, docsTopicContent(program, topic));
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Filename for a saved docs topic. */
|
|
45
|
+
export function docsSaveFilename(topic: string): string {
|
|
46
|
+
if (topic === "schema") {
|
|
47
|
+
return "schema.json";
|
|
48
|
+
}
|
|
49
|
+
return `${topic}.md`;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Relative path under cwd for a saved docs topic. */
|
|
53
|
+
export function docsSaveRelativePath(topic: string): string {
|
|
54
|
+
return join(DOCS_SAVE_DIR, docsSaveFilename(topic));
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Writes one docs topic under `./docs/`; returns relative path written. */
|
|
58
|
+
export function saveDocsTopic(program: CliProgram, topic: string): string {
|
|
59
|
+
const dir = join(process.cwd(), DOCS_SAVE_DIR);
|
|
60
|
+
mkdirSync(dir, { recursive: true });
|
|
61
|
+
|
|
62
|
+
const rel = docsSaveRelativePath(topic);
|
|
63
|
+
const abs = join(process.cwd(), rel);
|
|
64
|
+
writeFileSync(abs, docsTopicContentForSave(program, topic), "utf8");
|
|
65
|
+
return rel;
|
|
66
|
+
}
|