tlc-claude-code 1.4.9 → 1.5.2

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 (122) hide show
  1. package/CLAUDE.md +23 -0
  2. package/CODING-STANDARDS.md +408 -0
  3. package/bin/install.js +2 -0
  4. package/dashboard/dist/components/QualityGatePane.d.ts +38 -0
  5. package/dashboard/dist/components/QualityGatePane.js +31 -0
  6. package/dashboard/dist/components/QualityGatePane.test.d.ts +1 -0
  7. package/dashboard/dist/components/QualityGatePane.test.js +147 -0
  8. package/dashboard/dist/components/orchestration/AgentCard.d.ts +26 -0
  9. package/dashboard/dist/components/orchestration/AgentCard.js +60 -0
  10. package/dashboard/dist/components/orchestration/AgentCard.test.d.ts +1 -0
  11. package/dashboard/dist/components/orchestration/AgentCard.test.js +63 -0
  12. package/dashboard/dist/components/orchestration/AgentControls.d.ts +11 -0
  13. package/dashboard/dist/components/orchestration/AgentControls.js +20 -0
  14. package/dashboard/dist/components/orchestration/AgentControls.test.d.ts +1 -0
  15. package/dashboard/dist/components/orchestration/AgentControls.test.js +52 -0
  16. package/dashboard/dist/components/orchestration/AgentDetail.d.ts +35 -0
  17. package/dashboard/dist/components/orchestration/AgentDetail.js +37 -0
  18. package/dashboard/dist/components/orchestration/AgentDetail.test.d.ts +1 -0
  19. package/dashboard/dist/components/orchestration/AgentDetail.test.js +79 -0
  20. package/dashboard/dist/components/orchestration/AgentList.d.ts +31 -0
  21. package/dashboard/dist/components/orchestration/AgentList.js +47 -0
  22. package/dashboard/dist/components/orchestration/AgentList.test.d.ts +1 -0
  23. package/dashboard/dist/components/orchestration/AgentList.test.js +64 -0
  24. package/dashboard/dist/components/orchestration/CostMeter.d.ts +11 -0
  25. package/dashboard/dist/components/orchestration/CostMeter.js +28 -0
  26. package/dashboard/dist/components/orchestration/CostMeter.test.d.ts +1 -0
  27. package/dashboard/dist/components/orchestration/CostMeter.test.js +50 -0
  28. package/dashboard/dist/components/orchestration/ModelSelector.d.ts +20 -0
  29. package/dashboard/dist/components/orchestration/ModelSelector.js +12 -0
  30. package/dashboard/dist/components/orchestration/ModelSelector.test.d.ts +1 -0
  31. package/dashboard/dist/components/orchestration/ModelSelector.test.js +56 -0
  32. package/dashboard/dist/components/orchestration/OrchestrationDashboard.d.ts +28 -0
  33. package/dashboard/dist/components/orchestration/OrchestrationDashboard.js +28 -0
  34. package/dashboard/dist/components/orchestration/OrchestrationDashboard.test.d.ts +1 -0
  35. package/dashboard/dist/components/orchestration/OrchestrationDashboard.test.js +56 -0
  36. package/dashboard/dist/components/orchestration/QualityIndicator.d.ts +11 -0
  37. package/dashboard/dist/components/orchestration/QualityIndicator.js +37 -0
  38. package/dashboard/dist/components/orchestration/QualityIndicator.test.d.ts +1 -0
  39. package/dashboard/dist/components/orchestration/QualityIndicator.test.js +52 -0
  40. package/dashboard/dist/components/orchestration/index.d.ts +8 -0
  41. package/dashboard/dist/components/orchestration/index.js +8 -0
  42. package/package.json +1 -1
  43. package/server/lib/access-control.js +352 -0
  44. package/server/lib/access-control.test.js +322 -0
  45. package/server/lib/agents-cancel-command.js +139 -0
  46. package/server/lib/agents-cancel-command.test.js +180 -0
  47. package/server/lib/agents-get-command.js +159 -0
  48. package/server/lib/agents-get-command.test.js +167 -0
  49. package/server/lib/agents-list-command.js +150 -0
  50. package/server/lib/agents-list-command.test.js +149 -0
  51. package/server/lib/agents-logs-command.js +126 -0
  52. package/server/lib/agents-logs-command.test.js +198 -0
  53. package/server/lib/agents-retry-command.js +117 -0
  54. package/server/lib/agents-retry-command.test.js +192 -0
  55. package/server/lib/budget-limits.js +222 -0
  56. package/server/lib/budget-limits.test.js +214 -0
  57. package/server/lib/code-generator.js +291 -0
  58. package/server/lib/code-generator.test.js +307 -0
  59. package/server/lib/cost-command.js +290 -0
  60. package/server/lib/cost-command.test.js +202 -0
  61. package/server/lib/cost-optimizer.js +404 -0
  62. package/server/lib/cost-optimizer.test.js +232 -0
  63. package/server/lib/cost-projections.js +302 -0
  64. package/server/lib/cost-projections.test.js +217 -0
  65. package/server/lib/cost-reports.js +277 -0
  66. package/server/lib/cost-reports.test.js +254 -0
  67. package/server/lib/cost-tracker.js +216 -0
  68. package/server/lib/cost-tracker.test.js +302 -0
  69. package/server/lib/crypto-patterns.js +433 -0
  70. package/server/lib/crypto-patterns.test.js +346 -0
  71. package/server/lib/design-command.js +385 -0
  72. package/server/lib/design-command.test.js +249 -0
  73. package/server/lib/design-parser.js +237 -0
  74. package/server/lib/design-parser.test.js +290 -0
  75. package/server/lib/gemini-vision.js +377 -0
  76. package/server/lib/gemini-vision.test.js +282 -0
  77. package/server/lib/input-validator.js +360 -0
  78. package/server/lib/input-validator.test.js +295 -0
  79. package/server/lib/litellm-client.js +232 -0
  80. package/server/lib/litellm-client.test.js +267 -0
  81. package/server/lib/litellm-command.js +291 -0
  82. package/server/lib/litellm-command.test.js +260 -0
  83. package/server/lib/litellm-config.js +273 -0
  84. package/server/lib/litellm-config.test.js +212 -0
  85. package/server/lib/model-pricing.js +189 -0
  86. package/server/lib/model-pricing.test.js +178 -0
  87. package/server/lib/models-command.js +223 -0
  88. package/server/lib/models-command.test.js +193 -0
  89. package/server/lib/optimize-command.js +197 -0
  90. package/server/lib/optimize-command.test.js +193 -0
  91. package/server/lib/orchestration-integration.js +206 -0
  92. package/server/lib/orchestration-integration.test.js +235 -0
  93. package/server/lib/output-encoder.js +308 -0
  94. package/server/lib/output-encoder.test.js +312 -0
  95. package/server/lib/quality-evaluator.js +396 -0
  96. package/server/lib/quality-evaluator.test.js +337 -0
  97. package/server/lib/quality-gate-command.js +340 -0
  98. package/server/lib/quality-gate-command.test.js +321 -0
  99. package/server/lib/quality-gate-scorer.js +378 -0
  100. package/server/lib/quality-gate-scorer.test.js +376 -0
  101. package/server/lib/quality-history.js +265 -0
  102. package/server/lib/quality-history.test.js +359 -0
  103. package/server/lib/quality-presets.js +288 -0
  104. package/server/lib/quality-presets.test.js +269 -0
  105. package/server/lib/quality-retry.js +323 -0
  106. package/server/lib/quality-retry.test.js +325 -0
  107. package/server/lib/quality-thresholds.js +255 -0
  108. package/server/lib/quality-thresholds.test.js +237 -0
  109. package/server/lib/secure-auth.js +333 -0
  110. package/server/lib/secure-auth.test.js +288 -0
  111. package/server/lib/secure-code-command.js +540 -0
  112. package/server/lib/secure-code-command.test.js +309 -0
  113. package/server/lib/secure-errors.js +521 -0
  114. package/server/lib/secure-errors.test.js +298 -0
  115. package/server/lib/vision-command.js +372 -0
  116. package/server/lib/vision-command.test.js +255 -0
  117. package/server/lib/visual-command.js +350 -0
  118. package/server/lib/visual-command.test.js +256 -0
  119. package/server/lib/visual-testing.js +315 -0
  120. package/server/lib/visual-testing.test.js +357 -0
  121. package/server/package-lock.json +2 -2
  122. package/server/package.json +1 -1
@@ -0,0 +1,12 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Box, Text } from 'ink';
3
+ export function ModelSelector({ models, selected, currentOverride, showCapabilities, showPricing, allowOneTime, allowPersistent, onSelect, onClear, }) {
4
+ if (models.length === 0) {
5
+ return (_jsx(Box, { padding: 1, children: _jsx(Text, { dimColor: true, children: "No models available" }) }));
6
+ }
7
+ return (_jsxs(Box, { flexDirection: "column", padding: 1, children: [_jsx(Text, { bold: true, children: "Model Selection" }), currentOverride && (_jsxs(Box, { marginTop: 1, children: [_jsxs(Text, { color: "cyan", children: ["Override: ", currentOverride] }), onClear && (_jsx(Text, { color: "gray", children: " [Clear]" }))] })), _jsx(Box, { marginTop: 1, flexDirection: "column", children: models.map((model) => {
8
+ const isSelected = selected === model.id;
9
+ const isOverride = currentOverride === model.id;
10
+ return (_jsxs(Box, { marginBottom: 1, children: [_jsxs(Box, { children: [_jsx(Text, { color: isSelected ? 'cyan' : 'gray', children: isSelected ? '●' : '○' }), _jsxs(Text, { color: model.available ? 'white' : 'gray', dimColor: !model.available, children: [' ', model.name] }), isOverride && (_jsx(Text, { color: "yellow", children: " (override)" })), !model.available && (_jsx(Text, { color: "red", children: " (unavailable)" }))] }), showPricing && model.pricing !== undefined && (_jsxs(Box, { marginLeft: 2, children: [_jsx(Text, { dimColor: true, children: "Price: " }), _jsxs(Text, { children: ["$", model.pricing.toFixed(4), "/1K"] })] })), showCapabilities && model.capabilities && (_jsxs(Box, { marginLeft: 2, children: [_jsx(Text, { dimColor: true, children: "Capabilities: " }), _jsx(Text, { children: model.capabilities.join(', ') })] }))] }, model.id));
11
+ }) }), (allowOneTime || allowPersistent) && (_jsxs(Box, { marginTop: 1, children: [_jsx(Text, { dimColor: true, children: "Options: " }), allowOneTime && _jsx(Text, { children: "[One-time] " }), allowPersistent && _jsx(Text, { children: "[Persistent]" })] }))] }));
12
+ }
@@ -0,0 +1,56 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { describe, it, expect, vi } from 'vitest';
3
+ import { render } from 'ink-testing-library';
4
+ import { ModelSelector } from './ModelSelector.js';
5
+ describe('ModelSelector', () => {
6
+ const defaultModels = [
7
+ { id: 'gpt-4', name: 'GPT-4', available: true, pricing: 0.03 },
8
+ { id: 'gpt-3.5-turbo', name: 'GPT-3.5 Turbo', available: true, pricing: 0.002 },
9
+ { id: 'claude-3-opus', name: 'Claude 3 Opus', available: false, pricing: 0.06 },
10
+ ];
11
+ it('renders model list', () => {
12
+ const { lastFrame } = render(_jsx(ModelSelector, { models: defaultModels }));
13
+ expect(lastFrame()?.toLowerCase()).toMatch(/gpt-4/);
14
+ });
15
+ it('capabilities shown per model', () => {
16
+ const models = [
17
+ { ...defaultModels[0], capabilities: ['code', 'chat', 'vision'] },
18
+ ];
19
+ const { lastFrame } = render(_jsx(ModelSelector, { models: models, showCapabilities: true }));
20
+ expect(lastFrame()).toBeDefined();
21
+ });
22
+ it('pricing shown per model', () => {
23
+ const { lastFrame } = render(_jsx(ModelSelector, { models: defaultModels, showPricing: true }));
24
+ expect(lastFrame()).toBeDefined();
25
+ });
26
+ it('availability indicator shown', () => {
27
+ const { lastFrame } = render(_jsx(ModelSelector, { models: defaultModels }));
28
+ expect(lastFrame()).toBeDefined();
29
+ });
30
+ it('select triggers callback', () => {
31
+ const onSelect = vi.fn();
32
+ const { lastFrame } = render(_jsx(ModelSelector, { models: defaultModels, onSelect: onSelect }));
33
+ expect(lastFrame()).toBeDefined();
34
+ });
35
+ it('one-time override option available', () => {
36
+ const { lastFrame } = render(_jsx(ModelSelector, { models: defaultModels, allowOneTime: true }));
37
+ expect(lastFrame()).toBeDefined();
38
+ });
39
+ it('persistent override option available', () => {
40
+ const { lastFrame } = render(_jsx(ModelSelector, { models: defaultModels, allowPersistent: true }));
41
+ expect(lastFrame()).toBeDefined();
42
+ });
43
+ it('clear override button works', () => {
44
+ const onClear = vi.fn();
45
+ const { lastFrame } = render(_jsx(ModelSelector, { models: defaultModels, currentOverride: "gpt-4", onClear: onClear }));
46
+ expect(lastFrame()).toBeDefined();
47
+ });
48
+ it('unavailable models indicated', () => {
49
+ const { lastFrame } = render(_jsx(ModelSelector, { models: defaultModels }));
50
+ expect(lastFrame()).toBeDefined();
51
+ });
52
+ it('handles empty model list', () => {
53
+ const { lastFrame } = render(_jsx(ModelSelector, { models: [] }));
54
+ expect(lastFrame()).toBeDefined();
55
+ });
56
+ });
@@ -0,0 +1,28 @@
1
+ type AgentStatus = 'running' | 'queued' | 'completed' | 'failed' | 'paused' | 'cancelled';
2
+ interface Agent {
3
+ id: string;
4
+ name?: string;
5
+ model: string;
6
+ status: AgentStatus;
7
+ startTime: Date;
8
+ tokens: {
9
+ input: number;
10
+ output: number;
11
+ };
12
+ cost: number;
13
+ }
14
+ interface CostData {
15
+ spent: number;
16
+ budget: number;
17
+ breakdown?: Record<string, number>;
18
+ }
19
+ interface OrchestrationDashboardProps {
20
+ agents: Agent[];
21
+ cost: CostData;
22
+ width?: number;
23
+ error?: string;
24
+ onConnect?: () => void;
25
+ onSelectAgent?: (agentId: string) => void;
26
+ }
27
+ export declare function OrchestrationDashboard({ agents, cost, width, error, onConnect, onSelectAgent, }: OrchestrationDashboardProps): import("react/jsx-runtime").JSX.Element;
28
+ export {};
@@ -0,0 +1,28 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { Box, Text } from 'ink';
3
+ import { useState } from 'react';
4
+ import { AgentList } from './AgentList.js';
5
+ import { CostMeter } from './CostMeter.js';
6
+ export function OrchestrationDashboard({ agents, cost, width = 120, error, onConnect, onSelectAgent, }) {
7
+ const [selectedAgent, setSelectedAgent] = useState();
8
+ // Handle error state
9
+ if (error) {
10
+ return (_jsxs(Box, { padding: 1, flexDirection: "column", children: [_jsx(Text, { bold: true, color: "red", children: "Dashboard error" }), _jsx(Text, { color: "red", children: error })] }));
11
+ }
12
+ // Calculate summary stats
13
+ const stats = {
14
+ total: agents.length,
15
+ running: agents.filter((a) => a.status === 'running').length,
16
+ completed: agents.filter((a) => a.status === 'completed').length,
17
+ failed: agents.filter((a) => a.status === 'failed').length,
18
+ queued: agents.filter((a) => a.status === 'queued').length,
19
+ };
20
+ const totalTokens = agents.reduce((sum, a) => sum + a.tokens.input + a.tokens.output, 0);
21
+ const handleSelect = (agentId) => {
22
+ setSelectedAgent(agentId);
23
+ onSelectAgent?.(agentId);
24
+ };
25
+ // Responsive layout
26
+ const isNarrow = width < 100;
27
+ return (_jsxs(Box, { flexDirection: "column", padding: 1, children: [_jsxs(Box, { marginBottom: 1, children: [_jsx(Text, { bold: true, children: "Orchestration Dashboard" }), _jsxs(Text, { dimColor: true, children: [" - ", stats.total, " agents"] })] }), _jsxs(Box, { marginBottom: 1, children: [_jsxs(Text, { color: "blue", children: [stats.running, " running"] }), _jsx(Text, { children: " | " }), _jsxs(Text, { color: "yellow", children: [stats.queued, " queued"] }), _jsx(Text, { children: " | " }), _jsxs(Text, { color: "green", children: [stats.completed, " completed"] }), stats.failed > 0 && (_jsxs(_Fragment, { children: [_jsx(Text, { children: " | " }), _jsxs(Text, { color: "red", children: [stats.failed, " failed"] })] })), _jsxs(Text, { dimColor: true, children: [" | ", totalTokens.toLocaleString(), " tokens"] })] }), _jsxs(Box, { flexDirection: isNarrow ? 'column' : 'row', children: [_jsx(Box, { flexDirection: "column", flexGrow: 1, children: _jsx(AgentList, { agents: agents, selected: selectedAgent, onSelect: handleSelect }) }), _jsx(Box, { flexDirection: "column", marginLeft: isNarrow ? 0 : 2, marginTop: isNarrow ? 1 : 0, width: isNarrow ? '100%' : 30, children: _jsx(CostMeter, { spent: cost.spent, budget: cost.budget, breakdown: cost.breakdown }) })] }), agents.length === 0 && !error && (_jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "No agents currently running. Start an orchestration to see activity." }) })), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "\u2191/\u2193 Select | Enter View | P Pause | C Cancel | R Retry | Q Quit" }) })] }));
28
+ }
@@ -0,0 +1,56 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { describe, it, expect, vi } from 'vitest';
3
+ import { render } from 'ink-testing-library';
4
+ import { OrchestrationDashboard } from './OrchestrationDashboard.js';
5
+ describe('OrchestrationDashboard', () => {
6
+ const mockAgents = [
7
+ { id: 'agent-1', name: 'Test 1', model: 'gpt-4', status: 'running', startTime: new Date(), tokens: { input: 100, output: 50 }, cost: 0.01 },
8
+ { id: 'agent-2', name: 'Test 2', model: 'gpt-3.5', status: 'completed', startTime: new Date(), tokens: { input: 200, output: 100 }, cost: 0.005 },
9
+ ];
10
+ const mockCost = {
11
+ spent: 50,
12
+ budget: 100,
13
+ breakdown: { 'gpt-4': 40, 'gpt-3.5-turbo': 10 },
14
+ };
15
+ it('renders without error', () => {
16
+ const { lastFrame } = render(_jsx(OrchestrationDashboard, { agents: mockAgents, cost: mockCost }));
17
+ expect(lastFrame()).toBeDefined();
18
+ });
19
+ it('summary stats show totals', () => {
20
+ const { lastFrame } = render(_jsx(OrchestrationDashboard, { agents: mockAgents, cost: mockCost }));
21
+ expect(lastFrame()).toBeDefined();
22
+ });
23
+ it('agent list is visible', () => {
24
+ const { lastFrame } = render(_jsx(OrchestrationDashboard, { agents: mockAgents, cost: mockCost }));
25
+ expect(lastFrame()).toBeDefined();
26
+ });
27
+ it('cost sidebar visible', () => {
28
+ const { lastFrame } = render(_jsx(OrchestrationDashboard, { agents: mockAgents, cost: mockCost }));
29
+ expect(lastFrame()).toBeDefined();
30
+ });
31
+ it('handles WebSocket connection', () => {
32
+ const onConnect = vi.fn();
33
+ const { lastFrame } = render(_jsx(OrchestrationDashboard, { agents: mockAgents, cost: mockCost, onConnect: onConnect }));
34
+ expect(lastFrame()).toBeDefined();
35
+ });
36
+ it('updates on agent change', () => {
37
+ const { lastFrame } = render(_jsx(OrchestrationDashboard, { agents: mockAgents, cost: mockCost }));
38
+ expect(lastFrame()).toBeDefined();
39
+ });
40
+ it('handles mobile layout', () => {
41
+ const { lastFrame } = render(_jsx(OrchestrationDashboard, { agents: mockAgents, cost: mockCost, width: 80 }));
42
+ expect(lastFrame()).toBeDefined();
43
+ });
44
+ it('keyboard navigation works', () => {
45
+ const { lastFrame } = render(_jsx(OrchestrationDashboard, { agents: mockAgents, cost: mockCost }));
46
+ expect(lastFrame()).toBeDefined();
47
+ });
48
+ it('error boundary catches errors', () => {
49
+ const { lastFrame } = render(_jsx(OrchestrationDashboard, { agents: [], cost: mockCost, error: "Test error" }));
50
+ expect(lastFrame()).toBeDefined();
51
+ });
52
+ it('empty state shows message', () => {
53
+ const { lastFrame } = render(_jsx(OrchestrationDashboard, { agents: [], cost: mockCost }));
54
+ expect(lastFrame()).toBeDefined();
55
+ });
56
+ });
@@ -0,0 +1,11 @@
1
+ interface QualityIndicatorProps {
2
+ score?: number;
3
+ threshold?: number;
4
+ dimensions?: Record<string, number>;
5
+ history?: number[];
6
+ showThreshold?: boolean;
7
+ showRetryHint?: boolean;
8
+ loading?: boolean;
9
+ }
10
+ export declare function QualityIndicator({ score, threshold, dimensions, history, showThreshold, showRetryHint, loading, }: QualityIndicatorProps): import("react/jsx-runtime").JSX.Element;
11
+ export {};
@@ -0,0 +1,37 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Box, Text } from 'ink';
3
+ export function QualityIndicator({ score, threshold = 70, dimensions, history, showThreshold, showRetryHint, loading, }) {
4
+ if (loading) {
5
+ return (_jsx(Box, { padding: 1, children: _jsx(Text, { dimColor: true, children: "Loading quality data..." }) }));
6
+ }
7
+ if (score === undefined) {
8
+ return (_jsx(Box, { padding: 1, children: _jsx(Text, { dimColor: true, children: "No quality data" }) }));
9
+ }
10
+ const pass = score >= threshold;
11
+ const getScoreColor = (s, t) => {
12
+ if (s >= t + 10)
13
+ return 'green';
14
+ if (s >= t)
15
+ return 'yellow';
16
+ return 'red';
17
+ };
18
+ // Create simple gauge
19
+ const gaugeLength = 20;
20
+ const filledLength = Math.round((score / 100) * gaugeLength);
21
+ const thresholdPos = Math.round((threshold / 100) * gaugeLength);
22
+ const gauge = '█'.repeat(filledLength) + '░'.repeat(gaugeLength - filledLength);
23
+ // Create sparkline from history
24
+ const renderSparkline = (data) => {
25
+ if (!data || data.length === 0)
26
+ return '';
27
+ const blocks = ['▁', '▂', '▃', '▄', '▅', '▆', '▇', '█'];
28
+ const min = Math.min(...data);
29
+ const max = Math.max(...data);
30
+ const range = max - min || 1;
31
+ return data.map((v) => {
32
+ const idx = Math.round(((v - min) / range) * 7);
33
+ return blocks[idx];
34
+ }).join('');
35
+ };
36
+ return (_jsxs(Box, { flexDirection: "column", padding: 1, children: [_jsx(Text, { bold: true, children: "Quality Score" }), _jsxs(Box, { marginTop: 1, children: [_jsxs(Text, { color: getScoreColor(score, threshold), bold: true, children: [score, "%"] }), _jsx(Text, { children: " " }), _jsx(Text, { color: pass ? 'green' : 'red', children: pass ? '✓ PASS' : '✗ FAIL' })] }), _jsx(Box, { children: _jsxs(Text, { color: getScoreColor(score, threshold), children: ["[", gauge, "]"] }) }), showThreshold && (_jsx(Box, { children: _jsxs(Text, { dimColor: true, children: ["Threshold: ", threshold, "%"] }) })), dimensions && Object.keys(dimensions).length > 0 && (_jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { bold: true, dimColor: true, children: "Dimensions:" }), Object.entries(dimensions).map(([dim, dimScore]) => (_jsxs(Box, { children: [_jsxs(Text, { dimColor: true, children: [" ", dim, ": "] }), _jsxs(Text, { color: getScoreColor(dimScore, threshold), children: [dimScore, "%"] })] }, dim)))] })), history && history.length > 0 && (_jsxs(Box, { marginTop: 1, children: [_jsx(Text, { dimColor: true, children: "Trend: " }), _jsx(Text, { color: "cyan", children: renderSparkline(history) })] })), !pass && showRetryHint && (_jsx(Box, { marginTop: 1, children: _jsx(Text, { color: "yellow", children: "Consider retry with a better model" }) }))] }));
37
+ }
@@ -0,0 +1,52 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { describe, it, expect } from 'vitest';
3
+ import { render } from 'ink-testing-library';
4
+ import { QualityIndicator } from './QualityIndicator.js';
5
+ describe('QualityIndicator', () => {
6
+ it('gauge shows score', () => {
7
+ const { lastFrame } = render(_jsx(QualityIndicator, { score: 85, threshold: 70 }));
8
+ expect(lastFrame()).toContain('85');
9
+ });
10
+ it('gauge colored by threshold - pass', () => {
11
+ const { lastFrame } = render(_jsx(QualityIndicator, { score: 85, threshold: 70 }));
12
+ expect(lastFrame()).toBeDefined();
13
+ });
14
+ it('gauge colored by threshold - fail', () => {
15
+ const { lastFrame } = render(_jsx(QualityIndicator, { score: 50, threshold: 70 }));
16
+ expect(lastFrame()).toBeDefined();
17
+ });
18
+ it('dimension breakdown expandable', () => {
19
+ const dimensions = {
20
+ style: 80,
21
+ completeness: 90,
22
+ correctness: 85,
23
+ };
24
+ const { lastFrame } = render(_jsx(QualityIndicator, { score: 85, threshold: 70, dimensions: dimensions }));
25
+ expect(lastFrame()).toBeDefined();
26
+ });
27
+ it('pass indicator shows check', () => {
28
+ const { lastFrame } = render(_jsx(QualityIndicator, { score: 85, threshold: 70 }));
29
+ expect(lastFrame()).toBeDefined();
30
+ });
31
+ it('fail indicator shows x', () => {
32
+ const { lastFrame } = render(_jsx(QualityIndicator, { score: 50, threshold: 70 }));
33
+ expect(lastFrame()).toBeDefined();
34
+ });
35
+ it('threshold line visible', () => {
36
+ const { lastFrame } = render(_jsx(QualityIndicator, { score: 85, threshold: 70, showThreshold: true }));
37
+ expect(lastFrame()).toBeDefined();
38
+ });
39
+ it('trend sparkline shows history', () => {
40
+ const history = [60, 65, 70, 75, 80, 85];
41
+ const { lastFrame } = render(_jsx(QualityIndicator, { score: 85, threshold: 70, history: history }));
42
+ expect(lastFrame()).toBeDefined();
43
+ });
44
+ it('retry recommendation on fail', () => {
45
+ const { lastFrame } = render(_jsx(QualityIndicator, { score: 50, threshold: 70, showRetryHint: true }));
46
+ expect(lastFrame()).toBeDefined();
47
+ });
48
+ it('loading state handled', () => {
49
+ const { lastFrame } = render(_jsx(QualityIndicator, { loading: true }));
50
+ expect(lastFrame()).toBeDefined();
51
+ });
52
+ });
@@ -0,0 +1,8 @@
1
+ export { AgentCard } from './AgentCard.js';
2
+ export { AgentControls } from './AgentControls.js';
3
+ export { AgentDetail } from './AgentDetail.js';
4
+ export { AgentList } from './AgentList.js';
5
+ export { CostMeter } from './CostMeter.js';
6
+ export { ModelSelector } from './ModelSelector.js';
7
+ export { OrchestrationDashboard } from './OrchestrationDashboard.js';
8
+ export { QualityIndicator } from './QualityIndicator.js';
@@ -0,0 +1,8 @@
1
+ export { AgentCard } from './AgentCard.js';
2
+ export { AgentControls } from './AgentControls.js';
3
+ export { AgentDetail } from './AgentDetail.js';
4
+ export { AgentList } from './AgentList.js';
5
+ export { CostMeter } from './CostMeter.js';
6
+ export { ModelSelector } from './ModelSelector.js';
7
+ export { OrchestrationDashboard } from './OrchestrationDashboard.js';
8
+ export { QualityIndicator } from './QualityIndicator.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tlc-claude-code",
3
- "version": "1.4.9",
3
+ "version": "1.5.2",
4
4
  "description": "TLC - Test Led Coding for Claude Code",
5
5
  "bin": {
6
6
  "tlc": "./bin/tlc.js",