codexmate 0.0.42 → 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.
Files changed (34) hide show
  1. package/README.md +2 -0
  2. package/README.zh.md +2 -0
  3. package/cli/claude-proxy.js +611 -14
  4. package/cli/update.js +77 -7
  5. package/cli.js +191 -23
  6. package/package.json +2 -2
  7. package/web-ui/app.js +39 -10
  8. package/web-ui/logic.claude.mjs +65 -2
  9. package/web-ui/logic.runtime.mjs +0 -7
  10. package/web-ui/modules/app.methods.agents.mjs +6 -6
  11. package/web-ui/modules/app.methods.claude-config.mjs +65 -49
  12. package/web-ui/modules/app.methods.codex-config.mjs +10 -10
  13. package/web-ui/modules/app.methods.index.mjs +1 -1
  14. package/web-ui/modules/app.methods.install.mjs +129 -1
  15. package/web-ui/modules/app.methods.openclaw-persist.mjs +1 -1
  16. package/web-ui/modules/app.methods.providers.mjs +17 -16
  17. package/web-ui/modules/app.methods.runtime.mjs +27 -21
  18. package/web-ui/modules/app.methods.session-actions.mjs +25 -20
  19. package/web-ui/modules/app.methods.session-timeline.mjs +0 -1
  20. package/web-ui/modules/app.methods.startup-claude.mjs +29 -3
  21. package/web-ui/modules/app.methods.tool-config-permissions.mjs +3 -0
  22. package/web-ui/modules/i18n/locales/en.mjs +65 -1
  23. package/web-ui/modules/i18n/locales/ja.mjs +65 -1
  24. package/web-ui/modules/i18n/locales/vi.mjs +77 -2
  25. package/web-ui/modules/i18n/locales/zh.mjs +66 -2
  26. package/web-ui/partials/index/layout-header.html +24 -0
  27. package/web-ui/partials/index/modals-basic.html +18 -1
  28. package/web-ui/partials/index/panel-config-claude.html +5 -2
  29. package/web-ui/partials/index/panel-config-codex.html +2 -1
  30. package/web-ui/res/web-ui-render.precompiled.js +115 -22
  31. package/web-ui/styles/controls-forms.css +5 -5
  32. package/web-ui/styles/layout-shell.css +145 -0
  33. package/web-ui/styles/responsive.css +12 -0
  34. package/web-ui/styles/titles-cards.css +10 -4
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
- https.get(url, (res) => {
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(json.version || '');
89
+ finish(resolve, json.version || '');
77
90
  } catch (e) {
78
- reject(new Error('解析 NPM 响应失败'));
91
+ finish(reject, new Error('解析 NPM 响应失败'));
79
92
  }
80
93
  });
81
- }).on('error', (err) => {
82
- reject(err);
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
- assertToolConfigWriteAllowed('claude');
9440
+ async function applyToClaudeSettings(config = {}) {
9441
+ let proxyStarted = false;
9410
9442
  try {
9443
+ assertToolConfigWriteAllowed('claude');
9411
9444
  const apiKey = (config.apiKey || '').trim();
9412
- if (!apiKey) {
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 baseUrl = (config.baseUrl || 'https://open.bigmodel.cn/api/anthropic').trim();
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: apiKey,
9431
- ANTHROPIC_BASE_URL: baseUrl,
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 [baseUrl, apiKey, model] = argv;
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) {
@@ -10461,6 +10592,7 @@ function assertRequestAuthorized(req, res) {
10461
10592
 
10462
10593
  function isProtectedWebSurfacePath(requestPath) {
10463
10594
  return requestPath === '/'
10595
+ || requestPath === '/session'
10464
10596
  || requestPath === '/web-ui/index.html'
10465
10597
  || requestPath.startsWith('/web-ui/')
10466
10598
  || requestPath.startsWith('/res/');
@@ -11104,6 +11236,31 @@ function createWebServer({ htmlPath, assetsDir, webDir, host, port, openBrowser
11104
11236
  case 'install-status':
11105
11237
  result = buildInstallStatusReport();
11106
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
+ }
11107
11264
  case 'list':
11108
11265
  result = buildMcpProviderListPayload();
11109
11266
  break;
@@ -11296,7 +11453,7 @@ function createWebServer({ htmlPath, assetsDir, webDir, host, port, openBrowser
11296
11453
  result = applyClaudeSettingsRaw(params || {});
11297
11454
  break;
11298
11455
  case 'apply-claude-config':
11299
- result = applyToClaudeSettings(params.config);
11456
+ result = await applyToClaudeSettings(params.config);
11300
11457
  if (result && !result.error) {
11301
11458
  const cfgName = (params && params.config && typeof params.config.name === 'string') ? params.config.name : '';
11302
11459
  const cfgFrom = (params && typeof params.previousName === 'string') ? params.previousName : '';
@@ -11889,8 +12046,8 @@ function createWebServer({ htmlPath, assetsDir, webDir, host, port, openBrowser
11889
12046
  });
11890
12047
  fs.createReadStream(filePath).pipe(res);
11891
12048
  } else {
11892
- // Only serve HTML for root path; /web-ui returns 404.
11893
- if (requestPath === '/') {
12049
+ // Serve the SPA shell for routable entry points. Keep /web-ui as 404.
12050
+ if (requestPath === '/' || requestPath === '/session') {
11894
12051
  try {
11895
12052
  const html = readBundledWebUiHtml(htmlPath);
11896
12053
  res.writeHead(200, {
@@ -15893,9 +16050,20 @@ function createMcpTools(options = {}) {
15893
16050
  properties: {
15894
16051
  apiKey: { type: 'string' },
15895
16052
  baseUrl: { type: 'string' },
15896
- model: { type: 'string' }
16053
+ model: { type: 'string' },
16054
+ name: { type: 'string' },
16055
+ targetApi: { type: 'string' }
15897
16056
  },
15898
- required: ['apiKey'],
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
+ }],
15899
16067
  additionalProperties: false
15900
16068
  },
15901
16069
  handler: async (args = {}) => applyToClaudeSettings(args || {})
@@ -16351,7 +16519,7 @@ function printMainHelp() {
16351
16519
  console.log(' codexmate add <名称> <URL> [密钥] [--bridge <openai>]');
16352
16520
  console.log(' codexmate delete <名称> 删除提供商');
16353
16521
  console.log(' codexmate claude 等同于 claude --dangerously-skip-permissions');
16354
- console.log(' codexmate claude <BaseURL> <API密钥> [模型] 写入 Claude Code 配置');
16522
+ console.log(' codexmate claude <BaseURL> <API密钥> [模型] [--target-api responses|chat_completions|ollama] 写入 Claude Code 配置');
16355
16523
  console.log(' codexmate auth <list|import|switch|delete|status> 认证管理');
16356
16524
  console.log(' codexmate add-model <模型> 添加模型');
16357
16525
  console.log(' codexmate delete-model <模型> 删除模型');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codexmate",
3
- "version": "0.0.42",
3
+ "version": "0.0.44",
4
4
  "description": "Codex/Claude Code/OpenClaw 配置、会话与任务编排 CLI + Web 工具",
5
5
  "main": "cli.js",
6
6
  "bin": {
@@ -46,7 +46,6 @@
46
46
  },
47
47
  "dependencies": {
48
48
  "@iarna/toml": "^2.2.5",
49
- "@vue/compiler-dom": "^3.5.30",
50
49
  "json5": "^2.2.3",
51
50
  "yauzl": "^3.2.1",
52
51
  "zip-lib": "^1.2.1"
@@ -72,6 +71,7 @@
72
71
  "author": "ymkiux",
73
72
  "license": "Apache-2.0",
74
73
  "devDependencies": {
74
+ "@vue/compiler-dom": "^3.5.30",
75
75
  "vitepress": "^1.6.4"
76
76
  }
77
77
  }
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: {
@@ -366,7 +374,21 @@ document.addEventListener('DOMContentLoaded', () => {
366
374
  codexDownloadProgress: 0,
367
375
  codexDownloadTimer: null,
368
376
  settingsTab: 'general',
369
- toolConfigPermissions: { codex: false, claude: false },
377
+ toolConfigPermissions: (function() {
378
+ try {
379
+ const cached = localStorage.getItem('toolConfigPermissions');
380
+ if (cached) {
381
+ const parsed = JSON.parse(cached);
382
+ if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
383
+ return {
384
+ codex: parsed.codex === true,
385
+ claude: parsed.claude === true
386
+ };
387
+ }
388
+ }
389
+ } catch (_) {}
390
+ return { codex: false, claude: false };
391
+ })(),
370
392
  toolConfigPermissionSaving: { codex: false, claude: false },
371
393
  sessionTrashEnabled: true,
372
394
  sessionTrashItems: [],
@@ -445,13 +467,9 @@ document.addEventListener('DOMContentLoaded', () => {
445
467
  window.location.replace(url.toString());
446
468
  return;
447
469
  }
448
- // 清理任何查询参数和 hash,保持 URL /
449
- if (window.location.search || window.location.hash) {
450
- const url = new URL(window.location.href);
451
- url.search = '';
452
- url.hash = '';
453
- window.history.replaceState(null, '', url.toString());
454
- }
470
+ // Do not strip query/hash during startup: /session uses them to identify the
471
+ // standalone session, and shareable tab/filter URLs are consumed below before
472
+ // later runtime canonicalization can clean the address bar.
455
473
  } catch (_) {}
456
474
 
457
475
  if (typeof this.initI18n === 'function') {
@@ -544,6 +562,14 @@ document.addEventListener('DOMContentLoaded', () => {
544
562
  config.apiKey = '';
545
563
  config.hasKey = false;
546
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
+ }
547
573
  }
548
574
  localStorage.setItem('claudeConfigs', JSON.stringify(this.claudeConfigs));
549
575
  } catch (e) {
@@ -620,6 +646,9 @@ document.addEventListener('DOMContentLoaded', () => {
620
646
  }
621
647
  }
622
648
  }
649
+ if (typeof this.loadAppVersionStatus === 'function') {
650
+ void this.loadAppVersionStatus({ silent: true });
651
+ }
623
652
  void this.refreshClaudeSelectionFromSettings({ silent: true });
624
653
  void this.syncDefaultOpenclawConfigEntry({ silent: true });
625
654
  };