ccem 2.0.0-beta.16 → 2.0.0-beta.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +439 -81
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -7,8 +7,8 @@ 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
14
  // ../../packages/core/dist/chunk-DRN6L2YP.js
@@ -921,10 +921,10 @@ var renderLogoWithEnvPanel = (envName, env, defaultMode) => {
921
921
  const parsed = new URL(url);
922
922
  const protocol = parsed.protocol + "//";
923
923
  const host = parsed.host;
924
- const path7 = parsed.pathname + parsed.search;
924
+ const path8 = parsed.pathname + parsed.search;
925
925
  const hostStart = host.slice(0, 8);
926
926
  const hostEnd = host.slice(-4);
927
- const pathPart = path7.length > 10 ? path7.slice(0, 7) + "..." : path7;
927
+ const pathPart = path8.length > 10 ? path8.slice(0, 7) + "..." : path8;
928
928
  return `${protocol}${hostStart}...${hostEnd}${pathPart}`;
929
929
  } catch {
930
930
  return truncate(url, max);
@@ -1362,7 +1362,7 @@ var selectEnvWithKeys = (registries, current) => {
1362
1362
  };
1363
1363
 
1364
1364
  // src/permissions.ts
1365
- import fs5 from "fs";
1365
+ import fs6 from "fs";
1366
1366
  import chalk3 from "chalk";
1367
1367
  import Table2 from "cli-table3";
1368
1368
 
@@ -1415,9 +1415,347 @@ var ensureGlobalClaudeDir = () => {
1415
1415
 
1416
1416
  // src/launcher.ts
1417
1417
  import { spawn } from "child_process";
1418
- import * as fs4 from "fs";
1419
- import * as path4 from "path";
1418
+ import * as fs5 from "fs";
1419
+ import * as path5 from "path";
1420
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
1421
1759
  var MANAGED_CLAUDE_ENV_KEYS = [
1422
1760
  "ANTHROPIC_BASE_URL",
1423
1761
  "ANTHROPIC_AUTH_TOKEN",
@@ -1455,14 +1793,14 @@ function buildPermArgs(modeName) {
1455
1793
  return args;
1456
1794
  }
1457
1795
  function ensureSessionsDir() {
1458
- const dir = path4.join(ensureCcemDir(), "sessions");
1459
- if (!fs4.existsSync(dir)) {
1460
- fs4.mkdirSync(dir, { recursive: true });
1796
+ const dir = path5.join(ensureCcemDir(), "sessions");
1797
+ if (!fs5.existsSync(dir)) {
1798
+ fs5.mkdirSync(dir, { recursive: true });
1461
1799
  }
1462
1800
  return dir;
1463
1801
  }
1464
1802
  async function launchClaude(options) {
1465
- const { envConfig, permMode, workingDir, sessionId, resumeSessionId, silent } = options;
1803
+ const { envName, envConfig, permMode, workingDir, sessionId, resumeSessionId, silent } = options;
1466
1804
  const env = { ...process.env };
1467
1805
  for (const key of MANAGED_CLAUDE_ENV_KEYS) {
1468
1806
  delete env[key];
@@ -1489,22 +1827,36 @@ async function launchClaude(options) {
1489
1827
  if (workingDir) {
1490
1828
  process.chdir(workingDir);
1491
1829
  }
1830
+ const effectiveWorkingDir = process.cwd();
1492
1831
  if (!silent && !permMode) {
1493
1832
  console.log(renderStarting());
1494
1833
  }
1495
1834
  const sessionsDir = ensureSessionsDir();
1496
1835
  return new Promise((resolve2) => {
1836
+ let provenanceTracking = null;
1497
1837
  const child = spawn("claude", args, {
1498
1838
  stdio: "inherit",
1499
1839
  shell: false,
1500
1840
  // 直接执行二进制,避免 shell 注入风险
1501
1841
  env
1502
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
+ });
1503
1854
  child.on("exit", (code) => {
1855
+ provenanceTracking?.stop();
1504
1856
  if (sessionId) {
1505
1857
  try {
1506
- fs4.writeFileSync(
1507
- path4.join(sessionsDir, `${sessionId}.exit`),
1858
+ fs5.writeFileSync(
1859
+ path5.join(sessionsDir, `${sessionId}.exit`),
1508
1860
  String(code ?? 0)
1509
1861
  );
1510
1862
  } catch {
@@ -1513,6 +1865,7 @@ async function launchClaude(options) {
1513
1865
  process.exit(code ?? 0);
1514
1866
  });
1515
1867
  child.on("error", (err) => {
1868
+ provenanceTracking?.stop();
1516
1869
  if (err.code === "ENOENT") {
1517
1870
  console.error("");
1518
1871
  console.error(chalk2.red.bold("\u2718 \u672A\u627E\u5230 Claude Code"));
@@ -1537,14 +1890,14 @@ async function launchClaude(options) {
1537
1890
 
1538
1891
  // src/permissions.ts
1539
1892
  var readSettings = (settingsPath) => {
1540
- if (fs5.existsSync(settingsPath)) {
1893
+ if (fs6.existsSync(settingsPath)) {
1541
1894
  try {
1542
- const content = fs5.readFileSync(settingsPath, "utf-8");
1895
+ const content = fs6.readFileSync(settingsPath, "utf-8");
1543
1896
  return JSON.parse(content);
1544
1897
  } catch {
1545
1898
  console.warn(chalk3.yellow(`\u8B66\u544A: \u65E0\u6CD5\u89E3\u6790 ${settingsPath}\uFF0C\u5C06\u521B\u5EFA\u5907\u4EFD`));
1546
1899
  const backupPath = settingsPath + ".error." + Date.now();
1547
- fs5.copyFileSync(settingsPath, backupPath);
1900
+ fs6.copyFileSync(settingsPath, backupPath);
1548
1901
  console.log(chalk3.gray(`\u5907\u4EFD\u5DF2\u4FDD\u5B58\u5230: ${backupPath}`));
1549
1902
  return {};
1550
1903
  }
@@ -1553,7 +1906,7 @@ var readSettings = (settingsPath) => {
1553
1906
  };
1554
1907
  var writeSettings = (settingsPath, config3) => {
1555
1908
  ensureClaudeDir();
1556
- fs5.writeFileSync(settingsPath, JSON.stringify(config3, null, 2) + "\n");
1909
+ fs6.writeFileSync(settingsPath, JSON.stringify(config3, null, 2) + "\n");
1557
1910
  };
1558
1911
  var mergePermissions = (existing, preset) => {
1559
1912
  const existingAllow = existing.permissions?.allow || [];
@@ -1585,14 +1938,14 @@ var applyPermissionMode = (modeName) => {
1585
1938
  };
1586
1939
  var resetPermissions = () => {
1587
1940
  const settingsPath = getSettingsPath(true);
1588
- if (!fs5.existsSync(settingsPath)) {
1941
+ if (!fs6.existsSync(settingsPath)) {
1589
1942
  console.log(chalk3.yellow("\u6CA1\u6709\u81EA\u5B9A\u4E49\u6743\u9650\u914D\u7F6E\u9700\u8981\u91CD\u7F6E"));
1590
1943
  return;
1591
1944
  }
1592
1945
  const config3 = readSettings(settingsPath);
1593
1946
  delete config3.permissions;
1594
1947
  if (Object.keys(config3).length === 0) {
1595
- fs5.unlinkSync(settingsPath);
1948
+ fs6.unlinkSync(settingsPath);
1596
1949
  console.log(chalk3.green("\u5DF2\u5220\u9664\u914D\u7F6E\u6587\u4EF6\uFF08\u6587\u4EF6\u4E3A\u7A7A\uFF09"));
1597
1950
  } else {
1598
1951
  writeSettings(settingsPath, config3);
@@ -1602,7 +1955,7 @@ var resetPermissions = () => {
1602
1955
  };
1603
1956
  var showCurrentMode = () => {
1604
1957
  const settingsPath = getSettingsPath(true);
1605
- if (!fs5.existsSync(settingsPath)) {
1958
+ if (!fs6.existsSync(settingsPath)) {
1606
1959
  console.log(chalk3.yellow("\u672A\u914D\u7F6E\u81EA\u5B9A\u4E49\u6743\u9650"));
1607
1960
  console.log(chalk3.gray(`\u6587\u4EF6\u4E0D\u5B58\u5728: ${settingsPath}`));
1608
1961
  return;
@@ -1652,24 +2005,24 @@ var listAvailableModes = () => {
1652
2005
  console.log(chalk3.gray("\n\u4E34\u65F6\u6A21\u5F0F: ccem <mode>"));
1653
2006
  console.log(chalk3.gray("\u6C38\u4E45\u6A21\u5F0F: ccem setup perms --<mode>"));
1654
2007
  };
1655
- var runWithTempPermissions = async (modeName, envConfig) => {
2008
+ var runWithTempPermissions = async (modeName, envConfig, envName) => {
1656
2009
  const preset = PERMISSION_PRESETS[modeName];
1657
2010
  if (!preset) {
1658
2011
  console.error(chalk3.red(`\u672A\u77E5\u7684\u6743\u9650\u6A21\u5F0F: ${modeName}`));
1659
2012
  console.log(chalk3.yellow("\u53EF\u7528\u6A21\u5F0F: " + getPermissionModeNames().join(", ")));
1660
2013
  process.exit(1);
1661
2014
  }
1662
- await launchClaude({ envConfig, permMode: modeName });
2015
+ await launchClaude({ envConfig, envName, permMode: modeName });
1663
2016
  };
1664
2017
 
1665
2018
  // src/setup.ts
1666
- import fs6 from "fs";
2019
+ import fs7 from "fs";
1667
2020
  import chalk4 from "chalk";
1668
2021
  import { spawn as spawn2 } from "child_process";
1669
2022
  var readJsonFile = (filePath) => {
1670
- if (fs6.existsSync(filePath)) {
2023
+ if (fs7.existsSync(filePath)) {
1671
2024
  try {
1672
- const content = fs6.readFileSync(filePath, "utf-8");
2025
+ const content = fs7.readFileSync(filePath, "utf-8");
1673
2026
  return JSON.parse(content);
1674
2027
  } catch {
1675
2028
  console.warn(chalk4.yellow(`\u8B66\u544A: \u65E0\u6CD5\u89E3\u6790 ${filePath}`));
@@ -1679,7 +2032,7 @@ var readJsonFile = (filePath) => {
1679
2032
  return {};
1680
2033
  };
1681
2034
  var writeJsonFile = (filePath, data) => {
1682
- fs6.writeFileSync(filePath, JSON.stringify(data, null, 2) + "\n");
2035
+ fs7.writeFileSync(filePath, JSON.stringify(data, null, 2) + "\n");
1683
2036
  };
1684
2037
  var setupOnboarding = () => {
1685
2038
  const configPath = getGlobalClaudeConfigPath();
@@ -1806,8 +2159,8 @@ var runSetupInit = async (options = {}) => {
1806
2159
 
1807
2160
  // src/skills.ts
1808
2161
  import { execSync } from "child_process";
1809
- import * as fs7 from "fs";
1810
- import * as path5 from "path";
2162
+ import * as fs8 from "fs";
2163
+ import * as path6 from "path";
1811
2164
  import chalk5 from "chalk";
1812
2165
  var SKILL_GROUPS = {
1813
2166
  official: { label: "\u5B98\u65B9", icon: "\u{1F3E2}" },
@@ -1976,12 +2329,12 @@ function parseGitHubUrl(url) {
1976
2329
  };
1977
2330
  }
1978
2331
  function getSkillsDir() {
1979
- return path5.join(process.cwd(), ".claude", "skills");
2332
+ return path6.join(process.cwd(), ".claude", "skills");
1980
2333
  }
1981
2334
  function ensureSkillsDir() {
1982
2335
  const skillsDir = getSkillsDir();
1983
- if (!fs7.existsSync(skillsDir)) {
1984
- fs7.mkdirSync(skillsDir, { recursive: true });
2336
+ if (!fs8.existsSync(skillsDir)) {
2337
+ fs8.mkdirSync(skillsDir, { recursive: true });
1985
2338
  } else {
1986
2339
  cleanupTempDirs(skillsDir);
1987
2340
  }
@@ -1989,11 +2342,11 @@ function ensureSkillsDir() {
1989
2342
  }
1990
2343
  function cleanupTempDirs(skillsDir) {
1991
2344
  try {
1992
- const entries = fs7.readdirSync(skillsDir, { withFileTypes: true });
2345
+ const entries = fs8.readdirSync(skillsDir, { withFileTypes: true });
1993
2346
  for (const entry of entries) {
1994
2347
  if (entry.isDirectory() && entry.name.startsWith(".tmp-")) {
1995
- const tmpPath = path5.join(skillsDir, entry.name);
1996
- fs7.rmSync(tmpPath, { recursive: true });
2348
+ const tmpPath = path6.join(skillsDir, entry.name);
2349
+ fs8.rmSync(tmpPath, { recursive: true });
1997
2350
  }
1998
2351
  }
1999
2352
  } catch {
@@ -2001,24 +2354,24 @@ function cleanupTempDirs(skillsDir) {
2001
2354
  }
2002
2355
  function downloadSkillWithGit(owner, repo, branch, repoPath, targetName) {
2003
2356
  const skillsDir = ensureSkillsDir();
2004
- const targetDir = path5.join(skillsDir, targetName);
2005
- if (fs7.existsSync(targetDir)) {
2357
+ const targetDir = path6.join(skillsDir, targetName);
2358
+ if (fs8.existsSync(targetDir)) {
2006
2359
  console.log(chalk5.yellow(`Skill "${targetName}" already exists. Updating...`));
2007
- fs7.rmSync(targetDir, { recursive: true });
2360
+ fs8.rmSync(targetDir, { recursive: true });
2008
2361
  }
2009
2362
  const repoUrl = `https://github.com/${owner}/${repo}.git`;
2010
- const tempDir = path5.join(skillsDir, `.tmp-${Date.now()}`);
2363
+ const tempDir = path6.join(skillsDir, `.tmp-${Date.now()}`);
2011
2364
  try {
2012
- fs7.mkdirSync(tempDir, { recursive: true });
2365
+ fs8.mkdirSync(tempDir, { recursive: true });
2013
2366
  execSync(`git init`, { cwd: tempDir, stdio: "pipe" });
2014
2367
  execSync(`git remote add origin ${repoUrl}`, { cwd: tempDir, stdio: "pipe" });
2015
2368
  execSync(`git config core.sparseCheckout true`, { cwd: tempDir, stdio: "pipe" });
2016
- const sparseFile = path5.join(tempDir, ".git", "info", "sparse-checkout");
2017
- fs7.writeFileSync(sparseFile, repoPath ? `${repoPath}/
2369
+ const sparseFile = path6.join(tempDir, ".git", "info", "sparse-checkout");
2370
+ fs8.writeFileSync(sparseFile, repoPath ? `${repoPath}/
2018
2371
  ` : "*\n");
2019
2372
  execSync(`git pull --depth=1 origin ${branch}`, { cwd: tempDir, stdio: "pipe" });
2020
- const sourceDir = repoPath ? path5.join(tempDir, repoPath) : tempDir;
2021
- if (!fs7.existsSync(sourceDir)) {
2373
+ const sourceDir = repoPath ? path6.join(tempDir, repoPath) : tempDir;
2374
+ if (!fs8.existsSync(sourceDir)) {
2022
2375
  throw new Error(`Path "${repoPath}" not found in repository`);
2023
2376
  }
2024
2377
  copyDir(sourceDir, targetDir);
@@ -2029,22 +2382,22 @@ function downloadSkillWithGit(owner, repo, branch, repoPath, targetName) {
2029
2382
  console.error(chalk5.red(`Failed to download skill: ${errMsg}`));
2030
2383
  return false;
2031
2384
  } finally {
2032
- if (fs7.existsSync(tempDir)) {
2033
- fs7.rmSync(tempDir, { recursive: true });
2385
+ if (fs8.existsSync(tempDir)) {
2386
+ fs8.rmSync(tempDir, { recursive: true });
2034
2387
  }
2035
2388
  }
2036
2389
  }
2037
2390
  function copyDir(src, dest) {
2038
- fs7.mkdirSync(dest, { recursive: true });
2039
- const entries = fs7.readdirSync(src, { withFileTypes: true });
2391
+ fs8.mkdirSync(dest, { recursive: true });
2392
+ const entries = fs8.readdirSync(src, { withFileTypes: true });
2040
2393
  for (const entry of entries) {
2041
2394
  if (entry.name === ".git") continue;
2042
- const srcPath = path5.join(src, entry.name);
2043
- const destPath = path5.join(dest, entry.name);
2395
+ const srcPath = path6.join(src, entry.name);
2396
+ const destPath = path6.join(dest, entry.name);
2044
2397
  if (entry.isDirectory()) {
2045
2398
  copyDir(srcPath, destPath);
2046
2399
  } else {
2047
- fs7.copyFileSync(srcPath, destPath);
2400
+ fs8.copyFileSync(srcPath, destPath);
2048
2401
  }
2049
2402
  }
2050
2403
  }
@@ -2090,7 +2443,7 @@ function addSkillFromGitHub(urlOrPreset) {
2090
2443
  }
2091
2444
  let skillName;
2092
2445
  if (parsed.path) {
2093
- skillName = path5.basename(parsed.path);
2446
+ skillName = path6.basename(parsed.path);
2094
2447
  } else {
2095
2448
  skillName = parsed.repo;
2096
2449
  }
@@ -2104,23 +2457,23 @@ function addSkillFromGitHub(urlOrPreset) {
2104
2457
  }
2105
2458
  function listInstalledSkills() {
2106
2459
  const skillsDir = getSkillsDir();
2107
- if (!fs7.existsSync(skillsDir)) {
2460
+ if (!fs8.existsSync(skillsDir)) {
2108
2461
  return [];
2109
2462
  }
2110
- const entries = fs7.readdirSync(skillsDir, { withFileTypes: true });
2463
+ const entries = fs8.readdirSync(skillsDir, { withFileTypes: true });
2111
2464
  return entries.filter((entry) => entry.isDirectory() && !entry.name.startsWith(".")).map((entry) => ({
2112
2465
  name: entry.name,
2113
- path: path5.join(skillsDir, entry.name)
2466
+ path: path6.join(skillsDir, entry.name)
2114
2467
  }));
2115
2468
  }
2116
2469
  function removeSkill(name) {
2117
2470
  const skillsDir = getSkillsDir();
2118
- const targetDir = path5.join(skillsDir, name);
2119
- if (!fs7.existsSync(targetDir)) {
2471
+ const targetDir = path6.join(skillsDir, name);
2472
+ if (!fs8.existsSync(targetDir)) {
2120
2473
  console.error(chalk5.red(`Skill "${name}" not found`));
2121
2474
  return false;
2122
2475
  }
2123
- fs7.rmSync(targetDir, { recursive: true });
2476
+ fs8.rmSync(targetDir, { recursive: true });
2124
2477
  console.log(chalk5.green(`Removed skill "${name}"`));
2125
2478
  return true;
2126
2479
  }
@@ -2534,9 +2887,9 @@ Replace \\\`TARGET_ID\\\` or \\\`TARGET_NAME\\\` with the user's selection.
2534
2887
 
2535
2888
  // src/index.ts
2536
2889
  var __filename2 = fileURLToPath2(import.meta.url);
2537
- var __dirname2 = path6.dirname(__filename2);
2538
- var pkgPath = path6.resolve(__dirname2, "..", "package.json");
2539
- 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"));
2540
2893
  var program = new Command();
2541
2894
  var DEFAULT_OFFICIAL_ENV = {
2542
2895
  ANTHROPIC_BASE_URL: "https://api.anthropic.com",
@@ -2600,11 +2953,11 @@ var recoverRegistriesFromLegacy = (registries) => {
2600
2953
  return registries;
2601
2954
  }
2602
2955
  const legacyConfigPath = getLegacyConfigPath();
2603
- if (!fs8.existsSync(legacyConfigPath)) {
2956
+ if (!fs9.existsSync(legacyConfigPath)) {
2604
2957
  return registries;
2605
2958
  }
2606
2959
  try {
2607
- const legacyRaw = JSON.parse(fs8.readFileSync(legacyConfigPath, "utf-8"));
2960
+ const legacyRaw = JSON.parse(fs9.readFileSync(legacyConfigPath, "utf-8"));
2608
2961
  const legacyRegistries = legacyRaw.registries ?? {};
2609
2962
  let changed = false;
2610
2963
  const recovered = { ...registries };
@@ -2750,7 +3103,7 @@ PERMISSION_MODES.forEach((mode) => {
2750
3103
  const registries = getRegistries();
2751
3104
  const current = config2.get("current");
2752
3105
  const envConfig = registries[current];
2753
- await runWithTempPermissions(mode, envConfig);
3106
+ await runWithTempPermissions(mode, envConfig, current);
2754
3107
  });
2755
3108
  });
2756
3109
  var showCurrentEnv = (usageStats2, usageLoading2) => {
@@ -2798,14 +3151,14 @@ var switchEnvironment = async (name) => {
2798
3151
  exportCmds.forEach((cmd) => console.log(cmd));
2799
3152
  }
2800
3153
  };
2801
- var getSessionsFilePath = () => path6.join(getCcemConfigDir(), "sessions.json");
2802
- var getRuntimeStateFilePath = () => path6.join(getCcemConfigDir(), "runtime-state.json");
3154
+ var getSessionsFilePath = () => path7.join(getCcemConfigDir(), "sessions.json");
3155
+ var getRuntimeStateFilePath = () => path7.join(getCcemConfigDir(), "runtime-state.json");
2803
3156
  var parseJsonFile = (filePath) => {
2804
- if (!fs8.existsSync(filePath)) {
3157
+ if (!fs9.existsSync(filePath)) {
2805
3158
  return null;
2806
3159
  }
2807
3160
  try {
2808
- return JSON.parse(fs8.readFileSync(filePath, "utf-8"));
3161
+ return JSON.parse(fs9.readFileSync(filePath, "utf-8"));
2809
3162
  } catch {
2810
3163
  return null;
2811
3164
  }
@@ -3127,12 +3480,12 @@ setupCmd.command("migrate").description("\u8FC1\u79FB\u65E7\u7248\u914D\u7F6E\u5
3127
3480
  const newConfigPath = getCcemConfigPath();
3128
3481
  const legacyConfigPath = getLegacyConfigPath();
3129
3482
  console.log(chalk7.cyan("\n\u{1F504} \u914D\u7F6E\u8FC1\u79FB\n"));
3130
- if (!fs8.existsSync(legacyConfigPath)) {
3483
+ if (!fs9.existsSync(legacyConfigPath)) {
3131
3484
  console.log(chalk7.yellow("\u672A\u627E\u5230\u65E7\u7248\u914D\u7F6E\u6587\u4EF6"));
3132
3485
  console.log(chalk7.gray(` \u65E7\u8DEF\u5F84: ${legacyConfigPath}`));
3133
3486
  return;
3134
3487
  }
3135
- if (fs8.existsSync(newConfigPath) && !options.force) {
3488
+ if (fs9.existsSync(newConfigPath) && !options.force) {
3136
3489
  console.log(chalk7.green("\u2713 \u914D\u7F6E\u5DF2\u5728\u65B0\u8DEF\u5F84"));
3137
3490
  console.log(chalk7.gray(` \u8DEF\u5F84: ${newConfigPath}`));
3138
3491
  console.log(chalk7.gray("\n\u4F7F\u7528 --force \u5F3A\u5236\u91CD\u65B0\u8FC1\u79FB"));
@@ -3140,15 +3493,15 @@ setupCmd.command("migrate").description("\u8FC1\u79FB\u65E7\u7248\u914D\u7F6E\u5
3140
3493
  }
3141
3494
  try {
3142
3495
  ensureCcemDir();
3143
- fs8.copyFileSync(legacyConfigPath, newConfigPath);
3496
+ fs9.copyFileSync(legacyConfigPath, newConfigPath);
3144
3497
  console.log(chalk7.green("\u2713 \u914D\u7F6E\u5DF2\u8FC1\u79FB"));
3145
3498
  console.log(chalk7.gray(` \u4ECE: ${legacyConfigPath}`));
3146
3499
  console.log(chalk7.gray(` \u5230: ${newConfigPath}`));
3147
3500
  if (options.clean) {
3148
- fs8.unlinkSync(legacyConfigPath);
3149
- const legacyDir = path6.dirname(legacyConfigPath);
3501
+ fs9.unlinkSync(legacyConfigPath);
3502
+ const legacyDir = path7.dirname(legacyConfigPath);
3150
3503
  try {
3151
- fs8.rmdirSync(legacyDir);
3504
+ fs9.rmdirSync(legacyDir);
3152
3505
  } catch {
3153
3506
  }
3154
3507
  console.log(chalk7.green("\u2713 \u5DF2\u5220\u9664\u65E7\u914D\u7F6E\u6587\u4EF6"));
@@ -3159,13 +3512,13 @@ setupCmd.command("migrate").description("\u8FC1\u79FB\u65E7\u7248\u914D\u7F6E\u5
3159
3512
  });
3160
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() {
3161
3514
  const options = this.opts();
3162
- const skillDir = path6.join(process.env.HOME || "~", ".claude", "skills");
3163
- const targetPath = path6.join(skillDir, "ccem-cron.md");
3164
- if (!fs8.existsSync(skillDir)) {
3165
- 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 });
3166
3519
  console.log(chalk7.gray(`\u521B\u5EFA\u76EE\u5F55: ${skillDir}`));
3167
3520
  }
3168
- if (fs8.existsSync(targetPath) && !options.force) {
3521
+ if (fs9.existsSync(targetPath) && !options.force) {
3169
3522
  const { overwrite } = await inquirer.prompt([
3170
3523
  {
3171
3524
  type: "confirm",
@@ -3179,7 +3532,7 @@ setupCmd.command("cron").description("\u5B89\u88C5 ccem-cron skill \u5230 Claude
3179
3532
  return;
3180
3533
  }
3181
3534
  }
3182
- fs8.writeFileSync(targetPath, CCEM_CRON_SKILL_CONTENT, "utf-8");
3535
+ fs9.writeFileSync(targetPath, CCEM_CRON_SKILL_CONTENT, "utf-8");
3183
3536
  console.log(chalk7.green(`\u2713 \u5DF2\u5B89\u88C5 ccem-cron skill`));
3184
3537
  console.log(chalk7.gray(` \u8DEF\u5F84: ${targetPath}`));
3185
3538
  console.log(chalk7.cyan(`
@@ -3261,6 +3614,7 @@ program.command("launch").description(false).option("--env <name>", "\u73AF\u588
3261
3614
  const proxyBaseUrl = opts.proxyBaseUrl || opts.anthropicBaseUrl;
3262
3615
  const launchEnvConfig = proxyBaseUrl ? { ...envConfig, ANTHROPIC_BASE_URL: proxyBaseUrl } : envConfig;
3263
3616
  await launchClaude({
3617
+ envName,
3264
3618
  envConfig: launchEnvConfig,
3265
3619
  permMode: opts.perm,
3266
3620
  workingDir: opts.workingDir,
@@ -3319,7 +3673,11 @@ program.action(async (options) => {
3319
3673
  msg.error("No environment configuration found.");
3320
3674
  process.exit(1);
3321
3675
  }
3322
- await launchClaude({ envConfig, permMode: defaultMode || void 0 });
3676
+ await launchClaude({
3677
+ envName: current,
3678
+ envConfig,
3679
+ permMode: defaultMode || void 0
3680
+ });
3323
3681
  return;
3324
3682
  } else if (action === "usage") {
3325
3683
  console.clear();
@@ -3444,7 +3802,7 @@ Editing environment '${result.name}'`));
3444
3802
  }
3445
3803
  ]);
3446
3804
  if (permMode !== "back") {
3447
- await runWithTempPermissions(permMode, envConfig);
3805
+ await runWithTempPermissions(permMode, envConfig, current);
3448
3806
  return;
3449
3807
  }
3450
3808
  } else if (action === "setDefault") {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ccem",
3
- "version": "2.0.0-beta.16",
3
+ "version": "2.0.0-beta.18",
4
4
  "type": "module",
5
5
  "description": "Claude Code Environment Manager",
6
6
  "author": {