codexmate 0.0.20 → 0.0.21

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 (102) hide show
  1. package/README.en.md +349 -259
  2. package/README.md +284 -252
  3. package/cli/agents-files.js +162 -0
  4. package/cli/archive-helpers.js +446 -0
  5. package/cli/auth-profiles.js +359 -0
  6. package/cli/builtin-proxy.js +580 -0
  7. package/cli/claude-proxy.js +998 -0
  8. package/cli/config-bootstrap.js +384 -0
  9. package/cli/config-health.js +338 -338
  10. package/cli/openclaw-config.js +629 -0
  11. package/cli/skills.js +1141 -0
  12. package/cli/zip-commands.js +510 -0
  13. package/cli.js +13101 -13497
  14. package/lib/cli-file-utils.js +151 -151
  15. package/lib/cli-models-utils.js +419 -311
  16. package/lib/cli-network-utils.js +164 -164
  17. package/lib/cli-path-utils.js +69 -0
  18. package/lib/cli-session-utils.js +121 -121
  19. package/lib/cli-sessions.js +386 -0
  20. package/lib/cli-utils.js +155 -155
  21. package/lib/download-artifacts.js +77 -0
  22. package/lib/mcp-stdio.js +440 -440
  23. package/lib/task-orchestrator.js +869 -0
  24. package/lib/text-diff.js +303 -303
  25. package/lib/workflow-engine.js +340 -340
  26. package/package.json +74 -70
  27. package/res/json5.min.js +1 -1
  28. package/res/vue.global.prod.js +13 -0
  29. package/web-ui/app.js +530 -397
  30. package/web-ui/index.html +33 -30
  31. package/web-ui/logic.agents-diff.mjs +386 -386
  32. package/web-ui/logic.claude.mjs +168 -108
  33. package/web-ui/logic.mjs +5 -5
  34. package/web-ui/logic.runtime.mjs +124 -124
  35. package/web-ui/logic.sessions.mjs +581 -263
  36. package/web-ui/modules/api.mjs +90 -69
  37. package/web-ui/modules/app.computed.dashboard.mjs +113 -113
  38. package/web-ui/modules/app.computed.index.mjs +15 -13
  39. package/web-ui/modules/app.computed.main-tabs.mjs +195 -0
  40. package/web-ui/modules/app.computed.session.mjs +507 -141
  41. package/web-ui/modules/app.constants.mjs +15 -15
  42. package/web-ui/modules/app.methods.agents.mjs +493 -493
  43. package/web-ui/modules/app.methods.claude-config.mjs +174 -174
  44. package/web-ui/modules/app.methods.codex-config.mjs +640 -640
  45. package/web-ui/modules/app.methods.index.mjs +88 -86
  46. package/web-ui/modules/app.methods.install.mjs +149 -157
  47. package/web-ui/modules/app.methods.navigation.mjs +619 -478
  48. package/web-ui/modules/app.methods.openclaw-core.mjs +814 -514
  49. package/web-ui/modules/app.methods.openclaw-editing.mjs +372 -337
  50. package/web-ui/modules/app.methods.openclaw-persist.mjs +369 -251
  51. package/web-ui/modules/app.methods.providers.mjs +363 -265
  52. package/web-ui/modules/app.methods.runtime.mjs +323 -323
  53. package/web-ui/modules/app.methods.session-actions.mjs +520 -457
  54. package/web-ui/modules/app.methods.session-browser.mjs +626 -435
  55. package/web-ui/modules/app.methods.session-timeline.mjs +448 -441
  56. package/web-ui/modules/app.methods.session-trash.mjs +422 -419
  57. package/web-ui/modules/app.methods.startup-claude.mjs +412 -406
  58. package/web-ui/modules/app.methods.task-orchestration.mjs +471 -0
  59. package/web-ui/modules/config-mode.computed.mjs +126 -124
  60. package/web-ui/modules/skills.computed.mjs +107 -107
  61. package/web-ui/modules/skills.methods.mjs +481 -481
  62. package/web-ui/partials/index/layout-footer.html +13 -69
  63. package/web-ui/partials/index/layout-header.html +402 -337
  64. package/web-ui/partials/index/modal-config-template-agents.html +125 -125
  65. package/web-ui/partials/index/modal-confirm-toast.html +32 -32
  66. package/web-ui/partials/index/modal-health-check.html +72 -72
  67. package/web-ui/partials/index/modal-openclaw-config.html +280 -275
  68. package/web-ui/partials/index/modal-skills.html +184 -184
  69. package/web-ui/partials/index/modals-basic.html +156 -196
  70. package/web-ui/partials/index/panel-config-claude.html +126 -100
  71. package/web-ui/partials/index/panel-config-codex.html +237 -237
  72. package/web-ui/partials/index/panel-config-openclaw.html +78 -84
  73. package/web-ui/partials/index/panel-docs.html +130 -0
  74. package/web-ui/partials/index/panel-market.html +174 -174
  75. package/web-ui/partials/index/panel-orchestration.html +397 -0
  76. package/web-ui/partials/index/panel-sessions.html +292 -387
  77. package/web-ui/partials/index/panel-settings.html +190 -166
  78. package/web-ui/partials/index/panel-usage.html +213 -0
  79. package/web-ui/session-helpers.mjs +559 -362
  80. package/web-ui/source-bundle.cjs +233 -233
  81. package/web-ui/styles/base-theme.css +271 -373
  82. package/web-ui/styles/controls-forms.css +360 -354
  83. package/web-ui/styles/docs-panel.css +182 -0
  84. package/web-ui/styles/feedback.css +108 -108
  85. package/web-ui/styles/health-check-dialog.css +144 -144
  86. package/web-ui/styles/layout-shell.css +376 -330
  87. package/web-ui/styles/modals-core.css +464 -449
  88. package/web-ui/styles/navigation-panels.css +348 -381
  89. package/web-ui/styles/openclaw-structured.css +266 -266
  90. package/web-ui/styles/responsive.css +450 -416
  91. package/web-ui/styles/sessions-list.css +400 -414
  92. package/web-ui/styles/sessions-preview.css +411 -405
  93. package/web-ui/styles/sessions-toolbar-trash.css +243 -243
  94. package/web-ui/styles/sessions-usage.css +628 -276
  95. package/web-ui/styles/skills-list.css +296 -298
  96. package/web-ui/styles/skills-market.css +335 -335
  97. package/web-ui/styles/task-orchestration.css +776 -0
  98. package/web-ui/styles/titles-cards.css +408 -407
  99. package/web-ui/styles.css +18 -16
  100. package/web-ui.html +17 -17
  101. package/res/screenshot.png +0 -0
  102. package/res/vue.global.js +0 -18552
@@ -1,323 +1,323 @@
1
- import {
2
- buildSpeedTestIssue,
3
- formatLatency
4
- } from '../logic.mjs';
5
-
6
- function clearProgressResetTimer(context, timerKey) {
7
- if (!context || !timerKey || !context[timerKey]) {
8
- return;
9
- }
10
- clearTimeout(context[timerKey]);
11
- context[timerKey] = null;
12
- }
13
-
14
- function scheduleProgressResetTimer(context, timerKey, progressKey, delayMs = 800) {
15
- if (!context || !timerKey || !progressKey) {
16
- return;
17
- }
18
- clearProgressResetTimer(context, timerKey);
19
- context[timerKey] = setTimeout(() => {
20
- context[progressKey] = 0;
21
- context[timerKey] = null;
22
- }, delayMs);
23
- }
24
-
25
- export function createRuntimeMethods(options = {}) {
26
- const { api } = options;
27
-
28
- return {
29
- formatLatency,
30
-
31
- buildSpeedTestIssue(name, result) {
32
- return buildSpeedTestIssue(name, result);
33
- },
34
-
35
- async runSpeedTest(name, options = {}) {
36
- if (!name || this.speedLoading[name]) return null;
37
- const silent = !!options.silent;
38
- this.speedLoading[name] = true;
39
- try {
40
- const res = await api('speed-test', { name });
41
- if (res.error) {
42
- this.speedResults[name] = { ok: false, error: res.error };
43
- if (!silent) {
44
- this.showMessage(res.error, 'error');
45
- }
46
- return { ok: false, error: res.error };
47
- }
48
- this.speedResults[name] = res;
49
- if (!silent) {
50
- const status = res.status ? ` (${res.status})` : '';
51
- this.showMessage(`Speed ${name}: ${this.formatLatency(res)}${status}`, 'success');
52
- }
53
- return res;
54
- } catch (e) {
55
- const message = e && e.message ? e.message : 'Speed test failed';
56
- this.speedResults[name] = { ok: false, error: message };
57
- if (!silent) {
58
- this.showMessage(message, 'error');
59
- }
60
- return { ok: false, error: message };
61
- } finally {
62
- this.speedLoading[name] = false;
63
- }
64
- },
65
-
66
- async runClaudeSpeedTest(name, config) {
67
- if (!name || this.claudeSpeedLoading[name]) return null;
68
- const baseUrl = config && typeof config.baseUrl === 'string' ? config.baseUrl.trim() : '';
69
- this.claudeSpeedLoading[name] = true;
70
- try {
71
- if (!baseUrl) {
72
- const res = { ok: false, error: 'Missing base URL' };
73
- this.claudeSpeedResults[name] = res;
74
- return res;
75
- }
76
- const res = await api('speed-test', { url: baseUrl });
77
- if (res.error) {
78
- this.claudeSpeedResults[name] = { ok: false, error: res.error };
79
- return { ok: false, error: res.error };
80
- }
81
- this.claudeSpeedResults[name] = res;
82
- return res;
83
- } catch (e) {
84
- const message = e && e.message ? e.message : 'Speed test failed';
85
- const res = { ok: false, error: message };
86
- this.claudeSpeedResults[name] = res;
87
- return res;
88
- } finally {
89
- this.claudeSpeedLoading[name] = false;
90
- }
91
- },
92
-
93
- async downloadClaudeDirectory() {
94
- if (this.claudeDownloadLoading) return;
95
- clearProgressResetTimer(this, '__claudeDownloadResetTimer');
96
- this.claudeDownloadLoading = true;
97
- this.claudeDownloadProgress = 5;
98
- this.claudeDownloadTimer = setInterval(() => {
99
- if (this.claudeDownloadProgress < 90) {
100
- this.claudeDownloadProgress += 5;
101
- }
102
- }, 400);
103
- try {
104
- const res = await api('download-claude-dir');
105
- if (res && res.error) {
106
- this.showMessage(res.error, 'error');
107
- return;
108
- }
109
- if (!res || res.success !== true || !res.fileName) {
110
- this.showMessage('备份失败', 'error');
111
- return;
112
- }
113
- this.claudeDownloadProgress = 100;
114
- const downloadUrl = `/download/${encodeURIComponent(res.fileName)}`;
115
- const link = document.createElement('a');
116
- link.href = downloadUrl;
117
- link.download = res.fileName;
118
- document.body.appendChild(link);
119
- link.click();
120
- document.body.removeChild(link);
121
- this.showMessage('备份成功,开始下载', 'success');
122
- } catch (e) {
123
- this.showMessage('备份失败:' + (e && e.message ? e.message : '未知错误'), 'error');
124
- } finally {
125
- if (this.claudeDownloadTimer) {
126
- clearInterval(this.claudeDownloadTimer);
127
- this.claudeDownloadTimer = null;
128
- }
129
- this.claudeDownloadLoading = false;
130
- scheduleProgressResetTimer(this, '__claudeDownloadResetTimer', 'claudeDownloadProgress');
131
- }
132
- },
133
-
134
- async downloadCodexDirectory() {
135
- if (this.codexDownloadLoading) return;
136
- clearProgressResetTimer(this, '__codexDownloadResetTimer');
137
- this.codexDownloadLoading = true;
138
- this.codexDownloadProgress = 5;
139
- this.codexDownloadTimer = setInterval(() => {
140
- if (this.codexDownloadProgress < 90) {
141
- this.codexDownloadProgress += 5;
142
- }
143
- }, 400);
144
- try {
145
- const res = await api('download-codex-dir');
146
- if (res && res.error) {
147
- this.showMessage(res.error, 'error');
148
- return;
149
- }
150
- if (!res || res.success !== true || !res.fileName) {
151
- this.showMessage('备份失败', 'error');
152
- return;
153
- }
154
- this.codexDownloadProgress = 100;
155
- const downloadUrl = `/download/${encodeURIComponent(res.fileName)}`;
156
- const link = document.createElement('a');
157
- link.href = downloadUrl;
158
- link.download = res.fileName;
159
- document.body.appendChild(link);
160
- link.click();
161
- document.body.removeChild(link);
162
- this.showMessage('备份成功,开始下载', 'success');
163
- } catch (e) {
164
- this.showMessage('备份失败:' + (e && e.message ? e.message : '未知错误'), 'error');
165
- } finally {
166
- if (this.codexDownloadTimer) {
167
- clearInterval(this.codexDownloadTimer);
168
- this.codexDownloadTimer = null;
169
- }
170
- this.codexDownloadLoading = false;
171
- scheduleProgressResetTimer(this, '__codexDownloadResetTimer', 'codexDownloadProgress');
172
- }
173
- },
174
-
175
- triggerClaudeImport() {
176
- const input = this.$refs.claudeImportInput;
177
- if (input) {
178
- input.value = '';
179
- input.click();
180
- }
181
- },
182
-
183
- triggerCodexImport() {
184
- const input = this.$refs.codexImportInput;
185
- if (input) {
186
- input.value = '';
187
- input.click();
188
- }
189
- },
190
-
191
- handleClaudeImportChange(event) {
192
- const file = event && event.target && event.target.files ? event.target.files[0] : null;
193
- if (file) {
194
- void this.importBackupFile('claude', file);
195
- }
196
- },
197
-
198
- handleCodexImportChange(event) {
199
- const file = event && event.target && event.target.files ? event.target.files[0] : null;
200
- if (file) {
201
- void this.importBackupFile('codex', file);
202
- }
203
- },
204
-
205
- async importBackupFile(type, file) {
206
- const maxSize = 200 * 1024 * 1024;
207
- const loadingKey = type === 'claude' ? 'claudeImportLoading' : 'codexImportLoading';
208
- if (this[loadingKey]) {
209
- this.resetImportInput(type);
210
- return;
211
- }
212
- if (file.size > maxSize) {
213
- this.showMessage('备份文件过大,限制 200MB', 'error');
214
- this.resetImportInput(type);
215
- return;
216
- }
217
- this[loadingKey] = true;
218
- try {
219
- const base64 = await this.readFileAsBase64(file);
220
- const action = type === 'claude' ? 'restore-claude-dir' : 'restore-codex-dir';
221
- const res = await api(action, {
222
- fileName: file.name || `${type}-backup.zip`,
223
- fileBase64: base64
224
- });
225
- if (res && res.error) {
226
- this.showMessage(res.error, 'error');
227
- return;
228
- }
229
- const backupTip = res && res.backupPath ? `,原配置已备份到临时文件:${res.backupPath}` : '';
230
- this.showMessage(`导入成功${backupTip}`, 'success');
231
- try {
232
- if (type === 'claude') {
233
- await this.refreshClaudeSelectionFromSettings({ silent: true });
234
- } else {
235
- await this.loadAll();
236
- }
237
- } catch (_) {
238
- this.showMessage('导入已完成,但界面刷新失败,请手动刷新', 'error');
239
- }
240
- } catch (e) {
241
- this.showMessage('导入失败:' + (e && e.message ? e.message : '未知错误'), 'error');
242
- } finally {
243
- this[loadingKey] = false;
244
- this.resetImportInput(type);
245
- }
246
- },
247
-
248
- readFileAsBase64(file) {
249
- return new Promise((resolve, reject) => {
250
- const reader = new FileReader();
251
- reader.onload = () => {
252
- const result = reader.result;
253
- if (result instanceof ArrayBuffer) {
254
- resolve(this.arrayBufferToBase64(result));
255
- return;
256
- }
257
- if (typeof result === 'string') {
258
- const idx = result.indexOf('base64,');
259
- resolve(idx >= 0 ? result.slice(idx + 7) : result);
260
- return;
261
- }
262
- reject(new Error('不支持的文件读取结果'));
263
- };
264
- reader.onerror = () => reject(new Error('读取文件失败'));
265
- reader.readAsArrayBuffer(file);
266
- });
267
- },
268
-
269
- arrayBufferToBase64(buffer) {
270
- const bytes = new Uint8Array(buffer);
271
- const chunkSize = 0x8000;
272
- let binary = '';
273
- for (let i = 0; i < bytes.byteLength; i += chunkSize) {
274
- binary += String.fromCharCode(...bytes.subarray(i, i + chunkSize));
275
- }
276
- return btoa(binary);
277
- },
278
-
279
- resetImportInput(type) {
280
- const refName = type === 'claude' ? 'claudeImportInput' : 'codexImportInput';
281
- const el = this.$refs[refName];
282
- if (el) {
283
- el.value = '';
284
- }
285
- },
286
-
287
- async loadCodexAuthProfiles(options = {}) {
288
- const silent = !!options.silent;
289
- try {
290
- const res = await api('list-auth-profiles');
291
- if (res && res.error) {
292
- if (!silent) {
293
- this.showMessage(res.error, 'error');
294
- }
295
- return;
296
- }
297
- const list = Array.isArray(res && res.profiles) ? res.profiles : [];
298
- this.codexAuthProfiles = list.sort((a, b) => {
299
- if (!!a.current !== !!b.current) {
300
- return a.current ? -1 : 1;
301
- }
302
- return String(a.name || '').localeCompare(String(b.name || ''));
303
- });
304
- } catch (e) {
305
- if (!silent) {
306
- this.showMessage('读取认证列表失败', 'error');
307
- }
308
- }
309
- },
310
-
311
- showMessage(text, type) {
312
- if (this._messageTimer) {
313
- clearTimeout(this._messageTimer);
314
- }
315
- this.message = text;
316
- this.messageType = type || 'info';
317
- this._messageTimer = setTimeout(() => {
318
- this.message = '';
319
- this._messageTimer = null;
320
- }, 3000);
321
- }
322
- };
323
- }
1
+ import {
2
+ buildSpeedTestIssue,
3
+ formatLatency
4
+ } from '../logic.mjs';
5
+
6
+ function clearProgressResetTimer(context, timerKey) {
7
+ if (!context || !timerKey || !context[timerKey]) {
8
+ return;
9
+ }
10
+ clearTimeout(context[timerKey]);
11
+ context[timerKey] = null;
12
+ }
13
+
14
+ function scheduleProgressResetTimer(context, timerKey, progressKey, delayMs = 800) {
15
+ if (!context || !timerKey || !progressKey) {
16
+ return;
17
+ }
18
+ clearProgressResetTimer(context, timerKey);
19
+ context[timerKey] = setTimeout(() => {
20
+ context[progressKey] = 0;
21
+ context[timerKey] = null;
22
+ }, delayMs);
23
+ }
24
+
25
+ export function createRuntimeMethods(options = {}) {
26
+ const { api } = options;
27
+
28
+ return {
29
+ formatLatency,
30
+
31
+ buildSpeedTestIssue(name, result) {
32
+ return buildSpeedTestIssue(name, result);
33
+ },
34
+
35
+ async runSpeedTest(name, options = {}) {
36
+ if (!name || this.speedLoading[name]) return null;
37
+ const silent = !!options.silent;
38
+ this.speedLoading[name] = true;
39
+ try {
40
+ const res = await api('speed-test', { name });
41
+ if (res.error) {
42
+ this.speedResults[name] = { ok: false, error: res.error };
43
+ if (!silent) {
44
+ this.showMessage(res.error, 'error');
45
+ }
46
+ return { ok: false, error: res.error };
47
+ }
48
+ this.speedResults[name] = res;
49
+ if (!silent) {
50
+ const status = res.status ? ` (${res.status})` : '';
51
+ this.showMessage(`Speed ${name}: ${this.formatLatency(res)}${status}`, 'success');
52
+ }
53
+ return res;
54
+ } catch (e) {
55
+ const message = e && e.message ? e.message : 'Speed test failed';
56
+ this.speedResults[name] = { ok: false, error: message };
57
+ if (!silent) {
58
+ this.showMessage(message, 'error');
59
+ }
60
+ return { ok: false, error: message };
61
+ } finally {
62
+ this.speedLoading[name] = false;
63
+ }
64
+ },
65
+
66
+ async runClaudeSpeedTest(name, config) {
67
+ if (!name || this.claudeSpeedLoading[name]) return null;
68
+ const baseUrl = config && typeof config.baseUrl === 'string' ? config.baseUrl.trim() : '';
69
+ this.claudeSpeedLoading[name] = true;
70
+ try {
71
+ if (!baseUrl) {
72
+ const res = { ok: false, error: 'Missing base URL' };
73
+ this.claudeSpeedResults[name] = res;
74
+ return res;
75
+ }
76
+ const res = await api('speed-test', { url: baseUrl });
77
+ if (res.error) {
78
+ this.claudeSpeedResults[name] = { ok: false, error: res.error };
79
+ return { ok: false, error: res.error };
80
+ }
81
+ this.claudeSpeedResults[name] = res;
82
+ return res;
83
+ } catch (e) {
84
+ const message = e && e.message ? e.message : 'Speed test failed';
85
+ const res = { ok: false, error: message };
86
+ this.claudeSpeedResults[name] = res;
87
+ return res;
88
+ } finally {
89
+ this.claudeSpeedLoading[name] = false;
90
+ }
91
+ },
92
+
93
+ async downloadClaudeDirectory() {
94
+ if (this.claudeDownloadLoading) return;
95
+ clearProgressResetTimer(this, '__claudeDownloadResetTimer');
96
+ this.claudeDownloadLoading = true;
97
+ this.claudeDownloadProgress = 5;
98
+ this.claudeDownloadTimer = setInterval(() => {
99
+ if (this.claudeDownloadProgress < 90) {
100
+ this.claudeDownloadProgress += 5;
101
+ }
102
+ }, 400);
103
+ try {
104
+ const res = await api('download-claude-dir');
105
+ if (res && res.error) {
106
+ this.showMessage(res.error, 'error');
107
+ return;
108
+ }
109
+ if (!res || res.success !== true || !res.fileName) {
110
+ this.showMessage('备份失败', 'error');
111
+ return;
112
+ }
113
+ this.claudeDownloadProgress = 100;
114
+ const downloadUrl = `/download/${encodeURIComponent(res.fileName)}`;
115
+ const link = document.createElement('a');
116
+ link.href = downloadUrl;
117
+ link.download = res.fileName;
118
+ document.body.appendChild(link);
119
+ link.click();
120
+ document.body.removeChild(link);
121
+ this.showMessage('备份成功,开始下载', 'success');
122
+ } catch (e) {
123
+ this.showMessage('备份失败:' + (e && e.message ? e.message : '未知错误'), 'error');
124
+ } finally {
125
+ if (this.claudeDownloadTimer) {
126
+ clearInterval(this.claudeDownloadTimer);
127
+ this.claudeDownloadTimer = null;
128
+ }
129
+ this.claudeDownloadLoading = false;
130
+ scheduleProgressResetTimer(this, '__claudeDownloadResetTimer', 'claudeDownloadProgress');
131
+ }
132
+ },
133
+
134
+ async downloadCodexDirectory() {
135
+ if (this.codexDownloadLoading) return;
136
+ clearProgressResetTimer(this, '__codexDownloadResetTimer');
137
+ this.codexDownloadLoading = true;
138
+ this.codexDownloadProgress = 5;
139
+ this.codexDownloadTimer = setInterval(() => {
140
+ if (this.codexDownloadProgress < 90) {
141
+ this.codexDownloadProgress += 5;
142
+ }
143
+ }, 400);
144
+ try {
145
+ const res = await api('download-codex-dir');
146
+ if (res && res.error) {
147
+ this.showMessage(res.error, 'error');
148
+ return;
149
+ }
150
+ if (!res || res.success !== true || !res.fileName) {
151
+ this.showMessage('备份失败', 'error');
152
+ return;
153
+ }
154
+ this.codexDownloadProgress = 100;
155
+ const downloadUrl = `/download/${encodeURIComponent(res.fileName)}`;
156
+ const link = document.createElement('a');
157
+ link.href = downloadUrl;
158
+ link.download = res.fileName;
159
+ document.body.appendChild(link);
160
+ link.click();
161
+ document.body.removeChild(link);
162
+ this.showMessage('备份成功,开始下载', 'success');
163
+ } catch (e) {
164
+ this.showMessage('备份失败:' + (e && e.message ? e.message : '未知错误'), 'error');
165
+ } finally {
166
+ if (this.codexDownloadTimer) {
167
+ clearInterval(this.codexDownloadTimer);
168
+ this.codexDownloadTimer = null;
169
+ }
170
+ this.codexDownloadLoading = false;
171
+ scheduleProgressResetTimer(this, '__codexDownloadResetTimer', 'codexDownloadProgress');
172
+ }
173
+ },
174
+
175
+ triggerClaudeImport() {
176
+ const input = this.$refs.claudeImportInput;
177
+ if (input) {
178
+ input.value = '';
179
+ input.click();
180
+ }
181
+ },
182
+
183
+ triggerCodexImport() {
184
+ const input = this.$refs.codexImportInput;
185
+ if (input) {
186
+ input.value = '';
187
+ input.click();
188
+ }
189
+ },
190
+
191
+ handleClaudeImportChange(event) {
192
+ const file = event && event.target && event.target.files ? event.target.files[0] : null;
193
+ if (file) {
194
+ void this.importBackupFile('claude', file);
195
+ }
196
+ },
197
+
198
+ handleCodexImportChange(event) {
199
+ const file = event && event.target && event.target.files ? event.target.files[0] : null;
200
+ if (file) {
201
+ void this.importBackupFile('codex', file);
202
+ }
203
+ },
204
+
205
+ async importBackupFile(type, file) {
206
+ const maxSize = 200 * 1024 * 1024;
207
+ const loadingKey = type === 'claude' ? 'claudeImportLoading' : 'codexImportLoading';
208
+ if (this[loadingKey]) {
209
+ this.resetImportInput(type);
210
+ return;
211
+ }
212
+ if (file.size > maxSize) {
213
+ this.showMessage('备份文件过大,限制 200MB', 'error');
214
+ this.resetImportInput(type);
215
+ return;
216
+ }
217
+ this[loadingKey] = true;
218
+ try {
219
+ const base64 = await this.readFileAsBase64(file);
220
+ const action = type === 'claude' ? 'restore-claude-dir' : 'restore-codex-dir';
221
+ const res = await api(action, {
222
+ fileName: file.name || `${type}-backup.zip`,
223
+ fileBase64: base64
224
+ });
225
+ if (res && res.error) {
226
+ this.showMessage(res.error, 'error');
227
+ return;
228
+ }
229
+ const backupTip = res && res.backupPath ? `,原配置已备份到临时文件:${res.backupPath}` : '';
230
+ this.showMessage(`导入成功${backupTip}`, 'success');
231
+ try {
232
+ if (type === 'claude') {
233
+ await this.refreshClaudeSelectionFromSettings({ silent: true });
234
+ } else {
235
+ await this.loadAll();
236
+ }
237
+ } catch (_) {
238
+ this.showMessage('导入已完成,但界面刷新失败,请手动刷新', 'error');
239
+ }
240
+ } catch (e) {
241
+ this.showMessage('导入失败:' + (e && e.message ? e.message : '未知错误'), 'error');
242
+ } finally {
243
+ this[loadingKey] = false;
244
+ this.resetImportInput(type);
245
+ }
246
+ },
247
+
248
+ readFileAsBase64(file) {
249
+ return new Promise((resolve, reject) => {
250
+ const reader = new FileReader();
251
+ reader.onload = () => {
252
+ const result = reader.result;
253
+ if (result instanceof ArrayBuffer) {
254
+ resolve(this.arrayBufferToBase64(result));
255
+ return;
256
+ }
257
+ if (typeof result === 'string') {
258
+ const idx = result.indexOf('base64,');
259
+ resolve(idx >= 0 ? result.slice(idx + 7) : result);
260
+ return;
261
+ }
262
+ reject(new Error('不支持的文件读取结果'));
263
+ };
264
+ reader.onerror = () => reject(new Error('读取文件失败'));
265
+ reader.readAsArrayBuffer(file);
266
+ });
267
+ },
268
+
269
+ arrayBufferToBase64(buffer) {
270
+ const bytes = new Uint8Array(buffer);
271
+ const chunkSize = 0x8000;
272
+ let binary = '';
273
+ for (let i = 0; i < bytes.byteLength; i += chunkSize) {
274
+ binary += String.fromCharCode(...bytes.subarray(i, i + chunkSize));
275
+ }
276
+ return btoa(binary);
277
+ },
278
+
279
+ resetImportInput(type) {
280
+ const refName = type === 'claude' ? 'claudeImportInput' : 'codexImportInput';
281
+ const el = this.$refs[refName];
282
+ if (el) {
283
+ el.value = '';
284
+ }
285
+ },
286
+
287
+ async loadCodexAuthProfiles(options = {}) {
288
+ const silent = !!options.silent;
289
+ try {
290
+ const res = await api('list-auth-profiles');
291
+ if (res && res.error) {
292
+ if (!silent) {
293
+ this.showMessage(res.error, 'error');
294
+ }
295
+ return;
296
+ }
297
+ const list = Array.isArray(res && res.profiles) ? res.profiles : [];
298
+ this.codexAuthProfiles = list.sort((a, b) => {
299
+ if (!!a.current !== !!b.current) {
300
+ return a.current ? -1 : 1;
301
+ }
302
+ return String(a.name || '').localeCompare(String(b.name || ''));
303
+ });
304
+ } catch (e) {
305
+ if (!silent) {
306
+ this.showMessage('读取认证列表失败', 'error');
307
+ }
308
+ }
309
+ },
310
+
311
+ showMessage(text, type) {
312
+ if (this._messageTimer) {
313
+ clearTimeout(this._messageTimer);
314
+ }
315
+ this.message = text;
316
+ this.messageType = type || 'info';
317
+ this._messageTimer = setTimeout(() => {
318
+ this.message = '';
319
+ this._messageTimer = null;
320
+ }, 3000);
321
+ }
322
+ };
323
+ }