ccem 2.0.0-beta.3 → 2.0.0-beta.30

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