@jhizzard/termdeck 0.2.4 → 0.3.0

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 CHANGED
@@ -127,11 +127,19 @@ Restart Claude Code. Six MCP tools appear: `memory_remember`, `memory_recall`, `
127
127
 
128
128
  ### Tier 3 — Add Rumen for async learning
129
129
 
130
- Rumen is a separate npm package — `@jhizzard/rumen@0.2.0` — that ships as a Supabase Edge Function designed to run on a 15-minute `pg_cron` schedule. It's the async reflection layer over Mnestra: it reads recent session memories, cross-references them with your entire historical corpus via hybrid search, synthesizes insights via Claude Haiku, and writes the results back into `rumen_insights` (a new table alongside Mnestra's `memory_items`). TermDeck's Flashback and Claude Code's `memory_recall` both automatically benefit because insights flow back into the same database.
130
+ Rumen is a separate npm package — `@jhizzard/rumen@0.3.6` — that ships as a Supabase Edge Function designed to run on a 15-minute `pg_cron` schedule. It's the async reflection layer over Mnestra: it reads recent session memories, cross-references them with your entire historical corpus via hybrid search, synthesizes insights via Claude Haiku, and writes the results back into `rumen_insights` (a new table alongside Mnestra's `memory_items`). TermDeck's Flashback and Claude Code's `memory_recall` both automatically benefit because insights flow back into the same database.
131
131
 
132
- Rumen v0.2 is the current shipped version. Setup is still manual Sprint 3 (next) ships a `termdeck init --rumen` one-liner that automates the deploy. For now, follow [github.com/jhizzard/rumen#readme](https://github.com/jhizzard/rumen) to deploy via the Supabase CLI.
132
+ **Rumen is live.** First full-kickstart run against a production Mnestra store on 2026-04-15 19:47 UTC: **111 sessions processed, 111 insights generated** in one pass. Insights surfaced patterns like "the error detection regex in Flashback misses `No such file or directory` same class of blind spot as X" and "Practice sessions exist as a separate model but frontend components were built and never wired into the schedule view." The cognitive loop is closed.
133
133
 
134
- **Why you'd want Rumen:** without it, Flashback only surfaces memories that structurally match the current context (same project, similar error). With Rumen, Flashback can surface **cross-project patterns** that Haiku synthesized while you were away — "the CORS fix you applied in Project A probably solves this error in Project B." That's the moat.
134
+ Deploy with **one command**:
135
+
136
+ ```bash
137
+ termdeck init --rumen
138
+ ```
139
+
140
+ The wizard auto-resolves the latest published `@jhizzard/rumen` version from npm at deploy time, applies the self-healing SQL migration, stages the Edge Function with the correct version pin, deploys via the Supabase CLI, sets function secrets, applies the `pg_cron` schedule, and POSTs a manual test invocation. Prerequisites: Supabase CLI, Deno, and a `DATABASE_URL` pointing at a Mnestra-compatible Postgres. Full walkthrough including the five setup gotchas (the hidden IPv4 toggle in the Supabase Connect modal, the literal-string password bug, `DATABASE_URL` only / no `DIRECT_URL`, the macOS-13 + Homebrew + Deno incompatibility, and schema drift handling) is at **[rumen/install.md](https://github.com/jhizzard/rumen/blob/main/install.md)**.
141
+
142
+ **Why you'd want Rumen:** without it, Flashback only surfaces memories that structurally match the current context (same project, similar error). With Rumen, Flashback surfaces **cross-project patterns** that Haiku synthesized while you were away — "the CORS fix you applied in Project A probably solves this error in Project B." That's the moat.
135
143
 
136
144
  ---
137
145
 
@@ -143,7 +151,7 @@ Honest limits, stated upfront so the skeptic has nothing to chase:
143
151
  - **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.
144
152
  - **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 Sprint 3 roadmap.
145
153
  - **Not free forever.** Tier 2+ pays OpenAI fractions of a cent per memory for embeddings. Self-hosted embeddings via Ollama are on the roadmap.
146
- - **Not proven at scale.** v0.2, validated against 3,400+ memories in one developer's production store. No multi-user data yet. Bug reports and issues welcome.
154
+ - **Not proven at scale.** v0.2.5, validated against 3,527 memories in one developer's production store. First full Rumen kickstart on 2026-04-15 processed 111 sessions into 111 insights in one pass. No multi-user data yet. Bug reports and issues welcome.
147
155
 
148
156
  ---
149
157
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jhizzard/termdeck",
3
- "version": "0.2.4",
3
+ "version": "0.3.0",
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"
@@ -24,6 +24,7 @@
24
24
  "dev": "node packages/server/src/index.js",
25
25
  "server": "node packages/server/src/index.js",
26
26
  "start": "NODE_ENV=production node packages/cli/src/index.js",
27
+ "test": "node --test packages/server/tests/**/*.test.js",
27
28
  "install:app": "bash install.sh"
28
29
  },
29
30
  "dependencies": {
@@ -9,6 +9,9 @@
9
9
  // - `deno` on PATH
10
10
  // - `~/.termdeck/secrets.env` with SUPABASE_URL + SUPABASE_SERVICE_ROLE_KEY +
11
11
  // DATABASE_URL + ANTHROPIC_API_KEY populated (run `termdeck init --mnestra` first)
12
+ // - OPENAI_API_KEY in secrets.env is OPTIONAL: when present, Rumen's Relate
13
+ // phase uses semantic+keyword hybrid search via OpenAI text-embedding-3-large.
14
+ // When absent, Rumen falls back to keyword-only matching.
12
15
  //
13
16
  // Steps:
14
17
  // 1. Preflight: which supabase, which deno, read secrets.env
@@ -16,7 +19,7 @@
16
19
  // 3. supabase link --project-ref <ref>
17
20
  // 4. Apply rumen migration 001 via pg
18
21
  // 5. supabase functions deploy rumen-tick --no-verify-jwt
19
- // 6. supabase secrets set DATABASE_URL=... ANTHROPIC_API_KEY=...
22
+ // 6. supabase secrets set DATABASE_URL=... ANTHROPIC_API_KEY=... [OPENAI_API_KEY=...]
20
23
  // 7. Test the function with a manual POST (fetch)
21
24
  // 8. Apply pg_cron schedule migration (002) with project ref substituted
22
25
 
@@ -34,6 +37,44 @@ const {
34
37
  pgRunner
35
38
  } = require(SETUP_DIR);
36
39
 
40
+ // Pinned fallback used only when the npm registry is unreachable. Bump this
41
+ // when you republish @jhizzard/rumen and can't (or won't) rely on `npm view`
42
+ // at deploy time. The wizard prefers the live registry answer — this value
43
+ // exists so a fully offline machine can still ship a working Edge Function.
44
+ const FALLBACK_RUMEN_VERSION = '0.3.4';
45
+
46
+ // Resolve the current published version of @jhizzard/rumen. Source of truth
47
+ // is the npm registry (because the Edge Function runtime pulls from there,
48
+ // not from local node_modules). On network failure we fall back to
49
+ // FALLBACK_RUMEN_VERSION and tell the user what happened — we don't silently
50
+ // deploy a bogus version.
51
+ function resolveRumenVersion() {
52
+ const r = spawnSync('npm', ['view', '@jhizzard/rumen', 'version'], {
53
+ encoding: 'utf-8',
54
+ stdio: ['ignore', 'pipe', 'pipe'],
55
+ timeout: 15000
56
+ });
57
+ if (r.status === 0) {
58
+ const version = (r.stdout || '').trim();
59
+ if (/^\d+\.\d+\.\d+/.test(version)) {
60
+ return { version, source: 'npm registry' };
61
+ }
62
+ return {
63
+ version: FALLBACK_RUMEN_VERSION,
64
+ source: 'pinned fallback',
65
+ warning: `npm view returned unexpected output: ${JSON.stringify(version)}`
66
+ };
67
+ }
68
+ const stderr = (r.stderr || '').trim();
69
+ return {
70
+ version: FALLBACK_RUMEN_VERSION,
71
+ source: 'pinned fallback',
72
+ warning: stderr
73
+ ? `npm view failed: ${stderr.split('\n').pop()}`
74
+ : `npm view failed (exit ${r.status === null ? 'timeout' : r.status}) — offline?`
75
+ };
76
+ }
77
+
37
78
  const HELP = [
38
79
  '',
39
80
  'TermDeck Rumen Setup',
@@ -125,6 +166,18 @@ function preflight() {
125
166
  }
126
167
  ok();
127
168
 
169
+ // OPENAI_API_KEY is optional: when present, Rumen's Relate phase generates
170
+ // real embeddings for semantic+keyword hybrid search. When absent, Rumen
171
+ // falls back to keyword-only matching (still works, but loses cross-project
172
+ // conceptual retrieval).
173
+ if (!secrets.OPENAI_API_KEY) {
174
+ process.stderr.write(
175
+ '\n⚠ OPENAI_API_KEY is not set in ~/.termdeck/secrets.env.\n' +
176
+ ' Rumen will run in keyword-only mode — for full cross-project conceptual\n' +
177
+ ' retrieval, add OPENAI_API_KEY to secrets.env and re-run `termdeck init --rumen`.\n\n'
178
+ );
179
+ }
180
+
128
181
  return { supabaseBin: sb, denoBin: deno, secrets };
129
182
  }
130
183
 
@@ -196,7 +249,7 @@ async function applyRumenTables(secrets, dryRun) {
196
249
  }
197
250
  }
198
251
 
199
- function deployFunction(dryRun) {
252
+ function deployFunction(rumenVersion, dryRun) {
200
253
  step('Running: supabase functions deploy rumen-tick --no-verify-jwt...');
201
254
  if (dryRun) { ok('(dry-run)'); return true; }
202
255
 
@@ -204,7 +257,7 @@ function deployFunction(dryRun) {
204
257
  // `supabase/functions/rumen-tick/`. The TermDeck install does NOT include
205
258
  // a `supabase/` directory at the project root, so we stage a tiny working
206
259
  // directory under `os.tmpdir()` that mirrors what the CLI expects.
207
- const stage = stageRumenFunction();
260
+ const stage = stageRumenFunction(rumenVersion);
208
261
  if (!stage) {
209
262
  fail('could not stage rumen-tick function source');
210
263
  return false;
@@ -222,7 +275,10 @@ function deployFunction(dryRun) {
222
275
  // <stage>/supabase/functions/rumen-tick/{index.ts, tsconfig.json}
223
276
  // Also write a minimal `supabase/config.toml` so `supabase functions deploy`
224
277
  // doesn't complain about a missing project root.
225
- function stageRumenFunction() {
278
+ function stageRumenFunction(rumenVersion) {
279
+ if (!rumenVersion || !/^\d+\.\d+\.\d+/.test(rumenVersion)) {
280
+ throw new Error(`stageRumenFunction: invalid rumenVersion ${JSON.stringify(rumenVersion)}`);
281
+ }
226
282
  const stage = fs.mkdtempSync(path.join(os.tmpdir(), 'termdeck-rumen-stage-'));
227
283
  const functionSrc = migrations.rumenFunctionDir();
228
284
  if (!fs.existsSync(functionSrc)) return null;
@@ -230,7 +286,22 @@ function stageRumenFunction() {
230
286
  const dest = path.join(stage, 'supabase', 'functions', 'rumen-tick');
231
287
  fs.mkdirSync(dest, { recursive: true });
232
288
  for (const f of fs.readdirSync(functionSrc)) {
233
- fs.copyFileSync(path.join(functionSrc, f), path.join(dest, f));
289
+ const srcPath = path.join(functionSrc, f);
290
+ const destPath = path.join(dest, f);
291
+ // Substitute the version placeholder in the Deno entry point. Other files
292
+ // in the directory (tsconfig.json, etc.) are copied verbatim.
293
+ if (f === 'index.ts') {
294
+ const raw = fs.readFileSync(srcPath, 'utf-8');
295
+ if (!raw.includes('__RUMEN_VERSION__')) {
296
+ throw new Error(
297
+ `rumen-tick/index.ts is missing the __RUMEN_VERSION__ placeholder — ` +
298
+ `has someone reintroduced a hardcoded version?`
299
+ );
300
+ }
301
+ fs.writeFileSync(destPath, raw.replace(/__RUMEN_VERSION__/g, rumenVersion));
302
+ } else {
303
+ fs.copyFileSync(srcPath, destPath);
304
+ }
234
305
  }
235
306
 
236
307
  const configToml = `# staged by termdeck init --rumen
@@ -245,19 +316,27 @@ verify_jwt = false
245
316
  }
246
317
 
247
318
  function setFunctionSecrets(secrets, dryRun) {
248
- step('Setting function secrets (DATABASE_URL, ANTHROPIC_API_KEY)...');
319
+ const haveOpenAI = Boolean(secrets.OPENAI_API_KEY);
320
+ const label = haveOpenAI
321
+ ? 'DATABASE_URL, ANTHROPIC_API_KEY, OPENAI_API_KEY'
322
+ : 'DATABASE_URL, ANTHROPIC_API_KEY';
323
+ step(`Setting function secrets (${label})...`);
249
324
  if (dryRun) { ok('(dry-run)'); return true; }
250
- const r = runShellCaptured('supabase', [
325
+ const args = [
251
326
  'secrets', 'set',
252
327
  `DATABASE_URL=${secrets.DATABASE_URL}`,
253
328
  `ANTHROPIC_API_KEY=${secrets.ANTHROPIC_API_KEY}`
254
- ]);
329
+ ];
330
+ if (haveOpenAI) {
331
+ args.push(`OPENAI_API_KEY=${secrets.OPENAI_API_KEY}`);
332
+ }
333
+ const r = runShellCaptured('supabase', args);
255
334
  if (!r.ok) {
256
335
  fail(`secrets set failed (exit ${r.code})`);
257
336
  if (r.stderr) process.stderr.write(r.stderr + '\n');
258
337
  return false;
259
338
  }
260
- ok();
339
+ ok(haveOpenAI ? '(hybrid mode)' : '(keyword-only mode — OPENAI_API_KEY not set)');
261
340
  return true;
262
341
  }
263
342
 
@@ -400,7 +479,17 @@ async function main(argv) {
400
479
 
401
480
  if (!(await link(projectRef, flags.dryRun))) return 4;
402
481
  if (!(await applyRumenTables(secrets, flags.dryRun))) return 5;
403
- if (!deployFunction(flags.dryRun)) return 6;
482
+
483
+ step('Resolving @jhizzard/rumen version from npm registry...');
484
+ const resolved = resolveRumenVersion();
485
+ ok();
486
+ process.stdout.write(`→ Using rumen version: ${resolved.version} (from ${resolved.source})\n`);
487
+ if (resolved.warning) {
488
+ process.stderr.write(` ! ${resolved.warning}\n`);
489
+ process.stderr.write(` ! falling back to pinned FALLBACK_RUMEN_VERSION=${FALLBACK_RUMEN_VERSION}\n`);
490
+ }
491
+
492
+ if (!deployFunction(resolved.version, flags.dryRun)) return 6;
404
493
  if (!setFunctionSecrets(secrets, flags.dryRun)) return 7;
405
494
  if (!(await testFunction(projectRef, secrets, flags.dryRun))) return 8;
406
495
  if (!flags.skipSchedule) {