@bastdewfn/cc-remote 1.0.9

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.
@@ -0,0 +1,122 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.registerFeishuCommands = registerFeishuCommands;
37
+ const fs = __importStar(require("fs"));
38
+ const path = __importStar(require("path"));
39
+ const core_1 = require("./core");
40
+ function registerFeishuCommands(targetDir, relayPort, templatesDir) {
41
+ const contextPath = (0, core_1.dataPath)(targetDir, 'cc_remote_context.json');
42
+ if (!fs.existsSync(contextPath)) {
43
+ fs.writeFileSync(contextPath, JSON.stringify({ mode: 'close' }));
44
+ }
45
+ const commandsDir = path.join(targetDir, '.claude', 'commands');
46
+ if (!fs.existsSync(commandsDir))
47
+ fs.mkdirSync(commandsDir, { recursive: true });
48
+ // Copy command templates from our project to target project, replacing {{PORT}} placeholder
49
+ const srcTemplatesDir = templatesDir || path.join(__dirname, '../commands');
50
+ const cmdFiles = ['cc-remote-open.md', 'cc-remote-close.md', 'cc-remote-doctor.md'];
51
+ for (const f of cmdFiles) {
52
+ const src = path.join(srcTemplatesDir, f);
53
+ if (fs.existsSync(src)) {
54
+ const content = fs.readFileSync(src, 'utf-8').replace(/\{\{PORT\}\}/g, String(relayPort));
55
+ fs.writeFileSync(path.join(commandsDir, f), content);
56
+ }
57
+ }
58
+ // Auto-approve the feishu-mode commands
59
+ const settingsPath = path.join(targetDir, '.claude', 'settings.local.json');
60
+ let settings = {};
61
+ if (fs.existsSync(settingsPath)) {
62
+ try {
63
+ settings = JSON.parse(fs.readFileSync(settingsPath, 'utf-8'));
64
+ }
65
+ catch { /* ignore */ }
66
+ }
67
+ if (!settings.permissions)
68
+ settings.permissions = {};
69
+ if (!Array.isArray(settings.permissions.allow))
70
+ settings.permissions.allow = [];
71
+ for (const rule of ['Bash(cc-remote *)']) {
72
+ if (!settings.permissions.allow.includes(rule)) {
73
+ settings.permissions.allow.push(rule);
74
+ }
75
+ }
76
+ // Replace old curl hooks with new cc-remote ones: strip our own commands first
77
+ for (const hookType of ['PreToolUse', 'Stop']) {
78
+ const entries = settings.hooks?.[hookType];
79
+ if (!Array.isArray(entries))
80
+ continue;
81
+ for (const entry of entries) {
82
+ if (!Array.isArray(entry.hooks))
83
+ continue;
84
+ entry.hooks = entry.hooks.filter((h) => {
85
+ if (h.type !== 'command')
86
+ return true;
87
+ const c = h.command || '';
88
+ return !c.includes('cc-remote pre-tool-use')
89
+ && !c.includes('cc-remote stop-notify')
90
+ && !c.includes('/hooks/pre-tool-use')
91
+ && !c.includes('/hooks/stop-notify')
92
+ && !c.includes('curl') && !c.includes('node-pty')
93
+ && !c.includes('pre-tool-use.js') && !c.includes('stop-notify.js');
94
+ });
95
+ }
96
+ }
97
+ // Auto-configure hooks: merge into existing, don't overwrite user hooks
98
+ const preToolUseCmd = `cc-remote pre-tool-use`;
99
+ const stopCmd = `cc-remote stop-notify`;
100
+ if (!settings.hooks)
101
+ settings.hooks = {};
102
+ function ensureHook(hookType, cmd) {
103
+ const entries = settings.hooks[hookType];
104
+ if (!Array.isArray(entries)) {
105
+ settings.hooks[hookType] = [{ matcher: '', hooks: [{ type: 'command', command: cmd }] }];
106
+ return;
107
+ }
108
+ let entry = entries.find((e) => (e.matcher || '') === '');
109
+ if (!entry) {
110
+ entry = { matcher: '', hooks: [] };
111
+ entries.push(entry);
112
+ }
113
+ if (!Array.isArray(entry.hooks))
114
+ entry.hooks = [];
115
+ if (!entry.hooks.some((h) => h.type === 'command' && h.command === cmd)) {
116
+ entry.hooks.push({ type: 'command', command: cmd });
117
+ }
118
+ }
119
+ ensureHook('PreToolUse', preToolUseCmd);
120
+ ensureHook('Stop', stopCmd);
121
+ fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
122
+ }
@@ -0,0 +1,366 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.runConfigSetup = runConfigSetup;
37
+ // Interactive channel setup wizard: cc-remote config
38
+ const readline = __importStar(require("readline"));
39
+ const fs = __importStar(require("fs"));
40
+ const path = __importStar(require("path"));
41
+ const os = __importStar(require("os"));
42
+ // ---- Terminal helpers ----
43
+ const RESET = '\x1b[0m';
44
+ const BOLD = '\x1b[1m';
45
+ const GREEN = '\x1b[32m';
46
+ const CYAN = '\x1b[36m';
47
+ const YELLOW = '\x1b[33m';
48
+ function heading(text) {
49
+ console.log(`\n${BOLD}${CYAN}${text}${RESET}`);
50
+ }
51
+ function ok(text) {
52
+ console.log(`${GREEN}${text}${RESET}`);
53
+ }
54
+ function warn(text) {
55
+ console.log(`${YELLOW}${text}${RESET}`);
56
+ }
57
+ function prompt(rl, question) {
58
+ return new Promise((resolve) => {
59
+ rl.question(question, (answer) => resolve(answer.trim()));
60
+ });
61
+ }
62
+ // ---- Config persistence ----
63
+ const configDir = path.join(os.homedir(), '.cc-remote');
64
+ const configPath = path.join(configDir, 'config.json');
65
+ function loadConfig() {
66
+ if (!fs.existsSync(configPath))
67
+ return {};
68
+ try {
69
+ return JSON.parse(fs.readFileSync(configPath, 'utf-8'));
70
+ }
71
+ catch {
72
+ return {};
73
+ }
74
+ }
75
+ function saveConfig(data) {
76
+ fs.mkdirSync(configDir, { recursive: true });
77
+ fs.writeFileSync(configPath, JSON.stringify(data, null, 2), 'utf-8');
78
+ console.log(`\n 配置已保存: ${configPath}`);
79
+ }
80
+ function maskSecret(s) {
81
+ if (s.length <= 4)
82
+ return '***';
83
+ return s.slice(0, 2) + '***' + s.slice(-2);
84
+ }
85
+ function forceExit(code) {
86
+ process.exit(code);
87
+ // Fallback: if event loop handles (e.g., readline) block process.exit
88
+ // on Windows, force-kill after a short delay
89
+ setTimeout(() => { process.kill(process.pid, 'SIGTERM'); }, 200);
90
+ }
91
+ function hasFeishuConfig(cfg) {
92
+ return !!(cfg.appId && cfg.appSecret);
93
+ }
94
+ function hasWeixinConfig() {
95
+ try {
96
+ const { listAccountIds, loadAccount } = require('./weixin/api');
97
+ const ids = listAccountIds();
98
+ return ids.length > 0 && ids.some((id) => {
99
+ const data = loadAccount(id);
100
+ return data?.token;
101
+ });
102
+ }
103
+ catch {
104
+ return false;
105
+ }
106
+ }
107
+ // ---- Feishu setup ----
108
+ async function setupFeishu(rl) {
109
+ heading('=== 飞书应用配置 ===');
110
+ console.log('请到飞书开放平台 (open.feishu.cn) 创建应用,获取凭据。\n');
111
+ const existing = loadConfig();
112
+ const existingAppId = existing.appId;
113
+ const existingSecret = existing.appSecret;
114
+ const existingOpenId = existing.openId;
115
+ const existingPort = existing.relayPort;
116
+ const appId = await prompt(rl, `App ID${existingAppId ? ` [${existingAppId}]` : ''}: `);
117
+ const appSecret = await prompt(rl, `App Secret${existingSecret ? ` [${maskSecret(existingSecret)}]` : ''}: `);
118
+ const openId = await prompt(rl, `Open ID (可选)${existingOpenId ? ` [${existingOpenId}]` : ''}: `);
119
+ const port = await prompt(rl, `Relay 端口${existingPort ? ` [${existingPort}]` : ''} [19200]: `);
120
+ const isMac = process.platform === 'darwin';
121
+ const defaultClaudePath = isMac ? 'claude' : 'claude.exe';
122
+ const existingClaudePath = existing.claudePath;
123
+ const claudePath = await prompt(rl, `Claude 可执行文件路径${existingClaudePath ? ` [${existingClaudePath}]` : ''} [${defaultClaudePath}]: `);
124
+ rl.close();
125
+ const cfg = { ...existing };
126
+ if (appId)
127
+ cfg.appId = appId;
128
+ if (appSecret)
129
+ cfg.appSecret = appSecret;
130
+ if (openId)
131
+ cfg.openId = openId;
132
+ if (port) {
133
+ cfg.relayPort = parseInt(port, 10) || 19200;
134
+ }
135
+ else if (!cfg.relayPort) {
136
+ cfg.relayPort = 19200;
137
+ }
138
+ if (claudePath) {
139
+ cfg.claudePath = claudePath;
140
+ }
141
+ else if (!cfg.claudePath) {
142
+ cfg.claudePath = defaultClaudePath;
143
+ }
144
+ if (!cfg.appId || !cfg.appSecret) {
145
+ console.log('\n❌ appId 和 appSecret 为必填项');
146
+ process.exit(1);
147
+ }
148
+ cfg.enableFeishu = true;
149
+ cfg.enableWeixin = false;
150
+ saveConfig(cfg);
151
+ ok('\n✅ 飞书配置完成!运行 cc-remote 启动。');
152
+ }
153
+ // ---- WeChat setup ----
154
+ async function setupWeixin(rl) {
155
+ heading('=== 微信 Clawbot 登录 ===');
156
+ // Claude path prompt first (shared with Feishu)
157
+ const existing = loadConfig();
158
+ const existingClaudePath = existing.claudePath;
159
+ const defaultClaudePath = process.platform === 'darwin' ? 'claude' : 'claude.exe';
160
+ const claudePath = await prompt(rl, `Claude 可执行文件路径${existingClaudePath ? ` [${existingClaudePath}]` : ''} [${defaultClaudePath}]: `);
161
+ const cfg = { ...existing };
162
+ if (claudePath) {
163
+ cfg.claudePath = claudePath;
164
+ }
165
+ else if (!cfg.claudePath) {
166
+ cfg.claudePath = defaultClaudePath;
167
+ }
168
+ // Relay port config (shared with Feishu)
169
+ const existingPort = existing.relayPort;
170
+ const port = await prompt(rl, `Relay 端口${existingPort ? ` [${existingPort}]` : ''} [19200]: `);
171
+ if (port) {
172
+ cfg.relayPort = parseInt(port, 10) || 19200;
173
+ }
174
+ else if (!cfg.relayPort) {
175
+ cfg.relayPort = 19200;
176
+ }
177
+ // Save early so fetchQRCode can read the configured URL
178
+ saveConfig(cfg);
179
+ // Show existing account info if any
180
+ const { listAccountIds, loadAccount } = require('./weixin/api');
181
+ const existingIds = listAccountIds();
182
+ if (existingIds.length > 0) {
183
+ console.log('\n当前已登录的微信账号:');
184
+ for (const id of existingIds) {
185
+ const data = loadAccount(id);
186
+ if (data?.token) {
187
+ console.log(` Bot ID: ${id}`);
188
+ if (data.userId)
189
+ console.log(` User ID: ${data.userId}`);
190
+ }
191
+ }
192
+ const confirm = await prompt(rl, '\n是否重新登录?(y/N): ');
193
+ if (confirm.toLowerCase() !== 'y') {
194
+ rl.close();
195
+ cfg.enableFeishu = false;
196
+ cfg.enableWeixin = true;
197
+ saveConfig(cfg);
198
+ console.log('已取消,保留原有凭据。');
199
+ return;
200
+ }
201
+ }
202
+ // All prompts done — close readline before entering QR polling loop
203
+ // so the event loop can exit cleanly on cancellation
204
+ rl.close();
205
+ console.log('正在获取二维码...\n');
206
+ const { fetchQRCode, pollQRStatus, saveAccount, BOT_TYPE } = require('./weixin/api');
207
+ let qr;
208
+ try {
209
+ qr = await fetchQRCode(BOT_TYPE, []);
210
+ }
211
+ catch (err) {
212
+ console.log(`\n❌ 获取二维码失败: ${err}`);
213
+ console.log('请检查网络连接后重试。');
214
+ process.exit(1);
215
+ }
216
+ console.log(`二维码链接: ${qr.qrcode_img_content}\n`);
217
+ // On Windows CMD, qrcode-terminal renders poorly — write URL to file + open in browser instead
218
+ if (process.platform === 'win32') {
219
+ const qrUrlFile = path.join(configDir, 'qrcode-url.txt');
220
+ fs.writeFileSync(qrUrlFile, qr.qrcode_img_content, 'utf-8');
221
+ console.log(`二维码已保存到: ${qrUrlFile}`);
222
+ console.log('正在用浏览器打开二维码...\n');
223
+ try {
224
+ const { execSync } = require('child_process');
225
+ execSync(`start "" "${qr.qrcode_img_content}"`, { windowsHide: true });
226
+ }
227
+ catch {
228
+ console.log('请手动打开上面的链接查看二维码');
229
+ }
230
+ }
231
+ else {
232
+ // Try terminal QR display on macOS/Linux
233
+ try {
234
+ const qrterm = require('qrcode-terminal');
235
+ qrterm.generate(qr.qrcode_img_content, { small: true });
236
+ }
237
+ catch {
238
+ console.log('(提示: 安装 qrcode-terminal 可在终端显示二维码)');
239
+ }
240
+ }
241
+ warn('\n用手机微信扫描二维码,等待确认...');
242
+ console.log('(超时时间: 8 分钟,按 Ctrl+C 取消)\n');
243
+ // SIGINT handler: graceful cancellation
244
+ let cancelled = false;
245
+ const onSigint = () => { cancelled = true; };
246
+ process.on('SIGINT', onSigint);
247
+ const deadline = Date.now() + 480000;
248
+ let scannedPrinted = false;
249
+ try {
250
+ while (Date.now() < deadline) {
251
+ if (cancelled) {
252
+ console.log('\n\n配置已取消。');
253
+ forceExit(0);
254
+ }
255
+ let status;
256
+ try {
257
+ status = await pollQRStatus(qr.qrcode);
258
+ }
259
+ catch {
260
+ if (cancelled) {
261
+ console.log('\n\n配置已取消。');
262
+ forceExit(0);
263
+ }
264
+ process.stdout.write('.');
265
+ await new Promise((r) => setTimeout(r, 1000));
266
+ continue;
267
+ }
268
+ switch (status.status) {
269
+ case 'wait':
270
+ break;
271
+ case 'scaned':
272
+ if (!scannedPrinted) {
273
+ process.stdout.write('\n已扫描,正在确认...');
274
+ scannedPrinted = true;
275
+ }
276
+ break;
277
+ case 'confirmed':
278
+ process.removeListener('SIGINT', onSigint);
279
+ if (status.bot_token && status.ilink_bot_id) {
280
+ console.log(`\n\n✅ 登录成功!`);
281
+ console.log(` Bot ID: ${status.ilink_bot_id}`);
282
+ if (status.ilink_user_id)
283
+ console.log(` User ID: ${status.ilink_user_id}`);
284
+ saveAccount(status.ilink_bot_id, {
285
+ token: status.bot_token,
286
+ baseUrl: status.baseurl,
287
+ userId: status.ilink_user_id,
288
+ });
289
+ cfg.enableWeixin = true;
290
+ cfg.enableFeishu = false;
291
+ saveConfig(cfg);
292
+ ok('\n✅ 微信登录完成!运行 cc-remote 启动。');
293
+ process.exit(0);
294
+ }
295
+ else {
296
+ console.log('\n❌ 登录确认但缺少凭据,请重试');
297
+ process.exit(1);
298
+ }
299
+ return;
300
+ case 'expired':
301
+ process.removeListener('SIGINT', onSigint);
302
+ console.log('\n\n❌ 二维码已过期,请重新运行配置');
303
+ process.exit(1);
304
+ case 'binded_redirect':
305
+ process.removeListener('SIGINT', onSigint);
306
+ console.log('\n\n✅ 已连接过此微信,无需重复连接');
307
+ process.exit(0);
308
+ default:
309
+ break;
310
+ }
311
+ await new Promise((r) => setTimeout(r, 1000));
312
+ }
313
+ process.removeListener('SIGINT', onSigint);
314
+ console.log('\n\n❌ 登录超时,请重试');
315
+ process.exit(1);
316
+ }
317
+ finally {
318
+ process.removeListener('SIGINT', onSigint);
319
+ }
320
+ }
321
+ // ---- Main entry ----
322
+ async function runConfigSetup() {
323
+ // SIGINT handler for the menu/prompt phase
324
+ let menuRl = null;
325
+ const onSigint = () => {
326
+ console.log('\n\n配置已取消。');
327
+ if (menuRl)
328
+ menuRl.close();
329
+ forceExit(0);
330
+ };
331
+ process.on('SIGINT', onSigint);
332
+ const cfg = loadConfig();
333
+ const feishuOk = hasFeishuConfig(cfg);
334
+ const weixinOk = hasWeixinConfig();
335
+ console.log(`\n${BOLD}cc-remote 频道配置向导${RESET}\n`);
336
+ console.log('选择要配置的频道:\n');
337
+ console.log(` 1. 飞书 (Feishu)${feishuOk ? ' (已配置)' : ' (未配置)'}`);
338
+ console.log(` 2. 微信 (WeChat)${weixinOk ? ' (已配置)' : ' (未配置)'}`);
339
+ console.log('');
340
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
341
+ menuRl = rl;
342
+ let choice;
343
+ try {
344
+ choice = await prompt(rl, '请输入数字 (1/2): ');
345
+ }
346
+ catch {
347
+ console.log('\n\n配置已取消。');
348
+ return;
349
+ }
350
+ process.removeListener('SIGINT', onSigint);
351
+ try {
352
+ if (choice === '1') {
353
+ await setupFeishu(rl);
354
+ }
355
+ else if (choice === '2') {
356
+ await setupWeixin(rl);
357
+ }
358
+ else {
359
+ console.log('❌ 无效选择,请输入 1 或 2');
360
+ process.exit(1);
361
+ }
362
+ }
363
+ finally {
364
+ rl.close();
365
+ }
366
+ }