cc-goto-work 0.8.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.
Files changed (3) hide show
  1. package/README.md +21 -0
  2. package/bin/cli.js +483 -0
  3. package/package.json +30 -0
package/README.md ADDED
@@ -0,0 +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
package/bin/cli.js ADDED
@@ -0,0 +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}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
+ });
package/package.json ADDED
@@ -0,0 +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
+ }