ccem 2.27.0 → 2.30.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 +451 -38
- 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
|
|
@@ -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
|
|
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 =
|
|
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);
|
|
@@ -2702,16 +2702,70 @@ var config = new Conf({
|
|
|
2702
2702
|
cwd: getCcemConfigDir()
|
|
2703
2703
|
// 使用统一的配置目录
|
|
2704
2704
|
});
|
|
2705
|
-
var
|
|
2706
|
-
|
|
2705
|
+
var REMOTE_CRYPTO_ALGORITHM_V2 = "aes-256-gcm";
|
|
2706
|
+
var REMOTE_CRYPTO_ALGORITHM_V1 = "aes-256-cbc";
|
|
2707
|
+
var isEnvelopeV2 = (value) => {
|
|
2708
|
+
if (typeof value !== "object" || value === null) return false;
|
|
2709
|
+
const v = value;
|
|
2710
|
+
return v.v === 2 && typeof v.nonce === "string" && typeof v.ciphertext === "string" && typeof v.tag === "string";
|
|
2711
|
+
};
|
|
2712
|
+
var deriveRemoteKey = (secret) => crypto3.scryptSync(secret, "ccem-salt", 32);
|
|
2713
|
+
var decryptV2Envelope = (envelope, key) => {
|
|
2714
|
+
const nonce = Buffer.from(envelope.nonce, "base64");
|
|
2715
|
+
const ciphertext = Buffer.from(envelope.ciphertext, "base64");
|
|
2716
|
+
const tag = Buffer.from(envelope.tag, "base64");
|
|
2717
|
+
if (nonce.length === 0 || ciphertext.length === 0 || tag.length === 0) {
|
|
2718
|
+
throw new Error("v2 envelope has empty nonce/ciphertext/tag");
|
|
2719
|
+
}
|
|
2720
|
+
const decipher = crypto3.createDecipheriv(
|
|
2721
|
+
REMOTE_CRYPTO_ALGORITHM_V2,
|
|
2722
|
+
key,
|
|
2723
|
+
nonce
|
|
2724
|
+
);
|
|
2725
|
+
decipher.setAuthTag(tag);
|
|
2726
|
+
let decrypted = decipher.update(ciphertext);
|
|
2727
|
+
decrypted = Buffer.concat([decrypted, decipher.final()]);
|
|
2728
|
+
return decrypted.toString("utf8");
|
|
2729
|
+
};
|
|
2730
|
+
var decryptV1Legacy = (encryptedBase64, key) => {
|
|
2707
2731
|
const combined = Buffer.from(encryptedBase64, "base64");
|
|
2732
|
+
if (combined.length < 16) {
|
|
2733
|
+
throw new Error("v1 payload too short");
|
|
2734
|
+
}
|
|
2708
2735
|
const iv = combined.subarray(0, 16);
|
|
2709
2736
|
const encryptedHex = combined.subarray(16).toString("hex");
|
|
2710
|
-
const decipher = crypto3.createDecipheriv(
|
|
2737
|
+
const decipher = crypto3.createDecipheriv(REMOTE_CRYPTO_ALGORITHM_V1, key, iv);
|
|
2711
2738
|
let decrypted = decipher.update(encryptedHex, "hex", "utf8");
|
|
2712
2739
|
decrypted += decipher.final("utf8");
|
|
2713
2740
|
return decrypted;
|
|
2714
2741
|
};
|
|
2742
|
+
var decryptWithSecret = (encryptedBase64, secret) => {
|
|
2743
|
+
const key = deriveRemoteKey(secret);
|
|
2744
|
+
let parsedObj = null;
|
|
2745
|
+
try {
|
|
2746
|
+
const jsonStr = Buffer.from(encryptedBase64, "base64").toString("utf8");
|
|
2747
|
+
parsedObj = JSON.parse(jsonStr);
|
|
2748
|
+
} catch {
|
|
2749
|
+
}
|
|
2750
|
+
let envelopeV2 = null;
|
|
2751
|
+
if (parsedObj !== null) {
|
|
2752
|
+
if (isEnvelopeV2(parsedObj)) {
|
|
2753
|
+
envelopeV2 = parsedObj;
|
|
2754
|
+
} else if (parsedObj && typeof parsedObj === "object" && "v" in parsedObj) {
|
|
2755
|
+
const version = parsedObj.v;
|
|
2756
|
+
if (version === 2) {
|
|
2757
|
+
throw new Error(
|
|
2758
|
+
"Malformed v2 envelope: missing required fields (nonce, ciphertext, tag)"
|
|
2759
|
+
);
|
|
2760
|
+
}
|
|
2761
|
+
throw new Error(`Unsupported remote envelope version: ${version}`);
|
|
2762
|
+
}
|
|
2763
|
+
}
|
|
2764
|
+
if (envelopeV2) {
|
|
2765
|
+
return decryptV2Envelope(envelopeV2, key);
|
|
2766
|
+
}
|
|
2767
|
+
return decryptV1Legacy(encryptedBase64, key);
|
|
2768
|
+
};
|
|
2715
2769
|
var getUniqueName = (baseName, existingNames) => {
|
|
2716
2770
|
if (!existingNames.has(baseName)) {
|
|
2717
2771
|
return baseName;
|
|
@@ -3165,11 +3219,271 @@ function formatCronTaskTableRows(tasks) {
|
|
|
3165
3219
|
]);
|
|
3166
3220
|
}
|
|
3167
3221
|
|
|
3222
|
+
// src/desktopControl.ts
|
|
3223
|
+
import fs10 from "fs";
|
|
3224
|
+
import path8 from "path";
|
|
3225
|
+
var DEFAULT_REQUEST_TIMEOUT_MS = 5e3;
|
|
3226
|
+
var LOOPBACK_HOSTS = /* @__PURE__ */ new Set(["127.0.0.1", "localhost", "::1", "[::1]"]);
|
|
3227
|
+
var ENDPOINT_UNREACHABLE_CODES = /* @__PURE__ */ new Set([
|
|
3228
|
+
"ECONNREFUSED",
|
|
3229
|
+
"ECONNRESET",
|
|
3230
|
+
"ENOTCONN",
|
|
3231
|
+
"EHOSTUNREACH",
|
|
3232
|
+
"ECONNABORTED"
|
|
3233
|
+
]);
|
|
3234
|
+
var StaleDesktopControlDescriptorError = class extends Error {
|
|
3235
|
+
name = "StaleDesktopControlDescriptorError";
|
|
3236
|
+
reason;
|
|
3237
|
+
descriptorPath;
|
|
3238
|
+
pid;
|
|
3239
|
+
cleanedUp;
|
|
3240
|
+
cause;
|
|
3241
|
+
constructor(details) {
|
|
3242
|
+
const subject = details.pid !== null ? `process ${details.pid}` : "the publishing process";
|
|
3243
|
+
const symptom = details.reason === "dead-pid" ? `${subject} is no longer running` : details.reason === "endpoint-unreachable" ? "the control endpoint refused the connection" : `the control request timed out after ${details.timeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS}ms`;
|
|
3244
|
+
const remedy = details.cleanedUp ? "The stale descriptor was removed automatically; start CCEM Desktop and rerun the command." : details.pid !== null ? "Restart CCEM Desktop so it republishes a fresh control endpoint." : "Restart CCEM Desktop to refresh the descriptor, or remove the stale file manually if it is no longer managed.";
|
|
3245
|
+
super(
|
|
3246
|
+
`CCEM Desktop control descriptor at ${details.descriptorPath} is stale: ${symptom}. ${remedy}`
|
|
3247
|
+
);
|
|
3248
|
+
this.reason = details.reason;
|
|
3249
|
+
this.descriptorPath = details.descriptorPath;
|
|
3250
|
+
this.pid = details.pid;
|
|
3251
|
+
this.cleanedUp = details.cleanedUp;
|
|
3252
|
+
if (details.cause !== void 0) {
|
|
3253
|
+
this.cause = details.cause;
|
|
3254
|
+
}
|
|
3255
|
+
}
|
|
3256
|
+
};
|
|
3257
|
+
function getDesktopControlDescriptorPath() {
|
|
3258
|
+
return process.env.CCEM_CONTROL_FILE?.trim() || getDefaultControlDescriptorPath();
|
|
3259
|
+
}
|
|
3260
|
+
function getDefaultControlDescriptorPath() {
|
|
3261
|
+
return path8.join(getCcemConfigDir(), "control.json");
|
|
3262
|
+
}
|
|
3263
|
+
function isDefaultDescriptorPath(descriptorPath) {
|
|
3264
|
+
try {
|
|
3265
|
+
return path8.resolve(descriptorPath) === path8.resolve(getDefaultControlDescriptorPath());
|
|
3266
|
+
} catch {
|
|
3267
|
+
return false;
|
|
3268
|
+
}
|
|
3269
|
+
}
|
|
3270
|
+
function safeRemoveStaleDescriptor(descriptorPath) {
|
|
3271
|
+
if (!isDefaultDescriptorPath(descriptorPath)) {
|
|
3272
|
+
return false;
|
|
3273
|
+
}
|
|
3274
|
+
try {
|
|
3275
|
+
fs10.rmSync(descriptorPath);
|
|
3276
|
+
return true;
|
|
3277
|
+
} catch {
|
|
3278
|
+
return false;
|
|
3279
|
+
}
|
|
3280
|
+
}
|
|
3281
|
+
function readErrnoCode(error) {
|
|
3282
|
+
if (!error || typeof error !== "object") return void 0;
|
|
3283
|
+
const candidate = error;
|
|
3284
|
+
return candidate.code ?? candidate.cause?.code;
|
|
3285
|
+
}
|
|
3286
|
+
function readPid(value) {
|
|
3287
|
+
return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : null;
|
|
3288
|
+
}
|
|
3289
|
+
function isLoopbackHost(host) {
|
|
3290
|
+
const normalized = host.trim().toLowerCase().replace(/^\[|\]$/g, "");
|
|
3291
|
+
if (LOOPBACK_HOSTS.has(normalized)) {
|
|
3292
|
+
return true;
|
|
3293
|
+
}
|
|
3294
|
+
if (/^127\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(normalized)) {
|
|
3295
|
+
return true;
|
|
3296
|
+
}
|
|
3297
|
+
if (normalized === "::1" || normalized === "0:0:0:0:0:0:0:1") {
|
|
3298
|
+
return true;
|
|
3299
|
+
}
|
|
3300
|
+
return false;
|
|
3301
|
+
}
|
|
3302
|
+
function extractHost(endpoint) {
|
|
3303
|
+
const withoutScheme = endpoint.replace(/^[a-zA-Z][a-zA-Z0-9+.-]*:\/\//, "");
|
|
3304
|
+
const hostPort = withoutScheme.split(/[\/?#]/)[0] || "";
|
|
3305
|
+
if (hostPort.startsWith("[")) {
|
|
3306
|
+
const end = hostPort.indexOf("]");
|
|
3307
|
+
if (end === -1) return null;
|
|
3308
|
+
return hostPort.slice(1, end);
|
|
3309
|
+
}
|
|
3310
|
+
const colonIndex = hostPort.lastIndexOf(":");
|
|
3311
|
+
if (colonIndex === -1) return hostPort;
|
|
3312
|
+
return hostPort.slice(0, colonIndex);
|
|
3313
|
+
}
|
|
3314
|
+
function validateLoopbackEndpoint(endpoint) {
|
|
3315
|
+
const host = extractHost(endpoint);
|
|
3316
|
+
if (!host) {
|
|
3317
|
+
throw new Error(
|
|
3318
|
+
`CCEM Desktop control endpoint '${redactEndpoint(endpoint)}' is missing a host. Refusing to continue.`
|
|
3319
|
+
);
|
|
3320
|
+
}
|
|
3321
|
+
if (!isLoopbackHost(host)) {
|
|
3322
|
+
throw new Error(
|
|
3323
|
+
`CCEM Desktop control endpoint '${redactEndpoint(endpoint)}' is not bound to loopback. Only 127.0.0.1, localhost, or ::1 are allowed.`
|
|
3324
|
+
);
|
|
3325
|
+
}
|
|
3326
|
+
}
|
|
3327
|
+
function isPidAlive(pid) {
|
|
3328
|
+
if (!Number.isFinite(pid) || pid <= 0) return false;
|
|
3329
|
+
try {
|
|
3330
|
+
process.kill(pid, 0);
|
|
3331
|
+
return true;
|
|
3332
|
+
} catch (error) {
|
|
3333
|
+
const code = error.code;
|
|
3334
|
+
if (code === "ESRCH") return false;
|
|
3335
|
+
if (code === "EPERM") return true;
|
|
3336
|
+
return false;
|
|
3337
|
+
}
|
|
3338
|
+
}
|
|
3339
|
+
function redactEndpoint(endpoint) {
|
|
3340
|
+
try {
|
|
3341
|
+
const url = new URL(endpoint);
|
|
3342
|
+
return `${url.protocol}//${url.host}${url.pathname}`;
|
|
3343
|
+
} catch {
|
|
3344
|
+
return "<invalid endpoint>";
|
|
3345
|
+
}
|
|
3346
|
+
}
|
|
3347
|
+
function isDescriptor(value) {
|
|
3348
|
+
return Boolean(
|
|
3349
|
+
value && typeof value === "object" && typeof value.endpoint === "string" && typeof value.token === "string"
|
|
3350
|
+
);
|
|
3351
|
+
}
|
|
3352
|
+
function resolveDesktopControlDescriptor(descriptorPath = getDesktopControlDescriptorPath()) {
|
|
3353
|
+
if (!fs10.existsSync(descriptorPath)) {
|
|
3354
|
+
throw new Error(`CCEM Desktop control endpoint not found at ${descriptorPath}. Start CCEM Desktop first.`);
|
|
3355
|
+
}
|
|
3356
|
+
const parsed = JSON.parse(fs10.readFileSync(descriptorPath, "utf-8"));
|
|
3357
|
+
const endpoint = parsed.endpoint?.trim();
|
|
3358
|
+
const token = parsed.token?.trim();
|
|
3359
|
+
if (!endpoint || !token) {
|
|
3360
|
+
throw new Error(`Invalid CCEM Desktop control descriptor at ${descriptorPath}`);
|
|
3361
|
+
}
|
|
3362
|
+
validateLoopbackEndpoint(endpoint);
|
|
3363
|
+
const pid = readPid(parsed.pid);
|
|
3364
|
+
if (pid !== null && !isPidAlive(pid)) {
|
|
3365
|
+
const cleanedUp = safeRemoveStaleDescriptor(descriptorPath);
|
|
3366
|
+
throw new StaleDesktopControlDescriptorError({
|
|
3367
|
+
reason: "dead-pid",
|
|
3368
|
+
descriptorPath,
|
|
3369
|
+
pid,
|
|
3370
|
+
cleanedUp
|
|
3371
|
+
});
|
|
3372
|
+
}
|
|
3373
|
+
return { endpoint, token, pid };
|
|
3374
|
+
}
|
|
3375
|
+
async function requestDesktopControl(method, params, descriptorOrOptions, maybeOptions = {}) {
|
|
3376
|
+
const descriptorPath = getDesktopControlDescriptorPath();
|
|
3377
|
+
const hasInjectedDescriptor = isDescriptor(descriptorOrOptions);
|
|
3378
|
+
const descriptor = hasInjectedDescriptor ? descriptorOrOptions : resolveDesktopControlDescriptor(descriptorPath);
|
|
3379
|
+
const options = hasInjectedDescriptor ? maybeOptions : descriptorOrOptions ?? maybeOptions ?? {};
|
|
3380
|
+
const timeoutMs = options.timeoutMs ?? options.fetchTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
|
|
3381
|
+
const pid = readPid(descriptor.pid);
|
|
3382
|
+
validateLoopbackEndpoint(descriptor.endpoint);
|
|
3383
|
+
if (pid !== null && !isPidAlive(pid)) {
|
|
3384
|
+
const cleanedUp = hasInjectedDescriptor ? false : safeRemoveStaleDescriptor(descriptorPath);
|
|
3385
|
+
throw new StaleDesktopControlDescriptorError({
|
|
3386
|
+
reason: "dead-pid",
|
|
3387
|
+
descriptorPath,
|
|
3388
|
+
pid,
|
|
3389
|
+
cleanedUp
|
|
3390
|
+
});
|
|
3391
|
+
}
|
|
3392
|
+
const id = `ccem-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
3393
|
+
const controller = new AbortController();
|
|
3394
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
3395
|
+
const externalSignal = options.signal;
|
|
3396
|
+
const onExternalAbort = () => controller.abort();
|
|
3397
|
+
if (externalSignal) {
|
|
3398
|
+
if (externalSignal.aborted) {
|
|
3399
|
+
clearTimeout(timer);
|
|
3400
|
+
controller.abort();
|
|
3401
|
+
} else {
|
|
3402
|
+
externalSignal.addEventListener("abort", onExternalAbort, { once: true });
|
|
3403
|
+
}
|
|
3404
|
+
}
|
|
3405
|
+
let response;
|
|
3406
|
+
try {
|
|
3407
|
+
response = await fetch(descriptor.endpoint, {
|
|
3408
|
+
method: "POST",
|
|
3409
|
+
headers: {
|
|
3410
|
+
authorization: `Bearer ${descriptor.token}`,
|
|
3411
|
+
"content-type": "application/json"
|
|
3412
|
+
},
|
|
3413
|
+
body: JSON.stringify({
|
|
3414
|
+
jsonrpc: "2.0",
|
|
3415
|
+
id,
|
|
3416
|
+
method,
|
|
3417
|
+
params: params ?? {}
|
|
3418
|
+
}),
|
|
3419
|
+
signal: controller.signal
|
|
3420
|
+
});
|
|
3421
|
+
} catch (error) {
|
|
3422
|
+
if (controller.signal.aborted) {
|
|
3423
|
+
throw new StaleDesktopControlDescriptorError({
|
|
3424
|
+
reason: "request-timeout",
|
|
3425
|
+
descriptorPath,
|
|
3426
|
+
pid,
|
|
3427
|
+
cleanedUp: false,
|
|
3428
|
+
timeoutMs,
|
|
3429
|
+
cause: error
|
|
3430
|
+
});
|
|
3431
|
+
}
|
|
3432
|
+
const code = readErrnoCode(error);
|
|
3433
|
+
if (code && ENDPOINT_UNREACHABLE_CODES.has(code)) {
|
|
3434
|
+
throw new StaleDesktopControlDescriptorError({
|
|
3435
|
+
reason: "endpoint-unreachable",
|
|
3436
|
+
descriptorPath,
|
|
3437
|
+
pid,
|
|
3438
|
+
cleanedUp: false,
|
|
3439
|
+
cause: error
|
|
3440
|
+
});
|
|
3441
|
+
}
|
|
3442
|
+
throw error;
|
|
3443
|
+
} finally {
|
|
3444
|
+
clearTimeout(timer);
|
|
3445
|
+
if (externalSignal) {
|
|
3446
|
+
externalSignal.removeEventListener("abort", onExternalAbort);
|
|
3447
|
+
}
|
|
3448
|
+
}
|
|
3449
|
+
if (!response.ok) {
|
|
3450
|
+
throw new Error(`CCEM Desktop control request failed: HTTP ${response.status}`);
|
|
3451
|
+
}
|
|
3452
|
+
const payload = await response.json();
|
|
3453
|
+
if (payload.error) {
|
|
3454
|
+
throw new Error(payload.error.message || `CCEM Desktop control error ${payload.error.code ?? ""}`.trim());
|
|
3455
|
+
}
|
|
3456
|
+
return payload.result;
|
|
3457
|
+
}
|
|
3458
|
+
function parseSinceOption(raw) {
|
|
3459
|
+
if (raw === void 0 || raw === null || raw === "") return null;
|
|
3460
|
+
const value = Number(raw);
|
|
3461
|
+
if (!Number.isFinite(value) || value < 0 || !Number.isInteger(value)) {
|
|
3462
|
+
throw new Error(
|
|
3463
|
+
`Invalid --since value '${raw}'. Expected a non-negative integer sequence number (e.g. 0, 42).`
|
|
3464
|
+
);
|
|
3465
|
+
}
|
|
3466
|
+
return value;
|
|
3467
|
+
}
|
|
3468
|
+
function parseLimitOption(raw) {
|
|
3469
|
+
if (raw === void 0 || raw === null || raw === "") return null;
|
|
3470
|
+
const value = Number(raw);
|
|
3471
|
+
if (!Number.isFinite(value) || value <= 0 || !Number.isInteger(value)) {
|
|
3472
|
+
throw new Error(
|
|
3473
|
+
`Invalid --limit value '${raw}'. Expected a positive integer (e.g. 1, 100).`
|
|
3474
|
+
);
|
|
3475
|
+
}
|
|
3476
|
+
return value;
|
|
3477
|
+
}
|
|
3478
|
+
function printJson(value) {
|
|
3479
|
+
console.log(JSON.stringify(value, null, 2));
|
|
3480
|
+
}
|
|
3481
|
+
|
|
3168
3482
|
// src/index.ts
|
|
3169
3483
|
var __filename2 = fileURLToPath2(import.meta.url);
|
|
3170
|
-
var __dirname2 =
|
|
3171
|
-
var pkgPath =
|
|
3172
|
-
var pkg = JSON.parse(
|
|
3484
|
+
var __dirname2 = path9.dirname(__filename2);
|
|
3485
|
+
var pkgPath = path9.resolve(__dirname2, "..", "package.json");
|
|
3486
|
+
var pkg = JSON.parse(fs11.readFileSync(pkgPath, "utf-8"));
|
|
3173
3487
|
var program = new Command();
|
|
3174
3488
|
var DEFAULT_OFFICIAL_ENV = {
|
|
3175
3489
|
ANTHROPIC_BASE_URL: "https://api.anthropic.com",
|
|
@@ -3225,6 +3539,29 @@ var config2 = new Conf2({
|
|
|
3225
3539
|
defaultMode: null
|
|
3226
3540
|
}
|
|
3227
3541
|
});
|
|
3542
|
+
function outputDesktopResult(value, options) {
|
|
3543
|
+
if (options.json) {
|
|
3544
|
+
printJson(value);
|
|
3545
|
+
return;
|
|
3546
|
+
}
|
|
3547
|
+
console.log(JSON.stringify(value, null, 2));
|
|
3548
|
+
}
|
|
3549
|
+
function parseOptionalBoolean(value) {
|
|
3550
|
+
if (value === void 0 || value === null) {
|
|
3551
|
+
return void 0;
|
|
3552
|
+
}
|
|
3553
|
+
if (typeof value === "boolean") {
|
|
3554
|
+
return value;
|
|
3555
|
+
}
|
|
3556
|
+
const normalized = String(value).trim().toLowerCase();
|
|
3557
|
+
if (["1", "true", "yes", "y"].includes(normalized)) {
|
|
3558
|
+
return true;
|
|
3559
|
+
}
|
|
3560
|
+
if (["0", "false", "no", "n"].includes(normalized)) {
|
|
3561
|
+
return false;
|
|
3562
|
+
}
|
|
3563
|
+
return void 0;
|
|
3564
|
+
}
|
|
3228
3565
|
var recoverRegistriesFromLegacy = (registries) => {
|
|
3229
3566
|
const currentAuthCount = Object.values(registries).filter(
|
|
3230
3567
|
(env) => Boolean(env.ANTHROPIC_AUTH_TOKEN)
|
|
@@ -3233,11 +3570,11 @@ var recoverRegistriesFromLegacy = (registries) => {
|
|
|
3233
3570
|
return registries;
|
|
3234
3571
|
}
|
|
3235
3572
|
const legacyConfigPath = getLegacyConfigPath();
|
|
3236
|
-
if (!
|
|
3573
|
+
if (!fs11.existsSync(legacyConfigPath)) {
|
|
3237
3574
|
return registries;
|
|
3238
3575
|
}
|
|
3239
3576
|
try {
|
|
3240
|
-
const legacyRaw = JSON.parse(
|
|
3577
|
+
const legacyRaw = JSON.parse(fs11.readFileSync(legacyConfigPath, "utf-8"));
|
|
3241
3578
|
const legacyRegistries = legacyRaw.registries ?? {};
|
|
3242
3579
|
let changed = false;
|
|
3243
3580
|
const recovered = { ...registries };
|
|
@@ -3431,15 +3768,15 @@ var switchEnvironment = async (name) => {
|
|
|
3431
3768
|
exportCmds.forEach((cmd) => console.log(cmd));
|
|
3432
3769
|
}
|
|
3433
3770
|
};
|
|
3434
|
-
var getSessionsFilePath = () =>
|
|
3435
|
-
var getRuntimeStateFilePath = () =>
|
|
3436
|
-
var getBotBindRequestFilePath = () =>
|
|
3771
|
+
var getSessionsFilePath = () => path9.join(getCcemConfigDir(), "sessions.json");
|
|
3772
|
+
var getRuntimeStateFilePath = () => path9.join(getCcemConfigDir(), "runtime-state.json");
|
|
3773
|
+
var getBotBindRequestFilePath = () => path9.join(getCcemConfigDir(), "bot-bind-requests.jsonl");
|
|
3437
3774
|
var parseJsonFile = (filePath) => {
|
|
3438
|
-
if (!
|
|
3775
|
+
if (!fs11.existsSync(filePath)) {
|
|
3439
3776
|
return null;
|
|
3440
3777
|
}
|
|
3441
3778
|
try {
|
|
3442
|
-
return JSON.parse(
|
|
3779
|
+
return JSON.parse(fs11.readFileSync(filePath, "utf-8"));
|
|
3443
3780
|
} catch {
|
|
3444
3781
|
return null;
|
|
3445
3782
|
}
|
|
@@ -3531,8 +3868,8 @@ var resolveBotBindRuntimeId = (explicitRuntimeId) => {
|
|
|
3531
3868
|
};
|
|
3532
3869
|
var appendBotBindRequest = (payload) => {
|
|
3533
3870
|
const requestPath = getBotBindRequestFilePath();
|
|
3534
|
-
|
|
3535
|
-
|
|
3871
|
+
fs11.mkdirSync(path9.dirname(requestPath), { recursive: true });
|
|
3872
|
+
fs11.appendFileSync(requestPath, `${JSON.stringify(payload)}
|
|
3536
3873
|
`, "utf-8");
|
|
3537
3874
|
return requestPath;
|
|
3538
3875
|
};
|
|
@@ -3907,12 +4244,12 @@ setupCmd.command("migrate").description("\u8FC1\u79FB\u65E7\u7248\u914D\u7F6E\u5
|
|
|
3907
4244
|
const newConfigPath = getCcemConfigPath();
|
|
3908
4245
|
const legacyConfigPath = getLegacyConfigPath();
|
|
3909
4246
|
console.log(chalk7.cyan("\n\u{1F504} \u914D\u7F6E\u8FC1\u79FB\n"));
|
|
3910
|
-
if (!
|
|
4247
|
+
if (!fs11.existsSync(legacyConfigPath)) {
|
|
3911
4248
|
console.log(chalk7.yellow("\u672A\u627E\u5230\u65E7\u7248\u914D\u7F6E\u6587\u4EF6"));
|
|
3912
4249
|
console.log(chalk7.gray(` \u65E7\u8DEF\u5F84: ${legacyConfigPath}`));
|
|
3913
4250
|
return;
|
|
3914
4251
|
}
|
|
3915
|
-
if (
|
|
4252
|
+
if (fs11.existsSync(newConfigPath) && !options.force) {
|
|
3916
4253
|
console.log(chalk7.green("\u2713 \u914D\u7F6E\u5DF2\u5728\u65B0\u8DEF\u5F84"));
|
|
3917
4254
|
console.log(chalk7.gray(` \u8DEF\u5F84: ${newConfigPath}`));
|
|
3918
4255
|
console.log(chalk7.gray("\n\u4F7F\u7528 --force \u5F3A\u5236\u91CD\u65B0\u8FC1\u79FB"));
|
|
@@ -3920,15 +4257,15 @@ setupCmd.command("migrate").description("\u8FC1\u79FB\u65E7\u7248\u914D\u7F6E\u5
|
|
|
3920
4257
|
}
|
|
3921
4258
|
try {
|
|
3922
4259
|
ensureCcemDir();
|
|
3923
|
-
|
|
4260
|
+
fs11.copyFileSync(legacyConfigPath, newConfigPath);
|
|
3924
4261
|
console.log(chalk7.green("\u2713 \u914D\u7F6E\u5DF2\u8FC1\u79FB"));
|
|
3925
4262
|
console.log(chalk7.gray(` \u4ECE: ${legacyConfigPath}`));
|
|
3926
4263
|
console.log(chalk7.gray(` \u5230: ${newConfigPath}`));
|
|
3927
4264
|
if (options.clean) {
|
|
3928
|
-
|
|
3929
|
-
const legacyDir =
|
|
4265
|
+
fs11.unlinkSync(legacyConfigPath);
|
|
4266
|
+
const legacyDir = path9.dirname(legacyConfigPath);
|
|
3930
4267
|
try {
|
|
3931
|
-
|
|
4268
|
+
fs11.rmdirSync(legacyDir);
|
|
3932
4269
|
} catch {
|
|
3933
4270
|
}
|
|
3934
4271
|
console.log(chalk7.green("\u2713 \u5DF2\u5220\u9664\u65E7\u914D\u7F6E\u6587\u4EF6"));
|
|
@@ -3939,13 +4276,13 @@ setupCmd.command("migrate").description("\u8FC1\u79FB\u65E7\u7248\u914D\u7F6E\u5
|
|
|
3939
4276
|
});
|
|
3940
4277
|
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
4278
|
const options = this.opts();
|
|
3942
|
-
const skillDir =
|
|
3943
|
-
const targetPath =
|
|
3944
|
-
if (!
|
|
3945
|
-
|
|
4279
|
+
const skillDir = path9.join(getHomeDir(), ".claude", "skills");
|
|
4280
|
+
const targetPath = path9.join(skillDir, "ccem-cron.md");
|
|
4281
|
+
if (!fs11.existsSync(skillDir)) {
|
|
4282
|
+
fs11.mkdirSync(skillDir, { recursive: true });
|
|
3946
4283
|
console.log(chalk7.gray(`\u521B\u5EFA\u76EE\u5F55: ${skillDir}`));
|
|
3947
4284
|
}
|
|
3948
|
-
if (
|
|
4285
|
+
if (fs11.existsSync(targetPath) && !options.force) {
|
|
3949
4286
|
const { overwrite } = await inquirer.prompt([
|
|
3950
4287
|
{
|
|
3951
4288
|
type: "confirm",
|
|
@@ -3959,7 +4296,7 @@ setupCmd.command("cron").description("\u5B89\u88C5 ccem-cron skill \u5230 Claude
|
|
|
3959
4296
|
return;
|
|
3960
4297
|
}
|
|
3961
4298
|
}
|
|
3962
|
-
|
|
4299
|
+
fs11.writeFileSync(targetPath, CCEM_CRON_SKILL_CONTENT, "utf-8");
|
|
3963
4300
|
console.log(chalk7.green(`\u2713 \u5DF2\u5B89\u88C5 ccem-cron skill`));
|
|
3964
4301
|
console.log(chalk7.gray(` \u8DEF\u5F84: ${targetPath}`));
|
|
3965
4302
|
console.log(chalk7.cyan(`
|
|
@@ -3967,13 +4304,13 @@ setupCmd.command("cron").description("\u5B89\u88C5 ccem-cron skill \u5230 Claude
|
|
|
3967
4304
|
});
|
|
3968
4305
|
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
4306
|
const options = this.opts();
|
|
3970
|
-
const skillDir =
|
|
3971
|
-
const targetPath =
|
|
3972
|
-
if (!
|
|
3973
|
-
|
|
4307
|
+
const skillDir = path9.join(getHomeDir(), ".claude", "skills");
|
|
4308
|
+
const targetPath = path9.join(skillDir, "ccem-bot-bind.md");
|
|
4309
|
+
if (!fs11.existsSync(skillDir)) {
|
|
4310
|
+
fs11.mkdirSync(skillDir, { recursive: true });
|
|
3974
4311
|
console.log(chalk7.gray(`\u521B\u5EFA\u76EE\u5F55: ${skillDir}`));
|
|
3975
4312
|
}
|
|
3976
|
-
if (
|
|
4313
|
+
if (fs11.existsSync(targetPath) && !options.force) {
|
|
3977
4314
|
const { overwrite } = await inquirer.prompt([
|
|
3978
4315
|
{
|
|
3979
4316
|
type: "confirm",
|
|
@@ -3987,7 +4324,7 @@ setupCmd.command("bot-bind").description("\u5B89\u88C5 ccem-bot-bind skill \u523
|
|
|
3987
4324
|
return;
|
|
3988
4325
|
}
|
|
3989
4326
|
}
|
|
3990
|
-
|
|
4327
|
+
fs11.writeFileSync(targetPath, CCEM_BOT_BIND_SKILL_CONTENT, "utf-8");
|
|
3991
4328
|
console.log(chalk7.green(`\u2713 \u5DF2\u5B89\u88C5 ccem-bot-bind skill`));
|
|
3992
4329
|
console.log(chalk7.gray(` \u8DEF\u5F84: ${targetPath}`));
|
|
3993
4330
|
console.log(chalk7.cyan(`
|
|
@@ -4078,6 +4415,70 @@ program.command("launch").description(false).option("--env <name>", "\u73AF\u588
|
|
|
4078
4415
|
silent: true
|
|
4079
4416
|
});
|
|
4080
4417
|
});
|
|
4418
|
+
var desktopCmd = program.command("desktop").description("Control the running CCEM Desktop app");
|
|
4419
|
+
desktopCmd.command("health").description("Check the running CCEM Desktop control endpoint").option("--json", "Output JSON").action(async function() {
|
|
4420
|
+
const opts = this.opts();
|
|
4421
|
+
const result = await requestDesktopControl("ccem.health");
|
|
4422
|
+
outputDesktopResult(result, opts);
|
|
4423
|
+
});
|
|
4424
|
+
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() {
|
|
4425
|
+
const opts = this.opts();
|
|
4426
|
+
const provider = String(opts.provider).trim().toLowerCase();
|
|
4427
|
+
if (provider !== "claude" && provider !== "codex") {
|
|
4428
|
+
throw new Error("Unsupported provider. Use 'claude' or 'codex'.");
|
|
4429
|
+
}
|
|
4430
|
+
const result = await requestDesktopControl("ccem.workspace.createSession", {
|
|
4431
|
+
provider,
|
|
4432
|
+
cwd: opts.cwd,
|
|
4433
|
+
prompt: opts.prompt,
|
|
4434
|
+
envName: opts.env ?? null,
|
|
4435
|
+
permissionMode: opts.perm ?? null,
|
|
4436
|
+
runtimePermissionMode: opts.runtimePerm ?? null,
|
|
4437
|
+
providerSessionId: opts.providerSessionId ?? null,
|
|
4438
|
+
effort: opts.effort ?? null,
|
|
4439
|
+
open: parseOptionalBoolean(opts.open)
|
|
4440
|
+
});
|
|
4441
|
+
outputDesktopResult(result, opts);
|
|
4442
|
+
});
|
|
4443
|
+
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() {
|
|
4444
|
+
const opts = this.opts();
|
|
4445
|
+
const result = await requestDesktopControl("ccem.workspace.listSessions", {
|
|
4446
|
+
cwd: opts.cwd ?? null,
|
|
4447
|
+
provider: opts.provider ?? null,
|
|
4448
|
+
status: opts.status ?? null
|
|
4449
|
+
});
|
|
4450
|
+
outputDesktopResult(result, opts);
|
|
4451
|
+
});
|
|
4452
|
+
desktopCmd.command("status <runtimeId>").description("Get one CCEM Desktop workspace session").option("--json", "Output JSON").action(async function(runtimeId) {
|
|
4453
|
+
const opts = this.opts();
|
|
4454
|
+
const result = await requestDesktopControl("ccem.workspace.getSession", { runtimeId });
|
|
4455
|
+
outputDesktopResult(result, opts);
|
|
4456
|
+
});
|
|
4457
|
+
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) {
|
|
4458
|
+
const opts = this.opts();
|
|
4459
|
+
const sinceSeq = parseSinceOption(opts.since);
|
|
4460
|
+
const limit = parseLimitOption(opts.limit);
|
|
4461
|
+
const result = await requestDesktopControl("ccem.workspace.getEvents", {
|
|
4462
|
+
runtimeId,
|
|
4463
|
+
sinceSeq,
|
|
4464
|
+
limit
|
|
4465
|
+
});
|
|
4466
|
+
outputDesktopResult(result, opts);
|
|
4467
|
+
});
|
|
4468
|
+
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) {
|
|
4469
|
+
const opts = this.opts();
|
|
4470
|
+
const result = await requestDesktopControl("ccem.workspace.sendInput", {
|
|
4471
|
+
runtimeId,
|
|
4472
|
+
text: opts.text,
|
|
4473
|
+
displayText: opts.displayText ?? null
|
|
4474
|
+
});
|
|
4475
|
+
outputDesktopResult(result, opts);
|
|
4476
|
+
});
|
|
4477
|
+
desktopCmd.command("open <link>").description("Open a ccem:// workspace session link in Desktop").option("--json", "Output JSON").action(async function(link) {
|
|
4478
|
+
const opts = this.opts();
|
|
4479
|
+
const result = await requestDesktopControl("ccem.workspace.openSession", { link });
|
|
4480
|
+
outputDesktopResult(result, opts);
|
|
4481
|
+
});
|
|
4081
4482
|
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
4483
|
const opts = this.opts();
|
|
4083
4484
|
const stats = await getUsageStats();
|
|
@@ -4285,4 +4686,16 @@ Editing environment '${result.name}'`));
|
|
|
4285
4686
|
}
|
|
4286
4687
|
}
|
|
4287
4688
|
});
|
|
4288
|
-
|
|
4689
|
+
function handleCliError(error) {
|
|
4690
|
+
if (error instanceof StaleDesktopControlDescriptorError) {
|
|
4691
|
+
console.error(chalk7.red(error.message));
|
|
4692
|
+
process.exit(1);
|
|
4693
|
+
}
|
|
4694
|
+
if (error instanceof Error) {
|
|
4695
|
+
console.error(chalk7.red(error.message));
|
|
4696
|
+
process.exit(1);
|
|
4697
|
+
}
|
|
4698
|
+
console.error(chalk7.red(String(error)));
|
|
4699
|
+
process.exit(1);
|
|
4700
|
+
}
|
|
4701
|
+
await program.parseAsync(process.argv).catch(handleCliError);
|