claude-starter 1.3.2 → 1.3.4
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/index.js +83 -10
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -202,17 +202,20 @@ function loadSessionQuick(filePath, projectName) {
|
|
|
202
202
|
const sessionId = path.basename(filePath, '.jsonl');
|
|
203
203
|
const stat = fs.statSync(filePath);
|
|
204
204
|
|
|
205
|
+
// Use 32KB head buffer (up from 8KB) to handle sessions whose first user
|
|
206
|
+
// message is very large (e.g. pasted code blocks, long queries).
|
|
207
|
+
const HEAD_SIZE = 32768;
|
|
205
208
|
const fd = fs.openSync(filePath, 'r');
|
|
206
|
-
const headBuf = Buffer.alloc(Math.min(
|
|
209
|
+
const headBuf = Buffer.alloc(Math.min(HEAD_SIZE, stat.size));
|
|
207
210
|
fs.readSync(fd, headBuf, 0, headBuf.length, 0);
|
|
208
211
|
|
|
209
212
|
// Read tail with progressive expansion: start at 32KB, grow up to 256KB
|
|
210
213
|
// until we find a JSON line with a top-level timestamp (to get accurate lastTs).
|
|
211
214
|
let tailStr = '';
|
|
212
|
-
if (stat.size >
|
|
215
|
+
if (stat.size > HEAD_SIZE) {
|
|
213
216
|
const tailSizes = [32768, 65536, 131072, 262144];
|
|
214
217
|
for (const ts of tailSizes) {
|
|
215
|
-
const tailSize = Math.min(ts, stat.size -
|
|
218
|
+
const tailSize = Math.min(ts, stat.size - HEAD_SIZE);
|
|
216
219
|
const tailBuf = Buffer.alloc(tailSize);
|
|
217
220
|
fs.readSync(fd, tailBuf, 0, tailSize, stat.size - tailSize);
|
|
218
221
|
tailStr = tailBuf.toString('utf-8');
|
|
@@ -221,7 +224,7 @@ function loadSessionQuick(filePath, projectName) {
|
|
|
221
224
|
try { return !!JSON.parse(line).timestamp; } catch { return false; }
|
|
222
225
|
});
|
|
223
226
|
if (hasTopLevelTs) break;
|
|
224
|
-
if (tailSize >= stat.size -
|
|
227
|
+
if (tailSize >= stat.size - HEAD_SIZE) break; // already read entire file
|
|
225
228
|
}
|
|
226
229
|
}
|
|
227
230
|
fs.closeSync(fd);
|
|
@@ -250,7 +253,42 @@ function loadSessionQuick(filePath, projectName) {
|
|
|
250
253
|
userMsgCount++;
|
|
251
254
|
if (!firstUserMsg) firstUserMsg = extractUserText(d);
|
|
252
255
|
}
|
|
253
|
-
} catch (e) {
|
|
256
|
+
} catch (e) {
|
|
257
|
+
// The line was truncated by the head buffer. Try to salvage metadata
|
|
258
|
+
// via regex so we don't lose the session entirely.
|
|
259
|
+
if (!firstTs) {
|
|
260
|
+
const tsMatch = line.match(/"timestamp"\s*:\s*"([^"]+)"/);
|
|
261
|
+
if (tsMatch) firstTs = tsMatch[1];
|
|
262
|
+
}
|
|
263
|
+
if (!version) {
|
|
264
|
+
const vMatch = line.match(/"version"\s*:\s*"([^"]+)"/);
|
|
265
|
+
if (vMatch) version = vMatch[1];
|
|
266
|
+
}
|
|
267
|
+
if (!gitBranch) {
|
|
268
|
+
const bMatch = line.match(/"gitBranch"\s*:\s*"([^"]+)"/);
|
|
269
|
+
if (bMatch) gitBranch = bMatch[1];
|
|
270
|
+
}
|
|
271
|
+
if (!cwd) {
|
|
272
|
+
const cwdMatch = line.match(/"cwd"\s*:\s*"([^"]+)"/);
|
|
273
|
+
if (cwdMatch) cwd = cwdMatch[1];
|
|
274
|
+
}
|
|
275
|
+
// Try to extract user message text from the truncated JSON line.
|
|
276
|
+
// User messages have "type":"user" and text content embedded inside.
|
|
277
|
+
if (!firstUserMsg && /"type"\s*:\s*"user"/.test(line)) {
|
|
278
|
+
userMsgCount++;
|
|
279
|
+
// Match the text field inside message.content (handles both string
|
|
280
|
+
// content and array-of-objects content structures).
|
|
281
|
+
const textMatch = line.match(/"text"\s*:\s*"((?:[^"\\]|\\.)*)/) ||
|
|
282
|
+
line.match(/"content"\s*:\s*"((?:[^"\\]|\\.)*)/);
|
|
283
|
+
if (textMatch) {
|
|
284
|
+
let text = '';
|
|
285
|
+
try { text = JSON.parse('"' + textMatch[1] + '"'); } catch { text = textMatch[1]; }
|
|
286
|
+
if (!text.startsWith('<local-command') && !text.startsWith('<command-')) {
|
|
287
|
+
firstUserMsg = text.substring(0, 200);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
}
|
|
254
292
|
}
|
|
255
293
|
|
|
256
294
|
if (tailStr) {
|
|
@@ -259,7 +297,12 @@ function loadSessionQuick(filePath, projectName) {
|
|
|
259
297
|
try {
|
|
260
298
|
const d = JSON.parse(line);
|
|
261
299
|
if (d.timestamp) lastTs = d.timestamp;
|
|
262
|
-
if (d.type === 'user')
|
|
300
|
+
if (d.type === 'user') {
|
|
301
|
+
userMsgCount++;
|
|
302
|
+
// If no real user message was found in the head (all were commands),
|
|
303
|
+
// try to pick one from the tail as a fallback topic.
|
|
304
|
+
if (!firstUserMsg) firstUserMsg = extractUserText(d);
|
|
305
|
+
}
|
|
263
306
|
if (d.type === 'custom-title' && d.customTitle) customTitle = d.customTitle;
|
|
264
307
|
} catch (e) { /* partial line */ }
|
|
265
308
|
}
|
|
@@ -447,6 +490,16 @@ function runListMode(limit) {
|
|
|
447
490
|
function createApp() {
|
|
448
491
|
const allSessions = loadAllSessions();
|
|
449
492
|
const meta = loadMeta();
|
|
493
|
+
|
|
494
|
+
// Apply meta customTitles — these take priority over JSONL titles
|
|
495
|
+
// so renames persist even after continuing a conversation
|
|
496
|
+
for (const session of allSessions) {
|
|
497
|
+
const sm = meta.sessions[session.sessionId];
|
|
498
|
+
if (sm && sm.customTitle) {
|
|
499
|
+
session.customTitle = sm.customTitle;
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
|
|
450
503
|
let filteredSessions = [...allSessions];
|
|
451
504
|
let selectedIndex = -1; // -1 = "New Session", 0+ = session index
|
|
452
505
|
let filterText = '';
|
|
@@ -532,6 +585,16 @@ function createApp() {
|
|
|
532
585
|
});
|
|
533
586
|
|
|
534
587
|
function updateFooter() {
|
|
588
|
+
if (isSearchMode) {
|
|
589
|
+
const keys = [
|
|
590
|
+
'{#e0af68-fg}{bold}↵{/} {#e0af68-fg}Confirm{/}',
|
|
591
|
+
'{#7aa2f7-fg}{bold}↑↓{/} {#7aa2f7-fg}Navigate{/}',
|
|
592
|
+
'{#565f89-fg}{bold}⌫{/} {#565f89-fg}Delete char{/}',
|
|
593
|
+
'{#565f89-fg}{bold}Esc{/} {#565f89-fg}Clear{/}',
|
|
594
|
+
];
|
|
595
|
+
footer.setContent(`\n ${keys.join(' {#414868-fg}│{/} ')}`);
|
|
596
|
+
return;
|
|
597
|
+
}
|
|
535
598
|
const keys = [
|
|
536
599
|
'{#9ece6a-fg}{bold}n{/} {#9ece6a-fg}New{/}',
|
|
537
600
|
'{#7aa2f7-fg}{bold}↵{/} {#7aa2f7-fg}Resume{/}',
|
|
@@ -654,6 +717,10 @@ function createApp() {
|
|
|
654
717
|
const session = filteredSessions[selectedIndex];
|
|
655
718
|
loadSessionDetail(session);
|
|
656
719
|
|
|
720
|
+
// Meta customTitle takes priority over JSONL
|
|
721
|
+
const sm = meta.sessions[session.sessionId];
|
|
722
|
+
if (sm && sm.customTitle) session.customTitle = sm.customTitle;
|
|
723
|
+
|
|
657
724
|
const color = getProjectColor(session.project, projectColorMap);
|
|
658
725
|
let c = '';
|
|
659
726
|
const sep = ` {#414868-fg}${'─'.repeat(44)}{/}`;
|
|
@@ -843,11 +910,13 @@ function createApp() {
|
|
|
843
910
|
}
|
|
844
911
|
|
|
845
912
|
screen.key(['down'], () => {
|
|
846
|
-
if (renameMode || popupOpen
|
|
913
|
+
if (renameMode || popupOpen) return;
|
|
914
|
+
if (isSearchMode) { isSearchMode = false; updateHeader(); updateFooter(); screen.render(); }
|
|
847
915
|
moveSelection(1);
|
|
848
916
|
});
|
|
849
917
|
screen.key(['up'], () => {
|
|
850
|
-
if (renameMode || popupOpen
|
|
918
|
+
if (renameMode || popupOpen) return;
|
|
919
|
+
if (isSearchMode) { isSearchMode = false; updateHeader(); updateFooter(); screen.render(); }
|
|
851
920
|
moveSelection(-1);
|
|
852
921
|
});
|
|
853
922
|
screen.key(['home'], () => {
|
|
@@ -880,7 +949,9 @@ function createApp() {
|
|
|
880
949
|
// Search
|
|
881
950
|
screen.key(['/'], () => {
|
|
882
951
|
if (renameMode || isSearchMode) return;
|
|
883
|
-
isSearchMode = true;
|
|
952
|
+
isSearchMode = true;
|
|
953
|
+
if (!filterText) filterText = ''; // keep existing filterText if any
|
|
954
|
+
updateHeader(); updateFooter(); screen.render();
|
|
884
955
|
});
|
|
885
956
|
|
|
886
957
|
screen.on('keypress', (ch, key) => {
|
|
@@ -948,7 +1019,7 @@ function createApp() {
|
|
|
948
1019
|
}
|
|
949
1020
|
|
|
950
1021
|
if (!isSearchMode) return;
|
|
951
|
-
if (key.name === 'return' || key.name === 'enter') { isSearchMode = false; renderAll(); return; }
|
|
1022
|
+
if (key.name === 'return' || key.name === 'enter') { isSearchMode = false; searchJustConfirmed = true; renderAll(); return; }
|
|
952
1023
|
if (key.name === 'escape') { isSearchMode = false; filterText = ''; applyFilter(); return; }
|
|
953
1024
|
// Only accept printable characters (exclude control chars like \r \n \t)
|
|
954
1025
|
if (ch && ch.length === 1 && ch.charCodeAt(0) >= 32 && !key.ctrl && !key.meta) { filterText += ch; selectedIndex = -1; applyFilter(); }
|
|
@@ -1007,10 +1078,12 @@ function createApp() {
|
|
|
1007
1078
|
// Track the rename confirm popup and its session for Enter handling
|
|
1008
1079
|
let renameConfirmPopup = null;
|
|
1009
1080
|
let renameConfirmSession = null;
|
|
1081
|
+
let searchJustConfirmed = false;
|
|
1010
1082
|
|
|
1011
1083
|
screen.key(['enter'], () => {
|
|
1012
1084
|
if (renameMode) return;
|
|
1013
1085
|
if (renameJustFinished) return;
|
|
1086
|
+
if (searchJustConfirmed) { searchJustConfirmed = false; return; }
|
|
1014
1087
|
// Handle rename confirm popup Enter
|
|
1015
1088
|
if (renameConfirmPopup && popupOpen) {
|
|
1016
1089
|
const session = renameConfirmSession;
|