codedash-app 1.2.0 → 1.3.1
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/README.md +30 -26
- package/package.json +1 -1
- package/src/frontend/app.js +96 -37
package/README.md
CHANGED
|
@@ -1,60 +1,64 @@
|
|
|
1
|
-
#
|
|
1
|
+
# CodeDash
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Browser dashboard for Claude Code & Codex sessions. View, search, resume, and manage all your AI coding sessions.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
[Russian / Русский](docs/README_RU.md) | [Chinese / 中文](docs/README_ZH.md)
|
|
6
|
+
|
|
7
|
+
https://github.com/user-attachments/assets/15c45659-365b-49f8-86a3-9005fa155ca6
|
|
8
|
+
|
|
9
|
+
  
|
|
6
10
|
|
|
7
11
|
## Quick Start
|
|
8
12
|
|
|
9
13
|
```bash
|
|
10
|
-
npx
|
|
14
|
+
npx codedash-app run
|
|
11
15
|
```
|
|
12
16
|
|
|
13
|
-
Opens `http://localhost:3847`
|
|
14
|
-
|
|
15
|
-
Custom port:
|
|
17
|
+
Opens `http://localhost:3847` in your browser.
|
|
16
18
|
|
|
17
19
|
```bash
|
|
18
|
-
npx
|
|
20
|
+
npx codedash-app run --port=4000 # custom port
|
|
21
|
+
npx codedash-app run --no-browser # don't auto-open
|
|
22
|
+
npx codedash-app list # list sessions in terminal
|
|
23
|
+
npx codedash-app stats # show statistics
|
|
19
24
|
```
|
|
20
25
|
|
|
21
26
|
## Features
|
|
22
27
|
|
|
23
28
|
**Sessions**
|
|
24
|
-
-
|
|
25
|
-
-
|
|
26
|
-
-
|
|
27
|
-
-
|
|
29
|
+
- Grid and List view with project grouping
|
|
30
|
+
- Trigram fuzzy search across session content and projects
|
|
31
|
+
- Filter by tool (Claude/Codex), tags, date range
|
|
32
|
+
- Star/pin important sessions (always shown first)
|
|
33
|
+
- Tag sessions: bug, feature, research, infra, deploy, review
|
|
34
|
+
- Activity heatmap (GitHub-style)
|
|
35
|
+
- Cost estimation per session
|
|
28
36
|
|
|
29
37
|
**Launch**
|
|
30
|
-
- Resume
|
|
31
|
-
- One-click launch with `--dangerously-skip-permissions` option
|
|
38
|
+
- Resume sessions in iTerm2, Terminal.app, Warp, Kitty, Alacritty
|
|
32
39
|
- Auto `cd` into the correct project directory
|
|
33
40
|
- Copy resume command to clipboard
|
|
34
41
|
- Terminal preference saved between sessions
|
|
35
42
|
|
|
36
43
|
**Manage**
|
|
37
44
|
- Delete sessions (file + history + env cleanup)
|
|
38
|
-
-
|
|
39
|
-
-
|
|
45
|
+
- Bulk select and delete
|
|
46
|
+
- Export conversations as Markdown
|
|
47
|
+
- Related git commits shown per session
|
|
48
|
+
- Auto-update notifications
|
|
40
49
|
|
|
41
|
-
**
|
|
42
|
-
- `/` — Focus search
|
|
43
|
-
- `Escape` — Close panels
|
|
50
|
+
**Themes**: Dark (default), Light, System
|
|
44
51
|
|
|
45
|
-
|
|
52
|
+
**Keyboard Shortcuts**: `/` search, `j/k` navigate, `Enter` open, `x` star, `d` delete, `s` select, `g` group, `r` refresh, `Esc` close
|
|
46
53
|
|
|
47
|
-
|
|
48
|
-
- `history.jsonl` — session index with timestamps and projects
|
|
49
|
-
- `projects/*/\<session-id\>.jsonl` — full conversation data
|
|
50
|
-
- `session-env/` — session environment files
|
|
54
|
+
## How It Works
|
|
51
55
|
|
|
52
|
-
|
|
56
|
+
Reads session data from `~/.claude/` and `~/.codex/`. Zero dependencies. Everything runs on `localhost`.
|
|
53
57
|
|
|
54
58
|
## Requirements
|
|
55
59
|
|
|
56
60
|
- Node.js >= 16
|
|
57
|
-
- Claude Code
|
|
61
|
+
- Claude Code or Codex CLI
|
|
58
62
|
- macOS / Linux / Windows
|
|
59
63
|
|
|
60
64
|
## License
|
package/package.json
CHANGED
package/src/frontend/app.js
CHANGED
|
@@ -186,48 +186,103 @@ function saveTerminalPref(val) {
|
|
|
186
186
|
localStorage.setItem('codedash-terminal', val);
|
|
187
187
|
}
|
|
188
188
|
|
|
189
|
+
// ── Trigram search ─────────────────────────────────────────────
|
|
190
|
+
|
|
191
|
+
function trigrams(str) {
|
|
192
|
+
var s = ' ' + str.toLowerCase() + ' ';
|
|
193
|
+
var t = {};
|
|
194
|
+
for (var i = 0; i < s.length - 2; i++) {
|
|
195
|
+
var tri = s.substring(i, i + 3);
|
|
196
|
+
t[tri] = (t[tri] || 0) + 1;
|
|
197
|
+
}
|
|
198
|
+
return t;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function trigramScore(query, text) {
|
|
202
|
+
if (!query || !text) return 0;
|
|
203
|
+
var qt = trigrams(query);
|
|
204
|
+
var tt = trigrams(text);
|
|
205
|
+
var matches = 0;
|
|
206
|
+
var total = 0;
|
|
207
|
+
for (var k in qt) {
|
|
208
|
+
total += qt[k];
|
|
209
|
+
if (tt[k]) matches += Math.min(qt[k], tt[k]);
|
|
210
|
+
}
|
|
211
|
+
return total > 0 ? matches / total : 0;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function searchScore(query, session) {
|
|
215
|
+
var q = query.toLowerCase();
|
|
216
|
+
var fields = [
|
|
217
|
+
session.first_message || '',
|
|
218
|
+
session.project_short || '',
|
|
219
|
+
session.project || '',
|
|
220
|
+
session.id || '',
|
|
221
|
+
session.tool || ''
|
|
222
|
+
];
|
|
223
|
+
var haystack = fields.join(' ').toLowerCase();
|
|
224
|
+
|
|
225
|
+
// Exact substring match = highest score
|
|
226
|
+
if (haystack.indexOf(q) >= 0) return 1;
|
|
227
|
+
|
|
228
|
+
// Trigram fuzzy match
|
|
229
|
+
var best = 0;
|
|
230
|
+
for (var i = 0; i < fields.length; i++) {
|
|
231
|
+
var score = trigramScore(q, fields[i]);
|
|
232
|
+
if (score > best) best = score;
|
|
233
|
+
}
|
|
234
|
+
// Also score against full haystack
|
|
235
|
+
var fullScore = trigramScore(q, haystack);
|
|
236
|
+
if (fullScore > best) best = fullScore;
|
|
237
|
+
|
|
238
|
+
return best;
|
|
239
|
+
}
|
|
240
|
+
|
|
189
241
|
// ── Filtering ──────────────────────────────────────────────────
|
|
190
242
|
|
|
243
|
+
var SEARCH_THRESHOLD = 0.3;
|
|
244
|
+
|
|
191
245
|
function applyFilters() {
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
246
|
+
var scored = [];
|
|
247
|
+
for (var i = 0; i < allSessions.length; i++) {
|
|
248
|
+
var s = allSessions[i];
|
|
195
249
|
|
|
196
|
-
//
|
|
197
|
-
if (
|
|
198
|
-
var q = searchQuery.toLowerCase();
|
|
199
|
-
var haystack = (
|
|
200
|
-
(s.first_message || '') + ' ' +
|
|
201
|
-
(s.project || '') + ' ' +
|
|
202
|
-
(s.project_short || '') + ' ' +
|
|
203
|
-
(s.id || '') + ' ' +
|
|
204
|
-
(s.tool || '')
|
|
205
|
-
).toLowerCase();
|
|
206
|
-
if (haystack.indexOf(q) === -1) return false;
|
|
207
|
-
}
|
|
250
|
+
// Tool filter
|
|
251
|
+
if (toolFilter && s.tool !== toolFilter) continue;
|
|
208
252
|
|
|
209
253
|
// Tag filter
|
|
210
254
|
if (tagFilter) {
|
|
211
255
|
var sessionTags = tags[s.id] || [];
|
|
212
|
-
if (sessionTags.indexOf(tagFilter) === -1)
|
|
256
|
+
if (sessionTags.indexOf(tagFilter) === -1) continue;
|
|
213
257
|
}
|
|
214
258
|
|
|
215
259
|
// Date range
|
|
216
|
-
if (dateFrom && s.date < dateFrom)
|
|
217
|
-
if (dateTo && s.date > dateTo)
|
|
260
|
+
if (dateFrom && s.date < dateFrom) continue;
|
|
261
|
+
if (dateTo && s.date > dateTo) continue;
|
|
218
262
|
|
|
219
|
-
|
|
220
|
-
|
|
263
|
+
// Search with trigram scoring
|
|
264
|
+
var score = 1;
|
|
265
|
+
if (searchQuery) {
|
|
266
|
+
score = searchScore(searchQuery, s);
|
|
267
|
+
if (score < SEARCH_THRESHOLD) continue;
|
|
268
|
+
}
|
|
221
269
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
270
|
+
scored.push({ session: s, score: score });
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// Sort: starred first, then by search score (if searching), then by time
|
|
274
|
+
scored.sort(function(a, b) {
|
|
275
|
+
var aStarred = stars.indexOf(a.session.id) >= 0 ? 1 : 0;
|
|
276
|
+
var bStarred = stars.indexOf(b.session.id) >= 0 ? 1 : 0;
|
|
226
277
|
if (aStarred !== bStarred) return bStarred - aStarred;
|
|
227
|
-
return b.
|
|
278
|
+
if (searchQuery && a.score !== b.score) return b.score - a.score;
|
|
279
|
+
return b.session.last_ts - a.session.last_ts;
|
|
228
280
|
});
|
|
229
281
|
|
|
282
|
+
filteredSessions = scored.map(function(x) { return x.session; });
|
|
283
|
+
|
|
230
284
|
render();
|
|
285
|
+
|
|
231
286
|
}
|
|
232
287
|
|
|
233
288
|
function onSearch(val) {
|
|
@@ -547,10 +602,16 @@ function renderProjects(container, sessions) {
|
|
|
547
602
|
|
|
548
603
|
// ── Activity Heatmap ───────────────────────────────────────────
|
|
549
604
|
|
|
605
|
+
function localISO(date) {
|
|
606
|
+
var y = date.getFullYear();
|
|
607
|
+
var m = String(date.getMonth() + 1).padStart(2, '0');
|
|
608
|
+
var d = String(date.getDate()).padStart(2, '0');
|
|
609
|
+
return y + '-' + m + '-' + d;
|
|
610
|
+
}
|
|
611
|
+
|
|
550
612
|
function renderHeatmap(container) {
|
|
551
613
|
var now = new Date();
|
|
552
|
-
var oneYearAgo = new Date(now);
|
|
553
|
-
oneYearAgo.setFullYear(oneYearAgo.getFullYear() - 1);
|
|
614
|
+
var oneYearAgo = new Date(now.getFullYear() - 1, now.getMonth(), now.getDate());
|
|
554
615
|
|
|
555
616
|
// Count sessions per day
|
|
556
617
|
var counts = {};
|
|
@@ -560,17 +621,16 @@ function renderHeatmap(container) {
|
|
|
560
621
|
counts[d] = (counts[d] || 0) + 1;
|
|
561
622
|
});
|
|
562
623
|
|
|
563
|
-
// Build day array
|
|
624
|
+
// Build day array — start from Sunday before oneYearAgo, end on Saturday after today
|
|
564
625
|
var days = [];
|
|
565
626
|
var d = new Date(oneYearAgo);
|
|
566
|
-
|
|
567
|
-
d.setDate(d.getDate() - d.getDay());
|
|
627
|
+
d.setDate(d.getDate() - d.getDay()); // align to Sunday
|
|
568
628
|
|
|
569
629
|
var endDate = new Date(now);
|
|
570
|
-
endDate.setDate(endDate.getDate() + (6 - endDate.getDay())); //
|
|
630
|
+
endDate.setDate(endDate.getDate() + (6 - endDate.getDay())); // align to Saturday
|
|
571
631
|
|
|
572
632
|
while (d <= endDate) {
|
|
573
|
-
var iso = d
|
|
633
|
+
var iso = localISO(d);
|
|
574
634
|
var count = counts[iso] || 0;
|
|
575
635
|
var level = 0;
|
|
576
636
|
if (count >= 6) level = 4;
|
|
@@ -578,8 +638,7 @@ function renderHeatmap(container) {
|
|
|
578
638
|
else if (count >= 2) level = 2;
|
|
579
639
|
else if (count >= 1) level = 1;
|
|
580
640
|
days.push({ date: iso, count: count, level: level, day: d.getDay() });
|
|
581
|
-
d = new Date(d);
|
|
582
|
-
d.setDate(d.getDate() + 1);
|
|
641
|
+
d = new Date(d.getFullYear(), d.getMonth(), d.getDate() + 1);
|
|
583
642
|
}
|
|
584
643
|
|
|
585
644
|
// Build weeks (columns)
|
|
@@ -627,10 +686,10 @@ function renderHeatmap(container) {
|
|
|
627
686
|
var streak = 0;
|
|
628
687
|
var checkDate = new Date(now);
|
|
629
688
|
while (true) {
|
|
630
|
-
var
|
|
631
|
-
if (counts[
|
|
689
|
+
var ciso = localISO(checkDate);
|
|
690
|
+
if (counts[ciso] && counts[ciso] > 0) {
|
|
632
691
|
streak++;
|
|
633
|
-
checkDate.
|
|
692
|
+
checkDate = new Date(checkDate.getFullYear(), checkDate.getMonth(), checkDate.getDate() - 1);
|
|
634
693
|
} else {
|
|
635
694
|
break;
|
|
636
695
|
}
|