tlc-claude-code 1.4.1 → 1.4.4
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/dashboard/dist/App.js +229 -35
- package/dashboard/dist/components/AgentRegistryPane.d.ts +35 -0
- package/dashboard/dist/components/AgentRegistryPane.js +89 -0
- package/dashboard/dist/components/AgentRegistryPane.test.d.ts +1 -0
- package/dashboard/dist/components/AgentRegistryPane.test.js +200 -0
- package/dashboard/dist/components/RouterPane.d.ts +5 -0
- package/dashboard/dist/components/RouterPane.js +65 -0
- package/dashboard/dist/components/RouterPane.test.d.ts +1 -0
- package/dashboard/dist/components/RouterPane.test.js +176 -0
- package/dashboard/dist/components/accessibility.test.d.ts +1 -0
- package/dashboard/dist/components/accessibility.test.js +116 -0
- package/dashboard/dist/components/layout/MobileNav.d.ts +16 -0
- package/dashboard/dist/components/layout/MobileNav.js +31 -0
- package/dashboard/dist/components/layout/MobileNav.test.d.ts +1 -0
- package/dashboard/dist/components/layout/MobileNav.test.js +111 -0
- package/dashboard/dist/components/performance.test.d.ts +1 -0
- package/dashboard/dist/components/performance.test.js +114 -0
- package/dashboard/dist/components/responsive.test.d.ts +1 -0
- package/dashboard/dist/components/responsive.test.js +114 -0
- package/dashboard/dist/components/ui/Dropdown.d.ts +22 -0
- package/dashboard/dist/components/ui/Dropdown.js +109 -0
- package/dashboard/dist/components/ui/Dropdown.test.d.ts +1 -0
- package/dashboard/dist/components/ui/Dropdown.test.js +105 -0
- package/dashboard/dist/components/ui/Modal.d.ts +13 -0
- package/dashboard/dist/components/ui/Modal.js +25 -0
- package/dashboard/dist/components/ui/Modal.test.d.ts +1 -0
- package/dashboard/dist/components/ui/Modal.test.js +91 -0
- package/dashboard/dist/components/ui/Skeleton.d.ts +32 -0
- package/dashboard/dist/components/ui/Skeleton.js +48 -0
- package/dashboard/dist/components/ui/Skeleton.test.d.ts +1 -0
- package/dashboard/dist/components/ui/Skeleton.test.js +125 -0
- package/dashboard/dist/components/ui/Toast.d.ts +32 -0
- package/dashboard/dist/components/ui/Toast.js +21 -0
- package/dashboard/dist/components/ui/Toast.test.d.ts +1 -0
- package/dashboard/dist/components/ui/Toast.test.js +118 -0
- package/dashboard/dist/hooks/useTheme.d.ts +37 -0
- package/dashboard/dist/hooks/useTheme.js +96 -0
- package/dashboard/dist/hooks/useTheme.test.d.ts +1 -0
- package/dashboard/dist/hooks/useTheme.test.js +94 -0
- package/dashboard/dist/hooks/useWebSocket.d.ts +17 -0
- package/dashboard/dist/hooks/useWebSocket.js +100 -0
- package/dashboard/dist/hooks/useWebSocket.test.d.ts +1 -0
- package/dashboard/dist/hooks/useWebSocket.test.js +115 -0
- package/dashboard/dist/stores/projectStore.d.ts +44 -0
- package/dashboard/dist/stores/projectStore.js +76 -0
- package/dashboard/dist/stores/projectStore.test.d.ts +1 -0
- package/dashboard/dist/stores/projectStore.test.js +114 -0
- package/dashboard/dist/stores/uiStore.d.ts +29 -0
- package/dashboard/dist/stores/uiStore.js +72 -0
- package/dashboard/dist/stores/uiStore.test.d.ts +1 -0
- package/dashboard/dist/stores/uiStore.test.js +93 -0
- package/dashboard/package.json +3 -3
- package/docker-compose.dev.yml +6 -1
- package/package.json +5 -2
- package/server/dashboard/index.html +1336 -779
- package/server/index.js +178 -0
- package/server/lib/agent-cleanup.js +177 -0
- package/server/lib/agent-cleanup.test.js +359 -0
- package/server/lib/agent-hooks.js +126 -0
- package/server/lib/agent-hooks.test.js +303 -0
- package/server/lib/agent-metadata.js +179 -0
- package/server/lib/agent-metadata.test.js +383 -0
- package/server/lib/agent-persistence.js +191 -0
- package/server/lib/agent-persistence.test.js +475 -0
- package/server/lib/agent-registry-command.js +340 -0
- package/server/lib/agent-registry-command.test.js +334 -0
- package/server/lib/agent-registry.js +155 -0
- package/server/lib/agent-registry.test.js +239 -0
- package/server/lib/agent-state.js +236 -0
- package/server/lib/agent-state.test.js +375 -0
- package/server/lib/api-provider.js +186 -0
- package/server/lib/api-provider.test.js +336 -0
- package/server/lib/cli-detector.js +166 -0
- package/server/lib/cli-detector.test.js +269 -0
- package/server/lib/cli-provider.js +212 -0
- package/server/lib/cli-provider.test.js +349 -0
- package/server/lib/debug.test.js +62 -0
- package/server/lib/devserver-router-api.js +249 -0
- package/server/lib/devserver-router-api.test.js +426 -0
- package/server/lib/model-router.js +245 -0
- package/server/lib/model-router.test.js +313 -0
- package/server/lib/output-schemas.js +269 -0
- package/server/lib/output-schemas.test.js +307 -0
- package/server/lib/provider-interface.js +153 -0
- package/server/lib/provider-interface.test.js +394 -0
- package/server/lib/provider-queue.js +158 -0
- package/server/lib/provider-queue.test.js +315 -0
- package/server/lib/router-config.js +221 -0
- package/server/lib/router-config.test.js +237 -0
- package/server/lib/router-setup-command.js +419 -0
- package/server/lib/router-setup-command.test.js +375 -0
|
@@ -0,0 +1,375 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
+
import {
|
|
3
|
+
execute,
|
|
4
|
+
detectLocalCLIs,
|
|
5
|
+
testDevserverConnection,
|
|
6
|
+
configureProvider,
|
|
7
|
+
configureCapability,
|
|
8
|
+
testProvider,
|
|
9
|
+
formatRoutingSummary,
|
|
10
|
+
estimateCosts,
|
|
11
|
+
saveConfig,
|
|
12
|
+
} from './router-setup-command.js';
|
|
13
|
+
|
|
14
|
+
// Mock dependencies
|
|
15
|
+
vi.mock('./cli-detector.js', () => ({
|
|
16
|
+
detectAllCLIs: vi.fn(),
|
|
17
|
+
}));
|
|
18
|
+
|
|
19
|
+
vi.mock('fs/promises', () => ({
|
|
20
|
+
default: {
|
|
21
|
+
readFile: vi.fn(),
|
|
22
|
+
writeFile: vi.fn(),
|
|
23
|
+
},
|
|
24
|
+
}));
|
|
25
|
+
|
|
26
|
+
import { detectAllCLIs } from './cli-detector.js';
|
|
27
|
+
import fs from 'fs/promises';
|
|
28
|
+
|
|
29
|
+
describe('router-setup-command', () => {
|
|
30
|
+
beforeEach(() => {
|
|
31
|
+
vi.clearAllMocks();
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
describe('execute', () => {
|
|
35
|
+
it('detects local CLIs', async () => {
|
|
36
|
+
detectAllCLIs.mockResolvedValue(
|
|
37
|
+
new Map([['claude', { name: 'claude', version: 'v4.0.0' }]])
|
|
38
|
+
);
|
|
39
|
+
fs.readFile.mockRejectedValue(new Error('ENOENT'));
|
|
40
|
+
fs.writeFile.mockResolvedValue();
|
|
41
|
+
|
|
42
|
+
const result = await execute({ projectDir: '/project', dryRun: true });
|
|
43
|
+
|
|
44
|
+
expect(detectAllCLIs).toHaveBeenCalled();
|
|
45
|
+
expect(result.detected).toBeDefined();
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('tests devserver connection', async () => {
|
|
49
|
+
detectAllCLIs.mockResolvedValue(new Map());
|
|
50
|
+
fs.readFile.mockResolvedValue(
|
|
51
|
+
JSON.stringify({
|
|
52
|
+
router: { devserver: { url: 'https://devserver.example.com' } },
|
|
53
|
+
})
|
|
54
|
+
);
|
|
55
|
+
fs.writeFile.mockResolvedValue();
|
|
56
|
+
|
|
57
|
+
const result = await execute({ projectDir: '/project', dryRun: true });
|
|
58
|
+
|
|
59
|
+
expect(result.devserver).toBeDefined();
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('shows routing table', async () => {
|
|
63
|
+
detectAllCLIs.mockResolvedValue(
|
|
64
|
+
new Map([
|
|
65
|
+
['claude', { name: 'claude', version: 'v4.0.0' }],
|
|
66
|
+
['codex', { name: 'codex', version: 'v1.0.0' }],
|
|
67
|
+
])
|
|
68
|
+
);
|
|
69
|
+
fs.readFile.mockRejectedValue(new Error('ENOENT'));
|
|
70
|
+
fs.writeFile.mockResolvedValue();
|
|
71
|
+
|
|
72
|
+
const result = await execute({ projectDir: '/project', dryRun: true });
|
|
73
|
+
|
|
74
|
+
expect(result.routingTable).toBeDefined();
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('shows cost estimate', async () => {
|
|
78
|
+
detectAllCLIs.mockResolvedValue(new Map());
|
|
79
|
+
fs.readFile.mockRejectedValue(new Error('ENOENT'));
|
|
80
|
+
fs.writeFile.mockResolvedValue();
|
|
81
|
+
|
|
82
|
+
const result = await execute({ projectDir: '/project', dryRun: true });
|
|
83
|
+
|
|
84
|
+
expect(result.costEstimate).toBeDefined();
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
describe('detectLocalCLIs', () => {
|
|
89
|
+
it('returns detected CLI info', async () => {
|
|
90
|
+
detectAllCLIs.mockResolvedValue(
|
|
91
|
+
new Map([
|
|
92
|
+
['claude', { name: 'claude', version: 'v4.0.0', detected: true }],
|
|
93
|
+
['gemini', { name: 'gemini', version: 'v1.2.0', detected: true }],
|
|
94
|
+
])
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
const result = await detectLocalCLIs();
|
|
98
|
+
|
|
99
|
+
expect(result.claude).toBeDefined();
|
|
100
|
+
expect(result.claude.detected).toBe(true);
|
|
101
|
+
expect(result.gemini).toBeDefined();
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('marks missing CLIs as not detected', async () => {
|
|
105
|
+
detectAllCLIs.mockResolvedValue(
|
|
106
|
+
new Map([['claude', { name: 'claude', detected: true }]])
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
const result = await detectLocalCLIs();
|
|
110
|
+
|
|
111
|
+
expect(result.claude.detected).toBe(true);
|
|
112
|
+
expect(result.codex).toBeUndefined();
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
describe('testDevserverConnection', () => {
|
|
117
|
+
it('returns connected true when reachable', async () => {
|
|
118
|
+
global.fetch = vi.fn().mockResolvedValue({
|
|
119
|
+
ok: true,
|
|
120
|
+
json: () => Promise.resolve({ healthy: true }),
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
const result = await testDevserverConnection(
|
|
124
|
+
'https://devserver.example.com'
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
expect(result.connected).toBe(true);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it('returns connected false when unreachable', async () => {
|
|
131
|
+
global.fetch = vi.fn().mockRejectedValue(new Error('Connection refused'));
|
|
132
|
+
|
|
133
|
+
const result = await testDevserverConnection(
|
|
134
|
+
'https://devserver.example.com'
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
expect(result.connected).toBe(false);
|
|
138
|
+
expect(result.error).toBeDefined();
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it('returns not configured when no URL', async () => {
|
|
142
|
+
const result = await testDevserverConnection(null);
|
|
143
|
+
|
|
144
|
+
expect(result.connected).toBe(false);
|
|
145
|
+
expect(result.configured).toBe(false);
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
describe('configureProvider', () => {
|
|
150
|
+
it('adds provider to config', () => {
|
|
151
|
+
const config = { providers: {} };
|
|
152
|
+
|
|
153
|
+
const updated = configureProvider(config, 'claude', {
|
|
154
|
+
type: 'cli',
|
|
155
|
+
command: 'claude',
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
expect(updated.providers.claude).toBeDefined();
|
|
159
|
+
expect(updated.providers.claude.type).toBe('cli');
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
it('updates existing provider', () => {
|
|
163
|
+
const config = {
|
|
164
|
+
providers: {
|
|
165
|
+
claude: { type: 'cli', command: 'old-command' },
|
|
166
|
+
},
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
const updated = configureProvider(config, 'claude', {
|
|
170
|
+
type: 'cli',
|
|
171
|
+
command: 'new-command',
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
expect(updated.providers.claude.command).toBe('new-command');
|
|
175
|
+
});
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
describe('configureCapability', () => {
|
|
179
|
+
it('sets providers for capability', () => {
|
|
180
|
+
const config = { capabilities: {} };
|
|
181
|
+
|
|
182
|
+
const updated = configureCapability(config, 'review', ['claude', 'codex']);
|
|
183
|
+
|
|
184
|
+
expect(updated.capabilities.review).toBeDefined();
|
|
185
|
+
expect(updated.capabilities.review.providers).toEqual(['claude', 'codex']);
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
it('updates existing capability', () => {
|
|
189
|
+
const config = {
|
|
190
|
+
capabilities: {
|
|
191
|
+
review: { providers: ['claude'] },
|
|
192
|
+
},
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
const updated = configureCapability(config, 'review', [
|
|
196
|
+
'claude',
|
|
197
|
+
'codex',
|
|
198
|
+
'deepseek',
|
|
199
|
+
]);
|
|
200
|
+
|
|
201
|
+
expect(updated.capabilities.review.providers).toEqual([
|
|
202
|
+
'claude',
|
|
203
|
+
'codex',
|
|
204
|
+
'deepseek',
|
|
205
|
+
]);
|
|
206
|
+
});
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
describe('testProvider', () => {
|
|
210
|
+
it('validates CLI provider connectivity', async () => {
|
|
211
|
+
const provider = {
|
|
212
|
+
type: 'cli',
|
|
213
|
+
command: 'claude',
|
|
214
|
+
detected: true,
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
const result = await testProvider(provider);
|
|
218
|
+
|
|
219
|
+
expect(result.available).toBe(true);
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
it('validates API provider connectivity', async () => {
|
|
223
|
+
global.fetch = vi.fn().mockResolvedValue({
|
|
224
|
+
ok: true,
|
|
225
|
+
json: () => Promise.resolve({}),
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
const provider = {
|
|
229
|
+
type: 'api',
|
|
230
|
+
baseUrl: 'https://api.deepseek.com',
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
const result = await testProvider(provider);
|
|
234
|
+
|
|
235
|
+
expect(result.available).toBe(true);
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
it('returns unavailable for missing CLI', async () => {
|
|
239
|
+
const provider = {
|
|
240
|
+
type: 'cli',
|
|
241
|
+
command: 'nonexistent',
|
|
242
|
+
detected: false,
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
const result = await testProvider(provider);
|
|
246
|
+
|
|
247
|
+
expect(result.available).toBe(false);
|
|
248
|
+
});
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
describe('formatRoutingSummary', () => {
|
|
252
|
+
it('shows local vs devserver routing', () => {
|
|
253
|
+
const config = {
|
|
254
|
+
providers: {
|
|
255
|
+
claude: { type: 'cli', detected: true },
|
|
256
|
+
codex: { type: 'cli', detected: false },
|
|
257
|
+
deepseek: { type: 'api' },
|
|
258
|
+
},
|
|
259
|
+
capabilities: {
|
|
260
|
+
review: { providers: ['claude', 'codex', 'deepseek'] },
|
|
261
|
+
},
|
|
262
|
+
};
|
|
263
|
+
|
|
264
|
+
const summary = formatRoutingSummary(config);
|
|
265
|
+
|
|
266
|
+
expect(summary).toContain('claude');
|
|
267
|
+
expect(summary).toContain('local');
|
|
268
|
+
expect(summary).toContain('devserver');
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
it('groups by capability', () => {
|
|
272
|
+
const config = {
|
|
273
|
+
providers: {
|
|
274
|
+
claude: { type: 'cli', detected: true },
|
|
275
|
+
gemini: { type: 'cli', detected: true },
|
|
276
|
+
},
|
|
277
|
+
capabilities: {
|
|
278
|
+
review: { providers: ['claude'] },
|
|
279
|
+
design: { providers: ['gemini'] },
|
|
280
|
+
},
|
|
281
|
+
};
|
|
282
|
+
|
|
283
|
+
const summary = formatRoutingSummary(config);
|
|
284
|
+
|
|
285
|
+
expect(summary).toContain('review');
|
|
286
|
+
expect(summary).toContain('design');
|
|
287
|
+
});
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
describe('estimateCosts', () => {
|
|
291
|
+
it('calculates per capability costs', () => {
|
|
292
|
+
const config = {
|
|
293
|
+
providers: {
|
|
294
|
+
claude: { type: 'cli', detected: true },
|
|
295
|
+
deepseek: { type: 'api', devserverOnly: true },
|
|
296
|
+
},
|
|
297
|
+
capabilities: {
|
|
298
|
+
review: { providers: ['claude', 'deepseek'] },
|
|
299
|
+
},
|
|
300
|
+
};
|
|
301
|
+
|
|
302
|
+
const estimate = estimateCosts(config, { reviewsPerDay: 10 });
|
|
303
|
+
|
|
304
|
+
expect(estimate.review).toBeDefined();
|
|
305
|
+
expect(estimate.review.local).toBeDefined();
|
|
306
|
+
expect(estimate.review.devserver).toBeDefined();
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
it('shows zero cost for local providers', () => {
|
|
310
|
+
const config = {
|
|
311
|
+
providers: {
|
|
312
|
+
claude: { type: 'cli', detected: true },
|
|
313
|
+
},
|
|
314
|
+
capabilities: {
|
|
315
|
+
review: { providers: ['claude'] },
|
|
316
|
+
},
|
|
317
|
+
};
|
|
318
|
+
|
|
319
|
+
const estimate = estimateCosts(config, { reviewsPerDay: 10 });
|
|
320
|
+
|
|
321
|
+
expect(estimate.review.local).toBe(0);
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
it('estimates API costs', () => {
|
|
325
|
+
const config = {
|
|
326
|
+
providers: {
|
|
327
|
+
deepseek: { type: 'api', devserverOnly: true },
|
|
328
|
+
},
|
|
329
|
+
capabilities: {
|
|
330
|
+
review: { providers: ['deepseek'] },
|
|
331
|
+
},
|
|
332
|
+
};
|
|
333
|
+
|
|
334
|
+
const estimate = estimateCosts(config, { reviewsPerDay: 10 });
|
|
335
|
+
|
|
336
|
+
expect(estimate.review.devserver).toBeGreaterThan(0);
|
|
337
|
+
});
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
describe('saveConfig', () => {
|
|
341
|
+
it('writes .tlc.json', async () => {
|
|
342
|
+
fs.readFile.mockResolvedValue('{}');
|
|
343
|
+
fs.writeFile.mockResolvedValue();
|
|
344
|
+
|
|
345
|
+
const config = {
|
|
346
|
+
providers: { claude: { type: 'cli' } },
|
|
347
|
+
};
|
|
348
|
+
|
|
349
|
+
await saveConfig('/project', config);
|
|
350
|
+
|
|
351
|
+
expect(fs.writeFile).toHaveBeenCalled();
|
|
352
|
+
const writeCall = fs.writeFile.mock.calls[0];
|
|
353
|
+
expect(writeCall[0]).toContain('.tlc.json');
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
it('merges with existing config', async () => {
|
|
357
|
+
fs.readFile.mockResolvedValue(
|
|
358
|
+
JSON.stringify({ testFrameworks: { primary: 'vitest' } })
|
|
359
|
+
);
|
|
360
|
+
fs.writeFile.mockResolvedValue();
|
|
361
|
+
|
|
362
|
+
const routerConfig = {
|
|
363
|
+
providers: { claude: { type: 'cli' } },
|
|
364
|
+
};
|
|
365
|
+
|
|
366
|
+
await saveConfig('/project', routerConfig);
|
|
367
|
+
|
|
368
|
+
const writeCall = fs.writeFile.mock.calls[0];
|
|
369
|
+
const written = JSON.parse(writeCall[1]);
|
|
370
|
+
|
|
371
|
+
expect(written.testFrameworks.primary).toBe('vitest');
|
|
372
|
+
expect(written.router.providers.claude).toBeDefined();
|
|
373
|
+
});
|
|
374
|
+
});
|
|
375
|
+
});
|