codexmate 0.0.7 → 0.0.9

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 (47) hide show
  1. package/.github/workflows/release.yml +122 -8
  2. package/.planning/.fix-attempts +1 -0
  3. package/.planning/.lock +6 -0
  4. package/.planning/.verify-cache.json +14 -0
  5. package/.planning/CHECKPOINT.json +46 -0
  6. package/.planning/DESIGN.md +26 -0
  7. package/.planning/HISTORY.json +124 -0
  8. package/.planning/PLAN.md +69 -0
  9. package/.planning/REVIEW.md +41 -0
  10. package/.planning/STATE.md +12 -0
  11. package/.planning/STATS.json +13 -0
  12. package/.planning/VERIFICATION.md +70 -0
  13. package/.planning/daude-code-plan.md +51 -0
  14. package/.planning/research/architecture.md +32 -0
  15. package/.planning/research/conventions.md +36 -0
  16. package/.planning/task_1-REVIEW.md +29 -0
  17. package/.planning/task_1-SUMMARY.md +32 -0
  18. package/.planning/task_2-REVIEW.md +24 -0
  19. package/.planning/task_2-SUMMARY.md +37 -0
  20. package/.planning/task_3-REVIEW.md +25 -0
  21. package/.planning/task_3-SUMMARY.md +31 -0
  22. package/README.md +58 -52
  23. package/README.zh-CN.md +68 -56
  24. package/cli.js +1142 -1427
  25. package/lib/cli-file-utils.js +151 -0
  26. package/lib/cli-models-utils.js +152 -0
  27. package/lib/cli-network-utils.js +148 -0
  28. package/lib/cli-session-utils.js +121 -0
  29. package/lib/cli-utils.js +139 -0
  30. package/package.json +4 -2
  31. package/res/json5.min.js +1 -0
  32. package/res/vue.global.js +18552 -0
  33. package/tests/e2e/helpers.js +214 -0
  34. package/tests/e2e/recent-health.e2e.js +6 -0
  35. package/tests/e2e/run.js +103 -306
  36. package/tests/e2e/test-claude.js +21 -0
  37. package/tests/e2e/test-config.js +124 -0
  38. package/tests/e2e/test-health-speed.js +79 -0
  39. package/tests/e2e/test-openclaw.js +47 -0
  40. package/tests/e2e/test-session-search.js +114 -0
  41. package/tests/e2e/test-sessions.js +69 -0
  42. package/tests/e2e/test-setup.js +159 -0
  43. package/tests/unit/run.mjs +29 -0
  44. package/tests/unit/web-ui-logic.test.mjs +186 -0
  45. package/web-ui/app.js +2841 -0
  46. package/web-ui/logic.mjs +157 -0
  47. package/web-ui.html +1045 -2996
@@ -0,0 +1,186 @@
1
+ import assert from 'assert';
2
+ import path from 'path';
3
+ import { fileURLToPath, pathToFileURL } from 'url';
4
+
5
+ const __filename = fileURLToPath(import.meta.url);
6
+ const __dirname = path.dirname(__filename);
7
+
8
+ const logic = await import(pathToFileURL(path.join(__dirname, '..', '..', 'web-ui', 'logic.mjs')));
9
+ const {
10
+ normalizeClaudeValue,
11
+ normalizeClaudeConfig,
12
+ normalizeClaudeSettingsEnv,
13
+ matchClaudeConfigFromSettings,
14
+ findDuplicateClaudeConfigName,
15
+ formatLatency,
16
+ buildSpeedTestIssue,
17
+ isSessionQueryEnabled,
18
+ buildSessionListParams
19
+ } = logic;
20
+
21
+ test('normalizeClaudeValue trims strings and ignores non-string', () => {
22
+ assert.strictEqual(normalizeClaudeValue(' abc '), 'abc');
23
+ assert.strictEqual(normalizeClaudeValue(123), '');
24
+ assert.strictEqual(normalizeClaudeValue(null), '');
25
+ });
26
+
27
+ test('normalizeClaudeConfig trims all fields', () => {
28
+ const cfg = normalizeClaudeConfig({ apiKey: ' key ', baseUrl: ' url ', model: ' model ' });
29
+ assert.deepStrictEqual(cfg, { apiKey: 'key', baseUrl: 'url', model: 'model' });
30
+ });
31
+
32
+ test('normalizeClaudeSettingsEnv trims settings env', () => {
33
+ const env = { ANTHROPIC_API_KEY: ' key ', ANTHROPIC_BASE_URL: ' url ', ANTHROPIC_MODEL: ' model ' };
34
+ assert.deepStrictEqual(normalizeClaudeSettingsEnv(env), { apiKey: 'key', baseUrl: 'url', model: 'model' });
35
+ });
36
+
37
+ test('normalizeClaudeSettingsEnv fills missing fields with empty strings', () => {
38
+ const env = { ANTHROPIC_API_KEY: 'k' };
39
+ assert.deepStrictEqual(normalizeClaudeSettingsEnv(env), { apiKey: 'k', baseUrl: '', model: '' });
40
+ });
41
+
42
+ test('matchClaudeConfigFromSettings matches identical config', () => {
43
+ const configs = { default: { apiKey: 'k', baseUrl: 'u', model: 'm' } };
44
+ const env = { ANTHROPIC_API_KEY: 'k', ANTHROPIC_BASE_URL: 'u', ANTHROPIC_MODEL: 'm' };
45
+ assert.strictEqual(matchClaudeConfigFromSettings(configs, env), 'default');
46
+ });
47
+
48
+ test('matchClaudeConfigFromSettings returns empty when incomplete', () => {
49
+ const configs = { default: { apiKey: 'k', baseUrl: 'u', model: 'm' } };
50
+ const env = { ANTHROPIC_API_KEY: 'k', ANTHROPIC_BASE_URL: '', ANTHROPIC_MODEL: 'm' };
51
+ assert.strictEqual(matchClaudeConfigFromSettings(configs, env), '');
52
+ });
53
+
54
+ test('findDuplicateClaudeConfigName returns empty on missing fields', () => {
55
+ const configs = { only: { apiKey: 'k', baseUrl: 'u', model: 'm' } };
56
+ const incomplete = { apiKey: 'k', baseUrl: '', model: '' };
57
+ assert.strictEqual(findDuplicateClaudeConfigName(configs, incomplete), '');
58
+ });
59
+
60
+ test('findDuplicateClaudeConfigName detects duplicates', () => {
61
+ const configs = {
62
+ first: { apiKey: 'k1', baseUrl: 'u1', model: 'm1' },
63
+ second: { apiKey: 'k2', baseUrl: 'u2', model: 'm2' }
64
+ };
65
+ const duplicate = { apiKey: 'k2', baseUrl: 'u2', model: 'm2' };
66
+ assert.strictEqual(findDuplicateClaudeConfigName(configs, duplicate), 'second');
67
+ });
68
+
69
+ test('findDuplicateClaudeConfigName returns empty when no match', () => {
70
+ const configs = { only: { apiKey: 'k', baseUrl: 'u', model: 'm' } };
71
+ const another = { apiKey: 'k', baseUrl: 'u', model: 'm-2' };
72
+ assert.strictEqual(findDuplicateClaudeConfigName(configs, another), '');
73
+ });
74
+
75
+ test('formatLatency formats success and errors', () => {
76
+ assert.strictEqual(formatLatency({ ok: true, durationMs: 120 }), '120ms');
77
+ assert.strictEqual(formatLatency({ ok: false, status: 404 }), 'ERR 404');
78
+ assert.strictEqual(formatLatency({ ok: false }), 'ERR');
79
+ assert.strictEqual(formatLatency(null), '');
80
+ assert.strictEqual(formatLatency({ ok: true, durationMs: undefined }), '0ms');
81
+ assert.strictEqual(formatLatency({ ok: true, durationMs: '12' }), '0ms');
82
+ });
83
+
84
+ test('buildSpeedTestIssue maps errors and status codes', () => {
85
+ assert.strictEqual(buildSpeedTestIssue('p1', null), null);
86
+ const missing = buildSpeedTestIssue('p1', { error: 'Provider not found' });
87
+ assert.strictEqual(missing.code, 'remote-speedtest-provider-missing');
88
+
89
+ const timeout = buildSpeedTestIssue('p1', { error: 'Request timeout' });
90
+ assert.strictEqual(timeout.code, 'remote-speedtest-timeout');
91
+
92
+ const auth = buildSpeedTestIssue('p1', { ok: false, status: 401 });
93
+ assert.strictEqual(auth.code, 'remote-speedtest-auth-failed');
94
+
95
+ const httpErr = buildSpeedTestIssue('p1', { ok: false, status: 500 });
96
+ assert.strictEqual(httpErr.code, 'remote-speedtest-http-error');
97
+
98
+ const ok = buildSpeedTestIssue('p1', { ok: true, status: 200 });
99
+ assert.strictEqual(ok, null);
100
+
101
+ const invalidUrl = buildSpeedTestIssue('p1', { error: 'Invalid URL' });
102
+ assert.strictEqual(invalidUrl.code, 'remote-speedtest-invalid-url');
103
+
104
+ const missingUrl = buildSpeedTestIssue('p1', { error: 'Missing name or url' });
105
+ assert.strictEqual(missingUrl.code, 'remote-speedtest-baseurl-missing');
106
+
107
+ const timeoutLower = buildSpeedTestIssue('p1', { error: 'timeout while fetching' });
108
+ assert.strictEqual(timeoutLower.code, 'remote-speedtest-timeout');
109
+
110
+ const generic = buildSpeedTestIssue('p1', { error: 'network unreachable' });
111
+ assert.strictEqual(generic.code, 'remote-speedtest-unreachable');
112
+
113
+ const auth403 = buildSpeedTestIssue('p1', { ok: false, status: 403 });
114
+ assert.strictEqual(auth403.code, 'remote-speedtest-auth-failed');
115
+
116
+ const http400 = buildSpeedTestIssue('p1', { ok: false, status: 400 });
117
+ assert.strictEqual(http400.code, 'remote-speedtest-http-error');
118
+ });
119
+
120
+ test('isSessionQueryEnabled supports codex and claude only', () => {
121
+ assert.strictEqual(isSessionQueryEnabled('codex'), true);
122
+ assert.strictEqual(isSessionQueryEnabled('CODEX'), true);
123
+ assert.strictEqual(isSessionQueryEnabled('claude'), true);
124
+ assert.strictEqual(isSessionQueryEnabled('ALL'), false);
125
+ assert.strictEqual(isSessionQueryEnabled('openai'), false);
126
+ assert.strictEqual(isSessionQueryEnabled(''), false);
127
+ });
128
+
129
+ test('buildSessionListParams keeps claude code lexicon query when enabled', () => {
130
+ const paramsClaude = buildSessionListParams({
131
+ source: 'claude',
132
+ query: 'claude code',
133
+ roleFilter: 'all'
134
+ });
135
+ assert.strictEqual(paramsClaude.query, 'claude code');
136
+ assert.strictEqual(paramsClaude.queryMode, 'and');
137
+ assert.strictEqual(paramsClaude.queryScope, 'content');
138
+ });
139
+
140
+ test('buildSessionListParams keeps query for enabled sources', () => {
141
+ const paramsCodex = buildSessionListParams({
142
+ source: 'codex',
143
+ query: 'test',
144
+ pathFilter: ''
145
+ });
146
+ assert.strictEqual(paramsCodex.query, 'test');
147
+ assert.strictEqual(paramsCodex.source, 'codex');
148
+ assert.strictEqual(paramsCodex.limit, 200);
149
+
150
+ const paramsClaude = buildSessionListParams({
151
+ source: 'claude',
152
+ query: 'claude code',
153
+ roleFilter: 'user'
154
+ });
155
+ assert.strictEqual(paramsClaude.query, 'claude code');
156
+ assert.strictEqual(paramsClaude.source, 'claude');
157
+ assert.strictEqual(paramsClaude.roleFilter, 'user');
158
+ assert.strictEqual(paramsClaude.limit, 200);
159
+
160
+ const paramsAll = buildSessionListParams({
161
+ source: 'codex',
162
+ query: 'claudecode',
163
+ timeRangePreset: '7d'
164
+ });
165
+ assert.strictEqual(paramsAll.query, 'claudecode');
166
+ assert.strictEqual(paramsAll.source, 'codex');
167
+ assert.strictEqual(paramsAll.timeRangePreset, '7d');
168
+ assert.strictEqual(paramsAll.limit, 200);
169
+ });
170
+
171
+ test('buildSessionListParams clears query for unsupported sources', () => {
172
+ const params = buildSessionListParams({
173
+ source: 'openai',
174
+ query: 'hello',
175
+ pathFilter: '/tmp',
176
+ roleFilter: 'assistant'
177
+ });
178
+ assert.strictEqual(params.query, '');
179
+ assert.strictEqual(params.source, 'openai');
180
+ assert.strictEqual(params.pathFilter, '/tmp');
181
+ assert.strictEqual(params.roleFilter, 'assistant');
182
+ assert.strictEqual(params.limit, 200);
183
+ assert.strictEqual(params.forceRefresh, true);
184
+ assert.strictEqual(params.queryScope, 'content');
185
+ assert.strictEqual(params.contentScanLimit, 50);
186
+ });