opencode-tokenwatch 0.4.0 → 0.5.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.
package/dist/formatter.js DELETED
@@ -1,289 +0,0 @@
1
- export function formatTokens(n) {
2
- if (n >= 1_000_000_000)
3
- return `${(n / 1_000_000_000).toFixed(1)}B`;
4
- if (n >= 1_000_000)
5
- return `${(n / 1_000_000).toFixed(1)}M`;
6
- if (n >= 1_000)
7
- return `${(n / 1_000).toFixed(1)}K`;
8
- return String(n);
9
- }
10
- export function formatCost(n) {
11
- if (n === 0)
12
- return "$0.00";
13
- if (n < 0.01)
14
- return `$${n.toFixed(4)}`;
15
- return `$${n.toFixed(2)}`;
16
- }
17
- export function formatDuration(ms) {
18
- if (ms === null)
19
- return "—";
20
- if (ms < 1000)
21
- return `${ms.toFixed(0)}ms`;
22
- if (ms < 60000)
23
- return `${(ms / 1000).toFixed(1)}s`;
24
- const m = Math.floor(ms / 60000);
25
- const s = Math.floor((ms % 60000) / 1000);
26
- return `${m}m ${s}s`;
27
- }
28
- export function formatFilters(filters) {
29
- const parts = [];
30
- if (filters.sessionId)
31
- parts.push(`session=${filters.sessionId}`);
32
- if (filters.provider)
33
- parts.push(`provider=${filters.provider}`);
34
- if (filters.model)
35
- parts.push(`model=${filters.model}`);
36
- if (filters.startDate || filters.endDate) {
37
- parts.push(`date=${filters.startDate ?? "..."}..${filters.endDate ?? "..."}`);
38
- }
39
- return parts.length ? parts.join(" | ") : "scope=all local sessions";
40
- }
41
- function getVisualWidth(str) {
42
- let width = 0;
43
- for (let i = 0; i < str.length; i++) {
44
- width += str.charCodeAt(i) > 255 ? 2 : 1;
45
- }
46
- return width;
47
- }
48
- function truncateByWidth(str, maxWidth) {
49
- if (getVisualWidth(str) <= maxWidth)
50
- return str;
51
- let width = 0;
52
- let res = "";
53
- for (let i = 0; i < str.length; i++) {
54
- const charWidth = str.charCodeAt(i) > 255 ? 2 : 1;
55
- if (width + charWidth > maxWidth - 3) {
56
- return res + "...";
57
- }
58
- width += charWidth;
59
- res += str[i];
60
- }
61
- return res;
62
- }
63
- function table(columns, rows, totalRow) {
64
- const widths = columns.map((col, i) => {
65
- const rowWidths = rows.map((row) => getVisualWidth(row[i] ?? ""));
66
- const maxRow = rowWidths.length ? Math.max(...rowWidths) : 0;
67
- const totalWidth = totalRow ? getVisualWidth(totalRow[i] ?? "") : 0;
68
- return Math.max(getVisualWidth(col.label), maxRow, totalWidth);
69
- });
70
- const renderRow = (cells) => "║" + cells.map((cell, i) => {
71
- const value = cell ?? "";
72
- const vWidth = getVisualWidth(value);
73
- const padding = " ".repeat(widths[i] - vWidth);
74
- const content = columns[i].align === "right"
75
- ? padding + value
76
- : value + padding;
77
- return ` ${content} ║`;
78
- }).join("");
79
- const sep = (left, mid, right, fill) => left + widths.map((w) => fill.repeat(w + 2)).join(mid) + right;
80
- const lines = [
81
- sep("╔", "╦", "╗", "═"),
82
- renderRow(columns.map((col) => col.label)),
83
- sep("╠", "╬", "╣", "═"),
84
- ...rows.map(renderRow),
85
- ];
86
- if (totalRow) {
87
- lines.push(sep("╠", "╬", "╣", "═"));
88
- lines.push(renderRow(totalRow));
89
- }
90
- lines.push(sep("╚", "╩", "╝", "═"));
91
- return lines.join("\n");
92
- }
93
- export function formatSessionSummary(data, title = "Current Session") {
94
- const modelLabel = data.modelsUsed.length > 1
95
- ? `${data.modelsUsed.length} models`
96
- : data.model || "(unknown)";
97
- return [
98
- `═══ ${title} ═══`,
99
- `Models: ${modelLabel}`,
100
- `Provider: ${data.provider || "(mixed)"}`,
101
- `Req: ${data.requestCount}`,
102
- `Total Tokens: ${formatTokens(data.totalTokens)}`,
103
- ` Input: ${formatTokens(data.inputTokens)}`,
104
- ` Output: ${formatTokens(data.outputTokens)}`,
105
- ` Reasoning: ${formatTokens(data.reasoningTokens)}`,
106
- ` C.Read: ${formatTokens(data.cacheRead)}`,
107
- ` C.Write: ${formatTokens(data.cacheWrite)}`,
108
- ` Cost: ${formatCost(data.totalCost)}`,
109
- ].join("\n");
110
- }
111
- export function formatStatusBar(data) {
112
- return `Tok:${formatTokens(data.totalTokens)} Req:${data.requestCount} Cost:${formatCost(data.totalCost)}`;
113
- }
114
- export function formatModelBreakdown(items) {
115
- if (items.length === 0)
116
- return "═══ Model Breakdown ═══\n(no data)";
117
- const total = items.reduce((acc, item) => ({
118
- requests: acc.requests + item.requests,
119
- totalTokens: acc.totalTokens + item.totalTokens,
120
- inputTokens: acc.inputTokens + item.inputTokens,
121
- outputTokens: acc.outputTokens + item.outputTokens,
122
- cacheRead: acc.cacheRead + item.cacheRead,
123
- }), {
124
- requests: 0,
125
- totalTokens: 0,
126
- inputTokens: 0,
127
- outputTokens: 0,
128
- cacheRead: 0,
129
- });
130
- const rows = items.map((item) => [
131
- item.provider || "-",
132
- truncateByWidth(item.model, 20),
133
- String(item.requests),
134
- formatTokens(item.totalTokens),
135
- formatTokens(item.inputTokens),
136
- formatTokens(item.outputTokens),
137
- formatTokens(item.cacheRead),
138
- ]);
139
- return [
140
- "═══ Model Breakdown ═══",
141
- table([
142
- { label: "Provider", align: "left" },
143
- { label: "Model", align: "left" },
144
- { label: "Req", align: "right" },
145
- { label: "Total", align: "right" },
146
- { label: "In", align: "right" },
147
- { label: "Out", align: "right" },
148
- { label: "Cache", align: "right" },
149
- ], rows, [
150
- "TOTAL",
151
- "",
152
- String(total.requests),
153
- formatTokens(total.totalTokens),
154
- formatTokens(total.inputTokens),
155
- formatTokens(total.outputTokens),
156
- formatTokens(total.cacheRead),
157
- ]),
158
- ].join("\n");
159
- }
160
- export function formatProviderBreakdown(items) {
161
- if (items.length === 0)
162
- return "═══ Provider Breakdown ═══\n(no data)";
163
- const total = items.reduce((acc, item) => ({
164
- requests: acc.requests + item.requests,
165
- totalTokens: acc.totalTokens + item.totalTokens,
166
- inputTokens: acc.inputTokens + item.inputTokens,
167
- outputTokens: acc.outputTokens + item.outputTokens,
168
- cacheRead: acc.cacheRead + item.cacheRead,
169
- }), {
170
- requests: 0,
171
- totalTokens: 0,
172
- inputTokens: 0,
173
- outputTokens: 0,
174
- cacheRead: 0,
175
- });
176
- const rows = items.map((item) => [
177
- item.provider || "-",
178
- String(item.requests),
179
- formatTokens(item.totalTokens),
180
- formatTokens(item.inputTokens),
181
- formatTokens(item.outputTokens),
182
- formatTokens(item.cacheRead),
183
- ]);
184
- return [
185
- "═══ Provider Breakdown ═══",
186
- table([
187
- { label: "Provider", align: "left" },
188
- { label: "Req", align: "right" },
189
- { label: "Total", align: "right" },
190
- { label: "In", align: "right" },
191
- { label: "Out", align: "right" },
192
- { label: "Cache", align: "right" },
193
- ], rows, [
194
- "TOTAL",
195
- String(total.requests),
196
- formatTokens(total.totalTokens),
197
- formatTokens(total.inputTokens),
198
- formatTokens(total.outputTokens),
199
- formatTokens(total.cacheRead),
200
- ]),
201
- ].join("\n");
202
- }
203
- export function formatDailyBreakdown(items) {
204
- if (items.length === 0)
205
- return "═══ Daily Breakdown ═══\n(no data)";
206
- const total = items.reduce((acc, item) => ({
207
- requests: acc.requests + item.requests,
208
- totalTokens: acc.totalTokens + item.totalTokens,
209
- inputTokens: acc.inputTokens + item.inputTokens,
210
- outputTokens: acc.outputTokens + item.outputTokens,
211
- cacheRead: acc.cacheRead + item.cacheRead,
212
- }), {
213
- requests: 0,
214
- totalTokens: 0,
215
- inputTokens: 0,
216
- outputTokens: 0,
217
- cacheRead: 0,
218
- });
219
- const rows = items.map((item) => [
220
- item.day,
221
- String(item.requests),
222
- formatTokens(item.totalTokens),
223
- formatTokens(item.inputTokens),
224
- formatTokens(item.outputTokens),
225
- formatTokens(item.cacheRead),
226
- ]);
227
- return [
228
- "═══ Daily Breakdown ═══",
229
- table([
230
- { label: "Day", align: "left" },
231
- { label: "Req", align: "right" },
232
- { label: "Total", align: "right" },
233
- { label: "In", align: "right" },
234
- { label: "Out", align: "right" },
235
- { label: "Cache", align: "right" },
236
- ], rows, [
237
- "TOTAL",
238
- String(total.requests),
239
- formatTokens(total.totalTokens),
240
- formatTokens(total.inputTokens),
241
- formatTokens(total.outputTokens),
242
- formatTokens(total.cacheRead),
243
- ]),
244
- ].join("\n");
245
- }
246
- export function formatSessionBreakdown(items) {
247
- if (items.length === 0)
248
- return "═══ Session Breakdown ═══\n(no data)";
249
- const rows = items.map((item) => {
250
- // Truncate title by visual width (40 columns)
251
- const title = truncateByWidth(item.title, 40);
252
- return [
253
- item.day,
254
- item.provider || "-",
255
- truncateByWidth(item.model, 20),
256
- String(item.requests),
257
- formatTokens(item.totalTokens),
258
- formatTokens(item.cacheRead),
259
- title,
260
- ];
261
- });
262
- return [
263
- "═══ Session Breakdown ═══",
264
- table([
265
- { label: "Day", align: "left" },
266
- { label: "Provider", align: "left" },
267
- { label: "Model", align: "left" },
268
- { label: "Req", align: "right" },
269
- { label: "Total", align: "right" },
270
- { label: "Cache", align: "right" },
271
- { label: "Title", align: "left" },
272
- ], rows),
273
- ].join("\n");
274
- }
275
- export function formatUsageReport(report) {
276
- return [
277
- `Filters: ${formatFilters(report.filters)}`,
278
- "",
279
- formatSessionSummary(report.summary, "Usage Summary"),
280
- "",
281
- formatModelBreakdown(report.models),
282
- "",
283
- formatProviderBreakdown(report.providers),
284
- "",
285
- formatDailyBreakdown(report.daily),
286
- "",
287
- formatSessionBreakdown(report.sessions),
288
- ].join("\n");
289
- }