claude-starter 1.3.0 → 1.3.2
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 +10 -4
- package/index.js +247 -39
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -75,7 +75,9 @@ claude-starter
|
|
|
75
75
|
| 🔀 | **多种排序** | 时间 / 大小 / 消息数 / 项目 |
|
|
76
76
|
| 📎 | **复制 ID** | `c` 一键复制到剪贴板 |
|
|
77
77
|
| 🔒 | **权限模式** | `m` 设置权限模式,`d` 一键 danger 模式恢复 |
|
|
78
|
+
| ✏️ | **重命名会话** | `r` 直接重命名,支持中文输入 |
|
|
78
79
|
| 🗑️ | **删除会话** | `x` 删除不需要的会话 |
|
|
80
|
+
| ⌨️ | **Vim 快捷键** | `j`/`k` 上下,`g`/`G` 跳顶/底 |
|
|
79
81
|
| 🧠 | **智能 CLI** | 自动检测 `mai-claude` / `claude` |
|
|
80
82
|
| 🔐 | **完全本地** | 不联网,不上传,不追踪 |
|
|
81
83
|
|
|
@@ -110,11 +112,12 @@ claude-starter --help # 显示帮助信息
|
|
|
110
112
|
|
|
111
113
|
| 按键 | 功能 |
|
|
112
114
|
|:---:|------|
|
|
113
|
-
| `↑` `↓` | 上下导航 |
|
|
115
|
+
| `↑` `↓` / `j` `k` | 上下导航 |
|
|
114
116
|
| `Enter` | 新建 / 恢复对话 |
|
|
115
117
|
| `n` | 直接新建 |
|
|
116
118
|
| `d` | Danger 模式恢复(bypassPermissions) |
|
|
117
119
|
| `m` | 权限模式选择器 |
|
|
120
|
+
| `r` | 重命名会话 |
|
|
118
121
|
| `/` | 搜索 |
|
|
119
122
|
| `Backspace` | 删除搜索字符,删空自动退出 |
|
|
120
123
|
| `Esc` | 清空搜索 |
|
|
@@ -122,7 +125,7 @@ claude-starter --help # 显示帮助信息
|
|
|
122
125
|
| `s` | 切换排序(时间/大小/消息数/项目) |
|
|
123
126
|
| `c` | 复制 Session ID |
|
|
124
127
|
| `x` / `Delete` | 删除会话 |
|
|
125
|
-
| `
|
|
128
|
+
| `g` / `G` | 跳到顶 / 底 |
|
|
126
129
|
| `Ctrl-D` / `Ctrl-U` | 翻页 |
|
|
127
130
|
| `q` / `Ctrl-C` | 退出 |
|
|
128
131
|
|
|
@@ -180,7 +183,9 @@ Searches across **everything** — project names, Git branches, conversation con
|
|
|
180
183
|
| 🔀 | **Sort Modes** | Sort by time, size, messages, or project |
|
|
181
184
|
| 📎 | **Copy ID** | Press `c` to copy session ID |
|
|
182
185
|
| 🔒 | **Permission Modes** | Press `m` to configure, `d` for quick danger-mode resume |
|
|
186
|
+
| ✏️ | **Rename Sessions** | Press `r` to rename, supports CJK input |
|
|
183
187
|
| 🗑️ | **Delete Sessions** | Press `x` to remove unwanted sessions |
|
|
188
|
+
| ⌨️ | **Vim Keybindings** | `j`/`k` navigate, `g`/`G` jump to top/bottom |
|
|
184
189
|
| 🧠 | **Smart CLI** | Auto-detects `mai-claude` vs `claude` |
|
|
185
190
|
| 🔐 | **100% Local** | No network, no telemetry, no data leaves your machine |
|
|
186
191
|
|
|
@@ -219,11 +224,12 @@ claude-starter --help # Show help
|
|
|
219
224
|
|
|
220
225
|
| Key | Action |
|
|
221
226
|
|:---:|--------|
|
|
222
|
-
| `↑` `↓` | Navigate sessions |
|
|
227
|
+
| `↑` `↓` / `j` `k` | Navigate sessions |
|
|
223
228
|
| `Enter` | Start new / resume selected session |
|
|
224
229
|
| `n` | New session |
|
|
225
230
|
| `d` | Resume with bypassPermissions (danger mode) |
|
|
226
231
|
| `m` | Permission mode picker |
|
|
232
|
+
| `r` | Rename session |
|
|
227
233
|
| `/` | Search |
|
|
228
234
|
| `Backspace` | Edit search, auto-exit when empty |
|
|
229
235
|
| `Esc` | Clear filter |
|
|
@@ -231,7 +237,7 @@ claude-starter --help # Show help
|
|
|
231
237
|
| `s` | Cycle sort mode (time/size/messages/project) |
|
|
232
238
|
| `c` | Copy session ID |
|
|
233
239
|
| `x` / `Delete` | Delete session |
|
|
234
|
-
| `
|
|
240
|
+
| `g` / `G` | Jump to top / bottom |
|
|
235
241
|
| `Ctrl-D` / `Ctrl-U` | Page down / up |
|
|
236
242
|
| `q` / `Ctrl-C` | Quit |
|
|
237
243
|
|
package/index.js
CHANGED
|
@@ -206,16 +206,27 @@ function loadSessionQuick(filePath, projectName) {
|
|
|
206
206
|
const headBuf = Buffer.alloc(Math.min(8192, stat.size));
|
|
207
207
|
fs.readSync(fd, headBuf, 0, headBuf.length, 0);
|
|
208
208
|
|
|
209
|
-
|
|
209
|
+
// Read tail with progressive expansion: start at 32KB, grow up to 256KB
|
|
210
|
+
// until we find a JSON line with a top-level timestamp (to get accurate lastTs).
|
|
211
|
+
let tailStr = '';
|
|
210
212
|
if (stat.size > 8192) {
|
|
211
|
-
const
|
|
212
|
-
|
|
213
|
-
|
|
213
|
+
const tailSizes = [32768, 65536, 131072, 262144];
|
|
214
|
+
for (const ts of tailSizes) {
|
|
215
|
+
const tailSize = Math.min(ts, stat.size - 8192);
|
|
216
|
+
const tailBuf = Buffer.alloc(tailSize);
|
|
217
|
+
fs.readSync(fd, tailBuf, 0, tailSize, stat.size - tailSize);
|
|
218
|
+
tailStr = tailBuf.toString('utf-8');
|
|
219
|
+
// Check if any parseable JSON line has a top-level timestamp
|
|
220
|
+
const hasTopLevelTs = tailStr.split('\n').some(line => {
|
|
221
|
+
try { return !!JSON.parse(line).timestamp; } catch { return false; }
|
|
222
|
+
});
|
|
223
|
+
if (hasTopLevelTs) break;
|
|
224
|
+
if (tailSize >= stat.size - 8192) break; // already read entire file
|
|
225
|
+
}
|
|
214
226
|
}
|
|
215
227
|
fs.closeSync(fd);
|
|
216
228
|
|
|
217
229
|
const headStr = headBuf.toString('utf-8');
|
|
218
|
-
const tailStr = tailBuf.toString('utf-8');
|
|
219
230
|
|
|
220
231
|
let firstTs = null, lastTs = null;
|
|
221
232
|
let version = '', gitBranch = '', cwd = '', permissionMode = '';
|
|
@@ -382,7 +393,9 @@ function formatTimestamp(ts) {
|
|
|
382
393
|
if (!ts) return 'unknown';
|
|
383
394
|
const d = new Date(ts);
|
|
384
395
|
const now = new Date();
|
|
385
|
-
const
|
|
396
|
+
const todayStart = new Date(now.getFullYear(), now.getMonth(), now.getDate());
|
|
397
|
+
const targetStart = new Date(d.getFullYear(), d.getMonth(), d.getDate());
|
|
398
|
+
const diffDays = Math.round((todayStart.getTime() - targetStart.getTime()) / 86400000);
|
|
386
399
|
const time = d.toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit', hour12: false });
|
|
387
400
|
if (diffDays === 0) return `Today ${time}`;
|
|
388
401
|
if (diffDays === 1) return `Yesterday ${time}`;
|
|
@@ -520,16 +533,17 @@ function createApp() {
|
|
|
520
533
|
|
|
521
534
|
function updateFooter() {
|
|
522
535
|
const keys = [
|
|
523
|
-
'{#
|
|
524
|
-
'{#7aa2f7-fg}{bold}↵{/} {#
|
|
525
|
-
'{#
|
|
526
|
-
'{#f7768e-fg}{bold}d{/} {#
|
|
527
|
-
'{#
|
|
528
|
-
'{#
|
|
529
|
-
'{#
|
|
530
|
-
'{#
|
|
531
|
-
'{#
|
|
532
|
-
'{#
|
|
536
|
+
'{#9ece6a-fg}{bold}n{/} {#9ece6a-fg}New{/}',
|
|
537
|
+
'{#7aa2f7-fg}{bold}↵{/} {#7aa2f7-fg}Resume{/}',
|
|
538
|
+
'{#bb9af7-fg}{bold}m{/} {#bb9af7-fg}Mode{/}',
|
|
539
|
+
'{#f7768e-fg}{bold}d{/} {#f7768e-fg}Danger{/}',
|
|
540
|
+
'{#e0af68-fg}{bold}/{/} {#e0af68-fg}Search{/}',
|
|
541
|
+
'{#7dcfff-fg}{bold}p{/} {#7dcfff-fg}Project{/}',
|
|
542
|
+
'{#73daca-fg}{bold}s{/} {#73daca-fg}Sort{/}',
|
|
543
|
+
'{#565f89-fg}{bold}c{/} {#565f89-fg}Copy ID{/}',
|
|
544
|
+
'{#ff9e64-fg}{bold}r{/} {#ff9e64-fg}Rename{/}',
|
|
545
|
+
'{#f7768e-fg}{bold}x{/} {#f7768e-fg}Delete{/}',
|
|
546
|
+
'{#565f89-fg}{bold}q{/} {#565f89-fg}Quit{/}',
|
|
533
547
|
];
|
|
534
548
|
footer.setContent(`\n ${keys.join(' {#414868-fg}│{/} ')}`);
|
|
535
549
|
}
|
|
@@ -829,17 +843,15 @@ function createApp() {
|
|
|
829
843
|
}
|
|
830
844
|
|
|
831
845
|
screen.key(['down'], () => {
|
|
832
|
-
if (popupOpen) return;
|
|
833
|
-
if (isSearchMode) { isSearchMode = false; updateHeader(); screen.render(); }
|
|
846
|
+
if (renameMode || popupOpen || isSearchMode) return;
|
|
834
847
|
moveSelection(1);
|
|
835
848
|
});
|
|
836
849
|
screen.key(['up'], () => {
|
|
837
|
-
if (popupOpen) return;
|
|
838
|
-
if (isSearchMode) { isSearchMode = false; updateHeader(); screen.render(); }
|
|
850
|
+
if (renameMode || popupOpen || isSearchMode) return;
|
|
839
851
|
moveSelection(-1);
|
|
840
852
|
});
|
|
841
853
|
screen.key(['home'], () => {
|
|
842
|
-
if (popupOpen) return;
|
|
854
|
+
if (renameMode || popupOpen) return;
|
|
843
855
|
if (isSearchMode) { isSearchMode = false; }
|
|
844
856
|
selectedIndex = -1;
|
|
845
857
|
suppressSelectEvent = true; listPanel.select(0); suppressSelectEvent = false;
|
|
@@ -847,7 +859,7 @@ function createApp() {
|
|
|
847
859
|
renderDetail(); updateHeader(); screen.render();
|
|
848
860
|
});
|
|
849
861
|
screen.key(['end'], () => {
|
|
850
|
-
if (popupOpen) return;
|
|
862
|
+
if (renameMode || popupOpen) return;
|
|
851
863
|
if (isSearchMode) { isSearchMode = false; }
|
|
852
864
|
selectedIndex = Math.max(0, filteredSessions.length - 1);
|
|
853
865
|
suppressSelectEvent = true; listPanel.select(selectedIndex + 1); suppressSelectEvent = false;
|
|
@@ -855,31 +867,86 @@ function createApp() {
|
|
|
855
867
|
renderDetail(); updateHeader(); screen.render();
|
|
856
868
|
});
|
|
857
869
|
screen.key(['pagedown', 'C-d'], () => {
|
|
858
|
-
if (popupOpen) return;
|
|
870
|
+
if (renameMode || popupOpen) return;
|
|
859
871
|
if (isSearchMode) { isSearchMode = false; updateHeader(); screen.render(); }
|
|
860
872
|
moveSelection(Math.floor((listPanel.height || 20) / 2));
|
|
861
873
|
});
|
|
862
874
|
screen.key(['pageup', 'C-u'], () => {
|
|
863
|
-
if (popupOpen) return;
|
|
875
|
+
if (renameMode || popupOpen) return;
|
|
864
876
|
if (isSearchMode) { isSearchMode = false; updateHeader(); screen.render(); }
|
|
865
877
|
moveSelection(-Math.floor((listPanel.height || 20) / 2));
|
|
866
878
|
});
|
|
867
879
|
|
|
868
880
|
// Search
|
|
869
881
|
screen.key(['/'], () => {
|
|
870
|
-
if (isSearchMode) return;
|
|
882
|
+
if (renameMode || isSearchMode) return;
|
|
871
883
|
isSearchMode = true; filterText = ''; applyFilter();
|
|
872
884
|
});
|
|
873
885
|
|
|
874
886
|
screen.on('keypress', (ch, key) => {
|
|
875
|
-
//
|
|
876
|
-
if (
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
887
|
+
// ── Rename mode: capture all input ──
|
|
888
|
+
if (renameMode) {
|
|
889
|
+
if (key.name === 'return' || key.name === 'enter') {
|
|
890
|
+
const session = renameSession;
|
|
891
|
+
const value = renameValue;
|
|
892
|
+
closeRename();
|
|
893
|
+
submitRename(session, value);
|
|
894
|
+
return;
|
|
895
|
+
}
|
|
896
|
+
if (key.name === 'escape') {
|
|
897
|
+
closeRename();
|
|
898
|
+
listPanel.focus();
|
|
899
|
+
screen.render();
|
|
900
|
+
return;
|
|
901
|
+
}
|
|
902
|
+
if (key.name === 'backspace') {
|
|
903
|
+
if (renameValue.length > 0) {
|
|
904
|
+
renameValue = [...renameValue].slice(0, -1).join('');
|
|
905
|
+
renderRenameInput();
|
|
906
|
+
}
|
|
907
|
+
return;
|
|
908
|
+
}
|
|
909
|
+
if (ch && ch.length >= 1 && ch.charCodeAt(0) >= 32 && !key.ctrl && !key.meta) {
|
|
910
|
+
renameValue += ch;
|
|
911
|
+
renderRenameInput();
|
|
912
|
+
}
|
|
913
|
+
return; // swallow all keys while in rename mode
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
// Backspace: delete search char, or exit search mode if empty
|
|
917
|
+
if (key.name === 'backspace') {
|
|
918
|
+
if (filterText) {
|
|
919
|
+
filterText = filterText.slice(0, -1);
|
|
920
|
+
selectedIndex = -1;
|
|
921
|
+
isSearchMode = !!filterText;
|
|
922
|
+
applyFilter();
|
|
923
|
+
} else if (isSearchMode) {
|
|
924
|
+
isSearchMode = false;
|
|
925
|
+
applyFilter();
|
|
926
|
+
}
|
|
881
927
|
return;
|
|
882
928
|
}
|
|
929
|
+
|
|
930
|
+
// Vim-like navigation (only when NOT in search mode)
|
|
931
|
+
if (!isSearchMode && !popupOpen) {
|
|
932
|
+
if (ch === 'j') { moveSelection(1); return; }
|
|
933
|
+
if (ch === 'k') { moveSelection(-1); return; }
|
|
934
|
+
if (ch === 'G') {
|
|
935
|
+
selectedIndex = Math.max(0, filteredSessions.length - 1);
|
|
936
|
+
suppressSelectEvent = true; listPanel.select(selectedIndex + 1); suppressSelectEvent = false;
|
|
937
|
+
listPanel.childBase = Math.max(0, selectedIndex + 1 - listPanel.height + 1);
|
|
938
|
+
renderDetail(); updateHeader(); screen.render();
|
|
939
|
+
return;
|
|
940
|
+
}
|
|
941
|
+
if (ch === 'g') {
|
|
942
|
+
selectedIndex = -1;
|
|
943
|
+
suppressSelectEvent = true; listPanel.select(0); suppressSelectEvent = false;
|
|
944
|
+
listPanel.childBase = 0;
|
|
945
|
+
renderDetail(); updateHeader(); screen.render();
|
|
946
|
+
return;
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
|
|
883
950
|
if (!isSearchMode) return;
|
|
884
951
|
if (key.name === 'return' || key.name === 'enter') { isSearchMode = false; renderAll(); return; }
|
|
885
952
|
if (key.name === 'escape') { isSearchMode = false; filterText = ''; applyFilter(); return; }
|
|
@@ -937,7 +1004,23 @@ function createApp() {
|
|
|
937
1004
|
child.on('exit', (code) => process.exit(code || 0));
|
|
938
1005
|
}
|
|
939
1006
|
|
|
1007
|
+
// Track the rename confirm popup and its session for Enter handling
|
|
1008
|
+
let renameConfirmPopup = null;
|
|
1009
|
+
let renameConfirmSession = null;
|
|
1010
|
+
|
|
940
1011
|
screen.key(['enter'], () => {
|
|
1012
|
+
if (renameMode) return;
|
|
1013
|
+
if (renameJustFinished) return;
|
|
1014
|
+
// Handle rename confirm popup Enter
|
|
1015
|
+
if (renameConfirmPopup && popupOpen) {
|
|
1016
|
+
const session = renameConfirmSession;
|
|
1017
|
+
renameConfirmPopup.destroy();
|
|
1018
|
+
renameConfirmPopup = null;
|
|
1019
|
+
renameConfirmSession = null;
|
|
1020
|
+
popupOpen = false;
|
|
1021
|
+
resumeSession(session);
|
|
1022
|
+
return;
|
|
1023
|
+
}
|
|
941
1024
|
if (isSearchMode) { isSearchMode = false; renderAll(); return; }
|
|
942
1025
|
if (popupOpen) return;
|
|
943
1026
|
if (selectedIndex === -1) { startNewSession(); return; }
|
|
@@ -947,13 +1030,13 @@ function createApp() {
|
|
|
947
1030
|
|
|
948
1031
|
// Quick shortcut: n = new session
|
|
949
1032
|
screen.key(['n'], () => {
|
|
950
|
-
if (isSearchMode) return;
|
|
1033
|
+
if (renameMode || isSearchMode) return;
|
|
951
1034
|
startNewSession();
|
|
952
1035
|
});
|
|
953
1036
|
|
|
954
1037
|
// Copy session ID
|
|
955
1038
|
screen.key(['c'], () => {
|
|
956
|
-
if (isSearchMode) return;
|
|
1039
|
+
if (renameMode || isSearchMode) return;
|
|
957
1040
|
if (filteredSessions.length === 0) return;
|
|
958
1041
|
const sid = filteredSessions[selectedIndex].sessionId;
|
|
959
1042
|
try {
|
|
@@ -1097,14 +1180,14 @@ function createApp() {
|
|
|
1097
1180
|
|
|
1098
1181
|
// ─── Quick dangerous resume (d key) ────────────────────────────────────
|
|
1099
1182
|
screen.key(['d'], () => {
|
|
1100
|
-
if (isSearchMode || popupOpen) return;
|
|
1183
|
+
if (renameMode || isSearchMode || popupOpen) return;
|
|
1101
1184
|
if (selectedIndex < 0 || selectedIndex >= filteredSessions.length) return;
|
|
1102
1185
|
resumeSession(filteredSessions[selectedIndex], 'bypassPermissions');
|
|
1103
1186
|
});
|
|
1104
1187
|
|
|
1105
1188
|
// ─── Permission mode picker (m key) ───────────────────────────────────
|
|
1106
1189
|
screen.key(['m'], () => {
|
|
1107
|
-
if (isSearchMode || popupOpen) return;
|
|
1190
|
+
if (renameMode || isSearchMode || popupOpen) return;
|
|
1108
1191
|
if (selectedIndex < 0 || selectedIndex >= filteredSessions.length) return;
|
|
1109
1192
|
showPermissionModePicker(filteredSessions[selectedIndex]);
|
|
1110
1193
|
});
|
|
@@ -1169,18 +1252,143 @@ function createApp() {
|
|
|
1169
1252
|
}
|
|
1170
1253
|
|
|
1171
1254
|
screen.key(['x', 'delete'], () => {
|
|
1172
|
-
if (isSearchMode || popupOpen) return;
|
|
1255
|
+
if (renameMode || isSearchMode || popupOpen) return;
|
|
1173
1256
|
if (selectedIndex < 0 || selectedIndex >= filteredSessions.length) return;
|
|
1174
1257
|
showDeleteConfirm(filteredSessions[selectedIndex]);
|
|
1175
1258
|
});
|
|
1176
1259
|
|
|
1177
|
-
|
|
1178
|
-
|
|
1260
|
+
// ─── Rename Session ───────────────────────────────────────────────────
|
|
1261
|
+
const stringWidth = require('string-width');
|
|
1262
|
+
let renameMode = false;
|
|
1263
|
+
let renameJustFinished = false;
|
|
1264
|
+
let renameValue = '';
|
|
1265
|
+
let renameSession = null;
|
|
1266
|
+
let renamePopup = null;
|
|
1267
|
+
let renameDisplay = null;
|
|
1268
|
+
const renameMaxWidth = 46;
|
|
1269
|
+
|
|
1270
|
+
function renderRenameInput() {
|
|
1271
|
+
let display = renameValue;
|
|
1272
|
+
while (stringWidth(display) > renameMaxWidth && display.length > 0) {
|
|
1273
|
+
display = display.substring(1);
|
|
1274
|
+
}
|
|
1275
|
+
renameDisplay.setContent(display + '▌');
|
|
1276
|
+
screen.render();
|
|
1277
|
+
}
|
|
1278
|
+
|
|
1279
|
+
function showRenameInput(session) {
|
|
1280
|
+
renameSession = session;
|
|
1281
|
+
renameValue = session.customTitle || '';
|
|
1282
|
+
|
|
1283
|
+
renamePopup = blessed.box({
|
|
1284
|
+
parent: screen, top: 'center', left: 'center',
|
|
1285
|
+
width: 52, height: 7,
|
|
1286
|
+
label: ' {bold}{#73daca-fg}Rename Session{/} ',
|
|
1287
|
+
tags: true, border: { type: 'line' },
|
|
1288
|
+
style: {
|
|
1289
|
+
border: { fg: '#73daca' }, bg: '#24283b', fg: '#a9b1d6',
|
|
1290
|
+
label: { fg: '#73daca' },
|
|
1291
|
+
},
|
|
1292
|
+
});
|
|
1293
|
+
|
|
1294
|
+
renameDisplay = blessed.box({
|
|
1295
|
+
parent: renamePopup,
|
|
1296
|
+
top: 1, left: 1, right: 1, height: 1,
|
|
1297
|
+
tags: false,
|
|
1298
|
+
style: { fg: 'white', bg: '#1a1b26' },
|
|
1299
|
+
});
|
|
1300
|
+
|
|
1301
|
+
blessed.box({
|
|
1302
|
+
parent: renamePopup,
|
|
1303
|
+
top: 3, left: 1, right: 1, height: 1,
|
|
1304
|
+
tags: true,
|
|
1305
|
+
style: { bg: '#24283b' },
|
|
1306
|
+
content: ' {#9ece6a-fg}{bold}Enter{/}{#a9b1d6-fg} Save {/}{#565f89-fg}Esc{/}{#a9b1d6-fg} Cancel{/}',
|
|
1307
|
+
});
|
|
1308
|
+
|
|
1309
|
+
popupOpen = true;
|
|
1310
|
+
renameMode = true;
|
|
1311
|
+
renderRenameInput();
|
|
1312
|
+
}
|
|
1313
|
+
|
|
1314
|
+
function closeRename() {
|
|
1315
|
+
renameMode = false;
|
|
1316
|
+
if (renamePopup) { renamePopup.destroy(); renamePopup = null; }
|
|
1317
|
+
popupOpen = false;
|
|
1318
|
+
renameSession = null;
|
|
1319
|
+
renameDisplay = null;
|
|
1320
|
+
}
|
|
1321
|
+
|
|
1322
|
+
function submitRename(session, newTitle) {
|
|
1323
|
+
newTitle = (newTitle || '').trim();
|
|
1324
|
+
|
|
1325
|
+
// Save to meta
|
|
1326
|
+
if (!meta.sessions[session.sessionId]) meta.sessions[session.sessionId] = {};
|
|
1327
|
+
meta.sessions[session.sessionId].customTitle = newTitle || undefined;
|
|
1328
|
+
if (!newTitle) delete meta.sessions[session.sessionId].customTitle;
|
|
1329
|
+
saveMeta(meta);
|
|
1330
|
+
|
|
1331
|
+
// Update in-memory session
|
|
1332
|
+
session.customTitle = newTitle;
|
|
1333
|
+
|
|
1334
|
+
// Also append to JSONL file so Claude Code sees it
|
|
1335
|
+
if (newTitle && fs.existsSync(session.filePath)) {
|
|
1336
|
+
try {
|
|
1337
|
+
const entry = JSON.stringify({ type: 'custom-title', customTitle: newTitle });
|
|
1338
|
+
fs.appendFileSync(session.filePath, '\n' + entry);
|
|
1339
|
+
} catch (e) { /* silently fail */ }
|
|
1340
|
+
}
|
|
1341
|
+
|
|
1342
|
+
renderAll();
|
|
1343
|
+
|
|
1344
|
+
// Ask whether to resume this session after rename
|
|
1345
|
+
// We use renameJustFinished flag to prevent the Enter key from rename
|
|
1346
|
+
// from immediately triggering resume
|
|
1347
|
+
renameJustFinished = true;
|
|
1348
|
+
setTimeout(() => { renameJustFinished = false; }, 200);
|
|
1349
|
+
|
|
1350
|
+
setTimeout(() => {
|
|
1351
|
+
const titleLabel = newTitle ? `{#73daca-fg}${esc(newTitle)}{/}` : '{#565f89-fg}(title cleared){/}';
|
|
1352
|
+
renameConfirmSession = session;
|
|
1353
|
+
renameConfirmPopup = blessed.box({
|
|
1354
|
+
parent: screen, top: 'center', left: 'center',
|
|
1355
|
+
width: 48, height: 8,
|
|
1356
|
+
label: ' {bold}{#9ece6a-fg}Renamed{/} ',
|
|
1357
|
+
tags: true, border: { type: 'line' },
|
|
1358
|
+
style: {
|
|
1359
|
+
border: { fg: '#9ece6a' }, bg: '#24283b', fg: '#a9b1d6',
|
|
1360
|
+
label: { fg: '#9ece6a' },
|
|
1361
|
+
},
|
|
1362
|
+
content: `\n ${titleLabel}\n\n {#9ece6a-fg}{bold}Enter{/}{#a9b1d6-fg} Resume {/}{#565f89-fg}Esc{/}{#a9b1d6-fg} Back to list{/}`,
|
|
1363
|
+
});
|
|
1364
|
+
popupOpen = true;
|
|
1365
|
+
renameConfirmPopup.focus();
|
|
1366
|
+
screen.render();
|
|
1367
|
+
|
|
1368
|
+
renameConfirmPopup.key(['escape', 'q'], () => {
|
|
1369
|
+
renameConfirmPopup.destroy();
|
|
1370
|
+
renameConfirmPopup = null;
|
|
1371
|
+
renameConfirmSession = null;
|
|
1372
|
+
popupOpen = false;
|
|
1373
|
+
renderAll();
|
|
1374
|
+
});
|
|
1375
|
+
}, 50);
|
|
1376
|
+
}
|
|
1377
|
+
|
|
1378
|
+
screen.key(['r'], () => {
|
|
1379
|
+
if (isSearchMode || popupOpen) return;
|
|
1380
|
+
if (selectedIndex < 0 || selectedIndex >= filteredSessions.length) return;
|
|
1381
|
+
showRenameInput(filteredSessions[selectedIndex]);
|
|
1382
|
+
});
|
|
1383
|
+
|
|
1384
|
+
screen.key(['s'], () => { if (!renameMode && !isSearchMode) cycleSort(); });
|
|
1385
|
+
screen.key(['p'], () => { if (!renameMode && !isSearchMode) showProjectPicker(); });
|
|
1179
1386
|
screen.key(['escape'], () => {
|
|
1387
|
+
if (renameMode) return; // handled in keypress
|
|
1180
1388
|
if (isSearchMode) { isSearchMode = false; filterText = ''; applyFilter(); return; }
|
|
1181
1389
|
filterText = ''; selectedIndex = -1; applyFilter();
|
|
1182
1390
|
});
|
|
1183
|
-
screen.key(['q', 'C-c'], () => { process.stdout.write('\x1b[0m'); screen.destroy(); process.exit(0); });
|
|
1391
|
+
screen.key(['q', 'C-c'], () => { if (renameMode) return; process.stdout.write('\x1b[0m'); screen.destroy(); process.exit(0); });
|
|
1184
1392
|
|
|
1185
1393
|
// Remove blessed's built-in wheel handlers (they call select which changes selection)
|
|
1186
1394
|
listPanel.removeAllListeners('element wheeldown');
|