@ghostpaw/questlog 0.9.1 → 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 +180 -18
- package/SKILL.md +161 -32
- package/dist/{chunk-3Y5F3SMB.js → chunk-55JAQORX.js} +245 -665
- package/dist/{questlog_skill_spec-LOQ2FNOF.js → chunk-D2W5MYNO.js} +9 -54
- package/dist/chunk-GVD6A3RR.js +108 -0
- package/dist/chunk-IL2QRKZL.js +14 -0
- package/dist/chunk-N7V7VUI5.js +982 -0
- package/dist/chunk-QYSIJ2YN.js +47 -0
- package/dist/{chunk-3B6FMHZU.js → chunk-ZHTX64KD.js} +24 -0
- package/dist/cli.js +1 -1
- package/dist/{cli_main-ELAKW6KW.js → cli_main-7AIEZV75.js} +136 -120
- package/dist/index.cjs +2046 -1393
- package/dist/index.d.cts +142 -11
- package/dist/index.d.ts +142 -11
- package/dist/index.js +2046 -1394
- package/dist/mcp-server.js +959 -386
- package/dist/mcp.cjs +810 -328
- package/dist/mcp.js +811 -329
- package/dist/open_questlog_db-LAEH7UDM.js +2 -0
- package/dist/questlog_skill_spec-HEW2B6EB.js +76 -0
- package/dist/run_hook_command-C42BQC36.js +95 -0
- package/dist/{serve_questlog_mcp-QSHMNNFT.js → serve_questlog_mcp-W3R63HW4.js} +14 -4
- package/dist/{skill_markdown-7HCMSJYR.js → skill_markdown-HXYQ3K6U.js} +34 -6
- package/package.json +6 -3
- package/skills/questlog/SKILL.md +161 -32
- package/skills/questlog/references/webhooks.md +91 -0
- package/dist/chunk-KRQDV4KF.js +0 -58
- package/dist/open_questlog_db-SH3DAU4C.js +0 -2
|
@@ -1,7 +1,4 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { questlogMcpInstructions } from './chunk-SVWAEAKC.js';
|
|
3
|
-
import { reviewQuestlogTool, questlogSurfaceTools } from './chunk-3Y5F3SMB.js';
|
|
4
|
-
|
|
5
2
|
// src/skills/skill_types.ts
|
|
6
3
|
function defineQuestlogSkill(skill) {
|
|
7
4
|
return skill;
|
|
@@ -727,8 +724,16 @@ Failure handling:
|
|
|
727
724
|
- Do not filter by tags and assume that is the same as a schedule or dependency review.`
|
|
728
725
|
});
|
|
729
726
|
|
|
727
|
+
// src/skills/webhooks.ts
|
|
728
|
+
var webhooksSkill = defineQuestlogSkill({
|
|
729
|
+
name: "webhooks",
|
|
730
|
+
description: "Configure signed outbound event webhooks and verify deliveries.",
|
|
731
|
+
content: '# Webhooks\n\nUse `questlog` to configure opt-in outbound automation. No server or scheduler is added.\nNo targets means no network calls. `manage_hooks` supports list, remove, tick with\n`dry_run`, and deliveries; add/test are CLI-only. `review_questlog` view `deliveries`\nand `questlog show <id>` expose pending/failed deliveries.\n\n```sh\nquestlog hook add https://example.org/wake --min-gap 60\nquestlog hook add https://example.org/team --tag work --events quest.due,quest.overdue --secret-stdin\nquestlog hook label work\nquestlog hook list\nquestlog tick --dry-run\nquestlog tick\nquestlog watch --interval 60\nquestlog hook test 1\nquestlog review deliveries\nquestlog show 12\nquestlog hook remove 1\n```\n\n`hook add` prints its random 32-byte signing secret once, also as `secret` in `--json`.\nSave it securely. `--secret-stdin` supplies one via stdin; there is no secret flag.\nList output redacts userinfo/query strings. Removal deletes the target and deliveries.\nDefault scope is the database; `--tag` matches quest tags, never rumors.\n`--events` defaults to all types. The DB label defaults to the filename without its\npath/extension (`questlog` in memory); set it to a name suitable for disclosure.\n\n```cron\n* * * * * questlog tick\n```\n\nCron\'s PATH may need `$HOME/.local/bin/questlog tick --db /path/to/quests.db`.\nWatch defaults to 60 seconds (minimum 5); SIGINT/SIGTERM finishes the current tick.\nTick exits 0 even when deliveries need retries. Dry-run records nothing and sends nothing.\n\nEvents are detected from the model at tick time:\n\n- `quest.due`: an unresolved, undeleted quest reaches its effective due time (own `due_at`, otherwise its undeleted questline\'s due time).\n- `quest.overdue`: that effective deadline is strictly earlier than now and the quest remains unresolved.\n- `quest.unblocked`: an unresolved quest is available, all active prerequisites have succeeded or been deleted, and at least one prerequisite succeeded after its unlock was installed. Occurrence time is the latest active prerequisite success or not-before time, whichever is later.\n- `repeatable.spawned`: a concrete repeatable instance was created; occurrence time is its creation time. Tick first materializes due anchors through the existing spawn operation.\n- `rumor.captured`: an undeleted rumor was created; occurrence time is its creation time, even if subsequently settled or dismissed.\n\nPersistent event keys contain type, entity ids, and occurrence time. Restarts and repeated\nticks do not repeat occurrences. Removed transitions between ticks cannot be reconstructed.\nOnly occurrences at/after target creation are assigned; tags are evaluated at assignment.\nTick materializes due repeatables using the existing spawn path, including during preview\nwith rollback. Batches contain only ids, event types, timestamps, and the DB label:\n\n```json\n{"type":"questlog.events","db":"work","deliveryId":"d_\u2026","sentAt":"2026-10-28T10:00:00.000Z","events":[{"id":"e_\u2026","type":"quest.due","questId":12,"occurredAt":"2026-10-28T09:59:00.000Z"}]}\n```\n\nHeaders are `content-type: application/json`, `user-agent: questlog/<version>`,\n`x-questlog-delivery: <deliveryId>`, `x-questlog-timestamp: <unix seconds>`, and\n`x-questlog-signature: sha256=<hex HMAC-SHA256(secret, timestamp + "." + rawBody)>`.\nNo titles, objectives, details, or tag text are sent. `hook test` sends a logged `hook.test`\nevent immediately, ignoring the min gap. Add and test are CLI-only in the tool facade;\n`manage_hooks` supports list, remove, tick (with `dry_run`), and deliveries.\n\n2xx delivers. Timeout is 5 seconds. Failures retry after 30 s, 1 m, 2 m, 4 m, doubling\nto 1 h plus <1 s jitter, subject to the target\'s min gap. Eight failures mark failed.\nRetries keep the delivery id/events; new events queue behind them. Keep the last 500\ndelivered log entries per target and all pending/failed entries. Transactional claims\nlease work for two minutes. A crash after receipt may retry: dedupe at the receiver.\n\n### Verifying a delivery\n\nCompute HMAC-SHA256 over `timestamp + "." + rawBody` with the secret and compare in constant\ntime. Reject timestamps older than 5 minutes (and timestamps too far in the future), then\ndedupe on `x-questlog-delivery`. Use durable deduplication storage in a production receiver.\nThis 15-line Node example uses an in-memory set:\n\n```js\nimport { createHmac, timingSafeEqual } from \'node:crypto\';\nimport { createServer } from \'node:http\';\nconst secret = process.env.WEBHOOK_SECRET, seen = new Set();\ncreateServer(async (req, res) => {\n const chunks = []; for await (const chunk of req) chunks.push(chunk);\n const rawBody = Buffer.concat(chunks), h = req.headers;\n const timestamp = h[\'x-questlog-timestamp\'], id = h[\'x-questlog-delivery\'];\n const age = Date.now() / 1000 - Number(timestamp);\n const expected = createHmac(\'sha256\', secret).update(timestamp + \'.\').update(rawBody).digest();\n const signature = String(h[\'x-questlog-signature\'] || \'\');\n const actual = Buffer.from(signature.slice(7), \'hex\');\n if (!id || !/^\\d+$/.test(timestamp) || age > 300 || age < -300 || !/^sha256=[a-f0-9]{64}$/.test(signature) || actual.length !== expected.length || !timingSafeEqual(actual, expected)) { res.writeHead(401).end(); return; }\n if (!seen.has(id)) { seen.add(id); console.log(JSON.parse(rawBody)); }\n res.writeHead(204).end();\n}).listen(8080, \'127.0.0.1\');\n```\n'
|
|
732
|
+
});
|
|
733
|
+
|
|
730
734
|
// src/skills/skill_registry.ts
|
|
731
735
|
var questlogSkills = [
|
|
736
|
+
webhooksSkill,
|
|
732
737
|
archiveDeleteAndPruneSkill,
|
|
733
738
|
chooseWorkShapeSkill,
|
|
734
739
|
clarifyAmbiguousRequestsSkill,
|
|
@@ -756,54 +761,4 @@ var questlogSkills = [
|
|
|
756
761
|
tagAndFilterPortfoliosSkill
|
|
757
762
|
];
|
|
758
763
|
|
|
759
|
-
|
|
760
|
-
var questlogSoulTraits = [
|
|
761
|
-
{
|
|
762
|
-
principle: "Store the least committed truth first.",
|
|
763
|
-
provenance: "The worst downstream errors usually start upstream: vague asks are turned into quests, soft ideas become fake deadlines, and half-understood work is treated as fully shaped. Using rumors and clarification before commitment keeps uncertainty where it belongs."
|
|
764
|
-
},
|
|
765
|
-
{
|
|
766
|
-
principle: "Read before you prescribe.",
|
|
767
|
-
provenance: "Many management mistakes are category mistakes: blocked work gets treated as available, missed schedule as overdue, or resolved work as fully closed when follow-through still remains. Reading the derived state first prevents confident but wrong interventions."
|
|
768
|
-
},
|
|
769
|
-
{
|
|
770
|
-
principle: "Protect history when reality changes.",
|
|
771
|
-
provenance: "Once execution starts, rewriting a quest into a different commitment destroys the meaning of the record. Honest finishes, abandonments, and follow-up quests preserve what actually happened and keep later review, planning, and reporting trustworthy."
|
|
772
|
-
},
|
|
773
|
-
{
|
|
774
|
-
principle: "Close the last mile, not just the main work.",
|
|
775
|
-
provenance: "Operational debt often hides in almost-done states: work finished but not claimed, reported, handed off, reviewed, or retired. Treating closure as a real phase prevents stale rewards, lingering initiatives, and misleading progress signals."
|
|
776
|
-
}
|
|
777
|
-
];
|
|
778
|
-
|
|
779
|
-
// src/surface/questlog_skill_spec.ts
|
|
780
|
-
var questlogSkillSpec = {
|
|
781
|
-
name: "questlog",
|
|
782
|
-
cheatSheet: [
|
|
783
|
-
"No install? Use `npx -y @ghostpaw/questlog <command>`.",
|
|
784
|
-
"`questlog` and `ghostpaw-questlog` are the same binary.",
|
|
785
|
-
"",
|
|
786
|
-
"- `questlog search <words>` before `add` to avoid duplicates.",
|
|
787
|
-
'- `questlog add --title "..." --tag a,b --due tomorrow`',
|
|
788
|
-
`- \`questlog list [--view <view>]\`: ${reviewQuestlogTool.inputSchema.properties?.view?.enum?.join(", ")}.`,
|
|
789
|
-
'- `questlog done <id> --outcome "..."`',
|
|
790
|
-
"- `questlog search <words>`",
|
|
791
|
-
"- `questlog show <id>`",
|
|
792
|
-
"- Use `--json` on any plain command.",
|
|
793
|
-
"- Exit codes: 0 ok / 1 error / 2 usage / 3 needs input.",
|
|
794
|
-
"- Database: `~/.ghostpaw/questlog.db`; override with `--db` or `GHOSTPAW_QUESTLOG_DB`.",
|
|
795
|
-
"- Use one database per agent or project."
|
|
796
|
-
].join("\n"),
|
|
797
|
-
description: "Manage local tasks, calendar commitments, and recurring work. Use when planning, executing, reviewing, or organizing work.",
|
|
798
|
-
license: "MIT",
|
|
799
|
-
compatibility: "Node.js 24 or newer. MCP stdio or ghostpaw-questlog CLI.",
|
|
800
|
-
bin: "ghostpaw-questlog",
|
|
801
|
-
packageName: "@ghostpaw/questlog",
|
|
802
|
-
server: "ghostpaw-questlog",
|
|
803
|
-
about: questlogMcpInstructions,
|
|
804
|
-
tools: questlogSurfaceTools,
|
|
805
|
-
workflows: questlogSkills,
|
|
806
|
-
principles: questlogSoulTraits.map((trait) => trait.principle)
|
|
807
|
-
};
|
|
808
|
-
|
|
809
|
-
export { questlogSkillSpec };
|
|
764
|
+
export { questlogSkills };
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { readFileSync } from 'node:fs';
|
|
3
|
+
|
|
4
|
+
var CliUsageError = class extends Error {
|
|
5
|
+
exitCode = 2;
|
|
6
|
+
};
|
|
7
|
+
var keyOf = (name) => name.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
|
|
8
|
+
function parseCliArgs(argv, spec) {
|
|
9
|
+
const values = {};
|
|
10
|
+
const positions = [];
|
|
11
|
+
const flags = spec.flags ?? [];
|
|
12
|
+
const convert = (raw, arg) => {
|
|
13
|
+
let value = raw;
|
|
14
|
+
if (raw.startsWith("@")) {
|
|
15
|
+
try {
|
|
16
|
+
value = readFileSync(raw.slice(1), "utf8");
|
|
17
|
+
} catch {
|
|
18
|
+
throw new CliUsageError(`Cannot read value file: ${raw.slice(1)}`);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
let result = value;
|
|
22
|
+
if (arg.type === "number" || arg.type === "integer") {
|
|
23
|
+
result = Number(value);
|
|
24
|
+
if (!value.trim() || !Number.isFinite(result) || arg.type === "integer" && !Number.isInteger(result))
|
|
25
|
+
throw new CliUsageError(`Expected ${arg.type} for ${arg.name}: ${value}`);
|
|
26
|
+
} else if (arg.type === "boolean") {
|
|
27
|
+
if (!["true", "false"].includes(value))
|
|
28
|
+
throw new CliUsageError(`Expected boolean for ${arg.name}`);
|
|
29
|
+
result = value === "true";
|
|
30
|
+
} else if (arg.type === "json") {
|
|
31
|
+
try {
|
|
32
|
+
result = JSON.parse(value);
|
|
33
|
+
} catch {
|
|
34
|
+
throw new CliUsageError(`Invalid JSON for ${arg.name}`);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
if (arg.enum && !arg.enum.includes(result))
|
|
38
|
+
throw new CliUsageError(`${arg.name} must be one of: ${arg.enum.join(", ")}`);
|
|
39
|
+
return result;
|
|
40
|
+
};
|
|
41
|
+
let ended = false;
|
|
42
|
+
for (let i = 0; i < argv.length; i++) {
|
|
43
|
+
const token = argv[i];
|
|
44
|
+
if (!ended && token === "--") {
|
|
45
|
+
ended = true;
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
if (ended || token === "-" || !token.startsWith("-")) {
|
|
49
|
+
positions.push(token);
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
const [flag, inline] = token.split(/=(.*)/s);
|
|
53
|
+
const negative = flag?.startsWith("--no-");
|
|
54
|
+
const name = flag?.replace(/^--?/, "").replace(/^no-/, "");
|
|
55
|
+
const arg = flags.find(
|
|
56
|
+
(entry) => entry.name === name || keyOf(entry.name) === name || entry.short === name
|
|
57
|
+
);
|
|
58
|
+
if (!arg) {
|
|
59
|
+
const prefix = (name ?? "").slice(0, 3);
|
|
60
|
+
const suggestion = prefix ? flags.find((entry) => entry.name.startsWith(prefix)) : void 0;
|
|
61
|
+
throw new CliUsageError(
|
|
62
|
+
`Unknown flag: ${flag}${suggestion ? `; did you mean --${suggestion.name}?` : ""}`
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
if (negative && arg.type !== "boolean")
|
|
66
|
+
throw new CliUsageError(`--no-${arg.name} requires a boolean flag`);
|
|
67
|
+
let raw = inline;
|
|
68
|
+
if (arg.type === "boolean" && raw === void 0) raw = negative ? "false" : "true";
|
|
69
|
+
if (raw === void 0) {
|
|
70
|
+
const next = argv[i + 1];
|
|
71
|
+
if (next === void 0 || /^--[a-z]/i.test(next))
|
|
72
|
+
throw new CliUsageError(`Missing value: --${arg.name}`);
|
|
73
|
+
raw = next;
|
|
74
|
+
i++;
|
|
75
|
+
}
|
|
76
|
+
const key = keyOf(arg.name);
|
|
77
|
+
if (arg.type === "array") {
|
|
78
|
+
const item = convert(raw, { ...arg, type: arg.itemType ?? "string" });
|
|
79
|
+
values[key] = [...values[key] ?? [], item];
|
|
80
|
+
} else values[key] = convert(raw, arg);
|
|
81
|
+
}
|
|
82
|
+
let index = 0;
|
|
83
|
+
for (const arg of spec.positionals ?? []) {
|
|
84
|
+
if (index >= positions.length) continue;
|
|
85
|
+
values[keyOf(arg.name)] = arg.variadic ? positions.slice(index).map((value) => convert(value, { ...arg, type: arg.itemType ?? arg.type })) : convert(positions[index], arg);
|
|
86
|
+
index = arg.variadic ? positions.length : index + 1;
|
|
87
|
+
}
|
|
88
|
+
if (index < positions.length)
|
|
89
|
+
throw new CliUsageError(`Unexpected positional: ${positions[index]}`);
|
|
90
|
+
for (const arg of [...flags, ...spec.positionals ?? []]) {
|
|
91
|
+
if (arg.required && values[keyOf(arg.name)] === void 0)
|
|
92
|
+
throw new CliUsageError(`Missing required argument: ${arg.name}`);
|
|
93
|
+
}
|
|
94
|
+
return values;
|
|
95
|
+
}
|
|
96
|
+
function renderCliHelp(spec) {
|
|
97
|
+
const describe = (arg) => `${arg.type}${arg.enum ? ` (${arg.enum.join("|")})` : ""}${arg.required ? " required" : ""} ${arg.description ?? ""}`;
|
|
98
|
+
return `Usage: ${spec.command} ${(spec.positionals ?? []).map((arg) => `<${arg.name}${arg.variadic ? "..." : ""}>`).join(" ")} [flags]
|
|
99
|
+
${spec.description ?? ""}
|
|
100
|
+
|
|
101
|
+
Positionals:
|
|
102
|
+
${(spec.positionals ?? []).map((arg) => ` ${arg.name} ${describe(arg)}`).join("\n")}
|
|
103
|
+
Flags:
|
|
104
|
+
${(spec.flags ?? []).map((arg) => ` --${arg.name}${arg.short ? `, -${arg.short}` : ""} ${describe(arg)}`).join("\n")}
|
|
105
|
+
`;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export { CliUsageError, parseCliArgs, renderCliHelp };
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { homedir } from 'node:os';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
|
|
5
|
+
function resolveQuestlogDbPath(options = {}) {
|
|
6
|
+
return options.flag ?? ((options.env ?? process.env).GHOSTPAW_QUESTLOG_DB || join(homedir(), ".ghostpaw", "questlog.db"));
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
// src/surfaces/surface_types.ts
|
|
10
|
+
function isErrorResult(result) {
|
|
11
|
+
return typeof result === "object" && result !== null && "ok" in result && result.ok === false;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export { isErrorResult, resolveQuestlogDbPath };
|