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,22 +1,22 @@
1
- import { CliProgram } from "../types.ts";
1
+ import type { CliProgram } from "../types.ts";
2
2
  import { buildInstallStatus, detectInstalledArtifacts } from "./detect-installed.ts";
3
- import type { InstallOpts } from "./plan.ts";
4
3
  import { resolveInstallPaths } from "./paths.ts";
4
+ import type { InstallOpts } from "./plan.ts";
5
5
 
6
6
  export function installOut(msg: string, opts: InstallOpts): void {
7
7
  if (opts.quiet || opts.json) return;
8
- process.stdout.write(msg + "\n");
8
+ process.stdout.write(`${msg}\n`);
9
9
  }
10
10
 
11
11
  export function installInfo(msg: string, opts: InstallOpts): void {
12
12
  if (opts.quiet) return;
13
13
  if (opts.json && !opts.dry) return;
14
14
  const prefix = opts.dry ? "[dry run] " : "";
15
- process.stderr.write(prefix + msg + "\n");
15
+ process.stderr.write(`${prefix + msg}\n`);
16
16
  }
17
17
 
18
18
  export function installErr(msg: string): void {
19
- process.stderr.write(msg + "\n");
19
+ process.stderr.write(`${msg}\n`);
20
20
  }
21
21
 
22
22
  /** Prints install status to stdout (human or JSON). */
@@ -35,7 +35,11 @@ export function printInstallStatus(root: CliProgram, opts: InstallOpts): void {
35
35
  if (status.claudeSkill) json.claudeSkill = status.claudeSkill;
36
36
  if (status.cursorMcp) json.cursorMcp = status.cursorMcp;
37
37
  if (status.claudeMcp) json.claudeMcp = status.claudeMcp;
38
- process.stdout.write(JSON.stringify(json, null, 2) + "\n");
38
+ if (status.claudeDesktopMcp) json.claudeDesktopMcp = status.claudeDesktopMcp;
39
+ if (status.opencodeMcp) json.opencodeMcp = status.opencodeMcp;
40
+ if (status.codexMcp) json.codexMcp = status.codexMcp;
41
+ if (status.chatGptMcp) json.chatGptMcp = status.chatGptMcp;
42
+ process.stdout.write(`${JSON.stringify(json, null, 2)}\n`);
39
43
  return;
40
44
  }
41
45
 
@@ -48,7 +52,11 @@ export function printInstallStatus(root: CliProgram, opts: InstallOpts): void {
48
52
  ["cursor skill", status.cursorSkill],
49
53
  ["claude skill", status.claudeSkill],
50
54
  ["cursor mcp", status.cursorMcp],
51
- ["claude mcp", status.claudeMcp],
55
+ ["claude code mcp", status.claudeMcp],
56
+ ["claude desktop mcp", status.claudeDesktopMcp],
57
+ ["opencode mcp", status.opencodeMcp],
58
+ ["codex mcp", status.codexMcp],
59
+ ["chatgpt desktop mcp", status.chatGptMcp],
52
60
  ];
53
61
  let any = false;
54
62
  for (const [label, value] of lines) {
@@ -1,16 +1,18 @@
1
1
  import { existsSync, rmSync } from "node:fs";
2
- import { CliProgram } from "../types.ts";
2
+ import type { CliProgram } from "../types.ts";
3
3
  import { uninstallBinary } from "./binary.ts";
4
4
  import { uninstallCompletions } from "./completions.ts";
5
5
  import { detectInstalledArtifacts } from "./detect-installed.ts";
6
+ import { removeCodexMcpConfig } from "./mcp-codex.ts";
6
7
  import { removeMcpConfig } from "./mcp-config.ts";
7
- import { InstallPaths } from "./paths.ts";
8
+ import { detectOpenCodeMcpConfigPath, removeOpenCodeMcpConfig } from "./mcp-opencode.ts";
9
+ import { type InstallPaths, userHome } from "./paths.ts";
8
10
  import {
11
+ type InstallOpts,
9
12
  wantsInstallBin,
10
13
  wantsInstallCompletions,
11
14
  wantsInstallMcp,
12
15
  wantsInstallSkill,
13
- type InstallOpts,
14
16
  } from "./plan.ts";
15
17
 
16
18
  export interface UninstallAction {
@@ -93,7 +95,7 @@ export function buildUninstallPlan(
93
95
  }
94
96
  if (detected.claudeMcp) {
95
97
  actions.push({
96
- summary: `claude mcp: ${paths.claudeMcpPath}`,
98
+ summary: `claude code mcp: ${paths.claudeMcpPath}`,
97
99
  message: `Removing MCP server "${paths.mcpName}" from ${paths.claudeMcpPath}`,
98
100
  run: () => {
99
101
  removeMcpConfig(paths.claudeMcpPath, paths.mcpName, dry);
@@ -101,6 +103,48 @@ export function buildUninstallPlan(
101
103
  },
102
104
  });
103
105
  }
106
+ if (detected.claudeDesktopMcp) {
107
+ actions.push({
108
+ summary: `claude desktop mcp: ${paths.claudeDesktopMcpPath}`,
109
+ message: `Removing MCP server "${paths.mcpName}" from ${paths.claudeDesktopMcpPath}`,
110
+ run: () => {
111
+ removeMcpConfig(paths.claudeDesktopMcpPath, paths.mcpName, dry);
112
+ return [paths.claudeDesktopMcpPath];
113
+ },
114
+ });
115
+ }
116
+ if (detected.opencodeMcp) {
117
+ const openCodePath =
118
+ detectOpenCodeMcpConfigPath(userHome(), paths.mcpName) ?? paths.opencodeMcpPath;
119
+ actions.push({
120
+ summary: `opencode mcp: ${openCodePath}`,
121
+ message: `Removing MCP server "${paths.mcpName}" from ${openCodePath}`,
122
+ run: () => {
123
+ removeOpenCodeMcpConfig(openCodePath, paths.mcpName, dry);
124
+ return [openCodePath];
125
+ },
126
+ });
127
+ }
128
+ if (detected.codexMcp) {
129
+ actions.push({
130
+ summary: `codex mcp: ${paths.codexConfigPath}`,
131
+ message: `Removing MCP server "${paths.mcpName}" from Codex via codex mcp remove`,
132
+ run: () => {
133
+ removeCodexMcpConfig(userHome(), paths.mcpName, dry);
134
+ return [paths.codexConfigPath];
135
+ },
136
+ });
137
+ }
138
+ if (detected.chatGptMcp) {
139
+ actions.push({
140
+ summary: `chatgpt desktop mcp: ${paths.chatGptMcpPath}`,
141
+ message: `Removing MCP server "${paths.mcpName}" from ${paths.chatGptMcpPath}`,
142
+ run: () => {
143
+ removeMcpConfig(paths.chatGptMcpPath, paths.mcpName, dry);
144
+ return [paths.chatGptMcpPath];
145
+ },
146
+ });
147
+ }
104
148
  }
105
149
 
106
150
  return actions;
@@ -110,5 +154,5 @@ export function buildUninstallPlan(
110
154
  export function uninstallSkillDir(dir: string, dry: boolean): string[] {
111
155
  if (!existsSync(dir)) return [];
112
156
  if (!dry) rmSync(dir, { recursive: true, force: true });
113
- return [dir + "/"];
157
+ return [`${dir}/`];
114
158
  }
@@ -1,7 +1,7 @@
1
- import { expect, test, beforeEach, afterEach } from "bun:test";
1
+ import { afterEach, beforeEach, expect, test } from "bun:test";
2
2
  import { chmodSync, existsSync, mkdtempSync, readFileSync, rmSync, writeFileSync } 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 { cliInvoke } from "../index.ts";
7
7
  import type { CliProgram, CliUpdateArtifact } from "../types.ts";
@@ -7,7 +7,9 @@ import { installErr } from "./status.ts";
7
7
  export async function cliUpdate(root: CliProgram): Promise<never> {
8
8
  const hook = root.install?.updateGetLatest;
9
9
  if (!hook) {
10
- installErr("install --update is not configured. Set install.updateGetLatest on the program root.");
10
+ installErr(
11
+ "install --update is not configured. Set install.updateGetLatest on the program root.",
12
+ );
11
13
  process.exit(1);
12
14
  }
13
15
 
package/src/invoke.ts CHANGED
@@ -4,12 +4,12 @@ It parses argv against the user schema, captures stdout/stderr, and patches
4
4
  process.exit so MCP tool calls can run handlers repeatedly.
5
5
  */
6
6
 
7
- import { CliContext } from "./context.ts";
7
+ import { format } from "node:util";
8
8
  import { builtinInterceptRoot, dispatchBuiltin } from "./builtins/dispatch.ts";
9
9
  import { cliParseRoot, cliPresentationRoot } from "./builtins/presentation.ts";
10
- import { parse, postParseValidate, ParseKind } from "./parse.ts";
10
+ import { CliContext } from "./context.ts";
11
+ import { ParseKind, parse, postParseValidate } from "./parse.ts";
11
12
  import { type CliNode, type CliProgram, type CliRouter, isCliLeaf, isCliRouter } from "./types.ts";
12
- import { format } from "node:util";
13
13
 
14
14
  /** Outcome of a non-exiting CLI invocation. */
15
15
  export type CliInvokeKind = "ok" | "help" | "error";
@@ -159,21 +159,24 @@ export async function cliInvoke(root: CliProgram, argv: string[]): Promise<CliIn
159
159
  }) as typeof process.stderr.write;
160
160
 
161
161
  console.log = (...args: unknown[]) => {
162
- stdout += format(...args) + "\n";
162
+ stdout += `${format(...args)}\n`;
163
163
  };
164
164
  console.info = (...args: unknown[]) => {
165
- stdout += format(...args) + "\n";
165
+ stdout += `${format(...args)}\n`;
166
166
  };
167
167
  console.warn = (...args: unknown[]) => {
168
- stderr += format(...args) + "\n";
168
+ stderr += `${format(...args)}\n`;
169
169
  };
170
170
  console.error = (...args: unknown[]) => {
171
- stderr += format(...args) + "\n";
171
+ stderr += `${format(...args)}\n`;
172
172
  };
173
173
 
174
174
  try {
175
175
  if (pr.kind === ParseKind.Ok) {
176
- await dispatchBuiltin(root, pr, { isLeafCompletionIntercept, parseRoot: completionParseRoot });
176
+ await dispatchBuiltin(root, pr, {
177
+ isLeafCompletionIntercept,
178
+ parseRoot: completionParseRoot,
179
+ });
177
180
  }
178
181
 
179
182
  await Promise.resolve(handler(ctx));
@@ -191,7 +194,7 @@ export async function cliInvoke(root: CliProgram, argv: string[]): Promise<CliIn
191
194
  kind: "error",
192
195
  exitCode: 1,
193
196
  stdout,
194
- stderr: err.message + "\n",
197
+ stderr: `${err.message}\n`,
195
198
  errorMsg: err.message,
196
199
  };
197
200
  }
package/src/mcp/bundle.ts CHANGED
@@ -1,13 +1,21 @@
1
1
  /*
2
2
  Packs a CLI program into an MCP Bundle (`.mcpb`) for Claude Desktop.
3
- macOS-only v1: expects `dist/<program.key>` as the compiled binary input.
3
+ Expects `dist/<program.key>` as the compiled binary input.
4
4
  */
5
5
 
6
- import { cpSync, existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
6
+ import {
7
+ cpSync,
8
+ existsSync,
9
+ mkdirSync,
10
+ mkdtempSync,
11
+ readFileSync,
12
+ rmSync,
13
+ writeFileSync,
14
+ } from "node:fs";
7
15
  import { tmpdir } from "node:os";
8
16
  import { basename, join, resolve } from "node:path";
9
- import { collectMcpTools, mcpServerId } from "./tools.ts";
10
17
  import type { CliMcpBundleConfig, CliProgram } from "../types.ts";
18
+ import { collectMcpTools, mcpServerId } from "./tools.ts";
11
19
 
12
20
  const MANIFEST_VERSION = "0.3";
13
21
  const DIST_DIR = "dist";
@@ -51,7 +59,11 @@ function buildUserConfig(program: CliProgram): Record<string, unknown> | undefin
51
59
  }
52
60
  const out: Record<string, unknown> = {};
53
61
  for (const name of envVars) {
54
- const key = name.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_|_$/g, "") || "env_var";
62
+ const key =
63
+ name
64
+ .toLowerCase()
65
+ .replace(/[^a-z0-9]+/g, "_")
66
+ .replace(/^_|_$/g, "") || "env_var";
55
67
  out[key] = {
56
68
  type: "string",
57
69
  title: name,
@@ -64,12 +76,19 @@ function buildUserConfig(program: CliProgram): Record<string, unknown> | undefin
64
76
  }
65
77
 
66
78
  /** Default author when `mcpServer.bundle.author` is unset. */
67
- function defaultAuthor(bundle?: CliMcpBundleConfig): { name: string; email?: string; url?: string } {
79
+ function defaultAuthor(bundle?: CliMcpBundleConfig): {
80
+ name: string;
81
+ email?: string;
82
+ url?: string;
83
+ } {
68
84
  return bundle?.author ?? { name: "Unknown" };
69
85
  }
70
86
 
71
87
  /** Generates MCPB `manifest.json` object from program schema and MCP tools. */
72
- export function generateMcpManifest(program: CliProgram, binaryName: string): Record<string, unknown> {
88
+ export function generateMcpManifest(
89
+ program: CliProgram,
90
+ binaryName: string,
91
+ ): Record<string, unknown> {
73
92
  const bundle = program.mcpServer?.bundle;
74
93
  const tools = collectMcpTools(program).map((t) => ({
75
94
  name: t.name,
@@ -119,7 +138,11 @@ export function generateMcpManifest(program: CliProgram, binaryName: string): Re
119
138
  function crc32(data: Buffer): number {
120
139
  let crc = 0xffffffff;
121
140
  for (let i = 0; i < data.length; i++) {
122
- crc ^= data[i]!;
141
+ const byte = data[i];
142
+ if (byte === undefined) {
143
+ continue;
144
+ }
145
+ crc ^= byte;
123
146
  for (let j = 0; j < 8; j++) {
124
147
  crc = (crc >>> 1) ^ (crc & 1 ? 0xedb88320 : 0);
125
148
  }
@@ -198,13 +221,9 @@ export interface PackMcpBundleOpts {
198
221
 
199
222
  /**
200
223
  * Stages manifest + binary (+ optional icon) and writes a `.mcpb` ZIP.
201
- * macOS-only v1; requires the compiled binary to exist.
224
+ * Requires the compiled binary to exist.
202
225
  */
203
226
  export function packMcpBundle(program: CliProgram, opts: PackMcpBundleOpts = {}): string {
204
- if (process.platform !== "darwin") {
205
- throw new Error("mcp bundle is macOS-only in this release. Build the bundle on darwin.");
206
- }
207
-
208
227
  const cwd = opts.cwd ?? process.cwd();
209
228
  const defaults = defaultMcpBundlePaths(program, cwd);
210
229
  const binaryPath = resolve(cwd, opts.binaryPath ?? defaults.binaryPath);
@@ -212,7 +231,9 @@ export function packMcpBundle(program: CliProgram, opts: PackMcpBundleOpts = {})
212
231
  const binaryName = basename(binaryPath);
213
232
 
214
233
  if (!existsSync(binaryPath)) {
215
- throw new Error(`Binary not found: ${binaryPath}. Build with compile first (expected dist/${program.key}).`);
234
+ throw new Error(
235
+ `Binary not found: ${binaryPath}. Build with compile first (expected dist/${program.key}).`,
236
+ );
216
237
  }
217
238
 
218
239
  const staging = mkdtempSync(join(tmpdir(), "mcpb-"));
@@ -222,7 +243,10 @@ export function packMcpBundle(program: CliProgram, opts: PackMcpBundleOpts = {})
222
243
 
223
244
  const manifest = generateMcpManifest(program, binaryName);
224
245
  const files: { name: string; data: Buffer }[] = [
225
- { name: "manifest.json", data: Buffer.from(`${JSON.stringify(manifest, null, 2)}\n`, "utf8") },
246
+ {
247
+ name: "manifest.json",
248
+ data: Buffer.from(`${JSON.stringify(manifest, null, 2)}\n`, "utf8"),
249
+ },
226
250
  { name: binaryName, data: readFileSync(stagedBinary) },
227
251
  ];
228
252
 
package/src/mcp/env.ts CHANGED
@@ -2,8 +2,8 @@
2
2
  This module bootstraps process.env for MCP servers from login shell and .env files.
3
3
  */
4
4
 
5
- import { readFileSync } from "node:fs";
6
5
  import { spawnSync } from "node:child_process";
6
+ import { readFileSync } from "node:fs";
7
7
 
8
8
  /** Parses `env` stdout from a login shell into a key/value map. */
9
9
  export function captureShellEnv(shell: string): Record<string, string> {
@@ -42,9 +42,7 @@ export function applyShellEnv(env: Record<string, string>): void {
42
42
 
43
43
  /** Loads a .env file into process.env (always overwrites). Warns on stderr if missing. */
44
44
  export function loadEnvFile(envFile: string): void {
45
- const resolved = envFile.startsWith("~")
46
- ? envFile.replace("~", process.env.HOME ?? "")
47
- : envFile;
45
+ const resolved = envFile.startsWith("~") ? envFile.replace("~", process.env.HOME ?? "") : envFile;
48
46
  let text: string;
49
47
  try {
50
48
  text = readFileSync(resolved, "utf8");
@@ -63,10 +61,7 @@ export function loadEnvFile(envFile: string): void {
63
61
  }
64
62
  const key = trimmed.slice(0, eq).trim();
65
63
  let val = trimmed.slice(eq + 1).trim();
66
- if (
67
- (val.startsWith('"') && val.endsWith('"')) ||
68
- (val.startsWith("'") && val.endsWith("'"))
69
- ) {
64
+ if ((val.startsWith('"') && val.endsWith('"')) || (val.startsWith("'") && val.endsWith("'"))) {
70
65
  val = val.slice(1, -1);
71
66
  }
72
67
  if (key) {
@@ -76,10 +71,7 @@ export function loadEnvFile(envFile: string): void {
76
71
  }
77
72
 
78
73
  /** Applies mcpServer shellEnv and envFile bootstrap in order. */
79
- export function bootstrapMcpEnv(config: {
80
- shellEnv?: boolean | string;
81
- envFile?: string;
82
- }): void {
74
+ export function bootstrapMcpEnv(config: { shellEnv?: boolean | string; envFile?: string }): void {
83
75
  const shellEnvCfg = config.shellEnv;
84
76
  if (shellEnvCfg) {
85
77
  const shell =
@@ -88,7 +80,9 @@ export function bootstrapMcpEnv(config: {
88
80
  : (process.env.SHELL ?? (process.platform === "darwin" ? "/bin/zsh" : "/bin/bash"));
89
81
  const captured = captureShellEnv(shell);
90
82
  if (Object.keys(captured).length === 0) {
91
- process.stderr.write(`[argsbarg] shellEnv: failed to capture shell environment from ${shell}\n`);
83
+ process.stderr.write(
84
+ `[argsbarg] shellEnv: failed to capture shell environment from ${shell}\n`,
85
+ );
92
86
  } else {
93
87
  applyShellEnv(captured);
94
88
  }
package/src/mcp/server.ts CHANGED
@@ -4,7 +4,7 @@ resources, and ping. Responses are newline-delimited JSON on stdout only.
4
4
  */
5
5
 
6
6
  import { cliInvoke } from "../invoke.ts";
7
- import { CliProgram } from "../types.ts";
7
+ import type { CliProgram } from "../types.ts";
8
8
  import { buildToolCallSuccess } from "./result.ts";
9
9
  import {
10
10
  allMcpResources,
@@ -25,7 +25,7 @@ interface JsonRpcRequest {
25
25
 
26
26
  /** Writes a JSON-RPC response line to stdout. */
27
27
  function writeResponse(msg: Record<string, unknown>): void {
28
- process.stdout.write(JSON.stringify(msg) + "\n");
28
+ process.stdout.write(`${JSON.stringify(msg)}\n`);
29
29
  }
30
30
 
31
31
  /** Writes a JSON-RPC error response. */
@@ -108,7 +108,10 @@ async function handleRequestLine(root: CliProgram, line: string): Promise<void>
108
108
  return;
109
109
  }
110
110
  const rawArgs = params.arguments;
111
- if (rawArgs !== undefined && (typeof rawArgs !== "object" || rawArgs === null || Array.isArray(rawArgs))) {
111
+ if (
112
+ rawArgs !== undefined &&
113
+ (typeof rawArgs !== "object" || rawArgs === null || Array.isArray(rawArgs))
114
+ ) {
112
115
  writeError(id, -32602, "Invalid params: arguments must be an object");
113
116
  return;
114
117
  }
@@ -151,7 +154,8 @@ async function handleRequestLine(root: CliProgram, line: string): Promise<void>
151
154
  });
152
155
  return;
153
156
  }
154
- const errText = invokeResult.stderr.trim() || invokeResult.errorMsg || `Exit code ${invokeResult.exitCode}`;
157
+ const errText =
158
+ invokeResult.stderr.trim() || invokeResult.errorMsg || `Exit code ${invokeResult.exitCode}`;
155
159
  writeResponse({
156
160
  jsonrpc: "2.0",
157
161
  id,
@@ -222,14 +226,16 @@ export async function mcpServeStdioLoop(root: CliProgram): Promise<void> {
222
226
  let buffer = "";
223
227
  for await (const chunk of Bun.stdin.stream()) {
224
228
  buffer += new TextDecoder().decode(chunk);
225
- let nl: number;
226
- while ((nl = buffer.indexOf("\n")) !== -1) {
229
+ let nl = buffer.indexOf("\n");
230
+ while (nl !== -1) {
227
231
  const line = buffer.slice(0, nl).trim();
228
232
  buffer = buffer.slice(nl + 1);
229
233
  if (line.length === 0) {
234
+ nl = buffer.indexOf("\n");
230
235
  continue;
231
236
  }
232
237
  await handleRequestLine(root, line);
238
+ nl = buffer.indexOf("\n");
233
239
  }
234
240
  }
235
241
  const trailing = buffer.trim();
package/src/mcp/tools.ts CHANGED
@@ -4,10 +4,19 @@ flat JSON tool arguments into argv for cliInvoke.
4
4
  */
5
5
 
6
6
  import { cliResolveNotes } from "../help.ts";
7
- import { collectOptionDefs } from "../parse.ts";
8
7
  import { visibleOptions } from "../hidden.ts";
8
+ import { collectOptionDefs } from "../parse.ts";
9
9
  import { cliSchemaJson } from "../schema.ts";
10
- import { CliProgram, CliLeaf, CliNode, CliOption, CliOptionKind, CliPositional, isCliLeaf, isCliRouter, leafOutputSchema } from "../types.ts";
10
+ import {
11
+ type CliLeaf,
12
+ type CliNode,
13
+ type CliOption,
14
+ CliOptionKind,
15
+ type CliPositional,
16
+ type CliProgram,
17
+ isCliLeaf,
18
+ leafOutputSchema,
19
+ } from "../types.ts";
11
20
 
12
21
  /** Default URI pattern for the CLI schema MCP resource (`<mcpId>://schema`). */
13
22
  export function defaultMcpSchemaUri(mcpId: string): string {
@@ -80,7 +89,11 @@ function positionalProperty(p: CliPositional): Record<string, unknown> {
80
89
  }
81
90
 
82
91
  /** Builds inputSchema for a leaf command. */
83
- function buildInputSchema(root: CliProgram, path: string[], leaf: CliLeaf): Record<string, unknown> {
92
+ function buildInputSchema(
93
+ root: CliProgram,
94
+ path: string[],
95
+ leaf: CliLeaf,
96
+ ): Record<string, unknown> {
84
97
  const properties: Record<string, unknown> = {};
85
98
  const required: string[] = [];
86
99
 
@@ -252,7 +265,10 @@ export function mcpToolCallToArgv(
252
265
  items = raw.map(String);
253
266
  } else if (typeof raw === "string") {
254
267
  items = raw.includes(",")
255
- ? raw.split(",").map((s) => s.trim()).filter(Boolean)
268
+ ? raw
269
+ .split(",")
270
+ .map((s) => s.trim())
271
+ .filter(Boolean)
256
272
  : raw.trim()
257
273
  ? [raw.trim()]
258
274
  : [];
package/src/mcp.ts CHANGED
@@ -2,9 +2,9 @@
2
2
  This module starts the ArgsBarg MCP stdio server for opt-in program roots.
3
3
  */
4
4
 
5
- import { mcpServeStdioLoop } from "./mcp/server.ts";
6
5
  import { bootstrapMcpEnv } from "./mcp/env.ts";
7
- import { CliProgram } from "./types.ts";
6
+ import { mcpServeStdioLoop } from "./mcp/server.ts";
7
+ import type { CliProgram } from "./types.ts";
8
8
 
9
9
  /**
10
10
  * Runs the MCP JSON-RPC server on stdin/stdout until stdin closes, then exits.
@@ -19,7 +19,7 @@ export async function cliMcpServeStdio(root: CliProgram): Promise<never> {
19
19
  process.exit(0);
20
20
  } catch (err) {
21
21
  if (err instanceof Error) {
22
- process.stderr.write(err.message + "\n");
22
+ process.stderr.write(`${err.message}\n`);
23
23
  } else {
24
24
  process.stderr.write("MCP server error.\n");
25
25
  }