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
package/src/help.ts CHANGED
@@ -7,8 +7,16 @@ It keeps help formatting shared across help and error paths so users see one con
7
7
  style no matter how help is reached.
8
8
  */
9
9
 
10
- import { CliNode, CliOption, CliOptionKind, CliPositional, CliRouter, isCliLeaf, isCliRouter } from "./types.ts";
11
10
  import { visibleOptions, visibleSubcommands } from "./hidden.ts";
11
+ import {
12
+ type CliNode,
13
+ type CliOption,
14
+ CliOptionKind,
15
+ type CliPositional,
16
+ type CliRouter,
17
+ isCliLeaf,
18
+ isCliRouter,
19
+ } from "./types.ts";
12
20
 
13
21
  // ── ANSI Style Helpers ────────────────────────────────────────────────────────
14
22
 
@@ -52,10 +60,10 @@ const style = {
52
60
 
53
61
  const kBoxTL = "\u256D"; // ╭
54
62
  const kBoxTR = "\u256E"; // ╮
55
- const kBoxV = "\u2502"; // │
63
+ const kBoxV = "\u2502"; // │
56
64
  const kBoxBL = "\u2570"; // ╰
57
65
  const kBoxBR = "\u256F"; // ╯
58
- const kBoxH = "\u2500"; // ─
66
+ const kBoxH = "\u2500"; // ─
59
67
 
60
68
  // ── Terminal Detection ────────────────────────────────────────────────────────
61
69
 
@@ -119,7 +127,7 @@ function wrapParagraph(text: string, width: number): string[] {
119
127
  continue;
120
128
  }
121
129
  if (cur.length + 1 + word.length <= available) {
122
- cur += " " + word;
130
+ cur += ` ${word}`;
123
131
  } else {
124
132
  out.push(cur);
125
133
  cur = word;
@@ -166,24 +174,24 @@ function optKindLabel(k: CliOptionKind, o?: CliOption): string {
166
174
  return " <choice>";
167
175
  }
168
176
  if (choices.length <= 4) {
169
- return " <" + choices.join("|") + ">";
177
+ return ` <${choices.join("|")}>`;
170
178
  }
171
- return " <" + choices.slice(0, 3).join("|") + "|…>";
179
+ return ` <${choices.slice(0, 3).join("|")}|…>`;
172
180
  }
173
181
  }
174
182
  }
175
183
 
176
184
  /** Formats a flag/value option for help tables: `--name`, optional short, optional kind hint. */
177
185
  export function cliOptionLabel(o: CliOption, color: boolean): string {
178
- let r = "--" + o.name + optKindLabel(o.kind, o);
179
- if (o.shortName) r += ", -" + o.shortName;
186
+ let r = `--${o.name}${optKindLabel(o.kind, o)}`;
187
+ if (o.shortName) r += `, -${o.shortName}`;
180
188
  if (!color) return r;
181
189
 
182
190
  const sepIdx = r.indexOf(", ");
183
191
  if (sepIdx === -1) return style.aquaBold(r);
184
192
  const left = r.slice(0, sepIdx);
185
193
  const right = r.slice(sepIdx + 2);
186
- return style.aquaBold(left) + " " + style.greenBright(right);
194
+ return `${style.aquaBold(left)} ${style.greenBright(right)}`;
187
195
  }
188
196
 
189
197
  /** Placeholder in `notes` for the root program key (resolved in help, schema, and docs api). */
@@ -199,9 +207,9 @@ export function cliPositionalLabel(p: CliPositional, color: boolean): string {
199
207
  const { argMin = 1, argMax = 1 } = p;
200
208
  let r: string;
201
209
  if (argMax === 1) {
202
- r = argMin === 0 ? "[" + p.name + "]" : "<" + p.name + ">";
210
+ r = argMin === 0 ? `[${p.name}]` : `<${p.name}>`;
203
211
  } else {
204
- r = argMin === 0 ? "[" + p.name + "...]" : "<" + p.name + "...>";
212
+ r = argMin === 0 ? `[${p.name}...]` : `<${p.name}...>`;
205
213
  }
206
214
  if (!color) return r;
207
215
  return style.aquaBold(r);
@@ -222,8 +230,8 @@ function renderTextBox(title: string, lines: string[], hw: number, color: boolea
222
230
  if (lines.length === 0) return [];
223
231
 
224
232
  const titleLead = color
225
- ? style.gray(kBoxH + " ") + style.grayBoldTitle(title) + style.gray(" ")
226
- : kBoxH + " " + title + " ";
233
+ ? style.gray(`${kBoxH} `) + style.grayBoldTitle(title) + style.gray(" ")
234
+ : `${kBoxH} ${title} `;
227
235
 
228
236
  let contentWidth = visibleWidth(titleLead) + 1;
229
237
  for (const line of lines) {
@@ -238,19 +246,25 @@ function renderTextBox(title: string, lines: string[], hw: number, color: boolea
238
246
  const out: string[] = [];
239
247
  out.push(
240
248
  (color ? style.gray(kBoxTL) : kBoxTL) +
241
- titleLead +
242
- (color ? style.gray(repeatBoxH(headerFill) + kBoxTR) : repeatBoxH(headerFill) + kBoxTR),
249
+ titleLead +
250
+ (color ? style.gray(repeatBoxH(headerFill) + kBoxTR) : repeatBoxH(headerFill) + kBoxTR),
243
251
  );
244
252
 
245
253
  for (const line of lines) {
246
254
  const padded = padVisible(line, contentWidth);
247
255
  out.push(
248
- (color ? style.gray(kBoxV) : kBoxV) + " " + padded + " " + (color ? style.gray(kBoxV) : kBoxV),
256
+ (color ? style.gray(kBoxV) : kBoxV) +
257
+ " " +
258
+ padded +
259
+ " " +
260
+ (color ? style.gray(kBoxV) : kBoxV),
249
261
  );
250
262
  }
251
263
 
252
264
  out.push(
253
- (color ? style.gray(kBoxBL + repeatBoxH(borderWidth) + kBoxBR) : kBoxBL + repeatBoxH(borderWidth) + kBoxBR),
265
+ color
266
+ ? style.gray(kBoxBL + repeatBoxH(borderWidth) + kBoxBR)
267
+ : kBoxBL + repeatBoxH(borderWidth) + kBoxBR,
254
268
  );
255
269
 
256
270
  return out;
@@ -265,7 +279,7 @@ function renderTableBox(title: string, rows: HelpRow[], hw: number, color: boole
265
279
  labelWidth = Math.max(labelWidth, visibleWidth(row.label));
266
280
  }
267
281
 
268
- const titleChunk = kBoxH + " " + title + " ";
282
+ const titleChunk = `${kBoxH} ${title} `;
269
283
  const minimumContentWidth = Math.max(visibleWidth(titleChunk) + 1, labelWidth + 2 + 18);
270
284
  let contentWidth = Math.max(hw - 2, minimumContentWidth);
271
285
  const descWidth = Math.max(1, contentWidth - labelWidth - 2);
@@ -274,20 +288,22 @@ function renderTableBox(title: string, rows: HelpRow[], hw: number, color: boole
274
288
  for (const row of rows) {
275
289
  const wrapped = wrapText(row.description, descWidth);
276
290
  const first =
277
- row.label + spaces(labelWidth - visibleWidth(row.label)) + " " +
291
+ row.label +
292
+ spaces(labelWidth - visibleWidth(row.label)) +
293
+ " " +
278
294
  (color ? style.white(wrapped[0]) : wrapped[0]);
279
295
  bodyLines.push(first);
280
296
  for (let idx = 1; idx < wrapped.length; idx++) {
281
297
  const pad = color ? style.gray(spaces(labelWidth)) : spaces(labelWidth);
282
- bodyLines.push(pad + " " + (color ? style.white(wrapped[idx]) : wrapped[idx]));
298
+ bodyLines.push(`${pad} ${color ? style.white(wrapped[idx]) : wrapped[idx]}`);
283
299
  }
284
300
  }
285
301
 
286
302
  let titleLead: string;
287
303
  if (color) {
288
- titleLead = style.gray(kBoxH + " ") + style.grayBoldTitle(title) + style.gray(" ");
304
+ titleLead = style.gray(`${kBoxH} `) + style.grayBoldTitle(title) + style.gray(" ");
289
305
  } else {
290
- titleLead = kBoxH + " " + title + " ";
306
+ titleLead = `${kBoxH} ${title} `;
291
307
  }
292
308
 
293
309
  contentWidth = Math.max(contentWidth, visibleWidth(titleLead) + 1);
@@ -302,19 +318,25 @@ function renderTableBox(title: string, rows: HelpRow[], hw: number, color: boole
302
318
  const out: string[] = [];
303
319
  out.push(
304
320
  (color ? style.gray(kBoxTL) : kBoxTL) +
305
- titleLead +
306
- (color ? style.gray(repeatBoxH(headerFill) + kBoxTR) : repeatBoxH(headerFill) + kBoxTR),
321
+ titleLead +
322
+ (color ? style.gray(repeatBoxH(headerFill) + kBoxTR) : repeatBoxH(headerFill) + kBoxTR),
307
323
  );
308
324
 
309
325
  for (const line of bodyLines) {
310
326
  const padded = padVisible(line, contentWidth);
311
327
  out.push(
312
- (color ? style.gray(kBoxV) : kBoxV) + " " + padded + " " + (color ? style.gray(kBoxV) : kBoxV),
328
+ (color ? style.gray(kBoxV) : kBoxV) +
329
+ " " +
330
+ padded +
331
+ " " +
332
+ (color ? style.gray(kBoxV) : kBoxV),
313
333
  );
314
334
  }
315
335
 
316
336
  out.push(
317
- (color ? style.gray(kBoxBL + repeatBoxH(borderWidth) + kBoxBR) : kBoxBL + repeatBoxH(borderWidth) + kBoxBR),
337
+ color
338
+ ? style.gray(kBoxBL + repeatBoxH(borderWidth) + kBoxBR)
339
+ : kBoxBL + repeatBoxH(borderWidth) + kBoxBR,
318
340
  );
319
341
 
320
342
  return out;
@@ -332,7 +354,7 @@ function usageLines(
332
354
  ): string[] {
333
355
  let fullPath = appName;
334
356
  for (const seg of helpPath) {
335
- fullPath += " " + seg;
357
+ fullPath += ` ${seg}`;
336
358
  }
337
359
  const usageOpts = color ? style.aquaBold("[OPTIONS]") : "[OPTIONS]";
338
360
  const usageCmd = color ? style.aquaBold("COMMAND") : "COMMAND";
@@ -341,15 +363,15 @@ function usageLines(
341
363
  const out: string[] = [];
342
364
  if (helpPath.length === 0) {
343
365
  if (hasCommands) {
344
- out.push(fullPath + " " + usageOpts + " " + usageCmd + " " + usageArgs);
366
+ out.push(`${fullPath} ${usageOpts} ${usageCmd} ${usageArgs}`);
345
367
  } else {
346
- out.push(fullPath + " " + usageOpts);
368
+ out.push(`${fullPath} ${usageOpts}`);
347
369
  }
348
370
  return out;
349
371
  }
350
- out.push(fullPath + " " + usageOpts + (hasArgs ? (" " + usageArgs) : ""));
372
+ out.push(`${fullPath} ${usageOpts}${hasArgs ? ` ${usageArgs}` : ""}`);
351
373
  if (hasCommands) {
352
- out.push(fullPath + " " + usageCmd + " " + usageArgs);
374
+ out.push(`${fullPath} ${usageCmd} ${usageArgs}`);
353
375
  }
354
376
  return out;
355
377
  }
@@ -357,12 +379,10 @@ function usageLines(
357
379
  /** Table rows for named options, including synthetic built-in rows. */
358
380
  function rowsForOptions(defs: CliOption[], color: boolean): HelpRow[] {
359
381
  const rows: HelpRow[] = [];
360
- const helpLabel = color
361
- ? style.aquaBold("--help, ") + style.greenBright("-h")
362
- : "--help, -h";
382
+ const helpLabel = color ? style.aquaBold("--help, ") + style.greenBright("-h") : "--help, -h";
363
383
  rows.push({ label: helpLabel, description: "Show help for this command." });
364
384
  for (const o of defs) {
365
- const desc = o.required ? "(required) " + o.description : o.description;
385
+ const desc = o.required ? `(required) ${o.description}` : o.description;
366
386
  rows.push({ label: cliOptionLabel(o, color), description: desc });
367
387
  }
368
388
  return rows;
@@ -392,7 +412,7 @@ function appendNotesBox(
392
412
  if ((notes ?? "").length === 0) {
393
413
  return;
394
414
  }
395
- const resolved = cliResolveNotes(notes!, appKey);
415
+ const resolved = cliResolveNotes(notes ?? "", appKey);
396
416
  lines.push("");
397
417
  lines.push(renderTextBox("Notes", wrapText(resolved, hw - 4), hw, color).join("\n"));
398
418
  }
@@ -401,7 +421,7 @@ function appendNotesBox(
401
421
  * Renders full help for the app root or a nested command, following `helpPath` from the root key.
402
422
  * `useStderr` is reserved for call-site consistency; width and color use stdout TTY.
403
423
  */
404
- export function cliHelpRender(schema: CliRouter, helpPath: string[], useStderr: boolean): string {
424
+ export function cliHelpRender(schema: CliRouter, helpPath: string[], _useStderr: boolean): string {
405
425
  const hw = getHelpWidth();
406
426
  const color = isStdoutTTY();
407
427
 
@@ -421,7 +441,12 @@ export function cliHelpRender(schema: CliRouter, helpPath: string[], useStderr:
421
441
  ).join("\n"),
422
442
  );
423
443
 
424
- const optBox = renderTableBox("Options", rowsForOptions(visibleOptions(schema.options), color), hw, color);
444
+ const optBox = renderTableBox(
445
+ "Options",
446
+ rowsForOptions(visibleOptions(schema.options), color),
447
+ hw,
448
+ color,
449
+ );
425
450
  if (optBox.length > 0) {
426
451
  lines.push("");
427
452
  lines.push(optBox.join("\n"));
@@ -433,7 +458,7 @@ export function cliHelpRender(schema: CliRouter, helpPath: string[], useStderr:
433
458
  );
434
459
  }
435
460
  appendNotesBox(lines, schema.notes, schema.key, hw, color);
436
- return lines.join("\n") + "\n\n";
461
+ return `${lines.join("\n")}\n\n`;
437
462
  }
438
463
 
439
464
  let layer = schema.commands ?? [];
@@ -441,13 +466,13 @@ export function cliHelpRender(schema: CliRouter, helpPath: string[], useStderr:
441
466
  for (const seg of helpPath) {
442
467
  const ch = layer.find((c: CliNode) => c.key === seg);
443
468
  if (!ch) {
444
- return (color ? style.red("Unknown help path.") : "Unknown help path.") + "\n";
469
+ return `${color ? style.red("Unknown help path.") : "Unknown help path."}\n`;
445
470
  }
446
471
  node = ch;
447
472
  layer = isCliRouter(ch) ? ch.commands : [];
448
473
  }
449
474
  if (!node) {
450
- return (color ? style.red("Unknown help path.") : "Unknown help path.") + "\n";
475
+ return `${color ? style.red("Unknown help path.") : "Unknown help path."}\n`;
451
476
  }
452
477
 
453
478
  const lines: string[] = [];
@@ -471,7 +496,12 @@ export function cliHelpRender(schema: CliRouter, helpPath: string[], useStderr:
471
496
  ).join("\n"),
472
497
  );
473
498
 
474
- const optBox = renderTableBox("Options", rowsForOptions(visibleOptions(node.options), color), hw, color);
499
+ const optBox = renderTableBox(
500
+ "Options",
501
+ rowsForOptions(visibleOptions(node.options), color),
502
+ hw,
503
+ color,
504
+ );
475
505
  if (optBox.length > 0) {
476
506
  lines.push("");
477
507
  lines.push(optBox.join("\n"));
@@ -499,5 +529,5 @@ export function cliHelpRender(schema: CliRouter, helpPath: string[], useStderr:
499
529
  appendNotesBox(lines, node.notes, schema.key, hw, color);
500
530
  }
501
531
 
502
- return lines.join("\n") + "\n\n";
532
+ return `${lines.join("\n")}\n\n`;
503
533
  }
@@ -1,13 +1,13 @@
1
1
  import { describe, expect, test } from "bun:test";
2
- import { mkdtempSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
3
- import { join } from "node:path";
2
+ import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
4
3
  import { tmpdir } from "node:os";
5
- import { cliPresentationRoot, cliParseRoot } from "./builtins/presentation.ts";
4
+ import { join } from "node:path";
6
5
  import { exportPresentationBuiltins } from "./builtins/export.ts";
6
+ import { cliParseRoot, cliPresentationRoot } from "./builtins/presentation.ts";
7
7
  import { cliHelpRender } from "./help.ts";
8
- import { cliSchemaExport } from "./schema.ts";
9
- import { collectMcpTools } from "./mcp/tools.ts";
10
8
  import { defaultMcpBundlePaths, generateMcpManifest, packMcpBundle } from "./mcp/bundle.ts";
9
+ import { collectMcpTools } from "./mcp/tools.ts";
10
+ import { cliSchemaExport } from "./schema.ts";
11
11
  import { CliOptionKind, type CliProgram } from "./types.ts";
12
12
 
13
13
  const hiddenFixture: CliProgram = {
@@ -116,7 +116,8 @@ describe("mcp bundle", () => {
116
116
  expect(manifest.manifest_version).toBe("0.3");
117
117
  expect((manifest.server as { type: string }).type).toBe("binary");
118
118
  expect((manifest.server as { entry_point: string }).entry_point).toBe("myapp");
119
- const mcpConfig = (manifest.server as { mcp_config: { command: string; args: string[] } }).mcp_config;
119
+ const mcpConfig = (manifest.server as { mcp_config: { command: string; args: string[] } })
120
+ .mcp_config;
120
121
  expect(mcpConfig.command).toBe("${__dirname}/myapp");
121
122
  expect(mcpConfig.args).toEqual(["mcp"]);
122
123
  expect((manifest.compatibility as { platforms: string[] }).platforms).toEqual(["darwin"]);
package/src/hidden.ts CHANGED
@@ -3,8 +3,8 @@ Filters hidden commands and options from presentation surfaces (help, schema, co
3
3
  Parsing still uses the full tree via cliParseRoot.
4
4
  */
5
5
 
6
- import type { CliNode, CliOption, CliRouter } from "./types.ts";
7
- import { isCliLeaf, isCliRouter } from "./types.ts";
6
+ import type { CliNode, CliOption } from "./types.ts";
7
+ import { isCliRouter } from "./types.ts";
8
8
 
9
9
  /** Options visible in help, schema, completions, and MCP tool schemas. */
10
10
  export function visibleOptions(options: CliOption[] | undefined): CliOption[] {