@jhizzard/termdeck 0.6.1 → 0.6.3

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jhizzard/termdeck",
3
- "version": "0.6.1",
3
+ "version": "0.6.3",
4
4
  "description": "Browser-based terminal multiplexer with metadata overlays, panel flashback memory recall, and AI-aware session management",
5
5
  "bin": {
6
6
  "termdeck": "./packages/cli/src/index.js"
@@ -5,15 +5,23 @@
5
5
  //
6
6
  // Steps:
7
7
  // 1. Collect Supabase URL, service_role key, direct DB URL, OpenAI + Anthropic keys
8
- // 2. Connect via `pg` using the direct URL
9
- // 3. Apply the six bundled Mnestra migrations in order
10
- // 4. Write ~/.termdeck/secrets.env (merge-aware, preserves existing values)
8
+ // (or reuse the saved set in ~/.termdeck/secrets.env if present)
9
+ // 2. Persist ~/.termdeck/secrets.env immediately merge-aware, preserves
10
+ // existing values. Done BEFORE any database work so a later pg connect
11
+ // or migration failure doesn't lose the user's typed-in keys.
12
+ // 3. Connect via `pg` using the direct URL
13
+ // 4. Apply the six bundled Mnestra migrations in order
11
14
  // 5. Update ~/.termdeck/config.yaml to enable RAG + point at ${VAR} refs
15
+ // (only after migrations apply cleanly — otherwise the server would
16
+ // try to use an incomplete schema on next startup)
12
17
  // 6. Verify with a memory_status_aggregation() call
13
18
  //
14
19
  // Flags:
15
20
  // --help Print usage and exit
16
- // --yes Accept defaults, skip confirmations (still prompts for secrets)
21
+ // --yes Reuse saved secrets without re-prompting if a complete
22
+ // set is already on disk (otherwise the wizard asks
23
+ // interactively before reusing)
24
+ // --reset Ignore saved secrets and re-prompt for everything
17
25
  // --dry-run Print what the wizard would do; don't touch the DB or filesystem
18
26
  // --skip-verify Skip the final memory_status_aggregation() check
19
27
  //
@@ -41,15 +49,19 @@ const HELP = [
41
49
  '',
42
50
  'Flags:',
43
51
  ' --help Print this message and exit',
44
- ' --yes Assume "yes" on confirmations (still prompts for secret values)',
52
+ ' --yes Reuse saved secrets without prompting (if a complete',
53
+ ' set is already on disk in ~/.termdeck/secrets.env)',
54
+ ' --reset Ignore saved secrets and re-prompt for everything',
45
55
  ' --dry-run Print the plan without touching the database or filesystem',
46
56
  ' --skip-verify Skip the final memory_status_aggregation() sanity call',
47
57
  '',
48
58
  'What this does:',
49
59
  ' 1. Prompts for Supabase URL, service_role key, direct Postgres connection',
50
- ' string, OpenAI API key, and (optional) Anthropic API key.',
51
- ' 2. Applies the six Mnestra schema + RPC migrations via node-postgres.',
52
- ' 3. Writes ~/.termdeck/secrets.env (merge-aware, preserves existing values).',
60
+ ' string, OpenAI API key, and (optional) Anthropic API key — or reuses',
61
+ ' saved values if a complete set already exists in secrets.env.',
62
+ ' 2. Writes ~/.termdeck/secrets.env IMMEDIATELY (merge-aware) so a later',
63
+ ' pg connect or migration failure does not lose what you typed in.',
64
+ ' 3. Connects to Postgres and applies the six Mnestra schema + RPC migrations.',
53
65
  ' 4. Updates ~/.termdeck/config.yaml to enable RAG and reference ${VAR} keys.',
54
66
  ' 5. Verifies the Mnestra store is reachable via memory_status_aggregation().',
55
67
  '',
@@ -58,10 +70,11 @@ const HELP = [
58
70
  ].join('\n');
59
71
 
60
72
  function parseFlags(argv) {
61
- const out = { help: false, yes: false, dryRun: false, skipVerify: false };
73
+ const out = { help: false, yes: false, reset: false, dryRun: false, skipVerify: false };
62
74
  for (const a of argv) {
63
75
  if (a === '--help' || a === '-h') out.help = true;
64
76
  else if (a === '--yes' || a === '-y') out.yes = true;
77
+ else if (a === '--reset') out.reset = true;
65
78
  else if (a === '--dry-run') out.dryRun = true;
66
79
  else if (a === '--skip-verify') out.skipVerify = true;
67
80
  }
@@ -76,13 +89,18 @@ TermDeck Mnestra Setup
76
89
  This wizard configures TermDeck's Tier 2 memory layer (Mnestra) by:
77
90
  1. Asking for your Supabase URL and service_role key
78
91
  2. Asking for a direct Postgres connection string
79
- 3. Applying six SQL migrations to the database
80
- 4. Asking for an OpenAI API key (embeddings)
81
- 5. Asking for an Anthropic API key (optional, summaries)
82
- 6. Writing ~/.termdeck/secrets.env
83
- 7. Updating ~/.termdeck/config.yaml to enable RAG
92
+ 3. Asking for an OpenAI API key (embeddings)
93
+ 4. Asking for an Anthropic API key (optional, summaries)
94
+ 5. Writing ~/.termdeck/secrets.env (before any database work, so a
95
+ pg failure cannot lose what you typed in)
96
+ 6. Connecting to Postgres + applying six SQL migrations
97
+ 7. Updating ~/.termdeck/config.yaml to enable RAG (only after
98
+ migrations apply cleanly)
84
99
  8. Verifying the connection with a memory_status call
85
100
 
101
+ If you already have a complete ~/.termdeck/secrets.env, the wizard will
102
+ offer to reuse it (or pass --yes to skip the prompt and resume directly).
103
+
86
104
  Press Ctrl+C at any time to cancel.
87
105
 
88
106
  `);
@@ -92,7 +110,59 @@ function step(msg) { process.stdout.write(`→ ${msg}`); }
92
110
  function ok(suffix = '') { process.stdout.write(` ✓${suffix ? ' ' + suffix : ''}\n`); }
93
111
  function fail(err) { process.stdout.write(` ✗\n ${err}\n`); }
94
112
 
95
- async function collectInputs({ yes }) {
113
+ // Read whatever secrets are already on disk. Returns hydrated input shape if
114
+ // a complete set is present, or null if the user still needs to be prompted
115
+ // for at least one required value. Anthropic remains optional throughout.
116
+ function loadSavedSecrets() {
117
+ const saved = dotenv.readSecrets();
118
+ const required = ['SUPABASE_URL', 'SUPABASE_SERVICE_ROLE_KEY', 'DATABASE_URL', 'OPENAI_API_KEY'];
119
+ const present = required.filter((k) => saved[k]);
120
+ if (present.length < required.length) {
121
+ return { complete: false, present, saved };
122
+ }
123
+ const projectUrl = urlHelper.parseProjectUrl(saved.SUPABASE_URL);
124
+ if (!projectUrl.ok) return { complete: false, present, saved };
125
+ return {
126
+ complete: true,
127
+ present,
128
+ saved,
129
+ inputs: {
130
+ projectUrl,
131
+ serviceRoleKey: saved.SUPABASE_SERVICE_ROLE_KEY,
132
+ databaseUrl: saved.DATABASE_URL,
133
+ openaiKey: saved.OPENAI_API_KEY,
134
+ anthropicKey: saved.ANTHROPIC_API_KEY || null
135
+ }
136
+ };
137
+ }
138
+
139
+ async function collectInputs({ yes, reset }) {
140
+ // Resume path — Brad's case at 2026-04-25 17:50 ET ("If it got that far did
141
+ // it write the correct secret.env? If so, can I manually do the next steps?").
142
+ // If a complete set of secrets is already on disk, offer to reuse them so a
143
+ // re-run after a pg connect failure does not require retyping everything.
144
+ if (!reset) {
145
+ const found = loadSavedSecrets();
146
+ if (found.complete) {
147
+ const ref = found.inputs.projectUrl.projectRef;
148
+ const masked = urlHelper.maskSecret(found.inputs.databaseUrl);
149
+ process.stdout.write(
150
+ `Found saved secrets in ~/.termdeck/secrets.env (project ${ref}, db ${masked}).\n`
151
+ );
152
+ const reuse = yes ? true : await prompts.confirm(' Reuse saved secrets?', { defaultYes: true });
153
+ if (reuse) {
154
+ process.stdout.write(' Reusing saved secrets. Skipping prompts.\n\n');
155
+ return found.inputs;
156
+ }
157
+ process.stdout.write(' Re-prompting.\n\n');
158
+ } else if (found.present.length > 0) {
159
+ process.stdout.write(
160
+ `Note: ~/.termdeck/secrets.env has ${found.present.length}/4 required keys ` +
161
+ `(${found.present.join(', ')}). Re-prompting for the rest.\n\n`
162
+ );
163
+ }
164
+ }
165
+
96
166
  const projectUrlStr = await prompts.askRequired(
97
167
  '? Supabase Project URL (e.g. https://xyz.supabase.co)',
98
168
  {
@@ -129,14 +199,7 @@ async function collectInputs({ yes }) {
129
199
  }
130
200
  }
131
201
 
132
- if (!yes) {
133
- process.stdout.write('\n');
134
- const go = await prompts.confirm(`Proceed with setup for project ${projectUrl.projectRef}?`);
135
- if (!go) {
136
- process.stdout.write('Cancelled.\n');
137
- process.exit(0);
138
- }
139
- }
202
+ process.stdout.write('\n');
140
203
 
141
204
  return {
142
205
  projectUrl,
@@ -226,33 +289,39 @@ async function verifyStatus(client) {
226
289
  }
227
290
  }
228
291
 
229
- function writeLocalConfig(inputs, dryRun) {
292
+ // Persist secrets BEFORE any pg work so a connect/migration failure can't
293
+ // throw away what the user typed in. Brad's 2026-04-25 18:30 ET report
294
+ // ("It's killing before writing the file. Postgrep line not added to my
295
+ // existing file, so it wasn't changed") was caused by writeLocalConfig
296
+ // running AFTER applyMigrations — when migrations or pg connect failed,
297
+ // secrets.env was never updated. Splitting the writes lets secrets land
298
+ // on disk first; config.yaml only flips to rag.enabled=true once the
299
+ // schema is actually in place.
300
+ function writeSecretsFile(inputs, dryRun) {
230
301
  step('Writing ~/.termdeck/secrets.env...');
231
- if (dryRun) { ok('(dry-run)'); }
232
- else {
233
- dotenv.writeSecrets({
234
- SUPABASE_URL: inputs.projectUrl.url,
235
- SUPABASE_SERVICE_ROLE_KEY: inputs.serviceRoleKey,
236
- DATABASE_URL: inputs.databaseUrl,
237
- OPENAI_API_KEY: inputs.openaiKey,
238
- ...(inputs.anthropicKey ? { ANTHROPIC_API_KEY: inputs.anthropicKey } : {})
239
- });
240
- ok();
241
- }
302
+ if (dryRun) { ok('(dry-run)'); return; }
303
+ dotenv.writeSecrets({
304
+ SUPABASE_URL: inputs.projectUrl.url,
305
+ SUPABASE_SERVICE_ROLE_KEY: inputs.serviceRoleKey,
306
+ DATABASE_URL: inputs.databaseUrl,
307
+ OPENAI_API_KEY: inputs.openaiKey,
308
+ ...(inputs.anthropicKey ? { ANTHROPIC_API_KEY: inputs.anthropicKey } : {})
309
+ });
310
+ ok();
311
+ }
242
312
 
313
+ function writeYamlConfig(dryRun) {
243
314
  step('Updating ~/.termdeck/config.yaml (rag.enabled: true)...');
244
- if (dryRun) { ok('(dry-run)'); }
245
- else {
246
- const r = yaml.updateRagConfig({
247
- enabled: true,
248
- supabaseUrl: '${SUPABASE_URL}',
249
- supabaseKey: '${SUPABASE_SERVICE_ROLE_KEY}',
250
- openaiApiKey: '${OPENAI_API_KEY}',
251
- anthropicApiKey: '${ANTHROPIC_API_KEY}'
252
- });
253
- if (r.backup) ok(`(backup: ${path.basename(r.backup)})`);
254
- else ok();
255
- }
315
+ if (dryRun) { ok('(dry-run)'); return; }
316
+ const r = yaml.updateRagConfig({
317
+ enabled: true,
318
+ supabaseUrl: '${SUPABASE_URL}',
319
+ supabaseKey: '${SUPABASE_SERVICE_ROLE_KEY}',
320
+ openaiApiKey: '${OPENAI_API_KEY}',
321
+ anthropicApiKey: '${ANTHROPIC_API_KEY}'
322
+ });
323
+ if (r.backup) ok(`(backup: ${path.basename(r.backup)})`);
324
+ else ok();
256
325
  }
257
326
 
258
327
  function printNextSteps() {
@@ -267,6 +336,14 @@ Next steps:
267
336
  `);
268
337
  }
269
338
 
339
+ function printResumeHint() {
340
+ process.stderr.write(
341
+ '\nYour secrets are saved at ~/.termdeck/secrets.env.\n' +
342
+ 'To retry just the database step (no need to re-enter keys):\n' +
343
+ ' termdeck init --mnestra --yes\n'
344
+ );
345
+ }
346
+
270
347
  async function main(argv) {
271
348
  const flags = parseFlags(argv || []);
272
349
  if (flags.help) {
@@ -278,18 +355,31 @@ async function main(argv) {
278
355
 
279
356
  let inputs;
280
357
  try {
281
- inputs = await collectInputs({ yes: flags.yes });
358
+ inputs = await collectInputs({ yes: flags.yes, reset: flags.reset });
282
359
  } catch (err) {
283
360
  process.stderr.write(`\n[init --mnestra] ${err.message}\n`);
284
361
  return 2;
285
362
  }
286
363
 
364
+ // Persist secrets BEFORE pg work. If the wizard dies past this point
365
+ // (connect timeout, migration error, Ctrl-C), the saved file lets the
366
+ // user re-run with --yes and skip straight to the database step.
287
367
  process.stdout.write('\n');
368
+ try {
369
+ writeSecretsFile(inputs, flags.dryRun);
370
+ } catch (err) {
371
+ fail(err.message);
372
+ process.stderr.write(
373
+ '\nFailed to write ~/.termdeck/secrets.env. Check the directory is writable.\n'
374
+ );
375
+ return 6;
376
+ }
377
+
288
378
  step('Connecting to Supabase...');
289
379
  if (flags.dryRun) {
290
380
  ok('(dry-run, skipped)');
291
381
  await applyMigrations(null, true);
292
- writeLocalConfig(inputs, true);
382
+ writeYamlConfig(true);
293
383
  process.stdout.write('\nDry run complete. No changes were made.\n');
294
384
  return 0;
295
385
  }
@@ -303,13 +393,14 @@ async function main(argv) {
303
393
  process.stderr.write(
304
394
  '\nDouble-check the connection string from Supabase → Project Settings → Database → Connection String.\n'
305
395
  );
396
+ printResumeHint();
306
397
  return 3;
307
398
  }
308
399
 
309
400
  try {
310
401
  await checkExistingStore(client);
311
402
  await applyMigrations(client, false);
312
- writeLocalConfig(inputs, false);
403
+ writeYamlConfig(false);
313
404
  if (!flags.skipVerify) {
314
405
  const verified = await verifyStatus(client);
315
406
  if (!verified) {
@@ -323,6 +414,7 @@ async function main(argv) {
323
414
  }
324
415
  } catch (err) {
325
416
  process.stderr.write(`\n[init --mnestra] ${err.message}\n`);
417
+ printResumeHint();
326
418
  return 5;
327
419
  } finally {
328
420
  try { await client.end(); } catch (_err) { /* ignore */ }