codedash-app 1.6.0 → 1.9.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/bin/cli.js +50 -0
- package/package.json +1 -1
- package/src/data.js +95 -37
- package/src/frontend/app.js +38 -5
- package/src/frontend/index.html +5 -0
- package/src/frontend/styles.css +2 -7
package/bin/cli.js
CHANGED
|
@@ -57,6 +57,53 @@ switch (command) {
|
|
|
57
57
|
break;
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
+
case 'update':
|
|
61
|
+
case 'upgrade': {
|
|
62
|
+
const { execSync: execU } = require('child_process');
|
|
63
|
+
console.log('\n \x1b[36m\x1b[1mUpdating codedash-app...\x1b[0m\n');
|
|
64
|
+
try {
|
|
65
|
+
execU('npm i -g codedash-app@latest', { stdio: 'inherit' });
|
|
66
|
+
const newPkg = require('../package.json');
|
|
67
|
+
console.log(`\n \x1b[32mUpdated to v${newPkg.version}!\x1b[0m`);
|
|
68
|
+
console.log(' Run \x1b[2mcodedash restart\x1b[0m to apply.\n');
|
|
69
|
+
} catch (e) {
|
|
70
|
+
console.error(' \x1b[31mUpdate failed.\x1b[0m Try: npm i -g codedash-app@latest\n');
|
|
71
|
+
}
|
|
72
|
+
break;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
case 'restart': {
|
|
76
|
+
const { execSync } = require('child_process');
|
|
77
|
+
const portArg = args.find(a => a.startsWith('--port='));
|
|
78
|
+
const port = portArg ? parseInt(portArg.split('=')[1]) : DEFAULT_PORT;
|
|
79
|
+
console.log(`\n Stopping codedash on port ${port}...`);
|
|
80
|
+
try {
|
|
81
|
+
execSync(`lsof -ti:${port} | xargs kill -9 2>/dev/null`, { stdio: 'pipe' });
|
|
82
|
+
console.log(' Stopped.');
|
|
83
|
+
} catch {
|
|
84
|
+
console.log(' No running instance found.');
|
|
85
|
+
}
|
|
86
|
+
setTimeout(() => {
|
|
87
|
+
console.log(' Starting...\n');
|
|
88
|
+
const noBrowser = args.includes('--no-browser');
|
|
89
|
+
startServer(port, !noBrowser);
|
|
90
|
+
}, 500);
|
|
91
|
+
break;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
case 'stop': {
|
|
95
|
+
const { execSync: execS } = require('child_process');
|
|
96
|
+
const pArg = args.find(a => a.startsWith('--port='));
|
|
97
|
+
const p = pArg ? parseInt(pArg.split('=')[1]) : DEFAULT_PORT;
|
|
98
|
+
try {
|
|
99
|
+
execS(`lsof -ti:${p} | xargs kill -9 2>/dev/null`, { stdio: 'pipe' });
|
|
100
|
+
console.log(`\n codedash stopped (port ${p})\n`);
|
|
101
|
+
} catch {
|
|
102
|
+
console.log(`\n No codedash running on port ${p}\n`);
|
|
103
|
+
}
|
|
104
|
+
break;
|
|
105
|
+
}
|
|
106
|
+
|
|
60
107
|
case 'export': {
|
|
61
108
|
const outPath = args[1] || `codedash-export-${new Date().toISOString().slice(0,10)}.tar.gz`;
|
|
62
109
|
exportArchive(outPath);
|
|
@@ -90,6 +137,9 @@ switch (command) {
|
|
|
90
137
|
|
|
91
138
|
\x1b[1mUsage:\x1b[0m
|
|
92
139
|
codedash run [port] [--no-browser] Start the dashboard server
|
|
140
|
+
codedash update Update to latest version
|
|
141
|
+
codedash restart [--port=N] Restart the server
|
|
142
|
+
codedash stop [--port=N] Stop the server
|
|
93
143
|
codedash list [limit] List sessions in terminal
|
|
94
144
|
codedash stats Show session statistics
|
|
95
145
|
codedash export [file.tar.gz] Export all sessions to archive
|
package/package.json
CHANGED
package/src/data.js
CHANGED
|
@@ -214,7 +214,7 @@ function loadSessionDetail(sessionId, project) {
|
|
|
214
214
|
const role = entry.payload.role;
|
|
215
215
|
if (role === 'user' || role === 'assistant') {
|
|
216
216
|
const content = extractContent(entry.payload.content);
|
|
217
|
-
if (content && !
|
|
217
|
+
if (content && !isSystemMessage(content)) {
|
|
218
218
|
messages.push({ role: role, content: content.slice(0, 2000), uuid: '' });
|
|
219
219
|
}
|
|
220
220
|
}
|
|
@@ -370,6 +370,21 @@ function findSessionFile(sessionId, project) {
|
|
|
370
370
|
return null;
|
|
371
371
|
}
|
|
372
372
|
|
|
373
|
+
function isSystemMessage(text) {
|
|
374
|
+
if (!text) return true;
|
|
375
|
+
var t = text.trim();
|
|
376
|
+
if (t === 'exit' || t === 'quit' || t === '/exit') return true;
|
|
377
|
+
if (t.startsWith('<permissions')) return true;
|
|
378
|
+
if (t.startsWith('<environment_context')) return true;
|
|
379
|
+
if (t.startsWith('<collaboration_mode')) return true;
|
|
380
|
+
if (t.startsWith('# AGENTS.md')) return true;
|
|
381
|
+
if (t.startsWith('<INSTRUCTIONS>')) return true;
|
|
382
|
+
// Codex developer role system prompts
|
|
383
|
+
if (t.startsWith('You are Codex')) return true;
|
|
384
|
+
if (t.startsWith('Filesystem sandboxing')) return true;
|
|
385
|
+
return false;
|
|
386
|
+
}
|
|
387
|
+
|
|
373
388
|
function extractContent(raw) {
|
|
374
389
|
if (!raw) return '';
|
|
375
390
|
if (typeof raw === 'string') return raw;
|
|
@@ -410,7 +425,7 @@ function getSessionPreview(sessionId, project, limit) {
|
|
|
410
425
|
if (role === 'user' || role === 'assistant') {
|
|
411
426
|
const content = extractContent(entry.payload.content);
|
|
412
427
|
// Skip system-like messages
|
|
413
|
-
if (content && !
|
|
428
|
+
if (content && !isSystemMessage(content)) {
|
|
414
429
|
messages.push({ role: role, content: content.slice(0, 300) });
|
|
415
430
|
}
|
|
416
431
|
}
|
|
@@ -422,60 +437,103 @@ function getSessionPreview(sessionId, project, limit) {
|
|
|
422
437
|
return messages;
|
|
423
438
|
}
|
|
424
439
|
|
|
425
|
-
// ── Full-text search
|
|
440
|
+
// ── Full-text search index ─────────────────────────────────
|
|
441
|
+
//
|
|
442
|
+
// Built once on first search, then cached in memory.
|
|
443
|
+
// Each entry: { sessionId, texts: [{role, content}] }
|
|
444
|
+
// Total text is kept lowercase for fast substring matching.
|
|
426
445
|
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
446
|
+
let searchIndex = null;
|
|
447
|
+
let searchIndexBuiltAt = 0;
|
|
448
|
+
const INDEX_TTL = 60000; // rebuild every 60s
|
|
449
|
+
|
|
450
|
+
function buildSearchIndex(sessions) {
|
|
451
|
+
const startMs = Date.now();
|
|
452
|
+
const index = [];
|
|
431
453
|
|
|
432
454
|
for (const s of sessions) {
|
|
433
|
-
if (
|
|
455
|
+
if (!s.has_detail) continue;
|
|
434
456
|
|
|
435
|
-
const
|
|
436
|
-
|
|
437
|
-
if (!fs.existsSync(sessionFile)) continue;
|
|
457
|
+
const found = findSessionFile(s.id, s.project);
|
|
458
|
+
if (!found) continue;
|
|
438
459
|
|
|
439
460
|
try {
|
|
440
|
-
const
|
|
441
|
-
|
|
442
|
-
if (data.toLowerCase().indexOf(q) === -1) continue;
|
|
461
|
+
const lines = fs.readFileSync(found.file, 'utf8').split('\n').filter(Boolean);
|
|
462
|
+
const texts = [];
|
|
443
463
|
|
|
444
|
-
// Find matching messages
|
|
445
|
-
const lines = data.split('\n').filter(Boolean);
|
|
446
|
-
const matches = [];
|
|
447
464
|
for (const line of lines) {
|
|
448
|
-
if (matches.length >= 3) break; // max 3 matches per session
|
|
449
465
|
try {
|
|
450
466
|
const entry = JSON.parse(line);
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
467
|
+
let role, content;
|
|
468
|
+
|
|
469
|
+
if (found.format === 'claude') {
|
|
470
|
+
if (entry.type !== 'user' && entry.type !== 'assistant') continue;
|
|
471
|
+
role = entry.type;
|
|
472
|
+
content = extractContent((entry.message || {}).content);
|
|
473
|
+
} else {
|
|
474
|
+
if (entry.type !== 'response_item' || !entry.payload) continue;
|
|
475
|
+
role = entry.payload.role;
|
|
476
|
+
if (role !== 'user' && role !== 'assistant') continue;
|
|
477
|
+
content = extractContent(entry.payload.content);
|
|
459
478
|
}
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
const start = Math.max(0, idx - 50);
|
|
464
|
-
const end = Math.min(content.length, idx + q.length + 50);
|
|
465
|
-
matches.push({
|
|
466
|
-
role: entry.type,
|
|
467
|
-
snippet: (start > 0 ? '...' : '') + content.slice(start, end) + (end < content.length ? '...' : ''),
|
|
468
|
-
});
|
|
479
|
+
|
|
480
|
+
if (content && !isSystemMessage(content)) {
|
|
481
|
+
texts.push({ role, content: content.slice(0, 500) });
|
|
469
482
|
}
|
|
470
483
|
} catch {}
|
|
471
484
|
}
|
|
472
485
|
|
|
473
|
-
if (
|
|
474
|
-
|
|
486
|
+
if (texts.length > 0) {
|
|
487
|
+
// Pre-compute lowercase full text for fast matching
|
|
488
|
+
const fullText = texts.map(t => t.content).join(' ').toLowerCase();
|
|
489
|
+
index.push({ sessionId: s.id, texts, fullText });
|
|
475
490
|
}
|
|
476
491
|
} catch {}
|
|
477
492
|
}
|
|
478
493
|
|
|
494
|
+
const elapsed = Date.now() - startMs;
|
|
495
|
+
console.log(` \x1b[2mSearch index: ${index.length} sessions, ${elapsed}ms\x1b[0m`);
|
|
496
|
+
return index;
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
function getSearchIndex(sessions) {
|
|
500
|
+
const now = Date.now();
|
|
501
|
+
if (!searchIndex || (now - searchIndexBuiltAt) > INDEX_TTL) {
|
|
502
|
+
searchIndex = buildSearchIndex(sessions);
|
|
503
|
+
searchIndexBuiltAt = now;
|
|
504
|
+
}
|
|
505
|
+
return searchIndex;
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
function searchFullText(query, sessions) {
|
|
509
|
+
if (!query || query.length < 2) return [];
|
|
510
|
+
const q = query.toLowerCase();
|
|
511
|
+
const index = getSearchIndex(sessions);
|
|
512
|
+
const results = [];
|
|
513
|
+
|
|
514
|
+
for (const entry of index) {
|
|
515
|
+
if (entry.fullText.indexOf(q) === -1) continue;
|
|
516
|
+
|
|
517
|
+
// Find matching messages with snippets
|
|
518
|
+
const matches = [];
|
|
519
|
+
for (const t of entry.texts) {
|
|
520
|
+
if (matches.length >= 3) break;
|
|
521
|
+
const idx = t.content.toLowerCase().indexOf(q);
|
|
522
|
+
if (idx >= 0) {
|
|
523
|
+
const start = Math.max(0, idx - 50);
|
|
524
|
+
const end = Math.min(t.content.length, idx + q.length + 50);
|
|
525
|
+
matches.push({
|
|
526
|
+
role: t.role,
|
|
527
|
+
snippet: (start > 0 ? '...' : '') + t.content.slice(start, end) + (end < t.content.length ? '...' : ''),
|
|
528
|
+
});
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
if (matches.length > 0) {
|
|
533
|
+
results.push({ sessionId: entry.sessionId, matches });
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
|
|
479
537
|
return results;
|
|
480
538
|
}
|
|
481
539
|
|
package/src/frontend/app.js
CHANGED
|
@@ -104,10 +104,20 @@ function showTagDropdown(event, sessionId) {
|
|
|
104
104
|
document.querySelectorAll('.tag-dropdown').forEach(function(el) { el.remove(); });
|
|
105
105
|
var dd = document.createElement('div');
|
|
106
106
|
dd.className = 'tag-dropdown';
|
|
107
|
+
var existingTags = tags[sessionId] || [];
|
|
107
108
|
dd.innerHTML = TAG_OPTIONS.map(function(t) {
|
|
108
|
-
|
|
109
|
+
var has = existingTags.indexOf(t) >= 0;
|
|
110
|
+
return '<div class="tag-dropdown-item" onclick="event.stopPropagation();' +
|
|
111
|
+
(has ? 'removeTag' : 'addTag') + '(\'' + sessionId + '\',\'' + t + '\')">' +
|
|
112
|
+
(has ? '✓ ' : '') + t + '</div>';
|
|
109
113
|
}).join('');
|
|
110
|
-
|
|
114
|
+
|
|
115
|
+
// Position near the button
|
|
116
|
+
var rect = event.target.getBoundingClientRect();
|
|
117
|
+
dd.style.top = (rect.bottom + 4) + 'px';
|
|
118
|
+
dd.style.left = rect.left + 'px';
|
|
119
|
+
|
|
120
|
+
document.body.appendChild(dd);
|
|
111
121
|
setTimeout(function() {
|
|
112
122
|
document.addEventListener('click', function() { dd.remove(); }, { once: true });
|
|
113
123
|
}, 0);
|
|
@@ -1338,6 +1348,30 @@ document.addEventListener('keydown', function(e) {
|
|
|
1338
1348
|
}
|
|
1339
1349
|
});
|
|
1340
1350
|
|
|
1351
|
+
// ── Export/Import dialog ──────────────────────────────────────
|
|
1352
|
+
|
|
1353
|
+
function showExportDialog() {
|
|
1354
|
+
var overlay = document.getElementById('confirmOverlay');
|
|
1355
|
+
document.getElementById('confirmTitle').textContent = 'Export / Import Sessions';
|
|
1356
|
+
document.getElementById('confirmText').innerHTML =
|
|
1357
|
+
'<strong>Export</strong> all sessions to migrate to another PC:<br>' +
|
|
1358
|
+
'<code style="display:block;margin:8px 0;padding:8px;background:var(--bg-card);border-radius:6px;font-size:12px">codedash export</code>' +
|
|
1359
|
+
'Creates a tar.gz with all Claude & Codex session data.<br><br>' +
|
|
1360
|
+
'<strong>Import</strong> on the new machine:<br>' +
|
|
1361
|
+
'<code style="display:block;margin:8px 0;padding:8px;background:var(--bg-card);border-radius:6px;font-size:12px">codedash import <file.tar.gz></code>' +
|
|
1362
|
+
'<br><em style="color:var(--text-muted);font-size:12px">Don\'t forget to clone your git repos separately.</em>';
|
|
1363
|
+
document.getElementById('confirmId').textContent = '';
|
|
1364
|
+
document.getElementById('confirmAction').textContent = 'Copy Export Command';
|
|
1365
|
+
document.getElementById('confirmAction').className = 'launch-btn btn-primary';
|
|
1366
|
+
document.getElementById('confirmAction').onclick = function() {
|
|
1367
|
+
navigator.clipboard.writeText('codedash export').then(function() {
|
|
1368
|
+
showToast('Copied: codedash export');
|
|
1369
|
+
});
|
|
1370
|
+
closeConfirm();
|
|
1371
|
+
};
|
|
1372
|
+
if (overlay) overlay.style.display = 'flex';
|
|
1373
|
+
}
|
|
1374
|
+
|
|
1341
1375
|
// ── Update check ──────────────────────────────────────────────
|
|
1342
1376
|
|
|
1343
1377
|
async function checkForUpdates() {
|
|
@@ -1373,10 +1407,9 @@ async function checkForUpdates() {
|
|
|
1373
1407
|
}
|
|
1374
1408
|
|
|
1375
1409
|
function copyUpdate() {
|
|
1376
|
-
var
|
|
1377
|
-
var cmd = banner ? banner.dataset.cmd : 'npm update -g codedash-app';
|
|
1410
|
+
var cmd = 'codedash update && codedash restart';
|
|
1378
1411
|
navigator.clipboard.writeText(cmd).then(function() {
|
|
1379
|
-
showToast('Copied: ' + cmd);
|
|
1412
|
+
showToast('Copied: ' + cmd + ' (run in terminal)');
|
|
1380
1413
|
});
|
|
1381
1414
|
}
|
|
1382
1415
|
|
package/src/frontend/index.html
CHANGED
|
@@ -44,6 +44,11 @@
|
|
|
44
44
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="16 18 22 12 16 6"/><polyline points="8 6 2 12 8 18"/></svg>
|
|
45
45
|
Codex
|
|
46
46
|
</div>
|
|
47
|
+
<div class="sidebar-divider"></div>
|
|
48
|
+
<div class="sidebar-item" onclick="showExportDialog()">
|
|
49
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4"/><polyline points="17 8 12 3 7 8"/><line x1="12" y1="3" x2="12" y2="15"/></svg>
|
|
50
|
+
Export / Import
|
|
51
|
+
</div>
|
|
47
52
|
<div class="sidebar-settings">
|
|
48
53
|
<label>Terminal</label>
|
|
49
54
|
<select id="terminalSelect" onchange="saveTerminalPref(this.value)">
|
package/src/frontend/styles.css
CHANGED
|
@@ -484,20 +484,15 @@ body {
|
|
|
484
484
|
}
|
|
485
485
|
|
|
486
486
|
.tag-dropdown {
|
|
487
|
-
position:
|
|
488
|
-
top: 100%;
|
|
489
|
-
left: 0;
|
|
490
|
-
margin-top: 4px;
|
|
487
|
+
position: fixed;
|
|
491
488
|
background: var(--bg-secondary);
|
|
492
489
|
border: 1px solid var(--border);
|
|
493
490
|
border-radius: 8px;
|
|
494
491
|
padding: 6px;
|
|
495
492
|
min-width: 140px;
|
|
496
493
|
box-shadow: 0 8px 24px rgba(0,0,0,0.3);
|
|
497
|
-
z-index:
|
|
498
|
-
display: none;
|
|
494
|
+
z-index: 150;
|
|
499
495
|
}
|
|
500
|
-
.tag-dropdown.open { display: block; }
|
|
501
496
|
|
|
502
497
|
.tag-dropdown-item {
|
|
503
498
|
display: flex;
|