argsbarg 3.4.2 → 3.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/CHANGELOG.md +14 -1
  2. package/biome.json +29 -6
  3. package/bun.lock +22 -0
  4. package/docs/install.md +1 -1
  5. package/docs/mcp.md +45 -1
  6. package/index.d.ts +50 -50
  7. package/justfile +27 -6
  8. package/package.json +4 -2
  9. package/scripts/release.ts +26 -9
  10. package/src/builtins/builtins.test.ts +9 -4
  11. package/src/builtins/completion-bash.ts +74 -50
  12. package/src/builtins/completion-fish.ts +3 -8
  13. package/src/builtins/completion-group.ts +1 -1
  14. package/src/builtins/completion-zsh.ts +80 -42
  15. package/src/builtins/dispatch.ts +20 -16
  16. package/src/builtins/export.ts +19 -10
  17. package/src/builtins/index.ts +9 -4
  18. package/src/builtins/install.ts +10 -10
  19. package/src/builtins/mcp.ts +1 -1
  20. package/src/builtins/presentation.ts +8 -8
  21. package/src/builtins/scopes.ts +1 -1
  22. package/src/builtins/version.ts +1 -1
  23. package/src/completion.ts +4 -4
  24. package/src/docs/api-guide.test.ts +2 -2
  25. package/src/docs/api-guide.ts +2 -2
  26. package/src/docs/builtin.ts +27 -8
  27. package/src/docs/docs.test.ts +18 -11
  28. package/src/docs/mcp-guide.ts +105 -24
  29. package/src/docs/resolve.ts +10 -3
  30. package/src/docs/save.ts +11 -3
  31. package/src/headless.test.ts +8 -16
  32. package/src/help.ts +73 -43
  33. package/src/hidden-mcpb.test.ts +7 -6
  34. package/src/hidden.ts +2 -2
  35. package/src/index.test.ts +113 -89
  36. package/src/index.ts +24 -24
  37. package/src/install/binary.ts +12 -5
  38. package/src/install/completions.ts +7 -3
  39. package/src/install/detect-installed.ts +29 -4
  40. package/src/install/gh-release-update.ts +31 -23
  41. package/src/install/index.ts +69 -19
  42. package/src/install/install.test.ts +31 -8
  43. package/src/install/mcp-codex.test.ts +57 -0
  44. package/src/install/mcp-codex.ts +125 -0
  45. package/src/install/mcp-config.ts +12 -5
  46. package/src/install/mcp-opencode.test.ts +98 -0
  47. package/src/install/mcp-opencode.ts +149 -0
  48. package/src/install/paths.ts +29 -3
  49. package/src/install/plan.ts +73 -6
  50. package/src/install/shell.ts +1 -4
  51. package/src/install/status.ts +12 -6
  52. package/src/install/uninstall.ts +38 -4
  53. package/src/install/update.test.ts +2 -2
  54. package/src/install/update.ts +3 -1
  55. package/src/invoke.ts +12 -9
  56. package/src/mcp/bundle.ts +36 -8
  57. package/src/mcp/env.ts +7 -13
  58. package/src/mcp/server.ts +12 -6
  59. package/src/mcp/tools.ts +20 -4
  60. package/src/mcp.ts +3 -3
  61. package/src/parse.ts +96 -24
  62. package/src/runtime.ts +22 -12
  63. package/src/schema.ts +11 -5
  64. package/src/skill/generate.ts +4 -4
  65. package/src/skill/install.ts +6 -2
  66. package/src/validate.ts +21 -16
@@ -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,6 +1,6 @@
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 {
@@ -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
 
@@ -127,9 +129,9 @@ test("presentation includes docs subtree", () => {
127
129
  const presentation = cliPresentationRoot(docsFixture());
128
130
  const docsNode = presentation.commands.find((c) => c.key === "docs");
129
131
  expect(docsNode).toBeDefined();
130
- expect(docsNode && "commands" in docsNode && docsNode.commands.some((c) => c.key === "readme")).toBe(
131
- true,
132
- );
132
+ expect(
133
+ docsNode && "commands" in docsNode && docsNode.commands.some((c) => c.key === "readme"),
134
+ ).toBe(true);
133
135
  });
134
136
 
135
137
  test("docs schema prints JSON", async () => {
@@ -199,6 +201,11 @@ test("generateMcpGuide includes schema URI and install targets", () => {
199
201
  expect(guide).toContain("myapp://schema");
200
202
  expect(guide).toContain("~/.cursor/mcp.json");
201
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");
202
209
  });
203
210
 
204
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,23 +88,36 @@ 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",
35
- "",
36
- "```bash",
37
- `${root.key} mcp`,
38
- "```",
39
- "",
40
- "## Client setup",
102
+ "## Installation",
41
103
  "",
42
104
  "### `install --mcp`",
43
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(
44
121
  "```bash",
45
122
  `${root.key} install --mcp --yes`,
46
123
  "```",
@@ -52,18 +129,14 @@ export function generateMcpGuide(root: CliProgram): string {
52
129
  "| Cursor | `~/.cursor/mcp.json` (when `~/.cursor` exists) |",
53
130
  "| Claude Code | `~/.claude.json` |",
54
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",
55
138
  "",
56
- "Claude Desktop paths by platform:",
57
- "",
58
- "- **macOS:** `~/Library/Application Support/Claude/claude_desktop_config.json`",
59
- "- **Windows:** `%APPDATA%\\Claude\\claude_desktop_config.json`",
60
- "- **Linux:** `~/.config/Claude/claude_desktop_config.json`",
61
- "",
62
- "Restart Claude Desktop after changing its config.",
63
- "",
64
- "### Manual entry",
65
- "",
66
- "Add under `mcpServers` in the host config:",
139
+ "For Cursor, Claude, and ChatGPT desktop JSON configs, add under `mcpServers`:",
67
140
  "",
68
141
  "```json",
69
142
  JSON.stringify(
@@ -80,7 +153,15 @@ export function generateMcpGuide(root: CliProgram): string {
80
153
  ),
81
154
  "```",
82
155
  "",
83
- ];
156
+ "## Running directly",
157
+ "",
158
+ "Start the stdio MCP server without editing host config:",
159
+ "",
160
+ "```bash",
161
+ `${root.key} mcp`,
162
+ "```",
163
+ "",
164
+ );
84
165
 
85
166
  if (mcp.shellEnv || mcp.envFile) {
86
167
  lines.push("## Environment", "");
@@ -91,7 +172,7 @@ export function generateMcpGuide(root: CliProgram): string {
91
172
  }
92
173
  if (mcp.envFile) {
93
174
  lines.push(
94
- "- **`envFile`** — loads `" + mcp.envFile + "` after shell env (overrides for its keys).",
175
+ `- **\`envFile\`** — loads \`${mcp.envFile}\` after shell env (overrides for its keys).`,
95
176
  );
96
177
  }
97
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). */
@@ -22,28 +22,20 @@ test("shouldRunHeadless is true for MCP and json", () => {
22
22
  });
23
23
 
24
24
  test("shouldRunHeadlessWithPositionals requires positionals in non-tty", () => {
25
- expect(
26
- shouldRunHeadlessWithPositionals({ invocation: "cli" }, false, [], false, false),
27
- ).toBe(false);
28
- expect(
29
- shouldRunHeadlessWithPositionals({ invocation: "cli" }, false, ["a"], false, false),
30
- ).toBe(true);
25
+ expect(shouldRunHeadlessWithPositionals({ invocation: "cli" }, false, [], false, false)).toBe(
26
+ false,
27
+ );
28
+ expect(shouldRunHeadlessWithPositionals({ invocation: "cli" }, false, ["a"], false, false)).toBe(
29
+ true,
30
+ );
31
31
  });
32
32
 
33
33
  test("shouldRunHeadlessWithYes requires yes in non-tty", () => {
34
34
  expect(
35
- shouldRunHeadlessWithYes(
36
- { invocation: "cli" },
37
- { yes: true, hasRequiredArgs: true },
38
- false,
39
- ),
35
+ shouldRunHeadlessWithYes({ invocation: "cli" }, { yes: true, hasRequiredArgs: true }, false),
40
36
  ).toBe(true);
41
37
  expect(
42
- shouldRunHeadlessWithYes(
43
- { invocation: "cli" },
44
- { yes: false, hasRequiredArgs: true },
45
- false,
46
- ),
38
+ shouldRunHeadlessWithYes({ invocation: "cli" }, { yes: false, hasRequiredArgs: true }, false),
47
39
  ).toBe(false);
48
40
  expect(
49
41
  shouldRunHeadlessWithYes(