@jhizzard/termdeck 1.0.14 → 1.1.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.
Files changed (19) hide show
  1. package/package.json +1 -1
  2. package/packages/cli/src/init-mnestra.js +50 -10
  3. package/packages/client/public/app.js +57 -0
  4. package/packages/server/src/index.js +19 -1
  5. package/packages/server/src/setup/migrations.js +514 -1
  6. package/packages/server/src/setup/mnestra-migrations/001_mnestra_tables.sql +2 -2
  7. package/packages/server/src/setup/mnestra-migrations/002_mnestra_search_function.sql +1 -1
  8. package/packages/server/src/setup/mnestra-migrations/009_memory_relationship_metadata.sql +1 -1
  9. package/packages/server/src/setup/mnestra-migrations/011_project_tag_backfill.sql +7 -3
  10. package/packages/server/src/setup/mnestra-migrations/012_project_tag_re_taxonomy.sql +2 -2
  11. package/packages/server/src/setup/mnestra-migrations/014_explicit_grants.sql +3 -3
  12. package/packages/server/src/setup/mnestra-migrations/016_mnestra_doctor_probes.sql +3 -3
  13. package/packages/server/src/setup/mnestra-migrations/017_memory_sessions_session_metadata.sql +5 -5
  14. package/packages/server/src/setup/mnestra-migrations/018_rumen_processed_at.sql +1 -1
  15. package/packages/server/src/setup/mnestra-migrations/019_security_hardening.sql +190 -0
  16. package/packages/server/src/setup/mnestra-migrations/020_migration_tracking.sql +57 -0
  17. package/packages/server/src/setup/mnestra-migrations/021_project_tag_canonicalize_claimguard.sql +175 -0
  18. package/packages/server/src/setup/mnestra-migrations/022_source_agent_backfill.sql +182 -0
  19. package/packages/server/src/setup/rumen/functions/rumen-tick/index.ts +0 -30
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jhizzard/termdeck",
3
- "version": "1.0.14",
3
+ "version": "1.1.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"
@@ -332,23 +332,63 @@ async function promptSecretWithValidation(validator) {
332
332
  throw new Error('Too many invalid attempts — cancelling.');
333
333
  }
334
334
 
335
+ // Sprint 61 T2 — collapsed fresh-install / upgrade paths. Pre-Sprint-61, the
336
+ // wizard re-applied every bundled mnestra migration on every invocation,
337
+ // relying on per-file IF NOT EXISTS / CREATE OR REPLACE idempotency. That
338
+ // works for fresh installs but doesn't tell the wizard which migrations the
339
+ // live database has actually received — so a user running
340
+ // `npm install -g @latest` against an existing project lands in Class A
341
+ // (schema drift on package upgrade): the npm package files upgrade, the
342
+ // database stays at first-kickstart state. Brad reported this 2026-05-02.
343
+ //
344
+ // applyPendingMigrations (migrations.js) replaces the loop with a tracker-
345
+ // aware diff: SELECT applied filenames from public.mnestra_migrations, run
346
+ // only the bundled-but-unapplied ones, INSERT a tracker row per apply.
347
+ // Pre-020 installs trigger a one-time backfill probe pass that seeds the
348
+ // tracker for migrations whose schema artifacts are already present.
335
349
  async function applyMigrations(client, dryRun) {
336
350
  const files = migrations.listMnestraMigrations();
337
351
  if (files.length === 0) {
338
352
  throw new Error('No Mnestra migrations found. TermDeck install looks corrupted.');
339
353
  }
340
354
 
341
- for (const file of files) {
342
- const base = path.basename(file);
343
- step(`Applying migration ${base}...`);
344
- if (dryRun) { ok('(dry-run)'); continue; }
345
- const result = await pgRunner.applyFile(client, file);
346
- if (result.ok) {
347
- ok(`(${result.elapsedMs}ms)`);
348
- } else {
349
- fail(result.error);
350
- throw new Error(`Migration failed: ${base}`);
355
+ if (dryRun) {
356
+ // Preserve the per-file dry-run banner so the user sees the planned
357
+ // sequence without touching the database.
358
+ for (const file of files) {
359
+ const base = path.basename(file);
360
+ step(`Applying migration ${base}...`);
361
+ ok('(dry-run)');
351
362
  }
363
+ return;
364
+ }
365
+
366
+ step('Running tracker-aware diff-and-apply (skips already-applied migrations)...');
367
+ const summary = await migrations.applyPendingMigrations(client);
368
+
369
+ if (summary.errored) {
370
+ fail(`${summary.errored.file}: ${summary.errored.error}`);
371
+ throw new Error(`Migration failed: ${summary.errored.file}`);
372
+ }
373
+
374
+ ok(
375
+ `(applied ${summary.applied.length}, backfilled ${summary.backfilled.length}, ` +
376
+ `skipped ${summary.skipped.length})`
377
+ );
378
+
379
+ for (const f of summary.applied) {
380
+ process.stdout.write(` ✓ applied ${f}\n`);
381
+ }
382
+ for (const f of summary.backfilled) {
383
+ process.stdout.write(` ◇ backfilled ${f} (schema already present, recorded in tracker)\n`);
384
+ }
385
+ for (const w of summary.warnings) {
386
+ const tracked = (w.trackedChecksum || '').slice(0, 12) || '<empty>';
387
+ const bundled = (w.bundledChecksum || '').slice(0, 12) || '<empty>';
388
+ process.stdout.write(
389
+ ` ! checksum drift on ${w.file}: tracked=${tracked}, bundled=${bundled} ` +
390
+ `(no auto-overwrite — investigate before re-running)\n`
391
+ );
352
392
  }
353
393
  }
354
394
 
@@ -129,6 +129,12 @@
129
129
  // Sprint 37 T1: orchestrator Guide right-rail. Lazy — fetches the doc
130
130
  // on first expand to keep page load light.
131
131
  setupGuideRail();
132
+
133
+ // 2026-05-08 hotfix: document-level capture-phase image-paste handler.
134
+ // Intercepts Cmd+V image data before xterm-helper-textarea consumes it
135
+ // (xterm reads only text/plain, drops images silently). See comment on
136
+ // setupGlobalImagePaste() near uploadFilesAndType() for details.
137
+ setupGlobalImagePaste();
132
138
  }
133
139
 
134
140
  // ===== Drag/drop reorder of PTY panels (Sprint 42 T4) =====
@@ -275,6 +281,57 @@
275
281
  }
276
282
  }
277
283
 
284
+ // Document-level capture-phase image paste handler.
285
+ //
286
+ // The Sprint 59 per-panel `paste` listener at line 218 is bubble-phase, but
287
+ // xterm.js@5.5.0's hidden helper-textarea has its own `paste` handler that
288
+ // reads only `clipboardData.getData('text/plain')`. Image data lives in
289
+ // `clipboardData.items` with `kind: 'file'` and never reaches xterm's
290
+ // text path — and the panel-level bubble-phase handler runs after xterm's,
291
+ // by which point xterm has already returned (silently dropping the image).
292
+ // Net: pre-fix, Cmd+V'ing a screenshot into a focused TermDeck panel did
293
+ // nothing. Joshua reported this on 2026-05-08 (post-v1.1.0 upgrade).
294
+ //
295
+ // Fix: document-level listener with `{capture: true}` runs in capture
296
+ // phase BEFORE the event reaches xterm-helper-textarea. If the event
297
+ // target is inside a `.term-panel` AND the clipboard contains image
298
+ // files, we preventDefault + stopPropagation (so xterm + the bubble-phase
299
+ // panel handler don't see it) and route through `uploadFilesAndType`.
300
+ // For text paste (no image files) we let the event continue normally.
301
+ //
302
+ // Idempotent: setupGlobalImagePaste() is called once from init().
303
+ let _globalImagePasteSetup = false;
304
+ function setupGlobalImagePaste() {
305
+ if (_globalImagePasteSetup) return;
306
+ _globalImagePasteSetup = true;
307
+ document.addEventListener('paste', (e) => {
308
+ const target = e.target;
309
+ if (!(target instanceof Element)) return;
310
+ const panel = target.closest('.term-panel');
311
+ if (!panel) return;
312
+ const items = (e.clipboardData && e.clipboardData.items) || [];
313
+ const blobs = [];
314
+ for (const item of items) {
315
+ if (item.kind === 'file' && item.type && item.type.startsWith('image/')) {
316
+ const blob = item.getAsFile();
317
+ if (blob) blobs.push(blob);
318
+ }
319
+ }
320
+ if (blobs.length === 0) return;
321
+ e.preventDefault();
322
+ e.stopPropagation();
323
+ const ts = new Date().toISOString().replace(/[:.]/g, '-');
324
+ const named = blobs.map((b, i) => {
325
+ const ext = (b.type.split('/')[1] || 'png').replace(/[^a-z0-9]/gi, '');
326
+ const name = b.name && b.name.length > 0
327
+ ? b.name
328
+ : `pasted-${ts}${blobs.length > 1 ? '-' + i : ''}.${ext}`;
329
+ return new File([b], name, { type: b.type });
330
+ });
331
+ uploadFilesAndType(panel, named);
332
+ }, { capture: true });
333
+ }
334
+
278
335
  // ===== Create Terminal Panel =====
279
336
  function createTerminalPanel(sessionData) {
280
337
  const id = sessionData.id;
@@ -16,7 +16,25 @@ const { createCachedLookup, createFailureLogger } = require('./rumen-pool-resili
16
16
  // Conditional imports (graceful fallback if not installed yet)
17
17
  let pty, Database, pg;
18
18
  try { pty = require('@homebridge/node-pty-prebuilt-multiarch'); } catch { pty = null; }
19
- try { Database = require('better-sqlite3'); } catch { Database = null; }
19
+ try {
20
+ Database = require('better-sqlite3');
21
+ } catch (err) {
22
+ // Brad Heath 2026-05-11: distinguish a native-ABI mismatch (Node upgraded
23
+ // after install) from "package not installed yet." ABI mismatch leaves
24
+ // Database=null and cascades into a null-handle storm downstream that
25
+ // masquerades as "Mnestra unreachable / DB timeout" in health probes.
26
+ // Fail fast with the actionable rebuild hint instead.
27
+ const msg = err && err.message ? String(err.message) : '';
28
+ if (err && err.code === 'ERR_DLOPEN_FAILED' && /NODE_MODULE_VERSION/.test(msg)) {
29
+ console.error('[db] better-sqlite3 native ABI mismatch (Node was upgraded after install).');
30
+ console.error('[db] TermDeck cannot serve memory features without a working SQLite.');
31
+ console.error('[db] Fix:');
32
+ console.error(' cd "$(npm root -g)/@jhizzard/termdeck" && npm rebuild better-sqlite3');
33
+ console.error('[db] Then restart TermDeck. Aborting.');
34
+ process.exit(1);
35
+ }
36
+ Database = null;
37
+ }
20
38
  try { pg = require('pg'); } catch { pg = null; }
21
39
 
22
40
  // Module-level singleton Postgres pool for rumen_insights (petvetbid DB).