@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,370 @@
1
+ (function () {
2
+ const API_BASE = "https://api.fireworks.ai/v1"
3
+ const ACCOUNTS_URL = API_BASE + "/accounts?pageSize=200"
4
+ const KEYCHAIN_SERVICE = "OpenUsage Fireworks AI API Key"
5
+ const ENV_VAR = "FIREWORKS_API_KEY"
6
+ const BILLING_WINDOW_DAYS = 30
7
+ const TIER_BY_CAP = { 50: "Tier 1", 500: "Tier 2", 5000: "Tier 3", 50000: "Tier 4" }
8
+ const HEALTHY_STATES = ["READY", "ACTIVE", "STATE_UNSPECIFIED"]
9
+ const HEALTHY_SUSPEND = ["UNSUSPENDED", "SUSPEND_STATE_UNSPECIFIED", null]
10
+ const TOTAL_TOKENS_NAMES = ["serverless-inference-total-tokens", "serverless-inference-tokens", "serverless-usage-tokens"]
11
+
12
+ function text(value) {
13
+ if (typeof value !== "string") return null
14
+ const trimmed = value.trim()
15
+ return trimmed || null
16
+ }
17
+
18
+ function num(value) {
19
+ if (typeof value === "number") return Number.isFinite(value) ? value : null
20
+ const raw = text(value)
21
+ if (!raw) return null
22
+ const parsed = Number(raw)
23
+ return Number.isFinite(parsed) ? parsed : null
24
+ }
25
+
26
+ function title(value) {
27
+ const raw = text(value)
28
+ return raw ? raw.replace(/[_-]+/g, " ").replace(/\b[a-z]/g, (m) => m.toUpperCase()) : null
29
+ }
30
+
31
+ function lastPathPart(value) {
32
+ const raw = text(value)
33
+ return raw ? raw.split("/").filter(Boolean).pop() || null : null
34
+ }
35
+
36
+ function firstFinite(values) {
37
+ for (let i = 0; i < values.length; i += 1) {
38
+ if (Number.isFinite(values[i])) return values[i]
39
+ }
40
+ return null
41
+ }
42
+
43
+ function formatGroupedNumber(value) {
44
+ const parts = String(value).split(".")
45
+ parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ",")
46
+ return parts.join(".")
47
+ }
48
+
49
+ function formatFixed(value, decimals) {
50
+ const scale = Math.pow(10, decimals)
51
+ const rounded = Math.round(value * scale) / scale
52
+ const fixed = String(rounded.toFixed(decimals))
53
+ return decimals > 0 ? fixed.replace(/\.?0+$/, "") : fixed
54
+ }
55
+
56
+ function formatDollars(value) {
57
+ if (!Number.isFinite(value)) return null
58
+ const rounded = Math.round(value * 100) / 100
59
+ const fixed = formatFixed(rounded, rounded % 1 === 0 ? 0 : 2)
60
+ const parts = fixed.split(".")
61
+ return "$" + formatGroupedNumber(parts.join("."))
62
+ }
63
+
64
+ function formatCompactCount(value) {
65
+ if (!Number.isFinite(value)) return null
66
+ const abs = Math.abs(value)
67
+ const units = [{ threshold: 1e12, suffix: "T" }, { threshold: 1e9, suffix: "B" }, { threshold: 1e6, suffix: "M" }, { threshold: 1e3, suffix: "K" }]
68
+ for (let i = 0; i < units.length; i += 1) {
69
+ const unit = units[i]
70
+ if (abs >= unit.threshold) return formatFixed(value / unit.threshold, 2) + unit.suffix
71
+ }
72
+ return formatGroupedNumber(String(Math.round(value)))
73
+ }
74
+
75
+ function startOfUtcDayMs(ms) {
76
+ const date = new Date(ms)
77
+ return Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate())
78
+ }
79
+
80
+ function buildBillingWindow(nowMs) {
81
+ const endMs = startOfUtcDayMs(nowMs) + 24 * 60 * 60 * 1000
82
+ const startMs = endMs - BILLING_WINDOW_DAYS * 24 * 60 * 60 * 1000
83
+ const fmt = (ms) => new Date(ms).toISOString().slice(0, 10)
84
+ return { startTime: fmt(startMs), endTime: fmt(endMs), label: "Last " + BILLING_WINDOW_DAYS + " days" }
85
+ }
86
+
87
+ function parseCsvRow(text) {
88
+ const out = []
89
+ let current = ""
90
+ let inQuotes = false
91
+ for (let i = 0; i < text.length; i += 1) {
92
+ const char = text.charAt(i)
93
+ if (char === '"') {
94
+ if (inQuotes && text.charAt(i + 1) === '"') {
95
+ current += '"'
96
+ i += 1
97
+ } else {
98
+ inQuotes = !inQuotes
99
+ }
100
+ } else if (char === "," && !inQuotes) {
101
+ out.push(current)
102
+ current = ""
103
+ } else {
104
+ current += char
105
+ }
106
+ }
107
+ out.push(current)
108
+ return out
109
+ }
110
+
111
+ function parseBillingMetricsCsv(text) {
112
+ const raw = text && String(text).trim()
113
+ if (!raw) return []
114
+ const lines = raw.split(/\r?\n/).filter(Boolean)
115
+ if (lines.length < 2) return []
116
+ const header = parseCsvRow(lines[0]).map((value) => value.trim())
117
+ const rows = []
118
+ for (let i = 1; i < lines.length; i += 1) {
119
+ const cols = parseCsvRow(lines[i])
120
+ if (!cols.length) continue
121
+ const row = {}
122
+ for (let j = 0; j < header.length; j += 1) row[header[j]] = cols[j] || ""
123
+ rows.push(row)
124
+ }
125
+ return rows
126
+ }
127
+
128
+ function readKeychainApiKey(ctx) {
129
+ const keychain = ctx.host.keychain
130
+ if (!keychain) return null
131
+ if (typeof keychain.readGenericPasswordForCurrentUser === "function") {
132
+ try {
133
+ const value = text(keychain.readGenericPasswordForCurrentUser(KEYCHAIN_SERVICE))
134
+ if (value) return { value, source: "keychain-current-user" }
135
+ } catch (e) {
136
+ ctx.host.log.info("current-user keychain read failed, trying legacy lookup: " + String(e))
137
+ }
138
+ }
139
+ if (typeof keychain.readGenericPassword !== "function") return null
140
+ try {
141
+ const value = text(keychain.readGenericPassword(KEYCHAIN_SERVICE))
142
+ return value ? { value, source: "keychain-legacy" } : null
143
+ } catch (e) {
144
+ ctx.host.log.info("keychain read failed (may not exist): " + String(e))
145
+ return null
146
+ }
147
+ }
148
+
149
+ function loadApiKey(ctx) {
150
+ const keychainValue = readKeychainApiKey(ctx)
151
+ if (keychainValue) return keychainValue
152
+ try {
153
+ const value = text(ctx.host.env.get(ENV_VAR))
154
+ return value ? { value, source: ENV_VAR } : null
155
+ } catch (e) {
156
+ ctx.host.log.warn("env read failed for " + ENV_VAR + ": " + String(e))
157
+ return null
158
+ }
159
+ }
160
+
161
+ function requestJson(ctx, apiKey, url) {
162
+ let resp
163
+ try {
164
+ resp = ctx.util.request({
165
+ method: "GET",
166
+ url,
167
+ headers: {
168
+ Authorization: "Bearer " + apiKey,
169
+ Accept: "application/json",
170
+ "User-Agent": "usagestat/" + String(ctx.app && ctx.app.version ? ctx.app.version : "0.0.0"),
171
+ },
172
+ timeoutMs: 15000,
173
+ })
174
+ } catch (e) {
175
+ ctx.host.log.error("request failed: " + String(e))
176
+ throw "Request failed. Check your connection."
177
+ }
178
+ if (ctx.util.isAuthStatus(resp.status)) throw "API key invalid. Check your Fireworks AI API key."
179
+ if (resp.status < 200 || resp.status >= 300) throw "Request failed (HTTP " + resp.status + "). Try again later."
180
+ const parsed = ctx.util.tryParseJson(resp.bodyText)
181
+ if (!parsed || typeof parsed !== "object") throw "Response invalid. Try again later."
182
+ return parsed
183
+ }
184
+
185
+ function listAccounts(ctx, apiKey) {
186
+ const parsed = requestJson(ctx, apiKey, ACCOUNTS_URL)
187
+ const accounts = Array.isArray(parsed.accounts) ? parsed.accounts : Array.isArray(parsed.data) ? parsed.data : null
188
+ if (!accounts || !accounts.length) throw "No Fireworks account found for this API key."
189
+ return accounts
190
+ }
191
+
192
+ function pickAccount(accounts) {
193
+ let fallback = null
194
+ for (let i = 0; i < accounts.length; i += 1) {
195
+ const item = accounts[i]
196
+ if (!item || typeof item !== "object") continue
197
+ const accountId = lastPathPart(item.name) || text(item.accountId) || text(item.account_id)
198
+ if (!accountId) continue
199
+ const account = {
200
+ accountId,
201
+ state: text(item.state),
202
+ suspendState: text(item.suspendState || item.suspend_state),
203
+ }
204
+ if (!fallback) fallback = account
205
+ if (HEALTHY_STATES.includes(account.state) && HEALTHY_SUSPEND.includes(account.suspendState)) return account
206
+ }
207
+ return fallback
208
+ }
209
+
210
+ function listQuotas(ctx, apiKey, accountId) {
211
+ const parsed = requestJson(ctx, apiKey, API_BASE + "/accounts/" + encodeURIComponent(accountId) + "/quotas?pageSize=200")
212
+ return Array.isArray(parsed.quotas) ? parsed.quotas : Array.isArray(parsed.data) ? parsed.data : []
213
+ }
214
+
215
+ function normalizeQuota(ctx, raw) {
216
+ if (!raw || typeof raw !== "object") return null
217
+ const name = lastPathPart(raw.name) || text(raw.quotaId) || text(raw.id)
218
+ if (!name) return null
219
+ return {
220
+ name,
221
+ value: num(raw.value),
222
+ maxValue: num(raw.maxValue ?? raw.max_value),
223
+ usage: firstFinite([
224
+ num(raw.usage),
225
+ num(raw.currentUsage ?? raw.current_usage),
226
+ num(raw.consumedValue ?? raw.consumed_value),
227
+ ]),
228
+ updateTime: ctx.util.toIso(raw.updateTime ?? raw.update_time),
229
+ }
230
+ }
231
+
232
+ function findQuota(quotas, exactNames, pattern) {
233
+ for (let i = 0; i < quotas.length; i += 1) {
234
+ const quota = quotas[i]
235
+ if (!quota) continue
236
+ if (exactNames.includes(quota.name)) return quota
237
+ }
238
+ for (let i = 0; i < quotas.length; i += 1) {
239
+ const quota = quotas[i]
240
+ if (!quota) continue
241
+ if (pattern.test(quota.name)) return quota
242
+ }
243
+ return null
244
+ }
245
+
246
+ function inferPlan(spendQuota) {
247
+ if (!spendQuota || spendQuota.maxValue === null) return null
248
+ const cap = Math.round(spendQuota.maxValue)
249
+ return TIER_BY_CAP[cap] || null
250
+ }
251
+
252
+ function buildStatusLine(ctx, account) {
253
+ const suspendState = account && account.suspendState
254
+ if (suspendState && !HEALTHY_SUSPEND.includes(suspendState)) {
255
+ return ctx.line.badge({ label: "Status", text: title(suspendState) || suspendState, color: "#f97316" })
256
+ }
257
+ const state = account && account.state
258
+ if (state && !HEALTHY_STATES.includes(state)) {
259
+ return ctx.line.badge({ label: "Status", text: title(state) || state, color: "#f97316" })
260
+ }
261
+ return null
262
+ }
263
+
264
+ function tokenAmount(quota) {
265
+ if (!quota) return null
266
+ if (quota.usage !== null && quota.usage >= 0) return quota.usage
267
+ if (quota.maxValue === null && quota.value !== null && quota.value >= 0) return quota.value
268
+ return null
269
+ }
270
+
271
+ function loadBillingUsage(ctx, apiKey, accountId, nowMs) {
272
+ const fireworks = ctx.host.fireworks
273
+ if (!fireworks || typeof fireworks.exportBillingMetrics !== "function") return { status: "unsupported" }
274
+ const window = buildBillingWindow(nowMs)
275
+ let exported
276
+ try {
277
+ exported = fireworks.exportBillingMetrics({
278
+ apiKey,
279
+ accountId,
280
+ startTime: window.startTime,
281
+ endTime: window.endTime,
282
+ })
283
+ } catch (e) {
284
+ ctx.host.log.warn("billing export failed: " + String(e))
285
+ return { status: "runner_failed" }
286
+ }
287
+ if (!exported || typeof exported.status !== "string") return { status: "runner_failed" }
288
+ if (exported.status !== "ok" || !text(exported.csv)) return { status: exported.status }
289
+ const rows = parseBillingMetricsCsv(exported.csv)
290
+ let promptTokens = 0
291
+ let generatedTokens = 0
292
+ for (let i = 0; i < rows.length; i += 1) {
293
+ const row = rows[i]
294
+ const usageType = text(row.usage_type || row.usageType)
295
+ if (usageType && !/inference_usage/i.test(usageType)) continue
296
+ promptTokens += num(row.prompt_tokens || row.promptTokens) || 0
297
+ generatedTokens += num(row.completion_tokens || row.completionTokens) || 0
298
+ }
299
+ const totalTokens = promptTokens + generatedTokens
300
+ return { status: "ok", label: window.label, promptTokens, generatedTokens, totalTokens }
301
+ }
302
+
303
+ function buildOutput(ctx, account, quotas, billingUsage) {
304
+ const spendQuota = findQuota(quotas, ["monthly-spend-usd"], /^monthly-spend-usd$/)
305
+ const totalTokensQuota = findQuota(quotas, TOTAL_TOKENS_NAMES, /(serverless|inference).*(total|usage).*tokens/i)
306
+ const plan = inferPlan(spendQuota)
307
+ const lines = []
308
+
309
+ const promptTotal = billingUsage && billingUsage.status === "ok" ? billingUsage.promptTokens : null
310
+ const generatedTotal = billingUsage && billingUsage.status === "ok" ? billingUsage.generatedTokens : null
311
+ const totalTokens =
312
+ billingUsage && billingUsage.status === "ok"
313
+ ? billingUsage.totalTokens
314
+ : tokenAmount(totalTokensQuota)
315
+ if (totalTokens !== null) {
316
+ lines.push(ctx.line.text({ label: "Serverless usage", value: formatCompactCount(totalTokens) + " tokens", subtitle: billingUsage && billingUsage.status === "ok" ? billingUsage.label : "Tokens reported by Fireworks" }))
317
+ } else if (billingUsage && billingUsage.status !== "ok") {
318
+ const reason =
319
+ billingUsage.status === "no_runner" || billingUsage.status === "unsupported"
320
+ ? "Install firectl for official billing export"
321
+ : billingUsage.status === "runner_failed"
322
+ ? "Billing export failed; showing spend fallback"
323
+ : "Token totals not exposed for this account"
324
+ lines.push(ctx.line.text({ label: "Serverless usage", value: "Unavailable", subtitle: reason }))
325
+ }
326
+
327
+ if (promptTotal !== null) {
328
+ lines.push(ctx.line.text({ label: "Prompt tokens", value: formatCompactCount(promptTotal) + " tokens", subtitle: billingUsage && billingUsage.status === "ok" ? billingUsage.label : "Tokens reported by Fireworks" }))
329
+ }
330
+
331
+ if (generatedTotal !== null) {
332
+ lines.push(ctx.line.text({ label: "Generated tokens", value: formatCompactCount(generatedTotal) + " tokens", subtitle: billingUsage && billingUsage.status === "ok" ? billingUsage.label : "Tokens reported by Fireworks" }))
333
+ }
334
+
335
+ if (spendQuota && spendQuota.usage !== null && spendQuota.usage >= 0) {
336
+ const spendSubtitle =
337
+ spendQuota.value !== null && spendQuota.value > 0
338
+ ? "This month against budget " + formatDollars(spendQuota.value)
339
+ : "Calendar month to date"
340
+ lines.push(ctx.line.text({ label: "Month spend", value: formatDollars(spendQuota.usage), subtitle: spendSubtitle }))
341
+ }
342
+
343
+ if (spendQuota && spendQuota.maxValue !== null && spendQuota.maxValue > 0) {
344
+ const budgetValue = spendQuota.value !== null && spendQuota.value > 0 ? formatDollars(spendQuota.value) : "Not set"
345
+ lines.push(ctx.line.text({ label: "Budget", value: budgetValue, subtitle: "Tier cap " + formatDollars(spendQuota.maxValue) }))
346
+ }
347
+
348
+ const statusLine = buildStatusLine(ctx, account)
349
+ if (statusLine) lines.push(statusLine)
350
+
351
+ if (!lines.length) lines.push(ctx.line.badge({ label: "Status", text: "No usage data", color: "#a3a3a3" }))
352
+ return { plan, lines }
353
+ }
354
+
355
+ function probe(ctx) {
356
+ const nowMs = ctx.util.parseDateMs(ctx.nowIso) || Date.now()
357
+ const apiKey = loadApiKey(ctx)
358
+ if (!apiKey) {
359
+ throw 'No Fireworks API key found. Save it in macOS Keychain as "OpenUsage Fireworks AI API Key" or set FIREWORKS_API_KEY, then restart usagestat.'
360
+ }
361
+ ctx.host.log.info("api key loaded from " + apiKey.source)
362
+ const account = pickAccount(listAccounts(ctx, apiKey.value))
363
+ if (!account) throw "No Fireworks account found for this API key."
364
+ const quotas = listQuotas(ctx, apiKey.value, account.accountId).map((item) => normalizeQuota(ctx, item)).filter(Boolean)
365
+ const billingUsage = loadBillingUsage(ctx, apiKey.value, account.accountId, nowMs)
366
+ return buildOutput(ctx, account, quotas, billingUsage)
367
+ }
368
+
369
+ globalThis.__openusage_plugin = { id: "fireworks-ai", probe }
370
+ })()
@@ -0,0 +1,19 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "id": "fireworks-ai",
4
+ "name": "Fireworks AI",
5
+ "version": "0.0.1",
6
+ "entry": "plugin.js",
7
+ "icon": "icon.svg",
8
+ "brandColor": "#5019C5",
9
+ "supportedModes": ["api"],
10
+ "autoMode": "api",
11
+ "lines": [
12
+ { "type": "text", "label": "Serverless usage", "scope": "overview" },
13
+ { "type": "text", "label": "Prompt tokens", "scope": "overview" },
14
+ { "type": "text", "label": "Generated tokens", "scope": "overview" },
15
+ { "type": "text", "label": "Month spend", "scope": "overview" },
16
+ { "type": "text", "label": "Budget", "scope": "detail" },
17
+ { "type": "badge", "label": "Status", "scope": "detail" }
18
+ ]
19
+ }
@@ -0,0 +1 @@
1
+ <svg height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>Gemini</title><path d="M20.616 10.835a14.147 14.147 0 01-4.45-3.001 14.111 14.111 0 01-3.678-6.452.503.503 0 00-.975 0 14.134 14.134 0 01-3.679 6.452 14.155 14.155 0 01-4.45 3.001c-.65.28-1.318.505-2.002.678a.502.502 0 000 .975c.684.172 1.35.397 2.002.677a14.147 14.147 0 014.45 3.001 14.112 14.112 0 013.679 6.453.502.502 0 00.975 0c.172-.685.397-1.351.677-2.003a14.145 14.145 0 013.001-4.45 14.113 14.113 0 016.453-3.678.503.503 0 000-.975 13.245 13.245 0 01-2.003-.678z" fill="#3186FF"></path><path d="M20.616 10.835a14.147 14.147 0 01-4.45-3.001 14.111 14.111 0 01-3.678-6.452.503.503 0 00-.975 0 14.134 14.134 0 01-3.679 6.452 14.155 14.155 0 01-4.45 3.001c-.65.28-1.318.505-2.002.678a.502.502 0 000 .975c.684.172 1.35.397 2.002.677a14.147 14.147 0 014.45 3.001 14.112 14.112 0 013.679 6.453.502.502 0 00.975 0c.172-.685.397-1.351.677-2.003a14.145 14.145 0 013.001-4.45 14.113 14.113 0 016.453-3.678.503.503 0 000-.975 13.245 13.245 0 01-2.003-.678z" fill="url(#lobe-icons-gemini-0-_R_0_)"></path><path d="M20.616 10.835a14.147 14.147 0 01-4.45-3.001 14.111 14.111 0 01-3.678-6.452.503.503 0 00-.975 0 14.134 14.134 0 01-3.679 6.452 14.155 14.155 0 01-4.45 3.001c-.65.28-1.318.505-2.002.678a.502.502 0 000 .975c.684.172 1.35.397 2.002.677a14.147 14.147 0 014.45 3.001 14.112 14.112 0 013.679 6.453.502.502 0 00.975 0c.172-.685.397-1.351.677-2.003a14.145 14.145 0 013.001-4.45 14.113 14.113 0 016.453-3.678.503.503 0 000-.975 13.245 13.245 0 01-2.003-.678z" fill="url(#lobe-icons-gemini-1-_R_0_)"></path><path d="M20.616 10.835a14.147 14.147 0 01-4.45-3.001 14.111 14.111 0 01-3.678-6.452.503.503 0 00-.975 0 14.134 14.134 0 01-3.679 6.452 14.155 14.155 0 01-4.45 3.001c-.65.28-1.318.505-2.002.678a.502.502 0 000 .975c.684.172 1.35.397 2.002.677a14.147 14.147 0 014.45 3.001 14.112 14.112 0 013.679 6.453.502.502 0 00.975 0c.172-.685.397-1.351.677-2.003a14.145 14.145 0 013.001-4.45 14.113 14.113 0 016.453-3.678.503.503 0 000-.975 13.245 13.245 0 01-2.003-.678z" fill="url(#lobe-icons-gemini-2-_R_0_)"></path><defs><linearGradient gradientUnits="userSpaceOnUse" id="lobe-icons-gemini-0-_R_0_" x1="7" x2="11" y1="15.5" y2="12"><stop stop-color="#08B962"></stop><stop offset="1" stop-color="#08B962" stop-opacity="0"></stop></linearGradient><linearGradient gradientUnits="userSpaceOnUse" id="lobe-icons-gemini-1-_R_0_" x1="8" x2="11.5" y1="5.5" y2="11"><stop stop-color="#F94543"></stop><stop offset="1" stop-color="#F94543" stop-opacity="0"></stop></linearGradient><linearGradient gradientUnits="userSpaceOnUse" id="lobe-icons-gemini-2-_R_0_" x1="3.5" x2="17.5" y1="13.5" y2="12"><stop stop-color="#FABC12"></stop><stop offset=".46" stop-color="#FABC12" stop-opacity="0"></stop></linearGradient></defs></svg>
@@ -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>Gemini</title><path d="M20.616 10.835a14.147 14.147 0 01-4.45-3.001 14.111 14.111 0 01-3.678-6.452.503.503 0 00-.975 0 14.134 14.134 0 01-3.679 6.452 14.155 14.155 0 01-4.45 3.001c-.65.28-1.318.505-2.002.678a.502.502 0 000 .975c.684.172 1.35.397 2.002.677a14.147 14.147 0 014.45 3.001 14.112 14.112 0 013.679 6.453.502.502 0 00.975 0c.172-.685.397-1.351.677-2.003a14.145 14.145 0 013.001-4.45 14.113 14.113 0 016.453-3.678.503.503 0 000-.975 13.245 13.245 0 01-2.003-.678z"></path></svg>