argsbarg 6.1.2 → 6.1.3

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 (229) hide show
  1. package/CHANGELOG.md +65 -1
  2. package/README.md +17 -19
  3. package/bin/argsbarg +10 -0
  4. package/docs/README.md +4 -3
  5. package/docs/ai-skills.md +4 -2
  6. package/docs/bundled-docs.md +50 -25
  7. package/docs/cli-program.md +52 -10
  8. package/docs/config-schema.md +10 -11
  9. package/docs/configure.md +2 -0
  10. package/docs/decisions.md +40 -0
  11. package/docs/developing.md +43 -5
  12. package/docs/http-server.md +171 -0
  13. package/docs/json-schema-subset.md +51 -0
  14. package/docs/mcp.md +4 -2
  15. package/docs/output-schema.md +55 -62
  16. package/examples/formats.ts +6 -6
  17. package/examples/full-example/Formula/full-example.rb +35 -0
  18. package/examples/full-example/README.md +20 -21
  19. package/examples/full-example/docs/README.md +1 -1
  20. package/examples/full-example/docs/cli-schema.json +1790 -98
  21. package/examples/full-example/docs/cli.md +1990 -0
  22. package/examples/full-example/docs/http.md +28 -29
  23. package/examples/full-example/docs/mcp.md +8 -22
  24. package/examples/full-example/docs/openapi.json +783 -50
  25. package/examples/full-example/docs/skill.md +10 -10
  26. package/examples/full-example/justfile +11 -1
  27. package/examples/full-example/src/commands/render-json/__generated__/RenderJsonInputSchema.json +15 -0
  28. package/examples/full-example/src/commands/render-json/__generated__/index.ts +5 -0
  29. package/examples/full-example/src/commands/render-json/command.test.ts +46 -0
  30. package/examples/full-example/src/commands/render-json/command.ts +30 -0
  31. package/examples/full-example/src/commands/render-json/types.ts +9 -0
  32. package/examples/full-example/src/commands/status/__generated__/StatusJsonOutputSchema.json +15 -0
  33. package/examples/full-example/src/commands/status/__generated__/index.ts +2 -2
  34. package/examples/full-example/src/commands/status/command.ts +5 -13
  35. package/examples/full-example/src/commands/status/types.ts +1 -14
  36. package/examples/full-example/src/commands/workspaces/__generated__/WorkspaceNameInputSchema.json +15 -0
  37. package/examples/full-example/src/commands/workspaces/__generated__/index.ts +5 -0
  38. package/examples/full-example/src/commands/workspaces/command.test.ts +58 -0
  39. package/examples/full-example/src/commands/workspaces/command.ts +94 -0
  40. package/examples/full-example/src/commands/workspaces/types.ts +6 -0
  41. package/examples/full-example/src/db/index.test.ts +86 -0
  42. package/examples/full-example/src/db/index.ts +101 -0
  43. package/examples/full-example/src/db/migrate.test.ts +35 -0
  44. package/examples/full-example/src/db/migrate.ts +69 -0
  45. package/examples/full-example/src/db/migrations/001_workspaces.sql +6 -0
  46. package/examples/full-example/src/db/tables/workspaces.ts +66 -0
  47. package/examples/full-example/src/program.ts +11 -36
  48. package/examples/full-example/src/types/argsbarg.d.ts +11 -0
  49. package/examples/full-example/src/types/md.d.ts +4 -0
  50. package/examples/full-example/tsconfig.json +5 -2
  51. package/examples/mcp-test.ts +1 -2
  52. package/examples/minimal.ts +1 -7
  53. package/examples/nested.ts +1 -2
  54. package/examples/option-required.ts +1 -1
  55. package/examples/servers.ts +4 -5
  56. package/index.d.ts +431 -136
  57. package/package.json +19 -2
  58. package/src/builtins/builtins.test.ts +7 -7
  59. package/src/builtins/completion-bash.ts +1 -1
  60. package/src/builtins/completion-fish.ts +1 -1
  61. package/src/builtins/completion-group.ts +4 -4
  62. package/src/builtins/completion-simulate-shared.ts +9 -0
  63. package/src/builtins/completion-zsh.ts +1 -1
  64. package/src/builtins/config.test.ts +3 -3
  65. package/src/builtins/config.ts +9 -9
  66. package/src/builtins/configure-copy.ts +2 -2
  67. package/src/builtins/configure.ts +4 -4
  68. package/src/builtins/dispatch.ts +19 -18
  69. package/src/builtins/export.ts +7 -5
  70. package/src/builtins/http.ts +68 -0
  71. package/src/builtins/mcp.ts +28 -4
  72. package/src/builtins/presentation.ts +6 -6
  73. package/src/builtins/registry.ts +6 -6
  74. package/src/builtins/scopes.ts +2 -2
  75. package/src/builtins/version.ts +1 -1
  76. package/src/cli-tool/full-example-capabilities.test.ts +10 -15
  77. package/src/cli-tool/main.ts +1 -1
  78. package/src/cli-tool/program.ts +3 -2
  79. package/src/cli-tool/prompt.ts +1 -1
  80. package/src/cli-tool/run-schemagen.ts +1 -3
  81. package/src/cli-tool/schemagen/cleanup.ts +6 -7
  82. package/src/cli-tool/schemagen/discover-schema-roots.ts +66 -120
  83. package/src/cli-tool/schemagen/index.ts +2 -2
  84. package/src/cli-tool/schemagen/names.ts +8 -13
  85. package/src/cli-tool/schemagen/run.ts +21 -28
  86. package/src/cli-tool/schemagen/schemagen.test.ts +136 -46
  87. package/src/config/bindings.test.ts +1 -1
  88. package/src/config/bindings.ts +1 -1
  89. package/src/config/bootstrap.test.ts +1 -1
  90. package/src/config/bootstrap.ts +36 -4
  91. package/src/config/context.test.ts +1 -1
  92. package/src/config/context.ts +1 -1
  93. package/src/config/entry.ts +1 -1
  94. package/src/config/file.test.ts +1 -1
  95. package/src/config/file.ts +3 -3
  96. package/src/config/manifest.ts +1 -1
  97. package/src/config/resolve.test.ts +1 -1
  98. package/src/config/resolve.ts +1 -1
  99. package/src/config/schema.ts +1 -1
  100. package/src/config/validate.ts +1 -1
  101. package/src/{install → configure/artifacts}/binary-placement.test.ts +1 -1
  102. package/src/{install → configure/artifacts}/binary-placement.ts +1 -1
  103. package/src/{install → configure/artifacts}/gh-release-update.ts +1 -1
  104. package/src/{install → configure/artifacts}/install-validate.test.ts +3 -3
  105. package/src/{install → configure/artifacts}/mcp-config.ts +1 -1
  106. package/src/{install → configure/artifacts}/mcp-opencode.test.ts +1 -1
  107. package/src/{install → configure/artifacts}/mcp-opencode.ts +1 -1
  108. package/src/{install → configure/artifacts}/paths.ts +5 -5
  109. package/src/configure/artifacts/plan.ts +24 -0
  110. package/src/{install → configure/artifacts}/status.test.ts +1 -1
  111. package/src/{install → configure/artifacts}/status.ts +2 -2
  112. package/src/{install → configure/artifacts}/target-base.ts +1 -1
  113. package/src/{install → configure/artifacts}/target-detect.ts +1 -1
  114. package/src/{install → configure/artifacts}/target-effective.ts +3 -9
  115. package/src/{install → configure/artifacts}/target-mcp-cli.ts +1 -1
  116. package/src/{install → configure/artifacts}/target-mcp-json.ts +1 -1
  117. package/src/{install → configure/artifacts}/target-plan-build.ts +2 -2
  118. package/src/{install → configure/artifacts}/target-registry.ts +2 -2
  119. package/src/{install → configure/artifacts}/target-scope.ts +3 -3
  120. package/src/{install → configure/artifacts}/target-skill.ts +1 -1
  121. package/src/{install → configure/artifacts}/target-types.ts +2 -2
  122. package/src/{install → configure/artifacts}/targets/app.ts +5 -5
  123. package/src/{install → configure/artifacts}/targets/chatgpt-mcp.ts +2 -2
  124. package/src/{install → configure/artifacts}/targets/claude-code-mcp.ts +2 -2
  125. package/src/{install → configure/artifacts}/targets/claude-desktop-mcp.ts +2 -2
  126. package/src/{install → configure/artifacts}/targets/claude-skill.ts +2 -2
  127. package/src/{install → configure/artifacts}/targets/codex-mcp.ts +2 -2
  128. package/src/{install → configure/artifacts}/targets/codex-skill.ts +2 -2
  129. package/src/{install → configure/artifacts}/targets/configure.ts +5 -5
  130. package/src/{install → configure/artifacts}/targets/cursor-mcp.ts +2 -2
  131. package/src/{install → configure/artifacts}/targets/cursor-skill.ts +2 -2
  132. package/src/{install → configure/artifacts}/targets/index.ts +1 -1
  133. package/src/{install → configure/artifacts}/targets/openclaw-mcp.ts +2 -2
  134. package/src/{install → configure/artifacts}/targets/openclaw-skill.ts +3 -3
  135. package/src/{install → configure/artifacts}/targets/opencode-mcp.ts +5 -5
  136. package/src/{install → configure/artifacts}/targets/opencode-skill.ts +3 -3
  137. package/src/{install → configure/artifacts}/targets.test.ts +1 -1
  138. package/src/{install → configure/artifacts}/uninstall.ts +1 -1
  139. package/src/configure/configure.test.ts +11 -11
  140. package/src/configure/index.ts +14 -14
  141. package/src/configure/prompt.ts +2 -2
  142. package/src/{context.ts → core/context.ts} +26 -20
  143. package/src/{json-leaf.test.ts → core/json-leaf.test.ts} +4 -4
  144. package/src/{leaf-inputs.test.ts → core/leaf-inputs.test.ts} +7 -7
  145. package/src/{leaf-inputs.ts → core/leaf-inputs.ts} +16 -12
  146. package/src/{parse.test.ts → core/parse.test.ts} +97 -109
  147. package/src/{parse.ts → core/parse.ts} +129 -31
  148. package/src/{schema.ts → core/schema.ts} +25 -13
  149. package/src/{types.ts → core/types.ts} +225 -35
  150. package/src/{validate.ts → core/validate.ts} +39 -29
  151. package/src/docs/builtin.ts +8 -19
  152. package/src/docs/{api-guide.test.ts → cli-guide.test.ts} +21 -21
  153. package/src/docs/{api-guide.ts → cli-guide.ts} +45 -16
  154. package/src/docs/docs.test.ts +76 -41
  155. package/src/docs/http-guide.ts +37 -34
  156. package/src/docs/mcp-guide.ts +12 -14
  157. package/src/docs/mcp-resources.test.ts +2 -3
  158. package/src/docs/mcp-resources.ts +6 -11
  159. package/src/docs/resolve.ts +22 -30
  160. package/src/docs/save.ts +3 -3
  161. package/src/exports/cli.ts +47 -0
  162. package/src/exports/headless.ts +13 -0
  163. package/src/exports/http.ts +6 -0
  164. package/src/exports/mcp.ts +6 -0
  165. package/src/{headless.test.ts → headless/routing.test.ts} +3 -3
  166. package/src/{headless.ts → headless/routing.ts} +3 -3
  167. package/src/headless/tool-call.ts +114 -46
  168. package/src/help.test.ts +152 -0
  169. package/src/help.ts +3 -3
  170. package/src/hooks/builtin.ts +20 -0
  171. package/src/hooks/run.ts +142 -0
  172. package/src/http/openapi.ts +182 -0
  173. package/src/http/readiness.ts +78 -0
  174. package/src/{api → http}/result.ts +16 -5
  175. package/src/http/routes.ts +329 -0
  176. package/src/http/server.ts +225 -0
  177. package/src/index.ts +36 -25
  178. package/src/log/ecs.test.ts +43 -0
  179. package/src/log/ecs.ts +59 -0
  180. package/src/log/emitter.ts +166 -0
  181. package/src/mcp/bundle.ts +2 -2
  182. package/src/mcp/claude.test.ts +1 -1
  183. package/src/mcp/claude.ts +4 -4
  184. package/src/{hidden-mcpb.test.ts → mcp/hidden-mcpb.test.ts} +10 -9
  185. package/src/mcp/result.ts +2 -2
  186. package/src/mcp/server.ts +54 -6
  187. package/src/mcp/tools.ts +9 -20
  188. package/src/{capabilities.ts → runtime/capabilities.ts} +11 -11
  189. package/src/{cli-errors.ts → runtime/cli-errors.ts} +4 -4
  190. package/src/{cli.ts → runtime/cli.ts} +159 -49
  191. package/src/runtime/exposure.ts +102 -0
  192. package/src/{invoke.test.ts → runtime/invoke.test.ts} +31 -7
  193. package/src/server/context.ts +25 -0
  194. package/src/server/overrides.ts +112 -0
  195. package/src/skill/generate.ts +8 -8
  196. package/src/skill/hint.ts +1 -1
  197. package/src/skill/install.ts +2 -2
  198. package/src/skill/naming.ts +1 -1
  199. package/src/{test-fixtures.ts → test/fixtures.ts} +3 -2
  200. package/src/{config.integration.test.ts → test/integration/config.test.ts} +8 -8
  201. package/src/{api.integration.test.ts → test/integration/http.test.ts} +170 -67
  202. package/src/{mcp.integration.test.ts → test/integration/mcp.test.ts} +11 -57
  203. package/docs/api-server.md +0 -141
  204. package/examples/full-example/docs/api.md +0 -511
  205. package/examples/full-example/src/commands/status/__generated__/outputSchema.json +0 -28
  206. package/examples/full-example/src/config/__generated__/configSchema.json +0 -40
  207. package/examples/full-example/src/config/__generated__/index.ts +0 -5
  208. package/examples/full-example/src/config/types.ts +0 -24
  209. package/src/api/openapi.ts +0 -117
  210. package/src/api/server.ts +0 -120
  211. package/src/builtins/api.ts +0 -38
  212. package/src/hidden.ts +0 -30
  213. package/src/install/plan.ts +0 -53
  214. /package/src/{install → configure/artifacts}/detect-installed.ts +0 -0
  215. /package/src/{install → configure/artifacts}/gh-release-update.test.ts +0 -0
  216. /package/src/{install → configure/artifacts}/mcp-codex.test.ts +0 -0
  217. /package/src/{install → configure/artifacts}/mcp-codex.ts +0 -0
  218. /package/src/{install → configure/artifacts}/mcp-openclaw.test.ts +0 -0
  219. /package/src/{install → configure/artifacts}/mcp-openclaw.ts +0 -0
  220. /package/src/{install → configure/artifacts}/normalize-uninstall.ts +0 -0
  221. /package/src/{install → configure/artifacts}/normalize.ts +0 -0
  222. /package/src/{install → configure/artifacts}/opts.ts +0 -0
  223. /package/src/{install → configure/artifacts}/shell.ts +0 -0
  224. /package/src/{formats.test.ts → core/formats.test.ts} +0 -0
  225. /package/src/{formats.ts → core/formats.ts} +0 -0
  226. /package/src/{respond.ts → core/respond.ts} +0 -0
  227. /package/src/{types.test.ts → core/types.test.ts} +0 -0
  228. /package/src/{api → http}/schema-deref.test.ts +0 -0
  229. /package/src/{api → http}/schema-deref.ts +0 -0
@@ -1,18 +1,18 @@
1
1
  /*
2
- Tests for docs/api-guide module behavior.
2
+ Tests for docs/cli-guide module behavior.
3
3
  */
4
4
 
5
5
  import { expect, test } from "bun:test";
6
- import { cliSchemaExport } from "../schema.ts";
7
- import type { CliProgram } from "../types.ts";
8
- import { CliOptionKind } from "../types.ts";
9
- import { generateApiGuide, generateApiGuideBody } from "./api-guide.ts";
6
+ import { cliSchemaExport } from "~/core/schema.ts";
7
+ import type { CliProgram } from "~/core/types.ts";
8
+ import { CliOptionKind } from "~/core/types.ts";
9
+ import { generateCliGuide, generateCliGuideBody } from "./cli-guide.ts";
10
10
 
11
11
  const nestedFixture: CliProgram = {
12
12
  key: "nested.ts",
13
13
  version: "1.0.0",
14
14
  description: "Nested groups demo.",
15
- docs: { enabled: true, topics: { readme: { text: "# readme\n" } } },
15
+ docs: { topics: { readme: { text: "# readme\n" } } },
16
16
  commands: [
17
17
  {
18
18
  key: "stat",
@@ -49,16 +49,16 @@ const nestedFixture: CliProgram = {
49
49
  ],
50
50
  };
51
51
 
52
- test("generateApiGuideBody matches command section of full API guide", () => {
53
- const body = generateApiGuideBody(nestedFixture);
54
- const full = generateApiGuide(nestedFixture);
52
+ test("generateCliGuideBody matches command section of full API guide", () => {
53
+ const body = generateCliGuideBody(nestedFixture);
54
+ const full = generateCliGuide(nestedFixture);
55
55
  expect(full).toContain(body.trimEnd());
56
56
  expect(body).toContain("## `nested.ts stat`");
57
57
  expect(body).not.toContain("CLI API reference");
58
58
  });
59
59
 
60
- test("generateApiGuide covers the same command keys as cliSchemaExport", () => {
61
- const md = generateApiGuide(nestedFixture);
60
+ test("generateCliGuide covers the same command keys as cliSchemaExport", () => {
61
+ const md = generateCliGuide(nestedFixture);
62
62
  const schema = cliSchemaExport(nestedFixture);
63
63
  expect(md).toContain("`nested.ts stat owner lookup`");
64
64
  expect(md).toContain("`--user-name` (`-u`)");
@@ -66,34 +66,34 @@ test("generateApiGuide covers the same command keys as cliSchemaExport", () => {
66
66
  expect(schema.commands?.map((c) => c.key)).toEqual(["stat"]);
67
67
  });
68
68
 
69
- test("generateApiGuide configure notes point to README not brew install", () => {
69
+ test("generateCliGuide configure notes point to README not brew install", () => {
70
70
  const fixture: CliProgram = {
71
71
  key: "myapp",
72
72
  version: "1.0.0",
73
73
  description: "Demo app.",
74
74
  commands: [{ key: "run", description: "Run.", handler: () => {} }],
75
75
  };
76
- const md = generateApiGuide(fixture);
76
+ const md = generateCliGuide(fixture);
77
77
  expect(md).not.toContain("{argsbarg:program}");
78
78
  expect(md).toContain("README");
79
79
  expect(md).not.toContain("brew install <tap>");
80
80
  expect(md).not.toContain("Upgrade to latest release");
81
81
  });
82
82
 
83
- test("generateApiGuide mentions Homebrew upgrade", () => {
83
+ test("generateCliGuide mentions Homebrew upgrade", () => {
84
84
  const fixture: CliProgram = {
85
85
  key: "myapp",
86
86
  version: "1.0.0",
87
87
  description: "Demo app.",
88
88
  commands: [{ key: "run", description: "Run.", handler: () => {} }],
89
89
  };
90
- const md = generateApiGuide(fixture);
90
+ const md = generateCliGuide(fixture);
91
91
  expect(md).toContain("brew upgrade");
92
92
  expect(md).not.toContain("install --update");
93
93
  });
94
94
 
95
- /** Tests that generateApiGuide resolves {argsbarg:program} in consumer notes. */
96
- test("generateApiGuide resolves {argsbarg:program} in consumer notes", () => {
95
+ /** Tests that generateCliGuide resolves {argsbarg:program} in consumer notes. */
96
+ test("generateCliGuide resolves {argsbarg:program} in consumer notes", () => {
97
97
  const fixture: CliProgram = {
98
98
  key: "myapp",
99
99
  version: "1.0.0",
@@ -107,12 +107,12 @@ test("generateApiGuide resolves {argsbarg:program} in consumer notes", () => {
107
107
  },
108
108
  ],
109
109
  };
110
- const md = generateApiGuide(fixture);
110
+ const md = generateCliGuide(fixture);
111
111
  expect(md).toContain("Invoke `myapp run`.");
112
112
  });
113
113
 
114
- /** Tests that generateApiGuide and cliSchemaExport include leaf outputSchema. */
115
- test("generateApiGuide and cliSchemaExport include leaf outputSchema", () => {
114
+ /** Tests that generateCliGuide and cliSchemaExport include leaf outputSchema. */
115
+ test("generateCliGuide and cliSchemaExport include leaf outputSchema", () => {
116
116
  const fixture: CliProgram = {
117
117
  key: "myapp",
118
118
  version: "1.0.0",
@@ -136,7 +136,7 @@ test("generateApiGuide and cliSchemaExport include leaf outputSchema", () => {
136
136
  properties: { id: { type: "string" } },
137
137
  required: ["id"],
138
138
  });
139
- const md = generateApiGuide(fixture);
139
+ const md = generateCliGuide(fixture);
140
140
  expect(md).toContain("#### Output");
141
141
  expect(md).toContain('"id"');
142
142
  expect(md).toContain('"type": "string"');
@@ -1,8 +1,14 @@
1
- import type { CliSchemaExport } from "../builtins/export.ts";
2
- import { cliPositionalLabel, cliResolveNotes } from "../help.ts";
3
- import { cliSchemaExport } from "../schema.ts";
4
- import type { CliOption, CliPositional, CliProgram } from "../types.ts";
5
- import { CliFallbackMode, CliOptionKind } from "../types.ts";
1
+ import type { CliSchemaExport } from "~/builtins/export.ts";
2
+ import { cliSchemaExport } from "~/core/schema.ts";
3
+ import type { CliOption, CliPositional, CliProgram } from "~/core/types.ts";
4
+ import { CliFallbackMode, CliOptionKind } from "~/core/types.ts";
5
+ import { cliPositionalLabel, cliResolveNotes } from "~/help.ts";
6
+
7
+ /** Options for {@link generateCliGuideBody} and {@link generateCliGuide}. */
8
+ export interface CliGuideBodyOptions {
9
+ /** Omit embedded outputSchema JSON; point to `docs cli-schema` instead (skill reference). */
10
+ compact?: boolean;
11
+ }
6
12
 
7
13
  /** CLI invocation path as a single string (`myapp stat owner lookup`). */
8
14
  function commandPath(rootKey: string, path: string[]): string {
@@ -66,7 +72,7 @@ function formatNotesBlockquote(notes: string, appKey: string): string {
66
72
  .join("\n");
67
73
  }
68
74
 
69
- /** Markdown section for leaf outputSchema (docs api / skill reference). */
75
+ /** Markdown section for leaf outputSchema (docs cli / skill reference). */
70
76
  function formatOutputSchemaSection(schema: Record<string, unknown>): string[] {
71
77
  return [
72
78
  "#### Output",
@@ -89,8 +95,19 @@ function fallbackLine(node: CliSchemaExport): string | null {
89
95
  return `**Default subcommand:** \`${node.fallbackCommand}\` (\`${mode}\`)`;
90
96
  }
91
97
 
98
+ /** Compact outputSchema pointer instead of inlined JSON. */
99
+ function formatOutputSchemaPointer(rootKey: string): string[] {
100
+ return ["#### Output", "", `See \`${rootKey} docs cli-schema\` for outputSchema when set.`, ""];
101
+ }
102
+
92
103
  /** Renders one command node and recurses into subcommands. */
93
- function renderCommandNode(rootKey: string, path: string[], node: CliSchemaExport, lines: string[]): void {
104
+ function renderCommandNode(
105
+ rootKey: string,
106
+ path: string[],
107
+ node: CliSchemaExport,
108
+ lines: string[],
109
+ opts: CliGuideBodyOptions,
110
+ ): void {
94
111
  const level = Math.min(path.length + 2, 6);
95
112
  const heading = "#".repeat(level);
96
113
  const cmd = commandPath(rootKey, path);
@@ -106,7 +123,15 @@ function renderCommandNode(rootKey: string, path: string[], node: CliSchemaExpor
106
123
  lines.push(fb, "");
107
124
  }
108
125
 
109
- if ((node.options ?? []).length > 0) {
126
+ const isJsonStyleLeaf =
127
+ opts.compact &&
128
+ (node.options ?? []).length === 0 &&
129
+ (node.positionals ?? []).length === 0 &&
130
+ !node.commands?.length;
131
+
132
+ if (isJsonStyleLeaf) {
133
+ lines.push(`Input shape: see \`${rootKey} docs cli-schema\`.`, "");
134
+ } else if ((node.options ?? []).length > 0) {
110
135
  lines.push("#### Options", "");
111
136
  lines.push("| Option | Type | Required | Format / default | Description |");
112
137
  lines.push("| --- | --- | --- | --- | --- |");
@@ -127,7 +152,11 @@ function renderCommandNode(rootKey: string, path: string[], node: CliSchemaExpor
127
152
  }
128
153
 
129
154
  if (node.outputSchema !== undefined) {
130
- lines.push(...formatOutputSchemaSection(node.outputSchema));
155
+ if (opts.compact) {
156
+ lines.push(...formatOutputSchemaPointer(rootKey));
157
+ } else {
158
+ lines.push(...formatOutputSchemaSection(node.outputSchema));
159
+ }
131
160
  }
132
161
 
133
162
  const children = node.commands ?? [];
@@ -140,20 +169,20 @@ function renderCommandNode(rootKey: string, path: string[], node: CliSchemaExpor
140
169
  }
141
170
 
142
171
  for (const child of children) {
143
- renderCommandNode(rootKey, [...path, child.key], child, lines);
172
+ renderCommandNode(rootKey, [...path, child.key], child, lines, opts);
144
173
  }
145
174
  }
146
175
 
147
- /** Command-tree markdown shared by `docs api` and generated agent skills (no API doc header). */
148
- export function generateApiGuideBody(program: CliProgram): string {
176
+ /** Command-tree markdown shared by `docs cli` and generated agent skills (no API doc header). */
177
+ export function generateCliGuideBody(program: CliProgram, opts: CliGuideBodyOptions = {}): string {
149
178
  const schema = cliSchemaExport(program);
150
179
  const lines: string[] = [];
151
- renderCommandNode(program.key, [], schema, lines);
180
+ renderCommandNode(program.key, [], schema, lines, opts);
152
181
  return `${lines.join("\n").trimEnd()}\n`;
153
182
  }
154
183
 
155
- /** Generates markdown API reference from the same export as `docs cli-schema`. */
156
- export function generateApiGuide(program: CliProgram): string {
184
+ /** Generates markdown CLI reference from the same export as `docs cli-schema`. */
185
+ export function generateCliGuide(program: CliProgram, opts: CliGuideBodyOptions = {}): string {
157
186
  const schema = cliSchemaExport(program);
158
187
  const lines: string[] = [
159
188
  `# ${program.key} — CLI API reference`,
@@ -168,6 +197,6 @@ export function generateApiGuide(program: CliProgram): string {
168
197
  lines.push(formatNotesBlockquote(schema.notes, program.key), "");
169
198
  }
170
199
 
171
- lines.push(generateApiGuideBody(program).trimEnd(), "");
200
+ lines.push(generateCliGuideBody(program, opts).trimEnd(), "");
172
201
  return `${lines.join("\n").trimEnd()}\n`;
173
202
  }
@@ -6,14 +6,15 @@ import { afterEach, beforeEach, expect, test } from "bun:test";
6
6
  import { mkdtempSync, readFileSync, rmSync } from "node:fs";
7
7
  import { tmpdir } from "node:os";
8
8
  import { join } from "node:path";
9
- import { completionBashScript } from "../builtins/index.ts";
10
- import { cliPresentationRoot } from "../builtins/presentation.ts";
11
- import { resolveCapabilities, skipsRequiredAppConfigExit } from "../capabilities.ts";
12
- import { Cli } from "../index.ts";
13
- import type { CliProgram } from "../types.ts";
14
- import { cliValidateProgram } from "../validate.ts";
9
+ import { completionBashScript } from "~/builtins";
10
+ import { cliPresentationRoot } from "~/builtins/presentation.ts";
11
+ import { ParseKind, parse } from "~/core/parse.ts";
12
+ import type { CliProgram } from "~/core/types.ts";
13
+ import { cliValidateProgram } from "~/core/validate.ts";
14
+ import { cliHelpRender } from "~/help.ts";
15
+ import { Cli } from "~/index";
16
+ import { resolveCapabilities, skipsRequiredAppConfigExit } from "~/runtime/capabilities.ts";
15
17
  import { generateMcpGuide } from "./mcp-guide.ts";
16
- import { docsEffectiveDefaultTopic } from "./resolve.ts";
17
18
  import { saveDocsTopic } from "./save.ts";
18
19
 
19
20
  let workDir: string;
@@ -37,7 +38,6 @@ function docsFixture(mcp = true): CliProgram {
37
38
  description: "Demo app.",
38
39
  mcpServer: mcp ? { enabled: true } : undefined,
39
40
  docs: {
40
- enabled: true,
41
41
  topics: {
42
42
  readme: { text: "# Hello README\n" },
43
43
  arch: { text: "# Architecture\n", description: "Contributor notes." },
@@ -72,16 +72,16 @@ test("docs reserved when enabled", () => {
72
72
  test("docs rejects reserved topic keys", () => {
73
73
  const root = docsFixture();
74
74
  const docs = root.docs;
75
- if (!docs) throw new Error("expected docs fixture");
75
+ if (!docs?.topics) throw new Error("expected docs fixture");
76
76
  docs.topics["cli-schema"] = { text: "nope" };
77
77
  expect(() => cliValidateProgram(root)).toThrow(/reserved/);
78
78
  delete docs.topics["cli-schema"];
79
79
  docs.topics.skill = { text: "nope" };
80
80
  expect(() => cliValidateProgram(root)).toThrow(/reserved/);
81
81
  delete docs.topics.skill;
82
- docs.topics.api = { text: "nope" };
82
+ docs.topics.cli = { text: "nope" };
83
83
  expect(() => cliValidateProgram(root)).toThrow(/reserved/);
84
- delete docs.topics.api;
84
+ delete docs.topics.cli;
85
85
  docs.topics.openapi = { text: "nope" };
86
86
  expect(() => cliValidateProgram(root)).toThrow(/reserved/);
87
87
  delete docs.topics.openapi;
@@ -89,14 +89,57 @@ test("docs rejects reserved topic keys", () => {
89
89
  expect(() => cliValidateProgram(root)).toThrow(/reserved/);
90
90
  });
91
91
 
92
- test("docsEffectiveDefaultTopic uses first topic key", () => {
93
- expect(docsEffectiveDefaultTopic(docsFixture().docs!)).toBe("readme");
92
+ test("docs enabled by default without docs block", () => {
93
+ const root: CliProgram = {
94
+ key: "myapp",
95
+ version: "1.0.0",
96
+ description: "Demo.",
97
+ commands: [{ key: "run", description: "Run.", handler: () => {} }],
98
+ };
99
+ expect(resolveCapabilities(root).docs).toBe(true);
100
+ cliValidateProgram(root);
94
101
  });
95
102
 
96
- test("bare docs prints first topic via Cli.invoke", async () => {
97
- const result = await new Cli(docsFixture()).invoke(["docs"]);
98
- expect(result.exitCode).toBe(0);
99
- expect(result.stdout).toContain("Hello README");
103
+ test("docs opt-out allows user command named docs", () => {
104
+ const root: CliProgram = {
105
+ key: "myapp",
106
+ version: "1.0.0",
107
+ description: "Demo.",
108
+ docs: { enabled: false },
109
+ commands: [
110
+ {
111
+ key: "docs",
112
+ description: "Custom docs.",
113
+ handler: () => {},
114
+ },
115
+ ],
116
+ };
117
+ expect(resolveCapabilities(root).docs).toBe(false);
118
+ cliValidateProgram(root);
119
+ });
120
+
121
+ test("built-in docs work without topics", async () => {
122
+ const root: CliProgram = {
123
+ key: "myapp",
124
+ version: "1.0.0",
125
+ description: "Demo.",
126
+ commands: [{ key: "run", description: "Run.", handler: () => {} }],
127
+ };
128
+ const cliRef = await new Cli(root).invoke(["docs", "cli"]);
129
+ expect(cliRef.exitCode).toBe(0);
130
+ expect(cliRef.stdout).toContain("CLI API reference");
131
+ const skill = await new Cli(root).invoke(["docs", "skill"]);
132
+ expect(skill.exitCode).toBe(0);
133
+ expect(skill.stdout).toContain("name: myapp");
134
+ });
135
+
136
+ test("bare docs shows router help", () => {
137
+ const root = cliPresentationRoot(docsFixture());
138
+ const pr = parse(root, ["docs"]);
139
+ expect(pr.kind).toBe(ParseKind.Help);
140
+ const help = cliHelpRender(root, pr.helpPath, false);
141
+ expect(help).toContain("cli");
142
+ expect(help).not.toContain("Hello README");
100
143
  });
101
144
 
102
145
  test("docs readme prints bundled text", async () => {
@@ -104,14 +147,6 @@ test("docs readme prints bundled text", async () => {
104
147
  expect(result.exitCode).toBe(0);
105
148
  expect(result.stdout).toContain("Hello README");
106
149
  });
107
-
108
- test("docs defaultTopic override", async () => {
109
- const root = docsFixture();
110
- root.docs!.defaultTopic = "arch";
111
- const result = await new Cli(root).invoke(["docs"]);
112
- expect(result.stdout).toContain("Architecture");
113
- });
114
-
115
150
  test("docs mcp when MCP enabled", async () => {
116
151
  const result = await new Cli(docsFixture(true)).invoke(["docs", "mcp"]);
117
152
  expect(result.exitCode).toBe(0);
@@ -140,7 +175,7 @@ test("docs mcp absent from router when MCP disabled", async () => {
140
175
 
141
176
  test("docs http when API enabled", async () => {
142
177
  const root = docsFixture(true);
143
- root.apiServer = { enabled: true };
178
+ root.httpServer = { enabled: true };
144
179
  cliValidateProgram(root);
145
180
  const result = await new Cli(root).invoke(["docs", "http"]);
146
181
  expect(result.exitCode).toBe(0);
@@ -163,7 +198,7 @@ test("docs http absent from router when API disabled", async () => {
163
198
 
164
199
  test("docs openapi when API enabled", async () => {
165
200
  const root = docsFixture(true);
166
- root.apiServer = { enabled: true };
201
+ root.httpServer = { enabled: true };
167
202
  cliValidateProgram(root);
168
203
  const result = await new Cli(root).invoke(["docs", "openapi"]);
169
204
  expect(result.exitCode).toBe(0);
@@ -193,8 +228,8 @@ test("docs cli-schema prints JSON", async () => {
193
228
  expect(schema.commands.some((c: { key: string }) => c.key === "run")).toBe(true);
194
229
  });
195
230
 
196
- test("docs api prints markdown reference", async () => {
197
- const result = await new Cli(docsFixture()).invoke(["docs", "api"]);
231
+ test("docs cli prints markdown reference", async () => {
232
+ const result = await new Cli(docsFixture()).invoke(["docs", "cli"]);
198
233
  expect(result.exitCode).toBe(0);
199
234
  expect(result.stdout).toContain("# myapp — CLI API reference");
200
235
  expect(result.stdout).toContain("## `myapp run`");
@@ -210,7 +245,7 @@ test("skipsRequiredAppConfigExit includes docs and config builtins", () => {
210
245
  },
211
246
  };
212
247
  const caps = resolveCapabilities(program);
213
- expect(skipsRequiredAppConfigExit(["docs", "api"], caps)).toBe(true);
248
+ expect(skipsRequiredAppConfigExit(["docs", "cli"], caps)).toBe(true);
214
249
  expect(skipsRequiredAppConfigExit(["configure", "get"], caps)).toBe(true);
215
250
  expect(skipsRequiredAppConfigExit(["run"], caps)).toBe(false);
216
251
  });
@@ -247,7 +282,7 @@ test("presentation includes docs cli-schema and skill", () => {
247
282
  expect(docsNode && "commands" in docsNode).toBe(true);
248
283
  if (docsNode && "commands" in docsNode) {
249
284
  expect(docsNode.commands.some((c) => c.key === "cli-schema")).toBe(true);
250
- expect(docsNode.commands.some((c) => c.key === "api")).toBe(true);
285
+ expect(docsNode.commands.some((c) => c.key === "cli")).toBe(true);
251
286
  expect(docsNode.commands.some((c) => c.key === "skill")).toBe(true);
252
287
  }
253
288
  });
@@ -257,7 +292,7 @@ test("completions offer docs subcommands", () => {
257
292
  expect(bash).toContain("docs) echo");
258
293
  expect(bash).toContain("readme) echo");
259
294
  expect(bash).toContain("cli-schema) echo");
260
- expect(bash).toContain("api) echo");
295
+ expect(bash).toContain("cli) echo");
261
296
  expect(bash).toContain("skill) echo");
262
297
  });
263
298
 
@@ -283,11 +318,11 @@ test("docs --save writes topic file", async () => {
283
318
  expect(text).not.toContain("Generated by");
284
319
  });
285
320
 
286
- test("docs api --save prepends generated hint", async () => {
287
- const result = await new Cli(docsFixture()).invoke(["docs", "api", "--save"]);
321
+ test("docs cli --save prepends generated hint", async () => {
322
+ const result = await new Cli(docsFixture()).invoke(["docs", "cli", "--save"]);
288
323
  expect(result.exitCode).toBe(0);
289
- const text = readFileSync(join(workDir, "docs/api.md"), "utf8");
290
- expect(text.startsWith("<!-- Generated by myapp docs api --save; do not edit. -->\n\n")).toBe(true);
324
+ const text = readFileSync(join(workDir, "docs/cli.md"), "utf8");
325
+ expect(text.startsWith("<!-- Generated by myapp docs cli --save; do not edit. -->\n\n")).toBe(true);
291
326
  expect(text).toContain("CLI API reference");
292
327
  });
293
328
 
@@ -313,7 +348,7 @@ test("docs cli-schema --save writes JSON file", async () => {
313
348
 
314
349
  test("docs openapi --save writes JSON file", async () => {
315
350
  const root = docsFixture(true);
316
- root.apiServer = { enabled: true };
351
+ root.httpServer = { enabled: true };
317
352
  const result = await new Cli(root).invoke(["docs", "openapi", "--save"]);
318
353
  expect(result.exitCode).toBe(0);
319
354
  expect(result.stdout.trim()).toBe("docs/openapi.json");
@@ -324,9 +359,9 @@ test("docs openapi --save writes JSON file", async () => {
324
359
  });
325
360
 
326
361
  test("saveDocsTopic returns relative path", () => {
327
- const path = saveDocsTopic(docsFixture(), "api");
328
- expect(path).toBe("docs/api.md");
329
- const text = readFileSync(join(workDir, "docs/api.md"), "utf8");
330
- expect(text).toContain("<!-- Generated by myapp docs api --save; do not edit. -->");
362
+ const path = saveDocsTopic(docsFixture(), "cli");
363
+ expect(path).toBe("docs/cli.md");
364
+ const text = readFileSync(join(workDir, "docs/cli.md"), "utf8");
365
+ expect(text).toContain("<!-- Generated by myapp docs cli --save; do not edit. -->");
331
366
  expect(text).toContain("CLI API reference");
332
367
  });
@@ -1,15 +1,15 @@
1
- import { resolveApiListenAddress } from "../api/server.ts";
2
- import { defaultConfigEntryTitle } from "../config/entry.ts";
3
- import { displayAppConfigPath } from "../config/file.ts";
4
- import { collectMcpTools, type McpToolDef } from "../mcp/tools.ts";
5
- import { collectOptionDefs } from "../parse.ts";
6
- import { CliOptionKind, type CliProgram } from "../types.ts";
1
+ import { defaultConfigEntryTitle } from "~/config/entry.ts";
2
+ import { displayAppConfigPath } from "~/config/file.ts";
3
+ import { collectOptionDefs } from "~/core/parse.ts";
4
+ import { CliOptionKind, type CliProgram } from "~/core/types.ts";
5
+ import { collectHttpRoutes } from "~/http/routes.ts";
6
+ import { resolveHttpListenAddress } from "~/http/server.ts";
7
7
 
8
- /** Formats one exposed tool for the auto-generated HTTP guide. */
9
- function formatToolLine(root: CliProgram, tool: McpToolDef): string {
10
- const cliPath = tool.path.length > 0 ? `${root.key} ${tool.path.join(" ")}` : root.key;
11
- let line = `- \`${tool.apiName}\` (MCP: \`${tool.name}\`, CLI: \`${cliPath}\`) — ${tool.description}`;
12
- const opts = collectOptionDefs(root, tool.path);
8
+ /** Formats one HTTP route for the auto-generated HTTP guide. */
9
+ function formatRouteLine(root: CliProgram, route: ReturnType<typeof collectHttpRoutes>[number]): string {
10
+ const cliPath = route.commandPath.join(" ");
11
+ let line = `- \`${route.method} ${route.openApiPath}\` (CLI: \`${root.key} ${cliPath}\`) — ${route.leaf.description}`;
12
+ const opts = collectOptionDefs(root, route.commandPath);
13
13
  const flags = opts.filter((o) => o.kind === CliOptionKind.Presence).map((o) => `--${o.name}`);
14
14
  if (flags.length > 0) {
15
15
  line += ` (flags: ${flags.join(", ")})`;
@@ -19,27 +19,27 @@ function formatToolLine(root: CliProgram, tool: McpToolDef): string {
19
19
 
20
20
  /** Generates the auto `docs http` markdown guide from schema and API config. */
21
21
  export function generateHttpGuide(root: CliProgram): string {
22
- const api = root.apiServer;
22
+ const api = root.httpServer;
23
23
  if (!api) {
24
24
  throw new Error("HTTP API server not enabled");
25
25
  }
26
26
 
27
- const tools = collectMcpTools(root);
28
- const { hostname, port } = resolveApiListenAddress(root);
27
+ const routes = collectHttpRoutes(root);
28
+ const { hostname, port } = resolveHttpListenAddress(root);
29
29
  const baseUrl = `http://${hostname}:${port}`;
30
30
 
31
31
  const lines: string[] = [
32
32
  `# HTTP API (${root.key})`,
33
33
  "",
34
- `${root.key} exposes the same callable tools over HTTP as MCP.`,
34
+ `${root.key} exposes user commands over HTTP REST routes derived from the CLI tree.`,
35
35
  "",
36
36
  "## Running",
37
37
  "",
38
38
  "```bash",
39
- `${root.key} api`,
39
+ `${root.key} http`,
40
40
  "```",
41
41
  "",
42
- `Listens on **${baseUrl}** by default (\`apiServer.host\` / \`apiServer.port\`).`,
42
+ `Listens on **${baseUrl}** by default (\`httpServer.host\` / \`httpServer.port\`).`,
43
43
  "",
44
44
  "Bind is localhost-only in v0 — use a reverse proxy for remote access.",
45
45
  "",
@@ -47,27 +47,30 @@ export function generateHttpGuide(root: CliProgram): string {
47
47
  "",
48
48
  "| Method | Path | Purpose |",
49
49
  "| --- | --- | --- |",
50
- "| `GET` | `/health` | Liveness check |",
51
- "| `GET` | `/openapi.json` | OpenAPI 3.1 document (tool paths and request shapes) |",
50
+ "| `GET` | `/health` or `/health/live` | Liveness check |",
51
+ "| `GET` | `/health/ready` | Readiness (config + `program.readiness`) |",
52
+ "| `GET` | `/openapi.json` | OpenAPI 3.1 REST paths |",
52
53
  "| `GET` | `/openapi-browser` | Interactive Scalar API reference |",
53
- "| `POST` | `/tools/:name` | Invoke with flat JSON args object in the body |",
54
+ "| `*` | `/api/...` | Invoke user commands (method per route) |",
54
55
  "| `OPTIONS` | `*` | CORS preflight |",
55
56
  "",
56
- "Replace `{tool-key}` below with a path segment from `openapi.json` (`paths` keys are `/tools/{tool-key}`). Match body keys to that tool's `requestBody` schema in the spec.",
57
+ "Discover paths from `openapi.json` (`/api/...`). Query binds options; POST/PUT/PATCH body binds options and `inputSchema` fields.",
57
58
  "",
58
59
  "## Examples",
59
60
  "",
60
61
  "```bash",
61
62
  `curl -s ${baseUrl}/health`,
63
+ `curl -s ${baseUrl}/health/ready`,
62
64
  `curl -s ${baseUrl}/openapi.json`,
63
- `curl -s -X POST ${baseUrl}/tools/{tool-key} \\`,
65
+ `curl -s ${baseUrl}/api/workspaces`,
66
+ `curl -s -X POST ${baseUrl}/api/workspaces \\`,
64
67
  ' -H "content-type: application/json" \\',
65
- " -d '{...}'",
68
+ ` -d '{"name":"qa2"}'`,
66
69
  "```",
67
70
  "",
68
71
  "## Responses",
69
72
  "",
70
- "Success (`200`): raw response body (JSON object, string, or binary). No `{ ok, stdout }` envelope.",
73
+ "Success: status from handler → `http.successStatus` → method default (GET 200, POST 201, DELETE 204).",
71
74
  "",
72
75
  "Handlers must use `ctx.respond()` or return a value for API/MCP tool calls.",
73
76
  "",
@@ -92,29 +95,29 @@ export function generateHttpGuide(root: CliProgram): string {
92
95
  lines.push("");
93
96
  }
94
97
 
95
- lines.push("## Exposed tools", "");
98
+ lines.push("## REST routes", "");
96
99
 
97
- if (tools.length === 0) {
98
- lines.push("(No tools exposed.)", "");
100
+ if (routes.length === 0) {
101
+ lines.push("(No routes exposed.)", "");
99
102
  } else {
100
- for (const tool of tools) {
101
- lines.push(formatToolLine(root, tool));
103
+ for (const route of routes) {
104
+ lines.push(formatRouteLine(root, route));
102
105
  }
103
106
  lines.push("");
104
107
  }
105
108
 
106
109
  lines.push(
107
- "## Tool arguments",
110
+ "## Request bodies",
108
111
  "",
109
- "POST bodies are a flat JSON object keyed by long option and positional names (hyphenated option names are valid keys).",
112
+ "POST/PUT/PATCH bodies are a flat JSON object keyed by long option and positional names (hyphenated option names are valid keys).",
110
113
  "",
111
- `For HTTP clients, use **\`GET /openapi.json\`** (or **\`GET /openapi-browser\`**) for per-tool request shapes — each \`POST /tools/{name}\` path has a \`requestBody\` schema.`,
114
+ `For HTTP clients, use **\`GET /openapi.json\`** (or **\`GET /openapi-browser\`**) for per-route request shapes.`,
112
115
  "",
113
116
  "Varargs positionals accept a JSON array of strings (not a comma-separated string).",
114
117
  "Options with `format: comma-list` accept a comma-separated string or JSON array.",
115
118
  "Options with a schema `default` are applied when omitted.",
116
119
  "",
117
- `Shell invocation reference: \`${root.key} docs api\`. Full CLI tree JSON: \`${root.key} docs cli-schema\`.`,
120
+ `Shell invocation reference: \`${root.key} docs cli\`. Full CLI tree JSON: \`${root.key} docs cli-schema\`.`,
118
121
  "",
119
122
  "## OpenAPI",
120
123
  "",
@@ -124,7 +127,7 @@ export function generateHttpGuide(root: CliProgram): string {
124
127
  `- **Fetch** — \`curl -s ${baseUrl}/openapi.json\``,
125
128
  `- **Save offline** — \`${root.key} docs openapi --save\` → \`./docs/openapi.json\` (or \`just docgen\` in app repos)`,
126
129
  "",
127
- "Use the spec to discover tool names (`paths`) and request/response shapes before calling `POST /tools/:name`.",
130
+ "Use the spec to discover REST paths and request/response shapes before calling `/api/...`.",
128
131
  "",
129
132
  );
130
133
 
@@ -1,12 +1,12 @@
1
- import { resolveCapabilities } from "../capabilities.ts";
2
- import { defaultConfigEntryTitle } from "../config/entry.ts";
3
- import { displayAppConfigPath } from "../config/file.ts";
4
- import { expectedOpenCodeMcpEntry, OPENCODE_CONFIG_SCHEMA } from "../install/mcp-opencode.ts";
5
- import { collectMcpTools, type McpToolDef, mcpServerId, resolveMcpSchemaUri } from "../mcp/tools.ts";
6
- import { collectOptionDefs } from "../parse.ts";
7
- import { CliOptionKind, type CliProgram } from "../types.ts";
1
+ import { defaultConfigEntryTitle } from "~/config/entry.ts";
2
+ import { displayAppConfigPath } from "~/config/file.ts";
3
+ import { expectedOpenCodeMcpEntry, OPENCODE_CONFIG_SCHEMA } from "~/configure/artifacts/mcp-opencode.ts";
4
+ import { collectOptionDefs } from "~/core/parse.ts";
5
+ import { CliOptionKind, type CliProgram } from "~/core/types.ts";
6
+ import { collectMcpTools, type McpToolDef, mcpServerId, resolveMcpSchemaUri } from "~/mcp/tools.ts";
7
+ import { resolveCapabilities } from "~/runtime/capabilities.ts";
8
8
  import { resolveDocsTopicResourceUri } from "./mcp-resources.ts";
9
- import { docsEnabled, docsUserTopicKeys } from "./resolve.ts";
9
+ import { docsEnabled, docsUserTopicKeys, resolveDocsConfig } from "./resolve.ts";
10
10
 
11
11
  /** Extra host notes for generated `docs mcp` (manual fallbacks and ChatGPT Connectors). */
12
12
  function appendManualHostSetup(lines: string[], root: CliProgram, serverId: string): void {
@@ -205,12 +205,10 @@ export function generateMcpGuide(root: CliProgram): string {
205
205
  `| Schema resource | \`${schemaUri}\` — same JSON as \`${root.key} docs cli-schema\` |`,
206
206
  );
207
207
  if (docsEnabled(root)) {
208
- const docs = root.docs;
209
- if (docs) {
210
- for (const key of docsUserTopicKeys(docs)) {
211
- const uri = resolveDocsTopicResourceUri(root, key);
212
- lines.push(`| Docs topic \`${key}\` | \`${uri}\` — same markdown as \`${root.key} docs ${key}\` |`);
213
- }
208
+ const docs = resolveDocsConfig(root);
209
+ for (const key of docsUserTopicKeys(docs)) {
210
+ const uri = resolveDocsTopicResourceUri(root, key);
211
+ lines.push(`| Docs topic \`${key}\` | \`${uri}\` — same markdown as \`${root.key} docs ${key}\` |`);
214
212
  }
215
213
  }
216
214
  lines.push("", "## Exposed tools", "");