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 +4 -4
- package/dist/index.js +68 -76
- package/dist/templates/codex/config.toml +42 -13
- package/package.json +1 -1
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.
|
|
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
|
-
|
|
233
|
-
|
|
234
|
-
|
|
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
|
-
|
|
236
|
+
const backupPath = `${filePath}.bak`;
|
|
237
|
+
fs.copyFileSync(filePath, backupPath);
|
|
238
|
+
fs.chmodSync(backupPath, 384);
|
|
239
|
+
return backupPath;
|
|
260
240
|
}
|
|
261
|
-
var
|
|
262
|
-
"../core/dist/utils/
|
|
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
|
-
|
|
310
|
-
|
|
288
|
+
try {
|
|
289
|
+
backupFile(configPath);
|
|
290
|
+
} catch {
|
|
291
|
+
}
|
|
311
292
|
}
|
|
312
293
|
const templateConfig = loadCodexTemplateConfig();
|
|
313
|
-
const
|
|
314
|
-
if (
|
|
315
|
-
delete
|
|
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 (
|
|
328
|
-
|
|
298
|
+
if ("web_search_request" in nextConfig) {
|
|
299
|
+
delete nextConfig.web_search_request;
|
|
329
300
|
}
|
|
330
301
|
const providerKey = resolveCodexProviderKey(provider);
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
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(
|
|
312
|
+
fs2.writeFileSync(configPath, stringifyToml(nextConfig), { mode: 384 });
|
|
344
313
|
const authPath = getCodexAuthPath();
|
|
345
|
-
let auth;
|
|
346
314
|
if (fileExists(authPath)) {
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
315
|
+
try {
|
|
316
|
+
backupFile(authPath);
|
|
317
|
+
} catch {
|
|
318
|
+
}
|
|
351
319
|
}
|
|
352
|
-
auth
|
|
353
|
-
|
|
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
|
-
|
|
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
|
|
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 =
|
|
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 = "
|
|
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
|
-
|
|
15
|
-
|
|
13
|
+
[history]
|
|
14
|
+
persistence = "save-all"
|
|
16
15
|
|
|
17
|
-
|
|
18
|
-
|
|
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
|