@ghostpaw/questlog 0.7.0 → 0.9.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.
@@ -0,0 +1,626 @@
1
+ #!/usr/bin/env node
2
+ import { validateInput, isErrorResult, questlogVersion, resolveQuestlogDbPath } from './chunk-RUSE7ZFF.js';
3
+ import { reviewQuestlogTool, getQuestDetail, questlogSurfaceTools } from './chunk-3Y5F3SMB.js';
4
+ import { readFileSync, existsSync } from 'node:fs';
5
+ import { resolve } from 'node:path';
6
+
7
+ var CliUsageError = class extends Error {
8
+ exitCode = 2;
9
+ };
10
+ var keyOf = (name) => name.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
11
+ function parseCliArgs(argv, spec) {
12
+ const values = {};
13
+ const positions = [];
14
+ const flags = spec.flags ?? [];
15
+ const convert = (raw, arg) => {
16
+ let value = raw;
17
+ if (raw.startsWith("@")) {
18
+ try {
19
+ value = readFileSync(raw.slice(1), "utf8");
20
+ } catch {
21
+ throw new CliUsageError(`Cannot read value file: ${raw.slice(1)}`);
22
+ }
23
+ }
24
+ let result = value;
25
+ if (arg.type === "number" || arg.type === "integer") {
26
+ result = Number(value);
27
+ if (!value.trim() || !Number.isFinite(result) || arg.type === "integer" && !Number.isInteger(result))
28
+ throw new CliUsageError(`Expected ${arg.type} for ${arg.name}: ${value}`);
29
+ } else if (arg.type === "boolean") {
30
+ if (!["true", "false"].includes(value))
31
+ throw new CliUsageError(`Expected boolean for ${arg.name}`);
32
+ result = value === "true";
33
+ } else if (arg.type === "json") {
34
+ try {
35
+ result = JSON.parse(value);
36
+ } catch {
37
+ throw new CliUsageError(`Invalid JSON for ${arg.name}`);
38
+ }
39
+ }
40
+ if (arg.enum && !arg.enum.includes(result))
41
+ throw new CliUsageError(`${arg.name} must be one of: ${arg.enum.join(", ")}`);
42
+ return result;
43
+ };
44
+ let ended = false;
45
+ for (let i = 0; i < argv.length; i++) {
46
+ const token = argv[i];
47
+ if (!ended && token === "--") {
48
+ ended = true;
49
+ continue;
50
+ }
51
+ if (ended || token === "-" || !token.startsWith("-")) {
52
+ positions.push(token);
53
+ continue;
54
+ }
55
+ const [flag, inline] = token.split(/=(.*)/s);
56
+ const negative = flag?.startsWith("--no-");
57
+ const name = flag?.replace(/^--?/, "").replace(/^no-/, "");
58
+ const arg = flags.find(
59
+ (entry) => entry.name === name || keyOf(entry.name) === name || entry.short === name
60
+ );
61
+ if (!arg) {
62
+ const prefix = (name ?? "").slice(0, 3);
63
+ const suggestion = prefix ? flags.find((entry) => entry.name.startsWith(prefix)) : void 0;
64
+ throw new CliUsageError(
65
+ `Unknown flag: ${flag}${suggestion ? `; did you mean --${suggestion.name}?` : ""}`
66
+ );
67
+ }
68
+ if (negative && arg.type !== "boolean")
69
+ throw new CliUsageError(`--no-${arg.name} requires a boolean flag`);
70
+ let raw = inline;
71
+ if (arg.type === "boolean" && raw === void 0) raw = negative ? "false" : "true";
72
+ if (raw === void 0) {
73
+ const next = argv[i + 1];
74
+ if (next === void 0 || next.startsWith("--"))
75
+ throw new CliUsageError(`Missing value: --${arg.name}`);
76
+ raw = next;
77
+ i++;
78
+ }
79
+ const key = keyOf(arg.name);
80
+ if (arg.type === "array") {
81
+ const item = convert(raw, { ...arg, type: arg.itemType ?? "string" });
82
+ values[key] = [...values[key] ?? [], item];
83
+ } else values[key] = convert(raw, arg);
84
+ }
85
+ let index = 0;
86
+ for (const arg of spec.positionals ?? []) {
87
+ if (index >= positions.length) continue;
88
+ values[keyOf(arg.name)] = arg.variadic ? positions.slice(index).map((value) => convert(value, { ...arg, type: arg.itemType ?? arg.type })) : convert(positions[index], arg);
89
+ index = arg.variadic ? positions.length : index + 1;
90
+ }
91
+ if (index < positions.length)
92
+ throw new CliUsageError(`Unexpected positional: ${positions[index]}`);
93
+ for (const arg of [...flags, ...spec.positionals ?? []]) {
94
+ if (arg.required && values[keyOf(arg.name)] === void 0)
95
+ throw new CliUsageError(`Missing required argument: ${arg.name}`);
96
+ }
97
+ return values;
98
+ }
99
+ function renderCliHelp(spec) {
100
+ const describe = (arg) => `${arg.type}${arg.enum ? ` (${arg.enum.join("|")})` : ""}${arg.required ? " required" : ""} ${arg.description ?? ""}`;
101
+ return `Usage: ${spec.command} ${(spec.positionals ?? []).map((arg) => `<${arg.name}${arg.variadic ? "..." : ""}>`).join(" ")} [flags]
102
+ ${spec.description ?? ""}
103
+
104
+ Positionals:
105
+ ${(spec.positionals ?? []).map((arg) => ` ${arg.name} ${describe(arg)}`).join("\n")}
106
+ Flags:
107
+ ${(spec.flags ?? []).map((arg) => ` --${arg.name}${arg.short ? `, -${arg.short}` : ""} ${describe(arg)}`).join("\n")}
108
+ `;
109
+ }
110
+
111
+ // src/surface/plain_dates.ts
112
+ function parsePlainDue(raw, now = Date.now()) {
113
+ const invalid = () => new CliUsageError(
114
+ `Invalid --due "${raw}". Use YYYY-MM-DD, DD.MM.YYYY, today, tomorrow, +3d or an ISO date-time.`
115
+ );
116
+ if (/^-?\d+$/.test(raw)) {
117
+ const value = Number(raw);
118
+ if (Number.isSafeInteger(value) && Number.isFinite(new Date(value).getTime())) return value;
119
+ throw invalid();
120
+ }
121
+ const iso = /^(\d{4})-(\d{2})-(\d{2})$/.exec(raw);
122
+ const european = /^(\d{2})\.(\d{2})\.(\d{4})$/.exec(raw);
123
+ let date;
124
+ if (iso || european) {
125
+ const year = Number(iso?.[1] ?? european?.[3]);
126
+ const month = Number(iso?.[2] ?? european?.[2]);
127
+ const day = Number(iso?.[3] ?? european?.[1]);
128
+ date = /* @__PURE__ */ new Date(0);
129
+ date.setFullYear(year, month - 1, day);
130
+ date.setHours(23, 59, 59, 999);
131
+ if (date.getFullYear() !== year || date.getMonth() !== month - 1 || date.getDate() !== day)
132
+ throw invalid();
133
+ } else if (raw === "today" || raw === "tomorrow" || /^\+\d+d$/.test(raw)) {
134
+ date = new Date(now);
135
+ date.setDate(
136
+ date.getDate() + (raw === "today" ? 0 : raw === "tomorrow" ? 1 : Number(raw.slice(1, -1)))
137
+ );
138
+ date.setHours(23, 59, 59, 999);
139
+ } else {
140
+ if (!/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}(?::\d{2}(?:\.\d+)?)?(?:Z|[+-]\d{2}:\d{2})$/.test(raw))
141
+ throw invalid();
142
+ parsePlainDue(raw.slice(0, 10), now);
143
+ date = new Date(raw);
144
+ }
145
+ if (!Number.isFinite(date.getTime())) throw invalid();
146
+ return date.getTime();
147
+ }
148
+ function formatPlainDate(value, full = false, now = Date.now()) {
149
+ const date = new Date(value);
150
+ const month = [
151
+ "Jan",
152
+ "Feb",
153
+ "Mar",
154
+ "Apr",
155
+ "May",
156
+ "Jun",
157
+ "Jul",
158
+ "Aug",
159
+ "Sep",
160
+ "Oct",
161
+ "Nov",
162
+ "Dec"
163
+ ][date.getMonth()];
164
+ const year = full || date.getFullYear() !== new Date(now).getFullYear() ? ` ${date.getFullYear()}` : "";
165
+ const endOfDay = date.getHours() === 23 && date.getMinutes() === 59 && date.getSeconds() === 59 && date.getMilliseconds() === 999;
166
+ const time = endOfDay ? "" : ` ${String(date.getHours()).padStart(2, "0")}:${String(date.getMinutes()).padStart(2, "0")}`;
167
+ return `${date.getDate()} ${month}${year}${time}`;
168
+ }
169
+
170
+ // src/surface/render_questlog_plain_result.ts
171
+ function renderQuestlogPlainResult(result, db, values) {
172
+ const r = result;
173
+ const line = (item) => {
174
+ const detail = item.kind === "quest" ? getQuestDetail(db, item.id) : item;
175
+ const tags = "tagNames" in detail ? detail.tagNames : [];
176
+ return [
177
+ item.id,
178
+ detail.state ?? detail.markerId ?? "-",
179
+ detail.title,
180
+ ...tags.map((tag) => `#${tag}`),
181
+ ..."effectiveDueAt" in detail && detail.effectiveDueAt != null ? [
182
+ `${detail.resolvedAt == null && detail.effectiveDueAt < Date.now() ? "!overdue " : ""}due ${formatPlainDate(detail.effectiveDueAt)}`
183
+ ] : [],
184
+ ..."outcome" in detail && detail.outcome != null ? [`-> ${detail.outcome}`] : []
185
+ ].filter((v) => v != null).join(" ").replace(/[\r\n]+/g, " ");
186
+ };
187
+ if (!r.ok)
188
+ return r.outcome === "needs_clarification" ? `${r.clarification.question}
189
+ Missing: ${r.clarification.missing.map((name) => `--${name}`).join(", ")}
190
+ ` : `${r.summary}
191
+ ${r.error.message}
192
+ `;
193
+ if (r.data.items) {
194
+ const items = values.limit === void 0 ? r.data.items : r.data.items.slice(0, Math.max(0, Number(values.limit)));
195
+ return `${items.length ? items.map(line).join("\n") : r.summary}
196
+ `;
197
+ }
198
+ if (r.data.item) {
199
+ const detail = getQuestDetail(db, r.data.item.detail.id);
200
+ const fields = [
201
+ ["id", detail.id],
202
+ ["title", detail.title],
203
+ ["objective", detail.objective],
204
+ ["state", detail.state],
205
+ ["tags", detail.tagNames.map((tag) => `#${tag}`).join(" ")],
206
+ ["due", detail.effectiveDueAt == null ? "-" : formatPlainDate(detail.effectiveDueAt, true)],
207
+ ["created", formatPlainDate(detail.createdAt, true)]
208
+ ];
209
+ if (detail.startedAt != null) fields.push(["started", formatPlainDate(detail.startedAt, true)]);
210
+ if (detail.resolvedAt != null)
211
+ fields.push([
212
+ detail.success ? "finished" : "abandoned",
213
+ formatPlainDate(detail.resolvedAt, true)
214
+ ]);
215
+ if (detail.outcome != null) fields.push(["outcome", detail.outcome]);
216
+ if (detail.questlineId != null) fields.push(["questline", detail.questlineId]);
217
+ const width = Math.max(...fields.map(([label]) => label.length));
218
+ return `${fields.map(([label, value]) => `${label.padEnd(width)}: ${String(value).replace(/[\r\n]+/g, " ")}`).join("\n")}
219
+ `;
220
+ }
221
+ return `${r.summary.replace(/[\r\n]+/g, " ")}${r.data.primary ? ` (id ${r.data.primary.id})` : ""}
222
+ `;
223
+ }
224
+
225
+ // src/surface/questlog_plain_commands.ts
226
+ function questlogPlainCommands() {
227
+ const json = {
228
+ name: "json",
229
+ type: "boolean",
230
+ description: "Print the full tool result as JSON."
231
+ };
232
+ const limit = {
233
+ name: "limit",
234
+ type: "integer",
235
+ description: "Maximum displayed items (list text) or search matches."
236
+ };
237
+ const id = { name: "id", type: "integer", required: true };
238
+ const views = reviewQuestlogTool.inputSchema.properties?.view?.enum ?? [];
239
+ const commands = [
240
+ {
241
+ spec: {
242
+ command: "add",
243
+ description: "Create a quest. Objective defaults to the title.",
244
+ flags: [
245
+ { name: "title", type: "string", required: true },
246
+ { name: "tag", type: "array" },
247
+ { name: "objective", type: "string" },
248
+ {
249
+ name: "due",
250
+ type: "string",
251
+ description: "Local date, today, tomorrow, +3d, ISO date-time or epoch milliseconds."
252
+ },
253
+ { name: "questline", type: "integer" }
254
+ ]
255
+ },
256
+ tool: "shape_work",
257
+ input: (v) => {
258
+ const quest = {
259
+ title: v.title,
260
+ objective: v.objective ?? v.title
261
+ };
262
+ if (v.tag !== void 0)
263
+ quest.tags = v.tag.flatMap(
264
+ (tag) => tag.split(",").map((part) => part.trim()).filter(Boolean)
265
+ );
266
+ if (v.questline !== void 0) quest.questlineId = v.questline;
267
+ if (v.due !== void 0) {
268
+ quest.dueAt = parsePlainDue(String(v.due));
269
+ }
270
+ return { action: "create_quest", quest };
271
+ },
272
+ render: renderQuestlogPlainResult
273
+ },
274
+ {
275
+ spec: {
276
+ command: "list",
277
+ description: "Review quests and other work. Limit caps text output; JSON contains the full tool result.",
278
+ flags: [{ name: "view", type: "string", enum: ["available", ...views] }, limit]
279
+ },
280
+ tool: "review_questlog",
281
+ input: (v) => ({ view: !v.view || v.view === "available" ? "quests.available" : v.view }),
282
+ render: renderQuestlogPlainResult
283
+ },
284
+ {
285
+ spec: {
286
+ command: "done",
287
+ description: "Finish a quest with its outcome.",
288
+ positionals: [id],
289
+ flags: [
290
+ {
291
+ name: "outcome",
292
+ type: "string",
293
+ description: "What the finished quest produced; asked for when omitted."
294
+ }
295
+ ]
296
+ },
297
+ tool: "run_quest",
298
+ input: (v) => ({
299
+ action: "finish",
300
+ questId: v.id,
301
+ ...v.outcome === void 0 ? {} : { outcome: v.outcome }
302
+ }),
303
+ render: renderQuestlogPlainResult
304
+ },
305
+ {
306
+ spec: {
307
+ command: "search",
308
+ description: "Search quests and rumors.",
309
+ positionals: [{ name: "query", type: "string", variadic: true, required: true }],
310
+ flags: [limit]
311
+ },
312
+ tool: "search_questlog",
313
+ input: (v) => ({
314
+ query: v.query.join(" "),
315
+ ...v.limit === void 0 ? {} : { limit: v.limit }
316
+ }),
317
+ render: renderQuestlogPlainResult
318
+ },
319
+ {
320
+ spec: { command: "show", description: "Inspect a quest by id.", positionals: [id] },
321
+ tool: "inspect_questlog_item",
322
+ input: (v) => ({ target: { kind: "quest", id: v.id }, detailLevel: "full" }),
323
+ render: renderQuestlogPlainResult
324
+ }
325
+ ];
326
+ return commands.map((command) => ({
327
+ ...command,
328
+ spec: { ...command.spec, flags: [...command.spec.flags ?? [], json] },
329
+ input: (values) => {
330
+ if (values.limit !== void 0 && Number(values.limit) < 1)
331
+ throw new Error("Limit must be at least 1");
332
+ return command.input(values);
333
+ }
334
+ }));
335
+ }
336
+
337
+ // src/surface/questlog_cli_options.ts
338
+ function selectedDbPath(options) {
339
+ const selected = resolveQuestlogDbPath(options.db === void 0 ? {} : { flag: options.db });
340
+ return selected === ":memory:" ? selected : resolve(selected);
341
+ }
342
+ var questlogCliOptions = {
343
+ bin: "ghostpaw-questlog",
344
+ version: questlogVersion,
345
+ description: "Local task and calendar library",
346
+ tools: questlogSurfaceTools,
347
+ describeDb: (options) => `Database: ${selectedDbPath(options)}
348
+ Use --db <path> or GHOSTPAW_QUESTLOG_DB to override the default ~/.ghostpaw/questlog.db.`,
349
+ openContext: async (options) => {
350
+ const { openQuestlogDb } = await import('./open_questlog_db-SH3DAU4C.js');
351
+ const path = selectedDbPath(options);
352
+ const created = path !== ":memory:" && !existsSync(path);
353
+ const db = await openQuestlogDb(path);
354
+ if (created) process.stderr.write(`created ${path}
355
+ `);
356
+ return db;
357
+ },
358
+ closeContext: (db) => db.close(),
359
+ commands: {
360
+ plain: questlogPlainCommands(),
361
+ mcp: async (options) => {
362
+ const { serveQuestlogMcp } = await import('./serve_questlog_mcp-J4ZHNEOT.js');
363
+ await serveQuestlogMcp(options.db === void 0 ? {} : { dbPath: options.db });
364
+ },
365
+ skill: async () => {
366
+ const { renderSkillMarkdown } = await import('./skill_markdown-7HCMSJYR.js');
367
+ const { questlogSkillSpec } = await import('./questlog_skill_spec-JLZH3JL2.js');
368
+ return renderSkillMarkdown(questlogSkillSpec);
369
+ }
370
+ }
371
+ };
372
+
373
+ // src/surfaces/cli_output.ts
374
+ function enrichPlainJson(value) {
375
+ if (Array.isArray(value)) return value.map(enrichPlainJson);
376
+ if (value === null || typeof value !== "object") return value;
377
+ const result = {};
378
+ for (const [key, entry] of Object.entries(value)) {
379
+ result[key] = enrichPlainJson(entry);
380
+ if (key.endsWith("At") && typeof entry === "number" && Number.isFinite(new Date(entry).getTime()))
381
+ result[`${key}Iso`] = new Date(entry).toISOString();
382
+ }
383
+ return result;
384
+ }
385
+ function suggestCommand(input, names) {
386
+ let best;
387
+ let distance = 3;
388
+ for (const name of names) {
389
+ let row = Array.from({ length: name.length + 1 }, (_, i) => i);
390
+ for (let i = 0; i < input.length; i++) {
391
+ const next = [i + 1];
392
+ for (let j = 0; j < name.length; j++)
393
+ next.push(
394
+ Math.min(
395
+ (next[j] ?? 0) + 1,
396
+ (row[j + 1] ?? 0) + 1,
397
+ (row[j] ?? 0) + (input[i] === name[j] ? 0 : 1)
398
+ )
399
+ );
400
+ row = next;
401
+ }
402
+ const current = row[name.length] ?? 3;
403
+ if (current < distance) {
404
+ best = name;
405
+ distance = current;
406
+ }
407
+ }
408
+ return best;
409
+ }
410
+
411
+ // src/surfaces/cli.ts
412
+ async function runToolCli(argv, options) {
413
+ const stdout = options.stdout ?? process.stdout;
414
+ const stderr = options.stderr ?? process.stderr;
415
+ const stdin = options.stdin ?? process.stdin;
416
+ const args = [...argv];
417
+ let db;
418
+ let compact = false;
419
+ const fail = (message, code = 2) => {
420
+ stderr.write(`${message}
421
+ `);
422
+ return code;
423
+ };
424
+ for (let i = 0; i < args.length; i++) {
425
+ if (args[i] === "--db") {
426
+ if (!args[i + 1] || args[i + 1]?.startsWith("--")) return fail("--db requires a path");
427
+ db = args[i + 1];
428
+ args.splice(i, 2);
429
+ i--;
430
+ } else if (args[i] === "--compact") {
431
+ compact = true;
432
+ args.splice(i, 1);
433
+ i--;
434
+ }
435
+ }
436
+ const ctxOptions = db === void 0 ? {} : { db };
437
+ const command = args.shift() ?? "help";
438
+ const normalize = (name2) => name2.replace(/[-_]/g, "").toLowerCase();
439
+ if (["help", "--help", "-h"].includes(command)) {
440
+ stdout.write(
441
+ `${options.bin} ${options.version}
442
+ ${options.description}
443
+
444
+ Usage: ${options.bin} [--db path] <command>
445
+ 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
446
+ Options: --help, --version, --compact, --db <path>
447
+ ${options.describeDb ? `${options.describeDb(ctxOptions)}
448
+ ` : ""}
449
+ ${options.tools.map((tool2) => `${tool2.name} ${tool2.description}`).join("\n")}
450
+ `
451
+ );
452
+ return 0;
453
+ }
454
+ if (["--version", "-v"].includes(command)) {
455
+ stdout.write(`${options.version}
456
+ `);
457
+ return 0;
458
+ }
459
+ if (command === "tools") {
460
+ stdout.write(
461
+ args.includes("--json") ? `${JSON.stringify(
462
+ options.tools.map(({ handler: _handler, ...tool2 }) => tool2),
463
+ null,
464
+ 2
465
+ )}
466
+ ` : `${options.tools.map((tool2) => `${tool2.name} ${tool2.description.replace(/\s+/g, " ")}`).join("\n")}
467
+ `
468
+ );
469
+ return 0;
470
+ }
471
+ if (command === "skill") {
472
+ if (!options.commands?.skill) return fail("skill command unavailable");
473
+ stdout.write(await options.commands.skill());
474
+ return 0;
475
+ }
476
+ if (command === "mcp") {
477
+ if (!options.commands?.mcp) return fail("mcp command unavailable");
478
+ await options.commands.mcp(ctxOptions);
479
+ return 0;
480
+ }
481
+ const plain = options.commands?.plain?.find((entry) => entry.spec.command === command);
482
+ let plainValues = {};
483
+ if (plain) {
484
+ if (args.includes("--help") || args.includes("-h")) {
485
+ stdout.write(
486
+ renderCliHelp({
487
+ ...plain.spec,
488
+ command: `${options.bin} ${command}`,
489
+ flags: [
490
+ ...plain.spec.flags ?? [],
491
+ { name: "db", type: "string" },
492
+ { name: "compact", type: "boolean" }
493
+ ]
494
+ })
495
+ );
496
+ return 0;
497
+ }
498
+ try {
499
+ plainValues = parseCliArgs(args, plain.spec);
500
+ } catch (cause) {
501
+ return fail(String(cause), cause instanceof CliUsageError ? 2 : 1);
502
+ }
503
+ }
504
+ const name = plain ? plain.tool : command === "call" || command === "describe" ? args.shift() : command;
505
+ const tool = options.tools.find((tool2) => normalize(tool2.name) === normalize(name ?? ""));
506
+ if (!tool) {
507
+ if (command === "call" || command === "describe") return fail(`Unknown tool: ${name ?? ""}`);
508
+ const suggestion = suggestCommand(command, [
509
+ ...options.commands?.plain?.map((entry) => entry.spec.command) ?? [],
510
+ "help",
511
+ "tools",
512
+ "describe",
513
+ "call",
514
+ "mcp",
515
+ "skill",
516
+ ...options.tools.map((entry) => entry.name)
517
+ ]);
518
+ return fail(`Unknown command: ${command}.${suggestion ? ` Did you mean ${suggestion}?` : ""}`);
519
+ }
520
+ if (command === "describe") {
521
+ stdout.write(
522
+ `${tool.name}
523
+ ${tool.description}
524
+ Use when: ${tool.whenToUse ?? ""}
525
+ Not for: ${tool.whenNotToUse ?? ""}
526
+ Read only: ${tool.readOnly ?? false}
527
+
528
+ Parameter | Type | Required | Description
529
+ ${Object.entries(
530
+ tool.inputSchema.properties ?? {}
531
+ ).map(
532
+ ([key, schema]) => `${key} | ${schema.type ?? "oneOf"} | ${tool.inputSchema.required?.includes(key) ?? false} | ${schema.description ?? ""}`
533
+ ).join("\n")}
534
+
535
+ ${JSON.stringify(tool.inputSchema, null, 2)}
536
+ `
537
+ );
538
+ return 0;
539
+ }
540
+ let input = {};
541
+ try {
542
+ const readStdin = async () => {
543
+ let text = "";
544
+ for await (const chunk of stdin) text += String(chunk);
545
+ return text;
546
+ };
547
+ if (plain) {
548
+ input = plain.input(plainValues);
549
+ } else if (command === "call") {
550
+ if (args.length > 1) return fail("Expected a single JSON argument");
551
+ const raw = args[0];
552
+ input = JSON.parse(
553
+ raw === "-" || raw === void 0 && !stdin.isTTY ? await readStdin() : raw ?? "{}"
554
+ );
555
+ } else {
556
+ const values = {};
557
+ const coerce = (value, schema) => {
558
+ if (schema.type === "number" || schema.type === "integer") return Number(value);
559
+ if (schema.type === "boolean") {
560
+ if (!["true", "false"].includes(value)) throw new Error("Expected true or false");
561
+ return value === "true";
562
+ }
563
+ if (schema.type === "object" || schema.type === "array") return JSON.parse(value);
564
+ if (schema.oneOf) {
565
+ for (const branch of schema.oneOf) {
566
+ try {
567
+ const converted = coerce(value, branch);
568
+ if (!validateInput(branch, converted).length) return converted;
569
+ } catch {
570
+ }
571
+ }
572
+ }
573
+ return value;
574
+ };
575
+ while (args.length) {
576
+ const flag = args.shift();
577
+ if (!flag?.startsWith("--")) return fail(`Expected parameter flag: ${flag}`);
578
+ const equal = flag.indexOf("=");
579
+ const key = equal === -1 ? flag.slice(2) : flag.slice(2, equal);
580
+ const property = Object.keys(tool.inputSchema.properties ?? {}).find(
581
+ (name2) => normalize(name2) === normalize(key)
582
+ );
583
+ if (!property) return fail(`Unknown parameter: ${key}`);
584
+ const schema = tool.inputSchema.properties?.[property];
585
+ const raw = equal === -1 ? schema.type === "boolean" && (!args[0] || args[0].startsWith("--")) ? "true" : args.shift() : flag.slice(equal + 1);
586
+ if (raw === void 0) return fail(`Missing value: ${key}`);
587
+ if (schema.type === "array" && !raw.startsWith("[")) {
588
+ const prior = values[property];
589
+ values[property] = [
590
+ ...Array.isArray(prior) ? prior : [],
591
+ coerce(raw, schema.items ?? {})
592
+ ];
593
+ } else values[property] = coerce(raw, schema);
594
+ }
595
+ input = values;
596
+ }
597
+ } catch (cause) {
598
+ if (cause instanceof CliUsageError) return fail(cause.message, 2);
599
+ return fail(`Invalid input: ${cause instanceof Error ? cause.message : String(cause)}`, 1);
600
+ }
601
+ const errors = validateInput(tool.inputSchema, input);
602
+ if (errors.length) return fail(errors.join("\n"), 1);
603
+ let context;
604
+ try {
605
+ context = await options.openContext(ctxOptions);
606
+ } catch (cause) {
607
+ return fail(String(cause), 1);
608
+ }
609
+ try {
610
+ const result = await tool.handler(context, input);
611
+ stdout.write(
612
+ plain && !plainValues.json ? plain.render(result, context, plainValues) : `${JSON.stringify(plain ? enrichPlainJson(result) : result, null, compact ? void 0 : 2)}
613
+ `
614
+ );
615
+ if (typeof result === "object" && result !== null && "outcome" in result && result.outcome === "needs_clarification")
616
+ return 3;
617
+ return isErrorResult(result) ? 1 : 0;
618
+ } catch (cause) {
619
+ return fail(String(cause), 1);
620
+ } finally {
621
+ await options.closeContext?.(context);
622
+ }
623
+ }
624
+
625
+ // src/cli_main.ts
626
+ process.exitCode = await runToolCli(process.argv.slice(2), questlogCliOptions);