ccjk 2.0.7 → 2.0.8

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.
package/dist/cli.mjs CHANGED
@@ -1,9 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
  import cac from 'cac';
3
3
  import ansis from 'ansis';
4
- import { S as STATUS, ch as i18n, n as runConfigWizard, ci as testApiConnection, m as displayCurrentStatus, cj as quickSetup, ck as format, k as COLORS, cl as getAllPresets, cm as ensureI18nInitialized, cn as readCcrConfig, co as isCcrInstalled, cp as installCcr, cq as configureCcrFeature, cr as promptBoolean, cs as handleExitPromptError, ct as handleGeneralError, cu as COMETIX_COMMAND_NAME, cv as COMETIX_COMMANDS, cw as installCometixLine, cx as addNumbersToChoices, cy as checkAndUpdateTools, aW as runCodexUpdate, cz as resolveCodeType$1, a2 as resolveCodeToolType$1, cA as readZcfConfig, $ as isCodeToolType, W as DEFAULT_CODE_TOOL_TYPE, a_ as switchCodexProvider, aM as listCodexProviders, aR as readCodexConfig, aD as switchToOfficialLogin, a$ as switchToProvider, q as commandExists, y as CLAUDE_DIR, z as SETTINGS_FILE, cB as readJsonConfig, cC as writeJsonConfig, cD as moveToTrash, U as ZCF_CONFIG_FILE, ah as displayBanner, cE as updateZcfConfig, v as version, cF as resolveAiOutputLanguage, cG as updatePromptOnly, cH as selectAndInstallWorkflows, cI as checkClaudeCodeVersionAndPrompt, l as init, ai as displayBannerWithInfo, X as CODE_TOOL_BANNERS, aV as runCodexUninstall, b5 as configureCodexMcp, aE as configureCodexApi, aY as runCodexWorkflowImportWithLanguageSelection, aT as runCodexFullInit, cJ as checkSuperpowersInstalled, cK as getSuperpowersSkills, cL as updateSuperpowers, cM as uninstallSuperpowers, cN as installSuperpowers, cO as installSuperpowersViaGit, _ as CODE_TOOL_INFO, bb as getToolStatus, bc as getAllToolsStatus, V as CODE_TOOL_TYPES, aj as boxify, be as installTool, cP as readZcfConfigAsync, cQ as initI18n, bV as quickSync, cf as checkAllVersions, cg as upgradeAll, i as detectAllConfigs, bB as displayConfigScan, c9 as displayPermissions, cR as selectScriptLanguage, cS as changeLanguage, bU as runOnboarding } from './chunks/simple-config.mjs';
5
- import { existsSync, readdirSync, mkdirSync, writeFileSync, readFileSync } from 'node:fs';
6
- import { homedir } from 'node:os';
4
+ import { S as STATUS, ch as i18n, n as runConfigWizard$1, ci as testApiConnection, m as displayCurrentStatus, cj as quickSetup, ck as format, k as COLORS, cl as getAllPresets, cm as ensureI18nInitialized, cn as readCcrConfig, co as isCcrInstalled, cp as installCcr, cq as configureCcrFeature, cr as promptBoolean, cs as handleExitPromptError, ct as handleGeneralError, cu as COMETIX_COMMAND_NAME, cv as COMETIX_COMMANDS, cw as installCometixLine, cx as addNumbersToChoices, cy as checkAndUpdateTools, aW as runCodexUpdate, cz as resolveCodeType$1, a2 as resolveCodeToolType$1, cA as readZcfConfig, $ as isCodeToolType, W as DEFAULT_CODE_TOOL_TYPE, a_ as switchCodexProvider, aM as listCodexProviders, aR as readCodexConfig, aD as switchToOfficialLogin, a$ as switchToProvider, q as commandExists, y as CLAUDE_DIR, z as SETTINGS_FILE, cB as readJsonConfig, cC as writeJsonConfig, cD as moveToTrash, U as ZCF_CONFIG_FILE, ah as displayBanner, cE as updateZcfConfig, v as version, cF as resolveAiOutputLanguage, cG as updatePromptOnly, cH as selectAndInstallWorkflows, cI as checkClaudeCodeVersionAndPrompt, l as init, ai as displayBannerWithInfo, X as CODE_TOOL_BANNERS, aV as runCodexUninstall, b5 as configureCodexMcp, aE as configureCodexApi, aY as runCodexWorkflowImportWithLanguageSelection, aT as runCodexFullInit, cJ as checkSuperpowersInstalled, cK as getSuperpowersSkills, cL as updateSuperpowers, cM as uninstallSuperpowers, cN as installSuperpowers, cO as installSuperpowersViaGit, _ as CODE_TOOL_INFO, bb as getToolStatus, bc as getAllToolsStatus, V as CODE_TOOL_TYPES, aj as boxify, be as installTool, cP as readZcfConfigAsync, cQ as initI18n, bV as quickSync, cf as checkAllVersions, cg as upgradeAll, i as detectAllConfigs, bB as displayConfigScan, c9 as displayPermissions, cR as selectScriptLanguage, cS as changeLanguage, bU as runOnboarding } from './chunks/simple-config.mjs';
5
+ import nodeFs__default, { existsSync, readdirSync, mkdirSync, writeFileSync, readFileSync } from 'node:fs';
6
+ import os, { homedir } from 'node:os';
7
7
  import inquirer from 'inquirer';
8
8
  import { join, resolve as resolve$1 } from 'pathe';
9
9
  import { runCcrStop, runCcrStart, runCcrRestart, runCcrStatus, runCcrUi } from './chunks/commands.mjs';
@@ -16,8 +16,10 @@ import { spawn, exec } from 'node:child_process';
16
16
  import { promisify } from 'node:util';
17
17
  import { ClaudeCodeConfigManager } from './chunks/claude-code-config-manager.mjs';
18
18
  import { pathExists } from 'fs-extra';
19
- import { join as join$1, resolve, dirname } from 'node:path';
20
- import { randomUUID } from 'node:crypto';
19
+ import path, { join as join$1, resolve, dirname } from 'node:path';
20
+ import crypto, { randomUUID } from 'node:crypto';
21
+ import { parse, stringify } from '@iarna/toml';
22
+ import { Buffer } from 'node:buffer';
21
23
  import ora from 'ora';
22
24
  import 'smol-toml';
23
25
  import 'dayjs';
@@ -65,14 +67,14 @@ function setupApi(providerId, apiKey, _lang = "en") {
65
67
  console.log(STATUS.error(format(i18n.t("api:configFailed"), { error: result.error || "Unknown error" })));
66
68
  }
67
69
  }
68
- function showStatus(lang = "en") {
70
+ function showStatus$1(lang = "en") {
69
71
  displayCurrentStatus(lang);
70
72
  }
71
73
  async function testApi(lang = "en") {
72
74
  await testApiConnection(lang);
73
75
  }
74
76
  async function runWizard(lang = "en") {
75
- await runConfigWizard(lang);
77
+ await runConfigWizard$1(lang);
76
78
  }
77
79
  async function apiCommand(action = "wizard", args = [], options = {}) {
78
80
  const lang = options.lang || "en";
@@ -95,7 +97,7 @@ async function apiCommand(action = "wizard", args = [], options = {}) {
95
97
  break;
96
98
  case "status":
97
99
  case "s":
98
- showStatus(lang);
100
+ showStatus$1(lang);
99
101
  break;
100
102
  case "test":
101
103
  case "t":
@@ -6309,6 +6311,1239 @@ async function mcpMarket(action, args, options = {}) {
6309
6311
  }
6310
6312
  }
6311
6313
 
6314
+ const TOKEN_PREFIX = "ccjk_";
6315
+ const TOKEN_LENGTH = 64;
6316
+ const TOKEN_VERSION = 1;
6317
+ function generateDeviceToken() {
6318
+ const randomBytes = crypto.randomBytes(TOKEN_LENGTH / 2);
6319
+ const randomHex = randomBytes.toString("hex");
6320
+ return `${TOKEN_PREFIX}${TOKEN_VERSION}${randomHex}`;
6321
+ }
6322
+ function isValidTokenFormat(token) {
6323
+ if (!token || typeof token !== "string") {
6324
+ return false;
6325
+ }
6326
+ if (!token.startsWith(TOKEN_PREFIX)) {
6327
+ return false;
6328
+ }
6329
+ const expectedLength = TOKEN_PREFIX.length + 1 + TOKEN_LENGTH;
6330
+ if (token.length !== expectedLength) {
6331
+ return false;
6332
+ }
6333
+ const version = token[TOKEN_PREFIX.length];
6334
+ if (!/^\d$/.test(version)) {
6335
+ return false;
6336
+ }
6337
+ const hexPart = token.slice(TOKEN_PREFIX.length + 1);
6338
+ if (!/^[a-f0-9]+$/i.test(hexPart)) {
6339
+ return false;
6340
+ }
6341
+ return true;
6342
+ }
6343
+ function getDeviceInfo() {
6344
+ return {
6345
+ name: os.hostname(),
6346
+ platform: os.platform(),
6347
+ osVersion: os.release(),
6348
+ arch: os.arch(),
6349
+ username: os.userInfo().username,
6350
+ machineId: generateMachineId()
6351
+ };
6352
+ }
6353
+ function generateMachineId() {
6354
+ const components = [
6355
+ os.hostname(),
6356
+ os.platform(),
6357
+ os.arch(),
6358
+ os.cpus()[0]?.model || "unknown",
6359
+ os.userInfo().username,
6360
+ // Add network interface MAC addresses for uniqueness
6361
+ ...Object.values(os.networkInterfaces()).flat().filter((iface) => iface && !iface.internal && iface.mac !== "00:00:00:00:00:00").map((iface) => iface?.mac).filter(Boolean).slice(0, 3)
6362
+ // Limit to first 3 MACs
6363
+ ];
6364
+ const combined = components.join("|");
6365
+ return crypto.createHash("sha256").update(combined).digest("hex").slice(0, 32);
6366
+ }
6367
+ function deriveEncryptionKey() {
6368
+ const machineId = generateMachineId();
6369
+ const salt = "ccjk-notification-token-v1";
6370
+ return crypto.pbkdf2Sync(machineId, salt, 1e5, 32, "sha256");
6371
+ }
6372
+ function encryptToken(token) {
6373
+ const key = deriveEncryptionKey();
6374
+ const iv = crypto.randomBytes(16);
6375
+ const cipher = crypto.createCipheriv("aes-256-gcm", key, iv);
6376
+ let encrypted = cipher.update(token, "utf8", "hex");
6377
+ encrypted += cipher.final("hex");
6378
+ const authTag = cipher.getAuthTag();
6379
+ return `${iv.toString("hex")}:${authTag.toString("hex")}:${encrypted}`;
6380
+ }
6381
+ function decryptToken(encryptedToken) {
6382
+ try {
6383
+ const parts = encryptedToken.split(":");
6384
+ if (parts.length !== 3) {
6385
+ return null;
6386
+ }
6387
+ const [ivHex, authTagHex, encrypted] = parts;
6388
+ const key = deriveEncryptionKey();
6389
+ const iv = Buffer.from(ivHex, "hex");
6390
+ const authTag = Buffer.from(authTagHex, "hex");
6391
+ const decipher = crypto.createDecipheriv("aes-256-gcm", key, iv);
6392
+ decipher.setAuthTag(authTag);
6393
+ let decrypted = decipher.update(encrypted, "hex", "utf8");
6394
+ decrypted += decipher.final("utf8");
6395
+ return decrypted;
6396
+ } catch {
6397
+ return null;
6398
+ }
6399
+ }
6400
+ function maskToken(token) {
6401
+ if (!token || token.length < 12) {
6402
+ return "***";
6403
+ }
6404
+ const prefix = token.slice(0, TOKEN_PREFIX.length + 1);
6405
+ const suffix = token.slice(-4);
6406
+ return `${prefix}***...***${suffix}`;
6407
+ }
6408
+
6409
+ const DEFAULT_NOTIFICATION_CONFIG = {
6410
+ enabled: false,
6411
+ deviceToken: "",
6412
+ threshold: 10,
6413
+ // 10 minutes
6414
+ cloudEndpoint: "https://api.claudehome.cn",
6415
+ channels: {},
6416
+ quietHours: {
6417
+ enabled: false,
6418
+ startHour: 22,
6419
+ endHour: 8
6420
+ }
6421
+ };
6422
+ function validateNotificationConfig(config) {
6423
+ const errors = [];
6424
+ const warnings = [];
6425
+ if (config.threshold !== void 0) {
6426
+ if (config.threshold < 1) {
6427
+ errors.push({
6428
+ field: "threshold",
6429
+ message: "Threshold must be at least 1 minute",
6430
+ code: "THRESHOLD_TOO_LOW"
6431
+ });
6432
+ }
6433
+ if (config.threshold > 1440) {
6434
+ warnings.push("Threshold is set to more than 24 hours");
6435
+ }
6436
+ }
6437
+ if (config.channels?.feishu?.enabled) {
6438
+ if (!config.channels.feishu.webhookUrl) {
6439
+ errors.push({
6440
+ field: "channels.feishu.webhookUrl",
6441
+ message: "Feishu webhook URL is required when enabled",
6442
+ code: "FEISHU_WEBHOOK_REQUIRED"
6443
+ });
6444
+ } else if (!config.channels.feishu.webhookUrl.startsWith("https://")) {
6445
+ errors.push({
6446
+ field: "channels.feishu.webhookUrl",
6447
+ message: "Feishu webhook URL must use HTTPS",
6448
+ code: "FEISHU_WEBHOOK_INVALID"
6449
+ });
6450
+ }
6451
+ }
6452
+ if (config.channels?.wechat?.enabled) {
6453
+ if (!config.channels.wechat.corpId) {
6454
+ errors.push({
6455
+ field: "channels.wechat.corpId",
6456
+ message: "WeChat Work Corp ID is required when enabled",
6457
+ code: "WECHAT_CORPID_REQUIRED"
6458
+ });
6459
+ }
6460
+ if (!config.channels.wechat.agentId) {
6461
+ errors.push({
6462
+ field: "channels.wechat.agentId",
6463
+ message: "WeChat Work Agent ID is required when enabled",
6464
+ code: "WECHAT_AGENTID_REQUIRED"
6465
+ });
6466
+ }
6467
+ if (!config.channels.wechat.secret) {
6468
+ errors.push({
6469
+ field: "channels.wechat.secret",
6470
+ message: "WeChat Work Secret is required when enabled",
6471
+ code: "WECHAT_SECRET_REQUIRED"
6472
+ });
6473
+ }
6474
+ }
6475
+ if (config.channels?.email?.enabled) {
6476
+ if (!config.channels.email.address) {
6477
+ errors.push({
6478
+ field: "channels.email.address",
6479
+ message: "Email address is required when enabled",
6480
+ code: "EMAIL_ADDRESS_REQUIRED"
6481
+ });
6482
+ } else if (!/^[^\s@]+@[^\s@][^\s.@]*\.[^\s@]+$/.test(config.channels.email.address)) {
6483
+ errors.push({
6484
+ field: "channels.email.address",
6485
+ message: "Invalid email address format",
6486
+ code: "EMAIL_ADDRESS_INVALID"
6487
+ });
6488
+ }
6489
+ }
6490
+ if (config.channels?.sms?.enabled) {
6491
+ if (!config.channels.sms.phone) {
6492
+ errors.push({
6493
+ field: "channels.sms.phone",
6494
+ message: "Phone number is required when enabled",
6495
+ code: "SMS_PHONE_REQUIRED"
6496
+ });
6497
+ } else if (!/^\d{10,15}$/.test(config.channels.sms.phone.replace(/\D/g, ""))) {
6498
+ errors.push({
6499
+ field: "channels.sms.phone",
6500
+ message: "Invalid phone number format",
6501
+ code: "SMS_PHONE_INVALID"
6502
+ });
6503
+ }
6504
+ }
6505
+ if (config.quietHours?.enabled) {
6506
+ if (config.quietHours.startHour < 0 || config.quietHours.startHour > 23) {
6507
+ errors.push({
6508
+ field: "quietHours.startHour",
6509
+ message: "Start hour must be between 0 and 23",
6510
+ code: "QUIET_HOURS_INVALID"
6511
+ });
6512
+ }
6513
+ if (config.quietHours.endHour < 0 || config.quietHours.endHour > 23) {
6514
+ errors.push({
6515
+ field: "quietHours.endHour",
6516
+ message: "End hour must be between 0 and 23",
6517
+ code: "QUIET_HOURS_INVALID"
6518
+ });
6519
+ }
6520
+ }
6521
+ return {
6522
+ valid: errors.length === 0,
6523
+ errors,
6524
+ warnings
6525
+ };
6526
+ }
6527
+
6528
+ const CCJK_CONFIG_DIR = path.join(os.homedir(), ".ccjk");
6529
+ const CONFIG_FILE_PATH = path.join(CCJK_CONFIG_DIR, "config.toml");
6530
+ const SECRETS_FILE_PATH = path.join(CCJK_CONFIG_DIR, ".notification-secrets");
6531
+ async function loadNotificationConfig() {
6532
+ try {
6533
+ if (!nodeFs__default.existsSync(CCJK_CONFIG_DIR)) {
6534
+ nodeFs__default.mkdirSync(CCJK_CONFIG_DIR, { recursive: true });
6535
+ }
6536
+ if (!nodeFs__default.existsSync(CONFIG_FILE_PATH)) {
6537
+ return { ...DEFAULT_NOTIFICATION_CONFIG };
6538
+ }
6539
+ const configContent = nodeFs__default.readFileSync(CONFIG_FILE_PATH, "utf-8");
6540
+ const config = parse(configContent);
6541
+ const notificationConfig = config.notification;
6542
+ if (!notificationConfig) {
6543
+ return { ...DEFAULT_NOTIFICATION_CONFIG };
6544
+ }
6545
+ const deviceToken = await loadDeviceToken();
6546
+ const quietHoursConfig = notificationConfig.quietHours || {};
6547
+ const defaultQuietHours = DEFAULT_NOTIFICATION_CONFIG.quietHours || {
6548
+ enabled: false,
6549
+ startHour: 22,
6550
+ endHour: 8,
6551
+ timezone: "local"
6552
+ };
6553
+ return {
6554
+ ...DEFAULT_NOTIFICATION_CONFIG,
6555
+ ...notificationConfig,
6556
+ deviceToken: deviceToken || notificationConfig.deviceToken || "",
6557
+ channels: {
6558
+ ...DEFAULT_NOTIFICATION_CONFIG.channels,
6559
+ ...notificationConfig.channels
6560
+ },
6561
+ quietHours: {
6562
+ enabled: quietHoursConfig.enabled ?? defaultQuietHours.enabled,
6563
+ startHour: quietHoursConfig.startHour ?? defaultQuietHours.startHour,
6564
+ endHour: quietHoursConfig.endHour ?? defaultQuietHours.endHour,
6565
+ timezone: quietHoursConfig.timezone ?? defaultQuietHours.timezone
6566
+ }
6567
+ };
6568
+ } catch (error) {
6569
+ console.error("Failed to load notification config:", error);
6570
+ return { ...DEFAULT_NOTIFICATION_CONFIG };
6571
+ }
6572
+ }
6573
+ async function loadDeviceToken() {
6574
+ try {
6575
+ if (!nodeFs__default.existsSync(SECRETS_FILE_PATH)) {
6576
+ return null;
6577
+ }
6578
+ const secretsContent = nodeFs__default.readFileSync(SECRETS_FILE_PATH, "utf-8");
6579
+ const secrets = JSON.parse(secretsContent);
6580
+ if (!secrets.deviceToken) {
6581
+ return null;
6582
+ }
6583
+ const decryptedToken = decryptToken(secrets.deviceToken);
6584
+ return decryptedToken;
6585
+ } catch {
6586
+ return null;
6587
+ }
6588
+ }
6589
+ async function saveNotificationConfig(config) {
6590
+ try {
6591
+ if (!nodeFs__default.existsSync(CCJK_CONFIG_DIR)) {
6592
+ nodeFs__default.mkdirSync(CCJK_CONFIG_DIR, { recursive: true });
6593
+ }
6594
+ let existingConfig = {};
6595
+ if (nodeFs__default.existsSync(CONFIG_FILE_PATH)) {
6596
+ const configContent = nodeFs__default.readFileSync(CONFIG_FILE_PATH, "utf-8");
6597
+ existingConfig = parse(configContent);
6598
+ }
6599
+ const notificationConfig = { ...config };
6600
+ if (notificationConfig.deviceToken) {
6601
+ await saveDeviceToken(notificationConfig.deviceToken);
6602
+ delete notificationConfig.deviceToken;
6603
+ }
6604
+ existingConfig.notification = {
6605
+ ...existingConfig.notification || {},
6606
+ ...notificationConfig
6607
+ };
6608
+ const tomlContent = stringify(existingConfig);
6609
+ nodeFs__default.writeFileSync(CONFIG_FILE_PATH, tomlContent, "utf-8");
6610
+ } catch (error) {
6611
+ throw new Error(`Failed to save notification config: ${error}`);
6612
+ }
6613
+ }
6614
+ async function saveDeviceToken(token) {
6615
+ try {
6616
+ const encryptedToken = encryptToken(token);
6617
+ let secrets = {};
6618
+ if (nodeFs__default.existsSync(SECRETS_FILE_PATH)) {
6619
+ const secretsContent = nodeFs__default.readFileSync(SECRETS_FILE_PATH, "utf-8");
6620
+ secrets = JSON.parse(secretsContent);
6621
+ }
6622
+ secrets.deviceToken = encryptedToken;
6623
+ secrets.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
6624
+ nodeFs__default.writeFileSync(SECRETS_FILE_PATH, JSON.stringify(secrets, null, 2), {
6625
+ encoding: "utf-8",
6626
+ mode: 384
6627
+ // Owner read/write only
6628
+ });
6629
+ } catch (error) {
6630
+ throw new Error(`Failed to save device token: ${error}`);
6631
+ }
6632
+ }
6633
+ async function initializeNotificationConfig() {
6634
+ const existingConfig = await loadNotificationConfig();
6635
+ if (!existingConfig.deviceToken || !isValidTokenFormat(existingConfig.deviceToken)) {
6636
+ existingConfig.deviceToken = generateDeviceToken();
6637
+ await saveNotificationConfig(existingConfig);
6638
+ }
6639
+ return existingConfig;
6640
+ }
6641
+ async function updateNotificationConfig(updates) {
6642
+ const currentConfig = await loadNotificationConfig();
6643
+ const updatesQuietHours = updates.quietHours || {};
6644
+ const currentQuietHours = currentConfig.quietHours || {
6645
+ enabled: false,
6646
+ startHour: 22,
6647
+ endHour: 8,
6648
+ timezone: "local"
6649
+ };
6650
+ const newConfig = {
6651
+ ...currentConfig,
6652
+ ...updates,
6653
+ channels: {
6654
+ ...currentConfig.channels,
6655
+ ...updates.channels
6656
+ },
6657
+ quietHours: {
6658
+ enabled: updatesQuietHours.enabled ?? currentQuietHours.enabled,
6659
+ startHour: updatesQuietHours.startHour ?? currentQuietHours.startHour,
6660
+ endHour: updatesQuietHours.endHour ?? currentQuietHours.endHour,
6661
+ timezone: updatesQuietHours.timezone ?? currentQuietHours.timezone
6662
+ }
6663
+ };
6664
+ await saveNotificationConfig(newConfig);
6665
+ return newConfig;
6666
+ }
6667
+ async function enableChannel(channel, config) {
6668
+ const currentConfig = await loadNotificationConfig();
6669
+ currentConfig.channels[channel] = {
6670
+ ...config,
6671
+ enabled: true
6672
+ };
6673
+ await saveNotificationConfig(currentConfig);
6674
+ }
6675
+ async function disableChannel(channel) {
6676
+ const currentConfig = await loadNotificationConfig();
6677
+ if (currentConfig.channels[channel]) {
6678
+ currentConfig.channels[channel].enabled = false;
6679
+ await saveNotificationConfig(currentConfig);
6680
+ }
6681
+ }
6682
+ async function getEnabledChannels() {
6683
+ const config = await loadNotificationConfig();
6684
+ const enabledChannels = [];
6685
+ if (config.channels.feishu?.enabled) {
6686
+ enabledChannels.push("feishu");
6687
+ }
6688
+ if (config.channels.wechat?.enabled) {
6689
+ enabledChannels.push("wechat");
6690
+ }
6691
+ if (config.channels.email?.enabled) {
6692
+ enabledChannels.push("email");
6693
+ }
6694
+ if (config.channels.sms?.enabled) {
6695
+ enabledChannels.push("sms");
6696
+ }
6697
+ return enabledChannels;
6698
+ }
6699
+ async function validateCurrentConfig() {
6700
+ const config = await loadNotificationConfig();
6701
+ return validateNotificationConfig(config);
6702
+ }
6703
+ async function getConfigSummary() {
6704
+ const config = await loadNotificationConfig();
6705
+ const enabledChannels = await getEnabledChannels();
6706
+ return {
6707
+ enabled: config.enabled,
6708
+ deviceToken: maskToken(config.deviceToken),
6709
+ threshold: config.threshold,
6710
+ enabledChannels,
6711
+ quietHours: {
6712
+ enabled: config.quietHours?.enabled || false,
6713
+ hours: config.quietHours?.enabled ? `${config.quietHours.startHour}:00 - ${config.quietHours.endHour}:00` : void 0
6714
+ }
6715
+ };
6716
+ }
6717
+ const THRESHOLD_OPTIONS = [
6718
+ { value: 5, label: "5 minutes" },
6719
+ { value: 10, label: "10 minutes" },
6720
+ { value: 15, label: "15 minutes" },
6721
+ { value: 30, label: "30 minutes" },
6722
+ { value: 60, label: "1 hour" }
6723
+ ];
6724
+ async function setThreshold(minutes) {
6725
+ if (minutes < 1) {
6726
+ throw new Error("Threshold must be at least 1 minute");
6727
+ }
6728
+ await updateNotificationConfig({ threshold: minutes });
6729
+ }
6730
+ async function enableNotifications() {
6731
+ await updateNotificationConfig({ enabled: true });
6732
+ }
6733
+ async function disableNotifications() {
6734
+ await updateNotificationConfig({ enabled: false });
6735
+ }
6736
+
6737
+ const DEFAULT_CLOUD_ENDPOINT = "https://api.claudehome.cn";
6738
+ const REQUEST_TIMEOUT = 3e4;
6739
+ const POLL_TIMEOUT = 6e4;
6740
+ class CloudClient {
6741
+ static instance = null;
6742
+ endpoint = DEFAULT_CLOUD_ENDPOINT;
6743
+ deviceToken = "";
6744
+ isPolling = false;
6745
+ pollAbortController = null;
6746
+ constructor() {
6747
+ }
6748
+ /**
6749
+ * Get the singleton instance
6750
+ */
6751
+ static getInstance() {
6752
+ if (!CloudClient.instance) {
6753
+ CloudClient.instance = new CloudClient();
6754
+ }
6755
+ return CloudClient.instance;
6756
+ }
6757
+ /**
6758
+ * Initialize the cloud client
6759
+ */
6760
+ async initialize() {
6761
+ const config = await loadNotificationConfig();
6762
+ this.endpoint = config.cloudEndpoint || DEFAULT_CLOUD_ENDPOINT;
6763
+ this.deviceToken = config.deviceToken;
6764
+ }
6765
+ /**
6766
+ * Set the cloud endpoint
6767
+ */
6768
+ setEndpoint(endpoint) {
6769
+ this.endpoint = endpoint;
6770
+ }
6771
+ /**
6772
+ * Set the device token
6773
+ */
6774
+ setDeviceToken(token) {
6775
+ this.deviceToken = token;
6776
+ }
6777
+ // ==========================================================================
6778
+ // Device Registration
6779
+ // ==========================================================================
6780
+ /**
6781
+ * Register this device with the cloud service
6782
+ */
6783
+ async registerDevice(name) {
6784
+ const deviceInfo = getDeviceInfo();
6785
+ const config = await loadNotificationConfig();
6786
+ const channelsArray = this.convertChannelsToArray(config.channels);
6787
+ const request = {
6788
+ name: name || deviceInfo.name,
6789
+ platform: deviceInfo.platform,
6790
+ version: "1.0.0",
6791
+ // TODO: Get from package.json
6792
+ config: {
6793
+ channels: channelsArray,
6794
+ threshold: config.threshold
6795
+ }
6796
+ };
6797
+ const response = await this.request(
6798
+ "/device/register",
6799
+ {
6800
+ method: "POST",
6801
+ body: JSON.stringify(request)
6802
+ }
6803
+ );
6804
+ if (response.success && response.data) {
6805
+ await updateNotificationConfig({
6806
+ deviceToken: response.data.token
6807
+ });
6808
+ this.deviceToken = response.data.token;
6809
+ return response.data;
6810
+ }
6811
+ throw new Error(response.error || "Failed to register device");
6812
+ }
6813
+ /**
6814
+ * Get device info from cloud service
6815
+ */
6816
+ async getDeviceInfo() {
6817
+ const response = await this.request(
6818
+ "/device/info",
6819
+ { method: "GET" }
6820
+ );
6821
+ if (response.success && response.data) {
6822
+ return response.data;
6823
+ }
6824
+ throw new Error(response.error || "Failed to get device info");
6825
+ }
6826
+ /**
6827
+ * Update device channels on cloud service
6828
+ */
6829
+ async updateChannels(channels) {
6830
+ const channelsArray = this.convertChannelsToArray(channels);
6831
+ const response = await this.request(
6832
+ "/device/channels",
6833
+ {
6834
+ method: "PUT",
6835
+ body: JSON.stringify({ channels: channelsArray })
6836
+ }
6837
+ );
6838
+ if (!response.success) {
6839
+ throw new Error(response.error || "Failed to update channels");
6840
+ }
6841
+ }
6842
+ // ==========================================================================
6843
+ // Notification Sending
6844
+ // ==========================================================================
6845
+ /**
6846
+ * Send a notification through the cloud service
6847
+ */
6848
+ async sendNotification(message, channels) {
6849
+ const config = await loadNotificationConfig();
6850
+ const targetChannels = channels || this.getEnabledChannelsFromConfig(config.channels);
6851
+ if (targetChannels.length === 0) {
6852
+ return [];
6853
+ }
6854
+ const title = message.title || this.generateTitle(message.type);
6855
+ const body = this.generateBody(message);
6856
+ const response = await this.request(
6857
+ "/notify",
6858
+ {
6859
+ method: "POST",
6860
+ body: JSON.stringify({
6861
+ type: message.type,
6862
+ title,
6863
+ body,
6864
+ task: message.task,
6865
+ channels: targetChannels,
6866
+ actions: message.actions,
6867
+ priority: message.priority
6868
+ })
6869
+ }
6870
+ );
6871
+ if (response.success && response.data) {
6872
+ return response.data.results;
6873
+ }
6874
+ return targetChannels.map((channel) => ({
6875
+ success: false,
6876
+ channel,
6877
+ sentAt: /* @__PURE__ */ new Date(),
6878
+ error: response.error || "Failed to send notification"
6879
+ }));
6880
+ }
6881
+ /**
6882
+ * Send a test notification
6883
+ */
6884
+ async sendTestNotification() {
6885
+ const response = await this.request(
6886
+ "/notify/test",
6887
+ { method: "POST" }
6888
+ );
6889
+ if (response.success && response.data) {
6890
+ return response.data.results;
6891
+ }
6892
+ throw new Error(response.error || "Failed to send test notification");
6893
+ }
6894
+ // ==========================================================================
6895
+ // Reply Polling
6896
+ // ==========================================================================
6897
+ /**
6898
+ * Start polling for replies
6899
+ *
6900
+ * @param onReply - Callback when a reply is received
6901
+ * @param onError - Callback when an error occurs
6902
+ */
6903
+ startPolling(onReply, onError) {
6904
+ if (this.isPolling) {
6905
+ return;
6906
+ }
6907
+ this.isPolling = true;
6908
+ this.pollLoop(onReply, onError);
6909
+ }
6910
+ /**
6911
+ * Stop polling for replies
6912
+ */
6913
+ stopPolling() {
6914
+ this.isPolling = false;
6915
+ if (this.pollAbortController) {
6916
+ this.pollAbortController.abort();
6917
+ this.pollAbortController = null;
6918
+ }
6919
+ }
6920
+ /**
6921
+ * Poll loop for replies
6922
+ */
6923
+ async pollLoop(onReply, onError) {
6924
+ while (this.isPolling) {
6925
+ try {
6926
+ const reply = await this.pollForReply();
6927
+ if (reply) {
6928
+ onReply(reply);
6929
+ }
6930
+ } catch (error) {
6931
+ if (error instanceof Error && error.name === "AbortError") {
6932
+ break;
6933
+ }
6934
+ onError?.(error instanceof Error ? error : new Error(String(error)));
6935
+ await this.sleep(5e3);
6936
+ }
6937
+ }
6938
+ }
6939
+ /**
6940
+ * Poll for a single reply (long-polling)
6941
+ */
6942
+ async pollForReply() {
6943
+ this.pollAbortController = new AbortController();
6944
+ try {
6945
+ const response = await this.request(
6946
+ "/reply/poll",
6947
+ {
6948
+ method: "GET",
6949
+ signal: this.pollAbortController.signal,
6950
+ timeout: POLL_TIMEOUT
6951
+ }
6952
+ );
6953
+ if (response.success && response.data?.reply) {
6954
+ return {
6955
+ ...response.data.reply,
6956
+ timestamp: new Date(response.data.reply.timestamp)
6957
+ };
6958
+ }
6959
+ return null;
6960
+ } finally {
6961
+ this.pollAbortController = null;
6962
+ }
6963
+ }
6964
+ /**
6965
+ * Get reply for a specific notification
6966
+ */
6967
+ async getReply(notificationId) {
6968
+ const response = await this.request(
6969
+ `/reply/${notificationId}`,
6970
+ { method: "GET" }
6971
+ );
6972
+ if (response.success && response.data?.reply) {
6973
+ return {
6974
+ ...response.data.reply,
6975
+ timestamp: new Date(response.data.reply.timestamp)
6976
+ };
6977
+ }
6978
+ return null;
6979
+ }
6980
+ // ==========================================================================
6981
+ // HTTP Request Helper
6982
+ // ==========================================================================
6983
+ /**
6984
+ * Make an HTTP request to the cloud service
6985
+ */
6986
+ async request(path, options) {
6987
+ const url = `${this.endpoint}${path}`;
6988
+ const timeout = options.timeout || REQUEST_TIMEOUT;
6989
+ const timeoutController = new AbortController();
6990
+ const timeoutId = setTimeout(() => timeoutController.abort(), timeout);
6991
+ try {
6992
+ const response = await fetch(url, {
6993
+ method: options.method,
6994
+ headers: {
6995
+ "Content-Type": "application/json",
6996
+ "X-Device-Token": this.deviceToken
6997
+ },
6998
+ body: options.body,
6999
+ signal: options.signal || timeoutController.signal
7000
+ });
7001
+ clearTimeout(timeoutId);
7002
+ const data = await response.json();
7003
+ if (!response.ok) {
7004
+ return {
7005
+ success: false,
7006
+ error: data.error || `HTTP ${response.status}: ${response.statusText}`,
7007
+ code: data.code
7008
+ };
7009
+ }
7010
+ return data;
7011
+ } catch (error) {
7012
+ clearTimeout(timeoutId);
7013
+ if (error instanceof Error) {
7014
+ if (error.name === "AbortError") {
7015
+ throw error;
7016
+ }
7017
+ return {
7018
+ success: false,
7019
+ error: error.message,
7020
+ code: "NETWORK_ERROR"
7021
+ };
7022
+ }
7023
+ return {
7024
+ success: false,
7025
+ error: String(error),
7026
+ code: "UNKNOWN_ERROR"
7027
+ };
7028
+ }
7029
+ }
7030
+ // ==========================================================================
7031
+ // Utility Methods
7032
+ // ==========================================================================
7033
+ /**
7034
+ * Get enabled channels from config
7035
+ */
7036
+ getEnabledChannelsFromConfig(channels) {
7037
+ const enabledChannels = [];
7038
+ for (const [name, channelConfig] of Object.entries(channels)) {
7039
+ if (channelConfig?.enabled) {
7040
+ enabledChannels.push(name);
7041
+ }
7042
+ }
7043
+ return enabledChannels;
7044
+ }
7045
+ /**
7046
+ * Sleep for a specified duration
7047
+ */
7048
+ sleep(ms) {
7049
+ return new Promise((resolve) => setTimeout(resolve, ms));
7050
+ }
7051
+ /**
7052
+ * Convert channels from object format to array format
7053
+ *
7054
+ * Converts from: { feishu: { enabled: true, webhookUrl: "..." } }
7055
+ * To: [{ type: "feishu", enabled: true, config: { webhookUrl: "..." } }]
7056
+ */
7057
+ convertChannelsToArray(channels) {
7058
+ const result = [];
7059
+ for (const [channelType, channelData] of Object.entries(channels)) {
7060
+ if (channelData && typeof channelData === "object") {
7061
+ const { enabled, ...config } = channelData;
7062
+ result.push({
7063
+ type: channelType,
7064
+ enabled: Boolean(enabled),
7065
+ config
7066
+ });
7067
+ }
7068
+ }
7069
+ return result;
7070
+ }
7071
+ /**
7072
+ * Generate notification title based on type
7073
+ */
7074
+ generateTitle(type) {
7075
+ const titles = {
7076
+ task_started: "Task Started",
7077
+ task_progress: "Task Progress",
7078
+ task_completed: "Task Completed",
7079
+ task_failed: "Task Failed",
7080
+ task_cancelled: "Task Cancelled",
7081
+ system: "System Notification"
7082
+ };
7083
+ return titles[type] || "Notification";
7084
+ }
7085
+ /**
7086
+ * Generate notification body from message
7087
+ */
7088
+ generateBody(message) {
7089
+ const { task } = message;
7090
+ const lines = [];
7091
+ lines.push(`**Task**: ${task.description}`);
7092
+ lines.push(`**Status**: ${task.status}`);
7093
+ if (task.duration) {
7094
+ const minutes = Math.floor(task.duration / 6e4);
7095
+ const seconds = Math.floor(task.duration % 6e4 / 1e3);
7096
+ lines.push(`**Duration**: ${minutes}m ${seconds}s`);
7097
+ }
7098
+ if (task.result) {
7099
+ lines.push(`**Result**: ${task.result}`);
7100
+ }
7101
+ if (task.error) {
7102
+ lines.push(`**Error**: ${task.error}`);
7103
+ }
7104
+ return lines.join("\n");
7105
+ }
7106
+ /**
7107
+ * Reset the singleton instance (for testing)
7108
+ */
7109
+ static resetInstance() {
7110
+ if (CloudClient.instance) {
7111
+ CloudClient.instance.stopPolling();
7112
+ CloudClient.instance = null;
7113
+ }
7114
+ }
7115
+ }
7116
+
7117
+ async function notificationCommand(action = "menu") {
7118
+ switch (action) {
7119
+ case "config":
7120
+ case "configure":
7121
+ await runConfigWizard();
7122
+ break;
7123
+ case "status":
7124
+ await showStatus();
7125
+ break;
7126
+ case "test":
7127
+ await sendTestNotification();
7128
+ break;
7129
+ case "enable":
7130
+ await enableNotifications();
7131
+ console.log(ansis.green(`\u2705 ${i18n.t("notification:status.enabled")}`));
7132
+ break;
7133
+ case "disable":
7134
+ await disableNotifications();
7135
+ console.log(ansis.yellow(`\u23F8\uFE0F ${i18n.t("notification:status.disabled")}`));
7136
+ break;
7137
+ case "channels":
7138
+ await manageChannels();
7139
+ break;
7140
+ case "threshold":
7141
+ await configureThreshold();
7142
+ break;
7143
+ case "menu":
7144
+ default:
7145
+ await showNotificationMenu();
7146
+ break;
7147
+ }
7148
+ }
7149
+ async function showNotificationMenu() {
7150
+ const config = await loadNotificationConfig();
7151
+ const enabledChannels = await getEnabledChannels();
7152
+ console.log("");
7153
+ console.log(ansis.bold.cyan(i18n.t("notification:menu.title")));
7154
+ console.log("");
7155
+ const statusText = config.enabled ? ansis.green(i18n.t("notification:status.enabled")) : ansis.yellow(i18n.t("notification:status.disabled"));
7156
+ console.log(` ${ansis.dim("\u72B6\u6001:")} ${statusText}`);
7157
+ if (enabledChannels.length > 0) {
7158
+ const channelNames = enabledChannels.map((ch) => i18n.t(`notification:channels.${ch}`)).join(", ");
7159
+ console.log(` ${ansis.dim("\u6E20\u9053:")} ${channelNames}`);
7160
+ } else {
7161
+ console.log(` ${ansis.dim("\u6E20\u9053:")} ${ansis.yellow(i18n.t("notification:channels.noChannels"))}`);
7162
+ }
7163
+ console.log(` ${ansis.dim("\u9608\u503C:")} ${config.threshold} ${i18n.t("notification:config.threshold.minutes", { count: config.threshold })}`);
7164
+ console.log("");
7165
+ const { action } = await inquirer.prompt([{
7166
+ type: "list",
7167
+ name: "action",
7168
+ message: "\u9009\u62E9\u64CD\u4F5C:",
7169
+ choices: [
7170
+ {
7171
+ name: config.enabled ? "\u23F8\uFE0F \u7981\u7528\u901A\u77E5" : "\u25B6\uFE0F \u542F\u7528\u901A\u77E5",
7172
+ value: config.enabled ? "disable" : "enable"
7173
+ },
7174
+ { name: "\u2699\uFE0F \u914D\u7F6E\u5411\u5BFC", value: "config" },
7175
+ { name: "\u{1F4F1} \u7BA1\u7406\u6E20\u9053", value: "channels" },
7176
+ { name: "\u23F1\uFE0F \u8BBE\u7F6E\u9608\u503C", value: "threshold" },
7177
+ { name: "\u{1F4CA} \u67E5\u770B\u72B6\u6001", value: "status" },
7178
+ { name: "\u{1F9EA} \u53D1\u9001\u6D4B\u8BD5", value: "test" },
7179
+ { name: "\u2190 \u8FD4\u56DE", value: "back" }
7180
+ ]
7181
+ }]);
7182
+ if (action === "back") {
7183
+ return;
7184
+ }
7185
+ await notificationCommand(action);
7186
+ }
7187
+ async function runConfigWizard() {
7188
+ console.log("");
7189
+ console.log(ansis.bold.cyan(i18n.t("notification:config.wizard.title")));
7190
+ console.log(ansis.dim(i18n.t("notification:config.wizard.welcome")));
7191
+ console.log("");
7192
+ console.log(ansis.yellow(i18n.t("notification:config.wizard.step1")));
7193
+ const config = await initializeNotificationConfig();
7194
+ console.log(ansis.green(i18n.t("notification:config.wizard.tokenGenerated", { token: maskToken(config.deviceToken) })));
7195
+ console.log("");
7196
+ console.log(ansis.yellow(i18n.t("notification:config.wizard.step2")));
7197
+ const channels = await selectChannels();
7198
+ console.log("");
7199
+ if (channels.length > 0) {
7200
+ console.log(ansis.yellow(i18n.t("notification:config.wizard.step3")));
7201
+ for (const channel of channels) {
7202
+ await configureChannel(channel);
7203
+ }
7204
+ console.log("");
7205
+ }
7206
+ console.log(ansis.yellow(i18n.t("notification:config.wizard.step4")));
7207
+ await configureThreshold();
7208
+ console.log("");
7209
+ console.log(ansis.yellow(i18n.t("notification:config.wizard.step5")));
7210
+ await updateNotificationConfig({ enabled: true });
7211
+ const { shouldTest } = await inquirer.prompt([{
7212
+ type: "confirm",
7213
+ name: "shouldTest",
7214
+ message: "\u662F\u5426\u53D1\u9001\u6D4B\u8BD5\u901A\u77E5?",
7215
+ default: true
7216
+ }]);
7217
+ if (shouldTest) {
7218
+ await sendTestNotification();
7219
+ }
7220
+ console.log("");
7221
+ console.log(ansis.bold.green(i18n.t("notification:config.wizard.complete")));
7222
+ console.log("");
7223
+ }
7224
+ async function selectChannels() {
7225
+ const choices = [
7226
+ { name: `\u{1F4F1} ${i18n.t("notification:channels.feishu")}`, value: "feishu" },
7227
+ { name: `\u{1F4AC} ${i18n.t("notification:channels.wechat")}`, value: "wechat" },
7228
+ { name: `\u{1F4E7} ${i18n.t("notification:channels.email")}`, value: "email" },
7229
+ { name: `\u{1F4F2} ${i18n.t("notification:channels.sms")}`, value: "sms" }
7230
+ ];
7231
+ const selected = [];
7232
+ for (const choice of choices) {
7233
+ const { enable } = await inquirer.prompt([{
7234
+ type: "confirm",
7235
+ name: "enable",
7236
+ message: `\u542F\u7528 ${choice.name}?`,
7237
+ default: false
7238
+ }]);
7239
+ if (enable) {
7240
+ selected.push(choice.value);
7241
+ }
7242
+ }
7243
+ return selected;
7244
+ }
7245
+ async function configureChannel(channel) {
7246
+ console.log("");
7247
+ console.log(ansis.cyan(`\u914D\u7F6E ${i18n.t(`notification:channels.${channel}`)}:`));
7248
+ switch (channel) {
7249
+ case "feishu":
7250
+ await configureFeishu();
7251
+ break;
7252
+ case "wechat":
7253
+ await configureWechat();
7254
+ break;
7255
+ case "email":
7256
+ await configureEmail();
7257
+ break;
7258
+ case "sms":
7259
+ await configureSms();
7260
+ break;
7261
+ }
7262
+ }
7263
+ async function configureFeishu() {
7264
+ const { webhookUrl } = await inquirer.prompt([{
7265
+ type: "input",
7266
+ name: "webhookUrl",
7267
+ message: i18n.t("notification:feishu.webhookUrl"),
7268
+ validate: (value) => {
7269
+ if (!value.startsWith("https://")) {
7270
+ return i18n.t("notification:errors.invalidWebhook");
7271
+ }
7272
+ return true;
7273
+ }
7274
+ }]);
7275
+ const { secret } = await inquirer.prompt([{
7276
+ type: "input",
7277
+ name: "secret",
7278
+ message: `${i18n.t("notification:feishu.secret")} (\u53EF\u9009\uFF0C\u76F4\u63A5\u56DE\u8F66\u8DF3\u8FC7):`
7279
+ }]);
7280
+ const config = {
7281
+ enabled: true,
7282
+ webhookUrl,
7283
+ ...secret && { secret }
7284
+ };
7285
+ await enableChannel("feishu", config);
7286
+ console.log(ansis.green(`\u2705 ${i18n.t("notification:channels.feishu")} ${i18n.t("notification:status.configured")}`));
7287
+ }
7288
+ async function configureWechat() {
7289
+ const { corpId } = await inquirer.prompt([{
7290
+ type: "input",
7291
+ name: "corpId",
7292
+ message: i18n.t("notification:wechat.corpId"),
7293
+ validate: (value) => !!value || i18n.t("notification:errors.invalidWebhook")
7294
+ }]);
7295
+ const { agentId } = await inquirer.prompt([{
7296
+ type: "input",
7297
+ name: "agentId",
7298
+ message: i18n.t("notification:wechat.agentId"),
7299
+ validate: (value) => !!value || i18n.t("notification:errors.invalidWebhook")
7300
+ }]);
7301
+ const { secret } = await inquirer.prompt([{
7302
+ type: "input",
7303
+ name: "secret",
7304
+ message: i18n.t("notification:wechat.secret"),
7305
+ validate: (value) => !!value || i18n.t("notification:errors.invalidWebhook")
7306
+ }]);
7307
+ const config = {
7308
+ enabled: true,
7309
+ corpId,
7310
+ agentId,
7311
+ secret
7312
+ };
7313
+ await enableChannel("wechat", config);
7314
+ console.log(ansis.green(`\u2705 ${i18n.t("notification:channels.wechat")} ${i18n.t("notification:status.configured")}`));
7315
+ }
7316
+ async function configureEmail() {
7317
+ const { address } = await inquirer.prompt([{
7318
+ type: "input",
7319
+ name: "address",
7320
+ message: i18n.t("notification:email.address"),
7321
+ validate: (value) => {
7322
+ if (!/^[^\s@]+@[^\s@][^\s.@]*\.[^\s@]+$/.test(value)) {
7323
+ return i18n.t("notification:errors.invalidEmail");
7324
+ }
7325
+ return true;
7326
+ }
7327
+ }]);
7328
+ const config = {
7329
+ enabled: true,
7330
+ address
7331
+ };
7332
+ await enableChannel("email", config);
7333
+ console.log(ansis.green(`\u2705 ${i18n.t("notification:channels.email")} ${i18n.t("notification:status.configured")}`));
7334
+ }
7335
+ async function configureSms() {
7336
+ const { phone } = await inquirer.prompt([{
7337
+ type: "input",
7338
+ name: "phone",
7339
+ message: i18n.t("notification:sms.phone"),
7340
+ validate: (value) => {
7341
+ if (!/^\d{10,15}$/.test(value.replace(/\D/g, ""))) {
7342
+ return i18n.t("notification:errors.invalidPhone");
7343
+ }
7344
+ return true;
7345
+ }
7346
+ }]);
7347
+ const { countryCode } = await inquirer.prompt([{
7348
+ type: "input",
7349
+ name: "countryCode",
7350
+ message: i18n.t("notification:sms.countryCode"),
7351
+ default: "+86"
7352
+ }]);
7353
+ const config = {
7354
+ enabled: true,
7355
+ phone,
7356
+ countryCode
7357
+ };
7358
+ await enableChannel("sms", config);
7359
+ console.log(ansis.green(`\u2705 ${i18n.t("notification:channels.sms")} ${i18n.t("notification:status.configured")}`));
7360
+ }
7361
+ async function manageChannels() {
7362
+ const enabledChannels = await getEnabledChannels();
7363
+ console.log("");
7364
+ console.log(ansis.bold.cyan(i18n.t("notification:channels.title")));
7365
+ console.log("");
7366
+ if (enabledChannels.length === 0) {
7367
+ console.log(ansis.yellow(i18n.t("notification:channels.noChannels")));
7368
+ } else {
7369
+ console.log(i18n.t("notification:channels.enabledCount", { count: enabledChannels.length }));
7370
+ for (const channel of enabledChannels) {
7371
+ console.log(` \u2705 ${i18n.t(`notification:channels.${channel}`)}`);
7372
+ }
7373
+ }
7374
+ console.log("");
7375
+ const { action } = await inquirer.prompt([{
7376
+ type: "list",
7377
+ name: "action",
7378
+ message: "\u9009\u62E9\u64CD\u4F5C:",
7379
+ choices: [
7380
+ { name: "\u2795 \u6DFB\u52A0\u6E20\u9053", value: "add" },
7381
+ { name: "\u2796 \u79FB\u9664\u6E20\u9053", value: "remove" },
7382
+ { name: "\u2190 \u8FD4\u56DE", value: "back" }
7383
+ ]
7384
+ }]);
7385
+ if (action === "back") {
7386
+ return;
7387
+ }
7388
+ if (action === "add") {
7389
+ const allChannels = ["feishu", "wechat", "email", "sms"];
7390
+ const availableChannels = allChannels.filter((ch) => !enabledChannels.includes(ch));
7391
+ if (availableChannels.length === 0) {
7392
+ console.log(ansis.yellow("\u6240\u6709\u6E20\u9053\u90FD\u5DF2\u542F\u7528"));
7393
+ return;
7394
+ }
7395
+ const { channel } = await inquirer.prompt([{
7396
+ type: "list",
7397
+ name: "channel",
7398
+ message: "\u9009\u62E9\u8981\u6DFB\u52A0\u7684\u6E20\u9053:",
7399
+ choices: availableChannels.map((ch) => ({
7400
+ name: i18n.t(`notification:channels.${ch}`),
7401
+ value: ch
7402
+ }))
7403
+ }]);
7404
+ await configureChannel(channel);
7405
+ } else if (action === "remove") {
7406
+ if (enabledChannels.length === 0) {
7407
+ console.log(ansis.yellow("\u6CA1\u6709\u5DF2\u542F\u7528\u7684\u6E20\u9053"));
7408
+ return;
7409
+ }
7410
+ const { channel } = await inquirer.prompt([{
7411
+ type: "list",
7412
+ name: "channel",
7413
+ message: "\u9009\u62E9\u8981\u79FB\u9664\u7684\u6E20\u9053:",
7414
+ choices: enabledChannels.map((ch) => ({
7415
+ name: i18n.t(`notification:channels.${ch}`),
7416
+ value: ch
7417
+ }))
7418
+ }]);
7419
+ await disableChannel(channel);
7420
+ console.log(ansis.green(`\u2705 \u5DF2\u79FB\u9664 ${i18n.t(`notification:channels.${channel}`)}`));
7421
+ }
7422
+ }
7423
+ async function configureThreshold() {
7424
+ const config = await loadNotificationConfig();
7425
+ console.log("");
7426
+ console.log(ansis.dim(i18n.t("notification:config.threshold.description")));
7427
+ console.log("");
7428
+ const { threshold } = await inquirer.prompt([{
7429
+ type: "list",
7430
+ name: "threshold",
7431
+ message: i18n.t("notification:config.threshold.title"),
7432
+ choices: [
7433
+ ...THRESHOLD_OPTIONS.map((opt) => ({
7434
+ name: opt.label,
7435
+ value: opt.value
7436
+ })),
7437
+ { name: i18n.t("notification:config.threshold.custom"), value: -1 }
7438
+ ],
7439
+ default: config.threshold
7440
+ }]);
7441
+ let finalThreshold = threshold;
7442
+ if (threshold === -1) {
7443
+ const { customValue } = await inquirer.prompt([{
7444
+ type: "input",
7445
+ name: "customValue",
7446
+ message: "\u8F93\u5165\u81EA\u5B9A\u4E49\u9608\u503C\uFF08\u5206\u949F\uFF09:",
7447
+ validate: (value) => {
7448
+ const num = Number.parseInt(value, 10);
7449
+ if (Number.isNaN(num) || num < 1) {
7450
+ return "\u8BF7\u8F93\u5165\u5927\u4E8E 0 \u7684\u6570\u5B57";
7451
+ }
7452
+ return true;
7453
+ }
7454
+ }]);
7455
+ finalThreshold = Number.parseInt(customValue, 10);
7456
+ }
7457
+ await setThreshold(finalThreshold);
7458
+ console.log(ansis.green(`\u2705 \u9608\u503C\u5DF2\u8BBE\u7F6E\u4E3A ${finalThreshold} \u5206\u949F`));
7459
+ }
7460
+ async function showStatus() {
7461
+ const summary = await getConfigSummary();
7462
+ const validation = await validateCurrentConfig();
7463
+ console.log("");
7464
+ console.log(ansis.bold.cyan("\u{1F4CA} \u901A\u77E5\u7CFB\u7EDF\u72B6\u6001"));
7465
+ console.log("");
7466
+ const statusIcon = summary.enabled ? "\u2705" : "\u23F8\uFE0F";
7467
+ const statusText = summary.enabled ? ansis.green(i18n.t("notification:status.enabled")) : ansis.yellow(i18n.t("notification:status.disabled"));
7468
+ console.log(` ${statusIcon} \u72B6\u6001: ${statusText}`);
7469
+ console.log(` \u{1F511} \u8BBE\u5907\u4EE4\u724C: ${ansis.dim(summary.deviceToken)}`);
7470
+ console.log(` \u23F1\uFE0F \u9608\u503C: ${summary.threshold} \u5206\u949F`);
7471
+ console.log("");
7472
+ console.log(ansis.bold(" \u{1F4F1} \u901A\u77E5\u6E20\u9053:"));
7473
+ if (summary.enabledChannels.length === 0) {
7474
+ console.log(` ${ansis.yellow(i18n.t("notification:channels.noChannels"))}`);
7475
+ } else {
7476
+ for (const channel of summary.enabledChannels) {
7477
+ console.log(` \u2705 ${i18n.t(`notification:channels.${channel}`)}`);
7478
+ }
7479
+ }
7480
+ if (summary.quietHours.enabled) {
7481
+ console.log("");
7482
+ console.log(` \u{1F319} \u514D\u6253\u6270: ${summary.quietHours.hours}`);
7483
+ }
7484
+ if (!validation.valid) {
7485
+ console.log("");
7486
+ console.log(ansis.bold.red(" \u26A0\uFE0F \u914D\u7F6E\u95EE\u9898:"));
7487
+ for (const error of validation.errors) {
7488
+ console.log(` \u274C ${error.message}`);
7489
+ }
7490
+ }
7491
+ if (validation.warnings.length > 0) {
7492
+ console.log("");
7493
+ console.log(ansis.bold.yellow(" \u26A0\uFE0F \u8B66\u544A:"));
7494
+ for (const warning of validation.warnings) {
7495
+ console.log(` \u26A0\uFE0F ${warning}`);
7496
+ }
7497
+ }
7498
+ console.log("");
7499
+ }
7500
+ async function sendTestNotification() {
7501
+ const enabledChannels = await getEnabledChannels();
7502
+ if (enabledChannels.length === 0) {
7503
+ console.log(ansis.yellow(i18n.t("notification:errors.noChannels")));
7504
+ return;
7505
+ }
7506
+ console.log("");
7507
+ console.log(ansis.cyan(i18n.t("notification:test.sending")));
7508
+ try {
7509
+ const client = CloudClient.getInstance();
7510
+ await client.initialize();
7511
+ const results = await client.sendTestNotification();
7512
+ console.log("");
7513
+ let hasSuccess = false;
7514
+ let hasFailure = false;
7515
+ for (const result of results) {
7516
+ const channelName = i18n.t(`notification:channels.${result.channel}`);
7517
+ if (result.success) {
7518
+ console.log(ansis.green(`\u2705 ${channelName}: ${i18n.t("notification:test.success")}`));
7519
+ hasSuccess = true;
7520
+ } else {
7521
+ console.log(ansis.red(`\u274C ${channelName}: ${result.error || i18n.t("notification:test.failed")}`));
7522
+ hasFailure = true;
7523
+ }
7524
+ }
7525
+ console.log("");
7526
+ if (hasSuccess) {
7527
+ console.log(ansis.dim(i18n.t("notification:test.checkDevice")));
7528
+ }
7529
+ if (hasFailure) {
7530
+ console.log(ansis.yellow(i18n.t("notification:test.partialFailure")));
7531
+ }
7532
+ } catch (error) {
7533
+ console.log("");
7534
+ console.log(ansis.red(`\u274C ${i18n.t("notification:errors.sendFailed")}`));
7535
+ if (error instanceof Error) {
7536
+ console.log(ansis.dim(error.message));
7537
+ }
7538
+ console.log("");
7539
+ console.log(ansis.yellow(i18n.t("notification:test.troubleshooting")));
7540
+ console.log(ansis.dim(` 1. ${i18n.t("notification:test.checkConnection")}`));
7541
+ console.log(ansis.dim(` 2. ${i18n.t("notification:test.checkConfig")}`));
7542
+ console.log(ansis.dim(` 3. ${i18n.t("notification:test.checkToken")}`));
7543
+ }
7544
+ console.log("");
7545
+ }
7546
+
6312
7547
  const SESSIONS_DIR = join$1(homedir(), ".ccjk", "sessions");
6313
7548
  async function saveSession() {
6314
7549
  try {
@@ -7330,6 +8565,9 @@ async function setupCommands(cli) {
7330
8565
  cli.command("workflows", "Manage installed workflows").alias("wf").option("--lang, -l <lang>", "Display language (zh-CN, en)").action(await withLanguageResolution(async () => {
7331
8566
  await showWorkflows();
7332
8567
  }));
8568
+ cli.command("notification [action]", "Manage task completion notifications (config, status, test, enable, disable, channels)").alias("notify").option("--lang, -l <lang>", "Display language (zh-CN, en)").action(await withLanguageResolution(async (action) => {
8569
+ await notificationCommand(action);
8570
+ }));
7333
8571
  cli.help((sections) => customizeHelp(sections));
7334
8572
  cli.version(version);
7335
8573
  }