push-voice-notes-to-hosted.mjs
99 lines 3.2 KB
Raw
sha256:65ccb454656ea5acdea0a10e559b78bcde1eb6ff753ecc2911bc99d1c3d7cadd feat(calendar): enforce agent context tiers in retrieval AP… Human minor ⚠ breaking 1 day ago
1 #!/usr/bin/env node
2 /**
3 * Push voice-and-boundaries notes from the local vault to the hosted Hub via REST.
4 * Uses the same contract as POST /api/v1/notes (docs/HUB-API.md).
5 *
6 * Auth (pick one):
7 * export KNOWTATION_HUB_TOKEN='...' # raw JWT from Settings → Copy Hub API
8 * export KNOWTATION_HUB_URL='https://knowtation-gateway.netlify.app' # optional
9 *
10 * Or rely on Authorization in ~/.cursor/mcp.json → mcpServers.knowtation-hosted.headers.Authorization
11 */
12 import { readFileSync, existsSync } from 'node:fs';
13 import { homedir } from 'node:os';
14 import { dirname, join } from 'node:path';
15 import { fileURLToPath } from 'node:url';
16
17 const HUB = (process.env.KNOWTATION_HUB_URL || 'https://knowtation-gateway.netlify.app').replace(/\/$/, '');
18 const VAULT_ID = process.env.KNOWTATION_HUB_VAULT_ID || 'default';
19
20 let authHeader = process.env.KNOWTATION_HUB_TOKEN
21 ? `Bearer ${process.env.KNOWTATION_HUB_TOKEN.trim()}`
22 : null;
23
24 if (!authHeader) {
25 const mcpPath = join(homedir(), '.cursor', 'mcp.json');
26 if (existsSync(mcpPath)) {
27 const mcp = JSON.parse(readFileSync(mcpPath, 'utf8'));
28 const h = mcp?.mcpServers?.['knowtation-hosted']?.headers?.Authorization;
29 if (h && String(h).startsWith('Bearer ')) authHeader = h;
30 }
31 }
32
33 if (!authHeader) {
34 console.error('Set KNOWTATION_HUB_TOKEN or configure knowtation-hosted in ~/.cursor/mcp.json');
35 process.exit(1);
36 }
37
38 const roots = [
39 'vault/projects/store-free/style-guide/voice-and-boundaries.md',
40 'vault/projects/knowtation/style-guide/voice-and-boundaries.md',
41 ];
42
43 function parseNote(raw) {
44 const m = raw.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n([\s\S]*)$/);
45 if (!m) return { frontmatter: {}, body: raw };
46 const fmBlock = m[1];
47 const body = m[2];
48 const frontmatter = {};
49 for (const line of fmBlock.split(/\r?\n/)) {
50 const col = line.indexOf(':');
51 if (col === -1) continue;
52 const key = line.slice(0, col).trim();
53 let val = line.slice(col + 1).trim();
54 if (key === 'tags' && val.startsWith('[') && val.endsWith(']')) {
55 frontmatter.tags = val
56 .slice(1, -1)
57 .split(',')
58 .map((s) => s.trim())
59 .filter(Boolean);
60 } else if (val.startsWith('"') && val.endsWith('"')) {
61 try {
62 frontmatter[key] = JSON.parse(val);
63 } catch {
64 frontmatter[key] = val.slice(1, -1);
65 }
66 } else {
67 frontmatter[key] = val;
68 }
69 }
70 return { frontmatter, body };
71 }
72
73 const __dirname = dirname(fileURLToPath(import.meta.url));
74 const repoRoot = join(__dirname, '..');
75
76 for (const rel of roots) {
77 const abs = join(repoRoot, rel);
78 const raw = readFileSync(abs, 'utf8');
79 const { frontmatter, body } = parseNote(raw);
80 const path = rel.replace(/^vault\//, '');
81 const payload = { path, body, frontmatter };
82
83 const res = await fetch(`${HUB}/api/v1/notes`, {
84 method: 'POST',
85 headers: {
86 Authorization: authHeader,
87 'X-Vault-Id': VAULT_ID,
88 'Content-Type': 'application/json',
89 },
90 body: JSON.stringify(payload),
91 });
92 const text = await res.text();
93 if (!res.ok) {
94 console.error(`${path} → ${res.status} ${text}`);
95 process.exitCode = 1;
96 } else {
97 console.log(`${path} → ${res.status} ${text.slice(0, 200)}`);
98 }
99 }
File History 2 commits
sha256:65ccb454656ea5acdea0a10e559b78bcde1eb6ff753ecc2911bc99d1c3d7cadd feat(calendar): enforce agent context tiers in retrieval AP… Human minor 1 day ago
sha256:9103f98c89257ed2b01c237cea895dabb3e85ea337dccb1161c175e4422355b6 docs: accept Calendar Events v0 spec with Phase 0 security … Human 1 day ago