ccem 2.26.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 +80 -14
  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;
@@ -2691,13 +2724,17 @@ var getUniqueName = (baseName, existingNames) => {
2691
2724
  }
2692
2725
  return newName;
2693
2726
  };
2694
- var loadFromRemote = async (url, secret) => {
2727
+ var loadFromRemote = async (url, key, secret) => {
2695
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
+ }
2696
2733
  let response;
2697
2734
  try {
2698
2735
  response = await fetch(url, {
2699
2736
  headers: {
2700
- "X-CCEM-Key": secret
2737
+ "X-CCEM-Key": headerKey
2701
2738
  }
2702
2739
  });
2703
2740
  } catch (err) {
@@ -3003,6 +3040,27 @@ function parseStringList(value) {
3003
3040
  }
3004
3041
  return trimmed.split(",").map((item) => item.trim()).filter(Boolean);
3005
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
+ }
3006
3064
  function createCronTask(input, tasksPath = getCronTasksPath()) {
3007
3065
  const name = input.name?.trim();
3008
3066
  if (!name) {
@@ -3040,6 +3098,7 @@ function createCronTask(input, tasksPath = getCronTasksPath()) {
3040
3098
  enabled: input.enabled ?? true,
3041
3099
  timeoutSecs,
3042
3100
  templateId: input.templateId?.trim() || null,
3101
+ wecomNotification: normalizeWecomNotification(input.wecomNotification),
3043
3102
  triggerType: "schedule",
3044
3103
  parentTaskId: null,
3045
3104
  createdAt: now,
@@ -3078,6 +3137,7 @@ function resolveJsonInput(raw) {
3078
3137
  }
3079
3138
  function parseCronCreateJson(raw) {
3080
3139
  const parsed = JSON.parse(resolveJsonInput(raw));
3140
+ const wecomNotification = parsed.wecomNotification ?? parsed.wecom_notification;
3081
3141
  return {
3082
3142
  name: String(parsed.name ?? ""),
3083
3143
  cronExpression: String(parsed.cronExpression ?? parsed.schedule ?? ""),
@@ -3090,7 +3150,8 @@ function parseCronCreateJson(raw) {
3090
3150
  disallowedTools: Array.isArray(parsed.disallowedTools) ? parsed.disallowedTools.map(String) : null,
3091
3151
  enabled: typeof parsed.enabled === "boolean" ? parsed.enabled : null,
3092
3152
  timeoutSecs: typeof parsed.timeoutSecs === "number" ? parsed.timeoutSecs : null,
3093
- templateId: typeof parsed.templateId === "string" ? parsed.templateId : null
3153
+ templateId: typeof parsed.templateId === "string" ? parsed.templateId : null,
3154
+ wecomNotification: parseWecomNotification(wecomNotification)
3094
3155
  };
3095
3156
  }
3096
3157
  function formatCronTaskTableRows(tasks) {
@@ -3745,7 +3806,7 @@ cronCmd.command("list").description("List CCEM cron tasks").option("--json", "Ou
3745
3806
  process.exitCode = 1;
3746
3807
  }
3747
3808
  });
3748
- 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) => {
3749
3810
  try {
3750
3811
  const input = options.fromJson ? parseCronCreateJson(options.fromJson) : {
3751
3812
  name: options.name,
@@ -3759,7 +3820,12 @@ cronCmd.command("create").description("Create a CCEM cron task").option("--from-
3759
3820
  disallowedTools: parseStringList(options.disallowedTools),
3760
3821
  enabled: options.disabled ? false : true,
3761
3822
  timeoutSecs: options.timeoutSecs === void 0 ? null : Number(options.timeoutSecs),
3762
- 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
3763
3829
  };
3764
3830
  const task = createCronTask(input);
3765
3831
  if (options.json) {
@@ -3977,8 +4043,8 @@ skillCmd.command("ls").description("\u5217\u51FA\u5DF2\u5B89\u88C5\u7684 skills"
3977
4043
  skillCmd.command("rm <name>").description("\u5220\u9664\u5DF2\u5B89\u88C5\u7684 skill").action((name) => {
3978
4044
  removeSkill(name);
3979
4045
  });
3980
- 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) => {
3981
- 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);
3982
4048
  if (options.json) {
3983
4049
  console.log(JSON.stringify({
3984
4050
  count: results.length,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ccem",
3
- "version": "2.26.0",
3
+ "version": "2.27.0",
4
4
  "type": "module",
5
5
  "description": "Claude Code Environment Manager",
6
6
  "repository": {