tlc-claude-code 1.4.4 → 1.4.6

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 (72) hide show
  1. package/dashboard/dist/App.js +28 -2
  2. package/dashboard/dist/api/health-diagnostics.d.ts +26 -0
  3. package/dashboard/dist/api/health-diagnostics.js +85 -0
  4. package/dashboard/dist/api/health-diagnostics.test.d.ts +1 -0
  5. package/dashboard/dist/api/health-diagnostics.test.js +126 -0
  6. package/dashboard/dist/api/index.d.ts +5 -0
  7. package/dashboard/dist/api/index.js +5 -0
  8. package/dashboard/dist/api/notes-api.d.ts +18 -0
  9. package/dashboard/dist/api/notes-api.js +68 -0
  10. package/dashboard/dist/api/notes-api.test.d.ts +1 -0
  11. package/dashboard/dist/api/notes-api.test.js +113 -0
  12. package/dashboard/dist/api/safeFetch.d.ts +50 -0
  13. package/dashboard/dist/api/safeFetch.js +135 -0
  14. package/dashboard/dist/api/safeFetch.test.d.ts +1 -0
  15. package/dashboard/dist/api/safeFetch.test.js +215 -0
  16. package/dashboard/dist/api/tasks-api.d.ts +32 -0
  17. package/dashboard/dist/api/tasks-api.js +98 -0
  18. package/dashboard/dist/api/tasks-api.test.d.ts +1 -0
  19. package/dashboard/dist/api/tasks-api.test.js +383 -0
  20. package/dashboard/dist/components/BugsPane.d.ts +20 -0
  21. package/dashboard/dist/components/BugsPane.js +210 -0
  22. package/dashboard/dist/components/BugsPane.test.d.ts +1 -0
  23. package/dashboard/dist/components/BugsPane.test.js +256 -0
  24. package/dashboard/dist/components/HealthPane.d.ts +3 -1
  25. package/dashboard/dist/components/HealthPane.js +44 -6
  26. package/dashboard/dist/components/HealthPane.test.js +105 -2
  27. package/dashboard/dist/components/RouterPane.d.ts +4 -3
  28. package/dashboard/dist/components/RouterPane.js +60 -57
  29. package/dashboard/dist/components/RouterPane.test.js +150 -96
  30. package/dashboard/dist/components/UpdateBanner.d.ts +26 -0
  31. package/dashboard/dist/components/UpdateBanner.js +30 -0
  32. package/dashboard/dist/components/UpdateBanner.test.d.ts +1 -0
  33. package/dashboard/dist/components/UpdateBanner.test.js +96 -0
  34. package/dashboard/dist/components/ui/EmptyState.d.ts +14 -0
  35. package/dashboard/dist/components/ui/EmptyState.js +58 -0
  36. package/dashboard/dist/components/ui/EmptyState.test.d.ts +1 -0
  37. package/dashboard/dist/components/ui/EmptyState.test.js +97 -0
  38. package/dashboard/dist/components/ui/ErrorState.d.ts +17 -0
  39. package/dashboard/dist/components/ui/ErrorState.js +80 -0
  40. package/dashboard/dist/components/ui/ErrorState.test.d.ts +1 -0
  41. package/dashboard/dist/components/ui/ErrorState.test.js +166 -0
  42. package/dashboard/package.json +3 -0
  43. package/package.json +4 -1
  44. package/server/dashboard/index.html +284 -13
  45. package/server/dashboard/login.html +262 -0
  46. package/server/index.js +304 -0
  47. package/server/lib/api-provider.js +104 -186
  48. package/server/lib/api-provider.test.js +238 -336
  49. package/server/lib/cli-detector.js +90 -166
  50. package/server/lib/cli-detector.test.js +114 -269
  51. package/server/lib/cli-provider.js +142 -212
  52. package/server/lib/cli-provider.test.js +196 -349
  53. package/server/lib/debug.test.js +3 -3
  54. package/server/lib/devserver-router-api.js +54 -249
  55. package/server/lib/devserver-router-api.test.js +126 -426
  56. package/server/lib/introspect.js +309 -0
  57. package/server/lib/introspect.test.js +286 -0
  58. package/server/lib/model-router.js +107 -245
  59. package/server/lib/model-router.test.js +122 -313
  60. package/server/lib/output-schemas.js +146 -269
  61. package/server/lib/output-schemas.test.js +106 -307
  62. package/server/lib/plan-parser.js +59 -16
  63. package/server/lib/provider-interface.js +99 -153
  64. package/server/lib/provider-interface.test.js +228 -394
  65. package/server/lib/provider-queue.js +164 -158
  66. package/server/lib/provider-queue.test.js +186 -315
  67. package/server/lib/router-config.js +99 -221
  68. package/server/lib/router-config.test.js +83 -237
  69. package/server/lib/router-setup-command.js +94 -419
  70. package/server/lib/router-setup-command.test.js +96 -375
  71. package/server/lib/router-status-api.js +93 -0
  72. package/server/lib/router-status-api.test.js +270 -0
@@ -1,419 +1,94 @@
1
- /**
2
- * Router Setup Command - Interactive setup for multi-model routing
3
- */
4
-
5
- import { detectAllCLIs } from './cli-detector.js';
6
- import fs from 'fs/promises';
7
- import path from 'path';
8
-
9
- /**
10
- * Default cost estimates per 1K tokens (in USD)
11
- */
12
- const API_COSTS = {
13
- deepseek: { input: 0.0001, output: 0.0002 },
14
- mistral: { input: 0.0002, output: 0.0006 },
15
- default: { input: 0.001, output: 0.002 },
16
- };
17
-
18
- /**
19
- * Average tokens per request type
20
- */
21
- const AVG_TOKENS = {
22
- review: { input: 2000, output: 500 },
23
- design: { input: 1000, output: 2000 },
24
- 'code-gen': { input: 500, output: 1500 },
25
- };
26
-
27
- /**
28
- * Execute the router setup command
29
- * @param {Object} options - Command options
30
- * @param {string} options.projectDir - Project directory
31
- * @param {boolean} [options.dryRun] - Don't write config
32
- * @returns {Promise<Object>} Setup result
33
- */
34
- export async function execute(options = {}) {
35
- const { projectDir = process.cwd(), dryRun = false } = options;
36
-
37
- // Step 1: Detect local CLIs
38
- const detected = await detectLocalCLIs();
39
-
40
- // Step 2: Load existing config
41
- let existingConfig = {};
42
- try {
43
- const configPath = path.join(projectDir, '.tlc.json');
44
- const content = await fs.readFile(configPath, 'utf8');
45
- existingConfig = JSON.parse(content);
46
- } catch (err) {
47
- // No existing config
48
- }
49
-
50
- // Step 3: Test devserver connection
51
- const devserverUrl = existingConfig.router?.devserver?.url;
52
- const devserver = await testDevserverConnection(devserverUrl);
53
-
54
- // Step 4: Build routing table
55
- const routingTable = buildRoutingTable(detected, devserver);
56
-
57
- // Step 5: Estimate costs
58
- const costEstimate = estimateCosts(
59
- {
60
- providers: buildProviderConfig(detected),
61
- capabilities: buildCapabilityConfig(detected),
62
- },
63
- { reviewsPerDay: 10, designsPerDay: 2, codeGensPerDay: 5 }
64
- );
65
-
66
- // Step 6: Build final config
67
- const routerConfig = {
68
- providers: buildProviderConfig(detected),
69
- capabilities: buildCapabilityConfig(detected),
70
- devserver: {
71
- url: devserverUrl || null,
72
- queue: {
73
- maxConcurrent: 3,
74
- timeout: 120000,
75
- },
76
- },
77
- };
78
-
79
- // Step 7: Save config (unless dry run)
80
- if (!dryRun) {
81
- await saveConfig(projectDir, routerConfig);
82
- }
83
-
84
- return {
85
- detected,
86
- devserver,
87
- routingTable,
88
- costEstimate,
89
- config: routerConfig,
90
- };
91
- }
92
-
93
- /**
94
- * Detect locally installed CLIs
95
- * @returns {Promise<Object>} Detected CLI info
96
- */
97
- export async function detectLocalCLIs() {
98
- const detected = await detectAllCLIs();
99
- const result = {};
100
-
101
- for (const [name, info] of detected) {
102
- result[name] = {
103
- detected: true,
104
- version: info.version,
105
- capabilities: info.capabilities || [],
106
- };
107
- }
108
-
109
- return result;
110
- }
111
-
112
- /**
113
- * Test devserver connection
114
- * @param {string|null} url - Devserver URL
115
- * @returns {Promise<Object>} Connection result
116
- */
117
- export async function testDevserverConnection(url) {
118
- if (!url) {
119
- return { connected: false, configured: false };
120
- }
121
-
122
- try {
123
- const response = await fetch(`${url}/api/health`);
124
- if (response.ok) {
125
- const data = await response.json();
126
- return {
127
- connected: true,
128
- configured: true,
129
- healthy: data.healthy,
130
- providers: data.providers,
131
- };
132
- }
133
- return { connected: false, configured: true, error: 'Unhealthy response' };
134
- } catch (err) {
135
- return { connected: false, configured: true, error: err.message };
136
- }
137
- }
138
-
139
- /**
140
- * Configure a provider
141
- * @param {Object} config - Current config
142
- * @param {string} name - Provider name
143
- * @param {Object} providerConfig - Provider configuration
144
- * @returns {Object} Updated config
145
- */
146
- export function configureProvider(config, name, providerConfig) {
147
- return {
148
- ...config,
149
- providers: {
150
- ...config.providers,
151
- [name]: providerConfig,
152
- },
153
- };
154
- }
155
-
156
- /**
157
- * Configure a capability
158
- * @param {Object} config - Current config
159
- * @param {string} name - Capability name
160
- * @param {string[]} providers - Provider names
161
- * @returns {Object} Updated config
162
- */
163
- export function configureCapability(config, name, providers) {
164
- return {
165
- ...config,
166
- capabilities: {
167
- ...config.capabilities,
168
- [name]: { providers },
169
- },
170
- };
171
- }
172
-
173
- /**
174
- * Test provider connectivity
175
- * @param {Object} provider - Provider config
176
- * @returns {Promise<Object>} Test result
177
- */
178
- export async function testProvider(provider) {
179
- if (provider.type === 'cli') {
180
- // CLI providers are available if detected
181
- return {
182
- available: provider.detected === true,
183
- via: provider.detected ? 'local' : 'devserver',
184
- };
185
- }
186
-
187
- if (provider.type === 'api') {
188
- // Test API endpoint
189
- try {
190
- const response = await fetch(`${provider.baseUrl}/v1/models`);
191
- return { available: response.ok, via: 'api' };
192
- } catch (err) {
193
- return { available: true, via: 'api', note: 'Endpoint not tested' };
194
- }
195
- }
196
-
197
- return { available: false, error: 'Unknown provider type' };
198
- }
199
-
200
- /**
201
- * Format routing summary
202
- * @param {Object} config - Router config
203
- * @returns {string} Formatted summary
204
- */
205
- export function formatRoutingSummary(config) {
206
- const lines = ['Routing Summary:', ''];
207
-
208
- for (const [capName, capConfig] of Object.entries(
209
- config.capabilities || {}
210
- )) {
211
- lines.push(` ${capName}:`);
212
-
213
- for (const providerName of capConfig.providers || []) {
214
- const provider = config.providers?.[providerName];
215
- if (!provider) continue;
216
-
217
- let routing = 'unknown';
218
- if (provider.type === 'cli') {
219
- routing = provider.detected ? 'local' : 'devserver';
220
- } else if (provider.type === 'api') {
221
- routing = 'devserver';
222
- }
223
-
224
- lines.push(` - ${providerName} (${routing})`);
225
- }
226
-
227
- lines.push('');
228
- }
229
-
230
- return lines.join('\n');
231
- }
232
-
233
- /**
234
- * Estimate costs
235
- * @param {Object} config - Router config
236
- * @param {Object} usage - Usage estimates
237
- * @returns {Object} Cost estimates
238
- */
239
- export function estimateCosts(config, usage = {}) {
240
- const estimate = {};
241
-
242
- for (const [capName, capConfig] of Object.entries(
243
- config.capabilities || {}
244
- )) {
245
- const perDay = usage[`${capName}sPerDay`] || usage.reviewsPerDay || 10;
246
- const tokens = AVG_TOKENS[capName] || AVG_TOKENS.review;
247
-
248
- let localCost = 0;
249
- let devserverCost = 0;
250
-
251
- for (const providerName of capConfig.providers || []) {
252
- const provider = config.providers?.[providerName];
253
- if (!provider) continue;
254
-
255
- if (provider.type === 'cli' && provider.detected) {
256
- // Local CLI is free
257
- localCost += 0;
258
- } else {
259
- // API or devserver costs money
260
- const pricing = API_COSTS[providerName] || API_COSTS.default;
261
- const inputCost = (tokens.input / 1000) * pricing.input * perDay * 30;
262
- const outputCost = (tokens.output / 1000) * pricing.output * perDay * 30;
263
- devserverCost += inputCost + outputCost;
264
- }
265
- }
266
-
267
- estimate[capName] = {
268
- local: localCost,
269
- devserver: Math.round(devserverCost * 100) / 100,
270
- };
271
- }
272
-
273
- return estimate;
274
- }
275
-
276
- /**
277
- * Save router config
278
- * @param {string} projectDir - Project directory
279
- * @param {Object} routerConfig - Router configuration
280
- */
281
- export async function saveConfig(projectDir, routerConfig) {
282
- const configPath = path.join(projectDir, '.tlc.json');
283
-
284
- // Read existing config
285
- let existingConfig = {};
286
- try {
287
- const content = await fs.readFile(configPath, 'utf8');
288
- existingConfig = JSON.parse(content);
289
- } catch (err) {
290
- // No existing config
291
- }
292
-
293
- // Merge router config
294
- const newConfig = {
295
- ...existingConfig,
296
- router: routerConfig,
297
- };
298
-
299
- await fs.writeFile(configPath, JSON.stringify(newConfig, null, 2));
300
- }
301
-
302
- /**
303
- * Build provider config from detected CLIs
304
- * @param {Object} detected - Detected CLI info
305
- * @returns {Object} Provider config
306
- */
307
- function buildProviderConfig(detected) {
308
- const providers = {};
309
-
310
- // Add detected CLIs
311
- if (detected.claude) {
312
- providers.claude = {
313
- type: 'cli',
314
- command: 'claude',
315
- detected: true,
316
- headlessArgs: ['-p', '--output-format', 'json'],
317
- capabilities: ['review', 'code-gen', 'refactor', 'explain'],
318
- };
319
- }
320
-
321
- if (detected.codex) {
322
- providers.codex = {
323
- type: 'cli',
324
- command: 'codex',
325
- detected: true,
326
- headlessArgs: ['exec', '--json', '--sandbox', 'read-only'],
327
- capabilities: ['review', 'code-gen', 'refactor'],
328
- };
329
- }
330
-
331
- if (detected.gemini) {
332
- providers.gemini = {
333
- type: 'cli',
334
- command: 'gemini',
335
- detected: true,
336
- headlessArgs: ['-p', '--output-format', 'json'],
337
- capabilities: ['design', 'vision', 'review', 'image-gen'],
338
- };
339
- }
340
-
341
- // Always include API providers (devserver-only)
342
- providers.deepseek = {
343
- type: 'api',
344
- baseUrl: 'https://api.deepseek.com',
345
- model: 'deepseek-coder',
346
- capabilities: ['review'],
347
- devserverOnly: true,
348
- };
349
-
350
- return providers;
351
- }
352
-
353
- /**
354
- * Build capability config from detected CLIs
355
- * @param {Object} detected - Detected CLI info
356
- * @returns {Object} Capability config
357
- */
358
- function buildCapabilityConfig(detected) {
359
- const capabilities = {};
360
-
361
- // Review capability - use all available
362
- const reviewProviders = [];
363
- if (detected.claude) reviewProviders.push('claude');
364
- if (detected.codex) reviewProviders.push('codex');
365
- reviewProviders.push('deepseek'); // Always available via devserver
366
-
367
- capabilities.review = {
368
- providers: reviewProviders,
369
- consensus: 'majority',
370
- };
371
-
372
- // Design capability - gemini only
373
- if (detected.gemini) {
374
- capabilities.design = {
375
- providers: ['gemini'],
376
- };
377
- }
378
-
379
- // Code generation - claude preferred
380
- const codeGenProviders = [];
381
- if (detected.claude) codeGenProviders.push('claude');
382
- if (detected.codex) codeGenProviders.push('codex');
383
-
384
- if (codeGenProviders.length > 0) {
385
- capabilities['code-gen'] = {
386
- providers: codeGenProviders,
387
- };
388
- }
389
-
390
- return capabilities;
391
- }
392
-
393
- /**
394
- * Build routing table from detected CLIs and devserver
395
- * @param {Object} detected - Detected CLI info
396
- * @param {Object} devserver - Devserver status
397
- * @returns {Object} Routing table
398
- */
399
- function buildRoutingTable(detected, devserver) {
400
- const table = {};
401
-
402
- // CLI providers
403
- for (const name of ['claude', 'codex', 'gemini']) {
404
- table[name] = {
405
- local: detected[name]?.detected || false,
406
- devserver: devserver.connected,
407
- preferred: detected[name]?.detected ? 'local' : 'devserver',
408
- };
409
- }
410
-
411
- // API providers
412
- table.deepseek = {
413
- local: false,
414
- devserver: true,
415
- preferred: 'devserver',
416
- };
417
-
418
- return table;
419
- }
1
+ /**
2
+ * Router Setup Command - Interactive setup for multi-model routing
3
+ * Phase 33, Task 10
4
+ */
5
+
6
+ import { detectAllCLIs, detectCLI } from './cli-detector.js';
7
+ import { writeFile } from 'fs/promises';
8
+ import { join } from 'path';
9
+
10
+ const PRICING = {
11
+ claude: { inputPer1k: 0.003, outputPer1k: 0.015 },
12
+ codex: { inputPer1k: 0.002, outputPer1k: 0.008 },
13
+ gemini: { inputPer1k: 0.001, outputPer1k: 0.002 },
14
+ deepseek: { inputPer1k: 0.0001, outputPer1k: 0.0002 },
15
+ };
16
+
17
+ export class RouterSetup {
18
+ constructor() {
19
+ this.config = { providers: {}, capabilities: {}, devserver: {} };
20
+ this._detectAllCLIs = detectAllCLIs;
21
+ this._detectCLI = detectCLI;
22
+ this._fetch = globalThis.fetch;
23
+ this._writeFile = null;
24
+ }
25
+
26
+ async detectCLIs() {
27
+ return this._detectAllCLIs();
28
+ }
29
+
30
+ async testDevserver(url) {
31
+ try {
32
+ const res = await this._fetch(url + '/health');
33
+ return { connected: res.ok };
34
+ } catch {
35
+ return { connected: false };
36
+ }
37
+ }
38
+
39
+ formatRoutingTable(providers) {
40
+ const lines = ['Provider Location'];
41
+ for (const [name, info] of Object.entries(providers)) {
42
+ lines.push(name.padEnd(12) + ' ' + info.location);
43
+ }
44
+ return lines.join('\n');
45
+ }
46
+
47
+ estimateCosts(usage) {
48
+ let total = 0;
49
+ for (const [cap, count] of Object.entries(usage)) {
50
+ total += count * 0.01; // Simplified
51
+ }
52
+ return { total, breakdown: usage };
53
+ }
54
+
55
+ configureProvider(name, config) {
56
+ this.config.providers[name] = config;
57
+ }
58
+
59
+ configureCapability(name, providers) {
60
+ this.config.capabilities[name] = { providers };
61
+ }
62
+
63
+ async testProvider(name) {
64
+ const result = await this._detectCLI(name);
65
+ return { available: result.found };
66
+ }
67
+
68
+ formatRoutingSummary(config) {
69
+ const lines = [];
70
+ for (const [name, info] of Object.entries(config.providers || {})) {
71
+ lines.push(name + ': ' + info.location);
72
+ }
73
+ return lines.join('\n');
74
+ }
75
+
76
+ estimateCostsPerCapability(usage) {
77
+ const costs = {};
78
+ for (const [cap, info] of Object.entries(usage)) {
79
+ costs[cap] = info.count * info.avgTokens * 0.000001;
80
+ }
81
+ return costs;
82
+ }
83
+
84
+ async saveConfig() {
85
+ const json = JSON.stringify({ router: this.config }, null, 2);
86
+ if (this._writeFile) {
87
+ await this._writeFile(json);
88
+ } else {
89
+ await writeFile(join(process.cwd(), '.tlc.json'), json);
90
+ }
91
+ }
92
+ }
93
+
94
+ export default { RouterSetup };