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 CHANGED
@@ -1,60 +1,64 @@
1
- # claude-sessions-dash
1
+ # CodeDash
2
2
 
3
- Termius-style browser dashboard for your Claude Code (and Codex) sessions.
3
+ Browser dashboard for Claude Code & Codex sessions. View, search, resume, and manage all your AI coding sessions.
4
4
 
5
- ![Dashboard](https://img.shields.io/badge/UI-Dark%20Theme-1a1d23?style=flat-square) ![Node](https://img.shields.io/badge/node-%3E%3D16-green?style=flat-square) ![License](https://img.shields.io/badge/license-MIT-blue?style=flat-square)
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
+ ![npm](https://img.shields.io/npm/v/codedash-app?style=flat-square) ![Node](https://img.shields.io/badge/node-%3E%3D16-green?style=flat-square) ![License](https://img.shields.io/badge/license-MIT-blue?style=flat-square)
6
10
 
7
11
  ## Quick Start
8
12
 
9
13
  ```bash
10
- npx claude-sessions-dash
14
+ npx codedash-app run
11
15
  ```
12
16
 
13
- Opens `http://localhost:3847` with your sessions dashboard.
14
-
15
- Custom port:
17
+ Opens `http://localhost:3847` in your browser.
16
18
 
17
19
  ```bash
18
- npx claude-sessions-dash 4000
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
- - View all Claude Code and Codex sessions in a card grid
25
- - Group by project, view as timeline, or filter by tool
26
- - Full-text search across session names and projects
27
- - Preview conversation history in a side panel
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 any session directly in your terminal (iTerm2, Terminal.app, Warp, Kitty, Alacritty)
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
- - Confirmation dialog to prevent accidents
39
- - Refresh data without restarting
45
+ - Bulk select and delete
46
+ - Export conversations as Markdown
47
+ - Related git commits shown per session
48
+ - Auto-update notifications
40
49
 
41
- **Keyboard Shortcuts**
42
- - `/` — Focus search
43
- - `Escape` — Close panels
50
+ **Themes**: Dark (default), Light, System
44
51
 
45
- ## How It Works
52
+ **Keyboard Shortcuts**: `/` search, `j/k` navigate, `Enter` open, `x` star, `d` delete, `s` select, `g` group, `r` refresh, `Esc` close
46
53
 
47
- Reads session data from `~/.claude/`:
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
- Zero dependencies. Single Node.js file. Everything runs on `localhost`.
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 installed (`~/.claude/` directory exists)
61
+ - Claude Code or Codex CLI
58
62
  - macOS / Linux / Windows
59
63
 
60
64
  ## License
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codedash-app",
3
- "version": "1.2.0",
3
+ "version": "1.3.1",
4
4
  "description": "Termius-style browser dashboard for Claude Code sessions. View, search, resume, and delete sessions with a dark-themed UI.",
5
5
  "bin": {
6
6
  "codedash": "./bin/cli.js"
@@ -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
- filteredSessions = allSessions.filter(function(s) {
193
- // Tool filter
194
- if (toolFilter && s.tool !== toolFilter) return false;
246
+ var scored = [];
247
+ for (var i = 0; i < allSessions.length; i++) {
248
+ var s = allSessions[i];
195
249
 
196
- // Search
197
- if (searchQuery) {
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) return false;
256
+ if (sessionTags.indexOf(tagFilter) === -1) continue;
213
257
  }
214
258
 
215
259
  // Date range
216
- if (dateFrom && s.date < dateFrom) return false;
217
- if (dateTo && s.date > dateTo) return false;
260
+ if (dateFrom && s.date < dateFrom) continue;
261
+ if (dateTo && s.date > dateTo) continue;
218
262
 
219
- return true;
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
- // Starred sessions first
223
- filteredSessions.sort(function(a, b) {
224
- var aStarred = stars.indexOf(a.id) >= 0 ? 1 : 0;
225
- var bStarred = stars.indexOf(b.id) >= 0 ? 1 : 0;
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.last_ts - a.last_ts;
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 for last 365 days
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
- // Start from the most recent Sunday before or on oneYearAgo
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())); // end on Saturday
630
+ endDate.setDate(endDate.getDate() + (6 - endDate.getDay())); // align to Saturday
571
631
 
572
632
  while (d <= endDate) {
573
- var iso = d.toISOString().slice(0, 10);
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 iso = checkDate.toISOString().slice(0, 10);
631
- if (counts[iso] && counts[iso] > 0) {
689
+ var ciso = localISO(checkDate);
690
+ if (counts[ciso] && counts[ciso] > 0) {
632
691
  streak++;
633
- checkDate.setDate(checkDate.getDate() - 1);
692
+ checkDate = new Date(checkDate.getFullYear(), checkDate.getMonth(), checkDate.getDate() - 1);
634
693
  } else {
635
694
  break;
636
695
  }