@jhizzard/termdeck 0.14.0 → 0.15.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
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jhizzard/termdeck",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.15.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"
|
|
@@ -2479,56 +2479,20 @@
|
|
|
2479
2479
|
return;
|
|
2480
2480
|
}
|
|
2481
2481
|
|
|
2482
|
-
// Sprint 45 T4:
|
|
2483
|
-
//
|
|
2484
|
-
//
|
|
2485
|
-
// /
|
|
2486
|
-
// the python-server
|
|
2487
|
-
//
|
|
2488
|
-
//
|
|
2489
|
-
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
|
|
2496
|
-
// Claude `cc` alias normalization. Documented Claude shorthand —
|
|
2497
|
-
// does not generalize to other adapters, so it stays in client UX,
|
|
2498
|
-
// not in the server-side adapter contract.
|
|
2499
|
-
let canonical = command;
|
|
2500
|
-
if (/^cc\b/i.test(canonical)) {
|
|
2501
|
-
canonical = canonical.replace(/^cc\b/i, 'claude');
|
|
2502
|
-
}
|
|
2503
|
-
|
|
2504
|
-
const adapter = (state.agentAdapters || []).find((a) =>
|
|
2505
|
-
a && a.binary && new RegExp(`^${a.binary}\\b`, 'i').test(canonical)
|
|
2506
|
-
);
|
|
2507
|
-
|
|
2508
|
-
if (adapter) {
|
|
2509
|
-
resolvedType = adapter.sessionType;
|
|
2510
|
-
// Claude shorthand: `claude <project-or-cwd>` rewrites to `claude`
|
|
2511
|
-
// and routes the trailing arg into either the project dropdown
|
|
2512
|
-
// (if it's a known project name) or the cwd parameter. Other
|
|
2513
|
-
// adapters' arg-parsing — codex sub-commands, gemini -p flag,
|
|
2514
|
-
// grok --model — pass through unchanged via resolvedCommand.
|
|
2515
|
-
if (adapter.name === 'claude') {
|
|
2516
|
-
const argMatch = canonical.match(/^claude\s+(?:code\s+)?(.+)/i);
|
|
2517
|
-
if (argMatch) {
|
|
2518
|
-
const arg = argMatch[1].trim();
|
|
2519
|
-
if (state.config.projects && state.config.projects[arg]) {
|
|
2520
|
-
resolvedProject = arg;
|
|
2521
|
-
} else {
|
|
2522
|
-
resolvedCwd = arg;
|
|
2523
|
-
}
|
|
2524
|
-
}
|
|
2525
|
-
resolvedCommand = adapter.binary;
|
|
2526
|
-
}
|
|
2527
|
-
} else if (/^python3?\b.*(?:runserver|uvicorn|flask|gunicorn)/i.test(canonical)) {
|
|
2528
|
-
// python-server is a server SUBTYPE for status badges, not an
|
|
2529
|
-
// agent adapter. No registry entry for it; detection stays here.
|
|
2530
|
-
resolvedType = 'python-server';
|
|
2531
|
-
}
|
|
2482
|
+
// Sprint 45 T4 + Sprint 46 T4: resolver extracted to
|
|
2483
|
+
// packages/client/public/launcher-resolver.js so the same routing
|
|
2484
|
+
// logic runs in the browser AND under `node --test` (see
|
|
2485
|
+
// tests/launcher-resolver.test.js for the contract pin). Sprint 46
|
|
2486
|
+
// T4 also extended the python-server preemptive regex to recognize
|
|
2487
|
+
// `http.server` so the python topbar quick-launch button is typed
|
|
2488
|
+
// correctly from the first frame.
|
|
2489
|
+
const { resolvedCommand, resolvedType, resolvedCwd, resolvedProject } =
|
|
2490
|
+
LauncherResolver.resolve(
|
|
2491
|
+
command,
|
|
2492
|
+
project,
|
|
2493
|
+
state.agentAdapters,
|
|
2494
|
+
state.config.projects
|
|
2495
|
+
);
|
|
2532
2496
|
|
|
2533
2497
|
const session = await api('POST', '/api/sessions', {
|
|
2534
2498
|
command: resolvedCommand,
|
|
@@ -4493,18 +4457,28 @@
|
|
|
4493
4457
|
for (const sess of data.sessions) {
|
|
4494
4458
|
const id = sess.sessionId || sess.session_id || 'unknown';
|
|
4495
4459
|
const shortId = id.slice(0, 8);
|
|
4496
|
-
|
|
4460
|
+
// Server (/api/transcripts/recent) returns { sessions: [{ session_id, chunks: [...] }] }
|
|
4461
|
+
// with chunks already grouped per session in DESC created_at order. Type/project
|
|
4462
|
+
// metadata isn't on the transcripts table — fall back to optional fields if any
|
|
4463
|
+
// future server enrichment ships them.
|
|
4464
|
+
const chunks = Array.isArray(sess.chunks) ? sess.chunks : [];
|
|
4465
|
+
const type = sess.type || (chunks.length ? 'session' : 'shell');
|
|
4497
4466
|
const project = sess.project || '';
|
|
4498
|
-
const
|
|
4499
|
-
|
|
4467
|
+
const totalChunks = sess.totalLines || chunks.length;
|
|
4468
|
+
// Build preview from the most-recent chunks. Server returns DESC order, so
|
|
4469
|
+
// the first 6 entries are the newest — reverse for natural top-down reading.
|
|
4470
|
+
const previewChunks = chunks.slice(0, 6).reverse();
|
|
4471
|
+
const previewText = sess.preview
|
|
4472
|
+
? (Array.isArray(sess.preview) ? sess.preview.join('\n') : String(sess.preview))
|
|
4473
|
+
: previewChunks.map(c => (c && typeof c.content === 'string') ? c.content : '').join('');
|
|
4500
4474
|
html += `<div class="transcript-session" data-session-id="${escapeHtml(id)}">
|
|
4501
4475
|
<div class="ts-header">
|
|
4502
4476
|
<span class="ts-id">${escapeHtml(shortId)}</span>
|
|
4503
4477
|
<span class="ts-type">${escapeHtml(type)}</span>
|
|
4504
4478
|
${project ? `<span class="ts-project">${escapeHtml(project)}</span>` : ''}
|
|
4505
|
-
<span class="ts-lines">${
|
|
4479
|
+
<span class="ts-lines">${totalChunks} chunks</span>
|
|
4506
4480
|
</div>
|
|
4507
|
-
<pre class="ts-preview">${escapeHtml(
|
|
4481
|
+
<pre class="ts-preview">${escapeHtml(previewText)}</pre>
|
|
4508
4482
|
</div>`;
|
|
4509
4483
|
}
|
|
4510
4484
|
body.innerHTML = html;
|
|
@@ -4544,7 +4518,11 @@
|
|
|
4544
4518
|
const id = result.sessionId || result.session_id || 'unknown';
|
|
4545
4519
|
const shortId = id.slice(0, 8);
|
|
4546
4520
|
const line = result.line || result.content || '';
|
|
4547
|
-
|
|
4521
|
+
// Server (/api/transcripts/search) sends `created_at`; legacy `timestamp` kept
|
|
4522
|
+
// as a fallback in case a future enrichment swaps the field name.
|
|
4523
|
+
const tsSource = result.timestamp || result.created_at || '';
|
|
4524
|
+
const tsDate = tsSource ? new Date(tsSource) : null;
|
|
4525
|
+
const ts = (tsDate && !isNaN(tsDate.getTime())) ? tsDate.toLocaleTimeString() : '';
|
|
4548
4526
|
html += `<div class="transcript-result" data-session-id="${escapeHtml(id)}">
|
|
4549
4527
|
<div class="tr-meta">
|
|
4550
4528
|
<span class="tr-session">${escapeHtml(shortId)}</span>
|
|
@@ -552,7 +552,7 @@
|
|
|
552
552
|
.attr('stroke-width', 1.2)
|
|
553
553
|
.attr('filter', 'url(#nodeGlow)')
|
|
554
554
|
.style('cursor', 'pointer')
|
|
555
|
-
.on('mouseenter', (event, d) => onNodeHover(d.id))
|
|
555
|
+
.on('mouseenter', (event, d) => { onNodeHover(d.id); showNodeTooltip(event, d); })
|
|
556
556
|
.on('mouseleave', () => onNodeHover(null))
|
|
557
557
|
.on('click', (event, d) => onNodeClick(d))
|
|
558
558
|
.call(window.d3.drag()
|
|
@@ -738,6 +738,28 @@
|
|
|
738
738
|
tip.hidden = false;
|
|
739
739
|
moveTooltip(event);
|
|
740
740
|
}
|
|
741
|
+
|
|
742
|
+
// Sprint 46 T1 — node hover tooltip. Shows project (color-coded) + a short
|
|
743
|
+
// content snippet so the user can scan the graph without having to open the
|
|
744
|
+
// drawer for every node. Click still opens the full detail drawer.
|
|
745
|
+
function showNodeTooltip(event, node) {
|
|
746
|
+
const tip = $('graphTooltip');
|
|
747
|
+
if (!tip || !node) return;
|
|
748
|
+
const proj = node.project || 'global';
|
|
749
|
+
const text = escapeHtml(truncate(node.label || node.snippet || '(no content)', 80));
|
|
750
|
+
const meta = node.source_type ? `<span style="opacity:0.7">${escapeHtml(node.source_type)}</span>` : '';
|
|
751
|
+
tip.innerHTML = `<strong style="color:${hashHue(proj)}">${escapeHtml(proj)}</strong> ${meta} · ${text}`;
|
|
752
|
+
tip.hidden = false;
|
|
753
|
+
moveTooltip(event);
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
function escapeHtml(s) {
|
|
757
|
+
return String(s == null ? '' : s)
|
|
758
|
+
.replace(/&/g, '&')
|
|
759
|
+
.replace(/</g, '<')
|
|
760
|
+
.replace(/>/g, '>')
|
|
761
|
+
.replace(/"/g, '"');
|
|
762
|
+
}
|
|
741
763
|
function moveTooltip(event) {
|
|
742
764
|
const tip = $('graphTooltip');
|
|
743
765
|
if (tip.hidden) return;
|
|
@@ -372,6 +372,7 @@
|
|
|
372
372
|
<script src="https://cdn.jsdelivr.net/npm/@xterm/xterm@5.5.0/lib/xterm.min.js"></script>
|
|
373
373
|
<script src="https://cdn.jsdelivr.net/npm/@xterm/addon-fit@0.10.0/lib/addon-fit.min.js"></script>
|
|
374
374
|
<script src="https://cdn.jsdelivr.net/npm/@xterm/addon-web-links@0.11.0/lib/addon-web-links.min.js"></script>
|
|
375
|
+
<script src="launcher-resolver.js" defer></script>
|
|
375
376
|
<script src="app.js" defer></script>
|
|
376
377
|
</body>
|
|
377
378
|
</html>
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
// TermDeck launcher resolver — extracted Sprint 46 T4
|
|
2
|
+
//
|
|
3
|
+
// Pure function: given (command, project, agentAdapters, projects),
|
|
4
|
+
// returns the resolved spawn parameters the launcher POSTs to /api/sessions.
|
|
5
|
+
// Lives in its own file so the same code runs in the browser (via
|
|
6
|
+
// <script src="launcher-resolver.js">) AND under `node --test` (via
|
|
7
|
+
// `require('.../launcher-resolver')`). Sprint 46 T4 added this extraction
|
|
8
|
+
// to close a zero-coverage gap on the client-side routing logic — see
|
|
9
|
+
// tests/launcher-resolver.test.js for the contract pin.
|
|
10
|
+
//
|
|
11
|
+
// Sprint 45 T4 refactor lives here too: registry-driven shorthand
|
|
12
|
+
// resolution. Pre-Sprint-45 had hardcoded claude/cc/gemini/python branches;
|
|
13
|
+
// now the type detection consults `agentAdapters` (loaded from
|
|
14
|
+
// /api/agent-adapters at init), and only the Claude `cc` alias and the
|
|
15
|
+
// python-server detection (no adapter exists) stay as special-cases.
|
|
16
|
+
// Adapter matching uses an anchored prefix on the adapter's binary name
|
|
17
|
+
// (`^binary\b`, case-insensitive) which fits all four Sprint-45 adapters
|
|
18
|
+
// (claude / codex / gemini / grok) since each binary is uniquely named.
|
|
19
|
+
|
|
20
|
+
(function (root, factory) {
|
|
21
|
+
if (typeof module === 'object' && module.exports) {
|
|
22
|
+
module.exports = factory();
|
|
23
|
+
} else {
|
|
24
|
+
root.LauncherResolver = factory();
|
|
25
|
+
}
|
|
26
|
+
})(typeof self !== 'undefined' ? self : this, function () {
|
|
27
|
+
function resolve(command, project, agentAdapters, projects) {
|
|
28
|
+
let resolvedCommand = command;
|
|
29
|
+
let resolvedType = 'shell';
|
|
30
|
+
let resolvedCwd;
|
|
31
|
+
let resolvedProject = project || undefined;
|
|
32
|
+
|
|
33
|
+
let canonical = command;
|
|
34
|
+
if (/^cc\b/i.test(canonical)) {
|
|
35
|
+
canonical = canonical.replace(/^cc\b/i, 'claude');
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const adapter = (agentAdapters || []).find((a) =>
|
|
39
|
+
a && a.binary && new RegExp(`^${a.binary}\\b`, 'i').test(canonical)
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
if (adapter) {
|
|
43
|
+
resolvedType = adapter.sessionType;
|
|
44
|
+
if (adapter.name === 'claude') {
|
|
45
|
+
const argMatch = canonical.match(/^claude\s+(?:code\s+)?(.+)/i);
|
|
46
|
+
if (argMatch) {
|
|
47
|
+
const arg = argMatch[1].trim();
|
|
48
|
+
if (projects && projects[arg]) {
|
|
49
|
+
resolvedProject = arg;
|
|
50
|
+
} else {
|
|
51
|
+
resolvedCwd = arg;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
resolvedCommand = adapter.binary;
|
|
55
|
+
}
|
|
56
|
+
} else if (/^python3?\b.*(?:runserver|uvicorn|flask|gunicorn|http\.server)/i.test(canonical)) {
|
|
57
|
+
// Sprint 46 T4: extended `http\.server` so the python topbar
|
|
58
|
+
// quick-launch button is preemptively typed correctly. Without
|
|
59
|
+
// this, the badge flickers through `shell` for ~1s before
|
|
60
|
+
// session.js's runtime detection (`/Serving HTTP on/`) catches up.
|
|
61
|
+
resolvedType = 'python-server';
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return { resolvedCommand, resolvedType, resolvedCwd, resolvedProject };
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return { resolve };
|
|
68
|
+
});
|