@jhizzard/termdeck 0.6.0 → 0.6.1

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.1",
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"
@@ -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
  }