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.
- package/dist/index.js +80 -14
- 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
|
|
404
|
-
var
|
|
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
|
|
408
|
-
const
|
|
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
|
-
|
|
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(
|
|
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
|
|
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":
|
|
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,
|