argsbarg 3.4.1 → 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 +26 -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 +63 -3
  6. package/index.d.ts +51 -51
  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 +3 -3
  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 +23 -12
  28. package/src/docs/mcp-guide.ts +112 -11
  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 +8 -10
  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 +35 -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 +57 -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 +51 -3
  49. package/src/install/plan.ts +96 -7
  50. package/src/install/shell.ts +1 -4
  51. package/src/install/status.ts +15 -7
  52. package/src/install/uninstall.ts +49 -5
  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 +38 -14
  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
@@ -1,12 +1,12 @@
1
1
  import { type CliCapabilities, resolveCapabilities } from "../capabilities.ts";
2
+ import { cliBuiltinDocsGroupIfEnabled } from "../docs/builtin.ts";
3
+ import { visibleOptions } from "../hidden.ts";
2
4
  import type { CliFallbackMode, CliNode, CliOption, CliPositional, CliProgram } from "../types.ts";
3
5
  import { isCliRouter } from "../types.ts";
4
- import { visibleOptions } from "../hidden.ts";
5
6
  import { cliBuiltinCompletionGroup } from "./completion-group.ts";
6
7
  import { cliBuiltinInstallCommand } from "./install.ts";
7
8
  import { cliBuiltinMcpCommand } from "./mcp.ts";
8
9
  import { cliBuiltinVersionCommand } from "./version.ts";
9
- import { cliBuiltinDocsGroupIfEnabled } from "../docs/builtin.ts";
10
10
 
11
11
  /** JSON-safe command node (no handlers). */
12
12
  export interface CliSchemaExport {
@@ -55,22 +55,31 @@ function exportBuiltinNode(cmd: CliNode): CliSchemaExport | null {
55
55
  return out;
56
56
  }
57
57
 
58
+ function pushExportedBuiltin(builtins: CliSchemaExport[], cmd: CliNode): void {
59
+ const node = exportBuiltinNode(cmd);
60
+ if (node) {
61
+ builtins.push(node);
62
+ }
63
+ }
64
+
58
65
  /** Built-in subtrees matching help visibility for `--schema` export. */
59
- export function exportPresentationBuiltins(program: CliProgram, caps?: CliCapabilities): CliSchemaExport[] {
66
+ export function exportPresentationBuiltins(
67
+ program: CliProgram,
68
+ caps?: CliCapabilities,
69
+ ): CliSchemaExport[] {
60
70
  const resolved = caps ?? resolveCapabilities(program);
61
- const builtins: CliSchemaExport[] = [
62
- exportBuiltinNode(cliBuiltinCompletionGroup(program))!,
63
- exportBuiltinNode(cliBuiltinVersionCommand())!,
64
- ];
71
+ const builtins: CliSchemaExport[] = [];
72
+ pushExportedBuiltin(builtins, cliBuiltinCompletionGroup(program));
73
+ pushExportedBuiltin(builtins, cliBuiltinVersionCommand());
65
74
  if (resolved.install) {
66
- builtins.push(exportBuiltinNode(cliBuiltinInstallCommand(program))!);
75
+ pushExportedBuiltin(builtins, cliBuiltinInstallCommand(program));
67
76
  }
68
77
  const docsGroup = cliBuiltinDocsGroupIfEnabled(program);
69
78
  if (docsGroup) {
70
- builtins.push(exportBuiltinNode(docsGroup)!);
79
+ pushExportedBuiltin(builtins, docsGroup);
71
80
  }
72
81
  if (resolved.mcp) {
73
- builtins.push(exportBuiltinNode(cliBuiltinMcpCommand(program))!);
82
+ pushExportedBuiltin(builtins, cliBuiltinMcpCommand(program));
74
83
  }
75
84
  return builtins;
76
85
  }
@@ -1,10 +1,15 @@
1
1
  export { completionBashScript } from "./completion-bash.ts";
2
- export { completionZshScript } from "./completion-zsh.ts";
3
2
  export { completionFishScript } from "./completion-fish.ts";
4
3
  export { cliBuiltinCompletionGroup } from "./completion-group.ts";
4
+ export { completionZshScript } from "./completion-zsh.ts";
5
+ export { builtinInterceptRoot, dispatchBuiltin } from "./dispatch.ts";
6
+ export { type CliSchemaExport, exportPresentationBuiltins } from "./export.ts";
5
7
  export { cliBuiltinInstallCommand, installBuiltinOptions } from "./install.ts";
6
8
  export { cliBuiltinMcpCommand } from "./mcp.ts";
7
- export { cliParseRoot, cliPresentationRoot, parseBuiltins, presentationBuiltins } from "./presentation.ts";
8
- export { exportPresentationBuiltins, type CliSchemaExport } from "./export.ts";
9
- export { dispatchBuiltin, builtinInterceptRoot } from "./dispatch.ts";
9
+ export {
10
+ cliParseRoot,
11
+ cliPresentationRoot,
12
+ parseBuiltins,
13
+ presentationBuiltins,
14
+ } from "./presentation.ts";
10
15
  export { collectScopes, type ScopeRec } from "./scopes.ts";
@@ -1,5 +1,5 @@
1
1
  import { resolveCapabilities } from "../capabilities.ts";
2
- import { CliProgram, CliOption, CliOptionKind, type CliLeaf } from "../types.ts";
2
+ import { type CliLeaf, type CliOption, CliOptionKind, type CliProgram } from "../types.ts";
3
3
 
4
4
  /** Install command options (dynamic: `--mcp` only when MCP is enabled). */
5
5
  export function installBuiltinOptions(root: CliProgram): CliOption[] {
@@ -42,12 +42,14 @@ export function installBuiltinOptions(root: CliProgram): CliOption[] {
42
42
  },
43
43
  {
44
44
  name: "uninstall",
45
- description: "Remove installed artifacts (use --all or scoped flags; skips targets not on disk).",
45
+ description:
46
+ "Remove installed artifacts (use --all or scoped flags; skips targets not on disk).",
46
47
  kind: CliOptionKind.Presence,
47
48
  },
48
49
  {
49
50
  name: "prefix",
50
- description: "Install directory for the binary (default ~/.local/bin; overrides INSTALL_PREFIX).",
51
+ description:
52
+ "Install directory for the binary (default ~/.local/bin; overrides INSTALL_PREFIX).",
51
53
  kind: CliOptionKind.String,
52
54
  },
53
55
  {
@@ -67,7 +69,8 @@ export function installBuiltinOptions(root: CliProgram): CliOption[] {
67
69
  },
68
70
  {
69
71
  name: "quiet",
70
- description: "Suppress informational output (requires --yes, --json, --reinstall, or --update).",
72
+ description:
73
+ "Suppress informational output (requires --yes, --json, --reinstall, or --update).",
71
74
  kind: CliOptionKind.Presence,
72
75
  },
73
76
  ];
@@ -103,11 +106,7 @@ export function cliBuiltinInstallCommand(root: CliProgram): CliLeaf {
103
106
  ` ${app} install --reinstall`,
104
107
  ];
105
108
  if (resolveCapabilities(root).update) {
106
- notesLines.push(
107
- "",
108
- "Upgrade to latest release:",
109
- ` ${app} install --update`,
110
- );
109
+ notesLines.push("", "Upgrade to latest release:", ` ${app} install --update`);
111
110
  }
112
111
  notesLines.push(
113
112
  "",
@@ -122,7 +121,8 @@ export function cliBuiltinInstallCommand(root: CliProgram): CliLeaf {
122
121
  );
123
122
  return {
124
123
  key: "install",
125
- description: "Install the binary, shell completions, agent skills, and MCP config to your user environment.",
124
+ description:
125
+ "Install the binary, shell completions, agent skills, and MCP config to your user environment.",
126
126
  notes: notesLines.join("\n"),
127
127
  options: installBuiltinOptions(root),
128
128
  handler: () => {},
@@ -1,12 +1,12 @@
1
1
  import { resolveCapabilities } from "../capabilities.ts";
2
2
  import { docsEnabled } from "../docs/resolve.ts";
3
- import { type CliLeaf, type CliProgram, type CliRouter, CliFallbackMode } from "../types.ts";
3
+ import { CliFallbackMode, type CliLeaf, type CliProgram, type CliRouter } from "../types.ts";
4
4
 
5
5
  /** Built-in `mcp` router: bare `myapp mcp` runs stdio (via hidden `serve` fallback); `mcp bundle` packs `.mcpb`. */
6
6
  export function cliBuiltinMcpCommand(program: CliProgram): CliRouter {
7
7
  const caps = resolveCapabilities(program);
8
8
  const lines = [
9
- "Stdio MCP server. Add to Cursor or Claude:",
9
+ "Stdio MCP server. Add to Cursor, Claude Code, or Claude Desktop:",
10
10
  "",
11
11
  " command: {argsbarg:program}",
12
12
  " args: mcp",
@@ -28,7 +28,7 @@ export function cliBuiltinMcpCommand(program: CliProgram): CliRouter {
28
28
 
29
29
  const bundle: CliLeaf = {
30
30
  key: "bundle",
31
- description: "Pack a Claude Desktop MCP Bundle (.mcpb) from dist/<key> (macOS-only v1).",
31
+ description: "Pack a Claude Desktop MCP Bundle (.mcpb) from dist/<key>.",
32
32
  handler: () => {},
33
33
  };
34
34
 
@@ -1,20 +1,17 @@
1
1
  import type { CliCapabilities } from "../capabilities.ts";
2
2
  import { resolveCapabilities } from "../capabilities.ts";
3
+ import { cliBuiltinDocsGroupIfEnabled } from "../docs/builtin.ts";
3
4
  import { presentationNode, visibleOptions } from "../hidden.ts";
4
5
  import type { CliLeaf, CliNode, CliProgram, CliRouter } from "../types.ts";
5
- import { isCliLeaf, isCliRouter } from "../types.ts";
6
+ import { isCliLeaf } from "../types.ts";
6
7
  import { cliBuiltinCompletionGroup } from "./completion-group.ts";
7
8
  import { cliBuiltinInstallCommand } from "./install.ts";
8
9
  import { cliBuiltinMcpCommand } from "./mcp.ts";
9
10
  import { cliBuiltinVersionCommand } from "./version.ts";
10
- import { cliBuiltinDocsGroupIfEnabled } from "../docs/builtin.ts";
11
11
 
12
12
  /** All built-in command nodes for argv parsing (includes hidden builtins). */
13
13
  export function parseBuiltins(program: CliProgram, caps: CliCapabilities): CliNode[] {
14
- const builtins: CliNode[] = [
15
- cliBuiltinCompletionGroup(program),
16
- cliBuiltinVersionCommand(),
17
- ];
14
+ const builtins: CliNode[] = [cliBuiltinCompletionGroup(program), cliBuiltinVersionCommand()];
18
15
  if (caps.install) {
19
16
  builtins.push(cliBuiltinInstallCommand(program));
20
17
  }
@@ -98,10 +95,13 @@ export function cliPresentationRoot(program: CliProgram): CliRouter {
98
95
  }
99
96
 
100
97
  /** Root help notes: consumer `program.notes` plus agent discovery when `docs` is enabled. */
101
- export function presentationRootNotes(program: CliProgram, caps: CliCapabilities): string | undefined {
98
+ export function presentationRootNotes(
99
+ program: CliProgram,
100
+ caps: CliCapabilities,
101
+ ): string | undefined {
102
102
  const parts: string[] = [];
103
103
  if ((program.notes ?? "").trim().length > 0) {
104
- parts.push(program.notes!.trim());
104
+ parts.push((program.notes ?? "").trim());
105
105
  }
106
106
  if (caps.docs) {
107
107
  parts.push(`For AI agents: \`${program.key} docs skill\`.`);
@@ -25,7 +25,7 @@ function walkScopes(cmdPath: string, cmd: CliNode, acc: ScopeRec[]): void {
25
25
  wantsFiles: hasPositionalArguments(cmd),
26
26
  });
27
27
  for (const ch of kids) {
28
- const nextPath = cmdPath === "" ? ch.key : cmdPath + "/" + ch.key;
28
+ const nextPath = cmdPath === "" ? ch.key : `${cmdPath}/${ch.key}`;
29
29
  walkScopes(nextPath, ch, acc);
30
30
  }
31
31
  }
@@ -1,4 +1,4 @@
1
- import { type CliLeaf } from "../types.ts";
1
+ import type { CliLeaf } from "../types.ts";
2
2
 
3
3
  /** Top-level `version` built-in (leaf). */
4
4
  export function cliBuiltinVersionCommand(): CliLeaf {
package/src/completion.ts CHANGED
@@ -3,11 +3,11 @@ Re-export shim — completion emitters and built-in trees live in ./builtins/.
3
3
  */
4
4
 
5
5
  export {
6
- completionBashScript,
7
- completionZshScript,
8
- completionFishScript,
9
- cliPresentationRoot,
10
6
  cliBuiltinCompletionGroup,
11
7
  cliBuiltinInstallCommand,
12
8
  cliBuiltinMcpCommand,
9
+ cliPresentationRoot,
10
+ completionBashScript,
11
+ completionFishScript,
12
+ completionZshScript,
13
13
  } from "./builtins/index.ts";
@@ -1,8 +1,8 @@
1
1
  import { expect, test } from "bun:test";
2
+ import { cliSchemaExport } from "../schema.ts";
2
3
  import type { CliProgram } from "../types.ts";
3
4
  import { CliOptionKind } from "../types.ts";
4
5
  import { generateApiGuide, generateApiGuideBody } from "./api-guide.ts";
5
- import { cliSchemaExport } from "../schema.ts";
6
6
 
7
7
  const nestedFixture: CliProgram = {
8
8
  key: "nested.ts",
@@ -125,7 +125,7 @@ test("generateApiGuide and cliSchemaExport include leaf outputSchema", () => {
125
125
  ],
126
126
  };
127
127
  const schema = cliSchemaExport(fixture);
128
- expect(schema.commands![0]!.outputSchema).toEqual({
128
+ expect(schema.commands?.[0]?.outputSchema).toEqual({
129
129
  type: "object",
130
130
  properties: { id: { type: "string" } },
131
131
  required: ["id"],
@@ -101,7 +101,7 @@ function renderCommandNode(
101
101
  lines.push("#### Options", "");
102
102
  lines.push("| Option | Type | Required | Description |");
103
103
  lines.push("| --- | --- | --- | --- |");
104
- for (const opt of node.options!) {
104
+ for (const opt of node.options ?? []) {
105
105
  lines.push(formatOptionRow(opt));
106
106
  }
107
107
  lines.push("");
@@ -111,7 +111,7 @@ function renderCommandNode(
111
111
  lines.push("#### Positionals", "");
112
112
  lines.push("| Argument | Type | Required | Description |");
113
113
  lines.push("| --- | --- | --- | --- |");
114
- for (const p of node.positionals!) {
114
+ for (const p of node.positionals ?? []) {
115
115
  lines.push(formatPositionalRow(p));
116
116
  }
117
117
  lines.push("");
@@ -1,4 +1,11 @@
1
- import { CliFallbackMode, CliOptionKind, type CliLeaf, type CliOption, type CliProgram, type CliRouter } from "../types.ts";
1
+ import {
2
+ CliFallbackMode,
3
+ type CliLeaf,
4
+ type CliOption,
5
+ CliOptionKind,
6
+ type CliProgram,
7
+ type CliRouter,
8
+ } from "../types.ts";
2
9
  import {
3
10
  DOCS_ROUTER_DESCRIPTION,
4
11
  docsEffectiveDefaultTopic,
@@ -16,7 +23,11 @@ const DOCS_SAVE_OPTION: CliOption = {
16
23
  kind: CliOptionKind.Presence,
17
24
  };
18
25
 
19
- function runDocsTopic(program: CliProgram, topic: string, ctx: { hasFlag(name: string): boolean }): void {
26
+ function runDocsTopic(
27
+ program: CliProgram,
28
+ topic: string,
29
+ ctx: { hasFlag(name: string): boolean },
30
+ ): void {
20
31
  if (ctx.hasFlag("save")) {
21
32
  process.stdout.write(`${saveDocsTopic(program, topic)}\n`);
22
33
  return;
@@ -43,24 +54,32 @@ function docsRouterNotes(): string {
43
54
 
44
55
  /** Built-in `docs` router with bundled topic subcommands. */
45
56
  export function cliBuiltinDocsGroup(program: CliProgram): CliRouter {
46
- const docs = program.docs!;
57
+ const docs = program.docs;
58
+ if (!docs) {
59
+ throw new Error("docs not enabled");
60
+ }
47
61
  const leaves: CliLeaf[] = [];
48
62
 
49
63
  for (const key of docsUserTopicKeys(docs)) {
50
- const topic = docs.topics[key]!;
64
+ const topic = docs.topics[key];
65
+ if (!topic) {
66
+ throw new Error(`docs topic missing: ${key}`);
67
+ }
51
68
  leaves.push(docsLeaf(program, key, docsTopicDescription(key, topic.description)));
52
69
  }
53
70
 
54
71
  if (docsIncludesMcpTopic(program)) {
55
- leaves.push(
56
- docsLeaf(program, "mcp", "Print MCP server setup and tool guidance."),
57
- );
72
+ leaves.push(docsLeaf(program, "mcp", "Print MCP server setup and tool guidance."));
58
73
  }
59
74
 
60
75
  leaves.push(
61
76
  docsLeaf(program, "schema", "Print the full command tree as JSON."),
62
77
  docsLeaf(program, "api", "Print the full command reference as markdown."),
63
- docsLeaf(program, "skill", "Print a reference agent SKILL, use `install --skill` for optimized."),
78
+ docsLeaf(
79
+ program,
80
+ "skill",
81
+ "Print a reference agent SKILL, use `install --skill` for optimized.",
82
+ ),
64
83
  );
65
84
 
66
85
  return {
@@ -1,14 +1,14 @@
1
- import { describe, expect, test, beforeEach, afterEach } from "bun:test";
1
+ import { afterEach, beforeEach, expect, test } from "bun:test";
2
2
  import { mkdtempSync, readFileSync, rmSync } from "node:fs";
3
- import { join } from "node:path";
4
3
  import { tmpdir } from "node:os";
4
+ import { join } from "node:path";
5
5
  import { cliPresentationRoot } from "../builtins/presentation.ts";
6
6
  import { completionBashScript } from "../completion.ts";
7
7
  import { cliInvoke } from "../index.ts";
8
8
  import type { CliProgram } from "../types.ts";
9
9
  import { cliValidateProgram } from "../validate.ts";
10
- import { docsEffectiveDefaultTopic } from "./resolve.ts";
11
10
  import { generateMcpGuide } from "./mcp-guide.ts";
11
+ import { docsEffectiveDefaultTopic } from "./resolve.ts";
12
12
  import { saveDocsTopic } from "./save.ts";
13
13
 
14
14
  let workDir: string;
@@ -64,13 +64,15 @@ test("docs reserved when enabled", () => {
64
64
 
65
65
  test("docs rejects reserved topic keys", () => {
66
66
  const root = docsFixture();
67
- root.docs!.topics.schema = { text: "nope" };
67
+ const docs = root.docs;
68
+ if (!docs) throw new Error("expected docs fixture");
69
+ docs.topics.schema = { text: "nope" };
68
70
  expect(() => cliValidateProgram(root)).toThrow(/reserved/);
69
- delete root.docs!.topics.schema;
70
- root.docs!.topics.skill = { text: "nope" };
71
+ delete docs.topics.schema;
72
+ docs.topics.skill = { text: "nope" };
71
73
  expect(() => cliValidateProgram(root)).toThrow(/reserved/);
72
- delete root.docs!.topics.skill;
73
- root.docs!.topics.api = { text: "nope" };
74
+ delete docs.topics.skill;
75
+ docs.topics.api = { text: "nope" };
74
76
  expect(() => cliValidateProgram(root)).toThrow(/reserved/);
75
77
  });
76
78
 
@@ -102,6 +104,8 @@ test("docs mcp when MCP enabled", async () => {
102
104
  expect(result.exitCode).toBe(0);
103
105
  expect(result.stdout).toContain("MCP server (myapp)");
104
106
  expect(result.stdout).toContain("myapp mcp");
107
+ expect(result.stdout).toContain("claude_desktop_config.json");
108
+ expect(result.stdout).toContain("install --mcp --yes");
105
109
  });
106
110
 
107
111
  test("docs rejects unknown subcommand", async () => {
@@ -125,9 +129,9 @@ test("presentation includes docs subtree", () => {
125
129
  const presentation = cliPresentationRoot(docsFixture());
126
130
  const docsNode = presentation.commands.find((c) => c.key === "docs");
127
131
  expect(docsNode).toBeDefined();
128
- expect(docsNode && "commands" in docsNode && docsNode.commands.some((c) => c.key === "readme")).toBe(
129
- true,
130
- );
132
+ expect(
133
+ docsNode && "commands" in docsNode && docsNode.commands.some((c) => c.key === "readme"),
134
+ ).toBe(true);
131
135
  });
132
136
 
133
137
  test("docs schema prints JSON", async () => {
@@ -192,9 +196,16 @@ test("completions offer docs subcommands", () => {
192
196
  expect(bash).toContain("skill) echo");
193
197
  });
194
198
 
195
- test("generateMcpGuide includes schema URI", () => {
199
+ test("generateMcpGuide includes schema URI and install targets", () => {
196
200
  const guide = generateMcpGuide(docsFixture(true));
197
201
  expect(guide).toContain("myapp://schema");
202
+ expect(guide).toContain("~/.cursor/mcp.json");
203
+ expect(guide).toContain("claude_desktop_config.json");
204
+ expect(guide).toContain("## Installation");
205
+ expect(guide).toContain("## Running directly");
206
+ expect(guide).toContain("install --bin");
207
+ expect(guide).toContain("OpenAI Codex");
208
+ expect(guide).toContain("ChatGPT");
198
209
  });
199
210
 
200
211
  test("docs --save writes topic file", async () => {
@@ -1,11 +1,75 @@
1
- import { collectOptionDefs } from "../parse.ts";
1
+ import { resolveCapabilities } from "../capabilities.ts";
2
+ import { expectedOpenCodeMcpEntry, OPENCODE_CONFIG_SCHEMA } from "../install/mcp-opencode.ts";
2
3
  import {
3
4
  collectMcpTools,
5
+ type McpToolDef,
4
6
  mcpServerId,
5
7
  resolveMcpSchemaUri,
6
- type McpToolDef,
7
8
  } from "../mcp/tools.ts";
8
- import { type CliProgram, CliOptionKind } from "../types.ts";
9
+ import { collectOptionDefs } from "../parse.ts";
10
+ import { CliOptionKind, type CliProgram } from "../types.ts";
11
+
12
+ /** Extra host notes for generated `docs mcp` (manual fallbacks and ChatGPT Connectors). */
13
+ function appendManualHostSetup(lines: string[], root: CliProgram, serverId: string): void {
14
+ const openCodeEntry = expectedOpenCodeMcpEntry(root);
15
+
16
+ lines.push(
17
+ "| OpenCode | `~/.config/opencode/*` (when `~/.config/opencode` exists) |",
18
+ "| OpenAI Codex | `~/.codex/config.toml` via `codex mcp add` (when `codex` is on PATH) |",
19
+ "| ChatGPT desktop | `chatgpt_mcp_config.json` (when ChatGPT app data exists) |",
20
+ "",
21
+ "Claude Desktop paths by platform:",
22
+ "",
23
+ "- **macOS:** `~/Library/Application Support/Claude/claude_desktop_config.json`",
24
+ "- **Windows:** `%APPDATA%\\Claude\\claude_desktop_config.json`",
25
+ "- **Linux:** `~/.config/Claude/claude_desktop_config.json`",
26
+ "",
27
+ "ChatGPT desktop JSON (when auto-installed):",
28
+ "",
29
+ "- **macOS:** `~/Library/Application Support/ChatGPT/chatgpt_mcp_config.json`",
30
+ "- **Windows:** `%APPDATA%\\OpenAI\\ChatGPT\\chatgpt_mcp_config.json`",
31
+ "",
32
+ "Restart Claude Desktop and ChatGPT desktop after changing their config files.",
33
+ "",
34
+ "### Manual fallbacks",
35
+ "",
36
+ "**OpenCode** (no `~/.config/opencode` yet):",
37
+ "",
38
+ "```json",
39
+ JSON.stringify(
40
+ {
41
+ $schema: OPENCODE_CONFIG_SCHEMA,
42
+ mcp: { [serverId]: openCodeEntry },
43
+ },
44
+ null,
45
+ 2,
46
+ ),
47
+ "```",
48
+ "",
49
+ "**Codex** (`codex` not on PATH):",
50
+ "",
51
+ "```toml",
52
+ `[mcp_servers.${serverId}]`,
53
+ `command = "${root.key}"`,
54
+ 'args = ["mcp"]',
55
+ "```",
56
+ "",
57
+ `Or after installing Codex CLI: \`codex mcp add ${serverId} -- ${root.key} mcp\`.`,
58
+ "",
59
+ "### ChatGPT web (Connectors)",
60
+ "",
61
+ `OpenAI's documented path for **ChatGPT web/desktop** is **Settings → Connectors → Developer mode** with a **remote HTTPS MCP URL** — not local stdio. ChatGPT does not spawn \`${root.key} mcp\` directly.`,
62
+ "",
63
+ "For local stdio, bridge and tunnel, then register the HTTPS URL in Connectors:",
64
+ "",
65
+ `1. Expose \`${root.key} mcp\` over HTTP (e.g. \`mcp-remote\`).`,
66
+ "2. Tunnel if needed (ngrok, Cloudflare Tunnel).",
67
+ "3. Add the public URL as a custom connector.",
68
+ "",
69
+ "Desktop `chatgpt_mcp_config.json` is merged when the ChatGPT app is installed; support varies by build. Use Connectors when local JSON is absent or tools do not appear.",
70
+ "",
71
+ );
72
+ }
9
73
 
10
74
  /** Formats one exposed MCP tool for the auto-generated MCP guide. */
11
75
  function formatToolLine(root: CliProgram, tool: McpToolDef): string {
@@ -24,20 +88,55 @@ export function generateMcpGuide(root: CliProgram): string {
24
88
  const tools = collectMcpTools(root);
25
89
  const schemaUri = resolveMcpSchemaUri(root);
26
90
  const serverId = mcpServerId(root);
27
- const mcp = root.mcpServer!;
91
+ const mcp = root.mcpServer;
92
+ if (!mcp) {
93
+ throw new Error("MCP server not enabled");
94
+ }
95
+ const caps = resolveCapabilities(root);
28
96
 
29
97
  const lines: string[] = [
30
98
  `# MCP server (${root.key})`,
31
99
  "",
32
100
  `${root.key} exposes an MCP server with features similar to the CLI.`,
33
101
  "",
34
- "## Quick start",
102
+ "## Installation",
103
+ "",
104
+ "### `install --mcp`",
35
105
  "",
106
+ ];
107
+
108
+ if (caps.install) {
109
+ lines.push(
110
+ `Install the CLI first so \`${root.key}\` is on your PATH (e.g. \`${root.key} install --bin --yes\` or \`install --all --yes\`). Host configs reference the binary by name.`,
111
+ "",
112
+ );
113
+ } else {
114
+ lines.push(
115
+ `The CLI binary \`${root.key}\` must already be on your PATH. Host configs reference it by name.`,
116
+ "",
117
+ );
118
+ }
119
+
120
+ lines.push(
36
121
  "```bash",
37
- `${root.key} mcp`,
122
+ `${root.key} install --mcp --yes`,
38
123
  "```",
39
124
  "",
40
- "Cursor / Claude `mcp.json` entry:",
125
+ "Merges the server entry below into host config when each host is present:",
126
+ "",
127
+ "| Host | Config file |",
128
+ "| --- | --- |",
129
+ "| Cursor | `~/.cursor/mcp.json` (when `~/.cursor` exists) |",
130
+ "| Claude Code | `~/.claude.json` |",
131
+ "| Claude Desktop | `claude_desktop_config.json` (when Claude Desktop app data exists) |",
132
+ );
133
+
134
+ appendManualHostSetup(lines, root, serverId);
135
+
136
+ lines.push(
137
+ "### Manual `mcpServers` entry",
138
+ "",
139
+ "For Cursor, Claude, and ChatGPT desktop JSON configs, add under `mcpServers`:",
41
140
  "",
42
141
  "```json",
43
142
  JSON.stringify(
@@ -54,13 +153,15 @@ export function generateMcpGuide(root: CliProgram): string {
54
153
  ),
55
154
  "```",
56
155
  "",
57
- "Or run:",
156
+ "## Running directly",
157
+ "",
158
+ "Start the stdio MCP server without editing host config:",
58
159
  "",
59
160
  "```bash",
60
- `${root.key} install --mcp --yes`,
161
+ `${root.key} mcp`,
61
162
  "```",
62
163
  "",
63
- ];
164
+ );
64
165
 
65
166
  if (mcp.shellEnv || mcp.envFile) {
66
167
  lines.push("## Environment", "");
@@ -71,7 +172,7 @@ export function generateMcpGuide(root: CliProgram): string {
71
172
  }
72
173
  if (mcp.envFile) {
73
174
  lines.push(
74
- "- **`envFile`** — loads `" + mcp.envFile + "` after shell env (overrides for its keys).",
175
+ `- **\`envFile\`** — loads \`${mcp.envFile}\` after shell env (overrides for its keys).`,
75
176
  );
76
177
  }
77
178
  lines.push("");
@@ -1,6 +1,6 @@
1
- import type { CliDocsConfig, CliProgram } from "../types.ts";
2
1
  import { cliSchemaJson } from "../schema.ts";
3
2
  import { generateSkillBundle } from "../skill/generate.ts";
3
+ import type { CliDocsConfig, CliProgram } from "../types.ts";
4
4
  import { generateApiGuide } from "./api-guide.ts";
5
5
  import { generateMcpGuide } from "./mcp-guide.ts";
6
6
 
@@ -31,7 +31,11 @@ export function docsEffectiveDefaultTopic(docs: CliDocsConfig): string {
31
31
  if (keys.length === 0) {
32
32
  throw new Error("docs.topics must be non-empty");
33
33
  }
34
- return keys[0]!;
34
+ const first = keys[0];
35
+ if (first === undefined) {
36
+ throw new Error("docs.topics must be non-empty");
37
+ }
38
+ return first;
35
39
  }
36
40
 
37
41
  /** Whether MCP auto-guide topic is included. */
@@ -53,7 +57,10 @@ export function docsTopicDescription(key: string, custom?: string): string {
53
57
 
54
58
  /** Markdown body for one docs topic key. */
55
59
  export function docsTopicText(program: CliProgram, topic: string): string {
56
- const docs = program.docs!;
60
+ const docs = program.docs;
61
+ if (!docs) {
62
+ throw new Error("docs not enabled");
63
+ }
57
64
  if (topic === "mcp") {
58
65
  if (!docsIncludesMcpTopic(program)) {
59
66
  throw new Error("Unknown docs topic 'mcp'.");
package/src/docs/save.ts CHANGED
@@ -1,8 +1,8 @@
1
1
  import { mkdirSync, writeFileSync } from "node:fs";
2
2
  import { join } from "node:path";
3
+ import { generatedFileHtmlComment, insertGeneratedHint } from "../skill/hint.ts";
3
4
  import type { CliProgram } from "../types.ts";
4
5
  import { docsTopicContent } from "./resolve.ts";
5
- import { generatedFileHtmlComment, insertGeneratedHint } from "../skill/hint.ts";
6
6
 
7
7
  /** Relative output directory for `docs --save`. */
8
8
  export const DOCS_SAVE_DIR = "docs";
@@ -21,12 +21,20 @@ export function docsSaveGeneratedHint(program: CliProgram, topic: string): strin
21
21
  }
22
22
 
23
23
  /** Inserts save hint without breaking YAML frontmatter (`docs skill`). */
24
- export function applySaveGeneratedHint(program: CliProgram, topic: string, content: string): string {
24
+ export function applySaveGeneratedHint(
25
+ program: CliProgram,
26
+ topic: string,
27
+ content: string,
28
+ ): string {
25
29
  if (!docsTopicIsGeneratedByArgsbarg(topic)) {
26
30
  return content;
27
31
  }
28
32
  const hint = docsSaveGeneratedHint(program, topic);
29
- return insertGeneratedHint(content, hint, topic === "skill" ? { afterFrontmatter: true } : undefined);
33
+ return insertGeneratedHint(
34
+ content,
35
+ hint,
36
+ topic === "skill" ? { afterFrontmatter: true } : undefined,
37
+ );
30
38
  }
31
39
 
32
40
  /** File body for `--save` (hint on argsbarg-generated markdown only). */