@jhizzard/termdeck-stack 0.4.10 → 0.4.12
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/assets/hooks/README.md +2 -2
- package/assets/hooks/memory-session-end.js +3 -3
- package/package.json +1 -1
- package/src/index.js +76 -6
package/assets/hooks/README.md
CHANGED
|
@@ -18,7 +18,7 @@ The hook:
|
|
|
18
18
|
|
|
19
19
|
1. Skips transcripts smaller than 5 KB (no signal in tiny sessions —
|
|
20
20
|
override via `TERMDECK_HOOK_MIN_BYTES`).
|
|
21
|
-
2. Validates env vars (`SUPABASE_URL`, `
|
|
21
|
+
2. Validates env vars (`SUPABASE_URL`, `SUPABASE_SERVICE_ROLE_KEY`,
|
|
22
22
|
`OPENAI_API_KEY`); if any are missing, logs the missing list and
|
|
23
23
|
exits cleanly without blocking the session close.
|
|
24
24
|
3. Detects the project from `cwd` against a built-in regex table; falls
|
|
@@ -44,7 +44,7 @@ The hook needs three env vars at run time:
|
|
|
44
44
|
| Var | What | How to set |
|
|
45
45
|
|---|---|---|
|
|
46
46
|
| `SUPABASE_URL` | Your Supabase project URL (e.g. `https://abc.supabase.co`) | `~/.termdeck/secrets.env` (Tier 2) |
|
|
47
|
-
| `
|
|
47
|
+
| `SUPABASE_SERVICE_ROLE_KEY` | Service-role key with INSERT on `memory_items`. **Not the anon key.** | `~/.termdeck/secrets.env` |
|
|
48
48
|
| `OPENAI_API_KEY` | OpenAI key for embedding inference | `~/.termdeck/secrets.env` or your shell |
|
|
49
49
|
|
|
50
50
|
Claude Code propagates the parent shell's environment into hook
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
*
|
|
23
23
|
* Required env vars (validated at entry):
|
|
24
24
|
* - SUPABASE_URL e.g. https://<project-ref>.supabase.co
|
|
25
|
-
* -
|
|
25
|
+
* - SUPABASE_SERVICE_ROLE_KEY service-role key (NOT the anon key — needs INSERT on memory_items)
|
|
26
26
|
* - OPENAI_API_KEY sk-... for text-embedding-3-small
|
|
27
27
|
*
|
|
28
28
|
* Optional:
|
|
@@ -73,7 +73,7 @@ function detectProject(cwd) {
|
|
|
73
73
|
}
|
|
74
74
|
|
|
75
75
|
function readEnv() {
|
|
76
|
-
const required = ['SUPABASE_URL', '
|
|
76
|
+
const required = ['SUPABASE_URL', 'SUPABASE_SERVICE_ROLE_KEY', 'OPENAI_API_KEY'];
|
|
77
77
|
const missing = required.filter((k) => !process.env[k]);
|
|
78
78
|
if (missing.length) {
|
|
79
79
|
log(`env-var-missing: ${missing.join(', ')} — set these in ~/.termdeck/secrets.env or your shell to enable Mnestra ingestion. Skipping.`);
|
|
@@ -81,7 +81,7 @@ function readEnv() {
|
|
|
81
81
|
}
|
|
82
82
|
return {
|
|
83
83
|
supabaseUrl: process.env.SUPABASE_URL.replace(/\/$/, ''),
|
|
84
|
-
supabaseKey: process.env.
|
|
84
|
+
supabaseKey: process.env.SUPABASE_SERVICE_ROLE_KEY,
|
|
85
85
|
openaiKey: process.env.OPENAI_API_KEY,
|
|
86
86
|
};
|
|
87
87
|
}
|
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -53,6 +53,34 @@ const HOOK_DEST = path.join(HOOK_DEST_DIR, 'memory-session-end.js');
|
|
|
53
53
|
const HOOK_SOURCE = path.join(__dirname, '..', 'assets', 'hooks', 'memory-session-end.js');
|
|
54
54
|
const HOOK_COMMAND = 'node ~/.claude/hooks/memory-session-end.js';
|
|
55
55
|
const HOOK_TIMEOUT_SECONDS = 30;
|
|
56
|
+
const SECRETS_PATH = path.join(HOME, '.termdeck', 'secrets.env');
|
|
57
|
+
|
|
58
|
+
// Read ~/.termdeck/secrets.env into a plain object. Returns {} if the file
|
|
59
|
+
// is absent or unreadable. Used to populate the mnestra MCP env block with
|
|
60
|
+
// concrete values — Claude Code does NOT shell-expand `${VAR}` references
|
|
61
|
+
// in MCP env, so writing placeholders results in mnestra receiving the
|
|
62
|
+
// literal string `${SUPABASE_URL}` and Supabase rejecting it as an invalid
|
|
63
|
+
// URL. Writing concrete values is the only thing that works.
|
|
64
|
+
function readTermdeckSecrets() {
|
|
65
|
+
try {
|
|
66
|
+
const text = fs.readFileSync(SECRETS_PATH, 'utf8');
|
|
67
|
+
const out = {};
|
|
68
|
+
for (const raw of text.split('\n')) {
|
|
69
|
+
const line = raw.trim();
|
|
70
|
+
if (!line || line.startsWith('#')) continue;
|
|
71
|
+
const m = line.match(/^([A-Z_][A-Z0-9_]*)=(.*)$/);
|
|
72
|
+
if (!m) continue;
|
|
73
|
+
let v = m[2];
|
|
74
|
+
if (v.length >= 2 && (v[0] === '"' || v[0] === "'") && v[v.length - 1] === v[0]) {
|
|
75
|
+
v = v.slice(1, -1);
|
|
76
|
+
}
|
|
77
|
+
out[m[1]] = v;
|
|
78
|
+
}
|
|
79
|
+
return out;
|
|
80
|
+
} catch (_err) {
|
|
81
|
+
return {};
|
|
82
|
+
}
|
|
83
|
+
}
|
|
56
84
|
|
|
57
85
|
const LAYERS = [
|
|
58
86
|
{
|
|
@@ -297,18 +325,60 @@ function wireMcpEntries(plan, opts) {
|
|
|
297
325
|
const keptExisting = [];
|
|
298
326
|
|
|
299
327
|
if (installedTiers.has(2) && !servers.mnestra) {
|
|
328
|
+
// Claude Code does NOT expand `${VAR}` in MCP env — placeholders pass
|
|
329
|
+
// through literally and mnestra rejects them as an invalid SUPABASE_URL.
|
|
330
|
+
// Read concrete values from ~/.termdeck/secrets.env. Missing keys fall
|
|
331
|
+
// back to process.env (the installer was launched from the user's shell,
|
|
332
|
+
// which may export them); if still empty, leave the key out so mnestra's
|
|
333
|
+
// own secrets.env fallback gets a chance to load it.
|
|
334
|
+
const secrets = readTermdeckSecrets();
|
|
335
|
+
const env = {};
|
|
336
|
+
for (const key of ['SUPABASE_URL', 'SUPABASE_SERVICE_ROLE_KEY', 'OPENAI_API_KEY']) {
|
|
337
|
+
const v = secrets[key] || process.env[key] || '';
|
|
338
|
+
if (v) env[key] = v;
|
|
339
|
+
}
|
|
300
340
|
servers.mnestra = {
|
|
301
341
|
type: 'stdio',
|
|
302
342
|
command: 'mnestra',
|
|
303
|
-
env
|
|
304
|
-
SUPABASE_URL: '${SUPABASE_URL}',
|
|
305
|
-
SUPABASE_SERVICE_ROLE_KEY: '${SUPABASE_SERVICE_ROLE_KEY}',
|
|
306
|
-
OPENAI_API_KEY: '${OPENAI_API_KEY}',
|
|
307
|
-
},
|
|
343
|
+
env,
|
|
308
344
|
};
|
|
309
345
|
additions.push('mnestra');
|
|
346
|
+
if (!env.SUPABASE_URL || !env.SUPABASE_SERVICE_ROLE_KEY) {
|
|
347
|
+
process.stdout.write(
|
|
348
|
+
`${ANSI.yellow}!${ANSI.reset} mnestra MCP added with incomplete env — ` +
|
|
349
|
+
`set SUPABASE_URL and SUPABASE_SERVICE_ROLE_KEY in ${SECRETS_PATH} ` +
|
|
350
|
+
`or via \`claude mcp remove mnestra -s user\` followed by ` +
|
|
351
|
+
`\`claude mcp add mnestra -s user -e SUPABASE_URL=... -e SUPABASE_SERVICE_ROLE_KEY=... -e OPENAI_API_KEY=... -- mnestra\`.\n`
|
|
352
|
+
);
|
|
353
|
+
}
|
|
310
354
|
} else if (servers.mnestra) {
|
|
311
|
-
|
|
355
|
+
// Repair pass: existing entry from a buggy installer (≤ 0.4.11) used
|
|
356
|
+
// `${VAR}` placeholders that Claude Code never expands. If we detect
|
|
357
|
+
// those, swap in concrete values from secrets.env / process.env.
|
|
358
|
+
const env = { ...(servers.mnestra.env || {}) };
|
|
359
|
+
let repaired = false;
|
|
360
|
+
const secrets = readTermdeckSecrets();
|
|
361
|
+
for (const key of ['SUPABASE_URL', 'SUPABASE_SERVICE_ROLE_KEY', 'OPENAI_API_KEY']) {
|
|
362
|
+
const cur = env[key];
|
|
363
|
+
const looksLikePlaceholder = typeof cur === 'string'
|
|
364
|
+
&& cur.startsWith('${') && cur.endsWith('}');
|
|
365
|
+
if (looksLikePlaceholder || cur === '') {
|
|
366
|
+
const v = secrets[key] || process.env[key] || '';
|
|
367
|
+
if (v) {
|
|
368
|
+
env[key] = v;
|
|
369
|
+
repaired = true;
|
|
370
|
+
} else if (looksLikePlaceholder) {
|
|
371
|
+
delete env[key];
|
|
372
|
+
repaired = true;
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
if (repaired) {
|
|
377
|
+
servers.mnestra = { ...servers.mnestra, env };
|
|
378
|
+
additions.push('mnestra (env repaired)');
|
|
379
|
+
} else {
|
|
380
|
+
keptExisting.push('mnestra');
|
|
381
|
+
}
|
|
312
382
|
}
|
|
313
383
|
|
|
314
384
|
if (installedTiers.has(4) && !servers.supabase) {
|