@jhizzard/termdeck 1.2.0 → 1.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/package.json +2 -2
- package/packages/cli/src/index.js +53 -16
- package/packages/cli/src/init-mnestra.js +131 -0
- package/packages/cli/src/init.js +617 -0
- package/packages/cli/src/mcp-supabase-provision.js +685 -0
- package/packages/cli/src/os-detect.js +297 -0
- package/packages/server/src/agent-adapters/claude.js +11 -0
- package/packages/server/src/agent-adapters/codex.js +203 -1
- package/packages/server/src/agent-adapters/gemini.js +4 -0
- package/packages/server/src/agent-adapters/grok.js +4 -0
- package/packages/server/src/index.js +280 -6
- package/packages/server/src/setup/supabase-mcp.js +42 -3
- package/packages/stack-installer/assets/hooks/memory-pre-compact.js +277 -0
- package/packages/stack-installer/assets/hooks/memory-session-end.js +14 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jhizzard/termdeck",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.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"
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
"dev": "node packages/server/src/index.js",
|
|
31
31
|
"server": "node packages/server/src/index.js",
|
|
32
32
|
"start": "NODE_ENV=production node packages/cli/src/index.js",
|
|
33
|
-
"test": "node --test packages/server/tests/**/*.test.js",
|
|
33
|
+
"test": "node --test packages/server/tests/**/*.test.js packages/cli/tests/**/*.test.js packages/stack-installer/tests/**/*.test.js",
|
|
34
34
|
"install:app": "bash install.sh",
|
|
35
35
|
"sync-rumen-functions": "bash scripts/sync-rumen-functions.sh",
|
|
36
36
|
"sync:agents": "node scripts/sync-agent-instructions.js"
|
|
@@ -202,18 +202,27 @@ if (args.includes('--version') || args.includes('-v')) {
|
|
|
202
202
|
process.exit(0);
|
|
203
203
|
}
|
|
204
204
|
|
|
205
|
-
// Subcommand dispatch — handle `termdeck init --mnestra|--rumen`
|
|
206
|
-
// falling through to the default launcher's flag parsing. The `require`
|
|
207
|
-
// init-*.js is lazy so users running the normal `termdeck` command never
|
|
208
|
-
// the cost of loading pg / supabase helpers at startup.
|
|
205
|
+
// Subcommand dispatch — handle `termdeck init [--mnestra|--rumen|--project|--auto|--mcp-supabase]`
|
|
206
|
+
// before falling through to the default launcher's flag parsing. The `require`
|
|
207
|
+
// of init-*.js is lazy so users running the normal `termdeck` command never
|
|
208
|
+
// pay the cost of loading pg / supabase helpers at startup.
|
|
209
|
+
//
|
|
210
|
+
// Sprint 64 T1: added the no-subflag default (`termdeck init` with no mode
|
|
211
|
+
// argument or with `--auto` / `--mcp-supabase`) routing to the new
|
|
212
|
+
// `init.js` top-level orchestrator. The orchestrator runs init-mnestra
|
|
213
|
+
// then init-rumen with a unified UX. `--auto` drives MCP-mediated
|
|
214
|
+
// auto-provisioning. Existing modes (--mnestra / --rumen / --project)
|
|
215
|
+
// stay callable independently for advanced users + CI fixtures.
|
|
209
216
|
if (args[0] === 'init') {
|
|
210
217
|
const mode = args[1];
|
|
211
218
|
const rest = args.slice(2);
|
|
212
219
|
|
|
213
|
-
// Sprint 37 T2: refuse mode-mixing. The dispatch picks
|
|
214
|
-
// single mode flag, but a user who writes `init --project foo --mnestra`
|
|
220
|
+
// Sprint 37 T2 + Sprint 64 T1: refuse explicit-mode-mixing. The dispatch picks
|
|
221
|
+
// args[1] as the single mode flag, but a user who writes `init --project foo --mnestra`
|
|
215
222
|
// probably intended only one of those. Surface the conflict instead of
|
|
216
|
-
// silently picking the first.
|
|
223
|
+
// silently picking the first. The `--auto` / `--mcp-supabase` flags are
|
|
224
|
+
// NOT in this list — they're handled by init.js (the orchestrator) and
|
|
225
|
+
// can co-exist with init.js's own flag set.
|
|
217
226
|
const MODES = ['--project', '--mnestra', '--rumen'];
|
|
218
227
|
const presentModes = MODES.filter((m) => args.slice(1).includes(m));
|
|
219
228
|
if (presentModes.length > 1) {
|
|
@@ -221,19 +230,19 @@ if (args[0] === 'init') {
|
|
|
221
230
|
process.exit(1);
|
|
222
231
|
}
|
|
223
232
|
|
|
224
|
-
const run = (modPath) => {
|
|
233
|
+
const run = (modPath, argv) => {
|
|
225
234
|
const fn = require(modPath);
|
|
226
|
-
return fn(
|
|
235
|
+
return fn(argv).then((code) => process.exit(code || 0));
|
|
227
236
|
};
|
|
228
237
|
if (mode === '--mnestra') {
|
|
229
|
-
run(path.join(__dirname, 'init-mnestra.js')).catch((err) => {
|
|
238
|
+
run(path.join(__dirname, 'init-mnestra.js'), rest).catch((err) => {
|
|
230
239
|
console.error('[cli] init --mnestra failed:', err && err.stack || err);
|
|
231
240
|
process.exit(1);
|
|
232
241
|
});
|
|
233
242
|
return;
|
|
234
243
|
}
|
|
235
244
|
if (mode === '--rumen') {
|
|
236
|
-
run(path.join(__dirname, 'init-rumen.js')).catch((err) => {
|
|
245
|
+
run(path.join(__dirname, 'init-rumen.js'), rest).catch((err) => {
|
|
237
246
|
console.error('[cli] init --rumen failed:', err && err.stack || err);
|
|
238
247
|
process.exit(1);
|
|
239
248
|
});
|
|
@@ -242,16 +251,44 @@ if (args[0] === 'init') {
|
|
|
242
251
|
if (mode === '--project') {
|
|
243
252
|
// init-project takes the project name as its first positional arg, plus
|
|
244
253
|
// optional --dry-run / --force flags. Pass `rest` straight through.
|
|
245
|
-
run(path.join(__dirname, 'init-project.js')).catch((err) => {
|
|
254
|
+
run(path.join(__dirname, 'init-project.js'), rest).catch((err) => {
|
|
246
255
|
console.error('[cli] init --project failed:', err && err.stack || err);
|
|
247
256
|
process.exit(1);
|
|
248
257
|
});
|
|
249
258
|
return;
|
|
250
259
|
}
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
260
|
+
|
|
261
|
+
// Sprint 64 T1: default (no mode) OR `--auto` / `--mcp-supabase` → route to
|
|
262
|
+
// the unified orchestrator at init.js. It runs init-mnestra + init-rumen +
|
|
263
|
+
// doctor with a single progress UX. Forward ALL post-`init` args (mode
|
|
264
|
+
// arg included so init.js parses --auto / --mcp-supabase itself).
|
|
265
|
+
const orchestratorArgs = args.slice(1);
|
|
266
|
+
if (mode === undefined || mode === '--auto' || mode === '--mcp-supabase'
|
|
267
|
+
|| (typeof mode === 'string' && mode.startsWith('-') && mode !== '-h')) {
|
|
268
|
+
// The leading-dash check catches things like `--help`, `--reset`,
|
|
269
|
+
// `--from-env`, `--dry-run` etc. — those are init.js orchestrator flags,
|
|
270
|
+
// not unknown sub-modes. Route to init.js with them intact.
|
|
271
|
+
run(path.join(__dirname, 'init.js'), orchestratorArgs).catch((err) => {
|
|
272
|
+
console.error('[cli] init failed:', err && err.stack || err);
|
|
273
|
+
process.exit(1);
|
|
274
|
+
});
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
// `-h` alone after `init` → orchestrator help
|
|
278
|
+
if (mode === '-h') {
|
|
279
|
+
run(path.join(__dirname, 'init.js'), ['--help']).catch((err) => {
|
|
280
|
+
console.error('[cli] init failed:', err && err.stack || err);
|
|
281
|
+
process.exit(1);
|
|
282
|
+
});
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
console.error('Usage: termdeck init [--auto] | --mnestra | --rumen | --project <name>');
|
|
286
|
+
console.error(' termdeck init Unified setup (Mnestra + Rumen + doctor)');
|
|
287
|
+
console.error(' termdeck init --auto Auto-provision via Supabase MCP (alias: --mcp-supabase)');
|
|
288
|
+
console.error(' termdeck init --mnestra Configure Tier 2 memory (Supabase + Mnestra)');
|
|
289
|
+
console.error(' termdeck init --rumen Deploy Tier 3 async learning (Rumen)');
|
|
290
|
+
console.error(' termdeck init --project <name> Scaffold a new project with CLAUDE.md + orchestration docs');
|
|
291
|
+
console.error(' termdeck init --help Show full flag reference');
|
|
255
292
|
process.exit(1);
|
|
256
293
|
}
|
|
257
294
|
|
|
@@ -684,6 +684,12 @@ const SETTINGS_JSON_PATH = path.join(require('os').homedir(), '.claude', 'settin
|
|
|
684
684
|
const HOOK_COMMAND = 'node ~/.claude/hooks/memory-session-end.js';
|
|
685
685
|
const HOOK_TIMEOUT_SECONDS = 30;
|
|
686
686
|
|
|
687
|
+
// Sprint 64 T3 — PreCompact hook (Investigation 2 of CRITICAL-READ-FIRST-
|
|
688
|
+
// 2026-05-07.md). Lives alongside the SessionEnd hook; refreshes via the
|
|
689
|
+
// same Sprint 51.6 T3 version-stamp gate.
|
|
690
|
+
const PRECOMPACT_HOOK_COMMAND = 'node ~/.claude/hooks/memory-pre-compact.js';
|
|
691
|
+
const PRECOMPACT_HOOK_TIMEOUT_SECONDS = 30;
|
|
692
|
+
|
|
687
693
|
function _isSessionEndHookEntry(entry) {
|
|
688
694
|
return entry && typeof entry.command === 'string'
|
|
689
695
|
&& entry.command.includes('memory-session-end.js');
|
|
@@ -739,6 +745,42 @@ function _mergeSessionEndHookEntry(settings, opts = {}) {
|
|
|
739
745
|
return { settings, status: migrated ? 'migrated-from-stop' : 'installed' };
|
|
740
746
|
}
|
|
741
747
|
|
|
748
|
+
// Sprint 64 T3 — PreCompact entry detection + merge. Hoisted mirror of
|
|
749
|
+
// `_isPreCompactHookEntry` / `_mergePreCompactHookEntry` in
|
|
750
|
+
// `packages/stack-installer/src/index.js` (same reasoning as the SessionEnd
|
|
751
|
+
// duplication above: the published @jhizzard/termdeck tarball ships only
|
|
752
|
+
// `packages/stack-installer/assets/hooks/**`, not `.../src/**`).
|
|
753
|
+
function _isPreCompactHookEntry(entry) {
|
|
754
|
+
return entry && typeof entry.command === 'string'
|
|
755
|
+
&& entry.command.includes('memory-pre-compact.js');
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
function _mergePreCompactHookEntry(settings, opts = {}) {
|
|
759
|
+
const command = opts.command || PRECOMPACT_HOOK_COMMAND;
|
|
760
|
+
const timeout = opts.timeout != null ? opts.timeout : PRECOMPACT_HOOK_TIMEOUT_SECONDS;
|
|
761
|
+
const entry = { type: 'command', command, timeout };
|
|
762
|
+
|
|
763
|
+
if (!settings.hooks || typeof settings.hooks !== 'object') settings.hooks = {};
|
|
764
|
+
if (!Array.isArray(settings.hooks.PreCompact)) settings.hooks.PreCompact = [];
|
|
765
|
+
|
|
766
|
+
for (const group of settings.hooks.PreCompact) {
|
|
767
|
+
if (!group || !Array.isArray(group.hooks)) continue;
|
|
768
|
+
if (group.hooks.some(_isPreCompactHookEntry)) {
|
|
769
|
+
return { settings, status: 'already-installed' };
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
const wildcardGroup = settings.hooks.PreCompact.find(
|
|
774
|
+
(g) => g && g.matcher === '*' && Array.isArray(g.hooks)
|
|
775
|
+
);
|
|
776
|
+
if (wildcardGroup) {
|
|
777
|
+
wildcardGroup.hooks.push(entry);
|
|
778
|
+
} else {
|
|
779
|
+
settings.hooks.PreCompact.push({ matcher: '*', hooks: [entry] });
|
|
780
|
+
}
|
|
781
|
+
return { settings, status: 'installed' };
|
|
782
|
+
}
|
|
783
|
+
|
|
742
784
|
function _readSettingsJson(filePath) {
|
|
743
785
|
if (!fs.existsSync(filePath)) {
|
|
744
786
|
return { settings: {}, status: 'no-file' };
|
|
@@ -808,6 +850,43 @@ function migrateSettingsJsonHookEntry(opts = {}) {
|
|
|
808
850
|
return { status: merge.status, settingsPath, backup };
|
|
809
851
|
}
|
|
810
852
|
|
|
853
|
+
// Sprint 64 T3 — settings.json wiring for the PreCompact hook. Parallel to
|
|
854
|
+
// migrateSettingsJsonHookEntry above but simpler (no Stop→event migration
|
|
855
|
+
// branch; PreCompact didn't exist pre-Sprint-64).
|
|
856
|
+
function migrateSettingsJsonPreCompactEntry(opts = {}) {
|
|
857
|
+
const dryRun = !!opts.dryRun;
|
|
858
|
+
const settingsPath = opts.settingsPath || SETTINGS_JSON_PATH;
|
|
859
|
+
|
|
860
|
+
const read = _readSettingsJson(settingsPath);
|
|
861
|
+
if (read.status === 'malformed') {
|
|
862
|
+
return { status: 'malformed', error: read.error, settingsPath };
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
const before = JSON.stringify(read.settings);
|
|
866
|
+
const merge = _mergePreCompactHookEntry(read.settings);
|
|
867
|
+
const after = JSON.stringify(merge.settings);
|
|
868
|
+
const noChange = before === after;
|
|
869
|
+
|
|
870
|
+
if (merge.status === 'already-installed' || noChange) {
|
|
871
|
+
return { status: 'already-installed', settingsPath };
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
if (dryRun) {
|
|
875
|
+
return { status: 'would-' + merge.status, settingsPath };
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
let backup = null;
|
|
879
|
+
if (read.status === 'ok' || read.status === 'empty') {
|
|
880
|
+
const stamp = new Date().toISOString().replace(/[-:T.Z]/g, '').slice(0, 14);
|
|
881
|
+
backup = `${settingsPath}.bak.${stamp}`;
|
|
882
|
+
try { fs.copyFileSync(settingsPath, backup); }
|
|
883
|
+
catch (_) { backup = null; }
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
_writeSettingsJson(settingsPath, merge.settings);
|
|
887
|
+
return { status: merge.status, settingsPath, backup };
|
|
888
|
+
}
|
|
889
|
+
|
|
811
890
|
function runSettingsJsonMigration({ dryRun = false } = {}) {
|
|
812
891
|
const debug = !!process.env.TERMDECK_DEBUG_WIREUP;
|
|
813
892
|
step('Reconciling ~/.claude/settings.json hook event mapping (Stop → SessionEnd)...');
|
|
@@ -841,6 +920,29 @@ function runSettingsJsonMigration({ dryRun = false } = {}) {
|
|
|
841
920
|
process.stdout.write(` ! settings.json migration failed: ${err.message} (continuing)\n`);
|
|
842
921
|
if (debug) process.stderr.write(`[wire-up-debug] runSettingsJsonMigration threw: ${err && err.stack || err}\n`);
|
|
843
922
|
}
|
|
923
|
+
|
|
924
|
+
// Sprint 64 T3 — wire the PreCompact hook into settings.json. Brand new in
|
|
925
|
+
// Sprint 64 (no legacy migration), so the merge either creates the entry or
|
|
926
|
+
// reports already-installed; nothing else to do.
|
|
927
|
+
step('Reconciling ~/.claude/settings.json PreCompact wiring...');
|
|
928
|
+
try {
|
|
929
|
+
const r = migrateSettingsJsonPreCompactEntry({ dryRun });
|
|
930
|
+
if (debug) process.stderr.write(`[wire-up-debug] runSettingsJsonMigration pre-compact return: ${JSON.stringify(r)}\n`);
|
|
931
|
+
if (r.status === 'already-installed') {
|
|
932
|
+
ok('already wired (PreCompact)');
|
|
933
|
+
} else if (r.status === 'installed') {
|
|
934
|
+
ok(r.backup ? `installed (PreCompact; backup: ${path.basename(r.backup)})` : 'installed (PreCompact)');
|
|
935
|
+
} else if (r.status === 'would-installed') {
|
|
936
|
+
ok('would install (PreCompact) (dry-run)');
|
|
937
|
+
} else if (r.status === 'malformed') {
|
|
938
|
+
ok(`(skipped: settings.json malformed: ${r.error})`);
|
|
939
|
+
} else {
|
|
940
|
+
ok(`(${r.status})`);
|
|
941
|
+
}
|
|
942
|
+
} catch (err) {
|
|
943
|
+
process.stdout.write(` ! PreCompact wiring failed: ${err.message} (continuing)\n`);
|
|
944
|
+
if (debug) process.stderr.write(`[wire-up-debug] runSettingsJsonMigration pre-compact threw: ${err && err.stack || err}\n`);
|
|
945
|
+
}
|
|
844
946
|
}
|
|
845
947
|
|
|
846
948
|
function runHookRefresh({ dryRun = false } = {}) {
|
|
@@ -876,6 +978,35 @@ function runHookRefresh({ dryRun = false } = {}) {
|
|
|
876
978
|
process.stdout.write(` ! hook refresh failed: ${err.message} (continuing)\n`);
|
|
877
979
|
if (debug) process.stderr.write(`[wire-up-debug] runHookRefresh threw: ${err && err.stack || err}\n`);
|
|
878
980
|
}
|
|
981
|
+
|
|
982
|
+
// Sprint 64 T3 — also refresh the PreCompact hook. Re-uses the same
|
|
983
|
+
// version-stamp gate (refreshBundledHookIfNewer is parameterized by
|
|
984
|
+
// destPath + sourcePath; both hooks carry the `@termdeck/stack-installer-
|
|
985
|
+
// hook v<N>` marker the gate reads).
|
|
986
|
+
step('Refreshing ~/.claude/hooks/memory-pre-compact.js (if bundled is newer)...');
|
|
987
|
+
try {
|
|
988
|
+
const HOME = require('os').homedir();
|
|
989
|
+
const PRE_DEST = path.join(HOME, '.claude', 'hooks', 'memory-pre-compact.js');
|
|
990
|
+
const PRE_SOURCE = path.join(__dirname, '..', '..', 'stack-installer', 'assets', 'hooks', 'memory-pre-compact.js');
|
|
991
|
+
const r = refreshBundledHookIfNewer({ dryRun, destPath: PRE_DEST, sourcePath: PRE_SOURCE });
|
|
992
|
+
if (debug) process.stderr.write(`[wire-up-debug] runHookRefresh pre-compact return: ${JSON.stringify(r)}\n`);
|
|
993
|
+
if (r.status === 'refreshed') {
|
|
994
|
+
ok(`refreshed v${r.from ?? 0} → v${r.to} (backup: ${path.basename(r.backup)})`);
|
|
995
|
+
} else if (r.status === 'would-refresh') {
|
|
996
|
+
ok(`would-refresh v${r.from ?? 0} → v${r.to} (dry-run)`);
|
|
997
|
+
} else if (r.status === 'installed') {
|
|
998
|
+
ok(`installed v${r.bundled} (no prior copy)`);
|
|
999
|
+
} else if (r.status === 'would-install') {
|
|
1000
|
+
ok(`would-install v${r.bundled} (dry-run, no prior copy)`);
|
|
1001
|
+
} else if (r.status === 'up-to-date') {
|
|
1002
|
+
ok(`up-to-date (v${r.installed})`);
|
|
1003
|
+
} else {
|
|
1004
|
+
ok(`(${r.status}${r.message ? ': ' + r.message : ''})`);
|
|
1005
|
+
}
|
|
1006
|
+
} catch (err) {
|
|
1007
|
+
process.stdout.write(` ! pre-compact hook refresh failed: ${err.message} (continuing)\n`);
|
|
1008
|
+
if (debug) process.stderr.write(`[wire-up-debug] runHookRefresh pre-compact threw: ${err && err.stack || err}\n`);
|
|
1009
|
+
}
|
|
879
1010
|
}
|
|
880
1011
|
|
|
881
1012
|
function printNextSteps() {
|