elsabro 2.2.0 → 3.7.0

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 (88) hide show
  1. package/README.md +668 -20
  2. package/agents/elsabro-orchestrator.md +113 -0
  3. package/bin/install.js +0 -0
  4. package/commands/elsabro/execute.md +223 -46
  5. package/commands/elsabro/start.md +34 -0
  6. package/commands/elsabro/verify-work.md +29 -0
  7. package/flows/development-flow.json +452 -0
  8. package/flows/quick-flow.json +118 -0
  9. package/hooks/confirm-destructive.sh +145 -0
  10. package/hooks/hooks-config.json +81 -0
  11. package/hooks/lint-check.sh +238 -0
  12. package/hooks/post-edit-test.sh +189 -0
  13. package/package.json +5 -3
  14. package/references/SYSTEM_INDEX.md +379 -5
  15. package/references/agent-marketplace.md +2274 -0
  16. package/references/agent-protocol.md +1126 -0
  17. package/references/ai-code-suggestions.md +2413 -0
  18. package/references/checkpointing.md +595 -0
  19. package/references/collaboration-patterns.md +851 -0
  20. package/references/collaborative-sessions.md +1081 -0
  21. package/references/configuration-management.md +1810 -0
  22. package/references/cost-tracking.md +1095 -0
  23. package/references/enterprise-sso.md +2001 -0
  24. package/references/error-contracts-tests.md +1171 -0
  25. package/references/error-contracts-v2.md +968 -0
  26. package/references/error-contracts.md +3102 -0
  27. package/references/event-driven.md +1031 -0
  28. package/references/flow-orchestration.md +940 -0
  29. package/references/flow-visualization.md +1557 -0
  30. package/references/ide-integrations.md +3513 -0
  31. package/references/interrupt-system.md +681 -0
  32. package/references/kubernetes-deployment.md +3099 -0
  33. package/references/memory-system.md +683 -0
  34. package/references/mobile-companion.md +3236 -0
  35. package/references/multi-llm-providers.md +2494 -0
  36. package/references/multi-project-memory.md +1182 -0
  37. package/references/observability.md +793 -0
  38. package/references/output-schemas.md +858 -0
  39. package/references/parallel-worktrees.md +293 -0
  40. package/references/performance-profiler.md +955 -0
  41. package/references/plugin-system.md +1526 -0
  42. package/references/prompt-management.md +292 -0
  43. package/references/sandbox-execution.md +303 -0
  44. package/references/security-system.md +1253 -0
  45. package/references/streaming.md +696 -0
  46. package/references/testing-framework.md +1151 -0
  47. package/references/time-travel.md +802 -0
  48. package/references/tool-registry.md +886 -0
  49. package/references/voice-commands.md +3296 -0
  50. package/scripts/setup-parallel-worktrees.sh +319 -0
  51. package/skills/memory-update.md +207 -0
  52. package/skills/review.md +331 -0
  53. package/skills/techdebt.md +289 -0
  54. package/skills/tutor.md +219 -0
  55. package/templates/.planning/notes/.gitkeep +0 -0
  56. package/templates/CLAUDE.md.template +48 -0
  57. package/templates/agent-marketplace-config.json +220 -0
  58. package/templates/agent-protocol-config.json +136 -0
  59. package/templates/ai-suggestions-config.json +100 -0
  60. package/templates/checkpoint-state.json +61 -0
  61. package/templates/collaboration-config.json +157 -0
  62. package/templates/collaborative-sessions-config.json +153 -0
  63. package/templates/configuration-config.json +245 -0
  64. package/templates/cost-tracking-config.json +148 -0
  65. package/templates/enterprise-sso-config.json +438 -0
  66. package/templates/error-handling-config.json +79 -2
  67. package/templates/events-config.json +148 -0
  68. package/templates/flow-visualization-config.json +196 -0
  69. package/templates/ide-integrations-config.json +442 -0
  70. package/templates/kubernetes-config.json +764 -0
  71. package/templates/memory-state.json +84 -0
  72. package/templates/mistakes.md.template +52 -0
  73. package/templates/mobile-companion-config.json +600 -0
  74. package/templates/multi-llm-config.json +544 -0
  75. package/templates/multi-project-memory-config.json +145 -0
  76. package/templates/observability-config.json +109 -0
  77. package/templates/patterns.md.template +114 -0
  78. package/templates/performance-profiler-config.json +125 -0
  79. package/templates/plugin-config.json +170 -0
  80. package/templates/prompt-management-config.json +86 -0
  81. package/templates/sandbox-config.json +185 -0
  82. package/templates/schemas-config.json +65 -0
  83. package/templates/security-config.json +120 -0
  84. package/templates/streaming-config.json +72 -0
  85. package/templates/testing-config.json +81 -0
  86. package/templates/timetravel-config.json +62 -0
  87. package/templates/tool-registry-config.json +109 -0
  88. package/templates/voice-commands-config.json +658 -0
@@ -0,0 +1,1095 @@
1
+ # Cost Tracking & Optimization (v3.3)
2
+
3
+ Sistema de monitoreo de costos, presupuestos y optimización automática de selección de modelos.
4
+
5
+ ## Arquitectura
6
+
7
+ ```
8
+ ┌─────────────────────────────────────────────────────────────────────────┐
9
+ │ COST TRACKING SYSTEM │
10
+ ├─────────────────────────────────────────────────────────────────────────┤
11
+ │ │
12
+ │ ┌─────────────────────────────────────────────────────────────────┐ │
13
+ │ │ TOKEN TRACKER │ │
14
+ │ │ ┌─────────────────────────────────────────────────────────┐ │ │
15
+ │ │ │ Per-Request Metering │ │ │
16
+ │ │ │ ┌───────────┐ ┌───────────┐ ┌───────────┐ │ │ │
17
+ │ │ │ │ Input │ │ Output │ │ Cache │ │ │ │
18
+ │ │ │ │ Tokens │ │ Tokens │ │ Hits │ │ │ │
19
+ │ │ │ └───────────┘ └───────────┘ └───────────┘ │ │ │
20
+ │ │ └─────────────────────────────────────────────────────────┘ │ │
21
+ │ └─────────────────────────────────────────────────────────────────┘ │
22
+ │ │ │
23
+ │ ▼ │
24
+ │ ┌─────────────────────────────────────────────────────────────────┐ │
25
+ │ │ BUDGET MANAGER │ │
26
+ │ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │
27
+ │ │ │ Session │ │ Daily │ │ Monthly │ │ │
28
+ │ │ │ Budget │ │ Budget │ │ Budget │ │ │
29
+ │ │ └─────────────┘ └─────────────┘ └─────────────┘ │ │
30
+ │ │ ┌───────────────────────────────────────────────────────────┐ │ │
31
+ │ │ │ Alerts: warning (80%) │ critical (95%) │ hard_limit │ │ │
32
+ │ │ └───────────────────────────────────────────────────────────┘ │ │
33
+ │ └─────────────────────────────────────────────────────────────────┘ │
34
+ │ │ │
35
+ │ ▼ │
36
+ │ ┌─────────────────────────────────────────────────────────────────┐ │
37
+ │ │ MODEL OPTIMIZER │ │
38
+ │ │ ┌─────────────────────────────────────────────────────────┐ │ │
39
+ │ │ │ Task Analysis → Model Selection → Cost Prediction │ │ │
40
+ │ │ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │ │
41
+ │ │ │ │ Simple │ → │ Haiku │ │ $0.001 │ │ │ │
42
+ │ │ │ │ Medium │ → │ Sonnet │ │ $0.015 │ │ │ │
43
+ │ │ │ │ Complex │ → │ Opus │ │ $0.075 │ │ │ │
44
+ │ │ │ └─────────┘ └─────────┘ └─────────┘ │ │ │
45
+ │ │ └─────────────────────────────────────────────────────────┘ │ │
46
+ │ └─────────────────────────────────────────────────────────────────┘ │
47
+ │ │
48
+ └─────────────────────────────────────────────────────────────────────────┘
49
+ ```
50
+
51
+ ---
52
+
53
+ ## TokenTracker
54
+
55
+ ### API Principal
56
+
57
+ ```typescript
58
+ interface TokenUsage {
59
+ input_tokens: number;
60
+ output_tokens: number;
61
+ cache_creation_tokens?: number;
62
+ cache_read_tokens?: number;
63
+ total_tokens: number;
64
+ }
65
+
66
+ interface UsageRecord {
67
+ id: string;
68
+ timestamp: string;
69
+ model: string;
70
+ agent?: string;
71
+ task?: string;
72
+ flow?: string;
73
+ usage: TokenUsage;
74
+ cost: CostBreakdown;
75
+ duration_ms: number;
76
+ success: boolean;
77
+ }
78
+
79
+ interface CostBreakdown {
80
+ input_cost: number;
81
+ output_cost: number;
82
+ cache_cost: number;
83
+ total_cost: number;
84
+ currency: string;
85
+ }
86
+
87
+ interface ModelPricing {
88
+ model: string;
89
+ input_per_million: number;
90
+ output_per_million: number;
91
+ cache_creation_per_million?: number;
92
+ cache_read_per_million?: number;
93
+ }
94
+
95
+ class TokenTracker {
96
+ private records: UsageRecord[] = [];
97
+ private pricing: Map<string, ModelPricing>;
98
+ private config: TokenTrackerConfig;
99
+
100
+ constructor(config: TokenTrackerConfig) {
101
+ this.config = config;
102
+ this.pricing = this.initializePricing();
103
+ }
104
+
105
+ // Record usage from API response
106
+ record(
107
+ model: string,
108
+ usage: TokenUsage,
109
+ metadata?: UsageMetadata
110
+ ): UsageRecord {
111
+ const pricing = this.pricing.get(model);
112
+ if (!pricing) {
113
+ throw new Error(`Unknown model: ${model}`);
114
+ }
115
+
116
+ const cost = this.calculateCost(usage, pricing);
117
+
118
+ const record: UsageRecord = {
119
+ id: this.generateId(),
120
+ timestamp: new Date().toISOString(),
121
+ model,
122
+ agent: metadata?.agent,
123
+ task: metadata?.task,
124
+ flow: metadata?.flow,
125
+ usage,
126
+ cost,
127
+ duration_ms: metadata?.duration_ms || 0,
128
+ success: metadata?.success ?? true
129
+ };
130
+
131
+ this.records.push(record);
132
+
133
+ // Emit event for real-time tracking
134
+ EventBus.publish('cost.usage.recorded', record);
135
+
136
+ // Check budget alerts
137
+ BudgetManager.checkAlerts(record);
138
+
139
+ return record;
140
+ }
141
+
142
+ // Get usage summary
143
+ getSummary(options?: SummaryOptions): UsageSummary {
144
+ const filtered = this.filterRecords(options);
145
+
146
+ const summary: UsageSummary = {
147
+ total_requests: filtered.length,
148
+ total_tokens: {
149
+ input: 0,
150
+ output: 0,
151
+ cache_creation: 0,
152
+ cache_read: 0,
153
+ total: 0
154
+ },
155
+ total_cost: {
156
+ input: 0,
157
+ output: 0,
158
+ cache: 0,
159
+ total: 0,
160
+ currency: 'USD'
161
+ },
162
+ by_model: {},
163
+ by_agent: {},
164
+ by_task: {},
165
+ period: {
166
+ from: options?.from || this.records[0]?.timestamp,
167
+ to: options?.to || new Date().toISOString()
168
+ }
169
+ };
170
+
171
+ for (const record of filtered) {
172
+ // Total tokens
173
+ summary.total_tokens.input += record.usage.input_tokens;
174
+ summary.total_tokens.output += record.usage.output_tokens;
175
+ summary.total_tokens.cache_creation += record.usage.cache_creation_tokens || 0;
176
+ summary.total_tokens.cache_read += record.usage.cache_read_tokens || 0;
177
+ summary.total_tokens.total += record.usage.total_tokens;
178
+
179
+ // Total cost
180
+ summary.total_cost.input += record.cost.input_cost;
181
+ summary.total_cost.output += record.cost.output_cost;
182
+ summary.total_cost.cache += record.cost.cache_cost;
183
+ summary.total_cost.total += record.cost.total_cost;
184
+
185
+ // By model
186
+ if (!summary.by_model[record.model]) {
187
+ summary.by_model[record.model] = { requests: 0, tokens: 0, cost: 0 };
188
+ }
189
+ summary.by_model[record.model].requests++;
190
+ summary.by_model[record.model].tokens += record.usage.total_tokens;
191
+ summary.by_model[record.model].cost += record.cost.total_cost;
192
+
193
+ // By agent
194
+ if (record.agent) {
195
+ if (!summary.by_agent[record.agent]) {
196
+ summary.by_agent[record.agent] = { requests: 0, tokens: 0, cost: 0 };
197
+ }
198
+ summary.by_agent[record.agent].requests++;
199
+ summary.by_agent[record.agent].tokens += record.usage.total_tokens;
200
+ summary.by_agent[record.agent].cost += record.cost.total_cost;
201
+ }
202
+
203
+ // By task
204
+ if (record.task) {
205
+ if (!summary.by_task[record.task]) {
206
+ summary.by_task[record.task] = { requests: 0, tokens: 0, cost: 0 };
207
+ }
208
+ summary.by_task[record.task].requests++;
209
+ summary.by_task[record.task].tokens += record.usage.total_tokens;
210
+ summary.by_task[record.task].cost += record.cost.total_cost;
211
+ }
212
+ }
213
+
214
+ return summary;
215
+ }
216
+
217
+ // Get usage by time period
218
+ getTimeSeries(
219
+ granularity: 'minute' | 'hour' | 'day' | 'week' | 'month',
220
+ options?: TimeSeriesOptions
221
+ ): TimeSeriesData[] {
222
+ const filtered = this.filterRecords(options);
223
+ const buckets = new Map<string, TimeSeriesData>();
224
+
225
+ for (const record of filtered) {
226
+ const bucket = this.getBucket(record.timestamp, granularity);
227
+
228
+ if (!buckets.has(bucket)) {
229
+ buckets.set(bucket, {
230
+ timestamp: bucket,
231
+ requests: 0,
232
+ tokens: 0,
233
+ cost: 0
234
+ });
235
+ }
236
+
237
+ const data = buckets.get(bucket)!;
238
+ data.requests++;
239
+ data.tokens += record.usage.total_tokens;
240
+ data.cost += record.cost.total_cost;
241
+ }
242
+
243
+ return Array.from(buckets.values()).sort((a, b) =>
244
+ a.timestamp.localeCompare(b.timestamp)
245
+ );
246
+ }
247
+
248
+ // Estimate cost for task
249
+ estimate(
250
+ model: string,
251
+ estimatedInputTokens: number,
252
+ estimatedOutputTokens: number
253
+ ): CostBreakdown {
254
+ const pricing = this.pricing.get(model);
255
+ if (!pricing) {
256
+ throw new Error(`Unknown model: ${model}`);
257
+ }
258
+
259
+ return this.calculateCost(
260
+ {
261
+ input_tokens: estimatedInputTokens,
262
+ output_tokens: estimatedOutputTokens,
263
+ total_tokens: estimatedInputTokens + estimatedOutputTokens
264
+ },
265
+ pricing
266
+ );
267
+ }
268
+
269
+ // Export data
270
+ export(format: 'json' | 'csv'): string {
271
+ if (format === 'csv') {
272
+ return this.exportCSV();
273
+ }
274
+ return JSON.stringify(this.records, null, 2);
275
+ }
276
+
277
+ // Clear old records
278
+ cleanup(olderThan: Date): number {
279
+ const cutoff = olderThan.toISOString();
280
+ const before = this.records.length;
281
+ this.records = this.records.filter(r => r.timestamp >= cutoff);
282
+ return before - this.records.length;
283
+ }
284
+
285
+ private calculateCost(usage: TokenUsage, pricing: ModelPricing): CostBreakdown {
286
+ const input_cost = (usage.input_tokens / 1_000_000) * pricing.input_per_million;
287
+ const output_cost = (usage.output_tokens / 1_000_000) * pricing.output_per_million;
288
+
289
+ let cache_cost = 0;
290
+ if (usage.cache_creation_tokens && pricing.cache_creation_per_million) {
291
+ cache_cost += (usage.cache_creation_tokens / 1_000_000) * pricing.cache_creation_per_million;
292
+ }
293
+ if (usage.cache_read_tokens && pricing.cache_read_per_million) {
294
+ cache_cost += (usage.cache_read_tokens / 1_000_000) * pricing.cache_read_per_million;
295
+ }
296
+
297
+ return {
298
+ input_cost,
299
+ output_cost,
300
+ cache_cost,
301
+ total_cost: input_cost + output_cost + cache_cost,
302
+ currency: 'USD'
303
+ };
304
+ }
305
+
306
+ private initializePricing(): Map<string, ModelPricing> {
307
+ return new Map([
308
+ ['claude-3-5-haiku-20241022', {
309
+ model: 'claude-3-5-haiku-20241022',
310
+ input_per_million: 0.80,
311
+ output_per_million: 4.00,
312
+ cache_creation_per_million: 1.00,
313
+ cache_read_per_million: 0.08
314
+ }],
315
+ ['claude-sonnet-4-20250514', {
316
+ model: 'claude-sonnet-4-20250514',
317
+ input_per_million: 3.00,
318
+ output_per_million: 15.00,
319
+ cache_creation_per_million: 3.75,
320
+ cache_read_per_million: 0.30
321
+ }],
322
+ ['claude-opus-4-5-20251101', {
323
+ model: 'claude-opus-4-5-20251101',
324
+ input_per_million: 15.00,
325
+ output_per_million: 75.00,
326
+ cache_creation_per_million: 18.75,
327
+ cache_read_per_million: 1.50
328
+ }]
329
+ ]);
330
+ }
331
+
332
+ private filterRecords(options?: SummaryOptions): UsageRecord[] {
333
+ let filtered = this.records;
334
+
335
+ if (options?.from) {
336
+ filtered = filtered.filter(r => r.timestamp >= options.from!);
337
+ }
338
+ if (options?.to) {
339
+ filtered = filtered.filter(r => r.timestamp <= options.to!);
340
+ }
341
+ if (options?.model) {
342
+ filtered = filtered.filter(r => r.model === options.model);
343
+ }
344
+ if (options?.agent) {
345
+ filtered = filtered.filter(r => r.agent === options.agent);
346
+ }
347
+ if (options?.task) {
348
+ filtered = filtered.filter(r => r.task === options.task);
349
+ }
350
+
351
+ return filtered;
352
+ }
353
+
354
+ private getBucket(timestamp: string, granularity: string): string {
355
+ const date = new Date(timestamp);
356
+
357
+ switch (granularity) {
358
+ case 'minute':
359
+ return date.toISOString().slice(0, 16);
360
+ case 'hour':
361
+ return date.toISOString().slice(0, 13);
362
+ case 'day':
363
+ return date.toISOString().slice(0, 10);
364
+ case 'week':
365
+ const week = this.getWeekNumber(date);
366
+ return `${date.getFullYear()}-W${week.toString().padStart(2, '0')}`;
367
+ case 'month':
368
+ return date.toISOString().slice(0, 7);
369
+ default:
370
+ return date.toISOString().slice(0, 10);
371
+ }
372
+ }
373
+
374
+ private getWeekNumber(date: Date): number {
375
+ const firstDay = new Date(date.getFullYear(), 0, 1);
376
+ const days = Math.floor((date.getTime() - firstDay.getTime()) / 86400000);
377
+ return Math.ceil((days + firstDay.getDay() + 1) / 7);
378
+ }
379
+
380
+ private exportCSV(): string {
381
+ const headers = [
382
+ 'id', 'timestamp', 'model', 'agent', 'task',
383
+ 'input_tokens', 'output_tokens', 'total_tokens',
384
+ 'input_cost', 'output_cost', 'total_cost', 'duration_ms'
385
+ ];
386
+
387
+ const rows = this.records.map(r => [
388
+ r.id, r.timestamp, r.model, r.agent || '', r.task || '',
389
+ r.usage.input_tokens, r.usage.output_tokens, r.usage.total_tokens,
390
+ r.cost.input_cost.toFixed(6), r.cost.output_cost.toFixed(6),
391
+ r.cost.total_cost.toFixed(6), r.duration_ms
392
+ ]);
393
+
394
+ return [headers.join(','), ...rows.map(r => r.join(','))].join('\n');
395
+ }
396
+
397
+ private generateId(): string {
398
+ return `usage_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
399
+ }
400
+ }
401
+ ```
402
+
403
+ ---
404
+
405
+ ## BudgetManager
406
+
407
+ ```typescript
408
+ interface Budget {
409
+ id: string;
410
+ name: string;
411
+ type: 'session' | 'daily' | 'weekly' | 'monthly' | 'total';
412
+ limit: number;
413
+ currency: string;
414
+ spent: number;
415
+ remaining: number;
416
+ alerts: BudgetAlert[];
417
+ action_on_exceed: 'warn' | 'pause' | 'stop' | 'downgrade';
418
+ created_at: string;
419
+ reset_at?: string;
420
+ }
421
+
422
+ interface BudgetAlert {
423
+ type: 'warning' | 'critical' | 'exceeded';
424
+ threshold: number; // Percentage (0-100)
425
+ triggered: boolean;
426
+ triggered_at?: string;
427
+ notified: boolean;
428
+ }
429
+
430
+ class BudgetManager {
431
+ private budgets: Map<string, Budget>;
432
+ private config: BudgetManagerConfig;
433
+
434
+ constructor(config: BudgetManagerConfig) {
435
+ this.config = config;
436
+ this.budgets = new Map();
437
+ this.initializeDefaultBudgets();
438
+ }
439
+
440
+ // Create budget
441
+ createBudget(options: CreateBudgetOptions): Budget {
442
+ const budget: Budget = {
443
+ id: this.generateId(),
444
+ name: options.name,
445
+ type: options.type,
446
+ limit: options.limit,
447
+ currency: options.currency || 'USD',
448
+ spent: 0,
449
+ remaining: options.limit,
450
+ alerts: [
451
+ { type: 'warning', threshold: 80, triggered: false, notified: false },
452
+ { type: 'critical', threshold: 95, triggered: false, notified: false },
453
+ { type: 'exceeded', threshold: 100, triggered: false, notified: false }
454
+ ],
455
+ action_on_exceed: options.action_on_exceed || 'warn',
456
+ created_at: new Date().toISOString(),
457
+ reset_at: this.calculateResetTime(options.type)
458
+ };
459
+
460
+ this.budgets.set(budget.id, budget);
461
+ return budget;
462
+ }
463
+
464
+ // Update budget with cost
465
+ recordSpend(budgetId: string, cost: number): Budget {
466
+ const budget = this.budgets.get(budgetId);
467
+ if (!budget) {
468
+ throw new Error(`Budget not found: ${budgetId}`);
469
+ }
470
+
471
+ budget.spent += cost;
472
+ budget.remaining = Math.max(0, budget.limit - budget.spent);
473
+
474
+ this.checkAlerts(budget);
475
+
476
+ return budget;
477
+ }
478
+
479
+ // Check all budgets against usage record
480
+ checkAlerts(record?: UsageRecord): void {
481
+ for (const [_, budget] of this.budgets) {
482
+ if (record) {
483
+ budget.spent += record.cost.total_cost;
484
+ budget.remaining = Math.max(0, budget.limit - budget.spent);
485
+ }
486
+
487
+ const percentage = (budget.spent / budget.limit) * 100;
488
+
489
+ for (const alert of budget.alerts) {
490
+ if (percentage >= alert.threshold && !alert.triggered) {
491
+ alert.triggered = true;
492
+ alert.triggered_at = new Date().toISOString();
493
+
494
+ // Emit alert event
495
+ EventBus.publish('cost.budget.alert', {
496
+ budget_id: budget.id,
497
+ budget_name: budget.name,
498
+ alert_type: alert.type,
499
+ threshold: alert.threshold,
500
+ current_percentage: percentage,
501
+ spent: budget.spent,
502
+ limit: budget.limit
503
+ });
504
+
505
+ // Take action if exceeded
506
+ if (alert.type === 'exceeded') {
507
+ this.handleExceeded(budget);
508
+ }
509
+ }
510
+ }
511
+ }
512
+ }
513
+
514
+ // Get budget status
515
+ getStatus(budgetId?: string): BudgetStatus | BudgetStatus[] {
516
+ if (budgetId) {
517
+ const budget = this.budgets.get(budgetId);
518
+ if (!budget) throw new Error(`Budget not found: ${budgetId}`);
519
+ return this.formatStatus(budget);
520
+ }
521
+
522
+ return Array.from(this.budgets.values()).map(b => this.formatStatus(b));
523
+ }
524
+
525
+ // Reset budget
526
+ resetBudget(budgetId: string): Budget {
527
+ const budget = this.budgets.get(budgetId);
528
+ if (!budget) {
529
+ throw new Error(`Budget not found: ${budgetId}`);
530
+ }
531
+
532
+ budget.spent = 0;
533
+ budget.remaining = budget.limit;
534
+ budget.alerts.forEach(a => {
535
+ a.triggered = false;
536
+ a.triggered_at = undefined;
537
+ a.notified = false;
538
+ });
539
+ budget.reset_at = this.calculateResetTime(budget.type);
540
+
541
+ return budget;
542
+ }
543
+
544
+ // Check if operation is allowed
545
+ canSpend(estimatedCost: number): BudgetDecision {
546
+ const decisions: BudgetDecision[] = [];
547
+
548
+ for (const [_, budget] of this.budgets) {
549
+ const projectedSpent = budget.spent + estimatedCost;
550
+ const projectedPercentage = (projectedSpent / budget.limit) * 100;
551
+
552
+ if (projectedPercentage >= 100) {
553
+ decisions.push({
554
+ allowed: budget.action_on_exceed === 'warn',
555
+ budget_id: budget.id,
556
+ budget_name: budget.name,
557
+ reason: `Would exceed ${budget.name} budget`,
558
+ projected_percentage: projectedPercentage,
559
+ recommended_action: budget.action_on_exceed
560
+ });
561
+ }
562
+ }
563
+
564
+ // Return most restrictive decision
565
+ const blocked = decisions.find(d => !d.allowed);
566
+ if (blocked) return blocked;
567
+
568
+ return {
569
+ allowed: true,
570
+ reason: 'Within all budgets'
571
+ };
572
+ }
573
+
574
+ // Auto-reset periodic budgets
575
+ autoReset(): void {
576
+ const now = new Date();
577
+
578
+ for (const [_, budget] of this.budgets) {
579
+ if (budget.reset_at && new Date(budget.reset_at) <= now) {
580
+ this.resetBudget(budget.id);
581
+ }
582
+ }
583
+ }
584
+
585
+ private handleExceeded(budget: Budget): void {
586
+ switch (budget.action_on_exceed) {
587
+ case 'pause':
588
+ // Trigger interrupt
589
+ InterruptManager.trigger({
590
+ type: 'confirmation',
591
+ title: 'Budget Exceeded',
592
+ message: `${budget.name} budget has been exceeded. Continue?`,
593
+ options: [
594
+ { id: 'continue', label: 'Continue Anyway' },
595
+ { id: 'stop', label: 'Stop Execution' }
596
+ ]
597
+ });
598
+ break;
599
+
600
+ case 'stop':
601
+ // Stop all flows
602
+ FlowEngine.pauseAll('Budget exceeded');
603
+ break;
604
+
605
+ case 'downgrade':
606
+ // Switch to cheaper model
607
+ ModelOptimizer.forceDowngrade();
608
+ break;
609
+
610
+ case 'warn':
611
+ default:
612
+ // Just log warning
613
+ console.warn(`Budget exceeded: ${budget.name}`);
614
+ }
615
+ }
616
+
617
+ private initializeDefaultBudgets(): void {
618
+ if (this.config.defaultBudgets?.session) {
619
+ this.createBudget({
620
+ name: 'Session Budget',
621
+ type: 'session',
622
+ limit: this.config.defaultBudgets.session,
623
+ action_on_exceed: 'warn'
624
+ });
625
+ }
626
+
627
+ if (this.config.defaultBudgets?.daily) {
628
+ this.createBudget({
629
+ name: 'Daily Budget',
630
+ type: 'daily',
631
+ limit: this.config.defaultBudgets.daily,
632
+ action_on_exceed: 'pause'
633
+ });
634
+ }
635
+
636
+ if (this.config.defaultBudgets?.monthly) {
637
+ this.createBudget({
638
+ name: 'Monthly Budget',
639
+ type: 'monthly',
640
+ limit: this.config.defaultBudgets.monthly,
641
+ action_on_exceed: 'stop'
642
+ });
643
+ }
644
+ }
645
+
646
+ private calculateResetTime(type: string): string | undefined {
647
+ const now = new Date();
648
+
649
+ switch (type) {
650
+ case 'daily':
651
+ return new Date(now.setDate(now.getDate() + 1)).toISOString().slice(0, 10) + 'T00:00:00Z';
652
+ case 'weekly':
653
+ return new Date(now.setDate(now.getDate() + 7)).toISOString().slice(0, 10) + 'T00:00:00Z';
654
+ case 'monthly':
655
+ return new Date(now.setMonth(now.getMonth() + 1)).toISOString().slice(0, 7) + '-01T00:00:00Z';
656
+ default:
657
+ return undefined;
658
+ }
659
+ }
660
+
661
+ private formatStatus(budget: Budget): BudgetStatus {
662
+ const percentage = (budget.spent / budget.limit) * 100;
663
+ return {
664
+ id: budget.id,
665
+ name: budget.name,
666
+ type: budget.type,
667
+ spent: budget.spent,
668
+ limit: budget.limit,
669
+ remaining: budget.remaining,
670
+ percentage,
671
+ status: percentage >= 100 ? 'exceeded' :
672
+ percentage >= 95 ? 'critical' :
673
+ percentage >= 80 ? 'warning' : 'ok',
674
+ resets_at: budget.reset_at
675
+ };
676
+ }
677
+
678
+ private generateId(): string {
679
+ return `budget_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
680
+ }
681
+ }
682
+ ```
683
+
684
+ ---
685
+
686
+ ## ModelOptimizer
687
+
688
+ ```typescript
689
+ interface ModelProfile {
690
+ model: string;
691
+ tier: 'economy' | 'standard' | 'premium';
692
+ strengths: string[];
693
+ weaknesses: string[];
694
+ cost_per_1k_tokens: number;
695
+ quality_score: number; // 0-100
696
+ speed_score: number; // 0-100
697
+ }
698
+
699
+ interface TaskAnalysis {
700
+ complexity: 'simple' | 'medium' | 'complex';
701
+ requires_reasoning: boolean;
702
+ requires_creativity: boolean;
703
+ requires_accuracy: boolean;
704
+ estimated_tokens: number;
705
+ priority: 'speed' | 'quality' | 'cost';
706
+ }
707
+
708
+ interface ModelRecommendation {
709
+ recommended_model: string;
710
+ tier: string;
711
+ confidence: number;
712
+ estimated_cost: number;
713
+ alternatives: Array<{
714
+ model: string;
715
+ cost: number;
716
+ trade_off: string;
717
+ }>;
718
+ reasoning: string;
719
+ }
720
+
721
+ class ModelOptimizer {
722
+ private profiles: Map<string, ModelProfile>;
723
+ private history: Map<string, TaskResult[]>;
724
+ private config: ModelOptimizerConfig;
725
+
726
+ constructor(config: ModelOptimizerConfig) {
727
+ this.config = config;
728
+ this.profiles = this.initializeProfiles();
729
+ this.history = new Map();
730
+ }
731
+
732
+ // Analyze task and recommend model
733
+ recommend(task: TaskAnalysis, budget?: number): ModelRecommendation {
734
+ const candidates = this.rankCandidates(task);
735
+
736
+ // Filter by budget if provided
737
+ const affordable = budget
738
+ ? candidates.filter(c => c.estimated_cost <= budget)
739
+ : candidates;
740
+
741
+ if (affordable.length === 0) {
742
+ // Force cheapest option
743
+ const cheapest = candidates.sort((a, b) => a.estimated_cost - b.estimated_cost)[0];
744
+ return {
745
+ recommended_model: cheapest.model,
746
+ tier: cheapest.tier,
747
+ confidence: 0.5,
748
+ estimated_cost: cheapest.estimated_cost,
749
+ alternatives: [],
750
+ reasoning: 'Budget constraint - using cheapest available model'
751
+ };
752
+ }
753
+
754
+ const best = affordable[0];
755
+ const alternatives = affordable.slice(1, 4).map(c => ({
756
+ model: c.model,
757
+ cost: c.estimated_cost,
758
+ trade_off: this.getTradeOff(best, c)
759
+ }));
760
+
761
+ return {
762
+ recommended_model: best.model,
763
+ tier: best.tier,
764
+ confidence: best.score / 100,
765
+ estimated_cost: best.estimated_cost,
766
+ alternatives,
767
+ reasoning: this.generateReasoning(task, best)
768
+ };
769
+ }
770
+
771
+ // Auto-select model for task type
772
+ autoSelect(taskType: string, context?: AutoSelectContext): string {
773
+ const mappings: Record<string, string> = {
774
+ // Exploration tasks → Haiku
775
+ 'explore': 'claude-3-5-haiku-20241022',
776
+ 'search': 'claude-3-5-haiku-20241022',
777
+ 'list': 'claude-3-5-haiku-20241022',
778
+ 'quick-fix': 'claude-3-5-haiku-20241022',
779
+
780
+ // Standard tasks → Sonnet
781
+ 'implement': 'claude-sonnet-4-20250514',
782
+ 'refactor': 'claude-sonnet-4-20250514',
783
+ 'test': 'claude-sonnet-4-20250514',
784
+ 'document': 'claude-sonnet-4-20250514',
785
+
786
+ // Complex tasks → Opus
787
+ 'architect': 'claude-opus-4-5-20251101',
788
+ 'review': 'claude-opus-4-5-20251101',
789
+ 'debug-complex': 'claude-opus-4-5-20251101',
790
+ 'security-audit': 'claude-opus-4-5-20251101'
791
+ };
792
+
793
+ let model = mappings[taskType] || 'claude-sonnet-4-20250514';
794
+
795
+ // Check budget constraints
796
+ if (context?.budget) {
797
+ const decision = BudgetManager.canSpend(this.estimateCost(model, context.estimatedTokens || 1000));
798
+ if (!decision.allowed && decision.recommended_action === 'downgrade') {
799
+ model = this.downgrade(model);
800
+ }
801
+ }
802
+
803
+ // Check mode override
804
+ if (context?.mode === 'economy') {
805
+ model = 'claude-3-5-haiku-20241022';
806
+ } else if (context?.mode === 'quality') {
807
+ model = 'claude-opus-4-5-20251101';
808
+ }
809
+
810
+ return model;
811
+ }
812
+
813
+ // Force downgrade to cheaper model
814
+ forceDowngrade(): void {
815
+ this.config.forceEconomy = true;
816
+ EventBus.publish('cost.model.downgraded', {
817
+ reason: 'budget_exceeded',
818
+ new_default: 'claude-3-5-haiku-20241022'
819
+ });
820
+ }
821
+
822
+ // Get cost savings report
823
+ getSavingsReport(options?: ReportOptions): SavingsReport {
824
+ // Analyze historical usage
825
+ const summary = TokenTracker.getSummary(options);
826
+
827
+ // Calculate potential savings with different strategies
828
+ const strategies = [
829
+ this.calculateSavings(summary, 'aggressive'), // All Haiku
830
+ this.calculateSavings(summary, 'balanced'), // Smart routing
831
+ this.calculateSavings(summary, 'quality') // Minimal changes
832
+ ];
833
+
834
+ return {
835
+ current_cost: summary.total_cost.total,
836
+ strategies,
837
+ recommendations: this.generateSavingsRecommendations(summary)
838
+ };
839
+ }
840
+
841
+ // Learn from task results
842
+ recordResult(model: string, task: string, result: TaskResult): void {
843
+ if (!this.history.has(model)) {
844
+ this.history.set(model, []);
845
+ }
846
+ this.history.get(model)!.push(result);
847
+
848
+ // Update model profile based on results
849
+ this.updateProfile(model, result);
850
+ }
851
+
852
+ private rankCandidates(task: TaskAnalysis): RankedCandidate[] {
853
+ const candidates: RankedCandidate[] = [];
854
+
855
+ for (const [_, profile] of this.profiles) {
856
+ const score = this.calculateScore(task, profile);
857
+ const estimated_cost = this.estimateCost(profile.model, task.estimated_tokens);
858
+
859
+ candidates.push({
860
+ model: profile.model,
861
+ tier: profile.tier,
862
+ score,
863
+ estimated_cost
864
+ });
865
+ }
866
+
867
+ // Sort by score (descending)
868
+ return candidates.sort((a, b) => b.score - a.score);
869
+ }
870
+
871
+ private calculateScore(task: TaskAnalysis, profile: ModelProfile): number {
872
+ let score = 50; // Base score
873
+
874
+ // Complexity matching
875
+ if (task.complexity === 'simple' && profile.tier === 'economy') score += 20;
876
+ if (task.complexity === 'medium' && profile.tier === 'standard') score += 20;
877
+ if (task.complexity === 'complex' && profile.tier === 'premium') score += 20;
878
+
879
+ // Priority weighting
880
+ switch (task.priority) {
881
+ case 'speed':
882
+ score += profile.speed_score * 0.3;
883
+ break;
884
+ case 'quality':
885
+ score += profile.quality_score * 0.3;
886
+ break;
887
+ case 'cost':
888
+ score += (100 - profile.cost_per_1k_tokens * 10) * 0.3;
889
+ break;
890
+ }
891
+
892
+ // Requirement bonuses
893
+ if (task.requires_reasoning && profile.strengths.includes('reasoning')) score += 10;
894
+ if (task.requires_creativity && profile.strengths.includes('creativity')) score += 10;
895
+ if (task.requires_accuracy && profile.strengths.includes('accuracy')) score += 10;
896
+
897
+ return Math.min(100, score);
898
+ }
899
+
900
+ private estimateCost(model: string, tokens: number): number {
901
+ const profile = this.profiles.get(model);
902
+ if (!profile) return 0;
903
+ return (tokens / 1000) * profile.cost_per_1k_tokens;
904
+ }
905
+
906
+ private downgrade(model: string): string {
907
+ const downgrades: Record<string, string> = {
908
+ 'claude-opus-4-5-20251101': 'claude-sonnet-4-20250514',
909
+ 'claude-sonnet-4-20250514': 'claude-3-5-haiku-20241022',
910
+ 'claude-3-5-haiku-20241022': 'claude-3-5-haiku-20241022'
911
+ };
912
+ return downgrades[model] || model;
913
+ }
914
+
915
+ private initializeProfiles(): Map<string, ModelProfile> {
916
+ return new Map([
917
+ ['claude-3-5-haiku-20241022', {
918
+ model: 'claude-3-5-haiku-20241022',
919
+ tier: 'economy',
920
+ strengths: ['speed', 'efficiency', 'simple-tasks'],
921
+ weaknesses: ['complex-reasoning', 'nuance'],
922
+ cost_per_1k_tokens: 0.0024,
923
+ quality_score: 70,
924
+ speed_score: 95
925
+ }],
926
+ ['claude-sonnet-4-20250514', {
927
+ model: 'claude-sonnet-4-20250514',
928
+ tier: 'standard',
929
+ strengths: ['balance', 'coding', 'reasoning'],
930
+ weaknesses: ['highest-complexity'],
931
+ cost_per_1k_tokens: 0.009,
932
+ quality_score: 85,
933
+ speed_score: 80
934
+ }],
935
+ ['claude-opus-4-5-20251101', {
936
+ model: 'claude-opus-4-5-20251101',
937
+ tier: 'premium',
938
+ strengths: ['reasoning', 'creativity', 'accuracy', 'complex-tasks'],
939
+ weaknesses: ['cost', 'speed'],
940
+ cost_per_1k_tokens: 0.045,
941
+ quality_score: 98,
942
+ speed_score: 60
943
+ }]
944
+ ]);
945
+ }
946
+
947
+ private getTradeOff(best: RankedCandidate, alt: RankedCandidate): string {
948
+ const costDiff = ((alt.estimated_cost - best.estimated_cost) / best.estimated_cost * 100).toFixed(0);
949
+ if (alt.tier === 'economy') return `${Math.abs(parseInt(costDiff))}% cheaper, less capable`;
950
+ if (alt.tier === 'premium') return `${costDiff}% more expensive, higher quality`;
951
+ return 'Similar capability, different cost';
952
+ }
953
+
954
+ private generateReasoning(task: TaskAnalysis, candidate: RankedCandidate): string {
955
+ return `Selected ${candidate.model} (${candidate.tier}) for ${task.complexity} task. ` +
956
+ `Priority: ${task.priority}. Estimated cost: $${candidate.estimated_cost.toFixed(4)}`;
957
+ }
958
+
959
+ private calculateSavings(summary: UsageSummary, strategy: string): SavingsStrategy {
960
+ // Implementation of savings calculation
961
+ return {
962
+ strategy,
963
+ potential_savings: 0,
964
+ percentage_saved: 0,
965
+ trade_offs: []
966
+ };
967
+ }
968
+
969
+ private generateSavingsRecommendations(summary: UsageSummary): string[] {
970
+ const recommendations: string[] = [];
971
+
972
+ // Check for Opus overuse
973
+ const opusUsage = summary.by_model['claude-opus-4-5-20251101'];
974
+ if (opusUsage && opusUsage.requests > 10) {
975
+ recommendations.push('Consider using Sonnet for standard coding tasks to reduce costs by ~70%');
976
+ }
977
+
978
+ // Check for exploration with expensive models
979
+ const explorerUsage = summary.by_agent['Explore'];
980
+ if (explorerUsage && explorerUsage.cost > 1) {
981
+ recommendations.push('Switch exploration agents to Haiku for ~85% cost reduction');
982
+ }
983
+
984
+ return recommendations;
985
+ }
986
+
987
+ private updateProfile(model: string, result: TaskResult): void {
988
+ // Dynamic profile updates based on actual performance
989
+ }
990
+ }
991
+ ```
992
+
993
+ ---
994
+
995
+ ## Dashboard de Costos
996
+
997
+ ```
998
+ ╔═══════════════════════════════════════════════════════════════════════════╗
999
+ ║ COST DASHBOARD ║
1000
+ ╠═══════════════════════════════════════════════════════════════════════════╣
1001
+ ║ ║
1002
+ ║ SESSION SUMMARY BUDGET STATUS ║
1003
+ ║ ─────────────── ───────────── ║
1004
+ ║ Total Requests: 47 Session: ████████░░ 78% ($7.80/$10) ║
1005
+ ║ Total Tokens: 125,432 Daily: ████░░░░░░ 42% ($21/$50) ║
1006
+ ║ Total Cost: $7.82 Monthly: ██░░░░░░░░ 15% ($78/$500) ║
1007
+ ║ ║
1008
+ ║ COST BY MODEL COST BY AGENT ║
1009
+ ║ ───────────── ───────────── ║
1010
+ ║ Haiku ████░░░░░░ $0.45 (6%) Explore ████░░░░░░ $1.20 ║
1011
+ ║ Sonnet ██████░░░░ $2.87 (37%) Executor ██████████ $4.50 ║
1012
+ ║ Opus ████████░░ $4.50 (57%) Reviewer ████░░░░░░ $1.12 ║
1013
+ ║ QA ██░░░░░░░░ $1.00 ║
1014
+ ║ ║
1015
+ ║ RECENT ACTIVITY ║
1016
+ ║ ─────────────── ║
1017
+ ║ 12:45:32 Opus implement-feature $0.85 ✓ ║
1018
+ ║ 12:44:15 Sonnet review-code $0.32 ✓ ║
1019
+ ║ 12:43:01 Haiku explore-codebase $0.08 ✓ ║
1020
+ ║ 12:42:30 Opus architect-solution $1.20 ✓ ║
1021
+ ║ ║
1022
+ ║ RECOMMENDATIONS ║
1023
+ ║ ─────────────── ║
1024
+ ║ ⚡ Switch exploration to Haiku: Save ~$0.90/session ║
1025
+ ║ 💡 Use Sonnet for standard reviews: Save ~$0.45/review ║
1026
+ ║ ║
1027
+ ╚═══════════════════════════════════════════════════════════════════════════╝
1028
+ ```
1029
+
1030
+ ---
1031
+
1032
+ ## Configuración
1033
+
1034
+ ```json
1035
+ {
1036
+ "costTracking": {
1037
+ "enabled": true,
1038
+ "tokenTracker": {
1039
+ "enabled": true,
1040
+ "persistence": "file",
1041
+ "retentionDays": 90
1042
+ },
1043
+ "budgets": {
1044
+ "session": {
1045
+ "limit": 10.00,
1046
+ "action_on_exceed": "warn"
1047
+ },
1048
+ "daily": {
1049
+ "limit": 50.00,
1050
+ "action_on_exceed": "pause"
1051
+ },
1052
+ "monthly": {
1053
+ "limit": 500.00,
1054
+ "action_on_exceed": "stop"
1055
+ }
1056
+ },
1057
+ "optimizer": {
1058
+ "enabled": true,
1059
+ "autoSelect": true,
1060
+ "mode": "balanced"
1061
+ },
1062
+ "alerts": {
1063
+ "warning": 80,
1064
+ "critical": 95
1065
+ },
1066
+ "reporting": {
1067
+ "dailySummary": true,
1068
+ "weeklyReport": true
1069
+ }
1070
+ }
1071
+ }
1072
+ ```
1073
+
1074
+ ---
1075
+
1076
+ ## Comandos
1077
+
1078
+ ```bash
1079
+ /elsabro:cost # Ver dashboard de costos
1080
+ /elsabro:cost summary # Resumen de sesión
1081
+ /elsabro:cost budget # Estado de presupuestos
1082
+ /elsabro:cost export csv # Exportar datos
1083
+ /elsabro:cost optimize # Recomendaciones de ahorro
1084
+ /elsabro:cost set-budget daily 100 # Configurar presupuesto
1085
+ ```
1086
+
1087
+ ---
1088
+
1089
+ ## Changelog
1090
+
1091
+ - **v3.3.0**: Initial Cost Tracking System
1092
+ - TokenTracker with per-request metering
1093
+ - BudgetManager with alerts
1094
+ - ModelOptimizer for smart model selection
1095
+ - Cost dashboard ASCII