opencode-tokenwatch 0.4.0 → 0.6.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 (42) hide show
  1. package/README.en.md +32 -38
  2. package/README.md +33 -39
  3. package/dist/host/command-actions.d.ts +20 -0
  4. package/dist/host/runtime.d.ts +7 -0
  5. package/dist/host/types.d.ts +148 -0
  6. package/dist/host/v1/adapter.d.ts +9 -0
  7. package/dist/host/v1/commands.d.ts +9 -0
  8. package/dist/{queries.d.ts → host/v1/data-source.d.ts} +1 -6
  9. package/dist/host/v2/adapter.d.ts +63 -0
  10. package/dist/host/v2/commands.d.ts +8 -0
  11. package/dist/host/v2/data-source.d.ts +26 -0
  12. package/dist/kernel/config.d.ts +33 -0
  13. package/dist/{formatter.d.ts → kernel/format.d.ts} +9 -10
  14. package/dist/kernel/model.d.ts +19 -0
  15. package/dist/kernel/perf-aggregate.d.ts +62 -0
  16. package/dist/{perf-tracker.d.ts → kernel/perf.d.ts} +13 -7
  17. package/dist/{generate-usage-html.d.ts → kernel/report-html.d.ts} +1 -1
  18. package/dist/kernel/report.d.ts +19 -0
  19. package/dist/{stats-store.d.ts → kernel/store.d.ts} +5 -5
  20. package/dist/server.d.ts +15 -0
  21. package/dist/server.js +15 -0
  22. package/dist/tui.d.ts +11 -12
  23. package/dist/tui.js +4802 -0
  24. package/dist/ui/sidebar.d.ts +14 -0
  25. package/index.ts +11 -0
  26. package/package.json +39 -12
  27. package/tui.ts +9 -0
  28. package/dist/commands.d.ts +0 -2
  29. package/dist/commands.js +0 -208
  30. package/dist/commands.jsx +0 -381
  31. package/dist/formatter.js +0 -289
  32. package/dist/generate-usage-html.js +0 -962
  33. package/dist/i18n.js +0 -173
  34. package/dist/index.d.ts +0 -5
  35. package/dist/index.js +0 -7
  36. package/dist/perf-tracker.js +0 -299
  37. package/dist/queries.js +0 -393
  38. package/dist/sidebar.d.ts +0 -22
  39. package/dist/sidebar.jsx +0 -604
  40. package/dist/stats-store.js +0 -258
  41. package/dist/tui.jsx +0 -178
  42. /package/dist/{i18n.d.ts → kernel/i18n.d.ts} +0 -0
@@ -0,0 +1,14 @@
1
+ import type { HostAdapter } from "../host/types.js";
2
+ import type { PerfTracker } from "../kernel/perf.js";
3
+ import type { TokenMessage } from "../kernel/model.js";
4
+ interface TokenWatchPanelProps {
5
+ host: HostAdapter;
6
+ perfTracker: PerfTracker;
7
+ perfRevision?: () => number;
8
+ messages: () => readonly any[];
9
+ /** 绑定了当前会话的 part 查询(v2 下避免跨会话扫描) */
10
+ messageParts: (messageID: string) => readonly any[];
11
+ allTokenMessages: () => TokenMessage[];
12
+ }
13
+ export declare function TokenWatchPanel(props: TokenWatchPanelProps): import("solid-js").JSX.Element;
14
+ export {};
package/index.ts ADDED
@@ -0,0 +1,11 @@
1
+ /**
2
+ * 目录标记 + 服务端插件入口。
3
+ *
4
+ * opencode2 有两套本地插件发现:
5
+ * - 服务端(role=server)以 `index.*` 为入口,要求 default 导出为合法插件模块
6
+ * - TUI 侧以 `tui.*` 为入口(见 ./tui.ts)
7
+ *
8
+ * 本文件同时满足两者:作为 v2 目录发现的标记文件,并导出 v1/v2 通用的
9
+ * server 插件对象(v1 不使用本文件,它通过 package.json 的 exports 加载)。
10
+ */
11
+ export { default } from "./dist/server.js"
package/package.json CHANGED
@@ -1,30 +1,39 @@
1
1
  {
2
2
  "name": "opencode-tokenwatch",
3
- "version": "0.4.0",
3
+ "version": "0.6.0",
4
4
  "description": "Real-time token usage, cache analytics & performance dashboard plugin for OpenCode CLI",
5
5
  "type": "module",
6
- "main": "./dist/index.js",
7
- "types": "./dist/index.d.ts",
6
+ "main": "./dist/server.js",
7
+ "types": "./dist/server.d.ts",
8
8
  "exports": {
9
9
  ".": {
10
- "types": "./dist/index.d.ts",
11
- "import": "./dist/index.js"
10
+ "types": "./dist/server.d.ts",
11
+ "import": "./dist/server.js"
12
12
  },
13
13
  "./tui": {
14
14
  "types": "./dist/tui.d.ts",
15
- "import": "./dist/tui.jsx"
15
+ "import": "./dist/tui.js",
16
+ "config": {
17
+ "enabled": true
18
+ }
19
+ },
20
+ "./server": {
21
+ "types": "./dist/server.d.ts",
22
+ "import": "./dist/server.js"
16
23
  },
17
24
  "./package.json": "./package.json"
18
25
  },
19
26
  "files": [
20
- "dist"
27
+ "dist",
28
+ "tui.ts",
29
+ "index.ts"
21
30
  ],
22
31
  "sideEffects": false,
23
32
  "engines": {
24
33
  "node": ">=18"
25
34
  },
26
35
  "scripts": {
27
- "build": "tsc",
36
+ "build": "tsc && node build.tui.mjs",
28
37
  "release:check": "node ./scripts/publish-check.mjs",
29
38
  "prepublishOnly": "npm run build"
30
39
  },
@@ -50,14 +59,32 @@
50
59
  },
51
60
  "homepage": "https://github.com/TTWK/opencode-tokenwatch#readme",
52
61
  "devDependencies": {
53
- "@opencode-ai/plugin": "latest",
54
- "@opentui/core": "^0.2.9",
55
- "@opentui/keymap": "^0.2.9",
56
- "@opentui/solid": "^0.2.9",
62
+ "@opencode-ai/plugin": "1.18.30",
63
+ "@opentui/core": "^0.5.11",
64
+ "@opentui/keymap": "^0.5.11",
65
+ "@opentui/solid": "^0.5.11",
57
66
  "@types/node": "^22.0.0",
67
+ "esbuild": "^0.25.0",
68
+ "esbuild-plugin-solid": "^0.5.0",
58
69
  "typescript": "^5.7.0"
59
70
  },
60
71
  "publishConfig": {
61
72
  "access": "public"
73
+ },
74
+ "peerDependencies": {
75
+ "solid-js": ">=1.9.0",
76
+ "@opentui/core": ">=0.5.0",
77
+ "@opentui/solid": ">=0.5.0"
78
+ },
79
+ "peerDependenciesMeta": {
80
+ "solid-js": {
81
+ "optional": true
82
+ },
83
+ "@opentui/core": {
84
+ "optional": true
85
+ },
86
+ "@opentui/solid": {
87
+ "optional": true
88
+ }
62
89
  }
63
90
  }
package/tui.ts ADDED
@@ -0,0 +1,9 @@
1
+ /**
2
+ * opencode2(v2)入口。
3
+ *
4
+ * v2 以目录形式解析插件时,会把本文件作为入口模块导入并读取 default 导出,
5
+ * 再调用其 `setup(ctx)`。这里直接复用构建产物,保证 v1 / v2 走同一份实现。
6
+ *
7
+ * v1 不使用本文件——它通过 package.json 的 exports["./tui"] 加载 dist/tui.js。
8
+ */
9
+ export { default } from "./dist/tui.js"
@@ -1,2 +0,0 @@
1
- import type { TuiPluginApi } from "@opencode-ai/plugin/tui";
2
- export declare function registerCommands(api: TuiPluginApi): Promise<void>;
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
- }