@jhizzard/termdeck 0.3.1 → 0.3.2
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.
|
|
3
|
+
"version": "0.3.2",
|
|
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"
|
|
@@ -2403,29 +2403,43 @@
|
|
|
2403
2403
|
document.getElementById('healthBadgeLabel').textContent = 'Health: offline';
|
|
2404
2404
|
}
|
|
2405
2405
|
|
|
2406
|
+
// Tier 2/3 checks only shown when the user has configured those tiers.
|
|
2407
|
+
// Without DATABASE_URL, mnestra/rumen/database checks are irrelevant noise.
|
|
2408
|
+
const TIER1_CHECKS = new Set(['project_paths', 'shell_sanity']);
|
|
2409
|
+
const TIER23_CHECKS = new Set(['mnestra_reachable', 'mnestra_has_memories', 'rumen_recent', 'database_url']);
|
|
2410
|
+
|
|
2411
|
+
function filterChecksByTier(checks) {
|
|
2412
|
+
const hasDb = checks.some(c => c.name === 'database_url' && c.passed);
|
|
2413
|
+
if (hasDb) return checks; // full stack configured — show everything
|
|
2414
|
+
// No DATABASE_URL: only show Tier 1 checks
|
|
2415
|
+
return checks.filter(c => TIER1_CHECKS.has(c.name));
|
|
2416
|
+
}
|
|
2417
|
+
|
|
2406
2418
|
function renderHealthBadge(data) {
|
|
2407
2419
|
const badge = document.getElementById('healthBadge');
|
|
2408
2420
|
if (!badge) return;
|
|
2409
2421
|
badge.style.display = '';
|
|
2410
2422
|
|
|
2411
|
-
const
|
|
2423
|
+
const allChecks = data.checks || [];
|
|
2424
|
+
const checks = filterChecksByTier(allChecks);
|
|
2412
2425
|
const total = checks.length;
|
|
2413
|
-
const passed = checks.filter(c => c.
|
|
2426
|
+
const passed = checks.filter(c => c.passed).length;
|
|
2414
2427
|
const allOk = passed === total && total > 0;
|
|
2428
|
+
const tierLabel = total < allChecks.length ? 'Tier 1' : 'Stack';
|
|
2415
2429
|
|
|
2416
2430
|
if (allOk) {
|
|
2417
2431
|
badge.className = 'health-badge hb-green';
|
|
2418
|
-
document.getElementById('healthBadgeLabel').textContent =
|
|
2432
|
+
document.getElementById('healthBadgeLabel').textContent = `${tierLabel}: OK`;
|
|
2419
2433
|
} else if (total === 0) {
|
|
2420
2434
|
badge.className = 'health-badge hb-amber';
|
|
2421
2435
|
document.getElementById('healthBadgeLabel').textContent = 'Stack: ?';
|
|
2422
2436
|
} else {
|
|
2423
2437
|
badge.className = 'health-badge hb-red';
|
|
2424
|
-
document.getElementById('healthBadgeLabel').textContent =
|
|
2438
|
+
document.getElementById('healthBadgeLabel').textContent = `${tierLabel}: ${passed}/${total}`;
|
|
2425
2439
|
}
|
|
2426
2440
|
|
|
2427
|
-
// Update dropdown content
|
|
2428
|
-
renderHealthDropdown(data);
|
|
2441
|
+
// Update dropdown content — pass filtered checks
|
|
2442
|
+
renderHealthDropdown({ ...data, checks });
|
|
2429
2443
|
}
|
|
2430
2444
|
|
|
2431
2445
|
function renderHealthDropdown(data) {
|
|
@@ -2439,16 +2453,16 @@
|
|
|
2439
2453
|
|
|
2440
2454
|
let html = '';
|
|
2441
2455
|
for (const check of checks) {
|
|
2442
|
-
const icon = check.
|
|
2443
|
-
const cls = check.
|
|
2456
|
+
const icon = check.passed ? '✓' : '✗';
|
|
2457
|
+
const cls = check.passed ? 'hd-ok' : 'hd-fail';
|
|
2444
2458
|
const name = check.name || 'Unknown';
|
|
2445
2459
|
const detail = check.detail || '';
|
|
2446
|
-
const remediation = check.
|
|
2460
|
+
const remediation = check.passed ? '' : (check.remediation ? `<div class="hd-remediation">${escapeHtml(check.remediation)}</div>` : '');
|
|
2447
2461
|
html += `<div class="hd-check ${cls}">
|
|
2448
2462
|
<span class="hd-icon">${icon}</span>
|
|
2449
2463
|
<span class="hd-name">${escapeHtml(name)}</span>
|
|
2450
2464
|
<span class="hd-dots"></span>
|
|
2451
|
-
<span class="hd-status">${check.
|
|
2465
|
+
<span class="hd-status">${check.passed ? 'OK' : 'FAIL'}</span>
|
|
2452
2466
|
<span class="hd-detail">${escapeHtml(detail)}</span>
|
|
2453
2467
|
${remediation}
|
|
2454
2468
|
</div>`;
|
|
@@ -484,14 +484,14 @@ function createServer(config) {
|
|
|
484
484
|
// GET /api/transcripts/search - FTS across all sessions
|
|
485
485
|
// (Must be registered before :sessionId to avoid route collision)
|
|
486
486
|
app.get('/api/transcripts/search', async (req, res) => {
|
|
487
|
-
if (!transcriptWriter) return res.json([]);
|
|
487
|
+
if (!transcriptWriter) return res.json({ results: [] });
|
|
488
488
|
const q = req.query.q;
|
|
489
489
|
if (!q) return res.status(400).json({ error: 'Missing q parameter' });
|
|
490
490
|
const since = req.query.since || null;
|
|
491
491
|
const limit = Math.min(Math.max(parseInt(req.query.limit) || 50, 1), 200);
|
|
492
492
|
try {
|
|
493
493
|
const results = await transcriptWriter.search(q, { since, limit });
|
|
494
|
-
res.json(results);
|
|
494
|
+
res.json({ results });
|
|
495
495
|
} catch (err) {
|
|
496
496
|
console.error('[transcript] search endpoint error:', err.message);
|
|
497
497
|
res.status(500).json({ error: 'Transcript search failed' });
|
|
@@ -499,13 +499,24 @@ function createServer(config) {
|
|
|
499
499
|
});
|
|
500
500
|
|
|
501
501
|
// GET /api/transcripts/recent - time-windowed crash recovery
|
|
502
|
+
// Returns { sessions: [ { session_id, chunks: [...] }, ... ] }
|
|
502
503
|
app.get('/api/transcripts/recent', async (req, res) => {
|
|
503
|
-
if (!transcriptWriter) return res.json([]);
|
|
504
|
+
if (!transcriptWriter) return res.json({ sessions: [] });
|
|
504
505
|
const minutes = Math.min(Math.max(parseInt(req.query.minutes) || 60, 1), 1440);
|
|
505
506
|
const limit = Math.min(Math.max(parseInt(req.query.limit) || 500, 1), 2000);
|
|
506
507
|
try {
|
|
507
|
-
const
|
|
508
|
-
|
|
508
|
+
const rows = await transcriptWriter.getRecent(minutes, limit);
|
|
509
|
+
// Group by session_id for client consumption
|
|
510
|
+
const grouped = new Map();
|
|
511
|
+
for (const row of rows) {
|
|
512
|
+
if (!grouped.has(row.session_id)) grouped.set(row.session_id, []);
|
|
513
|
+
grouped.get(row.session_id).push(row);
|
|
514
|
+
}
|
|
515
|
+
const sessions = [];
|
|
516
|
+
for (const [session_id, chunks] of grouped) {
|
|
517
|
+
sessions.push({ session_id, chunks });
|
|
518
|
+
}
|
|
519
|
+
res.json({ sessions });
|
|
509
520
|
} catch (err) {
|
|
510
521
|
console.error('[transcript] recent endpoint error:', err.message);
|
|
511
522
|
res.status(500).json({ error: 'Transcript recent query failed' });
|
|
@@ -513,13 +524,16 @@ function createServer(config) {
|
|
|
513
524
|
});
|
|
514
525
|
|
|
515
526
|
// GET /api/transcripts/:sessionId - ordered chunks for a session
|
|
527
|
+
// Returns { content: string } (joined transcript text)
|
|
516
528
|
app.get('/api/transcripts/:sessionId', async (req, res) => {
|
|
517
|
-
if (!transcriptWriter) return res.json([]);
|
|
529
|
+
if (!transcriptWriter) return res.json({ content: '', lines: [] });
|
|
518
530
|
const limit = req.query.limit ? Math.min(Math.max(parseInt(req.query.limit), 1), 5000) : undefined;
|
|
519
531
|
const since = req.query.since || undefined;
|
|
520
532
|
try {
|
|
521
533
|
const chunks = await transcriptWriter.getSessionTranscript(req.params.sessionId, { limit, since });
|
|
522
|
-
|
|
534
|
+
const lines = chunks.map(c => c.content);
|
|
535
|
+
const content = lines.join('');
|
|
536
|
+
res.json({ content, lines, chunks });
|
|
523
537
|
} catch (err) {
|
|
524
538
|
console.error('[transcript] session transcript endpoint error:', err.message);
|
|
525
539
|
res.status(500).json({ error: 'Transcript retrieval failed' });
|
|
@@ -165,8 +165,8 @@ class RAGIntegration {
|
|
|
165
165
|
// Success — reset any accumulated 404 count for this table
|
|
166
166
|
this._resetCircuit(table);
|
|
167
167
|
} catch (err) {
|
|
168
|
-
// Will be retried by sync loop
|
|
169
168
|
console.error('[mnestra] Push failed:', err.message);
|
|
169
|
+
throw err; // Propagate to caller so sync loop knows this event failed
|
|
170
170
|
}
|
|
171
171
|
}
|
|
172
172
|
|
|
@@ -28,6 +28,7 @@ class TranscriptWriter {
|
|
|
28
28
|
this._databaseUrl = databaseUrl;
|
|
29
29
|
this._batchSize = options.batchSize || 50;
|
|
30
30
|
this._flushIntervalMs = options.flushIntervalMs || 2000;
|
|
31
|
+
this._maxBufferSize = options.maxBufferSize || 10000;
|
|
31
32
|
this._enabled = options.enabled !== false;
|
|
32
33
|
|
|
33
34
|
// Per-session monotonic chunk counters
|
|
@@ -91,6 +92,11 @@ class TranscriptWriter {
|
|
|
91
92
|
const idx = this._counters.get(sessionId) || 0;
|
|
92
93
|
this._counters.set(sessionId, idx + 1);
|
|
93
94
|
|
|
95
|
+
// Cap buffer to prevent unbounded growth during sustained DB failures
|
|
96
|
+
if (this._buffer.length >= this._maxBufferSize) {
|
|
97
|
+
this._buffer.splice(0, this._buffer.length - this._maxBufferSize + 1);
|
|
98
|
+
}
|
|
99
|
+
|
|
94
100
|
this._buffer.push({
|
|
95
101
|
sessionId,
|
|
96
102
|
content: stripped,
|