codexmate 0.0.54 → 0.0.56

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 (42) hide show
  1. package/README.md +3 -0
  2. package/README.vi.md +172 -0
  3. package/README.zh.md +2 -0
  4. package/cli/local-bridge.js +221 -22
  5. package/cli.js +666 -6
  6. package/package.json +1 -1
  7. package/web-ui/app.js +40 -0
  8. package/web-ui/logic.claude.mjs +8 -0
  9. package/web-ui/modules/app.computed.session.mjs +210 -0
  10. package/web-ui/modules/app.methods.claude-config.mjs +196 -13
  11. package/web-ui/modules/app.methods.codex-config.mjs +294 -65
  12. package/web-ui/modules/app.methods.index.mjs +4 -0
  13. package/web-ui/modules/app.methods.navigation.mjs +7 -1
  14. package/web-ui/modules/app.methods.provider-cache.mjs +223 -0
  15. package/web-ui/modules/app.methods.providers.mjs +15 -1
  16. package/web-ui/modules/app.methods.runtime.mjs +7 -2
  17. package/web-ui/modules/app.methods.session-actions.mjs +36 -0
  18. package/web-ui/modules/app.methods.session-browser.mjs +3 -0
  19. package/web-ui/modules/app.methods.session-trash.mjs +3 -0
  20. package/web-ui/modules/app.methods.startup-claude.mjs +47 -4
  21. package/web-ui/modules/app.methods.web-ui-preferences.mjs +161 -0
  22. package/web-ui/modules/i18n/locales/en.mjs +106 -3
  23. package/web-ui/modules/i18n/locales/ja.mjs +103 -3
  24. package/web-ui/modules/i18n/locales/vi.mjs +950 -0
  25. package/web-ui/modules/i18n/locales/zh-tw.mjs +103 -3
  26. package/web-ui/modules/i18n/locales/zh.mjs +103 -3
  27. package/web-ui/modules/provider-default-names.mjs +25 -0
  28. package/web-ui/partials/index/layout-header.html +18 -5
  29. package/web-ui/partials/index/modal-health-check.html +69 -5
  30. package/web-ui/partials/index/modals-basic.html +182 -0
  31. package/web-ui/partials/index/panel-config-codex.html +2 -2
  32. package/web-ui/partials/index/panel-sessions.html +99 -19
  33. package/web-ui/partials/index/panel-settings.html +20 -0
  34. package/web-ui/res/web-ui-render.precompiled.js +742 -113
  35. package/web-ui/session-helpers.mjs +4 -1
  36. package/web-ui/styles/layout-shell.css +51 -1
  37. package/web-ui/styles/responsive.css +98 -0
  38. package/web-ui/styles/sessions-preview.css +212 -2
  39. package/web-ui/styles/sessions-toolbar-trash.css +61 -18
  40. package/web-ui/styles/settings-panel.css +398 -0
  41. package/web-ui/styles/skills-list.css +122 -0
  42. package/web-ui/styles/titles-cards.css +52 -0
@@ -1,3 +1,5 @@
1
+ import { nextClaudeConfigName } from './provider-default-names.mjs';
2
+
1
3
  function normalizeClaudeText(value) {
2
4
  return typeof value === 'string' ? value.trim() : '';
3
5
  }
@@ -6,6 +8,21 @@ function normalizeClaudeBaseUrl(value) {
6
8
  return normalizeClaudeText(value).replace(/\/+$/g, '');
7
9
  }
8
10
 
11
+ const DELETED_CLAUDE_SETTINGS_IMPORTS_KEY = 'deletedClaudeSettingsImports';
12
+
13
+ function buildDeletedClaudeSettingsFingerprint(config = {}) {
14
+ const safe = config && typeof config === 'object' ? config : {};
15
+ const baseUrl = normalizeClaudeBaseUrl(safe.baseUrl);
16
+ const model = normalizeClaudeText(safe.model);
17
+ if (!baseUrl || !model) return null;
18
+ return {
19
+ baseUrl,
20
+ model,
21
+ providerCacheRef: normalizeClaudeText(safe.providerCacheRef),
22
+ deletedAt: Date.now()
23
+ };
24
+ }
25
+
9
26
  function isValidClaudeHttpUrl(value) {
10
27
  if (!value) return false;
11
28
  try {
@@ -20,6 +37,7 @@ function getClaudeConfigValidationForContext(vm, mode = 'add') {
20
37
  const draft = mode === 'edit' ? vm.editingConfig : vm.newClaudeConfig;
21
38
  const name = normalizeClaudeText(draft && draft.name);
22
39
  const apiKey = normalizeClaudeText(draft && draft.apiKey);
40
+ const providerCacheRef = normalizeClaudeText(draft && draft.providerCacheRef);
23
41
  const externalCredentialType = normalizeClaudeText(draft && draft.externalCredentialType);
24
42
  const baseUrl = normalizeClaudeBaseUrl(draft && draft.baseUrl);
25
43
  const model = normalizeClaudeText(draft && draft.model);
@@ -40,7 +58,7 @@ function getClaudeConfigValidationForContext(vm, mode = 'add') {
40
58
  errors.name = vm.t('validation.claude.nameExists');
41
59
  }
42
60
 
43
- if (!apiKey && !externalCredentialType && targetApi !== 'ollama') {
61
+ if (!apiKey && !externalCredentialType && !providerCacheRef && targetApi !== 'ollama') {
44
62
  errors.apiKey = vm.t('validation.claude.apiKeyRequired');
45
63
  }
46
64
 
@@ -58,6 +76,7 @@ function getClaudeConfigValidationForContext(vm, mode = 'add') {
58
76
  mode,
59
77
  name,
60
78
  apiKey,
79
+ providerCacheRef,
61
80
  externalCredentialType,
62
81
  baseUrl,
63
82
  model,
@@ -112,6 +131,122 @@ export function createClaudeConfigMethods(options = {}) {
112
131
  this.syncClaudeBridgeProviders();
113
132
  },
114
133
 
134
+ rememberDeletedClaudeSettingsImport(config) {
135
+ const fingerprint = buildDeletedClaudeSettingsFingerprint(config);
136
+ if (!fingerprint) return;
137
+ try {
138
+ const raw = localStorage.getItem(DELETED_CLAUDE_SETTINGS_IMPORTS_KEY);
139
+ const parsed = raw ? JSON.parse(raw) : [];
140
+ const entries = Array.isArray(parsed) ? parsed : [];
141
+ const deduped = entries.filter((entry) => {
142
+ if (!entry || typeof entry !== 'object') return false;
143
+ return normalizeClaudeBaseUrl(entry.baseUrl) !== fingerprint.baseUrl
144
+ || normalizeClaudeText(entry.model) !== fingerprint.model;
145
+ });
146
+ deduped.push(fingerprint);
147
+ localStorage.setItem(DELETED_CLAUDE_SETTINGS_IMPORTS_KEY, JSON.stringify(deduped.slice(-50)));
148
+ } catch (_) {}
149
+ },
150
+
151
+ async applyCurrentClaudeConfigSilently() {
152
+ const name = normalizeClaudeText(this.currentClaudeConfig);
153
+ if (!name || !this.claudeConfigs || !this.claudeConfigs[name]) return false;
154
+ if (typeof this.applyClaudeConfig !== 'function') return false;
155
+ return await this.applyClaudeConfig(name, { silent: true });
156
+ },
157
+
158
+ selectClaudeFallbackConfigName(excludedNames = []) {
159
+ const configs = this.claudeConfigs && typeof this.claudeConfigs === 'object' ? this.claudeConfigs : {};
160
+ const excluded = new Set((Array.isArray(excludedNames) ? excludedNames : [excludedNames])
161
+ .map((name) => normalizeClaudeText(name))
162
+ .filter(Boolean));
163
+ const names = Object.keys(configs).filter((name) => name && !excluded.has(name));
164
+ const applyable = names.find((name) => {
165
+ const config = configs[name] || {};
166
+ return !!(config.apiKey
167
+ || config.providerCacheRef
168
+ || config.externalCredentialType
169
+ || config.targetApi === 'ollama');
170
+ });
171
+ return applyable || names[0] || '';
172
+ },
173
+
174
+ async hydrateClaudeConfigsFromProviderCache(options = {}) {
175
+ const silent = options && options.silent === true;
176
+ try {
177
+ const res = await api('get-claude-provider-cache-configs');
178
+ if (!res || res.error) {
179
+ if (!silent) this.showMessage((res && res.error) || this.t('toast.claude.loadSettingsFail'), 'error');
180
+ return false;
181
+ }
182
+ const providers = Array.isArray(res.providers) ? res.providers : [];
183
+ const configs = this.claudeConfigs && typeof this.claudeConfigs === 'object' ? this.claudeConfigs : {};
184
+ let changed = false;
185
+ let firstCachedName = '';
186
+ const liveCacheRefs = new Set();
187
+ for (const provider of providers) {
188
+ if (!provider || typeof provider !== 'object') continue;
189
+ const name = normalizeClaudeText(provider.name);
190
+ const baseUrl = normalizeClaudeBaseUrl(provider.baseUrl);
191
+ const model = normalizeClaudeText(provider.model);
192
+ if (!name || !baseUrl || !model) continue;
193
+ if (!firstCachedName) firstCachedName = name;
194
+ const providerCacheRef = normalizeClaudeText(provider.providerCacheRef) || name;
195
+ liveCacheRefs.add(name);
196
+ liveCacheRefs.add(providerCacheRef);
197
+ const cachedConfig = {
198
+ apiKey: '',
199
+ baseUrl,
200
+ model,
201
+ hasKey: provider.hasKey === true,
202
+ providerCacheRef,
203
+ source: 'provider-cache',
204
+ targetApi: normalizeClaudeText(provider.targetApi) || 'responses'
205
+ };
206
+ const existing = configs[name];
207
+ if (existing && existing.source !== 'provider-cache' && existing.providerCacheRef !== cachedConfig.providerCacheRef) {
208
+ continue;
209
+ }
210
+ if (JSON.stringify(existing || {}) !== JSON.stringify(cachedConfig)) {
211
+ configs[name] = cachedConfig;
212
+ changed = true;
213
+ }
214
+ }
215
+ for (const [name, existing] of Object.entries(configs)) {
216
+ if (!existing || typeof existing !== 'object') continue;
217
+ const providerCacheRef = normalizeClaudeText(existing.providerCacheRef);
218
+ const source = normalizeClaudeText(existing.source);
219
+ const cacheBacked = source === 'provider-cache' || !!providerCacheRef;
220
+ if (!cacheBacked) continue;
221
+ const ref = providerCacheRef || normalizeClaudeText(name);
222
+ if (liveCacheRefs.has(ref) || liveCacheRefs.has(normalizeClaudeText(name))) continue;
223
+ delete configs[name];
224
+ changed = true;
225
+ }
226
+ this.claudeConfigs = configs;
227
+ const current = normalizeClaudeText(this.currentClaudeConfig);
228
+ if (current && !configs[current]) {
229
+ this.currentClaudeConfig = firstCachedName || Object.keys(configs)[0] || '';
230
+ try { localStorage.setItem('currentClaudeConfig', this.currentClaudeConfig); } catch (_) {}
231
+ changed = true;
232
+ } else if (firstCachedName) {
233
+ let savedCurrent = '';
234
+ try { savedCurrent = localStorage.getItem('currentClaudeConfig') || ''; } catch (_) {}
235
+ const currentConfig = current && configs[current] ? configs[current] : null;
236
+ if (!savedCurrent && (!current || (currentConfig && currentConfig.hasKey === false && !currentConfig.providerCacheRef))) {
237
+ this.currentClaudeConfig = firstCachedName;
238
+ try { localStorage.setItem('currentClaudeConfig', firstCachedName); } catch (_) {}
239
+ changed = true;
240
+ }
241
+ }
242
+ if (changed) this.saveClaudeConfigs();
243
+ return true;
244
+ } catch (e) {
245
+ if (!silent) this.showMessage(e && e.message ? e.message : this.t('toast.claude.loadSettingsFail'), 'error');
246
+ return false;
247
+ }
248
+ },
249
+
115
250
  async syncClaudeBridgeProviders() {
116
251
  try { await api('claude-local-bridge-sync-providers', { providers: this.claudeConfigs || {} }); } catch (_) {}
117
252
  },
@@ -154,6 +289,7 @@ export function createClaudeConfigMethods(options = {}) {
154
289
  model: config.model || '',
155
290
  targetApi: config.targetApi || 'responses'
156
291
  };
292
+ if (config.providerCacheRef) this.editingConfig.providerCacheRef = config.providerCacheRef;
157
293
  this.showEditClaudeConfigKey = false;
158
294
  this.showEditConfigModal = true;
159
295
  },
@@ -165,6 +301,8 @@ export function createClaudeConfigMethods(options = {}) {
165
301
  }
166
302
  const name = validation.name;
167
303
  this.editingConfig.apiKey = validation.apiKey;
304
+ if (validation.providerCacheRef) this.editingConfig.providerCacheRef = validation.providerCacheRef;
305
+ else delete this.editingConfig.providerCacheRef;
168
306
  this.editingConfig.externalCredentialType = validation.externalCredentialType;
169
307
  this.editingConfig.baseUrl = validation.baseUrl;
170
308
  this.editingConfig.model = validation.model;
@@ -195,6 +333,8 @@ export function createClaudeConfigMethods(options = {}) {
195
333
  }
196
334
  const name = validation.name;
197
335
  this.editingConfig.apiKey = validation.apiKey;
336
+ if (validation.providerCacheRef) this.editingConfig.providerCacheRef = validation.providerCacheRef;
337
+ else delete this.editingConfig.providerCacheRef;
198
338
  this.editingConfig.externalCredentialType = validation.externalCredentialType;
199
339
  this.editingConfig.baseUrl = validation.baseUrl;
200
340
  this.editingConfig.model = validation.model;
@@ -203,7 +343,7 @@ export function createClaudeConfigMethods(options = {}) {
203
343
  this.saveClaudeConfigs();
204
344
 
205
345
  const config = this.claudeConfigs[name];
206
- if (!config.apiKey && config.targetApi !== 'ollama') {
346
+ if (!config.apiKey && !config.providerCacheRef && config.targetApi !== 'ollama') {
207
347
  this.showMessage(this.t('toast.claude.savedWithoutKey'), 'info');
208
348
  this.closeEditConfigModal();
209
349
  if (name === this.currentClaudeConfig) {
@@ -212,7 +352,7 @@ export function createClaudeConfigMethods(options = {}) {
212
352
  return;
213
353
  }
214
354
 
215
- const _claudeKey = `${name}|${config.apiKey || ""}|${config.baseUrl || ""}|${config.model || ""}|${config.targetApi || "responses"}`;
355
+ const _claudeKey = `${name}|${config.apiKey || ""}|${config.providerCacheRef || ""}|${config.baseUrl || ""}|${config.model || ""}|${config.targetApi || "responses"}`;
216
356
  try {
217
357
  const res = await api('apply-claude-config', { config: { ...config, name } });
218
358
  if (res.error || res.success === false) {
@@ -238,6 +378,8 @@ export function createClaudeConfigMethods(options = {}) {
238
378
  }
239
379
  this.newClaudeConfig.name = validation.name;
240
380
  this.newClaudeConfig.apiKey = validation.apiKey;
381
+ if (validation.providerCacheRef) this.newClaudeConfig.providerCacheRef = validation.providerCacheRef;
382
+ else delete this.newClaudeConfig.providerCacheRef;
241
383
  this.newClaudeConfig.externalCredentialType = validation.externalCredentialType;
242
384
  this.newClaudeConfig.baseUrl = validation.baseUrl;
243
385
  this.newClaudeConfig.model = validation.model;
@@ -256,6 +398,28 @@ export function createClaudeConfigMethods(options = {}) {
256
398
  await this.applyClaudeConfig(name);
257
399
  },
258
400
 
401
+ async deleteClaudeProviderCacheRef(configOrName) {
402
+ const config = configOrName && typeof configOrName === 'object'
403
+ ? configOrName
404
+ : (this.claudeConfigs && this.claudeConfigs[configOrName] ? this.claudeConfigs[configOrName] : null);
405
+ const ref = normalizeClaudeText(config && config.providerCacheRef);
406
+ const source = normalizeClaudeText(config && config.source);
407
+ if (!ref && source !== 'provider-cache') return true;
408
+ const name = ref || normalizeClaudeText(config && config.name) || normalizeClaudeText(configOrName);
409
+ if (!name) return true;
410
+ try {
411
+ const res = await api('delete-provider-cache-record', { name, group: 'claude' });
412
+ if (res && res.error) {
413
+ this.showMessage(res.error, 'error');
414
+ return false;
415
+ }
416
+ return true;
417
+ } catch (e) {
418
+ this.showMessage(e && e.message ? e.message : this.t('toast.operation.fail'), 'error');
419
+ return false;
420
+ }
421
+ },
422
+
259
423
  async deleteClaudeConfig(name) {
260
424
  if (Object.keys(this.claudeConfigs).length <= 1) {
261
425
  return this.showMessage(this.t('toast.claude.keepOne'), 'error');
@@ -269,41 +433,60 @@ export function createClaudeConfigMethods(options = {}) {
269
433
  });
270
434
  if (!confirmed) return;
271
435
 
436
+ const config = this.claudeConfigs[name];
437
+ const cacheDeleted = await this.deleteClaudeProviderCacheRef(config || name);
438
+ if (!cacheDeleted) return;
439
+
440
+ if (typeof this.rememberDeletedClaudeSettingsImport === 'function') {
441
+ this.rememberDeletedClaudeSettingsImport(config);
442
+ }
272
443
  delete this.claudeConfigs[name];
273
444
  if (this.currentClaudeConfig === name) {
274
- this.currentClaudeConfig = Object.keys(this.claudeConfigs)[0];
445
+ this.currentClaudeConfig = this.selectClaudeFallbackConfigName();
275
446
  }
276
447
  this.saveClaudeConfigs();
277
448
  this.showMessage(this.t('toast.operation.success'), 'success');
278
- this.refreshClaudeModelContext();
449
+ if (this.currentClaudeConfig) {
450
+ await this.applyCurrentClaudeConfigSilently();
451
+ } else {
452
+ this.refreshClaudeModelContext();
453
+ }
279
454
  },
280
455
 
281
- async applyClaudeConfig(name) {
456
+ async applyClaudeConfig(name, options = {}) {
457
+ const silent = !!(options && options.silent);
458
+ const silentSuccess = silent || !!(options && options.silentSuccess);
459
+ const silentError = silent || !!(options && options.silentError);
282
460
  this.currentClaudeConfig = name;
283
461
  try { localStorage.setItem('currentClaudeConfig', name || ''); } catch (_) {}
284
462
  this.refreshClaudeModelContext();
285
463
  const config = this.claudeConfigs[name];
286
464
 
287
- if (!config.apiKey && config.targetApi !== 'ollama') {
465
+ if (!config.apiKey && !config.providerCacheRef && config.targetApi !== 'ollama') {
288
466
  if (config.externalCredentialType) {
467
+ if (silentError) return false;
289
468
  return this.showMessage(this.t('toast.claude.externalAuth'), 'info');
290
469
  }
470
+ if (silentError) return false;
291
471
  return this.showMessage(this.t('toast.claude.apiKeyRequired'), 'error');
292
472
  }
293
473
 
294
- const _claudeKey2 = `${name}|${config.apiKey || ""}|${config.baseUrl || ""}|${config.model || ""}|${config.targetApi || "responses"}`;
474
+ const _claudeKey2 = `${name}|${config.apiKey || ""}|${config.providerCacheRef || ""}|${config.baseUrl || ""}|${config.model || ""}|${config.targetApi || "responses"}`;
295
475
  try {
296
476
  const res = await api('apply-claude-config', { config: { ...config, name } });
297
477
  if (res.error || res.success === false) {
298
- this.showMessage(res.error || this.t('toast.apply.fail'), 'error');
478
+ if (!silentError) this.showMessage(res.error || this.t('toast.apply.fail'), 'error');
479
+ return false;
299
480
  } else {
300
- if (this._lastAppliedClaudeKey !== _claudeKey2) {
481
+ if (!silentSuccess && this._lastAppliedClaudeKey !== _claudeKey2) {
301
482
  this.showMessage(this.t('toast.apply.success'), 'success');
302
- this._lastAppliedClaudeKey = _claudeKey2;
303
483
  }
484
+ this._lastAppliedClaudeKey = _claudeKey2;
485
+ return true;
304
486
  }
305
487
  } catch (_) {
306
- this.showMessage(this.t('toast.apply.fail'), 'error');
488
+ if (!silentError) this.showMessage(this.t('toast.apply.fail'), 'error');
489
+ return false;
307
490
  }
308
491
  },
309
492
 
@@ -311,7 +494,7 @@ export function createClaudeConfigMethods(options = {}) {
311
494
  this.showClaudeConfigModal = false;
312
495
  this.showAddClaudeConfigKey = false;
313
496
  this.newClaudeConfig = {
314
- name: '',
497
+ name: nextClaudeConfigName(this.claudeConfigs),
315
498
  apiKey: '',
316
499
  externalCredentialType: '',
317
500
  baseUrl: '',