ccem 2.28.0 → 2.31.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 +473 -87
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -711,7 +711,7 @@ async function parseJSONLFileAsync(filePath, prices, signal) {
|
|
|
711
711
|
throw new Error("Aborted");
|
|
712
712
|
}
|
|
713
713
|
if (lineCount % 1e3 === 0) {
|
|
714
|
-
await new Promise((
|
|
714
|
+
await new Promise((resolve4) => setTimeout(resolve4, 0));
|
|
715
715
|
}
|
|
716
716
|
}
|
|
717
717
|
if (!line.trim()) continue;
|
|
@@ -800,7 +800,7 @@ async function getUsageStats(signal) {
|
|
|
800
800
|
fileStats = cachedFile.stats;
|
|
801
801
|
} else {
|
|
802
802
|
fileStats = await parseJSONLFileAsync(file, prices, signal);
|
|
803
|
-
await new Promise((
|
|
803
|
+
await new Promise((resolve4) => setTimeout(resolve4, 0));
|
|
804
804
|
}
|
|
805
805
|
return {
|
|
806
806
|
file,
|
|
@@ -1312,7 +1312,7 @@ var renderFooterHints = (hints) => {
|
|
|
1312
1312
|
return minimal;
|
|
1313
1313
|
};
|
|
1314
1314
|
var selectEnvWithKeys = (registries, current) => {
|
|
1315
|
-
return new Promise((
|
|
1315
|
+
return new Promise((resolve4) => {
|
|
1316
1316
|
const envNames = Object.keys(registries);
|
|
1317
1317
|
let selectedIndex = envNames.indexOf(current);
|
|
1318
1318
|
if (selectedIndex === -1) selectedIndex = 0;
|
|
@@ -1374,7 +1374,7 @@ var selectEnvWithKeys = (registries, current) => {
|
|
|
1374
1374
|
}
|
|
1375
1375
|
if (char === "\x1B" && key.length === 1) {
|
|
1376
1376
|
cleanup();
|
|
1377
|
-
|
|
1377
|
+
resolve4({ action: "cancel" });
|
|
1378
1378
|
return;
|
|
1379
1379
|
}
|
|
1380
1380
|
if (char === "\x1B[A" || char === "k") {
|
|
@@ -1391,27 +1391,27 @@ var selectEnvWithKeys = (registries, current) => {
|
|
|
1391
1391
|
}
|
|
1392
1392
|
if (char === "\r" || char === "\n") {
|
|
1393
1393
|
cleanup();
|
|
1394
|
-
|
|
1394
|
+
resolve4({ action: "select", name: envNames[selectedIndex] });
|
|
1395
1395
|
return;
|
|
1396
1396
|
}
|
|
1397
1397
|
if (char === "e" || char === "E") {
|
|
1398
1398
|
cleanup();
|
|
1399
|
-
|
|
1399
|
+
resolve4({ action: "edit", name: envNames[selectedIndex] });
|
|
1400
1400
|
return;
|
|
1401
1401
|
}
|
|
1402
1402
|
if (char === "r" || char === "R") {
|
|
1403
1403
|
cleanup();
|
|
1404
|
-
|
|
1404
|
+
resolve4({ action: "rename", name: envNames[selectedIndex] });
|
|
1405
1405
|
return;
|
|
1406
1406
|
}
|
|
1407
1407
|
if (char === "c" || char === "C") {
|
|
1408
1408
|
cleanup();
|
|
1409
|
-
|
|
1409
|
+
resolve4({ action: "copy", name: envNames[selectedIndex] });
|
|
1410
1410
|
return;
|
|
1411
1411
|
}
|
|
1412
1412
|
if (char === "d" || char === "D") {
|
|
1413
1413
|
cleanup();
|
|
1414
|
-
|
|
1414
|
+
resolve4({ action: "delete", name: envNames[selectedIndex] });
|
|
1415
1415
|
return;
|
|
1416
1416
|
}
|
|
1417
1417
|
};
|
|
@@ -1893,7 +1893,7 @@ async function launchClaude(options) {
|
|
|
1893
1893
|
console.log(renderStarting());
|
|
1894
1894
|
}
|
|
1895
1895
|
const sessionsDir = ensureSessionsDir();
|
|
1896
|
-
return new Promise((
|
|
1896
|
+
return new Promise((resolve4) => {
|
|
1897
1897
|
let provenanceTracking = null;
|
|
1898
1898
|
const child = spawn("claude", args, {
|
|
1899
1899
|
stdio: "inherit",
|
|
@@ -2151,7 +2151,7 @@ var setupEnvSettings = () => {
|
|
|
2151
2151
|
}
|
|
2152
2152
|
};
|
|
2153
2153
|
var setupMcpTool = () => {
|
|
2154
|
-
return new Promise((
|
|
2154
|
+
return new Promise((resolve4) => {
|
|
2155
2155
|
console.log(chalk4.cyan(" \u2192 \u6B63\u5728\u6DFB\u52A0 chrome-devtools MCP \u5DE5\u5177..."));
|
|
2156
2156
|
const child = spawn2("claude", [
|
|
2157
2157
|
"mcp",
|
|
@@ -2176,22 +2176,22 @@ var setupMcpTool = () => {
|
|
|
2176
2176
|
child.on("exit", (code) => {
|
|
2177
2177
|
if (code === 0) {
|
|
2178
2178
|
console.log(chalk4.green(" \u2713 \u5DF2\u6DFB\u52A0 chrome-devtools MCP \u5DE5\u5177"));
|
|
2179
|
-
|
|
2179
|
+
resolve4(true);
|
|
2180
2180
|
} else {
|
|
2181
2181
|
if (stderr.includes("already exists") || stdout.includes("already exists")) {
|
|
2182
2182
|
console.log(chalk4.gray(" \u2713 chrome-devtools MCP \u5DE5\u5177\u5DF2\u5B58\u5728"));
|
|
2183
|
-
|
|
2183
|
+
resolve4(true);
|
|
2184
2184
|
} else {
|
|
2185
2185
|
console.error(chalk4.red(` \u2717 \u6DFB\u52A0 MCP \u5DE5\u5177\u5931\u8D25 (code: ${code})`));
|
|
2186
2186
|
if (stderr) console.error(chalk4.gray(` ${stderr.trim()}`));
|
|
2187
|
-
|
|
2187
|
+
resolve4(false);
|
|
2188
2188
|
}
|
|
2189
2189
|
}
|
|
2190
2190
|
});
|
|
2191
2191
|
child.on("error", (err) => {
|
|
2192
2192
|
console.error(chalk4.red(` \u2717 \u6267\u884C claude \u547D\u4EE4\u5931\u8D25: ${err.message}`));
|
|
2193
2193
|
console.log(chalk4.yellow(" \u8BF7\u786E\u4FDD\u5DF2\u5B89\u88C5 Claude Code CLI"));
|
|
2194
|
-
|
|
2194
|
+
resolve4(false);
|
|
2195
2195
|
});
|
|
2196
2196
|
});
|
|
2197
2197
|
};
|
|
@@ -2221,7 +2221,7 @@ var runSetupInit = async (options = {}) => {
|
|
|
2221
2221
|
};
|
|
2222
2222
|
|
|
2223
2223
|
// src/skills.ts
|
|
2224
|
-
import {
|
|
2224
|
+
import { execFileSync as execFileSync2 } from "child_process";
|
|
2225
2225
|
import * as fs8 from "fs";
|
|
2226
2226
|
import * as path6 from "path";
|
|
2227
2227
|
import chalk5 from "chalk";
|
|
@@ -2230,6 +2230,58 @@ var SKILL_GROUPS = {
|
|
|
2230
2230
|
featured: { label: "\u7CBE\u9009", icon: "\u2B50" },
|
|
2231
2231
|
others: { label: "\u5176\u4ED6", icon: "\u{1F4E6}" }
|
|
2232
2232
|
};
|
|
2233
|
+
var SAFE_GITHUB_PART_RE = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
|
|
2234
|
+
var SAFE_PATH_PART_RE = /^[A-Za-z0-9._-]+$/;
|
|
2235
|
+
var SAFE_GIT_REF_RE = /^[A-Za-z0-9._/-]+$/;
|
|
2236
|
+
var SAFE_PLUGIN_PACKAGE_RE = /^[A-Za-z0-9._@-]+$/;
|
|
2237
|
+
function isSafeGitHubPart(value) {
|
|
2238
|
+
return SAFE_GITHUB_PART_RE.test(value) && value !== "." && value !== "..";
|
|
2239
|
+
}
|
|
2240
|
+
function isSafePathPart(value) {
|
|
2241
|
+
return SAFE_PATH_PART_RE.test(value) && value !== "." && value !== "..";
|
|
2242
|
+
}
|
|
2243
|
+
function isSafeRepoPath(value) {
|
|
2244
|
+
if (!value) return true;
|
|
2245
|
+
if (value.startsWith("/") || value.includes("\\")) return false;
|
|
2246
|
+
const parts = value.split("/");
|
|
2247
|
+
if (parts.some((part) => !part || !isSafePathPart(part))) return false;
|
|
2248
|
+
return true;
|
|
2249
|
+
}
|
|
2250
|
+
function isSafeGitRef(value) {
|
|
2251
|
+
if (!value || !SAFE_GIT_REF_RE.test(value)) return false;
|
|
2252
|
+
if (value.startsWith("/") || value.endsWith("/") || value.includes("//")) return false;
|
|
2253
|
+
if (value.includes("..") || value.includes("@{")) return false;
|
|
2254
|
+
if (value.split("/").some((part) => part === "." || part === ".." || part.endsWith(".lock"))) {
|
|
2255
|
+
return false;
|
|
2256
|
+
}
|
|
2257
|
+
return true;
|
|
2258
|
+
}
|
|
2259
|
+
function isSafeTargetName(value) {
|
|
2260
|
+
return isSafePathPart(value) && !value.startsWith(".");
|
|
2261
|
+
}
|
|
2262
|
+
function isSafeMarketplace(value) {
|
|
2263
|
+
return isSafeRepoPath(value);
|
|
2264
|
+
}
|
|
2265
|
+
function isSafePluginPackage(value) {
|
|
2266
|
+
return SAFE_PLUGIN_PACKAGE_RE.test(value) && !value.includes("..");
|
|
2267
|
+
}
|
|
2268
|
+
function validateGitHubInstallParts(owner, repo, branch, repoPath, targetName) {
|
|
2269
|
+
if (!isSafeGitHubPart(owner)) {
|
|
2270
|
+
throw new Error(`Invalid GitHub owner: ${owner}`);
|
|
2271
|
+
}
|
|
2272
|
+
if (!isSafeGitHubPart(repo)) {
|
|
2273
|
+
throw new Error(`Invalid GitHub repo: ${repo}`);
|
|
2274
|
+
}
|
|
2275
|
+
if (!isSafeGitRef(branch)) {
|
|
2276
|
+
throw new Error(`Invalid Git branch: ${branch}`);
|
|
2277
|
+
}
|
|
2278
|
+
if (!isSafeRepoPath(repoPath)) {
|
|
2279
|
+
throw new Error(`Invalid repository path: ${repoPath}`);
|
|
2280
|
+
}
|
|
2281
|
+
if (!isSafeTargetName(targetName)) {
|
|
2282
|
+
throw new Error(`Invalid skill name: ${targetName}`);
|
|
2283
|
+
}
|
|
2284
|
+
}
|
|
2233
2285
|
var SKILL_PRESETS = [
|
|
2234
2286
|
// ===== 官方 (Official) =====
|
|
2235
2287
|
{
|
|
@@ -2375,21 +2427,44 @@ function getGroupOrder() {
|
|
|
2375
2427
|
return ["official", "featured", "others"];
|
|
2376
2428
|
}
|
|
2377
2429
|
function parseGitHubUrl(url) {
|
|
2378
|
-
if (
|
|
2379
|
-
|
|
2380
|
-
|
|
2430
|
+
if (url !== url.trim()) return null;
|
|
2431
|
+
if (/(?:^|\/)\.{1,2}(?:\/|$)/.test(url) || /%2e|%2f/i.test(url)) return null;
|
|
2432
|
+
if (/^[A-Za-z0-9._-]+\/[A-Za-z0-9._-]+$/.test(url)) {
|
|
2433
|
+
const [owner, repo] = url.split("/");
|
|
2434
|
+
if (!isSafeGitHubPart(owner) || !isSafeGitHubPart(repo)) return null;
|
|
2435
|
+
return { owner, repo, branch: "main", path: "" };
|
|
2436
|
+
}
|
|
2437
|
+
try {
|
|
2438
|
+
const parsed = new URL(url);
|
|
2439
|
+
if (parsed.protocol !== "https:" || parsed.hostname !== "github.com") {
|
|
2440
|
+
return null;
|
|
2441
|
+
}
|
|
2442
|
+
if (parsed.search || parsed.hash || parsed.username || parsed.password) {
|
|
2443
|
+
return null;
|
|
2444
|
+
}
|
|
2445
|
+
const parts = parsed.pathname.split("/");
|
|
2446
|
+
if (parts.shift() !== "") return null;
|
|
2447
|
+
const [owner, rawRepo, ...rest] = parts;
|
|
2448
|
+
if (!owner || !rawRepo) return null;
|
|
2449
|
+
const repo = rawRepo.replace(/\.git$/, "");
|
|
2450
|
+
let branch = "main";
|
|
2451
|
+
let repoPath = "";
|
|
2452
|
+
if (rest.length > 0) {
|
|
2453
|
+
if (rest[0] !== "tree" || !rest[1]) return null;
|
|
2454
|
+
branch = rest[1];
|
|
2455
|
+
repoPath = rest.slice(2).join("/");
|
|
2456
|
+
}
|
|
2457
|
+
validateGitHubInstallParts(
|
|
2458
|
+
owner,
|
|
2459
|
+
repo,
|
|
2460
|
+
branch,
|
|
2461
|
+
repoPath,
|
|
2462
|
+
repoPath ? path6.basename(repoPath) : repo
|
|
2463
|
+
);
|
|
2464
|
+
return { owner, repo, branch, path: repoPath };
|
|
2465
|
+
} catch {
|
|
2466
|
+
return null;
|
|
2381
2467
|
}
|
|
2382
|
-
const match = url.match(
|
|
2383
|
-
/github\.com\/([^/]+)\/([^/]+)(?:\/tree\/([^/]+)(?:\/(.*))?)?/
|
|
2384
|
-
);
|
|
2385
|
-
if (!match) return null;
|
|
2386
|
-
const [, owner, repo, branch = "main", repoPath = ""] = match;
|
|
2387
|
-
return {
|
|
2388
|
-
owner,
|
|
2389
|
-
repo: repo.replace(/\.git$/, ""),
|
|
2390
|
-
branch,
|
|
2391
|
-
path: repoPath
|
|
2392
|
-
};
|
|
2393
2468
|
}
|
|
2394
2469
|
function getSkillsDir() {
|
|
2395
2470
|
return path6.join(process.cwd(), ".claude", "skills");
|
|
@@ -2416,23 +2491,29 @@ function cleanupTempDirs(skillsDir) {
|
|
|
2416
2491
|
}
|
|
2417
2492
|
}
|
|
2418
2493
|
function downloadSkillWithGit(owner, repo, branch, repoPath, targetName) {
|
|
2419
|
-
|
|
2420
|
-
const targetDir = path6.join(skillsDir, targetName);
|
|
2421
|
-
if (fs8.existsSync(targetDir)) {
|
|
2422
|
-
console.log(chalk5.yellow(`Skill "${targetName}" already exists. Updating...`));
|
|
2423
|
-
fs8.rmSync(targetDir, { recursive: true });
|
|
2424
|
-
}
|
|
2425
|
-
const repoUrl = `https://github.com/${owner}/${repo}.git`;
|
|
2426
|
-
const tempDir = path6.join(skillsDir, `.tmp-${Date.now()}`);
|
|
2494
|
+
let tempDir = null;
|
|
2427
2495
|
try {
|
|
2496
|
+
validateGitHubInstallParts(owner, repo, branch, repoPath, targetName);
|
|
2497
|
+
const skillsDir = ensureSkillsDir();
|
|
2498
|
+
const targetDir = path6.join(skillsDir, targetName);
|
|
2499
|
+
if (fs8.existsSync(targetDir)) {
|
|
2500
|
+
console.log(chalk5.yellow(`Skill "${targetName}" already exists. Updating...`));
|
|
2501
|
+
fs8.rmSync(targetDir, { recursive: true });
|
|
2502
|
+
}
|
|
2503
|
+
const repoUrl = `https://github.com/${owner}/${repo}.git`;
|
|
2504
|
+
tempDir = path6.join(skillsDir, `.tmp-${Date.now()}`);
|
|
2428
2505
|
fs8.mkdirSync(tempDir, { recursive: true });
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2506
|
+
execFileSync2("git", ["init"], { cwd: tempDir, stdio: "pipe" });
|
|
2507
|
+
execFileSync2("git", ["remote", "add", "origin", repoUrl], { cwd: tempDir, stdio: "pipe" });
|
|
2508
|
+
execFileSync2("git", ["config", "core.sparseCheckout", "true"], { cwd: tempDir, stdio: "pipe" });
|
|
2432
2509
|
const sparseFile = path6.join(tempDir, ".git", "info", "sparse-checkout");
|
|
2510
|
+
fs8.mkdirSync(path6.dirname(sparseFile), { recursive: true });
|
|
2433
2511
|
fs8.writeFileSync(sparseFile, repoPath ? `${repoPath}/
|
|
2434
2512
|
` : "*\n");
|
|
2435
|
-
|
|
2513
|
+
execFileSync2("git", ["pull", "--depth=1", "origin", branch], {
|
|
2514
|
+
cwd: tempDir,
|
|
2515
|
+
stdio: "pipe"
|
|
2516
|
+
});
|
|
2436
2517
|
const sourceDir = repoPath ? path6.join(tempDir, repoPath) : tempDir;
|
|
2437
2518
|
if (!fs8.existsSync(sourceDir)) {
|
|
2438
2519
|
throw new Error(`Path "${repoPath}" not found in repository`);
|
|
@@ -2445,7 +2526,7 @@ function downloadSkillWithGit(owner, repo, branch, repoPath, targetName) {
|
|
|
2445
2526
|
console.error(chalk5.red(`Failed to download skill: ${errMsg}`));
|
|
2446
2527
|
return false;
|
|
2447
2528
|
} finally {
|
|
2448
|
-
if (fs8.existsSync(tempDir)) {
|
|
2529
|
+
if (tempDir && fs8.existsSync(tempDir)) {
|
|
2449
2530
|
fs8.rmSync(tempDir, { recursive: true });
|
|
2450
2531
|
}
|
|
2451
2532
|
}
|
|
@@ -2464,6 +2545,13 @@ function copyDir(src, dest) {
|
|
|
2464
2545
|
}
|
|
2465
2546
|
}
|
|
2466
2547
|
}
|
|
2548
|
+
function isSafeSkillName(name) {
|
|
2549
|
+
return name.trim().length > 0 && name !== "." && name !== ".." && !name.startsWith(".") && !path6.isAbsolute(name) && !name.includes("/") && !name.includes("\\") && !name.includes("\0");
|
|
2550
|
+
}
|
|
2551
|
+
function isPathInside(childPath, parentPath) {
|
|
2552
|
+
const relativePath = path6.relative(parentPath, childPath);
|
|
2553
|
+
return relativePath === "" || relativePath.length > 0 && !relativePath.startsWith("..") && !path6.isAbsolute(relativePath);
|
|
2554
|
+
}
|
|
2467
2555
|
function addSkillFromGitHub(urlOrPreset) {
|
|
2468
2556
|
const preset = SKILL_PRESETS.find((p) => p.name === urlOrPreset);
|
|
2469
2557
|
if (preset) {
|
|
@@ -2530,22 +2618,42 @@ function listInstalledSkills() {
|
|
|
2530
2618
|
}));
|
|
2531
2619
|
}
|
|
2532
2620
|
function removeSkill(name) {
|
|
2621
|
+
if (!isSafeSkillName(name)) {
|
|
2622
|
+
console.error(chalk5.red(`Invalid skill name "${name}"`));
|
|
2623
|
+
return false;
|
|
2624
|
+
}
|
|
2533
2625
|
const skillsDir = getSkillsDir();
|
|
2534
|
-
const targetDir = path6.
|
|
2626
|
+
const targetDir = path6.resolve(skillsDir, name);
|
|
2535
2627
|
if (!fs8.existsSync(targetDir)) {
|
|
2536
2628
|
console.error(chalk5.red(`Skill "${name}" not found`));
|
|
2537
2629
|
return false;
|
|
2538
2630
|
}
|
|
2631
|
+
const canonicalSkillsDir = fs8.realpathSync(skillsDir);
|
|
2632
|
+
const canonicalTargetDir = fs8.realpathSync(targetDir);
|
|
2633
|
+
if (!isPathInside(canonicalTargetDir, canonicalSkillsDir)) {
|
|
2634
|
+
console.error(chalk5.red(`Refusing to remove skill outside skills directory: "${name}"`));
|
|
2635
|
+
return false;
|
|
2636
|
+
}
|
|
2539
2637
|
fs8.rmSync(targetDir, { recursive: true });
|
|
2540
2638
|
console.log(chalk5.green(`Removed skill "${name}"`));
|
|
2541
2639
|
return true;
|
|
2542
2640
|
}
|
|
2543
2641
|
function installFromPluginMarketplace(marketplace, packageName) {
|
|
2544
2642
|
try {
|
|
2643
|
+
if (!isSafeMarketplace(marketplace)) {
|
|
2644
|
+
throw new Error(`Invalid marketplace: ${marketplace}`);
|
|
2645
|
+
}
|
|
2646
|
+
if (!isSafePluginPackage(packageName)) {
|
|
2647
|
+
throw new Error(`Invalid package name: ${packageName}`);
|
|
2648
|
+
}
|
|
2545
2649
|
console.log(chalk5.cyan(`Adding marketplace: ${marketplace}...`));
|
|
2546
|
-
|
|
2650
|
+
execFileSync2("claude", ["plugin", "marketplace", "add", marketplace], {
|
|
2651
|
+
stdio: "inherit"
|
|
2652
|
+
});
|
|
2547
2653
|
console.log(chalk5.cyan(`Installing package: ${packageName}...`));
|
|
2548
|
-
|
|
2654
|
+
execFileSync2("claude", ["plugin", "install", packageName], {
|
|
2655
|
+
stdio: "inherit"
|
|
2656
|
+
});
|
|
2549
2657
|
console.log(chalk5.green(`Successfully installed ${packageName}`));
|
|
2550
2658
|
return true;
|
|
2551
2659
|
} catch (error) {
|
|
@@ -2660,7 +2768,7 @@ function SkillSelector({ onSelect, onCancel }) {
|
|
|
2660
2768
|
|
|
2661
2769
|
// src/components/index.tsx
|
|
2662
2770
|
async function runSkillSelector() {
|
|
2663
|
-
return new Promise((
|
|
2771
|
+
return new Promise((resolve4) => {
|
|
2664
2772
|
let resolved = false;
|
|
2665
2773
|
const { unmount, waitUntilExit } = render(
|
|
2666
2774
|
/* @__PURE__ */ React2.createElement(
|
|
@@ -2671,23 +2779,23 @@ async function runSkillSelector() {
|
|
|
2671
2779
|
resolved = true;
|
|
2672
2780
|
unmount();
|
|
2673
2781
|
if (result === "custom") {
|
|
2674
|
-
|
|
2782
|
+
resolve4({ type: "custom" });
|
|
2675
2783
|
} else {
|
|
2676
|
-
|
|
2784
|
+
resolve4({ type: "skill", skill: result });
|
|
2677
2785
|
}
|
|
2678
2786
|
},
|
|
2679
2787
|
onCancel: () => {
|
|
2680
2788
|
if (resolved) return;
|
|
2681
2789
|
resolved = true;
|
|
2682
2790
|
unmount();
|
|
2683
|
-
|
|
2791
|
+
resolve4({ type: "cancelled" });
|
|
2684
2792
|
}
|
|
2685
2793
|
}
|
|
2686
2794
|
)
|
|
2687
2795
|
);
|
|
2688
2796
|
waitUntilExit().then(() => {
|
|
2689
2797
|
if (!resolved) {
|
|
2690
|
-
|
|
2798
|
+
resolve4({ type: "cancelled" });
|
|
2691
2799
|
}
|
|
2692
2800
|
});
|
|
2693
2801
|
});
|
|
@@ -2702,16 +2810,70 @@ var config = new Conf({
|
|
|
2702
2810
|
cwd: getCcemConfigDir()
|
|
2703
2811
|
// 使用统一的配置目录
|
|
2704
2812
|
});
|
|
2705
|
-
var
|
|
2706
|
-
|
|
2813
|
+
var REMOTE_CRYPTO_ALGORITHM_V2 = "aes-256-gcm";
|
|
2814
|
+
var REMOTE_CRYPTO_ALGORITHM_V1 = "aes-256-cbc";
|
|
2815
|
+
var isEnvelopeV2 = (value) => {
|
|
2816
|
+
if (typeof value !== "object" || value === null) return false;
|
|
2817
|
+
const v = value;
|
|
2818
|
+
return v.v === 2 && typeof v.nonce === "string" && typeof v.ciphertext === "string" && typeof v.tag === "string";
|
|
2819
|
+
};
|
|
2820
|
+
var deriveRemoteKey = (secret) => crypto3.scryptSync(secret, "ccem-salt", 32);
|
|
2821
|
+
var decryptV2Envelope = (envelope, key) => {
|
|
2822
|
+
const nonce = Buffer.from(envelope.nonce, "base64");
|
|
2823
|
+
const ciphertext = Buffer.from(envelope.ciphertext, "base64");
|
|
2824
|
+
const tag = Buffer.from(envelope.tag, "base64");
|
|
2825
|
+
if (nonce.length === 0 || ciphertext.length === 0 || tag.length === 0) {
|
|
2826
|
+
throw new Error("v2 envelope has empty nonce/ciphertext/tag");
|
|
2827
|
+
}
|
|
2828
|
+
const decipher = crypto3.createDecipheriv(
|
|
2829
|
+
REMOTE_CRYPTO_ALGORITHM_V2,
|
|
2830
|
+
key,
|
|
2831
|
+
nonce
|
|
2832
|
+
);
|
|
2833
|
+
decipher.setAuthTag(tag);
|
|
2834
|
+
let decrypted = decipher.update(ciphertext);
|
|
2835
|
+
decrypted = Buffer.concat([decrypted, decipher.final()]);
|
|
2836
|
+
return decrypted.toString("utf8");
|
|
2837
|
+
};
|
|
2838
|
+
var decryptV1Legacy = (encryptedBase64, key) => {
|
|
2707
2839
|
const combined = Buffer.from(encryptedBase64, "base64");
|
|
2840
|
+
if (combined.length < 16) {
|
|
2841
|
+
throw new Error("v1 payload too short");
|
|
2842
|
+
}
|
|
2708
2843
|
const iv = combined.subarray(0, 16);
|
|
2709
2844
|
const encryptedHex = combined.subarray(16).toString("hex");
|
|
2710
|
-
const decipher = crypto3.createDecipheriv(
|
|
2845
|
+
const decipher = crypto3.createDecipheriv(REMOTE_CRYPTO_ALGORITHM_V1, key, iv);
|
|
2711
2846
|
let decrypted = decipher.update(encryptedHex, "hex", "utf8");
|
|
2712
2847
|
decrypted += decipher.final("utf8");
|
|
2713
2848
|
return decrypted;
|
|
2714
2849
|
};
|
|
2850
|
+
var decryptWithSecret = (encryptedBase64, secret) => {
|
|
2851
|
+
const key = deriveRemoteKey(secret);
|
|
2852
|
+
let parsedObj = null;
|
|
2853
|
+
try {
|
|
2854
|
+
const jsonStr = Buffer.from(encryptedBase64, "base64").toString("utf8");
|
|
2855
|
+
parsedObj = JSON.parse(jsonStr);
|
|
2856
|
+
} catch {
|
|
2857
|
+
}
|
|
2858
|
+
let envelopeV2 = null;
|
|
2859
|
+
if (parsedObj !== null) {
|
|
2860
|
+
if (isEnvelopeV2(parsedObj)) {
|
|
2861
|
+
envelopeV2 = parsedObj;
|
|
2862
|
+
} else if (parsedObj && typeof parsedObj === "object" && "v" in parsedObj) {
|
|
2863
|
+
const version = parsedObj.v;
|
|
2864
|
+
if (version === 2) {
|
|
2865
|
+
throw new Error(
|
|
2866
|
+
"Malformed v2 envelope: missing required fields (nonce, ciphertext, tag)"
|
|
2867
|
+
);
|
|
2868
|
+
}
|
|
2869
|
+
throw new Error(`Unsupported remote envelope version: ${version}`);
|
|
2870
|
+
}
|
|
2871
|
+
}
|
|
2872
|
+
if (envelopeV2) {
|
|
2873
|
+
return decryptV2Envelope(envelopeV2, key);
|
|
2874
|
+
}
|
|
2875
|
+
return decryptV1Legacy(encryptedBase64, key);
|
|
2876
|
+
};
|
|
2715
2877
|
var getUniqueName = (baseName, existingNames) => {
|
|
2716
2878
|
if (!existingNames.has(baseName)) {
|
|
2717
2879
|
return baseName;
|
|
@@ -3168,8 +3330,132 @@ function formatCronTaskTableRows(tasks) {
|
|
|
3168
3330
|
// src/desktopControl.ts
|
|
3169
3331
|
import fs10 from "fs";
|
|
3170
3332
|
import path8 from "path";
|
|
3333
|
+
var DEFAULT_REQUEST_TIMEOUT_MS = 5e3;
|
|
3334
|
+
var LOOPBACK_HOSTS = /* @__PURE__ */ new Set(["127.0.0.1", "localhost", "::1", "[::1]"]);
|
|
3335
|
+
var ENDPOINT_UNREACHABLE_CODES = /* @__PURE__ */ new Set([
|
|
3336
|
+
"ECONNREFUSED",
|
|
3337
|
+
"ECONNRESET",
|
|
3338
|
+
"ENOTCONN",
|
|
3339
|
+
"EHOSTUNREACH",
|
|
3340
|
+
"ECONNABORTED"
|
|
3341
|
+
]);
|
|
3342
|
+
var StaleDesktopControlDescriptorError = class extends Error {
|
|
3343
|
+
name = "StaleDesktopControlDescriptorError";
|
|
3344
|
+
reason;
|
|
3345
|
+
descriptorPath;
|
|
3346
|
+
pid;
|
|
3347
|
+
cleanedUp;
|
|
3348
|
+
cause;
|
|
3349
|
+
constructor(details) {
|
|
3350
|
+
const subject = details.pid !== null ? `process ${details.pid}` : "the publishing process";
|
|
3351
|
+
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`;
|
|
3352
|
+
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.";
|
|
3353
|
+
super(
|
|
3354
|
+
`CCEM Desktop control descriptor at ${details.descriptorPath} is stale: ${symptom}. ${remedy}`
|
|
3355
|
+
);
|
|
3356
|
+
this.reason = details.reason;
|
|
3357
|
+
this.descriptorPath = details.descriptorPath;
|
|
3358
|
+
this.pid = details.pid;
|
|
3359
|
+
this.cleanedUp = details.cleanedUp;
|
|
3360
|
+
if (details.cause !== void 0) {
|
|
3361
|
+
this.cause = details.cause;
|
|
3362
|
+
}
|
|
3363
|
+
}
|
|
3364
|
+
};
|
|
3171
3365
|
function getDesktopControlDescriptorPath() {
|
|
3172
|
-
return process.env.CCEM_CONTROL_FILE?.trim() ||
|
|
3366
|
+
return process.env.CCEM_CONTROL_FILE?.trim() || getDefaultControlDescriptorPath();
|
|
3367
|
+
}
|
|
3368
|
+
function getDefaultControlDescriptorPath() {
|
|
3369
|
+
return path8.join(getCcemConfigDir(), "control.json");
|
|
3370
|
+
}
|
|
3371
|
+
function isDefaultDescriptorPath(descriptorPath) {
|
|
3372
|
+
try {
|
|
3373
|
+
return path8.resolve(descriptorPath) === path8.resolve(getDefaultControlDescriptorPath());
|
|
3374
|
+
} catch {
|
|
3375
|
+
return false;
|
|
3376
|
+
}
|
|
3377
|
+
}
|
|
3378
|
+
function safeRemoveStaleDescriptor(descriptorPath) {
|
|
3379
|
+
if (!isDefaultDescriptorPath(descriptorPath)) {
|
|
3380
|
+
return false;
|
|
3381
|
+
}
|
|
3382
|
+
try {
|
|
3383
|
+
fs10.rmSync(descriptorPath);
|
|
3384
|
+
return true;
|
|
3385
|
+
} catch {
|
|
3386
|
+
return false;
|
|
3387
|
+
}
|
|
3388
|
+
}
|
|
3389
|
+
function readErrnoCode(error) {
|
|
3390
|
+
if (!error || typeof error !== "object") return void 0;
|
|
3391
|
+
const candidate = error;
|
|
3392
|
+
return candidate.code ?? candidate.cause?.code;
|
|
3393
|
+
}
|
|
3394
|
+
function readPid(value) {
|
|
3395
|
+
return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : null;
|
|
3396
|
+
}
|
|
3397
|
+
function isLoopbackHost(host) {
|
|
3398
|
+
const normalized = host.trim().toLowerCase().replace(/^\[|\]$/g, "");
|
|
3399
|
+
if (LOOPBACK_HOSTS.has(normalized)) {
|
|
3400
|
+
return true;
|
|
3401
|
+
}
|
|
3402
|
+
if (/^127\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(normalized)) {
|
|
3403
|
+
return true;
|
|
3404
|
+
}
|
|
3405
|
+
if (normalized === "::1" || normalized === "0:0:0:0:0:0:0:1") {
|
|
3406
|
+
return true;
|
|
3407
|
+
}
|
|
3408
|
+
return false;
|
|
3409
|
+
}
|
|
3410
|
+
function extractHost(endpoint) {
|
|
3411
|
+
const withoutScheme = endpoint.replace(/^[a-zA-Z][a-zA-Z0-9+.-]*:\/\//, "");
|
|
3412
|
+
const hostPort = withoutScheme.split(/[\/?#]/)[0] || "";
|
|
3413
|
+
if (hostPort.startsWith("[")) {
|
|
3414
|
+
const end = hostPort.indexOf("]");
|
|
3415
|
+
if (end === -1) return null;
|
|
3416
|
+
return hostPort.slice(1, end);
|
|
3417
|
+
}
|
|
3418
|
+
const colonIndex = hostPort.lastIndexOf(":");
|
|
3419
|
+
if (colonIndex === -1) return hostPort;
|
|
3420
|
+
return hostPort.slice(0, colonIndex);
|
|
3421
|
+
}
|
|
3422
|
+
function validateLoopbackEndpoint(endpoint) {
|
|
3423
|
+
const host = extractHost(endpoint);
|
|
3424
|
+
if (!host) {
|
|
3425
|
+
throw new Error(
|
|
3426
|
+
`CCEM Desktop control endpoint '${redactEndpoint(endpoint)}' is missing a host. Refusing to continue.`
|
|
3427
|
+
);
|
|
3428
|
+
}
|
|
3429
|
+
if (!isLoopbackHost(host)) {
|
|
3430
|
+
throw new Error(
|
|
3431
|
+
`CCEM Desktop control endpoint '${redactEndpoint(endpoint)}' is not bound to loopback. Only 127.0.0.1, localhost, or ::1 are allowed.`
|
|
3432
|
+
);
|
|
3433
|
+
}
|
|
3434
|
+
}
|
|
3435
|
+
function isPidAlive(pid) {
|
|
3436
|
+
if (!Number.isFinite(pid) || pid <= 0) return false;
|
|
3437
|
+
try {
|
|
3438
|
+
process.kill(pid, 0);
|
|
3439
|
+
return true;
|
|
3440
|
+
} catch (error) {
|
|
3441
|
+
const code = error.code;
|
|
3442
|
+
if (code === "ESRCH") return false;
|
|
3443
|
+
if (code === "EPERM") return true;
|
|
3444
|
+
return false;
|
|
3445
|
+
}
|
|
3446
|
+
}
|
|
3447
|
+
function redactEndpoint(endpoint) {
|
|
3448
|
+
try {
|
|
3449
|
+
const url = new URL(endpoint);
|
|
3450
|
+
return `${url.protocol}//${url.host}${url.pathname}`;
|
|
3451
|
+
} catch {
|
|
3452
|
+
return "<invalid endpoint>";
|
|
3453
|
+
}
|
|
3454
|
+
}
|
|
3455
|
+
function isDescriptor(value) {
|
|
3456
|
+
return Boolean(
|
|
3457
|
+
value && typeof value === "object" && typeof value.endpoint === "string" && typeof value.token === "string"
|
|
3458
|
+
);
|
|
3173
3459
|
}
|
|
3174
3460
|
function resolveDesktopControlDescriptor(descriptorPath = getDesktopControlDescriptorPath()) {
|
|
3175
3461
|
if (!fs10.existsSync(descriptorPath)) {
|
|
@@ -3181,27 +3467,93 @@ function resolveDesktopControlDescriptor(descriptorPath = getDesktopControlDescr
|
|
|
3181
3467
|
if (!endpoint || !token) {
|
|
3182
3468
|
throw new Error(`Invalid CCEM Desktop control descriptor at ${descriptorPath}`);
|
|
3183
3469
|
}
|
|
3184
|
-
|
|
3185
|
-
|
|
3186
|
-
|
|
3187
|
-
|
|
3188
|
-
|
|
3470
|
+
validateLoopbackEndpoint(endpoint);
|
|
3471
|
+
const pid = readPid(parsed.pid);
|
|
3472
|
+
if (pid !== null && !isPidAlive(pid)) {
|
|
3473
|
+
const cleanedUp = safeRemoveStaleDescriptor(descriptorPath);
|
|
3474
|
+
throw new StaleDesktopControlDescriptorError({
|
|
3475
|
+
reason: "dead-pid",
|
|
3476
|
+
descriptorPath,
|
|
3477
|
+
pid,
|
|
3478
|
+
cleanedUp
|
|
3479
|
+
});
|
|
3480
|
+
}
|
|
3481
|
+
return { endpoint, token, pid };
|
|
3189
3482
|
}
|
|
3190
|
-
async function requestDesktopControl(method, params,
|
|
3483
|
+
async function requestDesktopControl(method, params, descriptorOrOptions, maybeOptions = {}) {
|
|
3484
|
+
const descriptorPath = getDesktopControlDescriptorPath();
|
|
3485
|
+
const hasInjectedDescriptor = isDescriptor(descriptorOrOptions);
|
|
3486
|
+
const descriptor = hasInjectedDescriptor ? descriptorOrOptions : resolveDesktopControlDescriptor(descriptorPath);
|
|
3487
|
+
const options = hasInjectedDescriptor ? maybeOptions : descriptorOrOptions ?? maybeOptions ?? {};
|
|
3488
|
+
const timeoutMs = options.timeoutMs ?? options.fetchTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
|
|
3489
|
+
const pid = readPid(descriptor.pid);
|
|
3490
|
+
validateLoopbackEndpoint(descriptor.endpoint);
|
|
3491
|
+
if (pid !== null && !isPidAlive(pid)) {
|
|
3492
|
+
const cleanedUp = hasInjectedDescriptor ? false : safeRemoveStaleDescriptor(descriptorPath);
|
|
3493
|
+
throw new StaleDesktopControlDescriptorError({
|
|
3494
|
+
reason: "dead-pid",
|
|
3495
|
+
descriptorPath,
|
|
3496
|
+
pid,
|
|
3497
|
+
cleanedUp
|
|
3498
|
+
});
|
|
3499
|
+
}
|
|
3191
3500
|
const id = `ccem-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
3192
|
-
const
|
|
3193
|
-
|
|
3194
|
-
|
|
3195
|
-
|
|
3196
|
-
|
|
3197
|
-
|
|
3198
|
-
|
|
3199
|
-
|
|
3200
|
-
|
|
3201
|
-
|
|
3202
|
-
|
|
3203
|
-
|
|
3204
|
-
|
|
3501
|
+
const controller = new AbortController();
|
|
3502
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
3503
|
+
const externalSignal = options.signal;
|
|
3504
|
+
const onExternalAbort = () => controller.abort();
|
|
3505
|
+
if (externalSignal) {
|
|
3506
|
+
if (externalSignal.aborted) {
|
|
3507
|
+
clearTimeout(timer);
|
|
3508
|
+
controller.abort();
|
|
3509
|
+
} else {
|
|
3510
|
+
externalSignal.addEventListener("abort", onExternalAbort, { once: true });
|
|
3511
|
+
}
|
|
3512
|
+
}
|
|
3513
|
+
let response;
|
|
3514
|
+
try {
|
|
3515
|
+
response = await fetch(descriptor.endpoint, {
|
|
3516
|
+
method: "POST",
|
|
3517
|
+
headers: {
|
|
3518
|
+
authorization: `Bearer ${descriptor.token}`,
|
|
3519
|
+
"content-type": "application/json"
|
|
3520
|
+
},
|
|
3521
|
+
body: JSON.stringify({
|
|
3522
|
+
jsonrpc: "2.0",
|
|
3523
|
+
id,
|
|
3524
|
+
method,
|
|
3525
|
+
params: params ?? {}
|
|
3526
|
+
}),
|
|
3527
|
+
signal: controller.signal
|
|
3528
|
+
});
|
|
3529
|
+
} catch (error) {
|
|
3530
|
+
if (controller.signal.aborted) {
|
|
3531
|
+
throw new StaleDesktopControlDescriptorError({
|
|
3532
|
+
reason: "request-timeout",
|
|
3533
|
+
descriptorPath,
|
|
3534
|
+
pid,
|
|
3535
|
+
cleanedUp: false,
|
|
3536
|
+
timeoutMs,
|
|
3537
|
+
cause: error
|
|
3538
|
+
});
|
|
3539
|
+
}
|
|
3540
|
+
const code = readErrnoCode(error);
|
|
3541
|
+
if (code && ENDPOINT_UNREACHABLE_CODES.has(code)) {
|
|
3542
|
+
throw new StaleDesktopControlDescriptorError({
|
|
3543
|
+
reason: "endpoint-unreachable",
|
|
3544
|
+
descriptorPath,
|
|
3545
|
+
pid,
|
|
3546
|
+
cleanedUp: false,
|
|
3547
|
+
cause: error
|
|
3548
|
+
});
|
|
3549
|
+
}
|
|
3550
|
+
throw error;
|
|
3551
|
+
} finally {
|
|
3552
|
+
clearTimeout(timer);
|
|
3553
|
+
if (externalSignal) {
|
|
3554
|
+
externalSignal.removeEventListener("abort", onExternalAbort);
|
|
3555
|
+
}
|
|
3556
|
+
}
|
|
3205
3557
|
if (!response.ok) {
|
|
3206
3558
|
throw new Error(`CCEM Desktop control request failed: HTTP ${response.status}`);
|
|
3207
3559
|
}
|
|
@@ -3211,6 +3563,26 @@ async function requestDesktopControl(method, params, descriptor = resolveDesktop
|
|
|
3211
3563
|
}
|
|
3212
3564
|
return payload.result;
|
|
3213
3565
|
}
|
|
3566
|
+
function parseSinceOption(raw) {
|
|
3567
|
+
if (raw === void 0 || raw === null || raw === "") return null;
|
|
3568
|
+
const value = Number(raw);
|
|
3569
|
+
if (!Number.isFinite(value) || value < 0 || !Number.isInteger(value)) {
|
|
3570
|
+
throw new Error(
|
|
3571
|
+
`Invalid --since value '${raw}'. Expected a non-negative integer sequence number (e.g. 0, 42).`
|
|
3572
|
+
);
|
|
3573
|
+
}
|
|
3574
|
+
return value;
|
|
3575
|
+
}
|
|
3576
|
+
function parseLimitOption(raw) {
|
|
3577
|
+
if (raw === void 0 || raw === null || raw === "") return null;
|
|
3578
|
+
const value = Number(raw);
|
|
3579
|
+
if (!Number.isFinite(value) || value <= 0 || !Number.isInteger(value)) {
|
|
3580
|
+
throw new Error(
|
|
3581
|
+
`Invalid --limit value '${raw}'. Expected a positive integer (e.g. 1, 100).`
|
|
3582
|
+
);
|
|
3583
|
+
}
|
|
3584
|
+
return value;
|
|
3585
|
+
}
|
|
3214
3586
|
function printJson(value) {
|
|
3215
3587
|
console.log(JSON.stringify(value, null, 2));
|
|
3216
3588
|
}
|
|
@@ -3574,12 +3946,12 @@ var findAttachSession = (id) => {
|
|
|
3574
3946
|
};
|
|
3575
3947
|
var attachTmuxTarget = (target) => {
|
|
3576
3948
|
const args = process.env.TMUX ? ["switch-client", "-t", target] : ["attach-session", "-t", target];
|
|
3577
|
-
return new Promise((
|
|
3949
|
+
return new Promise((resolve4, reject) => {
|
|
3578
3950
|
const child = spawn3("tmux", args, { stdio: "inherit" });
|
|
3579
3951
|
child.on("error", reject);
|
|
3580
3952
|
child.on("exit", (code) => {
|
|
3581
3953
|
if (code === 0 || code === null) {
|
|
3582
|
-
|
|
3954
|
+
resolve4();
|
|
3583
3955
|
} else {
|
|
3584
3956
|
reject(new Error(`tmux exited with code ${code}`));
|
|
3585
3957
|
}
|
|
@@ -4192,10 +4564,12 @@ desktopCmd.command("status <runtimeId>").description("Get one CCEM Desktop works
|
|
|
4192
4564
|
});
|
|
4193
4565
|
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
4566
|
const opts = this.opts();
|
|
4567
|
+
const sinceSeq = parseSinceOption(opts.since);
|
|
4568
|
+
const limit = parseLimitOption(opts.limit);
|
|
4195
4569
|
const result = await requestDesktopControl("ccem.workspace.getEvents", {
|
|
4196
4570
|
runtimeId,
|
|
4197
|
-
sinceSeq
|
|
4198
|
-
limit
|
|
4571
|
+
sinceSeq,
|
|
4572
|
+
limit
|
|
4199
4573
|
});
|
|
4200
4574
|
outputDesktopResult(result, opts);
|
|
4201
4575
|
});
|
|
@@ -4296,11 +4670,11 @@ Editing environment '${result.name}'`));
|
|
|
4296
4670
|
registries[result.name] = applyPromptAnswers(envToEdit, answers, true);
|
|
4297
4671
|
setRegistries(registries);
|
|
4298
4672
|
msg.success(`Environment '${result.name}' updated.`);
|
|
4299
|
-
await new Promise((
|
|
4673
|
+
await new Promise((resolve4) => setTimeout(resolve4, 800));
|
|
4300
4674
|
} else if (result.action === "rename") {
|
|
4301
4675
|
if (result.name === "official") {
|
|
4302
4676
|
msg.error("Cannot rename default 'official' environment.");
|
|
4303
|
-
await new Promise((
|
|
4677
|
+
await new Promise((resolve4) => setTimeout(resolve4, 800));
|
|
4304
4678
|
} else {
|
|
4305
4679
|
const { newName } = await inquirer.prompt([
|
|
4306
4680
|
{
|
|
@@ -4321,7 +4695,7 @@ Editing environment '${result.name}'`));
|
|
|
4321
4695
|
config2.set("current", newName);
|
|
4322
4696
|
}
|
|
4323
4697
|
msg.success(`Environment '${result.name}' renamed to '${newName}'.`);
|
|
4324
|
-
await new Promise((
|
|
4698
|
+
await new Promise((resolve4) => setTimeout(resolve4, 800));
|
|
4325
4699
|
}
|
|
4326
4700
|
} else if (result.action === "copy") {
|
|
4327
4701
|
const { targetName } = await inquirer.prompt([
|
|
@@ -4354,11 +4728,11 @@ Editing environment '${result.name}'`));
|
|
|
4354
4728
|
setRegistries(registries);
|
|
4355
4729
|
msg.success(`Environment '${targetName}' updated.`);
|
|
4356
4730
|
}
|
|
4357
|
-
await new Promise((
|
|
4731
|
+
await new Promise((resolve4) => setTimeout(resolve4, 800));
|
|
4358
4732
|
} else if (result.action === "delete") {
|
|
4359
4733
|
if (result.name === "official") {
|
|
4360
4734
|
msg.error("Cannot delete default 'official' environment.");
|
|
4361
|
-
await new Promise((
|
|
4735
|
+
await new Promise((resolve4) => setTimeout(resolve4, 800));
|
|
4362
4736
|
} else {
|
|
4363
4737
|
const { confirm } = await inquirer.prompt([
|
|
4364
4738
|
{
|
|
@@ -4378,7 +4752,7 @@ Editing environment '${result.name}'`));
|
|
|
4378
4752
|
msg.success(`Environment '${result.name}' deleted.`);
|
|
4379
4753
|
}
|
|
4380
4754
|
}
|
|
4381
|
-
await new Promise((
|
|
4755
|
+
await new Promise((resolve4) => setTimeout(resolve4, 800));
|
|
4382
4756
|
}
|
|
4383
4757
|
}
|
|
4384
4758
|
} else if (action === "perm") {
|
|
@@ -4409,15 +4783,27 @@ Editing environment '${result.name}'`));
|
|
|
4409
4783
|
if (selectedMode === "clear") {
|
|
4410
4784
|
config2.set("defaultMode", null);
|
|
4411
4785
|
msg.success("Default mode cleared");
|
|
4412
|
-
await new Promise((
|
|
4786
|
+
await new Promise((resolve4) => setTimeout(resolve4, 800));
|
|
4413
4787
|
} else if (selectedMode !== "back") {
|
|
4414
4788
|
config2.set("defaultMode", selectedMode);
|
|
4415
4789
|
msg.success(`Default mode set: ${PERMISSION_PRESETS[selectedMode].name}`);
|
|
4416
|
-
await new Promise((
|
|
4790
|
+
await new Promise((resolve4) => setTimeout(resolve4, 800));
|
|
4417
4791
|
}
|
|
4418
4792
|
} else {
|
|
4419
4793
|
process.exit(0);
|
|
4420
4794
|
}
|
|
4421
4795
|
}
|
|
4422
4796
|
});
|
|
4423
|
-
|
|
4797
|
+
function handleCliError(error) {
|
|
4798
|
+
if (error instanceof StaleDesktopControlDescriptorError) {
|
|
4799
|
+
console.error(chalk7.red(error.message));
|
|
4800
|
+
process.exit(1);
|
|
4801
|
+
}
|
|
4802
|
+
if (error instanceof Error) {
|
|
4803
|
+
console.error(chalk7.red(error.message));
|
|
4804
|
+
process.exit(1);
|
|
4805
|
+
}
|
|
4806
|
+
console.error(chalk7.red(String(error)));
|
|
4807
|
+
process.exit(1);
|
|
4808
|
+
}
|
|
4809
|
+
await program.parseAsync(process.argv).catch(handleCliError);
|