claude-starter 1.1.1 → 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/README.md +52 -9
- package/index.js +273 -16
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
<p align="center">
|
|
2
2
|
<img src="https://img.shields.io/badge/%F0%9F%9A%80-Claude_Starter-7aa2f7?style=for-the-badge&labelColor=1a1b26" alt="Claude Starter" />
|
|
3
3
|
<br/>
|
|
4
|
+
<img src="https://img.shields.io/npm/v/claude-starter?style=flat-square&color=f7768e&logo=npm" alt="npm" />
|
|
4
5
|
<img src="https://img.shields.io/badge/node-%3E%3D18-9ece6a?style=flat-square&logo=node.js&logoColor=white" alt="Node.js" />
|
|
5
6
|
<img src="https://img.shields.io/badge/license-MIT-bb9af7?style=flat-square" alt="MIT License" />
|
|
6
7
|
<img src="https://img.shields.io/github/v/release/Bojun-Vvibe/claude-starter?style=flat-square&color=7dcfff" alt="Release" />
|
|
@@ -15,7 +16,7 @@
|
|
|
15
16
|
</p>
|
|
16
17
|
|
|
17
18
|
<p align="center">
|
|
18
|
-
<code>
|
|
19
|
+
<code>npm install -g claude-starter</code> → <code>claude-starter</code>
|
|
19
20
|
</p>
|
|
20
21
|
|
|
21
22
|
<p align="center">
|
|
@@ -58,26 +59,45 @@ claude-starter
|
|
|
58
59
|
- `auth` → 所有认证相关的对话
|
|
59
60
|
- `refactor` → 上周的代码重构
|
|
60
61
|
- `web-app fix` → 某个项目的 bug 修复
|
|
62
|
+
- `#bug-fix` → 所有打了 bug-fix 标签的对话
|
|
63
|
+
- `fav` → 所有收藏的对话
|
|
61
64
|
|
|
62
65
|
**不需要管理模式,不需要确认。输入即搜,方向键即走。**
|
|
63
66
|
|
|
67
|
+
## ⭐ 收藏 & 🏷️ 标签 — 组织你的对话
|
|
68
|
+
|
|
69
|
+
按 `f` 收藏/取消收藏,按 `#` 打标签。
|
|
70
|
+
|
|
71
|
+
- **收藏**:重要的对话一键标星,排序切到 `favorites` 模式收藏置顶
|
|
72
|
+
- **标签**:预设 8 种标签(`bug-fix`、`feature`、`refactor` 等),支持自定义
|
|
73
|
+
- **搜索联动**:`/` 搜索支持 `#tag` 语法和 `fav` 关键字
|
|
74
|
+
- **持久化**:数据存在 `~/.claude/claude-starter-meta.json`,重启不丢失
|
|
75
|
+
|
|
64
76
|
## 核心能力
|
|
65
77
|
|
|
66
78
|
| | 功能 | 说明 |
|
|
67
79
|
|---|---|---|
|
|
68
80
|
| 🎨 | **精美 TUI** | Tokyo Night 配色,分屏布局,终端里的 App |
|
|
69
81
|
| ✨ | **一键新建** | 列表顶部直接新建对话 |
|
|
70
|
-
| 🔍 | **即时搜索** | `/`
|
|
82
|
+
| 🔍 | **即时搜索** | `/` 全文搜索,无需回车,支持 `#tag` 和 `fav` |
|
|
83
|
+
| ⭐ | **收藏** | `f` 收藏重要对话,排序置顶 |
|
|
84
|
+
| 🏷️ | **标签** | `#` 分类管理,预设 + 自定义标签 |
|
|
71
85
|
| 📂 | **项目过滤** | `p` 按项目筛选 |
|
|
72
86
|
| ⚡ | **秒级恢复** | 选中 → Enter → 回到对话 |
|
|
73
87
|
| 📋 | **对话预览** | 右侧面板展示完整元数据和对话历史 |
|
|
74
|
-
| 🔀 | **多种排序** | 时间 / 大小 / 消息数 / 项目 |
|
|
88
|
+
| 🔀 | **多种排序** | 时间 / 大小 / 消息数 / 项目 / 收藏 |
|
|
75
89
|
| 📎 | **复制 ID** | `c` 一键复制到剪贴板 |
|
|
76
90
|
| 🧠 | **智能 CLI** | 自动检测 `mai-claude` / `claude` |
|
|
77
91
|
| 🔒 | **完全本地** | 不联网,不上传,不追踪 |
|
|
78
92
|
|
|
79
93
|
## 安装
|
|
80
94
|
|
|
95
|
+
```bash
|
|
96
|
+
npm install -g claude-starter
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
或者从源码安装:
|
|
100
|
+
|
|
81
101
|
```bash
|
|
82
102
|
git clone https://github.com/Bojun-Vvibe/claude-starter.git
|
|
83
103
|
cd claude-starter
|
|
@@ -94,11 +114,13 @@ npm link
|
|
|
94
114
|
| `↑` `↓` | 上下导航 |
|
|
95
115
|
| `Enter` | 新建 / 恢复对话 |
|
|
96
116
|
| `n` | 直接新建 |
|
|
97
|
-
| `/` |
|
|
117
|
+
| `/` | 搜索(支持 `#tag` 和 `fav`) |
|
|
118
|
+
| `f` | 收藏 / 取消收藏 ⭐ |
|
|
119
|
+
| `#` | 添加 / 管理标签 🏷️ |
|
|
98
120
|
| `Backspace` | 删除搜索字符,删空自动退出 |
|
|
99
121
|
| `Esc` | 清空搜索 |
|
|
100
122
|
| `p` | 按项目过滤 |
|
|
101
|
-
| `s` |
|
|
123
|
+
| `s` | 切换排序(时间/大小/消息数/项目/收藏) |
|
|
102
124
|
| `c` | 复制 Session ID |
|
|
103
125
|
| `Home` / `End` | 跳到顶 / 底 |
|
|
104
126
|
| `Ctrl-D` / `Ctrl-U` | 翻页 |
|
|
@@ -142,26 +164,45 @@ Searches across **everything** — project names, Git branches, conversation con
|
|
|
142
164
|
- `auth` → all auth-related sessions
|
|
143
165
|
- `refactor` → that cleanup from last week
|
|
144
166
|
- `web-app fix` → bug fixes in a specific project
|
|
167
|
+
- `#bug-fix` → all sessions tagged with bug-fix
|
|
168
|
+
- `fav` → all favorited sessions
|
|
145
169
|
|
|
146
170
|
**No modes. No confirmation. Just type and go.**
|
|
147
171
|
|
|
172
|
+
## ⭐ Favorites & 🏷️ Tags
|
|
173
|
+
|
|
174
|
+
Press `f` to favorite/unfavorite, `#` to add tags.
|
|
175
|
+
|
|
176
|
+
- **Favorites**: Star important sessions, sort by favorites to pin them at top
|
|
177
|
+
- **Tags**: 8 built-in tags (`bug-fix`, `feature`, `refactor`, etc.) + custom tags
|
|
178
|
+
- **Search integration**: Use `#tag` syntax or `fav` keyword in search
|
|
179
|
+
- **Persistent**: Stored in `~/.claude/claude-starter-meta.json`, survives restarts
|
|
180
|
+
|
|
148
181
|
## Features
|
|
149
182
|
|
|
150
183
|
| | Feature | Description |
|
|
151
184
|
|---|---|---|
|
|
152
185
|
| 🎨 | **Beautiful TUI** | Tokyo Night color scheme, split-pane layout, feels native in your terminal |
|
|
153
186
|
| ✨ | **New Session** | Launch a fresh conversation in one keystroke |
|
|
154
|
-
| 🔍 | **Instant Search** | Fuzzy search across everything,
|
|
187
|
+
| 🔍 | **Instant Search** | Fuzzy search across everything, supports `#tag` and `fav` |
|
|
188
|
+
| ⭐ | **Favorites** | Press `f` to star important sessions |
|
|
189
|
+
| 🏷️ | **Tags** | Press `#` to categorize with built-in + custom tags |
|
|
155
190
|
| 📂 | **Project Filter** | Press `p` to filter by project |
|
|
156
191
|
| ⚡ | **One-Key Resume** | Arrow, Enter, you're back in the conversation |
|
|
157
192
|
| 📋 | **Session Preview** | Full metadata + conversation history in the right panel |
|
|
158
|
-
| 🔀 | **Sort Modes** | Sort by time, size, messages, or
|
|
193
|
+
| 🔀 | **Sort Modes** | Sort by time, size, messages, project, or favorites |
|
|
159
194
|
| 📎 | **Copy ID** | Press `c` to copy session ID |
|
|
160
195
|
| 🧠 | **Smart CLI** | Auto-detects `mai-claude` vs `claude` |
|
|
161
196
|
| 🔒 | **100% Local** | No network, no telemetry, no data leaves your machine |
|
|
162
197
|
|
|
163
198
|
## Install
|
|
164
199
|
|
|
200
|
+
```bash
|
|
201
|
+
npm install -g claude-starter
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
Or install from source:
|
|
205
|
+
|
|
165
206
|
```bash
|
|
166
207
|
git clone https://github.com/Bojun-Vvibe/claude-starter.git
|
|
167
208
|
cd claude-starter
|
|
@@ -182,11 +223,13 @@ claude-starter
|
|
|
182
223
|
| `↑` `↓` | Navigate sessions |
|
|
183
224
|
| `Enter` | Start new / resume selected session |
|
|
184
225
|
| `n` | New session |
|
|
185
|
-
| `/` | Search |
|
|
226
|
+
| `/` | Search (supports `#tag` and `fav`) |
|
|
227
|
+
| `f` | Toggle favorite ⭐ |
|
|
228
|
+
| `#` | Add/manage tags 🏷️ |
|
|
186
229
|
| `Backspace` | Edit search, auto-exit when empty |
|
|
187
230
|
| `Esc` | Clear filter |
|
|
188
231
|
| `p` | Filter by project |
|
|
189
|
-
| `s` | Cycle sort mode |
|
|
232
|
+
| `s` | Cycle sort mode (time/size/messages/project/favorites) |
|
|
190
233
|
| `c` | Copy session ID |
|
|
191
234
|
| `Home` / `End` | Jump to first / last |
|
|
192
235
|
| `Ctrl-D` / `Ctrl-U` | Page down / up |
|
package/index.js
CHANGED
|
@@ -16,8 +16,10 @@
|
|
|
16
16
|
* / Start search (fuzzy filter)
|
|
17
17
|
* Esc Clear search / cancel
|
|
18
18
|
* p Filter by project (popup)
|
|
19
|
-
* s Cycle sort: time → size → messages → project
|
|
19
|
+
* s Cycle sort: time → size → messages → project → favorites
|
|
20
20
|
* n Start new session
|
|
21
|
+
* f Toggle favorite on selected session
|
|
22
|
+
* # Add/remove tags on selected session
|
|
21
23
|
* Home / End Jump to top / bottom
|
|
22
24
|
* Ctrl-D/U Page down / up
|
|
23
25
|
* c Copy session ID to clipboard
|
|
@@ -76,6 +78,53 @@ const PROJECT_COLORS = [
|
|
|
76
78
|
// ─── Paths ───────────────────────────────────────────────────────────────────
|
|
77
79
|
const CLAUDE_DIR = path.join(os.homedir(), '.claude');
|
|
78
80
|
const PROJECTS_DIR = path.join(CLAUDE_DIR, 'projects');
|
|
81
|
+
const META_FILE = path.join(CLAUDE_DIR, 'claude-starter-meta.json');
|
|
82
|
+
|
|
83
|
+
// ─── Session Meta (favorites & tags) ────────────────────────────────────────
|
|
84
|
+
// Stores user-defined metadata for sessions in a simple JSON file.
|
|
85
|
+
// Schema: { "sessions": { "<sessionId>": { "favorite": bool, "tags": string[] } } }
|
|
86
|
+
|
|
87
|
+
const DEFAULT_TAGS = ['bug-fix', 'feature', 'refactor', 'debug', 'review', 'config', 'docs', 'experiment'];
|
|
88
|
+
|
|
89
|
+
function loadMeta() {
|
|
90
|
+
try {
|
|
91
|
+
if (fs.existsSync(META_FILE)) {
|
|
92
|
+
return JSON.parse(fs.readFileSync(META_FILE, 'utf-8'));
|
|
93
|
+
}
|
|
94
|
+
} catch (e) { /* corrupt file, start fresh */ }
|
|
95
|
+
return { sessions: {} };
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function saveMeta(meta) {
|
|
99
|
+
try {
|
|
100
|
+
fs.writeFileSync(META_FILE, JSON.stringify(meta, null, 2), 'utf-8');
|
|
101
|
+
} catch (e) { /* silently fail */ }
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function getSessionMeta(meta, sessionId) {
|
|
105
|
+
return meta.sessions[sessionId] || { favorite: false, tags: [] };
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function toggleFavorite(meta, sessionId) {
|
|
109
|
+
if (!meta.sessions[sessionId]) meta.sessions[sessionId] = { favorite: false, tags: [] };
|
|
110
|
+
meta.sessions[sessionId].favorite = !meta.sessions[sessionId].favorite;
|
|
111
|
+
saveMeta(meta);
|
|
112
|
+
return meta.sessions[sessionId].favorite;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function setSessionTags(meta, sessionId, tags) {
|
|
116
|
+
if (!meta.sessions[sessionId]) meta.sessions[sessionId] = { favorite: false, tags: [] };
|
|
117
|
+
meta.sessions[sessionId].tags = tags;
|
|
118
|
+
saveMeta(meta);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function getAllUsedTags(meta) {
|
|
122
|
+
const tags = new Set();
|
|
123
|
+
for (const s of Object.values(meta.sessions)) {
|
|
124
|
+
if (s.tags) s.tags.forEach(t => tags.add(t));
|
|
125
|
+
}
|
|
126
|
+
return [...tags];
|
|
127
|
+
}
|
|
79
128
|
|
|
80
129
|
// ─── Data Layer ──────────────────────────────────────────────────────────────
|
|
81
130
|
|
|
@@ -110,6 +159,7 @@ function loadSessionQuick(filePath, projectName) {
|
|
|
110
159
|
let version = '', gitBranch = '', cwd = '';
|
|
111
160
|
let firstUserMsg = '';
|
|
112
161
|
let userMsgCount = 0;
|
|
162
|
+
let customTitle = '';
|
|
113
163
|
|
|
114
164
|
const headLines = headStr.split('\n').filter(Boolean);
|
|
115
165
|
for (const line of headLines) {
|
|
@@ -121,6 +171,7 @@ function loadSessionQuick(filePath, projectName) {
|
|
|
121
171
|
if (!version && d.version) version = d.version;
|
|
122
172
|
if (!gitBranch && d.gitBranch) gitBranch = d.gitBranch;
|
|
123
173
|
if (!cwd && d.cwd) cwd = d.cwd;
|
|
174
|
+
if (d.type === 'custom-title' && d.customTitle) customTitle = d.customTitle;
|
|
124
175
|
if (d.type === 'user') {
|
|
125
176
|
userMsgCount++;
|
|
126
177
|
if (!firstUserMsg) firstUserMsg = extractUserText(d);
|
|
@@ -135,6 +186,7 @@ function loadSessionQuick(filePath, projectName) {
|
|
|
135
186
|
const d = JSON.parse(line);
|
|
136
187
|
if (d.timestamp) lastTs = d.timestamp;
|
|
137
188
|
if (d.type === 'user') userMsgCount++;
|
|
189
|
+
if (d.type === 'custom-title' && d.customTitle) customTitle = d.customTitle;
|
|
138
190
|
} catch (e) { /* partial line */ }
|
|
139
191
|
}
|
|
140
192
|
}
|
|
@@ -157,6 +209,7 @@ function loadSessionQuick(filePath, projectName) {
|
|
|
157
209
|
return {
|
|
158
210
|
sessionId, project: projectName,
|
|
159
211
|
topic: topic || '(no user messages)',
|
|
212
|
+
customTitle,
|
|
160
213
|
firstTs, lastTs, version, gitBranch, cwd,
|
|
161
214
|
fileSize: stat.size, duration: durationStr,
|
|
162
215
|
estimatedMessages, filePath, _detailLoaded: false,
|
|
@@ -189,6 +242,7 @@ function loadSessionDetail(session) {
|
|
|
189
242
|
for (const line of lines) {
|
|
190
243
|
try {
|
|
191
244
|
const d = JSON.parse(line);
|
|
245
|
+
if (d.type === 'custom-title' && d.customTitle) session.customTitle = d.customTitle;
|
|
192
246
|
if (d.type === 'user') {
|
|
193
247
|
totalMessages++;
|
|
194
248
|
const text = extractUserText(d);
|
|
@@ -242,7 +296,11 @@ function loadAllSessions() {
|
|
|
242
296
|
for (const file of files) {
|
|
243
297
|
try {
|
|
244
298
|
const session = loadSessionQuick(path.join(projPath, file), projectName);
|
|
245
|
-
|
|
299
|
+
// Skip sessions without timestamps, without real user messages, or warmup sessions
|
|
300
|
+
if (session.firstTs
|
|
301
|
+
&& session.topic !== '(no user messages)'
|
|
302
|
+
&& !/^warmup$/i.test(session.topic.trim())
|
|
303
|
+
) sessions.push(session);
|
|
246
304
|
} catch (e) { /* skip */ }
|
|
247
305
|
}
|
|
248
306
|
}
|
|
@@ -312,6 +370,7 @@ function runListMode(limit) {
|
|
|
312
370
|
|
|
313
371
|
function createApp() {
|
|
314
372
|
const allSessions = loadAllSessions();
|
|
373
|
+
const meta = loadMeta();
|
|
315
374
|
let filteredSessions = [...allSessions];
|
|
316
375
|
let selectedIndex = -1; // -1 = "New Session", 0+ = session index
|
|
317
376
|
let filterText = '';
|
|
@@ -340,11 +399,17 @@ function createApp() {
|
|
|
340
399
|
const title = '{bold}{#7aa2f7-fg}🚀 Claude Starter{/}';
|
|
341
400
|
const count = `{#9ece6a-fg}${filteredSessions.length}{/}{#565f89-fg}/${allSessions.length} sessions{/}`;
|
|
342
401
|
const proj = `{#bb9af7-fg}${uniqueProjects.length}{/}{#565f89-fg} projects{/}`;
|
|
402
|
+
const favCount = allSessions.filter(s => getSessionMeta(meta, s.sessionId).favorite).length;
|
|
403
|
+
const fav = favCount > 0 ? `{#e0af68-fg}⭐${favCount}{/}` : '';
|
|
343
404
|
const sort = `{#73daca-fg}↕${sortMode}{/}`;
|
|
344
405
|
const search = isSearchMode
|
|
345
406
|
? `{#e0af68-fg}/ ${filterText}▌{/}`
|
|
346
407
|
: (filterText ? `{#e0af68-fg}/ ${filterText}{/}` : '');
|
|
347
|
-
|
|
408
|
+
let parts = [title, count, proj];
|
|
409
|
+
if (fav) parts.push(fav);
|
|
410
|
+
parts.push(sort);
|
|
411
|
+
if (search) parts.push(search);
|
|
412
|
+
header.setContent(`\n ${parts.join(' {#414868-fg}│{/} ')}`);
|
|
348
413
|
}
|
|
349
414
|
|
|
350
415
|
blessed.line({ parent: screen, top: 3, left: 0, width: '100%', orientation: 'horizontal', style: { fg: '#414868' } });
|
|
@@ -393,7 +458,8 @@ function createApp() {
|
|
|
393
458
|
'{#7aa2f7-fg}{bold}↵{/} {#565f89-fg}Start/Resume{/}',
|
|
394
459
|
'{#7aa2f7-fg}{bold}n{/} {#565f89-fg}New{/}',
|
|
395
460
|
'{#7aa2f7-fg}{bold}/{/} {#565f89-fg}Search{/}',
|
|
396
|
-
'{#7aa2f7-fg}{bold}
|
|
461
|
+
'{#7aa2f7-fg}{bold}f{/} {#565f89-fg}Fav{/}',
|
|
462
|
+
'{#7aa2f7-fg}{bold}#{/} {#565f89-fg}Tag{/}',
|
|
397
463
|
'{#7aa2f7-fg}{bold}p{/} {#565f89-fg}Project{/}',
|
|
398
464
|
'{#7aa2f7-fg}{bold}s{/} {#565f89-fg}Sort{/}',
|
|
399
465
|
'{#7aa2f7-fg}{bold}c{/} {#565f89-fg}Copy ID{/}',
|
|
@@ -448,16 +514,37 @@ function createApp() {
|
|
|
448
514
|
|
|
449
515
|
const sessionItems = filteredSessions.map((session) => {
|
|
450
516
|
const color = getProjectColor(session.project, projectColorMap);
|
|
451
|
-
const
|
|
517
|
+
const sm = getSessionMeta(meta, session.sessionId);
|
|
518
|
+
const favIcon = sm.favorite ? '{#e0af68-fg}⭐{/}' : ' ';
|
|
519
|
+
const proj = `{${color}-fg}${session.project.substring(0, 12).padEnd(12)}{/}`;
|
|
452
520
|
const time = `{#e0af68-fg}${formatTimestamp(session.lastTs).padEnd(16)}{/}`;
|
|
453
521
|
const msgs = `{#7aa2f7-fg}${String(session.estimatedMessages).padStart(4)}{/}{#565f89-fg}m{/}`;
|
|
454
522
|
|
|
455
|
-
const fixedLen =
|
|
456
|
-
const topicMaxLen = Math.max(
|
|
457
|
-
let topic = session.topic;
|
|
458
|
-
|
|
523
|
+
const fixedLen = 2 + 12 + 1 + 16 + 1 + 5 + 2 + 3;
|
|
524
|
+
const topicMaxLen = Math.max(10, listW - fixedLen);
|
|
525
|
+
let topic = session.customTitle || session.topic;
|
|
526
|
+
|
|
527
|
+
// Append tags inline after topic
|
|
528
|
+
const tagStr = sm.tags.length > 0
|
|
529
|
+
? ' ' + sm.tags.map(t => `#${t}`).join(' ')
|
|
530
|
+
: '';
|
|
531
|
+
|
|
532
|
+
let display = topic + tagStr;
|
|
533
|
+
if (display.length > topicMaxLen) display = display.substring(0, topicMaxLen) + '…';
|
|
534
|
+
|
|
535
|
+
// Split display back into topic part and tag part for coloring
|
|
536
|
+
const topicPart = display.substring(0, Math.min(topic.length, topicMaxLen));
|
|
537
|
+
const tagPart = display.substring(topicPart.length);
|
|
459
538
|
|
|
460
|
-
|
|
539
|
+
let label = `${favIcon}${proj} ${time} ${msgs} `;
|
|
540
|
+
if (session.customTitle) {
|
|
541
|
+
label += `{#73daca-fg}{bold}${esc(topicPart)}{/}`;
|
|
542
|
+
} else {
|
|
543
|
+
label += `{#a9b1d6-fg}${esc(topicPart)}{/}`;
|
|
544
|
+
}
|
|
545
|
+
if (tagPart) label += `{#f7768e-fg}${esc(tagPart)}{/}`;
|
|
546
|
+
|
|
547
|
+
return label;
|
|
461
548
|
});
|
|
462
549
|
|
|
463
550
|
const items = [NEW_SESSION_LABEL, ...sessionItems];
|
|
@@ -495,10 +582,16 @@ function createApp() {
|
|
|
495
582
|
loadSessionDetail(session);
|
|
496
583
|
|
|
497
584
|
const color = getProjectColor(session.project, projectColorMap);
|
|
585
|
+
const sm = getSessionMeta(meta, session.sessionId);
|
|
498
586
|
let c = '';
|
|
499
587
|
const sep = ` {#414868-fg}${'─'.repeat(44)}{/}`;
|
|
500
588
|
|
|
501
|
-
|
|
589
|
+
// Title with favorite indicator
|
|
590
|
+
const favLabel = sm.favorite ? ' {#e0af68-fg}⭐{/}' : '';
|
|
591
|
+
c += `\n {${color}-fg}{bold}█ ${session.project}{/}${favLabel}\n`;
|
|
592
|
+
if (session.customTitle) {
|
|
593
|
+
c += ` {#73daca-fg}{bold}📌 ${esc(session.customTitle)}{/}\n`;
|
|
594
|
+
}
|
|
502
595
|
c += sep + '\n\n';
|
|
503
596
|
|
|
504
597
|
const fields = [
|
|
@@ -517,6 +610,13 @@ function createApp() {
|
|
|
517
610
|
c += ` {#565f89-fg}${label.padEnd(12)}{/} ${value}\n`;
|
|
518
611
|
}
|
|
519
612
|
|
|
613
|
+
// Tags section
|
|
614
|
+
if (sm.tags.length > 0) {
|
|
615
|
+
const tagChips = sm.tags.map(t => `{#414868-fg}[{/}{#f7768e-fg}#${t}{/}{#414868-fg}]{/}`).join(' ');
|
|
616
|
+
c += `\n {#f7768e-fg}{bold}🏷️ Tags{/}\n`;
|
|
617
|
+
c += ` ${tagChips}\n`;
|
|
618
|
+
}
|
|
619
|
+
|
|
520
620
|
if (session.toolsUsed && session.toolsUsed.length > 0) {
|
|
521
621
|
c += `\n {#7dcfff-fg}{bold}Tools Used{/}\n`;
|
|
522
622
|
const chips = session.toolsUsed.slice(0, 10).map(t => `{#414868-fg}[{/}{#7dcfff-fg}${t}{/}{#414868-fg}]{/}`).join(' ');
|
|
@@ -570,8 +670,21 @@ function createApp() {
|
|
|
570
670
|
} else {
|
|
571
671
|
const terms = filterText.toLowerCase().split(/\s+/);
|
|
572
672
|
filteredSessions = allSessions.filter(s => {
|
|
573
|
-
const
|
|
574
|
-
|
|
673
|
+
const sm = getSessionMeta(meta, s.sessionId);
|
|
674
|
+
const haystack = [s.project, s.topic, s.customTitle || '', s.gitBranch || '', s.sessionId, ...(s.userMessages || [])].join(' ').toLowerCase();
|
|
675
|
+
|
|
676
|
+
return terms.every(t => {
|
|
677
|
+
// #tag syntax: match against session tags
|
|
678
|
+
if (t.startsWith('#') && t.length > 1) {
|
|
679
|
+
const tagQuery = t.substring(1);
|
|
680
|
+
return sm.tags.some(tag => tag.toLowerCase().includes(tagQuery));
|
|
681
|
+
}
|
|
682
|
+
// ⭐ or "fav" keyword: match only favorited sessions
|
|
683
|
+
if (t === '⭐' || t === 'fav' || t === 'favorite' || t === 'favorites') {
|
|
684
|
+
return sm.favorite;
|
|
685
|
+
}
|
|
686
|
+
return haystack.includes(t);
|
|
687
|
+
});
|
|
575
688
|
});
|
|
576
689
|
}
|
|
577
690
|
selectedIndex = Math.min(selectedIndex, Math.max(-1, filteredSessions.length - 1));
|
|
@@ -585,13 +698,19 @@ function createApp() {
|
|
|
585
698
|
|
|
586
699
|
// ─── Sort ──────────────────────────────────────────────────────────────
|
|
587
700
|
function cycleSort() {
|
|
588
|
-
const modes = ['time', 'size', 'messages', 'project'];
|
|
701
|
+
const modes = ['time', 'size', 'messages', 'project', 'favorites'];
|
|
589
702
|
sortMode = modes[(modes.indexOf(sortMode) + 1) % modes.length];
|
|
590
703
|
const sorters = {
|
|
591
704
|
time: (a, b) => (new Date(b.lastTs || 0).getTime()) - (new Date(a.lastTs || 0).getTime()),
|
|
592
705
|
size: (a, b) => b.fileSize - a.fileSize,
|
|
593
706
|
messages: (a, b) => b.estimatedMessages - a.estimatedMessages,
|
|
594
707
|
project: (a, b) => a.project.localeCompare(b.project) || (new Date(b.lastTs || 0).getTime()) - (new Date(a.lastTs || 0).getTime()),
|
|
708
|
+
favorites: (a, b) => {
|
|
709
|
+
const fa = getSessionMeta(meta, a.sessionId).favorite ? 1 : 0;
|
|
710
|
+
const fb = getSessionMeta(meta, b.sessionId).favorite ? 1 : 0;
|
|
711
|
+
if (fb !== fa) return fb - fa; // favorites first
|
|
712
|
+
return (new Date(b.lastTs || 0).getTime()) - (new Date(a.lastTs || 0).getTime());
|
|
713
|
+
},
|
|
595
714
|
};
|
|
596
715
|
allSessions.sort(sorters[sortMode]);
|
|
597
716
|
selectedIndex = 0;
|
|
@@ -670,14 +789,17 @@ function createApp() {
|
|
|
670
789
|
}
|
|
671
790
|
|
|
672
791
|
screen.key(['down'], () => {
|
|
792
|
+
if (popupOpen) return;
|
|
673
793
|
if (isSearchMode) { isSearchMode = false; updateHeader(); screen.render(); }
|
|
674
794
|
moveSelection(1);
|
|
675
795
|
});
|
|
676
796
|
screen.key(['up'], () => {
|
|
797
|
+
if (popupOpen) return;
|
|
677
798
|
if (isSearchMode) { isSearchMode = false; updateHeader(); screen.render(); }
|
|
678
799
|
moveSelection(-1);
|
|
679
800
|
});
|
|
680
801
|
screen.key(['home'], () => {
|
|
802
|
+
if (popupOpen) return;
|
|
681
803
|
if (isSearchMode) { isSearchMode = false; }
|
|
682
804
|
selectedIndex = -1;
|
|
683
805
|
suppressSelectEvent = true; listPanel.select(0); suppressSelectEvent = false;
|
|
@@ -685,6 +807,7 @@ function createApp() {
|
|
|
685
807
|
renderDetail(); updateHeader(); screen.render();
|
|
686
808
|
});
|
|
687
809
|
screen.key(['end'], () => {
|
|
810
|
+
if (popupOpen) return;
|
|
688
811
|
if (isSearchMode) { isSearchMode = false; }
|
|
689
812
|
selectedIndex = Math.max(0, filteredSessions.length - 1);
|
|
690
813
|
suppressSelectEvent = true; listPanel.select(selectedIndex + 1); suppressSelectEvent = false;
|
|
@@ -692,10 +815,12 @@ function createApp() {
|
|
|
692
815
|
renderDetail(); updateHeader(); screen.render();
|
|
693
816
|
});
|
|
694
817
|
screen.key(['pagedown', 'C-d'], () => {
|
|
818
|
+
if (popupOpen) return;
|
|
695
819
|
if (isSearchMode) { isSearchMode = false; updateHeader(); screen.render(); }
|
|
696
820
|
moveSelection(Math.floor((listPanel.height || 20) / 2));
|
|
697
821
|
});
|
|
698
822
|
screen.key(['pageup', 'C-u'], () => {
|
|
823
|
+
if (popupOpen) return;
|
|
699
824
|
if (isSearchMode) { isSearchMode = false; updateHeader(); screen.render(); }
|
|
700
825
|
moveSelection(-Math.floor((listPanel.height || 20) / 2));
|
|
701
826
|
});
|
|
@@ -789,6 +914,136 @@ function createApp() {
|
|
|
789
914
|
} catch (e) { /* silently fail */ }
|
|
790
915
|
});
|
|
791
916
|
|
|
917
|
+
// Toggle favorite
|
|
918
|
+
screen.key(['f'], () => {
|
|
919
|
+
if (isSearchMode || popupOpen) return;
|
|
920
|
+
if (selectedIndex < 0 || selectedIndex >= filteredSessions.length) return;
|
|
921
|
+
const session = filteredSessions[selectedIndex];
|
|
922
|
+
const nowFav = toggleFavorite(meta, session.sessionId);
|
|
923
|
+
const icon = nowFav ? '⭐' : '☆';
|
|
924
|
+
footer.setContent(`\n {#e0af68-fg}{bold}${icon} ${nowFav ? 'Favorited' : 'Unfavorited'}{/}`);
|
|
925
|
+
renderAll();
|
|
926
|
+
setTimeout(() => { updateFooter(); screen.render(); }, 1200);
|
|
927
|
+
});
|
|
928
|
+
|
|
929
|
+
// Tag management — handled via keypress since '#' is a shifted character
|
|
930
|
+
// that some terminal/blessed combos may not route through screen.key
|
|
931
|
+
screen.on('keypress', (ch, key) => {
|
|
932
|
+
if (ch === '#' && !isSearchMode && !popupOpen) {
|
|
933
|
+
if (selectedIndex < 0 || selectedIndex >= filteredSessions.length) return;
|
|
934
|
+
showTagPicker(filteredSessions[selectedIndex]);
|
|
935
|
+
}
|
|
936
|
+
});
|
|
937
|
+
|
|
938
|
+
function showTagPicker(session) {
|
|
939
|
+
const sm = getSessionMeta(meta, session.sessionId);
|
|
940
|
+
const currentTags = new Set(sm.tags);
|
|
941
|
+
|
|
942
|
+
// Build tag list: all known tags (defaults + used), with checkmarks for active ones
|
|
943
|
+
const usedTags = getAllUsedTags(meta);
|
|
944
|
+
const allTags = [...new Set([...DEFAULT_TAGS, ...usedTags])].sort();
|
|
945
|
+
|
|
946
|
+
const items = [
|
|
947
|
+
' {#9ece6a-fg}{bold}+ New custom tag…{/}',
|
|
948
|
+
...allTags.map(t => {
|
|
949
|
+
const checked = currentTags.has(t) ? '{#9ece6a-fg}✓{/}' : ' ';
|
|
950
|
+
return ` ${checked} {#f7768e-fg}#${t}{/}`;
|
|
951
|
+
}),
|
|
952
|
+
];
|
|
953
|
+
|
|
954
|
+
const popup = blessed.list({
|
|
955
|
+
parent: screen, top: 'center', left: 'center',
|
|
956
|
+
width: Math.min(45, Math.max(...items.map(i => i.replace(/\{[^}]*\}/g, '').length)) + 8),
|
|
957
|
+
height: Math.min(items.length + 4, 20),
|
|
958
|
+
label: ' {bold}{#f7768e-fg}🏷️ Tags{/} ',
|
|
959
|
+
tags: true, border: { type: 'line' },
|
|
960
|
+
style: {
|
|
961
|
+
border: { fg: '#f7768e' }, bg: '#24283b', fg: '#a9b1d6',
|
|
962
|
+
selected: { bg: '#3d59a1', fg: 'white', bold: true },
|
|
963
|
+
label: { fg: '#f7768e' },
|
|
964
|
+
},
|
|
965
|
+
items: items, keys: true, vi: true, mouse: true,
|
|
966
|
+
});
|
|
967
|
+
popupOpen = true;
|
|
968
|
+
popup.focus(); screen.render();
|
|
969
|
+
|
|
970
|
+
popup.on('select', (item, index) => {
|
|
971
|
+
if (index === 0) {
|
|
972
|
+
// New custom tag — show input
|
|
973
|
+
popup.destroy();
|
|
974
|
+
popupOpen = false;
|
|
975
|
+
showTagInput(session);
|
|
976
|
+
return;
|
|
977
|
+
}
|
|
978
|
+
// Toggle the selected tag
|
|
979
|
+
const tagName = allTags[index - 1];
|
|
980
|
+
if (currentTags.has(tagName)) {
|
|
981
|
+
currentTags.delete(tagName);
|
|
982
|
+
} else {
|
|
983
|
+
currentTags.add(tagName);
|
|
984
|
+
}
|
|
985
|
+
setSessionTags(meta, session.sessionId, [...currentTags]);
|
|
986
|
+
|
|
987
|
+
// Refresh the popup items to show updated checkmarks
|
|
988
|
+
const refreshedItems = [
|
|
989
|
+
' {#9ece6a-fg}{bold}+ New custom tag…{/}',
|
|
990
|
+
...allTags.map(t => {
|
|
991
|
+
const checked = currentTags.has(t) ? '{#9ece6a-fg}✓{/}' : ' ';
|
|
992
|
+
return ` ${checked} {#f7768e-fg}#${t}{/}`;
|
|
993
|
+
}),
|
|
994
|
+
];
|
|
995
|
+
popup.setItems(refreshedItems);
|
|
996
|
+
popup.select(index);
|
|
997
|
+
screen.render();
|
|
998
|
+
});
|
|
999
|
+
|
|
1000
|
+
popup.key(['escape', 'q'], () => {
|
|
1001
|
+
popup.destroy();
|
|
1002
|
+
popupOpen = false;
|
|
1003
|
+
renderAll();
|
|
1004
|
+
});
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
function showTagInput(session) {
|
|
1008
|
+
const inputBox = blessed.textbox({
|
|
1009
|
+
parent: screen, top: 'center', left: 'center',
|
|
1010
|
+
width: 40, height: 3,
|
|
1011
|
+
label: ' {bold}{#f7768e-fg}New Tag{/} ',
|
|
1012
|
+
tags: true, border: { type: 'line' },
|
|
1013
|
+
style: {
|
|
1014
|
+
border: { fg: '#f7768e' }, bg: '#24283b', fg: '#a9b1d6',
|
|
1015
|
+
label: { fg: '#f7768e' },
|
|
1016
|
+
},
|
|
1017
|
+
inputOnFocus: true,
|
|
1018
|
+
});
|
|
1019
|
+
popupOpen = true;
|
|
1020
|
+
inputBox.focus();
|
|
1021
|
+
screen.render();
|
|
1022
|
+
|
|
1023
|
+
inputBox.on('submit', (value) => {
|
|
1024
|
+
inputBox.destroy();
|
|
1025
|
+
popupOpen = false;
|
|
1026
|
+
const tagName = value.trim().toLowerCase().replace(/[^a-z0-9_-]/g, '');
|
|
1027
|
+
if (tagName) {
|
|
1028
|
+
const sm = getSessionMeta(meta, session.sessionId);
|
|
1029
|
+
const tags = new Set(sm.tags);
|
|
1030
|
+
tags.add(tagName);
|
|
1031
|
+
setSessionTags(meta, session.sessionId, [...tags]);
|
|
1032
|
+
footer.setContent(`\n {#9ece6a-fg}{bold}✓ Tagged:{/} {#f7768e-fg}#${tagName}{/}`);
|
|
1033
|
+
renderAll();
|
|
1034
|
+
setTimeout(() => { updateFooter(); screen.render(); }, 1500);
|
|
1035
|
+
} else {
|
|
1036
|
+
renderAll();
|
|
1037
|
+
}
|
|
1038
|
+
});
|
|
1039
|
+
|
|
1040
|
+
inputBox.on('cancel', () => {
|
|
1041
|
+
inputBox.destroy();
|
|
1042
|
+
popupOpen = false;
|
|
1043
|
+
renderAll();
|
|
1044
|
+
});
|
|
1045
|
+
}
|
|
1046
|
+
|
|
792
1047
|
screen.key(['s'], () => { if (!isSearchMode) cycleSort(); });
|
|
793
1048
|
screen.key(['p'], () => { if (!isSearchMode) showProjectPicker(); });
|
|
794
1049
|
screen.key(['escape'], () => {
|
|
@@ -859,9 +1114,11 @@ TUI Keyboard Shortcuts:
|
|
|
859
1114
|
↑/↓ Navigate sessions
|
|
860
1115
|
Enter Start new / resume selected session
|
|
861
1116
|
n Start new session
|
|
862
|
-
/ Search (fuzzy filter)
|
|
1117
|
+
/ Search (fuzzy filter, supports #tag and fav)
|
|
1118
|
+
f Toggle favorite ⭐ on selected session
|
|
1119
|
+
# Add/remove tags on selected session
|
|
863
1120
|
p Filter by project
|
|
864
|
-
s Cycle sort mode
|
|
1121
|
+
s Cycle sort mode (time/size/messages/project/favorites)
|
|
865
1122
|
c Copy session ID
|
|
866
1123
|
Home / End Jump to top / bottom
|
|
867
1124
|
Ctrl-D/U Page down / up
|