capture-webhook.mjs
134 lines 3.9 KB
Raw
sha256:65ccb454656ea5acdea0a10e559b78bcde1eb6ff753ecc2911bc99d1c3d7cadd feat(calendar): enforce agent context tiers in retrieval AP… Human minor ⚠ breaking 2 days ago
1 #!/usr/bin/env node
2 /**
3 * Webhook server for capture. Receives POST JSON and writes to vault inbox per CAPTURE-CONTRACT.
4 * Optional; use when Slack, Discord, or another service can POST to a URL.
5 *
6 * Usage:
7 * node scripts/capture-webhook.mjs [--port 3131]
8 * PORT=3131 node scripts/capture-webhook.mjs
9 *
10 * POST /capture with JSON body:
11 * { "body": "Message content", "source_id": "msg-123", "source": "slack", "project": "myproject", "tags": "a,b" }
12 *
13 * Required: body. Optional: source_id, source (default webhook), project, tags.
14 * Config: config/local.yaml or env KNOWTATION_VAULT_PATH.
15 */
16
17 import http from 'http';
18 import path from 'path';
19 import { fileURLToPath } from 'url';
20 import { loadConfig } from '../lib/config.mjs';
21 import { writeNote } from '../lib/write.mjs';
22 import { normalizeSlug } from '../lib/vault.mjs';
23
24 const __dirname = path.dirname(fileURLToPath(import.meta.url));
25 const projectRoot = path.resolve(__dirname, '..');
26
27 function parseArgs() {
28 const args = process.argv.slice(2);
29 let port = parseInt(process.env.PORT || '3131', 10);
30 for (let i = 0; i < args.length; i++) {
31 if (args[i] === '--port' && args[i + 1]) {
32 port = parseInt(args[++i], 10);
33 }
34 }
35 return port;
36 }
37
38 function sanitizeForFilename(id) {
39 if (typeof id !== 'string') return '';
40 return id.replace(/[^a-zA-Z0-9_-]/g, '_').slice(0, 64) || 'unknown';
41 }
42
43 function parseJsonBody(req) {
44 return new Promise((resolve, reject) => {
45 let data = '';
46 req.on('data', (chunk) => { data += chunk; });
47 req.on('end', () => {
48 try {
49 resolve(data ? JSON.parse(data) : {});
50 } catch (e) {
51 reject(new Error('Invalid JSON'));
52 }
53 });
54 req.on('error', reject);
55 });
56 }
57
58 function main() {
59 const port = parseArgs();
60 let config;
61 try {
62 config = loadConfig(projectRoot);
63 } catch (e) {
64 console.error('capture-webhook: config error:', e.message);
65 process.exit(2);
66 }
67
68 const server = http.createServer(async (req, res) => {
69 res.setHeader('Content-Type', 'application/json');
70 if (req.method !== 'POST' || req.url !== '/capture') {
71 res.writeHead(404);
72 res.end(JSON.stringify({ error: 'Not found. POST /capture with JSON body.' }));
73 return;
74 }
75
76 let payload;
77 try {
78 payload = await parseJsonBody(req);
79 } catch (e) {
80 res.writeHead(400);
81 res.end(JSON.stringify({ error: e.message }));
82 return;
83 }
84
85 const body = payload.body;
86 if (!body || typeof body !== 'string') {
87 res.writeHead(400);
88 res.end(JSON.stringify({ error: 'body (string) is required' }));
89 return;
90 }
91
92 const source = payload.source || 'webhook';
93 const sourceId = payload.source_id || null;
94 const project = payload.project || null;
95 const tags = payload.tags || null;
96 const now = new Date().toISOString().slice(0, 10);
97 const sourceSlug = normalizeSlug(source) || 'webhook';
98 const filename = sourceId
99 ? `${sourceSlug}_${sanitizeForFilename(sourceId)}.md`
100 : `${sourceSlug}_${Date.now()}.md`;
101
102 const relativePath = project
103 ? `projects/${normalizeSlug(project)}/inbox/${filename}`
104 : `inbox/${filename}`;
105
106 const frontmatter = {
107 source,
108 date: now,
109 ...(sourceId && { source_id: sourceId }),
110 ...(project && { project: normalizeSlug(project) }),
111 ...(tags && { tags }),
112 };
113
114 try {
115 const result = writeNote(config.vault_path, relativePath, {
116 body: body.trimEnd(),
117 frontmatter,
118 });
119 res.writeHead(200);
120 res.end(JSON.stringify({ ok: true, path: result.path }));
121 } catch (e) {
122 console.error('capture-webhook write error:', e.message);
123 res.writeHead(500);
124 res.end(JSON.stringify({ error: e.message }));
125 }
126 });
127
128 server.listen(port, () => {
129 console.log(`capture-webhook listening on http://localhost:${port}`);
130 console.log('POST /capture with JSON: { body, source_id?, source?, project?, tags? }');
131 });
132 }
133
134 main();
File History 2 commits
sha256:65ccb454656ea5acdea0a10e559b78bcde1eb6ff753ecc2911bc99d1c3d7cadd feat(calendar): enforce agent context tiers in retrieval AP… Human minor 2 days ago
sha256:9103f98c89257ed2b01c237cea895dabb3e85ea337dccb1161c175e4422355b6 docs: accept Calendar Events v0 spec with Phase 0 security … Human 2 days ago