@ghostpaw/questlog 0.10.1 → 0.11.0
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 +31 -5
- package/SKILL.md +69 -38
- package/dist/chunk-7KTRNXTU.js +21 -0
- package/dist/chunk-D6EGZCT7.js +23 -0
- package/dist/{chunk-D2W5MYNO.js → chunk-I2AMIXGP.js} +1 -1
- package/dist/{chunk-ZHTX64KD.js → chunk-IA2GVCZS.js} +11 -2
- package/dist/{chunk-55JAQORX.js → chunk-KOWIVBG6.js} +3 -1
- package/dist/chunk-NBCCA6AX.js +31 -0
- package/dist/chunk-QK3H2AL5.js +36 -0
- package/dist/{chunk-N7V7VUI5.js → chunk-RBGQK5WP.js} +54 -87
- package/dist/cli.js +1 -1
- package/dist/{cli_main-7AIEZV75.js → cli_main-TEG3QTDP.js} +78 -21
- package/dist/index.cjs +23 -6
- package/dist/index.js +23 -6
- package/dist/list_deliveries-WJEJUZIQ.js +2 -0
- package/dist/list_hooks-C5CAA2C7.js +2 -0
- package/dist/mcp-server.js +22 -5
- package/dist/mcp.cjs +21 -4
- package/dist/mcp.js +22 -5
- package/dist/open_questlog_db-6NS6BVBC.js +2 -0
- package/dist/{questlog_skill_spec-HEW2B6EB.js → questlog_skill_spec-ILTCTRYS.js} +15 -9
- package/dist/run_hook_command-BOENYNDT.js +155 -0
- package/dist/{serve_questlog_mcp-W3R63HW4.js → serve_questlog_mcp-X524X34F.js} +5 -3
- package/dist/{skill_markdown-HXYQ3K6U.js → skill_markdown-EGKYZ3W2.js} +4 -20
- package/dist/text_output-X7F3K23Q.js +2 -0
- package/package.json +1 -1
- package/skills/questlog/SKILL.md +69 -38
- package/skills/questlog/references/webhooks.md +3 -0
- package/dist/open_questlog_db-LAEH7UDM.js +0 -2
- package/dist/run_hook_command-C42BQC36.js +0 -95
package/dist/mcp-server.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { mkdirSync } from 'node:fs';
|
|
2
|
+
import { mkdirSync, closeSync, openSync } from 'node:fs';
|
|
3
3
|
import { basename, extname, dirname, join } from 'node:path';
|
|
4
4
|
import { randomBytes, createHmac } from 'node:crypto';
|
|
5
5
|
import { homedir } from 'node:os';
|
|
@@ -345,6 +345,8 @@ function initHookTables(db) {
|
|
|
345
345
|
CREATE INDEX IF NOT EXISTS idx_hook_deliveries_target ON hook_deliveries(target_id, status, next_attempt_at);
|
|
346
346
|
CREATE TABLE IF NOT EXISTS hook_settings (id INTEGER PRIMARY KEY CHECK(id = 1), label TEXT NOT NULL);
|
|
347
347
|
`);
|
|
348
|
+
if (!db.prepare("PRAGMA table_info(hook_targets)").all().some((row) => row.name === "headers"))
|
|
349
|
+
db.exec("ALTER TABLE hook_targets ADD COLUMN headers TEXT NOT NULL DEFAULT '{}'");
|
|
348
350
|
}
|
|
349
351
|
var init_init_hook_tables = __esm({
|
|
350
352
|
"src/hooks/init_hook_tables.ts"() {
|
|
@@ -766,7 +768,14 @@ async function openQuestlogDb(path) {
|
|
|
766
768
|
} finally {
|
|
767
769
|
process.emitWarning = original;
|
|
768
770
|
}
|
|
769
|
-
if (path !== ":memory:")
|
|
771
|
+
if (path !== ":memory:") {
|
|
772
|
+
mkdirSync(dirname(path), { recursive: true, mode: 448 });
|
|
773
|
+
try {
|
|
774
|
+
closeSync(openSync(path, "wx", 384));
|
|
775
|
+
} catch (error) {
|
|
776
|
+
if (error.code !== "EEXIST") throw error;
|
|
777
|
+
}
|
|
778
|
+
}
|
|
770
779
|
const db = new sqlite.DatabaseSync(path);
|
|
771
780
|
try {
|
|
772
781
|
db.exec("PRAGMA journal_mode=WAL; PRAGMA busy_timeout=5000;");
|
|
@@ -788,7 +797,7 @@ var init_open_questlog_db = __esm({
|
|
|
788
797
|
var questlogVersion;
|
|
789
798
|
var init_version = __esm({
|
|
790
799
|
"src/version.ts"() {
|
|
791
|
-
questlogVersion = "0.
|
|
800
|
+
questlogVersion = "0.11.0";
|
|
792
801
|
}
|
|
793
802
|
});
|
|
794
803
|
|
|
@@ -2137,7 +2146,7 @@ var init_hook_label = __esm({
|
|
|
2137
2146
|
|
|
2138
2147
|
// src/hooks/list_hooks.ts
|
|
2139
2148
|
function listHooks(db) {
|
|
2140
|
-
return db.prepare(`SELECT id, url, tag, events, min_gap, created_at, last_wake,
|
|
2149
|
+
return db.prepare(`SELECT id, url, headers, tag, events, min_gap, created_at, last_wake,
|
|
2141
2150
|
(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,
|
|
2142
2151
|
(SELECT COUNT(*) FROM hook_deliveries d WHERE d.target_id = t.id AND d.status = 'failed') AS failed
|
|
2143
2152
|
FROM hook_targets t ORDER BY id`).all().map((row) => {
|
|
@@ -2148,6 +2157,9 @@ function listHooks(db) {
|
|
|
2148
2157
|
url.hash = "";
|
|
2149
2158
|
return {
|
|
2150
2159
|
id: Number(row.id),
|
|
2160
|
+
headers: Object.fromEntries(
|
|
2161
|
+
Object.keys(JSON.parse(String(row.headers))).map((name) => [name, "***"])
|
|
2162
|
+
),
|
|
2151
2163
|
scope: row.tag == null ? "database" : "tag",
|
|
2152
2164
|
url: url.href,
|
|
2153
2165
|
tag: row.tag == null ? null : String(row.tag),
|
|
@@ -2236,8 +2248,13 @@ async function sendHookDelivery(db, delivery, target, options) {
|
|
|
2236
2248
|
method: "POST",
|
|
2237
2249
|
redirect: "error",
|
|
2238
2250
|
headers: {
|
|
2239
|
-
"content-type": "application/json",
|
|
2240
2251
|
"user-agent": `questlog/${questlogVersion}`,
|
|
2252
|
+
...JSON.parse(
|
|
2253
|
+
String(
|
|
2254
|
+
db.prepare("SELECT headers FROM hook_targets WHERE id = ?").get(target.id)?.headers ?? "{}"
|
|
2255
|
+
)
|
|
2256
|
+
),
|
|
2257
|
+
"content-type": "application/json",
|
|
2241
2258
|
"x-questlog-delivery": delivery.id,
|
|
2242
2259
|
"x-questlog-timestamp": timestamp,
|
|
2243
2260
|
"x-questlog-signature": `sha256=${signature}`
|
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:")
|
|
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.
|
|
423
|
+
var questlogVersion = "0.11.0";
|
|
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:")
|
|
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.
|
|
421
|
+
var questlogVersion = "0.11.0";
|
|
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}`
|
|
@@ -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 {
|
|
4
|
-
import
|
|
5
|
-
import './chunk-
|
|
4
|
+
import { reviewQuestlogTool, questlogSurfaceTools } from './chunk-KOWIVBG6.js';
|
|
5
|
+
import './chunk-RBGQK5WP.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 "
|
|
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
|
|
56
|
-
|
|
57
|
-
"- `questlog show
|
|
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
|
|
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-GVD6A3RR.js';
|
|
3
|
+
import { textOutput } from './chunk-QK3H2AL5.js';
|
|
4
|
+
import { watchQuestlog, testHook, removeHook, hookLabel, withTransaction, addHook } from './chunk-RBGQK5WP.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-
|
|
6
|
-
import {
|
|
4
|
+
import { validateInput, questlogSurfaceTools } from './chunk-KOWIVBG6.js';
|
|
5
|
+
import { openQuestlogDb } from './chunk-IA2GVCZS.js';
|
|
6
|
+
import { questlogVersion } from './chunk-RBGQK5WP.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
|
-
|
|
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}
|
|
66
|
+
(workflow) => `- \`${spec.bin} skill ${workflow.name}\`: ${workflow.description}`
|
|
83
67
|
),
|
|
84
68
|
"",
|
|
85
69
|
"## Principles",
|
package/package.json
CHANGED