@ghostpaw/questlog 0.9.2 → 0.10.1
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.
- package/README.md +116 -2
- package/SKILL.md +27 -3
- package/dist/{chunk-UTUQPSGO.js → chunk-55JAQORX.js} +66 -588
- package/dist/{chunk-6KTQK3ZE.js → chunk-D2W5MYNO.js} +8 -0
- package/dist/chunk-GVD6A3RR.js +108 -0
- package/dist/{chunk-DKH623HU.js → chunk-IL2QRKZL.js} +1 -4
- package/dist/chunk-N7V7VUI5.js +982 -0
- package/dist/{chunk-3B6FMHZU.js → chunk-ZHTX64KD.js} +24 -0
- package/dist/cli.js +1 -1
- package/dist/{cli_main-XNZP4TY2.js → cli_main-7AIEZV75.js} +80 -118
- package/dist/index.cjs +1135 -627
- package/dist/index.d.cts +142 -11
- package/dist/index.d.ts +142 -11
- package/dist/index.js +1135 -628
- package/dist/mcp-server.js +821 -387
- package/dist/mcp.cjs +691 -321
- package/dist/mcp.js +692 -322
- package/dist/open_questlog_db-LAEH7UDM.js +2 -0
- package/dist/{questlog_skill_spec-EM6DGGVN.js → questlog_skill_spec-HEW2B6EB.js} +5 -3
- package/dist/run_hook_command-C42BQC36.js +95 -0
- package/dist/{serve_questlog_mcp-KFUYJB2P.js → serve_questlog_mcp-W3R63HW4.js} +4 -3
- package/package.json +1 -1
- package/skills/questlog/SKILL.md +27 -3
- package/skills/questlog/references/webhooks.md +91 -0
- package/dist/open_questlog_db-SH3DAU4C.js +0 -2
|
@@ -2,6 +2,29 @@
|
|
|
2
2
|
import { mkdirSync } from 'node:fs';
|
|
3
3
|
import { dirname } from 'node:path';
|
|
4
4
|
|
|
5
|
+
// src/hooks/init_hook_tables.ts
|
|
6
|
+
function initHookTables(db) {
|
|
7
|
+
db.exec(`
|
|
8
|
+
CREATE TABLE IF NOT EXISTS hook_targets (
|
|
9
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT, url TEXT NOT NULL, secret TEXT NOT NULL,
|
|
10
|
+
tag TEXT, events TEXT NOT NULL, min_gap INTEGER NOT NULL, created_at INTEGER NOT NULL,
|
|
11
|
+
last_wake INTEGER, lease_until INTEGER, claim TEXT);
|
|
12
|
+
CREATE TABLE IF NOT EXISTS hook_events (
|
|
13
|
+
id TEXT PRIMARY KEY, occurred_at INTEGER NOT NULL, event TEXT NOT NULL);
|
|
14
|
+
CREATE TABLE IF NOT EXISTS hook_assignments (
|
|
15
|
+
target_id INTEGER NOT NULL REFERENCES hook_targets(id) ON DELETE CASCADE,
|
|
16
|
+
event_id TEXT NOT NULL REFERENCES hook_events(id), delivery_id TEXT,
|
|
17
|
+
PRIMARY KEY(target_id, event_id));
|
|
18
|
+
CREATE TABLE IF NOT EXISTS hook_deliveries (
|
|
19
|
+
id TEXT PRIMARY KEY, target_id INTEGER NOT NULL REFERENCES hook_targets(id) ON DELETE CASCADE,
|
|
20
|
+
events TEXT NOT NULL, status TEXT NOT NULL, attempts INTEGER NOT NULL DEFAULT 0,
|
|
21
|
+
last_attempt_at INTEGER, http_status INTEGER, error_class TEXT,
|
|
22
|
+
next_attempt_at INTEGER NOT NULL);
|
|
23
|
+
CREATE INDEX IF NOT EXISTS idx_hook_deliveries_target ON hook_deliveries(target_id, status, next_attempt_at);
|
|
24
|
+
CREATE TABLE IF NOT EXISTS hook_settings (id INTEGER PRIMARY KEY CHECK(id = 1), label TEXT NOT NULL);
|
|
25
|
+
`);
|
|
26
|
+
}
|
|
27
|
+
|
|
5
28
|
// src/questlines/init_questline_tables.ts
|
|
6
29
|
function initQuestlineTables(db) {
|
|
7
30
|
db.exec(`
|
|
@@ -350,6 +373,7 @@ function initQuestlogTables(db) {
|
|
|
350
373
|
initTagTables(db);
|
|
351
374
|
initQuestSearch(db);
|
|
352
375
|
initRumorSearch(db);
|
|
376
|
+
initHookTables(db);
|
|
353
377
|
}
|
|
354
378
|
|
|
355
379
|
// src/surface/open_questlog_db.ts
|
package/dist/cli.js
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
2
|
+
import { CliUsageError, renderCliHelp, parseCliArgs } from './chunk-GVD6A3RR.js';
|
|
3
|
+
import { isErrorResult, resolveQuestlogDbPath } from './chunk-IL2QRKZL.js';
|
|
4
|
+
import { questlogSkills } from './chunk-D2W5MYNO.js';
|
|
5
|
+
import { reviewQuestlogTool, validateInput, getQuestDetail, questlogSurfaceTools } from './chunk-55JAQORX.js';
|
|
6
|
+
import { listDeliveries, questlogVersion } from './chunk-N7V7VUI5.js';
|
|
5
7
|
import { minimalInput } from './chunk-QYSIJ2YN.js';
|
|
6
8
|
import { isSea } from 'node:sea';
|
|
7
|
-
import {
|
|
9
|
+
import { existsSync } from 'node:fs';
|
|
8
10
|
import { resolve } from 'node:path';
|
|
9
11
|
|
|
10
12
|
var npmEntry = false;
|
|
@@ -15,7 +17,8 @@ function cliVersion(version, {
|
|
|
15
17
|
sea = isSea,
|
|
16
18
|
npm = npmEntry,
|
|
17
19
|
platform = process.platform,
|
|
18
|
-
arch = process.arch
|
|
20
|
+
arch = process.arch,
|
|
21
|
+
args = []
|
|
19
22
|
} = {}) {
|
|
20
23
|
let binary = false;
|
|
21
24
|
try {
|
|
@@ -23,111 +26,12 @@ function cliVersion(version, {
|
|
|
23
26
|
} catch {
|
|
24
27
|
}
|
|
25
28
|
const target = `${platform === "win32" ? "windows" : platform}-${arch}`;
|
|
29
|
+
const variant = binary ? "binary" : npm ? "npm" : "library";
|
|
30
|
+
if (args.includes("--json"))
|
|
31
|
+
return JSON.stringify({ name: "questlog", version, variant, target });
|
|
32
|
+
if (args.includes("--short")) return version;
|
|
26
33
|
return `questlog ${version} (${binary ? `binary, ${target}` : npm ? "npm" : "library"})`;
|
|
27
34
|
}
|
|
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
35
|
|
|
132
36
|
// src/surface/plain_dates.ts
|
|
133
37
|
function parsePlainDue(raw, now = Date.now()) {
|
|
@@ -210,6 +114,8 @@ function renderQuestlogPlainResult(result, db, values) {
|
|
|
210
114
|
Missing: ${r.clarification.missing.map((name) => `--${name}`).join(", ")}
|
|
211
115
|
` : `${r.summary}
|
|
212
116
|
${r.error.message}
|
|
117
|
+
`;
|
|
118
|
+
if (r.data.deliveries && !r.data.item) return `${JSON.stringify(r.data.deliveries, null, 2)}
|
|
213
119
|
`;
|
|
214
120
|
if (r.data.items) {
|
|
215
121
|
const items = values.limit === void 0 ? r.data.items : r.data.items.slice(0, Math.max(0, Number(values.limit)));
|
|
@@ -235,6 +141,11 @@ ${r.error.message}
|
|
|
235
141
|
]);
|
|
236
142
|
if (detail.outcome != null) fields.push(["outcome", detail.outcome]);
|
|
237
143
|
if (detail.questlineId != null) fields.push(["questline", detail.questlineId]);
|
|
144
|
+
for (const delivery of listDeliveries(db, { questId: detail.id }))
|
|
145
|
+
fields.push([
|
|
146
|
+
"delivery",
|
|
147
|
+
`${delivery.id} target ${delivery.targetId} ${delivery.status} attempts ${delivery.attempts}`
|
|
148
|
+
]);
|
|
238
149
|
const width = Math.max(...fields.map(([label]) => label.length));
|
|
239
150
|
return `${fields.map(([label, value]) => `${label.padEnd(width)}: ${String(value).replace(/[\r\n]+/g, " ")}`).join("\n")}
|
|
240
151
|
`;
|
|
@@ -258,6 +169,34 @@ function questlogPlainCommands() {
|
|
|
258
169
|
const id = { name: "id", type: "integer", required: true };
|
|
259
170
|
const views = reviewQuestlogTool.inputSchema.properties?.view?.enum ?? [];
|
|
260
171
|
const commands = [
|
|
172
|
+
{
|
|
173
|
+
spec: {
|
|
174
|
+
command: "tick",
|
|
175
|
+
description: "Detect events and deliver outbound webhooks once.",
|
|
176
|
+
flags: [{ name: "dry-run", type: "boolean" }]
|
|
177
|
+
},
|
|
178
|
+
tool: "manage_hooks",
|
|
179
|
+
input: (v) => ({ action: "tick", dry_run: v.dryRun ?? false }),
|
|
180
|
+
render: (r, _db, values) => {
|
|
181
|
+
const result = r;
|
|
182
|
+
if (!result.ok || !result.data) return `${result.summary}
|
|
183
|
+
`;
|
|
184
|
+
const d = result.data;
|
|
185
|
+
return `${d.events.length} events found; ${d.sent} deliveries sent, ${d.queued} queued, ${d.retrying} retrying, ${d.failed} failed.
|
|
186
|
+
${values.dryRun ? `${JSON.stringify({ events: d.events, batches: d.batches }, null, 2)}
|
|
187
|
+
` : ""}`;
|
|
188
|
+
}
|
|
189
|
+
},
|
|
190
|
+
{
|
|
191
|
+
spec: {
|
|
192
|
+
command: "review",
|
|
193
|
+
description: "Review an operational view.",
|
|
194
|
+
positionals: [{ name: "view", type: "string", required: true, enum: views }]
|
|
195
|
+
},
|
|
196
|
+
tool: "review_questlog",
|
|
197
|
+
input: (v) => ({ view: v.view }),
|
|
198
|
+
render: renderQuestlogPlainResult
|
|
199
|
+
},
|
|
261
200
|
{
|
|
262
201
|
spec: {
|
|
263
202
|
command: "add",
|
|
@@ -370,13 +309,13 @@ function selectedDbPath(options) {
|
|
|
370
309
|
var questlogCliOptions = {
|
|
371
310
|
bin: "ghostpaw-questlog",
|
|
372
311
|
version: questlogVersion,
|
|
373
|
-
versionLine: () => cliVersion(questlogVersion),
|
|
312
|
+
versionLine: (args) => cliVersion(questlogVersion, { args }),
|
|
374
313
|
description: "Local task and calendar library",
|
|
375
314
|
tools: questlogSurfaceTools,
|
|
376
315
|
describeDb: (options) => `Database: ${selectedDbPath(options)}
|
|
377
316
|
Use --db <path> or GHOSTPAW_QUESTLOG_DB to override the default ~/.ghostpaw/questlog.db.`,
|
|
378
317
|
openContext: async (options) => {
|
|
379
|
-
const { openQuestlogDb } = await import('./open_questlog_db-
|
|
318
|
+
const { openQuestlogDb } = await import('./open_questlog_db-LAEH7UDM.js');
|
|
380
319
|
const path = selectedDbPath(options);
|
|
381
320
|
const created = path !== ":memory:" && !existsSync(path);
|
|
382
321
|
const db = await openQuestlogDb(path);
|
|
@@ -386,15 +325,24 @@ Use --db <path> or GHOSTPAW_QUESTLOG_DB to override the default ~/.ghostpaw/ques
|
|
|
386
325
|
},
|
|
387
326
|
closeContext: (db) => db.close(),
|
|
388
327
|
commands: {
|
|
328
|
+
names: ["hook", "watch"],
|
|
329
|
+
run: async (command, args, options, io) => {
|
|
330
|
+
if (command !== "hook" && command !== "watch") return void 0;
|
|
331
|
+
const { runHookCommand } = await import('./run_hook_command-C42BQC36.js');
|
|
332
|
+
return runHookCommand(command, args, io, async () => {
|
|
333
|
+
const { openQuestlogDb } = await import('./open_questlog_db-LAEH7UDM.js');
|
|
334
|
+
return openQuestlogDb(selectedDbPath(options));
|
|
335
|
+
});
|
|
336
|
+
},
|
|
389
337
|
plain: questlogPlainCommands(),
|
|
390
338
|
guides: questlogSkills,
|
|
391
339
|
mcp: async (options) => {
|
|
392
|
-
const { serveQuestlogMcp } = await import('./serve_questlog_mcp-
|
|
340
|
+
const { serveQuestlogMcp } = await import('./serve_questlog_mcp-W3R63HW4.js');
|
|
393
341
|
await serveQuestlogMcp(options.db === void 0 ? {} : { dbPath: options.db });
|
|
394
342
|
},
|
|
395
343
|
skill: async () => {
|
|
396
344
|
const { renderSkillMarkdown } = await import('./skill_markdown-HXYQ3K6U.js');
|
|
397
|
-
const { questlogSkillSpec } = await import('./questlog_skill_spec-
|
|
345
|
+
const { questlogSkillSpec } = await import('./questlog_skill_spec-HEW2B6EB.js');
|
|
398
346
|
return renderSkillMarkdown(questlogSkillSpec);
|
|
399
347
|
}
|
|
400
348
|
}
|
|
@@ -463,6 +411,12 @@ async function runToolCli(argv, options) {
|
|
|
463
411
|
i--;
|
|
464
412
|
}
|
|
465
413
|
}
|
|
414
|
+
const versionArgs = args.filter((arg) => arg !== "--short" && arg !== "--json");
|
|
415
|
+
if (versionArgs[0] === "--version" || versionArgs[0] === "-v") {
|
|
416
|
+
stdout.write(`${options.versionLine?.(args) ?? options.version}
|
|
417
|
+
`);
|
|
418
|
+
return 0;
|
|
419
|
+
}
|
|
466
420
|
const ctxOptions = db === void 0 ? {} : { db };
|
|
467
421
|
const command = args.shift() ?? "help";
|
|
468
422
|
const normalize = (name2) => name2.replace(/[-_]/g, "").toLowerCase();
|
|
@@ -472,8 +426,14 @@ async function runToolCli(argv, options) {
|
|
|
472
426
|
${options.description}
|
|
473
427
|
|
|
474
428
|
Usage: ${options.bin} [--db path] <command>
|
|
475
|
-
Commands: ${
|
|
429
|
+
Commands: ${[
|
|
430
|
+
...options.commands?.plain?.map((entry) => entry.spec.command) ?? [],
|
|
431
|
+
...options.commands?.names ?? []
|
|
432
|
+
].map((name2) => `${name2}, `).join(
|
|
433
|
+
""
|
|
434
|
+
)}tools [--json], describe <tool>, call <tool> [JSON|-], <tool> --parameter value, mcp, skill
|
|
476
435
|
Options: --help, --version, --compact, --db <path>
|
|
436
|
+
Version formats: --version --short, --version --json
|
|
477
437
|
${options.describeDb ? `${options.describeDb(ctxOptions)}
|
|
478
438
|
` : ""}
|
|
479
439
|
${options.tools.map((tool2) => `${tool2.name} ${tool2.description}`).join("\n")}
|
|
@@ -481,11 +441,6 @@ ${options.tools.map((tool2) => `${tool2.name} ${tool2.description}`).join("\n")
|
|
|
481
441
|
);
|
|
482
442
|
return 0;
|
|
483
443
|
}
|
|
484
|
-
if (["--version", "-v"].includes(command)) {
|
|
485
|
-
stdout.write(`${options.versionLine?.() ?? options.version}
|
|
486
|
-
`);
|
|
487
|
-
return 0;
|
|
488
|
-
}
|
|
489
444
|
if (command === "tools") {
|
|
490
445
|
stdout.write(
|
|
491
446
|
args.includes("--json") ? `${JSON.stringify(
|
|
@@ -527,6 +482,12 @@ ${options.tools.map((tool2) => `${tool2.name} ${tool2.description}`).join("\n")
|
|
|
527
482
|
await options.commands.mcp(ctxOptions);
|
|
528
483
|
return 0;
|
|
529
484
|
}
|
|
485
|
+
const custom = await options.commands?.run?.(command, args, ctxOptions, {
|
|
486
|
+
stdin,
|
|
487
|
+
stdout,
|
|
488
|
+
stderr
|
|
489
|
+
});
|
|
490
|
+
if (custom !== void 0) return custom;
|
|
530
491
|
const plain = options.commands?.plain?.find((entry) => entry.spec.command === command);
|
|
531
492
|
let plainValues = {};
|
|
532
493
|
if (plain) {
|
|
@@ -556,6 +517,7 @@ ${options.tools.map((tool2) => `${tool2.name} ${tool2.description}`).join("\n")
|
|
|
556
517
|
if (command === "call" || command === "describe") return fail(`Unknown tool: ${name ?? ""}`);
|
|
557
518
|
const suggestion = suggestCommand(command, [
|
|
558
519
|
...options.commands?.plain?.map((entry) => entry.spec.command) ?? [],
|
|
520
|
+
...options.commands?.names ?? [],
|
|
559
521
|
"help",
|
|
560
522
|
"tools",
|
|
561
523
|
"describe",
|