ccem 2.0.0-beta.2 → 2.0.0-beta.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +473 -196
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -9,29 +9,111 @@ import Table3 from "cli-table3";
9
9
  import { spawn as spawn3 } from "child_process";
10
10
  import * as fs8 from "fs";
11
11
  import * as path6 from "path";
12
- import { fileURLToPath } from "url";
12
+ import { fileURLToPath as fileURLToPath2 } from "url";
13
13
 
14
- // ../../packages/core/dist/chunk-YO7HO5AS.js
14
+ // ../../packages/core/dist/chunk-6W4EMMVY.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
- ANTHROPIC_MODEL: "glm-4.6",
19
- ANTHROPIC_SMALL_FAST_MODEL: "glm-4.5-air"
71
+ ANTHROPIC_DEFAULT_OPUS_MODEL: "glm-5",
72
+ ANTHROPIC_DEFAULT_SONNET_MODEL: "glm-5",
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
- ANTHROPIC_MODEL: "kimi-k2-thinking-turbo",
24
- ANTHROPIC_SMALL_FAST_MODEL: "kimi-k2-turbo-preview"
78
+ ANTHROPIC_DEFAULT_OPUS_MODEL: "kimi-k2-thinking-turbo",
79
+ ANTHROPIC_DEFAULT_SONNET_MODEL: "kimi-k2-thinking-turbo",
80
+ ANTHROPIC_DEFAULT_HAIKU_MODEL: "kimi-k2-turbo-preview",
81
+ ANTHROPIC_MODEL: "opus"
25
82
  },
26
83
  "MiniMax": {
27
84
  ANTHROPIC_BASE_URL: "https://api.minimaxi.com/anthropic",
28
- ANTHROPIC_MODEL: "MiniMax-M2",
29
- ANTHROPIC_SMALL_FAST_MODEL: "MiniMax-M2"
85
+ ANTHROPIC_DEFAULT_OPUS_MODEL: "MiniMax-M2.5",
86
+ ANTHROPIC_DEFAULT_SONNET_MODEL: "MiniMax-M2.5",
87
+ ANTHROPIC_DEFAULT_HAIKU_MODEL: "MiniMax-M2.5-highspeed",
88
+ ANTHROPIC_MODEL: "opus"
30
89
  },
31
90
  "DeepSeek": {
32
91
  ANTHROPIC_BASE_URL: "https://api.deepseek.com/anthropic",
33
- ANTHROPIC_MODEL: "deepseek-chat",
34
- ANTHROPIC_SMALL_FAST_MODEL: "deepseek-chat"
92
+ ANTHROPIC_DEFAULT_OPUS_MODEL: "deepseek-chat",
93
+ ANTHROPIC_DEFAULT_SONNET_MODEL: "deepseek-chat",
94
+ ANTHROPIC_DEFAULT_HAIKU_MODEL: "deepseek-chat",
95
+ ANTHROPIC_MODEL: "opus"
96
+ },
97
+ "Bailian": {
98
+ ANTHROPIC_BASE_URL: "https://dashscope.aliyuncs.com/api/v2/apps/claude-code-proxy",
99
+ ANTHROPIC_DEFAULT_OPUS_MODEL: "qwen3-coder-plus",
100
+ ANTHROPIC_DEFAULT_SONNET_MODEL: "qwen3-coder-plus",
101
+ ANTHROPIC_DEFAULT_HAIKU_MODEL: "qwen3-coder-flash",
102
+ ANTHROPIC_MODEL: "opus"
103
+ },
104
+ "BailianCodePlan": {
105
+ ANTHROPIC_BASE_URL: "https://coding.dashscope.aliyuncs.com/api/v2/apps/claude-code-proxy",
106
+ ANTHROPIC_DEFAULT_OPUS_MODEL: "qwen3-coder-plus",
107
+ ANTHROPIC_DEFAULT_SONNET_MODEL: "qwen3-coder-plus",
108
+ ANTHROPIC_DEFAULT_HAIKU_MODEL: "qwen3-coder-plus",
109
+ ANTHROPIC_MODEL: "opus"
110
+ },
111
+ "OpenRouter": {
112
+ ANTHROPIC_BASE_URL: "https://openrouter.ai/api/v1",
113
+ ANTHROPIC_DEFAULT_OPUS_MODEL: "anthropic/claude-opus-4-1",
114
+ ANTHROPIC_DEFAULT_SONNET_MODEL: "anthropic/claude-opus-4-1",
115
+ ANTHROPIC_DEFAULT_HAIKU_MODEL: "anthropic/claude-3.5-haiku",
116
+ ANTHROPIC_MODEL: "opus"
35
117
  }
36
118
  };
37
119
  var PERMISSION_PRESETS = {
@@ -338,6 +420,9 @@ import * as fsPromises from "fs/promises";
338
420
  import * as path2 from "path";
339
421
  import * as os from "os";
340
422
  import * as readline from "readline";
423
+ import { fileURLToPath } from "url";
424
+ var __filename = fileURLToPath(import.meta.url);
425
+ var __dirname = path2.dirname(__filename);
341
426
  var CLAUDE_PROJECTS_DIR = path2.join(os.homedir(), ".claude", "projects");
342
427
  var CCEM_DIR = path2.join(os.homedir(), ".ccem");
343
428
  var CACHE_VERSION = 1;
@@ -811,9 +896,10 @@ var renderLogoWithEnvPanel = (envName, env, defaultMode) => {
811
896
  const titleShort = theme.primary("CCEM");
812
897
  const envLabel = theme.muted("Env: ") + theme.primary(envName);
813
898
  const baseUrl = env.ANTHROPIC_BASE_URL || "-";
814
- const model = env.ANTHROPIC_MODEL || "-";
815
- const fastModel = env.ANTHROPIC_SMALL_FAST_MODEL || "-";
816
- const apiKey = env.ANTHROPIC_API_KEY ? env.ANTHROPIC_API_KEY.slice(0, 2) + "\u2022\u2022\u2022\u2022" + env.ANTHROPIC_API_KEY.slice(-4) : "-";
899
+ const runtimeModel = env.ANTHROPIC_MODEL || "-";
900
+ const opusModel = env.ANTHROPIC_DEFAULT_OPUS_MODEL || "-";
901
+ const haikuModel = env.ANTHROPIC_DEFAULT_HAIKU_MODEL || "-";
902
+ const authToken = env.ANTHROPIC_AUTH_TOKEN ? env.ANTHROPIC_AUTH_TOKEN.slice(0, 2) + "\u2022\u2022\u2022\u2022" + env.ANTHROPIC_AUTH_TOKEN.slice(-4) : "-";
817
903
  const truncate = (s, max) => s.length > max ? s.slice(0, max - 3) + "..." : s;
818
904
  const maskUrl = (url, max) => {
819
905
  if (url.length <= max) return url;
@@ -835,15 +921,15 @@ var renderLogoWithEnvPanel = (envName, env, defaultMode) => {
835
921
  if (isNarrow) {
836
922
  envLines = [
837
923
  envLabel,
838
- theme.muted("Model:".padEnd(labelWidth)) + theme.dim(truncate(model, 25)),
839
- theme.muted("Key:".padEnd(labelWidth)) + theme.dim(apiKey)
924
+ theme.muted("Opus:".padEnd(labelWidth)) + theme.dim(truncate(opusModel, 25)),
925
+ theme.muted("Token:".padEnd(labelWidth)) + theme.dim(authToken)
840
926
  ];
841
927
  } else {
842
928
  envLines = [
843
929
  envLabel + (defaultMode && PERMISSION_PRESETS[defaultMode] ? " " + theme.accent(`[${PERMISSION_PRESETS[defaultMode].name}]`) : ""),
844
930
  theme.muted("URL:".padEnd(labelWidth)) + theme.dim(maskUrl(baseUrl, 40)),
845
- theme.muted("Model:".padEnd(labelWidth)) + theme.dim(truncate(model, 15)) + " " + theme.muted("Fast:".padEnd(labelWidth)) + theme.dim(truncate(fastModel, 15)),
846
- theme.muted("Key:".padEnd(labelWidth)) + theme.dim(apiKey)
931
+ theme.muted("Run:".padEnd(labelWidth)) + theme.dim(truncate(runtimeModel, 12)) + " " + theme.muted("Opus:".padEnd(labelWidth)) + theme.dim(truncate(opusModel, 15)),
932
+ theme.muted("Haiku:".padEnd(labelWidth)) + theme.dim(truncate(haikuModel, 15)) + " " + theme.muted("Token:".padEnd(labelWidth)) + theme.dim(authToken)
847
933
  ];
848
934
  }
849
935
  const lines = [];
@@ -1318,12 +1404,26 @@ import { spawn } from "child_process";
1318
1404
  import * as fs4 from "fs";
1319
1405
  import * as path4 from "path";
1320
1406
  import chalk2 from "chalk";
1407
+ var MANAGED_CLAUDE_ENV_KEYS = [
1408
+ "ANTHROPIC_BASE_URL",
1409
+ "ANTHROPIC_AUTH_TOKEN",
1410
+ "ANTHROPIC_DEFAULT_OPUS_MODEL",
1411
+ "ANTHROPIC_DEFAULT_SONNET_MODEL",
1412
+ "ANTHROPIC_DEFAULT_HAIKU_MODEL",
1413
+ "ANTHROPIC_MODEL",
1414
+ "CLAUDE_CODE_SUBAGENT_MODEL",
1415
+ "ANTHROPIC_API_KEY",
1416
+ "ANTHROPIC_SMALL_FAST_MODEL"
1417
+ ];
1321
1418
  function buildEnvVars(envConfig) {
1322
1419
  const vars = {};
1323
1420
  if (envConfig.ANTHROPIC_BASE_URL) vars.ANTHROPIC_BASE_URL = envConfig.ANTHROPIC_BASE_URL;
1324
- if (envConfig.ANTHROPIC_API_KEY) vars.ANTHROPIC_API_KEY = decrypt(envConfig.ANTHROPIC_API_KEY);
1421
+ if (envConfig.ANTHROPIC_AUTH_TOKEN) vars.ANTHROPIC_AUTH_TOKEN = decrypt(envConfig.ANTHROPIC_AUTH_TOKEN);
1422
+ if (envConfig.ANTHROPIC_DEFAULT_OPUS_MODEL) vars.ANTHROPIC_DEFAULT_OPUS_MODEL = envConfig.ANTHROPIC_DEFAULT_OPUS_MODEL;
1423
+ if (envConfig.ANTHROPIC_DEFAULT_SONNET_MODEL) vars.ANTHROPIC_DEFAULT_SONNET_MODEL = envConfig.ANTHROPIC_DEFAULT_SONNET_MODEL;
1424
+ if (envConfig.ANTHROPIC_DEFAULT_HAIKU_MODEL) vars.ANTHROPIC_DEFAULT_HAIKU_MODEL = envConfig.ANTHROPIC_DEFAULT_HAIKU_MODEL;
1325
1425
  if (envConfig.ANTHROPIC_MODEL) vars.ANTHROPIC_MODEL = envConfig.ANTHROPIC_MODEL;
1326
- if (envConfig.ANTHROPIC_SMALL_FAST_MODEL) vars.ANTHROPIC_SMALL_FAST_MODEL = envConfig.ANTHROPIC_SMALL_FAST_MODEL;
1426
+ if (envConfig.CLAUDE_CODE_SUBAGENT_MODEL) vars.CLAUDE_CODE_SUBAGENT_MODEL = envConfig.CLAUDE_CODE_SUBAGENT_MODEL;
1327
1427
  return vars;
1328
1428
  }
1329
1429
  function buildPermArgs(modeName) {
@@ -1350,6 +1450,9 @@ function ensureSessionsDir() {
1350
1450
  async function launchClaude(options) {
1351
1451
  const { envConfig, permMode, workingDir, sessionId, resumeSessionId, silent } = options;
1352
1452
  const env = { ...process.env };
1453
+ for (const key of MANAGED_CLAUDE_ENV_KEYS) {
1454
+ delete env[key];
1455
+ }
1353
1456
  if (envConfig) {
1354
1457
  Object.assign(env, buildEnvVars(envConfig));
1355
1458
  }
@@ -1379,7 +1482,8 @@ async function launchClaude(options) {
1379
1482
  return new Promise((resolve2) => {
1380
1483
  const child = spawn("claude", args, {
1381
1484
  stdio: "inherit",
1382
- shell: true,
1485
+ shell: false,
1486
+ // 直接执行二进制,避免 shell 注入风险
1383
1487
  env
1384
1488
  });
1385
1489
  child.on("exit", (code) => {
@@ -2143,7 +2247,9 @@ import crypto3 from "crypto";
2143
2247
  import chalk6 from "chalk";
2144
2248
  import Conf from "conf";
2145
2249
  var config = new Conf({
2146
- projectName: "claude-code-env-manager"
2250
+ projectName: "claude-code-env-manager",
2251
+ cwd: getCcemConfigDir()
2252
+ // 使用统一的配置目录
2147
2253
  });
2148
2254
  var decryptWithSecret = (encryptedBase64, secret) => {
2149
2255
  const key = crypto3.scryptSync(secret, "ccem-salt", 32);
@@ -2171,7 +2277,11 @@ var loadFromRemote = async (url, secret) => {
2171
2277
  console.log(chalk6.gray("Fetching from remote..."));
2172
2278
  let response;
2173
2279
  try {
2174
- response = await fetch(url);
2280
+ response = await fetch(url, {
2281
+ headers: {
2282
+ "X-CCEM-Key": secret
2283
+ }
2284
+ });
2175
2285
  } catch (err) {
2176
2286
  console.error(chalk6.red("Error: Failed to connect to server"));
2177
2287
  console.error(chalk6.gray(err.message));
@@ -2218,9 +2328,10 @@ var loadFromRemote = async (url, secret) => {
2218
2328
  for (const [name, envConfig] of Object.entries(decrypted.environments)) {
2219
2329
  const uniqueName = getUniqueName(name, existingNames);
2220
2330
  const renamed = uniqueName !== name;
2221
- const configToSave = { ...envConfig };
2222
- if (configToSave.ANTHROPIC_API_KEY) {
2223
- configToSave.ANTHROPIC_API_KEY = encrypt(configToSave.ANTHROPIC_API_KEY);
2331
+ const normalizedConfig = normalizeEnvConfig(envConfig);
2332
+ const configToSave = { ...normalizedConfig };
2333
+ if (configToSave.ANTHROPIC_AUTH_TOKEN) {
2334
+ configToSave.ANTHROPIC_AUTH_TOKEN = encrypt(configToSave.ANTHROPIC_AUTH_TOKEN);
2224
2335
  }
2225
2336
  registries[uniqueName] = configToSave;
2226
2337
  existingNames.add(uniqueName);
@@ -2241,6 +2352,7 @@ Loaded ${results.length} environment(s) from remote:`));
2241
2352
  }
2242
2353
  }
2243
2354
  console.log(chalk6.gray("\nRun 'ccem ls' to see all environments."));
2355
+ return results;
2244
2356
  };
2245
2357
 
2246
2358
  // src/cron-skill.ts
@@ -2386,11 +2498,52 @@ Replace \\\`TARGET_ID\\\` or \\\`TARGET_NAME\\\` with the user's selection.
2386
2498
  `;
2387
2499
 
2388
2500
  // src/index.ts
2389
- var __filename = fileURLToPath(import.meta.url);
2390
- var __dirname2 = path6.dirname(__filename);
2501
+ var __filename2 = fileURLToPath2(import.meta.url);
2502
+ var __dirname2 = path6.dirname(__filename2);
2391
2503
  var pkgPath = path6.resolve(__dirname2, "..", "package.json");
2392
2504
  var pkg = JSON.parse(fs8.readFileSync(pkgPath, "utf-8"));
2393
2505
  var program = new Command();
2506
+ var DEFAULT_OFFICIAL_ENV = {
2507
+ ANTHROPIC_BASE_URL: "https://api.anthropic.com",
2508
+ ANTHROPIC_DEFAULT_OPUS_MODEL: "claude-opus-4-1-20250805",
2509
+ ANTHROPIC_DEFAULT_SONNET_MODEL: "claude-opus-4-1-20250805",
2510
+ ANTHROPIC_DEFAULT_HAIKU_MODEL: "claude-3-5-haiku-20241022",
2511
+ ANTHROPIC_MODEL: "opus"
2512
+ };
2513
+ var MANAGED_CLAUDE_ENV_KEYS2 = [
2514
+ "ANTHROPIC_BASE_URL",
2515
+ "ANTHROPIC_AUTH_TOKEN",
2516
+ "ANTHROPIC_DEFAULT_OPUS_MODEL",
2517
+ "ANTHROPIC_DEFAULT_SONNET_MODEL",
2518
+ "ANTHROPIC_DEFAULT_HAIKU_MODEL",
2519
+ "ANTHROPIC_MODEL",
2520
+ "CLAUDE_CODE_SUBAGENT_MODEL",
2521
+ "ANTHROPIC_API_KEY",
2522
+ "ANTHROPIC_SMALL_FAST_MODEL"
2523
+ ];
2524
+ var shellQuote = (value) => `'${value.replace(/'/g, `'\\''`)}'`;
2525
+ var clearManagedClaudeEnv = (env) => {
2526
+ for (const key of MANAGED_CLAUDE_ENV_KEYS2) {
2527
+ delete env[key];
2528
+ }
2529
+ };
2530
+ var buildResolvedEnvVars = (env) => {
2531
+ const resolved = {};
2532
+ if (env.ANTHROPIC_BASE_URL) resolved.ANTHROPIC_BASE_URL = env.ANTHROPIC_BASE_URL;
2533
+ if (env.ANTHROPIC_AUTH_TOKEN) resolved.ANTHROPIC_AUTH_TOKEN = decrypt(env.ANTHROPIC_AUTH_TOKEN);
2534
+ if (env.ANTHROPIC_DEFAULT_OPUS_MODEL) resolved.ANTHROPIC_DEFAULT_OPUS_MODEL = env.ANTHROPIC_DEFAULT_OPUS_MODEL;
2535
+ if (env.ANTHROPIC_DEFAULT_SONNET_MODEL) resolved.ANTHROPIC_DEFAULT_SONNET_MODEL = env.ANTHROPIC_DEFAULT_SONNET_MODEL;
2536
+ if (env.ANTHROPIC_DEFAULT_HAIKU_MODEL) resolved.ANTHROPIC_DEFAULT_HAIKU_MODEL = env.ANTHROPIC_DEFAULT_HAIKU_MODEL;
2537
+ if (env.ANTHROPIC_MODEL) resolved.ANTHROPIC_MODEL = env.ANTHROPIC_MODEL;
2538
+ if (env.CLAUDE_CODE_SUBAGENT_MODEL) resolved.CLAUDE_CODE_SUBAGENT_MODEL = env.CLAUDE_CODE_SUBAGENT_MODEL;
2539
+ return resolved;
2540
+ };
2541
+ var buildShellEnvCommands = (env) => {
2542
+ const resolved = buildResolvedEnvVars(env);
2543
+ return MANAGED_CLAUDE_ENV_KEYS2.map(
2544
+ (key) => resolved[key] ? `export ${key}=${shellQuote(resolved[key])}` : `unset ${key}`
2545
+ );
2546
+ };
2394
2547
  ensureCcemDir();
2395
2548
  var config2 = new Conf2({
2396
2549
  projectName: "claude-code-env-manager",
@@ -2398,16 +2551,129 @@ var config2 = new Conf2({
2398
2551
  // 使用新路径
2399
2552
  defaults: {
2400
2553
  registries: {
2401
- "official": {
2402
- ANTHROPIC_BASE_URL: "https://api.anthropic.com",
2403
- ANTHROPIC_MODEL: "claude-sonnet-4-5-20250929",
2404
- ANTHROPIC_SMALL_FAST_MODEL: "claude-haiku-4-5-20251001"
2405
- }
2554
+ official: DEFAULT_OFFICIAL_ENV
2406
2555
  },
2407
2556
  current: "official",
2408
2557
  defaultMode: null
2409
2558
  }
2410
2559
  });
2560
+ var recoverRegistriesFromLegacy = (registries) => {
2561
+ const currentAuthCount = Object.values(registries).filter(
2562
+ (env) => Boolean(env.ANTHROPIC_AUTH_TOKEN)
2563
+ ).length;
2564
+ if (currentAuthCount > 0) {
2565
+ return registries;
2566
+ }
2567
+ const legacyConfigPath = getLegacyConfigPath();
2568
+ if (!fs8.existsSync(legacyConfigPath)) {
2569
+ return registries;
2570
+ }
2571
+ try {
2572
+ const legacyRaw = JSON.parse(fs8.readFileSync(legacyConfigPath, "utf-8"));
2573
+ const legacyRegistries = legacyRaw.registries ?? {};
2574
+ let changed = false;
2575
+ const recovered = { ...registries };
2576
+ for (const [name, envConfig] of Object.entries(registries)) {
2577
+ const legacyEnvConfig = legacyRegistries[name];
2578
+ if (!legacyEnvConfig) {
2579
+ continue;
2580
+ }
2581
+ const recoveredEnv = recoverEnvConfigFromLegacy(envConfig, legacyEnvConfig);
2582
+ if (JSON.stringify(recoveredEnv) !== JSON.stringify(envConfig)) {
2583
+ recovered[name] = recoveredEnv;
2584
+ changed = true;
2585
+ }
2586
+ }
2587
+ return changed ? recovered : registries;
2588
+ } catch {
2589
+ return registries;
2590
+ }
2591
+ };
2592
+ var getRegistries = () => {
2593
+ const rawRegistries = config2.get("registries") ?? {};
2594
+ const normalizedEntries = Object.entries(rawRegistries).map(([name, envConfig]) => [
2595
+ name,
2596
+ normalizeEnvConfig(envConfig ?? {})
2597
+ ]);
2598
+ const normalizedRegistries = Object.fromEntries(normalizedEntries);
2599
+ const repairedRegistries = recoverRegistriesFromLegacy(normalizedRegistries);
2600
+ if (!repairedRegistries.official) {
2601
+ repairedRegistries.official = { ...DEFAULT_OFFICIAL_ENV };
2602
+ }
2603
+ const changed = Object.keys(rawRegistries).length !== Object.keys(repairedRegistries).length || JSON.stringify(rawRegistries) !== JSON.stringify(repairedRegistries);
2604
+ if (changed) {
2605
+ config2.set("registries", repairedRegistries);
2606
+ }
2607
+ return repairedRegistries;
2608
+ };
2609
+ var setRegistries = (registries) => {
2610
+ config2.set("registries", registries);
2611
+ };
2612
+ var getDecryptedAuthToken = (envConfig) => {
2613
+ return envConfig.ANTHROPIC_AUTH_TOKEN ? decrypt(envConfig.ANTHROPIC_AUTH_TOKEN) : void 0;
2614
+ };
2615
+ var applyPromptAnswers = (current, answers, keepCurrentSecret) => {
2616
+ const next = {
2617
+ ...current,
2618
+ ANTHROPIC_BASE_URL: answers.ANTHROPIC_BASE_URL?.trim() || current.ANTHROPIC_BASE_URL,
2619
+ ANTHROPIC_DEFAULT_OPUS_MODEL: answers.ANTHROPIC_DEFAULT_OPUS_MODEL?.trim() || current.ANTHROPIC_DEFAULT_OPUS_MODEL,
2620
+ ANTHROPIC_DEFAULT_HAIKU_MODEL: answers.ANTHROPIC_DEFAULT_HAIKU_MODEL?.trim() || current.ANTHROPIC_DEFAULT_HAIKU_MODEL,
2621
+ 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,
2622
+ ANTHROPIC_MODEL: answers.ANTHROPIC_MODEL?.trim() || current.ANTHROPIC_MODEL || "opus",
2623
+ CLAUDE_CODE_SUBAGENT_MODEL: answers.CLAUDE_CODE_SUBAGENT_MODEL?.trim() || current.CLAUDE_CODE_SUBAGENT_MODEL
2624
+ };
2625
+ if (!keepCurrentSecret) {
2626
+ next.ANTHROPIC_AUTH_TOKEN = answers.ANTHROPIC_AUTH_TOKEN ? encrypt(answers.ANTHROPIC_AUTH_TOKEN) : void 0;
2627
+ } else if (answers.ANTHROPIC_AUTH_TOKEN) {
2628
+ next.ANTHROPIC_AUTH_TOKEN = encrypt(answers.ANTHROPIC_AUTH_TOKEN);
2629
+ }
2630
+ return normalizeEnvConfig(next);
2631
+ };
2632
+ var promptForEnvironmentConfig = async (current = {}, keepCurrentSecret = false) => {
2633
+ return inquirer.prompt([
2634
+ {
2635
+ type: "input",
2636
+ name: "ANTHROPIC_BASE_URL",
2637
+ message: "ANTHROPIC_BASE_URL:",
2638
+ default: current.ANTHROPIC_BASE_URL || DEFAULT_OFFICIAL_ENV.ANTHROPIC_BASE_URL
2639
+ },
2640
+ {
2641
+ type: "password",
2642
+ name: "ANTHROPIC_AUTH_TOKEN",
2643
+ message: keepCurrentSecret ? "ANTHROPIC_AUTH_TOKEN (leave empty to keep current):" : "ANTHROPIC_AUTH_TOKEN:"
2644
+ },
2645
+ {
2646
+ type: "input",
2647
+ name: "ANTHROPIC_DEFAULT_OPUS_MODEL",
2648
+ message: "ANTHROPIC_DEFAULT_OPUS_MODEL:",
2649
+ default: current.ANTHROPIC_DEFAULT_OPUS_MODEL || DEFAULT_OFFICIAL_ENV.ANTHROPIC_DEFAULT_OPUS_MODEL
2650
+ },
2651
+ {
2652
+ type: "input",
2653
+ name: "ANTHROPIC_DEFAULT_HAIKU_MODEL",
2654
+ message: "ANTHROPIC_DEFAULT_HAIKU_MODEL:",
2655
+ default: current.ANTHROPIC_DEFAULT_HAIKU_MODEL || DEFAULT_OFFICIAL_ENV.ANTHROPIC_DEFAULT_HAIKU_MODEL
2656
+ },
2657
+ {
2658
+ type: "input",
2659
+ name: "ANTHROPIC_DEFAULT_SONNET_MODEL",
2660
+ message: "ANTHROPIC_DEFAULT_SONNET_MODEL (blank = same as opus):",
2661
+ default: current.ANTHROPIC_DEFAULT_SONNET_MODEL || current.ANTHROPIC_DEFAULT_OPUS_MODEL || ""
2662
+ },
2663
+ {
2664
+ type: "input",
2665
+ name: "ANTHROPIC_MODEL",
2666
+ message: "ANTHROPIC_MODEL (e.g. opus, opusplan, sonnet):",
2667
+ default: current.ANTHROPIC_MODEL || "opus"
2668
+ },
2669
+ {
2670
+ type: "input",
2671
+ name: "CLAUDE_CODE_SUBAGENT_MODEL",
2672
+ message: "CLAUDE_CODE_SUBAGENT_MODEL (optional):",
2673
+ default: current.CLAUDE_CODE_SUBAGENT_MODEL || ""
2674
+ }
2675
+ ]);
2676
+ };
2411
2677
  var PERMISSION_MODES = ["yolo", "dev", "readonly", "safe", "ci", "audit"];
2412
2678
  var usageStats = null;
2413
2679
  var usageLoading = true;
@@ -2446,7 +2712,7 @@ program.name("ccem").description("Claude Code Environment Manager - \u7BA1\u7406
2446
2712
  PERMISSION_MODES.forEach((mode) => {
2447
2713
  const preset = PERMISSION_PRESETS[mode];
2448
2714
  program.command(mode).description(`\u4E34\u65F6\u5E94\u7528 ${preset.name}\uFF0C\u9000\u51FA\u540E\u8FD8\u539F`).action(async () => {
2449
- const registries = config2.get("registries");
2715
+ const registries = getRegistries();
2450
2716
  const current = config2.get("current");
2451
2717
  const envConfig = registries[current];
2452
2718
  await runWithTempPermissions(mode, envConfig);
@@ -2455,15 +2721,18 @@ PERMISSION_MODES.forEach((mode) => {
2455
2721
  var showCurrentEnv = (usageStats2, usageLoading2) => {
2456
2722
  if (!process.stdout.isTTY) return;
2457
2723
  const current = config2.get("current");
2458
- const registries = config2.get("registries");
2724
+ const registries = getRegistries();
2459
2725
  const env = registries[current];
2460
2726
  const defaultMode = config2.get("defaultMode");
2461
2727
  if (!env) return;
2462
2728
  console.log(renderLogoWithEnvPanel(current, {
2463
2729
  ANTHROPIC_BASE_URL: env.ANTHROPIC_BASE_URL,
2464
- ANTHROPIC_API_KEY: env.ANTHROPIC_API_KEY ? decrypt(env.ANTHROPIC_API_KEY) : void 0,
2730
+ ANTHROPIC_AUTH_TOKEN: getDecryptedAuthToken(env),
2731
+ ANTHROPIC_DEFAULT_OPUS_MODEL: env.ANTHROPIC_DEFAULT_OPUS_MODEL,
2732
+ ANTHROPIC_DEFAULT_SONNET_MODEL: env.ANTHROPIC_DEFAULT_SONNET_MODEL,
2733
+ ANTHROPIC_DEFAULT_HAIKU_MODEL: env.ANTHROPIC_DEFAULT_HAIKU_MODEL,
2465
2734
  ANTHROPIC_MODEL: env.ANTHROPIC_MODEL,
2466
- ANTHROPIC_SMALL_FAST_MODEL: env.ANTHROPIC_SMALL_FAST_MODEL
2735
+ CLAUDE_CODE_SUBAGENT_MODEL: env.CLAUDE_CODE_SUBAGENT_MODEL
2467
2736
  }, defaultMode));
2468
2737
  console.log("");
2469
2738
  console.log(renderUsageLine(usageStats2, usageLoading2));
@@ -2471,7 +2740,7 @@ var showCurrentEnv = (usageStats2, usageLoading2) => {
2471
2740
  console.log("");
2472
2741
  };
2473
2742
  var switchEnvironment = async (name) => {
2474
- const registries = config2.get("registries");
2743
+ const registries = getRegistries();
2475
2744
  if (!registries[name]) {
2476
2745
  console.error(chalk7.red(`Environment '${name}' not found.`));
2477
2746
  return;
@@ -2484,11 +2753,7 @@ var switchEnvironment = async (name) => {
2484
2753
  }
2485
2754
  showCurrentEnv(null, false);
2486
2755
  const env = registries[name];
2487
- const exportCmds = [];
2488
- if (env.ANTHROPIC_BASE_URL) exportCmds.push(`export ANTHROPIC_BASE_URL="${env.ANTHROPIC_BASE_URL}"`);
2489
- if (env.ANTHROPIC_API_KEY) exportCmds.push(`export ANTHROPIC_API_KEY="${decrypt(env.ANTHROPIC_API_KEY)}"`);
2490
- if (env.ANTHROPIC_MODEL) exportCmds.push(`export ANTHROPIC_MODEL="${env.ANTHROPIC_MODEL}"`);
2491
- if (env.ANTHROPIC_SMALL_FAST_MODEL) exportCmds.push(`export ANTHROPIC_SMALL_FAST_MODEL="${env.ANTHROPIC_SMALL_FAST_MODEL}"`);
2756
+ const exportCmds = buildShellEnvCommands(env);
2492
2757
  if (process.stdout.isTTY) {
2493
2758
  console.log(chalk7.yellow("\nTo apply to current shell immediately, run:"));
2494
2759
  console.log(chalk7.cyan("eval $(ccem env)"));
@@ -2498,11 +2763,92 @@ var switchEnvironment = async (name) => {
2498
2763
  exportCmds.forEach((cmd) => console.log(cmd));
2499
2764
  }
2500
2765
  };
2766
+ var getSessionsFilePath = () => path6.join(getCcemConfigDir(), "sessions.json");
2767
+ var getRuntimeStateFilePath = () => path6.join(getCcemConfigDir(), "runtime-state.json");
2768
+ var parseJsonFile = (filePath) => {
2769
+ if (!fs8.existsSync(filePath)) {
2770
+ return null;
2771
+ }
2772
+ try {
2773
+ return JSON.parse(fs8.readFileSync(filePath, "utf-8"));
2774
+ } catch {
2775
+ return null;
2776
+ }
2777
+ };
2778
+ var readInteractiveAttachSessions = () => {
2779
+ const sessionsById = /* @__PURE__ */ new Map();
2780
+ const runtimeState = parseJsonFile(getRuntimeStateFilePath());
2781
+ const persistedSessions = parseJsonFile(getSessionsFilePath()) ?? [];
2782
+ for (const entry of runtimeState?.sessions ?? []) {
2783
+ if (entry.runtime_kind && entry.runtime_kind !== "interactive") {
2784
+ continue;
2785
+ }
2786
+ const fallback = persistedSessions.find((session) => session.id === entry.runtime_id);
2787
+ const tmuxTarget = entry.tmux_session && entry.tmux_window ? `${entry.tmux_session}:${entry.tmux_window}` : fallback?.window_id ?? null;
2788
+ if (!tmuxTarget) {
2789
+ continue;
2790
+ }
2791
+ sessionsById.set(entry.runtime_id, {
2792
+ id: entry.runtime_id,
2793
+ projectDir: entry.project_dir,
2794
+ envName: entry.env_name,
2795
+ permMode: entry.perm_mode,
2796
+ status: fallback?.status ?? "running",
2797
+ tmuxTarget,
2798
+ sortTime: entry.saved_at
2799
+ });
2800
+ }
2801
+ for (const session of persistedSessions) {
2802
+ if (session.status !== "running" || session.terminal_type !== "embedded" || !session.window_id) {
2803
+ continue;
2804
+ }
2805
+ if (sessionsById.has(session.id)) {
2806
+ continue;
2807
+ }
2808
+ sessionsById.set(session.id, {
2809
+ id: session.id,
2810
+ projectDir: session.working_dir,
2811
+ envName: session.env_name,
2812
+ permMode: session.perm_mode,
2813
+ status: session.status,
2814
+ tmuxTarget: session.window_id,
2815
+ sortTime: session.start_time
2816
+ });
2817
+ }
2818
+ return [...sessionsById.values()].sort(
2819
+ (left, right) => right.sortTime.localeCompare(left.sortTime)
2820
+ );
2821
+ };
2822
+ var findAttachSession = (id) => {
2823
+ const sessions = readInteractiveAttachSessions();
2824
+ if (!id) {
2825
+ return sessions[0];
2826
+ }
2827
+ const exact = sessions.find((session) => session.id === id);
2828
+ if (exact) {
2829
+ return exact;
2830
+ }
2831
+ return sessions.find((session) => session.id.startsWith(id));
2832
+ };
2833
+ var attachTmuxTarget = (target) => {
2834
+ const args = process.env.TMUX ? ["switch-client", "-t", target] : ["attach-session", "-t", target];
2835
+ return new Promise((resolve2, reject) => {
2836
+ const child = spawn3("tmux", args, { stdio: "inherit" });
2837
+ child.on("error", reject);
2838
+ child.on("exit", (code) => {
2839
+ if (code === 0 || code === null) {
2840
+ resolve2();
2841
+ } else {
2842
+ reject(new Error(`tmux exited with code ${code}`));
2843
+ }
2844
+ });
2845
+ });
2846
+ };
2501
2847
  program.command("ls").description("List all configured environments").action(() => {
2502
- const registries = config2.get("registries");
2848
+ const registries = getRegistries();
2503
2849
  const current = config2.get("current");
2504
2850
  const table = new Table3({
2505
- head: ["Name", "Base URL", "Model"],
2851
+ head: ["Name", "Base URL", "Opus"],
2506
2852
  style: { head: ["cyan"] }
2507
2853
  });
2508
2854
  Object.keys(registries).forEach((name) => {
@@ -2511,7 +2857,7 @@ program.command("ls").description("List all configured environments").action(()
2511
2857
  table.push([
2512
2858
  prefix + name,
2513
2859
  reg.ANTHROPIC_BASE_URL || "-",
2514
- reg.ANTHROPIC_MODEL || "-"
2860
+ reg.ANTHROPIC_DEFAULT_OPUS_MODEL || "-"
2515
2861
  ]);
2516
2862
  });
2517
2863
  console.log(table.toString());
@@ -2519,8 +2865,42 @@ program.command("ls").description("List all configured environments").action(()
2519
2865
  program.command("use <name>").description("Switch to a specific environment").action(async (name) => {
2520
2866
  await switchEnvironment(name);
2521
2867
  });
2868
+ program.command("sessions").description("List tmux-backed interactive sessions").action(() => {
2869
+ const sessions = readInteractiveAttachSessions();
2870
+ if (sessions.length === 0) {
2871
+ console.log(chalk7.yellow("No tmux-backed interactive sessions found."));
2872
+ return;
2873
+ }
2874
+ const table = new Table3({
2875
+ head: ["ID", "Project", "Env", "Status", "Tmux"],
2876
+ style: { head: ["cyan"] }
2877
+ });
2878
+ sessions.forEach((session) => {
2879
+ table.push([
2880
+ session.id,
2881
+ session.projectDir,
2882
+ session.envName,
2883
+ session.status,
2884
+ session.tmuxTarget
2885
+ ]);
2886
+ });
2887
+ console.log(table.toString());
2888
+ });
2889
+ program.command("attach [id]").description("Attach to a tmux-backed interactive session").action(async (id) => {
2890
+ const session = findAttachSession(id);
2891
+ if (!session) {
2892
+ console.error(chalk7.red(id ? `Interactive session '${id}' not found.` : "No interactive session available to attach."));
2893
+ process.exit(1);
2894
+ }
2895
+ try {
2896
+ await attachTmuxTarget(session.tmuxTarget);
2897
+ } catch (error) {
2898
+ console.error(chalk7.red(`Failed to attach ${session.tmuxTarget}: ${String(error)}`));
2899
+ process.exit(1);
2900
+ }
2901
+ });
2522
2902
  program.command("add <name>").description("Add a new environment configuration").action(async (name) => {
2523
- const registries = config2.get("registries");
2903
+ const registries = getRegistries();
2524
2904
  if (registries[name]) {
2525
2905
  console.log(chalk7.red(`Environment '${name}' already exists.`));
2526
2906
  return;
@@ -2545,40 +2925,13 @@ program.command("add <name>").description("Add a new environment configuration")
2545
2925
  ]);
2546
2926
  presetConfig = ENV_PRESETS[presetName];
2547
2927
  }
2548
- const answers = await inquirer.prompt([
2549
- {
2550
- type: "input",
2551
- name: "ANTHROPIC_BASE_URL",
2552
- message: "Enter ANTHROPIC_BASE_URL:",
2553
- default: presetConfig.ANTHROPIC_BASE_URL || "https://api.anthropic.com"
2554
- },
2555
- {
2556
- type: "password",
2557
- name: "ANTHROPIC_API_KEY",
2558
- message: "Enter ANTHROPIC_API_KEY:"
2559
- },
2560
- {
2561
- type: "input",
2562
- name: "ANTHROPIC_MODEL",
2563
- message: "Enter ANTHROPIC_MODEL:",
2564
- default: presetConfig.ANTHROPIC_MODEL || "claude-sonnet-4-5-20250929"
2565
- },
2566
- {
2567
- type: "input",
2568
- name: "ANTHROPIC_SMALL_FAST_MODEL",
2569
- message: "Enter ANTHROPIC_SMALL_FAST_MODEL:",
2570
- default: presetConfig.ANTHROPIC_SMALL_FAST_MODEL || "claude-haiku-4-5-20251001"
2571
- }
2572
- ]);
2573
- if (answers.ANTHROPIC_API_KEY) {
2574
- answers.ANTHROPIC_API_KEY = encrypt(answers.ANTHROPIC_API_KEY);
2575
- }
2576
- registries[name] = answers;
2577
- config2.set("registries", registries);
2928
+ const answers = await promptForEnvironmentConfig(presetConfig);
2929
+ registries[name] = applyPromptAnswers(normalizeEnvConfig(presetConfig), answers, false);
2930
+ setRegistries(registries);
2578
2931
  console.log(chalk7.green(`Environment '${name}' added successfully.`));
2579
2932
  });
2580
2933
  program.command("del <name>").description("Delete an environment configuration").action((name) => {
2581
- const registries = config2.get("registries");
2934
+ const registries = getRegistries();
2582
2935
  if (!registries[name]) {
2583
2936
  console.log(chalk7.red(`Environment '${name}' not found.`));
2584
2937
  return;
@@ -2588,7 +2941,7 @@ program.command("del <name>").description("Delete an environment configuration")
2588
2941
  return;
2589
2942
  }
2590
2943
  delete registries[name];
2591
- config2.set("registries", registries);
2944
+ setRegistries(registries);
2592
2945
  const current = config2.get("current");
2593
2946
  if (current === name) {
2594
2947
  config2.set("current", "official");
@@ -2597,7 +2950,7 @@ program.command("del <name>").description("Delete an environment configuration")
2597
2950
  console.log(chalk7.green(`Environment '${name}' deleted.`));
2598
2951
  });
2599
2952
  program.command("rename <old> <new>").description("Rename an environment configuration").action((oldName, newName) => {
2600
- const registries = config2.get("registries");
2953
+ const registries = getRegistries();
2601
2954
  if (!registries[oldName]) {
2602
2955
  console.log(chalk7.red(`Environment '${oldName}' not found.`));
2603
2956
  return;
@@ -2612,7 +2965,7 @@ program.command("rename <old> <new>").description("Rename an environment configu
2612
2965
  }
2613
2966
  registries[newName] = registries[oldName];
2614
2967
  delete registries[oldName];
2615
- config2.set("registries", registries);
2968
+ setRegistries(registries);
2616
2969
  const current = config2.get("current");
2617
2970
  if (current === oldName) {
2618
2971
  config2.set("current", newName);
@@ -2620,7 +2973,7 @@ program.command("rename <old> <new>").description("Rename an environment configu
2620
2973
  console.log(chalk7.green(`Environment '${oldName}' renamed to '${newName}'.`));
2621
2974
  });
2622
2975
  program.command("cp <source> <target>").description("Copy an environment configuration").action(async (source, target) => {
2623
- const registries = config2.get("registries");
2976
+ const registries = getRegistries();
2624
2977
  if (!registries[source]) {
2625
2978
  console.log(chalk7.red(`Environment '${source}' not found.`));
2626
2979
  return;
@@ -2630,7 +2983,7 @@ program.command("cp <source> <target>").description("Copy an environment configu
2630
2983
  return;
2631
2984
  }
2632
2985
  registries[target] = { ...registries[source] };
2633
- config2.set("registries", registries);
2986
+ setRegistries(registries);
2634
2987
  console.log(chalk7.green(`Environment '${source}' copied to '${target}'.`));
2635
2988
  const { modify } = await inquirer.prompt([
2636
2989
  {
@@ -2642,37 +2995,9 @@ program.command("cp <source> <target>").description("Copy an environment configu
2642
2995
  ]);
2643
2996
  if (modify) {
2644
2997
  const current = registries[target];
2645
- const answers = await inquirer.prompt([
2646
- {
2647
- type: "input",
2648
- name: "ANTHROPIC_BASE_URL",
2649
- message: "ANTHROPIC_BASE_URL:",
2650
- default: current.ANTHROPIC_BASE_URL
2651
- },
2652
- {
2653
- type: "password",
2654
- name: "ANTHROPIC_API_KEY",
2655
- message: "ANTHROPIC_API_KEY (leave empty to keep current):"
2656
- },
2657
- {
2658
- type: "input",
2659
- name: "ANTHROPIC_MODEL",
2660
- message: "ANTHROPIC_MODEL:",
2661
- default: current.ANTHROPIC_MODEL
2662
- },
2663
- {
2664
- type: "input",
2665
- name: "ANTHROPIC_SMALL_FAST_MODEL",
2666
- message: "ANTHROPIC_SMALL_FAST_MODEL:",
2667
- default: current.ANTHROPIC_SMALL_FAST_MODEL
2668
- }
2669
- ]);
2670
- if (answers.ANTHROPIC_BASE_URL) current.ANTHROPIC_BASE_URL = answers.ANTHROPIC_BASE_URL;
2671
- if (answers.ANTHROPIC_API_KEY) current.ANTHROPIC_API_KEY = encrypt(answers.ANTHROPIC_API_KEY);
2672
- if (answers.ANTHROPIC_MODEL) current.ANTHROPIC_MODEL = answers.ANTHROPIC_MODEL;
2673
- if (answers.ANTHROPIC_SMALL_FAST_MODEL) current.ANTHROPIC_SMALL_FAST_MODEL = answers.ANTHROPIC_SMALL_FAST_MODEL;
2674
- registries[target] = current;
2675
- config2.set("registries", registries);
2998
+ const answers = await promptForEnvironmentConfig(current, true);
2999
+ registries[target] = applyPromptAnswers(current, answers, true);
3000
+ setRegistries(registries);
2676
3001
  console.log(chalk7.green(`Environment '${target}' updated.`));
2677
3002
  }
2678
3003
  });
@@ -2681,25 +3006,22 @@ program.command("current").description("Show current environment name").action((
2681
3006
  console.log(chalk7.green(current));
2682
3007
  });
2683
3008
  program.command("env").description("Output environment variables for shell eval").option("--json", "Output as JSON").action((options) => {
2684
- const registries = config2.get("registries");
3009
+ const registries = getRegistries();
2685
3010
  const current = config2.get("current");
2686
3011
  const env = registries[current];
2687
3012
  if (!env) return;
2688
3013
  const outputEnv = { ...env };
2689
- if (outputEnv.ANTHROPIC_API_KEY) {
2690
- outputEnv.ANTHROPIC_API_KEY = decrypt(outputEnv.ANTHROPIC_API_KEY);
3014
+ if (outputEnv.ANTHROPIC_AUTH_TOKEN) {
3015
+ outputEnv.ANTHROPIC_AUTH_TOKEN = decrypt(outputEnv.ANTHROPIC_AUTH_TOKEN);
2691
3016
  }
2692
3017
  if (options.json) {
2693
3018
  console.log(JSON.stringify(outputEnv, null, 2));
2694
3019
  } else {
2695
- if (outputEnv.ANTHROPIC_BASE_URL) console.log(`export ANTHROPIC_BASE_URL="${outputEnv.ANTHROPIC_BASE_URL}"`);
2696
- if (outputEnv.ANTHROPIC_API_KEY) console.log(`export ANTHROPIC_API_KEY="${outputEnv.ANTHROPIC_API_KEY}"`);
2697
- if (outputEnv.ANTHROPIC_MODEL) console.log(`export ANTHROPIC_MODEL="${outputEnv.ANTHROPIC_MODEL}"`);
2698
- if (outputEnv.ANTHROPIC_SMALL_FAST_MODEL) console.log(`export ANTHROPIC_SMALL_FAST_MODEL="${outputEnv.ANTHROPIC_SMALL_FAST_MODEL}"`);
3020
+ buildShellEnvCommands(env).forEach((cmd) => console.log(cmd));
2699
3021
  }
2700
3022
  });
2701
3023
  program.command("run <command...>").description("Run a command with the current environment variables").action((command) => {
2702
- const registries = config2.get("registries");
3024
+ const registries = getRegistries();
2703
3025
  const current = config2.get("current");
2704
3026
  const envConfig = registries[current];
2705
3027
  if (!envConfig) {
@@ -2707,10 +3029,8 @@ program.command("run <command...>").description("Run a command with the current
2707
3029
  process.exit(1);
2708
3030
  }
2709
3031
  const env = { ...process.env };
2710
- if (envConfig.ANTHROPIC_BASE_URL) env.ANTHROPIC_BASE_URL = envConfig.ANTHROPIC_BASE_URL;
2711
- if (envConfig.ANTHROPIC_API_KEY) env.ANTHROPIC_API_KEY = decrypt(envConfig.ANTHROPIC_API_KEY || "");
2712
- if (envConfig.ANTHROPIC_MODEL) env.ANTHROPIC_MODEL = envConfig.ANTHROPIC_MODEL;
2713
- if (envConfig.ANTHROPIC_SMALL_FAST_MODEL) env.ANTHROPIC_SMALL_FAST_MODEL = envConfig.ANTHROPIC_SMALL_FAST_MODEL;
3032
+ clearManagedClaudeEnv(env);
3033
+ Object.assign(env, buildResolvedEnvVars(envConfig));
2714
3034
  const [cmd, ...args] = command;
2715
3035
  const child = spawn3(cmd, args, {
2716
3036
  env,
@@ -2879,20 +3199,33 @@ skillCmd.command("ls").description("\u5217\u51FA\u5DF2\u5B89\u88C5\u7684 skills"
2879
3199
  skillCmd.command("rm <name>").description("\u5220\u9664\u5DF2\u5B89\u88C5\u7684 skill").action((name) => {
2880
3200
  removeSkill(name);
2881
3201
  });
2882
- program.command("load <url>").description("\u4ECE\u8FDC\u7A0B\u670D\u52A1\u5668\u52A0\u8F7D\u73AF\u5883\u914D\u7F6E").requiredOption("--secret <secret>", "\u89E3\u5BC6\u5BC6\u94A5").action(async (url, options) => {
2883
- await loadFromRemote(url, options.secret);
3202
+ program.command("load <url>").description("\u4ECE\u8FDC\u7A0B\u670D\u52A1\u5668\u52A0\u8F7D\u73AF\u5883\u914D\u7F6E").requiredOption("--secret <secret>", "\u89E3\u5BC6\u5BC6\u94A5").option("--json", "\u4EE5 JSON \u683C\u5F0F\u8F93\u51FA\u7ED3\u679C\uFF08\u4F9B\u7A0B\u5E8F\u8C03\u7528\uFF09").action(async (url, options) => {
3203
+ const results = await loadFromRemote(url, options.secret);
3204
+ if (options.json) {
3205
+ console.log(JSON.stringify({
3206
+ count: results.length,
3207
+ environments: results.map((r) => ({
3208
+ name: r.name,
3209
+ original_name: r.originalName,
3210
+ // 使用 snake_case 匹配 Rust 结构体
3211
+ renamed: r.renamed
3212
+ }))
3213
+ }));
3214
+ }
2884
3215
  });
2885
- 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").action(async function() {
3216
+ 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() {
2886
3217
  const opts = this.opts();
2887
3218
  const envName = opts.env || config2.get("current");
2888
- const registries = config2.get("registries");
3219
+ const registries = getRegistries();
2889
3220
  const envConfig = registries[envName];
2890
3221
  if (!envConfig) {
2891
3222
  console.error(chalk7.red(`Environment '${envName}' not found.`));
2892
3223
  process.exit(1);
2893
3224
  }
3225
+ const proxyBaseUrl = opts.proxyBaseUrl || opts.anthropicBaseUrl;
3226
+ const launchEnvConfig = proxyBaseUrl ? { ...envConfig, ANTHROPIC_BASE_URL: proxyBaseUrl } : envConfig;
2894
3227
  await launchClaude({
2895
- envConfig,
3228
+ envConfig: launchEnvConfig,
2896
3229
  permMode: opts.perm,
2897
3230
  workingDir: opts.workingDir,
2898
3231
  sessionId: opts.sessionId,
@@ -2928,7 +3261,7 @@ program.action(async (options) => {
2928
3261
  showCurrentEnv(usageStats, usageLoading);
2929
3262
  console.log("");
2930
3263
  const defaultMode = config2.get("defaultMode");
2931
- const registries = config2.get("registries");
3264
+ const registries = getRegistries();
2932
3265
  const current = config2.get("current");
2933
3266
  const envConfig = registries[current];
2934
3267
  const { action } = await inquirer.prompt([
@@ -2975,37 +3308,9 @@ program.action(async (options) => {
2975
3308
  const envToEdit = registries[result.name];
2976
3309
  console.log(chalk7.yellow(`
2977
3310
  Editing environment '${result.name}'`));
2978
- const answers = await inquirer.prompt([
2979
- {
2980
- type: "input",
2981
- name: "ANTHROPIC_BASE_URL",
2982
- message: "ANTHROPIC_BASE_URL:",
2983
- default: envToEdit.ANTHROPIC_BASE_URL
2984
- },
2985
- {
2986
- type: "password",
2987
- name: "ANTHROPIC_API_KEY",
2988
- message: "ANTHROPIC_API_KEY (leave empty to keep current):"
2989
- },
2990
- {
2991
- type: "input",
2992
- name: "ANTHROPIC_MODEL",
2993
- message: "ANTHROPIC_MODEL:",
2994
- default: envToEdit.ANTHROPIC_MODEL
2995
- },
2996
- {
2997
- type: "input",
2998
- name: "ANTHROPIC_SMALL_FAST_MODEL",
2999
- message: "ANTHROPIC_SMALL_FAST_MODEL:",
3000
- default: envToEdit.ANTHROPIC_SMALL_FAST_MODEL
3001
- }
3002
- ]);
3003
- if (answers.ANTHROPIC_BASE_URL) envToEdit.ANTHROPIC_BASE_URL = answers.ANTHROPIC_BASE_URL;
3004
- if (answers.ANTHROPIC_API_KEY) envToEdit.ANTHROPIC_API_KEY = encrypt(answers.ANTHROPIC_API_KEY);
3005
- if (answers.ANTHROPIC_MODEL) envToEdit.ANTHROPIC_MODEL = answers.ANTHROPIC_MODEL;
3006
- if (answers.ANTHROPIC_SMALL_FAST_MODEL) envToEdit.ANTHROPIC_SMALL_FAST_MODEL = answers.ANTHROPIC_SMALL_FAST_MODEL;
3007
- registries[result.name] = envToEdit;
3008
- config2.set("registries", registries);
3311
+ const answers = await promptForEnvironmentConfig(envToEdit, true);
3312
+ registries[result.name] = applyPromptAnswers(envToEdit, answers, true);
3313
+ setRegistries(registries);
3009
3314
  msg.success(`Environment '${result.name}' updated.`);
3010
3315
  await new Promise((resolve2) => setTimeout(resolve2, 800));
3011
3316
  } else if (result.action === "rename") {
@@ -3027,7 +3332,7 @@ Editing environment '${result.name}'`));
3027
3332
  ]);
3028
3333
  registries[newName] = registries[result.name];
3029
3334
  delete registries[result.name];
3030
- config2.set("registries", registries);
3335
+ setRegistries(registries);
3031
3336
  if (current === result.name) {
3032
3337
  config2.set("current", newName);
3033
3338
  }
@@ -3048,7 +3353,7 @@ Editing environment '${result.name}'`));
3048
3353
  }
3049
3354
  ]);
3050
3355
  registries[targetName] = { ...registries[result.name] };
3051
- config2.set("registries", registries);
3356
+ setRegistries(registries);
3052
3357
  msg.success(`Environment '${result.name}' copied to '${targetName}'.`);
3053
3358
  const { modify } = await inquirer.prompt([
3054
3359
  {
@@ -3060,37 +3365,9 @@ Editing environment '${result.name}'`));
3060
3365
  ]);
3061
3366
  if (modify) {
3062
3367
  const envToEdit = registries[targetName];
3063
- const editAnswers = await inquirer.prompt([
3064
- {
3065
- type: "input",
3066
- name: "ANTHROPIC_BASE_URL",
3067
- message: "ANTHROPIC_BASE_URL:",
3068
- default: envToEdit.ANTHROPIC_BASE_URL
3069
- },
3070
- {
3071
- type: "password",
3072
- name: "ANTHROPIC_API_KEY",
3073
- message: "ANTHROPIC_API_KEY (leave empty to keep current):"
3074
- },
3075
- {
3076
- type: "input",
3077
- name: "ANTHROPIC_MODEL",
3078
- message: "ANTHROPIC_MODEL:",
3079
- default: envToEdit.ANTHROPIC_MODEL
3080
- },
3081
- {
3082
- type: "input",
3083
- name: "ANTHROPIC_SMALL_FAST_MODEL",
3084
- message: "ANTHROPIC_SMALL_FAST_MODEL:",
3085
- default: envToEdit.ANTHROPIC_SMALL_FAST_MODEL
3086
- }
3087
- ]);
3088
- if (editAnswers.ANTHROPIC_BASE_URL) envToEdit.ANTHROPIC_BASE_URL = editAnswers.ANTHROPIC_BASE_URL;
3089
- if (editAnswers.ANTHROPIC_API_KEY) envToEdit.ANTHROPIC_API_KEY = encrypt(editAnswers.ANTHROPIC_API_KEY);
3090
- if (editAnswers.ANTHROPIC_MODEL) envToEdit.ANTHROPIC_MODEL = editAnswers.ANTHROPIC_MODEL;
3091
- if (editAnswers.ANTHROPIC_SMALL_FAST_MODEL) envToEdit.ANTHROPIC_SMALL_FAST_MODEL = editAnswers.ANTHROPIC_SMALL_FAST_MODEL;
3092
- registries[targetName] = envToEdit;
3093
- config2.set("registries", registries);
3368
+ const editAnswers = await promptForEnvironmentConfig(envToEdit, true);
3369
+ registries[targetName] = applyPromptAnswers(envToEdit, editAnswers, true);
3370
+ setRegistries(registries);
3094
3371
  msg.success(`Environment '${targetName}' updated.`);
3095
3372
  }
3096
3373
  await new Promise((resolve2) => setTimeout(resolve2, 800));
@@ -3109,7 +3386,7 @@ Editing environment '${result.name}'`));
3109
3386
  ]);
3110
3387
  if (confirm) {
3111
3388
  delete registries[result.name];
3112
- config2.set("registries", registries);
3389
+ setRegistries(registries);
3113
3390
  if (current === result.name) {
3114
3391
  config2.set("current", "official");
3115
3392
  msg.warning(`Deleted current environment. Switched back to 'official'.`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ccem",
3
- "version": "2.0.0-beta.2",
3
+ "version": "2.0.0-beta.5",
4
4
  "type": "module",
5
5
  "description": "Claude Code Environment Manager",
6
6
  "author": {
@@ -36,7 +36,7 @@
36
36
  "tsup": "^8.0.2",
37
37
  "typescript": "^5.3.3",
38
38
  "vitest": "^4.0.18",
39
- "@ccem/core": "2.0.0-beta.2"
39
+ "@ccem/core": "2.0.0-beta.3"
40
40
  },
41
41
  "scripts": {
42
42
  "generate-logo": "bash scripts/generate-logo.sh",