@hashimkarim/usagestat-linux-x64-gnu 2.0.0-alpha.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.
Files changed (225) hide show
  1. package/LICENSE +57744 -0
  2. package/native-manifest.json +1414 -0
  3. package/package.json +254 -0
  4. package/plugins/abacus-ai/icon.svg +18 -0
  5. package/plugins/abacus-ai/plugin.js +121 -0
  6. package/plugins/abacus-ai/plugin.json +17 -0
  7. package/plugins/alibaba/icon-color.svg +1 -0
  8. package/plugins/alibaba/icon.svg +1 -0
  9. package/plugins/alibaba/plugin.js +322 -0
  10. package/plugins/alibaba/plugin.json +20 -0
  11. package/plugins/alibaba-token-plan/icon-color.svg +1 -0
  12. package/plugins/alibaba-token-plan/icon.svg +1 -0
  13. package/plugins/alibaba-token-plan/plugin.js +544 -0
  14. package/plugins/alibaba-token-plan/plugin.json +19 -0
  15. package/plugins/amp/icon-color.svg +1 -0
  16. package/plugins/amp/icon-conflict.svg +6 -0
  17. package/plugins/amp/icon.svg +1 -0
  18. package/plugins/amp/plugin.js +175 -0
  19. package/plugins/amp/plugin.json +16 -0
  20. package/plugins/antigravity/icon-color.svg +1 -0
  21. package/plugins/antigravity/icon-conflict.svg +3 -0
  22. package/plugins/antigravity/icon.svg +1 -0
  23. package/plugins/antigravity/plugin.js +739 -0
  24. package/plugins/antigravity/plugin.json +31 -0
  25. package/plugins/antigravity-cli/icon.svg +3 -0
  26. package/plugins/antigravity-cli/plugin.js +570 -0
  27. package/plugins/antigravity-cli/plugin.json +16 -0
  28. package/plugins/antigravity-ide/icon.svg +3 -0
  29. package/plugins/antigravity-ide/plugin.js +257 -0
  30. package/plugins/antigravity-ide/plugin.json +29 -0
  31. package/plugins/augment/icon.svg +14 -0
  32. package/plugins/augment/plugin.js +59 -0
  33. package/plugins/augment/plugin.json +18 -0
  34. package/plugins/aws-bedrock/icon-color.svg +1 -0
  35. package/plugins/aws-bedrock/icon.svg +1 -0
  36. package/plugins/aws-bedrock/plugin.js +206 -0
  37. package/plugins/aws-bedrock/plugin.json +18 -0
  38. package/plugins/azure-openai/icon-color.svg +1 -0
  39. package/plugins/azure-openai/icon.svg +1 -0
  40. package/plugins/azure-openai/plugin.js +145 -0
  41. package/plugins/azure-openai/plugin.json +17 -0
  42. package/plugins/chutes/icon.svg +7 -0
  43. package/plugins/chutes/plugin.js +236 -0
  44. package/plugins/chutes/plugin.json +19 -0
  45. package/plugins/claude/icon-color.svg +1 -0
  46. package/plugins/claude/icon.svg +1 -0
  47. package/plugins/claude/plugin.js +1723 -0
  48. package/plugins/claude/plugin.json +30 -0
  49. package/plugins/codebuff/icon.svg +92 -0
  50. package/plugins/codebuff/plugin.js +113 -0
  51. package/plugins/codebuff/plugin.json +17 -0
  52. package/plugins/codex/icon-color.svg +5 -0
  53. package/plugins/codex/icon.svg +1 -0
  54. package/plugins/codex/plugin.js +1009 -0
  55. package/plugins/codex/plugin.json +82 -0
  56. package/plugins/command-code/icon.svg +3 -0
  57. package/plugins/command-code/plugin.js +184 -0
  58. package/plugins/command-code/plugin.json +20 -0
  59. package/plugins/copilot/icon-color.svg +1 -0
  60. package/plugins/copilot/icon.svg +1 -0
  61. package/plugins/copilot/plugin.js +684 -0
  62. package/plugins/copilot/plugin.json +21 -0
  63. package/plugins/crof/icon.svg +3 -0
  64. package/plugins/crof/plugin.js +53 -0
  65. package/plugins/crof/plugin.json +17 -0
  66. package/plugins/crofai/icon.svg +16 -0
  67. package/plugins/crofai/plugin.js +81 -0
  68. package/plugins/crofai/plugin.json +18 -0
  69. package/plugins/cursor/icon-conflict.svg +3 -0
  70. package/plugins/cursor/icon.svg +1 -0
  71. package/plugins/cursor/plugin.js +1379 -0
  72. package/plugins/cursor/plugin.json +23 -0
  73. package/plugins/cursor-nightly/icon.png +0 -0
  74. package/plugins/cursor-nightly/plugin.js +1379 -0
  75. package/plugins/cursor-nightly/plugin.json +23 -0
  76. package/plugins/deepgram/icon.svg +1 -0
  77. package/plugins/deepgram/plugin.js +20 -0
  78. package/plugins/deepgram/plugin.json +17 -0
  79. package/plugins/deepseek/icon-color.svg +1 -0
  80. package/plugins/deepseek/icon.svg +1 -0
  81. package/plugins/deepseek/plugin.js +71 -0
  82. package/plugins/deepseek/plugin.json +17 -0
  83. package/plugins/devin/icon.svg +5 -0
  84. package/plugins/devin/plugin.js +341 -0
  85. package/plugins/devin/plugin.json +16 -0
  86. package/plugins/doubao/icon-color.svg +1 -0
  87. package/plugins/doubao/icon.svg +1 -0
  88. package/plugins/doubao/plugin.js +92 -0
  89. package/plugins/doubao/plugin.json +17 -0
  90. package/plugins/droid/icon.svg +1 -0
  91. package/plugins/droid/plugin.json +20 -0
  92. package/plugins/elevenlabs/icon.svg +4 -0
  93. package/plugins/elevenlabs/plugin.js +48 -0
  94. package/plugins/elevenlabs/plugin.json +18 -0
  95. package/plugins/factory/icon-conflict.svg +1 -0
  96. package/plugins/factory/icon.svg +1 -0
  97. package/plugins/factory/plugin.js +487 -0
  98. package/plugins/factory/plugin.json +15 -0
  99. package/plugins/fireworks-ai/icon.svg +6 -0
  100. package/plugins/fireworks-ai/plugin.js +370 -0
  101. package/plugins/fireworks-ai/plugin.json +19 -0
  102. package/plugins/gemini/icon-color.svg +1 -0
  103. package/plugins/gemini/icon.svg +1 -0
  104. package/plugins/gemini/plugin.js +460 -0
  105. package/plugins/gemini/plugin.json +16 -0
  106. package/plugins/grok/icon.svg +1 -0
  107. package/plugins/grok/plugin.js +297 -0
  108. package/plugins/grok/plugin.json +19 -0
  109. package/plugins/groqcloud/icon.svg +1 -0
  110. package/plugins/groqcloud/plugin.js +117 -0
  111. package/plugins/groqcloud/plugin.json +19 -0
  112. package/plugins/jetbrains-ai-assistant/icon-color.svg +14 -0
  113. package/plugins/jetbrains-ai-assistant/icon-conflict.svg +3 -0
  114. package/plugins/jetbrains-ai-assistant/icon.svg +14 -0
  115. package/plugins/jetbrains-ai-assistant/plugin.js +313 -0
  116. package/plugins/jetbrains-ai-assistant/plugin.json +16 -0
  117. package/plugins/kilo/icon.svg +1 -0
  118. package/plugins/kilo/plugin.js +111 -0
  119. package/plugins/kilo/plugin.json +19 -0
  120. package/plugins/kimi/icon-color.svg +1 -0
  121. package/plugins/kimi/icon-conflict.svg +3 -0
  122. package/plugins/kimi/icon.svg +1 -0
  123. package/plugins/kimi/plugin.js +358 -0
  124. package/plugins/kimi/plugin.json +15 -0
  125. package/plugins/kimi-k2/icon-color.svg +1 -0
  126. package/plugins/kimi-k2/icon.svg +1 -0
  127. package/plugins/kimi-k2/plugin.js +58 -0
  128. package/plugins/kimi-k2/plugin.json +18 -0
  129. package/plugins/kiro/icon-color.svg +5 -0
  130. package/plugins/kiro/icon-conflict.svg +1 -0
  131. package/plugins/kiro/icon.svg +4 -0
  132. package/plugins/kiro/plugin.js +390 -0
  133. package/plugins/kiro/plugin.json +16 -0
  134. package/plugins/litellm/icon.svg +16 -0
  135. package/plugins/litellm/plugin.js +199 -0
  136. package/plugins/litellm/plugin.json +16 -0
  137. package/plugins/llm-proxy/icon.svg +1 -0
  138. package/plugins/llm-proxy/plugin.js +40 -0
  139. package/plugins/llm-proxy/plugin.json +17 -0
  140. package/plugins/manus/icon.svg +6 -0
  141. package/plugins/manus/plugin.js +50 -0
  142. package/plugins/manus/plugin.json +17 -0
  143. package/plugins/mimo/icon.svg +1 -0
  144. package/plugins/mimo/plugin.js +177 -0
  145. package/plugins/mimo/plugin.json +19 -0
  146. package/plugins/minimax/icon-color.svg +1 -0
  147. package/plugins/minimax/icon-conflict.svg +4 -0
  148. package/plugins/minimax/icon.svg +1 -0
  149. package/plugins/minimax/plugin.js +453 -0
  150. package/plugins/minimax/plugin.json +14 -0
  151. package/plugins/mistral/icon-color.svg +1 -0
  152. package/plugins/mistral/icon.svg +1 -0
  153. package/plugins/mistral/plugin.js +152 -0
  154. package/plugins/mistral/plugin.json +18 -0
  155. package/plugins/moonshot/icon.svg +1 -0
  156. package/plugins/moonshot/plugin.js +43 -0
  157. package/plugins/moonshot/plugin.json +16 -0
  158. package/plugins/nanogpt/icon-color.svg +17 -0
  159. package/plugins/nanogpt/icon.svg +10 -0
  160. package/plugins/nanogpt/plugin.js +80 -0
  161. package/plugins/nanogpt/plugin.json +17 -0
  162. package/plugins/neuralwatt/icon.svg +3 -0
  163. package/plugins/neuralwatt/plugin.js +152 -0
  164. package/plugins/neuralwatt/plugin.json +17 -0
  165. package/plugins/ollama/icon.svg +1 -0
  166. package/plugins/ollama/plugin.js +92 -0
  167. package/plugins/ollama/plugin.json +20 -0
  168. package/plugins/openai-api/icon.svg +1 -0
  169. package/plugins/openai-api/plugin.js +533 -0
  170. package/plugins/openai-api/plugin.json +26 -0
  171. package/plugins/opencode/icon.svg +1 -0
  172. package/plugins/opencode/plugin.js +342 -0
  173. package/plugins/opencode/plugin.json +19 -0
  174. package/plugins/opencode-go/icon-conflict.svg +1 -0
  175. package/plugins/opencode-go/icon.svg +1 -0
  176. package/plugins/opencode-go/plugin.js +617 -0
  177. package/plugins/opencode-go/plugin.json +50 -0
  178. package/plugins/openrouter/icon.svg +1 -0
  179. package/plugins/openrouter/plugin.js +156 -0
  180. package/plugins/openrouter/plugin.json +8 -0
  181. package/plugins/perplexity/icon-color.svg +1 -0
  182. package/plugins/perplexity/icon-conflict.svg +1 -0
  183. package/plugins/perplexity/icon.svg +1 -0
  184. package/plugins/perplexity/plugin.js +425 -0
  185. package/plugins/perplexity/plugin.json +19 -0
  186. package/plugins/poe/icon.svg +1 -0
  187. package/plugins/poe/plugin.js +190 -0
  188. package/plugins/poe/plugin.json +19 -0
  189. package/plugins/stepfun/icon-color.svg +1 -0
  190. package/plugins/stepfun/icon.svg +1 -0
  191. package/plugins/stepfun/plugin.js +155 -0
  192. package/plugins/stepfun/plugin.json +18 -0
  193. package/plugins/synthetic/icon-conflict.svg +12 -0
  194. package/plugins/synthetic/icon.svg +14 -0
  195. package/plugins/synthetic/plugin.js +331 -0
  196. package/plugins/synthetic/plugin.json +23 -0
  197. package/plugins/t3chat/icon-color.svg +28 -0
  198. package/plugins/t3chat/icon.svg +4 -0
  199. package/plugins/t3chat/plugin.js +243 -0
  200. package/plugins/t3chat/plugin.json +20 -0
  201. package/plugins/venice/icon-color.svg +1 -0
  202. package/plugins/venice/icon.svg +1 -0
  203. package/plugins/venice/plugin.js +69 -0
  204. package/plugins/venice/plugin.json +16 -0
  205. package/plugins/vertex-ai/icon-color.svg +1 -0
  206. package/plugins/vertex-ai/icon.svg +1 -0
  207. package/plugins/vertex-ai/plugin.js +172 -0
  208. package/plugins/vertex-ai/plugin.json +18 -0
  209. package/plugins/warp/icon-color.svg +10 -0
  210. package/plugins/warp/icon.svg +6 -0
  211. package/plugins/warp/plugin.js +108 -0
  212. package/plugins/warp/plugin.json +18 -0
  213. package/plugins/windsurf/icon-conflict.svg +3 -0
  214. package/plugins/windsurf/icon.svg +1 -0
  215. package/plugins/windsurf/plugin.js +208 -0
  216. package/plugins/windsurf/plugin.json +16 -0
  217. package/plugins/zai/icon-conflict.svg +5 -0
  218. package/plugins/zai/icon.svg +1 -0
  219. package/plugins/zai/plugin.js +188 -0
  220. package/plugins/zai/plugin.json +16 -0
  221. package/plugins/zed/icon.svg +4 -0
  222. package/plugins/zed/plugin.js +270 -0
  223. package/plugins/zed/plugin.json +19 -0
  224. package/usagestat +0 -0
  225. package/usagestatd +0 -0
@@ -0,0 +1,617 @@
1
+ (function () {
2
+ const PROVIDER_ID = "opencode-go";
3
+ const BASE_URL = "https://opencode.ai";
4
+ const SERVER_URL = "https://opencode.ai/_server";
5
+ const WORKSPACES_SERVER_ID =
6
+ "def39973159c7f0483d8793a822b8dbb10d067e12c65455fcb4608459ba0234f";
7
+ const FIVE_HOURS_MS = 5 * 60 * 60 * 1000;
8
+ const WEEK_MS = 7 * 24 * 60 * 60 * 1000;
9
+ const LIMITS = {
10
+ session: 12,
11
+ weekly: 30,
12
+ monthly: 60,
13
+ };
14
+
15
+ const HISTORY_EXISTS_SQL = `
16
+ SELECT 1 AS present
17
+ FROM message
18
+ WHERE json_valid(data)
19
+ AND json_extract(data, '$.providerID') = 'opencode-go'
20
+ AND json_extract(data, '$.role') = 'assistant'
21
+ AND json_type(data, '$.cost') IN ('integer', 'real')
22
+ LIMIT 1
23
+ `;
24
+
25
+ const HISTORY_ROWS_SQL = `
26
+ SELECT
27
+ CAST(COALESCE(json_extract(data, '$.time.created'), time_created) AS INTEGER) AS createdMs,
28
+ CAST(json_extract(data, '$.cost') AS REAL) AS cost
29
+ FROM message
30
+ WHERE json_valid(data)
31
+ AND json_extract(data, '$.providerID') = 'opencode-go'
32
+ AND json_extract(data, '$.role') = 'assistant'
33
+ AND json_type(data, '$.cost') IN ('integer', 'real')
34
+ `;
35
+
36
+ function readNumber(value) {
37
+ const n = Number(value);
38
+ return Number.isFinite(n) ? n : null;
39
+ }
40
+
41
+ function trim(value) {
42
+ return typeof value === "string" && value.trim() ? value.trim() : null;
43
+ }
44
+
45
+ function env(ctx, name) {
46
+ try {
47
+ return trim(ctx.host.env.get(name));
48
+ } catch (_) {
49
+ return null;
50
+ }
51
+ }
52
+
53
+ function setting(ctx, names) {
54
+ const settings = ctx.provider && ctx.provider.settings ? ctx.provider.settings : {};
55
+ for (let i = 0; i < names.length; i += 1) {
56
+ const value = trim(settings[names[i]]);
57
+ if (value) return value;
58
+ }
59
+ return null;
60
+ }
61
+
62
+ function pathValue(ctx, name, fromEnv) {
63
+ const value = fromEnv ? ctx.host.env.get(name) : (ctx.provider.settings || {})[name];
64
+ if (value === undefined || value === null || value === "") return null;
65
+ if (typeof value !== "string" || value.indexOf("\u0000") >= 0) {
66
+ throw {code: "failed", message: "OpenCode " + name + " must be a path string."};
67
+ }
68
+ return value;
69
+ }
70
+
71
+ function absolute(ctx, value) {
72
+ return ctx.app.platform === "windows" ? /^(?:[A-Za-z]:[\\/]|[\\/]{2}[^\\/]+[\\/][^\\/]+)/.test(value) : value[0] === "/";
73
+ }
74
+
75
+ function dataPath(ctx, name) {
76
+ // Upstream uses xdg-basedir on Windows and macOS too, not native AppData.
77
+ const selected = pathValue(ctx, "dataDir", false);
78
+ const xdg = selected ? null : pathValue(ctx, "XDG_DATA_HOME", true);
79
+ const root = selected || (xdg ? xdg.replace(/[\\/]+$/, "") + "/opencode" : ctx.host.fs.homeDir + "/.local/share/opencode");
80
+ if (!absolute(ctx, root)) throw {code: "failed", message: "OpenCode dataDir/XDG_DATA_HOME must be absolute."};
81
+ return root.replace(/[\\/]+$/, "") + "/" + name;
82
+ }
83
+
84
+ function databasePath(ctx) {
85
+ const selected = pathValue(ctx, "databasePath", false);
86
+ if (selected) {
87
+ if (!absolute(ctx, selected)) throw {code: "failed", message: "OpenCode databasePath must be absolute."};
88
+ return selected;
89
+ }
90
+ const upstream = pathValue(ctx, "OPENCODE_DB", true);
91
+ if (upstream === ":memory:") throw {code: "unsupported", message: "OpenCode in-memory history cannot be read by another process. Select a persisted databasePath or use web credentials."};
92
+ return upstream && absolute(ctx, upstream) ? upstream : dataPath(ctx, upstream || "opencode.db");
93
+ }
94
+
95
+ function cookieHeader(ctx) {
96
+ const cookie = trim(ctx.provider && ctx.provider.cookieHeader) ||
97
+ setting(ctx, ["cookieHeader", "cookie"]) ||
98
+ env(ctx, "OPENCODE_GO_COOKIE") ||
99
+ env(ctx, "OPENCODE_COOKIE");
100
+ return cookie ? cookie.replace(/^Cookie:\s*/i, "").trim() : null;
101
+ }
102
+
103
+ function workspaceOverride(ctx) {
104
+ return normalizeWorkspaceId(
105
+ setting(ctx, ["workspaceId", "workspace"]) ||
106
+ (ctx.provider && trim(ctx.provider.workspaceId)) ||
107
+ env(ctx, "OPENCODE_GO_WORKSPACE_ID") ||
108
+ env(ctx, "OPENCODE_WORKSPACE_ID")
109
+ );
110
+ }
111
+
112
+ function normalizeWorkspaceId(raw) {
113
+ const text = trim(raw);
114
+ if (!text) return null;
115
+ const match = /wrk_[A-Za-z0-9_-]+/.exec(text);
116
+ return match ? match[0] : null;
117
+ }
118
+
119
+ function readNowMs() {
120
+ return Date.now();
121
+ }
122
+
123
+ function clampPercent(used, limit) {
124
+ if (!Number.isFinite(used) || !Number.isFinite(limit) || limit <= 0)
125
+ return 0;
126
+ const percent = (used / limit) * 100;
127
+ if (!Number.isFinite(percent)) return 0;
128
+ return Math.round(Math.max(0, Math.min(100, percent)) * 10) / 10;
129
+ }
130
+
131
+ function toIso(ms) {
132
+ if (!Number.isFinite(ms)) return null;
133
+ return new Date(ms).toISOString();
134
+ }
135
+
136
+ function serverInstance() {
137
+ return "server-fn:" + Math.random().toString(16).slice(2) + Date.now().toString(16);
138
+ }
139
+
140
+ function looksSignedOut(text) {
141
+ const lower = String(text || "").toLowerCase();
142
+ return lower.indexOf("auth/authorize") >= 0 ||
143
+ lower.indexOf("\"signin\"") >= 0 ||
144
+ lower.indexOf("please sign in") >= 0 ||
145
+ lower.indexOf("sign in") >= 0;
146
+ }
147
+
148
+ function serverRequest(ctx, cookie) {
149
+ const url = SERVER_URL + "?id=" + encodeURIComponent(WORKSPACES_SERVER_ID);
150
+ const resp = ctx.util.request({
151
+ method: "GET",
152
+ url,
153
+ headers: {
154
+ Cookie: cookie,
155
+ "X-Server-Id": WORKSPACES_SERVER_ID,
156
+ "X-Server-Instance": serverInstance(),
157
+ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/124.0.0.0 Safari/537.36",
158
+ Origin: BASE_URL,
159
+ Referer: BASE_URL,
160
+ Accept: "text/javascript, application/json;q=0.9, */*;q=0.8",
161
+ },
162
+ timeoutMs: 15000,
163
+ });
164
+ const text = String(resp.bodyText || "");
165
+ if (ctx.util.isAuthStatus(resp.status) || looksSignedOut(text)) {
166
+ throw "OpenCode Go session cookie is invalid or expired.";
167
+ }
168
+ if (resp.status < 200 || resp.status >= 300) {
169
+ throw "OpenCode Go workspace request failed (HTTP " + resp.status + ").";
170
+ }
171
+ return text;
172
+ }
173
+
174
+ function parseWorkspaceIds(text) {
175
+ const ids = [];
176
+ const regex = /(wrk_[A-Za-z0-9_-]+)/g;
177
+ let match;
178
+ while ((match = regex.exec(String(text || ""))) !== null) {
179
+ if (ids.indexOf(match[1]) < 0) ids.push(match[1]);
180
+ }
181
+ return ids;
182
+ }
183
+
184
+ function fetchWorkspaceId(ctx, cookie) {
185
+ const ids = parseWorkspaceIds(serverRequest(ctx, cookie));
186
+ if (ids.length) return ids[0];
187
+ throw "OpenCode Go parse error: missing workspace id.";
188
+ }
189
+
190
+ function fetchUsagePage(ctx, workspaceId, cookie) {
191
+ const url = BASE_URL + "/workspace/" + workspaceId + "/go";
192
+ const resp = ctx.util.request({
193
+ method: "GET",
194
+ url,
195
+ headers: {
196
+ Cookie: cookie,
197
+ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/124.0.0.0 Safari/537.36",
198
+ Referer: BASE_URL,
199
+ Accept: "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
200
+ },
201
+ timeoutMs: 15000,
202
+ });
203
+ const text = String(resp.bodyText || "");
204
+ if (ctx.util.isAuthStatus(resp.status) || looksSignedOut(text)) {
205
+ throw "OpenCode Go session cookie is invalid or expired.";
206
+ }
207
+ if (resp.status < 200 || resp.status >= 300) {
208
+ throw "OpenCode Go usage page failed (HTTP " + resp.status + ").";
209
+ }
210
+ return text;
211
+ }
212
+
213
+ function startOfUtcWeek(nowMs) {
214
+ const date = new Date(nowMs);
215
+ const offset = (date.getUTCDay() + 6) % 7;
216
+ date.setUTCDate(date.getUTCDate() - offset);
217
+ date.setUTCHours(0, 0, 0, 0);
218
+ return date.getTime();
219
+ }
220
+
221
+ function startOfUtcMonth(nowMs) {
222
+ const date = new Date(nowMs);
223
+ return Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), 1, 0, 0, 0, 0);
224
+ }
225
+
226
+ function startOfNextUtcMonth(nowMs) {
227
+ const date = new Date(nowMs);
228
+ return Date.UTC(
229
+ date.getUTCFullYear(),
230
+ date.getUTCMonth() + 1,
231
+ 1,
232
+ 0,
233
+ 0,
234
+ 0,
235
+ 0,
236
+ );
237
+ }
238
+
239
+ function shiftMonth(year, month, delta) {
240
+ const total = year * 12 + month + delta;
241
+ return [Math.floor(total / 12), ((total % 12) + 12) % 12];
242
+ }
243
+
244
+ function anchorMonth(year, month, anchorDate) {
245
+ const maxDay = new Date(Date.UTC(year, month + 1, 0)).getUTCDate();
246
+ return Date.UTC(
247
+ year,
248
+ month,
249
+ Math.min(anchorDate.getUTCDate(), maxDay),
250
+ anchorDate.getUTCHours(),
251
+ anchorDate.getUTCMinutes(),
252
+ anchorDate.getUTCSeconds(),
253
+ anchorDate.getUTCMilliseconds(),
254
+ );
255
+ }
256
+
257
+ function anchoredMonthBounds(nowMs, anchorMs) {
258
+ if (!Number.isFinite(anchorMs)) {
259
+ const startMs = startOfUtcMonth(nowMs);
260
+ return { startMs, endMs: startOfNextUtcMonth(nowMs) };
261
+ }
262
+
263
+ const nowDate = new Date(nowMs);
264
+ const anchorDate = new Date(anchorMs);
265
+ let year = nowDate.getUTCFullYear();
266
+ let month = nowDate.getUTCMonth();
267
+ let startMs = anchorMonth(year, month, anchorDate);
268
+
269
+ if (startMs > nowMs) {
270
+ const previous = shiftMonth(year, month, -1);
271
+ year = previous[0];
272
+ month = previous[1];
273
+ startMs = anchorMonth(year, month, anchorDate);
274
+ }
275
+
276
+ const next = shiftMonth(year, month, 1);
277
+ return {
278
+ startMs,
279
+ endMs: anchorMonth(next[0], next[1], anchorDate),
280
+ };
281
+ }
282
+
283
+ function sumRange(rows, startMs, endMs) {
284
+ let total = 0;
285
+ for (let i = 0; i < rows.length; i += 1) {
286
+ const row = rows[i];
287
+ if (row.createdMs < startMs || row.createdMs >= endMs) continue;
288
+ total += row.cost;
289
+ }
290
+ return Math.round(total * 10000) / 10000;
291
+ }
292
+
293
+ function nextRollingReset(rows, nowMs) {
294
+ const startMs = nowMs - FIVE_HOURS_MS;
295
+ let oldest = null;
296
+ for (let i = 0; i < rows.length; i += 1) {
297
+ const row = rows[i];
298
+ if (row.createdMs < startMs || row.createdMs >= nowMs) continue;
299
+ if (oldest === null || row.createdMs < oldest) oldest = row.createdMs;
300
+ }
301
+ return toIso((oldest === null ? nowMs : oldest) + FIVE_HOURS_MS);
302
+ }
303
+
304
+ function queryRows(ctx, sql) {
305
+ const path = databasePath(ctx);
306
+ try {
307
+ const raw = ctx.host.sqlite.query(path, sql);
308
+ const rows = Array.isArray(raw) ? raw : ctx.util.tryParseJson(raw);
309
+ if (!Array.isArray(rows)) {
310
+ ctx.host.log.warn("sqlite query returned non-array result");
311
+ return { ok: false, rows: [] };
312
+ }
313
+ return { ok: true, rows };
314
+ } catch (e) {
315
+ ctx.host.log.warn("sqlite query failed: " + String(e));
316
+ return { ok: false, rows: [] };
317
+ }
318
+ }
319
+
320
+ function loadAuthKey(ctx) {
321
+ const inline = ctx.host.env.get("OPENCODE_AUTH_CONTENT");
322
+ const path = inline ? null : dataPath(ctx, "auth.json");
323
+ if (!inline && !ctx.host.fs.exists(path)) return null;
324
+
325
+ try {
326
+ const text = inline || ctx.host.fs.readText(path);
327
+ const parsed = ctx.util.tryParseJson(text);
328
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
329
+ if (inline) throw {code: "credential-malformed", message: "OPENCODE_AUTH_CONTENT must contain an auth JSON object."};
330
+ ctx.host.log.warn("opencode auth file is not valid json");
331
+ return null;
332
+ }
333
+ const entry = parsed[PROVIDER_ID];
334
+ if (!entry || typeof entry !== "object") return null;
335
+ const key = typeof entry.key === "string" ? entry.key.trim() : "";
336
+ return key || null;
337
+ } catch (e) {
338
+ if (e && e.code) throw e;
339
+ ctx.host.log.warn("opencode auth read failed: " + String(e));
340
+ return null;
341
+ }
342
+ }
343
+
344
+ function hasHistory(ctx) {
345
+ const result = queryRows(ctx, HISTORY_EXISTS_SQL);
346
+ if (!result.ok) return { ok: false, present: false };
347
+ return { ok: true, present: result.rows.length > 0 };
348
+ }
349
+
350
+ function loadHistory(ctx) {
351
+ const result = queryRows(ctx, HISTORY_ROWS_SQL);
352
+ if (!result.ok) return result;
353
+
354
+ const rows = [];
355
+ for (let i = 0; i < result.rows.length; i += 1) {
356
+ const row = result.rows[i];
357
+ if (!row || typeof row !== "object") continue;
358
+ const createdMs = readNumber(row.createdMs);
359
+ const cost = readNumber(row.cost);
360
+ if (createdMs === null || createdMs <= 0) continue;
361
+ if (cost === null || cost < 0) continue;
362
+ rows.push({ createdMs, cost });
363
+ }
364
+
365
+ return { ok: true, rows };
366
+ }
367
+
368
+ function buildProgressLines(ctx, rows, nowMs) {
369
+ const sessionStartMs = nowMs - FIVE_HOURS_MS;
370
+ const weeklyStartMs = startOfUtcWeek(nowMs);
371
+ const weeklyEndMs = weeklyStartMs + WEEK_MS;
372
+ let earliestMs = null;
373
+ for (let i = 0; i < rows.length; i += 1) {
374
+ const createdMs = rows[i].createdMs;
375
+ if (!Number.isFinite(createdMs)) continue;
376
+ if (earliestMs === null || createdMs < earliestMs) earliestMs = createdMs;
377
+ }
378
+ const monthBounds = anchoredMonthBounds(nowMs, earliestMs);
379
+ const monthlyStartMs = monthBounds.startMs;
380
+ const monthlyEndMs = monthBounds.endMs;
381
+
382
+ const sessionCost = sumRange(rows, sessionStartMs, nowMs);
383
+ const weeklyCost = sumRange(rows, weeklyStartMs, weeklyEndMs);
384
+ const monthlyCost = sumRange(rows, monthlyStartMs, monthlyEndMs);
385
+
386
+ return [
387
+ ctx.line.progress({
388
+ label: "Session",
389
+ used: clampPercent(sessionCost, LIMITS.session),
390
+ limit: 100,
391
+ format: { kind: "percent" },
392
+ resetsAt: nextRollingReset(rows, nowMs),
393
+ periodDurationMs: FIVE_HOURS_MS,
394
+ }),
395
+ ctx.line.progress({
396
+ label: "Weekly",
397
+ used: clampPercent(weeklyCost, LIMITS.weekly),
398
+ limit: 100,
399
+ format: { kind: "percent" },
400
+ resetsAt: toIso(weeklyEndMs),
401
+ periodDurationMs: WEEK_MS,
402
+ }),
403
+ ctx.line.progress({
404
+ label: "Monthly",
405
+ used: clampPercent(monthlyCost, LIMITS.monthly),
406
+ limit: 100,
407
+ format: { kind: "percent" },
408
+ resetsAt: toIso(monthlyEndMs),
409
+ periodDurationMs: monthlyEndMs - monthlyStartMs,
410
+ }),
411
+ ];
412
+ }
413
+
414
+ function buildSoftEmptyLines(ctx) {
415
+ return [
416
+ ctx.line.badge({
417
+ label: "Status",
418
+ text: "No usage data",
419
+ color: "#a3a3a3",
420
+ }),
421
+ ];
422
+ }
423
+
424
+ function regexNumber(text, regex) {
425
+ const match = regex.exec(String(text || ""));
426
+ return match ? readNumber(match[1]) : null;
427
+ }
428
+
429
+ function extractWindow(text, names) {
430
+ for (let i = 0; i < names.length; i += 1) {
431
+ const name = names[i];
432
+ const percent = regexNumber(
433
+ text,
434
+ new RegExp(name + "[^}]*?(?:usagePercent|usedPercent|percentUsed|percent)\\s*[:=]\\s*([0-9]+(?:\\.[0-9]+)?)")
435
+ );
436
+ if (percent === null) continue;
437
+ const reset = regexNumber(
438
+ text,
439
+ new RegExp(name + "[^}]*?(?:resetInSec|resetInSeconds|resetSeconds|resetSec)\\s*[:=]\\s*([0-9]+)")
440
+ ) || 0;
441
+ const normalized = percent <= 1 ? percent * 100 : percent;
442
+ return {
443
+ percent: Math.max(0, Math.min(100, normalized)),
444
+ resetsAt: toIso(Date.now() + Math.max(0, reset) * 1000),
445
+ };
446
+ }
447
+ return null;
448
+ }
449
+
450
+ function dateFromText(value) {
451
+ const text = trim(value);
452
+ if (!text) return null;
453
+ const n = readNumber(text);
454
+ if (n !== null && n > 0) return toIso(n > 10000000000 ? n : n * 1000);
455
+ const ms = Date.parse(text);
456
+ return Number.isFinite(ms) ? toIso(ms) : null;
457
+ }
458
+
459
+ function extractRenewal(text) {
460
+ const match = /(?:"renewAt"|"renew_at"|renewAt|renew_at)\s*[:=]\s*"?([^",}\s]+)"?/.exec(String(text || ""));
461
+ return match ? dateFromText(match[1]) : null;
462
+ }
463
+
464
+ function findBalanceValue(value) {
465
+ if (Array.isArray(value)) {
466
+ for (let i = 0; i < value.length; i += 1) {
467
+ const found = findBalanceValue(value[i]);
468
+ if (found !== null) return found;
469
+ }
470
+ return null;
471
+ }
472
+ if (!value || typeof value !== "object") return null;
473
+ const keys = Object.keys(value);
474
+ for (let i = 0; i < keys.length; i += 1) {
475
+ const key = keys[i];
476
+ const normalized = key.toLowerCase().replace(/[^a-z0-9]/g, "");
477
+ if (
478
+ normalized === "zenbalance" ||
479
+ normalized === "zencurrentbalance" ||
480
+ normalized === "currentbalance" ||
481
+ normalized === "currentbalanceusd" ||
482
+ normalized === "balanceusd" ||
483
+ normalized === "usdbalance"
484
+ ) {
485
+ const parsed = readNumber(typeof value[key] === "string" ? value[key].replace(/,/g, "") : value[key]);
486
+ if (parsed !== null) return parsed;
487
+ }
488
+ const nested = findBalanceValue(value[key]);
489
+ if (nested !== null) return nested;
490
+ }
491
+ return null;
492
+ }
493
+
494
+ function parseZenBalance(ctx, text) {
495
+ const json = ctx.util.tryParseJson(text);
496
+ if (json) {
497
+ const found = findBalanceValue(json);
498
+ if (found !== null) return found;
499
+ }
500
+ const patterns = [
501
+ /(?:current\s+balance|zen\s+balance|現在の残高)[^$]{0,80}\$\s*([0-9][0-9,]*(?:\.[0-9]+)?)/i,
502
+ /(?:balance|残高)[\s\S]{0,120}?\$\s*([0-9][0-9,]*(?:\.[0-9]+)?)/i,
503
+ ];
504
+ for (let i = 0; i < patterns.length; i += 1) {
505
+ const match = patterns[i].exec(String(text || ""));
506
+ if (!match) continue;
507
+ const parsed = readNumber(match[1].replace(/,/g, ""));
508
+ if (parsed !== null) return parsed;
509
+ }
510
+ return null;
511
+ }
512
+
513
+ function buildWebLines(ctx, text) {
514
+ const rolling = extractWindow(text, ["rollingUsage", "rolling_usage", "rolling"]);
515
+ const weekly = extractWindow(text, ["weeklyUsage", "weekly_usage", "weekly"]);
516
+ const monthly = extractWindow(text, ["monthlyUsage", "monthly_usage", "monthly"]);
517
+ const lines = [];
518
+ if (rolling) {
519
+ lines.push(ctx.line.progress({
520
+ label: "Session",
521
+ used: rolling.percent,
522
+ limit: 100,
523
+ format: { kind: "percent" },
524
+ resetsAt: rolling.resetsAt,
525
+ periodDurationMs: FIVE_HOURS_MS,
526
+ }));
527
+ }
528
+ if (weekly) {
529
+ lines.push(ctx.line.progress({
530
+ label: "Weekly",
531
+ used: weekly.percent,
532
+ limit: 100,
533
+ format: { kind: "percent" },
534
+ resetsAt: weekly.resetsAt,
535
+ periodDurationMs: WEEK_MS,
536
+ }));
537
+ }
538
+ if (monthly) {
539
+ lines.push(ctx.line.progress({
540
+ label: "Monthly",
541
+ used: monthly.percent,
542
+ limit: 100,
543
+ format: { kind: "percent" },
544
+ resetsAt: monthly.resetsAt,
545
+ }));
546
+ }
547
+ const renewsAt = extractRenewal(text);
548
+ if (renewsAt) lines.push(ctx.line.text({ label: "Renews", value: renewsAt }));
549
+ const balance = parseZenBalance(ctx, text);
550
+ if (balance !== null) {
551
+ lines.push(ctx.line.text({ label: "Zen balance", value: "$" + balance.toFixed(2) }));
552
+ }
553
+ return { lines, balance };
554
+ }
555
+
556
+ function fetchWebResult(ctx, cookie) {
557
+ const workspaceId = workspaceOverride(ctx) || fetchWorkspaceId(ctx, cookie);
558
+ const page = fetchUsagePage(ctx, workspaceId, cookie);
559
+ const parsed = buildWebLines(ctx, page);
560
+ if (!parsed.lines.length) {
561
+ throw "OpenCode Go parse error: missing usage and balance fields.";
562
+ }
563
+ return { plan: "Go", lines: parsed.lines };
564
+ }
565
+
566
+ function probe(ctx) {
567
+ const authKey = loadAuthKey(ctx);
568
+ const history = hasHistory(ctx);
569
+ const cookie = cookieHeader(ctx);
570
+ const detected = !!authKey || !!cookie || (history.ok && history.present);
571
+
572
+ if (!detected) {
573
+ throw "OpenCode Go not detected. Log in with OpenCode Go or use it locally first.";
574
+ }
575
+
576
+ if (!history.ok && !cookie) {
577
+ return { plan: "Go", lines: buildSoftEmptyLines(ctx) };
578
+ }
579
+
580
+ const rowsResult = loadHistory(ctx);
581
+ if (rowsResult.ok && rowsResult.rows.length > 0) {
582
+ const lines = buildProgressLines(ctx, rowsResult.rows, readNowMs());
583
+ if (cookie) {
584
+ try {
585
+ const webResult = fetchWebResult(ctx, cookie);
586
+ for (let i = 0; i < webResult.lines.length; i += 1) {
587
+ if (webResult.lines[i].label === "Zen balance" || webResult.lines[i].label === "Renews") {
588
+ lines.push(webResult.lines[i]);
589
+ }
590
+ }
591
+ } catch (e) {
592
+ ctx.host.log.warn("OpenCode Go web enrichment failed: " + String(e));
593
+ }
594
+ }
595
+ return { plan: "Go", lines };
596
+ }
597
+
598
+ if (cookie) {
599
+ try {
600
+ return fetchWebResult(ctx, cookie);
601
+ } catch (e) {
602
+ ctx.host.log.warn("OpenCode Go web fallback failed: " + String(e));
603
+ }
604
+ }
605
+
606
+ if (!rowsResult.ok) {
607
+ return { plan: "Go", lines: buildSoftEmptyLines(ctx) };
608
+ }
609
+
610
+ return {
611
+ plan: "Go",
612
+ lines: buildProgressLines(ctx, rowsResult.rows, readNowMs()),
613
+ };
614
+ }
615
+
616
+ globalThis.__openusage_plugin = { id: PROVIDER_ID, probe };
617
+ })();
@@ -0,0 +1,50 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "id": "opencode-go",
4
+ "name": "OpenCode Go",
5
+ "version": "0.0.1",
6
+ "entry": "plugin.js",
7
+ "icon": "icon.svg",
8
+ "brandColor": "#000000",
9
+ "links": [
10
+ {
11
+ "label": "Console",
12
+ "url": "https://opencode.ai/auth"
13
+ },
14
+ {
15
+ "label": "Docs",
16
+ "url": "https://opencode.ai/docs/go/"
17
+ }
18
+ ],
19
+ "usageDashboardUrl": "https://opencode.ai/auth",
20
+ "supportedModes": ["local", "web"],
21
+ "autoMode": "local",
22
+ "lines": [
23
+ {
24
+ "type": "progress",
25
+ "label": "Session",
26
+ "scope": "overview",
27
+ "primaryOrder": 1
28
+ },
29
+ {
30
+ "type": "progress",
31
+ "label": "Weekly",
32
+ "scope": "overview"
33
+ },
34
+ {
35
+ "type": "progress",
36
+ "label": "Monthly",
37
+ "scope": "detail"
38
+ },
39
+ {
40
+ "type": "text",
41
+ "label": "Renews",
42
+ "scope": "detail"
43
+ },
44
+ {
45
+ "type": "text",
46
+ "label": "Zen balance",
47
+ "scope": "detail"
48
+ }
49
+ ]
50
+ }
@@ -0,0 +1 @@
1
+ <svg fill="currentColor" fill-rule="evenodd" height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>OpenRouter</title><path d="M16.804 1.957l7.22 4.105v.087L16.73 10.21l.017-2.117-.821-.03c-1.059-.028-1.611.002-2.268.11-1.064.175-2.038.577-3.147 1.352L8.345 11.03c-.284.195-.495.336-.68.455l-.515.322-.397.234.385.23.53.338c.476.314 1.17.796 2.701 1.866 1.11.775 2.083 1.177 3.147 1.352l.3.045c.694.091 1.375.094 2.825.033l.022-2.159 7.22 4.105v.087L16.589 22l.014-1.862-.635.022c-1.386.042-2.137.002-3.138-.162-1.694-.28-3.26-.926-4.881-2.059l-2.158-1.5a21.997 21.997 0 00-.755-.498l-.467-.28a55.927 55.927 0 00-.76-.43C2.908 14.73.563 14.116 0 14.116V9.888l.14.004c.564-.007 2.91-.622 3.809-1.124l1.016-.58.438-.274c.428-.28 1.072-.726 2.686-1.853 1.621-1.133 3.186-1.78 4.881-2.059 1.152-.19 1.974-.213 3.814-.138l.02-1.907z"></path></svg>