ccjk 9.6.1 → 9.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 (56) hide show
  1. package/dist/chunks/boost.mjs +246 -7
  2. package/dist/chunks/ccjk-mcp.mjs +1 -1
  3. package/dist/chunks/ccr.mjs +25 -28
  4. package/dist/chunks/check-updates.mjs +4 -3
  5. package/dist/chunks/claude-code-config-manager.mjs +1 -1
  6. package/dist/chunks/claude-code-incremental-manager.mjs +1 -1
  7. package/dist/chunks/claude-config.mjs +1 -1
  8. package/dist/chunks/codex-config-switch.mjs +3 -4
  9. package/dist/chunks/codex-provider-manager.mjs +1 -2
  10. package/dist/chunks/codex.mjs +204 -3
  11. package/dist/chunks/config-switch.mjs +2 -3
  12. package/dist/chunks/config.mjs +1 -1
  13. package/dist/chunks/doctor.mjs +1 -1
  14. package/dist/chunks/features.mjs +24 -15
  15. package/dist/chunks/hook-installer.mjs +44 -0
  16. package/dist/chunks/index3.mjs +32 -32
  17. package/dist/chunks/init.mjs +129 -87
  18. package/dist/chunks/installer2.mjs +1 -1
  19. package/dist/chunks/interview.mjs +1 -1
  20. package/dist/chunks/mcp.mjs +1058 -17
  21. package/dist/chunks/menu.mjs +140 -56
  22. package/dist/chunks/package.mjs +2 -210
  23. package/dist/chunks/platform.mjs +1 -1
  24. package/dist/chunks/quick-setup.mjs +35 -18
  25. package/dist/chunks/simple-config.mjs +1 -1
  26. package/dist/{shared/ccjk.q1koQxEE.mjs → chunks/smart-defaults.mjs} +77 -79
  27. package/dist/chunks/status.mjs +208 -101
  28. package/dist/chunks/thinking.mjs +1 -1
  29. package/dist/chunks/uninstall.mjs +6 -4
  30. package/dist/chunks/update.mjs +4 -7
  31. package/dist/chunks/version-checker.mjs +1 -1
  32. package/dist/cli.mjs +4 -80
  33. package/dist/index.d.mts +17 -1482
  34. package/dist/index.d.ts +17 -1482
  35. package/dist/index.mjs +12 -4191
  36. package/dist/shared/{ccjk.CSkyCZIM.mjs → ccjk.Bndhan7G.mjs} +4 -242
  37. package/dist/shared/ccjk.CeE8RLG2.mjs +62 -0
  38. package/dist/shared/ccjk.DKojSRzw.mjs +266 -0
  39. package/dist/shared/{ccjk.CItD1fpl.mjs → ccjk.DvIrK0wz.mjs} +1 -1
  40. package/dist/shared/ccjk.LsPZ2PYo.mjs +1048 -0
  41. package/package.json +1 -1
  42. package/dist/chunks/api-adapter.mjs +0 -180
  43. package/dist/chunks/cli.mjs +0 -2227
  44. package/dist/chunks/context-menu.mjs +0 -913
  45. package/dist/chunks/hooks-sync.mjs +0 -1627
  46. package/dist/chunks/mcp-market.mjs +0 -1077
  47. package/dist/chunks/mcp-server.mjs +0 -776
  48. package/dist/chunks/project-detector.mjs +0 -131
  49. package/dist/chunks/provider-registry.mjs +0 -92
  50. package/dist/chunks/setup-wizard.mjs +0 -362
  51. package/dist/chunks/tools.mjs +0 -143
  52. package/dist/chunks/workflows2.mjs +0 -232
  53. package/dist/shared/ccjk.C0pb50xH.mjs +0 -347
  54. package/dist/shared/ccjk.ChMkBmdL.mjs +0 -490
  55. package/dist/shared/ccjk.CtSfXUSh.mjs +0 -209
  56. package/dist/shared/ccjk.xfAjmbJp.mjs +0 -75
@@ -1,2227 +0,0 @@
1
- import { existsSync, writeFileSync, readFileSync } from 'node:fs';
2
- import * as os from 'node:os';
3
- import { homedir } from 'node:os';
4
- import inquirer from 'inquirer';
5
- import ora from 'ora';
6
- import { join } from 'pathe';
7
- import process__default from 'node:process';
8
- import { nanoid } from 'nanoid';
9
- import packageJson from './package.mjs';
10
- import { exec } from 'tinyexec';
11
- import { EventEmitter } from 'node:events';
12
-
13
- const CLOUD_API_BASE = "https://api.claudehome.cn/api/control";
14
- class CloudClient {
15
- config;
16
- heartbeatTimer;
17
- currentTasks = /* @__PURE__ */ new Set();
18
- deviceInfo;
19
- constructor(config) {
20
- this.config = {
21
- heartbeatInterval: 3e4,
22
- debug: false,
23
- ...config
24
- };
25
- }
26
- /**
27
- * Get API base URL
28
- */
29
- getApiBase() {
30
- return this.config.apiUrl || CLOUD_API_BASE;
31
- }
32
- /**
33
- * Get request headers
34
- */
35
- getHeaders() {
36
- return {
37
- "Content-Type": "application/json",
38
- "X-Device-Token": this.config.deviceToken
39
- };
40
- }
41
- /**
42
- * Log debug message
43
- */
44
- debugLog(message) {
45
- if (this.config.debug) {
46
- console.log(`[CloudClient] ${message}`);
47
- }
48
- }
49
- /**
50
- * Register device to cloud
51
- */
52
- async register(info) {
53
- try {
54
- const deviceInfo = {
55
- name: info?.name || `CCJK Device (${os.hostname()})`,
56
- platform: os.platform(),
57
- hostname: os.hostname(),
58
- version: info?.version || packageJson.version
59
- };
60
- this.debugLog(`Registering device: ${deviceInfo.name}`);
61
- const response = await fetch(`${this.getApiBase()}/devices/register`, {
62
- method: "POST",
63
- headers: this.getHeaders(),
64
- body: JSON.stringify(deviceInfo)
65
- });
66
- const result = await response.json();
67
- if (result.success && result.data) {
68
- this.deviceInfo = result.data;
69
- this.debugLog(`Device registered: ${result.data.device.id}`);
70
- }
71
- return result;
72
- } catch (error) {
73
- this.debugLog(`Registration failed: ${error}`);
74
- return {
75
- success: false,
76
- error: error instanceof Error ? error.message : "Unknown error"
77
- };
78
- }
79
- }
80
- /**
81
- * Send heartbeat to cloud and receive pending tasks
82
- */
83
- async heartbeat(status = "online") {
84
- try {
85
- const request = {
86
- status,
87
- currentTasks: Array.from(this.currentTasks),
88
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
89
- };
90
- this.debugLog(`Sending heartbeat: ${status}, tasks: ${request.currentTasks?.length || 0}`);
91
- const response = await fetch(`${this.getApiBase()}/devices/heartbeat`, {
92
- method: "POST",
93
- headers: this.getHeaders(),
94
- body: JSON.stringify(request)
95
- });
96
- const result = await response.json();
97
- if (result.success) {
98
- this.debugLog(`Heartbeat OK, pending tasks: ${result.data?.pendingTasks?.length || 0}`);
99
- }
100
- return result;
101
- } catch (error) {
102
- this.debugLog(`Heartbeat failed: ${error}`);
103
- return {
104
- success: false,
105
- error: error instanceof Error ? error.message : "Unknown error"
106
- };
107
- }
108
- }
109
- /**
110
- * Pull pending tasks from cloud
111
- */
112
- async pullTasks() {
113
- try {
114
- this.debugLog("Pulling pending tasks...");
115
- const response = await fetch(`${this.getApiBase()}/devices/pending`, {
116
- method: "GET",
117
- headers: this.getHeaders()
118
- });
119
- const result = await response.json();
120
- if (result.success && result.data?.commands) {
121
- this.debugLog(`Pulled ${result.data.commands.length} tasks`);
122
- return result.data.commands;
123
- }
124
- return [];
125
- } catch (error) {
126
- this.debugLog(`Pull tasks failed: ${error}`);
127
- return [];
128
- }
129
- }
130
- /**
131
- * Report command execution result to cloud
132
- */
133
- async reportResult(commandId, result) {
134
- try {
135
- this.debugLog(`Reporting result for command ${commandId}: ${result.success ? "success" : "failed"}`);
136
- const response = await fetch(`${this.getApiBase()}/commands/${commandId}/result`, {
137
- method: "POST",
138
- headers: this.getHeaders(),
139
- body: JSON.stringify(result)
140
- });
141
- const fetchResult = await response.json();
142
- if (fetchResult.success) {
143
- this.currentTasks.delete(commandId);
144
- }
145
- return fetchResult;
146
- } catch (error) {
147
- this.debugLog(`Report result failed: ${error}`);
148
- return {
149
- success: false,
150
- error: error instanceof Error ? error.message : "Unknown error"
151
- };
152
- }
153
- }
154
- /**
155
- * Add task to current tasks list
156
- */
157
- addTask(commandId) {
158
- this.currentTasks.add(commandId);
159
- }
160
- /**
161
- * Remove task from current tasks list
162
- */
163
- removeTask(commandId) {
164
- this.currentTasks.delete(commandId);
165
- }
166
- /**
167
- * Start automatic heartbeat
168
- */
169
- startHeartbeat(onTasks) {
170
- this.stopHeartbeat();
171
- this.debugLog(`Starting heartbeat (interval: ${this.config.heartbeatInterval}ms)`);
172
- this.heartbeatTimer = setInterval(async () => {
173
- try {
174
- const status = this.currentTasks.size > 0 ? "busy" : "online";
175
- const response = await this.heartbeat(status);
176
- if (response.success && response.data?.pendingTasks && onTasks) {
177
- const tasks = response.data.pendingTasks;
178
- if (tasks.length > 0) {
179
- onTasks(tasks);
180
- }
181
- }
182
- } catch (error) {
183
- this.debugLog(`Heartbeat error: ${error}`);
184
- }
185
- }, this.config.heartbeatInterval);
186
- }
187
- /**
188
- * Stop automatic heartbeat
189
- */
190
- stopHeartbeat() {
191
- if (this.heartbeatTimer) {
192
- clearInterval(this.heartbeatTimer);
193
- this.heartbeatTimer = void 0;
194
- this.debugLog("Heartbeat stopped");
195
- }
196
- }
197
- /**
198
- * Send offline status before disconnecting
199
- */
200
- async goOffline() {
201
- await this.heartbeat("offline");
202
- this.stopHeartbeat();
203
- this.currentTasks.clear();
204
- }
205
- /**
206
- * Get registered device info
207
- */
208
- getDeviceInfo() {
209
- return this.deviceInfo;
210
- }
211
- /**
212
- * Get current active tasks count
213
- */
214
- getActiveTasksCount() {
215
- return this.currentTasks.size;
216
- }
217
- /**
218
- * Check if client is connected (has active heartbeat)
219
- */
220
- isConnected() {
221
- return this.heartbeatTimer !== void 0;
222
- }
223
- }
224
-
225
- class EmailChecker {
226
- imap;
227
- config;
228
- connected = false;
229
- constructor(config) {
230
- this.config = {
231
- imapHost: "imap.gmail.com",
232
- imapPort: 993,
233
- tls: true,
234
- ...config
235
- };
236
- try {
237
- const ImapConstructor = require("imap");
238
- this.imap = new ImapConstructor({
239
- user: this.config.email,
240
- password: this.config.password,
241
- host: this.config.imapHost,
242
- port: this.config.imapPort,
243
- tls: this.config.tls,
244
- tlsOptions: { rejectUnauthorized: false }
245
- });
246
- } catch (_error) {
247
- throw new Error("imap package is not installed. Install it with: pnpm add imap @types/imap");
248
- }
249
- this.setupEventHandlers();
250
- }
251
- /**
252
- * Setup IMAP event handlers
253
- */
254
- setupEventHandlers() {
255
- this.imap.once("ready", () => {
256
- this.connected = true;
257
- });
258
- this.imap.once("error", (err) => {
259
- console.error("IMAP connection error:", err);
260
- this.connected = false;
261
- });
262
- this.imap.once("end", () => {
263
- this.connected = false;
264
- });
265
- }
266
- /**
267
- * Connect to IMAP server
268
- */
269
- async connect() {
270
- if (this.connected) {
271
- return;
272
- }
273
- return new Promise((resolve, reject) => {
274
- this.imap.once("ready", () => {
275
- this.connected = true;
276
- resolve();
277
- });
278
- this.imap.once("error", (err) => {
279
- reject(err);
280
- });
281
- this.imap.connect();
282
- });
283
- }
284
- /**
285
- * Disconnect from IMAP server
286
- */
287
- disconnect() {
288
- if (this.connected) {
289
- this.imap.end();
290
- this.connected = false;
291
- }
292
- }
293
- /**
294
- * Fetch new emails with [CCJK] tag in subject
295
- */
296
- async fetchNew() {
297
- try {
298
- await this.connect();
299
- return new Promise((resolve, reject) => {
300
- this.imap.openBox("INBOX", false, (err, _box) => {
301
- if (err) {
302
- reject(err);
303
- return;
304
- }
305
- this.imap.search(["UNSEEN", ["SUBJECT", "[CCJK]"]], (searchErr, results) => {
306
- if (searchErr) {
307
- reject(searchErr);
308
- return;
309
- }
310
- if (!results || results.length === 0) {
311
- resolve([]);
312
- return;
313
- }
314
- const emails = [];
315
- const fetch = this.imap.fetch(results, {
316
- bodies: "",
317
- markSeen: true
318
- // Mark as read after fetching
319
- });
320
- fetch.on("message", (msg, _seqno) => {
321
- msg.on("body", (stream) => {
322
- let simpleParserFn;
323
- try {
324
- simpleParserFn = require("mailparser").simpleParser;
325
- } catch {
326
- console.error("mailparser package is not installed. Install it with: pnpm add mailparser @types/mailparser");
327
- return;
328
- }
329
- simpleParserFn(stream, (parseErr, parsed) => {
330
- if (parseErr) {
331
- console.error("Email parse error:", parseErr);
332
- return;
333
- }
334
- emails.push({
335
- id: parsed.messageId,
336
- from: parsed.from?.text || "",
337
- subject: parsed.subject || "",
338
- body: parsed.text || "",
339
- date: parsed.date || /* @__PURE__ */ new Date(),
340
- raw: parsed
341
- });
342
- });
343
- });
344
- msg.once("attributes", (_attrs) => {
345
- });
346
- });
347
- fetch.once("error", (fetchErr) => {
348
- reject(fetchErr);
349
- });
350
- fetch.once("end", () => {
351
- resolve(emails);
352
- });
353
- });
354
- });
355
- });
356
- } catch (error) {
357
- console.error("Failed to fetch emails:", error);
358
- throw error;
359
- }
360
- }
361
- /**
362
- * Parse command from email
363
- */
364
- parseCommand(email) {
365
- let command = email.body.trim();
366
- const lines = command.split("\n");
367
- const commandLines = [];
368
- for (const line of lines) {
369
- const trimmed = line.trim();
370
- if (trimmed.startsWith("--") || trimmed.startsWith("___")) {
371
- break;
372
- }
373
- if (trimmed) {
374
- commandLines.push(trimmed);
375
- }
376
- }
377
- command = commandLines.join("\n").trim();
378
- return command;
379
- }
380
- /**
381
- * Extract sender email address
382
- */
383
- extractSenderEmail(from) {
384
- const match = from.match(/<(.+?)>/);
385
- if (match) {
386
- return match[1].toLowerCase().trim();
387
- }
388
- return from.toLowerCase().trim();
389
- }
390
- }
391
-
392
- const PRESET_TEMPLATES = {
393
- tpl_deploy: {
394
- id: "tpl_deploy",
395
- name: "\u90E8\u7F72\u63A7\u5236",
396
- description: "\u90E8\u7F72\u76F8\u5173\u64CD\u4F5C",
397
- category: "deploy",
398
- actions: [
399
- {
400
- id: "deploy",
401
- label: "\u{1F680} \u90E8\u7F72",
402
- command: "npm run deploy",
403
- confirm: true,
404
- style: "primary" /* Primary */
405
- },
406
- {
407
- id: "restart",
408
- label: "\u{1F504} \u91CD\u542F\u670D\u52A1",
409
- command: "pm2 restart all",
410
- confirm: true,
411
- style: "default" /* Default */
412
- },
413
- {
414
- id: "status",
415
- label: "\u{1F4CA} \u670D\u52A1\u72B6\u6001",
416
- command: "pm2 status",
417
- confirm: false,
418
- style: "default" /* Default */
419
- },
420
- {
421
- id: "logs",
422
- label: "\u{1F4CB} \u67E5\u770B\u65E5\u5FD7",
423
- command: "pm2 logs --lines 50 --nostream",
424
- confirm: false,
425
- style: "default" /* Default */
426
- },
427
- {
428
- id: "stop",
429
- label: "\u23F9\uFE0F \u505C\u6B62\u670D\u52A1",
430
- command: "pm2 stop all",
431
- confirm: true,
432
- style: "danger" /* Danger */
433
- }
434
- ]
435
- },
436
- tpl_database: {
437
- id: "tpl_database",
438
- name: "\u6570\u636E\u5E93\u63A7\u5236",
439
- description: "\u6570\u636E\u5E93\u76F8\u5173\u64CD\u4F5C",
440
- category: "database",
441
- actions: [
442
- {
443
- id: "migrate",
444
- label: "\u{1F504} \u8FD0\u884C\u8FC1\u79FB",
445
- command: "npm run db:migrate",
446
- confirm: true,
447
- style: "primary" /* Primary */
448
- },
449
- {
450
- id: "seed",
451
- label: "\u{1F331} \u586B\u5145\u6570\u636E",
452
- command: "npm run db:seed",
453
- confirm: true,
454
- style: "default" /* Default */
455
- },
456
- {
457
- id: "backup",
458
- label: "\u{1F4BE} \u5907\u4EFD\u6570\u636E\u5E93",
459
- command: "npm run db:backup",
460
- confirm: false,
461
- style: "default" /* Default */
462
- },
463
- {
464
- id: "restore",
465
- label: "\u267B\uFE0F \u6062\u590D\u6570\u636E\u5E93",
466
- command: "npm run db:restore",
467
- confirm: true,
468
- style: "danger" /* Danger */
469
- }
470
- ]
471
- },
472
- tpl_git: {
473
- id: "tpl_git",
474
- name: "Git \u64CD\u4F5C",
475
- description: "Git \u7248\u672C\u63A7\u5236",
476
- category: "git",
477
- actions: [
478
- {
479
- id: "pull",
480
- label: "\u2B07\uFE0F \u62C9\u53D6\u66F4\u65B0",
481
- command: "git pull",
482
- confirm: false,
483
- style: "primary" /* Primary */
484
- },
485
- {
486
- id: "status",
487
- label: "\u{1F4CA} \u72B6\u6001\u68C0\u67E5",
488
- command: "git status",
489
- confirm: false,
490
- style: "default" /* Default */
491
- },
492
- {
493
- id: "log",
494
- label: "\u{1F4CB} \u63D0\u4EA4\u5386\u53F2",
495
- command: "git log --oneline -10",
496
- confirm: false,
497
- style: "default" /* Default */
498
- },
499
- {
500
- id: "push",
501
- label: "\u2B06\uFE0F \u63A8\u9001\u66F4\u6539",
502
- command: "git push",
503
- confirm: true,
504
- style: "default" /* Default */
505
- }
506
- ]
507
- },
508
- tpl_build: {
509
- id: "tpl_build",
510
- name: "\u6784\u5EFA\u63A7\u5236",
511
- description: "\u9879\u76EE\u6784\u5EFA\u64CD\u4F5C",
512
- category: "build",
513
- actions: [
514
- {
515
- id: "build",
516
- label: "\u{1F528} \u6784\u5EFA",
517
- command: "npm run build",
518
- confirm: true,
519
- style: "primary" /* Primary */
520
- },
521
- {
522
- id: "dev",
523
- label: "\u{1F6E0}\uFE0F \u5F00\u53D1\u6A21\u5F0F",
524
- command: "npm run dev",
525
- confirm: false,
526
- style: "default" /* Default */
527
- },
528
- {
529
- id: "test",
530
- label: "\u{1F9EA} \u8FD0\u884C\u6D4B\u8BD5",
531
- command: "npm run test",
532
- confirm: false,
533
- style: "default" /* Default */
534
- },
535
- {
536
- id: "lint",
537
- label: "\u{1F50D} \u4EE3\u7801\u68C0\u67E5",
538
- command: "npm run lint",
539
- confirm: false,
540
- style: "default" /* Default */
541
- },
542
- {
543
- id: "clean",
544
- label: "\u{1F9F9} \u6E05\u7406\u6784\u5EFA",
545
- command: "npm run clean",
546
- confirm: true,
547
- style: "danger" /* Danger */
548
- }
549
- ]
550
- },
551
- tpl_docker: {
552
- id: "tpl_docker",
553
- name: "Docker \u63A7\u5236",
554
- description: "Docker \u5BB9\u5668\u64CD\u4F5C",
555
- category: "docker",
556
- actions: [
557
- {
558
- id: "ps",
559
- label: "\u{1F4CA} \u5BB9\u5668\u5217\u8868",
560
- command: "docker ps",
561
- confirm: false,
562
- style: "default" /* Default */
563
- },
564
- {
565
- id: "restart",
566
- label: "\u{1F504} \u91CD\u542F\u5BB9\u5668",
567
- command: "docker restart $(docker ps -q)",
568
- confirm: true,
569
- style: "primary" /* Primary */
570
- },
571
- {
572
- id: "logs",
573
- label: "\u{1F4CB} \u5BB9\u5668\u65E5\u5FD7",
574
- command: "docker logs --tail 100 $(docker ps -q | head -1)",
575
- confirm: false,
576
- style: "default" /* Default */
577
- },
578
- {
579
- id: "stop",
580
- label: "\u23F9\uFE0F \u505C\u6B62\u5BB9\u5668",
581
- command: "docker stop $(docker ps -q)",
582
- confirm: true,
583
- style: "danger" /* Danger */
584
- }
585
- ]
586
- },
587
- tpl_system: {
588
- id: "tpl_system",
589
- name: "\u7CFB\u7EDF\u63A7\u5236",
590
- description: "\u7CFB\u7EDF\u7EA7\u522B\u64CD\u4F5C",
591
- category: "system",
592
- actions: [
593
- {
594
- id: "uptime",
595
- label: "\u23F1\uFE0F \u8FD0\u884C\u65F6\u95F4",
596
- command: "uptime",
597
- confirm: false,
598
- style: "default" /* Default */
599
- },
600
- {
601
- id: "disk",
602
- label: "\u{1F4BE} \u78C1\u76D8\u4F7F\u7528",
603
- command: "df -h",
604
- confirm: false,
605
- style: "default" /* Default */
606
- },
607
- {
608
- id: "memory",
609
- label: "\u{1F9E0} \u5185\u5B58\u4F7F\u7528",
610
- command: "free -h",
611
- confirm: false,
612
- style: "default" /* Default */
613
- },
614
- {
615
- id: "top",
616
- label: "\u{1F4CA} \u8FDB\u7A0B\u76D1\u63A7",
617
- command: "top -b -n 1 | head -20",
618
- confirm: false,
619
- style: "default" /* Default */
620
- }
621
- ]
622
- }
623
- };
624
- class MobileControlClient {
625
- config;
626
- customTemplates = /* @__PURE__ */ new Map();
627
- constructor(config) {
628
- this.config = {
629
- apiUrl: "https://api.claudehome.cn/api/control",
630
- debug: false,
631
- ...config
632
- };
633
- }
634
- /**
635
- * Get API base URL
636
- */
637
- getApiBase() {
638
- return this.config.apiUrl || "https://api.claudehome.cn/api/control";
639
- }
640
- /**
641
- * Get request headers
642
- */
643
- getHeaders() {
644
- return {
645
- "Content-Type": "application/json",
646
- "Authorization": `Bearer ${this.config.userToken}`
647
- };
648
- }
649
- /**
650
- * Debug log
651
- */
652
- debugLog(message) {
653
- if (this.config.debug) {
654
- console.log(`[MobileControl] ${message}`);
655
- }
656
- }
657
- /**
658
- * Get all available templates (presets + custom)
659
- */
660
- getTemplates() {
661
- return [
662
- ...Object.values(PRESET_TEMPLATES),
663
- ...Array.from(this.customTemplates.values())
664
- ];
665
- }
666
- /**
667
- * Get template by ID
668
- */
669
- getTemplate(id) {
670
- return PRESET_TEMPLATES[id] || this.customTemplates.get(id);
671
- }
672
- /**
673
- * Register custom template
674
- */
675
- registerTemplate(template) {
676
- this.customTemplates.set(template.id, template);
677
- this.debugLog(`Registered custom template: ${template.id}`);
678
- }
679
- /**
680
- * Unregister custom template
681
- */
682
- unregisterTemplate(id) {
683
- this.customTemplates.delete(id);
684
- this.debugLog(`Unregistered custom template: ${id}`);
685
- }
686
- /**
687
- * Send control card to mobile
688
- */
689
- async sendCard(request) {
690
- try {
691
- const template = this.getTemplate(request.templateId);
692
- if (!template) {
693
- return {
694
- success: false,
695
- error: `Template not found: ${request.templateId}`
696
- };
697
- }
698
- this.debugLog(`Sending card: ${request.templateId} to ${request.channel}`);
699
- const response = await fetch(`${this.getApiBase()}/mobile/send-card`, {
700
- method: "POST",
701
- headers: this.getHeaders(),
702
- body: JSON.stringify({
703
- ...request,
704
- template: {
705
- ...template,
706
- message: request.message
707
- }
708
- })
709
- });
710
- const result = await response.json();
711
- if (result.success) {
712
- this.debugLog(`Card sent: ${result.data?.cardId}`);
713
- }
714
- return result;
715
- } catch (error) {
716
- this.debugLog(`Send card failed: ${error}`);
717
- return {
718
- success: false,
719
- error: error instanceof Error ? error.message : "Unknown error"
720
- };
721
- }
722
- }
723
- /**
724
- * Send quick action (single action card)
725
- */
726
- async sendQuickAction(deviceId, channel, action, message) {
727
- const tempTemplate = {
728
- id: `temp_${Date.now()}`,
729
- name: action.label,
730
- description: message || action.label};
731
- return this.sendCard({
732
- deviceId,
733
- channel,
734
- templateId: tempTemplate.id,
735
- message
736
- });
737
- }
738
- /**
739
- * List available templates by category
740
- */
741
- listTemplatesByCategory(category) {
742
- const all = this.getTemplates();
743
- return all.filter((t) => t.category === category);
744
- }
745
- /**
746
- * Search templates
747
- */
748
- searchTemplates(query) {
749
- const all = this.getTemplates();
750
- const lowerQuery = query.toLowerCase();
751
- return all.filter(
752
- (t) => t.name.toLowerCase().includes(lowerQuery) || t.description.toLowerCase().includes(lowerQuery) || t.actions.some((a) => a.label.toLowerCase().includes(lowerQuery))
753
- );
754
- }
755
- /**
756
- * Get all categories
757
- */
758
- getCategories() {
759
- const all = this.getTemplates();
760
- return Array.from(new Set(all.map((t) => t.category))).sort();
761
- }
762
- }
763
-
764
- class ResultSender {
765
- transporter;
766
- config;
767
- constructor(config) {
768
- this.config = {
769
- smtpHost: "smtp.gmail.com",
770
- smtpPort: 587,
771
- tls: true,
772
- ...config
773
- };
774
- try {
775
- const nodemailer = require("nodemailer");
776
- this.transporter = nodemailer.createTransport({
777
- host: this.config.smtpHost,
778
- port: this.config.smtpPort,
779
- secure: false,
780
- // Use STARTTLS
781
- auth: {
782
- user: this.config.email,
783
- pass: this.config.password
784
- }
785
- });
786
- } catch (_error) {
787
- throw new Error("nodemailer package is not installed. Install it with: pnpm add nodemailer @types/nodemailer");
788
- }
789
- }
790
- /**
791
- * Send task result email
792
- */
793
- async send(to, result, command) {
794
- try {
795
- const subject = result.success ? `\u2705 Task Completed: ${this.truncate(command, 50)}` : `\u274C Task Failed: ${this.truncate(command, 50)}`;
796
- const html = this.generateHtml(result, command);
797
- const text = this.generateText(result, command);
798
- await this.transporter.sendMail({
799
- from: `CCJK Daemon <${this.config.email}>`,
800
- to,
801
- subject,
802
- text,
803
- html
804
- });
805
- console.log(`\u{1F4E7} Result email sent to ${to}`);
806
- } catch (error) {
807
- console.error("Failed to send result email:", error);
808
- throw error;
809
- }
810
- }
811
- /**
812
- * Generate HTML email content
813
- */
814
- generateHtml(result, command) {
815
- const statusIcon = result.success ? "\u2705" : "\u274C";
816
- const statusText = result.success ? "Completed" : "Failed";
817
- const statusColor = result.success ? "#10b981" : "#ef4444";
818
- return `
819
- <!DOCTYPE html>
820
- <html>
821
- <head>
822
- <meta charset="utf-8">
823
- <style>
824
- body {
825
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
826
- line-height: 1.6;
827
- color: #333;
828
- max-width: 600px;
829
- margin: 0 auto;
830
- padding: 20px;
831
- }
832
- .header {
833
- background: ${statusColor};
834
- color: white;
835
- padding: 20px;
836
- border-radius: 8px 8px 0 0;
837
- text-align: center;
838
- }
839
- .content {
840
- background: #f9fafb;
841
- padding: 20px;
842
- border: 1px solid #e5e7eb;
843
- border-top: none;
844
- border-radius: 0 0 8px 8px;
845
- }
846
- .section {
847
- margin-bottom: 20px;
848
- }
849
- .label {
850
- font-weight: 600;
851
- color: #6b7280;
852
- font-size: 12px;
853
- text-transform: uppercase;
854
- margin-bottom: 5px;
855
- }
856
- .value {
857
- background: white;
858
- padding: 12px;
859
- border-radius: 4px;
860
- border: 1px solid #e5e7eb;
861
- font-family: 'Monaco', 'Courier New', monospace;
862
- font-size: 13px;
863
- white-space: pre-wrap;
864
- word-break: break-all;
865
- }
866
- .meta {
867
- display: flex;
868
- justify-content: space-between;
869
- font-size: 12px;
870
- color: #6b7280;
871
- margin-top: 20px;
872
- padding-top: 20px;
873
- border-top: 1px solid #e5e7eb;
874
- }
875
- .error {
876
- color: #ef4444;
877
- }
878
- </style>
879
- </head>
880
- <body>
881
- <div class="header">
882
- <h1>${statusIcon} Task ${statusText}</h1>
883
- </div>
884
- <div class="content">
885
- <div class="section">
886
- <div class="label">Command</div>
887
- <div class="value">${this.escapeHtml(command)}</div>
888
- </div>
889
-
890
- ${result.output ? `
891
- <div class="section">
892
- <div class="label">Output</div>
893
- <div class="value">${this.escapeHtml(result.output)}</div>
894
- </div>
895
- ` : ""}
896
-
897
- ${result.error ? `
898
- <div class="section">
899
- <div class="label error">Error</div>
900
- <div class="value error">${this.escapeHtml(result.error)}</div>
901
- </div>
902
- ` : ""}
903
-
904
- <div class="meta">
905
- <span>Duration: ${this.formatDuration(result.duration)}</span>
906
- <span>Exit Code: ${result.exitCode || 0}</span>
907
- <span>Task ID: ${result.taskId}</span>
908
- </div>
909
- </div>
910
- </body>
911
- </html>
912
- `.trim();
913
- }
914
- /**
915
- * Generate plain text email content
916
- */
917
- generateText(result, command) {
918
- const statusIcon = result.success ? "\u2705" : "\u274C";
919
- const statusText = result.success ? "Completed" : "Failed";
920
- let text = `${statusIcon} Task ${statusText}
921
-
922
- `;
923
- text += `Command:
924
- ${command}
925
-
926
- `;
927
- if (result.output) {
928
- text += `Output:
929
- ${result.output}
930
-
931
- `;
932
- }
933
- if (result.error) {
934
- text += `Error:
935
- ${result.error}
936
-
937
- `;
938
- }
939
- text += `---
940
- `;
941
- text += `Duration: ${this.formatDuration(result.duration)}
942
- `;
943
- text += `Exit Code: ${result.exitCode || 0}
944
- `;
945
- text += `Task ID: ${result.taskId}
946
- `;
947
- return text;
948
- }
949
- /**
950
- * Truncate string
951
- */
952
- truncate(str, maxLength) {
953
- if (str.length <= maxLength) {
954
- return str;
955
- }
956
- return `${str.substring(0, maxLength)}...`;
957
- }
958
- /**
959
- * Format duration
960
- */
961
- formatDuration(ms) {
962
- if (ms < 1e3) {
963
- return `${ms}ms`;
964
- }
965
- if (ms < 6e4) {
966
- return `${(ms / 1e3).toFixed(1)}s`;
967
- }
968
- const minutes = Math.floor(ms / 6e4);
969
- const seconds = (ms % 6e4 / 1e3).toFixed(0);
970
- return `${minutes}m ${seconds}s`;
971
- }
972
- /**
973
- * Escape HTML
974
- */
975
- escapeHtml(text) {
976
- const map = {
977
- "&": "&amp;",
978
- "<": "&lt;",
979
- ">": "&gt;",
980
- '"': "&quot;",
981
- "'": "&#039;"
982
- };
983
- return text.replace(/[&<>"']/g, (m) => map[m]);
984
- }
985
- /**
986
- * Test email connection
987
- */
988
- async testConnection() {
989
- try {
990
- await this.transporter.verify();
991
- return true;
992
- } catch (error) {
993
- console.error("Email connection test failed:", error);
994
- return false;
995
- }
996
- }
997
- }
998
-
999
- class TaskExecutor {
1000
- timeout;
1001
- constructor(timeout = 3e5) {
1002
- this.timeout = timeout;
1003
- }
1004
- /**
1005
- * Execute a task
1006
- */
1007
- async execute(task) {
1008
- const startTime = Date.now();
1009
- try {
1010
- console.log(`\u{1F504} Executing task ${task.id}: ${task.command}`);
1011
- const parts = task.command.trim().split(/\s+/);
1012
- const command = parts[0];
1013
- const args = parts.slice(1);
1014
- const result = await exec(command, args, {
1015
- nodeOptions: {
1016
- cwd: task.projectPath
1017
- },
1018
- timeout: this.timeout
1019
- });
1020
- const duration = Date.now() - startTime;
1021
- console.log(`\u2705 Task ${task.id} completed in ${duration}ms`);
1022
- return {
1023
- taskId: task.id,
1024
- success: true,
1025
- output: result.stdout || "",
1026
- error: null,
1027
- duration,
1028
- exitCode: 0
1029
- };
1030
- } catch (error) {
1031
- const duration = Date.now() - startTime;
1032
- console.error(`\u274C Task ${task.id} failed:`, error.message);
1033
- return {
1034
- taskId: task.id,
1035
- success: false,
1036
- output: error.stdout || null,
1037
- error: error.stderr || error.message,
1038
- duration,
1039
- exitCode: error.exitCode || 1
1040
- };
1041
- }
1042
- }
1043
- /**
1044
- * Execute multiple tasks sequentially
1045
- */
1046
- async executeSequential(tasks) {
1047
- const results = [];
1048
- for (const task of tasks) {
1049
- const result = await this.execute(task);
1050
- results.push(result);
1051
- if (!result.success) {
1052
- break;
1053
- }
1054
- }
1055
- return results;
1056
- }
1057
- /**
1058
- * Execute multiple tasks in parallel
1059
- */
1060
- async executeParallel(tasks) {
1061
- const promises = tasks.map((task) => this.execute(task));
1062
- return Promise.all(promises);
1063
- }
1064
- }
1065
-
1066
- class SecurityManager {
1067
- allowedSenders;
1068
- allowedCommands;
1069
- blockedCommands;
1070
- constructor(config) {
1071
- this.allowedSenders = new Set(config.allowedSenders);
1072
- this.allowedCommands = new Set(config.allowedCommands || this.getDefaultAllowedCommands());
1073
- this.blockedCommands = new Set(config.blockedCommands || this.getDefaultBlockedCommands());
1074
- }
1075
- /**
1076
- * Check if sender is allowed
1077
- */
1078
- checkSender(email) {
1079
- const normalizedEmail = email.toLowerCase().trim();
1080
- if (this.allowedSenders.has(normalizedEmail)) {
1081
- return { allowed: true };
1082
- }
1083
- return {
1084
- allowed: false,
1085
- reason: `Sender ${email} is not in the whitelist`
1086
- };
1087
- }
1088
- /**
1089
- * Check if command is allowed
1090
- */
1091
- checkCommand(command) {
1092
- const normalizedCommand = command.trim();
1093
- for (const blocked of this.blockedCommands) {
1094
- if (normalizedCommand.includes(blocked)) {
1095
- return {
1096
- allowed: false,
1097
- reason: `Command contains blocked pattern: ${blocked}`
1098
- };
1099
- }
1100
- }
1101
- if (this.allowedCommands.size === 0) {
1102
- return { allowed: true };
1103
- }
1104
- for (const allowed of this.allowedCommands) {
1105
- if (normalizedCommand.startsWith(allowed)) {
1106
- return { allowed: true };
1107
- }
1108
- }
1109
- return {
1110
- allowed: false,
1111
- reason: `Command does not match any allowed pattern`
1112
- };
1113
- }
1114
- /**
1115
- * Perform full security check
1116
- */
1117
- performSecurityCheck(sender, command) {
1118
- const senderCheck = this.checkSender(sender);
1119
- if (!senderCheck.allowed) {
1120
- return senderCheck;
1121
- }
1122
- const commandCheck = this.checkCommand(command);
1123
- if (!commandCheck.allowed) {
1124
- return commandCheck;
1125
- }
1126
- return { allowed: true };
1127
- }
1128
- /**
1129
- * Add allowed sender
1130
- */
1131
- addAllowedSender(email) {
1132
- this.allowedSenders.add(email.toLowerCase().trim());
1133
- }
1134
- /**
1135
- * Remove allowed sender
1136
- */
1137
- removeAllowedSender(email) {
1138
- this.allowedSenders.delete(email.toLowerCase().trim());
1139
- }
1140
- /**
1141
- * Get default allowed commands
1142
- */
1143
- getDefaultAllowedCommands() {
1144
- return [
1145
- "npm test",
1146
- "npm run",
1147
- "pnpm test",
1148
- "pnpm run",
1149
- "yarn test",
1150
- "yarn run",
1151
- "git status",
1152
- "git log",
1153
- "git diff",
1154
- "claude",
1155
- "ccjk",
1156
- "status",
1157
- "echo",
1158
- "ls",
1159
- "pwd"
1160
- ];
1161
- }
1162
- /**
1163
- * Get default blocked commands
1164
- */
1165
- getDefaultBlockedCommands() {
1166
- return [
1167
- "rm -rf",
1168
- "rm -fr",
1169
- "sudo",
1170
- "su",
1171
- "chmod 777",
1172
- "chown",
1173
- "git push --force",
1174
- "git push -f",
1175
- "dd if=",
1176
- "mkfs",
1177
- "format",
1178
- ":(){:|:&};:",
1179
- "curl | sh",
1180
- "wget | sh",
1181
- "eval",
1182
- "exec"
1183
- ];
1184
- }
1185
- }
1186
-
1187
- class WSLogStreamer extends EventEmitter {
1188
- config;
1189
- ws = null;
1190
- state = "disconnected" /* Disconnected */;
1191
- reconnectTimer = null;
1192
- reconnectAttempts = 0;
1193
- pingTimer = null;
1194
- activeStreams = /* @__PURE__ */ new Set();
1195
- // Default WebSocket base URL
1196
- static DEFAULT_WS_URL = "wss://api.claudehome.cn/api/control/logs";
1197
- constructor(config) {
1198
- super();
1199
- this.config = {
1200
- wsUrl: WSLogStreamer.DEFAULT_WS_URL,
1201
- autoReconnect: true,
1202
- reconnectDelay: 5e3,
1203
- maxReconnectAttempts: 10,
1204
- pingInterval: 3e4,
1205
- debug: false,
1206
- ...config
1207
- };
1208
- }
1209
- /**
1210
- * Get WebSocket URL with auth token
1211
- */
1212
- getUrl() {
1213
- return `${this.config.wsUrl}/${this.config.deviceId}?token=${this.config.token}`;
1214
- }
1215
- /**
1216
- * Debug log
1217
- */
1218
- debugLog(message) {
1219
- if (this.config.debug) {
1220
- console.log(`[WSLogStreamer] ${message}`);
1221
- }
1222
- }
1223
- /**
1224
- * Update state and emit event
1225
- */
1226
- setState(newState) {
1227
- const oldState = this.state;
1228
- this.state = newState;
1229
- this.emit("stateChange", { oldState, newState });
1230
- this.debugLog(`State: ${oldState} -> ${newState}`);
1231
- }
1232
- /**
1233
- * Connect to WebSocket server
1234
- */
1235
- async connect() {
1236
- if (this.ws && (this.ws.readyState === WebSocket.OPEN || this.ws.readyState === WebSocket.CONNECTING)) {
1237
- this.debugLog("Already connected or connecting");
1238
- return;
1239
- }
1240
- this.setState("connecting" /* Connecting */);
1241
- this.reconnectAttempts = 0;
1242
- return new Promise((resolve, reject) => {
1243
- try {
1244
- const url = this.getUrl();
1245
- this.debugLog(`Connecting to: ${url.replace(this.config.token, "***")}`);
1246
- this.connectWithFetch(url).then(() => {
1247
- this.onConnected();
1248
- resolve();
1249
- }).catch((err) => {
1250
- this.setState("error" /* Error */);
1251
- this.handleConnectError(err);
1252
- reject(err);
1253
- });
1254
- } catch (error) {
1255
- this.setState("error" /* Error */);
1256
- reject(error);
1257
- }
1258
- });
1259
- }
1260
- /**
1261
- * Connect using fetch with Server-Sent Events fallback
1262
- * This is needed for Node.js environments without native WebSocket
1263
- */
1264
- async connectWithFetch(_url) {
1265
- this.debugLog("Creating simulated WebSocket connection");
1266
- this.ws = {
1267
- readyState: WebSocket.OPEN,
1268
- send: (data) => {
1269
- this.debugLog(`Sent: ${typeof data === "string" ? data : "[binary]"}`);
1270
- },
1271
- close: () => {
1272
- this.debugLog("Connection closed");
1273
- this.cleanup();
1274
- },
1275
- addEventListener: (_event, _listener) => {
1276
- },
1277
- removeEventListener: (_event, _listener) => {
1278
- }
1279
- };
1280
- this.debugLog("Connected (simulated)");
1281
- }
1282
- /**
1283
- * Handle successful connection
1284
- */
1285
- onConnected() {
1286
- this.setState("connected" /* Connected */);
1287
- this.reconnectAttempts = 0;
1288
- this.emit("connected");
1289
- this.startPing();
1290
- this.subscribe(this.config.deviceId);
1291
- }
1292
- /**
1293
- * Handle connection error
1294
- */
1295
- handleConnectError(error) {
1296
- this.debugLog(`Connection error: ${error.message}`);
1297
- this.emit("error", error);
1298
- if (this.config.autoReconnect && this.reconnectAttempts < (this.config.maxReconnectAttempts || 10)) {
1299
- this.scheduleReconnect();
1300
- }
1301
- }
1302
- /**
1303
- * Schedule reconnection attempt
1304
- */
1305
- scheduleReconnect() {
1306
- if (this.reconnectTimer) {
1307
- return;
1308
- }
1309
- this.setState("reconnecting" /* Reconnecting */);
1310
- this.reconnectAttempts++;
1311
- const delay = (this.config.reconnectDelay || 5e3) * Math.min(this.reconnectAttempts, 5);
1312
- this.debugLog(`Reconnecting in ${delay}ms (attempt ${this.reconnectAttempts})`);
1313
- this.reconnectTimer = setTimeout(() => {
1314
- this.reconnectTimer = null;
1315
- this.connect().catch((err) => {
1316
- this.debugLog(`Reconnect failed: ${err.message}`);
1317
- });
1318
- }, delay);
1319
- }
1320
- /**
1321
- * Start ping interval
1322
- */
1323
- startPing() {
1324
- this.stopPing();
1325
- this.pingTimer = setInterval(() => {
1326
- if (this.isConnected()) {
1327
- this.ping();
1328
- }
1329
- }, this.config.pingInterval || 3e4);
1330
- }
1331
- /**
1332
- * Stop ping interval
1333
- */
1334
- stopPing() {
1335
- if (this.pingTimer) {
1336
- clearInterval(this.pingTimer);
1337
- this.pingTimer = null;
1338
- }
1339
- }
1340
- /**
1341
- * Subscribe to log stream
1342
- */
1343
- subscribe(deviceId, commandId) {
1344
- const message = {
1345
- type: "subscribe" /* Subscribe */,
1346
- deviceId,
1347
- commandId
1348
- };
1349
- this.send(message);
1350
- const streamKey = commandId ? `${deviceId}:${commandId}` : deviceId;
1351
- this.activeStreams.add(streamKey);
1352
- this.debugLog(`Subscribed to: ${streamKey}`);
1353
- this.emit("subscribed", { deviceId, commandId });
1354
- }
1355
- /**
1356
- * Unsubscribe from log stream
1357
- */
1358
- unsubscribe(streamId) {
1359
- const message = {
1360
- type: "unsubscribe" /* Unsubscribe */,
1361
- streamId
1362
- };
1363
- this.send(message);
1364
- this.activeStreams.delete(streamId);
1365
- this.debugLog(`Unsubscribed from: ${streamId}`);
1366
- this.emit("unsubscribed", { streamId });
1367
- }
1368
- /**
1369
- * Send ping to server
1370
- */
1371
- ping() {
1372
- const message = {
1373
- type: "ping" /* Ping */
1374
- };
1375
- this.send(message);
1376
- }
1377
- /**
1378
- * Send message to server
1379
- */
1380
- send(message) {
1381
- if (!this.isConnected()) {
1382
- this.debugLog("Cannot send: not connected");
1383
- return;
1384
- }
1385
- try {
1386
- this.ws?.send(JSON.stringify(message));
1387
- } catch (error) {
1388
- this.debugLog(`Send error: ${error}`);
1389
- }
1390
- }
1391
- /**
1392
- * Stream log entry to server
1393
- */
1394
- streamLog(entry) {
1395
- if (!this.isConnected()) {
1396
- this.debugLog("Cannot stream log: not connected");
1397
- return;
1398
- }
1399
- const message = {
1400
- type: "log" /* Log */,
1401
- data: entry
1402
- };
1403
- this.send(message);
1404
- this.emit("logSent", entry);
1405
- }
1406
- /**
1407
- * Check if connected
1408
- */
1409
- isConnected() {
1410
- return this.state === "connected" /* Connected */ && this.ws?.readyState === WebSocket.OPEN;
1411
- }
1412
- /**
1413
- * Get current state
1414
- */
1415
- getState() {
1416
- return this.state;
1417
- }
1418
- /**
1419
- * Disconnect from server
1420
- */
1421
- disconnect() {
1422
- this.debugLog("Disconnecting...");
1423
- if (this.reconnectTimer) {
1424
- clearTimeout(this.reconnectTimer);
1425
- this.reconnectTimer = null;
1426
- }
1427
- this.stopPing();
1428
- if (this.ws) {
1429
- this.ws.close();
1430
- this.ws = null;
1431
- }
1432
- this.activeStreams.clear();
1433
- this.setState("disconnected" /* Disconnected */);
1434
- this.emit("disconnected");
1435
- }
1436
- /**
1437
- * Cleanup resources
1438
- */
1439
- cleanup() {
1440
- this.stopPing();
1441
- if (this.reconnectTimer) {
1442
- clearTimeout(this.reconnectTimer);
1443
- this.reconnectTimer = null;
1444
- }
1445
- this.activeStreams.clear();
1446
- }
1447
- /**
1448
- * Get active streams count
1449
- */
1450
- getActiveStreamsCount() {
1451
- return this.activeStreams.size;
1452
- }
1453
- /**
1454
- * Get device ID
1455
- */
1456
- getDeviceId() {
1457
- return this.config.deviceId;
1458
- }
1459
- }
1460
- function createLogEntry(level, source, message, commandId) {
1461
- return {
1462
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1463
- level,
1464
- source,
1465
- message,
1466
- commandId
1467
- };
1468
- }
1469
- class DaemonLogStreamer {
1470
- streamer;
1471
- originalConsole;
1472
- consoleInterceptEnabled = false;
1473
- constructor(config) {
1474
- this.streamer = new WSLogStreamer(config);
1475
- this.originalConsole = {
1476
- log: console.log.bind(console),
1477
- warn: console.warn.bind(console),
1478
- error: console.error.bind(console),
1479
- debug: console.debug.bind(console)
1480
- };
1481
- }
1482
- /**
1483
- * Start log streaming
1484
- */
1485
- async start() {
1486
- await this.streamer.connect();
1487
- this.enableConsoleInterception();
1488
- }
1489
- /**
1490
- * Stop log streaming
1491
- */
1492
- stop() {
1493
- this.disableConsoleInterception();
1494
- this.streamer.disconnect();
1495
- }
1496
- /**
1497
- * Enable console interception
1498
- */
1499
- enableConsoleInterception() {
1500
- if (this.consoleInterceptEnabled) {
1501
- return;
1502
- }
1503
- const self = this;
1504
- console.log = function(...args) {
1505
- self.originalConsole.log(...args);
1506
- self.streamLog("info" /* Info */, "console", args.join(" "));
1507
- };
1508
- console.warn = function(...args) {
1509
- self.originalConsole.warn(...args);
1510
- self.streamLog("warn" /* Warn */, "console", args.join(" "));
1511
- };
1512
- console.error = function(...args) {
1513
- self.originalConsole.error(...args);
1514
- self.streamLog("error" /* Error */, "console", args.join(" "));
1515
- };
1516
- console.debug = function(...args) {
1517
- self.originalConsole.debug(...args);
1518
- self.streamLog("debug" /* Debug */, "console", args.join(" "));
1519
- };
1520
- this.consoleInterceptEnabled = true;
1521
- }
1522
- /**
1523
- * Disable console interception
1524
- */
1525
- disableConsoleInterception() {
1526
- if (!this.consoleInterceptEnabled) {
1527
- return;
1528
- }
1529
- console.log = this.originalConsole.log;
1530
- console.warn = this.originalConsole.warn;
1531
- console.error = this.originalConsole.error;
1532
- console.debug = this.originalConsole.debug;
1533
- this.consoleInterceptEnabled = false;
1534
- }
1535
- /**
1536
- * Stream log entry
1537
- */
1538
- streamLog(level, source, message) {
1539
- if (this.streamer.isConnected()) {
1540
- this.streamer.streamLog(createLogEntry(level, source, message));
1541
- }
1542
- }
1543
- /**
1544
- * Stream a custom log entry
1545
- */
1546
- log(entry) {
1547
- if (this.streamer.isConnected()) {
1548
- this.streamer.streamLog(entry);
1549
- }
1550
- }
1551
- /**
1552
- * Subscribe to command-specific logs
1553
- */
1554
- subscribeToCommand(commandId) {
1555
- this.streamer.subscribe(this.streamer.getDeviceId(), commandId);
1556
- }
1557
- /**
1558
- * Unsubscribe from command logs
1559
- */
1560
- unsubscribeFromCommand(commandId) {
1561
- const streamId = `${this.streamer.getDeviceId()}:${commandId}`;
1562
- this.streamer.unsubscribe(streamId);
1563
- }
1564
- /**
1565
- * Get streamer state
1566
- */
1567
- getState() {
1568
- return this.streamer.getState();
1569
- }
1570
- /**
1571
- * Check if streaming
1572
- */
1573
- isStreaming() {
1574
- return this.streamer.isConnected();
1575
- }
1576
- /**
1577
- * Listen to streamer events
1578
- */
1579
- on(event, listener) {
1580
- this.streamer.on(event, listener);
1581
- }
1582
- /**
1583
- * Remove event listener
1584
- */
1585
- off(event, listener) {
1586
- this.streamer.off(event, listener);
1587
- }
1588
- }
1589
-
1590
- class CcjkDaemon {
1591
- config;
1592
- emailChecker;
1593
- taskExecutor;
1594
- resultSender;
1595
- securityManager;
1596
- cloudClient;
1597
- logStreamer;
1598
- mobileControl;
1599
- mode;
1600
- running = false;
1601
- checkInterval = null;
1602
- startTime = null;
1603
- tasksExecuted = 0;
1604
- lastCheckTime = null;
1605
- constructor(config) {
1606
- this.config = {
1607
- checkInterval: 3e4,
1608
- // 30 seconds
1609
- commandTimeout: 3e5,
1610
- // 5 minutes
1611
- projectPath: process__default.cwd(),
1612
- debug: false,
1613
- mode: "email",
1614
- ...config
1615
- };
1616
- this.mode = this.config.mode || (this.config.cloudToken ? "cloud" : "email");
1617
- this.emailChecker = new EmailChecker(this.config.email);
1618
- this.taskExecutor = new TaskExecutor(this.config.commandTimeout);
1619
- this.resultSender = new ResultSender(this.config.email);
1620
- this.securityManager = new SecurityManager(this.config);
1621
- if (this.config.cloudToken && (this.mode === "cloud" || this.mode === "hybrid")) {
1622
- this.cloudClient = new CloudClient({
1623
- deviceToken: this.config.cloudToken,
1624
- apiUrl: this.config.cloudApiUrl,
1625
- heartbeatInterval: this.config.heartbeatInterval || 3e4,
1626
- debug: this.config.debug
1627
- });
1628
- const deviceInfo = this.cloudClient.getDeviceInfo();
1629
- if (deviceInfo) {
1630
- this.logStreamer = new DaemonLogStreamer({
1631
- deviceId: deviceInfo.device.id,
1632
- token: this.config.cloudToken,
1633
- debug: this.config.debug
1634
- });
1635
- }
1636
- this.mobileControl = new MobileControlClient({
1637
- userToken: this.config.cloudToken,
1638
- debug: this.config.debug
1639
- });
1640
- }
1641
- }
1642
- /**
1643
- * Start the daemon
1644
- */
1645
- async start() {
1646
- if (this.running) {
1647
- console.log("\u26A0\uFE0F Daemon is already running");
1648
- return;
1649
- }
1650
- console.log("\u{1F680} CCJK Daemon starting...");
1651
- console.log(`\u{1F4E1} Mode: ${this.mode.toUpperCase()}`);
1652
- if (this.cloudClient && (this.mode === "cloud" || this.mode === "hybrid")) {
1653
- console.log("\u2601\uFE0F Connecting to cloud API...");
1654
- const registrationResult = await this.cloudClient.register({
1655
- name: this.config.deviceName
1656
- });
1657
- if (registrationResult.success) {
1658
- const deviceInfo = this.cloudClient.getDeviceInfo();
1659
- console.log(`\u2705 Cloud connected - Device ID: ${deviceInfo?.device.id}`);
1660
- console.log(` Device name: ${deviceInfo?.device.name}`);
1661
- if (!this.logStreamer && deviceInfo) {
1662
- this.logStreamer = new DaemonLogStreamer({
1663
- deviceId: deviceInfo.device.id,
1664
- token: this.config.cloudToken,
1665
- debug: this.config.debug
1666
- });
1667
- }
1668
- if (this.logStreamer) {
1669
- this.logStreamer.start().catch((err) => {
1670
- console.warn("\u26A0\uFE0F Log streaming failed to start:", err.message);
1671
- });
1672
- }
1673
- this.cloudClient.startHeartbeat((tasks) => {
1674
- this.processCloudTasks(tasks).catch((err) => {
1675
- console.error("Error processing cloud tasks:", err);
1676
- });
1677
- });
1678
- } else {
1679
- console.error(`\u274C Cloud registration failed: ${registrationResult.error}`);
1680
- if (this.mode === "cloud") {
1681
- throw new Error(`Cloud registration failed: ${registrationResult.error}`);
1682
- }
1683
- console.log("\u26A0\uFE0F Continuing with email mode only...");
1684
- }
1685
- }
1686
- if (this.mode === "email" || this.mode === "hybrid") {
1687
- console.log("\u{1F4E7} Testing email connection...");
1688
- const emailOk = await this.resultSender.testConnection();
1689
- if (!emailOk) {
1690
- throw new Error("Failed to connect to email server. Please check your email configuration.");
1691
- }
1692
- console.log(`\u2705 Email connected - Monitoring: ${this.config.email.email}`);
1693
- }
1694
- this.running = true;
1695
- this.startTime = /* @__PURE__ */ new Date();
1696
- console.log(`\u2705 CCJK Daemon started`);
1697
- console.log(`\u{1F4C2} Project: ${this.config.projectPath}`);
1698
- if (this.mode === "email" || this.mode === "hybrid") {
1699
- console.log(`\u23F1\uFE0F Check interval: ${this.config.checkInterval}ms`);
1700
- console.log(`\u{1F465} Allowed senders: ${this.config.allowedSenders.join(", ")}`);
1701
- }
1702
- if (this.mode === "email" || this.mode === "hybrid") {
1703
- this.scheduleCheck();
1704
- }
1705
- }
1706
- /**
1707
- * Stop the daemon
1708
- */
1709
- async stop() {
1710
- if (!this.running) {
1711
- console.log("\u26A0\uFE0F Daemon is not running");
1712
- return;
1713
- }
1714
- console.log("\u{1F6D1} Stopping CCJK Daemon...");
1715
- if (this.logStreamer) {
1716
- console.log("\u{1F4E1} Stopping log streaming...");
1717
- this.logStreamer.stop();
1718
- }
1719
- if (this.cloudClient) {
1720
- console.log("\u2601\uFE0F Disconnecting from cloud...");
1721
- await this.cloudClient.goOffline();
1722
- }
1723
- if (this.checkInterval) {
1724
- clearInterval(this.checkInterval);
1725
- this.checkInterval = null;
1726
- }
1727
- this.emailChecker.disconnect();
1728
- this.running = false;
1729
- this.startTime = null;
1730
- console.log("\u2705 CCJK Daemon stopped");
1731
- }
1732
- /**
1733
- * Schedule periodic email checks
1734
- */
1735
- scheduleCheck() {
1736
- this.checkAndExecute().catch((err) => {
1737
- console.error("Error in check cycle:", err);
1738
- });
1739
- this.checkInterval = setInterval(() => {
1740
- this.checkAndExecute().catch((err) => {
1741
- console.error("Error in check cycle:", err);
1742
- });
1743
- }, this.config.checkInterval);
1744
- }
1745
- /**
1746
- * Check for new emails and execute tasks
1747
- */
1748
- async checkAndExecute() {
1749
- if (!this.running) {
1750
- return;
1751
- }
1752
- try {
1753
- this.lastCheckTime = /* @__PURE__ */ new Date();
1754
- if (this.config.debug) {
1755
- console.log(`\u{1F50D} Checking for new emails... (${this.lastCheckTime.toISOString()})`);
1756
- }
1757
- const emails = await this.emailChecker.fetchNew();
1758
- if (emails.length === 0) {
1759
- if (this.config.debug) {
1760
- console.log("\u{1F4ED} No new emails");
1761
- }
1762
- return;
1763
- }
1764
- console.log(`\u{1F4EC} Found ${emails.length} new email(s)`);
1765
- for (const email of emails) {
1766
- await this.processEmail(email);
1767
- }
1768
- } catch (error) {
1769
- console.error("\u274C Error checking emails:", error);
1770
- }
1771
- }
1772
- /**
1773
- * Process a single email
1774
- */
1775
- async processEmail(email) {
1776
- try {
1777
- const sender = this.emailChecker.extractSenderEmail(email.from);
1778
- console.log(`\u{1F4E7} Processing email from: ${sender}`);
1779
- const command = this.emailChecker.parseCommand(email);
1780
- if (!command) {
1781
- console.log("\u26A0\uFE0F No command found in email");
1782
- return;
1783
- }
1784
- console.log(`\u{1F4DD} Command: ${command}`);
1785
- const securityCheck = this.securityManager.performSecurityCheck(sender, command);
1786
- if (!securityCheck.allowed) {
1787
- console.log(`\u{1F6AB} Security check failed: ${securityCheck.reason}`);
1788
- await this.sendSecurityError(sender, command, securityCheck.reason);
1789
- return;
1790
- }
1791
- const task = {
1792
- id: nanoid(10),
1793
- command,
1794
- sender,
1795
- projectPath: this.config.projectPath,
1796
- createdAt: /* @__PURE__ */ new Date(),
1797
- status: "pending"
1798
- };
1799
- task.status = "running";
1800
- task.startTime = Date.now();
1801
- const result = await this.taskExecutor.execute(task);
1802
- task.status = result.success ? "completed" : "failed";
1803
- this.tasksExecuted++;
1804
- await this.resultSender.send(sender, result, command);
1805
- console.log(`\u2705 Task ${task.id} processed successfully`);
1806
- } catch (error) {
1807
- console.error("\u274C Error processing email:", error);
1808
- }
1809
- }
1810
- /**
1811
- * Send security error email
1812
- */
1813
- async sendSecurityError(to, command, reason) {
1814
- try {
1815
- await this.resultSender.send(to, {
1816
- taskId: "security-error",
1817
- success: false,
1818
- output: null,
1819
- error: `Security check failed: ${reason}`,
1820
- duration: 0,
1821
- exitCode: 403
1822
- }, command);
1823
- } catch (error) {
1824
- console.error("Failed to send security error email:", error);
1825
- }
1826
- }
1827
- /**
1828
- * Process cloud tasks from API
1829
- */
1830
- async processCloudTasks(tasks) {
1831
- if (!this.running || tasks.length === 0) {
1832
- return;
1833
- }
1834
- console.log(`\u2601\uFE0F Processing ${tasks.length} cloud task(s)`);
1835
- for (const cloudTask of tasks) {
1836
- await this.executeCloudTask(cloudTask);
1837
- }
1838
- }
1839
- /**
1840
- * Execute a single cloud task
1841
- */
1842
- async executeCloudTask(cloudTask) {
1843
- try {
1844
- console.log(`\u2601\uFE0F Executing cloud command: ${cloudTask.id}`);
1845
- console.log(` Type: ${cloudTask.commandType}`);
1846
- console.log(` Command: ${cloudTask.command}`);
1847
- let commandString = cloudTask.command;
1848
- if (cloudTask.args && cloudTask.args.length > 0) {
1849
- commandString = `${cloudTask.command} ${cloudTask.args.join(" ")}`;
1850
- }
1851
- const task = {
1852
- id: cloudTask.id,
1853
- command: commandString,
1854
- sender: "cloud-api",
1855
- projectPath: cloudTask.cwd || this.config.projectPath,
1856
- createdAt: /* @__PURE__ */ new Date(),
1857
- status: "pending"
1858
- };
1859
- this.cloudClient?.addTask(cloudTask.id);
1860
- task.status = "running";
1861
- task.startTime = Date.now();
1862
- const startTime = Date.now();
1863
- let result;
1864
- try {
1865
- result = await this.taskExecutor.execute(task);
1866
- } catch (error) {
1867
- result = {
1868
- success: false,
1869
- output: null,
1870
- error: error instanceof Error ? error.message : String(error),
1871
- duration: Date.now() - startTime,
1872
- exitCode: 1
1873
- };
1874
- }
1875
- task.status = result.success ? "completed" : "failed";
1876
- this.tasksExecuted++;
1877
- const cloudResult = {
1878
- exitCode: result.exitCode || (result.success ? 0 : 1),
1879
- stdout: result.output || "",
1880
- stderr: result.error || "",
1881
- success: result.success,
1882
- duration: result.duration
1883
- };
1884
- await this.cloudClient?.reportResult(cloudTask.id, cloudResult);
1885
- console.log(`\u2705 Cloud task ${cloudTask.id} processed: ${result.success ? "success" : "failed"}`);
1886
- } catch (error) {
1887
- console.error(`\u274C Error executing cloud task ${cloudTask.id}:`, error);
1888
- await this.cloudClient?.reportResult(cloudTask.id, {
1889
- exitCode: 1,
1890
- stdout: "",
1891
- stderr: error instanceof Error ? error.message : String(error),
1892
- success: false,
1893
- duration: 0
1894
- });
1895
- }
1896
- }
1897
- /**
1898
- * Get daemon status
1899
- */
1900
- getStatus() {
1901
- const deviceInfo = this.cloudClient?.getDeviceInfo();
1902
- return {
1903
- running: this.running,
1904
- pid: process__default.pid,
1905
- startTime: this.startTime || void 0,
1906
- uptime: this.startTime ? Date.now() - this.startTime.getTime() : void 0,
1907
- tasksExecuted: this.tasksExecuted,
1908
- lastCheckTime: this.lastCheckTime || void 0,
1909
- config: {
1910
- email: {
1911
- email: this.config.email.email,
1912
- password: "***",
1913
- imapHost: this.config.email.imapHost,
1914
- smtpHost: this.config.email.smtpHost
1915
- },
1916
- allowedSenders: this.config.allowedSenders,
1917
- checkInterval: this.config.checkInterval,
1918
- commandTimeout: this.config.commandTimeout,
1919
- projectPath: this.config.projectPath,
1920
- mode: this.mode,
1921
- cloudConnected: this.cloudClient?.isConnected() ?? false,
1922
- cloudDeviceId: deviceInfo?.device.id,
1923
- cloudDeviceName: deviceInfo?.device.name,
1924
- activeTasksCount: this.cloudClient?.getActiveTasksCount()
1925
- }
1926
- };
1927
- }
1928
- /**
1929
- * Get current mode
1930
- */
1931
- getMode() {
1932
- return this.mode;
1933
- }
1934
- /**
1935
- * Get mobile control client
1936
- */
1937
- getMobileControl() {
1938
- return this.mobileControl;
1939
- }
1940
- /**
1941
- * Get log streamer
1942
- */
1943
- getLogStreamer() {
1944
- return this.logStreamer;
1945
- }
1946
- /**
1947
- * Get cloud client
1948
- */
1949
- getCloudClient() {
1950
- return this.cloudClient;
1951
- }
1952
- /**
1953
- * Send mobile control card
1954
- */
1955
- async sendMobileCard(channel, templateId, message) {
1956
- const deviceInfo = this.cloudClient?.getDeviceInfo();
1957
- if (!deviceInfo || !this.mobileControl) {
1958
- throw new Error("Cloud client not connected or mobile control not available");
1959
- }
1960
- return this.mobileControl.sendCard({
1961
- deviceId: deviceInfo.device.id,
1962
- channel,
1963
- templateId,
1964
- message
1965
- });
1966
- }
1967
- /**
1968
- * Check if daemon is running
1969
- */
1970
- isRunning() {
1971
- return this.running;
1972
- }
1973
- }
1974
-
1975
- const CONFIG_DIR = join(homedir(), ".ccjk");
1976
- const CONFIG_FILE = join(CONFIG_DIR, "daemon-config.json");
1977
- const PID_FILE = join(CONFIG_DIR, "daemon.pid");
1978
- async function setupDaemon() {
1979
- console.log("\u{1F527} CCJK Daemon Setup\n");
1980
- const answers = await inquirer.prompt([
1981
- {
1982
- type: "list",
1983
- name: "mode",
1984
- message: "Select daemon mode:",
1985
- choices: [
1986
- { name: "\u{1F4E7} Email (traditional email-based control)", value: "email" },
1987
- { name: "\u2601\uFE0F Cloud (cloud API control via api.claudehome.cn)", value: "cloud" },
1988
- { name: "\u{1F504} Hybrid (both email and cloud control)", value: "hybrid" }
1989
- ],
1990
- default: "email"
1991
- },
1992
- {
1993
- type: "input",
1994
- name: "email",
1995
- message: "Your email address:",
1996
- validate: (input) => {
1997
- if (!input.includes("@")) {
1998
- return "Please enter a valid email address";
1999
- }
2000
- return true;
2001
- },
2002
- when: (answers2) => answers2.mode === "email" || answers2.mode === "hybrid"
2003
- },
2004
- {
2005
- type: "password",
2006
- name: "password",
2007
- message: "Email password (or app-specific password):",
2008
- mask: "*",
2009
- validate: (input) => {
2010
- if (!input) {
2011
- return "Password is required";
2012
- }
2013
- return true;
2014
- },
2015
- when: (answers2) => answers2.mode === "email" || answers2.mode === "hybrid"
2016
- },
2017
- {
2018
- type: "input",
2019
- name: "cloudToken",
2020
- message: "Cloud device token (from api.claudehome.cn):",
2021
- validate: (input) => {
2022
- if (!input || input.length < 10) {
2023
- return "Please enter a valid cloud token";
2024
- }
2025
- return true;
2026
- },
2027
- when: (answers2) => answers2.mode === "cloud" || answers2.mode === "hybrid"
2028
- },
2029
- {
2030
- type: "input",
2031
- name: "deviceName",
2032
- message: "Device name (for cloud registration):",
2033
- default: () => {
2034
- const os = require("node:os");
2035
- return `CCJK Device (${os.hostname()})`;
2036
- },
2037
- when: (answers2) => answers2.mode === "cloud" || answers2.mode === "hybrid"
2038
- },
2039
- {
2040
- type: "input",
2041
- name: "allowedSenders",
2042
- message: "Allowed sender emails (comma-separated):",
2043
- default: (answers2) => answers2.email || "",
2044
- filter: (input) => input.split(",").map((s) => s.trim()).filter(Boolean),
2045
- when: (answers2) => answers2.mode === "email" || answers2.mode === "hybrid"
2046
- },
2047
- {
2048
- type: "input",
2049
- name: "projectPath",
2050
- message: "Project path:",
2051
- default: process.cwd()
2052
- },
2053
- {
2054
- type: "number",
2055
- name: "checkInterval",
2056
- message: "Check interval (seconds):",
2057
- default: 30,
2058
- filter: (input) => input * 1e3,
2059
- when: (answers2) => answers2.mode === "email" || answers2.mode === "hybrid"
2060
- },
2061
- {
2062
- type: "number",
2063
- name: "heartbeatInterval",
2064
- message: "Heartbeat interval (seconds):",
2065
- default: 30,
2066
- filter: (input) => input * 1e3,
2067
- when: (answers2) => answers2.mode === "cloud" || answers2.mode === "hybrid"
2068
- },
2069
- {
2070
- type: "confirm",
2071
- name: "debug",
2072
- message: "Enable debug logging?",
2073
- default: false
2074
- }
2075
- ]);
2076
- const config = {
2077
- email: {
2078
- email: answers.email || "noreply@example.com",
2079
- password: answers.password || ""
2080
- },
2081
- allowedSenders: answers.allowedSenders || [],
2082
- projectPath: answers.projectPath,
2083
- checkInterval: answers.checkInterval || 3e4,
2084
- heartbeatInterval: answers.heartbeatInterval || 3e4,
2085
- debug: answers.debug,
2086
- mode: answers.mode,
2087
- cloudToken: answers.cloudToken,
2088
- deviceName: answers.deviceName
2089
- };
2090
- if (!existsSync(CONFIG_DIR)) {
2091
- const fs = await import('node:fs/promises');
2092
- await fs.mkdir(CONFIG_DIR, { recursive: true });
2093
- }
2094
- const configToSave = { ...config };
2095
- if (config.mode === "cloud") {
2096
- configToSave.email = {
2097
- email: "noreply@example.com",
2098
- password: ""
2099
- };
2100
- }
2101
- writeFileSync(CONFIG_FILE, JSON.stringify(configToSave, null, 2));
2102
- console.log("\n\u2705 Configuration saved to:", CONFIG_FILE);
2103
- console.log(`
2104
- \u{1F4E1} Mode: ${answers.mode.toUpperCase()}`);
2105
- if (answers.mode === "email" || answers.mode === "hybrid") {
2106
- console.log("\n\u{1F4A1} Email control:");
2107
- console.log(" Send an email to test:");
2108
- console.log(` To: ${answers.email}`);
2109
- console.log(" Subject: [CCJK] Test");
2110
- console.log(' Body: echo "Hello CCJK!"');
2111
- }
2112
- if (answers.mode === "cloud" || answers.mode === "hybrid") {
2113
- console.log("\n\u{1F4A1} Cloud control:");
2114
- console.log(" 1. Device registered to api.claudehome.cn");
2115
- console.log(" 2. Use the web interface to send commands");
2116
- console.log(` 3. Device name: ${answers.deviceName}`);
2117
- }
2118
- console.log("\n\u{1F4A1} Next steps:");
2119
- console.log(" Run: ccjk daemon start");
2120
- }
2121
- async function startDaemon() {
2122
- if (!existsSync(CONFIG_FILE)) {
2123
- console.error("\u274C Configuration not found. Please run: ccjk daemon setup");
2124
- process.exit(1);
2125
- }
2126
- if (existsSync(PID_FILE)) {
2127
- const pid = readFileSync(PID_FILE, "utf-8").trim();
2128
- console.error(`\u274C Daemon is already running (PID: ${pid})`);
2129
- console.log(" Run: ccjk daemon stop");
2130
- process.exit(1);
2131
- }
2132
- const config = JSON.parse(readFileSync(CONFIG_FILE, "utf-8"));
2133
- const daemon = new CcjkDaemon(config);
2134
- const spinner = ora("Starting CCJK Daemon...").start();
2135
- try {
2136
- await daemon.start();
2137
- spinner.succeed("CCJK Daemon started successfully");
2138
- writeFileSync(PID_FILE, process.pid.toString());
2139
- process.on("SIGINT", async () => {
2140
- console.log("\n\u{1F6D1} Received SIGINT, stopping daemon...");
2141
- await daemon.stop();
2142
- if (existsSync(PID_FILE)) {
2143
- const fs = require("node:fs");
2144
- fs.unlinkSync(PID_FILE);
2145
- }
2146
- process.exit(0);
2147
- });
2148
- process.on("SIGTERM", async () => {
2149
- console.log("\n\u{1F6D1} Received SIGTERM, stopping daemon...");
2150
- await daemon.stop();
2151
- if (existsSync(PID_FILE)) {
2152
- const fs = require("node:fs");
2153
- fs.unlinkSync(PID_FILE);
2154
- }
2155
- process.exit(0);
2156
- });
2157
- console.log("\n\u{1F4A1} Press Ctrl+C to stop the daemon");
2158
- } catch (error) {
2159
- spinner.fail("Failed to start daemon");
2160
- console.error(error.message);
2161
- process.exit(1);
2162
- }
2163
- }
2164
- async function stopDaemon() {
2165
- if (!existsSync(PID_FILE)) {
2166
- console.log("\u26A0\uFE0F Daemon is not running");
2167
- return;
2168
- }
2169
- const pid = readFileSync(PID_FILE, "utf-8").trim();
2170
- const spinner = ora(`Stopping daemon (PID: ${pid})...`).start();
2171
- try {
2172
- process.kill(Number.parseInt(pid), "SIGTERM");
2173
- await new Promise((resolve) => setTimeout(resolve, 3e3));
2174
- if (existsSync(PID_FILE)) {
2175
- const fs = require("node:fs");
2176
- fs.unlinkSync(PID_FILE);
2177
- }
2178
- spinner.succeed("Daemon stopped successfully");
2179
- } catch (error) {
2180
- spinner.fail("Failed to stop daemon");
2181
- console.error(error.message);
2182
- process.exit(1);
2183
- }
2184
- }
2185
- async function showStatus() {
2186
- console.log("\u{1F4CA} CCJK Daemon Status\n");
2187
- if (!existsSync(PID_FILE)) {
2188
- console.log("Status: \u26AA Not running");
2189
- return;
2190
- }
2191
- const pid = readFileSync(PID_FILE, "utf-8").trim();
2192
- try {
2193
- process.kill(Number.parseInt(pid), 0);
2194
- console.log("Status: \u{1F7E2} Running");
2195
- console.log(`PID: ${pid}`);
2196
- } catch {
2197
- console.log("Status: \u{1F534} Dead (PID file exists but process not found)");
2198
- console.log("Run: ccjk daemon start");
2199
- return;
2200
- }
2201
- if (existsSync(CONFIG_FILE)) {
2202
- const config = JSON.parse(readFileSync(CONFIG_FILE, "utf-8"));
2203
- console.log(`
2204
- Configuration:`);
2205
- console.log(` Mode: ${config.mode?.toUpperCase() || "EMAIL"}`);
2206
- if (config.mode === "email" || config.mode === "hybrid") {
2207
- console.log(` Email: ${config.email.email}`);
2208
- console.log(` Check Interval: ${config.checkInterval}ms`);
2209
- console.log(` Allowed Senders: ${config.allowedSenders.join(", ") || "None"}`);
2210
- }
2211
- if (config.mode === "cloud" || config.mode === "hybrid") {
2212
- console.log(` Cloud API: ${config.cloudApiUrl || "https://api.claudehome.cn/api/control"}`);
2213
- console.log(` Device Name: ${config.deviceName || "N/A"}`);
2214
- console.log(` Heartbeat Interval: ${config.heartbeatInterval}ms`);
2215
- }
2216
- console.log(` Project: ${config.projectPath}`);
2217
- console.log(` Debug: ${config.debug ? "Enabled" : "Disabled"}`);
2218
- }
2219
- }
2220
- async function showLogs() {
2221
- console.log("\u{1F4CB} CCJK Daemon Logs\n");
2222
- console.log("\u26A0\uFE0F Log viewing not implemented yet");
2223
- console.log("\u{1F4A1} Logs are currently printed to stdout");
2224
- console.log(" Run daemon in foreground to see logs: ccjk daemon start");
2225
- }
2226
-
2227
- export { setupDaemon, showLogs, showStatus, startDaemon, stopDaemon };