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.
- package/CLAUDE.md +23 -0
- package/CODING-STANDARDS.md +408 -0
- package/bin/install.js +2 -0
- package/dashboard/dist/components/QualityGatePane.d.ts +38 -0
- package/dashboard/dist/components/QualityGatePane.js +31 -0
- package/dashboard/dist/components/QualityGatePane.test.d.ts +1 -0
- package/dashboard/dist/components/QualityGatePane.test.js +147 -0
- package/dashboard/dist/components/orchestration/AgentCard.d.ts +26 -0
- package/dashboard/dist/components/orchestration/AgentCard.js +60 -0
- package/dashboard/dist/components/orchestration/AgentCard.test.d.ts +1 -0
- package/dashboard/dist/components/orchestration/AgentCard.test.js +63 -0
- package/dashboard/dist/components/orchestration/AgentControls.d.ts +11 -0
- package/dashboard/dist/components/orchestration/AgentControls.js +20 -0
- package/dashboard/dist/components/orchestration/AgentControls.test.d.ts +1 -0
- package/dashboard/dist/components/orchestration/AgentControls.test.js +52 -0
- package/dashboard/dist/components/orchestration/AgentDetail.d.ts +35 -0
- package/dashboard/dist/components/orchestration/AgentDetail.js +37 -0
- package/dashboard/dist/components/orchestration/AgentDetail.test.d.ts +1 -0
- package/dashboard/dist/components/orchestration/AgentDetail.test.js +79 -0
- package/dashboard/dist/components/orchestration/AgentList.d.ts +31 -0
- package/dashboard/dist/components/orchestration/AgentList.js +47 -0
- package/dashboard/dist/components/orchestration/AgentList.test.d.ts +1 -0
- package/dashboard/dist/components/orchestration/AgentList.test.js +64 -0
- package/dashboard/dist/components/orchestration/CostMeter.d.ts +11 -0
- package/dashboard/dist/components/orchestration/CostMeter.js +28 -0
- package/dashboard/dist/components/orchestration/CostMeter.test.d.ts +1 -0
- package/dashboard/dist/components/orchestration/CostMeter.test.js +50 -0
- package/dashboard/dist/components/orchestration/ModelSelector.d.ts +20 -0
- package/dashboard/dist/components/orchestration/ModelSelector.js +12 -0
- package/dashboard/dist/components/orchestration/ModelSelector.test.d.ts +1 -0
- package/dashboard/dist/components/orchestration/ModelSelector.test.js +56 -0
- package/dashboard/dist/components/orchestration/OrchestrationDashboard.d.ts +28 -0
- package/dashboard/dist/components/orchestration/OrchestrationDashboard.js +28 -0
- package/dashboard/dist/components/orchestration/OrchestrationDashboard.test.d.ts +1 -0
- package/dashboard/dist/components/orchestration/OrchestrationDashboard.test.js +56 -0
- package/dashboard/dist/components/orchestration/QualityIndicator.d.ts +11 -0
- package/dashboard/dist/components/orchestration/QualityIndicator.js +37 -0
- package/dashboard/dist/components/orchestration/QualityIndicator.test.d.ts +1 -0
- package/dashboard/dist/components/orchestration/QualityIndicator.test.js +52 -0
- package/dashboard/dist/components/orchestration/index.d.ts +8 -0
- package/dashboard/dist/components/orchestration/index.js +8 -0
- package/package.json +1 -1
- package/server/lib/access-control.js +352 -0
- package/server/lib/access-control.test.js +322 -0
- package/server/lib/agents-cancel-command.js +139 -0
- package/server/lib/agents-cancel-command.test.js +180 -0
- package/server/lib/agents-get-command.js +159 -0
- package/server/lib/agents-get-command.test.js +167 -0
- package/server/lib/agents-list-command.js +150 -0
- package/server/lib/agents-list-command.test.js +149 -0
- package/server/lib/agents-logs-command.js +126 -0
- package/server/lib/agents-logs-command.test.js +198 -0
- package/server/lib/agents-retry-command.js +117 -0
- package/server/lib/agents-retry-command.test.js +192 -0
- package/server/lib/budget-limits.js +222 -0
- package/server/lib/budget-limits.test.js +214 -0
- package/server/lib/code-generator.js +291 -0
- package/server/lib/code-generator.test.js +307 -0
- package/server/lib/cost-command.js +290 -0
- package/server/lib/cost-command.test.js +202 -0
- package/server/lib/cost-optimizer.js +404 -0
- package/server/lib/cost-optimizer.test.js +232 -0
- package/server/lib/cost-projections.js +302 -0
- package/server/lib/cost-projections.test.js +217 -0
- package/server/lib/cost-reports.js +277 -0
- package/server/lib/cost-reports.test.js +254 -0
- package/server/lib/cost-tracker.js +216 -0
- package/server/lib/cost-tracker.test.js +302 -0
- package/server/lib/crypto-patterns.js +433 -0
- package/server/lib/crypto-patterns.test.js +346 -0
- package/server/lib/design-command.js +385 -0
- package/server/lib/design-command.test.js +249 -0
- package/server/lib/design-parser.js +237 -0
- package/server/lib/design-parser.test.js +290 -0
- package/server/lib/gemini-vision.js +377 -0
- package/server/lib/gemini-vision.test.js +282 -0
- package/server/lib/input-validator.js +360 -0
- package/server/lib/input-validator.test.js +295 -0
- package/server/lib/litellm-client.js +232 -0
- package/server/lib/litellm-client.test.js +267 -0
- package/server/lib/litellm-command.js +291 -0
- package/server/lib/litellm-command.test.js +260 -0
- package/server/lib/litellm-config.js +273 -0
- package/server/lib/litellm-config.test.js +212 -0
- package/server/lib/model-pricing.js +189 -0
- package/server/lib/model-pricing.test.js +178 -0
- package/server/lib/models-command.js +223 -0
- package/server/lib/models-command.test.js +193 -0
- package/server/lib/optimize-command.js +197 -0
- package/server/lib/optimize-command.test.js +193 -0
- package/server/lib/orchestration-integration.js +206 -0
- package/server/lib/orchestration-integration.test.js +235 -0
- package/server/lib/output-encoder.js +308 -0
- package/server/lib/output-encoder.test.js +312 -0
- package/server/lib/quality-evaluator.js +396 -0
- package/server/lib/quality-evaluator.test.js +337 -0
- package/server/lib/quality-gate-command.js +340 -0
- package/server/lib/quality-gate-command.test.js +321 -0
- package/server/lib/quality-gate-scorer.js +378 -0
- package/server/lib/quality-gate-scorer.test.js +376 -0
- package/server/lib/quality-history.js +265 -0
- package/server/lib/quality-history.test.js +359 -0
- package/server/lib/quality-presets.js +288 -0
- package/server/lib/quality-presets.test.js +269 -0
- package/server/lib/quality-retry.js +323 -0
- package/server/lib/quality-retry.test.js +325 -0
- package/server/lib/quality-thresholds.js +255 -0
- package/server/lib/quality-thresholds.test.js +237 -0
- package/server/lib/secure-auth.js +333 -0
- package/server/lib/secure-auth.test.js +288 -0
- package/server/lib/secure-code-command.js +540 -0
- package/server/lib/secure-code-command.test.js +309 -0
- package/server/lib/secure-errors.js +521 -0
- package/server/lib/secure-errors.test.js +298 -0
- package/server/lib/vision-command.js +372 -0
- package/server/lib/vision-command.test.js +255 -0
- package/server/lib/visual-command.js +350 -0
- package/server/lib/visual-command.test.js +256 -0
- package/server/lib/visual-testing.js +315 -0
- package/server/lib/visual-testing.test.js +357 -0
- package/server/package-lock.json +2 -2
- package/server/package.json +1 -1
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Code Generator Tests
|
|
3
|
+
*
|
|
4
|
+
* Generate code from parsed design data
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const { describe, it, beforeEach } = require('node:test');
|
|
8
|
+
const assert = require('node:assert');
|
|
9
|
+
|
|
10
|
+
const {
|
|
11
|
+
createGenerator,
|
|
12
|
+
generateReact,
|
|
13
|
+
generateVue,
|
|
14
|
+
generateHTML,
|
|
15
|
+
generateTailwind,
|
|
16
|
+
mapToLibrary,
|
|
17
|
+
addDesignReference,
|
|
18
|
+
formatCode,
|
|
19
|
+
} = require('./code-generator.js');
|
|
20
|
+
|
|
21
|
+
describe('Code Generator', () => {
|
|
22
|
+
let generator;
|
|
23
|
+
let mockLLMClient;
|
|
24
|
+
|
|
25
|
+
beforeEach(() => {
|
|
26
|
+
mockLLMClient = {
|
|
27
|
+
_call: async () => ({
|
|
28
|
+
code: '<div>Generated code</div>',
|
|
29
|
+
}),
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
generator = createGenerator({
|
|
33
|
+
llmClient: mockLLMClient,
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
describe('createGenerator', () => {
|
|
38
|
+
it('creates generator with LLM client', () => {
|
|
39
|
+
assert.ok(generator);
|
|
40
|
+
assert.ok(generator.llmClient);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('accepts framework option', () => {
|
|
44
|
+
const reactGenerator = createGenerator({
|
|
45
|
+
llmClient: mockLLMClient,
|
|
46
|
+
framework: 'react',
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
assert.strictEqual(reactGenerator.framework, 'react');
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
describe('generateReact', () => {
|
|
54
|
+
it('generates React component', async () => {
|
|
55
|
+
mockLLMClient._call = async () => ({
|
|
56
|
+
code: `function LoginForm() {
|
|
57
|
+
return (
|
|
58
|
+
<div className="p-4">
|
|
59
|
+
<input type="email" placeholder="Email" />
|
|
60
|
+
<button>Submit</button>
|
|
61
|
+
</div>
|
|
62
|
+
);
|
|
63
|
+
}`,
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
const design = {
|
|
67
|
+
components: [
|
|
68
|
+
{ type: 'input', placeholder: 'Email' },
|
|
69
|
+
{ type: 'button', label: 'Submit' },
|
|
70
|
+
],
|
|
71
|
+
layout: { type: 'column' },
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
const result = await generateReact(generator, { design });
|
|
75
|
+
|
|
76
|
+
assert.ok(result.code);
|
|
77
|
+
assert.ok(result.code.includes('function') || result.code.includes('const'));
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('uses TypeScript when specified', async () => {
|
|
81
|
+
mockLLMClient._call = async () => ({
|
|
82
|
+
code: `interface Props { onSubmit: () => void; }
|
|
83
|
+
export const Form: React.FC<Props> = ({ onSubmit }) => { return <form />; }`,
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
const result = await generateReact(generator, {
|
|
87
|
+
design: { components: [] },
|
|
88
|
+
typescript: true,
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
assert.ok(result.code.includes('interface') || result.code.includes(':'));
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it('includes imports', async () => {
|
|
95
|
+
mockLLMClient._call = async () => ({
|
|
96
|
+
code: `import React from 'react';
|
|
97
|
+
|
|
98
|
+
export function Component() { return <div />; }`,
|
|
99
|
+
imports: ['react'],
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
const result = await generateReact(generator, {
|
|
103
|
+
design: { components: [] },
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
assert.ok(result.code.includes('import'));
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
describe('generateVue', () => {
|
|
111
|
+
it('generates Vue SFC', async () => {
|
|
112
|
+
mockLLMClient._call = async () => ({
|
|
113
|
+
code: `<template>
|
|
114
|
+
<div class="container">
|
|
115
|
+
<input v-model="email" placeholder="Email" />
|
|
116
|
+
<button @click="submit">Submit</button>
|
|
117
|
+
</div>
|
|
118
|
+
</template>
|
|
119
|
+
|
|
120
|
+
<script setup>
|
|
121
|
+
import { ref } from 'vue';
|
|
122
|
+
const email = ref('');
|
|
123
|
+
</script>`,
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
const result = await generateVue(generator, {
|
|
127
|
+
design: { components: [{ type: 'input' }, { type: 'button' }] },
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
assert.ok(result.code);
|
|
131
|
+
assert.ok(result.code.includes('<template>'));
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it('supports composition API', async () => {
|
|
135
|
+
mockLLMClient._call = async () => ({
|
|
136
|
+
code: `<script setup>
|
|
137
|
+
import { ref } from 'vue';
|
|
138
|
+
</script>`,
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
const result = await generateVue(generator, {
|
|
142
|
+
design: { components: [] },
|
|
143
|
+
composition: true,
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
assert.ok(result.code.includes('setup'));
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
describe('generateHTML', () => {
|
|
151
|
+
it('generates plain HTML', async () => {
|
|
152
|
+
mockLLMClient._call = async () => ({
|
|
153
|
+
code: `<div class="form">
|
|
154
|
+
<input type="email" placeholder="Email">
|
|
155
|
+
<button type="submit">Submit</button>
|
|
156
|
+
</div>`,
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
const result = await generateHTML(generator, {
|
|
160
|
+
design: { components: [{ type: 'input' }, { type: 'button' }] },
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
assert.ok(result.code);
|
|
164
|
+
assert.ok(result.code.includes('<div'));
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it('includes CSS when requested', async () => {
|
|
168
|
+
mockLLMClient._call = async () => ({
|
|
169
|
+
code: `<div class="form"></div>`,
|
|
170
|
+
css: `.form { padding: 1rem; }`,
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
const result = await generateHTML(generator, {
|
|
174
|
+
design: { components: [] },
|
|
175
|
+
includeCSS: true,
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
assert.ok(result.css || result.code.includes('style'));
|
|
179
|
+
});
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
describe('generateTailwind', () => {
|
|
183
|
+
it('generates Tailwind classes', async () => {
|
|
184
|
+
mockLLMClient._call = async () => ({
|
|
185
|
+
code: `<div class="flex flex-col gap-4 p-6">
|
|
186
|
+
<input class="border rounded px-4 py-2" placeholder="Email" />
|
|
187
|
+
<button class="bg-blue-500 text-white px-4 py-2 rounded">Submit</button>
|
|
188
|
+
</div>`,
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
const result = await generateTailwind(generator, {
|
|
192
|
+
design: { components: [], colors: [{ hex: '#3B82F6' }] },
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
assert.ok(result.code);
|
|
196
|
+
assert.ok(result.code.includes('class="'));
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
it('maps colors to Tailwind', async () => {
|
|
200
|
+
mockLLMClient._call = async () => ({
|
|
201
|
+
code: `<button class="bg-blue-500">Button</button>`,
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
const result = await generateTailwind(generator, {
|
|
205
|
+
design: { colors: [{ hex: '#3B82F6', role: 'primary' }] },
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
assert.ok(result.code.includes('blue') || result.code.includes('primary'));
|
|
209
|
+
});
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
describe('mapToLibrary', () => {
|
|
213
|
+
it('maps to shadcn/ui components', async () => {
|
|
214
|
+
const components = [
|
|
215
|
+
{ type: 'button', variant: 'primary' },
|
|
216
|
+
{ type: 'input', variant: 'text' },
|
|
217
|
+
];
|
|
218
|
+
|
|
219
|
+
const mapped = await mapToLibrary(generator, {
|
|
220
|
+
components,
|
|
221
|
+
library: 'shadcn',
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
assert.ok(mapped);
|
|
225
|
+
assert.ok(Array.isArray(mapped));
|
|
226
|
+
mapped.forEach(m => {
|
|
227
|
+
assert.ok(m.original);
|
|
228
|
+
assert.ok(m.mapped);
|
|
229
|
+
});
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
it('maps to MUI components', async () => {
|
|
233
|
+
const components = [
|
|
234
|
+
{ type: 'button', label: 'Click' },
|
|
235
|
+
];
|
|
236
|
+
|
|
237
|
+
const mapped = await mapToLibrary(generator, {
|
|
238
|
+
components,
|
|
239
|
+
library: 'mui',
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
assert.ok(mapped[0].mapped.includes('Button') || mapped[0].mapped.includes('Mui'));
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
it('maps to Chakra components', async () => {
|
|
246
|
+
const components = [
|
|
247
|
+
{ type: 'input', placeholder: 'Enter text' },
|
|
248
|
+
];
|
|
249
|
+
|
|
250
|
+
const mapped = await mapToLibrary(generator, {
|
|
251
|
+
components,
|
|
252
|
+
library: 'chakra',
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
assert.ok(mapped[0].mapped);
|
|
256
|
+
});
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
describe('addDesignReference', () => {
|
|
260
|
+
it('adds source comment', () => {
|
|
261
|
+
const code = `function Component() { return <div />; }`;
|
|
262
|
+
const mockupPath = '/designs/login.png';
|
|
263
|
+
|
|
264
|
+
const result = addDesignReference(code, { mockupPath });
|
|
265
|
+
|
|
266
|
+
assert.ok(result.includes('login.png') || result.includes('design'));
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
it('adds figma link', () => {
|
|
270
|
+
const code = `function Component() { return <div />; }`;
|
|
271
|
+
const figmaUrl = 'https://figma.com/file/abc123';
|
|
272
|
+
|
|
273
|
+
const result = addDesignReference(code, { figmaUrl });
|
|
274
|
+
|
|
275
|
+
assert.ok(result.includes('figma.com'));
|
|
276
|
+
});
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
describe('formatCode', () => {
|
|
280
|
+
it('formats JavaScript', async () => {
|
|
281
|
+
const code = `function Component(){return <div className="test"></div>}`;
|
|
282
|
+
|
|
283
|
+
const formatted = await formatCode(code, { language: 'javascript' });
|
|
284
|
+
|
|
285
|
+
// Should have some formatting applied
|
|
286
|
+
assert.ok(formatted);
|
|
287
|
+
assert.ok(typeof formatted === 'string');
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
it('formats TypeScript', async () => {
|
|
291
|
+
const code = `const x:string="hello"`;
|
|
292
|
+
|
|
293
|
+
const formatted = await formatCode(code, { language: 'typescript' });
|
|
294
|
+
|
|
295
|
+
assert.ok(formatted);
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
it('preserves code if formatting fails', async () => {
|
|
299
|
+
const invalidCode = `function ( { return }`;
|
|
300
|
+
|
|
301
|
+
const formatted = await formatCode(invalidCode, { language: 'javascript' });
|
|
302
|
+
|
|
303
|
+
// Should return original or close to it if parsing fails
|
|
304
|
+
assert.ok(formatted);
|
|
305
|
+
});
|
|
306
|
+
});
|
|
307
|
+
});
|
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cost Command Module
|
|
3
|
+
*
|
|
4
|
+
* CLI interface for cost management
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const { projectCost, compareModels } = require('./cost-projections.js');
|
|
8
|
+
const { generateReport, groupByModel, groupByOperation, formatReport } = require('./cost-reports.js');
|
|
9
|
+
const { analyzeUsage, suggestCheaperModel, createOptimizer, formatSuggestions } = require('./cost-optimizer.js');
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Parse command line arguments
|
|
13
|
+
* @param {string} input - Command input string
|
|
14
|
+
* @returns {Object} Parsed arguments
|
|
15
|
+
*/
|
|
16
|
+
function parseArgs(input) {
|
|
17
|
+
const parts = [];
|
|
18
|
+
let current = '';
|
|
19
|
+
let inQuotes = false;
|
|
20
|
+
|
|
21
|
+
// Parse respecting quotes
|
|
22
|
+
for (const char of input) {
|
|
23
|
+
if (char === '"' && !inQuotes) {
|
|
24
|
+
inQuotes = true;
|
|
25
|
+
} else if (char === '"' && inQuotes) {
|
|
26
|
+
inQuotes = false;
|
|
27
|
+
} else if (char === ' ' && !inQuotes) {
|
|
28
|
+
if (current) {
|
|
29
|
+
parts.push(current);
|
|
30
|
+
current = '';
|
|
31
|
+
}
|
|
32
|
+
} else {
|
|
33
|
+
current += char;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
if (current) parts.push(current);
|
|
37
|
+
|
|
38
|
+
const result = {
|
|
39
|
+
command: parts[0] || 'status',
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
// Parse flags
|
|
43
|
+
for (let i = 1; i < parts.length; i++) {
|
|
44
|
+
const part = parts[i];
|
|
45
|
+
|
|
46
|
+
if (part === '--daily' && parts[i + 1]) {
|
|
47
|
+
result.daily = parseFloat(parts[i + 1]);
|
|
48
|
+
i++;
|
|
49
|
+
} else if (part === '--monthly' && parts[i + 1]) {
|
|
50
|
+
result.monthly = parseFloat(parts[i + 1]);
|
|
51
|
+
i++;
|
|
52
|
+
} else if (part === '--from' && parts[i + 1]) {
|
|
53
|
+
result.from = parts[i + 1];
|
|
54
|
+
i++;
|
|
55
|
+
} else if (part === '--to' && parts[i + 1]) {
|
|
56
|
+
result.to = parts[i + 1];
|
|
57
|
+
i++;
|
|
58
|
+
} else if (part === '--compare') {
|
|
59
|
+
result.compare = true;
|
|
60
|
+
} else if (!part.startsWith('--')) {
|
|
61
|
+
result.prompt = part;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return result;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Format status output
|
|
70
|
+
* @param {Object} status - Status data
|
|
71
|
+
* @returns {string} Formatted output
|
|
72
|
+
*/
|
|
73
|
+
function formatStatus(status) {
|
|
74
|
+
const lines = [
|
|
75
|
+
'Cost Status',
|
|
76
|
+
'═'.repeat(40),
|
|
77
|
+
'',
|
|
78
|
+
`Daily: $${status.dailySpend.toFixed(2)} / $${status.dailyBudget.toFixed(2)} ($${status.dailyRemaining.toFixed(2)} remaining)`,
|
|
79
|
+
`Monthly: $${status.monthlySpend.toFixed(2)} / $${status.monthlyBudget.toFixed(2)} ($${status.monthlyRemaining.toFixed(2)} remaining)`,
|
|
80
|
+
];
|
|
81
|
+
|
|
82
|
+
if (status.byModel && Object.keys(status.byModel).length > 0) {
|
|
83
|
+
lines.push('', 'By Model:');
|
|
84
|
+
for (const [model, cost] of Object.entries(status.byModel)) {
|
|
85
|
+
lines.push(` ${model}: $${cost.toFixed(2)}`);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return lines.join('\n');
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Cost Command class
|
|
94
|
+
*/
|
|
95
|
+
class CostCommand {
|
|
96
|
+
/**
|
|
97
|
+
* Create a cost command instance
|
|
98
|
+
* @param {Object} options - Dependencies
|
|
99
|
+
* @param {Object} options.tracker - Cost tracker instance
|
|
100
|
+
* @param {Object} options.pricing - Pricing module
|
|
101
|
+
* @param {Object} options.budget - Budget manager
|
|
102
|
+
*/
|
|
103
|
+
constructor(options) {
|
|
104
|
+
this.tracker = options.tracker;
|
|
105
|
+
this.pricing = options.pricing;
|
|
106
|
+
this.budget = options.budget;
|
|
107
|
+
this.optimizer = createOptimizer();
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Execute a command
|
|
112
|
+
* @param {string} input - Command input
|
|
113
|
+
* @returns {Object} Execution result
|
|
114
|
+
*/
|
|
115
|
+
async execute(input) {
|
|
116
|
+
const args = parseArgs(input);
|
|
117
|
+
|
|
118
|
+
switch (args.command) {
|
|
119
|
+
case 'status':
|
|
120
|
+
return this.executeStatus();
|
|
121
|
+
|
|
122
|
+
case 'budget':
|
|
123
|
+
return this.executeBudget(args);
|
|
124
|
+
|
|
125
|
+
case 'report':
|
|
126
|
+
return this.executeReport(args);
|
|
127
|
+
|
|
128
|
+
case 'estimate':
|
|
129
|
+
return this.executeEstimate(args);
|
|
130
|
+
|
|
131
|
+
case 'optimize':
|
|
132
|
+
return this.executeOptimize();
|
|
133
|
+
|
|
134
|
+
default:
|
|
135
|
+
return {
|
|
136
|
+
success: false,
|
|
137
|
+
output: `Unknown command: ${args.command}`,
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Execute status command
|
|
144
|
+
* @returns {Object} Status result
|
|
145
|
+
*/
|
|
146
|
+
executeStatus() {
|
|
147
|
+
const today = new Date().toISOString().split('T')[0];
|
|
148
|
+
|
|
149
|
+
const status = {
|
|
150
|
+
dailySpend: this.tracker.getDailyCost(today),
|
|
151
|
+
monthlySpend: this.tracker.getMonthlyCost(),
|
|
152
|
+
dailyBudget: this.budget.getDailyBudget() || 0,
|
|
153
|
+
monthlyBudget: this.budget.getMonthlyBudget() || 0,
|
|
154
|
+
byModel: this.tracker.getCostByModel(),
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
const remaining = this.budget.budgetRemaining({
|
|
158
|
+
currentSpend: status.dailySpend,
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
status.dailyRemaining = remaining.daily || 0;
|
|
162
|
+
status.monthlyRemaining = remaining.monthly || 0;
|
|
163
|
+
|
|
164
|
+
return {
|
|
165
|
+
success: true,
|
|
166
|
+
output: formatStatus(status),
|
|
167
|
+
status,
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Execute budget command
|
|
173
|
+
* @param {Object} args - Parsed arguments
|
|
174
|
+
* @returns {Object} Budget result
|
|
175
|
+
*/
|
|
176
|
+
executeBudget(args) {
|
|
177
|
+
if (args.daily !== undefined) {
|
|
178
|
+
this.budget.setBudget({ type: 'daily', limit: args.daily });
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if (args.monthly !== undefined) {
|
|
182
|
+
this.budget.setBudget({ type: 'monthly', limit: args.monthly });
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return {
|
|
186
|
+
success: true,
|
|
187
|
+
output: `Budget updated${args.daily ? ` - Daily: $${args.daily}` : ''}${args.monthly ? ` - Monthly: $${args.monthly}` : ''}`,
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Execute report command
|
|
193
|
+
* @param {Object} args - Parsed arguments
|
|
194
|
+
* @returns {Object} Report result
|
|
195
|
+
*/
|
|
196
|
+
executeReport(args) {
|
|
197
|
+
const records = this.tracker.getRecords?.({
|
|
198
|
+
startDate: args.from,
|
|
199
|
+
endDate: args.to,
|
|
200
|
+
}) || [];
|
|
201
|
+
|
|
202
|
+
const report = generateReport(records);
|
|
203
|
+
report.byModel = groupByModel(records);
|
|
204
|
+
report.byOperation = groupByOperation(records);
|
|
205
|
+
|
|
206
|
+
return {
|
|
207
|
+
success: true,
|
|
208
|
+
output: formatReport(report),
|
|
209
|
+
report,
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Execute estimate command
|
|
215
|
+
* @param {Object} args - Parsed arguments
|
|
216
|
+
* @returns {Object} Estimate result
|
|
217
|
+
*/
|
|
218
|
+
executeEstimate(args) {
|
|
219
|
+
const prompt = args.prompt || '';
|
|
220
|
+
const model = 'claude-3-sonnet'; // Default model
|
|
221
|
+
|
|
222
|
+
if (args.compare) {
|
|
223
|
+
const models = ['claude-3-opus', 'claude-3-sonnet', 'claude-3-haiku', 'gpt-4', 'gpt-4o'];
|
|
224
|
+
const comparison = compareModels({ prompt, taskType: 'code-generation', models });
|
|
225
|
+
|
|
226
|
+
const lines = ['Cost Comparison:', ''];
|
|
227
|
+
for (const proj of comparison) {
|
|
228
|
+
lines.push(`${proj.model}: $${proj.estimatedCost.toFixed(4)}`);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
return {
|
|
232
|
+
success: true,
|
|
233
|
+
output: lines.join('\n'),
|
|
234
|
+
comparison,
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const estimate = projectCost({
|
|
239
|
+
prompt,
|
|
240
|
+
model,
|
|
241
|
+
taskType: 'code-generation',
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
return {
|
|
245
|
+
success: true,
|
|
246
|
+
output: `Estimated cost for "${prompt.substring(0, 30)}...": $${estimate.estimatedCost.toFixed(4)}`,
|
|
247
|
+
estimate,
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Execute optimize command
|
|
253
|
+
* @returns {Object} Optimization result
|
|
254
|
+
*/
|
|
255
|
+
executeOptimize() {
|
|
256
|
+
const records = this.tracker.getRecords?.() || [];
|
|
257
|
+
const analysis = analyzeUsage(this.optimizer, records);
|
|
258
|
+
|
|
259
|
+
const suggestions = [];
|
|
260
|
+
|
|
261
|
+
// Check for expensive operations using high-cost models
|
|
262
|
+
for (const op of analysis.expensiveOperations.slice(0, 3)) {
|
|
263
|
+
const suggestion = suggestCheaperModel(this.optimizer, {
|
|
264
|
+
currentModel: op.model,
|
|
265
|
+
taskType: op.operation || 'code-generation',
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
if (suggestion) {
|
|
269
|
+
suggestions.push({
|
|
270
|
+
type: 'model',
|
|
271
|
+
current: op.model,
|
|
272
|
+
suggested: suggestion.alternativeModel,
|
|
273
|
+
savings: suggestion.estimatedSavings,
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
return {
|
|
279
|
+
success: true,
|
|
280
|
+
output: formatSuggestions(suggestions),
|
|
281
|
+
suggestions,
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
module.exports = {
|
|
287
|
+
CostCommand,
|
|
288
|
+
parseArgs,
|
|
289
|
+
formatStatus,
|
|
290
|
+
};
|