@blockrun/cc 0.8.1 → 0.9.1

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.
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Usage tracking for brcc
3
+ * Records all requests with cost, tokens, and latency for stats display
4
+ */
5
+ export interface UsageRecord {
6
+ timestamp: number;
7
+ model: string;
8
+ inputTokens: number;
9
+ outputTokens: number;
10
+ costUsd: number;
11
+ latencyMs: number;
12
+ fallback?: boolean;
13
+ }
14
+ export interface ModelStats {
15
+ requests: number;
16
+ costUsd: number;
17
+ inputTokens: number;
18
+ outputTokens: number;
19
+ fallbackCount: number;
20
+ avgLatencyMs: number;
21
+ totalLatencyMs: number;
22
+ }
23
+ export interface Stats {
24
+ version: number;
25
+ totalRequests: number;
26
+ totalCostUsd: number;
27
+ totalInputTokens: number;
28
+ totalOutputTokens: number;
29
+ totalFallbacks: number;
30
+ byModel: Record<string, ModelStats>;
31
+ history: UsageRecord[];
32
+ firstRequest?: number;
33
+ lastRequest?: number;
34
+ }
35
+ export declare function loadStats(): Stats;
36
+ export declare function saveStats(stats: Stats): void;
37
+ export declare function clearStats(): void;
38
+ /**
39
+ * Record a completed request for stats tracking
40
+ */
41
+ export declare function recordUsage(model: string, inputTokens: number, outputTokens: number, costUsd: number, latencyMs: number, fallback?: boolean): void;
42
+ /**
43
+ * Get stats summary for display
44
+ */
45
+ export declare function getStatsSummary(): {
46
+ stats: Stats;
47
+ opusCost: number;
48
+ saved: number;
49
+ savedPct: number;
50
+ avgCostPerRequest: number;
51
+ period: string;
52
+ };
@@ -0,0 +1,130 @@
1
+ /**
2
+ * Usage tracking for brcc
3
+ * Records all requests with cost, tokens, and latency for stats display
4
+ */
5
+ import fs from 'node:fs';
6
+ import path from 'node:path';
7
+ import os from 'node:os';
8
+ const STATS_FILE = path.join(os.homedir(), '.blockrun', 'brcc-stats.json');
9
+ const EMPTY_STATS = {
10
+ version: 1,
11
+ totalRequests: 0,
12
+ totalCostUsd: 0,
13
+ totalInputTokens: 0,
14
+ totalOutputTokens: 0,
15
+ totalFallbacks: 0,
16
+ byModel: {},
17
+ history: [],
18
+ };
19
+ export function loadStats() {
20
+ try {
21
+ if (fs.existsSync(STATS_FILE)) {
22
+ const data = JSON.parse(fs.readFileSync(STATS_FILE, 'utf-8'));
23
+ // Migration: add missing fields
24
+ return {
25
+ ...EMPTY_STATS,
26
+ ...data,
27
+ version: 1,
28
+ };
29
+ }
30
+ }
31
+ catch {
32
+ /* ignore parse errors, return empty */
33
+ }
34
+ return { ...EMPTY_STATS };
35
+ }
36
+ export function saveStats(stats) {
37
+ try {
38
+ fs.mkdirSync(path.dirname(STATS_FILE), { recursive: true });
39
+ // Keep only last 1000 history records
40
+ stats.history = stats.history.slice(-1000);
41
+ fs.writeFileSync(STATS_FILE, JSON.stringify(stats, null, 2));
42
+ }
43
+ catch {
44
+ /* ignore write errors */
45
+ }
46
+ }
47
+ export function clearStats() {
48
+ try {
49
+ if (fs.existsSync(STATS_FILE)) {
50
+ fs.unlinkSync(STATS_FILE);
51
+ }
52
+ }
53
+ catch {
54
+ /* ignore */
55
+ }
56
+ }
57
+ /**
58
+ * Record a completed request for stats tracking
59
+ */
60
+ export function recordUsage(model, inputTokens, outputTokens, costUsd, latencyMs, fallback = false) {
61
+ const stats = loadStats();
62
+ const now = Date.now();
63
+ // Update totals
64
+ stats.totalRequests++;
65
+ stats.totalCostUsd += costUsd;
66
+ stats.totalInputTokens += inputTokens;
67
+ stats.totalOutputTokens += outputTokens;
68
+ if (fallback)
69
+ stats.totalFallbacks++;
70
+ // Update timestamps
71
+ if (!stats.firstRequest)
72
+ stats.firstRequest = now;
73
+ stats.lastRequest = now;
74
+ // Update per-model stats
75
+ if (!stats.byModel[model]) {
76
+ stats.byModel[model] = {
77
+ requests: 0,
78
+ costUsd: 0,
79
+ inputTokens: 0,
80
+ outputTokens: 0,
81
+ fallbackCount: 0,
82
+ avgLatencyMs: 0,
83
+ totalLatencyMs: 0,
84
+ };
85
+ }
86
+ const modelStats = stats.byModel[model];
87
+ modelStats.requests++;
88
+ modelStats.costUsd += costUsd;
89
+ modelStats.inputTokens += inputTokens;
90
+ modelStats.outputTokens += outputTokens;
91
+ modelStats.totalLatencyMs += latencyMs;
92
+ modelStats.avgLatencyMs = modelStats.totalLatencyMs / modelStats.requests;
93
+ if (fallback)
94
+ modelStats.fallbackCount++;
95
+ // Add to history
96
+ stats.history.push({
97
+ timestamp: now,
98
+ model,
99
+ inputTokens,
100
+ outputTokens,
101
+ costUsd,
102
+ latencyMs,
103
+ fallback,
104
+ });
105
+ saveStats(stats);
106
+ }
107
+ /**
108
+ * Get stats summary for display
109
+ */
110
+ export function getStatsSummary() {
111
+ const stats = loadStats();
112
+ // Calculate what it would cost with Claude Opus
113
+ const opusCost = (stats.totalInputTokens / 1_000_000) * 5 +
114
+ (stats.totalOutputTokens / 1_000_000) * 25;
115
+ const saved = opusCost - stats.totalCostUsd;
116
+ const savedPct = opusCost > 0 ? (saved / opusCost) * 100 : 0;
117
+ const avgCostPerRequest = stats.totalRequests > 0 ? stats.totalCostUsd / stats.totalRequests : 0;
118
+ // Calculate period
119
+ let period = 'No data';
120
+ if (stats.firstRequest && stats.lastRequest) {
121
+ const days = Math.ceil((stats.lastRequest - stats.firstRequest) / (1000 * 60 * 60 * 24));
122
+ if (days === 0)
123
+ period = 'Today';
124
+ else if (days === 1)
125
+ period = '1 day';
126
+ else
127
+ period = `${days} days`;
128
+ }
129
+ return { stats, opusCost, saved, savedPct, avgCostPerRequest, period };
130
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blockrun/cc",
3
- "version": "0.8.1",
3
+ "version": "0.9.1",
4
4
  "description": "Run Claude Code with any model — no rate limits, no account locks, no phone verification. Pay per use with USDC.",
5
5
  "type": "module",
6
6
  "bin": {