@teddysc/claude-run 0.12.0 → 0.13.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/README.md +8 -1
- package/dist/index.js +83 -1
- package/dist/web/assets/index-Bz7pnCx9.js +328 -0
- package/dist/web/assets/index-CSMaeBYb.css +1 -0
- package/dist/web/index.html +2 -2
- package/package.json +1 -1
- package/dist/web/assets/index-CrTCiHsZ.js +0 -328
- package/dist/web/assets/index-_XHzPMzO.css +0 -1
package/README.md
CHANGED
|
@@ -36,6 +36,11 @@ See [spec.md](spec.md)
|
|
|
36
36
|
|
|
37
37
|
## Changelog
|
|
38
38
|
|
|
39
|
+
### 0.13.0
|
|
40
|
+
- Display message count, duration, and JSONL size in conversation header
|
|
41
|
+
- Sidebar shows compact message count, duration, and JSONL size per session
|
|
42
|
+
- Add sorting options for sessions (by duration or JSONL size, asc/desc)
|
|
43
|
+
|
|
39
44
|
### 0.12.0
|
|
40
45
|
- Rename sessions directly from the web UI
|
|
41
46
|
- Searchable project dropdown with fuzzy matching on name and path
|
|
@@ -99,7 +104,9 @@ See [spec.md](spec.md)
|
|
|
99
104
|
- **Filter by project** - Searchable dropdown with fuzzy matching on project name and path
|
|
100
105
|
- **Resume sessions** - Copy the resume command to continue any conversation in your terminal
|
|
101
106
|
- **Copy messages** - Click the copy button on any message to copy its text content
|
|
102
|
-
- **Session metadata** - See model and
|
|
107
|
+
- **Session metadata** - See model, message count, duration, and JSONL size in the conversation header
|
|
108
|
+
- **Sidebar stats** - Compact message count, duration, and JSONL size per session
|
|
109
|
+
- **Sidebar sorting** - Sort sessions by duration or JSONL size (asc/desc)
|
|
103
110
|
- **Rename sessions** - Click the conversation title to rename a session
|
|
104
111
|
- **Collapsible sidebar** - Maximize your viewing area
|
|
105
112
|
- **Dark mode** - Easy on the eyes
|
package/dist/index.js
CHANGED
|
@@ -89,6 +89,7 @@ var claudeDir = join(homedir(), ".claude");
|
|
|
89
89
|
var projectsDir = join(claudeDir, "projects");
|
|
90
90
|
var fileIndex = /* @__PURE__ */ new Map();
|
|
91
91
|
var summaryCache = /* @__PURE__ */ new Map();
|
|
92
|
+
var sessionStatsCache = /* @__PURE__ */ new Map();
|
|
92
93
|
var historyCache = null;
|
|
93
94
|
var pendingRequests = /* @__PURE__ */ new Map();
|
|
94
95
|
function initStorage(dir) {
|
|
@@ -104,6 +105,80 @@ function invalidateHistoryCache() {
|
|
|
104
105
|
function addToFileIndex(sessionId, filePath) {
|
|
105
106
|
fileIndex.set(sessionId, filePath);
|
|
106
107
|
summaryCache.delete(sessionId);
|
|
108
|
+
sessionStatsCache.delete(sessionId);
|
|
109
|
+
}
|
|
110
|
+
function invalidateSessionStats(sessionId) {
|
|
111
|
+
sessionStatsCache.delete(sessionId);
|
|
112
|
+
}
|
|
113
|
+
function parseMessageTimestamp(rawTimestamp) {
|
|
114
|
+
if (typeof rawTimestamp === "string") {
|
|
115
|
+
const parsed = Date.parse(rawTimestamp);
|
|
116
|
+
return Number.isNaN(parsed) ? null : parsed;
|
|
117
|
+
}
|
|
118
|
+
if (typeof rawTimestamp === "number" && Number.isFinite(rawTimestamp)) {
|
|
119
|
+
return rawTimestamp;
|
|
120
|
+
}
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
async function getSessionStats(sessionId) {
|
|
124
|
+
if (sessionStatsCache.has(sessionId)) {
|
|
125
|
+
return sessionStatsCache.get(sessionId);
|
|
126
|
+
}
|
|
127
|
+
const filePath = await findSessionFile(sessionId);
|
|
128
|
+
if (!filePath) {
|
|
129
|
+
const empty = {
|
|
130
|
+
messageCount: 0,
|
|
131
|
+
startTime: null,
|
|
132
|
+
endTime: null,
|
|
133
|
+
fileSizeBytes: null
|
|
134
|
+
};
|
|
135
|
+
sessionStatsCache.set(sessionId, empty);
|
|
136
|
+
return empty;
|
|
137
|
+
}
|
|
138
|
+
let fileSizeBytes = null;
|
|
139
|
+
try {
|
|
140
|
+
const fileStat = await stat(filePath);
|
|
141
|
+
fileSizeBytes = fileStat.size;
|
|
142
|
+
} catch {
|
|
143
|
+
fileSizeBytes = null;
|
|
144
|
+
}
|
|
145
|
+
let messageCount = 0;
|
|
146
|
+
let startTime = null;
|
|
147
|
+
let endTime = null;
|
|
148
|
+
const stream = createReadStream(filePath, { encoding: "utf-8" });
|
|
149
|
+
const rl = createInterface({ input: stream, crlfDelay: Infinity });
|
|
150
|
+
try {
|
|
151
|
+
for await (const line of rl) {
|
|
152
|
+
if (!line.trim()) {
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
try {
|
|
156
|
+
const msg = JSON.parse(line);
|
|
157
|
+
if (msg.type !== "user" && msg.type !== "assistant") {
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
messageCount += 1;
|
|
161
|
+
const parsed = parseMessageTimestamp(msg.timestamp);
|
|
162
|
+
if (parsed !== null) {
|
|
163
|
+
startTime = startTime === null ? parsed : Math.min(startTime, parsed);
|
|
164
|
+
endTime = endTime === null ? parsed : Math.max(endTime, parsed);
|
|
165
|
+
}
|
|
166
|
+
} catch {
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
} finally {
|
|
171
|
+
rl.close();
|
|
172
|
+
stream.close();
|
|
173
|
+
}
|
|
174
|
+
const stats = {
|
|
175
|
+
messageCount,
|
|
176
|
+
startTime,
|
|
177
|
+
endTime,
|
|
178
|
+
fileSizeBytes
|
|
179
|
+
};
|
|
180
|
+
sessionStatsCache.set(sessionId, stats);
|
|
181
|
+
return stats;
|
|
107
182
|
}
|
|
108
183
|
async function readLastNonEmptyLine(filePath) {
|
|
109
184
|
try {
|
|
@@ -370,12 +445,17 @@ async function getSessions() {
|
|
|
370
445
|
}
|
|
371
446
|
seenIds.add(sessionId);
|
|
372
447
|
const summary = await getSessionSummary(sessionId);
|
|
448
|
+
const stats = await getSessionStats(sessionId);
|
|
373
449
|
sessions.push({
|
|
374
450
|
id: sessionId,
|
|
375
451
|
display: summary ?? entry.display,
|
|
376
452
|
timestamp: entry.timestamp,
|
|
377
453
|
project: entry.project,
|
|
378
|
-
projectName: getProjectName(entry.project)
|
|
454
|
+
projectName: getProjectName(entry.project),
|
|
455
|
+
messageCount: stats.messageCount,
|
|
456
|
+
startTime: stats.startTime,
|
|
457
|
+
endTime: stats.endTime,
|
|
458
|
+
fileSizeBytes: stats.fileSizeBytes
|
|
379
459
|
});
|
|
380
460
|
}
|
|
381
461
|
return sessions.sort((a, b) => b.timestamp - a.timestamp);
|
|
@@ -424,6 +504,7 @@ async function deleteSession(sessionId) {
|
|
|
424
504
|
}
|
|
425
505
|
fileIndex.delete(sessionId);
|
|
426
506
|
summaryCache.delete(sessionId);
|
|
507
|
+
sessionStatsCache.delete(sessionId);
|
|
427
508
|
const historyPath = join(claudeDir, "history.jsonl");
|
|
428
509
|
let historyChanged = false;
|
|
429
510
|
try {
|
|
@@ -1096,6 +1177,7 @@ function createServer(options) {
|
|
|
1096
1177
|
});
|
|
1097
1178
|
onSessionChange((sessionId, filePath) => {
|
|
1098
1179
|
addToFileIndex(sessionId, filePath);
|
|
1180
|
+
invalidateSessionStats(sessionId);
|
|
1099
1181
|
});
|
|
1100
1182
|
startWatcher();
|
|
1101
1183
|
let httpServer = null;
|