ccem 2.27.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 +169 -34
  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
@@ -979,10 +979,10 @@ var renderLogoWithEnvPanel = (envName, env, defaultMode) => {
979
979
  const parsed = new URL(url);
980
980
  const protocol = parsed.protocol + "//";
981
981
  const host = parsed.host;
982
- const path9 = parsed.pathname + parsed.search;
982
+ const path10 = parsed.pathname + parsed.search;
983
983
  const hostStart = host.slice(0, 8);
984
984
  const hostEnd = host.slice(-4);
985
- const pathPart = path9.length > 10 ? path9.slice(0, 7) + "..." : path9;
985
+ const pathPart = path10.length > 10 ? path10.slice(0, 7) + "..." : path10;
986
986
  return `${protocol}${hostStart}...${hostEnd}${pathPart}`;
987
987
  } catch {
988
988
  return truncate(url, max);
@@ -3165,11 +3165,61 @@ function formatCronTaskTableRows(tasks) {
3165
3165
  ]);
3166
3166
  }
3167
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
+
3168
3218
  // src/index.ts
3169
3219
  var __filename2 = fileURLToPath2(import.meta.url);
3170
- var __dirname2 = path8.dirname(__filename2);
3171
- var pkgPath = path8.resolve(__dirname2, "..", "package.json");
3172
- 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"));
3173
3223
  var program = new Command();
3174
3224
  var DEFAULT_OFFICIAL_ENV = {
3175
3225
  ANTHROPIC_BASE_URL: "https://api.anthropic.com",
@@ -3225,6 +3275,29 @@ var config2 = new Conf2({
3225
3275
  defaultMode: null
3226
3276
  }
3227
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
+ }
3228
3301
  var recoverRegistriesFromLegacy = (registries) => {
3229
3302
  const currentAuthCount = Object.values(registries).filter(
3230
3303
  (env) => Boolean(env.ANTHROPIC_AUTH_TOKEN)
@@ -3233,11 +3306,11 @@ var recoverRegistriesFromLegacy = (registries) => {
3233
3306
  return registries;
3234
3307
  }
3235
3308
  const legacyConfigPath = getLegacyConfigPath();
3236
- if (!fs10.existsSync(legacyConfigPath)) {
3309
+ if (!fs11.existsSync(legacyConfigPath)) {
3237
3310
  return registries;
3238
3311
  }
3239
3312
  try {
3240
- const legacyRaw = JSON.parse(fs10.readFileSync(legacyConfigPath, "utf-8"));
3313
+ const legacyRaw = JSON.parse(fs11.readFileSync(legacyConfigPath, "utf-8"));
3241
3314
  const legacyRegistries = legacyRaw.registries ?? {};
3242
3315
  let changed = false;
3243
3316
  const recovered = { ...registries };
@@ -3431,15 +3504,15 @@ var switchEnvironment = async (name) => {
3431
3504
  exportCmds.forEach((cmd) => console.log(cmd));
3432
3505
  }
3433
3506
  };
3434
- var getSessionsFilePath = () => path8.join(getCcemConfigDir(), "sessions.json");
3435
- var getRuntimeStateFilePath = () => path8.join(getCcemConfigDir(), "runtime-state.json");
3436
- 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");
3437
3510
  var parseJsonFile = (filePath) => {
3438
- if (!fs10.existsSync(filePath)) {
3511
+ if (!fs11.existsSync(filePath)) {
3439
3512
  return null;
3440
3513
  }
3441
3514
  try {
3442
- return JSON.parse(fs10.readFileSync(filePath, "utf-8"));
3515
+ return JSON.parse(fs11.readFileSync(filePath, "utf-8"));
3443
3516
  } catch {
3444
3517
  return null;
3445
3518
  }
@@ -3531,8 +3604,8 @@ var resolveBotBindRuntimeId = (explicitRuntimeId) => {
3531
3604
  };
3532
3605
  var appendBotBindRequest = (payload) => {
3533
3606
  const requestPath = getBotBindRequestFilePath();
3534
- fs10.mkdirSync(path8.dirname(requestPath), { recursive: true });
3535
- fs10.appendFileSync(requestPath, `${JSON.stringify(payload)}
3607
+ fs11.mkdirSync(path9.dirname(requestPath), { recursive: true });
3608
+ fs11.appendFileSync(requestPath, `${JSON.stringify(payload)}
3536
3609
  `, "utf-8");
3537
3610
  return requestPath;
3538
3611
  };
@@ -3907,12 +3980,12 @@ setupCmd.command("migrate").description("\u8FC1\u79FB\u65E7\u7248\u914D\u7F6E\u5
3907
3980
  const newConfigPath = getCcemConfigPath();
3908
3981
  const legacyConfigPath = getLegacyConfigPath();
3909
3982
  console.log(chalk7.cyan("\n\u{1F504} \u914D\u7F6E\u8FC1\u79FB\n"));
3910
- if (!fs10.existsSync(legacyConfigPath)) {
3983
+ if (!fs11.existsSync(legacyConfigPath)) {
3911
3984
  console.log(chalk7.yellow("\u672A\u627E\u5230\u65E7\u7248\u914D\u7F6E\u6587\u4EF6"));
3912
3985
  console.log(chalk7.gray(` \u65E7\u8DEF\u5F84: ${legacyConfigPath}`));
3913
3986
  return;
3914
3987
  }
3915
- if (fs10.existsSync(newConfigPath) && !options.force) {
3988
+ if (fs11.existsSync(newConfigPath) && !options.force) {
3916
3989
  console.log(chalk7.green("\u2713 \u914D\u7F6E\u5DF2\u5728\u65B0\u8DEF\u5F84"));
3917
3990
  console.log(chalk7.gray(` \u8DEF\u5F84: ${newConfigPath}`));
3918
3991
  console.log(chalk7.gray("\n\u4F7F\u7528 --force \u5F3A\u5236\u91CD\u65B0\u8FC1\u79FB"));
@@ -3920,15 +3993,15 @@ setupCmd.command("migrate").description("\u8FC1\u79FB\u65E7\u7248\u914D\u7F6E\u5
3920
3993
  }
3921
3994
  try {
3922
3995
  ensureCcemDir();
3923
- fs10.copyFileSync(legacyConfigPath, newConfigPath);
3996
+ fs11.copyFileSync(legacyConfigPath, newConfigPath);
3924
3997
  console.log(chalk7.green("\u2713 \u914D\u7F6E\u5DF2\u8FC1\u79FB"));
3925
3998
  console.log(chalk7.gray(` \u4ECE: ${legacyConfigPath}`));
3926
3999
  console.log(chalk7.gray(` \u5230: ${newConfigPath}`));
3927
4000
  if (options.clean) {
3928
- fs10.unlinkSync(legacyConfigPath);
3929
- const legacyDir = path8.dirname(legacyConfigPath);
4001
+ fs11.unlinkSync(legacyConfigPath);
4002
+ const legacyDir = path9.dirname(legacyConfigPath);
3930
4003
  try {
3931
- fs10.rmdirSync(legacyDir);
4004
+ fs11.rmdirSync(legacyDir);
3932
4005
  } catch {
3933
4006
  }
3934
4007
  console.log(chalk7.green("\u2713 \u5DF2\u5220\u9664\u65E7\u914D\u7F6E\u6587\u4EF6"));
@@ -3939,13 +4012,13 @@ setupCmd.command("migrate").description("\u8FC1\u79FB\u65E7\u7248\u914D\u7F6E\u5
3939
4012
  });
3940
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() {
3941
4014
  const options = this.opts();
3942
- const skillDir = path8.join(getHomeDir(), ".claude", "skills");
3943
- const targetPath = path8.join(skillDir, "ccem-cron.md");
3944
- if (!fs10.existsSync(skillDir)) {
3945
- 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 });
3946
4019
  console.log(chalk7.gray(`\u521B\u5EFA\u76EE\u5F55: ${skillDir}`));
3947
4020
  }
3948
- if (fs10.existsSync(targetPath) && !options.force) {
4021
+ if (fs11.existsSync(targetPath) && !options.force) {
3949
4022
  const { overwrite } = await inquirer.prompt([
3950
4023
  {
3951
4024
  type: "confirm",
@@ -3959,7 +4032,7 @@ setupCmd.command("cron").description("\u5B89\u88C5 ccem-cron skill \u5230 Claude
3959
4032
  return;
3960
4033
  }
3961
4034
  }
3962
- fs10.writeFileSync(targetPath, CCEM_CRON_SKILL_CONTENT, "utf-8");
4035
+ fs11.writeFileSync(targetPath, CCEM_CRON_SKILL_CONTENT, "utf-8");
3963
4036
  console.log(chalk7.green(`\u2713 \u5DF2\u5B89\u88C5 ccem-cron skill`));
3964
4037
  console.log(chalk7.gray(` \u8DEF\u5F84: ${targetPath}`));
3965
4038
  console.log(chalk7.cyan(`
@@ -3967,13 +4040,13 @@ setupCmd.command("cron").description("\u5B89\u88C5 ccem-cron skill \u5230 Claude
3967
4040
  });
3968
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() {
3969
4042
  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 });
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 });
3974
4047
  console.log(chalk7.gray(`\u521B\u5EFA\u76EE\u5F55: ${skillDir}`));
3975
4048
  }
3976
- if (fs10.existsSync(targetPath) && !options.force) {
4049
+ if (fs11.existsSync(targetPath) && !options.force) {
3977
4050
  const { overwrite } = await inquirer.prompt([
3978
4051
  {
3979
4052
  type: "confirm",
@@ -3987,7 +4060,7 @@ setupCmd.command("bot-bind").description("\u5B89\u88C5 ccem-bot-bind skill \u523
3987
4060
  return;
3988
4061
  }
3989
4062
  }
3990
- fs10.writeFileSync(targetPath, CCEM_BOT_BIND_SKILL_CONTENT, "utf-8");
4063
+ fs11.writeFileSync(targetPath, CCEM_BOT_BIND_SKILL_CONTENT, "utf-8");
3991
4064
  console.log(chalk7.green(`\u2713 \u5DF2\u5B89\u88C5 ccem-bot-bind skill`));
3992
4065
  console.log(chalk7.gray(` \u8DEF\u5F84: ${targetPath}`));
3993
4066
  console.log(chalk7.cyan(`
@@ -4078,6 +4151,68 @@ program.command("launch").description(false).option("--env <name>", "\u73AF\u588
4078
4151
  silent: true
4079
4152
  });
4080
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
+ });
4081
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() {
4082
4217
  const opts = this.opts();
4083
4218
  const stats = await getUsageStats();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ccem",
3
- "version": "2.27.0",
3
+ "version": "2.28.0",
4
4
  "type": "module",
5
5
  "description": "Claude Code Environment Manager",
6
6
  "repository": {