ccem 2.0.0-beta.2 → 2.0.0-beta.21

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 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 fs8 from "fs";
11
- import * as path6 from "path";
12
- import { fileURLToPath } from "url";
10
+ import * as fs9 from "fs";
11
+ import * as path7 from "path";
12
+ import { fileURLToPath as fileURLToPath2 } from "url";
13
13
 
14
- // ../../packages/core/dist/chunk-YO7HO5AS.js
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
- ANTHROPIC_MODEL: "glm-4.6",
19
- ANTHROPIC_SMALL_FAST_MODEL: "glm-4.5-air"
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
- ANTHROPIC_MODEL: "kimi-k2-thinking-turbo",
24
- ANTHROPIC_SMALL_FAST_MODEL: "kimi-k2-turbo-preview"
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
- ANTHROPIC_MODEL: "MiniMax-M2",
29
- ANTHROPIC_SMALL_FAST_MODEL: "MiniMax-M2"
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
- ANTHROPIC_MODEL: "deepseek-chat",
34
- ANTHROPIC_SMALL_FAST_MODEL: "deepseek-chat"
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 = {
@@ -338,6 +434,9 @@ import * as fsPromises from "fs/promises";
338
434
  import * as path2 from "path";
339
435
  import * as os from "os";
340
436
  import * as readline from "readline";
437
+ import { fileURLToPath } from "url";
438
+ var __filename = fileURLToPath(import.meta.url);
439
+ var __dirname = path2.dirname(__filename);
341
440
  var CLAUDE_PROJECTS_DIR = path2.join(os.homedir(), ".claude", "projects");
342
441
  var CCEM_DIR = path2.join(os.homedir(), ".ccem");
343
442
  var CACHE_VERSION = 1;
@@ -811,9 +910,10 @@ var renderLogoWithEnvPanel = (envName, env, defaultMode) => {
811
910
  const titleShort = theme.primary("CCEM");
812
911
  const envLabel = theme.muted("Env: ") + theme.primary(envName);
813
912
  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) : "-";
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) : "-";
817
917
  const truncate = (s, max) => s.length > max ? s.slice(0, max - 3) + "..." : s;
818
918
  const maskUrl = (url, max) => {
819
919
  if (url.length <= max) return url;
@@ -821,10 +921,10 @@ var renderLogoWithEnvPanel = (envName, env, defaultMode) => {
821
921
  const parsed = new URL(url);
822
922
  const protocol = parsed.protocol + "//";
823
923
  const host = parsed.host;
824
- const path7 = parsed.pathname + parsed.search;
924
+ const path8 = parsed.pathname + parsed.search;
825
925
  const hostStart = host.slice(0, 8);
826
926
  const hostEnd = host.slice(-4);
827
- const pathPart = path7.length > 10 ? path7.slice(0, 7) + "..." : path7;
927
+ const pathPart = path8.length > 10 ? path8.slice(0, 7) + "..." : path8;
828
928
  return `${protocol}${hostStart}...${hostEnd}${pathPart}`;
829
929
  } catch {
830
930
  return truncate(url, max);
@@ -835,15 +935,15 @@ var renderLogoWithEnvPanel = (envName, env, defaultMode) => {
835
935
  if (isNarrow) {
836
936
  envLines = [
837
937
  envLabel,
838
- theme.muted("Model:".padEnd(labelWidth)) + theme.dim(truncate(model, 25)),
839
- theme.muted("Key:".padEnd(labelWidth)) + theme.dim(apiKey)
938
+ theme.muted("Opus:".padEnd(labelWidth)) + theme.dim(truncate(opusModel, 25)),
939
+ theme.muted("Token:".padEnd(labelWidth)) + theme.dim(authToken)
840
940
  ];
841
941
  } else {
842
942
  envLines = [
843
943
  envLabel + (defaultMode && PERMISSION_PRESETS[defaultMode] ? " " + theme.accent(`[${PERMISSION_PRESETS[defaultMode].name}]`) : ""),
844
944
  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)
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)
847
947
  ];
848
948
  }
849
949
  const lines = [];
@@ -1262,7 +1362,7 @@ var selectEnvWithKeys = (registries, current) => {
1262
1362
  };
1263
1363
 
1264
1364
  // src/permissions.ts
1265
- import fs5 from "fs";
1365
+ import fs6 from "fs";
1266
1366
  import chalk3 from "chalk";
1267
1367
  import Table2 from "cli-table3";
1268
1368
 
@@ -1315,15 +1415,367 @@ var ensureGlobalClaudeDir = () => {
1315
1415
 
1316
1416
  // src/launcher.ts
1317
1417
  import { spawn } from "child_process";
1318
- import * as fs4 from "fs";
1319
- import * as path4 from "path";
1418
+ import * as fs5 from "fs";
1419
+ import * as path5 from "path";
1320
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
+ ];
1321
1770
  function buildEnvVars(envConfig) {
1322
1771
  const vars = {};
1323
1772
  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);
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;
1325
1777
  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;
1778
+ if (envConfig.CLAUDE_CODE_SUBAGENT_MODEL) vars.CLAUDE_CODE_SUBAGENT_MODEL = envConfig.CLAUDE_CODE_SUBAGENT_MODEL;
1327
1779
  return vars;
1328
1780
  }
1329
1781
  function buildPermArgs(modeName) {
@@ -1341,15 +1793,18 @@ function buildPermArgs(modeName) {
1341
1793
  return args;
1342
1794
  }
1343
1795
  function ensureSessionsDir() {
1344
- const dir = path4.join(ensureCcemDir(), "sessions");
1345
- if (!fs4.existsSync(dir)) {
1346
- fs4.mkdirSync(dir, { recursive: true });
1796
+ const dir = path5.join(ensureCcemDir(), "sessions");
1797
+ if (!fs5.existsSync(dir)) {
1798
+ fs5.mkdirSync(dir, { recursive: true });
1347
1799
  }
1348
1800
  return dir;
1349
1801
  }
1350
1802
  async function launchClaude(options) {
1351
- const { envConfig, permMode, workingDir, sessionId, resumeSessionId, silent } = options;
1803
+ const { envName, envConfig, permMode, workingDir, sessionId, resumeSessionId, silent } = options;
1352
1804
  const env = { ...process.env };
1805
+ for (const key of MANAGED_CLAUDE_ENV_KEYS) {
1806
+ delete env[key];
1807
+ }
1353
1808
  if (envConfig) {
1354
1809
  Object.assign(env, buildEnvVars(envConfig));
1355
1810
  }
@@ -1372,21 +1827,36 @@ async function launchClaude(options) {
1372
1827
  if (workingDir) {
1373
1828
  process.chdir(workingDir);
1374
1829
  }
1830
+ const effectiveWorkingDir = process.cwd();
1375
1831
  if (!silent && !permMode) {
1376
1832
  console.log(renderStarting());
1377
1833
  }
1378
1834
  const sessionsDir = ensureSessionsDir();
1379
1835
  return new Promise((resolve2) => {
1836
+ let provenanceTracking = null;
1380
1837
  const child = spawn("claude", args, {
1381
1838
  stdio: "inherit",
1382
- shell: true,
1839
+ shell: false,
1840
+ // 直接执行二进制,避免 shell 注入风险
1383
1841
  env
1384
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
+ });
1385
1854
  child.on("exit", (code) => {
1855
+ provenanceTracking?.stop();
1386
1856
  if (sessionId) {
1387
1857
  try {
1388
- fs4.writeFileSync(
1389
- path4.join(sessionsDir, `${sessionId}.exit`),
1858
+ fs5.writeFileSync(
1859
+ path5.join(sessionsDir, `${sessionId}.exit`),
1390
1860
  String(code ?? 0)
1391
1861
  );
1392
1862
  } catch {
@@ -1395,7 +1865,24 @@ async function launchClaude(options) {
1395
1865
  process.exit(code ?? 0);
1396
1866
  });
1397
1867
  child.on("error", (err) => {
1398
- console.error(chalk2.red(`\u542F\u52A8 Claude Code \u5931\u8D25: ${err.message}`));
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
+ }
1399
1886
  process.exit(1);
1400
1887
  });
1401
1888
  });
@@ -1403,14 +1890,14 @@ async function launchClaude(options) {
1403
1890
 
1404
1891
  // src/permissions.ts
1405
1892
  var readSettings = (settingsPath) => {
1406
- if (fs5.existsSync(settingsPath)) {
1893
+ if (fs6.existsSync(settingsPath)) {
1407
1894
  try {
1408
- const content = fs5.readFileSync(settingsPath, "utf-8");
1895
+ const content = fs6.readFileSync(settingsPath, "utf-8");
1409
1896
  return JSON.parse(content);
1410
1897
  } catch {
1411
1898
  console.warn(chalk3.yellow(`\u8B66\u544A: \u65E0\u6CD5\u89E3\u6790 ${settingsPath}\uFF0C\u5C06\u521B\u5EFA\u5907\u4EFD`));
1412
1899
  const backupPath = settingsPath + ".error." + Date.now();
1413
- fs5.copyFileSync(settingsPath, backupPath);
1900
+ fs6.copyFileSync(settingsPath, backupPath);
1414
1901
  console.log(chalk3.gray(`\u5907\u4EFD\u5DF2\u4FDD\u5B58\u5230: ${backupPath}`));
1415
1902
  return {};
1416
1903
  }
@@ -1419,7 +1906,7 @@ var readSettings = (settingsPath) => {
1419
1906
  };
1420
1907
  var writeSettings = (settingsPath, config3) => {
1421
1908
  ensureClaudeDir();
1422
- fs5.writeFileSync(settingsPath, JSON.stringify(config3, null, 2) + "\n");
1909
+ fs6.writeFileSync(settingsPath, JSON.stringify(config3, null, 2) + "\n");
1423
1910
  };
1424
1911
  var mergePermissions = (existing, preset) => {
1425
1912
  const existingAllow = existing.permissions?.allow || [];
@@ -1451,14 +1938,14 @@ var applyPermissionMode = (modeName) => {
1451
1938
  };
1452
1939
  var resetPermissions = () => {
1453
1940
  const settingsPath = getSettingsPath(true);
1454
- if (!fs5.existsSync(settingsPath)) {
1941
+ if (!fs6.existsSync(settingsPath)) {
1455
1942
  console.log(chalk3.yellow("\u6CA1\u6709\u81EA\u5B9A\u4E49\u6743\u9650\u914D\u7F6E\u9700\u8981\u91CD\u7F6E"));
1456
1943
  return;
1457
1944
  }
1458
1945
  const config3 = readSettings(settingsPath);
1459
1946
  delete config3.permissions;
1460
1947
  if (Object.keys(config3).length === 0) {
1461
- fs5.unlinkSync(settingsPath);
1948
+ fs6.unlinkSync(settingsPath);
1462
1949
  console.log(chalk3.green("\u5DF2\u5220\u9664\u914D\u7F6E\u6587\u4EF6\uFF08\u6587\u4EF6\u4E3A\u7A7A\uFF09"));
1463
1950
  } else {
1464
1951
  writeSettings(settingsPath, config3);
@@ -1468,7 +1955,7 @@ var resetPermissions = () => {
1468
1955
  };
1469
1956
  var showCurrentMode = () => {
1470
1957
  const settingsPath = getSettingsPath(true);
1471
- if (!fs5.existsSync(settingsPath)) {
1958
+ if (!fs6.existsSync(settingsPath)) {
1472
1959
  console.log(chalk3.yellow("\u672A\u914D\u7F6E\u81EA\u5B9A\u4E49\u6743\u9650"));
1473
1960
  console.log(chalk3.gray(`\u6587\u4EF6\u4E0D\u5B58\u5728: ${settingsPath}`));
1474
1961
  return;
@@ -1518,24 +2005,24 @@ var listAvailableModes = () => {
1518
2005
  console.log(chalk3.gray("\n\u4E34\u65F6\u6A21\u5F0F: ccem <mode>"));
1519
2006
  console.log(chalk3.gray("\u6C38\u4E45\u6A21\u5F0F: ccem setup perms --<mode>"));
1520
2007
  };
1521
- var runWithTempPermissions = async (modeName, envConfig) => {
2008
+ var runWithTempPermissions = async (modeName, envConfig, envName) => {
1522
2009
  const preset = PERMISSION_PRESETS[modeName];
1523
2010
  if (!preset) {
1524
2011
  console.error(chalk3.red(`\u672A\u77E5\u7684\u6743\u9650\u6A21\u5F0F: ${modeName}`));
1525
2012
  console.log(chalk3.yellow("\u53EF\u7528\u6A21\u5F0F: " + getPermissionModeNames().join(", ")));
1526
2013
  process.exit(1);
1527
2014
  }
1528
- await launchClaude({ envConfig, permMode: modeName });
2015
+ await launchClaude({ envConfig, envName, permMode: modeName });
1529
2016
  };
1530
2017
 
1531
2018
  // src/setup.ts
1532
- import fs6 from "fs";
2019
+ import fs7 from "fs";
1533
2020
  import chalk4 from "chalk";
1534
2021
  import { spawn as spawn2 } from "child_process";
1535
2022
  var readJsonFile = (filePath) => {
1536
- if (fs6.existsSync(filePath)) {
2023
+ if (fs7.existsSync(filePath)) {
1537
2024
  try {
1538
- const content = fs6.readFileSync(filePath, "utf-8");
2025
+ const content = fs7.readFileSync(filePath, "utf-8");
1539
2026
  return JSON.parse(content);
1540
2027
  } catch {
1541
2028
  console.warn(chalk4.yellow(`\u8B66\u544A: \u65E0\u6CD5\u89E3\u6790 ${filePath}`));
@@ -1545,7 +2032,7 @@ var readJsonFile = (filePath) => {
1545
2032
  return {};
1546
2033
  };
1547
2034
  var writeJsonFile = (filePath, data) => {
1548
- fs6.writeFileSync(filePath, JSON.stringify(data, null, 2) + "\n");
2035
+ fs7.writeFileSync(filePath, JSON.stringify(data, null, 2) + "\n");
1549
2036
  };
1550
2037
  var setupOnboarding = () => {
1551
2038
  const configPath = getGlobalClaudeConfigPath();
@@ -1645,14 +2132,19 @@ var setupMcpTool = () => {
1645
2132
  });
1646
2133
  });
1647
2134
  };
1648
- var runSetupInit = async () => {
2135
+ var runSetupInit = async (options = {}) => {
1649
2136
  console.log(chalk4.bold("\n\u{1F527} Claude Code \u521D\u59CB\u5316\u8BBE\u7F6E\n"));
1650
2137
  console.log(chalk4.cyan("1. \u8BBE\u7F6E onboarding \u72B6\u6001"));
1651
2138
  const step1 = setupOnboarding();
1652
2139
  console.log(chalk4.cyan("\n2. \u914D\u7F6E\u9690\u79C1\u8BBE\u7F6E"));
1653
2140
  const step2 = setupEnvSettings();
1654
- console.log(chalk4.cyan("\n3. \u5B89\u88C5 MCP \u5DE5\u5177"));
1655
- const step3 = await setupMcpTool();
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
+ }
1656
2148
  console.log("");
1657
2149
  if (step1 && step2 && step3) {
1658
2150
  console.log(chalk4.green.bold("\u2705 \u521D\u59CB\u5316\u5B8C\u6210\uFF01"));
@@ -1667,8 +2159,8 @@ var runSetupInit = async () => {
1667
2159
 
1668
2160
  // src/skills.ts
1669
2161
  import { execSync } from "child_process";
1670
- import * as fs7 from "fs";
1671
- import * as path5 from "path";
2162
+ import * as fs8 from "fs";
2163
+ import * as path6 from "path";
1672
2164
  import chalk5 from "chalk";
1673
2165
  var SKILL_GROUPS = {
1674
2166
  official: { label: "\u5B98\u65B9", icon: "\u{1F3E2}" },
@@ -1837,12 +2329,12 @@ function parseGitHubUrl(url) {
1837
2329
  };
1838
2330
  }
1839
2331
  function getSkillsDir() {
1840
- return path5.join(process.cwd(), ".claude", "skills");
2332
+ return path6.join(process.cwd(), ".claude", "skills");
1841
2333
  }
1842
2334
  function ensureSkillsDir() {
1843
2335
  const skillsDir = getSkillsDir();
1844
- if (!fs7.existsSync(skillsDir)) {
1845
- fs7.mkdirSync(skillsDir, { recursive: true });
2336
+ if (!fs8.existsSync(skillsDir)) {
2337
+ fs8.mkdirSync(skillsDir, { recursive: true });
1846
2338
  } else {
1847
2339
  cleanupTempDirs(skillsDir);
1848
2340
  }
@@ -1850,11 +2342,11 @@ function ensureSkillsDir() {
1850
2342
  }
1851
2343
  function cleanupTempDirs(skillsDir) {
1852
2344
  try {
1853
- const entries = fs7.readdirSync(skillsDir, { withFileTypes: true });
2345
+ const entries = fs8.readdirSync(skillsDir, { withFileTypes: true });
1854
2346
  for (const entry of entries) {
1855
2347
  if (entry.isDirectory() && entry.name.startsWith(".tmp-")) {
1856
- const tmpPath = path5.join(skillsDir, entry.name);
1857
- fs7.rmSync(tmpPath, { recursive: true });
2348
+ const tmpPath = path6.join(skillsDir, entry.name);
2349
+ fs8.rmSync(tmpPath, { recursive: true });
1858
2350
  }
1859
2351
  }
1860
2352
  } catch {
@@ -1862,24 +2354,24 @@ function cleanupTempDirs(skillsDir) {
1862
2354
  }
1863
2355
  function downloadSkillWithGit(owner, repo, branch, repoPath, targetName) {
1864
2356
  const skillsDir = ensureSkillsDir();
1865
- const targetDir = path5.join(skillsDir, targetName);
1866
- if (fs7.existsSync(targetDir)) {
2357
+ const targetDir = path6.join(skillsDir, targetName);
2358
+ if (fs8.existsSync(targetDir)) {
1867
2359
  console.log(chalk5.yellow(`Skill "${targetName}" already exists. Updating...`));
1868
- fs7.rmSync(targetDir, { recursive: true });
2360
+ fs8.rmSync(targetDir, { recursive: true });
1869
2361
  }
1870
2362
  const repoUrl = `https://github.com/${owner}/${repo}.git`;
1871
- const tempDir = path5.join(skillsDir, `.tmp-${Date.now()}`);
2363
+ const tempDir = path6.join(skillsDir, `.tmp-${Date.now()}`);
1872
2364
  try {
1873
- fs7.mkdirSync(tempDir, { recursive: true });
2365
+ fs8.mkdirSync(tempDir, { recursive: true });
1874
2366
  execSync(`git init`, { cwd: tempDir, stdio: "pipe" });
1875
2367
  execSync(`git remote add origin ${repoUrl}`, { cwd: tempDir, stdio: "pipe" });
1876
2368
  execSync(`git config core.sparseCheckout true`, { cwd: tempDir, stdio: "pipe" });
1877
- const sparseFile = path5.join(tempDir, ".git", "info", "sparse-checkout");
1878
- fs7.writeFileSync(sparseFile, repoPath ? `${repoPath}/
2369
+ const sparseFile = path6.join(tempDir, ".git", "info", "sparse-checkout");
2370
+ fs8.writeFileSync(sparseFile, repoPath ? `${repoPath}/
1879
2371
  ` : "*\n");
1880
2372
  execSync(`git pull --depth=1 origin ${branch}`, { cwd: tempDir, stdio: "pipe" });
1881
- const sourceDir = repoPath ? path5.join(tempDir, repoPath) : tempDir;
1882
- if (!fs7.existsSync(sourceDir)) {
2373
+ const sourceDir = repoPath ? path6.join(tempDir, repoPath) : tempDir;
2374
+ if (!fs8.existsSync(sourceDir)) {
1883
2375
  throw new Error(`Path "${repoPath}" not found in repository`);
1884
2376
  }
1885
2377
  copyDir(sourceDir, targetDir);
@@ -1890,22 +2382,22 @@ function downloadSkillWithGit(owner, repo, branch, repoPath, targetName) {
1890
2382
  console.error(chalk5.red(`Failed to download skill: ${errMsg}`));
1891
2383
  return false;
1892
2384
  } finally {
1893
- if (fs7.existsSync(tempDir)) {
1894
- fs7.rmSync(tempDir, { recursive: true });
2385
+ if (fs8.existsSync(tempDir)) {
2386
+ fs8.rmSync(tempDir, { recursive: true });
1895
2387
  }
1896
2388
  }
1897
2389
  }
1898
2390
  function copyDir(src, dest) {
1899
- fs7.mkdirSync(dest, { recursive: true });
1900
- const entries = fs7.readdirSync(src, { withFileTypes: true });
2391
+ fs8.mkdirSync(dest, { recursive: true });
2392
+ const entries = fs8.readdirSync(src, { withFileTypes: true });
1901
2393
  for (const entry of entries) {
1902
2394
  if (entry.name === ".git") continue;
1903
- const srcPath = path5.join(src, entry.name);
1904
- const destPath = path5.join(dest, entry.name);
2395
+ const srcPath = path6.join(src, entry.name);
2396
+ const destPath = path6.join(dest, entry.name);
1905
2397
  if (entry.isDirectory()) {
1906
2398
  copyDir(srcPath, destPath);
1907
2399
  } else {
1908
- fs7.copyFileSync(srcPath, destPath);
2400
+ fs8.copyFileSync(srcPath, destPath);
1909
2401
  }
1910
2402
  }
1911
2403
  }
@@ -1951,7 +2443,7 @@ function addSkillFromGitHub(urlOrPreset) {
1951
2443
  }
1952
2444
  let skillName;
1953
2445
  if (parsed.path) {
1954
- skillName = path5.basename(parsed.path);
2446
+ skillName = path6.basename(parsed.path);
1955
2447
  } else {
1956
2448
  skillName = parsed.repo;
1957
2449
  }
@@ -1965,23 +2457,23 @@ function addSkillFromGitHub(urlOrPreset) {
1965
2457
  }
1966
2458
  function listInstalledSkills() {
1967
2459
  const skillsDir = getSkillsDir();
1968
- if (!fs7.existsSync(skillsDir)) {
2460
+ if (!fs8.existsSync(skillsDir)) {
1969
2461
  return [];
1970
2462
  }
1971
- const entries = fs7.readdirSync(skillsDir, { withFileTypes: true });
2463
+ const entries = fs8.readdirSync(skillsDir, { withFileTypes: true });
1972
2464
  return entries.filter((entry) => entry.isDirectory() && !entry.name.startsWith(".")).map((entry) => ({
1973
2465
  name: entry.name,
1974
- path: path5.join(skillsDir, entry.name)
2466
+ path: path6.join(skillsDir, entry.name)
1975
2467
  }));
1976
2468
  }
1977
2469
  function removeSkill(name) {
1978
2470
  const skillsDir = getSkillsDir();
1979
- const targetDir = path5.join(skillsDir, name);
1980
- if (!fs7.existsSync(targetDir)) {
2471
+ const targetDir = path6.join(skillsDir, name);
2472
+ if (!fs8.existsSync(targetDir)) {
1981
2473
  console.error(chalk5.red(`Skill "${name}" not found`));
1982
2474
  return false;
1983
2475
  }
1984
- fs7.rmSync(targetDir, { recursive: true });
2476
+ fs8.rmSync(targetDir, { recursive: true });
1985
2477
  console.log(chalk5.green(`Removed skill "${name}"`));
1986
2478
  return true;
1987
2479
  }
@@ -2143,7 +2635,9 @@ import crypto3 from "crypto";
2143
2635
  import chalk6 from "chalk";
2144
2636
  import Conf from "conf";
2145
2637
  var config = new Conf({
2146
- projectName: "claude-code-env-manager"
2638
+ projectName: "claude-code-env-manager",
2639
+ cwd: getCcemConfigDir()
2640
+ // 使用统一的配置目录
2147
2641
  });
2148
2642
  var decryptWithSecret = (encryptedBase64, secret) => {
2149
2643
  const key = crypto3.scryptSync(secret, "ccem-salt", 32);
@@ -2171,7 +2665,11 @@ var loadFromRemote = async (url, secret) => {
2171
2665
  console.log(chalk6.gray("Fetching from remote..."));
2172
2666
  let response;
2173
2667
  try {
2174
- response = await fetch(url);
2668
+ response = await fetch(url, {
2669
+ headers: {
2670
+ "X-CCEM-Key": secret
2671
+ }
2672
+ });
2175
2673
  } catch (err) {
2176
2674
  console.error(chalk6.red("Error: Failed to connect to server"));
2177
2675
  console.error(chalk6.gray(err.message));
@@ -2218,9 +2716,10 @@ var loadFromRemote = async (url, secret) => {
2218
2716
  for (const [name, envConfig] of Object.entries(decrypted.environments)) {
2219
2717
  const uniqueName = getUniqueName(name, existingNames);
2220
2718
  const renamed = uniqueName !== name;
2221
- const configToSave = { ...envConfig };
2222
- if (configToSave.ANTHROPIC_API_KEY) {
2223
- configToSave.ANTHROPIC_API_KEY = encrypt(configToSave.ANTHROPIC_API_KEY);
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);
2224
2723
  }
2225
2724
  registries[uniqueName] = configToSave;
2226
2725
  existingNames.add(uniqueName);
@@ -2241,6 +2740,7 @@ Loaded ${results.length} environment(s) from remote:`));
2241
2740
  }
2242
2741
  }
2243
2742
  console.log(chalk6.gray("\nRun 'ccem ls' to see all environments."));
2743
+ return results;
2244
2744
  };
2245
2745
 
2246
2746
  // src/cron-skill.ts
@@ -2386,11 +2886,52 @@ Replace \\\`TARGET_ID\\\` or \\\`TARGET_NAME\\\` with the user's selection.
2386
2886
  `;
2387
2887
 
2388
2888
  // src/index.ts
2389
- var __filename = fileURLToPath(import.meta.url);
2390
- var __dirname2 = path6.dirname(__filename);
2391
- var pkgPath = path6.resolve(__dirname2, "..", "package.json");
2392
- var pkg = JSON.parse(fs8.readFileSync(pkgPath, "utf-8"));
2889
+ var __filename2 = fileURLToPath2(import.meta.url);
2890
+ var __dirname2 = path7.dirname(__filename2);
2891
+ var pkgPath = path7.resolve(__dirname2, "..", "package.json");
2892
+ var pkg = JSON.parse(fs9.readFileSync(pkgPath, "utf-8"));
2393
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
+ };
2394
2935
  ensureCcemDir();
2395
2936
  var config2 = new Conf2({
2396
2937
  projectName: "claude-code-env-manager",
@@ -2398,16 +2939,129 @@ var config2 = new Conf2({
2398
2939
  // 使用新路径
2399
2940
  defaults: {
2400
2941
  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
- }
2942
+ official: DEFAULT_OFFICIAL_ENV
2406
2943
  },
2407
2944
  current: "official",
2408
2945
  defaultMode: null
2409
2946
  }
2410
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
+ };
2411
3065
  var PERMISSION_MODES = ["yolo", "dev", "readonly", "safe", "ci", "audit"];
2412
3066
  var usageStats = null;
2413
3067
  var usageLoading = true;
@@ -2446,24 +3100,27 @@ program.name("ccem").description("Claude Code Environment Manager - \u7BA1\u7406
2446
3100
  PERMISSION_MODES.forEach((mode) => {
2447
3101
  const preset = PERMISSION_PRESETS[mode];
2448
3102
  program.command(mode).description(`\u4E34\u65F6\u5E94\u7528 ${preset.name}\uFF0C\u9000\u51FA\u540E\u8FD8\u539F`).action(async () => {
2449
- const registries = config2.get("registries");
3103
+ const registries = getRegistries();
2450
3104
  const current = config2.get("current");
2451
3105
  const envConfig = registries[current];
2452
- await runWithTempPermissions(mode, envConfig);
3106
+ await runWithTempPermissions(mode, envConfig, current);
2453
3107
  });
2454
3108
  });
2455
3109
  var showCurrentEnv = (usageStats2, usageLoading2) => {
2456
3110
  if (!process.stdout.isTTY) return;
2457
3111
  const current = config2.get("current");
2458
- const registries = config2.get("registries");
3112
+ const registries = getRegistries();
2459
3113
  const env = registries[current];
2460
3114
  const defaultMode = config2.get("defaultMode");
2461
3115
  if (!env) return;
2462
3116
  console.log(renderLogoWithEnvPanel(current, {
2463
3117
  ANTHROPIC_BASE_URL: env.ANTHROPIC_BASE_URL,
2464
- ANTHROPIC_API_KEY: env.ANTHROPIC_API_KEY ? decrypt(env.ANTHROPIC_API_KEY) : void 0,
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,
2465
3122
  ANTHROPIC_MODEL: env.ANTHROPIC_MODEL,
2466
- ANTHROPIC_SMALL_FAST_MODEL: env.ANTHROPIC_SMALL_FAST_MODEL
3123
+ CLAUDE_CODE_SUBAGENT_MODEL: env.CLAUDE_CODE_SUBAGENT_MODEL
2467
3124
  }, defaultMode));
2468
3125
  console.log("");
2469
3126
  console.log(renderUsageLine(usageStats2, usageLoading2));
@@ -2471,7 +3128,7 @@ var showCurrentEnv = (usageStats2, usageLoading2) => {
2471
3128
  console.log("");
2472
3129
  };
2473
3130
  var switchEnvironment = async (name) => {
2474
- const registries = config2.get("registries");
3131
+ const registries = getRegistries();
2475
3132
  if (!registries[name]) {
2476
3133
  console.error(chalk7.red(`Environment '${name}' not found.`));
2477
3134
  return;
@@ -2484,11 +3141,7 @@ var switchEnvironment = async (name) => {
2484
3141
  }
2485
3142
  showCurrentEnv(null, false);
2486
3143
  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}"`);
3144
+ const exportCmds = buildShellEnvCommands(env);
2492
3145
  if (process.stdout.isTTY) {
2493
3146
  console.log(chalk7.yellow("\nTo apply to current shell immediately, run:"));
2494
3147
  console.log(chalk7.cyan("eval $(ccem env)"));
@@ -2498,11 +3151,92 @@ var switchEnvironment = async (name) => {
2498
3151
  exportCmds.forEach((cmd) => console.log(cmd));
2499
3152
  }
2500
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
+ };
2501
3235
  program.command("ls").description("List all configured environments").action(() => {
2502
- const registries = config2.get("registries");
3236
+ const registries = getRegistries();
2503
3237
  const current = config2.get("current");
2504
3238
  const table = new Table3({
2505
- head: ["Name", "Base URL", "Model"],
3239
+ head: ["Name", "Base URL", "Opus"],
2506
3240
  style: { head: ["cyan"] }
2507
3241
  });
2508
3242
  Object.keys(registries).forEach((name) => {
@@ -2511,7 +3245,7 @@ program.command("ls").description("List all configured environments").action(()
2511
3245
  table.push([
2512
3246
  prefix + name,
2513
3247
  reg.ANTHROPIC_BASE_URL || "-",
2514
- reg.ANTHROPIC_MODEL || "-"
3248
+ reg.ANTHROPIC_DEFAULT_OPUS_MODEL || "-"
2515
3249
  ]);
2516
3250
  });
2517
3251
  console.log(table.toString());
@@ -2519,8 +3253,42 @@ program.command("ls").description("List all configured environments").action(()
2519
3253
  program.command("use <name>").description("Switch to a specific environment").action(async (name) => {
2520
3254
  await switchEnvironment(name);
2521
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
+ });
2522
3290
  program.command("add <name>").description("Add a new environment configuration").action(async (name) => {
2523
- const registries = config2.get("registries");
3291
+ const registries = getRegistries();
2524
3292
  if (registries[name]) {
2525
3293
  console.log(chalk7.red(`Environment '${name}' already exists.`));
2526
3294
  return;
@@ -2545,40 +3313,13 @@ program.command("add <name>").description("Add a new environment configuration")
2545
3313
  ]);
2546
3314
  presetConfig = ENV_PRESETS[presetName];
2547
3315
  }
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);
3316
+ const answers = await promptForEnvironmentConfig(presetConfig);
3317
+ registries[name] = applyPromptAnswers(normalizeEnvConfig(presetConfig), answers, false);
3318
+ setRegistries(registries);
2578
3319
  console.log(chalk7.green(`Environment '${name}' added successfully.`));
2579
3320
  });
2580
3321
  program.command("del <name>").description("Delete an environment configuration").action((name) => {
2581
- const registries = config2.get("registries");
3322
+ const registries = getRegistries();
2582
3323
  if (!registries[name]) {
2583
3324
  console.log(chalk7.red(`Environment '${name}' not found.`));
2584
3325
  return;
@@ -2588,7 +3329,7 @@ program.command("del <name>").description("Delete an environment configuration")
2588
3329
  return;
2589
3330
  }
2590
3331
  delete registries[name];
2591
- config2.set("registries", registries);
3332
+ setRegistries(registries);
2592
3333
  const current = config2.get("current");
2593
3334
  if (current === name) {
2594
3335
  config2.set("current", "official");
@@ -2597,7 +3338,7 @@ program.command("del <name>").description("Delete an environment configuration")
2597
3338
  console.log(chalk7.green(`Environment '${name}' deleted.`));
2598
3339
  });
2599
3340
  program.command("rename <old> <new>").description("Rename an environment configuration").action((oldName, newName) => {
2600
- const registries = config2.get("registries");
3341
+ const registries = getRegistries();
2601
3342
  if (!registries[oldName]) {
2602
3343
  console.log(chalk7.red(`Environment '${oldName}' not found.`));
2603
3344
  return;
@@ -2612,7 +3353,7 @@ program.command("rename <old> <new>").description("Rename an environment configu
2612
3353
  }
2613
3354
  registries[newName] = registries[oldName];
2614
3355
  delete registries[oldName];
2615
- config2.set("registries", registries);
3356
+ setRegistries(registries);
2616
3357
  const current = config2.get("current");
2617
3358
  if (current === oldName) {
2618
3359
  config2.set("current", newName);
@@ -2620,7 +3361,7 @@ program.command("rename <old> <new>").description("Rename an environment configu
2620
3361
  console.log(chalk7.green(`Environment '${oldName}' renamed to '${newName}'.`));
2621
3362
  });
2622
3363
  program.command("cp <source> <target>").description("Copy an environment configuration").action(async (source, target) => {
2623
- const registries = config2.get("registries");
3364
+ const registries = getRegistries();
2624
3365
  if (!registries[source]) {
2625
3366
  console.log(chalk7.red(`Environment '${source}' not found.`));
2626
3367
  return;
@@ -2630,7 +3371,7 @@ program.command("cp <source> <target>").description("Copy an environment configu
2630
3371
  return;
2631
3372
  }
2632
3373
  registries[target] = { ...registries[source] };
2633
- config2.set("registries", registries);
3374
+ setRegistries(registries);
2634
3375
  console.log(chalk7.green(`Environment '${source}' copied to '${target}'.`));
2635
3376
  const { modify } = await inquirer.prompt([
2636
3377
  {
@@ -2642,37 +3383,9 @@ program.command("cp <source> <target>").description("Copy an environment configu
2642
3383
  ]);
2643
3384
  if (modify) {
2644
3385
  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);
3386
+ const answers = await promptForEnvironmentConfig(current, true);
3387
+ registries[target] = applyPromptAnswers(current, answers, true);
3388
+ setRegistries(registries);
2676
3389
  console.log(chalk7.green(`Environment '${target}' updated.`));
2677
3390
  }
2678
3391
  });
@@ -2681,25 +3394,22 @@ program.command("current").description("Show current environment name").action((
2681
3394
  console.log(chalk7.green(current));
2682
3395
  });
2683
3396
  program.command("env").description("Output environment variables for shell eval").option("--json", "Output as JSON").action((options) => {
2684
- const registries = config2.get("registries");
3397
+ const registries = getRegistries();
2685
3398
  const current = config2.get("current");
2686
3399
  const env = registries[current];
2687
3400
  if (!env) return;
2688
3401
  const outputEnv = { ...env };
2689
- if (outputEnv.ANTHROPIC_API_KEY) {
2690
- outputEnv.ANTHROPIC_API_KEY = decrypt(outputEnv.ANTHROPIC_API_KEY);
3402
+ if (outputEnv.ANTHROPIC_AUTH_TOKEN) {
3403
+ outputEnv.ANTHROPIC_AUTH_TOKEN = decrypt(outputEnv.ANTHROPIC_AUTH_TOKEN);
2691
3404
  }
2692
3405
  if (options.json) {
2693
3406
  console.log(JSON.stringify(outputEnv, null, 2));
2694
3407
  } 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}"`);
3408
+ buildShellEnvCommands(env).forEach((cmd) => console.log(cmd));
2699
3409
  }
2700
3410
  });
2701
3411
  program.command("run <command...>").description("Run a command with the current environment variables").action((command) => {
2702
- const registries = config2.get("registries");
3412
+ const registries = getRegistries();
2703
3413
  const current = config2.get("current");
2704
3414
  const envConfig = registries[current];
2705
3415
  if (!envConfig) {
@@ -2707,10 +3417,8 @@ program.command("run <command...>").description("Run a command with the current
2707
3417
  process.exit(1);
2708
3418
  }
2709
3419
  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;
3420
+ clearManagedClaudeEnv(env);
3421
+ Object.assign(env, buildResolvedEnvVars(envConfig));
2714
3422
  const [cmd, ...args] = command;
2715
3423
  const child = spawn3(cmd, args, {
2716
3424
  env,
@@ -2763,20 +3471,21 @@ setupCmd.command("default-mode").description("\u8BBE\u7F6E\u9ED8\u8BA4\u6743\u96
2763
3471
  console.log(chalk7.gray("\u6E05\u9664\u9ED8\u8BA4\u6A21\u5F0F: ccem setup default-mode --reset"));
2764
3472
  console.log(chalk7.gray("\u53EF\u7528\u6A21\u5F0F: " + PERMISSION_MODES.join(", ")));
2765
3473
  });
2766
- setupCmd.command("init").description("\u521D\u59CB\u5316 Claude Code \u5168\u5C40\u914D\u7F6E\uFF08\u8DF3\u8FC7 onboarding\u3001\u7981\u7528\u9065\u6D4B\u3001\u5B89\u88C5 MCP \u5DE5\u5177\uFF09").action(async () => {
2767
- await runSetupInit();
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 });
2768
3477
  });
2769
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() {
2770
3479
  const options = this.opts();
2771
3480
  const newConfigPath = getCcemConfigPath();
2772
3481
  const legacyConfigPath = getLegacyConfigPath();
2773
3482
  console.log(chalk7.cyan("\n\u{1F504} \u914D\u7F6E\u8FC1\u79FB\n"));
2774
- if (!fs8.existsSync(legacyConfigPath)) {
3483
+ if (!fs9.existsSync(legacyConfigPath)) {
2775
3484
  console.log(chalk7.yellow("\u672A\u627E\u5230\u65E7\u7248\u914D\u7F6E\u6587\u4EF6"));
2776
3485
  console.log(chalk7.gray(` \u65E7\u8DEF\u5F84: ${legacyConfigPath}`));
2777
3486
  return;
2778
3487
  }
2779
- if (fs8.existsSync(newConfigPath) && !options.force) {
3488
+ if (fs9.existsSync(newConfigPath) && !options.force) {
2780
3489
  console.log(chalk7.green("\u2713 \u914D\u7F6E\u5DF2\u5728\u65B0\u8DEF\u5F84"));
2781
3490
  console.log(chalk7.gray(` \u8DEF\u5F84: ${newConfigPath}`));
2782
3491
  console.log(chalk7.gray("\n\u4F7F\u7528 --force \u5F3A\u5236\u91CD\u65B0\u8FC1\u79FB"));
@@ -2784,15 +3493,15 @@ setupCmd.command("migrate").description("\u8FC1\u79FB\u65E7\u7248\u914D\u7F6E\u5
2784
3493
  }
2785
3494
  try {
2786
3495
  ensureCcemDir();
2787
- fs8.copyFileSync(legacyConfigPath, newConfigPath);
3496
+ fs9.copyFileSync(legacyConfigPath, newConfigPath);
2788
3497
  console.log(chalk7.green("\u2713 \u914D\u7F6E\u5DF2\u8FC1\u79FB"));
2789
3498
  console.log(chalk7.gray(` \u4ECE: ${legacyConfigPath}`));
2790
3499
  console.log(chalk7.gray(` \u5230: ${newConfigPath}`));
2791
3500
  if (options.clean) {
2792
- fs8.unlinkSync(legacyConfigPath);
2793
- const legacyDir = path6.dirname(legacyConfigPath);
3501
+ fs9.unlinkSync(legacyConfigPath);
3502
+ const legacyDir = path7.dirname(legacyConfigPath);
2794
3503
  try {
2795
- fs8.rmdirSync(legacyDir);
3504
+ fs9.rmdirSync(legacyDir);
2796
3505
  } catch {
2797
3506
  }
2798
3507
  console.log(chalk7.green("\u2713 \u5DF2\u5220\u9664\u65E7\u914D\u7F6E\u6587\u4EF6"));
@@ -2803,13 +3512,13 @@ setupCmd.command("migrate").description("\u8FC1\u79FB\u65E7\u7248\u914D\u7F6E\u5
2803
3512
  });
2804
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() {
2805
3514
  const options = this.opts();
2806
- const skillDir = path6.join(process.env.HOME || "~", ".claude", "skills");
2807
- const targetPath = path6.join(skillDir, "ccem-cron.md");
2808
- if (!fs8.existsSync(skillDir)) {
2809
- fs8.mkdirSync(skillDir, { recursive: true });
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 });
2810
3519
  console.log(chalk7.gray(`\u521B\u5EFA\u76EE\u5F55: ${skillDir}`));
2811
3520
  }
2812
- if (fs8.existsSync(targetPath) && !options.force) {
3521
+ if (fs9.existsSync(targetPath) && !options.force) {
2813
3522
  const { overwrite } = await inquirer.prompt([
2814
3523
  {
2815
3524
  type: "confirm",
@@ -2823,7 +3532,7 @@ setupCmd.command("cron").description("\u5B89\u88C5 ccem-cron skill \u5230 Claude
2823
3532
  return;
2824
3533
  }
2825
3534
  }
2826
- fs8.writeFileSync(targetPath, CCEM_CRON_SKILL_CONTENT, "utf-8");
3535
+ fs9.writeFileSync(targetPath, CCEM_CRON_SKILL_CONTENT, "utf-8");
2827
3536
  console.log(chalk7.green(`\u2713 \u5DF2\u5B89\u88C5 ccem-cron skill`));
2828
3537
  console.log(chalk7.gray(` \u8DEF\u5F84: ${targetPath}`));
2829
3538
  console.log(chalk7.cyan(`
@@ -2879,20 +3588,34 @@ skillCmd.command("ls").description("\u5217\u51FA\u5DF2\u5B89\u88C5\u7684 skills"
2879
3588
  skillCmd.command("rm <name>").description("\u5220\u9664\u5DF2\u5B89\u88C5\u7684 skill").action((name) => {
2880
3589
  removeSkill(name);
2881
3590
  });
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);
3591
+ 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) => {
3592
+ const results = await loadFromRemote(url, options.secret);
3593
+ if (options.json) {
3594
+ console.log(JSON.stringify({
3595
+ count: results.length,
3596
+ environments: results.map((r) => ({
3597
+ name: r.name,
3598
+ original_name: r.originalName,
3599
+ // 使用 snake_case 匹配 Rust 结构体
3600
+ renamed: r.renamed
3601
+ }))
3602
+ }));
3603
+ }
2884
3604
  });
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() {
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() {
2886
3606
  const opts = this.opts();
2887
3607
  const envName = opts.env || config2.get("current");
2888
- const registries = config2.get("registries");
3608
+ const registries = getRegistries();
2889
3609
  const envConfig = registries[envName];
2890
3610
  if (!envConfig) {
2891
3611
  console.error(chalk7.red(`Environment '${envName}' not found.`));
2892
3612
  process.exit(1);
2893
3613
  }
3614
+ const proxyBaseUrl = opts.proxyBaseUrl || opts.anthropicBaseUrl;
3615
+ const launchEnvConfig = proxyBaseUrl ? { ...envConfig, ANTHROPIC_BASE_URL: proxyBaseUrl } : envConfig;
2894
3616
  await launchClaude({
2895
- envConfig,
3617
+ envName,
3618
+ envConfig: launchEnvConfig,
2896
3619
  permMode: opts.perm,
2897
3620
  workingDir: opts.workingDir,
2898
3621
  sessionId: opts.sessionId,
@@ -2928,7 +3651,7 @@ program.action(async (options) => {
2928
3651
  showCurrentEnv(usageStats, usageLoading);
2929
3652
  console.log("");
2930
3653
  const defaultMode = config2.get("defaultMode");
2931
- const registries = config2.get("registries");
3654
+ const registries = getRegistries();
2932
3655
  const current = config2.get("current");
2933
3656
  const envConfig = registries[current];
2934
3657
  const { action } = await inquirer.prompt([
@@ -2950,7 +3673,11 @@ program.action(async (options) => {
2950
3673
  msg.error("No environment configuration found.");
2951
3674
  process.exit(1);
2952
3675
  }
2953
- await launchClaude({ envConfig, permMode: defaultMode || void 0 });
3676
+ await launchClaude({
3677
+ envName: current,
3678
+ envConfig,
3679
+ permMode: defaultMode || void 0
3680
+ });
2954
3681
  return;
2955
3682
  } else if (action === "usage") {
2956
3683
  console.clear();
@@ -2975,37 +3702,9 @@ program.action(async (options) => {
2975
3702
  const envToEdit = registries[result.name];
2976
3703
  console.log(chalk7.yellow(`
2977
3704
  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);
3705
+ const answers = await promptForEnvironmentConfig(envToEdit, true);
3706
+ registries[result.name] = applyPromptAnswers(envToEdit, answers, true);
3707
+ setRegistries(registries);
3009
3708
  msg.success(`Environment '${result.name}' updated.`);
3010
3709
  await new Promise((resolve2) => setTimeout(resolve2, 800));
3011
3710
  } else if (result.action === "rename") {
@@ -3027,7 +3726,7 @@ Editing environment '${result.name}'`));
3027
3726
  ]);
3028
3727
  registries[newName] = registries[result.name];
3029
3728
  delete registries[result.name];
3030
- config2.set("registries", registries);
3729
+ setRegistries(registries);
3031
3730
  if (current === result.name) {
3032
3731
  config2.set("current", newName);
3033
3732
  }
@@ -3048,7 +3747,7 @@ Editing environment '${result.name}'`));
3048
3747
  }
3049
3748
  ]);
3050
3749
  registries[targetName] = { ...registries[result.name] };
3051
- config2.set("registries", registries);
3750
+ setRegistries(registries);
3052
3751
  msg.success(`Environment '${result.name}' copied to '${targetName}'.`);
3053
3752
  const { modify } = await inquirer.prompt([
3054
3753
  {
@@ -3060,37 +3759,9 @@ Editing environment '${result.name}'`));
3060
3759
  ]);
3061
3760
  if (modify) {
3062
3761
  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);
3762
+ const editAnswers = await promptForEnvironmentConfig(envToEdit, true);
3763
+ registries[targetName] = applyPromptAnswers(envToEdit, editAnswers, true);
3764
+ setRegistries(registries);
3094
3765
  msg.success(`Environment '${targetName}' updated.`);
3095
3766
  }
3096
3767
  await new Promise((resolve2) => setTimeout(resolve2, 800));
@@ -3109,7 +3780,7 @@ Editing environment '${result.name}'`));
3109
3780
  ]);
3110
3781
  if (confirm) {
3111
3782
  delete registries[result.name];
3112
- config2.set("registries", registries);
3783
+ setRegistries(registries);
3113
3784
  if (current === result.name) {
3114
3785
  config2.set("current", "official");
3115
3786
  msg.warning(`Deleted current environment. Switched back to 'official'.`);
@@ -3131,7 +3802,7 @@ Editing environment '${result.name}'`));
3131
3802
  }
3132
3803
  ]);
3133
3804
  if (permMode !== "back") {
3134
- await runWithTempPermissions(permMode, envConfig);
3805
+ await runWithTempPermissions(permMode, envConfig, current);
3135
3806
  return;
3136
3807
  }
3137
3808
  } else if (action === "setDefault") {