opencode-tokenwatch 0.3.1 → 0.3.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/package.json CHANGED
@@ -1,63 +1,63 @@
1
- {
2
- "name": "opencode-tokenwatch",
3
- "version": "0.3.1",
4
- "description": "Real-time token usage, cache analytics & performance dashboard plugin for OpenCode CLI",
5
- "type": "module",
6
- "main": "./dist/index.js",
7
- "types": "./dist/index.d.ts",
8
- "exports": {
9
- ".": {
10
- "types": "./dist/index.d.ts",
11
- "import": "./dist/index.js"
12
- },
13
- "./tui": {
14
- "types": "./dist/tui.d.ts",
15
- "import": "./dist/tui.jsx"
16
- },
17
- "./package.json": "./package.json"
18
- },
19
- "files": [
20
- "dist"
21
- ],
22
- "sideEffects": false,
23
- "engines": {
24
- "node": ">=18"
25
- },
26
- "scripts": {
27
- "build": "tsc",
28
- "release:check": "node ./scripts/publish-check.mjs",
29
- "prepublishOnly": "npm run build"
30
- },
31
- "keywords": [
32
- "opencode",
33
- "plugin",
34
- "opencode-plugin",
35
- "tokens",
36
- "usage",
37
- "stats",
38
- "sqlite",
39
- "analytics",
40
- "tui"
41
- ],
42
- "license": "MIT",
43
- "author": "TTWK",
44
- "repository": {
45
- "type": "git",
46
- "url": "git+https://github.com/TTWK/opencode-tokenwatch.git"
47
- },
48
- "bugs": {
49
- "url": "https://github.com/TTWK/opencode-tokenwatch/issues"
50
- },
51
- "homepage": "https://github.com/TTWK/opencode-tokenwatch#readme",
52
- "devDependencies": {
53
- "@opencode-ai/plugin": "latest",
54
- "@opentui/core": "^0.2.9",
55
- "@opentui/keymap": "^0.2.9",
56
- "@opentui/solid": "^0.2.9",
57
- "@types/node": "^22.0.0",
58
- "typescript": "^5.7.0"
59
- },
60
- "publishConfig": {
61
- "access": "public"
62
- }
63
- }
1
+ {
2
+ "name": "opencode-tokenwatch",
3
+ "version": "0.3.2",
4
+ "description": "Real-time token usage, cache analytics & performance dashboard plugin for OpenCode CLI",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js"
12
+ },
13
+ "./tui": {
14
+ "types": "./dist/tui.d.ts",
15
+ "import": "./dist/tui.jsx"
16
+ },
17
+ "./package.json": "./package.json"
18
+ },
19
+ "files": [
20
+ "dist"
21
+ ],
22
+ "sideEffects": false,
23
+ "engines": {
24
+ "node": ">=18"
25
+ },
26
+ "scripts": {
27
+ "build": "tsc",
28
+ "release:check": "node ./scripts/publish-check.mjs",
29
+ "prepublishOnly": "npm run build"
30
+ },
31
+ "keywords": [
32
+ "opencode",
33
+ "plugin",
34
+ "opencode-plugin",
35
+ "tokens",
36
+ "usage",
37
+ "stats",
38
+ "sqlite",
39
+ "analytics",
40
+ "tui"
41
+ ],
42
+ "license": "MIT",
43
+ "author": "TTWK",
44
+ "repository": {
45
+ "type": "git",
46
+ "url": "git+https://github.com/TTWK/opencode-tokenwatch.git"
47
+ },
48
+ "bugs": {
49
+ "url": "https://github.com/TTWK/opencode-tokenwatch/issues"
50
+ },
51
+ "homepage": "https://github.com/TTWK/opencode-tokenwatch#readme",
52
+ "devDependencies": {
53
+ "@opencode-ai/plugin": "latest",
54
+ "@opentui/core": "^0.2.9",
55
+ "@opentui/keymap": "^0.2.9",
56
+ "@opentui/solid": "^0.2.9",
57
+ "@types/node": "^22.0.0",
58
+ "typescript": "^5.7.0"
59
+ },
60
+ "publishConfig": {
61
+ "access": "public"
62
+ }
63
+ }
package/dist/commands.js DELETED
@@ -1,208 +0,0 @@
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
- }