@jhizzard/termdeck 0.6.0 → 0.6.2

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
@@ -81,7 +81,7 @@ What's excluded at this tier: **Flashback is silent**, the "Ask about this termi
81
81
 
82
82
  ### Tier 2 — Add Mnestra to light up Flashback
83
83
 
84
- Mnestra is a separate npm package — `@jhizzard/mnestra@0.2.0` — that ships a Postgres-backed persistent memory store with an MCP server, a webhook server, six search tools, and six SQL migrations. It can be consumed by TermDeck (for Flashback), by Claude Code (as an MCP memory layer), or by any tool that speaks the MCP protocol.
84
+ Mnestra is a separate npm package — `@jhizzard/mnestra@0.2.1` — that ships a Postgres-backed persistent memory store with an MCP server, a webhook server, six search tools, and six SQL migrations. It can be consumed by TermDeck (for Flashback), by Claude Code (as an MCP memory layer), or by any tool that speaks the MCP protocol.
85
85
 
86
86
  To enable Flashback in TermDeck:
87
87
 
@@ -147,7 +147,7 @@ Restart Claude Code. Six MCP tools appear: `memory_remember`, `memory_recall`, `
147
147
 
148
148
  ### Tier 3 — Add Rumen for async learning
149
149
 
150
- Rumen is a separate npm package — `@jhizzard/rumen@0.4.5` — 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.
150
+ Rumen is a separate npm package — `@jhizzard/rumen@0.4.3` — 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.
151
151
 
152
152
  **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.
153
153
 
@@ -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.4.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.
174
+ - **Not proven at scale.** v0.6.1, validated against 4,590 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.0",
3
+ "version": "0.6.2",
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"
@@ -13,7 +13,7 @@
13
13
  //
14
14
  // Flags:
15
15
  // --help Print usage and exit
16
- // --yes Accept defaults, skip confirmations (still prompts for secrets)
16
+ // --yes Reserved (no-op as of v0.6.2 wizard no longer asks "Proceed?" after secrets)
17
17
  // --dry-run Print what the wizard would do; don't touch the DB or filesystem
18
18
  // --skip-verify Skip the final memory_status_aggregation() check
19
19
  //
@@ -41,7 +41,7 @@ const HELP = [
41
41
  '',
42
42
  'Flags:',
43
43
  ' --help Print this message and exit',
44
- ' --yes Assume "yes" on confirmations (still prompts for secret values)',
44
+ ' --yes Reserved (no-op as of v0.6.2 kept for forward compatibility)',
45
45
  ' --dry-run Print the plan without touching the database or filesystem',
46
46
  ' --skip-verify Skip the final memory_status_aggregation() sanity call',
47
47
  '',
@@ -129,14 +129,17 @@ async function collectInputs({ yes }) {
129
129
  }
130
130
  }
131
131
 
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
- }
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
+ process.stdout.write('\n');
140
143
 
141
144
  return {
142
145
  projectUrl,
@@ -105,13 +105,38 @@ async function askOptional(question, { validate } = {}) {
105
105
  // Secret prompt. On TTY we mute echo; on non-TTY we fall back to a visible
106
106
  // line read. Callers typically pass an empty string for `question` and write
107
107
  // their own label first.
108
+ //
109
+ // Reported as broken on MobaXterm SSH (Brad, 2026-04-25): the wizard would
110
+ // abort after the Anthropic key prompt as if Ctrl-C were pressed. Three real
111
+ // bugs hardened against here, all from the original raw-mode loop:
112
+ //
113
+ // 1. CRLF leak. When the terminal sends "\r\n" as a single chunk (Windows /
114
+ // MobaXterm Enter key), the original loop matched the "\r", resolved,
115
+ // and dropped the rest of the chunk on the floor. The trailing "\n"
116
+ // then surfaced through the next prompt's data path. Worst case the
117
+ // dropped chunk also contained  from a stray keystroke and the
118
+ // original SIGINT branch fired, killing the wizard mid-flow.
119
+ //
120
+ // 2. ANSI / escape-sequence pollution. Some terminals emit "[..."
121
+ // sequences for non-character events (focus changes, cursor reports,
122
+ // paste-bracketing). The old loop fed those bytes into the password
123
+ // buffer and echoed "*" for each one. We now consume escape sequences
124
+ // silently.
125
+ //
126
+ // 3. SIGINT during a secret prompt is now a soft cancel — return empty
127
+ // string, let the caller's shape validator re-prompt — instead of
128
+ // `process.kill`-ing the process. The hard-kill was masking the CRLF
129
+ // bug above and aborting the wizard from stray bytes.
130
+ //
131
+ // Carry-over bytes (anything in the chunk after the resolving "\r"/"\n")
132
+ // are pushed back onto stdin via `stdin.unshift` so the next consumer
133
+ // (readline, the next askSecret) reads from a clean stream. Regression
134
+ // fixtures in tests/setup-prompts.test.js.
108
135
  async function askSecret(question) {
109
136
  if (!process.stdin.isTTY) {
110
137
  return ask(question);
111
138
  }
112
139
  if (question) process.stdout.write(`${question}: `);
113
- // Raw-mode reader. Detach the shared readline for the duration so both
114
- // consumers aren't racing on stdin 'data' events.
115
140
  return new Promise((resolve) => {
116
141
  if (rl) rl.pause();
117
142
  const stdin = process.stdin;
@@ -120,30 +145,78 @@ async function askSecret(question) {
120
145
  stdin.setEncoding('utf-8');
121
146
 
122
147
  let buffer = '';
148
+ let inEscape = false;
149
+ let escapeDepth = 0;
150
+
151
+ const cleanup = () => {
152
+ stdin.setRawMode(false);
153
+ stdin.removeListener('data', onData);
154
+ process.stdout.write('\n');
155
+ if (rl) rl.resume();
156
+ };
157
+
158
+ const carryOver = (chunk, fromIndex) => {
159
+ if (fromIndex >= chunk.length) return;
160
+ const tail = chunk.slice(fromIndex);
161
+ // Drop a single leading \n if we just consumed \r (CRLF pair already
162
+ // accounted for at the call site).
163
+ const trimmed = tail[0] === '\n' ? tail.slice(1) : tail;
164
+ if (trimmed.length > 0) {
165
+ try { stdin.unshift(Buffer.from(trimmed, 'utf-8')); } catch (_e) { /* unsupported on some Node versions */ }
166
+ }
167
+ };
168
+
123
169
  const onData = (chunk) => {
124
- for (const ch of chunk) {
125
- if (ch === '\n' || ch === '\r' || ch === '\u0004') {
126
- stdin.setRawMode(false);
127
- stdin.removeListener('data', onData);
128
- process.stdout.write('\n');
129
- if (rl) rl.resume();
170
+ for (let i = 0; i < chunk.length; i++) {
171
+ const ch = chunk[i];
172
+
173
+ // Bug #2: silently consume ANSI escape sequences. ESC (\u001b) starts one;
174
+ // we consume until a final byte (CSI: 0x40..0x7E) or run out of
175
+ // patience (16 bytes).
176
+ if (inEscape) {
177
+ escapeDepth++;
178
+ if ((escapeDepth === 1 && ch !== '[') ||
179
+ (escapeDepth > 1 && ch >= '@' && ch <= '~') ||
180
+ escapeDepth > 16) {
181
+ inEscape = false;
182
+ escapeDepth = 0;
183
+ }
184
+ continue;
185
+ }
186
+ if (ch === '') { inEscape = true; escapeDepth = 0; continue; }
187
+
188
+ // Line terminators — the resolve path. Bug #1 handled here via the
189
+ // explicit CRLF drain inside the same chunk.
190
+ if (ch === '\n' || ch === '\r' || ch === '') {
191
+ let consumeUpTo = i + 1;
192
+ if (ch === '\r' && chunk[i + 1] === '\n') consumeUpTo++;
193
+ cleanup();
194
+ carryOver(chunk, consumeUpTo);
130
195
  resolve(buffer);
131
196
  return;
132
197
  }
133
- if (ch === '\u0003') {
134
- stdin.setRawMode(false);
135
- stdin.removeListener('data', onData);
136
- process.stdout.write('\n');
137
- process.kill(process.pid, 'SIGINT');
198
+
199
+ // Bug #3: Ctrl-C during a secret prompt → soft cancel, not SIGINT.
200
+ if (ch === '') {
201
+ cleanup();
202
+ carryOver(chunk, i + 1);
203
+ resolve('');
138
204
  return;
139
205
  }
140
- if (ch === '\u007f' || ch === '\b') {
206
+
207
+ // Backspace / DEL.
208
+ if (ch === '' || ch === '\b') {
141
209
  if (buffer.length > 0) {
142
210
  buffer = buffer.slice(0, -1);
143
211
  process.stdout.write('\b \b');
144
212
  }
145
213
  continue;
146
214
  }
215
+
216
+ // Drop any other control character silently; never let them into
217
+ // the password buffer.
218
+ if (ch < ' ' && ch !== '\t') continue;
219
+
147
220
  buffer += ch;
148
221
  process.stdout.write('*');
149
222
  }