@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.
@@ -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`, `SUPABASE_SERVICE_KEY`,
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
- | `SUPABASE_SERVICE_KEY` | Service-role key with INSERT on `memory_items`. **Not the anon key.** | `~/.termdeck/secrets.env` |
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
- * - SUPABASE_SERVICE_KEY service-role key (NOT the anon key — needs INSERT on memory_items)
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', 'SUPABASE_SERVICE_KEY', 'OPENAI_API_KEY'];
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.SUPABASE_SERVICE_KEY,
84
+ supabaseKey: process.env.SUPABASE_SERVICE_ROLE_KEY,
85
85
  openaiKey: process.env.OPENAI_API_KEY,
86
86
  };
87
87
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jhizzard/termdeck-stack",
3
- "version": "0.4.10",
3
+ "version": "0.4.12",
4
4
  "description": "One-command installer for the TermDeck developer memory stack: TermDeck + Mnestra + Rumen + Supabase MCP",
5
5
  "bin": {
6
6
  "termdeck-stack": "./src/index.js"
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
- keptExisting.push('mnestra');
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) {