ccem 2.0.0-beta → 2.0.0-beta.11

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 +916 -314
  2. package/package.json +14 -14
package/dist/index.js CHANGED
@@ -7,28 +7,435 @@ 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 fs7 from "fs";
11
- import * as path5 from "path";
12
- import { fileURLToPath } from "url";
13
- import { encrypt as encrypt2, decrypt as decrypt2, ENV_PRESETS, PERMISSION_PRESETS as PERMISSION_PRESETS4, getCcemConfigDir, ensureCcemDir as ensureCcemDir3, getCcemConfigPath, getLegacyConfigPath } from "@ccem/core";
10
+ import * as fs8 from "fs";
11
+ import * as path6 from "path";
12
+ import { fileURLToPath as fileURLToPath2 } from "url";
13
+
14
+ // ../../packages/core/dist/chunk-6STWB3RD.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
+ }
68
+ var ENV_PRESETS = {
69
+ "GLM": {
70
+ ANTHROPIC_BASE_URL: "https://open.bigmodel.cn/api/anthropic",
71
+ ANTHROPIC_DEFAULT_OPUS_MODEL: "glm-5",
72
+ ANTHROPIC_DEFAULT_SONNET_MODEL: "glm-5",
73
+ ANTHROPIC_DEFAULT_HAIKU_MODEL: "glm-4.5-air",
74
+ ANTHROPIC_MODEL: "opus"
75
+ },
76
+ "KIMI": {
77
+ ANTHROPIC_BASE_URL: "https://api.moonshot.cn/anthropic",
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"
89
+ },
90
+ "MiniMax": {
91
+ ANTHROPIC_BASE_URL: "https://api.minimaxi.com/anthropic",
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"
96
+ },
97
+ "DeepSeek": {
98
+ ANTHROPIC_BASE_URL: "https://api.deepseek.com/anthropic",
99
+ ANTHROPIC_DEFAULT_OPUS_MODEL: "deepseek-chat",
100
+ ANTHROPIC_DEFAULT_SONNET_MODEL: "deepseek-chat",
101
+ ANTHROPIC_DEFAULT_HAIKU_MODEL: "deepseek-chat",
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-plus",
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-plus",
114
+ ANTHROPIC_DEFAULT_SONNET_MODEL: "qwen3-coder-plus",
115
+ ANTHROPIC_DEFAULT_HAIKU_MODEL: "qwen3-coder-plus",
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-1",
121
+ ANTHROPIC_DEFAULT_SONNET_MODEL: "anthropic/claude-opus-4-1",
122
+ ANTHROPIC_DEFAULT_HAIKU_MODEL: "anthropic/claude-3.5-haiku",
123
+ ANTHROPIC_MODEL: "opus"
124
+ }
125
+ };
126
+ var PERMISSION_PRESETS = {
127
+ "yolo": {
128
+ name: "YOLO \u6A21\u5F0F",
129
+ description: "\u5168\u90E8\u653E\u5F00\uFF0C\u65E0\u4EFB\u4F55\u9650\u5236",
130
+ permissionMode: "bypassPermissions",
131
+ permissions: {
132
+ allow: [
133
+ "Bash(*)",
134
+ "Read(*)",
135
+ "Edit(*)",
136
+ "Write(*)",
137
+ "WebFetch(*)",
138
+ "WebSearch(*)",
139
+ "Glob(*)",
140
+ "Grep(*)",
141
+ "LSP(*)",
142
+ "NotebookEdit(*)"
143
+ ],
144
+ deny: []
145
+ }
146
+ },
147
+ "dev": {
148
+ name: "\u5F00\u53D1\u6A21\u5F0F",
149
+ description: "\u65E5\u5E38\u5F00\u53D1\u6743\u9650\uFF0C\u4FDD\u62A4\u654F\u611F\u6587\u4EF6",
150
+ permissionMode: "acceptEdits",
151
+ permissions: {
152
+ allow: [
153
+ "Read(*)",
154
+ "Edit(*)",
155
+ "Write(*)",
156
+ "Glob(*)",
157
+ "Grep(*)",
158
+ "LSP(*)",
159
+ "NotebookEdit(*)",
160
+ "Bash(npm:*)",
161
+ "Bash(pnpm:*)",
162
+ "Bash(yarn:*)",
163
+ "Bash(bun:*)",
164
+ "Bash(node:*)",
165
+ "Bash(npx:*)",
166
+ "Bash(git:*)",
167
+ "Bash(tsc:*)",
168
+ "Bash(tsx:*)",
169
+ "Bash(eslint:*)",
170
+ "Bash(prettier:*)",
171
+ "Bash(jest:*)",
172
+ "Bash(vitest:*)",
173
+ "Bash(cargo:*)",
174
+ "Bash(python:*)",
175
+ "Bash(pip:*)",
176
+ "Bash(go:*)",
177
+ "Bash(make:*)",
178
+ "Bash(cmake:*)",
179
+ "Bash(ls:*)",
180
+ "Bash(cat:*)",
181
+ "Bash(head:*)",
182
+ "Bash(tail:*)",
183
+ "Bash(find:*)",
184
+ "Bash(wc:*)",
185
+ "Bash(mkdir:*)",
186
+ "Bash(cp:*)",
187
+ "Bash(mv:*)",
188
+ "Bash(touch:*)",
189
+ "WebSearch"
190
+ ],
191
+ deny: [
192
+ "Read(.env)",
193
+ "Read(.env.*)",
194
+ "Read(**/secrets/**)",
195
+ "Read(**/*.pem)",
196
+ "Read(**/*.key)",
197
+ "Read(**/*credential*)",
198
+ "Bash(rm -rf:*)",
199
+ "Bash(sudo:*)",
200
+ "Bash(chmod:*)",
201
+ "Bash(chown:*)"
202
+ ]
203
+ }
204
+ },
205
+ "readonly": {
206
+ name: "\u53EA\u8BFB\u6A21\u5F0F",
207
+ description: "\u4EC5\u5141\u8BB8\u8BFB\u53D6\u548C\u641C\u7D22\uFF0C\u7981\u6B62\u4EFB\u4F55\u4FEE\u6539",
208
+ permissionMode: "plan",
209
+ permissions: {
210
+ allow: [
211
+ "Read(*)",
212
+ "Glob(*)",
213
+ "Grep(*)",
214
+ "LSP(*)",
215
+ "Bash(git status:*)",
216
+ "Bash(git log:*)",
217
+ "Bash(git diff:*)",
218
+ "Bash(git branch:*)",
219
+ "Bash(git show:*)",
220
+ "Bash(ls:*)",
221
+ "Bash(cat:*)",
222
+ "Bash(head:*)",
223
+ "Bash(tail:*)",
224
+ "Bash(find:*)",
225
+ "Bash(wc:*)",
226
+ "Bash(file:*)",
227
+ "WebSearch"
228
+ ],
229
+ deny: [
230
+ "Edit(*)",
231
+ "Write(*)",
232
+ "NotebookEdit(*)",
233
+ "Bash(rm:*)",
234
+ "Bash(mv:*)",
235
+ "Bash(cp:*)",
236
+ "Bash(mkdir:*)",
237
+ "Bash(touch:*)",
238
+ "Bash(git add:*)",
239
+ "Bash(git commit:*)",
240
+ "Bash(git push:*)",
241
+ "Bash(git checkout:*)",
242
+ "Bash(git reset:*)",
243
+ "Bash(npm install:*)",
244
+ "Bash(pnpm install:*)",
245
+ "Bash(yarn add:*)"
246
+ ]
247
+ }
248
+ },
249
+ "safe": {
250
+ name: "\u5B89\u5168\u6A21\u5F0F",
251
+ description: "\u4FDD\u5B88\u6743\u9650\uFF0C\u9002\u5408\u4E0D\u719F\u6089\u7684\u4EE3\u7801\u5E93",
252
+ permissionMode: "default",
253
+ permissions: {
254
+ allow: [
255
+ "Read(*)",
256
+ "Glob(*)",
257
+ "Grep(*)",
258
+ "LSP(*)",
259
+ "Bash(git status:*)",
260
+ "Bash(git log:*)",
261
+ "Bash(git diff:*)",
262
+ "Bash(ls:*)",
263
+ "Bash(cat:*)",
264
+ "Bash(head:*)",
265
+ "Bash(tail:*)",
266
+ "Bash(find:*)",
267
+ "Bash(wc:*)"
268
+ ],
269
+ deny: [
270
+ "Read(.env)",
271
+ "Read(.env.*)",
272
+ "Read(**/secrets/**)",
273
+ "Read(**/*.pem)",
274
+ "Read(**/*.key)",
275
+ "Read(**/*credential*)",
276
+ "Read(**/*password*)",
277
+ "Edit(*)",
278
+ "Write(*)",
279
+ "NotebookEdit(*)",
280
+ "Bash(curl:*)",
281
+ "Bash(wget:*)",
282
+ "Bash(ssh:*)",
283
+ "Bash(scp:*)",
284
+ "Bash(rm:*)",
285
+ "Bash(mv:*)",
286
+ "WebFetch(*)"
287
+ ]
288
+ }
289
+ },
290
+ "ci": {
291
+ name: "CI/CD \u6A21\u5F0F",
292
+ description: "\u9002\u5408\u81EA\u52A8\u5316\u6D41\u6C34\u7EBF\u7684\u6743\u9650",
293
+ permissionMode: "default",
294
+ permissions: {
295
+ allow: [
296
+ "Read(*)",
297
+ "Edit(*)",
298
+ "Write(*)",
299
+ "Glob(*)",
300
+ "Grep(*)",
301
+ "LSP(*)",
302
+ "Bash(npm:*)",
303
+ "Bash(pnpm:*)",
304
+ "Bash(yarn:*)",
305
+ "Bash(node:*)",
306
+ "Bash(git:*)",
307
+ "Bash(docker:*)",
308
+ "Bash(make:*)",
309
+ "Bash(cargo:*)",
310
+ "Bash(go:*)",
311
+ "Bash(python:*)",
312
+ "Bash(pip:*)",
313
+ "Bash(pytest:*)",
314
+ "Bash(jest:*)",
315
+ "Bash(vitest:*)"
316
+ ],
317
+ deny: [
318
+ "Read(.env.local)",
319
+ "Read(**/secrets/**)",
320
+ "Bash(sudo:*)",
321
+ "Bash(ssh:*)",
322
+ "Bash(scp:*)",
323
+ "WebFetch(*)",
324
+ "WebSearch"
325
+ ]
326
+ }
327
+ },
328
+ "audit": {
329
+ name: "\u5BA1\u8BA1\u6A21\u5F0F",
330
+ description: "\u4EC5\u8BFB\u53D6\u548C\u641C\u7D22\uFF0C\u7528\u4E8E\u5B89\u5168\u5BA1\u8BA1",
331
+ permissionMode: "plan",
332
+ permissions: {
333
+ allow: [
334
+ "Read(*)",
335
+ "Glob(*)",
336
+ "Grep(*)",
337
+ "LSP(*)",
338
+ "Bash(git log:*)",
339
+ "Bash(git blame:*)",
340
+ "Bash(git show:*)",
341
+ "Bash(git diff:*)",
342
+ "Bash(ls:*)",
343
+ "Bash(find:*)",
344
+ "Bash(wc:*)",
345
+ "Bash(file:*)",
346
+ "Bash(stat:*)"
347
+ ],
348
+ deny: [
349
+ "Edit(*)",
350
+ "Write(*)",
351
+ "NotebookEdit(*)",
352
+ "Bash(rm:*)",
353
+ "Bash(mv:*)",
354
+ "Bash(cp:*)",
355
+ "Bash(curl:*)",
356
+ "Bash(wget:*)",
357
+ "Bash(ssh:*)",
358
+ "WebFetch(*)"
359
+ ]
360
+ }
361
+ }
362
+ };
363
+ var getPermissionModeNames = () => {
364
+ return Object.keys(PERMISSION_PRESETS);
365
+ };
366
+
367
+ // ../../packages/core/dist/index.js
368
+ import crypto from "crypto";
369
+ import fs from "fs";
370
+ import path from "path";
371
+ var ALGORITHM = "aes-256-cbc";
372
+ var SECRET_KEY = crypto.scryptSync("claude-code-env-manager-secret", "salt", 32);
373
+ var encrypt = (text) => {
374
+ if (!text) return text;
375
+ const iv = crypto.randomBytes(16);
376
+ const cipher = crypto.createCipheriv(ALGORITHM, SECRET_KEY, iv);
377
+ let encrypted = cipher.update(text, "utf8", "hex");
378
+ encrypted += cipher.final("hex");
379
+ return `enc:${iv.toString("hex")}:${encrypted}`;
380
+ };
381
+ var decrypt = (text) => {
382
+ if (!text || !text.startsWith("enc:")) return text;
383
+ try {
384
+ const parts = text.split(":");
385
+ if (parts.length !== 3) return text;
386
+ const iv = Buffer.from(parts[1], "hex");
387
+ const encryptedText = parts[2];
388
+ const decipher = crypto.createDecipheriv(ALGORITHM, SECRET_KEY, iv);
389
+ let decrypted = decipher.update(encryptedText, "hex", "utf8");
390
+ decrypted += decipher.final("utf8");
391
+ return decrypted;
392
+ } catch {
393
+ return text;
394
+ }
395
+ };
396
+ var getHomeDir = () => {
397
+ return process.env.HOME || process.env.USERPROFILE || "";
398
+ };
399
+ var getCcemConfigDir = () => {
400
+ return path.join(getHomeDir(), ".ccem");
401
+ };
402
+ var getCcemConfigPath = () => {
403
+ return path.join(getCcemConfigDir(), "config.json");
404
+ };
405
+ var ensureCcemDir = () => {
406
+ const ccemDir = getCcemConfigDir();
407
+ if (!fs.existsSync(ccemDir)) {
408
+ fs.mkdirSync(ccemDir, { recursive: true });
409
+ }
410
+ return ccemDir;
411
+ };
412
+ var getLegacyConfigPath = () => {
413
+ const home = getHomeDir();
414
+ if (process.platform === "darwin") {
415
+ return path.join(home, "Library", "Preferences", "claude-code-env-manager-nodejs", "config.json");
416
+ }
417
+ return path.join(home, ".config", "claude-code-env-manager-nodejs", "config.json");
418
+ };
14
419
 
15
420
  // src/ui.ts
16
421
  import chalk from "chalk";
17
422
  import Table from "cli-table3";
18
- import { PERMISSION_PRESETS } from "@ccem/core";
19
423
 
20
424
  // src/usage.ts
21
- import * as fs from "fs";
425
+ import * as fs2 from "fs";
22
426
  import * as fsPromises from "fs/promises";
23
- import * as path from "path";
427
+ import * as path2 from "path";
24
428
  import * as os from "os";
25
429
  import * as readline from "readline";
26
- var CLAUDE_PROJECTS_DIR = path.join(os.homedir(), ".claude", "projects");
27
- var CCEM_DIR = path.join(os.homedir(), ".ccem");
430
+ import { fileURLToPath } from "url";
431
+ var __filename = fileURLToPath(import.meta.url);
432
+ var __dirname = path2.dirname(__filename);
433
+ var CLAUDE_PROJECTS_DIR = path2.join(os.homedir(), ".claude", "projects");
434
+ var CCEM_DIR = path2.join(os.homedir(), ".ccem");
28
435
  var CACHE_VERSION = 1;
29
- var getCachePath = () => path.join(CCEM_DIR, "usage-cache.json");
30
- var getPricesPath = () => path.join(CCEM_DIR, "model-prices.json");
31
- async function ensureCcemDir() {
436
+ var getCachePath = () => path2.join(CCEM_DIR, "usage-cache.json");
437
+ var getPricesPath = () => path2.join(CCEM_DIR, "model-prices.json");
438
+ async function ensureCcemDir2() {
32
439
  try {
33
440
  await fsPromises.access(CCEM_DIR);
34
441
  } catch {
@@ -37,7 +444,7 @@ async function ensureCcemDir() {
37
444
  }
38
445
  var LITELLM_PRICES_URL = "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json";
39
446
  var getBundledPricesPath = () => {
40
- return path.join(__dirname, "..", "model-prices.json");
447
+ return path2.join(__dirname, "..", "model-prices.json");
41
448
  };
42
449
  var DEFAULT_PRICES = {
43
450
  "claude-opus-4-5": {
@@ -66,7 +473,7 @@ function normalizeModelName(model) {
66
473
  var pricesCache = null;
67
474
  async function loadPrices() {
68
475
  if (pricesCache) return pricesCache;
69
- await ensureCcemDir();
476
+ await ensureCcemDir2();
70
477
  const pricesPath = getPricesPath();
71
478
  try {
72
479
  const response = await fetch(LITELLM_PRICES_URL, { signal: AbortSignal.timeout(1e3) });
@@ -156,8 +563,8 @@ async function getFileMetaAsync(filePath) {
156
563
  function loadCacheSync() {
157
564
  try {
158
565
  const cachePath = getCachePath();
159
- if (!fs.existsSync(cachePath)) return null;
160
- const data = JSON.parse(fs.readFileSync(cachePath, "utf-8"));
566
+ if (!fs2.existsSync(cachePath)) return null;
567
+ const data = JSON.parse(fs2.readFileSync(cachePath, "utf-8"));
161
568
  if (data.version !== CACHE_VERSION) return null;
162
569
  return data;
163
570
  } catch {
@@ -216,7 +623,7 @@ function getUsageStatsFromCache() {
216
623
  }
217
624
  async function saveCacheAsync(cache) {
218
625
  try {
219
- await ensureCcemDir();
626
+ await ensureCcemDir2();
220
627
  await fsPromises.writeFile(getCachePath(), JSON.stringify(cache, null, 2));
221
628
  } catch {
222
629
  }
@@ -224,7 +631,7 @@ async function saveCacheAsync(cache) {
224
631
  async function parseJSONLFileAsync(filePath, prices, signal) {
225
632
  const entries = [];
226
633
  try {
227
- const fileStream = fs.createReadStream(filePath, { encoding: "utf-8" });
634
+ const fileStream = fs2.createReadStream(filePath, { encoding: "utf-8" });
228
635
  const rl = readline.createInterface({
229
636
  input: fileStream,
230
637
  crlfDelay: Infinity
@@ -278,14 +685,14 @@ async function getAllJSONLFilesAsync() {
278
685
  if (!projectsDirExists) return files;
279
686
  const projects = await fsPromises.readdir(CLAUDE_PROJECTS_DIR);
280
687
  for (const project of projects) {
281
- const projectPath = path.join(CLAUDE_PROJECTS_DIR, project);
688
+ const projectPath = path2.join(CLAUDE_PROJECTS_DIR, project);
282
689
  try {
283
690
  const stat2 = await fsPromises.stat(projectPath);
284
691
  if (stat2.isDirectory()) {
285
692
  const projectFiles = await fsPromises.readdir(projectPath);
286
693
  for (const file of projectFiles) {
287
694
  if (file.endsWith(".jsonl")) {
288
- files.push(path.join(projectPath, file));
695
+ files.push(path2.join(projectPath, file));
289
696
  }
290
697
  }
291
698
  }
@@ -496,9 +903,10 @@ var renderLogoWithEnvPanel = (envName, env, defaultMode) => {
496
903
  const titleShort = theme.primary("CCEM");
497
904
  const envLabel = theme.muted("Env: ") + theme.primary(envName);
498
905
  const baseUrl = env.ANTHROPIC_BASE_URL || "-";
499
- const model = env.ANTHROPIC_MODEL || "-";
500
- const fastModel = env.ANTHROPIC_SMALL_FAST_MODEL || "-";
501
- const apiKey = env.ANTHROPIC_API_KEY ? env.ANTHROPIC_API_KEY.slice(0, 2) + "\u2022\u2022\u2022\u2022" + env.ANTHROPIC_API_KEY.slice(-4) : "-";
906
+ const runtimeModel = env.ANTHROPIC_MODEL || "-";
907
+ const opusModel = env.ANTHROPIC_DEFAULT_OPUS_MODEL || "-";
908
+ const haikuModel = env.ANTHROPIC_DEFAULT_HAIKU_MODEL || "-";
909
+ const authToken = env.ANTHROPIC_AUTH_TOKEN ? env.ANTHROPIC_AUTH_TOKEN.slice(0, 2) + "\u2022\u2022\u2022\u2022" + env.ANTHROPIC_AUTH_TOKEN.slice(-4) : "-";
502
910
  const truncate = (s, max) => s.length > max ? s.slice(0, max - 3) + "..." : s;
503
911
  const maskUrl = (url, max) => {
504
912
  if (url.length <= max) return url;
@@ -506,10 +914,10 @@ var renderLogoWithEnvPanel = (envName, env, defaultMode) => {
506
914
  const parsed = new URL(url);
507
915
  const protocol = parsed.protocol + "//";
508
916
  const host = parsed.host;
509
- const path6 = parsed.pathname + parsed.search;
917
+ const path7 = parsed.pathname + parsed.search;
510
918
  const hostStart = host.slice(0, 8);
511
919
  const hostEnd = host.slice(-4);
512
- const pathPart = path6.length > 10 ? path6.slice(0, 7) + "..." : path6;
920
+ const pathPart = path7.length > 10 ? path7.slice(0, 7) + "..." : path7;
513
921
  return `${protocol}${hostStart}...${hostEnd}${pathPart}`;
514
922
  } catch {
515
923
  return truncate(url, max);
@@ -520,15 +928,15 @@ var renderLogoWithEnvPanel = (envName, env, defaultMode) => {
520
928
  if (isNarrow) {
521
929
  envLines = [
522
930
  envLabel,
523
- theme.muted("Model:".padEnd(labelWidth)) + theme.dim(truncate(model, 25)),
524
- theme.muted("Key:".padEnd(labelWidth)) + theme.dim(apiKey)
931
+ theme.muted("Opus:".padEnd(labelWidth)) + theme.dim(truncate(opusModel, 25)),
932
+ theme.muted("Token:".padEnd(labelWidth)) + theme.dim(authToken)
525
933
  ];
526
934
  } else {
527
935
  envLines = [
528
936
  envLabel + (defaultMode && PERMISSION_PRESETS[defaultMode] ? " " + theme.accent(`[${PERMISSION_PRESETS[defaultMode].name}]`) : ""),
529
937
  theme.muted("URL:".padEnd(labelWidth)) + theme.dim(maskUrl(baseUrl, 40)),
530
- theme.muted("Model:".padEnd(labelWidth)) + theme.dim(truncate(model, 15)) + " " + theme.muted("Fast:".padEnd(labelWidth)) + theme.dim(truncate(fastModel, 15)),
531
- theme.muted("Key:".padEnd(labelWidth)) + theme.dim(apiKey)
938
+ theme.muted("Run:".padEnd(labelWidth)) + theme.dim(truncate(runtimeModel, 12)) + " " + theme.muted("Opus:".padEnd(labelWidth)) + theme.dim(truncate(opusModel, 15)),
939
+ theme.muted("Haiku:".padEnd(labelWidth)) + theme.dim(truncate(haikuModel, 15)) + " " + theme.muted("Token:".padEnd(labelWidth)) + theme.dim(authToken)
532
940
  ];
533
941
  }
534
942
  const lines = [];
@@ -947,74 +1355,86 @@ var selectEnvWithKeys = (registries, current) => {
947
1355
  };
948
1356
 
949
1357
  // src/permissions.ts
950
- import fs4 from "fs";
1358
+ import fs5 from "fs";
951
1359
  import chalk3 from "chalk";
952
1360
  import Table2 from "cli-table3";
953
- import { PERMISSION_PRESETS as PERMISSION_PRESETS3, getPermissionModeNames } from "@ccem/core";
954
1361
 
955
1362
  // src/utils.ts
956
- import crypto from "crypto";
957
- import fs2 from "fs";
958
- import path2 from "path";
959
- var SECRET_KEY = crypto.scryptSync("claude-code-env-manager-secret", "salt", 32);
1363
+ import crypto2 from "crypto";
1364
+ import fs3 from "fs";
1365
+ import path3 from "path";
1366
+ var SECRET_KEY2 = crypto2.scryptSync("claude-code-env-manager-secret", "salt", 32);
960
1367
  var findProjectRoot = () => {
961
1368
  let currentDir = process.cwd();
962
- const root = path2.parse(currentDir).root;
1369
+ const root = path3.parse(currentDir).root;
963
1370
  while (currentDir !== root) {
964
- if (fs2.existsSync(path2.join(currentDir, ".git")) || fs2.existsSync(path2.join(currentDir, "package.json"))) {
1371
+ if (fs3.existsSync(path3.join(currentDir, ".git")) || fs3.existsSync(path3.join(currentDir, "package.json"))) {
965
1372
  return currentDir;
966
1373
  }
967
- currentDir = path2.dirname(currentDir);
1374
+ currentDir = path3.dirname(currentDir);
968
1375
  }
969
1376
  return process.cwd();
970
1377
  };
971
1378
  var getSettingsPath = (useLocal = true) => {
972
1379
  const projectRoot = findProjectRoot();
973
- const claudeDir = path2.join(projectRoot, ".claude");
1380
+ const claudeDir = path3.join(projectRoot, ".claude");
974
1381
  const filename = useLocal ? "settings.local.json" : "settings.json";
975
- return path2.join(claudeDir, filename);
1382
+ return path3.join(claudeDir, filename);
976
1383
  };
977
1384
  var ensureClaudeDir = () => {
978
1385
  const projectRoot = findProjectRoot();
979
- const claudeDir = path2.join(projectRoot, ".claude");
980
- if (!fs2.existsSync(claudeDir)) {
981
- fs2.mkdirSync(claudeDir, { recursive: true });
1386
+ const claudeDir = path3.join(projectRoot, ".claude");
1387
+ if (!fs3.existsSync(claudeDir)) {
1388
+ fs3.mkdirSync(claudeDir, { recursive: true });
982
1389
  }
983
1390
  return claudeDir;
984
1391
  };
985
- var getHomeDir = () => {
1392
+ var getHomeDir2 = () => {
986
1393
  return process.env.HOME || process.env.USERPROFILE || "";
987
1394
  };
988
1395
  var getGlobalClaudeConfigPath = () => {
989
- return path2.join(getHomeDir(), ".claude.json");
1396
+ return path3.join(getHomeDir2(), ".claude.json");
990
1397
  };
991
1398
  var getGlobalClaudeSettingsPath = () => {
992
- return path2.join(getHomeDir(), ".claude", "settings.json");
1399
+ return path3.join(getHomeDir2(), ".claude", "settings.json");
993
1400
  };
994
1401
  var ensureGlobalClaudeDir = () => {
995
- const claudeDir = path2.join(getHomeDir(), ".claude");
996
- if (!fs2.existsSync(claudeDir)) {
997
- fs2.mkdirSync(claudeDir, { recursive: true });
1402
+ const claudeDir = path3.join(getHomeDir2(), ".claude");
1403
+ if (!fs3.existsSync(claudeDir)) {
1404
+ fs3.mkdirSync(claudeDir, { recursive: true });
998
1405
  }
999
1406
  return claudeDir;
1000
1407
  };
1001
1408
 
1002
1409
  // src/launcher.ts
1003
1410
  import { spawn } from "child_process";
1004
- import * as fs3 from "fs";
1005
- import * as path3 from "path";
1411
+ import * as fs4 from "fs";
1412
+ import * as path4 from "path";
1006
1413
  import chalk2 from "chalk";
1007
- import { decrypt, PERMISSION_PRESETS as PERMISSION_PRESETS2, ensureCcemDir as ensureCcemDir2 } from "@ccem/core";
1414
+ var MANAGED_CLAUDE_ENV_KEYS = [
1415
+ "ANTHROPIC_BASE_URL",
1416
+ "ANTHROPIC_AUTH_TOKEN",
1417
+ "ANTHROPIC_DEFAULT_OPUS_MODEL",
1418
+ "ANTHROPIC_DEFAULT_SONNET_MODEL",
1419
+ "ANTHROPIC_DEFAULT_HAIKU_MODEL",
1420
+ "ANTHROPIC_MODEL",
1421
+ "CLAUDE_CODE_SUBAGENT_MODEL",
1422
+ "ANTHROPIC_API_KEY",
1423
+ "ANTHROPIC_SMALL_FAST_MODEL"
1424
+ ];
1008
1425
  function buildEnvVars(envConfig) {
1009
1426
  const vars = {};
1010
1427
  if (envConfig.ANTHROPIC_BASE_URL) vars.ANTHROPIC_BASE_URL = envConfig.ANTHROPIC_BASE_URL;
1011
- if (envConfig.ANTHROPIC_API_KEY) vars.ANTHROPIC_API_KEY = decrypt(envConfig.ANTHROPIC_API_KEY);
1428
+ if (envConfig.ANTHROPIC_AUTH_TOKEN) vars.ANTHROPIC_AUTH_TOKEN = decrypt(envConfig.ANTHROPIC_AUTH_TOKEN);
1429
+ if (envConfig.ANTHROPIC_DEFAULT_OPUS_MODEL) vars.ANTHROPIC_DEFAULT_OPUS_MODEL = envConfig.ANTHROPIC_DEFAULT_OPUS_MODEL;
1430
+ if (envConfig.ANTHROPIC_DEFAULT_SONNET_MODEL) vars.ANTHROPIC_DEFAULT_SONNET_MODEL = envConfig.ANTHROPIC_DEFAULT_SONNET_MODEL;
1431
+ if (envConfig.ANTHROPIC_DEFAULT_HAIKU_MODEL) vars.ANTHROPIC_DEFAULT_HAIKU_MODEL = envConfig.ANTHROPIC_DEFAULT_HAIKU_MODEL;
1012
1432
  if (envConfig.ANTHROPIC_MODEL) vars.ANTHROPIC_MODEL = envConfig.ANTHROPIC_MODEL;
1013
- if (envConfig.ANTHROPIC_SMALL_FAST_MODEL) vars.ANTHROPIC_SMALL_FAST_MODEL = envConfig.ANTHROPIC_SMALL_FAST_MODEL;
1433
+ if (envConfig.CLAUDE_CODE_SUBAGENT_MODEL) vars.CLAUDE_CODE_SUBAGENT_MODEL = envConfig.CLAUDE_CODE_SUBAGENT_MODEL;
1014
1434
  return vars;
1015
1435
  }
1016
1436
  function buildPermArgs(modeName) {
1017
- const preset = PERMISSION_PRESETS2[modeName];
1437
+ const preset = PERMISSION_PRESETS[modeName];
1018
1438
  if (!preset) return [];
1019
1439
  const args = ["--permission-mode", preset.permissionMode];
1020
1440
  if (preset.permissions.allow.length > 0) {
@@ -1028,22 +1448,25 @@ function buildPermArgs(modeName) {
1028
1448
  return args;
1029
1449
  }
1030
1450
  function ensureSessionsDir() {
1031
- const dir = path3.join(ensureCcemDir2(), "sessions");
1032
- if (!fs3.existsSync(dir)) {
1033
- fs3.mkdirSync(dir, { recursive: true });
1451
+ const dir = path4.join(ensureCcemDir(), "sessions");
1452
+ if (!fs4.existsSync(dir)) {
1453
+ fs4.mkdirSync(dir, { recursive: true });
1034
1454
  }
1035
1455
  return dir;
1036
1456
  }
1037
1457
  async function launchClaude(options) {
1038
1458
  const { envConfig, permMode, workingDir, sessionId, resumeSessionId, silent } = options;
1039
1459
  const env = { ...process.env };
1460
+ for (const key of MANAGED_CLAUDE_ENV_KEYS) {
1461
+ delete env[key];
1462
+ }
1040
1463
  if (envConfig) {
1041
1464
  Object.assign(env, buildEnvVars(envConfig));
1042
1465
  }
1043
1466
  delete env.CLAUDECODE;
1044
1467
  const args = [];
1045
1468
  if (permMode) {
1046
- const preset = PERMISSION_PRESETS2[permMode];
1469
+ const preset = PERMISSION_PRESETS[permMode];
1047
1470
  if (preset) {
1048
1471
  if (!silent) {
1049
1472
  console.log(chalk2.green(`\u5DF2\u5E94\u7528 ${preset.name}\uFF08\u4E34\u65F6\uFF09`));
@@ -1066,14 +1489,15 @@ async function launchClaude(options) {
1066
1489
  return new Promise((resolve2) => {
1067
1490
  const child = spawn("claude", args, {
1068
1491
  stdio: "inherit",
1069
- shell: true,
1492
+ shell: false,
1493
+ // 直接执行二进制,避免 shell 注入风险
1070
1494
  env
1071
1495
  });
1072
1496
  child.on("exit", (code) => {
1073
1497
  if (sessionId) {
1074
1498
  try {
1075
- fs3.writeFileSync(
1076
- path3.join(sessionsDir, `${sessionId}.exit`),
1499
+ fs4.writeFileSync(
1500
+ path4.join(sessionsDir, `${sessionId}.exit`),
1077
1501
  String(code ?? 0)
1078
1502
  );
1079
1503
  } catch {
@@ -1090,14 +1514,14 @@ async function launchClaude(options) {
1090
1514
 
1091
1515
  // src/permissions.ts
1092
1516
  var readSettings = (settingsPath) => {
1093
- if (fs4.existsSync(settingsPath)) {
1517
+ if (fs5.existsSync(settingsPath)) {
1094
1518
  try {
1095
- const content = fs4.readFileSync(settingsPath, "utf-8");
1519
+ const content = fs5.readFileSync(settingsPath, "utf-8");
1096
1520
  return JSON.parse(content);
1097
1521
  } catch {
1098
1522
  console.warn(chalk3.yellow(`\u8B66\u544A: \u65E0\u6CD5\u89E3\u6790 ${settingsPath}\uFF0C\u5C06\u521B\u5EFA\u5907\u4EFD`));
1099
1523
  const backupPath = settingsPath + ".error." + Date.now();
1100
- fs4.copyFileSync(settingsPath, backupPath);
1524
+ fs5.copyFileSync(settingsPath, backupPath);
1101
1525
  console.log(chalk3.gray(`\u5907\u4EFD\u5DF2\u4FDD\u5B58\u5230: ${backupPath}`));
1102
1526
  return {};
1103
1527
  }
@@ -1106,7 +1530,7 @@ var readSettings = (settingsPath) => {
1106
1530
  };
1107
1531
  var writeSettings = (settingsPath, config3) => {
1108
1532
  ensureClaudeDir();
1109
- fs4.writeFileSync(settingsPath, JSON.stringify(config3, null, 2) + "\n");
1533
+ fs5.writeFileSync(settingsPath, JSON.stringify(config3, null, 2) + "\n");
1110
1534
  };
1111
1535
  var mergePermissions = (existing, preset) => {
1112
1536
  const existingAllow = existing.permissions?.allow || [];
@@ -1122,7 +1546,7 @@ var mergePermissions = (existing, preset) => {
1122
1546
  };
1123
1547
  };
1124
1548
  var applyPermissionMode = (modeName) => {
1125
- const preset = PERMISSION_PRESETS3[modeName];
1549
+ const preset = PERMISSION_PRESETS[modeName];
1126
1550
  if (!preset) {
1127
1551
  console.error(chalk3.red(`\u672A\u77E5\u7684\u6743\u9650\u6A21\u5F0F: ${modeName}`));
1128
1552
  console.log(chalk3.yellow("\u53EF\u7528\u6A21\u5F0F: " + getPermissionModeNames().join(", ")));
@@ -1138,14 +1562,14 @@ var applyPermissionMode = (modeName) => {
1138
1562
  };
1139
1563
  var resetPermissions = () => {
1140
1564
  const settingsPath = getSettingsPath(true);
1141
- if (!fs4.existsSync(settingsPath)) {
1565
+ if (!fs5.existsSync(settingsPath)) {
1142
1566
  console.log(chalk3.yellow("\u6CA1\u6709\u81EA\u5B9A\u4E49\u6743\u9650\u914D\u7F6E\u9700\u8981\u91CD\u7F6E"));
1143
1567
  return;
1144
1568
  }
1145
1569
  const config3 = readSettings(settingsPath);
1146
1570
  delete config3.permissions;
1147
1571
  if (Object.keys(config3).length === 0) {
1148
- fs4.unlinkSync(settingsPath);
1572
+ fs5.unlinkSync(settingsPath);
1149
1573
  console.log(chalk3.green("\u5DF2\u5220\u9664\u914D\u7F6E\u6587\u4EF6\uFF08\u6587\u4EF6\u4E3A\u7A7A\uFF09"));
1150
1574
  } else {
1151
1575
  writeSettings(settingsPath, config3);
@@ -1155,7 +1579,7 @@ var resetPermissions = () => {
1155
1579
  };
1156
1580
  var showCurrentMode = () => {
1157
1581
  const settingsPath = getSettingsPath(true);
1158
- if (!fs4.existsSync(settingsPath)) {
1582
+ if (!fs5.existsSync(settingsPath)) {
1159
1583
  console.log(chalk3.yellow("\u672A\u914D\u7F6E\u81EA\u5B9A\u4E49\u6743\u9650"));
1160
1584
  console.log(chalk3.gray(`\u6587\u4EF6\u4E0D\u5B58\u5728: ${settingsPath}`));
1161
1585
  return;
@@ -1165,7 +1589,7 @@ var showCurrentMode = () => {
1165
1589
  console.log(chalk3.yellow("\u672A\u914D\u7F6E\u81EA\u5B9A\u4E49\u6743\u9650"));
1166
1590
  return;
1167
1591
  }
1168
- const matchedPreset = Object.entries(PERMISSION_PRESETS3).find(([_, preset]) => {
1592
+ const matchedPreset = Object.entries(PERMISSION_PRESETS).find(([_, preset]) => {
1169
1593
  const configAllow = new Set(config3.permissions?.allow || []);
1170
1594
  const configDeny = new Set(config3.permissions?.deny || []);
1171
1595
  const presetAllow = new Set(preset.permissions.allow);
@@ -1197,7 +1621,7 @@ var listAvailableModes = () => {
1197
1621
  style: { head: ["cyan"] },
1198
1622
  colWidths: [15, 15, 50]
1199
1623
  });
1200
- Object.entries(PERMISSION_PRESETS3).forEach(([key, preset]) => {
1624
+ Object.entries(PERMISSION_PRESETS).forEach(([key, preset]) => {
1201
1625
  table.push([preset.name, `--${key}`, preset.description]);
1202
1626
  });
1203
1627
  console.log(chalk3.bold("\u53EF\u7528\u6743\u9650\u6A21\u5F0F:\n"));
@@ -1206,7 +1630,7 @@ var listAvailableModes = () => {
1206
1630
  console.log(chalk3.gray("\u6C38\u4E45\u6A21\u5F0F: ccem setup perms --<mode>"));
1207
1631
  };
1208
1632
  var runWithTempPermissions = async (modeName, envConfig) => {
1209
- const preset = PERMISSION_PRESETS3[modeName];
1633
+ const preset = PERMISSION_PRESETS[modeName];
1210
1634
  if (!preset) {
1211
1635
  console.error(chalk3.red(`\u672A\u77E5\u7684\u6743\u9650\u6A21\u5F0F: ${modeName}`));
1212
1636
  console.log(chalk3.yellow("\u53EF\u7528\u6A21\u5F0F: " + getPermissionModeNames().join(", ")));
@@ -1216,13 +1640,13 @@ var runWithTempPermissions = async (modeName, envConfig) => {
1216
1640
  };
1217
1641
 
1218
1642
  // src/setup.ts
1219
- import fs5 from "fs";
1643
+ import fs6 from "fs";
1220
1644
  import chalk4 from "chalk";
1221
1645
  import { spawn as spawn2 } from "child_process";
1222
1646
  var readJsonFile = (filePath) => {
1223
- if (fs5.existsSync(filePath)) {
1647
+ if (fs6.existsSync(filePath)) {
1224
1648
  try {
1225
- const content = fs5.readFileSync(filePath, "utf-8");
1649
+ const content = fs6.readFileSync(filePath, "utf-8");
1226
1650
  return JSON.parse(content);
1227
1651
  } catch {
1228
1652
  console.warn(chalk4.yellow(`\u8B66\u544A: \u65E0\u6CD5\u89E3\u6790 ${filePath}`));
@@ -1232,7 +1656,7 @@ var readJsonFile = (filePath) => {
1232
1656
  return {};
1233
1657
  };
1234
1658
  var writeJsonFile = (filePath, data) => {
1235
- fs5.writeFileSync(filePath, JSON.stringify(data, null, 2) + "\n");
1659
+ fs6.writeFileSync(filePath, JSON.stringify(data, null, 2) + "\n");
1236
1660
  };
1237
1661
  var setupOnboarding = () => {
1238
1662
  const configPath = getGlobalClaudeConfigPath();
@@ -1332,14 +1756,19 @@ var setupMcpTool = () => {
1332
1756
  });
1333
1757
  });
1334
1758
  };
1335
- var runSetupInit = async () => {
1759
+ var runSetupInit = async (options = {}) => {
1336
1760
  console.log(chalk4.bold("\n\u{1F527} Claude Code \u521D\u59CB\u5316\u8BBE\u7F6E\n"));
1337
1761
  console.log(chalk4.cyan("1. \u8BBE\u7F6E onboarding \u72B6\u6001"));
1338
1762
  const step1 = setupOnboarding();
1339
1763
  console.log(chalk4.cyan("\n2. \u914D\u7F6E\u9690\u79C1\u8BBE\u7F6E"));
1340
1764
  const step2 = setupEnvSettings();
1341
- console.log(chalk4.cyan("\n3. \u5B89\u88C5 MCP \u5DE5\u5177"));
1342
- const step3 = await setupMcpTool();
1765
+ let step3 = true;
1766
+ if (options.chrome) {
1767
+ console.log(chalk4.cyan("\n3. \u5B89\u88C5 MCP \u5DE5\u5177"));
1768
+ step3 = await setupMcpTool();
1769
+ } else {
1770
+ console.log(chalk4.gray("\n3. \u5B89\u88C5 MCP \u5DE5\u5177\uFF08\u5DF2\u8DF3\u8FC7\uFF0C\u4F7F\u7528 --chrome \u542F\u7528\uFF09"));
1771
+ }
1343
1772
  console.log("");
1344
1773
  if (step1 && step2 && step3) {
1345
1774
  console.log(chalk4.green.bold("\u2705 \u521D\u59CB\u5316\u5B8C\u6210\uFF01"));
@@ -1354,8 +1783,8 @@ var runSetupInit = async () => {
1354
1783
 
1355
1784
  // src/skills.ts
1356
1785
  import { execSync } from "child_process";
1357
- import * as fs6 from "fs";
1358
- import * as path4 from "path";
1786
+ import * as fs7 from "fs";
1787
+ import * as path5 from "path";
1359
1788
  import chalk5 from "chalk";
1360
1789
  var SKILL_GROUPS = {
1361
1790
  official: { label: "\u5B98\u65B9", icon: "\u{1F3E2}" },
@@ -1524,12 +1953,12 @@ function parseGitHubUrl(url) {
1524
1953
  };
1525
1954
  }
1526
1955
  function getSkillsDir() {
1527
- return path4.join(process.cwd(), ".claude", "skills");
1956
+ return path5.join(process.cwd(), ".claude", "skills");
1528
1957
  }
1529
1958
  function ensureSkillsDir() {
1530
1959
  const skillsDir = getSkillsDir();
1531
- if (!fs6.existsSync(skillsDir)) {
1532
- fs6.mkdirSync(skillsDir, { recursive: true });
1960
+ if (!fs7.existsSync(skillsDir)) {
1961
+ fs7.mkdirSync(skillsDir, { recursive: true });
1533
1962
  } else {
1534
1963
  cleanupTempDirs(skillsDir);
1535
1964
  }
@@ -1537,11 +1966,11 @@ function ensureSkillsDir() {
1537
1966
  }
1538
1967
  function cleanupTempDirs(skillsDir) {
1539
1968
  try {
1540
- const entries = fs6.readdirSync(skillsDir, { withFileTypes: true });
1969
+ const entries = fs7.readdirSync(skillsDir, { withFileTypes: true });
1541
1970
  for (const entry of entries) {
1542
1971
  if (entry.isDirectory() && entry.name.startsWith(".tmp-")) {
1543
- const tmpPath = path4.join(skillsDir, entry.name);
1544
- fs6.rmSync(tmpPath, { recursive: true });
1972
+ const tmpPath = path5.join(skillsDir, entry.name);
1973
+ fs7.rmSync(tmpPath, { recursive: true });
1545
1974
  }
1546
1975
  }
1547
1976
  } catch {
@@ -1549,24 +1978,24 @@ function cleanupTempDirs(skillsDir) {
1549
1978
  }
1550
1979
  function downloadSkillWithGit(owner, repo, branch, repoPath, targetName) {
1551
1980
  const skillsDir = ensureSkillsDir();
1552
- const targetDir = path4.join(skillsDir, targetName);
1553
- if (fs6.existsSync(targetDir)) {
1981
+ const targetDir = path5.join(skillsDir, targetName);
1982
+ if (fs7.existsSync(targetDir)) {
1554
1983
  console.log(chalk5.yellow(`Skill "${targetName}" already exists. Updating...`));
1555
- fs6.rmSync(targetDir, { recursive: true });
1984
+ fs7.rmSync(targetDir, { recursive: true });
1556
1985
  }
1557
1986
  const repoUrl = `https://github.com/${owner}/${repo}.git`;
1558
- const tempDir = path4.join(skillsDir, `.tmp-${Date.now()}`);
1987
+ const tempDir = path5.join(skillsDir, `.tmp-${Date.now()}`);
1559
1988
  try {
1560
- fs6.mkdirSync(tempDir, { recursive: true });
1989
+ fs7.mkdirSync(tempDir, { recursive: true });
1561
1990
  execSync(`git init`, { cwd: tempDir, stdio: "pipe" });
1562
1991
  execSync(`git remote add origin ${repoUrl}`, { cwd: tempDir, stdio: "pipe" });
1563
1992
  execSync(`git config core.sparseCheckout true`, { cwd: tempDir, stdio: "pipe" });
1564
- const sparseFile = path4.join(tempDir, ".git", "info", "sparse-checkout");
1565
- fs6.writeFileSync(sparseFile, repoPath ? `${repoPath}/
1993
+ const sparseFile = path5.join(tempDir, ".git", "info", "sparse-checkout");
1994
+ fs7.writeFileSync(sparseFile, repoPath ? `${repoPath}/
1566
1995
  ` : "*\n");
1567
1996
  execSync(`git pull --depth=1 origin ${branch}`, { cwd: tempDir, stdio: "pipe" });
1568
- const sourceDir = repoPath ? path4.join(tempDir, repoPath) : tempDir;
1569
- if (!fs6.existsSync(sourceDir)) {
1997
+ const sourceDir = repoPath ? path5.join(tempDir, repoPath) : tempDir;
1998
+ if (!fs7.existsSync(sourceDir)) {
1570
1999
  throw new Error(`Path "${repoPath}" not found in repository`);
1571
2000
  }
1572
2001
  copyDir(sourceDir, targetDir);
@@ -1577,22 +2006,22 @@ function downloadSkillWithGit(owner, repo, branch, repoPath, targetName) {
1577
2006
  console.error(chalk5.red(`Failed to download skill: ${errMsg}`));
1578
2007
  return false;
1579
2008
  } finally {
1580
- if (fs6.existsSync(tempDir)) {
1581
- fs6.rmSync(tempDir, { recursive: true });
2009
+ if (fs7.existsSync(tempDir)) {
2010
+ fs7.rmSync(tempDir, { recursive: true });
1582
2011
  }
1583
2012
  }
1584
2013
  }
1585
2014
  function copyDir(src, dest) {
1586
- fs6.mkdirSync(dest, { recursive: true });
1587
- const entries = fs6.readdirSync(src, { withFileTypes: true });
2015
+ fs7.mkdirSync(dest, { recursive: true });
2016
+ const entries = fs7.readdirSync(src, { withFileTypes: true });
1588
2017
  for (const entry of entries) {
1589
2018
  if (entry.name === ".git") continue;
1590
- const srcPath = path4.join(src, entry.name);
1591
- const destPath = path4.join(dest, entry.name);
2019
+ const srcPath = path5.join(src, entry.name);
2020
+ const destPath = path5.join(dest, entry.name);
1592
2021
  if (entry.isDirectory()) {
1593
2022
  copyDir(srcPath, destPath);
1594
2023
  } else {
1595
- fs6.copyFileSync(srcPath, destPath);
2024
+ fs7.copyFileSync(srcPath, destPath);
1596
2025
  }
1597
2026
  }
1598
2027
  }
@@ -1638,7 +2067,7 @@ function addSkillFromGitHub(urlOrPreset) {
1638
2067
  }
1639
2068
  let skillName;
1640
2069
  if (parsed.path) {
1641
- skillName = path4.basename(parsed.path);
2070
+ skillName = path5.basename(parsed.path);
1642
2071
  } else {
1643
2072
  skillName = parsed.repo;
1644
2073
  }
@@ -1652,23 +2081,23 @@ function addSkillFromGitHub(urlOrPreset) {
1652
2081
  }
1653
2082
  function listInstalledSkills() {
1654
2083
  const skillsDir = getSkillsDir();
1655
- if (!fs6.existsSync(skillsDir)) {
2084
+ if (!fs7.existsSync(skillsDir)) {
1656
2085
  return [];
1657
2086
  }
1658
- const entries = fs6.readdirSync(skillsDir, { withFileTypes: true });
2087
+ const entries = fs7.readdirSync(skillsDir, { withFileTypes: true });
1659
2088
  return entries.filter((entry) => entry.isDirectory() && !entry.name.startsWith(".")).map((entry) => ({
1660
2089
  name: entry.name,
1661
- path: path4.join(skillsDir, entry.name)
2090
+ path: path5.join(skillsDir, entry.name)
1662
2091
  }));
1663
2092
  }
1664
2093
  function removeSkill(name) {
1665
2094
  const skillsDir = getSkillsDir();
1666
- const targetDir = path4.join(skillsDir, name);
1667
- if (!fs6.existsSync(targetDir)) {
2095
+ const targetDir = path5.join(skillsDir, name);
2096
+ if (!fs7.existsSync(targetDir)) {
1668
2097
  console.error(chalk5.red(`Skill "${name}" not found`));
1669
2098
  return false;
1670
2099
  }
1671
- fs6.rmSync(targetDir, { recursive: true });
2100
+ fs7.rmSync(targetDir, { recursive: true });
1672
2101
  console.log(chalk5.green(`Removed skill "${name}"`));
1673
2102
  return true;
1674
2103
  }
@@ -1826,19 +2255,20 @@ async function runSkillSelector() {
1826
2255
  }
1827
2256
 
1828
2257
  // src/remote.ts
1829
- import crypto2 from "crypto";
2258
+ import crypto3 from "crypto";
1830
2259
  import chalk6 from "chalk";
1831
2260
  import Conf from "conf";
1832
- import { encrypt } from "@ccem/core";
1833
2261
  var config = new Conf({
1834
- projectName: "claude-code-env-manager"
2262
+ projectName: "claude-code-env-manager",
2263
+ cwd: getCcemConfigDir()
2264
+ // 使用统一的配置目录
1835
2265
  });
1836
2266
  var decryptWithSecret = (encryptedBase64, secret) => {
1837
- const key = crypto2.scryptSync(secret, "ccem-salt", 32);
2267
+ const key = crypto3.scryptSync(secret, "ccem-salt", 32);
1838
2268
  const combined = Buffer.from(encryptedBase64, "base64");
1839
2269
  const iv = combined.subarray(0, 16);
1840
2270
  const encryptedHex = combined.subarray(16).toString("hex");
1841
- const decipher = crypto2.createDecipheriv("aes-256-cbc", key, iv);
2271
+ const decipher = crypto3.createDecipheriv("aes-256-cbc", key, iv);
1842
2272
  let decrypted = decipher.update(encryptedHex, "hex", "utf8");
1843
2273
  decrypted += decipher.final("utf8");
1844
2274
  return decrypted;
@@ -1859,7 +2289,11 @@ var loadFromRemote = async (url, secret) => {
1859
2289
  console.log(chalk6.gray("Fetching from remote..."));
1860
2290
  let response;
1861
2291
  try {
1862
- response = await fetch(url);
2292
+ response = await fetch(url, {
2293
+ headers: {
2294
+ "X-CCEM-Key": secret
2295
+ }
2296
+ });
1863
2297
  } catch (err) {
1864
2298
  console.error(chalk6.red("Error: Failed to connect to server"));
1865
2299
  console.error(chalk6.gray(err.message));
@@ -1906,9 +2340,10 @@ var loadFromRemote = async (url, secret) => {
1906
2340
  for (const [name, envConfig] of Object.entries(decrypted.environments)) {
1907
2341
  const uniqueName = getUniqueName(name, existingNames);
1908
2342
  const renamed = uniqueName !== name;
1909
- const configToSave = { ...envConfig };
1910
- if (configToSave.ANTHROPIC_API_KEY) {
1911
- configToSave.ANTHROPIC_API_KEY = encrypt(configToSave.ANTHROPIC_API_KEY);
2343
+ const normalizedConfig = normalizeEnvConfig(envConfig);
2344
+ const configToSave = { ...normalizedConfig };
2345
+ if (configToSave.ANTHROPIC_AUTH_TOKEN) {
2346
+ configToSave.ANTHROPIC_AUTH_TOKEN = encrypt(configToSave.ANTHROPIC_AUTH_TOKEN);
1912
2347
  }
1913
2348
  registries[uniqueName] = configToSave;
1914
2349
  existingNames.add(uniqueName);
@@ -1929,6 +2364,7 @@ Loaded ${results.length} environment(s) from remote:`));
1929
2364
  }
1930
2365
  }
1931
2366
  console.log(chalk6.gray("\nRun 'ccem ls' to see all environments."));
2367
+ return results;
1932
2368
  };
1933
2369
 
1934
2370
  // src/cron-skill.ts
@@ -2074,28 +2510,182 @@ Replace \\\`TARGET_ID\\\` or \\\`TARGET_NAME\\\` with the user's selection.
2074
2510
  `;
2075
2511
 
2076
2512
  // src/index.ts
2077
- var __filename = fileURLToPath(import.meta.url);
2078
- var __dirname2 = path5.dirname(__filename);
2079
- var pkgPath = path5.resolve(__dirname2, "..", "package.json");
2080
- var pkg = JSON.parse(fs7.readFileSync(pkgPath, "utf-8"));
2513
+ var __filename2 = fileURLToPath2(import.meta.url);
2514
+ var __dirname2 = path6.dirname(__filename2);
2515
+ var pkgPath = path6.resolve(__dirname2, "..", "package.json");
2516
+ var pkg = JSON.parse(fs8.readFileSync(pkgPath, "utf-8"));
2081
2517
  var program = new Command();
2082
- ensureCcemDir3();
2518
+ var DEFAULT_OFFICIAL_ENV = {
2519
+ ANTHROPIC_BASE_URL: "https://api.anthropic.com",
2520
+ ANTHROPIC_DEFAULT_OPUS_MODEL: "claude-opus-4-1-20250805",
2521
+ ANTHROPIC_DEFAULT_SONNET_MODEL: "claude-opus-4-1-20250805",
2522
+ ANTHROPIC_DEFAULT_HAIKU_MODEL: "claude-3-5-haiku-20241022",
2523
+ ANTHROPIC_MODEL: "opus"
2524
+ };
2525
+ var MANAGED_CLAUDE_ENV_KEYS2 = [
2526
+ "ANTHROPIC_BASE_URL",
2527
+ "ANTHROPIC_AUTH_TOKEN",
2528
+ "ANTHROPIC_DEFAULT_OPUS_MODEL",
2529
+ "ANTHROPIC_DEFAULT_SONNET_MODEL",
2530
+ "ANTHROPIC_DEFAULT_HAIKU_MODEL",
2531
+ "ANTHROPIC_MODEL",
2532
+ "CLAUDE_CODE_SUBAGENT_MODEL",
2533
+ "ANTHROPIC_API_KEY",
2534
+ "ANTHROPIC_SMALL_FAST_MODEL"
2535
+ ];
2536
+ var shellQuote = (value) => `'${value.replace(/'/g, `'\\''`)}'`;
2537
+ var clearManagedClaudeEnv = (env) => {
2538
+ for (const key of MANAGED_CLAUDE_ENV_KEYS2) {
2539
+ delete env[key];
2540
+ }
2541
+ };
2542
+ var buildResolvedEnvVars = (env) => {
2543
+ const resolved = {};
2544
+ if (env.ANTHROPIC_BASE_URL) resolved.ANTHROPIC_BASE_URL = env.ANTHROPIC_BASE_URL;
2545
+ if (env.ANTHROPIC_AUTH_TOKEN) resolved.ANTHROPIC_AUTH_TOKEN = decrypt(env.ANTHROPIC_AUTH_TOKEN);
2546
+ if (env.ANTHROPIC_DEFAULT_OPUS_MODEL) resolved.ANTHROPIC_DEFAULT_OPUS_MODEL = env.ANTHROPIC_DEFAULT_OPUS_MODEL;
2547
+ if (env.ANTHROPIC_DEFAULT_SONNET_MODEL) resolved.ANTHROPIC_DEFAULT_SONNET_MODEL = env.ANTHROPIC_DEFAULT_SONNET_MODEL;
2548
+ if (env.ANTHROPIC_DEFAULT_HAIKU_MODEL) resolved.ANTHROPIC_DEFAULT_HAIKU_MODEL = env.ANTHROPIC_DEFAULT_HAIKU_MODEL;
2549
+ if (env.ANTHROPIC_MODEL) resolved.ANTHROPIC_MODEL = env.ANTHROPIC_MODEL;
2550
+ if (env.CLAUDE_CODE_SUBAGENT_MODEL) resolved.CLAUDE_CODE_SUBAGENT_MODEL = env.CLAUDE_CODE_SUBAGENT_MODEL;
2551
+ return resolved;
2552
+ };
2553
+ var buildShellEnvCommands = (env) => {
2554
+ const resolved = buildResolvedEnvVars(env);
2555
+ return MANAGED_CLAUDE_ENV_KEYS2.map(
2556
+ (key) => resolved[key] ? `export ${key}=${shellQuote(resolved[key])}` : `unset ${key}`
2557
+ );
2558
+ };
2559
+ ensureCcemDir();
2083
2560
  var config2 = new Conf2({
2084
2561
  projectName: "claude-code-env-manager",
2085
2562
  cwd: getCcemConfigDir(),
2086
2563
  // 使用新路径
2087
2564
  defaults: {
2088
2565
  registries: {
2089
- "official": {
2090
- ANTHROPIC_BASE_URL: "https://api.anthropic.com",
2091
- ANTHROPIC_MODEL: "claude-sonnet-4-5-20250929",
2092
- ANTHROPIC_SMALL_FAST_MODEL: "claude-haiku-4-5-20251001"
2093
- }
2566
+ official: DEFAULT_OFFICIAL_ENV
2094
2567
  },
2095
2568
  current: "official",
2096
2569
  defaultMode: null
2097
2570
  }
2098
2571
  });
2572
+ var recoverRegistriesFromLegacy = (registries) => {
2573
+ const currentAuthCount = Object.values(registries).filter(
2574
+ (env) => Boolean(env.ANTHROPIC_AUTH_TOKEN)
2575
+ ).length;
2576
+ if (currentAuthCount > 0) {
2577
+ return registries;
2578
+ }
2579
+ const legacyConfigPath = getLegacyConfigPath();
2580
+ if (!fs8.existsSync(legacyConfigPath)) {
2581
+ return registries;
2582
+ }
2583
+ try {
2584
+ const legacyRaw = JSON.parse(fs8.readFileSync(legacyConfigPath, "utf-8"));
2585
+ const legacyRegistries = legacyRaw.registries ?? {};
2586
+ let changed = false;
2587
+ const recovered = { ...registries };
2588
+ for (const [name, envConfig] of Object.entries(registries)) {
2589
+ const legacyEnvConfig = legacyRegistries[name];
2590
+ if (!legacyEnvConfig) {
2591
+ continue;
2592
+ }
2593
+ const recoveredEnv = recoverEnvConfigFromLegacy(envConfig, legacyEnvConfig);
2594
+ if (JSON.stringify(recoveredEnv) !== JSON.stringify(envConfig)) {
2595
+ recovered[name] = recoveredEnv;
2596
+ changed = true;
2597
+ }
2598
+ }
2599
+ return changed ? recovered : registries;
2600
+ } catch {
2601
+ return registries;
2602
+ }
2603
+ };
2604
+ var getRegistries = () => {
2605
+ const rawRegistries = config2.get("registries") ?? {};
2606
+ const normalizedEntries = Object.entries(rawRegistries).map(([name, envConfig]) => [
2607
+ name,
2608
+ normalizeEnvConfig(envConfig ?? {})
2609
+ ]);
2610
+ const normalizedRegistries = Object.fromEntries(normalizedEntries);
2611
+ const repairedRegistries = recoverRegistriesFromLegacy(normalizedRegistries);
2612
+ if (!repairedRegistries.official) {
2613
+ repairedRegistries.official = { ...DEFAULT_OFFICIAL_ENV };
2614
+ }
2615
+ const changed = Object.keys(rawRegistries).length !== Object.keys(repairedRegistries).length || JSON.stringify(rawRegistries) !== JSON.stringify(repairedRegistries);
2616
+ if (changed) {
2617
+ config2.set("registries", repairedRegistries);
2618
+ }
2619
+ return repairedRegistries;
2620
+ };
2621
+ var setRegistries = (registries) => {
2622
+ config2.set("registries", registries);
2623
+ };
2624
+ var getDecryptedAuthToken = (envConfig) => {
2625
+ return envConfig.ANTHROPIC_AUTH_TOKEN ? decrypt(envConfig.ANTHROPIC_AUTH_TOKEN) : void 0;
2626
+ };
2627
+ var applyPromptAnswers = (current, answers, keepCurrentSecret) => {
2628
+ const next = {
2629
+ ...current,
2630
+ ANTHROPIC_BASE_URL: answers.ANTHROPIC_BASE_URL?.trim() || current.ANTHROPIC_BASE_URL,
2631
+ ANTHROPIC_DEFAULT_OPUS_MODEL: answers.ANTHROPIC_DEFAULT_OPUS_MODEL?.trim() || current.ANTHROPIC_DEFAULT_OPUS_MODEL,
2632
+ ANTHROPIC_DEFAULT_HAIKU_MODEL: answers.ANTHROPIC_DEFAULT_HAIKU_MODEL?.trim() || current.ANTHROPIC_DEFAULT_HAIKU_MODEL,
2633
+ 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,
2634
+ ANTHROPIC_MODEL: answers.ANTHROPIC_MODEL?.trim() || current.ANTHROPIC_MODEL || "opus",
2635
+ CLAUDE_CODE_SUBAGENT_MODEL: answers.CLAUDE_CODE_SUBAGENT_MODEL?.trim() || current.CLAUDE_CODE_SUBAGENT_MODEL
2636
+ };
2637
+ if (!keepCurrentSecret) {
2638
+ next.ANTHROPIC_AUTH_TOKEN = answers.ANTHROPIC_AUTH_TOKEN ? encrypt(answers.ANTHROPIC_AUTH_TOKEN) : void 0;
2639
+ } else if (answers.ANTHROPIC_AUTH_TOKEN) {
2640
+ next.ANTHROPIC_AUTH_TOKEN = encrypt(answers.ANTHROPIC_AUTH_TOKEN);
2641
+ }
2642
+ return normalizeEnvConfig(next);
2643
+ };
2644
+ var promptForEnvironmentConfig = async (current = {}, keepCurrentSecret = false) => {
2645
+ return inquirer.prompt([
2646
+ {
2647
+ type: "input",
2648
+ name: "ANTHROPIC_BASE_URL",
2649
+ message: "ANTHROPIC_BASE_URL:",
2650
+ default: current.ANTHROPIC_BASE_URL || DEFAULT_OFFICIAL_ENV.ANTHROPIC_BASE_URL
2651
+ },
2652
+ {
2653
+ type: "password",
2654
+ name: "ANTHROPIC_AUTH_TOKEN",
2655
+ message: keepCurrentSecret ? "ANTHROPIC_AUTH_TOKEN (leave empty to keep current):" : "ANTHROPIC_AUTH_TOKEN:"
2656
+ },
2657
+ {
2658
+ type: "input",
2659
+ name: "ANTHROPIC_DEFAULT_OPUS_MODEL",
2660
+ message: "ANTHROPIC_DEFAULT_OPUS_MODEL:",
2661
+ default: current.ANTHROPIC_DEFAULT_OPUS_MODEL || DEFAULT_OFFICIAL_ENV.ANTHROPIC_DEFAULT_OPUS_MODEL
2662
+ },
2663
+ {
2664
+ type: "input",
2665
+ name: "ANTHROPIC_DEFAULT_HAIKU_MODEL",
2666
+ message: "ANTHROPIC_DEFAULT_HAIKU_MODEL:",
2667
+ default: current.ANTHROPIC_DEFAULT_HAIKU_MODEL || DEFAULT_OFFICIAL_ENV.ANTHROPIC_DEFAULT_HAIKU_MODEL
2668
+ },
2669
+ {
2670
+ type: "input",
2671
+ name: "ANTHROPIC_DEFAULT_SONNET_MODEL",
2672
+ message: "ANTHROPIC_DEFAULT_SONNET_MODEL (blank = same as opus):",
2673
+ default: current.ANTHROPIC_DEFAULT_SONNET_MODEL || current.ANTHROPIC_DEFAULT_OPUS_MODEL || ""
2674
+ },
2675
+ {
2676
+ type: "input",
2677
+ name: "ANTHROPIC_MODEL",
2678
+ message: "ANTHROPIC_MODEL (e.g. opus, opusplan, sonnet):",
2679
+ default: current.ANTHROPIC_MODEL || "opus"
2680
+ },
2681
+ {
2682
+ type: "input",
2683
+ name: "CLAUDE_CODE_SUBAGENT_MODEL",
2684
+ message: "CLAUDE_CODE_SUBAGENT_MODEL (optional):",
2685
+ default: current.CLAUDE_CODE_SUBAGENT_MODEL || ""
2686
+ }
2687
+ ]);
2688
+ };
2099
2689
  var PERMISSION_MODES = ["yolo", "dev", "readonly", "safe", "ci", "audit"];
2100
2690
  var usageStats = null;
2101
2691
  var usageLoading = true;
@@ -2132,9 +2722,9 @@ var initUsageStats = (onUpdate) => {
2132
2722
  };
2133
2723
  program.name("ccem").description("Claude Code Environment Manager - \u7BA1\u7406 Claude Code \u73AF\u5883\u53D8\u91CF\u548C\u6743\u9650").version(pkg.version).option("--mode", "\u67E5\u770B\u5F53\u524D\u6743\u9650\u6A21\u5F0F").option("--list-modes", "\u5217\u51FA\u6240\u6709\u53EF\u7528\u6743\u9650\u6A21\u5F0F");
2134
2724
  PERMISSION_MODES.forEach((mode) => {
2135
- const preset = PERMISSION_PRESETS4[mode];
2725
+ const preset = PERMISSION_PRESETS[mode];
2136
2726
  program.command(mode).description(`\u4E34\u65F6\u5E94\u7528 ${preset.name}\uFF0C\u9000\u51FA\u540E\u8FD8\u539F`).action(async () => {
2137
- const registries = config2.get("registries");
2727
+ const registries = getRegistries();
2138
2728
  const current = config2.get("current");
2139
2729
  const envConfig = registries[current];
2140
2730
  await runWithTempPermissions(mode, envConfig);
@@ -2143,15 +2733,18 @@ PERMISSION_MODES.forEach((mode) => {
2143
2733
  var showCurrentEnv = (usageStats2, usageLoading2) => {
2144
2734
  if (!process.stdout.isTTY) return;
2145
2735
  const current = config2.get("current");
2146
- const registries = config2.get("registries");
2736
+ const registries = getRegistries();
2147
2737
  const env = registries[current];
2148
2738
  const defaultMode = config2.get("defaultMode");
2149
2739
  if (!env) return;
2150
2740
  console.log(renderLogoWithEnvPanel(current, {
2151
2741
  ANTHROPIC_BASE_URL: env.ANTHROPIC_BASE_URL,
2152
- ANTHROPIC_API_KEY: env.ANTHROPIC_API_KEY ? decrypt2(env.ANTHROPIC_API_KEY) : void 0,
2742
+ ANTHROPIC_AUTH_TOKEN: getDecryptedAuthToken(env),
2743
+ ANTHROPIC_DEFAULT_OPUS_MODEL: env.ANTHROPIC_DEFAULT_OPUS_MODEL,
2744
+ ANTHROPIC_DEFAULT_SONNET_MODEL: env.ANTHROPIC_DEFAULT_SONNET_MODEL,
2745
+ ANTHROPIC_DEFAULT_HAIKU_MODEL: env.ANTHROPIC_DEFAULT_HAIKU_MODEL,
2153
2746
  ANTHROPIC_MODEL: env.ANTHROPIC_MODEL,
2154
- ANTHROPIC_SMALL_FAST_MODEL: env.ANTHROPIC_SMALL_FAST_MODEL
2747
+ CLAUDE_CODE_SUBAGENT_MODEL: env.CLAUDE_CODE_SUBAGENT_MODEL
2155
2748
  }, defaultMode));
2156
2749
  console.log("");
2157
2750
  console.log(renderUsageLine(usageStats2, usageLoading2));
@@ -2159,7 +2752,7 @@ var showCurrentEnv = (usageStats2, usageLoading2) => {
2159
2752
  console.log("");
2160
2753
  };
2161
2754
  var switchEnvironment = async (name) => {
2162
- const registries = config2.get("registries");
2755
+ const registries = getRegistries();
2163
2756
  if (!registries[name]) {
2164
2757
  console.error(chalk7.red(`Environment '${name}' not found.`));
2165
2758
  return;
@@ -2172,11 +2765,7 @@ var switchEnvironment = async (name) => {
2172
2765
  }
2173
2766
  showCurrentEnv(null, false);
2174
2767
  const env = registries[name];
2175
- const exportCmds = [];
2176
- if (env.ANTHROPIC_BASE_URL) exportCmds.push(`export ANTHROPIC_BASE_URL="${env.ANTHROPIC_BASE_URL}"`);
2177
- if (env.ANTHROPIC_API_KEY) exportCmds.push(`export ANTHROPIC_API_KEY="${decrypt2(env.ANTHROPIC_API_KEY)}"`);
2178
- if (env.ANTHROPIC_MODEL) exportCmds.push(`export ANTHROPIC_MODEL="${env.ANTHROPIC_MODEL}"`);
2179
- if (env.ANTHROPIC_SMALL_FAST_MODEL) exportCmds.push(`export ANTHROPIC_SMALL_FAST_MODEL="${env.ANTHROPIC_SMALL_FAST_MODEL}"`);
2768
+ const exportCmds = buildShellEnvCommands(env);
2180
2769
  if (process.stdout.isTTY) {
2181
2770
  console.log(chalk7.yellow("\nTo apply to current shell immediately, run:"));
2182
2771
  console.log(chalk7.cyan("eval $(ccem env)"));
@@ -2186,11 +2775,92 @@ var switchEnvironment = async (name) => {
2186
2775
  exportCmds.forEach((cmd) => console.log(cmd));
2187
2776
  }
2188
2777
  };
2778
+ var getSessionsFilePath = () => path6.join(getCcemConfigDir(), "sessions.json");
2779
+ var getRuntimeStateFilePath = () => path6.join(getCcemConfigDir(), "runtime-state.json");
2780
+ var parseJsonFile = (filePath) => {
2781
+ if (!fs8.existsSync(filePath)) {
2782
+ return null;
2783
+ }
2784
+ try {
2785
+ return JSON.parse(fs8.readFileSync(filePath, "utf-8"));
2786
+ } catch {
2787
+ return null;
2788
+ }
2789
+ };
2790
+ var readInteractiveAttachSessions = () => {
2791
+ const sessionsById = /* @__PURE__ */ new Map();
2792
+ const runtimeState = parseJsonFile(getRuntimeStateFilePath());
2793
+ const persistedSessions = parseJsonFile(getSessionsFilePath()) ?? [];
2794
+ for (const entry of runtimeState?.sessions ?? []) {
2795
+ if (entry.runtime_kind && entry.runtime_kind !== "interactive") {
2796
+ continue;
2797
+ }
2798
+ const fallback = persistedSessions.find((session) => session.id === entry.runtime_id);
2799
+ const tmuxTarget = entry.tmux_session && entry.tmux_window ? `${entry.tmux_session}:${entry.tmux_window}` : fallback?.window_id ?? null;
2800
+ if (!tmuxTarget) {
2801
+ continue;
2802
+ }
2803
+ sessionsById.set(entry.runtime_id, {
2804
+ id: entry.runtime_id,
2805
+ projectDir: entry.project_dir,
2806
+ envName: entry.env_name,
2807
+ permMode: entry.perm_mode,
2808
+ status: fallback?.status ?? "running",
2809
+ tmuxTarget,
2810
+ sortTime: entry.saved_at
2811
+ });
2812
+ }
2813
+ for (const session of persistedSessions) {
2814
+ if (session.status !== "running" || session.terminal_type !== "embedded" || !session.window_id) {
2815
+ continue;
2816
+ }
2817
+ if (sessionsById.has(session.id)) {
2818
+ continue;
2819
+ }
2820
+ sessionsById.set(session.id, {
2821
+ id: session.id,
2822
+ projectDir: session.working_dir,
2823
+ envName: session.env_name,
2824
+ permMode: session.perm_mode,
2825
+ status: session.status,
2826
+ tmuxTarget: session.window_id,
2827
+ sortTime: session.start_time
2828
+ });
2829
+ }
2830
+ return [...sessionsById.values()].sort(
2831
+ (left, right) => right.sortTime.localeCompare(left.sortTime)
2832
+ );
2833
+ };
2834
+ var findAttachSession = (id) => {
2835
+ const sessions = readInteractiveAttachSessions();
2836
+ if (!id) {
2837
+ return sessions[0];
2838
+ }
2839
+ const exact = sessions.find((session) => session.id === id);
2840
+ if (exact) {
2841
+ return exact;
2842
+ }
2843
+ return sessions.find((session) => session.id.startsWith(id));
2844
+ };
2845
+ var attachTmuxTarget = (target) => {
2846
+ const args = process.env.TMUX ? ["switch-client", "-t", target] : ["attach-session", "-t", target];
2847
+ return new Promise((resolve2, reject) => {
2848
+ const child = spawn3("tmux", args, { stdio: "inherit" });
2849
+ child.on("error", reject);
2850
+ child.on("exit", (code) => {
2851
+ if (code === 0 || code === null) {
2852
+ resolve2();
2853
+ } else {
2854
+ reject(new Error(`tmux exited with code ${code}`));
2855
+ }
2856
+ });
2857
+ });
2858
+ };
2189
2859
  program.command("ls").description("List all configured environments").action(() => {
2190
- const registries = config2.get("registries");
2860
+ const registries = getRegistries();
2191
2861
  const current = config2.get("current");
2192
2862
  const table = new Table3({
2193
- head: ["Name", "Base URL", "Model"],
2863
+ head: ["Name", "Base URL", "Opus"],
2194
2864
  style: { head: ["cyan"] }
2195
2865
  });
2196
2866
  Object.keys(registries).forEach((name) => {
@@ -2199,7 +2869,7 @@ program.command("ls").description("List all configured environments").action(()
2199
2869
  table.push([
2200
2870
  prefix + name,
2201
2871
  reg.ANTHROPIC_BASE_URL || "-",
2202
- reg.ANTHROPIC_MODEL || "-"
2872
+ reg.ANTHROPIC_DEFAULT_OPUS_MODEL || "-"
2203
2873
  ]);
2204
2874
  });
2205
2875
  console.log(table.toString());
@@ -2207,8 +2877,42 @@ program.command("ls").description("List all configured environments").action(()
2207
2877
  program.command("use <name>").description("Switch to a specific environment").action(async (name) => {
2208
2878
  await switchEnvironment(name);
2209
2879
  });
2880
+ program.command("sessions").description("List tmux-backed interactive sessions").action(() => {
2881
+ const sessions = readInteractiveAttachSessions();
2882
+ if (sessions.length === 0) {
2883
+ console.log(chalk7.yellow("No tmux-backed interactive sessions found."));
2884
+ return;
2885
+ }
2886
+ const table = new Table3({
2887
+ head: ["ID", "Project", "Env", "Status", "Tmux"],
2888
+ style: { head: ["cyan"] }
2889
+ });
2890
+ sessions.forEach((session) => {
2891
+ table.push([
2892
+ session.id,
2893
+ session.projectDir,
2894
+ session.envName,
2895
+ session.status,
2896
+ session.tmuxTarget
2897
+ ]);
2898
+ });
2899
+ console.log(table.toString());
2900
+ });
2901
+ program.command("attach [id]").description("Attach to a tmux-backed interactive session").action(async (id) => {
2902
+ const session = findAttachSession(id);
2903
+ if (!session) {
2904
+ console.error(chalk7.red(id ? `Interactive session '${id}' not found.` : "No interactive session available to attach."));
2905
+ process.exit(1);
2906
+ }
2907
+ try {
2908
+ await attachTmuxTarget(session.tmuxTarget);
2909
+ } catch (error) {
2910
+ console.error(chalk7.red(`Failed to attach ${session.tmuxTarget}: ${String(error)}`));
2911
+ process.exit(1);
2912
+ }
2913
+ });
2210
2914
  program.command("add <name>").description("Add a new environment configuration").action(async (name) => {
2211
- const registries = config2.get("registries");
2915
+ const registries = getRegistries();
2212
2916
  if (registries[name]) {
2213
2917
  console.log(chalk7.red(`Environment '${name}' already exists.`));
2214
2918
  return;
@@ -2233,40 +2937,13 @@ program.command("add <name>").description("Add a new environment configuration")
2233
2937
  ]);
2234
2938
  presetConfig = ENV_PRESETS[presetName];
2235
2939
  }
2236
- const answers = await inquirer.prompt([
2237
- {
2238
- type: "input",
2239
- name: "ANTHROPIC_BASE_URL",
2240
- message: "Enter ANTHROPIC_BASE_URL:",
2241
- default: presetConfig.ANTHROPIC_BASE_URL || "https://api.anthropic.com"
2242
- },
2243
- {
2244
- type: "password",
2245
- name: "ANTHROPIC_API_KEY",
2246
- message: "Enter ANTHROPIC_API_KEY:"
2247
- },
2248
- {
2249
- type: "input",
2250
- name: "ANTHROPIC_MODEL",
2251
- message: "Enter ANTHROPIC_MODEL:",
2252
- default: presetConfig.ANTHROPIC_MODEL || "claude-sonnet-4-5-20250929"
2253
- },
2254
- {
2255
- type: "input",
2256
- name: "ANTHROPIC_SMALL_FAST_MODEL",
2257
- message: "Enter ANTHROPIC_SMALL_FAST_MODEL:",
2258
- default: presetConfig.ANTHROPIC_SMALL_FAST_MODEL || "claude-haiku-4-5-20251001"
2259
- }
2260
- ]);
2261
- if (answers.ANTHROPIC_API_KEY) {
2262
- answers.ANTHROPIC_API_KEY = encrypt2(answers.ANTHROPIC_API_KEY);
2263
- }
2264
- registries[name] = answers;
2265
- config2.set("registries", registries);
2940
+ const answers = await promptForEnvironmentConfig(presetConfig);
2941
+ registries[name] = applyPromptAnswers(normalizeEnvConfig(presetConfig), answers, false);
2942
+ setRegistries(registries);
2266
2943
  console.log(chalk7.green(`Environment '${name}' added successfully.`));
2267
2944
  });
2268
2945
  program.command("del <name>").description("Delete an environment configuration").action((name) => {
2269
- const registries = config2.get("registries");
2946
+ const registries = getRegistries();
2270
2947
  if (!registries[name]) {
2271
2948
  console.log(chalk7.red(`Environment '${name}' not found.`));
2272
2949
  return;
@@ -2276,7 +2953,7 @@ program.command("del <name>").description("Delete an environment configuration")
2276
2953
  return;
2277
2954
  }
2278
2955
  delete registries[name];
2279
- config2.set("registries", registries);
2956
+ setRegistries(registries);
2280
2957
  const current = config2.get("current");
2281
2958
  if (current === name) {
2282
2959
  config2.set("current", "official");
@@ -2285,7 +2962,7 @@ program.command("del <name>").description("Delete an environment configuration")
2285
2962
  console.log(chalk7.green(`Environment '${name}' deleted.`));
2286
2963
  });
2287
2964
  program.command("rename <old> <new>").description("Rename an environment configuration").action((oldName, newName) => {
2288
- const registries = config2.get("registries");
2965
+ const registries = getRegistries();
2289
2966
  if (!registries[oldName]) {
2290
2967
  console.log(chalk7.red(`Environment '${oldName}' not found.`));
2291
2968
  return;
@@ -2300,7 +2977,7 @@ program.command("rename <old> <new>").description("Rename an environment configu
2300
2977
  }
2301
2978
  registries[newName] = registries[oldName];
2302
2979
  delete registries[oldName];
2303
- config2.set("registries", registries);
2980
+ setRegistries(registries);
2304
2981
  const current = config2.get("current");
2305
2982
  if (current === oldName) {
2306
2983
  config2.set("current", newName);
@@ -2308,7 +2985,7 @@ program.command("rename <old> <new>").description("Rename an environment configu
2308
2985
  console.log(chalk7.green(`Environment '${oldName}' renamed to '${newName}'.`));
2309
2986
  });
2310
2987
  program.command("cp <source> <target>").description("Copy an environment configuration").action(async (source, target) => {
2311
- const registries = config2.get("registries");
2988
+ const registries = getRegistries();
2312
2989
  if (!registries[source]) {
2313
2990
  console.log(chalk7.red(`Environment '${source}' not found.`));
2314
2991
  return;
@@ -2318,7 +2995,7 @@ program.command("cp <source> <target>").description("Copy an environment configu
2318
2995
  return;
2319
2996
  }
2320
2997
  registries[target] = { ...registries[source] };
2321
- config2.set("registries", registries);
2998
+ setRegistries(registries);
2322
2999
  console.log(chalk7.green(`Environment '${source}' copied to '${target}'.`));
2323
3000
  const { modify } = await inquirer.prompt([
2324
3001
  {
@@ -2330,37 +3007,9 @@ program.command("cp <source> <target>").description("Copy an environment configu
2330
3007
  ]);
2331
3008
  if (modify) {
2332
3009
  const current = registries[target];
2333
- const answers = await inquirer.prompt([
2334
- {
2335
- type: "input",
2336
- name: "ANTHROPIC_BASE_URL",
2337
- message: "ANTHROPIC_BASE_URL:",
2338
- default: current.ANTHROPIC_BASE_URL
2339
- },
2340
- {
2341
- type: "password",
2342
- name: "ANTHROPIC_API_KEY",
2343
- message: "ANTHROPIC_API_KEY (leave empty to keep current):"
2344
- },
2345
- {
2346
- type: "input",
2347
- name: "ANTHROPIC_MODEL",
2348
- message: "ANTHROPIC_MODEL:",
2349
- default: current.ANTHROPIC_MODEL
2350
- },
2351
- {
2352
- type: "input",
2353
- name: "ANTHROPIC_SMALL_FAST_MODEL",
2354
- message: "ANTHROPIC_SMALL_FAST_MODEL:",
2355
- default: current.ANTHROPIC_SMALL_FAST_MODEL
2356
- }
2357
- ]);
2358
- if (answers.ANTHROPIC_BASE_URL) current.ANTHROPIC_BASE_URL = answers.ANTHROPIC_BASE_URL;
2359
- if (answers.ANTHROPIC_API_KEY) current.ANTHROPIC_API_KEY = encrypt2(answers.ANTHROPIC_API_KEY);
2360
- if (answers.ANTHROPIC_MODEL) current.ANTHROPIC_MODEL = answers.ANTHROPIC_MODEL;
2361
- if (answers.ANTHROPIC_SMALL_FAST_MODEL) current.ANTHROPIC_SMALL_FAST_MODEL = answers.ANTHROPIC_SMALL_FAST_MODEL;
2362
- registries[target] = current;
2363
- config2.set("registries", registries);
3010
+ const answers = await promptForEnvironmentConfig(current, true);
3011
+ registries[target] = applyPromptAnswers(current, answers, true);
3012
+ setRegistries(registries);
2364
3013
  console.log(chalk7.green(`Environment '${target}' updated.`));
2365
3014
  }
2366
3015
  });
@@ -2369,25 +3018,22 @@ program.command("current").description("Show current environment name").action((
2369
3018
  console.log(chalk7.green(current));
2370
3019
  });
2371
3020
  program.command("env").description("Output environment variables for shell eval").option("--json", "Output as JSON").action((options) => {
2372
- const registries = config2.get("registries");
3021
+ const registries = getRegistries();
2373
3022
  const current = config2.get("current");
2374
3023
  const env = registries[current];
2375
3024
  if (!env) return;
2376
3025
  const outputEnv = { ...env };
2377
- if (outputEnv.ANTHROPIC_API_KEY) {
2378
- outputEnv.ANTHROPIC_API_KEY = decrypt2(outputEnv.ANTHROPIC_API_KEY);
3026
+ if (outputEnv.ANTHROPIC_AUTH_TOKEN) {
3027
+ outputEnv.ANTHROPIC_AUTH_TOKEN = decrypt(outputEnv.ANTHROPIC_AUTH_TOKEN);
2379
3028
  }
2380
3029
  if (options.json) {
2381
3030
  console.log(JSON.stringify(outputEnv, null, 2));
2382
3031
  } else {
2383
- if (outputEnv.ANTHROPIC_BASE_URL) console.log(`export ANTHROPIC_BASE_URL="${outputEnv.ANTHROPIC_BASE_URL}"`);
2384
- if (outputEnv.ANTHROPIC_API_KEY) console.log(`export ANTHROPIC_API_KEY="${outputEnv.ANTHROPIC_API_KEY}"`);
2385
- if (outputEnv.ANTHROPIC_MODEL) console.log(`export ANTHROPIC_MODEL="${outputEnv.ANTHROPIC_MODEL}"`);
2386
- if (outputEnv.ANTHROPIC_SMALL_FAST_MODEL) console.log(`export ANTHROPIC_SMALL_FAST_MODEL="${outputEnv.ANTHROPIC_SMALL_FAST_MODEL}"`);
3032
+ buildShellEnvCommands(env).forEach((cmd) => console.log(cmd));
2387
3033
  }
2388
3034
  });
2389
3035
  program.command("run <command...>").description("Run a command with the current environment variables").action((command) => {
2390
- const registries = config2.get("registries");
3036
+ const registries = getRegistries();
2391
3037
  const current = config2.get("current");
2392
3038
  const envConfig = registries[current];
2393
3039
  if (!envConfig) {
@@ -2395,10 +3041,8 @@ program.command("run <command...>").description("Run a command with the current
2395
3041
  process.exit(1);
2396
3042
  }
2397
3043
  const env = { ...process.env };
2398
- if (envConfig.ANTHROPIC_BASE_URL) env.ANTHROPIC_BASE_URL = envConfig.ANTHROPIC_BASE_URL;
2399
- if (envConfig.ANTHROPIC_API_KEY) env.ANTHROPIC_API_KEY = decrypt2(envConfig.ANTHROPIC_API_KEY || "");
2400
- if (envConfig.ANTHROPIC_MODEL) env.ANTHROPIC_MODEL = envConfig.ANTHROPIC_MODEL;
2401
- if (envConfig.ANTHROPIC_SMALL_FAST_MODEL) env.ANTHROPIC_SMALL_FAST_MODEL = envConfig.ANTHROPIC_SMALL_FAST_MODEL;
3044
+ clearManagedClaudeEnv(env);
3045
+ Object.assign(env, buildResolvedEnvVars(envConfig));
2402
3046
  const [cmd, ...args] = command;
2403
3047
  const child = spawn3(cmd, args, {
2404
3048
  env,
@@ -2436,14 +3080,14 @@ setupCmd.command("default-mode").description("\u8BBE\u7F6E\u9ED8\u8BA4\u6743\u96
2436
3080
  for (const mode of PERMISSION_MODES) {
2437
3081
  if (options[mode]) {
2438
3082
  config2.set("defaultMode", mode);
2439
- console.log(chalk7.green(`\u5DF2\u8BBE\u7F6E\u9ED8\u8BA4\u6743\u9650\u6A21\u5F0F: ${PERMISSION_PRESETS4[mode].name}`));
3083
+ console.log(chalk7.green(`\u5DF2\u8BBE\u7F6E\u9ED8\u8BA4\u6743\u9650\u6A21\u5F0F: ${PERMISSION_PRESETS[mode].name}`));
2440
3084
  console.log(chalk7.gray(`\u4E0B\u6B21\u542F\u52A8 ccem \u65F6\u5C06\u9ED8\u8BA4\u4F7F\u7528\u6B64\u6A21\u5F0F`));
2441
3085
  return;
2442
3086
  }
2443
3087
  }
2444
3088
  const currentDefault = config2.get("defaultMode");
2445
- if (currentDefault && PERMISSION_PRESETS4[currentDefault]) {
2446
- console.log(chalk7.green(`\u5F53\u524D\u9ED8\u8BA4\u6A21\u5F0F: ${PERMISSION_PRESETS4[currentDefault].name}`));
3089
+ if (currentDefault && PERMISSION_PRESETS[currentDefault]) {
3090
+ console.log(chalk7.green(`\u5F53\u524D\u9ED8\u8BA4\u6A21\u5F0F: ${PERMISSION_PRESETS[currentDefault].name}`));
2447
3091
  } else {
2448
3092
  console.log(chalk7.yellow("\u672A\u8BBE\u7F6E\u9ED8\u8BA4\u6743\u9650\u6A21\u5F0F"));
2449
3093
  }
@@ -2451,36 +3095,37 @@ setupCmd.command("default-mode").description("\u8BBE\u7F6E\u9ED8\u8BA4\u6743\u96
2451
3095
  console.log(chalk7.gray("\u6E05\u9664\u9ED8\u8BA4\u6A21\u5F0F: ccem setup default-mode --reset"));
2452
3096
  console.log(chalk7.gray("\u53EF\u7528\u6A21\u5F0F: " + PERMISSION_MODES.join(", ")));
2453
3097
  });
2454
- 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 () => {
2455
- await runSetupInit();
3098
+ 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() {
3099
+ const options = this.opts();
3100
+ await runSetupInit({ chrome: !!options.chrome });
2456
3101
  });
2457
3102
  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() {
2458
3103
  const options = this.opts();
2459
3104
  const newConfigPath = getCcemConfigPath();
2460
3105
  const legacyConfigPath = getLegacyConfigPath();
2461
3106
  console.log(chalk7.cyan("\n\u{1F504} \u914D\u7F6E\u8FC1\u79FB\n"));
2462
- if (!fs7.existsSync(legacyConfigPath)) {
3107
+ if (!fs8.existsSync(legacyConfigPath)) {
2463
3108
  console.log(chalk7.yellow("\u672A\u627E\u5230\u65E7\u7248\u914D\u7F6E\u6587\u4EF6"));
2464
3109
  console.log(chalk7.gray(` \u65E7\u8DEF\u5F84: ${legacyConfigPath}`));
2465
3110
  return;
2466
3111
  }
2467
- if (fs7.existsSync(newConfigPath) && !options.force) {
3112
+ if (fs8.existsSync(newConfigPath) && !options.force) {
2468
3113
  console.log(chalk7.green("\u2713 \u914D\u7F6E\u5DF2\u5728\u65B0\u8DEF\u5F84"));
2469
3114
  console.log(chalk7.gray(` \u8DEF\u5F84: ${newConfigPath}`));
2470
3115
  console.log(chalk7.gray("\n\u4F7F\u7528 --force \u5F3A\u5236\u91CD\u65B0\u8FC1\u79FB"));
2471
3116
  return;
2472
3117
  }
2473
3118
  try {
2474
- ensureCcemDir3();
2475
- fs7.copyFileSync(legacyConfigPath, newConfigPath);
3119
+ ensureCcemDir();
3120
+ fs8.copyFileSync(legacyConfigPath, newConfigPath);
2476
3121
  console.log(chalk7.green("\u2713 \u914D\u7F6E\u5DF2\u8FC1\u79FB"));
2477
3122
  console.log(chalk7.gray(` \u4ECE: ${legacyConfigPath}`));
2478
3123
  console.log(chalk7.gray(` \u5230: ${newConfigPath}`));
2479
3124
  if (options.clean) {
2480
- fs7.unlinkSync(legacyConfigPath);
2481
- const legacyDir = path5.dirname(legacyConfigPath);
3125
+ fs8.unlinkSync(legacyConfigPath);
3126
+ const legacyDir = path6.dirname(legacyConfigPath);
2482
3127
  try {
2483
- fs7.rmdirSync(legacyDir);
3128
+ fs8.rmdirSync(legacyDir);
2484
3129
  } catch {
2485
3130
  }
2486
3131
  console.log(chalk7.green("\u2713 \u5DF2\u5220\u9664\u65E7\u914D\u7F6E\u6587\u4EF6"));
@@ -2491,13 +3136,13 @@ setupCmd.command("migrate").description("\u8FC1\u79FB\u65E7\u7248\u914D\u7F6E\u5
2491
3136
  });
2492
3137
  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() {
2493
3138
  const options = this.opts();
2494
- const skillDir = path5.join(process.env.HOME || "~", ".claude", "skills");
2495
- const targetPath = path5.join(skillDir, "ccem-cron.md");
2496
- if (!fs7.existsSync(skillDir)) {
2497
- fs7.mkdirSync(skillDir, { recursive: true });
3139
+ const skillDir = path6.join(process.env.HOME || "~", ".claude", "skills");
3140
+ const targetPath = path6.join(skillDir, "ccem-cron.md");
3141
+ if (!fs8.existsSync(skillDir)) {
3142
+ fs8.mkdirSync(skillDir, { recursive: true });
2498
3143
  console.log(chalk7.gray(`\u521B\u5EFA\u76EE\u5F55: ${skillDir}`));
2499
3144
  }
2500
- if (fs7.existsSync(targetPath) && !options.force) {
3145
+ if (fs8.existsSync(targetPath) && !options.force) {
2501
3146
  const { overwrite } = await inquirer.prompt([
2502
3147
  {
2503
3148
  type: "confirm",
@@ -2511,7 +3156,7 @@ setupCmd.command("cron").description("\u5B89\u88C5 ccem-cron skill \u5230 Claude
2511
3156
  return;
2512
3157
  }
2513
3158
  }
2514
- fs7.writeFileSync(targetPath, CCEM_CRON_SKILL_CONTENT, "utf-8");
3159
+ fs8.writeFileSync(targetPath, CCEM_CRON_SKILL_CONTENT, "utf-8");
2515
3160
  console.log(chalk7.green(`\u2713 \u5DF2\u5B89\u88C5 ccem-cron skill`));
2516
3161
  console.log(chalk7.gray(` \u8DEF\u5F84: ${targetPath}`));
2517
3162
  console.log(chalk7.cyan(`
@@ -2567,20 +3212,33 @@ skillCmd.command("ls").description("\u5217\u51FA\u5DF2\u5B89\u88C5\u7684 skills"
2567
3212
  skillCmd.command("rm <name>").description("\u5220\u9664\u5DF2\u5B89\u88C5\u7684 skill").action((name) => {
2568
3213
  removeSkill(name);
2569
3214
  });
2570
- program.command("load <url>").description("\u4ECE\u8FDC\u7A0B\u670D\u52A1\u5668\u52A0\u8F7D\u73AF\u5883\u914D\u7F6E").requiredOption("--secret <secret>", "\u89E3\u5BC6\u5BC6\u94A5").action(async (url, options) => {
2571
- await loadFromRemote(url, options.secret);
3215
+ program.command("load <url>").description("\u4ECE\u8FDC\u7A0B\u670D\u52A1\u5668\u52A0\u8F7D\u73AF\u5883\u914D\u7F6E").requiredOption("--secret <secret>", "\u89E3\u5BC6\u5BC6\u94A5").option("--json", "\u4EE5 JSON \u683C\u5F0F\u8F93\u51FA\u7ED3\u679C\uFF08\u4F9B\u7A0B\u5E8F\u8C03\u7528\uFF09").action(async (url, options) => {
3216
+ const results = await loadFromRemote(url, options.secret);
3217
+ if (options.json) {
3218
+ console.log(JSON.stringify({
3219
+ count: results.length,
3220
+ environments: results.map((r) => ({
3221
+ name: r.name,
3222
+ original_name: r.originalName,
3223
+ // 使用 snake_case 匹配 Rust 结构体
3224
+ renamed: r.renamed
3225
+ }))
3226
+ }));
3227
+ }
2572
3228
  });
2573
- program.command("launch").description(false).option("--env <name>", "\u73AF\u5883\u540D\u79F0").option("--perm <mode>", "\u6743\u9650\u6A21\u5F0F").option("--session-id <id>", "\u4F1A\u8BDD ID").option("--resume-session <id>", "\u6062\u590D\u4F1A\u8BDD ID").option("--working-dir <path>", "\u5DE5\u4F5C\u76EE\u5F55").action(async function() {
3229
+ 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() {
2574
3230
  const opts = this.opts();
2575
3231
  const envName = opts.env || config2.get("current");
2576
- const registries = config2.get("registries");
3232
+ const registries = getRegistries();
2577
3233
  const envConfig = registries[envName];
2578
3234
  if (!envConfig) {
2579
3235
  console.error(chalk7.red(`Environment '${envName}' not found.`));
2580
3236
  process.exit(1);
2581
3237
  }
3238
+ const proxyBaseUrl = opts.proxyBaseUrl || opts.anthropicBaseUrl;
3239
+ const launchEnvConfig = proxyBaseUrl ? { ...envConfig, ANTHROPIC_BASE_URL: proxyBaseUrl } : envConfig;
2582
3240
  await launchClaude({
2583
- envConfig,
3241
+ envConfig: launchEnvConfig,
2584
3242
  permMode: opts.perm,
2585
3243
  workingDir: opts.workingDir,
2586
3244
  sessionId: opts.sessionId,
@@ -2616,7 +3274,7 @@ program.action(async (options) => {
2616
3274
  showCurrentEnv(usageStats, usageLoading);
2617
3275
  console.log("");
2618
3276
  const defaultMode = config2.get("defaultMode");
2619
- const registries = config2.get("registries");
3277
+ const registries = getRegistries();
2620
3278
  const current = config2.get("current");
2621
3279
  const envConfig = registries[current];
2622
3280
  const { action } = await inquirer.prompt([
@@ -2663,37 +3321,9 @@ program.action(async (options) => {
2663
3321
  const envToEdit = registries[result.name];
2664
3322
  console.log(chalk7.yellow(`
2665
3323
  Editing environment '${result.name}'`));
2666
- const answers = await inquirer.prompt([
2667
- {
2668
- type: "input",
2669
- name: "ANTHROPIC_BASE_URL",
2670
- message: "ANTHROPIC_BASE_URL:",
2671
- default: envToEdit.ANTHROPIC_BASE_URL
2672
- },
2673
- {
2674
- type: "password",
2675
- name: "ANTHROPIC_API_KEY",
2676
- message: "ANTHROPIC_API_KEY (leave empty to keep current):"
2677
- },
2678
- {
2679
- type: "input",
2680
- name: "ANTHROPIC_MODEL",
2681
- message: "ANTHROPIC_MODEL:",
2682
- default: envToEdit.ANTHROPIC_MODEL
2683
- },
2684
- {
2685
- type: "input",
2686
- name: "ANTHROPIC_SMALL_FAST_MODEL",
2687
- message: "ANTHROPIC_SMALL_FAST_MODEL:",
2688
- default: envToEdit.ANTHROPIC_SMALL_FAST_MODEL
2689
- }
2690
- ]);
2691
- if (answers.ANTHROPIC_BASE_URL) envToEdit.ANTHROPIC_BASE_URL = answers.ANTHROPIC_BASE_URL;
2692
- if (answers.ANTHROPIC_API_KEY) envToEdit.ANTHROPIC_API_KEY = encrypt2(answers.ANTHROPIC_API_KEY);
2693
- if (answers.ANTHROPIC_MODEL) envToEdit.ANTHROPIC_MODEL = answers.ANTHROPIC_MODEL;
2694
- if (answers.ANTHROPIC_SMALL_FAST_MODEL) envToEdit.ANTHROPIC_SMALL_FAST_MODEL = answers.ANTHROPIC_SMALL_FAST_MODEL;
2695
- registries[result.name] = envToEdit;
2696
- config2.set("registries", registries);
3324
+ const answers = await promptForEnvironmentConfig(envToEdit, true);
3325
+ registries[result.name] = applyPromptAnswers(envToEdit, answers, true);
3326
+ setRegistries(registries);
2697
3327
  msg.success(`Environment '${result.name}' updated.`);
2698
3328
  await new Promise((resolve2) => setTimeout(resolve2, 800));
2699
3329
  } else if (result.action === "rename") {
@@ -2715,7 +3345,7 @@ Editing environment '${result.name}'`));
2715
3345
  ]);
2716
3346
  registries[newName] = registries[result.name];
2717
3347
  delete registries[result.name];
2718
- config2.set("registries", registries);
3348
+ setRegistries(registries);
2719
3349
  if (current === result.name) {
2720
3350
  config2.set("current", newName);
2721
3351
  }
@@ -2736,7 +3366,7 @@ Editing environment '${result.name}'`));
2736
3366
  }
2737
3367
  ]);
2738
3368
  registries[targetName] = { ...registries[result.name] };
2739
- config2.set("registries", registries);
3369
+ setRegistries(registries);
2740
3370
  msg.success(`Environment '${result.name}' copied to '${targetName}'.`);
2741
3371
  const { modify } = await inquirer.prompt([
2742
3372
  {
@@ -2748,37 +3378,9 @@ Editing environment '${result.name}'`));
2748
3378
  ]);
2749
3379
  if (modify) {
2750
3380
  const envToEdit = registries[targetName];
2751
- const editAnswers = await inquirer.prompt([
2752
- {
2753
- type: "input",
2754
- name: "ANTHROPIC_BASE_URL",
2755
- message: "ANTHROPIC_BASE_URL:",
2756
- default: envToEdit.ANTHROPIC_BASE_URL
2757
- },
2758
- {
2759
- type: "password",
2760
- name: "ANTHROPIC_API_KEY",
2761
- message: "ANTHROPIC_API_KEY (leave empty to keep current):"
2762
- },
2763
- {
2764
- type: "input",
2765
- name: "ANTHROPIC_MODEL",
2766
- message: "ANTHROPIC_MODEL:",
2767
- default: envToEdit.ANTHROPIC_MODEL
2768
- },
2769
- {
2770
- type: "input",
2771
- name: "ANTHROPIC_SMALL_FAST_MODEL",
2772
- message: "ANTHROPIC_SMALL_FAST_MODEL:",
2773
- default: envToEdit.ANTHROPIC_SMALL_FAST_MODEL
2774
- }
2775
- ]);
2776
- if (editAnswers.ANTHROPIC_BASE_URL) envToEdit.ANTHROPIC_BASE_URL = editAnswers.ANTHROPIC_BASE_URL;
2777
- if (editAnswers.ANTHROPIC_API_KEY) envToEdit.ANTHROPIC_API_KEY = encrypt2(editAnswers.ANTHROPIC_API_KEY);
2778
- if (editAnswers.ANTHROPIC_MODEL) envToEdit.ANTHROPIC_MODEL = editAnswers.ANTHROPIC_MODEL;
2779
- if (editAnswers.ANTHROPIC_SMALL_FAST_MODEL) envToEdit.ANTHROPIC_SMALL_FAST_MODEL = editAnswers.ANTHROPIC_SMALL_FAST_MODEL;
2780
- registries[targetName] = envToEdit;
2781
- config2.set("registries", registries);
3381
+ const editAnswers = await promptForEnvironmentConfig(envToEdit, true);
3382
+ registries[targetName] = applyPromptAnswers(envToEdit, editAnswers, true);
3383
+ setRegistries(registries);
2782
3384
  msg.success(`Environment '${targetName}' updated.`);
2783
3385
  }
2784
3386
  await new Promise((resolve2) => setTimeout(resolve2, 800));
@@ -2797,7 +3399,7 @@ Editing environment '${result.name}'`));
2797
3399
  ]);
2798
3400
  if (confirm) {
2799
3401
  delete registries[result.name];
2800
- config2.set("registries", registries);
3402
+ setRegistries(registries);
2801
3403
  if (current === result.name) {
2802
3404
  config2.set("current", "official");
2803
3405
  msg.warning(`Deleted current environment. Switched back to 'official'.`);
@@ -2839,7 +3441,7 @@ Editing environment '${result.name}'`));
2839
3441
  await new Promise((resolve2) => setTimeout(resolve2, 800));
2840
3442
  } else if (selectedMode !== "back") {
2841
3443
  config2.set("defaultMode", selectedMode);
2842
- msg.success(`Default mode set: ${PERMISSION_PRESETS4[selectedMode].name}`);
3444
+ msg.success(`Default mode set: ${PERMISSION_PRESETS[selectedMode].name}`);
2843
3445
  await new Promise((resolve2) => setTimeout(resolve2, 800));
2844
3446
  }
2845
3447
  } else {