ccem 2.0.0-beta.3 → 2.0.0-beta.30
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 +914 -267
- package/model-prices.json +12 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -7,31 +7,127 @@ 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 fs9 from "fs";
|
|
11
|
+
import * as path7 from "path";
|
|
12
12
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
13
13
|
|
|
14
|
-
// ../../packages/core/dist/chunk-
|
|
14
|
+
// ../../packages/core/dist/chunk-KHQOO3XJ.js
|
|
15
|
+
var TIER_MODEL_ALIASES = /* @__PURE__ */ new Set(["opus", "sonnet", "haiku"]);
|
|
16
|
+
function normalizeEnvConfig(envConfig, defaultRuntimeModel = "opus") {
|
|
17
|
+
const hasTierDefaults = Boolean(envConfig.ANTHROPIC_DEFAULT_OPUS_MODEL) || Boolean(envConfig.ANTHROPIC_DEFAULT_SONNET_MODEL) || Boolean(envConfig.ANTHROPIC_DEFAULT_HAIKU_MODEL);
|
|
18
|
+
const defaultOpusModel = envConfig.ANTHROPIC_DEFAULT_OPUS_MODEL ?? (hasTierDefaults ? void 0 : envConfig.ANTHROPIC_MODEL);
|
|
19
|
+
const defaultSonnetModel = envConfig.ANTHROPIC_DEFAULT_SONNET_MODEL ?? defaultOpusModel ?? (hasTierDefaults ? void 0 : envConfig.ANTHROPIC_MODEL);
|
|
20
|
+
const defaultHaikuModel = envConfig.ANTHROPIC_DEFAULT_HAIKU_MODEL ?? envConfig.ANTHROPIC_SMALL_FAST_MODEL;
|
|
21
|
+
return {
|
|
22
|
+
...envConfig.ANTHROPIC_BASE_URL && {
|
|
23
|
+
ANTHROPIC_BASE_URL: envConfig.ANTHROPIC_BASE_URL
|
|
24
|
+
},
|
|
25
|
+
...(envConfig.ANTHROPIC_AUTH_TOKEN ?? envConfig.ANTHROPIC_API_KEY) && {
|
|
26
|
+
ANTHROPIC_AUTH_TOKEN: envConfig.ANTHROPIC_AUTH_TOKEN ?? envConfig.ANTHROPIC_API_KEY
|
|
27
|
+
},
|
|
28
|
+
...defaultOpusModel && {
|
|
29
|
+
ANTHROPIC_DEFAULT_OPUS_MODEL: defaultOpusModel
|
|
30
|
+
},
|
|
31
|
+
...defaultSonnetModel && {
|
|
32
|
+
ANTHROPIC_DEFAULT_SONNET_MODEL: defaultSonnetModel
|
|
33
|
+
},
|
|
34
|
+
...defaultHaikuModel && {
|
|
35
|
+
ANTHROPIC_DEFAULT_HAIKU_MODEL: defaultHaikuModel
|
|
36
|
+
},
|
|
37
|
+
ANTHROPIC_MODEL: hasTierDefaults ? envConfig.ANTHROPIC_MODEL ?? defaultRuntimeModel : defaultRuntimeModel,
|
|
38
|
+
...envConfig.CLAUDE_CODE_SUBAGENT_MODEL && {
|
|
39
|
+
CLAUDE_CODE_SUBAGENT_MODEL: envConfig.CLAUDE_CODE_SUBAGENT_MODEL
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
function shouldRecoverTierModel(model) {
|
|
44
|
+
return !model || TIER_MODEL_ALIASES.has(model);
|
|
45
|
+
}
|
|
46
|
+
function recoverEnvConfigFromLegacy(currentEnvConfig, legacyEnvConfig) {
|
|
47
|
+
const current = normalizeEnvConfig(currentEnvConfig);
|
|
48
|
+
const legacy = normalizeEnvConfig(legacyEnvConfig);
|
|
49
|
+
return {
|
|
50
|
+
...current,
|
|
51
|
+
...!current.ANTHROPIC_AUTH_TOKEN && legacy.ANTHROPIC_AUTH_TOKEN && {
|
|
52
|
+
ANTHROPIC_AUTH_TOKEN: legacy.ANTHROPIC_AUTH_TOKEN
|
|
53
|
+
},
|
|
54
|
+
...shouldRecoverTierModel(current.ANTHROPIC_DEFAULT_OPUS_MODEL) && legacy.ANTHROPIC_DEFAULT_OPUS_MODEL && {
|
|
55
|
+
ANTHROPIC_DEFAULT_OPUS_MODEL: legacy.ANTHROPIC_DEFAULT_OPUS_MODEL
|
|
56
|
+
},
|
|
57
|
+
...shouldRecoverTierModel(current.ANTHROPIC_DEFAULT_SONNET_MODEL) && legacy.ANTHROPIC_DEFAULT_SONNET_MODEL && {
|
|
58
|
+
ANTHROPIC_DEFAULT_SONNET_MODEL: legacy.ANTHROPIC_DEFAULT_SONNET_MODEL
|
|
59
|
+
},
|
|
60
|
+
...shouldRecoverTierModel(current.ANTHROPIC_DEFAULT_HAIKU_MODEL) && legacy.ANTHROPIC_DEFAULT_HAIKU_MODEL && {
|
|
61
|
+
ANTHROPIC_DEFAULT_HAIKU_MODEL: legacy.ANTHROPIC_DEFAULT_HAIKU_MODEL
|
|
62
|
+
},
|
|
63
|
+
...!current.CLAUDE_CODE_SUBAGENT_MODEL && legacy.CLAUDE_CODE_SUBAGENT_MODEL && {
|
|
64
|
+
CLAUDE_CODE_SUBAGENT_MODEL: legacy.CLAUDE_CODE_SUBAGENT_MODEL
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
}
|
|
15
68
|
var ENV_PRESETS = {
|
|
16
69
|
"GLM": {
|
|
17
70
|
ANTHROPIC_BASE_URL: "https://open.bigmodel.cn/api/anthropic",
|
|
18
|
-
|
|
19
|
-
|
|
71
|
+
ANTHROPIC_DEFAULT_OPUS_MODEL: "glm-5.1",
|
|
72
|
+
ANTHROPIC_DEFAULT_SONNET_MODEL: "glm-5.1",
|
|
73
|
+
ANTHROPIC_DEFAULT_HAIKU_MODEL: "glm-4.5-air",
|
|
74
|
+
ANTHROPIC_MODEL: "opus"
|
|
20
75
|
},
|
|
21
76
|
"KIMI": {
|
|
22
77
|
ANTHROPIC_BASE_URL: "https://api.moonshot.cn/anthropic",
|
|
23
|
-
|
|
24
|
-
|
|
78
|
+
ANTHROPIC_DEFAULT_OPUS_MODEL: "kimi-k2.5",
|
|
79
|
+
ANTHROPIC_DEFAULT_SONNET_MODEL: "kimi-k2.5",
|
|
80
|
+
ANTHROPIC_DEFAULT_HAIKU_MODEL: "kimi-k2.5",
|
|
81
|
+
ANTHROPIC_MODEL: "opus"
|
|
82
|
+
},
|
|
83
|
+
"KimiCodePlan": {
|
|
84
|
+
ANTHROPIC_BASE_URL: "https://api.kimi.com/coding/",
|
|
85
|
+
ANTHROPIC_DEFAULT_OPUS_MODEL: "kimi-for-coding",
|
|
86
|
+
ANTHROPIC_DEFAULT_SONNET_MODEL: "kimi-for-coding",
|
|
87
|
+
ANTHROPIC_DEFAULT_HAIKU_MODEL: "kimi-for-coding",
|
|
88
|
+
ANTHROPIC_MODEL: "opus"
|
|
25
89
|
},
|
|
26
90
|
"MiniMax": {
|
|
27
91
|
ANTHROPIC_BASE_URL: "https://api.minimaxi.com/anthropic",
|
|
28
|
-
|
|
29
|
-
|
|
92
|
+
ANTHROPIC_DEFAULT_OPUS_MODEL: "MiniMax-M2.7",
|
|
93
|
+
ANTHROPIC_DEFAULT_SONNET_MODEL: "MiniMax-M2.7",
|
|
94
|
+
ANTHROPIC_DEFAULT_HAIKU_MODEL: "MiniMax-M2.7-highspeed",
|
|
95
|
+
ANTHROPIC_MODEL: "opus"
|
|
30
96
|
},
|
|
31
97
|
"DeepSeek": {
|
|
32
98
|
ANTHROPIC_BASE_URL: "https://api.deepseek.com/anthropic",
|
|
33
|
-
|
|
34
|
-
|
|
99
|
+
ANTHROPIC_DEFAULT_OPUS_MODEL: "deepseek-v4-pro",
|
|
100
|
+
ANTHROPIC_DEFAULT_SONNET_MODEL: "deepseek-v4-pro",
|
|
101
|
+
ANTHROPIC_DEFAULT_HAIKU_MODEL: "deepseek-v4-flash",
|
|
102
|
+
ANTHROPIC_MODEL: "opus"
|
|
103
|
+
},
|
|
104
|
+
"Bailian": {
|
|
105
|
+
ANTHROPIC_BASE_URL: "https://dashscope.aliyuncs.com/api/v2/apps/claude-code-proxy",
|
|
106
|
+
ANTHROPIC_DEFAULT_OPUS_MODEL: "qwen3-coder-next",
|
|
107
|
+
ANTHROPIC_DEFAULT_SONNET_MODEL: "qwen3-coder-plus",
|
|
108
|
+
ANTHROPIC_DEFAULT_HAIKU_MODEL: "qwen3-coder-flash",
|
|
109
|
+
ANTHROPIC_MODEL: "opus"
|
|
110
|
+
},
|
|
111
|
+
"BailianCodePlan": {
|
|
112
|
+
ANTHROPIC_BASE_URL: "https://coding.dashscope.aliyuncs.com/api/v2/apps/claude-code-proxy",
|
|
113
|
+
ANTHROPIC_DEFAULT_OPUS_MODEL: "qwen3-coder-next",
|
|
114
|
+
ANTHROPIC_DEFAULT_SONNET_MODEL: "qwen3-coder-plus",
|
|
115
|
+
ANTHROPIC_DEFAULT_HAIKU_MODEL: "qwen3-coder-flash",
|
|
116
|
+
ANTHROPIC_MODEL: "opus"
|
|
117
|
+
},
|
|
118
|
+
"OpenRouter": {
|
|
119
|
+
ANTHROPIC_BASE_URL: "https://openrouter.ai/api/v1",
|
|
120
|
+
ANTHROPIC_DEFAULT_OPUS_MODEL: "anthropic/claude-opus-4.6",
|
|
121
|
+
ANTHROPIC_DEFAULT_SONNET_MODEL: "anthropic/claude-sonnet-4.6",
|
|
122
|
+
ANTHROPIC_DEFAULT_HAIKU_MODEL: "anthropic/claude-haiku-4.5",
|
|
123
|
+
ANTHROPIC_MODEL: "opus"
|
|
124
|
+
},
|
|
125
|
+
"Ollama": {
|
|
126
|
+
ANTHROPIC_BASE_URL: "http://localhost:11434",
|
|
127
|
+
ANTHROPIC_DEFAULT_OPUS_MODEL: "gemma4:31b",
|
|
128
|
+
ANTHROPIC_DEFAULT_SONNET_MODEL: "gemma4:26b",
|
|
129
|
+
ANTHROPIC_DEFAULT_HAIKU_MODEL: "gemma4:e4b",
|
|
130
|
+
ANTHROPIC_MODEL: "opus"
|
|
35
131
|
}
|
|
36
132
|
};
|
|
37
133
|
var PERMISSION_PRESETS = {
|
|
@@ -814,9 +910,10 @@ var renderLogoWithEnvPanel = (envName, env, defaultMode) => {
|
|
|
814
910
|
const titleShort = theme.primary("CCEM");
|
|
815
911
|
const envLabel = theme.muted("Env: ") + theme.primary(envName);
|
|
816
912
|
const baseUrl = env.ANTHROPIC_BASE_URL || "-";
|
|
817
|
-
const
|
|
818
|
-
const
|
|
819
|
-
const
|
|
913
|
+
const runtimeModel = env.ANTHROPIC_MODEL || "-";
|
|
914
|
+
const opusModel = env.ANTHROPIC_DEFAULT_OPUS_MODEL || "-";
|
|
915
|
+
const haikuModel = env.ANTHROPIC_DEFAULT_HAIKU_MODEL || "-";
|
|
916
|
+
const authToken = env.ANTHROPIC_AUTH_TOKEN ? env.ANTHROPIC_AUTH_TOKEN.slice(0, 2) + "\u2022\u2022\u2022\u2022" + env.ANTHROPIC_AUTH_TOKEN.slice(-4) : "-";
|
|
820
917
|
const truncate = (s, max) => s.length > max ? s.slice(0, max - 3) + "..." : s;
|
|
821
918
|
const maskUrl = (url, max) => {
|
|
822
919
|
if (url.length <= max) return url;
|
|
@@ -824,10 +921,10 @@ var renderLogoWithEnvPanel = (envName, env, defaultMode) => {
|
|
|
824
921
|
const parsed = new URL(url);
|
|
825
922
|
const protocol = parsed.protocol + "//";
|
|
826
923
|
const host = parsed.host;
|
|
827
|
-
const
|
|
924
|
+
const path8 = parsed.pathname + parsed.search;
|
|
828
925
|
const hostStart = host.slice(0, 8);
|
|
829
926
|
const hostEnd = host.slice(-4);
|
|
830
|
-
const pathPart =
|
|
927
|
+
const pathPart = path8.length > 10 ? path8.slice(0, 7) + "..." : path8;
|
|
831
928
|
return `${protocol}${hostStart}...${hostEnd}${pathPart}`;
|
|
832
929
|
} catch {
|
|
833
930
|
return truncate(url, max);
|
|
@@ -838,15 +935,15 @@ var renderLogoWithEnvPanel = (envName, env, defaultMode) => {
|
|
|
838
935
|
if (isNarrow) {
|
|
839
936
|
envLines = [
|
|
840
937
|
envLabel,
|
|
841
|
-
theme.muted("
|
|
842
|
-
theme.muted("
|
|
938
|
+
theme.muted("Opus:".padEnd(labelWidth)) + theme.dim(truncate(opusModel, 25)),
|
|
939
|
+
theme.muted("Token:".padEnd(labelWidth)) + theme.dim(authToken)
|
|
843
940
|
];
|
|
844
941
|
} else {
|
|
845
942
|
envLines = [
|
|
846
943
|
envLabel + (defaultMode && PERMISSION_PRESETS[defaultMode] ? " " + theme.accent(`[${PERMISSION_PRESETS[defaultMode].name}]`) : ""),
|
|
847
944
|
theme.muted("URL:".padEnd(labelWidth)) + theme.dim(maskUrl(baseUrl, 40)),
|
|
848
|
-
theme.muted("
|
|
849
|
-
theme.muted("
|
|
945
|
+
theme.muted("Run:".padEnd(labelWidth)) + theme.dim(truncate(runtimeModel, 12)) + " " + theme.muted("Opus:".padEnd(labelWidth)) + theme.dim(truncate(opusModel, 15)),
|
|
946
|
+
theme.muted("Haiku:".padEnd(labelWidth)) + theme.dim(truncate(haikuModel, 15)) + " " + theme.muted("Token:".padEnd(labelWidth)) + theme.dim(authToken)
|
|
850
947
|
];
|
|
851
948
|
}
|
|
852
949
|
const lines = [];
|
|
@@ -1265,7 +1362,7 @@ var selectEnvWithKeys = (registries, current) => {
|
|
|
1265
1362
|
};
|
|
1266
1363
|
|
|
1267
1364
|
// src/permissions.ts
|
|
1268
|
-
import
|
|
1365
|
+
import fs6 from "fs";
|
|
1269
1366
|
import chalk3 from "chalk";
|
|
1270
1367
|
import Table2 from "cli-table3";
|
|
1271
1368
|
|
|
@@ -1318,15 +1415,367 @@ var ensureGlobalClaudeDir = () => {
|
|
|
1318
1415
|
|
|
1319
1416
|
// src/launcher.ts
|
|
1320
1417
|
import { spawn } from "child_process";
|
|
1321
|
-
import * as
|
|
1322
|
-
import * as
|
|
1418
|
+
import * as fs5 from "fs";
|
|
1419
|
+
import * as path5 from "path";
|
|
1323
1420
|
import chalk2 from "chalk";
|
|
1421
|
+
|
|
1422
|
+
// src/sessionProvenance.ts
|
|
1423
|
+
import { execFileSync } from "child_process";
|
|
1424
|
+
import fs4 from "fs";
|
|
1425
|
+
import { createRequire } from "module";
|
|
1426
|
+
import os2 from "os";
|
|
1427
|
+
import path4 from "path";
|
|
1428
|
+
var DEFAULT_CONFIG_SOURCE = "ccem";
|
|
1429
|
+
var STATE_DB_FILE_NAME = "state.sqlite";
|
|
1430
|
+
var BIND_POLL_INTERVAL_MS = 500;
|
|
1431
|
+
var BIND_POLL_TIMEOUT_MS = 2e4;
|
|
1432
|
+
var SQLITE_EXPERIMENTAL_WARNING = "SQLite is an experimental feature";
|
|
1433
|
+
var require2 = createRequire(import.meta.url);
|
|
1434
|
+
var databaseSyncCtor = null;
|
|
1435
|
+
var databaseSyncResolved = false;
|
|
1436
|
+
var sqlite3CliAvailable = null;
|
|
1437
|
+
function startCliClaudeProvenanceTracking(options) {
|
|
1438
|
+
const envName = normalizeText(options.envName) ?? "unknown";
|
|
1439
|
+
const workingDir = normalizeText(options.workingDir);
|
|
1440
|
+
if (!workingDir) {
|
|
1441
|
+
return null;
|
|
1442
|
+
}
|
|
1443
|
+
const ccemSessionId = `cli-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
1444
|
+
try {
|
|
1445
|
+
registerLaunch({
|
|
1446
|
+
ccemSessionId,
|
|
1447
|
+
client: "claude",
|
|
1448
|
+
envName,
|
|
1449
|
+
configSource: DEFAULT_CONFIG_SOURCE,
|
|
1450
|
+
workingDir,
|
|
1451
|
+
permMode: normalizeText(options.permMode),
|
|
1452
|
+
launchMode: "cli_external",
|
|
1453
|
+
startedVia: "cli",
|
|
1454
|
+
sourceSessionId: normalizeText(options.resumeSessionId)
|
|
1455
|
+
});
|
|
1456
|
+
} catch (error) {
|
|
1457
|
+
reportTrackingError("register launch", error);
|
|
1458
|
+
return null;
|
|
1459
|
+
}
|
|
1460
|
+
let timer = null;
|
|
1461
|
+
const stop = () => {
|
|
1462
|
+
if (timer) {
|
|
1463
|
+
clearInterval(timer);
|
|
1464
|
+
timer = null;
|
|
1465
|
+
}
|
|
1466
|
+
};
|
|
1467
|
+
const resumeSessionId = normalizeText(options.resumeSessionId);
|
|
1468
|
+
if (resumeSessionId) {
|
|
1469
|
+
return { ccemSessionId, stop };
|
|
1470
|
+
}
|
|
1471
|
+
const startedAtMs = Date.now();
|
|
1472
|
+
timer = setInterval(() => {
|
|
1473
|
+
if (Date.now() - startedAtMs > BIND_POLL_TIMEOUT_MS) {
|
|
1474
|
+
stop();
|
|
1475
|
+
return;
|
|
1476
|
+
}
|
|
1477
|
+
try {
|
|
1478
|
+
const jsonlPath = discoverClaudeJsonlPath(workingDir, startedAtMs);
|
|
1479
|
+
if (!jsonlPath) {
|
|
1480
|
+
return;
|
|
1481
|
+
}
|
|
1482
|
+
const sourceSessionId = readClaudeSessionId(jsonlPath) ?? normalizeText(path4.parse(jsonlPath).name);
|
|
1483
|
+
if (!sourceSessionId) {
|
|
1484
|
+
return;
|
|
1485
|
+
}
|
|
1486
|
+
bindSourceSessionId("claude", ccemSessionId, sourceSessionId);
|
|
1487
|
+
stop();
|
|
1488
|
+
} catch (error) {
|
|
1489
|
+
reportTrackingError("bind source session id", error);
|
|
1490
|
+
stop();
|
|
1491
|
+
}
|
|
1492
|
+
}, BIND_POLL_INTERVAL_MS);
|
|
1493
|
+
return { ccemSessionId, stop };
|
|
1494
|
+
}
|
|
1495
|
+
function registerLaunch(options) {
|
|
1496
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1497
|
+
const DatabaseSync = getDatabaseSyncCtor();
|
|
1498
|
+
if (DatabaseSync) {
|
|
1499
|
+
const db = openNodeSqliteDb(DatabaseSync);
|
|
1500
|
+
try {
|
|
1501
|
+
const statement = db.prepare(`
|
|
1502
|
+
INSERT INTO session_provenance (
|
|
1503
|
+
ccem_session_id,
|
|
1504
|
+
client,
|
|
1505
|
+
env_name,
|
|
1506
|
+
config_source,
|
|
1507
|
+
working_dir,
|
|
1508
|
+
perm_mode,
|
|
1509
|
+
launch_mode,
|
|
1510
|
+
started_via,
|
|
1511
|
+
source_session_id,
|
|
1512
|
+
created_at,
|
|
1513
|
+
updated_at
|
|
1514
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
1515
|
+
ON CONFLICT(ccem_session_id) DO UPDATE SET
|
|
1516
|
+
client = excluded.client,
|
|
1517
|
+
env_name = excluded.env_name,
|
|
1518
|
+
config_source = COALESCE(excluded.config_source, session_provenance.config_source),
|
|
1519
|
+
working_dir = excluded.working_dir,
|
|
1520
|
+
perm_mode = COALESCE(excluded.perm_mode, session_provenance.perm_mode),
|
|
1521
|
+
launch_mode = excluded.launch_mode,
|
|
1522
|
+
started_via = excluded.started_via,
|
|
1523
|
+
source_session_id = COALESCE(excluded.source_session_id, session_provenance.source_session_id),
|
|
1524
|
+
updated_at = excluded.updated_at
|
|
1525
|
+
`);
|
|
1526
|
+
statement.run(
|
|
1527
|
+
options.ccemSessionId,
|
|
1528
|
+
options.client,
|
|
1529
|
+
options.envName,
|
|
1530
|
+
normalizeText(options.configSource),
|
|
1531
|
+
options.workingDir,
|
|
1532
|
+
normalizeText(options.permMode),
|
|
1533
|
+
options.launchMode,
|
|
1534
|
+
options.startedVia,
|
|
1535
|
+
normalizeText(options.sourceSessionId),
|
|
1536
|
+
now,
|
|
1537
|
+
now
|
|
1538
|
+
);
|
|
1539
|
+
return;
|
|
1540
|
+
} finally {
|
|
1541
|
+
db.close();
|
|
1542
|
+
}
|
|
1543
|
+
}
|
|
1544
|
+
execSqlite3Cli(`
|
|
1545
|
+
INSERT INTO session_provenance (
|
|
1546
|
+
ccem_session_id,
|
|
1547
|
+
client,
|
|
1548
|
+
env_name,
|
|
1549
|
+
config_source,
|
|
1550
|
+
working_dir,
|
|
1551
|
+
perm_mode,
|
|
1552
|
+
launch_mode,
|
|
1553
|
+
started_via,
|
|
1554
|
+
source_session_id,
|
|
1555
|
+
created_at,
|
|
1556
|
+
updated_at
|
|
1557
|
+
) VALUES (
|
|
1558
|
+
${sqliteLiteral(options.ccemSessionId)},
|
|
1559
|
+
${sqliteLiteral(options.client)},
|
|
1560
|
+
${sqliteLiteral(options.envName)},
|
|
1561
|
+
${sqliteLiteral(normalizeText(options.configSource))},
|
|
1562
|
+
${sqliteLiteral(options.workingDir)},
|
|
1563
|
+
${sqliteLiteral(normalizeText(options.permMode))},
|
|
1564
|
+
${sqliteLiteral(options.launchMode)},
|
|
1565
|
+
${sqliteLiteral(options.startedVia)},
|
|
1566
|
+
${sqliteLiteral(normalizeText(options.sourceSessionId))},
|
|
1567
|
+
${sqliteLiteral(now)},
|
|
1568
|
+
${sqliteLiteral(now)}
|
|
1569
|
+
)
|
|
1570
|
+
ON CONFLICT(ccem_session_id) DO UPDATE SET
|
|
1571
|
+
client = excluded.client,
|
|
1572
|
+
env_name = excluded.env_name,
|
|
1573
|
+
config_source = COALESCE(excluded.config_source, session_provenance.config_source),
|
|
1574
|
+
working_dir = excluded.working_dir,
|
|
1575
|
+
perm_mode = COALESCE(excluded.perm_mode, session_provenance.perm_mode),
|
|
1576
|
+
launch_mode = excluded.launch_mode,
|
|
1577
|
+
started_via = excluded.started_via,
|
|
1578
|
+
source_session_id = COALESCE(excluded.source_session_id, session_provenance.source_session_id),
|
|
1579
|
+
updated_at = excluded.updated_at;
|
|
1580
|
+
`);
|
|
1581
|
+
}
|
|
1582
|
+
function bindSourceSessionId(client, ccemSessionId, sourceSessionId) {
|
|
1583
|
+
const updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1584
|
+
const DatabaseSync = getDatabaseSyncCtor();
|
|
1585
|
+
if (DatabaseSync) {
|
|
1586
|
+
const db = openNodeSqliteDb(DatabaseSync);
|
|
1587
|
+
try {
|
|
1588
|
+
const statement = db.prepare(`
|
|
1589
|
+
UPDATE session_provenance
|
|
1590
|
+
SET source_session_id = ?, updated_at = ?
|
|
1591
|
+
WHERE ccem_session_id = ? AND client = ?
|
|
1592
|
+
`);
|
|
1593
|
+
statement.run(sourceSessionId, updatedAt, ccemSessionId, client);
|
|
1594
|
+
return;
|
|
1595
|
+
} finally {
|
|
1596
|
+
db.close();
|
|
1597
|
+
}
|
|
1598
|
+
}
|
|
1599
|
+
execSqlite3Cli(`
|
|
1600
|
+
UPDATE session_provenance
|
|
1601
|
+
SET source_session_id = ${sqliteLiteral(sourceSessionId)},
|
|
1602
|
+
updated_at = ${sqliteLiteral(updatedAt)}
|
|
1603
|
+
WHERE ccem_session_id = ${sqliteLiteral(ccemSessionId)}
|
|
1604
|
+
AND client = ${sqliteLiteral(client)};
|
|
1605
|
+
`);
|
|
1606
|
+
}
|
|
1607
|
+
function openNodeSqliteDb(DatabaseSync) {
|
|
1608
|
+
const dbPath = getStateDbPath();
|
|
1609
|
+
fs4.mkdirSync(path4.dirname(dbPath), { recursive: true });
|
|
1610
|
+
const db = new DatabaseSync(dbPath);
|
|
1611
|
+
db.exec(schemaSql());
|
|
1612
|
+
return db;
|
|
1613
|
+
}
|
|
1614
|
+
function getDatabaseSyncCtor() {
|
|
1615
|
+
if (databaseSyncResolved) {
|
|
1616
|
+
return databaseSyncCtor;
|
|
1617
|
+
}
|
|
1618
|
+
const originalEmitWarning = process.emitWarning;
|
|
1619
|
+
process.emitWarning = ((warning, ...args) => {
|
|
1620
|
+
const message = typeof warning === "string" ? warning : warning instanceof Error ? warning.message : String(warning);
|
|
1621
|
+
if (message.includes(SQLITE_EXPERIMENTAL_WARNING)) {
|
|
1622
|
+
return;
|
|
1623
|
+
}
|
|
1624
|
+
return originalEmitWarning(warning, ...args);
|
|
1625
|
+
});
|
|
1626
|
+
try {
|
|
1627
|
+
const loaded = require2("node:sqlite");
|
|
1628
|
+
databaseSyncCtor = loaded.DatabaseSync;
|
|
1629
|
+
} catch {
|
|
1630
|
+
databaseSyncCtor = null;
|
|
1631
|
+
} finally {
|
|
1632
|
+
process.emitWarning = originalEmitWarning;
|
|
1633
|
+
databaseSyncResolved = true;
|
|
1634
|
+
}
|
|
1635
|
+
return databaseSyncCtor;
|
|
1636
|
+
}
|
|
1637
|
+
function execSqlite3Cli(statementSql) {
|
|
1638
|
+
if (sqlite3CliAvailable == null) {
|
|
1639
|
+
try {
|
|
1640
|
+
execFileSync("sqlite3", ["-version"], { stdio: "ignore" });
|
|
1641
|
+
sqlite3CliAvailable = true;
|
|
1642
|
+
} catch {
|
|
1643
|
+
sqlite3CliAvailable = false;
|
|
1644
|
+
}
|
|
1645
|
+
}
|
|
1646
|
+
if (!sqlite3CliAvailable) {
|
|
1647
|
+
throw new Error("sqlite3 CLI is unavailable and node:sqlite is not supported by this Node runtime");
|
|
1648
|
+
}
|
|
1649
|
+
const dbPath = getStateDbPath();
|
|
1650
|
+
fs4.mkdirSync(path4.dirname(dbPath), { recursive: true });
|
|
1651
|
+
execFileSync("sqlite3", [dbPath], {
|
|
1652
|
+
input: `${schemaSql()}
|
|
1653
|
+
${statementSql}
|
|
1654
|
+
`,
|
|
1655
|
+
stdio: ["pipe", "ignore", "pipe"]
|
|
1656
|
+
});
|
|
1657
|
+
}
|
|
1658
|
+
function getStateDbPath() {
|
|
1659
|
+
const override = normalizeText(process.env.CCEM_STATE_DB_PATH);
|
|
1660
|
+
if (override) {
|
|
1661
|
+
return override;
|
|
1662
|
+
}
|
|
1663
|
+
return path4.join(os2.homedir(), ".ccem", STATE_DB_FILE_NAME);
|
|
1664
|
+
}
|
|
1665
|
+
function schemaSql() {
|
|
1666
|
+
return `
|
|
1667
|
+
PRAGMA journal_mode = WAL;
|
|
1668
|
+
PRAGMA synchronous = NORMAL;
|
|
1669
|
+
CREATE TABLE IF NOT EXISTS session_provenance (
|
|
1670
|
+
ccem_session_id TEXT PRIMARY KEY,
|
|
1671
|
+
client TEXT NOT NULL,
|
|
1672
|
+
env_name TEXT NOT NULL,
|
|
1673
|
+
config_source TEXT,
|
|
1674
|
+
working_dir TEXT NOT NULL,
|
|
1675
|
+
perm_mode TEXT,
|
|
1676
|
+
launch_mode TEXT NOT NULL,
|
|
1677
|
+
started_via TEXT NOT NULL,
|
|
1678
|
+
source_session_id TEXT,
|
|
1679
|
+
created_at TEXT NOT NULL,
|
|
1680
|
+
updated_at TEXT NOT NULL
|
|
1681
|
+
);
|
|
1682
|
+
CREATE INDEX IF NOT EXISTS idx_session_provenance_client_source
|
|
1683
|
+
ON session_provenance (client, source_session_id);
|
|
1684
|
+
CREATE INDEX IF NOT EXISTS idx_session_provenance_client_updated
|
|
1685
|
+
ON session_provenance (client, updated_at DESC);
|
|
1686
|
+
`;
|
|
1687
|
+
}
|
|
1688
|
+
function discoverClaudeJsonlPath(projectDir, startedAtMs) {
|
|
1689
|
+
const projectsDir = path4.join(os2.homedir(), ".claude", "projects");
|
|
1690
|
+
const earliestModifiedAt = startedAtMs - 15e3;
|
|
1691
|
+
for (const key of projectDirKeys(projectDir)) {
|
|
1692
|
+
const baseDir = path4.join(projectsDir, key);
|
|
1693
|
+
if (!fs4.existsSync(baseDir)) {
|
|
1694
|
+
continue;
|
|
1695
|
+
}
|
|
1696
|
+
const candidates = fs4.readdirSync(baseDir, { withFileTypes: true }).filter((entry) => entry.isFile() && entry.name.endsWith(".jsonl")).map((entry) => {
|
|
1697
|
+
const fullPath = path4.join(baseDir, entry.name);
|
|
1698
|
+
return { path: fullPath, modifiedAt: fs4.statSync(fullPath).mtimeMs };
|
|
1699
|
+
}).filter((candidate) => candidate.modifiedAt >= earliestModifiedAt).sort((left, right) => right.modifiedAt - left.modifiedAt);
|
|
1700
|
+
if (candidates.length > 0) {
|
|
1701
|
+
return candidates[0].path;
|
|
1702
|
+
}
|
|
1703
|
+
}
|
|
1704
|
+
return null;
|
|
1705
|
+
}
|
|
1706
|
+
function projectDirKeys(projectDir) {
|
|
1707
|
+
const candidates = [projectDir];
|
|
1708
|
+
try {
|
|
1709
|
+
candidates.push(fs4.realpathSync(projectDir));
|
|
1710
|
+
} catch {
|
|
1711
|
+
}
|
|
1712
|
+
if (process.platform === "darwin") {
|
|
1713
|
+
if (projectDir.startsWith("/private/")) {
|
|
1714
|
+
candidates.push(projectDir.slice("/private".length));
|
|
1715
|
+
} else if (projectDir.startsWith("/tmp")) {
|
|
1716
|
+
candidates.push(path4.posix.join("/private", projectDir));
|
|
1717
|
+
} else if (projectDir.startsWith("/var/")) {
|
|
1718
|
+
candidates.push(path4.posix.join("/private", projectDir));
|
|
1719
|
+
}
|
|
1720
|
+
}
|
|
1721
|
+
return [...new Set(candidates.map(projectDirKey))];
|
|
1722
|
+
}
|
|
1723
|
+
function projectDirKey(projectDir) {
|
|
1724
|
+
return projectDir.replace(/[\/\\: ]/g, "-");
|
|
1725
|
+
}
|
|
1726
|
+
function readClaudeSessionId(jsonlPath) {
|
|
1727
|
+
const lines = fs4.readFileSync(jsonlPath, "utf8").split("\n");
|
|
1728
|
+
for (const line of lines) {
|
|
1729
|
+
if (!line.trim()) {
|
|
1730
|
+
continue;
|
|
1731
|
+
}
|
|
1732
|
+
try {
|
|
1733
|
+
const value = JSON.parse(line);
|
|
1734
|
+
const sessionId = normalizeText(value.sessionId);
|
|
1735
|
+
if (sessionId) {
|
|
1736
|
+
return sessionId;
|
|
1737
|
+
}
|
|
1738
|
+
} catch {
|
|
1739
|
+
}
|
|
1740
|
+
}
|
|
1741
|
+
return null;
|
|
1742
|
+
}
|
|
1743
|
+
function normalizeText(value) {
|
|
1744
|
+
const trimmed = value?.trim();
|
|
1745
|
+
return trimmed ? trimmed : void 0;
|
|
1746
|
+
}
|
|
1747
|
+
function sqliteLiteral(value) {
|
|
1748
|
+
if (!value) {
|
|
1749
|
+
return "NULL";
|
|
1750
|
+
}
|
|
1751
|
+
return `'${value.replace(/'/g, "''")}'`;
|
|
1752
|
+
}
|
|
1753
|
+
function reportTrackingError(stage, error) {
|
|
1754
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1755
|
+
console.error(`[ccem] Failed to ${stage}: ${message}`);
|
|
1756
|
+
}
|
|
1757
|
+
|
|
1758
|
+
// src/launcher.ts
|
|
1759
|
+
var MANAGED_CLAUDE_ENV_KEYS = [
|
|
1760
|
+
"ANTHROPIC_BASE_URL",
|
|
1761
|
+
"ANTHROPIC_AUTH_TOKEN",
|
|
1762
|
+
"ANTHROPIC_DEFAULT_OPUS_MODEL",
|
|
1763
|
+
"ANTHROPIC_DEFAULT_SONNET_MODEL",
|
|
1764
|
+
"ANTHROPIC_DEFAULT_HAIKU_MODEL",
|
|
1765
|
+
"ANTHROPIC_MODEL",
|
|
1766
|
+
"CLAUDE_CODE_SUBAGENT_MODEL",
|
|
1767
|
+
"ANTHROPIC_API_KEY",
|
|
1768
|
+
"ANTHROPIC_SMALL_FAST_MODEL"
|
|
1769
|
+
];
|
|
1324
1770
|
function buildEnvVars(envConfig) {
|
|
1325
1771
|
const vars = {};
|
|
1326
1772
|
if (envConfig.ANTHROPIC_BASE_URL) vars.ANTHROPIC_BASE_URL = envConfig.ANTHROPIC_BASE_URL;
|
|
1327
|
-
if (envConfig.
|
|
1773
|
+
if (envConfig.ANTHROPIC_AUTH_TOKEN) vars.ANTHROPIC_AUTH_TOKEN = decrypt(envConfig.ANTHROPIC_AUTH_TOKEN);
|
|
1774
|
+
if (envConfig.ANTHROPIC_DEFAULT_OPUS_MODEL) vars.ANTHROPIC_DEFAULT_OPUS_MODEL = envConfig.ANTHROPIC_DEFAULT_OPUS_MODEL;
|
|
1775
|
+
if (envConfig.ANTHROPIC_DEFAULT_SONNET_MODEL) vars.ANTHROPIC_DEFAULT_SONNET_MODEL = envConfig.ANTHROPIC_DEFAULT_SONNET_MODEL;
|
|
1776
|
+
if (envConfig.ANTHROPIC_DEFAULT_HAIKU_MODEL) vars.ANTHROPIC_DEFAULT_HAIKU_MODEL = envConfig.ANTHROPIC_DEFAULT_HAIKU_MODEL;
|
|
1328
1777
|
if (envConfig.ANTHROPIC_MODEL) vars.ANTHROPIC_MODEL = envConfig.ANTHROPIC_MODEL;
|
|
1329
|
-
if (envConfig.
|
|
1778
|
+
if (envConfig.CLAUDE_CODE_SUBAGENT_MODEL) vars.CLAUDE_CODE_SUBAGENT_MODEL = envConfig.CLAUDE_CODE_SUBAGENT_MODEL;
|
|
1330
1779
|
return vars;
|
|
1331
1780
|
}
|
|
1332
1781
|
function buildPermArgs(modeName) {
|
|
@@ -1344,15 +1793,18 @@ function buildPermArgs(modeName) {
|
|
|
1344
1793
|
return args;
|
|
1345
1794
|
}
|
|
1346
1795
|
function ensureSessionsDir() {
|
|
1347
|
-
const dir =
|
|
1348
|
-
if (!
|
|
1349
|
-
|
|
1796
|
+
const dir = path5.join(ensureCcemDir(), "sessions");
|
|
1797
|
+
if (!fs5.existsSync(dir)) {
|
|
1798
|
+
fs5.mkdirSync(dir, { recursive: true });
|
|
1350
1799
|
}
|
|
1351
1800
|
return dir;
|
|
1352
1801
|
}
|
|
1353
1802
|
async function launchClaude(options) {
|
|
1354
|
-
const { envConfig, permMode, workingDir, sessionId, resumeSessionId, silent } = options;
|
|
1803
|
+
const { envName, envConfig, permMode, workingDir, sessionId, resumeSessionId, silent } = options;
|
|
1355
1804
|
const env = { ...process.env };
|
|
1805
|
+
for (const key of MANAGED_CLAUDE_ENV_KEYS) {
|
|
1806
|
+
delete env[key];
|
|
1807
|
+
}
|
|
1356
1808
|
if (envConfig) {
|
|
1357
1809
|
Object.assign(env, buildEnvVars(envConfig));
|
|
1358
1810
|
}
|
|
@@ -1375,22 +1827,36 @@ async function launchClaude(options) {
|
|
|
1375
1827
|
if (workingDir) {
|
|
1376
1828
|
process.chdir(workingDir);
|
|
1377
1829
|
}
|
|
1830
|
+
const effectiveWorkingDir = process.cwd();
|
|
1378
1831
|
if (!silent && !permMode) {
|
|
1379
1832
|
console.log(renderStarting());
|
|
1380
1833
|
}
|
|
1381
1834
|
const sessionsDir = ensureSessionsDir();
|
|
1382
1835
|
return new Promise((resolve2) => {
|
|
1836
|
+
let provenanceTracking = null;
|
|
1383
1837
|
const child = spawn("claude", args, {
|
|
1384
1838
|
stdio: "inherit",
|
|
1385
1839
|
shell: false,
|
|
1386
1840
|
// 直接执行二进制,避免 shell 注入风险
|
|
1387
1841
|
env
|
|
1388
1842
|
});
|
|
1843
|
+
child.once("spawn", () => {
|
|
1844
|
+
if (sessionId) {
|
|
1845
|
+
return;
|
|
1846
|
+
}
|
|
1847
|
+
provenanceTracking = startCliClaudeProvenanceTracking({
|
|
1848
|
+
envName: envName ?? "unknown",
|
|
1849
|
+
workingDir: effectiveWorkingDir,
|
|
1850
|
+
permMode,
|
|
1851
|
+
resumeSessionId
|
|
1852
|
+
});
|
|
1853
|
+
});
|
|
1389
1854
|
child.on("exit", (code) => {
|
|
1855
|
+
provenanceTracking?.stop();
|
|
1390
1856
|
if (sessionId) {
|
|
1391
1857
|
try {
|
|
1392
|
-
|
|
1393
|
-
|
|
1858
|
+
fs5.writeFileSync(
|
|
1859
|
+
path5.join(sessionsDir, `${sessionId}.exit`),
|
|
1394
1860
|
String(code ?? 0)
|
|
1395
1861
|
);
|
|
1396
1862
|
} catch {
|
|
@@ -1399,7 +1865,24 @@ async function launchClaude(options) {
|
|
|
1399
1865
|
process.exit(code ?? 0);
|
|
1400
1866
|
});
|
|
1401
1867
|
child.on("error", (err) => {
|
|
1402
|
-
|
|
1868
|
+
provenanceTracking?.stop();
|
|
1869
|
+
if (err.code === "ENOENT") {
|
|
1870
|
+
console.error("");
|
|
1871
|
+
console.error(chalk2.red.bold("\u2718 \u672A\u627E\u5230 Claude Code"));
|
|
1872
|
+
console.error("");
|
|
1873
|
+
console.error(chalk2.white(" CCEM \u9700\u8981 Claude Code CLI \u624D\u80FD\u542F\u52A8\u4F1A\u8BDD\uFF0C\u4F46\u5728\u7CFB\u7EDF\u4E2D\u672A\u68C0\u6D4B\u5230 ") + chalk2.cyan("claude") + chalk2.white(" \u547D\u4EE4\u3002"));
|
|
1874
|
+
console.error("");
|
|
1875
|
+
console.error(chalk2.white(" \u8BF7\u5148\u5B89\u88C5 Claude Code:"));
|
|
1876
|
+
console.error(chalk2.cyan(" npm install -g @anthropic-ai/claude-code"));
|
|
1877
|
+
console.error("");
|
|
1878
|
+
console.error(chalk2.gray(" \u5982\u679C\u5DF2\u5B89\u88C5\u4F46\u4ECD\u62A5\u9519\uFF0C\u8BF7\u68C0\u67E5:"));
|
|
1879
|
+
console.error(chalk2.gray(" 1. \u8FD0\u884C claude --version \u786E\u8BA4\u5B89\u88C5\u6210\u529F"));
|
|
1880
|
+
console.error(chalk2.gray(" 2. \u786E\u4FDD npm \u5168\u5C40\u76EE\u5F55\u5728\u7CFB\u7EDF PATH \u4E2D\uFF08npm config get prefix\uFF09"));
|
|
1881
|
+
console.error(chalk2.gray(" 3. \u5B89\u88C5\u540E\u8BF7\u91CD\u542F\u7EC8\u7AEF"));
|
|
1882
|
+
console.error("");
|
|
1883
|
+
} else {
|
|
1884
|
+
console.error(chalk2.red(`\u542F\u52A8 Claude Code \u5931\u8D25: ${err.message}`));
|
|
1885
|
+
}
|
|
1403
1886
|
process.exit(1);
|
|
1404
1887
|
});
|
|
1405
1888
|
});
|
|
@@ -1407,14 +1890,14 @@ async function launchClaude(options) {
|
|
|
1407
1890
|
|
|
1408
1891
|
// src/permissions.ts
|
|
1409
1892
|
var readSettings = (settingsPath) => {
|
|
1410
|
-
if (
|
|
1893
|
+
if (fs6.existsSync(settingsPath)) {
|
|
1411
1894
|
try {
|
|
1412
|
-
const content =
|
|
1895
|
+
const content = fs6.readFileSync(settingsPath, "utf-8");
|
|
1413
1896
|
return JSON.parse(content);
|
|
1414
1897
|
} catch {
|
|
1415
1898
|
console.warn(chalk3.yellow(`\u8B66\u544A: \u65E0\u6CD5\u89E3\u6790 ${settingsPath}\uFF0C\u5C06\u521B\u5EFA\u5907\u4EFD`));
|
|
1416
1899
|
const backupPath = settingsPath + ".error." + Date.now();
|
|
1417
|
-
|
|
1900
|
+
fs6.copyFileSync(settingsPath, backupPath);
|
|
1418
1901
|
console.log(chalk3.gray(`\u5907\u4EFD\u5DF2\u4FDD\u5B58\u5230: ${backupPath}`));
|
|
1419
1902
|
return {};
|
|
1420
1903
|
}
|
|
@@ -1423,7 +1906,7 @@ var readSettings = (settingsPath) => {
|
|
|
1423
1906
|
};
|
|
1424
1907
|
var writeSettings = (settingsPath, config3) => {
|
|
1425
1908
|
ensureClaudeDir();
|
|
1426
|
-
|
|
1909
|
+
fs6.writeFileSync(settingsPath, JSON.stringify(config3, null, 2) + "\n");
|
|
1427
1910
|
};
|
|
1428
1911
|
var mergePermissions = (existing, preset) => {
|
|
1429
1912
|
const existingAllow = existing.permissions?.allow || [];
|
|
@@ -1455,14 +1938,14 @@ var applyPermissionMode = (modeName) => {
|
|
|
1455
1938
|
};
|
|
1456
1939
|
var resetPermissions = () => {
|
|
1457
1940
|
const settingsPath = getSettingsPath(true);
|
|
1458
|
-
if (!
|
|
1941
|
+
if (!fs6.existsSync(settingsPath)) {
|
|
1459
1942
|
console.log(chalk3.yellow("\u6CA1\u6709\u81EA\u5B9A\u4E49\u6743\u9650\u914D\u7F6E\u9700\u8981\u91CD\u7F6E"));
|
|
1460
1943
|
return;
|
|
1461
1944
|
}
|
|
1462
1945
|
const config3 = readSettings(settingsPath);
|
|
1463
1946
|
delete config3.permissions;
|
|
1464
1947
|
if (Object.keys(config3).length === 0) {
|
|
1465
|
-
|
|
1948
|
+
fs6.unlinkSync(settingsPath);
|
|
1466
1949
|
console.log(chalk3.green("\u5DF2\u5220\u9664\u914D\u7F6E\u6587\u4EF6\uFF08\u6587\u4EF6\u4E3A\u7A7A\uFF09"));
|
|
1467
1950
|
} else {
|
|
1468
1951
|
writeSettings(settingsPath, config3);
|
|
@@ -1472,7 +1955,7 @@ var resetPermissions = () => {
|
|
|
1472
1955
|
};
|
|
1473
1956
|
var showCurrentMode = () => {
|
|
1474
1957
|
const settingsPath = getSettingsPath(true);
|
|
1475
|
-
if (!
|
|
1958
|
+
if (!fs6.existsSync(settingsPath)) {
|
|
1476
1959
|
console.log(chalk3.yellow("\u672A\u914D\u7F6E\u81EA\u5B9A\u4E49\u6743\u9650"));
|
|
1477
1960
|
console.log(chalk3.gray(`\u6587\u4EF6\u4E0D\u5B58\u5728: ${settingsPath}`));
|
|
1478
1961
|
return;
|
|
@@ -1522,24 +2005,24 @@ var listAvailableModes = () => {
|
|
|
1522
2005
|
console.log(chalk3.gray("\n\u4E34\u65F6\u6A21\u5F0F: ccem <mode>"));
|
|
1523
2006
|
console.log(chalk3.gray("\u6C38\u4E45\u6A21\u5F0F: ccem setup perms --<mode>"));
|
|
1524
2007
|
};
|
|
1525
|
-
var runWithTempPermissions = async (modeName, envConfig) => {
|
|
2008
|
+
var runWithTempPermissions = async (modeName, envConfig, envName) => {
|
|
1526
2009
|
const preset = PERMISSION_PRESETS[modeName];
|
|
1527
2010
|
if (!preset) {
|
|
1528
2011
|
console.error(chalk3.red(`\u672A\u77E5\u7684\u6743\u9650\u6A21\u5F0F: ${modeName}`));
|
|
1529
2012
|
console.log(chalk3.yellow("\u53EF\u7528\u6A21\u5F0F: " + getPermissionModeNames().join(", ")));
|
|
1530
2013
|
process.exit(1);
|
|
1531
2014
|
}
|
|
1532
|
-
await launchClaude({ envConfig, permMode: modeName });
|
|
2015
|
+
await launchClaude({ envConfig, envName, permMode: modeName });
|
|
1533
2016
|
};
|
|
1534
2017
|
|
|
1535
2018
|
// src/setup.ts
|
|
1536
|
-
import
|
|
2019
|
+
import fs7 from "fs";
|
|
1537
2020
|
import chalk4 from "chalk";
|
|
1538
2021
|
import { spawn as spawn2 } from "child_process";
|
|
1539
2022
|
var readJsonFile = (filePath) => {
|
|
1540
|
-
if (
|
|
2023
|
+
if (fs7.existsSync(filePath)) {
|
|
1541
2024
|
try {
|
|
1542
|
-
const content =
|
|
2025
|
+
const content = fs7.readFileSync(filePath, "utf-8");
|
|
1543
2026
|
return JSON.parse(content);
|
|
1544
2027
|
} catch {
|
|
1545
2028
|
console.warn(chalk4.yellow(`\u8B66\u544A: \u65E0\u6CD5\u89E3\u6790 ${filePath}`));
|
|
@@ -1549,7 +2032,7 @@ var readJsonFile = (filePath) => {
|
|
|
1549
2032
|
return {};
|
|
1550
2033
|
};
|
|
1551
2034
|
var writeJsonFile = (filePath, data) => {
|
|
1552
|
-
|
|
2035
|
+
fs7.writeFileSync(filePath, JSON.stringify(data, null, 2) + "\n");
|
|
1553
2036
|
};
|
|
1554
2037
|
var setupOnboarding = () => {
|
|
1555
2038
|
const configPath = getGlobalClaudeConfigPath();
|
|
@@ -1649,14 +2132,19 @@ var setupMcpTool = () => {
|
|
|
1649
2132
|
});
|
|
1650
2133
|
});
|
|
1651
2134
|
};
|
|
1652
|
-
var runSetupInit = async () => {
|
|
2135
|
+
var runSetupInit = async (options = {}) => {
|
|
1653
2136
|
console.log(chalk4.bold("\n\u{1F527} Claude Code \u521D\u59CB\u5316\u8BBE\u7F6E\n"));
|
|
1654
2137
|
console.log(chalk4.cyan("1. \u8BBE\u7F6E onboarding \u72B6\u6001"));
|
|
1655
2138
|
const step1 = setupOnboarding();
|
|
1656
2139
|
console.log(chalk4.cyan("\n2. \u914D\u7F6E\u9690\u79C1\u8BBE\u7F6E"));
|
|
1657
2140
|
const step2 = setupEnvSettings();
|
|
1658
|
-
|
|
1659
|
-
|
|
2141
|
+
let step3 = true;
|
|
2142
|
+
if (options.chrome) {
|
|
2143
|
+
console.log(chalk4.cyan("\n3. \u5B89\u88C5 MCP \u5DE5\u5177"));
|
|
2144
|
+
step3 = await setupMcpTool();
|
|
2145
|
+
} else {
|
|
2146
|
+
console.log(chalk4.gray("\n3. \u5B89\u88C5 MCP \u5DE5\u5177\uFF08\u5DF2\u8DF3\u8FC7\uFF0C\u4F7F\u7528 --chrome \u542F\u7528\uFF09"));
|
|
2147
|
+
}
|
|
1660
2148
|
console.log("");
|
|
1661
2149
|
if (step1 && step2 && step3) {
|
|
1662
2150
|
console.log(chalk4.green.bold("\u2705 \u521D\u59CB\u5316\u5B8C\u6210\uFF01"));
|
|
@@ -1671,8 +2159,8 @@ var runSetupInit = async () => {
|
|
|
1671
2159
|
|
|
1672
2160
|
// src/skills.ts
|
|
1673
2161
|
import { execSync } from "child_process";
|
|
1674
|
-
import * as
|
|
1675
|
-
import * as
|
|
2162
|
+
import * as fs8 from "fs";
|
|
2163
|
+
import * as path6 from "path";
|
|
1676
2164
|
import chalk5 from "chalk";
|
|
1677
2165
|
var SKILL_GROUPS = {
|
|
1678
2166
|
official: { label: "\u5B98\u65B9", icon: "\u{1F3E2}" },
|
|
@@ -1841,12 +2329,12 @@ function parseGitHubUrl(url) {
|
|
|
1841
2329
|
};
|
|
1842
2330
|
}
|
|
1843
2331
|
function getSkillsDir() {
|
|
1844
|
-
return
|
|
2332
|
+
return path6.join(process.cwd(), ".claude", "skills");
|
|
1845
2333
|
}
|
|
1846
2334
|
function ensureSkillsDir() {
|
|
1847
2335
|
const skillsDir = getSkillsDir();
|
|
1848
|
-
if (!
|
|
1849
|
-
|
|
2336
|
+
if (!fs8.existsSync(skillsDir)) {
|
|
2337
|
+
fs8.mkdirSync(skillsDir, { recursive: true });
|
|
1850
2338
|
} else {
|
|
1851
2339
|
cleanupTempDirs(skillsDir);
|
|
1852
2340
|
}
|
|
@@ -1854,11 +2342,11 @@ function ensureSkillsDir() {
|
|
|
1854
2342
|
}
|
|
1855
2343
|
function cleanupTempDirs(skillsDir) {
|
|
1856
2344
|
try {
|
|
1857
|
-
const entries =
|
|
2345
|
+
const entries = fs8.readdirSync(skillsDir, { withFileTypes: true });
|
|
1858
2346
|
for (const entry of entries) {
|
|
1859
2347
|
if (entry.isDirectory() && entry.name.startsWith(".tmp-")) {
|
|
1860
|
-
const tmpPath =
|
|
1861
|
-
|
|
2348
|
+
const tmpPath = path6.join(skillsDir, entry.name);
|
|
2349
|
+
fs8.rmSync(tmpPath, { recursive: true });
|
|
1862
2350
|
}
|
|
1863
2351
|
}
|
|
1864
2352
|
} catch {
|
|
@@ -1866,24 +2354,24 @@ function cleanupTempDirs(skillsDir) {
|
|
|
1866
2354
|
}
|
|
1867
2355
|
function downloadSkillWithGit(owner, repo, branch, repoPath, targetName) {
|
|
1868
2356
|
const skillsDir = ensureSkillsDir();
|
|
1869
|
-
const targetDir =
|
|
1870
|
-
if (
|
|
2357
|
+
const targetDir = path6.join(skillsDir, targetName);
|
|
2358
|
+
if (fs8.existsSync(targetDir)) {
|
|
1871
2359
|
console.log(chalk5.yellow(`Skill "${targetName}" already exists. Updating...`));
|
|
1872
|
-
|
|
2360
|
+
fs8.rmSync(targetDir, { recursive: true });
|
|
1873
2361
|
}
|
|
1874
2362
|
const repoUrl = `https://github.com/${owner}/${repo}.git`;
|
|
1875
|
-
const tempDir =
|
|
2363
|
+
const tempDir = path6.join(skillsDir, `.tmp-${Date.now()}`);
|
|
1876
2364
|
try {
|
|
1877
|
-
|
|
2365
|
+
fs8.mkdirSync(tempDir, { recursive: true });
|
|
1878
2366
|
execSync(`git init`, { cwd: tempDir, stdio: "pipe" });
|
|
1879
2367
|
execSync(`git remote add origin ${repoUrl}`, { cwd: tempDir, stdio: "pipe" });
|
|
1880
2368
|
execSync(`git config core.sparseCheckout true`, { cwd: tempDir, stdio: "pipe" });
|
|
1881
|
-
const sparseFile =
|
|
1882
|
-
|
|
2369
|
+
const sparseFile = path6.join(tempDir, ".git", "info", "sparse-checkout");
|
|
2370
|
+
fs8.writeFileSync(sparseFile, repoPath ? `${repoPath}/
|
|
1883
2371
|
` : "*\n");
|
|
1884
2372
|
execSync(`git pull --depth=1 origin ${branch}`, { cwd: tempDir, stdio: "pipe" });
|
|
1885
|
-
const sourceDir = repoPath ?
|
|
1886
|
-
if (!
|
|
2373
|
+
const sourceDir = repoPath ? path6.join(tempDir, repoPath) : tempDir;
|
|
2374
|
+
if (!fs8.existsSync(sourceDir)) {
|
|
1887
2375
|
throw new Error(`Path "${repoPath}" not found in repository`);
|
|
1888
2376
|
}
|
|
1889
2377
|
copyDir(sourceDir, targetDir);
|
|
@@ -1894,22 +2382,22 @@ function downloadSkillWithGit(owner, repo, branch, repoPath, targetName) {
|
|
|
1894
2382
|
console.error(chalk5.red(`Failed to download skill: ${errMsg}`));
|
|
1895
2383
|
return false;
|
|
1896
2384
|
} finally {
|
|
1897
|
-
if (
|
|
1898
|
-
|
|
2385
|
+
if (fs8.existsSync(tempDir)) {
|
|
2386
|
+
fs8.rmSync(tempDir, { recursive: true });
|
|
1899
2387
|
}
|
|
1900
2388
|
}
|
|
1901
2389
|
}
|
|
1902
2390
|
function copyDir(src, dest) {
|
|
1903
|
-
|
|
1904
|
-
const entries =
|
|
2391
|
+
fs8.mkdirSync(dest, { recursive: true });
|
|
2392
|
+
const entries = fs8.readdirSync(src, { withFileTypes: true });
|
|
1905
2393
|
for (const entry of entries) {
|
|
1906
2394
|
if (entry.name === ".git") continue;
|
|
1907
|
-
const srcPath =
|
|
1908
|
-
const destPath =
|
|
2395
|
+
const srcPath = path6.join(src, entry.name);
|
|
2396
|
+
const destPath = path6.join(dest, entry.name);
|
|
1909
2397
|
if (entry.isDirectory()) {
|
|
1910
2398
|
copyDir(srcPath, destPath);
|
|
1911
2399
|
} else {
|
|
1912
|
-
|
|
2400
|
+
fs8.copyFileSync(srcPath, destPath);
|
|
1913
2401
|
}
|
|
1914
2402
|
}
|
|
1915
2403
|
}
|
|
@@ -1955,7 +2443,7 @@ function addSkillFromGitHub(urlOrPreset) {
|
|
|
1955
2443
|
}
|
|
1956
2444
|
let skillName;
|
|
1957
2445
|
if (parsed.path) {
|
|
1958
|
-
skillName =
|
|
2446
|
+
skillName = path6.basename(parsed.path);
|
|
1959
2447
|
} else {
|
|
1960
2448
|
skillName = parsed.repo;
|
|
1961
2449
|
}
|
|
@@ -1969,23 +2457,23 @@ function addSkillFromGitHub(urlOrPreset) {
|
|
|
1969
2457
|
}
|
|
1970
2458
|
function listInstalledSkills() {
|
|
1971
2459
|
const skillsDir = getSkillsDir();
|
|
1972
|
-
if (!
|
|
2460
|
+
if (!fs8.existsSync(skillsDir)) {
|
|
1973
2461
|
return [];
|
|
1974
2462
|
}
|
|
1975
|
-
const entries =
|
|
2463
|
+
const entries = fs8.readdirSync(skillsDir, { withFileTypes: true });
|
|
1976
2464
|
return entries.filter((entry) => entry.isDirectory() && !entry.name.startsWith(".")).map((entry) => ({
|
|
1977
2465
|
name: entry.name,
|
|
1978
|
-
path:
|
|
2466
|
+
path: path6.join(skillsDir, entry.name)
|
|
1979
2467
|
}));
|
|
1980
2468
|
}
|
|
1981
2469
|
function removeSkill(name) {
|
|
1982
2470
|
const skillsDir = getSkillsDir();
|
|
1983
|
-
const targetDir =
|
|
1984
|
-
if (!
|
|
2471
|
+
const targetDir = path6.join(skillsDir, name);
|
|
2472
|
+
if (!fs8.existsSync(targetDir)) {
|
|
1985
2473
|
console.error(chalk5.red(`Skill "${name}" not found`));
|
|
1986
2474
|
return false;
|
|
1987
2475
|
}
|
|
1988
|
-
|
|
2476
|
+
fs8.rmSync(targetDir, { recursive: true });
|
|
1989
2477
|
console.log(chalk5.green(`Removed skill "${name}"`));
|
|
1990
2478
|
return true;
|
|
1991
2479
|
}
|
|
@@ -2228,9 +2716,10 @@ var loadFromRemote = async (url, secret) => {
|
|
|
2228
2716
|
for (const [name, envConfig] of Object.entries(decrypted.environments)) {
|
|
2229
2717
|
const uniqueName = getUniqueName(name, existingNames);
|
|
2230
2718
|
const renamed = uniqueName !== name;
|
|
2231
|
-
const
|
|
2232
|
-
|
|
2233
|
-
|
|
2719
|
+
const normalizedConfig = normalizeEnvConfig(envConfig);
|
|
2720
|
+
const configToSave = { ...normalizedConfig };
|
|
2721
|
+
if (configToSave.ANTHROPIC_AUTH_TOKEN) {
|
|
2722
|
+
configToSave.ANTHROPIC_AUTH_TOKEN = encrypt(configToSave.ANTHROPIC_AUTH_TOKEN);
|
|
2234
2723
|
}
|
|
2235
2724
|
registries[uniqueName] = configToSave;
|
|
2236
2725
|
existingNames.add(uniqueName);
|
|
@@ -2398,10 +2887,51 @@ Replace \\\`TARGET_ID\\\` or \\\`TARGET_NAME\\\` with the user's selection.
|
|
|
2398
2887
|
|
|
2399
2888
|
// src/index.ts
|
|
2400
2889
|
var __filename2 = fileURLToPath2(import.meta.url);
|
|
2401
|
-
var __dirname2 =
|
|
2402
|
-
var pkgPath =
|
|
2403
|
-
var pkg = JSON.parse(
|
|
2890
|
+
var __dirname2 = path7.dirname(__filename2);
|
|
2891
|
+
var pkgPath = path7.resolve(__dirname2, "..", "package.json");
|
|
2892
|
+
var pkg = JSON.parse(fs9.readFileSync(pkgPath, "utf-8"));
|
|
2404
2893
|
var program = new Command();
|
|
2894
|
+
var DEFAULT_OFFICIAL_ENV = {
|
|
2895
|
+
ANTHROPIC_BASE_URL: "https://api.anthropic.com",
|
|
2896
|
+
ANTHROPIC_DEFAULT_OPUS_MODEL: "claude-opus-4-1-20250805",
|
|
2897
|
+
ANTHROPIC_DEFAULT_SONNET_MODEL: "claude-opus-4-1-20250805",
|
|
2898
|
+
ANTHROPIC_DEFAULT_HAIKU_MODEL: "claude-3-5-haiku-20241022",
|
|
2899
|
+
ANTHROPIC_MODEL: "opus"
|
|
2900
|
+
};
|
|
2901
|
+
var MANAGED_CLAUDE_ENV_KEYS2 = [
|
|
2902
|
+
"ANTHROPIC_BASE_URL",
|
|
2903
|
+
"ANTHROPIC_AUTH_TOKEN",
|
|
2904
|
+
"ANTHROPIC_DEFAULT_OPUS_MODEL",
|
|
2905
|
+
"ANTHROPIC_DEFAULT_SONNET_MODEL",
|
|
2906
|
+
"ANTHROPIC_DEFAULT_HAIKU_MODEL",
|
|
2907
|
+
"ANTHROPIC_MODEL",
|
|
2908
|
+
"CLAUDE_CODE_SUBAGENT_MODEL",
|
|
2909
|
+
"ANTHROPIC_API_KEY",
|
|
2910
|
+
"ANTHROPIC_SMALL_FAST_MODEL"
|
|
2911
|
+
];
|
|
2912
|
+
var shellQuote = (value) => `'${value.replace(/'/g, `'\\''`)}'`;
|
|
2913
|
+
var clearManagedClaudeEnv = (env) => {
|
|
2914
|
+
for (const key of MANAGED_CLAUDE_ENV_KEYS2) {
|
|
2915
|
+
delete env[key];
|
|
2916
|
+
}
|
|
2917
|
+
};
|
|
2918
|
+
var buildResolvedEnvVars = (env) => {
|
|
2919
|
+
const resolved = {};
|
|
2920
|
+
if (env.ANTHROPIC_BASE_URL) resolved.ANTHROPIC_BASE_URL = env.ANTHROPIC_BASE_URL;
|
|
2921
|
+
if (env.ANTHROPIC_AUTH_TOKEN) resolved.ANTHROPIC_AUTH_TOKEN = decrypt(env.ANTHROPIC_AUTH_TOKEN);
|
|
2922
|
+
if (env.ANTHROPIC_DEFAULT_OPUS_MODEL) resolved.ANTHROPIC_DEFAULT_OPUS_MODEL = env.ANTHROPIC_DEFAULT_OPUS_MODEL;
|
|
2923
|
+
if (env.ANTHROPIC_DEFAULT_SONNET_MODEL) resolved.ANTHROPIC_DEFAULT_SONNET_MODEL = env.ANTHROPIC_DEFAULT_SONNET_MODEL;
|
|
2924
|
+
if (env.ANTHROPIC_DEFAULT_HAIKU_MODEL) resolved.ANTHROPIC_DEFAULT_HAIKU_MODEL = env.ANTHROPIC_DEFAULT_HAIKU_MODEL;
|
|
2925
|
+
if (env.ANTHROPIC_MODEL) resolved.ANTHROPIC_MODEL = env.ANTHROPIC_MODEL;
|
|
2926
|
+
if (env.CLAUDE_CODE_SUBAGENT_MODEL) resolved.CLAUDE_CODE_SUBAGENT_MODEL = env.CLAUDE_CODE_SUBAGENT_MODEL;
|
|
2927
|
+
return resolved;
|
|
2928
|
+
};
|
|
2929
|
+
var buildShellEnvCommands = (env) => {
|
|
2930
|
+
const resolved = buildResolvedEnvVars(env);
|
|
2931
|
+
return MANAGED_CLAUDE_ENV_KEYS2.map(
|
|
2932
|
+
(key) => resolved[key] ? `export ${key}=${shellQuote(resolved[key])}` : `unset ${key}`
|
|
2933
|
+
);
|
|
2934
|
+
};
|
|
2405
2935
|
ensureCcemDir();
|
|
2406
2936
|
var config2 = new Conf2({
|
|
2407
2937
|
projectName: "claude-code-env-manager",
|
|
@@ -2409,16 +2939,129 @@ var config2 = new Conf2({
|
|
|
2409
2939
|
// 使用新路径
|
|
2410
2940
|
defaults: {
|
|
2411
2941
|
registries: {
|
|
2412
|
-
|
|
2413
|
-
ANTHROPIC_BASE_URL: "https://api.anthropic.com",
|
|
2414
|
-
ANTHROPIC_MODEL: "claude-sonnet-4-5-20250929",
|
|
2415
|
-
ANTHROPIC_SMALL_FAST_MODEL: "claude-haiku-4-5-20251001"
|
|
2416
|
-
}
|
|
2942
|
+
official: DEFAULT_OFFICIAL_ENV
|
|
2417
2943
|
},
|
|
2418
2944
|
current: "official",
|
|
2419
2945
|
defaultMode: null
|
|
2420
2946
|
}
|
|
2421
2947
|
});
|
|
2948
|
+
var recoverRegistriesFromLegacy = (registries) => {
|
|
2949
|
+
const currentAuthCount = Object.values(registries).filter(
|
|
2950
|
+
(env) => Boolean(env.ANTHROPIC_AUTH_TOKEN)
|
|
2951
|
+
).length;
|
|
2952
|
+
if (currentAuthCount > 0) {
|
|
2953
|
+
return registries;
|
|
2954
|
+
}
|
|
2955
|
+
const legacyConfigPath = getLegacyConfigPath();
|
|
2956
|
+
if (!fs9.existsSync(legacyConfigPath)) {
|
|
2957
|
+
return registries;
|
|
2958
|
+
}
|
|
2959
|
+
try {
|
|
2960
|
+
const legacyRaw = JSON.parse(fs9.readFileSync(legacyConfigPath, "utf-8"));
|
|
2961
|
+
const legacyRegistries = legacyRaw.registries ?? {};
|
|
2962
|
+
let changed = false;
|
|
2963
|
+
const recovered = { ...registries };
|
|
2964
|
+
for (const [name, envConfig] of Object.entries(registries)) {
|
|
2965
|
+
const legacyEnvConfig = legacyRegistries[name];
|
|
2966
|
+
if (!legacyEnvConfig) {
|
|
2967
|
+
continue;
|
|
2968
|
+
}
|
|
2969
|
+
const recoveredEnv = recoverEnvConfigFromLegacy(envConfig, legacyEnvConfig);
|
|
2970
|
+
if (JSON.stringify(recoveredEnv) !== JSON.stringify(envConfig)) {
|
|
2971
|
+
recovered[name] = recoveredEnv;
|
|
2972
|
+
changed = true;
|
|
2973
|
+
}
|
|
2974
|
+
}
|
|
2975
|
+
return changed ? recovered : registries;
|
|
2976
|
+
} catch {
|
|
2977
|
+
return registries;
|
|
2978
|
+
}
|
|
2979
|
+
};
|
|
2980
|
+
var getRegistries = () => {
|
|
2981
|
+
const rawRegistries = config2.get("registries") ?? {};
|
|
2982
|
+
const normalizedEntries = Object.entries(rawRegistries).map(([name, envConfig]) => [
|
|
2983
|
+
name,
|
|
2984
|
+
normalizeEnvConfig(envConfig ?? {})
|
|
2985
|
+
]);
|
|
2986
|
+
const normalizedRegistries = Object.fromEntries(normalizedEntries);
|
|
2987
|
+
const repairedRegistries = recoverRegistriesFromLegacy(normalizedRegistries);
|
|
2988
|
+
if (!repairedRegistries.official) {
|
|
2989
|
+
repairedRegistries.official = { ...DEFAULT_OFFICIAL_ENV };
|
|
2990
|
+
}
|
|
2991
|
+
const changed = Object.keys(rawRegistries).length !== Object.keys(repairedRegistries).length || JSON.stringify(rawRegistries) !== JSON.stringify(repairedRegistries);
|
|
2992
|
+
if (changed) {
|
|
2993
|
+
config2.set("registries", repairedRegistries);
|
|
2994
|
+
}
|
|
2995
|
+
return repairedRegistries;
|
|
2996
|
+
};
|
|
2997
|
+
var setRegistries = (registries) => {
|
|
2998
|
+
config2.set("registries", registries);
|
|
2999
|
+
};
|
|
3000
|
+
var getDecryptedAuthToken = (envConfig) => {
|
|
3001
|
+
return envConfig.ANTHROPIC_AUTH_TOKEN ? decrypt(envConfig.ANTHROPIC_AUTH_TOKEN) : void 0;
|
|
3002
|
+
};
|
|
3003
|
+
var applyPromptAnswers = (current, answers, keepCurrentSecret) => {
|
|
3004
|
+
const next = {
|
|
3005
|
+
...current,
|
|
3006
|
+
ANTHROPIC_BASE_URL: answers.ANTHROPIC_BASE_URL?.trim() || current.ANTHROPIC_BASE_URL,
|
|
3007
|
+
ANTHROPIC_DEFAULT_OPUS_MODEL: answers.ANTHROPIC_DEFAULT_OPUS_MODEL?.trim() || current.ANTHROPIC_DEFAULT_OPUS_MODEL,
|
|
3008
|
+
ANTHROPIC_DEFAULT_HAIKU_MODEL: answers.ANTHROPIC_DEFAULT_HAIKU_MODEL?.trim() || current.ANTHROPIC_DEFAULT_HAIKU_MODEL,
|
|
3009
|
+
ANTHROPIC_DEFAULT_SONNET_MODEL: answers.ANTHROPIC_DEFAULT_SONNET_MODEL?.trim() || answers.ANTHROPIC_DEFAULT_OPUS_MODEL?.trim() || current.ANTHROPIC_DEFAULT_SONNET_MODEL || current.ANTHROPIC_DEFAULT_OPUS_MODEL,
|
|
3010
|
+
ANTHROPIC_MODEL: answers.ANTHROPIC_MODEL?.trim() || current.ANTHROPIC_MODEL || "opus",
|
|
3011
|
+
CLAUDE_CODE_SUBAGENT_MODEL: answers.CLAUDE_CODE_SUBAGENT_MODEL?.trim() || current.CLAUDE_CODE_SUBAGENT_MODEL
|
|
3012
|
+
};
|
|
3013
|
+
if (!keepCurrentSecret) {
|
|
3014
|
+
next.ANTHROPIC_AUTH_TOKEN = answers.ANTHROPIC_AUTH_TOKEN ? encrypt(answers.ANTHROPIC_AUTH_TOKEN) : void 0;
|
|
3015
|
+
} else if (answers.ANTHROPIC_AUTH_TOKEN) {
|
|
3016
|
+
next.ANTHROPIC_AUTH_TOKEN = encrypt(answers.ANTHROPIC_AUTH_TOKEN);
|
|
3017
|
+
}
|
|
3018
|
+
return normalizeEnvConfig(next);
|
|
3019
|
+
};
|
|
3020
|
+
var promptForEnvironmentConfig = async (current = {}, keepCurrentSecret = false) => {
|
|
3021
|
+
return inquirer.prompt([
|
|
3022
|
+
{
|
|
3023
|
+
type: "input",
|
|
3024
|
+
name: "ANTHROPIC_BASE_URL",
|
|
3025
|
+
message: "ANTHROPIC_BASE_URL:",
|
|
3026
|
+
default: current.ANTHROPIC_BASE_URL || DEFAULT_OFFICIAL_ENV.ANTHROPIC_BASE_URL
|
|
3027
|
+
},
|
|
3028
|
+
{
|
|
3029
|
+
type: "password",
|
|
3030
|
+
name: "ANTHROPIC_AUTH_TOKEN",
|
|
3031
|
+
message: keepCurrentSecret ? "ANTHROPIC_AUTH_TOKEN (leave empty to keep current):" : "ANTHROPIC_AUTH_TOKEN:"
|
|
3032
|
+
},
|
|
3033
|
+
{
|
|
3034
|
+
type: "input",
|
|
3035
|
+
name: "ANTHROPIC_DEFAULT_OPUS_MODEL",
|
|
3036
|
+
message: "ANTHROPIC_DEFAULT_OPUS_MODEL:",
|
|
3037
|
+
default: current.ANTHROPIC_DEFAULT_OPUS_MODEL || DEFAULT_OFFICIAL_ENV.ANTHROPIC_DEFAULT_OPUS_MODEL
|
|
3038
|
+
},
|
|
3039
|
+
{
|
|
3040
|
+
type: "input",
|
|
3041
|
+
name: "ANTHROPIC_DEFAULT_HAIKU_MODEL",
|
|
3042
|
+
message: "ANTHROPIC_DEFAULT_HAIKU_MODEL:",
|
|
3043
|
+
default: current.ANTHROPIC_DEFAULT_HAIKU_MODEL || DEFAULT_OFFICIAL_ENV.ANTHROPIC_DEFAULT_HAIKU_MODEL
|
|
3044
|
+
},
|
|
3045
|
+
{
|
|
3046
|
+
type: "input",
|
|
3047
|
+
name: "ANTHROPIC_DEFAULT_SONNET_MODEL",
|
|
3048
|
+
message: "ANTHROPIC_DEFAULT_SONNET_MODEL (blank = same as opus):",
|
|
3049
|
+
default: current.ANTHROPIC_DEFAULT_SONNET_MODEL || current.ANTHROPIC_DEFAULT_OPUS_MODEL || ""
|
|
3050
|
+
},
|
|
3051
|
+
{
|
|
3052
|
+
type: "input",
|
|
3053
|
+
name: "ANTHROPIC_MODEL",
|
|
3054
|
+
message: "ANTHROPIC_MODEL (e.g. opus, opusplan, sonnet):",
|
|
3055
|
+
default: current.ANTHROPIC_MODEL || "opus"
|
|
3056
|
+
},
|
|
3057
|
+
{
|
|
3058
|
+
type: "input",
|
|
3059
|
+
name: "CLAUDE_CODE_SUBAGENT_MODEL",
|
|
3060
|
+
message: "CLAUDE_CODE_SUBAGENT_MODEL (optional):",
|
|
3061
|
+
default: current.CLAUDE_CODE_SUBAGENT_MODEL || ""
|
|
3062
|
+
}
|
|
3063
|
+
]);
|
|
3064
|
+
};
|
|
2422
3065
|
var PERMISSION_MODES = ["yolo", "dev", "readonly", "safe", "ci", "audit"];
|
|
2423
3066
|
var usageStats = null;
|
|
2424
3067
|
var usageLoading = true;
|
|
@@ -2457,24 +3100,27 @@ program.name("ccem").description("Claude Code Environment Manager - \u7BA1\u7406
|
|
|
2457
3100
|
PERMISSION_MODES.forEach((mode) => {
|
|
2458
3101
|
const preset = PERMISSION_PRESETS[mode];
|
|
2459
3102
|
program.command(mode).description(`\u4E34\u65F6\u5E94\u7528 ${preset.name}\uFF0C\u9000\u51FA\u540E\u8FD8\u539F`).action(async () => {
|
|
2460
|
-
const registries =
|
|
3103
|
+
const registries = getRegistries();
|
|
2461
3104
|
const current = config2.get("current");
|
|
2462
3105
|
const envConfig = registries[current];
|
|
2463
|
-
await runWithTempPermissions(mode, envConfig);
|
|
3106
|
+
await runWithTempPermissions(mode, envConfig, current);
|
|
2464
3107
|
});
|
|
2465
3108
|
});
|
|
2466
3109
|
var showCurrentEnv = (usageStats2, usageLoading2) => {
|
|
2467
3110
|
if (!process.stdout.isTTY) return;
|
|
2468
3111
|
const current = config2.get("current");
|
|
2469
|
-
const registries =
|
|
3112
|
+
const registries = getRegistries();
|
|
2470
3113
|
const env = registries[current];
|
|
2471
3114
|
const defaultMode = config2.get("defaultMode");
|
|
2472
3115
|
if (!env) return;
|
|
2473
3116
|
console.log(renderLogoWithEnvPanel(current, {
|
|
2474
3117
|
ANTHROPIC_BASE_URL: env.ANTHROPIC_BASE_URL,
|
|
2475
|
-
|
|
3118
|
+
ANTHROPIC_AUTH_TOKEN: getDecryptedAuthToken(env),
|
|
3119
|
+
ANTHROPIC_DEFAULT_OPUS_MODEL: env.ANTHROPIC_DEFAULT_OPUS_MODEL,
|
|
3120
|
+
ANTHROPIC_DEFAULT_SONNET_MODEL: env.ANTHROPIC_DEFAULT_SONNET_MODEL,
|
|
3121
|
+
ANTHROPIC_DEFAULT_HAIKU_MODEL: env.ANTHROPIC_DEFAULT_HAIKU_MODEL,
|
|
2476
3122
|
ANTHROPIC_MODEL: env.ANTHROPIC_MODEL,
|
|
2477
|
-
|
|
3123
|
+
CLAUDE_CODE_SUBAGENT_MODEL: env.CLAUDE_CODE_SUBAGENT_MODEL
|
|
2478
3124
|
}, defaultMode));
|
|
2479
3125
|
console.log("");
|
|
2480
3126
|
console.log(renderUsageLine(usageStats2, usageLoading2));
|
|
@@ -2482,7 +3128,7 @@ var showCurrentEnv = (usageStats2, usageLoading2) => {
|
|
|
2482
3128
|
console.log("");
|
|
2483
3129
|
};
|
|
2484
3130
|
var switchEnvironment = async (name) => {
|
|
2485
|
-
const registries =
|
|
3131
|
+
const registries = getRegistries();
|
|
2486
3132
|
if (!registries[name]) {
|
|
2487
3133
|
console.error(chalk7.red(`Environment '${name}' not found.`));
|
|
2488
3134
|
return;
|
|
@@ -2495,11 +3141,7 @@ var switchEnvironment = async (name) => {
|
|
|
2495
3141
|
}
|
|
2496
3142
|
showCurrentEnv(null, false);
|
|
2497
3143
|
const env = registries[name];
|
|
2498
|
-
const exportCmds =
|
|
2499
|
-
if (env.ANTHROPIC_BASE_URL) exportCmds.push(`export ANTHROPIC_BASE_URL="${env.ANTHROPIC_BASE_URL}"`);
|
|
2500
|
-
if (env.ANTHROPIC_API_KEY) exportCmds.push(`export ANTHROPIC_API_KEY="${decrypt(env.ANTHROPIC_API_KEY)}"`);
|
|
2501
|
-
if (env.ANTHROPIC_MODEL) exportCmds.push(`export ANTHROPIC_MODEL="${env.ANTHROPIC_MODEL}"`);
|
|
2502
|
-
if (env.ANTHROPIC_SMALL_FAST_MODEL) exportCmds.push(`export ANTHROPIC_SMALL_FAST_MODEL="${env.ANTHROPIC_SMALL_FAST_MODEL}"`);
|
|
3144
|
+
const exportCmds = buildShellEnvCommands(env);
|
|
2503
3145
|
if (process.stdout.isTTY) {
|
|
2504
3146
|
console.log(chalk7.yellow("\nTo apply to current shell immediately, run:"));
|
|
2505
3147
|
console.log(chalk7.cyan("eval $(ccem env)"));
|
|
@@ -2509,11 +3151,92 @@ var switchEnvironment = async (name) => {
|
|
|
2509
3151
|
exportCmds.forEach((cmd) => console.log(cmd));
|
|
2510
3152
|
}
|
|
2511
3153
|
};
|
|
3154
|
+
var getSessionsFilePath = () => path7.join(getCcemConfigDir(), "sessions.json");
|
|
3155
|
+
var getRuntimeStateFilePath = () => path7.join(getCcemConfigDir(), "runtime-state.json");
|
|
3156
|
+
var parseJsonFile = (filePath) => {
|
|
3157
|
+
if (!fs9.existsSync(filePath)) {
|
|
3158
|
+
return null;
|
|
3159
|
+
}
|
|
3160
|
+
try {
|
|
3161
|
+
return JSON.parse(fs9.readFileSync(filePath, "utf-8"));
|
|
3162
|
+
} catch {
|
|
3163
|
+
return null;
|
|
3164
|
+
}
|
|
3165
|
+
};
|
|
3166
|
+
var readInteractiveAttachSessions = () => {
|
|
3167
|
+
const sessionsById = /* @__PURE__ */ new Map();
|
|
3168
|
+
const runtimeState = parseJsonFile(getRuntimeStateFilePath());
|
|
3169
|
+
const persistedSessions = parseJsonFile(getSessionsFilePath()) ?? [];
|
|
3170
|
+
for (const entry of runtimeState?.sessions ?? []) {
|
|
3171
|
+
if (entry.runtime_kind && entry.runtime_kind !== "interactive") {
|
|
3172
|
+
continue;
|
|
3173
|
+
}
|
|
3174
|
+
const fallback = persistedSessions.find((session) => session.id === entry.runtime_id);
|
|
3175
|
+
const tmuxTarget = entry.tmux_session && entry.tmux_window ? `${entry.tmux_session}:${entry.tmux_window}` : fallback?.window_id ?? null;
|
|
3176
|
+
if (!tmuxTarget) {
|
|
3177
|
+
continue;
|
|
3178
|
+
}
|
|
3179
|
+
sessionsById.set(entry.runtime_id, {
|
|
3180
|
+
id: entry.runtime_id,
|
|
3181
|
+
projectDir: entry.project_dir,
|
|
3182
|
+
envName: entry.env_name,
|
|
3183
|
+
permMode: entry.perm_mode,
|
|
3184
|
+
status: fallback?.status ?? "running",
|
|
3185
|
+
tmuxTarget,
|
|
3186
|
+
sortTime: entry.saved_at
|
|
3187
|
+
});
|
|
3188
|
+
}
|
|
3189
|
+
for (const session of persistedSessions) {
|
|
3190
|
+
if (session.status !== "running" || session.terminal_type !== "embedded" || !session.window_id) {
|
|
3191
|
+
continue;
|
|
3192
|
+
}
|
|
3193
|
+
if (sessionsById.has(session.id)) {
|
|
3194
|
+
continue;
|
|
3195
|
+
}
|
|
3196
|
+
sessionsById.set(session.id, {
|
|
3197
|
+
id: session.id,
|
|
3198
|
+
projectDir: session.working_dir,
|
|
3199
|
+
envName: session.env_name,
|
|
3200
|
+
permMode: session.perm_mode,
|
|
3201
|
+
status: session.status,
|
|
3202
|
+
tmuxTarget: session.window_id,
|
|
3203
|
+
sortTime: session.start_time
|
|
3204
|
+
});
|
|
3205
|
+
}
|
|
3206
|
+
return [...sessionsById.values()].sort(
|
|
3207
|
+
(left, right) => right.sortTime.localeCompare(left.sortTime)
|
|
3208
|
+
);
|
|
3209
|
+
};
|
|
3210
|
+
var findAttachSession = (id) => {
|
|
3211
|
+
const sessions = readInteractiveAttachSessions();
|
|
3212
|
+
if (!id) {
|
|
3213
|
+
return sessions[0];
|
|
3214
|
+
}
|
|
3215
|
+
const exact = sessions.find((session) => session.id === id);
|
|
3216
|
+
if (exact) {
|
|
3217
|
+
return exact;
|
|
3218
|
+
}
|
|
3219
|
+
return sessions.find((session) => session.id.startsWith(id));
|
|
3220
|
+
};
|
|
3221
|
+
var attachTmuxTarget = (target) => {
|
|
3222
|
+
const args = process.env.TMUX ? ["switch-client", "-t", target] : ["attach-session", "-t", target];
|
|
3223
|
+
return new Promise((resolve2, reject) => {
|
|
3224
|
+
const child = spawn3("tmux", args, { stdio: "inherit" });
|
|
3225
|
+
child.on("error", reject);
|
|
3226
|
+
child.on("exit", (code) => {
|
|
3227
|
+
if (code === 0 || code === null) {
|
|
3228
|
+
resolve2();
|
|
3229
|
+
} else {
|
|
3230
|
+
reject(new Error(`tmux exited with code ${code}`));
|
|
3231
|
+
}
|
|
3232
|
+
});
|
|
3233
|
+
});
|
|
3234
|
+
};
|
|
2512
3235
|
program.command("ls").description("List all configured environments").action(() => {
|
|
2513
|
-
const registries =
|
|
3236
|
+
const registries = getRegistries();
|
|
2514
3237
|
const current = config2.get("current");
|
|
2515
3238
|
const table = new Table3({
|
|
2516
|
-
head: ["Name", "Base URL", "
|
|
3239
|
+
head: ["Name", "Base URL", "Opus"],
|
|
2517
3240
|
style: { head: ["cyan"] }
|
|
2518
3241
|
});
|
|
2519
3242
|
Object.keys(registries).forEach((name) => {
|
|
@@ -2522,7 +3245,7 @@ program.command("ls").description("List all configured environments").action(()
|
|
|
2522
3245
|
table.push([
|
|
2523
3246
|
prefix + name,
|
|
2524
3247
|
reg.ANTHROPIC_BASE_URL || "-",
|
|
2525
|
-
reg.
|
|
3248
|
+
reg.ANTHROPIC_DEFAULT_OPUS_MODEL || "-"
|
|
2526
3249
|
]);
|
|
2527
3250
|
});
|
|
2528
3251
|
console.log(table.toString());
|
|
@@ -2530,8 +3253,42 @@ program.command("ls").description("List all configured environments").action(()
|
|
|
2530
3253
|
program.command("use <name>").description("Switch to a specific environment").action(async (name) => {
|
|
2531
3254
|
await switchEnvironment(name);
|
|
2532
3255
|
});
|
|
3256
|
+
program.command("sessions").description("List tmux-backed interactive sessions").action(() => {
|
|
3257
|
+
const sessions = readInteractiveAttachSessions();
|
|
3258
|
+
if (sessions.length === 0) {
|
|
3259
|
+
console.log(chalk7.yellow("No tmux-backed interactive sessions found."));
|
|
3260
|
+
return;
|
|
3261
|
+
}
|
|
3262
|
+
const table = new Table3({
|
|
3263
|
+
head: ["ID", "Project", "Env", "Status", "Tmux"],
|
|
3264
|
+
style: { head: ["cyan"] }
|
|
3265
|
+
});
|
|
3266
|
+
sessions.forEach((session) => {
|
|
3267
|
+
table.push([
|
|
3268
|
+
session.id,
|
|
3269
|
+
session.projectDir,
|
|
3270
|
+
session.envName,
|
|
3271
|
+
session.status,
|
|
3272
|
+
session.tmuxTarget
|
|
3273
|
+
]);
|
|
3274
|
+
});
|
|
3275
|
+
console.log(table.toString());
|
|
3276
|
+
});
|
|
3277
|
+
program.command("attach [id]").description("Attach to a tmux-backed interactive session").action(async (id) => {
|
|
3278
|
+
const session = findAttachSession(id);
|
|
3279
|
+
if (!session) {
|
|
3280
|
+
console.error(chalk7.red(id ? `Interactive session '${id}' not found.` : "No interactive session available to attach."));
|
|
3281
|
+
process.exit(1);
|
|
3282
|
+
}
|
|
3283
|
+
try {
|
|
3284
|
+
await attachTmuxTarget(session.tmuxTarget);
|
|
3285
|
+
} catch (error) {
|
|
3286
|
+
console.error(chalk7.red(`Failed to attach ${session.tmuxTarget}: ${String(error)}`));
|
|
3287
|
+
process.exit(1);
|
|
3288
|
+
}
|
|
3289
|
+
});
|
|
2533
3290
|
program.command("add <name>").description("Add a new environment configuration").action(async (name) => {
|
|
2534
|
-
const registries =
|
|
3291
|
+
const registries = getRegistries();
|
|
2535
3292
|
if (registries[name]) {
|
|
2536
3293
|
console.log(chalk7.red(`Environment '${name}' already exists.`));
|
|
2537
3294
|
return;
|
|
@@ -2556,40 +3313,13 @@ program.command("add <name>").description("Add a new environment configuration")
|
|
|
2556
3313
|
]);
|
|
2557
3314
|
presetConfig = ENV_PRESETS[presetName];
|
|
2558
3315
|
}
|
|
2559
|
-
const answers = await
|
|
2560
|
-
|
|
2561
|
-
|
|
2562
|
-
name: "ANTHROPIC_BASE_URL",
|
|
2563
|
-
message: "Enter ANTHROPIC_BASE_URL:",
|
|
2564
|
-
default: presetConfig.ANTHROPIC_BASE_URL || "https://api.anthropic.com"
|
|
2565
|
-
},
|
|
2566
|
-
{
|
|
2567
|
-
type: "password",
|
|
2568
|
-
name: "ANTHROPIC_API_KEY",
|
|
2569
|
-
message: "Enter ANTHROPIC_API_KEY:"
|
|
2570
|
-
},
|
|
2571
|
-
{
|
|
2572
|
-
type: "input",
|
|
2573
|
-
name: "ANTHROPIC_MODEL",
|
|
2574
|
-
message: "Enter ANTHROPIC_MODEL:",
|
|
2575
|
-
default: presetConfig.ANTHROPIC_MODEL || "claude-sonnet-4-5-20250929"
|
|
2576
|
-
},
|
|
2577
|
-
{
|
|
2578
|
-
type: "input",
|
|
2579
|
-
name: "ANTHROPIC_SMALL_FAST_MODEL",
|
|
2580
|
-
message: "Enter ANTHROPIC_SMALL_FAST_MODEL:",
|
|
2581
|
-
default: presetConfig.ANTHROPIC_SMALL_FAST_MODEL || "claude-haiku-4-5-20251001"
|
|
2582
|
-
}
|
|
2583
|
-
]);
|
|
2584
|
-
if (answers.ANTHROPIC_API_KEY) {
|
|
2585
|
-
answers.ANTHROPIC_API_KEY = encrypt(answers.ANTHROPIC_API_KEY);
|
|
2586
|
-
}
|
|
2587
|
-
registries[name] = answers;
|
|
2588
|
-
config2.set("registries", registries);
|
|
3316
|
+
const answers = await promptForEnvironmentConfig(presetConfig);
|
|
3317
|
+
registries[name] = applyPromptAnswers(normalizeEnvConfig(presetConfig), answers, false);
|
|
3318
|
+
setRegistries(registries);
|
|
2589
3319
|
console.log(chalk7.green(`Environment '${name}' added successfully.`));
|
|
2590
3320
|
});
|
|
2591
3321
|
program.command("del <name>").description("Delete an environment configuration").action((name) => {
|
|
2592
|
-
const registries =
|
|
3322
|
+
const registries = getRegistries();
|
|
2593
3323
|
if (!registries[name]) {
|
|
2594
3324
|
console.log(chalk7.red(`Environment '${name}' not found.`));
|
|
2595
3325
|
return;
|
|
@@ -2599,7 +3329,7 @@ program.command("del <name>").description("Delete an environment configuration")
|
|
|
2599
3329
|
return;
|
|
2600
3330
|
}
|
|
2601
3331
|
delete registries[name];
|
|
2602
|
-
|
|
3332
|
+
setRegistries(registries);
|
|
2603
3333
|
const current = config2.get("current");
|
|
2604
3334
|
if (current === name) {
|
|
2605
3335
|
config2.set("current", "official");
|
|
@@ -2608,7 +3338,7 @@ program.command("del <name>").description("Delete an environment configuration")
|
|
|
2608
3338
|
console.log(chalk7.green(`Environment '${name}' deleted.`));
|
|
2609
3339
|
});
|
|
2610
3340
|
program.command("rename <old> <new>").description("Rename an environment configuration").action((oldName, newName) => {
|
|
2611
|
-
const registries =
|
|
3341
|
+
const registries = getRegistries();
|
|
2612
3342
|
if (!registries[oldName]) {
|
|
2613
3343
|
console.log(chalk7.red(`Environment '${oldName}' not found.`));
|
|
2614
3344
|
return;
|
|
@@ -2623,7 +3353,7 @@ program.command("rename <old> <new>").description("Rename an environment configu
|
|
|
2623
3353
|
}
|
|
2624
3354
|
registries[newName] = registries[oldName];
|
|
2625
3355
|
delete registries[oldName];
|
|
2626
|
-
|
|
3356
|
+
setRegistries(registries);
|
|
2627
3357
|
const current = config2.get("current");
|
|
2628
3358
|
if (current === oldName) {
|
|
2629
3359
|
config2.set("current", newName);
|
|
@@ -2631,7 +3361,7 @@ program.command("rename <old> <new>").description("Rename an environment configu
|
|
|
2631
3361
|
console.log(chalk7.green(`Environment '${oldName}' renamed to '${newName}'.`));
|
|
2632
3362
|
});
|
|
2633
3363
|
program.command("cp <source> <target>").description("Copy an environment configuration").action(async (source, target) => {
|
|
2634
|
-
const registries =
|
|
3364
|
+
const registries = getRegistries();
|
|
2635
3365
|
if (!registries[source]) {
|
|
2636
3366
|
console.log(chalk7.red(`Environment '${source}' not found.`));
|
|
2637
3367
|
return;
|
|
@@ -2641,7 +3371,7 @@ program.command("cp <source> <target>").description("Copy an environment configu
|
|
|
2641
3371
|
return;
|
|
2642
3372
|
}
|
|
2643
3373
|
registries[target] = { ...registries[source] };
|
|
2644
|
-
|
|
3374
|
+
setRegistries(registries);
|
|
2645
3375
|
console.log(chalk7.green(`Environment '${source}' copied to '${target}'.`));
|
|
2646
3376
|
const { modify } = await inquirer.prompt([
|
|
2647
3377
|
{
|
|
@@ -2653,37 +3383,9 @@ program.command("cp <source> <target>").description("Copy an environment configu
|
|
|
2653
3383
|
]);
|
|
2654
3384
|
if (modify) {
|
|
2655
3385
|
const current = registries[target];
|
|
2656
|
-
const answers = await
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
name: "ANTHROPIC_BASE_URL",
|
|
2660
|
-
message: "ANTHROPIC_BASE_URL:",
|
|
2661
|
-
default: current.ANTHROPIC_BASE_URL
|
|
2662
|
-
},
|
|
2663
|
-
{
|
|
2664
|
-
type: "password",
|
|
2665
|
-
name: "ANTHROPIC_API_KEY",
|
|
2666
|
-
message: "ANTHROPIC_API_KEY (leave empty to keep current):"
|
|
2667
|
-
},
|
|
2668
|
-
{
|
|
2669
|
-
type: "input",
|
|
2670
|
-
name: "ANTHROPIC_MODEL",
|
|
2671
|
-
message: "ANTHROPIC_MODEL:",
|
|
2672
|
-
default: current.ANTHROPIC_MODEL
|
|
2673
|
-
},
|
|
2674
|
-
{
|
|
2675
|
-
type: "input",
|
|
2676
|
-
name: "ANTHROPIC_SMALL_FAST_MODEL",
|
|
2677
|
-
message: "ANTHROPIC_SMALL_FAST_MODEL:",
|
|
2678
|
-
default: current.ANTHROPIC_SMALL_FAST_MODEL
|
|
2679
|
-
}
|
|
2680
|
-
]);
|
|
2681
|
-
if (answers.ANTHROPIC_BASE_URL) current.ANTHROPIC_BASE_URL = answers.ANTHROPIC_BASE_URL;
|
|
2682
|
-
if (answers.ANTHROPIC_API_KEY) current.ANTHROPIC_API_KEY = encrypt(answers.ANTHROPIC_API_KEY);
|
|
2683
|
-
if (answers.ANTHROPIC_MODEL) current.ANTHROPIC_MODEL = answers.ANTHROPIC_MODEL;
|
|
2684
|
-
if (answers.ANTHROPIC_SMALL_FAST_MODEL) current.ANTHROPIC_SMALL_FAST_MODEL = answers.ANTHROPIC_SMALL_FAST_MODEL;
|
|
2685
|
-
registries[target] = current;
|
|
2686
|
-
config2.set("registries", registries);
|
|
3386
|
+
const answers = await promptForEnvironmentConfig(current, true);
|
|
3387
|
+
registries[target] = applyPromptAnswers(current, answers, true);
|
|
3388
|
+
setRegistries(registries);
|
|
2687
3389
|
console.log(chalk7.green(`Environment '${target}' updated.`));
|
|
2688
3390
|
}
|
|
2689
3391
|
});
|
|
@@ -2692,25 +3394,22 @@ program.command("current").description("Show current environment name").action((
|
|
|
2692
3394
|
console.log(chalk7.green(current));
|
|
2693
3395
|
});
|
|
2694
3396
|
program.command("env").description("Output environment variables for shell eval").option("--json", "Output as JSON").action((options) => {
|
|
2695
|
-
const registries =
|
|
3397
|
+
const registries = getRegistries();
|
|
2696
3398
|
const current = config2.get("current");
|
|
2697
3399
|
const env = registries[current];
|
|
2698
3400
|
if (!env) return;
|
|
2699
3401
|
const outputEnv = { ...env };
|
|
2700
|
-
if (outputEnv.
|
|
2701
|
-
outputEnv.
|
|
3402
|
+
if (outputEnv.ANTHROPIC_AUTH_TOKEN) {
|
|
3403
|
+
outputEnv.ANTHROPIC_AUTH_TOKEN = decrypt(outputEnv.ANTHROPIC_AUTH_TOKEN);
|
|
2702
3404
|
}
|
|
2703
3405
|
if (options.json) {
|
|
2704
3406
|
console.log(JSON.stringify(outputEnv, null, 2));
|
|
2705
3407
|
} else {
|
|
2706
|
-
|
|
2707
|
-
if (outputEnv.ANTHROPIC_API_KEY) console.log(`export ANTHROPIC_API_KEY="${outputEnv.ANTHROPIC_API_KEY}"`);
|
|
2708
|
-
if (outputEnv.ANTHROPIC_MODEL) console.log(`export ANTHROPIC_MODEL="${outputEnv.ANTHROPIC_MODEL}"`);
|
|
2709
|
-
if (outputEnv.ANTHROPIC_SMALL_FAST_MODEL) console.log(`export ANTHROPIC_SMALL_FAST_MODEL="${outputEnv.ANTHROPIC_SMALL_FAST_MODEL}"`);
|
|
3408
|
+
buildShellEnvCommands(env).forEach((cmd) => console.log(cmd));
|
|
2710
3409
|
}
|
|
2711
3410
|
});
|
|
2712
3411
|
program.command("run <command...>").description("Run a command with the current environment variables").action((command) => {
|
|
2713
|
-
const registries =
|
|
3412
|
+
const registries = getRegistries();
|
|
2714
3413
|
const current = config2.get("current");
|
|
2715
3414
|
const envConfig = registries[current];
|
|
2716
3415
|
if (!envConfig) {
|
|
@@ -2718,10 +3417,8 @@ program.command("run <command...>").description("Run a command with the current
|
|
|
2718
3417
|
process.exit(1);
|
|
2719
3418
|
}
|
|
2720
3419
|
const env = { ...process.env };
|
|
2721
|
-
|
|
2722
|
-
|
|
2723
|
-
if (envConfig.ANTHROPIC_MODEL) env.ANTHROPIC_MODEL = envConfig.ANTHROPIC_MODEL;
|
|
2724
|
-
if (envConfig.ANTHROPIC_SMALL_FAST_MODEL) env.ANTHROPIC_SMALL_FAST_MODEL = envConfig.ANTHROPIC_SMALL_FAST_MODEL;
|
|
3420
|
+
clearManagedClaudeEnv(env);
|
|
3421
|
+
Object.assign(env, buildResolvedEnvVars(envConfig));
|
|
2725
3422
|
const [cmd, ...args] = command;
|
|
2726
3423
|
const child = spawn3(cmd, args, {
|
|
2727
3424
|
env,
|
|
@@ -2774,20 +3471,21 @@ setupCmd.command("default-mode").description("\u8BBE\u7F6E\u9ED8\u8BA4\u6743\u96
|
|
|
2774
3471
|
console.log(chalk7.gray("\u6E05\u9664\u9ED8\u8BA4\u6A21\u5F0F: ccem setup default-mode --reset"));
|
|
2775
3472
|
console.log(chalk7.gray("\u53EF\u7528\u6A21\u5F0F: " + PERMISSION_MODES.join(", ")));
|
|
2776
3473
|
});
|
|
2777
|
-
setupCmd.command("init").description("\u521D\u59CB\u5316 Claude Code \u5168\u5C40\u914D\u7F6E\uFF08\u8DF3\u8FC7 onboarding\u3001\u7981\u7528\u9065\u6D4B\
|
|
2778
|
-
|
|
3474
|
+
setupCmd.command("init").description("\u521D\u59CB\u5316 Claude Code \u5168\u5C40\u914D\u7F6E\uFF08\u8DF3\u8FC7 onboarding\u3001\u7981\u7528\u9065\u6D4B\uFF09").option("--chrome", "\u540C\u65F6\u5B89\u88C5 chrome-devtools MCP \u5DE5\u5177").action(async function() {
|
|
3475
|
+
const options = this.opts();
|
|
3476
|
+
await runSetupInit({ chrome: !!options.chrome });
|
|
2779
3477
|
});
|
|
2780
3478
|
setupCmd.command("migrate").description("\u8FC1\u79FB\u65E7\u7248\u914D\u7F6E\u5230 ~/.ccem/").option("--clean", "\u8FC1\u79FB\u540E\u5220\u9664\u65E7\u914D\u7F6E\u6587\u4EF6").option("--force", "\u5F3A\u5236\u91CD\u65B0\u8FC1\u79FB\uFF08\u8986\u76D6\u73B0\u6709\u914D\u7F6E\uFF09").action(async function() {
|
|
2781
3479
|
const options = this.opts();
|
|
2782
3480
|
const newConfigPath = getCcemConfigPath();
|
|
2783
3481
|
const legacyConfigPath = getLegacyConfigPath();
|
|
2784
3482
|
console.log(chalk7.cyan("\n\u{1F504} \u914D\u7F6E\u8FC1\u79FB\n"));
|
|
2785
|
-
if (!
|
|
3483
|
+
if (!fs9.existsSync(legacyConfigPath)) {
|
|
2786
3484
|
console.log(chalk7.yellow("\u672A\u627E\u5230\u65E7\u7248\u914D\u7F6E\u6587\u4EF6"));
|
|
2787
3485
|
console.log(chalk7.gray(` \u65E7\u8DEF\u5F84: ${legacyConfigPath}`));
|
|
2788
3486
|
return;
|
|
2789
3487
|
}
|
|
2790
|
-
if (
|
|
3488
|
+
if (fs9.existsSync(newConfigPath) && !options.force) {
|
|
2791
3489
|
console.log(chalk7.green("\u2713 \u914D\u7F6E\u5DF2\u5728\u65B0\u8DEF\u5F84"));
|
|
2792
3490
|
console.log(chalk7.gray(` \u8DEF\u5F84: ${newConfigPath}`));
|
|
2793
3491
|
console.log(chalk7.gray("\n\u4F7F\u7528 --force \u5F3A\u5236\u91CD\u65B0\u8FC1\u79FB"));
|
|
@@ -2795,15 +3493,15 @@ setupCmd.command("migrate").description("\u8FC1\u79FB\u65E7\u7248\u914D\u7F6E\u5
|
|
|
2795
3493
|
}
|
|
2796
3494
|
try {
|
|
2797
3495
|
ensureCcemDir();
|
|
2798
|
-
|
|
3496
|
+
fs9.copyFileSync(legacyConfigPath, newConfigPath);
|
|
2799
3497
|
console.log(chalk7.green("\u2713 \u914D\u7F6E\u5DF2\u8FC1\u79FB"));
|
|
2800
3498
|
console.log(chalk7.gray(` \u4ECE: ${legacyConfigPath}`));
|
|
2801
3499
|
console.log(chalk7.gray(` \u5230: ${newConfigPath}`));
|
|
2802
3500
|
if (options.clean) {
|
|
2803
|
-
|
|
2804
|
-
const legacyDir =
|
|
3501
|
+
fs9.unlinkSync(legacyConfigPath);
|
|
3502
|
+
const legacyDir = path7.dirname(legacyConfigPath);
|
|
2805
3503
|
try {
|
|
2806
|
-
|
|
3504
|
+
fs9.rmdirSync(legacyDir);
|
|
2807
3505
|
} catch {
|
|
2808
3506
|
}
|
|
2809
3507
|
console.log(chalk7.green("\u2713 \u5DF2\u5220\u9664\u65E7\u914D\u7F6E\u6587\u4EF6"));
|
|
@@ -2814,13 +3512,13 @@ setupCmd.command("migrate").description("\u8FC1\u79FB\u65E7\u7248\u914D\u7F6E\u5
|
|
|
2814
3512
|
});
|
|
2815
3513
|
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() {
|
|
2816
3514
|
const options = this.opts();
|
|
2817
|
-
const skillDir =
|
|
2818
|
-
const targetPath =
|
|
2819
|
-
if (!
|
|
2820
|
-
|
|
3515
|
+
const skillDir = path7.join(process.env.HOME || "~", ".claude", "skills");
|
|
3516
|
+
const targetPath = path7.join(skillDir, "ccem-cron.md");
|
|
3517
|
+
if (!fs9.existsSync(skillDir)) {
|
|
3518
|
+
fs9.mkdirSync(skillDir, { recursive: true });
|
|
2821
3519
|
console.log(chalk7.gray(`\u521B\u5EFA\u76EE\u5F55: ${skillDir}`));
|
|
2822
3520
|
}
|
|
2823
|
-
if (
|
|
3521
|
+
if (fs9.existsSync(targetPath) && !options.force) {
|
|
2824
3522
|
const { overwrite } = await inquirer.prompt([
|
|
2825
3523
|
{
|
|
2826
3524
|
type: "confirm",
|
|
@@ -2834,7 +3532,7 @@ setupCmd.command("cron").description("\u5B89\u88C5 ccem-cron skill \u5230 Claude
|
|
|
2834
3532
|
return;
|
|
2835
3533
|
}
|
|
2836
3534
|
}
|
|
2837
|
-
|
|
3535
|
+
fs9.writeFileSync(targetPath, CCEM_CRON_SKILL_CONTENT, "utf-8");
|
|
2838
3536
|
console.log(chalk7.green(`\u2713 \u5DF2\u5B89\u88C5 ccem-cron skill`));
|
|
2839
3537
|
console.log(chalk7.gray(` \u8DEF\u5F84: ${targetPath}`));
|
|
2840
3538
|
console.log(chalk7.cyan(`
|
|
@@ -2907,7 +3605,7 @@ program.command("load <url>").description("\u4ECE\u8FDC\u7A0B\u670D\u52A1\u5668\
|
|
|
2907
3605
|
program.command("launch").description(false).option("--env <name>", "\u73AF\u5883\u540D\u79F0").option("--perm <mode>", "\u6743\u9650\u6A21\u5F0F").option("--session-id <id>", "\u4F1A\u8BDD ID").option("--resume-session <id>", "\u6062\u590D\u4F1A\u8BDD ID").option("--working-dir <path>", "\u5DE5\u4F5C\u76EE\u5F55").option("--proxy-base-url <url>", "Desktop internal override for ANTHROPIC_BASE_URL").option("--anthropic-base-url <url>", "Deprecated alias for --proxy-base-url").action(async function() {
|
|
2908
3606
|
const opts = this.opts();
|
|
2909
3607
|
const envName = opts.env || config2.get("current");
|
|
2910
|
-
const registries =
|
|
3608
|
+
const registries = getRegistries();
|
|
2911
3609
|
const envConfig = registries[envName];
|
|
2912
3610
|
if (!envConfig) {
|
|
2913
3611
|
console.error(chalk7.red(`Environment '${envName}' not found.`));
|
|
@@ -2916,6 +3614,7 @@ program.command("launch").description(false).option("--env <name>", "\u73AF\u588
|
|
|
2916
3614
|
const proxyBaseUrl = opts.proxyBaseUrl || opts.anthropicBaseUrl;
|
|
2917
3615
|
const launchEnvConfig = proxyBaseUrl ? { ...envConfig, ANTHROPIC_BASE_URL: proxyBaseUrl } : envConfig;
|
|
2918
3616
|
await launchClaude({
|
|
3617
|
+
envName,
|
|
2919
3618
|
envConfig: launchEnvConfig,
|
|
2920
3619
|
permMode: opts.perm,
|
|
2921
3620
|
workingDir: opts.workingDir,
|
|
@@ -2952,7 +3651,7 @@ program.action(async (options) => {
|
|
|
2952
3651
|
showCurrentEnv(usageStats, usageLoading);
|
|
2953
3652
|
console.log("");
|
|
2954
3653
|
const defaultMode = config2.get("defaultMode");
|
|
2955
|
-
const registries =
|
|
3654
|
+
const registries = getRegistries();
|
|
2956
3655
|
const current = config2.get("current");
|
|
2957
3656
|
const envConfig = registries[current];
|
|
2958
3657
|
const { action } = await inquirer.prompt([
|
|
@@ -2974,7 +3673,11 @@ program.action(async (options) => {
|
|
|
2974
3673
|
msg.error("No environment configuration found.");
|
|
2975
3674
|
process.exit(1);
|
|
2976
3675
|
}
|
|
2977
|
-
await launchClaude({
|
|
3676
|
+
await launchClaude({
|
|
3677
|
+
envName: current,
|
|
3678
|
+
envConfig,
|
|
3679
|
+
permMode: defaultMode || void 0
|
|
3680
|
+
});
|
|
2978
3681
|
return;
|
|
2979
3682
|
} else if (action === "usage") {
|
|
2980
3683
|
console.clear();
|
|
@@ -2999,37 +3702,9 @@ program.action(async (options) => {
|
|
|
2999
3702
|
const envToEdit = registries[result.name];
|
|
3000
3703
|
console.log(chalk7.yellow(`
|
|
3001
3704
|
Editing environment '${result.name}'`));
|
|
3002
|
-
const answers = await
|
|
3003
|
-
|
|
3004
|
-
|
|
3005
|
-
name: "ANTHROPIC_BASE_URL",
|
|
3006
|
-
message: "ANTHROPIC_BASE_URL:",
|
|
3007
|
-
default: envToEdit.ANTHROPIC_BASE_URL
|
|
3008
|
-
},
|
|
3009
|
-
{
|
|
3010
|
-
type: "password",
|
|
3011
|
-
name: "ANTHROPIC_API_KEY",
|
|
3012
|
-
message: "ANTHROPIC_API_KEY (leave empty to keep current):"
|
|
3013
|
-
},
|
|
3014
|
-
{
|
|
3015
|
-
type: "input",
|
|
3016
|
-
name: "ANTHROPIC_MODEL",
|
|
3017
|
-
message: "ANTHROPIC_MODEL:",
|
|
3018
|
-
default: envToEdit.ANTHROPIC_MODEL
|
|
3019
|
-
},
|
|
3020
|
-
{
|
|
3021
|
-
type: "input",
|
|
3022
|
-
name: "ANTHROPIC_SMALL_FAST_MODEL",
|
|
3023
|
-
message: "ANTHROPIC_SMALL_FAST_MODEL:",
|
|
3024
|
-
default: envToEdit.ANTHROPIC_SMALL_FAST_MODEL
|
|
3025
|
-
}
|
|
3026
|
-
]);
|
|
3027
|
-
if (answers.ANTHROPIC_BASE_URL) envToEdit.ANTHROPIC_BASE_URL = answers.ANTHROPIC_BASE_URL;
|
|
3028
|
-
if (answers.ANTHROPIC_API_KEY) envToEdit.ANTHROPIC_API_KEY = encrypt(answers.ANTHROPIC_API_KEY);
|
|
3029
|
-
if (answers.ANTHROPIC_MODEL) envToEdit.ANTHROPIC_MODEL = answers.ANTHROPIC_MODEL;
|
|
3030
|
-
if (answers.ANTHROPIC_SMALL_FAST_MODEL) envToEdit.ANTHROPIC_SMALL_FAST_MODEL = answers.ANTHROPIC_SMALL_FAST_MODEL;
|
|
3031
|
-
registries[result.name] = envToEdit;
|
|
3032
|
-
config2.set("registries", registries);
|
|
3705
|
+
const answers = await promptForEnvironmentConfig(envToEdit, true);
|
|
3706
|
+
registries[result.name] = applyPromptAnswers(envToEdit, answers, true);
|
|
3707
|
+
setRegistries(registries);
|
|
3033
3708
|
msg.success(`Environment '${result.name}' updated.`);
|
|
3034
3709
|
await new Promise((resolve2) => setTimeout(resolve2, 800));
|
|
3035
3710
|
} else if (result.action === "rename") {
|
|
@@ -3051,7 +3726,7 @@ Editing environment '${result.name}'`));
|
|
|
3051
3726
|
]);
|
|
3052
3727
|
registries[newName] = registries[result.name];
|
|
3053
3728
|
delete registries[result.name];
|
|
3054
|
-
|
|
3729
|
+
setRegistries(registries);
|
|
3055
3730
|
if (current === result.name) {
|
|
3056
3731
|
config2.set("current", newName);
|
|
3057
3732
|
}
|
|
@@ -3072,7 +3747,7 @@ Editing environment '${result.name}'`));
|
|
|
3072
3747
|
}
|
|
3073
3748
|
]);
|
|
3074
3749
|
registries[targetName] = { ...registries[result.name] };
|
|
3075
|
-
|
|
3750
|
+
setRegistries(registries);
|
|
3076
3751
|
msg.success(`Environment '${result.name}' copied to '${targetName}'.`);
|
|
3077
3752
|
const { modify } = await inquirer.prompt([
|
|
3078
3753
|
{
|
|
@@ -3084,37 +3759,9 @@ Editing environment '${result.name}'`));
|
|
|
3084
3759
|
]);
|
|
3085
3760
|
if (modify) {
|
|
3086
3761
|
const envToEdit = registries[targetName];
|
|
3087
|
-
const editAnswers = await
|
|
3088
|
-
|
|
3089
|
-
|
|
3090
|
-
name: "ANTHROPIC_BASE_URL",
|
|
3091
|
-
message: "ANTHROPIC_BASE_URL:",
|
|
3092
|
-
default: envToEdit.ANTHROPIC_BASE_URL
|
|
3093
|
-
},
|
|
3094
|
-
{
|
|
3095
|
-
type: "password",
|
|
3096
|
-
name: "ANTHROPIC_API_KEY",
|
|
3097
|
-
message: "ANTHROPIC_API_KEY (leave empty to keep current):"
|
|
3098
|
-
},
|
|
3099
|
-
{
|
|
3100
|
-
type: "input",
|
|
3101
|
-
name: "ANTHROPIC_MODEL",
|
|
3102
|
-
message: "ANTHROPIC_MODEL:",
|
|
3103
|
-
default: envToEdit.ANTHROPIC_MODEL
|
|
3104
|
-
},
|
|
3105
|
-
{
|
|
3106
|
-
type: "input",
|
|
3107
|
-
name: "ANTHROPIC_SMALL_FAST_MODEL",
|
|
3108
|
-
message: "ANTHROPIC_SMALL_FAST_MODEL:",
|
|
3109
|
-
default: envToEdit.ANTHROPIC_SMALL_FAST_MODEL
|
|
3110
|
-
}
|
|
3111
|
-
]);
|
|
3112
|
-
if (editAnswers.ANTHROPIC_BASE_URL) envToEdit.ANTHROPIC_BASE_URL = editAnswers.ANTHROPIC_BASE_URL;
|
|
3113
|
-
if (editAnswers.ANTHROPIC_API_KEY) envToEdit.ANTHROPIC_API_KEY = encrypt(editAnswers.ANTHROPIC_API_KEY);
|
|
3114
|
-
if (editAnswers.ANTHROPIC_MODEL) envToEdit.ANTHROPIC_MODEL = editAnswers.ANTHROPIC_MODEL;
|
|
3115
|
-
if (editAnswers.ANTHROPIC_SMALL_FAST_MODEL) envToEdit.ANTHROPIC_SMALL_FAST_MODEL = editAnswers.ANTHROPIC_SMALL_FAST_MODEL;
|
|
3116
|
-
registries[targetName] = envToEdit;
|
|
3117
|
-
config2.set("registries", registries);
|
|
3762
|
+
const editAnswers = await promptForEnvironmentConfig(envToEdit, true);
|
|
3763
|
+
registries[targetName] = applyPromptAnswers(envToEdit, editAnswers, true);
|
|
3764
|
+
setRegistries(registries);
|
|
3118
3765
|
msg.success(`Environment '${targetName}' updated.`);
|
|
3119
3766
|
}
|
|
3120
3767
|
await new Promise((resolve2) => setTimeout(resolve2, 800));
|
|
@@ -3133,7 +3780,7 @@ Editing environment '${result.name}'`));
|
|
|
3133
3780
|
]);
|
|
3134
3781
|
if (confirm) {
|
|
3135
3782
|
delete registries[result.name];
|
|
3136
|
-
|
|
3783
|
+
setRegistries(registries);
|
|
3137
3784
|
if (current === result.name) {
|
|
3138
3785
|
config2.set("current", "official");
|
|
3139
3786
|
msg.warning(`Deleted current environment. Switched back to 'official'.`);
|
|
@@ -3155,7 +3802,7 @@ Editing environment '${result.name}'`));
|
|
|
3155
3802
|
}
|
|
3156
3803
|
]);
|
|
3157
3804
|
if (permMode !== "back") {
|
|
3158
|
-
await runWithTempPermissions(permMode, envConfig);
|
|
3805
|
+
await runWithTempPermissions(permMode, envConfig, current);
|
|
3159
3806
|
return;
|
|
3160
3807
|
}
|
|
3161
3808
|
} else if (action === "setDefault") {
|