@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
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { questlogMcpInstructions } from './chunk-SVWAEAKC.js';
|
|
3
|
-
import { questlogSkills } from './chunk-
|
|
4
|
-
import { reviewQuestlogTool, questlogSurfaceTools } from './chunk-
|
|
3
|
+
import { questlogSkills } from './chunk-D2W5MYNO.js';
|
|
4
|
+
import { reviewQuestlogTool, questlogSurfaceTools } from './chunk-55JAQORX.js';
|
|
5
|
+
import './chunk-N7V7VUI5.js';
|
|
5
6
|
import './chunk-QYSIJ2YN.js';
|
|
6
7
|
|
|
7
8
|
// src/soul.ts
|
|
@@ -57,7 +58,8 @@ var questlogSkillSpec = {
|
|
|
57
58
|
"- Use `--json` on any plain command.",
|
|
58
59
|
"- Exit codes: 0 ok / 1 error / 2 usage / 3 needs input.",
|
|
59
60
|
"- Database: `~/.ghostpaw/questlog.db`; override with `--db` or `GHOSTPAW_QUESTLOG_DB`.",
|
|
60
|
-
"- Use one database per agent or project."
|
|
61
|
+
"- Use one database per agent or project.",
|
|
62
|
+
"- Webhooks: `questlog hook add <url>`, `questlog tick --dry-run`, `questlog watch`. Read `questlog skill webhooks` for events, signing, verification, and cron setup."
|
|
61
63
|
].join("\n"),
|
|
62
64
|
description: "Use the release binary ghostpaw-questlog to manage local tasks, calendar commitments, and recurring work. Use when planning, executing, reviewing, or organizing work.",
|
|
63
65
|
license: "MIT",
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { parseCliArgs } from './chunk-GVD6A3RR.js';
|
|
3
|
+
import { watchQuestlog, testHook, removeHook, hookLabel, listHooks, addHook } from './chunk-N7V7VUI5.js';
|
|
4
|
+
|
|
5
|
+
// src/surface/run_hook_command.ts
|
|
6
|
+
async function runHookCommand(command, args, io, open) {
|
|
7
|
+
let db;
|
|
8
|
+
try {
|
|
9
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
10
|
+
io.stdout.write(
|
|
11
|
+
"questlog hook add <url> [--tag name] [--events types] [--min-gap seconds] [--secret-stdin] [--json]\nquestlog hook list|remove <id>|test <id>|label [name]\nquestlog watch [--interval seconds] [--json]\n"
|
|
12
|
+
);
|
|
13
|
+
return 0;
|
|
14
|
+
}
|
|
15
|
+
const action = command === "watch" ? "watch" : args.shift();
|
|
16
|
+
if (!["add", "list", "remove", "test", "label", "watch"].includes(action ?? ""))
|
|
17
|
+
throw new Error("Expected hook add, list, remove, test, or label");
|
|
18
|
+
const values = parseCliArgs(args, {
|
|
19
|
+
command,
|
|
20
|
+
flags: [
|
|
21
|
+
{ name: "json", type: "boolean" },
|
|
22
|
+
...action === "add" ? [
|
|
23
|
+
{ name: "tag", type: "string" },
|
|
24
|
+
{ name: "events", type: "string" },
|
|
25
|
+
{ name: "min-gap", type: "integer" },
|
|
26
|
+
{ name: "secret-stdin", type: "boolean" }
|
|
27
|
+
] : action === "watch" ? [{ name: "interval", type: "number" }] : []
|
|
28
|
+
],
|
|
29
|
+
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" }] : []
|
|
30
|
+
});
|
|
31
|
+
if (action === "watch" && values.interval !== void 0 && Number(values.interval) < 5)
|
|
32
|
+
throw new Error("Interval must be at least 5 seconds");
|
|
33
|
+
db = await open();
|
|
34
|
+
let result;
|
|
35
|
+
switch (action) {
|
|
36
|
+
case "add": {
|
|
37
|
+
let secret;
|
|
38
|
+
if (values.secretStdin) {
|
|
39
|
+
secret = "";
|
|
40
|
+
for await (const chunk of io.stdin) secret += String(chunk);
|
|
41
|
+
secret = secret.replace(/\r?\n$/, "");
|
|
42
|
+
}
|
|
43
|
+
result = addHook(db, {
|
|
44
|
+
url: String(values.url),
|
|
45
|
+
...values.tag === void 0 ? {} : { tag: String(values.tag) },
|
|
46
|
+
...values.events === void 0 ? {} : { events: String(values.events).split(",") },
|
|
47
|
+
...values.minGap === void 0 ? {} : { minGap: Number(values.minGap) },
|
|
48
|
+
...secret === void 0 ? {} : { secret }
|
|
49
|
+
});
|
|
50
|
+
break;
|
|
51
|
+
}
|
|
52
|
+
case "list":
|
|
53
|
+
result = listHooks(db);
|
|
54
|
+
break;
|
|
55
|
+
case "label":
|
|
56
|
+
result = { label: hookLabel(db, values.name) };
|
|
57
|
+
break;
|
|
58
|
+
case "remove":
|
|
59
|
+
removeHook(db, Number(values.id));
|
|
60
|
+
result = { removed: Number(values.id) };
|
|
61
|
+
break;
|
|
62
|
+
case "test":
|
|
63
|
+
result = await testHook(db, Number(values.id));
|
|
64
|
+
break;
|
|
65
|
+
case "watch": {
|
|
66
|
+
const controller = new AbortController();
|
|
67
|
+
const stop = () => controller.abort();
|
|
68
|
+
process.on("SIGINT", stop);
|
|
69
|
+
process.on("SIGTERM", stop);
|
|
70
|
+
try {
|
|
71
|
+
await watchQuestlog(db, {
|
|
72
|
+
signal: controller.signal,
|
|
73
|
+
interval: Number(values.interval ?? 60),
|
|
74
|
+
onTick: (tick) => io.stdout.write(`${JSON.stringify(tick)}
|
|
75
|
+
`)
|
|
76
|
+
});
|
|
77
|
+
} finally {
|
|
78
|
+
process.off("SIGINT", stop);
|
|
79
|
+
process.off("SIGTERM", stop);
|
|
80
|
+
}
|
|
81
|
+
return 0;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
io.stdout.write(`${JSON.stringify(result, null, 2)}
|
|
85
|
+
`);
|
|
86
|
+
return 0;
|
|
87
|
+
} catch {
|
|
88
|
+
io.stderr.write("Invalid webhook command or unavailable target. Run questlog hook --help.\n");
|
|
89
|
+
return 2;
|
|
90
|
+
} finally {
|
|
91
|
+
db?.close();
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export { runHookCommand };
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { openQuestlogDb } from './chunk-
|
|
2
|
+
import { openQuestlogDb } from './chunk-ZHTX64KD.js';
|
|
3
3
|
import { questlogMcpInstructions } from './chunk-SVWAEAKC.js';
|
|
4
|
-
import { isErrorResult,
|
|
5
|
-
import { validateInput, questlogSurfaceTools } from './chunk-
|
|
4
|
+
import { isErrorResult, resolveQuestlogDbPath } from './chunk-IL2QRKZL.js';
|
|
5
|
+
import { validateInput, questlogSurfaceTools } from './chunk-55JAQORX.js';
|
|
6
|
+
import { questlogVersion } from './chunk-N7V7VUI5.js';
|
|
6
7
|
import { minimalInput } from './chunk-QYSIJ2YN.js';
|
|
7
8
|
|
|
8
9
|
// src/surfaces/mcp_server.ts
|
package/package.json
CHANGED
package/skills/questlog/SKILL.md
CHANGED
|
@@ -29,7 +29,7 @@ Run tools as `ghostpaw-questlog ...`; use `~/.local/bin/ghostpaw-questlog` if it
|
|
|
29
29
|
|
|
30
30
|
- `questlog search <words>` before `add` to avoid duplicates.
|
|
31
31
|
- `ghostpaw-questlog add "..." --tag a,b --due tomorrow`
|
|
32
|
-
- `questlog list [--view <view>]`: 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.
|
|
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
33
|
- `questlog done <id> --outcome "..."`
|
|
34
34
|
- `questlog search <words>`
|
|
35
35
|
- `questlog show <id>`
|
|
@@ -37,6 +37,7 @@ Run tools as `ghostpaw-questlog ...`; use `~/.local/bin/ghostpaw-questlog` if it
|
|
|
37
37
|
- Exit codes: 0 ok / 1 error / 2 usage / 3 needs input.
|
|
38
38
|
- Database: `~/.ghostpaw/questlog.db`; override with `--db` or `GHOSTPAW_QUESTLOG_DB`.
|
|
39
39
|
- Use one database per agent or project.
|
|
40
|
+
- Webhooks: `questlog hook add <url>`, `questlog tick --dry-run`, `questlog watch`. Read `questlog skill webhooks` for events, signing, verification, and cron setup.
|
|
40
41
|
|
|
41
42
|
## Appendix: MCP and tool calls
|
|
42
43
|
|
|
@@ -50,6 +51,28 @@ CLI: `ghostpaw-questlog call <tool> '<JSON object>'` or `ghostpaw-questlog <tool
|
|
|
50
51
|
|
|
51
52
|
Inspect schemas with `ghostpaw-questlog describe <tool>`. Use `--db <path>` to select a database.
|
|
52
53
|
|
|
54
|
+
### manage_hooks
|
|
55
|
+
|
|
56
|
+
List or remove outbound webhook targets, run a tick, or inspect pending and failed deliveries.
|
|
57
|
+
|
|
58
|
+
Use when: Use to operate configured outbound webhooks. Add and test targets with the CLI.
|
|
59
|
+
|
|
60
|
+
Not for: Use quest tools to change work itself.
|
|
61
|
+
|
|
62
|
+
Writes: writes_state.
|
|
63
|
+
|
|
64
|
+
| Parameter | Type | Required | Values / description |
|
|
65
|
+
| --- | --- | --- | --- |
|
|
66
|
+
| action | string | yes | list, remove, tick, deliveries. Operation. |
|
|
67
|
+
| id | integer | no | Target id. |
|
|
68
|
+
| dry_run | boolean | no | Preview only. |
|
|
69
|
+
|
|
70
|
+
```sh
|
|
71
|
+
ghostpaw-questlog call manage_hooks '{"action":"list"}'
|
|
72
|
+
ghostpaw-questlog call manage_hooks '{"action":"remove","id":1}'
|
|
73
|
+
ghostpaw-questlog call manage_hooks '{"action":"tick"}'
|
|
74
|
+
```
|
|
75
|
+
|
|
53
76
|
### search_questlog
|
|
54
77
|
|
|
55
78
|
Search quests and rumors by text when you need to find an existing item before inspecting or changing it.
|
|
@@ -83,7 +106,7 @@ Read-only.
|
|
|
83
106
|
|
|
84
107
|
| Parameter | Type | Required | Values / description |
|
|
85
108
|
| --- | --- | --- | --- |
|
|
86
|
-
| view | string | yes | 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. Which operational review surface to return. |
|
|
109
|
+
| view | string | yes | 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. Which operational review surface to return. |
|
|
87
110
|
| filters | object | no | Optional filters for supported review surfaces. |
|
|
88
111
|
| filters.questlineId | integer | no | Optional questline id used by quest views. |
|
|
89
112
|
| filters.tagNames | array | no | Optional tag names used by quest views. |
|
|
@@ -92,7 +115,7 @@ Read-only.
|
|
|
92
115
|
| now | integer | no | Optional evaluation timestamp for time-sensitive views. |
|
|
93
116
|
|
|
94
117
|
```sh
|
|
95
|
-
ghostpaw-questlog call review_questlog '{"view":"
|
|
118
|
+
ghostpaw-questlog call review_questlog '{"view":"deliveries"}'
|
|
96
119
|
```
|
|
97
120
|
|
|
98
121
|
### inspect_questlog_item
|
|
@@ -376,6 +399,7 @@ ghostpaw-questlog call retire_work '{"action":"hide","target":{"kind":"quest","i
|
|
|
376
399
|
|
|
377
400
|
### Workflow guides
|
|
378
401
|
|
|
402
|
+
- `ghostpaw-questlog skill webhooks` ([webhooks](references/webhooks.md)): Configure signed outbound event webhooks and verify deliveries.
|
|
379
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.
|
|
380
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.
|
|
381
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.
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# Webhooks
|
|
2
|
+
|
|
3
|
+
Use `questlog` to configure opt-in outbound automation. No server or scheduler is added.
|
|
4
|
+
No targets means no network calls. `manage_hooks` supports list, remove, tick with
|
|
5
|
+
`dry_run`, and deliveries; add/test are CLI-only. `review_questlog` view `deliveries`
|
|
6
|
+
and `questlog show <id>` expose pending/failed deliveries.
|
|
7
|
+
|
|
8
|
+
```sh
|
|
9
|
+
questlog hook add https://example.org/wake --min-gap 60
|
|
10
|
+
questlog hook add https://example.org/team --tag work --events quest.due,quest.overdue --secret-stdin
|
|
11
|
+
questlog hook label work
|
|
12
|
+
questlog hook list
|
|
13
|
+
questlog tick --dry-run
|
|
14
|
+
questlog tick
|
|
15
|
+
questlog watch --interval 60
|
|
16
|
+
questlog hook test 1
|
|
17
|
+
questlog review deliveries
|
|
18
|
+
questlog show 12
|
|
19
|
+
questlog hook remove 1
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
`hook add` prints its random 32-byte signing secret once, also as `secret` in `--json`.
|
|
23
|
+
Save it securely. `--secret-stdin` supplies one via stdin; there is no secret flag.
|
|
24
|
+
List output redacts userinfo/query strings. Removal deletes the target and deliveries.
|
|
25
|
+
Default scope is the database; `--tag` matches quest tags, never rumors.
|
|
26
|
+
`--events` defaults to all types. The DB label defaults to the filename without its
|
|
27
|
+
path/extension (`questlog` in memory); set it to a name suitable for disclosure.
|
|
28
|
+
|
|
29
|
+
```cron
|
|
30
|
+
* * * * * questlog tick
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Cron's PATH may need `$HOME/.local/bin/questlog tick --db /path/to/quests.db`.
|
|
34
|
+
Watch defaults to 60 seconds (minimum 5); SIGINT/SIGTERM finishes the current tick.
|
|
35
|
+
Tick exits 0 even when deliveries need retries. Dry-run records nothing and sends nothing.
|
|
36
|
+
|
|
37
|
+
Events are detected from the model at tick time:
|
|
38
|
+
|
|
39
|
+
- `quest.due`: an unresolved, undeleted quest reaches its effective due time (own `due_at`, otherwise its undeleted questline's due time).
|
|
40
|
+
- `quest.overdue`: that effective deadline is strictly earlier than now and the quest remains unresolved.
|
|
41
|
+
- `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.
|
|
42
|
+
- `repeatable.spawned`: a concrete repeatable instance was created; occurrence time is its creation time. Tick first materializes due anchors through the existing spawn operation.
|
|
43
|
+
- `rumor.captured`: an undeleted rumor was created; occurrence time is its creation time, even if subsequently settled or dismissed.
|
|
44
|
+
|
|
45
|
+
Persistent event keys contain type, entity ids, and occurrence time. Restarts and repeated
|
|
46
|
+
ticks do not repeat occurrences. Removed transitions between ticks cannot be reconstructed.
|
|
47
|
+
Only occurrences at/after target creation are assigned; tags are evaluated at assignment.
|
|
48
|
+
Tick materializes due repeatables using the existing spawn path, including during preview
|
|
49
|
+
with rollback. Batches contain only ids, event types, timestamps, and the DB label:
|
|
50
|
+
|
|
51
|
+
```json
|
|
52
|
+
{"type":"questlog.events","db":"work","deliveryId":"d_…","sentAt":"2026-10-28T10:00:00.000Z","events":[{"id":"e_…","type":"quest.due","questId":12,"occurredAt":"2026-10-28T09:59:00.000Z"}]}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Headers are `content-type: application/json`, `user-agent: questlog/<version>`,
|
|
56
|
+
`x-questlog-delivery: <deliveryId>`, `x-questlog-timestamp: <unix seconds>`, and
|
|
57
|
+
`x-questlog-signature: sha256=<hex HMAC-SHA256(secret, timestamp + "." + rawBody)>`.
|
|
58
|
+
No titles, objectives, details, or tag text are sent. `hook test` sends a logged `hook.test`
|
|
59
|
+
event immediately, ignoring the min gap. Add and test are CLI-only in the tool facade;
|
|
60
|
+
`manage_hooks` supports list, remove, tick (with `dry_run`), and deliveries.
|
|
61
|
+
|
|
62
|
+
2xx delivers. Timeout is 5 seconds. Failures retry after 30 s, 1 m, 2 m, 4 m, doubling
|
|
63
|
+
to 1 h plus <1 s jitter, subject to the target's min gap. Eight failures mark failed.
|
|
64
|
+
Retries keep the delivery id/events; new events queue behind them. Keep the last 500
|
|
65
|
+
delivered log entries per target and all pending/failed entries. Transactional claims
|
|
66
|
+
lease work for two minutes. A crash after receipt may retry: dedupe at the receiver.
|
|
67
|
+
|
|
68
|
+
### Verifying a delivery
|
|
69
|
+
|
|
70
|
+
Compute HMAC-SHA256 over `timestamp + "." + rawBody` with the secret and compare in constant
|
|
71
|
+
time. Reject timestamps older than 5 minutes (and timestamps too far in the future), then
|
|
72
|
+
dedupe on `x-questlog-delivery`. Use durable deduplication storage in a production receiver.
|
|
73
|
+
This 15-line Node example uses an in-memory set:
|
|
74
|
+
|
|
75
|
+
```js
|
|
76
|
+
import { createHmac, timingSafeEqual } from 'node:crypto';
|
|
77
|
+
import { createServer } from 'node:http';
|
|
78
|
+
const secret = process.env.WEBHOOK_SECRET, seen = new Set();
|
|
79
|
+
createServer(async (req, res) => {
|
|
80
|
+
const chunks = []; for await (const chunk of req) chunks.push(chunk);
|
|
81
|
+
const rawBody = Buffer.concat(chunks), h = req.headers;
|
|
82
|
+
const timestamp = h['x-questlog-timestamp'], id = h['x-questlog-delivery'];
|
|
83
|
+
const age = Date.now() / 1000 - Number(timestamp);
|
|
84
|
+
const expected = createHmac('sha256', secret).update(timestamp + '.').update(rawBody).digest();
|
|
85
|
+
const signature = String(h['x-questlog-signature'] || '');
|
|
86
|
+
const actual = Buffer.from(signature.slice(7), 'hex');
|
|
87
|
+
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; }
|
|
88
|
+
if (!seen.has(id)) { seen.add(id); console.log(JSON.parse(rawBody)); }
|
|
89
|
+
res.writeHead(204).end();
|
|
90
|
+
}).listen(8080, '127.0.0.1');
|
|
91
|
+
```
|