codexmate 0.0.43 → 0.0.44
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 +2 -0
- package/README.zh.md +2 -0
- package/cli/claude-proxy.js +611 -14
- package/cli/update.js +77 -7
- package/cli.js +188 -21
- package/package.json +1 -1
- package/web-ui/app.js +21 -2
- package/web-ui/logic.claude.mjs +65 -2
- package/web-ui/logic.runtime.mjs +0 -7
- package/web-ui/modules/app.methods.claude-config.mjs +28 -12
- package/web-ui/modules/app.methods.index.mjs +1 -1
- package/web-ui/modules/app.methods.install.mjs +129 -1
- package/web-ui/modules/app.methods.session-actions.mjs +7 -2
- package/web-ui/modules/app.methods.session-timeline.mjs +0 -1
- package/web-ui/modules/app.methods.startup-claude.mjs +26 -3
- package/web-ui/modules/i18n/locales/en.mjs +18 -0
- package/web-ui/modules/i18n/locales/ja.mjs +18 -0
- package/web-ui/modules/i18n/locales/vi.mjs +20 -0
- package/web-ui/modules/i18n/locales/zh.mjs +18 -0
- package/web-ui/partials/index/layout-header.html +24 -0
- package/web-ui/partials/index/modals-basic.html +18 -1
- package/web-ui/partials/index/panel-config-claude.html +4 -1
- package/web-ui/partials/index/panel-config-codex.html +2 -1
- package/web-ui/res/web-ui-render.precompiled.js +114 -21
- package/web-ui/styles/controls-forms.css +5 -5
- package/web-ui/styles/layout-shell.css +145 -0
- package/web-ui/styles/responsive.css +12 -0
package/cli/update.js
CHANGED
|
@@ -64,26 +64,92 @@ async function cmdToolUpdate(args = []) {
|
|
|
64
64
|
}
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
-
async function fetchLatestVersion() {
|
|
67
|
+
async function fetchLatestVersion(options = {}) {
|
|
68
68
|
return new Promise((resolve, reject) => {
|
|
69
|
+
const timeoutMs = Number.isFinite(Number(options.timeoutMs))
|
|
70
|
+
? Math.max(0, Number(options.timeoutMs))
|
|
71
|
+
: 5000;
|
|
69
72
|
const url = 'https://registry.npmjs.org/codexmate/latest';
|
|
70
|
-
|
|
73
|
+
let settled = false;
|
|
74
|
+
const finish = (fn, value) => {
|
|
75
|
+
if (settled) return;
|
|
76
|
+
settled = true;
|
|
77
|
+
fn(value);
|
|
78
|
+
};
|
|
79
|
+
const req = https.get(url, (res) => {
|
|
71
80
|
let data = '';
|
|
72
81
|
res.on('data', (chunk) => { data += chunk; });
|
|
73
82
|
res.on('end', () => {
|
|
74
83
|
try {
|
|
84
|
+
if (res.statusCode && (res.statusCode < 200 || res.statusCode >= 300)) {
|
|
85
|
+
finish(reject, new Error(`NPM registry returned ${res.statusCode}`));
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
75
88
|
const json = JSON.parse(data);
|
|
76
|
-
resolve
|
|
89
|
+
finish(resolve, json.version || '');
|
|
77
90
|
} catch (e) {
|
|
78
|
-
reject
|
|
91
|
+
finish(reject, new Error('解析 NPM 响应失败'));
|
|
79
92
|
}
|
|
80
93
|
});
|
|
81
|
-
})
|
|
82
|
-
|
|
94
|
+
});
|
|
95
|
+
if (timeoutMs > 0) {
|
|
96
|
+
req.setTimeout(timeoutMs, () => {
|
|
97
|
+
req.destroy(new Error('获取 NPM 最新版本超时'));
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
req.on('error', (err) => {
|
|
101
|
+
finish(reject, err);
|
|
83
102
|
});
|
|
84
103
|
});
|
|
85
104
|
}
|
|
86
105
|
|
|
106
|
+
function normalizePackageVersion(value) {
|
|
107
|
+
const normalized = typeof value === 'string' ? value.trim().replace(/^v/i, '') : '';
|
|
108
|
+
return /^\d+(?:\.\d+){0,2}(?:[-+][0-9A-Za-z.-]+)?$/.test(normalized) ? normalized : '';
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function comparePackageVersions(left, right) {
|
|
112
|
+
const normalizeParts = (value) => {
|
|
113
|
+
const normalized = normalizePackageVersion(value);
|
|
114
|
+
if (!normalized) return null;
|
|
115
|
+
return normalized.split(/[+-]/)[0].split('.').map((part) => Number.parseInt(part, 10) || 0);
|
|
116
|
+
};
|
|
117
|
+
const a = normalizeParts(left);
|
|
118
|
+
const b = normalizeParts(right);
|
|
119
|
+
if (!a || !b) return 0;
|
|
120
|
+
for (let i = 0; i < 3; i += 1) {
|
|
121
|
+
const diff = (a[i] || 0) - (b[i] || 0);
|
|
122
|
+
if (diff < 0) return -1;
|
|
123
|
+
if (diff > 0) return 1;
|
|
124
|
+
}
|
|
125
|
+
return 0;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
let latestVersionStatusCache = null;
|
|
129
|
+
|
|
130
|
+
async function fetchLatestVersionStatus(options = {}) {
|
|
131
|
+
const currentVersion = normalizePackageVersion(options.currentVersion) || String(options.currentVersion || '');
|
|
132
|
+
const timeoutMs = Number.isFinite(Number(options.timeoutMs)) ? Number(options.timeoutMs) : 5000;
|
|
133
|
+
const cacheTtlMs = Number.isFinite(Number(options.cacheTtlMs)) ? Math.max(0, Number(options.cacheTtlMs)) : 10 * 60 * 1000;
|
|
134
|
+
const now = typeof options.now === 'function' ? options.now() : Date.now();
|
|
135
|
+
if (latestVersionStatusCache && cacheTtlMs > 0 && now - latestVersionStatusCache.checkedAtMs < cacheTtlMs) {
|
|
136
|
+
return { ...latestVersionStatusCache.payload, cached: true };
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const latestVersionRaw = await fetchLatestVersion({ timeoutMs });
|
|
140
|
+
const latestVersion = normalizePackageVersion(latestVersionRaw) || String(latestVersionRaw || '');
|
|
141
|
+
const payload = {
|
|
142
|
+
currentVersion,
|
|
143
|
+
latestVersion,
|
|
144
|
+
updateAvailable: !!currentVersion && !!latestVersion && comparePackageVersions(currentVersion, latestVersion) < 0,
|
|
145
|
+
source: 'npm',
|
|
146
|
+
checkedAt: new Date(now).toISOString(),
|
|
147
|
+
cached: false
|
|
148
|
+
};
|
|
149
|
+
latestVersionStatusCache = { checkedAtMs: now, payload };
|
|
150
|
+
return payload;
|
|
151
|
+
}
|
|
152
|
+
|
|
87
153
|
function detectInstallMethod() {
|
|
88
154
|
const cliPath = path.resolve(__dirname, '..');
|
|
89
155
|
|
|
@@ -167,5 +233,9 @@ function updateViaStandalone(version) {
|
|
|
167
233
|
}
|
|
168
234
|
|
|
169
235
|
module.exports = {
|
|
170
|
-
cmdToolUpdate
|
|
236
|
+
cmdToolUpdate,
|
|
237
|
+
fetchLatestVersion,
|
|
238
|
+
fetchLatestVersionStatus,
|
|
239
|
+
normalizePackageVersion,
|
|
240
|
+
comparePackageVersions
|
|
171
241
|
};
|
package/cli.js
CHANGED
|
@@ -148,7 +148,7 @@ const {
|
|
|
148
148
|
deleteCodexSkills
|
|
149
149
|
} = require('./cli/skills');
|
|
150
150
|
const { cmdImportSkills: cmdImportSkillsFromUrl } = require('./cli/import-skills-url');
|
|
151
|
-
const { cmdToolUpdate } = require('./cli/update');
|
|
151
|
+
const { cmdToolUpdate, fetchLatestVersionStatus } = require('./cli/update');
|
|
152
152
|
const {
|
|
153
153
|
getFileStatSafe,
|
|
154
154
|
isBootstrapLikeText,
|
|
@@ -291,7 +291,11 @@ const DEFAULT_BUILTIN_CLAUDE_PROXY_SETTINGS = Object.freeze({
|
|
|
291
291
|
host: '127.0.0.1',
|
|
292
292
|
port: 8328,
|
|
293
293
|
provider: '',
|
|
294
|
+
upstreamProviderName: '',
|
|
295
|
+
upstreamBaseUrl: '',
|
|
296
|
+
upstreamApiKey: '',
|
|
294
297
|
authSource: 'provider',
|
|
298
|
+
targetApi: 'responses',
|
|
295
299
|
timeoutMs: 30000
|
|
296
300
|
});
|
|
297
301
|
const CLI_INSTALL_TARGETS = Object.freeze([
|
|
@@ -5740,7 +5744,9 @@ const {
|
|
|
5740
5744
|
HTTPS_KEEP_ALIVE_AGENT,
|
|
5741
5745
|
readConfigOrVirtualDefault,
|
|
5742
5746
|
resolveBuiltinProxyProviderName,
|
|
5743
|
-
resolveAuthTokenFromCurrentProfile
|
|
5747
|
+
resolveAuthTokenFromCurrentProfile,
|
|
5748
|
+
OPENAI_BRIDGE_SETTINGS_FILE,
|
|
5749
|
+
resolveOpenaiBridgeUpstream
|
|
5744
5750
|
});
|
|
5745
5751
|
|
|
5746
5752
|
function applyBuiltinProxyProvider(params = {}) {
|
|
@@ -8082,15 +8088,17 @@ function buildClaudeSharePayload(config = {}) {
|
|
|
8082
8088
|
const apiKey = typeof config.apiKey === 'string' ? config.apiKey : '';
|
|
8083
8089
|
const baseUrl = typeof config.baseUrl === 'string' ? config.baseUrl : '';
|
|
8084
8090
|
const model = typeof config.model === 'string' ? config.model : '';
|
|
8091
|
+
const targetApi = normalizeClaudeTargetApi(config.targetApi);
|
|
8085
8092
|
|
|
8086
8093
|
if (!baseUrl) return { error: 'Claude Base URL 未设置' };
|
|
8087
|
-
if (!apiKey) return { error: 'Claude API 密钥未设置' };
|
|
8094
|
+
if (!apiKey && targetApi !== 'ollama') return { error: 'Claude API 密钥未设置' };
|
|
8088
8095
|
|
|
8089
8096
|
return {
|
|
8090
8097
|
payload: {
|
|
8091
8098
|
baseUrl: baseUrl.trim(),
|
|
8092
8099
|
apiKey: apiKey.trim(),
|
|
8093
|
-
model: (model && model.trim()) || DEFAULT_CLAUDE_MODEL
|
|
8100
|
+
model: (model && model.trim()) || DEFAULT_CLAUDE_MODEL,
|
|
8101
|
+
targetApi
|
|
8094
8102
|
}
|
|
8095
8103
|
};
|
|
8096
8104
|
}
|
|
@@ -9404,19 +9412,93 @@ function maskKey(key) {
|
|
|
9404
9412
|
return key.substring(0, 4) + '...' + key.substring(key.length - 4);
|
|
9405
9413
|
}
|
|
9406
9414
|
|
|
9415
|
+
function normalizeClaudeTargetApi(value) {
|
|
9416
|
+
const raw = typeof value === 'string' ? value.trim().toLowerCase() : '';
|
|
9417
|
+
if (raw === 'chat_completions' || raw === 'chat-completions' || raw === 'chat/completions') {
|
|
9418
|
+
return 'chat_completions';
|
|
9419
|
+
}
|
|
9420
|
+
if (raw === 'ollama') {
|
|
9421
|
+
return 'ollama';
|
|
9422
|
+
}
|
|
9423
|
+
return 'responses';
|
|
9424
|
+
}
|
|
9425
|
+
|
|
9426
|
+
function resetBuiltinClaudeProxySavedSettingsToResponses() {
|
|
9427
|
+
const proxySettingsResult = readJsonObjectFromFile(BUILTIN_CLAUDE_PROXY_SETTINGS_FILE, DEFAULT_BUILTIN_CLAUDE_PROXY_SETTINGS);
|
|
9428
|
+
const proxySettings = proxySettingsResult.ok && proxySettingsResult.data && typeof proxySettingsResult.data === 'object' && !Array.isArray(proxySettingsResult.data)
|
|
9429
|
+
? proxySettingsResult.data
|
|
9430
|
+
: DEFAULT_BUILTIN_CLAUDE_PROXY_SETTINGS;
|
|
9431
|
+
writeJsonAtomic(BUILTIN_CLAUDE_PROXY_SETTINGS_FILE, {
|
|
9432
|
+
...DEFAULT_BUILTIN_CLAUDE_PROXY_SETTINGS,
|
|
9433
|
+
...proxySettings,
|
|
9434
|
+
enabled: false,
|
|
9435
|
+
targetApi: 'responses'
|
|
9436
|
+
});
|
|
9437
|
+
}
|
|
9438
|
+
|
|
9407
9439
|
// 应用到 Claude Code settings.json(跨平台)
|
|
9408
|
-
function applyToClaudeSettings(config = {}) {
|
|
9409
|
-
|
|
9440
|
+
async function applyToClaudeSettings(config = {}) {
|
|
9441
|
+
let proxyStarted = false;
|
|
9410
9442
|
try {
|
|
9443
|
+
assertToolConfigWriteAllowed('claude');
|
|
9411
9444
|
const apiKey = (config.apiKey || '').trim();
|
|
9412
|
-
|
|
9445
|
+
const targetApi = normalizeClaudeTargetApi(config.targetApi);
|
|
9446
|
+
if (!apiKey && targetApi !== 'ollama') {
|
|
9413
9447
|
return { success: false, mode: 'settings-file', error: '请先输入 API Key' };
|
|
9414
9448
|
}
|
|
9415
9449
|
|
|
9416
|
-
const
|
|
9450
|
+
const configuredBaseUrl = typeof config.baseUrl === 'string' ? config.baseUrl.trim() : '';
|
|
9451
|
+
const baseUrl = (configuredBaseUrl || (targetApi === 'ollama' ? 'http://127.0.0.1:11434' : 'https://open.bigmodel.cn/api/anthropic')).trim();
|
|
9417
9452
|
const model = (config.model || DEFAULT_CLAUDE_MODEL).trim();
|
|
9453
|
+
let settingsBaseUrl = baseUrl;
|
|
9454
|
+
let settingsApiKey = apiKey;
|
|
9455
|
+
let proxyResult = null;
|
|
9456
|
+
|
|
9457
|
+
if (targetApi === 'chat_completions' || targetApi === 'ollama') {
|
|
9458
|
+
const upstreamProviderName = typeof config.name === 'string' ? config.name.trim() : '';
|
|
9459
|
+
if (targetApi === 'chat_completions' && !configuredBaseUrl && !upstreamProviderName) {
|
|
9460
|
+
return {
|
|
9461
|
+
success: false,
|
|
9462
|
+
mode: 'claude-proxy',
|
|
9463
|
+
error: 'chat_completions 模式需要显式的上游 Base URL 或可解析的 provider 名称'
|
|
9464
|
+
};
|
|
9465
|
+
}
|
|
9466
|
+
await stopBuiltinClaudeProxyRuntime();
|
|
9467
|
+
const proxyToken = crypto.randomBytes(24).toString('hex');
|
|
9468
|
+
proxyResult = await startBuiltinClaudeProxyRuntime({
|
|
9469
|
+
enabled: true,
|
|
9470
|
+
host: DEFAULT_BUILTIN_CLAUDE_PROXY_SETTINGS.host,
|
|
9471
|
+
provider: upstreamProviderName,
|
|
9472
|
+
authSource: 'provider',
|
|
9473
|
+
targetApi,
|
|
9474
|
+
timeoutMs: DEFAULT_BUILTIN_CLAUDE_PROXY_SETTINGS.timeoutMs,
|
|
9475
|
+
upstreamProviderName,
|
|
9476
|
+
...(configuredBaseUrl ? { upstreamBaseUrl: configuredBaseUrl } : {}),
|
|
9477
|
+
upstreamApiKey: apiKey
|
|
9478
|
+
});
|
|
9479
|
+
if (!proxyResult || proxyResult.error || proxyResult.success === false || !proxyResult.listenUrl) {
|
|
9480
|
+
await stopBuiltinClaudeProxyRuntime();
|
|
9481
|
+
resetBuiltinClaudeProxySavedSettingsToResponses();
|
|
9482
|
+
return {
|
|
9483
|
+
success: false,
|
|
9484
|
+
mode: 'claude-proxy',
|
|
9485
|
+
error: (proxyResult && proxyResult.error) || '启动 Claude 兼容代理失败'
|
|
9486
|
+
};
|
|
9487
|
+
}
|
|
9488
|
+
proxyStarted = true;
|
|
9489
|
+
settingsBaseUrl = proxyResult.listenUrl;
|
|
9490
|
+
settingsApiKey = proxyToken;
|
|
9491
|
+
} else {
|
|
9492
|
+
await stopBuiltinClaudeProxyRuntime();
|
|
9493
|
+
resetBuiltinClaudeProxySavedSettingsToResponses();
|
|
9494
|
+
}
|
|
9495
|
+
|
|
9418
9496
|
const readResult = readJsonObjectFromFile(CLAUDE_SETTINGS_FILE, {});
|
|
9419
9497
|
if (!readResult.ok) {
|
|
9498
|
+
if (proxyStarted) {
|
|
9499
|
+
await stopBuiltinClaudeProxyRuntime();
|
|
9500
|
+
resetBuiltinClaudeProxySavedSettingsToResponses();
|
|
9501
|
+
}
|
|
9420
9502
|
return { success: false, mode: 'settings-file', error: readResult.error };
|
|
9421
9503
|
}
|
|
9422
9504
|
|
|
@@ -9427,8 +9509,8 @@ function applyToClaudeSettings(config = {}) {
|
|
|
9427
9509
|
|
|
9428
9510
|
const nextEnv = {
|
|
9429
9511
|
...currentEnv,
|
|
9430
|
-
ANTHROPIC_API_KEY:
|
|
9431
|
-
ANTHROPIC_BASE_URL:
|
|
9512
|
+
ANTHROPIC_API_KEY: settingsApiKey,
|
|
9513
|
+
ANTHROPIC_BASE_URL: settingsBaseUrl,
|
|
9432
9514
|
ANTHROPIC_MODEL: model
|
|
9433
9515
|
};
|
|
9434
9516
|
delete nextEnv.ANTHROPIC_AUTH_TOKEN;
|
|
@@ -9445,7 +9527,8 @@ function applyToClaudeSettings(config = {}) {
|
|
|
9445
9527
|
|
|
9446
9528
|
const result = {
|
|
9447
9529
|
success: true,
|
|
9448
|
-
mode: 'settings-file',
|
|
9530
|
+
mode: targetApi === 'responses' ? 'settings-file' : 'claude-proxy',
|
|
9531
|
+
targetApi,
|
|
9449
9532
|
targetPath: CLAUDE_SETTINGS_FILE,
|
|
9450
9533
|
updatedKeys: [
|
|
9451
9534
|
'env.ANTHROPIC_API_KEY',
|
|
@@ -9453,11 +9536,23 @@ function applyToClaudeSettings(config = {}) {
|
|
|
9453
9536
|
'env.ANTHROPIC_MODEL'
|
|
9454
9537
|
]
|
|
9455
9538
|
};
|
|
9539
|
+
if (proxyResult) {
|
|
9540
|
+
result.proxy = {
|
|
9541
|
+
running: true,
|
|
9542
|
+
listenUrl: proxyResult.listenUrl,
|
|
9543
|
+
upstreamProvider: proxyResult.upstreamProvider || '',
|
|
9544
|
+
mode: proxyResult.mode || (targetApi === 'ollama' ? 'anthropic-to-ollama' : 'anthropic-to-chat-completions')
|
|
9545
|
+
};
|
|
9546
|
+
}
|
|
9456
9547
|
if (backupPath) {
|
|
9457
9548
|
result.backupPath = backupPath;
|
|
9458
9549
|
}
|
|
9459
9550
|
return result;
|
|
9460
9551
|
} catch (e) {
|
|
9552
|
+
if (proxyStarted) {
|
|
9553
|
+
try { await stopBuiltinClaudeProxyRuntime(); } catch (_) {}
|
|
9554
|
+
try { resetBuiltinClaudeProxySavedSettingsToResponses(); } catch (_) {}
|
|
9555
|
+
}
|
|
9461
9556
|
return {
|
|
9462
9557
|
success: false,
|
|
9463
9558
|
mode: 'settings-file',
|
|
@@ -9570,6 +9665,40 @@ async function restoreCodexDir(payload) {
|
|
|
9570
9665
|
}
|
|
9571
9666
|
|
|
9572
9667
|
// CLI: 一行写入 Claude Code 配置
|
|
9668
|
+
function parseClaudeCommandArgs(argv = []) {
|
|
9669
|
+
const positionals = [];
|
|
9670
|
+
let targetApi = 'responses';
|
|
9671
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
9672
|
+
const token = String(argv[i] ?? '');
|
|
9673
|
+
if (token === '--target-api' || token === '--targetApi') {
|
|
9674
|
+
const nextValue = String(argv[i + 1] ?? '');
|
|
9675
|
+
if (!nextValue || nextValue.startsWith('--')) {
|
|
9676
|
+
throw new Error('错误: --target-api 需要一个值(responses、chat_completions 或 ollama)');
|
|
9677
|
+
}
|
|
9678
|
+
targetApi = normalizeClaudeTargetApi(nextValue);
|
|
9679
|
+
i += 1;
|
|
9680
|
+
continue;
|
|
9681
|
+
}
|
|
9682
|
+
positionals.push(token);
|
|
9683
|
+
}
|
|
9684
|
+
|
|
9685
|
+
const baseUrl = positionals[0];
|
|
9686
|
+
if (targetApi === 'ollama' && positionals.length === 2) {
|
|
9687
|
+
return {
|
|
9688
|
+
baseUrl,
|
|
9689
|
+
apiKey: '',
|
|
9690
|
+
model: positionals[1],
|
|
9691
|
+
targetApi
|
|
9692
|
+
};
|
|
9693
|
+
}
|
|
9694
|
+
return {
|
|
9695
|
+
baseUrl,
|
|
9696
|
+
apiKey: positionals[1],
|
|
9697
|
+
model: positionals[2],
|
|
9698
|
+
targetApi
|
|
9699
|
+
};
|
|
9700
|
+
}
|
|
9701
|
+
|
|
9573
9702
|
async function cmdClaude(args = []) {
|
|
9574
9703
|
const argv = Array.isArray(args) ? args : [];
|
|
9575
9704
|
// 无参数 → 代理启动
|
|
@@ -9577,7 +9706,7 @@ async function cmdClaude(args = []) {
|
|
|
9577
9706
|
return runProxyCommand('Claude', 'claude', [], '', { autoFlag: '--dangerously-skip-permissions' });
|
|
9578
9707
|
}
|
|
9579
9708
|
// 有参数 → 配置写入
|
|
9580
|
-
const
|
|
9709
|
+
const { baseUrl, apiKey, model, targetApi } = parseClaudeCommandArgs(argv);
|
|
9581
9710
|
const normalizedBaseUrl = typeof baseUrl === 'string' ? baseUrl.trim() : '';
|
|
9582
9711
|
const normalizedKey = typeof apiKey === 'string' ? apiKey.trim() : '';
|
|
9583
9712
|
const normalizedModel = typeof model === 'string' && model.trim()
|
|
@@ -9586,19 +9715,21 @@ async function cmdClaude(args = []) {
|
|
|
9586
9715
|
|
|
9587
9716
|
const silent = false;
|
|
9588
9717
|
|
|
9589
|
-
if (!normalizedBaseUrl || !normalizedKey) {
|
|
9718
|
+
if (!normalizedBaseUrl || (!normalizedKey && targetApi !== 'ollama')) {
|
|
9590
9719
|
if (!silent) {
|
|
9591
|
-
console.error('用法: codexmate claude <BaseURL> <API密钥> [模型]');
|
|
9720
|
+
console.error('用法: codexmate claude <BaseURL> <API密钥> [模型] [--target-api responses|chat_completions|ollama]');
|
|
9592
9721
|
console.log('\n示例:');
|
|
9593
9722
|
console.log(' codexmate claude https://open.bigmodel.cn/api/anthropic sk-ant-xxx glm-4.7');
|
|
9723
|
+
console.log(" codexmate claude http://127.0.0.1:11434 '' llama3.1:8b --target-api ollama");
|
|
9594
9724
|
}
|
|
9595
|
-
throw new Error('BaseURL 和 API 密钥必填');
|
|
9725
|
+
throw new Error(targetApi === 'ollama' ? 'BaseURL 必填' : 'BaseURL 和 API 密钥必填');
|
|
9596
9726
|
}
|
|
9597
9727
|
|
|
9598
|
-
const result = applyToClaudeSettings({
|
|
9728
|
+
const result = await applyToClaudeSettings({
|
|
9599
9729
|
baseUrl: normalizedBaseUrl,
|
|
9600
9730
|
apiKey: normalizedKey,
|
|
9601
|
-
model: normalizedModel
|
|
9731
|
+
model: normalizedModel,
|
|
9732
|
+
targetApi
|
|
9602
9733
|
});
|
|
9603
9734
|
|
|
9604
9735
|
if (!result || result.success === false) {
|
|
@@ -11105,6 +11236,31 @@ function createWebServer({ htmlPath, assetsDir, webDir, host, port, openBrowser
|
|
|
11105
11236
|
case 'install-status':
|
|
11106
11237
|
result = buildInstallStatusReport();
|
|
11107
11238
|
break;
|
|
11239
|
+
case 'version-status': {
|
|
11240
|
+
const currentVersion = (() => {
|
|
11241
|
+
try {
|
|
11242
|
+
const pkg = require('./package.json');
|
|
11243
|
+
return pkg && pkg.version ? pkg.version : '';
|
|
11244
|
+
} catch (_) {
|
|
11245
|
+
return '';
|
|
11246
|
+
}
|
|
11247
|
+
})();
|
|
11248
|
+
try {
|
|
11249
|
+
const force = !!(params && params.force);
|
|
11250
|
+
result = await fetchLatestVersionStatus({ currentVersion, timeoutMs: 2000, cacheTtlMs: force ? 0 : undefined });
|
|
11251
|
+
} catch (e) {
|
|
11252
|
+
result = {
|
|
11253
|
+
currentVersion,
|
|
11254
|
+
latestVersion: '',
|
|
11255
|
+
updateAvailable: false,
|
|
11256
|
+
source: 'npm',
|
|
11257
|
+
checkedAt: new Date().toISOString(),
|
|
11258
|
+
cached: false,
|
|
11259
|
+
error: e && e.message ? e.message : '获取最新版本失败'
|
|
11260
|
+
};
|
|
11261
|
+
}
|
|
11262
|
+
break;
|
|
11263
|
+
}
|
|
11108
11264
|
case 'list':
|
|
11109
11265
|
result = buildMcpProviderListPayload();
|
|
11110
11266
|
break;
|
|
@@ -11297,7 +11453,7 @@ function createWebServer({ htmlPath, assetsDir, webDir, host, port, openBrowser
|
|
|
11297
11453
|
result = applyClaudeSettingsRaw(params || {});
|
|
11298
11454
|
break;
|
|
11299
11455
|
case 'apply-claude-config':
|
|
11300
|
-
result = applyToClaudeSettings(params.config);
|
|
11456
|
+
result = await applyToClaudeSettings(params.config);
|
|
11301
11457
|
if (result && !result.error) {
|
|
11302
11458
|
const cfgName = (params && params.config && typeof params.config.name === 'string') ? params.config.name : '';
|
|
11303
11459
|
const cfgFrom = (params && typeof params.previousName === 'string') ? params.previousName : '';
|
|
@@ -15894,9 +16050,20 @@ function createMcpTools(options = {}) {
|
|
|
15894
16050
|
properties: {
|
|
15895
16051
|
apiKey: { type: 'string' },
|
|
15896
16052
|
baseUrl: { type: 'string' },
|
|
15897
|
-
model: { type: 'string' }
|
|
16053
|
+
model: { type: 'string' },
|
|
16054
|
+
name: { type: 'string' },
|
|
16055
|
+
targetApi: { type: 'string' }
|
|
15898
16056
|
},
|
|
15899
|
-
|
|
16057
|
+
allOf: [{
|
|
16058
|
+
if: {
|
|
16059
|
+
not: {
|
|
16060
|
+
type: 'object',
|
|
16061
|
+
properties: { targetApi: { type: 'string', pattern: '^[\\s]*[oO][lL][lL][aA][mM][aA][\\s]*$' } },
|
|
16062
|
+
required: ['targetApi']
|
|
16063
|
+
}
|
|
16064
|
+
},
|
|
16065
|
+
then: { required: ['apiKey'] }
|
|
16066
|
+
}],
|
|
15900
16067
|
additionalProperties: false
|
|
15901
16068
|
},
|
|
15902
16069
|
handler: async (args = {}) => applyToClaudeSettings(args || {})
|
|
@@ -16352,7 +16519,7 @@ function printMainHelp() {
|
|
|
16352
16519
|
console.log(' codexmate add <名称> <URL> [密钥] [--bridge <openai>]');
|
|
16353
16520
|
console.log(' codexmate delete <名称> 删除提供商');
|
|
16354
16521
|
console.log(' codexmate claude 等同于 claude --dangerously-skip-permissions');
|
|
16355
|
-
console.log(' codexmate claude <BaseURL> <API密钥> [模型] 写入 Claude Code 配置');
|
|
16522
|
+
console.log(' codexmate claude <BaseURL> <API密钥> [模型] [--target-api responses|chat_completions|ollama] 写入 Claude Code 配置');
|
|
16356
16523
|
console.log(' codexmate auth <list|import|switch|delete|status> 认证管理');
|
|
16357
16524
|
console.log(' codexmate add-model <模型> 添加模型');
|
|
16358
16525
|
console.log(' codexmate delete-model <模型> 删除模型');
|
package/package.json
CHANGED
package/web-ui/app.js
CHANGED
|
@@ -270,6 +270,12 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
270
270
|
installRegistryPreset: 'default',
|
|
271
271
|
installRegistryCustom: '',
|
|
272
272
|
installStatusTargets: null,
|
|
273
|
+
appLatestVersion: '',
|
|
274
|
+
appVersionStatusLoading: false,
|
|
275
|
+
appVersionStatusError: '',
|
|
276
|
+
appVersionStatusChecked: false,
|
|
277
|
+
appVersionStatusCheckedAt: '',
|
|
278
|
+
appVersionStatusSource: '',
|
|
273
279
|
newProvider: { name: '', url: '', key: '', model: '', useTransform: false },
|
|
274
280
|
resetConfigLoading: false,
|
|
275
281
|
editingProvider: { name: '', url: '', key: '', readOnly: false, nonEditable: false },
|
|
@@ -277,12 +283,13 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
277
283
|
currentClaudeConfig: '',
|
|
278
284
|
currentClaudeModel: '',
|
|
279
285
|
claudeCustomModelDraft: '',
|
|
280
|
-
editingConfig: { name: '', apiKey: '', baseUrl: '', model: '' },
|
|
286
|
+
editingConfig: { name: '', apiKey: '', baseUrl: '', model: '', targetApi: 'responses' },
|
|
281
287
|
claudeConfigs: {
|
|
282
288
|
'智谱GLM': {
|
|
283
289
|
apiKey: '',
|
|
284
290
|
baseUrl: 'https://open.bigmodel.cn/api/anthropic',
|
|
285
291
|
model: 'glm-4.7',
|
|
292
|
+
targetApi: 'responses',
|
|
286
293
|
hasKey: false
|
|
287
294
|
}
|
|
288
295
|
},
|
|
@@ -290,7 +297,8 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
290
297
|
name: '',
|
|
291
298
|
apiKey: '',
|
|
292
299
|
baseUrl: '',
|
|
293
|
-
model: ''
|
|
300
|
+
model: '',
|
|
301
|
+
targetApi: 'responses'
|
|
294
302
|
},
|
|
295
303
|
currentOpenclawConfig: '',
|
|
296
304
|
openclawConfigs: {
|
|
@@ -554,6 +562,14 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
554
562
|
config.apiKey = '';
|
|
555
563
|
config.hasKey = false;
|
|
556
564
|
}
|
|
565
|
+
const targetApiRaw = typeof config.targetApi === 'string' ? config.targetApi.trim().toLowerCase() : '';
|
|
566
|
+
if (targetApiRaw === 'chat_completions' || targetApiRaw === 'chat-completions' || targetApiRaw === 'chat/completions') {
|
|
567
|
+
config.targetApi = 'chat_completions';
|
|
568
|
+
} else if (targetApiRaw === 'ollama') {
|
|
569
|
+
config.targetApi = 'ollama';
|
|
570
|
+
} else {
|
|
571
|
+
config.targetApi = 'responses';
|
|
572
|
+
}
|
|
557
573
|
}
|
|
558
574
|
localStorage.setItem('claudeConfigs', JSON.stringify(this.claudeConfigs));
|
|
559
575
|
} catch (e) {
|
|
@@ -630,6 +646,9 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
630
646
|
}
|
|
631
647
|
}
|
|
632
648
|
}
|
|
649
|
+
if (typeof this.loadAppVersionStatus === 'function') {
|
|
650
|
+
void this.loadAppVersionStatus({ silent: true });
|
|
651
|
+
}
|
|
633
652
|
void this.refreshClaudeSelectionFromSettings({ silent: true });
|
|
634
653
|
void this.syncDefaultOpenclawConfigEntry({ silent: true });
|
|
635
654
|
};
|
package/web-ui/logic.claude.mjs
CHANGED
|
@@ -69,13 +69,21 @@ export function normalizeClaudeConfig(config) {
|
|
|
69
69
|
const useKey = normalizeClaudeValue(safe.useKey);
|
|
70
70
|
const externalCredentialType = normalizeClaudeValue(safe.externalCredentialType)
|
|
71
71
|
|| (apiKey ? '' : (authToken ? 'auth-token' : (useKey ? 'claude-code-use-key' : '')));
|
|
72
|
+
const targetApiRaw = normalizeClaudeValue(safe.targetApi).toLowerCase();
|
|
73
|
+
let targetApi = 'responses';
|
|
74
|
+
if (targetApiRaw === 'chat_completions' || targetApiRaw === 'chat-completions' || targetApiRaw === 'chat/completions') {
|
|
75
|
+
targetApi = 'chat_completions';
|
|
76
|
+
} else if (targetApiRaw === 'ollama') {
|
|
77
|
+
targetApi = 'ollama';
|
|
78
|
+
}
|
|
72
79
|
return {
|
|
73
80
|
apiKey,
|
|
74
81
|
baseUrl: normalizeClaudeValue(safe.baseUrl),
|
|
75
82
|
model: normalizeClaudeValue(safe.model),
|
|
76
83
|
authToken,
|
|
77
84
|
useKey,
|
|
78
|
-
externalCredentialType
|
|
85
|
+
externalCredentialType,
|
|
86
|
+
targetApi
|
|
79
87
|
};
|
|
80
88
|
}
|
|
81
89
|
|
|
@@ -102,6 +110,60 @@ function normalizeClaudeComparableUrl(value) {
|
|
|
102
110
|
return trimmed.replace(/\/+$/g, '');
|
|
103
111
|
}
|
|
104
112
|
|
|
113
|
+
function isLoopbackClaudeProxyUrl(value) {
|
|
114
|
+
const raw = normalizeClaudeComparableUrl(value);
|
|
115
|
+
if (!raw) return false;
|
|
116
|
+
try {
|
|
117
|
+
const parsed = new URL(raw);
|
|
118
|
+
if (parsed.protocol !== 'http:') return false;
|
|
119
|
+
const host = normalizeClaudeValue(parsed.hostname).toLowerCase();
|
|
120
|
+
return host === '127.0.0.1' || host === 'localhost' || host === '[::1]' || host === '::1';
|
|
121
|
+
} catch (_) {
|
|
122
|
+
return false;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export function isLikelyBuiltinClaudeProxySettingsEnv(env = {}) {
|
|
127
|
+
const normalized = normalizeClaudeSettingsEnv(env);
|
|
128
|
+
return !!(
|
|
129
|
+
normalized.baseUrl
|
|
130
|
+
&& normalized.model
|
|
131
|
+
&& /^[a-f0-9]{48}$/i.test(normalized.apiKey)
|
|
132
|
+
&& isLoopbackClaudeProxyUrl(normalized.baseUrl)
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function isClaudeTransformConfig(config = {}) {
|
|
137
|
+
const targetApi = normalizeClaudeConfig(config).targetApi;
|
|
138
|
+
return targetApi === 'chat_completions' || targetApi === 'ollama';
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export function matchBuiltinClaudeProxyConfigFromSettings(claudeConfigs = {}, env = {}, preferredName = '') {
|
|
142
|
+
if (!isLikelyBuiltinClaudeProxySettingsEnv(env)) {
|
|
143
|
+
return '';
|
|
144
|
+
}
|
|
145
|
+
const normalizedSettings = normalizeClaudeSettingsEnv(env);
|
|
146
|
+
const preferred = normalizeClaudeValue(preferredName);
|
|
147
|
+
if (preferred && claudeConfigs && claudeConfigs[preferred]) {
|
|
148
|
+
const config = normalizeClaudeConfig(claudeConfigs[preferred]);
|
|
149
|
+
if (isClaudeTransformConfig(config) && config.model === normalizedSettings.model) {
|
|
150
|
+
return preferred;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const matches = [];
|
|
155
|
+
for (const [name, config] of Object.entries(claudeConfigs || {})) {
|
|
156
|
+
const normalizedConfig = normalizeClaudeConfig(config);
|
|
157
|
+
if (!isClaudeTransformConfig(normalizedConfig)) {
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
if (normalizedConfig.model === normalizedSettings.model) {
|
|
161
|
+
matches.push(name);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
return matches.length === 1 ? matches[0] : '';
|
|
165
|
+
}
|
|
166
|
+
|
|
105
167
|
function hasClaudeCredential(config = {}) {
|
|
106
168
|
return !!(config.apiKey || config.authToken || config.useKey);
|
|
107
169
|
}
|
|
@@ -156,7 +218,8 @@ export function findDuplicateClaudeConfigName(claudeConfigs = {}, config) {
|
|
|
156
218
|
continue;
|
|
157
219
|
}
|
|
158
220
|
if (normalizeClaudeComparableUrl(normalizedExisting.baseUrl) !== comparableUrl
|
|
159
|
-
|| normalizedExisting.model !== normalized.model
|
|
221
|
+
|| normalizedExisting.model !== normalized.model
|
|
222
|
+
|| normalizedExisting.targetApi !== normalized.targetApi) {
|
|
160
223
|
continue;
|
|
161
224
|
}
|
|
162
225
|
if (normalized.apiKey && normalizedExisting.apiKey === normalized.apiKey) {
|
package/web-ui/logic.runtime.mjs
CHANGED
|
@@ -101,10 +101,6 @@ export function shouldForceCompactLayoutMode(options = {}) {
|
|
|
101
101
|
const screenHeight = Number(options.screenHeight || 0);
|
|
102
102
|
const shortEdge = Number(options.shortEdge || (screenWidth > 0 && screenHeight > 0 ? Math.min(screenWidth, screenHeight) : 0));
|
|
103
103
|
const maxTouchPoints = Number(options.maxTouchPoints || 0);
|
|
104
|
-
const userAgent = typeof options.userAgent === 'string' ? options.userAgent : '';
|
|
105
|
-
const isMobileUa = typeof options.isMobileUa === 'boolean'
|
|
106
|
-
? options.isMobileUa
|
|
107
|
-
: /(Android|iPhone|iPad|iPod|Mobile)/i.test(userAgent);
|
|
108
104
|
const coarsePointer = !!options.coarsePointer;
|
|
109
105
|
const noHover = !!options.noHover;
|
|
110
106
|
const isSmallPhysicalScreen = shortEdge > 0 && shortEdge <= 920;
|
|
@@ -115,9 +111,6 @@ export function shouldForceCompactLayoutMode(options = {}) {
|
|
|
115
111
|
if (!isNarrowViewport) {
|
|
116
112
|
return false;
|
|
117
113
|
}
|
|
118
|
-
if (isMobileUa) {
|
|
119
|
-
return true;
|
|
120
|
-
}
|
|
121
114
|
return pointerSuggestsTouchOnly && maxTouchPoints > 0;
|
|
122
115
|
}
|
|
123
116
|
|