cc-goto-work 0.8.0 → 0.8.3

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 +21 -21
  2. package/bin/cli.js +711 -483
  3. package/package.json +30 -30
package/README.md CHANGED
@@ -1,21 +1,21 @@
1
- # cc-goto-work
2
-
3
- 让 Claude Code 自动继续未完成的工作。
4
-
5
- ## 使用
6
-
7
- ```bash
8
- npx cc-goto-work
9
- ```
10
-
11
- 运行后会显示交互式菜单,可以选择:
12
-
13
- - **1** - 完整安装 (下载 + 配置 API + 配置 Hook)
14
- - **2** - 仅下载二进制文件
15
- - **3** - 仅配置 API 设置
16
- - **4** - 仅配置 Claude Code Hook
17
- - **0** - 退出
18
-
19
- ## 许可证
20
-
21
- MIT License
1
+ # cc-goto-work
2
+
3
+ 让 Claude Code 自动继续未完成的工作。
4
+
5
+ ## 使用
6
+
7
+ ```bash
8
+ npx cc-goto-work
9
+ ```
10
+
11
+ 运行后会显示交互式菜单,可以选择:
12
+
13
+ - **1** - 完整安装 (下载 + 配置 API + 配置 Hook)
14
+ - **2** - 仅下载二进制文件
15
+ - **3** - 仅配置 API 设置
16
+ - **4** - 仅配置 Claude Code Hook
17
+ - **0** - 退出
18
+
19
+ ## 许可证
20
+
21
+ MIT License
package/bin/cli.js CHANGED
@@ -1,483 +1,711 @@
1
- #!/usr/bin/env node
2
-
3
- const readline = require('readline');
4
- const https = require('https');
5
- const fs = require('fs');
6
- const path = require('path');
7
- const os = require('os');
8
- const { spawn } = require('child_process');
9
-
10
- // ============================================================================
11
- // Constants
12
- // ============================================================================
13
-
14
- const REPO = 'pdxxxx/cc-goto-work';
15
- const INSTALL_DIR = path.join(os.homedir(), '.claude', 'cc-goto-work');
16
- const CONFIG_FILE = path.join(INSTALL_DIR, 'config.yaml');
17
- const CLAUDE_SETTINGS_DIR = path.join(os.homedir(), '.claude');
18
- const CLAUDE_SETTINGS_FILE = path.join(CLAUDE_SETTINGS_DIR, 'settings.json');
19
-
20
- // Colors (ANSI escape codes)
21
- const colors = {
22
- reset: '\x1b[0m',
23
- red: '\x1b[31m',
24
- green: '\x1b[32m',
25
- yellow: '\x1b[33m',
26
- blue: '\x1b[34m',
27
- cyan: '\x1b[36m',
28
- bold: '\x1b[1m',
29
- };
30
-
31
- // ============================================================================
32
- // Utilities
33
- // ============================================================================
34
-
35
- function print(msg) {
36
- console.log(msg);
37
- }
38
-
39
- function printBanner() {
40
- print(`${colors.cyan}${colors.bold}`);
41
- print('╔════════════════════════════════════════════════════════════╗');
42
- print('║ cc-goto-work 安装程序 ║');
43
- print('║ 让 Claude Code 自动继续未完成的工作 ║');
44
- print('╚════════════════════════════════════════════════════════════╝');
45
- print(`${colors.reset}`);
46
- }
47
-
48
- function printStep(msg) {
49
- print(`${colors.green}▶${colors.reset} ${msg}`);
50
- }
51
-
52
- function printSuccess(msg) {
53
- print(`${colors.green}✔${colors.reset} ${msg}`);
54
- }
55
-
56
- function printWarning(msg) {
57
- print(`${colors.yellow}⚠${colors.reset} ${msg}`);
58
- }
59
-
60
- function printError(msg) {
61
- print(`${colors.red}✖${colors.reset} ${msg}`);
62
- }
63
-
64
- function printMenu() {
65
- print('');
66
- print(`${colors.bold}请选择操作:${colors.reset}`);
67
- print('');
68
- print(` ${colors.cyan}1${colors.reset} - 完整安装 (下载 + 配置 API + 配置 Hook)`);
69
- print(` ${colors.cyan}2${colors.reset} - 仅下载二进制文件`);
70
- print(` ${colors.cyan}3${colors.reset} - 仅配置 API 设置`);
71
- print(` ${colors.cyan}4${colors.reset} - 仅配置 Claude Code Hook`);
72
- print(` ${colors.cyan}0${colors.reset} - 退出`);
73
- print('');
74
- }
75
-
76
- // ============================================================================
77
- // Readline Interface
78
- // ============================================================================
79
-
80
- function createRL() {
81
- return readline.createInterface({
82
- input: process.stdin,
83
- output: process.stdout,
84
- });
85
- }
86
-
87
- function question(rl, prompt) {
88
- return new Promise((resolve) => {
89
- rl.question(prompt, (answer) => {
90
- resolve(answer.trim());
91
- });
92
- });
93
- }
94
-
95
- async function promptInput(rl, prompt, defaultValue = '') {
96
- const displayDefault = defaultValue ? ` [${defaultValue}]` : '';
97
- const answer = await question(rl, `${prompt}${displayDefault}: `);
98
- return answer || defaultValue;
99
- }
100
-
101
- async function promptConfirm(rl, prompt, defaultYes = true) {
102
- const hint = defaultYes ? '[Y/n]' : '[y/N]';
103
- const answer = await question(rl, `${prompt} ${hint}: `);
104
- if (!answer) return defaultYes;
105
- return answer.toLowerCase().startsWith('y');
106
- }
107
-
108
- // ============================================================================
109
- // Platform Detection
110
- // ============================================================================
111
-
112
- function detectPlatform() {
113
- const platform = os.platform();
114
- const arch = os.arch();
115
-
116
- if (platform === 'linux') {
117
- if (arch === 'x64') return 'linux-amd64';
118
- if (arch === 'arm64') return 'linux-arm64';
119
- } else if (platform === 'darwin') {
120
- if (arch === 'x64') return 'macos-amd64';
121
- if (arch === 'arm64') return 'macos-arm64';
122
- } else if (platform === 'win32') {
123
- if (arch === 'x64') return 'windows-amd64';
124
- }
125
-
126
- return null;
127
- }
128
-
129
- function getBinaryName(platformStr) {
130
- if (platformStr.startsWith('windows')) {
131
- return 'cc-goto-work.exe';
132
- }
133
- return 'cc-goto-work';
134
- }
135
-
136
- // ============================================================================
137
- // GitHub API
138
- // ============================================================================
139
-
140
- function httpsGet(url) {
141
- return new Promise((resolve, reject) => {
142
- const options = {
143
- headers: { 'User-Agent': 'cc-goto-work-installer' },
144
- };
145
-
146
- https.get(url, options, (res) => {
147
- if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
148
- // Follow redirect
149
- httpsGet(res.headers.location).then(resolve).catch(reject);
150
- return;
151
- }
152
-
153
- if (res.statusCode !== 200) {
154
- reject(new Error(`HTTP ${res.statusCode}`));
155
- return;
156
- }
157
-
158
- const chunks = [];
159
- res.on('data', (chunk) => chunks.push(chunk));
160
- res.on('end', () => resolve(Buffer.concat(chunks)));
161
- res.on('error', reject);
162
- }).on('error', reject);
163
- });
164
- }
165
-
166
- async function getLatestVersion() {
167
- const url = `https://api.github.com/repos/${REPO}/releases/latest`;
168
- const data = await httpsGet(url);
169
- const json = JSON.parse(data.toString());
170
- return json.tag_name;
171
- }
172
-
173
- // ============================================================================
174
- // Download Binary
175
- // ============================================================================
176
-
177
- async function downloadBinary(version, platformStr) {
178
- const binaryName = getBinaryName(platformStr);
179
- const fileName = platformStr.startsWith('windows')
180
- ? `cc-goto-work-${platformStr}.exe`
181
- : `cc-goto-work-${platformStr}`;
182
-
183
- const url = `https://github.com/${REPO}/releases/download/${version}/${fileName}`;
184
- const destPath = path.join(INSTALL_DIR, binaryName);
185
-
186
- printStep(`正在下载 ${version} (${platformStr})...`);
187
-
188
- // Ensure directory exists
189
- fs.mkdirSync(INSTALL_DIR, { recursive: true });
190
-
191
- const data = await httpsGet(url);
192
- fs.writeFileSync(destPath, data);
193
-
194
- // Make executable on Unix
195
- if (!platformStr.startsWith('windows')) {
196
- fs.chmodSync(destPath, 0o755);
197
- }
198
-
199
- printSuccess(`二进制文件已下载到: ${destPath}`);
200
- return destPath;
201
- }
202
-
203
- // ============================================================================
204
- // Configuration
205
- // ============================================================================
206
-
207
- function createConfig(apiBase, apiKey, model) {
208
- const configContent = `# cc-goto-work configuration
209
- # https://github.com/${REPO}
210
-
211
- # OpenAI compatible API base URL
212
- api_base: ${apiBase}
213
-
214
- # API key for authentication
215
- api_key: ${apiKey}
216
-
217
- # Model name to use
218
- model: ${model}
219
-
220
- # Request timeout in seconds (optional)
221
- timeout: 30
222
-
223
- # Enable debug logging (optional)
224
- debug: false
225
- `;
226
-
227
- fs.mkdirSync(INSTALL_DIR, { recursive: true });
228
- fs.writeFileSync(CONFIG_FILE, configContent, { mode: 0o600 });
229
- printSuccess(`配置文件已创建: ${CONFIG_FILE}`);
230
- }
231
-
232
- async function configureAPI(rl) {
233
- print('');
234
- print('此工具使用 AI 模型来检测未完成的会话。');
235
- print('请提供 OpenAI 兼容的 API 端点信息。');
236
- print('');
237
-
238
- const apiBase = await promptInput(rl, 'API 地址', 'https://api.openai.com/v1');
239
- const apiKey = await promptInput(rl, 'API 密钥', '');
240
- const model = await promptInput(rl, '模型名称', 'gpt-4o-mini');
241
-
242
- if (!apiKey) {
243
- printWarning('未提供 API 密钥,请在使用前编辑配置文件');
244
- }
245
-
246
- createConfig(apiBase, apiKey, model);
247
- }
248
-
249
- // ============================================================================
250
- // Claude Settings
251
- // ============================================================================
252
-
253
- function configureClaudeSettings(binaryPath) {
254
- fs.mkdirSync(CLAUDE_SETTINGS_DIR, { recursive: true });
255
-
256
- const hookConfig = {
257
- hooks: {
258
- Stop: [
259
- {
260
- hooks: [
261
- {
262
- type: 'command',
263
- command: binaryPath,
264
- timeout: 120,
265
- },
266
- ],
267
- },
268
- ],
269
- },
270
- };
271
-
272
- let settings = {};
273
-
274
- if (fs.existsSync(CLAUDE_SETTINGS_FILE)) {
275
- try {
276
- const content = fs.readFileSync(CLAUDE_SETTINGS_FILE, 'utf8');
277
- if (content.trim()) {
278
- settings = JSON.parse(content);
279
-
280
- // Backup existing
281
- fs.writeFileSync(`${CLAUDE_SETTINGS_FILE}.backup`, content);
282
- printWarning(`已备份现有配置到: ${CLAUDE_SETTINGS_FILE}.backup`);
283
-
284
- // Check if Stop hook exists
285
- if (settings.hooks && settings.hooks.Stop) {
286
- printWarning('Stop hook 已存在,正在更新...');
287
- }
288
- }
289
- } catch (e) {
290
- printWarning('无法解析现有配置,将创建新配置');
291
- }
292
- }
293
-
294
- // Merge hooks
295
- settings.hooks = settings.hooks || {};
296
- settings.hooks.Stop = hookConfig.hooks.Stop;
297
-
298
- fs.writeFileSync(CLAUDE_SETTINGS_FILE, JSON.stringify(settings, null, 2));
299
- printSuccess(`Claude Code 配置已更新: ${CLAUDE_SETTINGS_FILE}`);
300
- }
301
-
302
- // ============================================================================
303
- // Installation Actions
304
- // ============================================================================
305
-
306
- async function fullInstall(rl) {
307
- printStep('开始完整安装...');
308
- print('');
309
-
310
- // Detect platform
311
- const platformStr = detectPlatform();
312
- if (!platformStr) {
313
- printError(`不支持的平台: ${os.platform()} ${os.arch()}`);
314
- return false;
315
- }
316
- print(` 平台: ${platformStr}`);
317
-
318
- // Get latest version
319
- printStep('获取最新版本...');
320
- let version;
321
- try {
322
- version = await getLatestVersion();
323
- print(` 版本: ${version}`);
324
- } catch (e) {
325
- printError(`获取版本失败: ${e.message}`);
326
- return false;
327
- }
328
-
329
- print('');
330
- if (!(await promptConfirm(rl, `确认安装 cc-goto-work ${version}?`))) {
331
- print('安装已取消');
332
- return false;
333
- }
334
-
335
- // Download binary
336
- print('');
337
- let binaryPath;
338
- try {
339
- binaryPath = await downloadBinary(version, platformStr);
340
- } catch (e) {
341
- printError(`下载失败: ${e.message}`);
342
- return false;
343
- }
344
-
345
- // Configure API
346
- print('');
347
- await configureAPI(rl);
348
-
349
- // Configure Claude settings
350
- print('');
351
- if (await promptConfirm(rl, '自动配置 Claude Code?')) {
352
- configureClaudeSettings(binaryPath);
353
- } else {
354
- print('');
355
- print('请手动添加以下配置到 Claude Code:');
356
- print(` 命令: ${binaryPath}`);
357
- }
358
-
359
- return true;
360
- }
361
-
362
- async function downloadOnly(rl) {
363
- printStep('仅下载二进制文件...');
364
- print('');
365
-
366
- const platformStr = detectPlatform();
367
- if (!platformStr) {
368
- printError(`不支持的平台: ${os.platform()} ${os.arch()}`);
369
- return false;
370
- }
371
- print(` 平台: ${platformStr}`);
372
-
373
- printStep('获取最新版本...');
374
- let version;
375
- try {
376
- version = await getLatestVersion();
377
- print(` 版本: ${version}`);
378
- } catch (e) {
379
- printError(`获取版本失败: ${e.message}`);
380
- return false;
381
- }
382
-
383
- print('');
384
- try {
385
- await downloadBinary(version, platformStr);
386
- } catch (e) {
387
- printError(`下载失败: ${e.message}`);
388
- return false;
389
- }
390
-
391
- return true;
392
- }
393
-
394
- async function configureAPIOnly(rl) {
395
- printStep('配置 API 设置...');
396
- await configureAPI(rl);
397
- return true;
398
- }
399
-
400
- async function configureHookOnly(rl) {
401
- printStep('配置 Claude Code Hook...');
402
- print('');
403
-
404
- const platformStr = detectPlatform();
405
- const binaryName = getBinaryName(platformStr || 'linux-amd64');
406
- const binaryPath = path.join(INSTALL_DIR, binaryName);
407
-
408
- if (!fs.existsSync(binaryPath)) {
409
- printWarning(`二进制文件不存在: ${binaryPath}`);
410
- if (!(await promptConfirm(rl, '是否继续配置?', false))) {
411
- return false;
412
- }
413
- }
414
-
415
- configureClaudeSettings(binaryPath);
416
- return true;
417
- }
418
-
419
- // ============================================================================
420
- // Main
421
- // ============================================================================
422
-
423
- async function main() {
424
- printBanner();
425
-
426
- const rl = createRL();
427
-
428
- try {
429
- while (true) {
430
- printMenu();
431
-
432
- const choice = await question(rl, '请输入选项 (0-4): ');
433
-
434
- let success = false;
435
-
436
- switch (choice) {
437
- case '1':
438
- success = await fullInstall(rl);
439
- break;
440
- case '2':
441
- success = await downloadOnly(rl);
442
- break;
443
- case '3':
444
- success = await configureAPIOnly(rl);
445
- break;
446
- case '4':
447
- success = await configureHookOnly(rl);
448
- break;
449
- case '0':
450
- case 'q':
451
- case 'exit':
452
- print('');
453
- print('再见!');
454
- rl.close();
455
- return;
456
- default:
457
- printError('无效选项,请输入 0-4');
458
- continue;
459
- }
460
-
461
- if (success) {
462
- print('');
463
- printSuccess('操作完成!');
464
- print('');
465
- print('请重启 Claude Code 以使配置生效。');
466
- }
467
-
468
- print('');
469
- if (!(await promptConfirm(rl, '继续其他操作?', false))) {
470
- print('');
471
- print('再见!');
472
- break;
473
- }
474
- }
475
- } finally {
476
- rl.close();
477
- }
478
- }
479
-
480
- main().catch((err) => {
481
- printError(`发生错误: ${err.message}`);
482
- process.exit(1);
483
- });
1
+ #!/usr/bin/env node
2
+
3
+ const readline = require('readline');
4
+ const https = require('https');
5
+ const fs = require('fs');
6
+ const path = require('path');
7
+ const os = require('os');
8
+ const { spawn } = require('child_process');
9
+
10
+ // ============================================================================
11
+ // Constants
12
+ // ============================================================================
13
+
14
+ const REPO = 'pdxxxx/cc-goto-work';
15
+ const INSTALL_DIR = path.join(os.homedir(), '.claude', 'cc-goto-work');
16
+ const CONFIG_FILE = path.join(INSTALL_DIR, 'config.yaml');
17
+ const CLAUDE_SETTINGS_DIR = path.join(os.homedir(), '.claude');
18
+ const CLAUDE_SETTINGS_FILE = path.join(CLAUDE_SETTINGS_DIR, 'settings.json');
19
+
20
+ // Colors (ANSI escape codes)
21
+ const colors = {
22
+ reset: '\x1b[0m',
23
+ red: '\x1b[31m',
24
+ green: '\x1b[32m',
25
+ yellow: '\x1b[33m',
26
+ blue: '\x1b[34m',
27
+ cyan: '\x1b[36m',
28
+ bold: '\x1b[1m',
29
+ };
30
+
31
+ // ============================================================================
32
+ // Utilities
33
+ // ============================================================================
34
+
35
+ function print(msg) {
36
+ console.log(msg);
37
+ }
38
+
39
+ function printBanner() {
40
+ print(`${colors.cyan}${colors.bold}`);
41
+ print('╔════════════════════════════════════════════════════════════╗');
42
+ print('║ cc-goto-work 安装程序 ║');
43
+ print('║ 让 Claude Code 自动继续未完成的工作 ║');
44
+ print('╚════════════════════════════════════════════════════════════╝');
45
+ print(`${colors.reset}`);
46
+ }
47
+
48
+ function printStep(msg) {
49
+ print(`${colors.green}▶${colors.reset} ${msg}`);
50
+ }
51
+
52
+ function printSuccess(msg) {
53
+ print(`${colors.green}✔${colors.reset} ${msg}`);
54
+ }
55
+
56
+ function printWarning(msg) {
57
+ print(`${colors.yellow}⚠${colors.reset} ${msg}`);
58
+ }
59
+
60
+ function printError(msg) {
61
+ print(`${colors.red}✖${colors.reset} ${msg}`);
62
+ }
63
+
64
+ function printMenu() {
65
+ print('');
66
+ print(`${colors.bold}请选择操作:${colors.reset}`);
67
+ print('');
68
+ print(` ${colors.cyan}1${colors.reset} - 完整安装 (下载 + 配置 API + 配置 Hook)`);
69
+ print(` ${colors.cyan}2${colors.reset} - 仅下载二进制文件`);
70
+ print(` ${colors.cyan}3${colors.reset} - 仅配置 API 设置`);
71
+ print(` ${colors.cyan}4${colors.reset} - 仅配置 Claude Code Hook`);
72
+ print(` ${colors.cyan}5${colors.reset} - ${colors.blue}检查更新${colors.reset}`);
73
+ print(` ${colors.cyan}6${colors.reset} - ${colors.red}卸载 cc-goto-work${colors.reset}`);
74
+ print(` ${colors.cyan}0${colors.reset} - 退出`);
75
+ print('');
76
+ }
77
+
78
+ // ============================================================================
79
+ // Readline Interface
80
+ // ============================================================================
81
+
82
+ function createRL() {
83
+ return readline.createInterface({
84
+ input: process.stdin,
85
+ output: process.stdout,
86
+ });
87
+ }
88
+
89
+ function question(rl, prompt) {
90
+ return new Promise((resolve) => {
91
+ rl.question(prompt, (answer) => {
92
+ resolve(answer.trim());
93
+ });
94
+ });
95
+ }
96
+
97
+ async function promptInput(rl, prompt, defaultValue = '') {
98
+ const displayDefault = defaultValue ? ` [${defaultValue}]` : '';
99
+ const answer = await question(rl, `${prompt}${displayDefault}: `);
100
+ return answer || defaultValue;
101
+ }
102
+
103
+ async function promptConfirm(rl, prompt, defaultYes = true) {
104
+ const hint = defaultYes ? '[Y/n]' : '[y/N]';
105
+ const answer = await question(rl, `${prompt} ${hint}: `);
106
+ if (!answer) return defaultYes;
107
+ return answer.toLowerCase().startsWith('y');
108
+ }
109
+
110
+ // ============================================================================
111
+ // Platform Detection
112
+ // ============================================================================
113
+
114
+ function detectPlatform() {
115
+ const platform = os.platform();
116
+ const arch = os.arch();
117
+
118
+ if (platform === 'linux') {
119
+ if (arch === 'x64') return 'linux-amd64';
120
+ if (arch === 'arm64') return 'linux-arm64';
121
+ } else if (platform === 'darwin') {
122
+ if (arch === 'x64') return 'macos-amd64';
123
+ if (arch === 'arm64') return 'macos-arm64';
124
+ } else if (platform === 'win32') {
125
+ if (arch === 'x64') return 'windows-amd64';
126
+ }
127
+
128
+ return null;
129
+ }
130
+
131
+ function getBinaryName(platformStr) {
132
+ if (platformStr.startsWith('windows')) {
133
+ return 'cc-goto-work.exe';
134
+ }
135
+ return 'cc-goto-work';
136
+ }
137
+
138
+ // ============================================================================
139
+ // GitHub API
140
+ // ============================================================================
141
+
142
+ function httpsGet(url) {
143
+ return new Promise((resolve, reject) => {
144
+ const options = {
145
+ headers: { 'User-Agent': 'cc-goto-work-installer' },
146
+ };
147
+
148
+ https.get(url, options, (res) => {
149
+ if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
150
+ // Follow redirect
151
+ httpsGet(res.headers.location).then(resolve).catch(reject);
152
+ return;
153
+ }
154
+
155
+ if (res.statusCode !== 200) {
156
+ reject(new Error(`HTTP ${res.statusCode}`));
157
+ return;
158
+ }
159
+
160
+ const chunks = [];
161
+ res.on('data', (chunk) => chunks.push(chunk));
162
+ res.on('end', () => resolve(Buffer.concat(chunks)));
163
+ res.on('error', reject);
164
+ }).on('error', reject);
165
+ });
166
+ }
167
+
168
+ async function getLatestVersion() {
169
+ const url = `https://api.github.com/repos/${REPO}/releases/latest`;
170
+ const data = await httpsGet(url);
171
+ const json = JSON.parse(data.toString());
172
+ return json.tag_name;
173
+ }
174
+
175
+ // ============================================================================
176
+ // Download Binary
177
+ // ============================================================================
178
+
179
+ async function downloadBinary(version, platformStr) {
180
+ const binaryName = getBinaryName(platformStr);
181
+ const fileName = platformStr.startsWith('windows')
182
+ ? `cc-goto-work-${platformStr}.exe`
183
+ : `cc-goto-work-${platformStr}`;
184
+
185
+ const url = `https://github.com/${REPO}/releases/download/${version}/${fileName}`;
186
+ const destPath = path.join(INSTALL_DIR, binaryName);
187
+
188
+ printStep(`正在下载 ${version} (${platformStr})...`);
189
+
190
+ // Ensure directory exists
191
+ fs.mkdirSync(INSTALL_DIR, { recursive: true });
192
+
193
+ const data = await httpsGet(url);
194
+ fs.writeFileSync(destPath, data);
195
+
196
+ // Make executable on Unix
197
+ if (!platformStr.startsWith('windows')) {
198
+ fs.chmodSync(destPath, 0o755);
199
+ }
200
+
201
+ // Save version info
202
+ const versionFile = path.join(INSTALL_DIR, '.version');
203
+ fs.writeFileSync(versionFile, version);
204
+
205
+ printSuccess(`二进制文件已下载到: ${destPath}`);
206
+ return destPath;
207
+ }
208
+
209
+ // ============================================================================
210
+ // Configuration
211
+ // ============================================================================
212
+
213
+ function createConfig(apiBase, apiKey, model) {
214
+ const configContent = `# cc-goto-work configuration
215
+ # https://github.com/${REPO}
216
+
217
+ # OpenAI compatible API base URL
218
+ api_base: ${apiBase}
219
+
220
+ # API key for authentication
221
+ api_key: ${apiKey}
222
+
223
+ # Model name to use
224
+ model: ${model}
225
+
226
+ # Request timeout in seconds (optional)
227
+ timeout: 30
228
+
229
+ # Enable debug logging (optional)
230
+ debug: false
231
+ `;
232
+
233
+ fs.mkdirSync(INSTALL_DIR, { recursive: true });
234
+ fs.writeFileSync(CONFIG_FILE, configContent, { mode: 0o600 });
235
+ printSuccess(`配置文件已创建: ${CONFIG_FILE}`);
236
+ }
237
+
238
+ async function configureAPI(rl) {
239
+ print('');
240
+ print('此工具使用 AI 模型来检测未完成的会话。');
241
+ print('请提供 OpenAI 兼容的 API 端点信息。');
242
+ print('');
243
+
244
+ const apiBase = await promptInput(rl, 'API 地址', 'https://api.openai.com/v1');
245
+ const apiKey = await promptInput(rl, 'API 密钥', '');
246
+ const model = await promptInput(rl, '模型名称', 'gpt-4o-mini');
247
+
248
+ if (!apiKey) {
249
+ printWarning('未提供 API 密钥,请在使用前编辑配置文件');
250
+ }
251
+
252
+ createConfig(apiBase, apiKey, model);
253
+ }
254
+
255
+ // ============================================================================
256
+ // Claude Settings
257
+ // ============================================================================
258
+
259
+ function configureClaudeSettings(binaryPath) {
260
+ fs.mkdirSync(CLAUDE_SETTINGS_DIR, { recursive: true });
261
+
262
+ const hookConfig = {
263
+ hooks: {
264
+ Stop: [
265
+ {
266
+ hooks: [
267
+ {
268
+ type: 'command',
269
+ command: binaryPath,
270
+ timeout: 120,
271
+ },
272
+ ],
273
+ },
274
+ ],
275
+ },
276
+ };
277
+
278
+ let settings = {};
279
+
280
+ if (fs.existsSync(CLAUDE_SETTINGS_FILE)) {
281
+ try {
282
+ const content = fs.readFileSync(CLAUDE_SETTINGS_FILE, 'utf8');
283
+ if (content.trim()) {
284
+ settings = JSON.parse(content);
285
+
286
+ // Backup existing
287
+ fs.writeFileSync(`${CLAUDE_SETTINGS_FILE}.backup`, content);
288
+ printWarning(`已备份现有配置到: ${CLAUDE_SETTINGS_FILE}.backup`);
289
+
290
+ // Check if Stop hook exists
291
+ if (settings.hooks && settings.hooks.Stop) {
292
+ printWarning('Stop hook 已存在,正在更新...');
293
+ }
294
+ }
295
+ } catch (e) {
296
+ printWarning('无法解析现有配置,将创建新配置');
297
+ }
298
+ }
299
+
300
+ // Merge hooks
301
+ settings.hooks = settings.hooks || {};
302
+ settings.hooks.Stop = hookConfig.hooks.Stop;
303
+
304
+ fs.writeFileSync(CLAUDE_SETTINGS_FILE, JSON.stringify(settings, null, 2));
305
+ printSuccess(`Claude Code 配置已更新: ${CLAUDE_SETTINGS_FILE}`);
306
+ }
307
+
308
+ // ============================================================================
309
+ // Installation Actions
310
+ // ============================================================================
311
+
312
+ async function fullInstall(rl) {
313
+ printStep('开始完整安装...');
314
+ print('');
315
+
316
+ // Detect platform
317
+ const platformStr = detectPlatform();
318
+ if (!platformStr) {
319
+ printError(`不支持的平台: ${os.platform()} ${os.arch()}`);
320
+ return false;
321
+ }
322
+ print(` 平台: ${platformStr}`);
323
+
324
+ // Get latest version
325
+ printStep('获取最新版本...');
326
+ let version;
327
+ try {
328
+ version = await getLatestVersion();
329
+ print(` 版本: ${version}`);
330
+ } catch (e) {
331
+ printError(`获取版本失败: ${e.message}`);
332
+ return false;
333
+ }
334
+
335
+ print('');
336
+ if (!(await promptConfirm(rl, `确认安装 cc-goto-work ${version}?`))) {
337
+ print('安装已取消');
338
+ return false;
339
+ }
340
+
341
+ // Download binary
342
+ print('');
343
+ let binaryPath;
344
+ try {
345
+ binaryPath = await downloadBinary(version, platformStr);
346
+ } catch (e) {
347
+ printError(`下载失败: ${e.message}`);
348
+ return false;
349
+ }
350
+
351
+ // Configure API
352
+ print('');
353
+ await configureAPI(rl);
354
+
355
+ // Configure Claude settings
356
+ print('');
357
+ if (await promptConfirm(rl, '自动配置 Claude Code?')) {
358
+ configureClaudeSettings(binaryPath);
359
+ } else {
360
+ print('');
361
+ print('请手动添加以下配置到 Claude Code:');
362
+ print(` 命令: ${binaryPath}`);
363
+ }
364
+
365
+ return true;
366
+ }
367
+
368
+ async function downloadOnly(rl) {
369
+ printStep('仅下载二进制文件...');
370
+ print('');
371
+
372
+ const platformStr = detectPlatform();
373
+ if (!platformStr) {
374
+ printError(`不支持的平台: ${os.platform()} ${os.arch()}`);
375
+ return false;
376
+ }
377
+ print(` 平台: ${platformStr}`);
378
+
379
+ printStep('获取最新版本...');
380
+ let version;
381
+ try {
382
+ version = await getLatestVersion();
383
+ print(` 版本: ${version}`);
384
+ } catch (e) {
385
+ printError(`获取版本失败: ${e.message}`);
386
+ return false;
387
+ }
388
+
389
+ print('');
390
+ try {
391
+ await downloadBinary(version, platformStr);
392
+ } catch (e) {
393
+ printError(`下载失败: ${e.message}`);
394
+ return false;
395
+ }
396
+
397
+ return true;
398
+ }
399
+
400
+ async function configureAPIOnly(rl) {
401
+ printStep('配置 API 设置...');
402
+ await configureAPI(rl);
403
+ return true;
404
+ }
405
+
406
+ async function configureHookOnly(rl) {
407
+ printStep('配置 Claude Code Hook...');
408
+ print('');
409
+
410
+ const platformStr = detectPlatform();
411
+ const binaryName = getBinaryName(platformStr || 'linux-amd64');
412
+ const binaryPath = path.join(INSTALL_DIR, binaryName);
413
+
414
+ if (!fs.existsSync(binaryPath)) {
415
+ printWarning(`二进制文件不存在: ${binaryPath}`);
416
+ if (!(await promptConfirm(rl, '是否继续配置?', false))) {
417
+ return false;
418
+ }
419
+ }
420
+
421
+ configureClaudeSettings(binaryPath);
422
+ return true;
423
+ }
424
+
425
+ // ============================================================================
426
+ // Update
427
+ // ============================================================================
428
+
429
+ const VERSION_FILE = path.join(INSTALL_DIR, '.version');
430
+
431
+ function getInstalledVersion() {
432
+ try {
433
+ if (fs.existsSync(VERSION_FILE)) {
434
+ return fs.readFileSync(VERSION_FILE, 'utf8').trim();
435
+ }
436
+ } catch (e) {
437
+ // Ignore
438
+ }
439
+ return null;
440
+ }
441
+
442
+ function saveInstalledVersion(version) {
443
+ try {
444
+ fs.mkdirSync(INSTALL_DIR, { recursive: true });
445
+ fs.writeFileSync(VERSION_FILE, version);
446
+ } catch (e) {
447
+ // Ignore
448
+ }
449
+ }
450
+
451
+ function compareVersions(v1, v2) {
452
+ // Remove 'v' prefix if present
453
+ const normalize = (v) => v.replace(/^v/, '').split('.').map(Number);
454
+ const parts1 = normalize(v1);
455
+ const parts2 = normalize(v2);
456
+
457
+ for (let i = 0; i < Math.max(parts1.length, parts2.length); i++) {
458
+ const p1 = parts1[i] || 0;
459
+ const p2 = parts2[i] || 0;
460
+ if (p1 > p2) return 1;
461
+ if (p1 < p2) return -1;
462
+ }
463
+ return 0;
464
+ }
465
+
466
+ async function checkUpdate(rl) {
467
+ printStep('检查更新...');
468
+ print('');
469
+
470
+ const platformStr = detectPlatform();
471
+ if (!platformStr) {
472
+ printError(`不支持的平台: ${os.platform()} ${os.arch()}`);
473
+ return false;
474
+ }
475
+
476
+ const binaryName = getBinaryName(platformStr);
477
+ const binaryPath = path.join(INSTALL_DIR, binaryName);
478
+
479
+ if (!fs.existsSync(binaryPath)) {
480
+ printWarning('未检测到安装,请先安装');
481
+ return false;
482
+ }
483
+
484
+ // Get installed version
485
+ const installedVersion = getInstalledVersion();
486
+ if (installedVersion) {
487
+ print(` 当前版本: ${installedVersion}`);
488
+ } else {
489
+ print(` 当前版本: ${colors.yellow}未知${colors.reset}`);
490
+ }
491
+
492
+ // Get latest version
493
+ printStep('获取最新版本...');
494
+ let latestVersion;
495
+ try {
496
+ latestVersion = await getLatestVersion();
497
+ print(` 最新版本: ${latestVersion}`);
498
+ } catch (e) {
499
+ printError(`获取版本失败: ${e.message}`);
500
+ return false;
501
+ }
502
+
503
+ print('');
504
+
505
+ // Compare versions
506
+ if (installedVersion && compareVersions(installedVersion, latestVersion) >= 0) {
507
+ printSuccess('已是最新版本,无需更新');
508
+ return true;
509
+ }
510
+
511
+ // Ask to update
512
+ const updateMsg = installedVersion
513
+ ? `发现新版本!是否从 ${installedVersion} 更新到 ${latestVersion}?`
514
+ : `是否更新到 ${latestVersion}?`;
515
+
516
+ if (!(await promptConfirm(rl, updateMsg))) {
517
+ print('更新已取消');
518
+ return false;
519
+ }
520
+
521
+ // Download new version
522
+ print('');
523
+ try {
524
+ await downloadBinary(latestVersion, platformStr);
525
+ saveInstalledVersion(latestVersion);
526
+ printSuccess(`已更新到 ${latestVersion}`);
527
+ return true;
528
+ } catch (e) {
529
+ printError(`更新失败: ${e.message}`);
530
+ return false;
531
+ }
532
+ }
533
+
534
+ // ============================================================================
535
+ // Uninstall
536
+ // ============================================================================
537
+
538
+ function removeStopHook() {
539
+ if (!fs.existsSync(CLAUDE_SETTINGS_FILE)) {
540
+ printWarning('Claude Code 配置文件不存在');
541
+ return false;
542
+ }
543
+
544
+ try {
545
+ const content = fs.readFileSync(CLAUDE_SETTINGS_FILE, 'utf8');
546
+ if (!content.trim()) {
547
+ printWarning('Claude Code 配置文件为空');
548
+ return false;
549
+ }
550
+
551
+ const settings = JSON.parse(content);
552
+
553
+ if (!settings.hooks || !settings.hooks.Stop) {
554
+ printWarning('Stop hook 不存在,无需移除');
555
+ return false;
556
+ }
557
+
558
+ // Backup before modifying
559
+ fs.writeFileSync(`${CLAUDE_SETTINGS_FILE}.backup`, content);
560
+ printWarning(`已备份现有配置到: ${CLAUDE_SETTINGS_FILE}.backup`);
561
+
562
+ // Remove Stop hook
563
+ delete settings.hooks.Stop;
564
+
565
+ // Remove hooks object if empty
566
+ if (Object.keys(settings.hooks).length === 0) {
567
+ delete settings.hooks;
568
+ }
569
+
570
+ fs.writeFileSync(CLAUDE_SETTINGS_FILE, JSON.stringify(settings, null, 2));
571
+ printSuccess('已从 Claude Code 配置中移除 Stop hook');
572
+ return true;
573
+ } catch (e) {
574
+ printError(`移除 Stop hook 失败: ${e.message}`);
575
+ return false;
576
+ }
577
+ }
578
+
579
+ function removeDirectory(dirPath) {
580
+ if (!fs.existsSync(dirPath)) {
581
+ return false;
582
+ }
583
+
584
+ try {
585
+ fs.rmSync(dirPath, { recursive: true, force: true });
586
+ return true;
587
+ } catch (e) {
588
+ printError(`删除目录失败: ${e.message}`);
589
+ return false;
590
+ }
591
+ }
592
+
593
+ async function uninstall(rl) {
594
+ printStep('卸载 cc-goto-work...');
595
+ print('');
596
+
597
+ // Check what exists
598
+ const platformStr = detectPlatform();
599
+ const binaryName = getBinaryName(platformStr || 'linux-amd64');
600
+ const binaryPath = path.join(INSTALL_DIR, binaryName);
601
+
602
+ const binaryExists = fs.existsSync(binaryPath);
603
+ const configExists = fs.existsSync(CONFIG_FILE);
604
+ const dirExists = fs.existsSync(INSTALL_DIR);
605
+
606
+ if (!binaryExists && !configExists && !dirExists) {
607
+ printWarning('未检测到安装,无需卸载');
608
+ return false;
609
+ }
610
+
611
+ print('检测到以下已安装内容:');
612
+ if (binaryExists) print(` - 二进制文件: ${binaryPath}`);
613
+ if (configExists) print(` - 配置文件: ${CONFIG_FILE}`);
614
+ if (dirExists) print(` - 安装目录: ${INSTALL_DIR}`);
615
+ print('');
616
+
617
+ if (!(await promptConfirm(rl, `${colors.red}确认卸载 cc-goto-work?${colors.reset}`, false))) {
618
+ print('卸载已取消');
619
+ return false;
620
+ }
621
+
622
+ print('');
623
+
624
+ // Remove Stop hook from Claude settings
625
+ printStep('移除 Claude Code Hook...');
626
+ removeStopHook();
627
+
628
+ // Remove installation directory
629
+ printStep('删除安装目录...');
630
+ if (dirExists) {
631
+ if (removeDirectory(INSTALL_DIR)) {
632
+ printSuccess(`已删除: ${INSTALL_DIR}`);
633
+ }
634
+ } else {
635
+ printWarning('安装目录不存在');
636
+ }
637
+
638
+ return true;
639
+ }
640
+
641
+ // ============================================================================
642
+ // Main
643
+ // ============================================================================
644
+
645
+ async function main() {
646
+ printBanner();
647
+
648
+ const rl = createRL();
649
+
650
+ try {
651
+ while (true) {
652
+ printMenu();
653
+
654
+ const choice = await question(rl, '请输入选项 (0-6): ');
655
+
656
+ let success = false;
657
+
658
+ switch (choice) {
659
+ case '1':
660
+ success = await fullInstall(rl);
661
+ break;
662
+ case '2':
663
+ success = await downloadOnly(rl);
664
+ break;
665
+ case '3':
666
+ success = await configureAPIOnly(rl);
667
+ break;
668
+ case '4':
669
+ success = await configureHookOnly(rl);
670
+ break;
671
+ case '5':
672
+ success = await checkUpdate(rl);
673
+ break;
674
+ case '6':
675
+ success = await uninstall(rl);
676
+ break;
677
+ case '0':
678
+ case 'q':
679
+ case 'exit':
680
+ print('');
681
+ print('再见!');
682
+ rl.close();
683
+ return;
684
+ default:
685
+ printError('无效选项,请输入 0-6');
686
+ continue;
687
+ }
688
+
689
+ if (success) {
690
+ print('');
691
+ printSuccess('操作完成!');
692
+ print('');
693
+ print('请重启 Claude Code 以使配置生效。');
694
+ }
695
+
696
+ print('');
697
+ if (!(await promptConfirm(rl, '继续其他操作?', false))) {
698
+ print('');
699
+ print('再见!');
700
+ break;
701
+ }
702
+ }
703
+ } finally {
704
+ rl.close();
705
+ }
706
+ }
707
+
708
+ main().catch((err) => {
709
+ printError(`发生错误: ${err.message}`);
710
+ process.exit(1);
711
+ });
package/package.json CHANGED
@@ -1,30 +1,30 @@
1
- {
2
- "name": "cc-goto-work",
3
- "version": "0.8.0",
4
- "description": "让 Claude Code 自动继续未完成的工作",
5
- "bin": {
6
- "cc-goto-work": "./bin/cli.js"
7
- },
8
- "scripts": {
9
- "test": "node bin/cli.js"
10
- },
11
- "keywords": [
12
- "claude",
13
- "claude-code",
14
- "hook",
15
- "ai",
16
- "automation"
17
- ],
18
- "author": "",
19
- "license": "MIT",
20
- "repository": {
21
- "type": "git",
22
- "url": "https://github.com/pdxxxx/cc-goto-work.git"
23
- },
24
- "engines": {
25
- "node": ">=14.0.0"
26
- },
27
- "files": [
28
- "bin/"
29
- ]
30
- }
1
+ {
2
+ "name": "cc-goto-work",
3
+ "version": "0.8.3",
4
+ "description": "让 Claude Code 自动继续未完成的工作",
5
+ "bin": {
6
+ "cc-goto-work": "./bin/cli.js"
7
+ },
8
+ "scripts": {
9
+ "test": "node bin/cli.js"
10
+ },
11
+ "keywords": [
12
+ "claude",
13
+ "claude-code",
14
+ "hook",
15
+ "ai",
16
+ "automation"
17
+ ],
18
+ "author": "",
19
+ "license": "MIT",
20
+ "repository": {
21
+ "type": "git",
22
+ "url": "https://github.com/pdxxxx/cc-goto-work.git"
23
+ },
24
+ "engines": {
25
+ "node": ">=14.0.0"
26
+ },
27
+ "files": [
28
+ "bin/"
29
+ ]
30
+ }