@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 CHANGED
@@ -124,6 +124,8 @@ ghostpaw-questlog skill > SKILL.md
124
124
 
125
125
  Each line downloads the latest release asset, checks it against the release’s SHA256SUMS and installs it on the PATH (~/.local/bin on Linux/macOS; the Windows script adds its folder to the user PATH).
126
126
 
127
+ These lines never edit ~/.profile or other shell startup files: `export PATH` applies to the current shell only. To keep it, add `export PATH="$HOME/.local/bin:$PATH"` to your shell profile yourself.
128
+
127
129
  linux-x64:
128
130
 
129
131
  ```sh
@@ -148,17 +150,31 @@ darwin-x64:
148
150
  curl -fsSL https://raw.githubusercontent.com/GhostPawJS/questlog/main/install.sh | sh -s -- --target darwin-x64 && export PATH="$HOME/.local/bin:$PATH"
149
151
  ```
150
152
 
151
- windows-x64 (PowerShell):
153
+ windows-x64 (PowerShell; persistently adds its folder to the user PATH):
152
154
 
153
155
  ```powershell
154
156
  irm https://raw.githubusercontent.com/GhostPawJS/questlog/main/install.ps1 | iex
155
157
  ```
156
158
 
159
+ Pinned to a release:
160
+
161
+ ```sh
162
+ curl -fsSL https://raw.githubusercontent.com/GhostPawJS/questlog/main/install.sh | sh -s -- --target linux-x64 --version 0.10.1 && export PATH="$HOME/.local/bin:$PATH"
163
+ ```
164
+
165
+ To skip the persistent Windows user PATH edit (the installer still installs and prints the folder to add):
166
+
167
+ ```powershell
168
+ $env:GHOSTPAW_NO_MODIFY_PATH=1; irm https://raw.githubusercontent.com/GhostPawJS/questlog/main/install.ps1 | iex
169
+ ```
170
+
157
171
  ```text
158
172
  $ ghostpaw-questlog --version
159
- questlog 0.9.2 (binary, linux-x64)
173
+ questlog 0.10.1 (binary, linux-x64)
160
174
  ```
161
175
 
176
+ Scripts that compare versions should use `--version --short` or `--version --json`.
177
+
162
178
  Standalone binaries are available from [GitHub Releases](https://github.com/GhostPawJS/questlog/releases).
163
179
 
164
180
  A session with the 0.9.1 linux-x64 binary against a fresh database:
@@ -498,3 +514,101 @@ If this package helps your project, consider sponsoring its maintenance:
498
514
  ---
499
515
 
500
516
  **[Anonyfox](https://anonyfox.com) • [MIT License](LICENSE)**
517
+
518
+ ## Webhooks
519
+
520
+ Configure opt-in outbound wakes with the release binary. No server or scheduler is started.
521
+ With no targets, tick makes no network calls. Targets and retry state live in the database.
522
+
523
+ ```sh
524
+ questlog hook add https://example.org/wake --min-gap 60
525
+ questlog hook add https://example.org/team --tag work --events quest.due,quest.overdue --secret-stdin
526
+ questlog hook label work
527
+ questlog hook list
528
+ questlog tick --dry-run
529
+ questlog tick
530
+ questlog watch --interval 60
531
+ questlog hook test 1
532
+ questlog review deliveries
533
+ questlog show 12
534
+ questlog hook remove 1
535
+ ```
536
+
537
+ Save the signing secret printed once by `hook add` (including its `--json` result).
538
+ `--secret-stdin` reads a supplied secret from standard input; there is no secret flag.
539
+ List output redacts URL userinfo and query strings. Removal deletes the target and its deliveries.
540
+ A target covers the database unless `--tag` restricts it to quests with that tag;
541
+ rumors have no tags and reach database targets only. `--events` defaults to all five events.
542
+ The label defaults to the database filename without directory or extension (`questlog` for memory).
543
+ Only use labels suitable for disclosure to the receiver.
544
+
545
+ For cron:
546
+
547
+ ```cron
548
+ * * * * * questlog tick
549
+ ```
550
+
551
+ Cron's PATH may need `$HOME/.local/bin/questlog tick --db /path/to/quests.db`.
552
+ `watch` repeats tick (default 60 seconds, minimum 5), finishing its current tick on SIGINT/SIGTERM.
553
+ Tick exits 0 after running even when deliveries need retrying. Dry runs roll back all changes,
554
+ including repeatable materialization, and make no network calls.
555
+
556
+ Events are detected from the model at tick time:
557
+
558
+ - `quest.due`: an unresolved, undeleted quest reaches its effective due time (own `due_at`, otherwise its undeleted questline's due time).
559
+ - `quest.overdue`: that effective deadline is strictly earlier than now and the quest remains unresolved.
560
+ - `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.
561
+ - `repeatable.spawned`: a concrete repeatable instance was created; occurrence time is its creation time. Tick first materializes due anchors through the existing spawn operation.
562
+ - `rumor.captured`: an undeleted rumor was created; occurrence time is its creation time, even if subsequently settled or dismissed.
563
+
564
+ Each type, entity and occurrence timestamp has a persistent unique event key. Repeated ticks
565
+ and restarts do not create another occurrence. Changes removed before any tick cannot be
566
+ reconstructed. Occurrences earlier than target creation are excluded, preventing backlog floods.
567
+ Tags are evaluated when assigning an event. Already assigned events retain their batch membership.
568
+
569
+ One POST batch per target per tick contains only identifiers, types, timestamps, and the DB label:
570
+
571
+ ```json
572
+ {"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"}]}
573
+ ```
574
+
575
+ Headers are `content-type: application/json`, `user-agent: questlog/<version>`,
576
+ `x-questlog-delivery: <deliveryId>`, `x-questlog-timestamp: <unix seconds>`, and
577
+ `x-questlog-signature: sha256=<hex HMAC-SHA256(secret, timestamp + "." + rawBody)>`.
578
+ No titles, objectives, details, or tag text are sent. `hook test` sends a logged `hook.test`
579
+ event immediately, ignoring the min gap. Add and test are CLI-only in the tool facade;
580
+ `manage_hooks` supports list, remove, tick (with `dry_run`), and deliveries.
581
+
582
+ 2xx delivers the batch. Requests time out after 5 seconds. Other responses, timeouts, and
583
+ network errors retry after 30 seconds, 1 minute, 2 minutes, 4 minutes, and so on, capped
584
+ at 1 hour, plus less than 1 second jitter. The target's min gap also applies to retries.
585
+ Eight unsuccessful attempts mark a delivery failed. Retries retain the delivery id and events.
586
+ Pending batches wait behind retries. Only the last 500 delivered entries per target are kept;
587
+ pending and failed entries are retained. Transactions and two-minute leases prevent overlapping
588
+ ticks from claiming the same delivery. A crash after receipt but before recording success can
589
+ cause another request: receivers must dedupe by delivery id.
590
+
591
+ ### Verifying a delivery
592
+
593
+ Compute HMAC-SHA256 over `timestamp + "." + rawBody` with the secret and compare in constant
594
+ time. Reject timestamps older than 5 minutes (and timestamps too far in the future), then
595
+ dedupe on `x-questlog-delivery`. Use durable deduplication storage in a production receiver.
596
+ This 15-line Node example uses an in-memory set:
597
+
598
+ ```js
599
+ import { createHmac, timingSafeEqual } from 'node:crypto';
600
+ import { createServer } from 'node:http';
601
+ const secret = process.env.WEBHOOK_SECRET, seen = new Set();
602
+ createServer(async (req, res) => {
603
+ const chunks = []; for await (const chunk of req) chunks.push(chunk);
604
+ const rawBody = Buffer.concat(chunks), h = req.headers;
605
+ const timestamp = h['x-questlog-timestamp'], id = h['x-questlog-delivery'];
606
+ const age = Date.now() / 1000 - Number(timestamp);
607
+ const expected = createHmac('sha256', secret).update(timestamp + '.').update(rawBody).digest();
608
+ const signature = String(h['x-questlog-signature'] || '');
609
+ const actual = Buffer.from(signature.slice(7), 'hex');
610
+ 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; }
611
+ if (!seen.has(id)) { seen.add(id); console.log(JSON.parse(rawBody)); }
612
+ res.writeHead(204).end();
613
+ }).listen(8080, '127.0.0.1');
614
+ ```
package/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":"questlines"}'
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](skills/questlog/references/webhooks.md)): Configure signed outbound event webhooks and verify deliveries.
379
403
  - `ghostpaw-questlog skill archive-delete-and-prune` ([archive-delete-and-prune](skills/questlog/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](skills/questlog/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](skills/questlog/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.