@jhizzard/termdeck-stack 0.4.11 → 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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/index.js +76 -6
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jhizzard/termdeck-stack",
3
- "version": "0.4.11",
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) {