@tokenaut/opentoken 1.3.0
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 +100 -0
- package/bin/opentoken.js +9 -0
- package/package.json +32 -0
- package/src/cli.js +234 -0
- package/src/codex-config.js +173 -0
- package/src/config.js +82 -0
package/README.md
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# OpenToken
|
|
2
|
+
|
|
3
|
+
OpenToken 一键配置 CLI,支持 Claude Code 和 Codex。
|
|
4
|
+
|
|
5
|
+
## 用法
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
opentoken <tool> [--key <api_key>] [--model <model>] [--header <prefix>] [--url <base_url>]
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
各参数均可独立使用,只修改指定的配置项,未指定的保持不变。
|
|
12
|
+
|
|
13
|
+
## 工具
|
|
14
|
+
|
|
15
|
+
| 命令 | 说明 | 配置文件 |
|
|
16
|
+
|------|------|---------|
|
|
17
|
+
| `opentoken cc` | Claude Code | `~/.claude/settings.json` |
|
|
18
|
+
| `opentoken codex` / `cx` | Codex | `~/.codex/config.toml` + `~/.codex/auth.json` |
|
|
19
|
+
|
|
20
|
+
## 模型别名
|
|
21
|
+
|
|
22
|
+
### Claude Code
|
|
23
|
+
|
|
24
|
+
| 别名 | 模型 ID |
|
|
25
|
+
|------|---------|
|
|
26
|
+
| `opus` | `claude-opus-4-8`(默认) |
|
|
27
|
+
| `sonnet` | `claude-sonnet-4-6` |
|
|
28
|
+
| `haiku` | `claude-haiku-4-5` |
|
|
29
|
+
|
|
30
|
+
### Codex
|
|
31
|
+
|
|
32
|
+
| 别名 | 模型 ID |
|
|
33
|
+
|------|---------|
|
|
34
|
+
| `5.4` / `gpt5.4` / `gpt-5.4` | `gpt-5.4`(默认) |
|
|
35
|
+
| `5.5` / `gpt5.5` / `gpt-5.5` | `gpt-5.5` |
|
|
36
|
+
|
|
37
|
+
## 示例
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
# Claude Code - 一键初始化
|
|
41
|
+
opentoken cc --key sk-xxxxx
|
|
42
|
+
|
|
43
|
+
# Claude Code - 只改模型
|
|
44
|
+
opentoken cc --model sonnet
|
|
45
|
+
|
|
46
|
+
# Claude Code - 带前缀
|
|
47
|
+
opentoken cc --key sk-xxxxx --model opus --header vor
|
|
48
|
+
|
|
49
|
+
# Codex - 一键初始化
|
|
50
|
+
opentoken cx --key sk-xxxxx --url https://gw.opentoken.io/v1
|
|
51
|
+
|
|
52
|
+
# Codex - 只改模型
|
|
53
|
+
opentoken cx --model 5.5
|
|
54
|
+
|
|
55
|
+
# Codex - 完整指定
|
|
56
|
+
opentoken cx --key sk-xxxxx --model gpt-5.5 --url https://gw.opentoken.io/v1
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## 写入字段
|
|
60
|
+
|
|
61
|
+
### Claude Code(`~/.claude/settings.json`)
|
|
62
|
+
|
|
63
|
+
- `ANTHROPIC_AUTH_TOKEN`
|
|
64
|
+
- `ANTHROPIC_BASE_URL`
|
|
65
|
+
- `ANTHROPIC_MODEL`
|
|
66
|
+
- `API_TIMEOUT_MS`
|
|
67
|
+
- `CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS`
|
|
68
|
+
- `ENABLE_TOOL_SEARCH`
|
|
69
|
+
|
|
70
|
+
默认 Base URL:`https://gw.opentoken.io/`
|
|
71
|
+
|
|
72
|
+
### Codex(`~/.codex/config.toml` + `~/.codex/auth.json`)
|
|
73
|
+
|
|
74
|
+
- `model` / `model_provider`
|
|
75
|
+
- `model_providers.opentoken`(name、base_url、wire_api)
|
|
76
|
+
- `profiles.<model>`
|
|
77
|
+
- `tui.model_availability_nux`
|
|
78
|
+
- `OPENAI_API_KEY`(写入 auth.json)
|
|
79
|
+
|
|
80
|
+
默认 Base URL:`https://gw.opentoken.io/v1`
|
|
81
|
+
|
|
82
|
+
## --header
|
|
83
|
+
|
|
84
|
+
为模型 ID 添加前缀,例如:
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
opentoken cc --key sk-xxxxx --header vor
|
|
88
|
+
# 模型变为 vor/claude-opus-4-8
|
|
89
|
+
```
|
|
90
|
+
一键配置命令示例:
|
|
91
|
+
codex和claude code直接配置
|
|
92
|
+
opentoken cx --key your api key --model gpt-5.5 --url https://gw.opentoken.io/v1
|
|
93
|
+
opentoken cc --key your api key --model claude-opus-4-7 --url https://gw.opentoken.io
|
|
94
|
+
|
|
95
|
+
注意cc 的url没有/v1
|
|
96
|
+
|
|
97
|
+
codex和claude code连omni网关配置
|
|
98
|
+
opentoken cx --key your api key --model 前缀/gpt-5.5 --url
|
|
99
|
+
https://omniroute.goio.uk/v1
|
|
100
|
+
opentoken cc --key your api key --model 前缀/claude-opus-4-7 --url https://omniroute.goio.uk/v1
|
package/bin/opentoken.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@tokenaut/opentoken",
|
|
3
|
+
"version": "1.3.0",
|
|
4
|
+
"description": "OpenToken Claude Code 一键配置 CLI",
|
|
5
|
+
"main": "src/cli.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"opentoken": "bin/opentoken.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"bin",
|
|
11
|
+
"src"
|
|
12
|
+
],
|
|
13
|
+
"scripts": {
|
|
14
|
+
"start": "node bin/opentoken.js",
|
|
15
|
+
"dev": "node bin/opentoken.js"
|
|
16
|
+
},
|
|
17
|
+
"keywords": [
|
|
18
|
+
"opentoken",
|
|
19
|
+
"claude",
|
|
20
|
+
"claude-code",
|
|
21
|
+
"cli",
|
|
22
|
+
"config"
|
|
23
|
+
],
|
|
24
|
+
"author": "opentoken",
|
|
25
|
+
"license": "MIT",
|
|
26
|
+
"engines": {
|
|
27
|
+
"node": ">=14.0.0"
|
|
28
|
+
},
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"@iarna/toml": "^2.2.5"
|
|
31
|
+
}
|
|
32
|
+
}
|
package/src/cli.js
ADDED
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { applyConfig, SETTINGS_PATH } = require('./config');
|
|
4
|
+
const { applyCodexConfig, CONFIG_PATH: CODEX_CONFIG_PATH, AUTH_PATH: CODEX_AUTH_PATH, getCurrentCodexConfig } = require('./codex-config');
|
|
5
|
+
|
|
6
|
+
const DEFAULT_BASE_URL = 'https://gw.opentoken.io/';
|
|
7
|
+
const CODEX_DEFAULT_BASE_URL = 'https://gw.opentoken.io/v1';
|
|
8
|
+
|
|
9
|
+
const CC_DEFAULT_MODEL_ALIAS = 'opus';
|
|
10
|
+
const CX_DEFAULT_MODEL_ALIAS = '5.4';
|
|
11
|
+
|
|
12
|
+
const CC_MODEL_MAP = {
|
|
13
|
+
opus: 'claude-opus-4-8',
|
|
14
|
+
sonnet: 'claude-sonnet-4-6',
|
|
15
|
+
haiku: 'claude-haiku-4-5',
|
|
16
|
+
'claude-opus-4-8': 'claude-opus-4-8',
|
|
17
|
+
'claude-sonnet-4-6': 'claude-sonnet-4-6',
|
|
18
|
+
'claude-haiku-4-5': 'claude-haiku-4-5',
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const CX_MODEL_MAP = {
|
|
22
|
+
'5.5': 'gpt-5.5',
|
|
23
|
+
'5.4': 'gpt-5.4',
|
|
24
|
+
'gpt5.5': 'gpt-5.5',
|
|
25
|
+
'gpt5.4': 'gpt-5.4',
|
|
26
|
+
'gpt-5.5': 'gpt-5.5',
|
|
27
|
+
'gpt-5.4': 'gpt-5.4',
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
function resolveModel(input, header, modelMap, defaultAlias) {
|
|
31
|
+
let modelId;
|
|
32
|
+
|
|
33
|
+
if (!input) {
|
|
34
|
+
modelId = modelMap[defaultAlias];
|
|
35
|
+
} else {
|
|
36
|
+
const trimmed = String(input).trim().toLowerCase();
|
|
37
|
+
if (modelMap[trimmed]) {
|
|
38
|
+
modelId = modelMap[trimmed];
|
|
39
|
+
} else {
|
|
40
|
+
modelId = String(input).trim();
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (header && String(header).trim()) {
|
|
45
|
+
return `${String(header).trim()}/${modelId}`;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return modelId;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function maskApiKey(key) {
|
|
52
|
+
if (!key || key.length < 8) {
|
|
53
|
+
return key || '';
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return `${key.slice(0, 6)}****${key.slice(-4)}`;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function printHelp() {
|
|
60
|
+
console.log('');
|
|
61
|
+
console.log('OpenToken 一键配置 CLI');
|
|
62
|
+
console.log('');
|
|
63
|
+
console.log('用法:');
|
|
64
|
+
console.log(' opentoken <tool> [--key <api_key>] [--model <model>] [--header <prefix>] [--url <base_url>]');
|
|
65
|
+
console.log('');
|
|
66
|
+
console.log('工具:');
|
|
67
|
+
console.log(' cc Claude Code 配置(~/.claude/settings.json)');
|
|
68
|
+
console.log(' codex Codex 配置(~/.codex/config.toml + ~/.codex/auth.json)');
|
|
69
|
+
console.log(' cx 同 codex(缩写)');
|
|
70
|
+
console.log('');
|
|
71
|
+
console.log('Claude Code 模型别名:');
|
|
72
|
+
console.log(` opus → ${CC_MODEL_MAP.opus} (默认)`);
|
|
73
|
+
console.log(` sonnet → ${CC_MODEL_MAP.sonnet}`);
|
|
74
|
+
console.log(` haiku → ${CC_MODEL_MAP.haiku}`);
|
|
75
|
+
console.log('');
|
|
76
|
+
console.log('Codex 模型别名:');
|
|
77
|
+
console.log(` 5.5 / gpt5.5 / gpt-5.5 → gpt-5.5`);
|
|
78
|
+
console.log(` 5.4 / gpt5.4 / gpt-5.4 → gpt-5.4 (默认)`);
|
|
79
|
+
console.log('');
|
|
80
|
+
console.log('示例:');
|
|
81
|
+
console.log(' opentoken cc --key sk-xxxxx');
|
|
82
|
+
console.log(' opentoken cc --model sonnet --header vor');
|
|
83
|
+
console.log(' opentoken codex --key sk-xxxxx');
|
|
84
|
+
console.log(' opentoken cx --model 5.5');
|
|
85
|
+
console.log(' opentoken cx --url https://gw.opentoken.io/v1');
|
|
86
|
+
console.log('');
|
|
87
|
+
console.log('说明:');
|
|
88
|
+
console.log(' 各参数均可独立使用,只修改指定的配置项,未指定的保持不变');
|
|
89
|
+
console.log(` Claude Code 默认 Base URL: ${DEFAULT_BASE_URL}`);
|
|
90
|
+
console.log(` Codex 默认 Base URL: ${CODEX_DEFAULT_BASE_URL}`);
|
|
91
|
+
console.log(' --header 为模型 ID 添加前缀,如 --header vor → vor/claude-opus-4-8');
|
|
92
|
+
console.log('');
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function parseArgs(argv) {
|
|
96
|
+
const args = argv.slice(2);
|
|
97
|
+
const result = {
|
|
98
|
+
tool: null,
|
|
99
|
+
key: null,
|
|
100
|
+
model: null,
|
|
101
|
+
header: null,
|
|
102
|
+
url: null,
|
|
103
|
+
help: false,
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
let i = 0;
|
|
107
|
+
if (args[0] && !args[0].startsWith('-')) {
|
|
108
|
+
result.tool = args[0].toLowerCase();
|
|
109
|
+
i = 1;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
for (; i < args.length; i += 1) {
|
|
113
|
+
const current = args[i];
|
|
114
|
+
const next = args[i + 1];
|
|
115
|
+
|
|
116
|
+
if (current === '--help' || current === '-h') {
|
|
117
|
+
result.help = true;
|
|
118
|
+
} else if ((current === '--key' || current === '-k') && next) {
|
|
119
|
+
result.key = next;
|
|
120
|
+
i += 1;
|
|
121
|
+
} else if ((current === '--model' || current === '-m') && next) {
|
|
122
|
+
result.model = next;
|
|
123
|
+
i += 1;
|
|
124
|
+
} else if ((current === '--header' || current === '-H') && next) {
|
|
125
|
+
result.header = next;
|
|
126
|
+
i += 1;
|
|
127
|
+
} else if ((current === '--url' || current === '-u') && next) {
|
|
128
|
+
result.url = next;
|
|
129
|
+
i += 1;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return result;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function isCcCommand(tool) {
|
|
137
|
+
return tool === 'cc';
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function isCodexCommand(tool) {
|
|
141
|
+
return tool === 'codex' || tool === 'cx';
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function validateArgs(args) {
|
|
145
|
+
if (!isCcCommand(args.tool) && !isCodexCommand(args.tool)) {
|
|
146
|
+
throw new Error('未知工具,请使用: opentoken cc 或 opentoken codex');
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
async function runCc(args) {
|
|
151
|
+
const patch = {};
|
|
152
|
+
|
|
153
|
+
if (args.key && String(args.key).trim()) {
|
|
154
|
+
patch.apiKey = String(args.key).trim();
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (args.model !== null || args.header !== null) {
|
|
158
|
+
patch.model = resolveModel(args.model, args.header, CC_MODEL_MAP, CC_DEFAULT_MODEL_ALIAS);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (args.url && String(args.url).trim()) {
|
|
162
|
+
patch.baseUrl = String(args.url).trim();
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (Object.keys(patch).length === 0) {
|
|
166
|
+
console.log('');
|
|
167
|
+
console.log('未指定任何配置项,请使用 --key / --model / --header / --url');
|
|
168
|
+
console.log('');
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
applyConfig(patch);
|
|
173
|
+
|
|
174
|
+
console.log('');
|
|
175
|
+
console.log('Claude Code 配置已更新:');
|
|
176
|
+
if (patch.apiKey) console.log(` API Key: ${maskApiKey(patch.apiKey)}`);
|
|
177
|
+
if (patch.baseUrl) console.log(` Base URL: ${patch.baseUrl}`);
|
|
178
|
+
if (patch.model) console.log(` Model: ${patch.model}`);
|
|
179
|
+
console.log(` File: ${SETTINGS_PATH}`);
|
|
180
|
+
console.log('');
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
async function runCodex(args) {
|
|
184
|
+
const patch = {};
|
|
185
|
+
|
|
186
|
+
if (args.key && String(args.key).trim()) {
|
|
187
|
+
patch.apiKey = String(args.key).trim();
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (args.model !== null || args.header !== null) {
|
|
191
|
+
patch.model = resolveModel(args.model, args.header, CX_MODEL_MAP, CX_DEFAULT_MODEL_ALIAS);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
if (args.url && String(args.url).trim()) {
|
|
195
|
+
patch.baseUrl = String(args.url).trim();
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
if (Object.keys(patch).length === 0) {
|
|
199
|
+
console.log('');
|
|
200
|
+
console.log('未指定任何配置项,请使用 --key / --model / --header / --url');
|
|
201
|
+
console.log('');
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
applyCodexConfig(patch);
|
|
206
|
+
|
|
207
|
+
console.log('');
|
|
208
|
+
console.log('Codex 配置已更新:');
|
|
209
|
+
if (patch.apiKey) console.log(` API Key: ${maskApiKey(patch.apiKey)}`);
|
|
210
|
+
if (patch.baseUrl) console.log(` Base URL: ${patch.baseUrl}`);
|
|
211
|
+
if (patch.model) console.log(` Model: ${patch.model}`);
|
|
212
|
+
console.log(` Config: ${CODEX_CONFIG_PATH}`);
|
|
213
|
+
console.log(` Auth: ${CODEX_AUTH_PATH}`);
|
|
214
|
+
console.log('');
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
async function run() {
|
|
218
|
+
const args = parseArgs(process.argv);
|
|
219
|
+
|
|
220
|
+
if (args.help || args.tool === null) {
|
|
221
|
+
printHelp();
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
validateArgs(args);
|
|
226
|
+
|
|
227
|
+
if (isCcCommand(args.tool)) {
|
|
228
|
+
await runCc(args);
|
|
229
|
+
} else if (isCodexCommand(args.tool)) {
|
|
230
|
+
await runCodex(args);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
module.exports = run;
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const os = require('os');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
const TOML = require('@iarna/toml');
|
|
7
|
+
|
|
8
|
+
const CONFIG_DIR = path.join(os.homedir(), '.codex');
|
|
9
|
+
const CONFIG_PATH = path.join(CONFIG_DIR, 'config.toml');
|
|
10
|
+
const AUTH_PATH = path.join(CONFIG_DIR, 'auth.json');
|
|
11
|
+
|
|
12
|
+
const PROVIDER_KEY = 'opentoken';
|
|
13
|
+
const AUTH_KEY = 'OPENAI_API_KEY';
|
|
14
|
+
const MODEL_AVAILABILITY_LEVEL = 4;
|
|
15
|
+
|
|
16
|
+
function readAuthFile() {
|
|
17
|
+
if (!fs.existsSync(AUTH_PATH)) return {};
|
|
18
|
+
try {
|
|
19
|
+
const data = JSON.parse(fs.readFileSync(AUTH_PATH, 'utf-8'));
|
|
20
|
+
if (data === null || typeof data !== 'object' || Array.isArray(data)) {
|
|
21
|
+
return {};
|
|
22
|
+
}
|
|
23
|
+
return data;
|
|
24
|
+
} catch {
|
|
25
|
+
return {};
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function applyCodexAuth(apiKey) {
|
|
30
|
+
const key = apiKey == null ? '' : String(apiKey).trim();
|
|
31
|
+
if (!key) return;
|
|
32
|
+
|
|
33
|
+
const prev = readAuthFile();
|
|
34
|
+
const data = {
|
|
35
|
+
...prev,
|
|
36
|
+
auth_mode: 'apikey',
|
|
37
|
+
[AUTH_KEY]: key,
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
if (!fs.existsSync(CONFIG_DIR)) {
|
|
41
|
+
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
42
|
+
}
|
|
43
|
+
fs.writeFileSync(AUTH_PATH, JSON.stringify(data, null, 2) + '\n', 'utf-8');
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function ensurePlainObject(parent, key) {
|
|
47
|
+
if (!parent[key] || typeof parent[key] !== 'object' || Array.isArray(parent[key])) {
|
|
48
|
+
parent[key] = {};
|
|
49
|
+
}
|
|
50
|
+
return parent[key];
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function parseConfig() {
|
|
54
|
+
if (!fs.existsSync(CONFIG_PATH)) return {};
|
|
55
|
+
const raw = fs.readFileSync(CONFIG_PATH, 'utf-8');
|
|
56
|
+
if (!raw || !String(raw).trim()) return {};
|
|
57
|
+
try {
|
|
58
|
+
const data = TOML.parse(raw);
|
|
59
|
+
if (data === null || typeof data !== 'object' || Array.isArray(data)) {
|
|
60
|
+
throw new Error('config.toml 根节点必须是表(TOML table)');
|
|
61
|
+
}
|
|
62
|
+
return data;
|
|
63
|
+
} catch (e) {
|
|
64
|
+
throw new Error('现有 config.toml 无法解析,请先备份后手动修复:' + e.message);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function getCurrentCodexConfig() {
|
|
69
|
+
const data = parseConfig();
|
|
70
|
+
const providers =
|
|
71
|
+
data.model_providers && typeof data.model_providers === 'object' && !Array.isArray(data.model_providers)
|
|
72
|
+
? data.model_providers
|
|
73
|
+
: {};
|
|
74
|
+
const provider =
|
|
75
|
+
providers[PROVIDER_KEY] && typeof providers[PROVIDER_KEY] === 'object' && !Array.isArray(providers[PROVIDER_KEY])
|
|
76
|
+
? providers[PROVIDER_KEY]
|
|
77
|
+
: {};
|
|
78
|
+
|
|
79
|
+
const auth = readAuthFile();
|
|
80
|
+
const apiKey = auth[AUTH_KEY] || '';
|
|
81
|
+
|
|
82
|
+
return {
|
|
83
|
+
apiKey,
|
|
84
|
+
baseUrl: provider.base_url != null ? String(provider.base_url) : '',
|
|
85
|
+
model: data.model != null ? String(data.model) : '',
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function uniqueModels(models, selectedModel) {
|
|
90
|
+
const result = [];
|
|
91
|
+
function push(model) {
|
|
92
|
+
const id = model == null ? '' : String(model).trim();
|
|
93
|
+
if (id && !result.includes(id)) result.push(id);
|
|
94
|
+
}
|
|
95
|
+
push(selectedModel);
|
|
96
|
+
if (Array.isArray(models)) {
|
|
97
|
+
models.forEach(push);
|
|
98
|
+
}
|
|
99
|
+
return result;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function applyCodexConfig(patch) {
|
|
103
|
+
const { baseUrl, model, models, apiKey } = patch || {};
|
|
104
|
+
const data = parseConfig();
|
|
105
|
+
|
|
106
|
+
const providers = ensurePlainObject(data, 'model_providers');
|
|
107
|
+
const prevProvider =
|
|
108
|
+
providers[PROVIDER_KEY] && typeof providers[PROVIDER_KEY] === 'object' && !Array.isArray(providers[PROVIDER_KEY])
|
|
109
|
+
? { ...providers[PROVIDER_KEY] }
|
|
110
|
+
: {};
|
|
111
|
+
|
|
112
|
+
const nextModel =
|
|
113
|
+
model !== undefined && String(model).trim()
|
|
114
|
+
? String(model).trim()
|
|
115
|
+
: data.model != null && String(data.model).trim()
|
|
116
|
+
? String(data.model).trim()
|
|
117
|
+
: 'gpt-5.4';
|
|
118
|
+
const nextBaseUrl =
|
|
119
|
+
baseUrl !== undefined && String(baseUrl).trim()
|
|
120
|
+
? String(baseUrl).trim()
|
|
121
|
+
: prevProvider.base_url != null && String(prevProvider.base_url).trim()
|
|
122
|
+
? String(prevProvider.base_url).trim()
|
|
123
|
+
: 'https://gw.opentoken.io/v1';
|
|
124
|
+
|
|
125
|
+
data.model = nextModel;
|
|
126
|
+
data.model_provider = PROVIDER_KEY;
|
|
127
|
+
const nextProvider = {
|
|
128
|
+
...prevProvider,
|
|
129
|
+
name: 'OpenToken',
|
|
130
|
+
base_url: nextBaseUrl,
|
|
131
|
+
wire_api: 'responses',
|
|
132
|
+
};
|
|
133
|
+
delete nextProvider.env_key;
|
|
134
|
+
providers[PROVIDER_KEY] = nextProvider;
|
|
135
|
+
|
|
136
|
+
const profileModels = uniqueModels(models, nextModel);
|
|
137
|
+
const profiles = ensurePlainObject(data, 'profiles');
|
|
138
|
+
profileModels.forEach((id) => {
|
|
139
|
+
const prevProfile =
|
|
140
|
+
profiles[id] && typeof profiles[id] === 'object' && !Array.isArray(profiles[id])
|
|
141
|
+
? { ...profiles[id] }
|
|
142
|
+
: {};
|
|
143
|
+
profiles[id] = {
|
|
144
|
+
...prevProfile,
|
|
145
|
+
model_provider: PROVIDER_KEY,
|
|
146
|
+
model: id,
|
|
147
|
+
};
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
const tui = ensurePlainObject(data, 'tui');
|
|
151
|
+
const availability = ensurePlainObject(tui, 'model_availability_nux');
|
|
152
|
+
profileModels.forEach((id) => {
|
|
153
|
+
availability[id] = MODEL_AVAILABILITY_LEVEL;
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
if (!fs.existsSync(CONFIG_DIR)) {
|
|
157
|
+
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
158
|
+
}
|
|
159
|
+
fs.writeFileSync(CONFIG_PATH, TOML.stringify(data), 'utf-8');
|
|
160
|
+
|
|
161
|
+
if (apiKey !== undefined && String(apiKey).trim()) {
|
|
162
|
+
applyCodexAuth(apiKey);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
module.exports = {
|
|
167
|
+
CONFIG_PATH,
|
|
168
|
+
AUTH_PATH,
|
|
169
|
+
PROVIDER_KEY,
|
|
170
|
+
getCurrentCodexConfig,
|
|
171
|
+
applyCodexConfig,
|
|
172
|
+
applyCodexAuth,
|
|
173
|
+
};
|
package/src/config.js
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const os = require('os');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
|
|
7
|
+
const SETTINGS_PATH = path.join(os.homedir(), '.claude', 'settings.json');
|
|
8
|
+
|
|
9
|
+
const DEFAULT_CLAUDE_AUX_ENV = {
|
|
10
|
+
API_TIMEOUT_MS: '300000',
|
|
11
|
+
CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS: '1',
|
|
12
|
+
ENABLE_TOOL_SEARCH: '0',
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
function mergeDefaultClaudeAuxEnv(env) {
|
|
16
|
+
for (const [key, value] of Object.entries(DEFAULT_CLAUDE_AUX_ENV)) {
|
|
17
|
+
if (!(key in env)) {
|
|
18
|
+
env[key] = value;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function readClaudeSettings() {
|
|
24
|
+
try {
|
|
25
|
+
if (!fs.existsSync(SETTINGS_PATH)) {
|
|
26
|
+
return {};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const raw = fs.readFileSync(SETTINGS_PATH, 'utf-8');
|
|
30
|
+
const parsed = JSON.parse(raw);
|
|
31
|
+
|
|
32
|
+
if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
33
|
+
throw new Error('settings.json 内容格式不合法');
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return parsed;
|
|
37
|
+
} catch (error) {
|
|
38
|
+
if (error.code === 'ENOENT') {
|
|
39
|
+
return {};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
throw error;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function writeClaudeSettings(settings) {
|
|
47
|
+
const dir = path.dirname(SETTINGS_PATH);
|
|
48
|
+
if (!fs.existsSync(dir)) {
|
|
49
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
fs.writeFileSync(SETTINGS_PATH, JSON.stringify(settings, null, 2), 'utf-8');
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function applyConfig(patch) {
|
|
56
|
+
const settings = readClaudeSettings();
|
|
57
|
+
|
|
58
|
+
if (!settings.env || typeof settings.env !== 'object' || Array.isArray(settings.env)) {
|
|
59
|
+
settings.env = {};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (patch.apiKey !== undefined) {
|
|
63
|
+
settings.env.ANTHROPIC_AUTH_TOKEN = patch.apiKey;
|
|
64
|
+
settings.env.ANTHROPIC_API_KEY = '';
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (patch.baseUrl !== undefined) {
|
|
68
|
+
settings.env.ANTHROPIC_BASE_URL = patch.baseUrl;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (patch.model !== undefined) {
|
|
72
|
+
settings.env.ANTHROPIC_MODEL = patch.model;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
mergeDefaultClaudeAuxEnv(settings.env);
|
|
76
|
+
writeClaudeSettings(settings);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
module.exports = {
|
|
80
|
+
SETTINGS_PATH,
|
|
81
|
+
applyConfig,
|
|
82
|
+
};
|