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.
- package/dist/index.js +249 -48
- 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
|
|
11
|
-
import * as
|
|
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
|
|
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;
|
|
@@ -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
|
|
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 =
|
|
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
|
|
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) {
|
|
@@ -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 =
|
|
3110
|
-
var pkgPath =
|
|
3111
|
-
var pkg = JSON.parse(
|
|
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 (!
|
|
3309
|
+
if (!fs11.existsSync(legacyConfigPath)) {
|
|
3176
3310
|
return registries;
|
|
3177
3311
|
}
|
|
3178
3312
|
try {
|
|
3179
|
-
const legacyRaw = JSON.parse(
|
|
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 = () =>
|
|
3374
|
-
var getRuntimeStateFilePath = () =>
|
|
3375
|
-
var getBotBindRequestFilePath = () =>
|
|
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 (!
|
|
3511
|
+
if (!fs11.existsSync(filePath)) {
|
|
3378
3512
|
return null;
|
|
3379
3513
|
}
|
|
3380
3514
|
try {
|
|
3381
|
-
return JSON.parse(
|
|
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
|
-
|
|
3474
|
-
|
|
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 (!
|
|
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 (
|
|
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
|
-
|
|
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
|
-
|
|
3863
|
-
const legacyDir =
|
|
4001
|
+
fs11.unlinkSync(legacyConfigPath);
|
|
4002
|
+
const legacyDir = path9.dirname(legacyConfigPath);
|
|
3864
4003
|
try {
|
|
3865
|
-
|
|
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 =
|
|
3877
|
-
const targetPath =
|
|
3878
|
-
if (!
|
|
3879
|
-
|
|
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 (
|
|
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
|
-
|
|
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 =
|
|
3905
|
-
const targetPath =
|
|
3906
|
-
if (!
|
|
3907
|
-
|
|
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 (
|
|
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
|
-
|
|
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();
|