@ghostpaw/questlog 0.9.2 → 0.11.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 (33) hide show
  1. package/README.md +144 -4
  2. package/SKILL.md +95 -40
  3. package/dist/chunk-7KTRNXTU.js +21 -0
  4. package/dist/chunk-D6EGZCT7.js +23 -0
  5. package/dist/chunk-GVD6A3RR.js +108 -0
  6. package/dist/{chunk-6KTQK3ZE.js → chunk-I2AMIXGP.js} +8 -0
  7. package/dist/{chunk-3B6FMHZU.js → chunk-IA2GVCZS.js} +35 -2
  8. package/dist/{chunk-DKH623HU.js → chunk-IL2QRKZL.js} +1 -4
  9. package/dist/{chunk-UTUQPSGO.js → chunk-KOWIVBG6.js} +68 -588
  10. package/dist/chunk-NBCCA6AX.js +31 -0
  11. package/dist/chunk-QK3H2AL5.js +36 -0
  12. package/dist/chunk-RBGQK5WP.js +949 -0
  13. package/dist/cli.js +1 -1
  14. package/dist/{cli_main-XNZP4TY2.js → cli_main-TEG3QTDP.js} +147 -128
  15. package/dist/index.cjs +1152 -627
  16. package/dist/index.d.cts +142 -11
  17. package/dist/index.d.ts +142 -11
  18. package/dist/index.js +1152 -628
  19. package/dist/list_deliveries-WJEJUZIQ.js +2 -0
  20. package/dist/list_hooks-C5CAA2C7.js +2 -0
  21. package/dist/mcp-server.js +840 -389
  22. package/dist/mcp.cjs +709 -322
  23. package/dist/mcp.js +711 -324
  24. package/dist/open_questlog_db-6NS6BVBC.js +2 -0
  25. package/dist/{questlog_skill_spec-EM6DGGVN.js → questlog_skill_spec-ILTCTRYS.js} +16 -8
  26. package/dist/run_hook_command-BOENYNDT.js +155 -0
  27. package/dist/{serve_questlog_mcp-KFUYJB2P.js → serve_questlog_mcp-X524X34F.js} +6 -3
  28. package/dist/{skill_markdown-HXYQ3K6U.js → skill_markdown-EGKYZ3W2.js} +4 -20
  29. package/dist/text_output-X7F3K23Q.js +2 -0
  30. package/package.json +1 -1
  31. package/skills/questlog/SKILL.md +95 -40
  32. package/skills/questlog/references/webhooks.md +94 -0
  33. package/dist/open_questlog_db-SH3DAU4C.js +0 -2
package/dist/cli.js CHANGED
@@ -13,5 +13,5 @@ function checkNodeVersion(version, stderr) {
13
13
  if (!checkNodeVersion(process.versions.node, process.stderr)) {
14
14
  process.exitCode = 2;
15
15
  } else {
16
- await import('./cli_main-XNZP4TY2.js');
16
+ await import('./cli_main-TEG3QTDP.js');
17
17
  }
@@ -1,10 +1,15 @@
1
1
  #!/usr/bin/env node
2
- import { isErrorResult, questlogVersion, resolveQuestlogDbPath } from './chunk-DKH623HU.js';
3
- import { questlogSkills } from './chunk-6KTQK3ZE.js';
4
- import { reviewQuestlogTool, validateInput, getQuestDetail, questlogSurfaceTools } from './chunk-UTUQPSGO.js';
2
+ import { questlogSkills } from './chunk-I2AMIXGP.js';
3
+ import { CliUsageError, renderCliHelp, parseCliArgs } from './chunk-GVD6A3RR.js';
4
+ import { isErrorResult, resolveQuestlogDbPath } from './chunk-IL2QRKZL.js';
5
+ import { reviewQuestlogTool, validateInput, getQuestDetail, questlogSurfaceTools } from './chunk-KOWIVBG6.js';
6
+ import { questlogVersion } from './chunk-RBGQK5WP.js';
7
+ import { listDeliveries } from './chunk-D6EGZCT7.js';
8
+ import './chunk-NBCCA6AX.js';
9
+ import { schemaRows } from './chunk-7KTRNXTU.js';
5
10
  import { minimalInput } from './chunk-QYSIJ2YN.js';
6
11
  import { isSea } from 'node:sea';
7
- import { readFileSync, existsSync } from 'node:fs';
12
+ import { existsSync } from 'node:fs';
8
13
  import { resolve } from 'node:path';
9
14
 
10
15
  var npmEntry = false;
@@ -15,7 +20,8 @@ function cliVersion(version, {
15
20
  sea = isSea,
16
21
  npm = npmEntry,
17
22
  platform = process.platform,
18
- arch = process.arch
23
+ arch = process.arch,
24
+ args = []
19
25
  } = {}) {
20
26
  let binary = false;
21
27
  try {
@@ -23,116 +29,17 @@ function cliVersion(version, {
23
29
  } catch {
24
30
  }
25
31
  const target = `${platform === "win32" ? "windows" : platform}-${arch}`;
32
+ const variant = binary ? "binary" : npm ? "npm" : "library";
33
+ if (args.includes("--json"))
34
+ return JSON.stringify({ name: "questlog", version, variant, target });
35
+ if (args.includes("--short")) return version;
26
36
  return `questlog ${version} (${binary ? `binary, ${target}` : npm ? "npm" : "library"})`;
27
37
  }
28
- var CliUsageError = class extends Error {
29
- exitCode = 2;
30
- };
31
- var keyOf = (name) => name.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
32
- function parseCliArgs(argv, spec) {
33
- const values = {};
34
- const positions = [];
35
- const flags = spec.flags ?? [];
36
- const convert = (raw, arg) => {
37
- let value = raw;
38
- if (raw.startsWith("@")) {
39
- try {
40
- value = readFileSync(raw.slice(1), "utf8");
41
- } catch {
42
- throw new CliUsageError(`Cannot read value file: ${raw.slice(1)}`);
43
- }
44
- }
45
- let result = value;
46
- if (arg.type === "number" || arg.type === "integer") {
47
- result = Number(value);
48
- if (!value.trim() || !Number.isFinite(result) || arg.type === "integer" && !Number.isInteger(result))
49
- throw new CliUsageError(`Expected ${arg.type} for ${arg.name}: ${value}`);
50
- } else if (arg.type === "boolean") {
51
- if (!["true", "false"].includes(value))
52
- throw new CliUsageError(`Expected boolean for ${arg.name}`);
53
- result = value === "true";
54
- } else if (arg.type === "json") {
55
- try {
56
- result = JSON.parse(value);
57
- } catch {
58
- throw new CliUsageError(`Invalid JSON for ${arg.name}`);
59
- }
60
- }
61
- if (arg.enum && !arg.enum.includes(result))
62
- throw new CliUsageError(`${arg.name} must be one of: ${arg.enum.join(", ")}`);
63
- return result;
64
- };
65
- let ended = false;
66
- for (let i = 0; i < argv.length; i++) {
67
- const token = argv[i];
68
- if (!ended && token === "--") {
69
- ended = true;
70
- continue;
71
- }
72
- if (ended || token === "-" || !token.startsWith("-")) {
73
- positions.push(token);
74
- continue;
75
- }
76
- const [flag, inline] = token.split(/=(.*)/s);
77
- const negative = flag?.startsWith("--no-");
78
- const name = flag?.replace(/^--?/, "").replace(/^no-/, "");
79
- const arg = flags.find(
80
- (entry) => entry.name === name || keyOf(entry.name) === name || entry.short === name
81
- );
82
- if (!arg) {
83
- const prefix = (name ?? "").slice(0, 3);
84
- const suggestion = prefix ? flags.find((entry) => entry.name.startsWith(prefix)) : void 0;
85
- throw new CliUsageError(
86
- `Unknown flag: ${flag}${suggestion ? `; did you mean --${suggestion.name}?` : ""}`
87
- );
88
- }
89
- if (negative && arg.type !== "boolean")
90
- throw new CliUsageError(`--no-${arg.name} requires a boolean flag`);
91
- let raw = inline;
92
- if (arg.type === "boolean" && raw === void 0) raw = negative ? "false" : "true";
93
- if (raw === void 0) {
94
- const next = argv[i + 1];
95
- if (next === void 0 || /^--[a-z]/i.test(next))
96
- throw new CliUsageError(`Missing value: --${arg.name}`);
97
- raw = next;
98
- i++;
99
- }
100
- const key = keyOf(arg.name);
101
- if (arg.type === "array") {
102
- const item = convert(raw, { ...arg, type: arg.itemType ?? "string" });
103
- values[key] = [...values[key] ?? [], item];
104
- } else values[key] = convert(raw, arg);
105
- }
106
- let index = 0;
107
- for (const arg of spec.positionals ?? []) {
108
- if (index >= positions.length) continue;
109
- values[keyOf(arg.name)] = arg.variadic ? positions.slice(index).map((value) => convert(value, { ...arg, type: arg.itemType ?? arg.type })) : convert(positions[index], arg);
110
- index = arg.variadic ? positions.length : index + 1;
111
- }
112
- if (index < positions.length)
113
- throw new CliUsageError(`Unexpected positional: ${positions[index]}`);
114
- for (const arg of [...flags, ...spec.positionals ?? []]) {
115
- if (arg.required && values[keyOf(arg.name)] === void 0)
116
- throw new CliUsageError(`Missing required argument: ${arg.name}`);
117
- }
118
- return values;
119
- }
120
- function renderCliHelp(spec) {
121
- const describe = (arg) => `${arg.type}${arg.enum ? ` (${arg.enum.join("|")})` : ""}${arg.required ? " required" : ""} ${arg.description ?? ""}`;
122
- return `Usage: ${spec.command} ${(spec.positionals ?? []).map((arg) => `<${arg.name}${arg.variadic ? "..." : ""}>`).join(" ")} [flags]
123
- ${spec.description ?? ""}
124
-
125
- Positionals:
126
- ${(spec.positionals ?? []).map((arg) => ` ${arg.name} ${describe(arg)}`).join("\n")}
127
- Flags:
128
- ${(spec.flags ?? []).map((arg) => ` --${arg.name}${arg.short ? `, -${arg.short}` : ""} ${describe(arg)}`).join("\n")}
129
- `;
130
- }
131
38
 
132
39
  // src/surface/plain_dates.ts
133
40
  function parsePlainDue(raw, now = Date.now()) {
134
41
  const invalid = () => new CliUsageError(
135
- `Invalid --due "${raw}". Use YYYY-MM-DD, DD.MM.YYYY, today, tomorrow, +3d or an ISO date-time.`
42
+ `Invalid --due "${raw}". Use YYYY-MM-DD, DD.MM.YYYY, today, tomorrow, +Nd (for example +3d), integer epoch milliseconds, or YYYY-MM-DDTHH:mm[:ss[.fraction]][Z|\xB1HH:mm] (no zone means local time).`
136
43
  );
137
44
  if (/^-?\d+$/.test(raw)) {
138
45
  const value = Number(raw);
@@ -158,7 +65,7 @@ function parsePlainDue(raw, now = Date.now()) {
158
65
  );
159
66
  date.setHours(23, 59, 59, 999);
160
67
  } else {
161
- if (!/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}(?::\d{2}(?:\.\d+)?)?(?:Z|[+-]\d{2}:\d{2})$/.test(raw))
68
+ if (!/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}(?::\d{2}(?:\.\d+)?)?(?:Z|[+-]\d{2}:\d{2})?$/.test(raw))
162
69
  throw invalid();
163
70
  parsePlainDue(raw.slice(0, 10), now);
164
71
  date = new Date(raw);
@@ -210,6 +117,9 @@ function renderQuestlogPlainResult(result, db, values) {
210
117
  Missing: ${r.clarification.missing.map((name) => `--${name}`).join(", ")}
211
118
  ` : `${r.summary}
212
119
  ${r.error.message}
120
+ `;
121
+ if (r.data.deliveries && !r.data.item)
122
+ return `${r.data.deliveries.map((d) => `${d.id} target ${d.targetId} ${d.status} attempts ${d.attempts} HTTP ${d.httpStatus ?? "-"} next ${new Date(d.nextAttemptAt).toISOString()} events ${d.events.map((e) => e.id).join(", ")}`).join("\n") || "No deliveries"}
213
123
  `;
214
124
  if (r.data.items) {
215
125
  const items = values.limit === void 0 ? r.data.items : r.data.items.slice(0, Math.max(0, Number(values.limit)));
@@ -235,6 +145,11 @@ ${r.error.message}
235
145
  ]);
236
146
  if (detail.outcome != null) fields.push(["outcome", detail.outcome]);
237
147
  if (detail.questlineId != null) fields.push(["questline", detail.questlineId]);
148
+ for (const delivery of listDeliveries(db, { questId: detail.id }))
149
+ fields.push([
150
+ "delivery",
151
+ `${delivery.id} target ${delivery.targetId} ${delivery.status} attempts ${delivery.attempts}`
152
+ ]);
238
153
  const width = Math.max(...fields.map(([label]) => label.length));
239
154
  return `${fields.map(([label, value]) => `${label.padEnd(width)}: ${String(value).replace(/[\r\n]+/g, " ")}`).join("\n")}
240
155
  `;
@@ -258,6 +173,34 @@ function questlogPlainCommands() {
258
173
  const id = { name: "id", type: "integer", required: true };
259
174
  const views = reviewQuestlogTool.inputSchema.properties?.view?.enum ?? [];
260
175
  const commands = [
176
+ {
177
+ spec: {
178
+ command: "tick",
179
+ description: "Detect events and deliver outbound webhooks once.",
180
+ flags: [{ name: "dry-run", type: "boolean" }]
181
+ },
182
+ tool: "manage_hooks",
183
+ input: (v) => ({ action: "tick", dry_run: v.dryRun ?? false }),
184
+ render: (r, _db, values) => {
185
+ const result = r;
186
+ if (!result.ok || !result.data) return `${result.summary}
187
+ `;
188
+ const d = result.data;
189
+ return `${d.events.length} events found; ${d.sent} deliveries sent, ${d.queued} queued, ${d.retrying} retrying, ${d.failed} failed.
190
+ ${values.dryRun ? `${JSON.stringify({ events: d.events, batches: d.batches }, null, 2)}
191
+ ` : ""}`;
192
+ }
193
+ },
194
+ {
195
+ spec: {
196
+ command: "review",
197
+ description: "Review an operational view.",
198
+ positionals: [{ name: "view", type: "string", required: true, enum: views }]
199
+ },
200
+ tool: "review_questlog",
201
+ input: (v) => ({ view: v.view }),
202
+ render: renderQuestlogPlainResult
203
+ },
261
204
  {
262
205
  spec: {
263
206
  command: "add",
@@ -282,7 +225,10 @@ function questlogPlainCommands() {
282
225
  "give the title once: either as the first argument or with --title"
283
226
  );
284
227
  const title = v.positionalTitle ?? v.title;
285
- if (title === void 0) throw new CliUsageError("Missing required argument: title");
228
+ if (title === void 0)
229
+ throw new CliUsageError(
230
+ 'Missing required argument: title. Example: questlog add "Write the report" --due tomorrow'
231
+ );
286
232
  const quest = {
287
233
  title,
288
234
  objective: v.objective ?? title
@@ -370,13 +316,13 @@ function selectedDbPath(options) {
370
316
  var questlogCliOptions = {
371
317
  bin: "ghostpaw-questlog",
372
318
  version: questlogVersion,
373
- versionLine: () => cliVersion(questlogVersion),
319
+ versionLine: (args) => cliVersion(questlogVersion, { args }),
374
320
  description: "Local task and calendar library",
375
321
  tools: questlogSurfaceTools,
376
322
  describeDb: (options) => `Database: ${selectedDbPath(options)}
377
323
  Use --db <path> or GHOSTPAW_QUESTLOG_DB to override the default ~/.ghostpaw/questlog.db.`,
378
324
  openContext: async (options) => {
379
- const { openQuestlogDb } = await import('./open_questlog_db-SH3DAU4C.js');
325
+ const { openQuestlogDb } = await import('./open_questlog_db-6NS6BVBC.js');
380
326
  const path = selectedDbPath(options);
381
327
  const created = path !== ":memory:" && !existsSync(path);
382
328
  const db = await openQuestlogDb(path);
@@ -386,15 +332,58 @@ Use --db <path> or GHOSTPAW_QUESTLOG_DB to override the default ~/.ghostpaw/ques
386
332
  },
387
333
  closeContext: (db) => db.close(),
388
334
  commands: {
335
+ names: ["hook", "watch"],
336
+ run: async (command, args, options, io) => {
337
+ if (command === "show" && args[0]?.startsWith("d_")) {
338
+ const { listDeliveries: listDeliveries2 } = await import('./list_deliveries-WJEJUZIQ.js');
339
+ const { listHooks } = await import('./list_hooks-C5CAA2C7.js');
340
+ const { textOutput } = await import('./text_output-X7F3K23Q.js');
341
+ if (args.includes("--help") || args.includes("-h")) {
342
+ io.stdout.write("questlog show <delivery-id> [--json]\n");
343
+ return 0;
344
+ }
345
+ const { openQuestlogDb } = await import('./open_questlog_db-6NS6BVBC.js');
346
+ const db = await openQuestlogDb(selectedDbPath(options));
347
+ try {
348
+ const delivery = listDeliveries2(db, { includeDelivered: true }).find(
349
+ (d) => d.id === args[0]
350
+ );
351
+ if (!delivery) {
352
+ io.stderr.write(`Delivery ${args[0]} not found
353
+ `);
354
+ return 1;
355
+ }
356
+ const target = listHooks(db).find((h) => h.id === delivery.targetId);
357
+ const result = {
358
+ ...delivery,
359
+ target: target ?? null,
360
+ eventIds: delivery.events.map((e) => e.id)
361
+ };
362
+ io.stdout.write(
363
+ `${args.includes("--json") ? JSON.stringify(result, null, 2) : textOutput(result)}
364
+ `
365
+ );
366
+ return 0;
367
+ } finally {
368
+ db.close();
369
+ }
370
+ }
371
+ if (command !== "hook" && command !== "watch") return void 0;
372
+ const { runHookCommand } = await import('./run_hook_command-BOENYNDT.js');
373
+ return runHookCommand(command, args, io, async () => {
374
+ const { openQuestlogDb } = await import('./open_questlog_db-6NS6BVBC.js');
375
+ return openQuestlogDb(selectedDbPath(options));
376
+ });
377
+ },
389
378
  plain: questlogPlainCommands(),
390
379
  guides: questlogSkills,
391
380
  mcp: async (options) => {
392
- const { serveQuestlogMcp } = await import('./serve_questlog_mcp-KFUYJB2P.js');
381
+ const { serveQuestlogMcp } = await import('./serve_questlog_mcp-X524X34F.js');
393
382
  await serveQuestlogMcp(options.db === void 0 ? {} : { dbPath: options.db });
394
383
  },
395
384
  skill: async () => {
396
- const { renderSkillMarkdown } = await import('./skill_markdown-HXYQ3K6U.js');
397
- const { questlogSkillSpec } = await import('./questlog_skill_spec-EM6DGGVN.js');
385
+ const { renderSkillMarkdown } = await import('./skill_markdown-EGKYZ3W2.js');
386
+ const { questlogSkillSpec } = await import('./questlog_skill_spec-ILTCTRYS.js');
398
387
  return renderSkillMarkdown(questlogSkillSpec);
399
388
  }
400
389
  }
@@ -463,6 +452,12 @@ async function runToolCli(argv, options) {
463
452
  i--;
464
453
  }
465
454
  }
455
+ const versionArgs = args.filter((arg) => arg !== "--short" && arg !== "--json");
456
+ if (versionArgs[0] === "--version" || versionArgs[0] === "-v") {
457
+ stdout.write(`${options.versionLine?.(args) ?? options.version}
458
+ `);
459
+ return 0;
460
+ }
466
461
  const ctxOptions = db === void 0 ? {} : { db };
467
462
  const command = args.shift() ?? "help";
468
463
  const normalize = (name2) => name2.replace(/[-_]/g, "").toLowerCase();
@@ -472,8 +467,14 @@ async function runToolCli(argv, options) {
472
467
  ${options.description}
473
468
 
474
469
  Usage: ${options.bin} [--db path] <command>
475
- Commands: ${options.commands?.plain?.map((entry) => entry.spec.command).join(", ") ?? ""}${options.commands?.plain?.length ? ", " : ""}tools [--json], describe <tool>, call <tool> [JSON|-], <tool> --parameter value, mcp, skill
470
+ Commands: ${[
471
+ ...options.commands?.plain?.map((entry) => entry.spec.command) ?? [],
472
+ ...options.commands?.names ?? []
473
+ ].map((name2) => `${name2}, `).join(
474
+ ""
475
+ )}tools [--json], describe <tool>, call <tool> [JSON|-], <tool> --parameter value, mcp, skill
476
476
  Options: --help, --version, --compact, --db <path>
477
+ Version formats: --version --short, --version --json
477
478
  ${options.describeDb ? `${options.describeDb(ctxOptions)}
478
479
  ` : ""}
479
480
  ${options.tools.map((tool2) => `${tool2.name} ${tool2.description}`).join("\n")}
@@ -481,9 +482,11 @@ ${options.tools.map((tool2) => `${tool2.name} ${tool2.description}`).join("\n")
481
482
  );
482
483
  return 0;
483
484
  }
484
- if (["--version", "-v"].includes(command)) {
485
- stdout.write(`${options.versionLine?.() ?? options.version}
486
- `);
485
+ if (["tools", "call", "describe"].includes(command) && args.length === 1 && ["--help", "-h"].includes(args[0] ?? "")) {
486
+ stdout.write(
487
+ `Usage: ${options.bin} ${command}${command === "tools" ? " [--json]" : " <tool> [--help]"}
488
+ `
489
+ );
487
490
  return 0;
488
491
  }
489
492
  if (command === "tools") {
@@ -500,16 +503,21 @@ ${options.tools.map((tool2) => `${tool2.name} ${tool2.description}`).join("\n")
500
503
  }
501
504
  if (command === "skill") {
502
505
  if (!options.commands?.skill) return fail("skill command unavailable");
503
- if (args.length > 1) return fail(`Usage: ${options.bin} skill [--list | <guide> | --help]`);
504
- const guide = args[0];
506
+ if (args.length > 1 && !args.some((arg) => ["--help", "-h"].includes(arg)))
507
+ return fail(`Usage: ${options.bin} skill [--short | --list | <guide> | --help]`);
508
+ const guide = args.some((arg) => ["--help", "-h"].includes(arg)) ? "--help" : args[0];
505
509
  if (guide === "--help" || guide === "-h") {
506
510
  stdout.write(
507
511
  `Usage:
508
512
  ${options.bin} skill
509
513
  ${options.bin} skill --list
514
+ ${options.bin} skill --short
510
515
  ${options.bin} skill <guide>
511
516
  `
512
517
  );
518
+ } else if (guide === "--short") {
519
+ const full = await options.commands.skill();
520
+ stdout.write(full.split("## Appendix:")[0]?.replace(/^---[\s\S]*?---\n+/, "") ?? full);
513
521
  } else if (guide === "--list") {
514
522
  stdout.write(
515
523
  `${(options.commands.guides ?? []).map((entry) => `${entry.name} ${entry.description.replace(/\s+/g, " ")}`).join("\n")}
@@ -522,11 +530,22 @@ ${options.tools.map((tool2) => `${tool2.name} ${tool2.description}`).join("\n")
522
530
  } else stdout.write(await options.commands.skill());
523
531
  return 0;
524
532
  }
533
+ if (command === "mcp" && (args.includes("--help") || args.includes("-h"))) {
534
+ stdout.write(`${options.bin} mcp [--db path]
535
+ `);
536
+ return 0;
537
+ }
525
538
  if (command === "mcp") {
526
539
  if (!options.commands?.mcp) return fail("mcp command unavailable");
527
540
  await options.commands.mcp(ctxOptions);
528
541
  return 0;
529
542
  }
543
+ const custom = await options.commands?.run?.(command, args, ctxOptions, {
544
+ stdin,
545
+ stdout,
546
+ stderr
547
+ });
548
+ if (custom !== void 0) return custom;
530
549
  const plain = options.commands?.plain?.find((entry) => entry.spec.command === command);
531
550
  let plainValues = {};
532
551
  if (plain) {
@@ -556,6 +575,7 @@ ${options.tools.map((tool2) => `${tool2.name} ${tool2.description}`).join("\n")
556
575
  if (command === "call" || command === "describe") return fail(`Unknown tool: ${name ?? ""}`);
557
576
  const suggestion = suggestCommand(command, [
558
577
  ...options.commands?.plain?.map((entry) => entry.spec.command) ?? [],
578
+ ...options.commands?.names ?? [],
559
579
  "help",
560
580
  "tools",
561
581
  "describe",
@@ -566,26 +586,25 @@ ${options.tools.map((tool2) => `${tool2.name} ${tool2.description}`).join("\n")
566
586
  ]);
567
587
  return fail(`Unknown command: ${command}.${suggestion ? ` Did you mean ${suggestion}?` : ""}`);
568
588
  }
569
- if (command === "describe") {
589
+ if (command === "describe" || args.includes("--help") || args.includes("-h")) {
570
590
  stdout.write(
571
591
  `${tool.name}
592
+ Usage: ${options.bin} ${tool.name} --parameter value [--json]
572
593
  ${tool.description}
573
594
  Use when: ${tool.whenToUse ?? ""}
574
595
  Not for: ${tool.whenNotToUse ?? ""}
575
596
  Read only: ${tool.readOnly ?? false}
576
597
 
577
598
  Parameter | Type | Required | Description
578
- ${Object.entries(
579
- tool.inputSchema.properties ?? {}
580
- ).map(
581
- ([key, schema]) => `${key} | ${schema.type ?? "oneOf"} | ${tool.inputSchema.required?.includes(key) ?? false} | ${schema.description ?? ""}`
582
- ).join("\n")}
599
+ ${schemaRows(tool.inputSchema).join("\n")}
583
600
 
584
601
  ${JSON.stringify(tool.inputSchema, null, 2)}
585
602
  `
586
603
  );
587
604
  return 0;
588
605
  }
606
+ const json = args.includes("--json");
607
+ if (!plain && json) args.splice(args.indexOf("--json"), 1);
589
608
  let input = {};
590
609
  try {
591
610
  const readStdin = async () => {