tlc-claude-code 1.4.4 → 1.4.5
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 +28 -2
- package/dashboard/dist/api/health-diagnostics.d.ts +26 -0
- package/dashboard/dist/api/health-diagnostics.js +85 -0
- package/dashboard/dist/api/health-diagnostics.test.d.ts +1 -0
- package/dashboard/dist/api/health-diagnostics.test.js +126 -0
- package/dashboard/dist/api/index.d.ts +5 -0
- package/dashboard/dist/api/index.js +5 -0
- package/dashboard/dist/api/notes-api.d.ts +18 -0
- package/dashboard/dist/api/notes-api.js +68 -0
- package/dashboard/dist/api/notes-api.test.d.ts +1 -0
- package/dashboard/dist/api/notes-api.test.js +113 -0
- package/dashboard/dist/api/safeFetch.d.ts +50 -0
- package/dashboard/dist/api/safeFetch.js +135 -0
- package/dashboard/dist/api/safeFetch.test.d.ts +1 -0
- package/dashboard/dist/api/safeFetch.test.js +215 -0
- package/dashboard/dist/api/tasks-api.d.ts +32 -0
- package/dashboard/dist/api/tasks-api.js +98 -0
- package/dashboard/dist/api/tasks-api.test.d.ts +1 -0
- package/dashboard/dist/api/tasks-api.test.js +383 -0
- package/dashboard/dist/components/BugsPane.d.ts +20 -0
- package/dashboard/dist/components/BugsPane.js +210 -0
- package/dashboard/dist/components/BugsPane.test.d.ts +1 -0
- package/dashboard/dist/components/BugsPane.test.js +256 -0
- package/dashboard/dist/components/HealthPane.d.ts +3 -1
- package/dashboard/dist/components/HealthPane.js +44 -6
- package/dashboard/dist/components/HealthPane.test.js +105 -2
- package/dashboard/dist/components/RouterPane.d.ts +4 -3
- package/dashboard/dist/components/RouterPane.js +60 -57
- package/dashboard/dist/components/RouterPane.test.js +150 -96
- package/dashboard/dist/components/UpdateBanner.d.ts +26 -0
- package/dashboard/dist/components/UpdateBanner.js +30 -0
- package/dashboard/dist/components/UpdateBanner.test.d.ts +1 -0
- package/dashboard/dist/components/UpdateBanner.test.js +96 -0
- package/dashboard/dist/components/ui/EmptyState.d.ts +14 -0
- package/dashboard/dist/components/ui/EmptyState.js +58 -0
- package/dashboard/dist/components/ui/EmptyState.test.d.ts +1 -0
- package/dashboard/dist/components/ui/EmptyState.test.js +97 -0
- package/dashboard/dist/components/ui/ErrorState.d.ts +17 -0
- package/dashboard/dist/components/ui/ErrorState.js +80 -0
- package/dashboard/dist/components/ui/ErrorState.test.d.ts +1 -0
- package/dashboard/dist/components/ui/ErrorState.test.js +166 -0
- package/dashboard/package.json +3 -0
- package/package.json +1 -1
- package/server/dashboard/index.html +205 -8
- package/server/index.js +64 -0
- package/server/lib/api-provider.js +104 -186
- package/server/lib/api-provider.test.js +238 -336
- package/server/lib/cli-detector.js +90 -166
- package/server/lib/cli-detector.test.js +114 -269
- package/server/lib/cli-provider.js +142 -212
- package/server/lib/cli-provider.test.js +196 -349
- package/server/lib/debug.test.js +1 -1
- package/server/lib/devserver-router-api.js +54 -249
- package/server/lib/devserver-router-api.test.js +126 -426
- package/server/lib/introspect.js +309 -0
- package/server/lib/introspect.test.js +286 -0
- package/server/lib/model-router.js +107 -245
- package/server/lib/model-router.test.js +122 -313
- package/server/lib/output-schemas.js +146 -269
- package/server/lib/output-schemas.test.js +106 -307
- package/server/lib/provider-interface.js +99 -153
- package/server/lib/provider-interface.test.js +228 -394
- package/server/lib/provider-queue.js +164 -158
- package/server/lib/provider-queue.test.js +186 -315
- package/server/lib/router-config.js +99 -221
- package/server/lib/router-config.test.js +83 -237
- package/server/lib/router-setup-command.js +94 -419
- package/server/lib/router-setup-command.test.js +96 -375
- package/server/lib/router-status-api.js +93 -0
- package/server/lib/router-status-api.test.js +270 -0
|
@@ -1,313 +1,122 @@
|
|
|
1
|
-
import { describe, it, expect, vi, beforeEach
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
resolveProvider,
|
|
5
|
-
resolveCapability,
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
describe('
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
expect(
|
|
66
|
-
});
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
describe('
|
|
70
|
-
it('
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
expect(
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
},
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
const router = await createRouter();
|
|
124
|
-
|
|
125
|
-
const result = router.resolveProvider('unknown');
|
|
126
|
-
|
|
127
|
-
expect(result).toBeNull();
|
|
128
|
-
});
|
|
129
|
-
});
|
|
130
|
-
|
|
131
|
-
describe('resolveCapability', () => {
|
|
132
|
-
it('returns ordered providers for capability', async () => {
|
|
133
|
-
detectAllCLIs.mockResolvedValue(new Map([
|
|
134
|
-
['claude', { name: 'claude' }],
|
|
135
|
-
]));
|
|
136
|
-
|
|
137
|
-
const router = await createRouter({
|
|
138
|
-
providers: {
|
|
139
|
-
claude: { type: 'cli', command: 'claude', capabilities: ['review'] },
|
|
140
|
-
deepseek: { type: 'api', baseUrl: 'https://api.deepseek.com', capabilities: ['review'] },
|
|
141
|
-
},
|
|
142
|
-
capabilities: {
|
|
143
|
-
review: { providers: ['claude', 'deepseek'] },
|
|
144
|
-
},
|
|
145
|
-
});
|
|
146
|
-
|
|
147
|
-
const providers = router.resolveCapability('review');
|
|
148
|
-
|
|
149
|
-
expect(providers.length).toBe(2);
|
|
150
|
-
expect(providers[0].name).toBe('claude');
|
|
151
|
-
expect(providers[1].name).toBe('deepseek');
|
|
152
|
-
});
|
|
153
|
-
|
|
154
|
-
it('filters by capability', async () => {
|
|
155
|
-
detectAllCLIs.mockResolvedValue(new Map());
|
|
156
|
-
|
|
157
|
-
const router = await createRouter({
|
|
158
|
-
providers: {
|
|
159
|
-
claude: { type: 'cli', command: 'claude', capabilities: ['review', 'code-gen'] },
|
|
160
|
-
gemini: { type: 'cli', command: 'gemini', capabilities: ['design'] },
|
|
161
|
-
},
|
|
162
|
-
capabilities: {
|
|
163
|
-
review: { providers: ['claude'] },
|
|
164
|
-
design: { providers: ['gemini'] },
|
|
165
|
-
},
|
|
166
|
-
});
|
|
167
|
-
|
|
168
|
-
const reviewProviders = router.resolveCapability('review');
|
|
169
|
-
const designProviders = router.resolveCapability('design');
|
|
170
|
-
|
|
171
|
-
expect(reviewProviders.length).toBe(1);
|
|
172
|
-
expect(reviewProviders[0].name).toBe('claude');
|
|
173
|
-
|
|
174
|
-
expect(designProviders.length).toBe(1);
|
|
175
|
-
expect(designProviders[0].name).toBe('gemini');
|
|
176
|
-
});
|
|
177
|
-
|
|
178
|
-
it('returns empty array for unknown capability', async () => {
|
|
179
|
-
detectAllCLIs.mockResolvedValue(new Map());
|
|
180
|
-
|
|
181
|
-
const router = await createRouter();
|
|
182
|
-
|
|
183
|
-
const providers = router.resolveCapability('unknown');
|
|
184
|
-
|
|
185
|
-
expect(providers).toEqual([]);
|
|
186
|
-
});
|
|
187
|
-
});
|
|
188
|
-
|
|
189
|
-
describe('cascade behavior', () => {
|
|
190
|
-
it('tries local first', async () => {
|
|
191
|
-
detectAllCLIs.mockResolvedValue(new Map([
|
|
192
|
-
['claude', { name: 'claude', detected: true }],
|
|
193
|
-
]));
|
|
194
|
-
|
|
195
|
-
const router = await createRouter({
|
|
196
|
-
providers: {
|
|
197
|
-
claude: { type: 'cli', command: 'claude', capabilities: ['review'] },
|
|
198
|
-
},
|
|
199
|
-
devserver: { url: 'https://devserver.example.com' },
|
|
200
|
-
});
|
|
201
|
-
|
|
202
|
-
const result = router.resolveProvider('claude');
|
|
203
|
-
|
|
204
|
-
expect(result.via).toBe('local');
|
|
205
|
-
});
|
|
206
|
-
|
|
207
|
-
it('falls back to devserver when local unavailable', async () => {
|
|
208
|
-
detectAllCLIs.mockResolvedValue(new Map()); // No CLIs detected
|
|
209
|
-
|
|
210
|
-
const router = await createRouter({
|
|
211
|
-
providers: {
|
|
212
|
-
claude: { type: 'cli', command: 'claude', capabilities: ['review'] },
|
|
213
|
-
},
|
|
214
|
-
devserver: { url: 'https://devserver.example.com' },
|
|
215
|
-
});
|
|
216
|
-
|
|
217
|
-
const result = router.resolveProvider('claude');
|
|
218
|
-
|
|
219
|
-
expect(result.via).toBe('devserver');
|
|
220
|
-
});
|
|
221
|
-
});
|
|
222
|
-
|
|
223
|
-
describe('loadConfig', () => {
|
|
224
|
-
it('reads from .tlc.json', async () => {
|
|
225
|
-
const config = {
|
|
226
|
-
router: {
|
|
227
|
-
providers: { claude: { type: 'cli', command: 'claude' } },
|
|
228
|
-
},
|
|
229
|
-
};
|
|
230
|
-
|
|
231
|
-
fs.readFile.mockResolvedValue(JSON.stringify(config));
|
|
232
|
-
|
|
233
|
-
const loaded = await loadConfig('/project');
|
|
234
|
-
|
|
235
|
-
expect(fs.readFile).toHaveBeenCalledWith('/project/.tlc.json', 'utf8');
|
|
236
|
-
expect(loaded.providers.claude).toBeDefined();
|
|
237
|
-
});
|
|
238
|
-
|
|
239
|
-
it('uses defaults when file missing', async () => {
|
|
240
|
-
fs.readFile.mockRejectedValue(new Error('ENOENT'));
|
|
241
|
-
|
|
242
|
-
const loaded = await loadConfig('/project');
|
|
243
|
-
|
|
244
|
-
expect(loaded).toEqual(DEFAULT_CONFIG);
|
|
245
|
-
});
|
|
246
|
-
|
|
247
|
-
it('merges with defaults', async () => {
|
|
248
|
-
const config = {
|
|
249
|
-
router: {
|
|
250
|
-
providers: { custom: { type: 'api', baseUrl: 'https://example.com' } },
|
|
251
|
-
},
|
|
252
|
-
};
|
|
253
|
-
|
|
254
|
-
fs.readFile.mockResolvedValue(JSON.stringify(config));
|
|
255
|
-
|
|
256
|
-
const loaded = await loadConfig('/project');
|
|
257
|
-
|
|
258
|
-
// Should have custom provider
|
|
259
|
-
expect(loaded.providers.custom).toBeDefined();
|
|
260
|
-
// Should still have defaults
|
|
261
|
-
expect(loaded.providers.claude).toBeDefined();
|
|
262
|
-
});
|
|
263
|
-
});
|
|
264
|
-
|
|
265
|
-
describe('DEFAULT_CONFIG', () => {
|
|
266
|
-
it('includes claude provider', () => {
|
|
267
|
-
expect(DEFAULT_CONFIG.providers.claude).toBeDefined();
|
|
268
|
-
expect(DEFAULT_CONFIG.providers.claude.type).toBe('cli');
|
|
269
|
-
});
|
|
270
|
-
|
|
271
|
-
it('includes codex provider', () => {
|
|
272
|
-
expect(DEFAULT_CONFIG.providers.codex).toBeDefined();
|
|
273
|
-
expect(DEFAULT_CONFIG.providers.codex.type).toBe('cli');
|
|
274
|
-
});
|
|
275
|
-
|
|
276
|
-
it('includes gemini provider', () => {
|
|
277
|
-
expect(DEFAULT_CONFIG.providers.gemini).toBeDefined();
|
|
278
|
-
expect(DEFAULT_CONFIG.providers.gemini.type).toBe('cli');
|
|
279
|
-
});
|
|
280
|
-
|
|
281
|
-
it('includes deepseek provider', () => {
|
|
282
|
-
expect(DEFAULT_CONFIG.providers.deepseek).toBeDefined();
|
|
283
|
-
expect(DEFAULT_CONFIG.providers.deepseek.type).toBe('api');
|
|
284
|
-
});
|
|
285
|
-
|
|
286
|
-
it('includes review capability', () => {
|
|
287
|
-
expect(DEFAULT_CONFIG.capabilities.review).toBeDefined();
|
|
288
|
-
expect(DEFAULT_CONFIG.capabilities.review.providers).toContain('claude');
|
|
289
|
-
});
|
|
290
|
-
});
|
|
291
|
-
|
|
292
|
-
describe('handleUnavailable', () => {
|
|
293
|
-
it('skips unavailable providers', async () => {
|
|
294
|
-
detectAllCLIs.mockResolvedValue(new Map()); // No local CLIs
|
|
295
|
-
|
|
296
|
-
const router = await createRouter({
|
|
297
|
-
providers: {
|
|
298
|
-
claude: { type: 'cli', command: 'claude', capabilities: ['review'] },
|
|
299
|
-
deepseek: { type: 'api', baseUrl: 'https://api.deepseek.com', capabilities: ['review'] },
|
|
300
|
-
},
|
|
301
|
-
capabilities: {
|
|
302
|
-
review: { providers: ['claude', 'deepseek'] },
|
|
303
|
-
},
|
|
304
|
-
devserver: { url: 'https://devserver.example.com' },
|
|
305
|
-
});
|
|
306
|
-
|
|
307
|
-
const providers = router.resolveCapability('review');
|
|
308
|
-
|
|
309
|
-
// Both should be available (claude via devserver, deepseek via api)
|
|
310
|
-
expect(providers.length).toBe(2);
|
|
311
|
-
});
|
|
312
|
-
});
|
|
313
|
-
});
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
+
import {
|
|
3
|
+
ModelRouter,
|
|
4
|
+
resolveProvider,
|
|
5
|
+
resolveCapability,
|
|
6
|
+
} from './model-router.js';
|
|
7
|
+
|
|
8
|
+
describe('Model Router', () => {
|
|
9
|
+
let router;
|
|
10
|
+
|
|
11
|
+
beforeEach(() => {
|
|
12
|
+
router = new ModelRouter({
|
|
13
|
+
providers: {
|
|
14
|
+
claude: { type: 'cli', command: 'claude', capabilities: ['review', 'code-gen'] },
|
|
15
|
+
codex: { type: 'cli', command: 'codex', capabilities: ['review', 'code-gen'] },
|
|
16
|
+
deepseek: { type: 'api', baseUrl: 'https://api.deepseek.com', capabilities: ['review'] },
|
|
17
|
+
},
|
|
18
|
+
capabilities: {
|
|
19
|
+
review: { providers: ['claude', 'codex', 'deepseek'] },
|
|
20
|
+
'code-gen': { providers: ['claude', 'codex'] },
|
|
21
|
+
},
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
describe('resolveProvider', () => {
|
|
26
|
+
it('returns local when CLI detected', async () => {
|
|
27
|
+
router._detectCLI = vi.fn().mockResolvedValue({ found: true, path: '/usr/bin/claude' });
|
|
28
|
+
|
|
29
|
+
const provider = await router.resolveProvider('claude');
|
|
30
|
+
|
|
31
|
+
expect(provider.location).toBe('local');
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('returns devserver when CLI not detected', async () => {
|
|
35
|
+
router._detectCLI = vi.fn().mockResolvedValue({ found: false });
|
|
36
|
+
router.devserverUrl = 'https://dev.example.com';
|
|
37
|
+
|
|
38
|
+
const provider = await router.resolveProvider('claude');
|
|
39
|
+
|
|
40
|
+
expect(provider.location).toBe('devserver');
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('returns devserver for API type', async () => {
|
|
44
|
+
router.devserverUrl = 'https://dev.example.com';
|
|
45
|
+
|
|
46
|
+
const provider = await router.resolveProvider('deepseek');
|
|
47
|
+
|
|
48
|
+
expect(provider.location).toBe('devserver');
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
describe('resolveCapability', () => {
|
|
53
|
+
it('returns ordered providers', async () => {
|
|
54
|
+
const providers = await router.resolveCapability('review');
|
|
55
|
+
|
|
56
|
+
expect(providers.length).toBeGreaterThan(0);
|
|
57
|
+
expect(providers[0]).toHaveProperty('name');
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('filters by capability', async () => {
|
|
61
|
+
const reviewProviders = await router.resolveCapability('review');
|
|
62
|
+
const codeGenProviders = await router.resolveCapability('code-gen');
|
|
63
|
+
|
|
64
|
+
expect(reviewProviders.length).toBe(3);
|
|
65
|
+
expect(codeGenProviders.length).toBe(2);
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
describe('cascade', () => {
|
|
70
|
+
it('tries local first', async () => {
|
|
71
|
+
router._detectCLI = vi.fn().mockResolvedValue({ found: true, path: '/usr/bin/claude' });
|
|
72
|
+
|
|
73
|
+
const provider = await router.resolveProvider('claude');
|
|
74
|
+
|
|
75
|
+
expect(provider.location).toBe('local');
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('falls back to devserver', async () => {
|
|
79
|
+
router._detectCLI = vi.fn().mockResolvedValue({ found: false });
|
|
80
|
+
router.devserverUrl = 'https://dev.example.com';
|
|
81
|
+
|
|
82
|
+
const provider = await router.resolveProvider('claude');
|
|
83
|
+
|
|
84
|
+
expect(provider.location).toBe('devserver');
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
describe('loadConfig', () => {
|
|
89
|
+
it('reads from .tlc.json', async () => {
|
|
90
|
+
router._readConfig = vi.fn().mockResolvedValue({
|
|
91
|
+
router: {
|
|
92
|
+
providers: { test: { type: 'cli', command: 'test' } },
|
|
93
|
+
},
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
await router.loadConfig();
|
|
97
|
+
|
|
98
|
+
expect(router.config.providers).toHaveProperty('test');
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it('uses defaults when missing', async () => {
|
|
102
|
+
router._readConfig = vi.fn().mockResolvedValue({});
|
|
103
|
+
|
|
104
|
+
await router.loadConfig();
|
|
105
|
+
|
|
106
|
+
expect(router.config).toBeDefined();
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
describe('handleUnavailable', () => {
|
|
111
|
+
it('skips to next provider', async () => {
|
|
112
|
+
router._detectCLI = vi.fn()
|
|
113
|
+
.mockResolvedValueOnce({ found: false })
|
|
114
|
+
.mockResolvedValueOnce({ found: true, path: '/usr/bin/codex' });
|
|
115
|
+
|
|
116
|
+
const providers = await router.resolveCapability('review');
|
|
117
|
+
const available = providers.filter(p => p.available);
|
|
118
|
+
|
|
119
|
+
expect(available.length).toBeGreaterThan(0);
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
});
|