ccem 2.26.0 → 2.28.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 +249 -48
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -7,8 +7,8 @@ import inquirer from "inquirer";
7
7
  import chalk7 from "chalk";
8
8
  import Table3 from "cli-table3";
9
9
  import { spawn as spawn3 } from "child_process";
10
- import * as fs10 from "fs";
11
- import * as path8 from "path";
10
+ import * as fs11 from "fs";
11
+ import * as path9 from "path";
12
12
  import { fileURLToPath as fileURLToPath2 } from "url";
13
13
 
14
14
  // ../../packages/core/dist/chunk-DFS6BUXP.js
@@ -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;
@@ -946,10 +979,10 @@ var renderLogoWithEnvPanel = (envName, env, defaultMode) => {
946
979
  const parsed = new URL(url);
947
980
  const protocol = parsed.protocol + "//";
948
981
  const host = parsed.host;
949
- const path9 = parsed.pathname + parsed.search;
982
+ const path10 = parsed.pathname + parsed.search;
950
983
  const hostStart = host.slice(0, 8);
951
984
  const hostEnd = host.slice(-4);
952
- const pathPart = path9.length > 10 ? path9.slice(0, 7) + "..." : path9;
985
+ const pathPart = path10.length > 10 ? path10.slice(0, 7) + "..." : path10;
953
986
  return `${protocol}${hostStart}...${hostEnd}${pathPart}`;
954
987
  } catch {
955
988
  return truncate(url, max);
@@ -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) {
@@ -3104,11 +3165,61 @@ function formatCronTaskTableRows(tasks) {
3104
3165
  ]);
3105
3166
  }
3106
3167
 
3168
+ // src/desktopControl.ts
3169
+ import fs10 from "fs";
3170
+ import path8 from "path";
3171
+ function getDesktopControlDescriptorPath() {
3172
+ return process.env.CCEM_CONTROL_FILE?.trim() || path8.join(getCcemConfigDir(), "control.json");
3173
+ }
3174
+ function resolveDesktopControlDescriptor(descriptorPath = getDesktopControlDescriptorPath()) {
3175
+ if (!fs10.existsSync(descriptorPath)) {
3176
+ throw new Error(`CCEM Desktop control endpoint not found at ${descriptorPath}. Start CCEM Desktop first.`);
3177
+ }
3178
+ const parsed = JSON.parse(fs10.readFileSync(descriptorPath, "utf-8"));
3179
+ const endpoint = parsed.endpoint?.trim();
3180
+ const token = parsed.token?.trim();
3181
+ if (!endpoint || !token) {
3182
+ throw new Error(`Invalid CCEM Desktop control descriptor at ${descriptorPath}`);
3183
+ }
3184
+ return {
3185
+ endpoint,
3186
+ token,
3187
+ pid: typeof parsed.pid === "number" ? parsed.pid : null
3188
+ };
3189
+ }
3190
+ async function requestDesktopControl(method, params, descriptor = resolveDesktopControlDescriptor()) {
3191
+ const id = `ccem-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
3192
+ const response = await fetch(descriptor.endpoint, {
3193
+ method: "POST",
3194
+ headers: {
3195
+ authorization: `Bearer ${descriptor.token}`,
3196
+ "content-type": "application/json"
3197
+ },
3198
+ body: JSON.stringify({
3199
+ jsonrpc: "2.0",
3200
+ id,
3201
+ method,
3202
+ params: params ?? {}
3203
+ })
3204
+ });
3205
+ if (!response.ok) {
3206
+ throw new Error(`CCEM Desktop control request failed: HTTP ${response.status}`);
3207
+ }
3208
+ const payload = await response.json();
3209
+ if (payload.error) {
3210
+ throw new Error(payload.error.message || `CCEM Desktop control error ${payload.error.code ?? ""}`.trim());
3211
+ }
3212
+ return payload.result;
3213
+ }
3214
+ function printJson(value) {
3215
+ console.log(JSON.stringify(value, null, 2));
3216
+ }
3217
+
3107
3218
  // src/index.ts
3108
3219
  var __filename2 = fileURLToPath2(import.meta.url);
3109
- var __dirname2 = path8.dirname(__filename2);
3110
- var pkgPath = path8.resolve(__dirname2, "..", "package.json");
3111
- var pkg = JSON.parse(fs10.readFileSync(pkgPath, "utf-8"));
3220
+ var __dirname2 = path9.dirname(__filename2);
3221
+ var pkgPath = path9.resolve(__dirname2, "..", "package.json");
3222
+ var pkg = JSON.parse(fs11.readFileSync(pkgPath, "utf-8"));
3112
3223
  var program = new Command();
3113
3224
  var DEFAULT_OFFICIAL_ENV = {
3114
3225
  ANTHROPIC_BASE_URL: "https://api.anthropic.com",
@@ -3164,6 +3275,29 @@ var config2 = new Conf2({
3164
3275
  defaultMode: null
3165
3276
  }
3166
3277
  });
3278
+ function outputDesktopResult(value, options) {
3279
+ if (options.json) {
3280
+ printJson(value);
3281
+ return;
3282
+ }
3283
+ console.log(JSON.stringify(value, null, 2));
3284
+ }
3285
+ function parseOptionalBoolean(value) {
3286
+ if (value === void 0 || value === null) {
3287
+ return void 0;
3288
+ }
3289
+ if (typeof value === "boolean") {
3290
+ return value;
3291
+ }
3292
+ const normalized = String(value).trim().toLowerCase();
3293
+ if (["1", "true", "yes", "y"].includes(normalized)) {
3294
+ return true;
3295
+ }
3296
+ if (["0", "false", "no", "n"].includes(normalized)) {
3297
+ return false;
3298
+ }
3299
+ return void 0;
3300
+ }
3167
3301
  var recoverRegistriesFromLegacy = (registries) => {
3168
3302
  const currentAuthCount = Object.values(registries).filter(
3169
3303
  (env) => Boolean(env.ANTHROPIC_AUTH_TOKEN)
@@ -3172,11 +3306,11 @@ var recoverRegistriesFromLegacy = (registries) => {
3172
3306
  return registries;
3173
3307
  }
3174
3308
  const legacyConfigPath = getLegacyConfigPath();
3175
- if (!fs10.existsSync(legacyConfigPath)) {
3309
+ if (!fs11.existsSync(legacyConfigPath)) {
3176
3310
  return registries;
3177
3311
  }
3178
3312
  try {
3179
- const legacyRaw = JSON.parse(fs10.readFileSync(legacyConfigPath, "utf-8"));
3313
+ const legacyRaw = JSON.parse(fs11.readFileSync(legacyConfigPath, "utf-8"));
3180
3314
  const legacyRegistries = legacyRaw.registries ?? {};
3181
3315
  let changed = false;
3182
3316
  const recovered = { ...registries };
@@ -3370,15 +3504,15 @@ var switchEnvironment = async (name) => {
3370
3504
  exportCmds.forEach((cmd) => console.log(cmd));
3371
3505
  }
3372
3506
  };
3373
- var getSessionsFilePath = () => path8.join(getCcemConfigDir(), "sessions.json");
3374
- var getRuntimeStateFilePath = () => path8.join(getCcemConfigDir(), "runtime-state.json");
3375
- var getBotBindRequestFilePath = () => path8.join(getCcemConfigDir(), "bot-bind-requests.jsonl");
3507
+ var getSessionsFilePath = () => path9.join(getCcemConfigDir(), "sessions.json");
3508
+ var getRuntimeStateFilePath = () => path9.join(getCcemConfigDir(), "runtime-state.json");
3509
+ var getBotBindRequestFilePath = () => path9.join(getCcemConfigDir(), "bot-bind-requests.jsonl");
3376
3510
  var parseJsonFile = (filePath) => {
3377
- if (!fs10.existsSync(filePath)) {
3511
+ if (!fs11.existsSync(filePath)) {
3378
3512
  return null;
3379
3513
  }
3380
3514
  try {
3381
- return JSON.parse(fs10.readFileSync(filePath, "utf-8"));
3515
+ return JSON.parse(fs11.readFileSync(filePath, "utf-8"));
3382
3516
  } catch {
3383
3517
  return null;
3384
3518
  }
@@ -3470,8 +3604,8 @@ var resolveBotBindRuntimeId = (explicitRuntimeId) => {
3470
3604
  };
3471
3605
  var appendBotBindRequest = (payload) => {
3472
3606
  const requestPath = getBotBindRequestFilePath();
3473
- fs10.mkdirSync(path8.dirname(requestPath), { recursive: true });
3474
- fs10.appendFileSync(requestPath, `${JSON.stringify(payload)}
3607
+ fs11.mkdirSync(path9.dirname(requestPath), { recursive: true });
3608
+ fs11.appendFileSync(requestPath, `${JSON.stringify(payload)}
3475
3609
  `, "utf-8");
3476
3610
  return requestPath;
3477
3611
  };
@@ -3745,7 +3879,7 @@ cronCmd.command("list").description("List CCEM cron tasks").option("--json", "Ou
3745
3879
  process.exitCode = 1;
3746
3880
  }
3747
3881
  });
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) => {
3882
+ 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
3883
  try {
3750
3884
  const input = options.fromJson ? parseCronCreateJson(options.fromJson) : {
3751
3885
  name: options.name,
@@ -3759,7 +3893,12 @@ cronCmd.command("create").description("Create a CCEM cron task").option("--from-
3759
3893
  disallowedTools: parseStringList(options.disallowedTools),
3760
3894
  enabled: options.disabled ? false : true,
3761
3895
  timeoutSecs: options.timeoutSecs === void 0 ? null : Number(options.timeoutSecs),
3762
- templateId: options.templateId
3896
+ templateId: options.templateId,
3897
+ wecomNotification: options.wecomResult || options.wecomBotId || options.wecomPeerId ? {
3898
+ botId: options.wecomBotId ?? null,
3899
+ peerId: options.wecomPeerId ?? null,
3900
+ enabled: true
3901
+ } : null
3763
3902
  };
3764
3903
  const task = createCronTask(input);
3765
3904
  if (options.json) {
@@ -3841,12 +3980,12 @@ setupCmd.command("migrate").description("\u8FC1\u79FB\u65E7\u7248\u914D\u7F6E\u5
3841
3980
  const newConfigPath = getCcemConfigPath();
3842
3981
  const legacyConfigPath = getLegacyConfigPath();
3843
3982
  console.log(chalk7.cyan("\n\u{1F504} \u914D\u7F6E\u8FC1\u79FB\n"));
3844
- if (!fs10.existsSync(legacyConfigPath)) {
3983
+ if (!fs11.existsSync(legacyConfigPath)) {
3845
3984
  console.log(chalk7.yellow("\u672A\u627E\u5230\u65E7\u7248\u914D\u7F6E\u6587\u4EF6"));
3846
3985
  console.log(chalk7.gray(` \u65E7\u8DEF\u5F84: ${legacyConfigPath}`));
3847
3986
  return;
3848
3987
  }
3849
- if (fs10.existsSync(newConfigPath) && !options.force) {
3988
+ if (fs11.existsSync(newConfigPath) && !options.force) {
3850
3989
  console.log(chalk7.green("\u2713 \u914D\u7F6E\u5DF2\u5728\u65B0\u8DEF\u5F84"));
3851
3990
  console.log(chalk7.gray(` \u8DEF\u5F84: ${newConfigPath}`));
3852
3991
  console.log(chalk7.gray("\n\u4F7F\u7528 --force \u5F3A\u5236\u91CD\u65B0\u8FC1\u79FB"));
@@ -3854,15 +3993,15 @@ setupCmd.command("migrate").description("\u8FC1\u79FB\u65E7\u7248\u914D\u7F6E\u5
3854
3993
  }
3855
3994
  try {
3856
3995
  ensureCcemDir();
3857
- fs10.copyFileSync(legacyConfigPath, newConfigPath);
3996
+ fs11.copyFileSync(legacyConfigPath, newConfigPath);
3858
3997
  console.log(chalk7.green("\u2713 \u914D\u7F6E\u5DF2\u8FC1\u79FB"));
3859
3998
  console.log(chalk7.gray(` \u4ECE: ${legacyConfigPath}`));
3860
3999
  console.log(chalk7.gray(` \u5230: ${newConfigPath}`));
3861
4000
  if (options.clean) {
3862
- fs10.unlinkSync(legacyConfigPath);
3863
- const legacyDir = path8.dirname(legacyConfigPath);
4001
+ fs11.unlinkSync(legacyConfigPath);
4002
+ const legacyDir = path9.dirname(legacyConfigPath);
3864
4003
  try {
3865
- fs10.rmdirSync(legacyDir);
4004
+ fs11.rmdirSync(legacyDir);
3866
4005
  } catch {
3867
4006
  }
3868
4007
  console.log(chalk7.green("\u2713 \u5DF2\u5220\u9664\u65E7\u914D\u7F6E\u6587\u4EF6"));
@@ -3873,13 +4012,13 @@ setupCmd.command("migrate").description("\u8FC1\u79FB\u65E7\u7248\u914D\u7F6E\u5
3873
4012
  });
3874
4013
  setupCmd.command("cron").description("\u5B89\u88C5 ccem-cron skill \u5230 Claude Code\uFF08~/.claude/skills/\uFF09").option("--force", "\u5F3A\u5236\u8986\u76D6\u5DF2\u6709\u6587\u4EF6").action(async function() {
3875
4014
  const options = this.opts();
3876
- const skillDir = path8.join(getHomeDir(), ".claude", "skills");
3877
- const targetPath = path8.join(skillDir, "ccem-cron.md");
3878
- if (!fs10.existsSync(skillDir)) {
3879
- fs10.mkdirSync(skillDir, { recursive: true });
4015
+ const skillDir = path9.join(getHomeDir(), ".claude", "skills");
4016
+ const targetPath = path9.join(skillDir, "ccem-cron.md");
4017
+ if (!fs11.existsSync(skillDir)) {
4018
+ fs11.mkdirSync(skillDir, { recursive: true });
3880
4019
  console.log(chalk7.gray(`\u521B\u5EFA\u76EE\u5F55: ${skillDir}`));
3881
4020
  }
3882
- if (fs10.existsSync(targetPath) && !options.force) {
4021
+ if (fs11.existsSync(targetPath) && !options.force) {
3883
4022
  const { overwrite } = await inquirer.prompt([
3884
4023
  {
3885
4024
  type: "confirm",
@@ -3893,7 +4032,7 @@ setupCmd.command("cron").description("\u5B89\u88C5 ccem-cron skill \u5230 Claude
3893
4032
  return;
3894
4033
  }
3895
4034
  }
3896
- fs10.writeFileSync(targetPath, CCEM_CRON_SKILL_CONTENT, "utf-8");
4035
+ fs11.writeFileSync(targetPath, CCEM_CRON_SKILL_CONTENT, "utf-8");
3897
4036
  console.log(chalk7.green(`\u2713 \u5DF2\u5B89\u88C5 ccem-cron skill`));
3898
4037
  console.log(chalk7.gray(` \u8DEF\u5F84: ${targetPath}`));
3899
4038
  console.log(chalk7.cyan(`
@@ -3901,13 +4040,13 @@ setupCmd.command("cron").description("\u5B89\u88C5 ccem-cron skill \u5230 Claude
3901
4040
  });
3902
4041
  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() {
3903
4042
  const options = this.opts();
3904
- const skillDir = path8.join(getHomeDir(), ".claude", "skills");
3905
- const targetPath = path8.join(skillDir, "ccem-bot-bind.md");
3906
- if (!fs10.existsSync(skillDir)) {
3907
- fs10.mkdirSync(skillDir, { recursive: true });
4043
+ const skillDir = path9.join(getHomeDir(), ".claude", "skills");
4044
+ const targetPath = path9.join(skillDir, "ccem-bot-bind.md");
4045
+ if (!fs11.existsSync(skillDir)) {
4046
+ fs11.mkdirSync(skillDir, { recursive: true });
3908
4047
  console.log(chalk7.gray(`\u521B\u5EFA\u76EE\u5F55: ${skillDir}`));
3909
4048
  }
3910
- if (fs10.existsSync(targetPath) && !options.force) {
4049
+ if (fs11.existsSync(targetPath) && !options.force) {
3911
4050
  const { overwrite } = await inquirer.prompt([
3912
4051
  {
3913
4052
  type: "confirm",
@@ -3921,7 +4060,7 @@ setupCmd.command("bot-bind").description("\u5B89\u88C5 ccem-bot-bind skill \u523
3921
4060
  return;
3922
4061
  }
3923
4062
  }
3924
- fs10.writeFileSync(targetPath, CCEM_BOT_BIND_SKILL_CONTENT, "utf-8");
4063
+ fs11.writeFileSync(targetPath, CCEM_BOT_BIND_SKILL_CONTENT, "utf-8");
3925
4064
  console.log(chalk7.green(`\u2713 \u5DF2\u5B89\u88C5 ccem-bot-bind skill`));
3926
4065
  console.log(chalk7.gray(` \u8DEF\u5F84: ${targetPath}`));
3927
4066
  console.log(chalk7.cyan(`
@@ -3977,8 +4116,8 @@ skillCmd.command("ls").description("\u5217\u51FA\u5DF2\u5B89\u88C5\u7684 skills"
3977
4116
  skillCmd.command("rm <name>").description("\u5220\u9664\u5DF2\u5B89\u88C5\u7684 skill").action((name) => {
3978
4117
  removeSkill(name);
3979
4118
  });
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);
4119
+ 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) => {
4120
+ const results = await loadFromRemote(url, options.key ?? "", options.secret);
3982
4121
  if (options.json) {
3983
4122
  console.log(JSON.stringify({
3984
4123
  count: results.length,
@@ -4012,6 +4151,68 @@ program.command("launch").description(false).option("--env <name>", "\u73AF\u588
4012
4151
  silent: true
4013
4152
  });
4014
4153
  });
4154
+ var desktopCmd = program.command("desktop").description("Control the running CCEM Desktop app");
4155
+ desktopCmd.command("health").description("Check the running CCEM Desktop control endpoint").option("--json", "Output JSON").action(async function() {
4156
+ const opts = this.opts();
4157
+ const result = await requestDesktopControl("ccem.health");
4158
+ outputDesktopResult(result, opts);
4159
+ });
4160
+ desktopCmd.command("create").description("Create a workspace session in the running CCEM Desktop app").requiredOption("--provider <provider>", "Agent provider: claude or codex").requiredOption("--cwd <path>", "Working directory").requiredOption("--prompt <text>", "Initial prompt").option("--env <name>", "Environment name").option("--perm <mode>", "Permission mode").option("--runtime-perm <mode>", "Runtime permission mode").option("--provider-session-id <id>", "Provider session id to continue").option("--effort <level>", "Codex effort level").option("--open <value>", "Open the created session in Desktop (true/false)").option("--json", "Output JSON").action(async function() {
4161
+ const opts = this.opts();
4162
+ const provider = String(opts.provider).trim().toLowerCase();
4163
+ if (provider !== "claude" && provider !== "codex") {
4164
+ throw new Error("Unsupported provider. Use 'claude' or 'codex'.");
4165
+ }
4166
+ const result = await requestDesktopControl("ccem.workspace.createSession", {
4167
+ provider,
4168
+ cwd: opts.cwd,
4169
+ prompt: opts.prompt,
4170
+ envName: opts.env ?? null,
4171
+ permissionMode: opts.perm ?? null,
4172
+ runtimePermissionMode: opts.runtimePerm ?? null,
4173
+ providerSessionId: opts.providerSessionId ?? null,
4174
+ effort: opts.effort ?? null,
4175
+ open: parseOptionalBoolean(opts.open)
4176
+ });
4177
+ outputDesktopResult(result, opts);
4178
+ });
4179
+ desktopCmd.command("sessions").description("List CCEM Desktop workspace sessions").option("--cwd <path>", "Filter by working directory").option("--provider <provider>", "Filter by provider").option("--status <status>", "Filter by status").option("--json", "Output JSON").action(async function() {
4180
+ const opts = this.opts();
4181
+ const result = await requestDesktopControl("ccem.workspace.listSessions", {
4182
+ cwd: opts.cwd ?? null,
4183
+ provider: opts.provider ?? null,
4184
+ status: opts.status ?? null
4185
+ });
4186
+ outputDesktopResult(result, opts);
4187
+ });
4188
+ desktopCmd.command("status <runtimeId>").description("Get one CCEM Desktop workspace session").option("--json", "Output JSON").action(async function(runtimeId) {
4189
+ const opts = this.opts();
4190
+ const result = await requestDesktopControl("ccem.workspace.getSession", { runtimeId });
4191
+ outputDesktopResult(result, opts);
4192
+ });
4193
+ desktopCmd.command("events <runtimeId>").description("Read CCEM Desktop workspace session events").option("--since <seq>", "First event sequence after this value").option("--limit <count>", "Maximum events to return").option("--json", "Output JSON").action(async function(runtimeId) {
4194
+ const opts = this.opts();
4195
+ const result = await requestDesktopControl("ccem.workspace.getEvents", {
4196
+ runtimeId,
4197
+ sinceSeq: opts.since ? Number(opts.since) : null,
4198
+ limit: opts.limit ? Number(opts.limit) : null
4199
+ });
4200
+ outputDesktopResult(result, opts);
4201
+ });
4202
+ desktopCmd.command("send <runtimeId>").description("Send input to a CCEM Desktop workspace session").requiredOption("--text <text>", "Text to send").option("--display-text <text>", "Text to show in Desktop").option("--json", "Output JSON").action(async function(runtimeId) {
4203
+ const opts = this.opts();
4204
+ const result = await requestDesktopControl("ccem.workspace.sendInput", {
4205
+ runtimeId,
4206
+ text: opts.text,
4207
+ displayText: opts.displayText ?? null
4208
+ });
4209
+ outputDesktopResult(result, opts);
4210
+ });
4211
+ desktopCmd.command("open <link>").description("Open a ccem:// workspace session link in Desktop").option("--json", "Output JSON").action(async function(link) {
4212
+ const opts = this.opts();
4213
+ const result = await requestDesktopControl("ccem.workspace.openSession", { link });
4214
+ outputDesktopResult(result, opts);
4215
+ });
4015
4216
  program.command("usage").description("\u663E\u793A\u4F7F\u7528\u7EDF\u8BA1\u5E76\u5237\u65B0\u7F13\u5B58").option("--json", "\u4EE5 JSON \u683C\u5F0F\u8F93\u51FA").action(async function() {
4016
4217
  const opts = this.opts();
4017
4218
  const stats = await getUsageStats();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ccem",
3
- "version": "2.26.0",
3
+ "version": "2.28.0",
4
4
  "type": "module",
5
5
  "description": "Claude Code Environment Manager",
6
6
  "repository": {