claude-starter 1.3.3 → 1.3.5

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.
Files changed (3) hide show
  1. package/README.md +113 -113
  2. package/index.js +139 -54
  3. package/package.json +6 -2
package/README.md CHANGED
@@ -11,8 +11,8 @@
11
11
  <h1 align="center">🚀 Claude Starter</h1>
12
12
 
13
13
  <p align="center">
14
- <strong>Claude Code 的主页。</strong>你的所有会话,一目了然。<br/>
15
- <strong>Your homepage for Claude Code.</strong> All your sessions, at a glance.
14
+ <strong>Your homepage for Claude Code.</strong> All your sessions, at a glance.<br/>
15
+ <strong>Claude Code 的主页。</strong>你的所有会话,一目了然。
16
16
  </p>
17
17
 
18
18
  <p align="center">
@@ -25,117 +25,7 @@
25
25
 
26
26
  ---
27
27
 
28
- # 🇨🇳 中文
29
-
30
- ## 痛点
31
-
32
- 用过 Claude Code 的 `/resume` 吗?它给你的是这样一坨东西:
33
-
34
- ```
35
- ? Select a conversation
36
- 3ee0f33a-b882-424f-9ba4-260342e4dd5b - 4/3/2026, 10:53:41 AM
37
- 87570bab-ee92-4681-9591-54abf2fcb486 - 4/3/2026, 10:18:55 AM
38
- ...200 个 UUID...
39
- ```
40
-
41
- 一堆 UUID,没有上下文,无法搜索。**想找到上周帮你调过 bug 的那个 session?祝你好运。**
42
-
43
- ## 解决方案
44
-
45
- **Claude Starter** 是一个精美的终端可视化工具,让你能像浏览网页一样浏览所有 Claude 历史会话。它是你的 **Claude 主页** —— 每次打开终端,`claude-starter` 一敲,所有 session 一目了然。
46
-
47
- ```bash
48
- claude-starter
49
- ```
50
-
51
- 精美的分屏 UI,Tokyo Night 配色。左侧列表一目了然,右侧实时预览对话详情。不是 UUID,是你**真正说过的话**。
52
-
53
- ## 🔍 搜索 — 杀手级功能
54
-
55
- 按 `/` 开始输入,**就这么简单**。无需按回车。
56
-
57
- 跨项目名、Git 分支、对话内容**全文实时搜索**。输入即过滤,`↑↓` 直接导航结果。
58
-
59
- - `auth` → 所有认证相关的对话
60
- - `refactor` → 上周的代码重构
61
- - `web-app fix` → 某个项目的 bug 修复
62
-
63
- **不需要管理模式,不需要确认。输入即搜,方向键即走。**
64
-
65
- ## 核心能力
66
-
67
- | | 功能 | 说明 |
68
- |---|---|---|
69
- | 🎨 | **精美 TUI** | Tokyo Night 配色,分屏布局,终端里的 App |
70
- | ✨ | **一键新建** | 列表顶部直接新建对话 |
71
- | 🔍 | **即时搜索** | `/` 全文搜索,无需回车 |
72
- | 📂 | **项目过滤** | `p` 按项目筛选 |
73
- | ⚡ | **秒级恢复** | 选中 → Enter → 回到对话 |
74
- | 📋 | **对话预览** | 右侧面板展示完整元数据和对话历史 |
75
- | 🔀 | **多种排序** | 时间 / 大小 / 消息数 / 项目 |
76
- | 📎 | **复制 ID** | `c` 一键复制到剪贴板 |
77
- | 🔒 | **权限模式** | `m` 设置权限模式,`d` 一键 danger 模式恢复 |
78
- | ✏️ | **重命名会话** | `r` 直接重命名,支持中文输入 |
79
- | 🗑️ | **删除会话** | `x` 删除不需要的会话 |
80
- | ⌨️ | **Vim 快捷键** | `j`/`k` 上下,`g`/`G` 跳顶/底 |
81
- | 🧠 | **智能 CLI** | 自动检测 `mai-claude` / `claude` |
82
- | 🔐 | **完全本地** | 不联网,不上传,不追踪 |
83
-
84
- ## 安装
85
-
86
- ```bash
87
- npm install -g claude-starter
88
- ```
89
-
90
- 或者从源码安装:
91
-
92
- ```bash
93
- git clone https://github.com/Bojun-Vvibe/claude-starter.git
94
- cd claude-starter
95
- npm install
96
- npm link
97
- ```
98
-
99
- 然后运行 `claude-starter`,就这么简单。
100
-
101
- ## CLI 参数
102
-
103
- ```bash
104
- claude-starter # 启动交互式 TUI
105
- claude-starter --list [N] # 打印最近 N 个会话(默认 30)
106
- claude-starter --version # 显示版本号
107
- claude-starter --update # 检查并更新到最新版本
108
- claude-starter --help # 显示帮助信息
109
- ```
110
-
111
- ## 快捷键
112
-
113
- | 按键 | 功能 |
114
- |:---:|------|
115
- | `↑` `↓` / `j` `k` | 上下导航 |
116
- | `Enter` | 新建 / 恢复对话 |
117
- | `n` | 直接新建 |
118
- | `d` | Danger 模式恢复(bypassPermissions) |
119
- | `m` | 权限模式选择器 |
120
- | `r` | 重命名会话 |
121
- | `/` | 搜索 |
122
- | `Backspace` | 删除搜索字符,删空自动退出 |
123
- | `Esc` | 清空搜索 |
124
- | `p` | 按项目过滤 |
125
- | `s` | 切换排序(时间/大小/消息数/项目) |
126
- | `c` | 复制 Session ID |
127
- | `x` / `Delete` | 删除会话 |
128
- | `g` / `G` | 跳到顶 / 底 |
129
- | `Ctrl-D` / `Ctrl-U` | 翻页 |
130
- | `q` / `Ctrl-C` | 退出 |
131
-
132
- ## 原理
133
-
134
- 读取 `~/.claude/projects/` 下的 JSONL 会话文件,解析元数据和对话内容。200 个 session 加载耗时 ~10ms。**所有数据留在本地,不联网。**
135
-
136
- ---
137
-
138
- # 🇬🇧 English
28
+ # English
139
29
 
140
30
  ## The Problem
141
31
 
@@ -256,6 +146,116 @@ MIT
256
146
 
257
147
  ---
258
148
 
149
+ # 中文
150
+
151
+ ## 痛点
152
+
153
+ 用过 Claude Code 的 `/resume` 吗?它给你的是这样一坨东西:
154
+
155
+ ```
156
+ ? Select a conversation
157
+ 3ee0f33a-b882-424f-9ba4-260342e4dd5b - 4/3/2026, 10:53:41 AM
158
+ 87570bab-ee92-4681-9591-54abf2fcb486 - 4/3/2026, 10:18:55 AM
159
+ ...200 个 UUID...
160
+ ```
161
+
162
+ 一堆 UUID,没有上下文,无法搜索。**想找到上周帮你调过 bug 的那个 session?祝你好运。**
163
+
164
+ ## 解决方案
165
+
166
+ **Claude Starter** 是一个精美的终端可视化工具,让你能像浏览网页一样浏览所有 Claude 历史会话。它是你的 **Claude 主页** —— 每次打开终端,`claude-starter` 一敲,所有 session 一目了然。
167
+
168
+ ```bash
169
+ claude-starter
170
+ ```
171
+
172
+ 精美的分屏 UI,Tokyo Night 配色。左侧列表一目了然,右侧实时预览对话详情。不是 UUID,是你**真正说过的话**。
173
+
174
+ ## 🔍 搜索 — 杀手级功能
175
+
176
+ 按 `/` 开始输入,**就这么简单**。无需按回车。
177
+
178
+ 跨项目名、Git 分支、对话内容**全文实时搜索**。输入即过滤,`↑↓` 直接导航结果。
179
+
180
+ - `auth` → 所有认证相关的对话
181
+ - `refactor` → 上周的代码重构
182
+ - `web-app fix` → 某个项目的 bug 修复
183
+
184
+ **不需要管理模式,不需要确认。输入即搜,方向键即走。**
185
+
186
+ ## 核心能力
187
+
188
+ | | 功能 | 说明 |
189
+ |---|---|---|
190
+ | 🎨 | **精美 TUI** | Tokyo Night 配色,分屏布局,终端里的 App |
191
+ | ✨ | **一键新建** | 列表顶部直接新建对话 |
192
+ | 🔍 | **即时搜索** | `/` 全文搜索,无需回车 |
193
+ | 📂 | **项目过滤** | `p` 按项目筛选 |
194
+ | ⚡ | **秒级恢复** | 选中 → Enter → 回到对话 |
195
+ | 📋 | **对话预览** | 右侧面板展示完整元数据和对话历史 |
196
+ | 🔀 | **多种排序** | 时间 / 大小 / 消息数 / 项目 |
197
+ | 📎 | **复制 ID** | `c` 一键复制到剪贴板 |
198
+ | 🔒 | **权限模式** | `m` 设置权限模式,`d` 一键 danger 模式恢复 |
199
+ | ✏️ | **重命名会话** | `r` 直接重命名,支持中文输入 |
200
+ | 🗑️ | **删除会话** | `x` 删除不需要的会话 |
201
+ | ⌨️ | **Vim 快捷键** | `j`/`k` 上下,`g`/`G` 跳顶/底 |
202
+ | 🧠 | **智能 CLI** | 自动检测 `mai-claude` / `claude` |
203
+ | 🔐 | **完全本地** | 不联网,不上传,不追踪 |
204
+
205
+ ## 安装
206
+
207
+ ```bash
208
+ npm install -g claude-starter
209
+ ```
210
+
211
+ 或者从源码安装:
212
+
213
+ ```bash
214
+ git clone https://github.com/Bojun-Vvibe/claude-starter.git
215
+ cd claude-starter
216
+ npm install
217
+ npm link
218
+ ```
219
+
220
+ 然后运行 `claude-starter`,就这么简单。
221
+
222
+ ## CLI 参数
223
+
224
+ ```bash
225
+ claude-starter # 启动交互式 TUI
226
+ claude-starter --list [N] # 打印最近 N 个会话(默认 30)
227
+ claude-starter --version # 显示版本号
228
+ claude-starter --update # 检查并更新到最新版本
229
+ claude-starter --help # 显示帮助信息
230
+ ```
231
+
232
+ ## 快捷键
233
+
234
+ | 按键 | 功能 |
235
+ |:---:|------|
236
+ | `↑` `↓` / `j` `k` | 上下导航 |
237
+ | `Enter` | 新建 / 恢复对话 |
238
+ | `n` | 直接新建 |
239
+ | `d` | Danger 模式恢复(bypassPermissions) |
240
+ | `m` | 权限模式选择器 |
241
+ | `r` | 重命名会话 |
242
+ | `/` | 搜索 |
243
+ | `Backspace` | 删除搜索字符,删空自动退出 |
244
+ | `Esc` | 清空搜索 |
245
+ | `p` | 按项目过滤 |
246
+ | `s` | 切换排序(时间/大小/消息数/项目) |
247
+ | `c` | 复制 Session ID |
248
+ | `x` / `Delete` | 删除会话 |
249
+ | `g` / `G` | 跳到顶 / 底 |
250
+ | `Ctrl-D` / `Ctrl-U` | 翻页 |
251
+ | `q` / `Ctrl-C` | 退出 |
252
+
253
+ ## 原理
254
+
255
+ 读取 `~/.claude/projects/` 下的 JSONL 会话文件,解析元数据和对话内容。200 个 session 加载耗时 ~10ms。**所有数据留在本地,不联网。**
256
+
257
+ ---
258
+
259
259
  <p align="center">
260
260
  <sub>Built with 💜 by <a href="https://github.com/Bojun-Vvibe">Bojun</a> — powered by Claude Code itself</sub>
261
261
  </p>
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(8192, stat.size));
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 > 8192) {
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 - 8192);
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 - 8192) break; // already read entire file
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) { /* partial line */ }
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') userMsgCount++;
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
  }
@@ -1465,58 +1508,99 @@ function createApp() {
1465
1508
  listPanel.focus();
1466
1509
  }
1467
1510
 
1468
- // ─── Entry Point ─────────────────────────────────────────────────────────────
1511
+ // ─── Exports for Testing ────────────────────────────────────────────────────
1512
+ // When required as a module (e.g. by tests), export helpers without launching
1513
+ // the CLI / TUI. The entry-point logic only runs when executed directly.
1514
+
1515
+ if (typeof module !== 'undefined') {
1516
+ module.exports = {
1517
+ // Data helpers
1518
+ getProjectDisplayName,
1519
+ extractUserText,
1520
+ loadSessionQuick,
1521
+ loadSessionDetail,
1522
+ loadAllSessions,
1523
+ // Formatting
1524
+ formatTimestamp,
1525
+ formatFileSize,
1526
+ getProjectColor,
1527
+ esc,
1528
+ // Meta
1529
+ loadMeta,
1530
+ saveMeta,
1531
+ getSessionMeta,
1532
+ getEffectivePermissionMode,
1533
+ setSessionPermissionMode,
1534
+ setGlobalPermissionMode,
1535
+ // Constants
1536
+ PERMISSION_MODES,
1537
+ PROJECT_COLORS,
1538
+ CLAUDE_DIR,
1539
+ PROJECTS_DIR,
1540
+ META_FILE,
1541
+ // CLI
1542
+ detectCLI,
1543
+ // List mode (for integration tests)
1544
+ runListMode,
1545
+ // TUI (for interaction tests)
1546
+ createApp,
1547
+ };
1548
+ }
1469
1549
 
1470
- const PKG = JSON.parse(fs.readFileSync(path.join(__dirname, 'package.json'), 'utf-8'));
1550
+ // ─── Entry Point ─────────────────────────────────────────────────────────────
1551
+ // Only run CLI/TUI when executed directly (not when required as a module).
1471
1552
 
1472
- const args = process.argv.slice(2);
1553
+ if (require.main === module) {
1554
+ const PKG = JSON.parse(fs.readFileSync(path.join(__dirname, 'package.json'), 'utf-8'));
1473
1555
 
1474
- if (args.includes('--version') || args.includes('-v') || args.includes('-V')) {
1475
- console.log(`claude-starter v${PKG.version}`);
1476
- process.exit(0);
1477
- }
1556
+ const args = process.argv.slice(2);
1478
1557
 
1479
- if (args.includes('--update') || args.includes('-u')) {
1480
- const C = {
1481
- reset: '\x1b[0m', dim: '\x1b[2m', bold: '\x1b[1m',
1482
- cyan: '\x1b[36m', yellow: '\x1b[33m', green: '\x1b[32m',
1483
- red: '\x1b[31m',
1484
- };
1485
- console.log(`\n${C.cyan}🔄 Checking for updates…${C.reset}\n`);
1558
+ if (args.includes('--version') || args.includes('-v') || args.includes('-V')) {
1559
+ console.log(`claude-starter v${PKG.version}`);
1560
+ process.exit(0);
1561
+ }
1486
1562
 
1487
- try {
1488
- const latest = execSync('npm view claude-starter version 2>/dev/null', {
1489
- stdio: ['pipe', 'pipe', 'pipe'],
1490
- timeout: 10000,
1491
- }).toString().trim();
1563
+ if (args.includes('--update') || args.includes('-u')) {
1564
+ const C = {
1565
+ reset: '\x1b[0m', dim: '\x1b[2m', bold: '\x1b[1m',
1566
+ cyan: '\x1b[36m', yellow: '\x1b[33m', green: '\x1b[32m',
1567
+ red: '\x1b[31m',
1568
+ };
1569
+ console.log(`\n${C.cyan}🔄 Checking for updates…${C.reset}\n`);
1492
1570
 
1493
- if (latest === PKG.version) {
1494
- console.log(`${C.green}✓ Already on the latest version (v${PKG.version})${C.reset}\n`);
1495
- process.exit(0);
1496
- }
1571
+ try {
1572
+ const latest = execSync('npm view claude-starter version 2>/dev/null', {
1573
+ stdio: ['pipe', 'pipe', 'pipe'],
1574
+ timeout: 10000,
1575
+ }).toString().trim();
1576
+
1577
+ if (latest === PKG.version) {
1578
+ console.log(`${C.green}✓ Already on the latest version (v${PKG.version})${C.reset}\n`);
1579
+ process.exit(0);
1580
+ }
1497
1581
 
1498
- console.log(`${C.yellow} Current: v${PKG.version}${C.reset}`);
1499
- console.log(`${C.green} Latest: v${latest}${C.reset}\n`);
1500
- console.log(`${C.cyan}📦 Updating…${C.reset}\n`);
1582
+ console.log(`${C.yellow} Current: v${PKG.version}${C.reset}`);
1583
+ console.log(`${C.green} Latest: v${latest}${C.reset}\n`);
1584
+ console.log(`${C.cyan}📦 Updating…${C.reset}\n`);
1501
1585
 
1502
- try {
1503
- execSync('npm install -g claude-starter@latest', { stdio: 'inherit', timeout: 60000 });
1504
- console.log(`\n${C.green}${C.bold}✓ Updated to v${latest}${C.reset}\n`);
1586
+ try {
1587
+ execSync('npm install -g claude-starter@latest', { stdio: 'inherit', timeout: 60000 });
1588
+ console.log(`\n${C.green}${C.bold}✓ Updated to v${latest}${C.reset}\n`);
1589
+ } catch (e) {
1590
+ console.error(`\n${C.red}✗ Update failed. Try manually:${C.reset}`);
1591
+ console.log(`${C.yellow} npm install -g claude-starter@latest${C.reset}\n`);
1592
+ process.exit(1);
1593
+ }
1505
1594
  } catch (e) {
1506
- console.error(`\n${C.red}✗ Update failed. Try manually:${C.reset}`);
1507
- console.log(`${C.yellow} npm install -g claude-starter@latest${C.reset}\n`);
1595
+ console.error(`${C.red}✗ Could not check for updates (network error or npm not found)${C.reset}\n`);
1508
1596
  process.exit(1);
1509
1597
  }
1510
- } catch (e) {
1511
- console.error(`${C.red}✗ Could not check for updates (network error or npm not found)${C.reset}\n`);
1512
- process.exit(1);
1513
- }
1514
1598
 
1515
- process.exit(0);
1516
- }
1599
+ process.exit(0);
1600
+ }
1517
1601
 
1518
- if (args.includes('--help') || args.includes('-h')) {
1519
- console.log(`
1602
+ if (args.includes('--help') || args.includes('-h')) {
1603
+ console.log(`
1520
1604
  \x1b[36m🚀 Claude Starter\x1b[0m \x1b[2mv${PKG.version}\x1b[0m
1521
1605
 
1522
1606
  Usage:
@@ -1542,14 +1626,15 @@ TUI Keyboard Shortcuts:
1542
1626
  Esc Clear filter
1543
1627
  q / Ctrl-C Quit
1544
1628
  `);
1545
- process.exit(0);
1546
- }
1629
+ process.exit(0);
1630
+ }
1547
1631
 
1548
- if (args.includes('--list') || args.includes('-l')) {
1549
- const limitIdx = args.indexOf('--list') !== -1 ? args.indexOf('--list') : args.indexOf('-l');
1550
- const limit = parseInt(args[limitIdx + 1]) || 30;
1551
- runListMode(limit);
1552
- process.exit(0);
1553
- }
1632
+ if (args.includes('--list') || args.includes('-l')) {
1633
+ const limitIdx = args.indexOf('--list') !== -1 ? args.indexOf('--list') : args.indexOf('-l');
1634
+ const limit = parseInt(args[limitIdx + 1]) || 30;
1635
+ runListMode(limit);
1636
+ process.exit(0);
1637
+ }
1554
1638
 
1555
- createApp();
1639
+ createApp();
1640
+ }
package/package.json CHANGED
@@ -1,13 +1,17 @@
1
1
  {
2
2
  "name": "claude-starter",
3
- "version": "1.3.3",
3
+ "version": "1.3.5",
4
4
  "description": "A beautiful terminal UI for managing Claude Code sessions — start new or resume past conversations",
5
5
  "main": "index.js",
6
6
  "bin": {
7
7
  "claude-starter": "./index.js"
8
8
  },
9
9
  "scripts": {
10
- "start": "node index.js"
10
+ "start": "node index.js",
11
+ "test": "node --test test.js",
12
+ "test:tui": "node --test --test-force-exit test-tui.js",
13
+ "test:all": "node --test --test-force-exit test.js test-tui.js",
14
+ "prepublishOnly": "npm run test:all"
11
15
  },
12
16
  "keywords": [
13
17
  "claude",