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.
- package/dist/index.js +248 -15
- 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;
|
|
@@ -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":
|
|
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,
|