ccem 2.25.0 → 2.27.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 (2) hide show
  1. package/dist/index.js +248 -15
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -400,24 +400,57 @@ import crypto from "crypto";
400
400
  import fs from "fs";
401
401
  import os from "os";
402
402
  import path from "path";
403
- var ALGORITHM = "aes-256-cbc";
404
- var SECRET_KEY = crypto.scryptSync("claude-code-env-manager-secret", "salt", 32);
403
+ var LEGACY_ALGORITHM = "aes-256-cbc";
404
+ var LEGACY_KEY = crypto.scryptSync("claude-code-env-manager-secret", "salt", 32);
405
+ var V2_ALGORITHM = "aes-256-gcm";
406
+ var _installKey = null;
407
+ function getOrCreateInstallKey() {
408
+ if (_installKey) return _installKey;
409
+ const keyPath = path.join(getCcemConfigDir(), ".install-key");
410
+ if (fs.existsSync(keyPath)) {
411
+ _installKey = Buffer.from(fs.readFileSync(keyPath, "utf-8").trim(), "hex");
412
+ return _installKey;
413
+ }
414
+ ensureCcemDir();
415
+ _installKey = crypto.randomBytes(32);
416
+ fs.writeFileSync(keyPath, _installKey.toString("hex"), { mode: 384 });
417
+ return _installKey;
418
+ }
405
419
  var encrypt = (text) => {
406
420
  if (!text) return text;
407
- const iv = crypto.randomBytes(16);
408
- const cipher = crypto.createCipheriv(ALGORITHM, SECRET_KEY, iv);
421
+ const key = getOrCreateInstallKey();
422
+ const nonce = crypto.randomBytes(12);
423
+ const cipher = crypto.createCipheriv(V2_ALGORITHM, key, nonce);
409
424
  let encrypted = cipher.update(text, "utf8", "hex");
410
425
  encrypted += cipher.final("hex");
411
- return `enc:${iv.toString("hex")}:${encrypted}`;
426
+ const tag = cipher.getAuthTag();
427
+ return `enc:v2:${nonce.toString("hex")}:${encrypted}:${tag.toString("hex")}`;
412
428
  };
413
429
  var decrypt = (text) => {
414
430
  if (!text || !text.startsWith("enc:")) return text;
431
+ if (text.startsWith("enc:v2:")) {
432
+ const parts = text.split(":");
433
+ if (parts.length !== 5) return text;
434
+ try {
435
+ const key = getOrCreateInstallKey();
436
+ const nonce = Buffer.from(parts[2], "hex");
437
+ const ciphertext = parts[3];
438
+ const tag = Buffer.from(parts[4], "hex");
439
+ const decipher = crypto.createDecipheriv(V2_ALGORITHM, key, nonce);
440
+ decipher.setAuthTag(tag);
441
+ let decrypted = decipher.update(ciphertext, "hex", "utf8");
442
+ decrypted += decipher.final("utf8");
443
+ return decrypted;
444
+ } catch {
445
+ throw new Error("Failed to decrypt enc:v2: data (tampered or wrong install key)");
446
+ }
447
+ }
415
448
  try {
416
449
  const parts = text.split(":");
417
450
  if (parts.length !== 3) return text;
418
451
  const iv = Buffer.from(parts[1], "hex");
419
452
  const encryptedText = parts[2];
420
- const decipher = crypto.createDecipheriv(ALGORITHM, SECRET_KEY, iv);
453
+ const decipher = crypto.createDecipheriv(LEGACY_ALGORITHM, LEGACY_KEY, iv);
421
454
  let decrypted = decipher.update(encryptedText, "hex", "utf8");
422
455
  decrypted += decipher.final("utf8");
423
456
  return decrypted;
@@ -1396,7 +1429,7 @@ import crypto2 from "crypto";
1396
1429
  import fs3 from "fs";
1397
1430
  import os3 from "os";
1398
1431
  import path3 from "path";
1399
- var SECRET_KEY2 = crypto2.scryptSync("claude-code-env-manager-secret", "salt", 32);
1432
+ var SECRET_KEY = crypto2.scryptSync("claude-code-env-manager-secret", "salt", 32);
1400
1433
  var findProjectRoot = () => {
1401
1434
  let currentDir = process.cwd();
1402
1435
  const root = path3.parse(currentDir).root;
@@ -1466,7 +1499,7 @@ function startCliClaudeProvenanceTracking(options) {
1466
1499
  if (!workingDir) {
1467
1500
  return null;
1468
1501
  }
1469
- const ccemSessionId = `cli-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
1502
+ const ccemSessionId = normalizeText(options.ccemSessionId) ?? `cli-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
1470
1503
  try {
1471
1504
  registerLaunch({
1472
1505
  ccemSessionId,
@@ -1826,6 +1859,7 @@ function ensureSessionsDir() {
1826
1859
  }
1827
1860
  async function launchClaude(options) {
1828
1861
  const { envName, envConfig, permMode, workingDir, sessionId, resumeSessionId, silent } = options;
1862
+ const ccemRuntimeId = sessionId ?? `cli-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
1829
1863
  const env = { ...process.env };
1830
1864
  for (const key of MANAGED_CLAUDE_ENV_KEYS) {
1831
1865
  delete env[key];
@@ -1834,6 +1868,8 @@ async function launchClaude(options) {
1834
1868
  Object.assign(env, buildEnvVars(envConfig));
1835
1869
  }
1836
1870
  delete env.CLAUDECODE;
1871
+ env.CCEM_RUNTIME_ID = ccemRuntimeId;
1872
+ env.CCEM_SESSION_ID = ccemRuntimeId;
1837
1873
  const args = [];
1838
1874
  if (permMode) {
1839
1875
  const preset = PERMISSION_PRESETS[permMode];
@@ -1870,6 +1906,7 @@ async function launchClaude(options) {
1870
1906
  return;
1871
1907
  }
1872
1908
  provenanceTracking = startCliClaudeProvenanceTracking({
1909
+ ccemSessionId: ccemRuntimeId,
1873
1910
  envName: envName ?? "unknown",
1874
1911
  workingDir: effectiveWorkingDir,
1875
1912
  permMode,
@@ -2687,13 +2724,17 @@ var getUniqueName = (baseName, existingNames) => {
2687
2724
  }
2688
2725
  return newName;
2689
2726
  };
2690
- var loadFromRemote = async (url, secret) => {
2727
+ var loadFromRemote = async (url, key, secret) => {
2691
2728
  console.log(chalk6.gray("Fetching from remote..."));
2729
+ const headerKey = key || secret;
2730
+ if (!key) {
2731
+ console.log(chalk6.yellow("Warning: --key not provided; using --secret for authentication (deprecated). Use --key <access-key> --secret <encryption-secret>."));
2732
+ }
2692
2733
  let response;
2693
2734
  try {
2694
2735
  response = await fetch(url, {
2695
2736
  headers: {
2696
- "X-CCEM-Key": secret
2737
+ "X-CCEM-Key": headerKey
2697
2738
  }
2698
2739
  });
2699
2740
  } catch (err) {
@@ -2769,6 +2810,66 @@ Loaded ${results.length} environment(s) from remote:`));
2769
2810
  return results;
2770
2811
  };
2771
2812
 
2813
+ // src/bot-bind-skill.ts
2814
+ var CCEM_BOT_BIND_SKILL_CONTENT = `# ccem-bot-bind
2815
+
2816
+ Bind the current CCEM-launched Claude Code session to a bot target such as WeCom or Weixin.
2817
+
2818
+ Use this skill when the user asks to bind, attach, connect, or push the current task/session to a bot, chat, WeCom, Weixin, Telegram, or a remote task card.
2819
+
2820
+ ## What this skill does
2821
+
2822
+ - Creates a bot-binding request for the current CCEM runtime.
2823
+ - Relies on \`CCEM_RUNTIME_ID\` or \`CCEM_SESSION_ID\`, which CCEM injects into sessions it launches.
2824
+ - Writes the request to \`~/.ccem/bot-bind-requests.jsonl\`.
2825
+ - CCEM Desktop consumes the request and binds the matching session to the bot outbox.
2826
+
2827
+ ## Required inputs
2828
+
2829
+ Ask for any missing target information:
2830
+
2831
+ - \`platform\`: one of \`wecom\`, \`weixin\`, or \`telegram\`.
2832
+ - \`peer_id\`: user id, chat id, group id, or equivalent target id.
2833
+ - For WeCom, include \`bot_id\` when known.
2834
+
2835
+ ## Command
2836
+
2837
+ Run:
2838
+
2839
+ \`\`\`bash
2840
+ ccem bot-bind --platform <platform> --peer-id <peer_id> --title "<short task title>" --summary "<one sentence summary>"
2841
+ \`\`\`
2842
+
2843
+ For WeCom:
2844
+
2845
+ \`\`\`bash
2846
+ ccem bot-bind --platform wecom --bot-id <bot_id> --peer-id <userid_or_chatid> --title "<short task title>" --summary "<one sentence summary>"
2847
+ \`\`\`
2848
+
2849
+ If the user only wants to create the binding without pushing a task card, add \`--no-send-card\`.
2850
+
2851
+ If CCEM did not inject a runtime id, ask the user for the runtime id or list sessions with:
2852
+
2853
+ \`\`\`bash
2854
+ ccem sessions
2855
+ \`\`\`
2856
+
2857
+ Then pass:
2858
+
2859
+ \`\`\`bash
2860
+ ccem bot-bind --runtime-id <runtime_id> --platform <platform> --peer-id <peer_id>
2861
+ \`\`\`
2862
+
2863
+ ## Behavior rules
2864
+
2865
+ - Do not invent peer ids or bot ids.
2866
+ - Keep the title short enough for a task card.
2867
+ - Include a summary that explains what this session is currently doing.
2868
+ - After the command succeeds, tell the user the request was queued and CCEM Desktop will consume it when running.
2869
+ - Do not ask the bot target to execute shell commands; user commands should flow back through the CCEM binding.
2870
+ - For WeCom, authorization and target membership are owned by the configured WeCom bot; do not create a separate allowlist.
2871
+ `;
2872
+
2772
2873
  // src/cron-skill.ts
2773
2874
  var CCEM_CRON_SKILL_CONTENT = `# ccem-cron
2774
2875
 
@@ -2939,6 +3040,27 @@ function parseStringList(value) {
2939
3040
  }
2940
3041
  return trimmed.split(",").map((item) => item.trim()).filter(Boolean);
2941
3042
  }
3043
+ function normalizeWecomNotification(value) {
3044
+ if (!value) {
3045
+ return null;
3046
+ }
3047
+ return {
3048
+ botId: value.botId?.trim() || null,
3049
+ peerId: value.peerId?.trim() || null,
3050
+ enabled: value.enabled ?? true
3051
+ };
3052
+ }
3053
+ function parseWecomNotification(value) {
3054
+ if (typeof value !== "object" || value === null) {
3055
+ return null;
3056
+ }
3057
+ const record = value;
3058
+ return {
3059
+ botId: typeof record.botId === "string" ? record.botId : typeof record.bot_id === "string" ? record.bot_id : null,
3060
+ peerId: typeof record.peerId === "string" ? record.peerId : typeof record.peer_id === "string" ? record.peer_id : null,
3061
+ enabled: typeof record.enabled === "boolean" ? record.enabled : true
3062
+ };
3063
+ }
2942
3064
  function createCronTask(input, tasksPath = getCronTasksPath()) {
2943
3065
  const name = input.name?.trim();
2944
3066
  if (!name) {
@@ -2976,6 +3098,7 @@ function createCronTask(input, tasksPath = getCronTasksPath()) {
2976
3098
  enabled: input.enabled ?? true,
2977
3099
  timeoutSecs,
2978
3100
  templateId: input.templateId?.trim() || null,
3101
+ wecomNotification: normalizeWecomNotification(input.wecomNotification),
2979
3102
  triggerType: "schedule",
2980
3103
  parentTaskId: null,
2981
3104
  createdAt: now,
@@ -3014,6 +3137,7 @@ function resolveJsonInput(raw) {
3014
3137
  }
3015
3138
  function parseCronCreateJson(raw) {
3016
3139
  const parsed = JSON.parse(resolveJsonInput(raw));
3140
+ const wecomNotification = parsed.wecomNotification ?? parsed.wecom_notification;
3017
3141
  return {
3018
3142
  name: String(parsed.name ?? ""),
3019
3143
  cronExpression: String(parsed.cronExpression ?? parsed.schedule ?? ""),
@@ -3026,7 +3150,8 @@ function parseCronCreateJson(raw) {
3026
3150
  disallowedTools: Array.isArray(parsed.disallowedTools) ? parsed.disallowedTools.map(String) : null,
3027
3151
  enabled: typeof parsed.enabled === "boolean" ? parsed.enabled : null,
3028
3152
  timeoutSecs: typeof parsed.timeoutSecs === "number" ? parsed.timeoutSecs : null,
3029
- templateId: typeof parsed.templateId === "string" ? parsed.templateId : null
3153
+ templateId: typeof parsed.templateId === "string" ? parsed.templateId : null,
3154
+ wecomNotification: parseWecomNotification(wecomNotification)
3030
3155
  };
3031
3156
  }
3032
3157
  function formatCronTaskTableRows(tasks) {
@@ -3308,6 +3433,7 @@ var switchEnvironment = async (name) => {
3308
3433
  };
3309
3434
  var getSessionsFilePath = () => path8.join(getCcemConfigDir(), "sessions.json");
3310
3435
  var getRuntimeStateFilePath = () => path8.join(getCcemConfigDir(), "runtime-state.json");
3436
+ var getBotBindRequestFilePath = () => path8.join(getCcemConfigDir(), "bot-bind-requests.jsonl");
3311
3437
  var parseJsonFile = (filePath) => {
3312
3438
  if (!fs10.existsSync(filePath)) {
3313
3439
  return null;
@@ -3387,6 +3513,29 @@ var attachTmuxTarget = (target) => {
3387
3513
  });
3388
3514
  });
3389
3515
  };
3516
+ var normalizeBotBindPlatform = (value) => {
3517
+ const normalized = value.trim().toLowerCase();
3518
+ return normalized === "telegram" || normalized === "weixin" || normalized === "wecom" ? normalized : null;
3519
+ };
3520
+ var resolveBotBindRuntimeId = (explicitRuntimeId) => {
3521
+ const explicit = explicitRuntimeId?.trim();
3522
+ if (explicit) {
3523
+ return explicit;
3524
+ }
3525
+ const fromEnv = process.env.CCEM_RUNTIME_ID?.trim() || process.env.CCEM_SESSION_ID?.trim();
3526
+ if (fromEnv) {
3527
+ return fromEnv;
3528
+ }
3529
+ const sessions = readInteractiveAttachSessions();
3530
+ return sessions.length === 1 ? sessions[0].id : null;
3531
+ };
3532
+ var appendBotBindRequest = (payload) => {
3533
+ const requestPath = getBotBindRequestFilePath();
3534
+ fs10.mkdirSync(path8.dirname(requestPath), { recursive: true });
3535
+ fs10.appendFileSync(requestPath, `${JSON.stringify(payload)}
3536
+ `, "utf-8");
3537
+ return requestPath;
3538
+ };
3390
3539
  program.command("ls").description("List all configured environments").action(() => {
3391
3540
  const registries = getRegistries();
3392
3541
  const current = config2.get("current");
@@ -3442,6 +3591,57 @@ program.command("attach [id]").description("Attach to a tmux-backed interactive
3442
3591
  process.exit(1);
3443
3592
  }
3444
3593
  });
3594
+ program.command("bot-bind").description("Request binding the current ccem session to a chat bot target").requiredOption("--platform <platform>", "target platform: telegram, weixin, or wecom").option("--peer-id <peerId>", "target user/chat id").option("--peer <peerId>", "alias for --peer-id").option("--runtime-id <runtimeId>", "ccem runtime id; defaults to CCEM_RUNTIME_ID").option("--bot-id <botId>", "WeCom bot id or platform bot id").option("--thread-id <threadId>", "thread/topic id for platforms that support it").option("--title <title>", "task card title").option("--summary <summary>", "task card summary").option("--no-send-card", "bind without sending the initial task card").option("--json", "print the queued request as JSON").action((options) => {
3595
+ const platform = normalizeBotBindPlatform(options.platform);
3596
+ if (!platform) {
3597
+ console.error(chalk7.red("\u2717 --platform must be telegram, weixin, or wecom"));
3598
+ process.exitCode = 1;
3599
+ return;
3600
+ }
3601
+ const peerId = (options.peerId ?? options.peer ?? "").trim();
3602
+ if (!peerId) {
3603
+ console.error(chalk7.red("\u2717 --peer-id is required"));
3604
+ process.exitCode = 1;
3605
+ return;
3606
+ }
3607
+ const runtimeId = resolveBotBindRuntimeId(options.runtimeId);
3608
+ if (!runtimeId) {
3609
+ console.error(chalk7.red("\u2717 Unable to resolve ccem runtime id"));
3610
+ console.error(chalk7.gray(" Run from a ccem-launched session or pass --runtime-id explicitly."));
3611
+ process.exitCode = 1;
3612
+ return;
3613
+ }
3614
+ const botId = options.botId?.trim() || void 0;
3615
+ if (platform === "wecom" && !botId) {
3616
+ console.error(chalk7.red("\u2717 --bot-id is required for WeCom bot bindings"));
3617
+ process.exitCode = 1;
3618
+ return;
3619
+ }
3620
+ const payload = {
3621
+ request_id: `botbind-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
3622
+ runtime_id: runtimeId,
3623
+ platform,
3624
+ peer_id: peerId,
3625
+ bot_id: botId,
3626
+ thread_id: options.threadId?.trim() || void 0,
3627
+ task_title: options.title?.trim() || void 0,
3628
+ task_summary: options.summary?.trim() || void 0,
3629
+ send_task_card: options.sendCard !== false,
3630
+ created_at: (/* @__PURE__ */ new Date()).toISOString()
3631
+ };
3632
+ const requestPath = appendBotBindRequest(payload);
3633
+ if (options.json) {
3634
+ console.log(JSON.stringify({ requestPath, payload }, null, 2));
3635
+ return;
3636
+ }
3637
+ console.log(chalk7.green("\u2713 \u5DF2\u63D0\u4EA4 bot \u7ED1\u5B9A\u8BF7\u6C42"));
3638
+ console.log(chalk7.gray(` runtime: ${runtimeId}`));
3639
+ console.log(chalk7.gray(` target: ${platform}/${peerId}`));
3640
+ console.log(chalk7.gray(` queue: ${requestPath}`));
3641
+ console.log(chalk7.cyan(
3642
+ options.sendCard === false ? " \u5982\u679C CCEM Desktop \u6B63\u5728\u8FD0\u884C\uFF0C\u4F1A\u81EA\u52A8\u6D88\u8D39\u8BE5\u8BF7\u6C42\u5E76\u5EFA\u7ACB\u7ED1\u5B9A\u3002" : " \u5982\u679C CCEM Desktop \u6B63\u5728\u8FD0\u884C\uFF0C\u4F1A\u81EA\u52A8\u6D88\u8D39\u8BE5\u8BF7\u6C42\u5E76\u53D1\u9001\u4EFB\u52A1\u5361\u7247\u5230\u7ED1\u5B9A\u76EE\u6807\u3002"
3643
+ ));
3644
+ });
3445
3645
  program.command("add <name>").description("Add a new environment configuration").action(async (name) => {
3446
3646
  const registries = getRegistries();
3447
3647
  if (registries[name]) {
@@ -3606,7 +3806,7 @@ cronCmd.command("list").description("List CCEM cron tasks").option("--json", "Ou
3606
3806
  process.exitCode = 1;
3607
3807
  }
3608
3808
  });
3609
- cronCmd.command("create").description("Create a CCEM cron task").option("--from-json <json>", "Read task input from inline JSON, @file, or - for stdin").option("--name <name>", "Task name").option("--cron-expression <expr>", "5-field cron expression").option("--schedule <expr>", "Alias for --cron-expression").option("--prompt <prompt>", "Prompt to run when the task fires").option("--working-dir <dir>", "Task working directory").option("--env-name <name>", "CCEM environment name").option("--execution-profile <profile>", "conservative, standard, or autonomous").option("--max-budget-usd <amount>", "Optional max budget in USD").option("--allowed-tools <items>", "Comma-separated or JSON array of allowed tools").option("--disallowed-tools <items>", "Comma-separated or JSON array of disallowed tools").option("--timeout-secs <seconds>", "Task timeout in seconds").option("--template-id <id>", "Optional template id").option("--disabled", "Create task disabled").option("--json", "Output as JSON").action((options) => {
3809
+ cronCmd.command("create").description("Create a CCEM cron task").option("--from-json <json>", "Read task input from inline JSON, @file, or - for stdin").option("--name <name>", "Task name").option("--cron-expression <expr>", "5-field cron expression").option("--schedule <expr>", "Alias for --cron-expression").option("--prompt <prompt>", "Prompt to run when the task fires").option("--working-dir <dir>", "Task working directory").option("--env-name <name>", "CCEM environment name").option("--execution-profile <profile>", "conservative, standard, or autonomous").option("--max-budget-usd <amount>", "Optional max budget in USD").option("--allowed-tools <items>", "Comma-separated or JSON array of allowed tools").option("--disallowed-tools <items>", "Comma-separated or JSON array of disallowed tools").option("--timeout-secs <seconds>", "Task timeout in seconds").option("--template-id <id>", "Optional template id").option("--wecom-result", "Send cron result summary to the configured WeCom default target").option("--wecom-bot-id <id>", "WeCom bot id for cron result notifications").option("--wecom-peer-id <id>", "WeCom peer id for cron result notifications").option("--disabled", "Create task disabled").option("--json", "Output as JSON").action((options) => {
3610
3810
  try {
3611
3811
  const input = options.fromJson ? parseCronCreateJson(options.fromJson) : {
3612
3812
  name: options.name,
@@ -3620,7 +3820,12 @@ cronCmd.command("create").description("Create a CCEM cron task").option("--from-
3620
3820
  disallowedTools: parseStringList(options.disallowedTools),
3621
3821
  enabled: options.disabled ? false : true,
3622
3822
  timeoutSecs: options.timeoutSecs === void 0 ? null : Number(options.timeoutSecs),
3623
- templateId: options.templateId
3823
+ templateId: options.templateId,
3824
+ wecomNotification: options.wecomResult || options.wecomBotId || options.wecomPeerId ? {
3825
+ botId: options.wecomBotId ?? null,
3826
+ peerId: options.wecomPeerId ?? null,
3827
+ enabled: true
3828
+ } : null
3624
3829
  };
3625
3830
  const task = createCronTask(input);
3626
3831
  if (options.json) {
@@ -3760,6 +3965,34 @@ setupCmd.command("cron").description("\u5B89\u88C5 ccem-cron skill \u5230 Claude
3760
3965
  console.log(chalk7.cyan(`
3761
3966
  \u5728 Claude Code \u4E2D\u4F7F\u7528 /ccem-cron \u5373\u53EF\u8C03\u7528\u6B64 skill`));
3762
3967
  });
3968
+ setupCmd.command("bot-bind").description("\u5B89\u88C5 ccem-bot-bind skill \u5230 Claude Code\uFF08~/.claude/skills/\uFF09").option("--force", "\u5F3A\u5236\u8986\u76D6\u5DF2\u6709\u6587\u4EF6").action(async function() {
3969
+ const options = this.opts();
3970
+ const skillDir = path8.join(getHomeDir(), ".claude", "skills");
3971
+ const targetPath = path8.join(skillDir, "ccem-bot-bind.md");
3972
+ if (!fs10.existsSync(skillDir)) {
3973
+ fs10.mkdirSync(skillDir, { recursive: true });
3974
+ console.log(chalk7.gray(`\u521B\u5EFA\u76EE\u5F55: ${skillDir}`));
3975
+ }
3976
+ if (fs10.existsSync(targetPath) && !options.force) {
3977
+ const { overwrite } = await inquirer.prompt([
3978
+ {
3979
+ type: "confirm",
3980
+ name: "overwrite",
3981
+ message: `${targetPath} \u5DF2\u5B58\u5728\uFF0C\u662F\u5426\u8986\u76D6\uFF1F`,
3982
+ default: false
3983
+ }
3984
+ ]);
3985
+ if (!overwrite) {
3986
+ console.log(chalk7.yellow("\u5DF2\u53D6\u6D88"));
3987
+ return;
3988
+ }
3989
+ }
3990
+ fs10.writeFileSync(targetPath, CCEM_BOT_BIND_SKILL_CONTENT, "utf-8");
3991
+ console.log(chalk7.green(`\u2713 \u5DF2\u5B89\u88C5 ccem-bot-bind skill`));
3992
+ console.log(chalk7.gray(` \u8DEF\u5F84: ${targetPath}`));
3993
+ console.log(chalk7.cyan(`
3994
+ \u5728 Claude Code \u4E2D\u8BA9\u5B83\u4F7F\u7528 ccem-bot-bind skill \u5373\u53EF\u7ED1\u5B9A\u5F53\u524D\u4F1A\u8BDD\u5230 bot`));
3995
+ });
3763
3996
  var skillCmd = program.command("skill").description("\u7BA1\u7406 Claude Code skills");
3764
3997
  skillCmd.command("add [url]").description("\u6DFB\u52A0 skill\uFF08\u4ECE\u5B98\u65B9\u9884\u8BBE\u6216 GitHub URL\uFF09").action(async (url) => {
3765
3998
  if (url) {
@@ -3810,8 +4043,8 @@ skillCmd.command("ls").description("\u5217\u51FA\u5DF2\u5B89\u88C5\u7684 skills"
3810
4043
  skillCmd.command("rm <name>").description("\u5220\u9664\u5DF2\u5B89\u88C5\u7684 skill").action((name) => {
3811
4044
  removeSkill(name);
3812
4045
  });
3813
- program.command("load <url>").description("\u4ECE\u8FDC\u7A0B\u670D\u52A1\u5668\u52A0\u8F7D\u73AF\u5883\u914D\u7F6E").requiredOption("--secret <secret>", "\u89E3\u5BC6\u5BC6\u94A5").option("--json", "\u4EE5 JSON \u683C\u5F0F\u8F93\u51FA\u7ED3\u679C\uFF08\u4F9B\u7A0B\u5E8F\u8C03\u7528\uFF09").action(async (url, options) => {
3814
- const results = await loadFromRemote(url, options.secret);
4046
+ program.command("load <url>").description("\u4ECE\u8FDC\u7A0B\u670D\u52A1\u5668\u52A0\u8F7D\u73AF\u5883\u914D\u7F6E").requiredOption("--secret <secret>", "\u89E3\u5BC6\u5BC6\u94A5\uFF08encryption secret\uFF09").option("--key <key>", "\u8BBF\u95EE\u5BC6\u94A5\uFF08access key\uFF0C\u7528\u4E8E\u8BA4\u8BC1\uFF1B\u4E0D\u586B\u5219\u56DE\u9000\u7528 --secret \u8BA4\u8BC1\uFF09").option("--json", "\u4EE5 JSON \u683C\u5F0F\u8F93\u51FA\u7ED3\u679C\uFF08\u4F9B\u7A0B\u5E8F\u8C03\u7528\uFF09").action(async (url, options) => {
4047
+ const results = await loadFromRemote(url, options.key ?? "", options.secret);
3815
4048
  if (options.json) {
3816
4049
  console.log(JSON.stringify({
3817
4050
  count: results.length,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ccem",
3
- "version": "2.25.0",
3
+ "version": "2.27.0",
4
4
  "type": "module",
5
5
  "description": "Claude Code Environment Manager",
6
6
  "repository": {