@jhizzard/termdeck 0.6.3 → 0.6.4
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/README.md +1 -1
- package/package.json +1 -1
- package/packages/cli/src/init-mnestra.js +107 -6
- package/packages/cli/src/init-rumen.js +33 -0
package/README.md
CHANGED
|
@@ -171,7 +171,7 @@ Honest limits, stated upfront so the skeptic has nothing to chase:
|
|
|
171
171
|
- **Not a replacement for reading docs.** It's the shortest path to a memory you already wrote. If the memory isn't there, the feature does nothing.
|
|
172
172
|
- **Not fully local by default.** Tier 2+ reaches out to Supabase for storage and OpenAI for embeddings. Tier 1 is fully local. A fully-local Tier 2 (local Postgres + local embeddings) is on the roadmap.
|
|
173
173
|
- **Not free forever.** Tier 2+ pays OpenAI fractions of a cent per memory for embeddings. Self-hosted embeddings via Ollama are on the roadmap.
|
|
174
|
-
- **Not proven at scale.** v0.6.
|
|
174
|
+
- **Not proven at scale.** v0.6.4, validated against 4,669 memories in one developer's production store. The Rumen 2026-04-19 re-kickstart processed 166 sessions into 166 insights in ~5.5 minutes. No multi-user data yet. Bug reports and issues welcome.
|
|
175
175
|
|
|
176
176
|
---
|
|
177
177
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jhizzard/termdeck",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.4",
|
|
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"
|
|
@@ -22,6 +22,11 @@
|
|
|
22
22
|
// set is already on disk (otherwise the wizard asks
|
|
23
23
|
// interactively before reusing)
|
|
24
24
|
// --reset Ignore saved secrets and re-prompt for everything
|
|
25
|
+
// --from-env Skip every prompt; read all five secrets from the
|
|
26
|
+
// process environment (SUPABASE_URL, SUPABASE_SERVICE_ROLE_KEY,
|
|
27
|
+
// DATABASE_URL, OPENAI_API_KEY, ANTHROPIC_API_KEY).
|
|
28
|
+
// Required for terminals that fight with our raw-mode
|
|
29
|
+
// secret prompt and for CI / scripted installs.
|
|
25
30
|
// --dry-run Print what the wizard would do; don't touch the DB or filesystem
|
|
26
31
|
// --skip-verify Skip the final memory_status_aggregation() check
|
|
27
32
|
//
|
|
@@ -52,6 +57,12 @@ const HELP = [
|
|
|
52
57
|
' --yes Reuse saved secrets without prompting (if a complete',
|
|
53
58
|
' set is already on disk in ~/.termdeck/secrets.env)',
|
|
54
59
|
' --reset Ignore saved secrets and re-prompt for everything',
|
|
60
|
+
' --from-env Skip every prompt; read SUPABASE_URL,',
|
|
61
|
+
' SUPABASE_SERVICE_ROLE_KEY, DATABASE_URL, OPENAI_API_KEY,',
|
|
62
|
+
' ANTHROPIC_API_KEY from environment variables instead.',
|
|
63
|
+
' Useful for terminals that fight with raw-mode secret',
|
|
64
|
+
' prompts (MobaXterm SSH, some Windows shells) and for',
|
|
65
|
+
' CI / scripted installs.',
|
|
55
66
|
' --dry-run Print the plan without touching the database or filesystem',
|
|
56
67
|
' --skip-verify Skip the final memory_status_aggregation() sanity call',
|
|
57
68
|
'',
|
|
@@ -70,17 +81,91 @@ const HELP = [
|
|
|
70
81
|
].join('\n');
|
|
71
82
|
|
|
72
83
|
function parseFlags(argv) {
|
|
73
|
-
const out = {
|
|
84
|
+
const out = {
|
|
85
|
+
help: false,
|
|
86
|
+
yes: false,
|
|
87
|
+
reset: false,
|
|
88
|
+
fromEnv: false,
|
|
89
|
+
dryRun: false,
|
|
90
|
+
skipVerify: false
|
|
91
|
+
};
|
|
74
92
|
for (const a of argv) {
|
|
75
93
|
if (a === '--help' || a === '-h') out.help = true;
|
|
76
94
|
else if (a === '--yes' || a === '-y') out.yes = true;
|
|
77
95
|
else if (a === '--reset') out.reset = true;
|
|
96
|
+
else if (a === '--from-env') out.fromEnv = true;
|
|
78
97
|
else if (a === '--dry-run') out.dryRun = true;
|
|
79
98
|
else if (a === '--skip-verify') out.skipVerify = true;
|
|
80
99
|
}
|
|
81
100
|
return out;
|
|
82
101
|
}
|
|
83
102
|
|
|
103
|
+
// Build inputs from process.env directly, skipping every askSecret prompt.
|
|
104
|
+
// Used by --from-env so callers on terminals that fight with our raw-mode
|
|
105
|
+
// secret prompt (Brad's MobaXterm SSH report 2026-04-25, fourth occurrence)
|
|
106
|
+
// can hand the wizard their secrets via env vars instead. Also makes the
|
|
107
|
+
// wizard scriptable for CI / one-shot installers. Returns the same shape
|
|
108
|
+
// as collectInputs() so the rest of main() doesn't care which path filled
|
|
109
|
+
// it. Throws with an actionable message when a required env var is missing
|
|
110
|
+
// or fails its shape check — `--from-env` is strict by design (no fallback
|
|
111
|
+
// to prompts), since callers using it have explicitly opted into the
|
|
112
|
+
// non-interactive path.
|
|
113
|
+
function inputsFromEnv() {
|
|
114
|
+
const env = process.env;
|
|
115
|
+
const missing = [];
|
|
116
|
+
const required = {
|
|
117
|
+
SUPABASE_URL: env.SUPABASE_URL,
|
|
118
|
+
SUPABASE_SERVICE_ROLE_KEY: env.SUPABASE_SERVICE_ROLE_KEY,
|
|
119
|
+
DATABASE_URL: env.DATABASE_URL,
|
|
120
|
+
OPENAI_API_KEY: env.OPENAI_API_KEY
|
|
121
|
+
};
|
|
122
|
+
for (const [k, v] of Object.entries(required)) {
|
|
123
|
+
if (!v || !v.trim()) missing.push(k);
|
|
124
|
+
}
|
|
125
|
+
if (missing.length > 0) {
|
|
126
|
+
throw new Error(
|
|
127
|
+
`--from-env is missing required environment variable(s): ${missing.join(', ')}.\n` +
|
|
128
|
+
'Set every required key in your shell or pass them on the command line, e.g.:\n' +
|
|
129
|
+
' SUPABASE_URL=https://xyz.supabase.co \\\n' +
|
|
130
|
+
' SUPABASE_SERVICE_ROLE_KEY=sb_secret_... \\\n' +
|
|
131
|
+
' DATABASE_URL=postgres://postgres.<ref>:<pw>@<pooler-host>:6543/postgres \\\n' +
|
|
132
|
+
' OPENAI_API_KEY=sk-proj-... \\\n' +
|
|
133
|
+
' ANTHROPIC_API_KEY=sk-ant-... \\\n' +
|
|
134
|
+
' termdeck init --mnestra --from-env'
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const projectUrl = urlHelper.parseProjectUrl(required.SUPABASE_URL);
|
|
139
|
+
if (!projectUrl.ok) {
|
|
140
|
+
throw new Error(`SUPABASE_URL is malformed: ${projectUrl.error}`);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const dbErr = urlHelper.looksLikePostgresUrl(required.DATABASE_URL);
|
|
144
|
+
if (dbErr) throw new Error(`DATABASE_URL: ${dbErr}`);
|
|
145
|
+
|
|
146
|
+
const srErr = urlHelper.looksLikeServiceRole(required.SUPABASE_SERVICE_ROLE_KEY);
|
|
147
|
+
if (srErr) throw new Error(`SUPABASE_SERVICE_ROLE_KEY: ${srErr}`);
|
|
148
|
+
|
|
149
|
+
const oaErr = urlHelper.looksLikeOpenAiKey(required.OPENAI_API_KEY);
|
|
150
|
+
if (oaErr) throw new Error(`OPENAI_API_KEY: ${oaErr}`);
|
|
151
|
+
|
|
152
|
+
const anthropicKey = (env.ANTHROPIC_API_KEY || '').trim() || null;
|
|
153
|
+
if (anthropicKey) {
|
|
154
|
+
const aErr = urlHelper.looksLikeAnthropicKey(anthropicKey);
|
|
155
|
+
if (aErr) {
|
|
156
|
+
process.stdout.write(` (warning: ANTHROPIC_API_KEY ${aErr} — storing anyway)\n`);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return {
|
|
161
|
+
projectUrl,
|
|
162
|
+
serviceRoleKey: required.SUPABASE_SERVICE_ROLE_KEY,
|
|
163
|
+
databaseUrl: required.DATABASE_URL,
|
|
164
|
+
openaiKey: required.OPENAI_API_KEY,
|
|
165
|
+
anthropicKey
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
|
|
84
169
|
function printBanner() {
|
|
85
170
|
process.stdout.write(`
|
|
86
171
|
TermDeck Mnestra Setup
|
|
@@ -100,6 +185,8 @@ This wizard configures TermDeck's Tier 2 memory layer (Mnestra) by:
|
|
|
100
185
|
|
|
101
186
|
If you already have a complete ~/.termdeck/secrets.env, the wizard will
|
|
102
187
|
offer to reuse it (or pass --yes to skip the prompt and resume directly).
|
|
188
|
+
If your terminal fights with the secret prompt, set the five env vars and
|
|
189
|
+
pass --from-env to skip every prompt entirely.
|
|
103
190
|
|
|
104
191
|
Press Ctrl+C at any time to cancel.
|
|
105
192
|
|
|
@@ -354,11 +441,25 @@ async function main(argv) {
|
|
|
354
441
|
printBanner();
|
|
355
442
|
|
|
356
443
|
let inputs;
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
444
|
+
if (flags.fromEnv) {
|
|
445
|
+
// Skip every interactive prompt — secrets come from process.env. Used
|
|
446
|
+
// when the user's terminal fights with the raw-mode secret prompt
|
|
447
|
+
// (Brad/MobaXterm SSH, 2026-04-25 fourth report) or when the wizard
|
|
448
|
+
// is being driven from a CI/install script.
|
|
449
|
+
process.stdout.write('Reading secrets from environment variables (--from-env).\n\n');
|
|
450
|
+
try {
|
|
451
|
+
inputs = inputsFromEnv();
|
|
452
|
+
} catch (err) {
|
|
453
|
+
process.stderr.write(`\n[init --mnestra] ${err.message}\n`);
|
|
454
|
+
return 2;
|
|
455
|
+
}
|
|
456
|
+
} else {
|
|
457
|
+
try {
|
|
458
|
+
inputs = await collectInputs({ yes: flags.yes, reset: flags.reset });
|
|
459
|
+
} catch (err) {
|
|
460
|
+
process.stderr.write(`\n[init --mnestra] ${err.message}\n`);
|
|
461
|
+
return 2;
|
|
462
|
+
}
|
|
362
463
|
}
|
|
363
464
|
|
|
364
465
|
// Persist secrets BEFORE pg work. If the wizard dies past this point
|
|
@@ -210,6 +210,35 @@ function runShellCaptured(command, args, opts = {}) {
|
|
|
210
210
|
return { ok: r.status === 0, code: r.status, stdout: r.stdout || '', stderr: r.stderr || '' };
|
|
211
211
|
}
|
|
212
212
|
|
|
213
|
+
// Detect the "no access token" signature in `supabase link` stderr so the
|
|
214
|
+
// wizard can surface a path-aware hint instead of dumping raw CLI output at
|
|
215
|
+
// the user. Brad hit this on 2026-04-26 00:25 UTC on MobaXterm SSH after
|
|
216
|
+
// v0.6.3 unblocked init --mnestra: he had no SUPABASE_ACCESS_TOKEN env var
|
|
217
|
+
// and `supabase login` requires a browser, which his SSH session doesn't
|
|
218
|
+
// have. The actionable path on a non-desktop install is always a PAT from
|
|
219
|
+
// the dashboard — link() now points users straight at it.
|
|
220
|
+
function looksLikeMissingAccessToken(stderr) {
|
|
221
|
+
if (!stderr) return false;
|
|
222
|
+
return /Access token not provided/i.test(stderr) ||
|
|
223
|
+
/SUPABASE_ACCESS_TOKEN environment variable/i.test(stderr);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function printAccessTokenHint() {
|
|
227
|
+
process.stderr.write(
|
|
228
|
+
'\nThe Supabase CLI needs a Personal Access Token to link your project.\n' +
|
|
229
|
+
'On a desktop install you can run `supabase login`, but that opens a\n' +
|
|
230
|
+
'browser, so SSH/headless users should use the env-var path instead:\n' +
|
|
231
|
+
'\n' +
|
|
232
|
+
' 1. Generate a token: https://supabase.com/dashboard/account/tokens\n' +
|
|
233
|
+
' 2. Export it in your shell:\n' +
|
|
234
|
+
' export SUPABASE_ACCESS_TOKEN=sbp_...\n' +
|
|
235
|
+
' 3. Re-run: termdeck init --rumen\n' +
|
|
236
|
+
'\n' +
|
|
237
|
+
'TermDeck does not store this token — it only lives in your shell\n' +
|
|
238
|
+
'environment for the duration of the install.\n'
|
|
239
|
+
);
|
|
240
|
+
}
|
|
241
|
+
|
|
213
242
|
async function link(projectRef, dryRun) {
|
|
214
243
|
step(`Running: supabase link --project-ref ${projectRef}...`);
|
|
215
244
|
if (dryRun) { ok('(dry-run)'); return true; }
|
|
@@ -217,6 +246,7 @@ async function link(projectRef, dryRun) {
|
|
|
217
246
|
if (!r.ok) {
|
|
218
247
|
fail(`supabase link failed (exit ${r.code})`);
|
|
219
248
|
if (r.stderr) process.stderr.write(r.stderr + '\n');
|
|
249
|
+
if (looksLikeMissingAccessToken(r.stderr)) printAccessTokenHint();
|
|
220
250
|
return false;
|
|
221
251
|
}
|
|
222
252
|
ok();
|
|
@@ -512,3 +542,6 @@ if (require.main === module) {
|
|
|
512
542
|
}
|
|
513
543
|
|
|
514
544
|
module.exports = main;
|
|
545
|
+
// Test surface — kept on the same export object so the regression suite can
|
|
546
|
+
// pin the access-token detection without spawning a real `supabase` binary.
|
|
547
|
+
module.exports._looksLikeMissingAccessToken = looksLikeMissingAccessToken;
|