@jhizzard/termdeck 1.6.1 → 1.8.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/package.json +1 -1
- package/packages/cli/src/doctor.js +100 -0
- package/packages/cli/src/init-mnestra.js +50 -6
- package/packages/cli/src/init-rumen.js +3 -3
- package/packages/client/public/app.js +341 -30
- package/packages/client/public/index.html +0 -1
- package/packages/client/public/style.css +2 -31
- package/packages/server/src/agent-adapters/agy.js +396 -0
- package/packages/server/src/agent-adapters/gemini.js +309 -42
- package/packages/server/src/agent-adapters/grok-models.js +112 -76
- package/packages/server/src/agent-adapters/index.js +19 -0
- package/packages/server/src/agent-adapters/web-chat-grok.js +259 -0
- package/packages/server/src/index.js +572 -10
- package/packages/server/src/setup/audit-upgrade.js +3 -3
- package/packages/server/src/setup/rumen/functions/graph-inference/index.ts +1 -1
- package/packages/stack-installer/assets/hooks/memory-session-end.js +73 -32
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jhizzard/termdeck",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.8.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"
|
|
@@ -256,6 +256,7 @@ function parseArgv(argv) {
|
|
|
256
256
|
json: args.includes('--json'),
|
|
257
257
|
noColor: args.includes('--no-color'),
|
|
258
258
|
noSchema: args.includes('--no-schema'),
|
|
259
|
+
noAgents: args.includes('--no-agents'),
|
|
259
260
|
};
|
|
260
261
|
}
|
|
261
262
|
|
|
@@ -547,6 +548,83 @@ function renderSchemaResult(result, c) {
|
|
|
547
548
|
return out.join('\n');
|
|
548
549
|
}
|
|
549
550
|
|
|
551
|
+
// ── Sprint 70 T2: agent-CLI auth-probe section ─────────────────────────────
|
|
552
|
+
//
|
|
553
|
+
// Surfaces each agent adapter's `checkAuth()` verdict in `termdeck doctor` so a
|
|
554
|
+
// misconfigured agent CLI is caught here instead of failing silently at panel
|
|
555
|
+
// spawn. The motivating case: Google ends Gemini's OAuth serving path on
|
|
556
|
+
// 2026-06-18, after which a Gemini CLI still set to `oauth-personal` stops
|
|
557
|
+
// working — `checkAuth()` reports that as `wrong-mode` and this section turns
|
|
558
|
+
// it into a doctor RED (exit 1) with a remediation hint.
|
|
559
|
+
//
|
|
560
|
+
// Only adapters that expose a `checkAuth` function participate (Gemini today;
|
|
561
|
+
// forward-compatible as Codex/Grok/agy add probes). Static-only (`live:false`)
|
|
562
|
+
// — no spawn / network, so the section never hangs or hits an API. The whole
|
|
563
|
+
// registry require is wrapped: on any load failure the section is skipped
|
|
564
|
+
// (never a crash, never a false RED). Monkey-patchable as
|
|
565
|
+
// `module.exports._runAgentAuthCheck`, the same seam pattern as
|
|
566
|
+
// `_runSchemaCheck`.
|
|
567
|
+
async function _runAgentAuthCheck(opts = {}) {
|
|
568
|
+
let registry;
|
|
569
|
+
try {
|
|
570
|
+
registry = require(path.join(__dirname, '..', '..', 'server', 'src', 'agent-adapters'));
|
|
571
|
+
} catch (err) {
|
|
572
|
+
return {
|
|
573
|
+
skipped: true,
|
|
574
|
+
reason: `agent adapters unavailable: ${err && err.message || err}`,
|
|
575
|
+
agents: [], passed: 0, total: 0, hasGaps: false,
|
|
576
|
+
};
|
|
577
|
+
}
|
|
578
|
+
const adapters = Object.values((registry && registry.AGENT_ADAPTERS) || {})
|
|
579
|
+
.filter((a) => a && typeof a.checkAuth === 'function');
|
|
580
|
+
const agents = [];
|
|
581
|
+
for (const a of adapters) {
|
|
582
|
+
let v;
|
|
583
|
+
try {
|
|
584
|
+
// live:false → static checks only (env + settings.json); never spawns.
|
|
585
|
+
v = await a.checkAuth({ live: false, ...opts });
|
|
586
|
+
} catch (err) {
|
|
587
|
+
v = { ok: false, state: 'error', detail: `probe threw: ${err && err.message || err}`, hint: '' };
|
|
588
|
+
}
|
|
589
|
+
agents.push({
|
|
590
|
+
name: a.displayName || a.name,
|
|
591
|
+
state: v.state,
|
|
592
|
+
ok: v.ok === true,
|
|
593
|
+
detail: v.detail || '',
|
|
594
|
+
hint: v.hint || '',
|
|
595
|
+
});
|
|
596
|
+
}
|
|
597
|
+
const passed = agents.filter((x) => x.ok).length;
|
|
598
|
+
return { skipped: false, agents, passed, total: agents.length, hasGaps: passed < agents.length };
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
function renderAgentAuthResult(result, c) {
|
|
602
|
+
const out = [];
|
|
603
|
+
out.push('');
|
|
604
|
+
out.push(c.bold('Agent CLI auth'));
|
|
605
|
+
out.push('');
|
|
606
|
+
if (result.skipped) {
|
|
607
|
+
out.push(` ${c.dim(`(skipped) ${result.reason}`)}`);
|
|
608
|
+
return out.join('\n');
|
|
609
|
+
}
|
|
610
|
+
if (!result.agents || result.agents.length === 0) {
|
|
611
|
+
out.push(` ${c.dim('(no agent adapters expose an auth probe)')}`);
|
|
612
|
+
return out.join('\n');
|
|
613
|
+
}
|
|
614
|
+
for (const a of result.agents) {
|
|
615
|
+
if (a.ok) {
|
|
616
|
+
out.push(` ${c.green('✓')} ${a.name}: ${a.state}`);
|
|
617
|
+
} else {
|
|
618
|
+
out.push(` ${c.yellow('✗')} ${a.name}: ${a.state}`);
|
|
619
|
+
if (a.detail) out.push(` ${c.dim(a.detail)}`);
|
|
620
|
+
if (a.hint) out.push(` ${c.dim(a.hint)}`);
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
out.push('');
|
|
624
|
+
out.push(` ${result.passed}/${result.total} agent auth checks passed`);
|
|
625
|
+
return out.join('\n');
|
|
626
|
+
}
|
|
627
|
+
|
|
550
628
|
async function doctor(argv) {
|
|
551
629
|
const opts = parseArgv(argv);
|
|
552
630
|
|
|
@@ -586,6 +664,22 @@ async function doctor(argv) {
|
|
|
586
664
|
}
|
|
587
665
|
}
|
|
588
666
|
|
|
667
|
+
// Sprint 70 T2: agent-CLI auth probe (skippable for tests / hosts without
|
|
668
|
+
// agent CLIs). Static-only by default — no spawn / network.
|
|
669
|
+
let agents = null;
|
|
670
|
+
if (!opts.noAgents) {
|
|
671
|
+
try {
|
|
672
|
+
agents = await module.exports._runAgentAuthCheck();
|
|
673
|
+
} catch (err) {
|
|
674
|
+
agents = {
|
|
675
|
+
skipped: false,
|
|
676
|
+
agents: [{ name: 'agent auth', state: 'error', ok: false,
|
|
677
|
+
detail: `unexpected error: ${err && err.message || err}`, hint: '' }],
|
|
678
|
+
passed: 0, total: 1, hasGaps: true,
|
|
679
|
+
};
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
|
|
589
683
|
// Exit-code priority: any network failure → 2; any update available OR
|
|
590
684
|
// schema gap → 1; else 0. Computed after all rows resolve so a single
|
|
591
685
|
// transient failure doesn't mask real updates in stdout. A schema connect
|
|
@@ -600,10 +694,12 @@ async function doctor(argv) {
|
|
|
600
694
|
}
|
|
601
695
|
if (schema && schema.connectError && exitCode < 2) exitCode = 2;
|
|
602
696
|
if (schema && !schema.skipped && schema.hasGaps && exitCode < 1) exitCode = 1;
|
|
697
|
+
if (agents && !agents.skipped && agents.hasGaps && exitCode < 1) exitCode = 1;
|
|
603
698
|
|
|
604
699
|
if (opts.json) {
|
|
605
700
|
const payload = { exitCode, rows };
|
|
606
701
|
if (schema) payload.schema = schema;
|
|
702
|
+
if (agents) payload.agents = agents;
|
|
607
703
|
process.stdout.write(JSON.stringify(payload, null, 2) + '\n');
|
|
608
704
|
return exitCode;
|
|
609
705
|
}
|
|
@@ -615,6 +711,9 @@ async function doctor(argv) {
|
|
|
615
711
|
if (schema) {
|
|
616
712
|
process.stdout.write(renderSchemaResult(schema, c) + '\n');
|
|
617
713
|
}
|
|
714
|
+
if (agents) {
|
|
715
|
+
process.stdout.write(renderAgentAuthResult(agents, c) + '\n');
|
|
716
|
+
}
|
|
618
717
|
return exitCode;
|
|
619
718
|
}
|
|
620
719
|
|
|
@@ -625,5 +724,6 @@ module.exports._compareSemver = _compareSemver;
|
|
|
625
724
|
module.exports._detectMnestraVersion = _detectMnestraVersion;
|
|
626
725
|
module.exports._selectHybridSearchRpcNames = _selectHybridSearchRpcNames;
|
|
627
726
|
module.exports._runSchemaCheck = _runSchemaCheck;
|
|
727
|
+
module.exports._runAgentAuthCheck = _runAgentAuthCheck;
|
|
628
728
|
module.exports.STACK_PACKAGES = STACK_PACKAGES;
|
|
629
729
|
module.exports.STATUS = STATUS;
|
|
@@ -597,7 +597,39 @@ function refreshBundledHookIfNewer(opts = {}) {
|
|
|
597
597
|
}
|
|
598
598
|
const installed = readVersion(HOOK_DEST);
|
|
599
599
|
if (installed !== null && installed >= bundled) {
|
|
600
|
-
|
|
600
|
+
// Sprint 67 T1 — content-drift gate. Stamp-equal does NOT prove content-
|
|
601
|
+
// equal. Sprints 62/63/64 each grew the v2-stamped session-end body
|
|
602
|
+
// without bumping to v3; the daily-driver sat on the Sprint-51.7-era v2
|
|
603
|
+
// body for ~2 weeks (May 4 → May 19, 2026) because this early-return
|
|
604
|
+
// fired before any bytes were compared. Compare bytes; if they differ
|
|
605
|
+
// AND the installed file is TermDeck-managed (same trust signal that
|
|
606
|
+
// gates the unsigned-installed safety branch below), refresh with a
|
|
607
|
+
// backup. Hand-edited user files (no TermDeck markers) are still
|
|
608
|
+
// preserved — drift in that case is the user's intent.
|
|
609
|
+
let identical = false;
|
|
610
|
+
try {
|
|
611
|
+
identical = fs.readFileSync(HOOK_SOURCE).equals(fs.readFileSync(HOOK_DEST));
|
|
612
|
+
} catch (_) {
|
|
613
|
+
// Best-effort: a transient read error shouldn't trigger an overwrite.
|
|
614
|
+
// Treat as identical so we exit through the safe up-to-date path.
|
|
615
|
+
identical = true;
|
|
616
|
+
}
|
|
617
|
+
if (identical) return { status: 'up-to-date', installed, bundled };
|
|
618
|
+
if (!looksTermdeckManaged(HOOK_DEST)) {
|
|
619
|
+
return {
|
|
620
|
+
status: 'custom-hook-preserved-content-drift',
|
|
621
|
+
message: 'installed hook is stamp-equal to bundled but bytes differ and the file lacks TermDeck-managed markers; keeping as-is.',
|
|
622
|
+
installed,
|
|
623
|
+
bundled,
|
|
624
|
+
};
|
|
625
|
+
}
|
|
626
|
+
if (dryRun) return { status: 'would-refresh-content-drift', from: installed, to: bundled };
|
|
627
|
+
const dStamp = new Date().toISOString().replace(/[-:T.Z]/g, '').slice(0, 14);
|
|
628
|
+
const dBackup = `${HOOK_DEST}.bak.${dStamp}`;
|
|
629
|
+
try { fs.copyFileSync(HOOK_DEST, dBackup); } catch (_) { /* best-effort */ }
|
|
630
|
+
fs.copyFileSync(HOOK_SOURCE, HOOK_DEST);
|
|
631
|
+
fs.chmodSync(HOOK_DEST, 0o644);
|
|
632
|
+
return { status: 'refreshed-content-drift', from: installed, to: bundled, backup: dBackup };
|
|
601
633
|
}
|
|
602
634
|
// Sprint 51.6 T4-CODEX audit 20:23 ET safety gate: an unsigned installed
|
|
603
635
|
// hook gets refreshed ONLY if it looks TermDeck-managed (carries one of
|
|
@@ -625,11 +657,11 @@ function refreshBundledHookIfNewer(opts = {}) {
|
|
|
625
657
|
// DB-side failures (Class A schema drift, network blips, partial state)
|
|
626
658
|
// cannot strand the hook upgrade. Joshua's 2026-05-03 Phase B run threw at
|
|
627
659
|
// `applyMigrations()` on `001_mnestra_tables.sql` (the `match_memories`
|
|
628
|
-
// CREATE OR REPLACE return-type drift on
|
|
660
|
+
// CREATE OR REPLACE return-type drift on the daily-driver project — existing function had
|
|
629
661
|
// columns in a different order, Postgres rejected with "cannot change return
|
|
630
662
|
// type of existing function"). Outer catch at the old call site fired and
|
|
631
663
|
// returned exit 5; the refresh at the old wire-up never ran. Brad's
|
|
632
|
-
//
|
|
664
|
+
// peer install reproduced the same symptom under v1.0.2.
|
|
633
665
|
//
|
|
634
666
|
// Hook refresh is a LOCAL filesystem operation. It has no dependency on DB
|
|
635
667
|
// success, so it should run as part of the initial local-setup phase next
|
|
@@ -660,7 +692,7 @@ function refreshBundledHookIfNewer(opts = {}) {
|
|
|
660
692
|
// v1.0.0/v1.0.1) gets the v2 hook FILE post-1.0.3, but the file is still
|
|
661
693
|
// wired under `Stop`. The v2 hook does not gate on event type, so it
|
|
662
694
|
// fires every assistant turn and writes N `session_summary` rows in
|
|
663
|
-
// `memory_items` per session (Brad's 2026-05-04
|
|
695
|
+
// `memory_items` per session (Brad's 2026-05-04 peer install repro).
|
|
664
696
|
//
|
|
665
697
|
// `_mergeSessionEndHookEntry` is a 1:1 hoist of the same-named function
|
|
666
698
|
// in `packages/stack-installer/src/index.js:451`. We can't `require()`
|
|
@@ -965,6 +997,12 @@ function runHookRefresh({ dryRun = false } = {}) {
|
|
|
965
997
|
ok(`installed v${r.bundled} (no prior copy)`);
|
|
966
998
|
} else if (r.status === 'would-install') {
|
|
967
999
|
ok(`would-install v${r.bundled} (dry-run, no prior copy)`);
|
|
1000
|
+
} else if (r.status === 'refreshed-content-drift') {
|
|
1001
|
+
ok(`refreshed v${r.from} → v${r.to} (content-drift; backup: ${path.basename(r.backup)})`);
|
|
1002
|
+
} else if (r.status === 'would-refresh-content-drift') {
|
|
1003
|
+
ok(`would-refresh v${r.from} → v${r.to} (content-drift; dry-run)`);
|
|
1004
|
+
} else if (r.status === 'custom-hook-preserved-content-drift') {
|
|
1005
|
+
ok(`custom-hook-preserved (bytes differ from bundled but no TermDeck markers; keeping as-is)`);
|
|
968
1006
|
} else if (r.status === 'up-to-date') {
|
|
969
1007
|
ok(`up-to-date (v${r.installed})`);
|
|
970
1008
|
} else {
|
|
@@ -998,6 +1036,12 @@ function runHookRefresh({ dryRun = false } = {}) {
|
|
|
998
1036
|
ok(`installed v${r.bundled} (no prior copy)`);
|
|
999
1037
|
} else if (r.status === 'would-install') {
|
|
1000
1038
|
ok(`would-install v${r.bundled} (dry-run, no prior copy)`);
|
|
1039
|
+
} else if (r.status === 'refreshed-content-drift') {
|
|
1040
|
+
ok(`refreshed v${r.from} → v${r.to} (content-drift; backup: ${path.basename(r.backup)})`);
|
|
1041
|
+
} else if (r.status === 'would-refresh-content-drift') {
|
|
1042
|
+
ok(`would-refresh v${r.from} → v${r.to} (content-drift; dry-run)`);
|
|
1043
|
+
} else if (r.status === 'custom-hook-preserved-content-drift') {
|
|
1044
|
+
ok(`custom-hook-preserved (bytes differ from bundled but no TermDeck markers; keeping as-is)`);
|
|
1001
1045
|
} else if (r.status === 'up-to-date') {
|
|
1002
1046
|
ok(`up-to-date (v${r.installed})`);
|
|
1003
1047
|
} else {
|
|
@@ -1083,7 +1127,7 @@ async function main(argv) {
|
|
|
1083
1127
|
// DB phase. Hook refresh is local FS work; coupling it downstream of pg
|
|
1084
1128
|
// connect + 17-migration replay (the old wire-up at line 677 in v1.0.2)
|
|
1085
1129
|
// meant ANY DB-side error (Joshua's mig-001 `match_memories` return-type
|
|
1086
|
-
// drift, Brad's same on
|
|
1130
|
+
// drift, Brad's same on peer install) silently skipped the upgrade. With
|
|
1087
1131
|
// refresh here, the user always lands the bundled hook even when the DB
|
|
1088
1132
|
// phase later fails — decoupled concerns, idempotent re-runs, and the
|
|
1089
1133
|
// helper handles its own try/catch internally so a refresh failure never
|
|
@@ -1098,7 +1142,7 @@ async function main(argv) {
|
|
|
1098
1142
|
// and writes N session_summary rows in memory_items per session. This
|
|
1099
1143
|
// migration is idempotent and runs alongside the file refresh so the
|
|
1100
1144
|
// wire-up + wiring stay in lockstep on every wizard pass. Brad's
|
|
1101
|
-
// 2026-05-04
|
|
1145
|
+
// 2026-05-04 peer install repro is the canonical fixture for this
|
|
1102
1146
|
// class of bug (INSTALLER-PITFALLS.md ledger #16).
|
|
1103
1147
|
runSettingsJsonMigration({ dryRun: flags.dryRun });
|
|
1104
1148
|
|
|
@@ -331,7 +331,7 @@ async function applyRumenTables(secrets, dryRun) {
|
|
|
331
331
|
// up front. Idempotent: a re-run on an up-to-date project reports
|
|
332
332
|
// "install up to date" and applies nothing.
|
|
333
333
|
//
|
|
334
|
-
// Brad's 2026-05-02
|
|
334
|
+
// Brad's 2026-05-02 peer install report (INSTALLER-PITFALLS.md ledger #13)
|
|
335
335
|
// is the originating motivation: he upgraded npm packages but his database
|
|
336
336
|
// stayed frozen at first-kickstart because no installer code path diffed an
|
|
337
337
|
// existing install against the bundled migration set. After v1.0.1 ships,
|
|
@@ -391,7 +391,7 @@ const FUNCTIONS_WITH_VERSION_PLACEHOLDER = new Set(['rumen-tick']);
|
|
|
391
391
|
|
|
392
392
|
// Sprint 51.6 T3 — `projectRef` is required and passed explicitly to every
|
|
393
393
|
// `supabase functions deploy` invocation as `--project-ref <ref>`. Brad's
|
|
394
|
-
// 2026-05-03
|
|
394
|
+
// 2026-05-03 peer install install hit Bug C: `supabase link --project-ref`
|
|
395
395
|
// runs successfully (audit-upgrade probes confirm the link is live), but a
|
|
396
396
|
// few subprocess calls later `supabase functions deploy` errors with
|
|
397
397
|
// `Cannot find project ref. Have you run supabase link?` because the link
|
|
@@ -577,7 +577,7 @@ function vaultSqlEditorUrl(projectRef, secretName, secretValue) {
|
|
|
577
577
|
// - graph_inference_service_role_key (used by 003_graph_inference_schedule.sql)
|
|
578
578
|
//
|
|
579
579
|
// Both keys hold the same value (`secrets.SUPABASE_SERVICE_ROLE_KEY`). Brad's
|
|
580
|
-
// 2026-05-02 recovery on
|
|
580
|
+
// 2026-05-02 recovery on peer install literally cloned rumen → graph_inference
|
|
581
581
|
// in vault.
|
|
582
582
|
//
|
|
583
583
|
// Strategy:
|