@ghostpaw/questlog 0.10.1 → 0.11.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/dist/mcp.cjs CHANGED
@@ -28,6 +28,8 @@ function initHookTables(db) {
28
28
  CREATE INDEX IF NOT EXISTS idx_hook_deliveries_target ON hook_deliveries(target_id, status, next_attempt_at);
29
29
  CREATE TABLE IF NOT EXISTS hook_settings (id INTEGER PRIMARY KEY CHECK(id = 1), label TEXT NOT NULL);
30
30
  `);
31
+ if (!db.prepare("PRAGMA table_info(hook_targets)").all().some((row) => row.name === "headers"))
32
+ db.exec("ALTER TABLE hook_targets ADD COLUMN headers TEXT NOT NULL DEFAULT '{}'");
31
33
  }
32
34
 
33
35
  // src/questlines/init_questline_tables.ts
@@ -397,7 +399,14 @@ async function openQuestlogDb(path) {
397
399
  } finally {
398
400
  process.emitWarning = original;
399
401
  }
400
- if (path !== ":memory:") node_fs.mkdirSync(node_path.dirname(path), { recursive: true });
402
+ if (path !== ":memory:") {
403
+ node_fs.mkdirSync(node_path.dirname(path), { recursive: true, mode: 448 });
404
+ try {
405
+ node_fs.closeSync(node_fs.openSync(path, "wx", 384));
406
+ } catch (error) {
407
+ if (error.code !== "EEXIST") throw error;
408
+ }
409
+ }
401
410
  const db = new sqlite.DatabaseSync(path);
402
411
  try {
403
412
  db.exec("PRAGMA journal_mode=WAL; PRAGMA busy_timeout=5000;");
@@ -411,7 +420,7 @@ async function openQuestlogDb(path) {
411
420
  }
412
421
 
413
422
  // src/version.ts
414
- var questlogVersion = "0.10.1";
423
+ var questlogVersion = "0.11.1";
415
424
 
416
425
  // src/surface/questlog_mcp_instructions.ts
417
426
  var questlogMcpInstructions = "Questlog manages local tasks, calendar commitments, and recurring work. Search before creating work, inspect before changing it, and keep uncertain work as rumors. Use unlock prerequisites to sequence work. run_quest refuses blocked starts unless force is true. Review commitments and resolve work with an honest outcome.";
@@ -1663,7 +1672,7 @@ function hookLabel(db, name) {
1663
1672
 
1664
1673
  // src/hooks/list_hooks.ts
1665
1674
  function listHooks(db) {
1666
- return db.prepare(`SELECT id, url, tag, events, min_gap, created_at, last_wake,
1675
+ return db.prepare(`SELECT id, url, headers, tag, events, min_gap, created_at, last_wake,
1667
1676
  (SELECT COUNT(*) FROM hook_assignments a LEFT JOIN hook_deliveries d ON d.id = a.delivery_id WHERE a.target_id = t.id AND (a.delivery_id IS NULL OR d.status = 'pending')) AS pending,
1668
1677
  (SELECT COUNT(*) FROM hook_deliveries d WHERE d.target_id = t.id AND d.status = 'failed') AS failed
1669
1678
  FROM hook_targets t ORDER BY id`).all().map((row) => {
@@ -1674,6 +1683,9 @@ function listHooks(db) {
1674
1683
  url.hash = "";
1675
1684
  return {
1676
1685
  id: Number(row.id),
1686
+ headers: Object.fromEntries(
1687
+ Object.keys(JSON.parse(String(row.headers))).map((name) => [name, "***"])
1688
+ ),
1677
1689
  scope: row.tag == null ? "database" : "tag",
1678
1690
  url: url.href,
1679
1691
  tag: row.tag == null ? null : String(row.tag),
@@ -1748,8 +1760,13 @@ async function sendHookDelivery(db, delivery, target, options) {
1748
1760
  method: "POST",
1749
1761
  redirect: "error",
1750
1762
  headers: {
1751
- "content-type": "application/json",
1752
1763
  "user-agent": `questlog/${questlogVersion}`,
1764
+ ...JSON.parse(
1765
+ String(
1766
+ db.prepare("SELECT headers FROM hook_targets WHERE id = ?").get(target.id)?.headers ?? "{}"
1767
+ )
1768
+ ),
1769
+ "content-type": "application/json",
1753
1770
  "x-questlog-delivery": delivery.id,
1754
1771
  "x-questlog-timestamp": timestamp,
1755
1772
  "x-questlog-signature": `sha256=${signature}`
package/dist/mcp.js CHANGED
@@ -1,4 +1,4 @@
1
- import { mkdirSync } from 'node:fs';
1
+ import { mkdirSync, closeSync, openSync } from 'node:fs';
2
2
  import { basename, extname, dirname, join } from 'node:path';
3
3
  import { randomBytes, createHmac } from 'node:crypto';
4
4
  import { homedir } from 'node:os';
@@ -26,6 +26,8 @@ function initHookTables(db) {
26
26
  CREATE INDEX IF NOT EXISTS idx_hook_deliveries_target ON hook_deliveries(target_id, status, next_attempt_at);
27
27
  CREATE TABLE IF NOT EXISTS hook_settings (id INTEGER PRIMARY KEY CHECK(id = 1), label TEXT NOT NULL);
28
28
  `);
29
+ if (!db.prepare("PRAGMA table_info(hook_targets)").all().some((row) => row.name === "headers"))
30
+ db.exec("ALTER TABLE hook_targets ADD COLUMN headers TEXT NOT NULL DEFAULT '{}'");
29
31
  }
30
32
 
31
33
  // src/questlines/init_questline_tables.ts
@@ -395,7 +397,14 @@ async function openQuestlogDb(path) {
395
397
  } finally {
396
398
  process.emitWarning = original;
397
399
  }
398
- if (path !== ":memory:") mkdirSync(dirname(path), { recursive: true });
400
+ if (path !== ":memory:") {
401
+ mkdirSync(dirname(path), { recursive: true, mode: 448 });
402
+ try {
403
+ closeSync(openSync(path, "wx", 384));
404
+ } catch (error) {
405
+ if (error.code !== "EEXIST") throw error;
406
+ }
407
+ }
399
408
  const db = new sqlite.DatabaseSync(path);
400
409
  try {
401
410
  db.exec("PRAGMA journal_mode=WAL; PRAGMA busy_timeout=5000;");
@@ -409,7 +418,7 @@ async function openQuestlogDb(path) {
409
418
  }
410
419
 
411
420
  // src/version.ts
412
- var questlogVersion = "0.10.1";
421
+ var questlogVersion = "0.11.1";
413
422
 
414
423
  // src/surface/questlog_mcp_instructions.ts
415
424
  var questlogMcpInstructions = "Questlog manages local tasks, calendar commitments, and recurring work. Search before creating work, inspect before changing it, and keep uncertain work as rumors. Use unlock prerequisites to sequence work. run_quest refuses blocked starts unless force is true. Review commitments and resolve work with an honest outcome.";
@@ -1661,7 +1670,7 @@ function hookLabel(db, name) {
1661
1670
 
1662
1671
  // src/hooks/list_hooks.ts
1663
1672
  function listHooks(db) {
1664
- return db.prepare(`SELECT id, url, tag, events, min_gap, created_at, last_wake,
1673
+ return db.prepare(`SELECT id, url, headers, tag, events, min_gap, created_at, last_wake,
1665
1674
  (SELECT COUNT(*) FROM hook_assignments a LEFT JOIN hook_deliveries d ON d.id = a.delivery_id WHERE a.target_id = t.id AND (a.delivery_id IS NULL OR d.status = 'pending')) AS pending,
1666
1675
  (SELECT COUNT(*) FROM hook_deliveries d WHERE d.target_id = t.id AND d.status = 'failed') AS failed
1667
1676
  FROM hook_targets t ORDER BY id`).all().map((row) => {
@@ -1672,6 +1681,9 @@ function listHooks(db) {
1672
1681
  url.hash = "";
1673
1682
  return {
1674
1683
  id: Number(row.id),
1684
+ headers: Object.fromEntries(
1685
+ Object.keys(JSON.parse(String(row.headers))).map((name) => [name, "***"])
1686
+ ),
1675
1687
  scope: row.tag == null ? "database" : "tag",
1676
1688
  url: url.href,
1677
1689
  tag: row.tag == null ? null : String(row.tag),
@@ -1746,8 +1758,13 @@ async function sendHookDelivery(db, delivery, target, options) {
1746
1758
  method: "POST",
1747
1759
  redirect: "error",
1748
1760
  headers: {
1749
- "content-type": "application/json",
1750
1761
  "user-agent": `questlog/${questlogVersion}`,
1762
+ ...JSON.parse(
1763
+ String(
1764
+ db.prepare("SELECT headers FROM hook_targets WHERE id = ?").get(target.id)?.headers ?? "{}"
1765
+ )
1766
+ ),
1767
+ "content-type": "application/json",
1751
1768
  "x-questlog-delivery": delivery.id,
1752
1769
  "x-questlog-timestamp": timestamp,
1753
1770
  "x-questlog-signature": `sha256=${signature}`
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export { openQuestlogDb } from './chunk-IA2GVCZS.js';
@@ -1,8 +1,10 @@
1
1
  #!/usr/bin/env node
2
+ import { questlogSkills } from './chunk-I2AMIXGP.js';
2
3
  import { questlogMcpInstructions } from './chunk-SVWAEAKC.js';
3
- import { questlogSkills } from './chunk-D2W5MYNO.js';
4
- import { reviewQuestlogTool, questlogSurfaceTools } from './chunk-55JAQORX.js';
5
- import './chunk-N7V7VUI5.js';
4
+ import { reviewQuestlogTool, questlogSurfaceTools } from './chunk-R56MXZ2K.js';
5
+ import './chunk-FORPYJT4.js';
6
+ import './chunk-D6EGZCT7.js';
7
+ import './chunk-NBCCA6AX.js';
6
8
  import './chunk-QYSIJ2YN.js';
7
9
 
8
10
  // src/soul.ts
@@ -50,15 +52,19 @@ var questlogSkillSpec = {
50
52
  "`questlog` and `ghostpaw-questlog` are the same binary.",
51
53
  "",
52
54
  "- `questlog search <words>` before `add` to avoid duplicates.",
53
- '- `ghostpaw-questlog add "..." --tag a,b --due tomorrow`',
55
+ '- `ghostpaw-questlog add "Write the report" --tag work --due tomorrow`',
54
56
  `- \`questlog list [--view <view>]\`: ${reviewQuestlogTool.inputSchema.properties?.view?.enum?.join(", ")}.`,
55
- '- `questlog done <id> --outcome "..."`',
56
- "- `questlog search <words>`",
57
- "- `questlog show <id>`",
57
+ '- `questlog done 12 --outcome "Report sent"`',
58
+ '- `questlog search "release"`',
59
+ "- `questlog show 12`",
60
+ "- `questlog list --view quests.available`",
58
61
  "- Use `--json` on any plain command.",
59
- "- Exit codes: 0 ok / 1 error / 2 usage / 3 needs input.",
62
+ "- Exit codes: 0 ok / 1 error / 2 usage / 3 needs input or hook target not found.",
60
63
  "- Database: `~/.ghostpaw/questlog.db`; override with `--db` or `GHOSTPAW_QUESTLOG_DB`.",
61
- "- Use one database per agent or project.",
64
+ "- Use one database per project.",
65
+ "- `questlog --version` prints the installed version.",
66
+ "- `questlog skill --short` prints this compact cheat sheet.",
67
+ "- `questlog skill --list` lists guides; `questlog skill webhooks` reads one.",
62
68
  "- Webhooks: `questlog hook add <url>`, `questlog tick --dry-run`, `questlog watch`. Read `questlog skill webhooks` for events, signing, verification, and cron setup."
63
69
  ].join("\n"),
64
70
  description: "Use the release binary ghostpaw-questlog to manage local tasks, calendar commitments, and recurring work. Use when planning, executing, reviewing, or organizing work.",
@@ -0,0 +1,155 @@
1
+ #!/usr/bin/env node
2
+ import { parseCliArgs } from './chunk-B5KVA6XE.js';
3
+ import { textOutput } from './chunk-QK3H2AL5.js';
4
+ import { watchQuestlog, testHook, removeHook, hookLabel, withTransaction, addHook } from './chunk-FORPYJT4.js';
5
+ import './chunk-D6EGZCT7.js';
6
+ import { listHooks } from './chunk-NBCCA6AX.js';
7
+
8
+ // src/hooks/hook_headers.ts
9
+ function parseHookHeaders(lines) {
10
+ const headers = /* @__PURE__ */ Object.create(null);
11
+ for (const line of lines) {
12
+ const colon = line.indexOf(":");
13
+ const name = line.slice(0, colon).trim().toLowerCase();
14
+ if (colon < 1 || !/^[!#$%&'*+.^_`|~0-9a-z-]+$/.test(name))
15
+ throw new Error("Invalid header name; use Name: value");
16
+ if (["host", "content-length", "content-type"].includes(name) || name.startsWith("x-questlog-"))
17
+ throw new Error(`Reserved header name: ${name}`);
18
+ const value = line.slice(colon + 1).trim();
19
+ if (/[\r\n\0]/.test(value)) throw new Error("Invalid header value");
20
+ headers[name] = value;
21
+ }
22
+ return headers;
23
+ }
24
+
25
+ // src/surface/run_hook_command.ts
26
+ async function runHookCommand(command, args, io, open) {
27
+ let db;
28
+ try {
29
+ if (args.includes("--help") || args.includes("-h")) {
30
+ io.stdout.write(
31
+ 'questlog hook add <url> [--tag name] [--events types] [--min-gap seconds] [--secret-stdin] [--header "Name: value"] [--header-stdin] [--json]\nquestlog hook list|remove <id>|test <id>|label [name]\nquestlog watch [--interval seconds] [--json]\n'
32
+ );
33
+ return 0;
34
+ }
35
+ const action = command === "watch" ? "watch" : args.shift();
36
+ if (!["add", "list", "remove", "test", "label", "watch"].includes(action ?? ""))
37
+ throw new Error("Expected hook add, list, remove, test, or label");
38
+ const values = parseCliArgs(args, {
39
+ command,
40
+ flags: [
41
+ { name: "json", type: "boolean" },
42
+ ...action === "add" ? [
43
+ { name: "tag", type: "string" },
44
+ { name: "events", type: "string" },
45
+ { name: "min-gap", type: "integer" },
46
+ { name: "secret-stdin", type: "boolean" },
47
+ { name: "header", type: "array" },
48
+ { name: "header-stdin", type: "boolean" }
49
+ ] : action === "watch" ? [{ name: "interval", type: "number" }] : []
50
+ ],
51
+ positionals: action === "add" ? [{ name: "url", type: "string", required: true }] : ["remove", "test"].includes(action ?? "") ? [{ name: "id", type: "integer", required: true }] : action === "label" ? [{ name: "name", type: "string" }] : []
52
+ });
53
+ if (action === "watch" && values.interval !== void 0 && Number(values.interval) < 5)
54
+ throw new Error(`--interval ${values.interval} is below the minimum of 5 seconds`);
55
+ db = await open();
56
+ let result;
57
+ switch (action) {
58
+ case "add": {
59
+ let secret;
60
+ if (values.secretStdin && values.headerStdin)
61
+ throw new Error("Use --secret-stdin and --header-stdin separately");
62
+ const headerLines = values.header ?? [];
63
+ if (values.headerStdin) {
64
+ let raw = "";
65
+ for await (const chunk of io.stdin) raw += String(chunk);
66
+ headerLines.push(...raw.split(/\r?\n/).filter(Boolean));
67
+ }
68
+ const headers = parseHookHeaders(headerLines);
69
+ if (values.secretStdin) {
70
+ secret = "";
71
+ for await (const chunk of io.stdin) secret += String(chunk);
72
+ secret = secret.replace(/\r?\n$/, "");
73
+ }
74
+ result = withTransaction(db, () => {
75
+ const created = addHook(db, {
76
+ url: String(values.url),
77
+ ...values.tag === void 0 ? {} : { tag: String(values.tag) },
78
+ ...values.events === void 0 ? {} : { events: String(values.events).split(",") },
79
+ ...values.minGap === void 0 ? {} : { minGap: Number(values.minGap) },
80
+ ...secret === void 0 ? {} : { secret }
81
+ });
82
+ db.prepare("UPDATE hook_targets SET headers = ? WHERE id = ?").run(JSON.stringify(headers), created.id);
83
+ return created;
84
+ });
85
+ break;
86
+ }
87
+ case "list":
88
+ result = listHooks(db);
89
+ break;
90
+ case "label":
91
+ result = { label: hookLabel(db, values.name) };
92
+ break;
93
+ case "remove":
94
+ if (!listHooks(db).some((hook) => hook.id === Number(values.id))) {
95
+ io.stderr.write(`Hook ${values.id} not found
96
+ `);
97
+ return 3;
98
+ }
99
+ removeHook(db, Number(values.id));
100
+ result = { removed: Number(values.id) };
101
+ break;
102
+ case "test":
103
+ if (!listHooks(db).some((hook) => hook.id === Number(values.id))) {
104
+ io.stderr.write(`Hook ${values.id} not found
105
+ `);
106
+ return 3;
107
+ }
108
+ result = await testHook(db, Number(values.id));
109
+ break;
110
+ case "watch": {
111
+ const controller = new AbortController();
112
+ const stop = () => controller.abort();
113
+ process.on("SIGINT", stop);
114
+ process.on("SIGTERM", stop);
115
+ try {
116
+ await watchQuestlog(db, {
117
+ signal: controller.signal,
118
+ interval: Number(values.interval ?? 60),
119
+ onTick: (tick) => io.stdout.write(
120
+ values.json ? `${JSON.stringify(tick)}
121
+ ` : `${tick.events.length} events; ${tick.sent} sent, ${tick.queued} queued, ${tick.retrying} retrying, ${tick.failed} failed
122
+ `
123
+ )
124
+ });
125
+ } finally {
126
+ process.off("SIGINT", stop);
127
+ process.off("SIGTERM", stop);
128
+ }
129
+ return 0;
130
+ }
131
+ }
132
+ io.stdout.write(
133
+ values.json ? `${JSON.stringify(result, null, 2)}
134
+ ` : action === "add" ? `Created hook ${result.id}
135
+ Secret (shown once): ${result.secret}
136
+ ` : `${textOutput(result)}
137
+ `
138
+ );
139
+ return 0;
140
+ } catch (error) {
141
+ const message = error instanceof Error ? error.message : "";
142
+ const safe = /^(unsupported scheme |URL userinfo |unknown event |--interval |Invalid header |Reserved header |Use --secret-stdin|Expected |Min gap |Unknown event |Hook )/.test(
143
+ message
144
+ );
145
+ io.stderr.write(
146
+ `${safe ? message : "Invalid webhook command or unavailable target. Run questlog hook --help."}
147
+ `
148
+ );
149
+ return 2;
150
+ } finally {
151
+ db?.close();
152
+ }
153
+ }
154
+
155
+ export { runHookCommand };
@@ -1,9 +1,11 @@
1
1
  #!/usr/bin/env node
2
- import { openQuestlogDb } from './chunk-ZHTX64KD.js';
3
2
  import { questlogMcpInstructions } from './chunk-SVWAEAKC.js';
4
3
  import { isErrorResult, resolveQuestlogDbPath } from './chunk-IL2QRKZL.js';
5
- import { validateInput, questlogSurfaceTools } from './chunk-55JAQORX.js';
6
- import { questlogVersion } from './chunk-N7V7VUI5.js';
4
+ import { validateInput, questlogSurfaceTools } from './chunk-R56MXZ2K.js';
5
+ import { openQuestlogDb } from './chunk-IA2GVCZS.js';
6
+ import { questlogVersion } from './chunk-FORPYJT4.js';
7
+ import './chunk-D6EGZCT7.js';
8
+ import './chunk-NBCCA6AX.js';
7
9
  import { minimalInput } from './chunk-QYSIJ2YN.js';
8
10
 
9
11
  // src/surfaces/mcp_server.ts
@@ -1,8 +1,9 @@
1
1
  #!/usr/bin/env node
2
+ import { schemaRows } from './chunk-7KTRNXTU.js';
2
3
  import { minimalInput } from './chunk-QYSIJ2YN.js';
3
4
 
4
5
  // src/surfaces/skill_markdown.ts
5
- function renderSkillMarkdown(spec, { referencesBase = "references" } = {}) {
6
+ function renderSkillMarkdown(spec, { referencesBase: _referencesBase = "references" } = {}) {
6
7
  if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(spec.name) || spec.name.length > 64)
7
8
  throw new Error("Invalid skill name");
8
9
  if (!spec.description.trim() || spec.description.length > 1024 || !spec.description.includes("Use when"))
@@ -48,24 +49,7 @@ function renderSkillMarkdown(spec, { referencesBase = "references" } = {}) {
48
49
  "| Parameter | Type | Required | Values / description |",
49
50
  "| --- | --- | --- | --- |"
50
51
  );
51
- const rows = (schema, prefix = "") => {
52
- const properties = { ...schema.properties };
53
- const required = new Set(schema.required);
54
- for (const branch of schema.oneOf ?? []) {
55
- for (const [key, value] of Object.entries(branch.properties ?? {}))
56
- properties[key] ??= value;
57
- for (const key of branch.required ?? []) required.add(key);
58
- }
59
- for (const [name, property] of Object.entries(properties)) {
60
- if (prefix && Object.keys(properties).length > 8 && !required.has(name)) continue;
61
- const path = `${prefix}${name}`;
62
- lines.push(
63
- `| ${path} | ${property.type ?? property.oneOf?.map((branch) => branch.type).join(" / ") ?? "any"} | ${required.has(name) ? "yes" : "no"} | ${((property.enum ? `${property.enum.join(", ")}. ` : "") + (property.description ?? "")).replaceAll("|", "\\|").replaceAll("\n", " ")} |`
64
- );
65
- if (property.properties || property.oneOf) rows(property, `${path}.`);
66
- }
67
- };
68
- rows(tool.inputSchema);
52
+ lines.push(...schemaRows(tool.inputSchema).map((row) => `| ${row} |`));
69
53
  lines.push("", "```sh");
70
54
  for (const action of tool.inputSchema.properties?.action?.enum?.slice(0, 3) ?? [void 0]) {
71
55
  lines.push(
@@ -79,7 +63,7 @@ function renderSkillMarkdown(spec, { referencesBase = "references" } = {}) {
79
63
  "## Workflow guides",
80
64
  "",
81
65
  ...spec.workflows.map(
82
- (workflow) => `- \`${spec.bin} skill ${workflow.name}\` ([${workflow.name}](${referencesBase}/${workflow.name}.md)): ${workflow.description}`
66
+ (workflow) => `- \`${spec.bin} skill ${workflow.name}\`: ${workflow.description}`
83
67
  ),
84
68
  "",
85
69
  "## Principles",
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export { textOutput } from './chunk-QK3H2AL5.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ghostpaw/questlog",
3
- "version": "0.10.1",
3
+ "version": "0.11.1",
4
4
  "description": "Unified task and calendar model for Node.js — SQLite, zero dependencies, with a built-in LLM tool facade",
5
5
  "type": "module",
6
6
  "exports": {
@@ -28,15 +28,19 @@ Run tools as `ghostpaw-questlog ...`; use `~/.local/bin/ghostpaw-questlog` if it
28
28
  `questlog` and `ghostpaw-questlog` are the same binary.
29
29
 
30
30
  - `questlog search <words>` before `add` to avoid duplicates.
31
- - `ghostpaw-questlog add "..." --tag a,b --due tomorrow`
31
+ - `ghostpaw-questlog add "Write the report" --tag work --due tomorrow`
32
32
  - `questlog list [--view <view>]`: deliveries, questlines, quests.active, quests.available, quests.blocked, quests.deferred, quests.due_soon, quests.in_progress, quests.missed_scheduled, quests.open, quests.overdue, quests.resolved, quests.scheduled_for_day, quests.scheduled_now, repeatables.due_anchors, rumors.
33
- - `questlog done <id> --outcome "..."`
34
- - `questlog search <words>`
35
- - `questlog show <id>`
33
+ - `questlog done 12 --outcome "Report sent"`
34
+ - `questlog search "release"`
35
+ - `questlog show 12`
36
+ - `questlog list --view quests.available`
36
37
  - Use `--json` on any plain command.
37
- - Exit codes: 0 ok / 1 error / 2 usage / 3 needs input.
38
+ - Exit codes: 0 ok / 1 error / 2 usage / 3 needs input or hook target not found.
38
39
  - Database: `~/.ghostpaw/questlog.db`; override with `--db` or `GHOSTPAW_QUESTLOG_DB`.
39
- - Use one database per agent or project.
40
+ - Use one database per project.
41
+ - `questlog --version` prints the installed version.
42
+ - `questlog skill --short` prints this compact cheat sheet.
43
+ - `questlog skill --list` lists guides; `questlog skill webhooks` reads one.
40
44
  - Webhooks: `questlog hook add <url>`, `questlog tick --dry-run`, `questlog watch`. Read `questlog skill webhooks` for events, signing, verification, and cron setup.
41
45
 
42
46
  ## Appendix: MCP and tool calls
@@ -175,16 +179,39 @@ Writes: writes_state.
175
179
  | action | string | yes | attach_quest_to_questline, create_quest, create_questline, detach_quest_from_questline, dismiss_rumor, reopen_rumor, settle_rumor, update_rumor. Action to perform. |
176
180
  | questId | integer | no | Quest to attach or detach. |
177
181
  | questlineId | integer | no | Questline that should receive the quest. |
178
- | quest | object | no | |
182
+ | quest | object | no | Object: title (required), objective (required), questlineId, tags, rewards, dueAt, notBeforeAt, scheduledStartAt, scheduledEndAt, allDay, estimateSeconds |
179
183
  | quest.title | string | yes | Title for the new quest. |
180
184
  | quest.objective | string | yes | Objective for the new quest. |
181
- | questline | object | no | |
185
+ | quest.questlineId | integer | no | Optional questline to place the quest in. |
186
+ | quest.tags | array | no | Optional tags to apply at creation. |
187
+ | quest.rewards | array | no | Optional rewards to attach at creation. |
188
+ | quest.rewards[].kind | string | yes | Reward kind. |
189
+ | quest.rewards[].name | string | yes | Reward name. |
190
+ | quest.dueAt | integer | no | Optional latest acceptable completion timestamp. |
191
+ | quest.notBeforeAt | integer | no | Optional earliest actionable timestamp. |
192
+ | quest.scheduledStartAt | integer | no | Optional planned schedule window start. |
193
+ | quest.scheduledEndAt | integer | no | Optional planned schedule window end. |
194
+ | quest.allDay | boolean | no | Whether the scheduled window is all-day. |
195
+ | quest.estimateSeconds | integer | no | Optional active-effort estimate in seconds. |
196
+ | questline | object | no | Object: title (required), description |
182
197
  | questline.title | string | yes | Questline title. |
183
198
  | questline.description | string | no | Optional questline description. |
184
199
  | rumorId | integer | no | Rumor to settle, dismiss, or reopen. |
185
200
  | dismissedAt | integer | no | Optional dismissal timestamp. |
186
201
  | settledAt | integer | no | Optional settlement timestamp. |
187
202
  | quests | array | no | Quests to create during rumor settlement. Include timing and tags directly. |
203
+ | quests[].title | string | yes | Title for a created quest. |
204
+ | quests[].objective | string | yes | Objective for a created quest. |
205
+ | quests[].tags | array | no | Optional tags to apply at creation. |
206
+ | quests[].rewards | array | no | Optional rewards to attach at creation. |
207
+ | quests[].rewards[].kind | string | yes | Reward kind. |
208
+ | quests[].rewards[].name | string | yes | Reward name. |
209
+ | quests[].dueAt | integer | no | Optional latest acceptable completion timestamp. |
210
+ | quests[].notBeforeAt | integer | no | Optional earliest actionable timestamp. |
211
+ | quests[].scheduledStartAt | integer | no | Optional planned schedule window start. |
212
+ | quests[].scheduledEndAt | integer | no | Optional planned schedule window end. |
213
+ | quests[].allDay | boolean | no | Whether the scheduled window is all-day. |
214
+ | quests[].estimateSeconds | integer | no | Optional active-effort estimate in seconds. |
188
215
  | now | integer | no | Optional timestamp for membership, reopen, or rumor update. |
189
216
  | title | string | no | New title for the update_rumor action. |
190
217
  | details | string | no | New or enriched context text for the update_rumor action. |
@@ -244,6 +271,8 @@ Writes: writes_state.
244
271
  | outcome | string | no | Terminal outcome text. Required for finish, abandon, and abandon_and_spawn_followups. |
245
272
  | resolvedAt | integer | no | Optional resolved timestamp for finish or abandon actions. |
246
273
  | followups | array | no | Follow-up quests to create atomically. Required for abandon_and_spawn_followups. |
274
+ | followups[].title | string | yes | Title for a follow-up quest. |
275
+ | followups[].objective | string | yes | Objective for a follow-up quest. |
247
276
 
248
277
  ```sh
249
278
  ghostpaw-questlog call run_quest '{"action":"start","questId":1}'
@@ -295,7 +324,7 @@ Writes: writes_state.
295
324
  | --- | --- | --- | --- |
296
325
  | action | string | yes | archive, create, preview_due, spawn_due, update. Action to perform. |
297
326
  | repeatableQuestId | integer | no | Repeatable quest to archive or update. |
298
- | repeatableQuest | object | no | |
327
+ | repeatableQuest | object | no | Object: title (required), objective (required), rrule (required), anchorAt (required) |
299
328
  | repeatableQuest.title | string | yes | Title for the new repeatable quest. |
300
329
  | repeatableQuest.objective | string | yes | Objective for the new repeatable quest. |
301
330
  | repeatableQuest.rrule | string | yes | RRULE describing recurrence. |
@@ -333,7 +362,7 @@ Writes: writes_state.
333
362
  | Parameter | Type | Required | Values / description |
334
363
  | --- | --- | --- | --- |
335
364
  | action | string | yes | add, remove, replace. Action to perform. |
336
- | target | object | yes | |
365
+ | target | object | yes | Object: id (required), kind (required) |
337
366
  | target.id | integer | yes | Target entity id. |
338
367
  | target.kind | string | yes | quest, repeatable_quest. Target kind. |
339
368
  | tagNames | array | yes | Tag names to add, remove, or replace. |
@@ -358,14 +387,16 @@ Writes: writes_state.
358
387
  | Parameter | Type | Required | Values / description |
359
388
  | --- | --- | --- | --- |
360
389
  | action | string | yes | add, claim, remove, replace_repeatable_template, update. Action to perform. |
361
- | target | object | yes | |
390
+ | target | object | yes | Object: id (required), kind (required) |
362
391
  | target.id | integer | yes | Target entity id. |
363
392
  | target.kind | string | yes | quest, reward, repeatable_quest. Target kind. |
364
- | reward | object | no | |
393
+ | reward | object | no | Object: kind (required), name (required), now |
365
394
  | reward.kind | string | yes | Reward kind. |
366
395
  | reward.name | string | yes | Reward name. |
367
396
  | reward.now | integer | no | Optional update timestamp for the update action. |
368
397
  | rewards | array | no | Full future reward template set. Required for replace_repeatable_template. |
398
+ | rewards[].kind | string | yes | Reward kind. |
399
+ | rewards[].name | string | yes | Reward name. |
369
400
  | claimedAt | integer | no | Optional claim timestamp. |
370
401
  | now | integer | no | Optional timestamp for removal or template replacement. |
371
402
 
@@ -399,32 +430,32 @@ ghostpaw-questlog call retire_work '{"action":"hide","target":{"kind":"quest","i
399
430
 
400
431
  ### Workflow guides
401
432
 
402
- - `ghostpaw-questlog skill webhooks` ([webhooks](references/webhooks.md)): Configure signed outbound event webhooks and verify deliveries.
403
- - `ghostpaw-questlog skill archive-delete-and-prune` ([archive-delete-and-prune](references/archive-delete-and-prune.md)): Choose the right retirement action for each kind of work so history stays honest and hiding does not get confused with archival or terminal execution truth.
404
- - `ghostpaw-questlog skill choose-work-shape` ([choose-work-shape](references/choose-work-shape.md)): Choose the right structure for new work by separating uncertainty, one-shot execution, multi-step arcs, and recurring patterns.
405
- - `ghostpaw-questlog skill clarify-ambiguous-requests` ([clarify-ambiguous-requests](references/clarify-ambiguous-requests.md)): Treat ambiguity as a real state, capture uncertainty honestly, and only commit work structure once the next executable shape is clear.
406
- - `ghostpaw-questlog skill close-work-and-claim-outcomes` ([close-work-and-claim-outcomes](references/close-work-and-claim-outcomes.md)): Close work with the correct terminal lifecycle action, then handle any concrete reward claim as a separate explicit step.
407
- - `ghostpaw-questlog skill create-concrete-quest` ([create-concrete-quest](references/create-concrete-quest.md)): Create real, bounded executable quests only when the objective is concrete enough to execute without interpretive guesswork.
408
- - `ghostpaw-questlog skill detect-slips-and-staleness` ([detect-slips-and-staleness](references/detect-slips-and-staleness.md)): Detect work that is slipping or going stale by reading overdue, deferred, in-progress, and related operational views before choosing one recovery move.
409
- - `ghostpaw-questlog skill escalate-and-unblock` ([escalate-and-unblock](references/escalate-and-unblock.md)): Diagnose blockers honestly, then remove or realign hard gates when the model is wrong instead of treating every delay as generic escalation theater.
410
- - `ghostpaw-questlog skill frame-initiative-questline` ([frame-initiative-questline](references/frame-initiative-questline.md)): Use questlines for shared initiative context, then attach concrete quests and maintain the structure without confusing one quest with one arc.
411
- - `ghostpaw-questlog skill handle-exceptions-and-weird-cases` ([handle-exceptions-and-weird-cases](references/handle-exceptions-and-weird-cases.md)): Handle weird cases by diagnosing the actual tool outcome, routing into clarification or recovery when needed, and refusing to fake a clean normal-state story.
412
- - `ghostpaw-questlog skill intake-triage` ([intake-triage](references/intake-triage.md)): Triage new asks, ideas, and vague incoming work into the right next step without prematurely committing execution structure.
413
- - `ghostpaw-questlog skill maintain-clear-ownership` ([maintain-clear-ownership](references/maintain-clear-ownership.md)): Keep ownership explicit through quest content and classification because Questlog does not provide a separate assignee field or ownership tool.
414
- - `ghostpaw-questlog skill manage-approvals-and-promises` ([manage-approvals-and-promises](references/manage-approvals-and-promises.md)): Treat approvals and commitments as modeled work with timing and dependencies instead of burying them as vague narrative promises.
415
- - `ghostpaw-questlog skill plan-successor-work` ([plan-successor-work](references/plan-successor-work.md)): Create explicit successor work after closure by spawning follow-ups or shaping new work, while keeping uncertain next steps in intake instead of faking certainty.
416
- - `ghostpaw-questlog skill plan-time-correctly` ([plan-time-correctly](references/plan-time-correctly.md)): Use quest planning fields honestly so availability, due pressure, and schedule intent stay meaningful instead of getting collapsed into one generic date.
417
- - `ghostpaw-questlog skill prioritize-across-streams` ([prioritize-across-streams](references/prioritize-across-streams.md)): Prioritize across competing work streams by reading real candidate sets, distinguishing available from blocked work, and then reflecting the choice in planning.
418
- - `ghostpaw-questlog skill protect-capacity-and-focus` ([protect-capacity-and-focus](references/protect-capacity-and-focus.md)): Protect capacity and focus by reading the real in-progress and scheduled-now load, then using planning to rebalance instead of silently overcommitting.
419
- - `ghostpaw-questlog skill recover-from-modeling-mistakes` ([recover-from-modeling-mistakes](references/recover-from-modeling-mistakes.md)): Recover from modeling mistakes by diagnosing the exact current state first, then applying the smallest honest corrective tool instead of rewriting history blindly.
420
- - `ghostpaw-questlog skill report-status-and-manage-expectations` ([report-status-and-manage-expectations](references/report-status-and-manage-expectations.md)): Build status reporting from the real questlog state, separating available, blocked, in-progress, slipping, and resolved work without relying on memory.
421
- - `ghostpaw-questlog skill revise-or-reshape-commitment` ([revise-or-reshape-commitment](references/revise-or-reshape-commitment.md)): Revise a quest objective before execution starts, but reshape commitments honestly with lifecycle tools once real execution history exists.
422
- - `ghostpaw-questlog skill run-operational-review` ([run-operational-review](references/run-operational-review.md)): Drive daily or weekly operational review from structured list views, then move from diagnosis into actual planning or execution changes.
423
- - `ghostpaw-questlog skill run-recurring-rhythms` ([run-recurring-rhythms](references/run-recurring-rhythms.md)): Model recurring work as repeatable definitions that spawn fresh concrete quests over time instead of living forever as one immortal task.
424
- - `ghostpaw-questlog skill search-and-retrieve-context` ([search-and-retrieve-context](references/search-and-retrieve-context.md)): Find relevant questlog context quickly, then switch from discovery to exact inspection without confusing free-text search with structured review surfaces.
425
- - `ghostpaw-questlog skill sequence-with-unlocks` ([sequence-with-unlocks](references/sequence-with-unlocks.md)): Model hard sequencing with unlocks between concrete quests so blocked and available work stay truthful and reviewable.
426
- - `ghostpaw-questlog skill settle-rumor-into-structure` ([settle-rumor-into-structure](references/settle-rumor-into-structure.md)): Turn a ready rumor into committed quest or questline structure without losing provenance or pretending vague intake is settled.
427
- - `ghostpaw-questlog skill tag-and-filter-portfolios` ([tag-and-filter-portfolios](references/tag-and-filter-portfolios.md)): Use tags for cross-cutting classification and portfolio filtering without confusing tags for status, timing, or dependency modeling.
433
+ - `ghostpaw-questlog skill webhooks`: Configure signed outbound event webhooks and verify deliveries.
434
+ - `ghostpaw-questlog skill archive-delete-and-prune`: Choose the right retirement action for each kind of work so history stays honest and hiding does not get confused with archival or terminal execution truth.
435
+ - `ghostpaw-questlog skill choose-work-shape`: Choose the right structure for new work by separating uncertainty, one-shot execution, multi-step arcs, and recurring patterns.
436
+ - `ghostpaw-questlog skill clarify-ambiguous-requests`: Treat ambiguity as a real state, capture uncertainty honestly, and only commit work structure once the next executable shape is clear.
437
+ - `ghostpaw-questlog skill close-work-and-claim-outcomes`: Close work with the correct terminal lifecycle action, then handle any concrete reward claim as a separate explicit step.
438
+ - `ghostpaw-questlog skill create-concrete-quest`: Create real, bounded executable quests only when the objective is concrete enough to execute without interpretive guesswork.
439
+ - `ghostpaw-questlog skill detect-slips-and-staleness`: Detect work that is slipping or going stale by reading overdue, deferred, in-progress, and related operational views before choosing one recovery move.
440
+ - `ghostpaw-questlog skill escalate-and-unblock`: Diagnose blockers honestly, then remove or realign hard gates when the model is wrong instead of treating every delay as generic escalation theater.
441
+ - `ghostpaw-questlog skill frame-initiative-questline`: Use questlines for shared initiative context, then attach concrete quests and maintain the structure without confusing one quest with one arc.
442
+ - `ghostpaw-questlog skill handle-exceptions-and-weird-cases`: Handle weird cases by diagnosing the actual tool outcome, routing into clarification or recovery when needed, and refusing to fake a clean normal-state story.
443
+ - `ghostpaw-questlog skill intake-triage`: Triage new asks, ideas, and vague incoming work into the right next step without prematurely committing execution structure.
444
+ - `ghostpaw-questlog skill maintain-clear-ownership`: Keep ownership explicit through quest content and classification because Questlog does not provide a separate assignee field or ownership tool.
445
+ - `ghostpaw-questlog skill manage-approvals-and-promises`: Treat approvals and commitments as modeled work with timing and dependencies instead of burying them as vague narrative promises.
446
+ - `ghostpaw-questlog skill plan-successor-work`: Create explicit successor work after closure by spawning follow-ups or shaping new work, while keeping uncertain next steps in intake instead of faking certainty.
447
+ - `ghostpaw-questlog skill plan-time-correctly`: Use quest planning fields honestly so availability, due pressure, and schedule intent stay meaningful instead of getting collapsed into one generic date.
448
+ - `ghostpaw-questlog skill prioritize-across-streams`: Prioritize across competing work streams by reading real candidate sets, distinguishing available from blocked work, and then reflecting the choice in planning.
449
+ - `ghostpaw-questlog skill protect-capacity-and-focus`: Protect capacity and focus by reading the real in-progress and scheduled-now load, then using planning to rebalance instead of silently overcommitting.
450
+ - `ghostpaw-questlog skill recover-from-modeling-mistakes`: Recover from modeling mistakes by diagnosing the exact current state first, then applying the smallest honest corrective tool instead of rewriting history blindly.
451
+ - `ghostpaw-questlog skill report-status-and-manage-expectations`: Build status reporting from the real questlog state, separating available, blocked, in-progress, slipping, and resolved work without relying on memory.
452
+ - `ghostpaw-questlog skill revise-or-reshape-commitment`: Revise a quest objective before execution starts, but reshape commitments honestly with lifecycle tools once real execution history exists.
453
+ - `ghostpaw-questlog skill run-operational-review`: Drive daily or weekly operational review from structured list views, then move from diagnosis into actual planning or execution changes.
454
+ - `ghostpaw-questlog skill run-recurring-rhythms`: Model recurring work as repeatable definitions that spawn fresh concrete quests over time instead of living forever as one immortal task.
455
+ - `ghostpaw-questlog skill search-and-retrieve-context`: Find relevant questlog context quickly, then switch from discovery to exact inspection without confusing free-text search with structured review surfaces.
456
+ - `ghostpaw-questlog skill sequence-with-unlocks`: Model hard sequencing with unlocks between concrete quests so blocked and available work stay truthful and reviewable.
457
+ - `ghostpaw-questlog skill settle-rumor-into-structure`: Turn a ready rumor into committed quest or questline structure without losing provenance or pretending vague intake is settled.
458
+ - `ghostpaw-questlog skill tag-and-filter-portfolios`: Use tags for cross-cutting classification and portfolio filtering without confusing tags for status, timing, or dependency modeling.
428
459
 
429
460
  ### Principles
430
461
 
@@ -21,6 +21,9 @@ questlog hook remove 1
21
21
 
22
22
  `hook add` prints its random 32-byte signing secret once, also as `secret` in `--json`.
23
23
  Save it securely. `--secret-stdin` supplies one via stdin; there is no secret flag.
24
+ Secrets and custom header values are stored in the DB. New POSIX DB/WAL/SHM files use 0600 and created directories use 0700; existing DB modes are preserved. Windows uses existing access controls.
25
+ Use repeatable `--header "Name: value"` or `--header-stdin` (one line per header, avoiding shell history). Headers are sent on every delivery and test; output shows names with `***` values. HTTP token names are required; host, content-length, content-type, and x-questlog-* are reserved. Change headers by removing and adding the hook again. Use --header-stdin and --secret-stdin separately. URL userinfo is rejected; use a custom header or query key.
26
+ `questlog show d_…` displays delivery status, attempts, HTTP status, next attempt, redacted target URL and event ids. Text is the default; --json prints JSON. Missing hook remove/test targets exit 3; missing quests/deliveries exit 1. hook label names the database.
24
27
  List output redacts userinfo/query strings. Removal deletes the target and deliveries.
25
28
  Default scope is the database; `--tag` matches quest tags, never rumors.
26
29
  `--events` defaults to all types. The DB label defaults to the filename without its
@@ -1,2 +0,0 @@
1
- #!/usr/bin/env node
2
- export { openQuestlogDb } from './chunk-ZHTX64KD.js';