opencode-tokenwatch 0.3.2 → 0.4.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/LICENSE CHANGED
@@ -1,21 +1,21 @@
1
- MIT License
2
-
3
- Copyright (c) 2026 TTWK
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
1
+ MIT License
2
+
3
+ Copyright (c) 2026 TTWK
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.en.md CHANGED
@@ -1,107 +1,79 @@
1
- # opencode-tokenwatch
2
-
3
- **English** · [简体中文](./README.md)
4
-
5
- ![Sidebar](./assets/sidebar.png)
6
-
7
- Real-time token usage, cache analytics & performance dashboard plugin for OpenCode CLI.
8
-
9
- ## Features
10
-
11
- - **Sidebar panel** — Session-level and per-model real-time stats (requests, tokens, cache, cost)
12
- - **Smart sorting** — Models sorted by most recent call time, current model always on top
13
- - **Provider name truncation** — Long provider names auto-truncated (12 chars) to prevent layout overflow
14
- - **Cache hit rate** — Inline color progress bars with trend indicators (↑/↓) and global weighted hit rate
15
- - **Performance metrics** — TTFT / TPS / End-to-end latency + P50/P95/P99 latency percentiles
16
- - **Token distribution** — 5-bucket role breakdown (system / user / toolCall / toolResult / output + other fallback)
17
- - **Error rate tracking** — Detects failed requests (empty token response) and computes real-time error rate
18
- - **Invalid data filtering** — Full-chain defense: filters zero-token entries from perf-tracker, sidebar, and HTML reports
19
- - **Cost display** — Per-model cost (requires provider billing data)
20
- - **`/usage` command** — HTML Report JSON Export Text Report → Settings
21
- - **HTML report** — Interactive ECharts dashboard: token distribution, performance percentiles, TPS horizontal ranking, error rate analysis — auto-opened in browser
22
- - **Persistent stats** — Performance metrics (TPS/TTFT/latency) written to a dedicated JSON file that accumulates forever, unaffected by log rotation
23
- - **Multi-level collapse** — Panel, models, and sub-blocks collapsible with persisted state
24
- - **Language switching** — Auto-detect or manually switch between Chinese and English
25
-
26
- ## Install
27
-
28
- ```sh
29
- npm install opencode-tokenwatch
30
- ```
31
-
32
- Add to `opencode.json` or `opencode.jsonc`:
33
-
34
- ```json
35
- {
36
- "$schema": "https://opencode.ai/config.json",
37
- "plugin": ["opencode-tokenwatch"]
38
- }
39
- ```
40
-
41
- ## Configuration
42
-
43
- ```jsonc
44
- {
45
- "plugin": ["opencode-tokenwatch"],
46
- "pluginConfig": {
47
- "opencode-tokenwatch": {
48
- "sidebar": {
49
- "showPerformance": true,
50
- "showPricing": true,
51
- "showTokenDistribution": true,
52
- "showTrend": true
53
- },
54
- "language": "auto"
55
- }
56
- }
57
- }
58
- ```
59
-
60
- | Option | Type | Default | Description |
61
- |--------|------|---------|-------------|
62
- | `sidebar.showPerformance` | boolean | `true` | Show TTFT/TPS/latency |
63
- | `sidebar.showPricing` | boolean | `true` | Show request cost |
64
- | `sidebar.showTokenDistribution` | boolean | `true` | Show token distribution |
65
- | `sidebar.showTrend` | boolean | `true` | Show trend indicator |
66
- | `language` | `"auto"` / `"zh"` / `"en"` | `"auto"` | UI language |
67
-
68
- Settings can also be toggled via `/usage` → Settings, taking precedence over `pluginConfig`.
69
-
70
- ## Usage
71
-
72
- In OpenCode TUI, run `/usage`:
73
-
74
- - **HTML Report** Pick a date range, generates a dashboard and opens it in browser
75
- - **JSON Export** Exports full usage data to `~/.opencode/reports/`
76
- - **Text Report** — Exports Markdown report to `~/.opencode/reports/`
77
- - **Settings** — Toggle sidebar blocks, switch language
78
-
79
- ## Data Files
80
-
81
- | File | Path | Description |
82
- |------|------|-------------|
83
- | JSONL log | `~/.opencode/tokenwatch.jsonl` | Raw per-request log, auto-rotated at 5 MB |
84
- | Aggregated stats | `~/.opencode/tokenwatch-stats.json` | Persistent performance stats, accumulates forever |
85
- | Report output | `~/.opencode/reports/` | HTML / JSON / Markdown reports |
86
-
87
- ## Requirements
88
-
89
- - OpenCode CLI (with `opencode db` command)
90
- - Node.js 18+
91
-
92
- ## Build
93
-
94
- ```sh
95
- npm install
96
- npm run build
97
- ```
98
-
99
- ## Related
100
-
101
- - [opencode-throughput](https://github.com/Howardzhangdqs/opencode-throughput) — Real-time LLM performance monitoring (TTFT/TPS/latency/cost)
102
- - [opencode-visual-cache](https://github.com/Hotakus/opencode-visual-cache) — TUI sidebar cache hit rate visualization, token distribution analysis
103
- - [magic-context](https://github.com/cortexkit/magic-context/) — Cache-aware infinite context + cross-session memory system
104
-
105
- ## License
106
-
107
- MIT
1
+ # opencode-tokenwatch
2
+
3
+ **English** · [简体中文](./README.md)
4
+
5
+ ![Sidebar](./assets/sidebar.png)
6
+
7
+ Real-time token usage, cache analytics & performance dashboard plugin for OpenCode CLI.
8
+
9
+ ## Features
10
+
11
+ - **Sidebar panel** — Session-level and per-model real-time stats (requests, tokens, cache, cost)
12
+ - **Cache hit rate** — Per-model tracking with trend indicators (↑/↓) and global weighted total
13
+ - **Performance metrics** — TTFT / TPS / End-to-end latency + P50/P95/P99 latency percentiles
14
+ - **Token distribution** — Per-role breakdown (system / user / tool / output)
15
+ - **Cost tracking** — Per-model cost display (requires provider billing data)
16
+ - **Error rate tracking** — Detects failed requests and computes real-time error rate
17
+ - **`/usage` command** — HTML Report JSON Export Text Report Settings
18
+ - **HTML report** — Interactive ECharts dashboard: token trends, performance comparison, TPS ranking, error rate analysis — auto-opened in browser
19
+ - **Persistent stats** — Performance metrics accumulate forever in a dedicated file, unaffected by session resets
20
+ - **Multi-level collapse** — Panel, models, and sub-blocks collapsible with persisted state
21
+ - **Language switching** — Auto-detect or manually switch between Chinese and English
22
+
23
+ ## Install
24
+
25
+ ```sh
26
+ npm install opencode-tokenwatch
27
+ ```
28
+
29
+ Add to `opencode.json` or `opencode.jsonc`:
30
+
31
+ ```json
32
+ {
33
+ "$schema": "https://opencode.ai/config.json",
34
+ "plugin": ["opencode-tokenwatch"]
35
+ }
36
+ ```
37
+
38
+ ## Configuration
39
+
40
+ In OpenCode TUI, run `/usage` → **Settings** to interactively toggle display items and switch language. Settings are persisted automatically — no need to edit config files manually.
41
+
42
+ ## Usage
43
+
44
+ In OpenCode TUI, run `/usage`:
45
+
46
+ - **HTML Report** — Pick a date range, generates a dashboard and opens it in browser
47
+ - **JSON Export** — Exports full usage data to `~/.opencode/reports/`
48
+ - **Text Report** — Exports Markdown report to `~/.opencode/reports/`
49
+ - **Settings** — Toggle sidebar blocks, switch language
50
+
51
+ ## Data Files
52
+
53
+ | File | Path | Description |
54
+ |------|------|-------------|
55
+ | JSONL log | `~/.opencode/tokenwatch.jsonl` | Raw per-request log |
56
+ | Aggregated stats | `~/.opencode/tokenwatch-stats.json` | Persistent performance stats |
57
+ | Report output | `~/.opencode/reports/` | HTML / JSON / Markdown reports |
58
+
59
+ ## Requirements
60
+
61
+ - OpenCode CLI (with `opencode db` command)
62
+ - Node.js 18+
63
+
64
+ ## Build
65
+
66
+ ```sh
67
+ npm install
68
+ npm run build
69
+ ```
70
+
71
+ ## Related
72
+
73
+ - [opencode-throughput](https://github.com/Howardzhangdqs/opencode-throughput) — Real-time LLM performance monitoring (TTFT/TPS/latency/cost)
74
+ - [opencode-visual-cache](https://github.com/Hotakus/opencode-visual-cache)TUI sidebar cache hit rate visualization, token distribution analysis
75
+ - [magic-context](https://github.com/cortexkit/magic-context/)Cache-aware infinite context + cross-session memory system
76
+
77
+ ## License
78
+
79
+ MIT
package/README.md CHANGED
@@ -1,107 +1,79 @@
1
- # opencode-tokenwatch
2
-
3
- [English](./README.en.md) · **简体中文**
4
-
5
- ![Sidebar](./assets/sidebar.png)
6
-
7
- OpenCode CLI 的实时 Token 用量统计、缓存分析与性能指标插件。
8
-
9
- ## 功能
10
-
11
- - **侧边栏面板** — 会话级与按模型的实时统计(请求数、Token、缓存、成本)
12
- - **智能排序**模型按最近调用时间降序排列,当前焦点模型始终在顶部
13
- - **供应商名称截断**超长供应商名自动截断(12 字符),避免撑破布局
14
- - **缓存命中率**模型行内彩色进度条,带趋势指示器(↑/↓),全局总计行显示**加权命中率**
15
- - **性能指标**TTFT / TPS / 端到端延迟 + P50/P95/P99 延迟分位数
16
- - **Token 分布** 5 桶角色分解(system / user / toolCall / toolResult / output + other 兜底)
17
- - **错误率统计**识别并统计失败请求(空 Token 响应),实时计算错误率
18
- - **无效数据过滤**全链路防御:从 perf-tracker sidebar、HTML 报告均过滤全零 token 无效请求
19
- - **成本展示**按模型显示 cost(需 provider 返回计费数据)
20
- - **`/usage` 命令** HTML 报告 → JSON 导出 → 文本报告 → 设置
21
- - **HTML 报告** 交互式 ECharts 仪表盘:Token 分布、性能分位数、TPS 水平排名、错误率分析,自动在浏览器打开
22
- - **持久化统计** — 性能指标(TPS/TTFT/延迟)写入独立 JSON 文件,永久累积,不受日志轮转影响
23
- - **多级折叠** — 面板、模型、子区块均可折叠,状态持久化
24
- - **语言切换** — 中英双语,跟随系统或手动切换
25
-
26
- ## 安装
27
-
28
- ```sh
29
- npm install opencode-tokenwatch
30
- ```
31
-
32
- 在 `opencode.json` 或 `opencode.jsonc` 中添加:
33
-
34
- ```json
35
- {
36
- "$schema": "https://opencode.ai/config.json",
37
- "plugin": ["opencode-tokenwatch"]
38
- }
39
- ```
40
-
41
- ## 配置
42
-
43
- ```jsonc
44
- {
45
- "plugin": ["opencode-tokenwatch"],
46
- "pluginConfig": {
47
- "opencode-tokenwatch": {
48
- "sidebar": {
49
- "showPerformance": true,
50
- "showPricing": true,
51
- "showTokenDistribution": true,
52
- "showTrend": true
53
- },
54
- "language": "auto"
55
- }
56
- }
57
- }
58
- ```
59
-
60
- | 配置项 | 类型 | 默认值 | 说明 |
61
- |--------|------|--------|------|
62
- | `sidebar.showPerformance` | boolean | `true` | 显示 TTFT/TPS/延迟 |
63
- | `sidebar.showPricing` | boolean | `true` | 显示请求成本 |
64
- | `sidebar.showTokenDistribution` | boolean | `true` | 显示 Token 分布 |
65
- | `sidebar.showTrend` | boolean | `true` | 显示趋势指示器 |
66
- | `language` | `"auto"` / `"zh"` / `"en"` | `"auto"` | 界面语言 |
67
-
68
- 运行时也可通过 `/usage` → 设置 调整,优先级高于 `pluginConfig`。
69
-
70
- ## 用法
71
-
72
- 在 OpenCode TUI 中输入 `/usage`,选择:
73
-
74
- - **HTML 报告** 选择日期范围,生成仪表盘并在浏览器打开
75
- - **JSON 导出** 导出完整用量数据至 `~/.opencode/reports/`
76
- - **文本报告** — 导出 Markdown 格式至 `~/.opencode/reports/`
77
- - **设置** — 开关侧边栏显示项、切换语言
78
-
79
- ## 数据文件
80
-
81
- | 文件 | 路径 | 说明 |
82
- |------|------|------|
83
- | JSONL 日志 | `~/.opencode/tokenwatch.jsonl` | 原始请求日志,超 5MB 自动轮转 |
84
- | 聚合统计 | `~/.opencode/tokenwatch-stats.json` | 持久化性能统计,永久累积 |
85
- | 报告输出 | `~/.opencode/reports/` | HTML / JSON / Markdown 报告 |
86
-
87
- ## 系统要求
88
-
89
- - OpenCode CLI(支持 `opencode db` 命令)
90
- - Node.js 18+
91
-
92
- ## 构建
93
-
94
- ```sh
95
- npm install
96
- npm run build
97
- ```
98
-
99
- ## 相关项目
100
-
101
- - [opencode-throughput](https://github.com/Howardzhangdqs/opencode-throughput) — 实时 LLM 性能监控,采集 TTFT/TPS/延迟和成本
102
- - [opencode-visual-cache](https://github.com/Hotakus/opencode-visual-cache) — TUI 侧边栏缓存命中率可视化,Token 分布分析
103
- - [magic-context](https://github.com/cortexkit/magic-context/) — 缓存感知的无限上下文 + 跨会话记忆系统
104
-
105
- ## 许可
106
-
107
- MIT
1
+ # opencode-tokenwatch
2
+
3
+ [English](./README.en.md) · **简体中文**
4
+
5
+ ![Sidebar](./assets/sidebar.png)
6
+
7
+ OpenCode CLI 的实时 Token 用量统计、缓存分析与性能指标插件。
8
+
9
+ ## 功能
10
+
11
+ - **侧边栏面板** — 会话级与按模型的实时统计(请求数、Token、缓存、成本)
12
+ - **缓存命中率**按模型实时追踪,带趋势指示器(↑/↓)与全局加权总计
13
+ - **性能指标**TTFT / TPS / 端到端延迟,含 P50/P95/P99 延迟分位数
14
+ - **Token 分布** 按角色(system / user / tool / output)分解用量占比
15
+ - **成本追踪**按模型实时显示请求成本(需 provider 返回计费数据)
16
+ - **错误率统计**识别并统计失败请求,实时计算错误率
17
+ - **`/usage` 命令** HTML 报告 → JSON 导出 → 文本报告 → 设置
18
+ - **HTML 报告** 交互式 ECharts 仪表盘:Token 趋势、性能对比、TPS 排名、错误率分析,自动在浏览器打开
19
+ - **持久化统计**性能指标永久累积写入独立文件,历史数据不受会话重置影响
20
+ - **多级折叠**面板、模型、子区块均可折叠,状态持久化
21
+ - **语言切换**中英双语,跟随系统或手动切换
22
+
23
+ ## 安装
24
+
25
+ ```sh
26
+ npm install opencode-tokenwatch
27
+ ```
28
+
29
+ `opencode.json` 或 `opencode.jsonc` 中添加:
30
+
31
+ ```json
32
+ {
33
+ "$schema": "https://opencode.ai/config.json",
34
+ "plugin": ["opencode-tokenwatch"]
35
+ }
36
+ ```
37
+
38
+ ## 配置
39
+
40
+ 在 OpenCode TUI 中输入 `/usage` → **设置**,可交互式开关各显示项和切换界面语言,配置自动持久化,无需手动编辑配置文件。
41
+
42
+ ## 用法
43
+
44
+ 在 OpenCode TUI 中输入 `/usage`,选择:
45
+
46
+ - **HTML 报告** — 选择日期范围,生成仪表盘并在浏览器打开
47
+ - **JSON 导出** — 导出完整用量数据至 `~/.opencode/reports/`
48
+ - **文本报告** — 导出 Markdown 格式至 `~/.opencode/reports/`
49
+ - **设置** — 开关侧边栏显示项、切换语言
50
+
51
+ ## 数据文件
52
+
53
+ | 文件 | 路径 | 说明 |
54
+ |------|------|------|
55
+ | JSONL 日志 | `~/.opencode/tokenwatch.jsonl` | 原始请求日志 |
56
+ | 聚合统计 | `~/.opencode/tokenwatch-stats.json` | 持久化性能统计 |
57
+ | 报告输出 | `~/.opencode/reports/` | HTML / JSON / Markdown 报告 |
58
+
59
+ ## 系统要求
60
+
61
+ - OpenCode CLI(支持 `opencode db` 命令)
62
+ - Node.js 18+
63
+
64
+ ## 构建
65
+
66
+ ```sh
67
+ npm install
68
+ npm run build
69
+ ```
70
+
71
+ ## 相关项目
72
+
73
+ - [opencode-throughput](https://github.com/Howardzhangdqs/opencode-throughput) — 实时 LLM 性能监控,采集 TTFT/TPS/延迟和成本
74
+ - [opencode-visual-cache](https://github.com/Hotakus/opencode-visual-cache)TUI 侧边栏缓存命中率可视化,Token 分布分析
75
+ - [magic-context](https://github.com/cortexkit/magic-context/)缓存感知的无限上下文 + 跨会话记忆系统
76
+
77
+ ## 许可
78
+
79
+ MIT
@@ -0,0 +1,208 @@
1
+ import { getUsageReport } from "./queries.js";
2
+ import { formatUsageReport } from "./formatter.js";
3
+ import { generateUsageHtml } from "./generate-usage-html.js";
4
+ import { t } from "./i18n.js";
5
+ import { readLogs } from "./perf-tracker.js";
6
+ import { existsSync, mkdirSync, writeFileSync } from "node:fs";
7
+ import { join } from "node:path";
8
+ import { homedir } from "node:os";
9
+ import { execSync } from "node:child_process";
10
+ const DEFAULT_CONFIG = {
11
+ sidebar: { showPerformance: true, showPricing: true, showTokenDistribution: true, showTrend: true },
12
+ language: "auto",
13
+ };
14
+ export async function registerCommands(api) {
15
+ api.command?.register(() => [
16
+ {
17
+ value: "tokenwatch-html-report",
18
+ title: "Generate HTML report",
19
+ description: "Generate an HTML dashboard with token usage, cache efficiency, and performance charts",
20
+ category: "Stats",
21
+ slash: { name: "usage-html", aliases: ["usage"] },
22
+ onSelect: async () => {
23
+ await showHtmlReport(api);
24
+ },
25
+ },
26
+ {
27
+ value: "tokenwatch-json-export",
28
+ title: "Export as JSON",
29
+ description: "Export usage data as JSON file",
30
+ category: "Stats",
31
+ slash: { name: "usage-json" },
32
+ onSelect: async () => {
33
+ await showJsonExport(api);
34
+ },
35
+ },
36
+ {
37
+ value: "tokenwatch-text-report",
38
+ title: "Text report (legacy)",
39
+ description: "View plain text usage report in terminal",
40
+ category: "Stats",
41
+ slash: { name: "usage-text" },
42
+ onSelect: async () => {
43
+ await showTextReport(api);
44
+ },
45
+ },
46
+ {
47
+ value: "tokenwatch-settings",
48
+ title: "TokenWatch Settings",
49
+ description: "Configure sidebar display options",
50
+ category: "Stats",
51
+ slash: { name: "usage-settings", aliases: ["tokenwatch-settings"] },
52
+ onSelect: async () => {
53
+ await showSettingsDialog(api);
54
+ },
55
+ },
56
+ ]);
57
+ }
58
+ function ensureReportDir() {
59
+ const dir = join(homedir(), ".opencode", "reports");
60
+ if (!existsSync(dir))
61
+ mkdirSync(dir, { recursive: true });
62
+ return dir;
63
+ }
64
+ function openInBrowser(filePath) {
65
+ try {
66
+ const platform = process.platform;
67
+ if (platform === "win32")
68
+ execSync(`start "" "${filePath}"`, { windowsHide: true, timeout: 5000 });
69
+ else if (platform === "darwin")
70
+ execSync(`open "${filePath}"`, { timeout: 5000 });
71
+ else
72
+ execSync(`xdg-open "${filePath}"`, { timeout: 5000 });
73
+ }
74
+ catch { /* silently fail */ }
75
+ }
76
+ function aggregatePerfStats(logs) {
77
+ const map = new Map();
78
+ for (const entry of logs) {
79
+ const key = entry.model;
80
+ let s = map.get(key);
81
+ if (!s) {
82
+ s = {
83
+ model: key,
84
+ providerID: entry.providerID,
85
+ requestCount: 0,
86
+ totalInput: 0, totalOutput: 0, totalCacheRead: 0, totalCacheWrite: 0, totalCost: 0,
87
+ avgTTFT: null, maxTTFT: null, minTTFT: null,
88
+ avgTPS: null, maxTPS: null, minTPS: null,
89
+ avgLatency: null, maxLatency: null, minLatency: null,
90
+ };
91
+ map.set(key, s);
92
+ }
93
+ s.requestCount++;
94
+ s.totalInput += entry.inputTokens;
95
+ s.totalOutput += entry.outputTokens;
96
+ s.totalCacheRead += entry.cacheReadTokens;
97
+ s.totalCacheWrite += entry.cacheWriteTokens;
98
+ s.totalCost += entry.cost;
99
+ const c = s.requestCount;
100
+ if (entry.ttft_ms != null) {
101
+ s.avgTTFT = s.avgTTFT != null ? s.avgTTFT + (entry.ttft_ms - s.avgTTFT) / c : entry.ttft_ms;
102
+ s.maxTTFT = s.maxTTFT != null ? Math.max(s.maxTTFT, entry.ttft_ms) : entry.ttft_ms;
103
+ s.minTTFT = s.minTTFT != null ? Math.min(s.minTTFT, entry.ttft_ms) : entry.ttft_ms;
104
+ }
105
+ if (entry.tps != null) {
106
+ s.avgTPS = s.avgTPS != null ? s.avgTPS + (entry.tps - s.avgTPS) / c : entry.tps;
107
+ s.maxTPS = s.maxTPS != null ? Math.max(s.maxTPS, entry.tps) : entry.tps;
108
+ s.minTPS = s.minTPS != null ? Math.min(s.minTPS, entry.tps) : entry.tps;
109
+ }
110
+ if (entry.latency_ms != null) {
111
+ s.avgLatency = s.avgLatency != null ? s.avgLatency + (entry.latency_ms - s.avgLatency) / c : entry.latency_ms;
112
+ s.maxLatency = s.maxLatency != null ? Math.max(s.maxLatency, entry.latency_ms) : entry.latency_ms;
113
+ s.minLatency = s.minLatency != null ? Math.min(s.minLatency, entry.latency_ms) : entry.latency_ms;
114
+ }
115
+ }
116
+ return Array.from(map.values());
117
+ }
118
+ async function buildCombinedData(api) {
119
+ const report = await getUsageReport({});
120
+ const logs = readLogs(1000);
121
+ const now = new Date();
122
+ const pad = (n) => String(n).padStart(2, '0');
123
+ const meta = {
124
+ generatedAt: `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())} ${pad(now.getHours())}:${pad(now.getMinutes())}:${pad(now.getSeconds())}`,
125
+ dateRange: {
126
+ start: report.daily.length > 0 ? report.daily[report.daily.length - 1].day : "—",
127
+ end: report.daily.length > 0 ? report.daily[0].day : "—",
128
+ },
129
+ };
130
+ return {
131
+ ...report,
132
+ perfLogs: logs,
133
+ perfSummary: aggregatePerfStats(logs),
134
+ meta,
135
+ };
136
+ }
137
+ async function showHtmlReport(api) {
138
+ try {
139
+ const data = await buildCombinedData(api);
140
+ const html = generateUsageHtml(data);
141
+ const dir = ensureReportDir();
142
+ const dateStr = new Date().toISOString().slice(0, 10);
143
+ const filePath = join(dir, `tokenwatch-${dateStr}.html`);
144
+ writeFileSync(filePath, html, "utf-8");
145
+ api.ui.toast?.({ message: `Report: ${filePath}`, variant: "info" });
146
+ openInBrowser(filePath);
147
+ }
148
+ catch (err) {
149
+ const msg = err instanceof Error ? err.message : String(err);
150
+ api.ui.toast?.({ message: `Error: ${msg}`, variant: "error" });
151
+ }
152
+ }
153
+ async function showJsonExport(api) {
154
+ try {
155
+ const report = await getUsageReport({});
156
+ const dir = ensureReportDir();
157
+ const dateStr = new Date().toISOString().slice(0, 10);
158
+ const filePath = join(dir, `tokenwatch-${dateStr}.json`);
159
+ writeFileSync(filePath, JSON.stringify(report, null, 2), "utf-8");
160
+ api.ui.toast?.({ message: `JSON: ${filePath}`, variant: "info" });
161
+ }
162
+ catch (err) {
163
+ const msg = err instanceof Error ? err.message : String(err);
164
+ api.ui.toast?.({ message: `Error: ${msg}`, variant: "error" });
165
+ }
166
+ }
167
+ async function showTextReport(api) {
168
+ try {
169
+ const report = await getUsageReport({});
170
+ const formatted = formatUsageReport(report);
171
+ const dir = ensureReportDir();
172
+ const dateStr = new Date().toISOString().slice(0, 10);
173
+ const filePath = join(dir, `tokenwatch-${dateStr}.md`);
174
+ writeFileSync(filePath, formatted, "utf-8");
175
+ api.ui.toast?.({ message: `Report saved to ${filePath}`, variant: "info" });
176
+ }
177
+ catch (err) {
178
+ const msg = err instanceof Error ? err.message : String(err);
179
+ api.ui.toast?.({ message: `Error: ${msg}`, variant: "error" });
180
+ }
181
+ }
182
+ async function showSettingsDialog(api) {
183
+ const currentConfig = loadConfigFromStore(api);
184
+ const cfg = currentConfig.sidebar;
185
+ const options = [
186
+ `[${cfg.showPerformance ? "x" : " "}] ${t("showPerformance")}`,
187
+ `[${cfg.showPricing ? "x" : " "}] ${t("showPricing")}`,
188
+ `[${cfg.showTokenDistribution ? "x" : " "}] ${t("showTokenDistribution")}`,
189
+ `[${cfg.showTrend ? "x" : " "}] ${t("showTrend")}`,
190
+ `---`,
191
+ `${t("language")}: ${currentConfig.language}`,
192
+ ].join("\n");
193
+ api.ui.toast?.({ message: `TokenWatch settings:\n${options}`, variant: "info" });
194
+ }
195
+ function loadConfigFromStore(api) {
196
+ const base = { sidebar: { ...DEFAULT_CONFIG.sidebar }, language: DEFAULT_CONFIG.language };
197
+ try {
198
+ const stored = api.kv?.get?.("tokenwatch-config");
199
+ if (stored) {
200
+ if (stored.sidebar)
201
+ Object.assign(base.sidebar, stored.sidebar);
202
+ if (stored.language)
203
+ base.language = stored.language;
204
+ }
205
+ }
206
+ catch { /* defaults */ }
207
+ return base;
208
+ }