ccman 3.3.6 → 3.3.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -9,7 +9,7 @@
9
9
  - 🔄 **一键切换**:一条命令切换服务商,自动修改配置文件
10
10
  - 📦 **内置预设**:提供常用预设(Claude: 1 个,Gemini: 2 个,Codex: 2 个,OpenCode: 1 个,MCP: 多个),只需填写 API Key
11
11
  - 🛠️ **自定义配置**:支持添加任意第三方服务商
12
- - 🔐 **零破坏性**:只修改管理的字段,写入前备份,失败回滚
12
+ - 🔐 **安全写入**:写入前备份;Codex 的 `config.toml/auth.json` 备份后覆盖写入,其他工具尽量保留用户字段
13
13
  - 🎯 **多工具支持**:同时管理 Codex、Claude Code、Gemini CLI、OpenCode 和 MCP 服务器
14
14
  - 📱 **双界面**:提供 CLI(命令行)和 Desktop(图形界面)
15
15
  - 🔁 **克隆功能**:快速复制配置,管理多个 API Key
@@ -350,9 +350,9 @@ $ ccman cc use "Claude Test"
350
350
  - **Gemini CLI**: `~/.gemini/settings.json` 和 `~/.gemini/.env`
351
351
  - **OpenCode**: `~/.config/opencode/opencode.json`
352
352
 
353
- **零破坏性承诺**:
354
- - 只修改管理的字段,保留其他所有配置
355
- - 写入前备份,失败时自动回滚
353
+ **安全写入承诺**:
354
+ - 写入前备份(`.bak`),失败时自动回滚
355
+ - Codex 的 `config.toml/auth.json` 采用备份后覆盖写入(避免遗留字段导致报错/告警)
356
356
  - API Key 存储在本地,权限 `0600`
357
357
 
358
358
  ---
package/dist/index.js CHANGED
@@ -15,7 +15,7 @@ var init_package = __esm({
15
15
  "../core/package.json"() {
16
16
  package_default = {
17
17
  name: "@ccman/core",
18
- version: "3.3.6",
18
+ version: "3.3.8",
19
19
  type: "module",
20
20
  description: "Core business logic for ccman - Manage Codex, Claude Code, Gemini CLI, and MCP configurations",
21
21
  main: "./dist/index.js",
@@ -229,37 +229,17 @@ function fileExists(filePath) {
229
229
  return false;
230
230
  }
231
231
  }
232
- var init_file = __esm({
233
- "../core/dist/utils/file.js"() {
234
- "use strict";
235
- }
236
- });
237
-
238
- // ../core/dist/utils/template.js
239
- function replaceVariables(template, variables) {
240
- const jsonStr = JSON.stringify(template);
241
- let result = jsonStr;
242
- for (const [key, value] of Object.entries(variables)) {
243
- const escapedValue = value.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
244
- result = result.replace(new RegExp(`{{${key}}}`, "g"), escapedValue);
245
- }
246
- return JSON.parse(result);
247
- }
248
- function deepMerge(target, source) {
249
- const result = { ...target };
250
- for (const key in source) {
251
- const sourceValue = source[key];
252
- const targetValue = result[key];
253
- if (sourceValue && typeof sourceValue === "object" && !Array.isArray(sourceValue) && targetValue && typeof targetValue === "object" && !Array.isArray(targetValue)) {
254
- result[key] = deepMerge(targetValue, sourceValue);
255
- } else {
256
- result[key] = sourceValue;
257
- }
232
+ function backupFile(filePath) {
233
+ if (!fileExists(filePath)) {
234
+ throw new Error(`File not found: ${filePath}`);
258
235
  }
259
- return result;
236
+ const backupPath = `${filePath}.bak`;
237
+ fs.copyFileSync(filePath, backupPath);
238
+ fs.chmodSync(backupPath, 384);
239
+ return backupPath;
260
240
  }
261
- var init_template = __esm({
262
- "../core/dist/utils/template.js"() {
241
+ var init_file = __esm({
242
+ "../core/dist/utils/file.js"() {
263
243
  "use strict";
264
244
  }
265
245
  });
@@ -304,53 +284,41 @@ function loadCodexTemplateConfig() {
304
284
  function writeCodexConfig(provider) {
305
285
  ensureDir(getCodexDir());
306
286
  const configPath = getCodexConfigPath();
307
- let userConfig = {};
308
287
  if (fileExists(configPath)) {
309
- const content = fs2.readFileSync(configPath, "utf-8");
310
- userConfig = parseToml(content);
288
+ try {
289
+ backupFile(configPath);
290
+ } catch {
291
+ }
311
292
  }
312
293
  const templateConfig = loadCodexTemplateConfig();
313
- const mergedConfig = deepMerge(templateConfig, userConfig);
314
- if (mergedConfig.features && typeof mergedConfig.features === "object" && !Array.isArray(mergedConfig.features) && "web_search_request" in mergedConfig.features) {
315
- delete mergedConfig.features.web_search_request;
316
- }
317
- if (mergedConfig.features && typeof mergedConfig.features === "object" && !Array.isArray(mergedConfig.features)) {
318
- for (const key of ["plan_tool", "view_image_tool", "rmcp_client", "streamable_shell"]) {
319
- if (key in mergedConfig.features) {
320
- delete mergedConfig.features[key];
321
- }
322
- }
323
- if (Object.keys(mergedConfig.features).length === 0) {
324
- delete mergedConfig.features;
325
- }
294
+ const nextConfig = { ...templateConfig };
295
+ if (nextConfig.features && typeof nextConfig.features === "object" && !Array.isArray(nextConfig.features) && "web_search_request" in nextConfig.features) {
296
+ delete nextConfig.features.web_search_request;
326
297
  }
327
- if (!mergedConfig.web_search) {
328
- mergedConfig.web_search = "live";
298
+ if ("web_search_request" in nextConfig) {
299
+ delete nextConfig.web_search_request;
329
300
  }
330
301
  const providerKey = resolveCodexProviderKey(provider);
331
- mergedConfig.model_provider = providerKey;
332
- mergedConfig.model = provider.model || mergedConfig.model || "gpt-5.2-codex";
333
- mergedConfig.model_providers = mergedConfig.model_providers || {};
334
- if (providerKey !== provider.name) {
335
- delete mergedConfig.model_providers[provider.name];
336
- }
337
- mergedConfig.model_providers[providerKey] = {
338
- name: providerKey,
339
- base_url: provider.baseUrl,
340
- wire_api: "responses",
341
- requires_openai_auth: true
302
+ nextConfig.model_provider = providerKey;
303
+ nextConfig.model = provider.model || nextConfig.model || "gpt-5.2-codex";
304
+ nextConfig.model_providers = {
305
+ [providerKey]: {
306
+ name: providerKey,
307
+ base_url: provider.baseUrl,
308
+ wire_api: "responses",
309
+ requires_openai_auth: true
310
+ }
342
311
  };
343
- fs2.writeFileSync(configPath, stringifyToml(mergedConfig), { mode: 384 });
312
+ fs2.writeFileSync(configPath, stringifyToml(nextConfig), { mode: 384 });
344
313
  const authPath = getCodexAuthPath();
345
- let auth;
346
314
  if (fileExists(authPath)) {
347
- const content = fs2.readFileSync(authPath, "utf-8");
348
- auth = JSON.parse(content);
349
- } else {
350
- auth = { OPENAI_API_KEY: "" };
315
+ try {
316
+ backupFile(authPath);
317
+ } catch {
318
+ }
351
319
  }
352
- auth.OPENAI_API_KEY = provider.apiKey;
353
- fs2.writeFileSync(authPath, JSON.stringify(auth, null, 2), { mode: 384 });
320
+ const auth = { OPENAI_API_KEY: provider.apiKey };
321
+ writeJSON(authPath, auth);
354
322
  }
355
323
  var __filename, __dirname, CODEX_DEFAULT_CONFIG;
356
324
  var init_codex = __esm({
@@ -358,24 +326,48 @@ var init_codex = __esm({
358
326
  "use strict";
359
327
  init_paths();
360
328
  init_file();
361
- init_template();
362
329
  __filename = fileURLToPath(import.meta.url);
363
330
  __dirname = path3.dirname(__filename);
364
331
  CODEX_DEFAULT_CONFIG = {
365
332
  model: "gpt-5.2-codex",
366
333
  model_reasoning_effort: "high",
367
334
  model_verbosity: "high",
368
- web_search: "live",
335
+ network_access: "enabled",
369
336
  disable_response_storage: true,
370
- windows_wsl_setup_acknowledged: true,
371
- sandbox_mode: "workspace-write",
372
- sandbox_workspace_write: {
373
- network_access: true
374
- }
337
+ windows_wsl_setup_acknowledged: true
375
338
  };
376
339
  }
377
340
  });
378
341
 
342
+ // ../core/dist/utils/template.js
343
+ function replaceVariables(template, variables) {
344
+ const jsonStr = JSON.stringify(template);
345
+ let result = jsonStr;
346
+ for (const [key, value] of Object.entries(variables)) {
347
+ const escapedValue = value.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
348
+ result = result.replace(new RegExp(`{{${key}}}`, "g"), escapedValue);
349
+ }
350
+ return JSON.parse(result);
351
+ }
352
+ function deepMerge(target, source) {
353
+ const result = { ...target };
354
+ for (const key in source) {
355
+ const sourceValue = source[key];
356
+ const targetValue = result[key];
357
+ if (sourceValue && typeof sourceValue === "object" && !Array.isArray(sourceValue) && targetValue && typeof targetValue === "object" && !Array.isArray(targetValue)) {
358
+ result[key] = deepMerge(targetValue, sourceValue);
359
+ } else {
360
+ result[key] = sourceValue;
361
+ }
362
+ }
363
+ return result;
364
+ }
365
+ var init_template = __esm({
366
+ "../core/dist/utils/template.js"() {
367
+ "use strict";
368
+ }
369
+ });
370
+
379
371
  // ../core/dist/writers/claude.js
380
372
  import * as fs3 from "fs";
381
373
  import * as path4 from "path";
@@ -2202,7 +2194,7 @@ function getFileSize(filePath) {
2202
2194
  return 0;
2203
2195
  }
2204
2196
  }
2205
- function backupFile(filePath) {
2197
+ function backupFile2(filePath) {
2206
2198
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/:/g, "-").split(".")[0];
2207
2199
  const backupPath = `${filePath}.backup-${timestamp}`;
2208
2200
  fs11.copyFileSync(filePath, backupPath);
@@ -2269,7 +2261,7 @@ function cleanClaudeJson(options = {}) {
2269
2261
  if (!fs11.existsSync(filePath)) {
2270
2262
  throw new Error(`${filePath} \u6587\u4EF6\u4E0D\u5B58\u5728`);
2271
2263
  }
2272
- const backupPath = backupFile(filePath);
2264
+ const backupPath = backupFile2(filePath);
2273
2265
  const sizeBefore = getFileSize(filePath);
2274
2266
  const content = fs11.readFileSync(filePath, "utf-8");
2275
2267
  const config = JSON.parse(content);
@@ -1,21 +1,50 @@
1
- ########################################
2
- # Codex 最小模板(ccman 默认)
3
- #
4
- # - model_provider 与 [model_providers.*] 由 ccman 根据当前服务商自动写入
5
- # - 配置越少越稳定;需要扩展再手动加
6
- ########################################
7
-
8
1
  model = "gpt-5.2-codex"
9
- model_reasoning_effort = "high"
10
- model_verbosity = "high"
2
+ model_reasoning_effort = "xhigh"
11
3
  disable_response_storage = true
4
+ sandbox_mode = "danger-full-access"
12
5
  windows_wsl_setup_acknowledged = true
6
+ approval_policy = "never"
7
+ profile = "auto-max"
8
+ file_opener = "vscode"
9
+ model_provider = "gmn"
10
+ web_search = "cached"
11
+ suppress_unstable_features_warning = true
13
12
 
14
- # Web 搜索策略(替代旧的 [features].web_search_request)
15
- web_search = "live" # "live" | "cached" | "disabled"
13
+ [history]
14
+ persistence = "save-all"
16
15
 
17
- # 沙箱与网络访问:workspace-write + 允许联网
18
- sandbox_mode = "workspace-write"
16
+ [tui]
17
+ notifications = true
18
+
19
+ [shell_environment_policy]
20
+ inherit = "all"
21
+ ignore_default_excludes = false
19
22
 
20
23
  [sandbox_workspace_write]
21
24
  network_access = true
25
+
26
+ [features]
27
+ plan_tool = true
28
+ apply_patch_freeform = true
29
+ view_image_tool = true
30
+ unified_exec = false
31
+ streamable_shell = false
32
+ rmcp_client = true
33
+ elevated_windows_sandbox = true
34
+
35
+ [profiles.auto-max]
36
+ approval_policy = "never"
37
+ sandbox_mode = "workspace-write"
38
+
39
+ [profiles.review]
40
+ approval_policy = "on-request"
41
+ sandbox_mode = "workspace-write"
42
+
43
+ [notice]
44
+ hide_gpt5_1_migration_prompt = true
45
+
46
+ [model_providers.gmn]
47
+ name = "gmn"
48
+ base_url = "https://gmn.chuangzuoli.com"
49
+ wire_api = "responses"
50
+ requires_openai_auth = true
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ccman",
3
- "version": "3.3.6",
3
+ "version": "3.3.8",
4
4
  "type": "module",
5
5
  "description": "Manage Codex, Claude Code, Gemini CLI, OpenCode, and MCP API service provider configurations",
6
6
  "main": "./dist/index.js",