@jhizzard/termdeck 0.6.2 → 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 +1 -1
- package/packages/cli/src/init-mnestra.js +141 -52
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jhizzard/termdeck",
|
|
3
|
-
"version": "0.6.
|
|
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
|
-
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
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
|
|
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
|
|
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
|
-
'
|
|
52
|
-
'
|
|
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.
|
|
80
|
-
4. Asking for an
|
|
81
|
-
5.
|
|
82
|
-
|
|
83
|
-
|
|
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
|
-
|
|
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,16 +199,6 @@ async function collectInputs({ yes }) {
|
|
|
129
199
|
}
|
|
130
200
|
}
|
|
131
201
|
|
|
132
|
-
// No confirm here — the user already opted in by typing
|
|
133
|
-
// `termdeck init --mnestra` and supplying every secret. The previous
|
|
134
|
-
// confirm gate was the consistent failure point in Brad's reports
|
|
135
|
-
// (2026-04-25 twice + a third report after v0.6.1) on terminals that
|
|
136
|
-
// emit stray bytes (CRLF, ANSI cursor reports, paste-bracketing) which
|
|
137
|
-
// contaminated readline and made the confirm fast-resolve to "no" or
|
|
138
|
-
// an empty cancel. Migrations are `IF NOT EXISTS` so a re-run is safe;
|
|
139
|
-
// Ctrl-C still aborts cleanly. The `--yes` flag is preserved as a
|
|
140
|
-
// stable CLI surface for callers/scripts and for future use.
|
|
141
|
-
void yes;
|
|
142
202
|
process.stdout.write('\n');
|
|
143
203
|
|
|
144
204
|
return {
|
|
@@ -229,33 +289,39 @@ async function verifyStatus(client) {
|
|
|
229
289
|
}
|
|
230
290
|
}
|
|
231
291
|
|
|
232
|
-
|
|
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) {
|
|
233
301
|
step('Writing ~/.termdeck/secrets.env...');
|
|
234
|
-
if (dryRun) { ok('(dry-run)'); }
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
}
|
|
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
|
+
}
|
|
245
312
|
|
|
313
|
+
function writeYamlConfig(dryRun) {
|
|
246
314
|
step('Updating ~/.termdeck/config.yaml (rag.enabled: true)...');
|
|
247
|
-
if (dryRun) { ok('(dry-run)'); }
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
else ok();
|
|
258
|
-
}
|
|
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();
|
|
259
325
|
}
|
|
260
326
|
|
|
261
327
|
function printNextSteps() {
|
|
@@ -270,6 +336,14 @@ Next steps:
|
|
|
270
336
|
`);
|
|
271
337
|
}
|
|
272
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
|
+
|
|
273
347
|
async function main(argv) {
|
|
274
348
|
const flags = parseFlags(argv || []);
|
|
275
349
|
if (flags.help) {
|
|
@@ -281,18 +355,31 @@ async function main(argv) {
|
|
|
281
355
|
|
|
282
356
|
let inputs;
|
|
283
357
|
try {
|
|
284
|
-
inputs = await collectInputs({ yes: flags.yes });
|
|
358
|
+
inputs = await collectInputs({ yes: flags.yes, reset: flags.reset });
|
|
285
359
|
} catch (err) {
|
|
286
360
|
process.stderr.write(`\n[init --mnestra] ${err.message}\n`);
|
|
287
361
|
return 2;
|
|
288
362
|
}
|
|
289
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.
|
|
290
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
|
+
|
|
291
378
|
step('Connecting to Supabase...');
|
|
292
379
|
if (flags.dryRun) {
|
|
293
380
|
ok('(dry-run, skipped)');
|
|
294
381
|
await applyMigrations(null, true);
|
|
295
|
-
|
|
382
|
+
writeYamlConfig(true);
|
|
296
383
|
process.stdout.write('\nDry run complete. No changes were made.\n');
|
|
297
384
|
return 0;
|
|
298
385
|
}
|
|
@@ -306,13 +393,14 @@ async function main(argv) {
|
|
|
306
393
|
process.stderr.write(
|
|
307
394
|
'\nDouble-check the connection string from Supabase → Project Settings → Database → Connection String.\n'
|
|
308
395
|
);
|
|
396
|
+
printResumeHint();
|
|
309
397
|
return 3;
|
|
310
398
|
}
|
|
311
399
|
|
|
312
400
|
try {
|
|
313
401
|
await checkExistingStore(client);
|
|
314
402
|
await applyMigrations(client, false);
|
|
315
|
-
|
|
403
|
+
writeYamlConfig(false);
|
|
316
404
|
if (!flags.skipVerify) {
|
|
317
405
|
const verified = await verifyStatus(client);
|
|
318
406
|
if (!verified) {
|
|
@@ -326,6 +414,7 @@ async function main(argv) {
|
|
|
326
414
|
}
|
|
327
415
|
} catch (err) {
|
|
328
416
|
process.stderr.write(`\n[init --mnestra] ${err.message}\n`);
|
|
417
|
+
printResumeHint();
|
|
329
418
|
return 5;
|
|
330
419
|
} finally {
|
|
331
420
|
try { await client.end(); } catch (_err) { /* ignore */ }
|