codedash-app 1.1.0 → 1.2.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": "codedash-app",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
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"
package/src/data.js CHANGED
@@ -65,16 +65,30 @@ function scanCodexSessions() {
65
65
  const uuidMatch = basename.match(/([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})/);
66
66
  if (!uuidMatch) continue;
67
67
  const sid = uuidMatch[1];
68
+ // Try to extract cwd from session_meta
69
+ let cwd = '';
70
+ try {
71
+ const firstLine = fs.readFileSync(f, 'utf8').split('\n')[0];
72
+ const meta = JSON.parse(firstLine);
73
+ if (meta.type === 'session_meta' && meta.payload && meta.payload.cwd) {
74
+ cwd = meta.payload.cwd;
75
+ }
76
+ } catch {}
77
+
68
78
  const existing = sessions.find(s => s.id === sid);
69
79
  if (existing) {
70
80
  existing.has_detail = true;
71
81
  existing.file_size = stat.size;
82
+ if (cwd && !existing.project) {
83
+ existing.project = cwd;
84
+ existing.project_short = cwd.replace(os.homedir(), '~');
85
+ }
72
86
  } else {
73
87
  sessions.push({
74
88
  id: sid,
75
89
  tool: 'codex',
76
- project: '',
77
- project_short: '',
90
+ project: cwd,
91
+ project_short: cwd ? cwd.replace(os.homedir(), '~') : '',
78
92
  first_ts: stat.mtimeMs,
79
93
  last_ts: stat.mtimeMs,
80
94
  messages: 0,
@@ -219,6 +219,14 @@ function applyFilters() {
219
219
  return true;
220
220
  });
221
221
 
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;
226
+ if (aStarred !== bStarred) return bStarred - aStarred;
227
+ return b.last_ts - a.last_ts;
228
+ });
229
+
222
230
  render();
223
231
  }
224
232
 
@@ -821,7 +829,7 @@ function launchSession(sessionId, tool, project) {
821
829
 
822
830
  function copyResume(sessionId, tool) {
823
831
  var cmd = tool === 'codex'
824
- ? 'codex --resume ' + sessionId
832
+ ? 'codex resume ' + sessionId
825
833
  : 'claude --resume ' + sessionId;
826
834
  navigator.clipboard.writeText(cmd).then(function() {
827
835
  showToast('Copied: ' + cmd);
@@ -863,6 +871,17 @@ async function confirmDelete() {
863
871
  if (data.ok) {
864
872
  showToast('Session deleted');
865
873
  allSessions = allSessions.filter(function(s) { return s.id !== pendingDelete.id; });
874
+ // Clear search if no more results
875
+ if (searchQuery) {
876
+ var remaining = allSessions.filter(function(s) {
877
+ return (s.project || '').toLowerCase().indexOf(searchQuery.toLowerCase()) >= 0 ||
878
+ (s.first_message || '').toLowerCase().indexOf(searchQuery.toLowerCase()) >= 0;
879
+ });
880
+ if (remaining.length === 0) {
881
+ searchQuery = '';
882
+ document.querySelector('.search-box').value = '';
883
+ }
884
+ }
866
885
  closeConfirm();
867
886
  closeDetail();
868
887
  applyFilters();
@@ -1080,12 +1099,44 @@ document.addEventListener('keydown', function(e) {
1080
1099
  }
1081
1100
  });
1082
1101
 
1102
+ // ── Update check ──────────────────────────────────────────────
1103
+
1104
+ async function checkForUpdates() {
1105
+ try {
1106
+ var resp = await fetch('/api/version');
1107
+ var data = await resp.json();
1108
+ if (data.updateAvailable) {
1109
+ var banner = document.getElementById('updateBanner');
1110
+ var text = document.getElementById('updateText');
1111
+ if (banner && text) {
1112
+ text.textContent = 'Update available: v' + data.current + ' → v' + data.latest;
1113
+ banner.style.display = 'flex';
1114
+ banner.dataset.cmd = 'npm update -g codedash-app && codedash run';
1115
+ }
1116
+ }
1117
+ } catch {}
1118
+ }
1119
+
1120
+ function copyUpdate() {
1121
+ var banner = document.getElementById('updateBanner');
1122
+ var cmd = banner ? banner.dataset.cmd : 'npm update -g codedash-app';
1123
+ navigator.clipboard.writeText(cmd).then(function() {
1124
+ showToast('Copied: ' + cmd);
1125
+ });
1126
+ }
1127
+
1128
+ function dismissUpdate() {
1129
+ var banner = document.getElementById('updateBanner');
1130
+ if (banner) banner.style.display = 'none';
1131
+ }
1132
+
1083
1133
  // ── Initialization ─────────────────────────────────────────────
1084
1134
 
1085
1135
  (function init() {
1086
1136
  // Load data
1087
1137
  loadSessions();
1088
1138
  loadTerminals();
1139
+ checkForUpdates();
1089
1140
 
1090
1141
  // Apply saved theme
1091
1142
  var savedTheme = localStorage.getItem('codedash-theme') || 'dark';
@@ -111,6 +111,12 @@
111
111
 
112
112
  <div class="toast" id="toast"></div>
113
113
 
114
+ <div class="update-banner" id="updateBanner" style="display:none">
115
+ <span id="updateText"></span>
116
+ <button class="update-btn" onclick="copyUpdate()">Copy update command</button>
117
+ <button class="update-dismiss" onclick="dismissUpdate()">&times;</button>
118
+ </div>
119
+
114
120
  <script>{{SCRIPT}}</script>
115
121
  </body>
116
122
  </html>
@@ -1370,6 +1370,52 @@ body {
1370
1370
  color: #fff;
1371
1371
  }
1372
1372
 
1373
+ /* ── Update banner ──────────────────────────────────────────── */
1374
+
1375
+ .update-banner {
1376
+ position: fixed;
1377
+ top: 0;
1378
+ left: 200px;
1379
+ right: 0;
1380
+ background: linear-gradient(135deg, var(--accent-blue), var(--accent-purple));
1381
+ color: #fff;
1382
+ padding: 10px 20px;
1383
+ display: flex;
1384
+ align-items: center;
1385
+ gap: 12px;
1386
+ font-size: 13px;
1387
+ z-index: 200;
1388
+ animation: slideDown 0.3s ease;
1389
+ }
1390
+
1391
+ @keyframes slideDown {
1392
+ from { transform: translateY(-100%); }
1393
+ to { transform: translateY(0); }
1394
+ }
1395
+
1396
+ .update-btn {
1397
+ background: rgba(255,255,255,0.2);
1398
+ border: 1px solid rgba(255,255,255,0.3);
1399
+ color: #fff;
1400
+ padding: 4px 12px;
1401
+ border-radius: 6px;
1402
+ font-size: 12px;
1403
+ cursor: pointer;
1404
+ white-space: nowrap;
1405
+ }
1406
+ .update-btn:hover { background: rgba(255,255,255,0.3); }
1407
+
1408
+ .update-dismiss {
1409
+ background: none;
1410
+ border: none;
1411
+ color: rgba(255,255,255,0.7);
1412
+ font-size: 18px;
1413
+ cursor: pointer;
1414
+ margin-left: auto;
1415
+ padding: 0 4px;
1416
+ }
1417
+ .update-dismiss:hover { color: #fff; }
1418
+
1373
1419
  /* ── List view ──────────────────────────────────────────────── */
1374
1420
 
1375
1421
  .list-view {
package/src/server.js CHANGED
@@ -1,5 +1,6 @@
1
1
  // HTTP server + API routes
2
2
  const http = require('http');
3
+ const https = require('https');
3
4
  const { URL } = require('url');
4
5
  const { exec } = require('child_process');
5
6
  const { loadSessions, loadSessionDetail, deleteSession, getGitCommits, exportSessionMarkdown } = require('./data');
@@ -103,6 +104,18 @@ function startServer(port, openBrowser = true) {
103
104
  json(res, commits);
104
105
  }
105
106
 
107
+ // ── Version check ────────────────────────
108
+ else if (req.method === 'GET' && pathname === '/api/version') {
109
+ const pkg = require('../package.json');
110
+ const current = pkg.version;
111
+ // Fetch latest from npm registry
112
+ fetchLatestVersion(pkg.name).then(latest => {
113
+ json(res, { current, latest, updateAvailable: latest && latest !== current && isNewer(latest, current) });
114
+ }).catch(() => {
115
+ json(res, { current, latest: null, updateAvailable: false });
116
+ });
117
+ }
118
+
106
119
  // ── 404 ─────────────────────────────────
107
120
  else {
108
121
  res.writeHead(404);
@@ -139,4 +152,29 @@ function readBody(req, cb) {
139
152
  req.on('end', () => cb(body));
140
153
  }
141
154
 
155
+ // ── npm version check ───────────────────
156
+ function fetchLatestVersion(packageName) {
157
+ return new Promise((resolve, reject) => {
158
+ https.get(`https://registry.npmjs.org/${packageName}/latest`, { timeout: 5000 }, (res) => {
159
+ let data = '';
160
+ res.on('data', chunk => data += chunk);
161
+ res.on('end', () => {
162
+ try {
163
+ resolve(JSON.parse(data).version);
164
+ } catch { reject(); }
165
+ });
166
+ }).on('error', reject);
167
+ });
168
+ }
169
+
170
+ function isNewer(latest, current) {
171
+ const l = latest.split('.').map(Number);
172
+ const c = current.split('.').map(Number);
173
+ for (let i = 0; i < 3; i++) {
174
+ if ((l[i] || 0) > (c[i] || 0)) return true;
175
+ if ((l[i] || 0) < (c[i] || 0)) return false;
176
+ }
177
+ return false;
178
+ }
179
+
142
180
  module.exports = { startServer };
package/src/terminals.js CHANGED
@@ -70,7 +70,7 @@ function openInTerminal(sessionId, tool, flags, projectDir, terminalId) {
70
70
  let cmd;
71
71
 
72
72
  if (tool === 'codex') {
73
- cmd = `codex --resume ${sessionId}`;
73
+ cmd = `codex resume ${sessionId}`;
74
74
  } else {
75
75
  cmd = `claude --resume ${sessionId}`;
76
76
  if (skipPerms) cmd += ' --dangerously-skip-permissions';