openusage 0.1.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 (58) hide show
  1. package/bin/openusage +91 -0
  2. package/package.json +33 -0
  3. package/plugins/amp/icon.svg +6 -0
  4. package/plugins/amp/plugin.js +175 -0
  5. package/plugins/amp/plugin.json +20 -0
  6. package/plugins/amp/plugin.test.js +365 -0
  7. package/plugins/antigravity/icon.svg +3 -0
  8. package/plugins/antigravity/plugin.js +484 -0
  9. package/plugins/antigravity/plugin.json +17 -0
  10. package/plugins/antigravity/plugin.test.js +1356 -0
  11. package/plugins/claude/icon.svg +3 -0
  12. package/plugins/claude/plugin.js +565 -0
  13. package/plugins/claude/plugin.json +28 -0
  14. package/plugins/claude/plugin.test.js +1012 -0
  15. package/plugins/codex/icon.svg +3 -0
  16. package/plugins/codex/plugin.js +673 -0
  17. package/plugins/codex/plugin.json +30 -0
  18. package/plugins/codex/plugin.test.js +1071 -0
  19. package/plugins/copilot/icon.svg +3 -0
  20. package/plugins/copilot/plugin.js +264 -0
  21. package/plugins/copilot/plugin.json +20 -0
  22. package/plugins/copilot/plugin.test.js +529 -0
  23. package/plugins/cursor/icon.svg +3 -0
  24. package/plugins/cursor/plugin.js +526 -0
  25. package/plugins/cursor/plugin.json +24 -0
  26. package/plugins/cursor/plugin.test.js +1168 -0
  27. package/plugins/factory/icon.svg +1 -0
  28. package/plugins/factory/plugin.js +407 -0
  29. package/plugins/factory/plugin.json +19 -0
  30. package/plugins/factory/plugin.test.js +833 -0
  31. package/plugins/gemini/icon.svg +4 -0
  32. package/plugins/gemini/plugin.js +413 -0
  33. package/plugins/gemini/plugin.json +20 -0
  34. package/plugins/gemini/plugin.test.js +735 -0
  35. package/plugins/jetbrains-ai-assistant/icon.svg +3 -0
  36. package/plugins/jetbrains-ai-assistant/plugin.js +357 -0
  37. package/plugins/jetbrains-ai-assistant/plugin.json +17 -0
  38. package/plugins/jetbrains-ai-assistant/plugin.test.js +338 -0
  39. package/plugins/kimi/icon.svg +3 -0
  40. package/plugins/kimi/plugin.js +358 -0
  41. package/plugins/kimi/plugin.json +19 -0
  42. package/plugins/kimi/plugin.test.js +619 -0
  43. package/plugins/minimax/icon.svg +4 -0
  44. package/plugins/minimax/plugin.js +388 -0
  45. package/plugins/minimax/plugin.json +17 -0
  46. package/plugins/minimax/plugin.test.js +943 -0
  47. package/plugins/perplexity/icon.svg +1 -0
  48. package/plugins/perplexity/plugin.js +378 -0
  49. package/plugins/perplexity/plugin.json +15 -0
  50. package/plugins/perplexity/plugin.test.js +602 -0
  51. package/plugins/windsurf/icon.svg +3 -0
  52. package/plugins/windsurf/plugin.js +218 -0
  53. package/plugins/windsurf/plugin.json +16 -0
  54. package/plugins/windsurf/plugin.test.js +455 -0
  55. package/plugins/zai/icon.svg +5 -0
  56. package/plugins/zai/plugin.js +156 -0
  57. package/plugins/zai/plugin.json +18 -0
  58. package/plugins/zai/plugin.test.js +396 -0
@@ -0,0 +1,3 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 100 100">
2
+ <path fill="currentColor" d="M13.476 21.473c7.78-4.43 18.478-6.592 27.663-6.592 26.907 0 43.98 12.535 43.98 29.5 0 13.507-11.778 21.828-27.447 21.828-12.643 0-22.044-4.647-22.044-10.806 0-5.835 5.943-8.645 14.372-5.403l2.161-6.592c-13.075-4.214-23.88 1.73-23.88 11.995s12.75 17.613 29.823 17.613c20.315 0 35.12-11.238 35.12-28.635 0-21.72-20.532-37.604-52.085-37.604-10.157 0-21.611 2.593-31.013 7.672zm73.048 57.054c-7.78 4.43-18.478 6.592-27.663 6.592-26.907 0-43.98-12.535-43.98-29.5 0-13.507 11.778-21.828 27.447-21.828 12.643 0 22.044 4.647 22.044 10.806 0 5.835-5.943 8.645-14.372 5.403l-2.161 6.592c13.075 4.214 23.88-1.73 23.88-11.995S58.97 26.984 41.897 26.984c-20.315 0-35.12 11.238-35.12 28.635 0 21.72 20.532 37.604 52.085 37.604 10.157 0 21.611-2.593 31.013-7.672z" />
3
+ </svg>
@@ -0,0 +1,357 @@
1
+ (function () {
2
+ var QUOTA_FILENAME = "AIAssistantQuotaManager2.xml"
3
+
4
+ // JetBrains stores AI quota in 1e-5 credit units; divide by this to get credits.
5
+ var CREDIT_UNIT_SCALE = 100000
6
+
7
+ var PRODUCT_PREFIXES = [
8
+ "Aqua",
9
+ "AndroidStudio",
10
+ "CLion",
11
+ "DataGrip",
12
+ "DataSpell",
13
+ "GoLand",
14
+ "IdeaIC",
15
+ "IntelliJIdea",
16
+ "IntelliJIdeaCE",
17
+ "PhpStorm",
18
+ "PyCharm",
19
+ "PyCharmCE",
20
+ "Rider",
21
+ "RubyMine",
22
+ "RustRover",
23
+ "WebStorm",
24
+ "Writerside",
25
+ ]
26
+
27
+ function platformBaseDirs(platform) {
28
+ if (platform === "macos") {
29
+ return ["~/Library/Application Support/JetBrains"]
30
+ }
31
+ if (platform === "linux") {
32
+ return ["~/.config/JetBrains"]
33
+ }
34
+ if (platform === "windows") {
35
+ return ["~/AppData/Roaming/JetBrains"]
36
+ }
37
+ return [
38
+ "~/Library/Application Support/JetBrains",
39
+ "~/.config/JetBrains",
40
+ "~/AppData/Roaming/JetBrains",
41
+ ]
42
+ }
43
+
44
+ function isLikelyIdeDirName(name) {
45
+ if (typeof name !== "string") return false
46
+ var trimmed = name.trim()
47
+ if (!trimmed) return false
48
+ var hasPrefix = false
49
+ for (var i = 0; i < PRODUCT_PREFIXES.length; i += 1) {
50
+ if (trimmed.indexOf(PRODUCT_PREFIXES[i]) === 0) {
51
+ hasPrefix = true
52
+ break
53
+ }
54
+ }
55
+ if (!hasPrefix) return false
56
+ return /\d{4}\.\d/.test(trimmed)
57
+ }
58
+
59
+ function safeListDir(ctx, path) {
60
+ if (
61
+ !ctx.host.fs ||
62
+ typeof ctx.host.fs.listDir !== "function" ||
63
+ !ctx.host.fs.exists(path)
64
+ ) {
65
+ return []
66
+ }
67
+
68
+ try {
69
+ var entries = ctx.host.fs.listDir(path)
70
+ return Array.isArray(entries) ? entries : []
71
+ } catch (e) {
72
+ ctx.host.log.warn("listDir failed for " + path + ": " + String(e))
73
+ return []
74
+ }
75
+ }
76
+
77
+ function buildQuotaPaths(ctx) {
78
+ var bases = platformBaseDirs(ctx.app.platform)
79
+ var paths = []
80
+ var seen = Object.create(null)
81
+ for (var b = 0; b < bases.length; b += 1) {
82
+ var base = bases[b]
83
+ var entries = safeListDir(ctx, base)
84
+ for (var i = 0; i < entries.length; i += 1) {
85
+ var dirName = entries[i]
86
+ if (!isLikelyIdeDirName(dirName)) continue
87
+ var quotaPath = base + "/" + dirName + "/options/" + QUOTA_FILENAME
88
+ if (!ctx.host.fs.exists(quotaPath)) continue
89
+ if (!seen[quotaPath]) {
90
+ seen[quotaPath] = true
91
+ paths.push(quotaPath)
92
+ }
93
+ }
94
+ }
95
+ return paths
96
+ }
97
+
98
+ function decodeXmlEntities(text) {
99
+ if (!text) return ""
100
+ return String(text)
101
+ .replace(/&#10;/g, "\n")
102
+ .replace(/&#13;/g, "\r")
103
+ .replace(/&quot;/g, '"')
104
+ .replace(/&apos;/g, "'")
105
+ .replace(/&lt;/g, "<")
106
+ .replace(/&gt;/g, ">")
107
+ .replace(/&amp;/g, "&")
108
+ }
109
+
110
+ function parseOptionJson(ctx, xml, optionName) {
111
+ var elemMatch = xml.match(new RegExp('<option\\b[^>]*\\bname="' + optionName + '"[^>]*/>'))
112
+ if (!elemMatch) return null
113
+ var valueMatch = elemMatch[0].match(/\bvalue="([^"]*)"/)
114
+ if (!valueMatch) return null
115
+ return ctx.util.tryParseJson(decodeXmlEntities(valueMatch[1]))
116
+ }
117
+
118
+ function toNumber(value) {
119
+ var n = Number(value)
120
+ return Number.isFinite(n) ? n : null
121
+ }
122
+
123
+ function clamp(value, min, max) {
124
+ if (value < min) return min
125
+ if (value > max) return max
126
+ return value
127
+ }
128
+
129
+ function normalizeQuota(quotaInfo) {
130
+ if (!quotaInfo || typeof quotaInfo !== "object") return null
131
+
132
+ var maximum = toNumber(quotaInfo.maximum)
133
+ var used = toNumber(quotaInfo.current)
134
+ var remaining = toNumber(quotaInfo.available)
135
+
136
+ var tariff = quotaInfo.tariffQuota && typeof quotaInfo.tariffQuota === "object"
137
+ ? quotaInfo.tariffQuota
138
+ : null
139
+ var topUp = quotaInfo.topUpQuota && typeof quotaInfo.topUpQuota === "object"
140
+ ? quotaInfo.topUpQuota
141
+ : null
142
+
143
+ if (maximum === null) {
144
+ var tariffMaximum = tariff ? toNumber(tariff.maximum) : null
145
+ var topUpMaximum = topUp ? toNumber(topUp.maximum) : null
146
+ if (tariffMaximum !== null || topUpMaximum !== null) {
147
+ maximum = (tariffMaximum !== null ? tariffMaximum : 0) + (topUpMaximum !== null ? topUpMaximum : 0)
148
+ }
149
+ }
150
+
151
+ if (used === null) {
152
+ var tariffUsed = tariff ? toNumber(tariff.current) : null
153
+ var topUpUsed = topUp ? toNumber(topUp.current) : null
154
+ if (tariffUsed !== null || topUpUsed !== null) {
155
+ used = (tariffUsed !== null ? tariffUsed : 0) + (topUpUsed !== null ? topUpUsed : 0)
156
+ }
157
+ }
158
+
159
+ if (remaining === null) {
160
+ var tariffRemaining = tariff ? toNumber(tariff.available) : null
161
+ var topUpRemaining = topUp ? toNumber(topUp.available) : null
162
+ if (tariffRemaining !== null || topUpRemaining !== null) {
163
+ remaining = (tariffRemaining !== null ? tariffRemaining : 0) + (topUpRemaining !== null ? topUpRemaining : 0)
164
+ }
165
+ }
166
+
167
+ if (remaining === null && maximum !== null && used !== null) {
168
+ remaining = maximum - used
169
+ }
170
+
171
+ if (maximum === null || maximum <= 0 || used === null) return null
172
+
173
+ used = clamp(used, 0, maximum)
174
+ if (remaining !== null) remaining = clamp(remaining, 0, maximum)
175
+
176
+ return {
177
+ used: used,
178
+ maximum: maximum,
179
+ remaining: remaining,
180
+ until: quotaInfo.until || null,
181
+ }
182
+ }
183
+
184
+ function readQuotaState(ctx, path) {
185
+ if (!ctx.host.fs.exists(path)) return null
186
+ try {
187
+ var xml = ctx.host.fs.readText(path)
188
+ var quotaInfo = parseOptionJson(ctx, xml, "quotaInfo")
189
+ var nextRefill = parseOptionJson(ctx, xml, "nextRefill")
190
+ var quota = normalizeQuota(quotaInfo)
191
+ if (!quota) return null
192
+ return { path: path, quota: quota, nextRefill: nextRefill }
193
+ } catch (e) {
194
+ ctx.host.log.warn("failed reading quota state " + path + ": " + String(e))
195
+ return null
196
+ }
197
+ }
198
+
199
+ // Handles only simple single-component durations (PTnH, PnD, PnW).
200
+ // JetBrains currently uses values like PT720H or P30D.
201
+ function parseIsoDurationMs(value) {
202
+ if (typeof value !== "string" || !value) return null
203
+
204
+ var h = value.match(/^PT(\d+)H$/)
205
+ if (h) return Number(h[1]) * 60 * 60 * 1000
206
+
207
+ var d = value.match(/^P(\d+)D$/)
208
+ if (d) return Number(d[1]) * 24 * 60 * 60 * 1000
209
+
210
+ var w = value.match(/^P(\d+)W$/)
211
+ if (w) return Number(w[1]) * 7 * 24 * 60 * 60 * 1000
212
+
213
+ return null
214
+ }
215
+
216
+ function pickBestState(ctx, states) {
217
+ var best = null
218
+ var bestMs = -Infinity
219
+
220
+ for (var i = 0; i < states.length; i += 1) {
221
+ var state = states[i]
222
+ var untilMs = null
223
+
224
+ if (state.quota.until) {
225
+ untilMs = ctx.util.parseDateMs(state.quota.until)
226
+ }
227
+ if (untilMs === null && state.nextRefill && state.nextRefill.next) {
228
+ untilMs = ctx.util.parseDateMs(state.nextRefill.next)
229
+ }
230
+ if (untilMs === null) untilMs = -Infinity
231
+
232
+ if (!best || untilMs > bestMs) {
233
+ best = state
234
+ bestMs = untilMs
235
+ continue
236
+ }
237
+
238
+ if (untilMs === bestMs) {
239
+ var currentRatio =
240
+ state.quota.maximum > 0 ? state.quota.used / state.quota.maximum : 0
241
+ var bestRatio =
242
+ best.quota.maximum > 0 ? best.quota.used / best.quota.maximum : 0
243
+ if (currentRatio > bestRatio) {
244
+ best = state
245
+ continue
246
+ }
247
+ if (currentRatio === bestRatio && state.quota.used > best.quota.used) {
248
+ best = state
249
+ continue
250
+ }
251
+ }
252
+ }
253
+
254
+ return best
255
+ }
256
+
257
+ function formatDecimal(value, places) {
258
+ if (!Number.isFinite(value)) return null
259
+ var factor = Math.pow(10, places)
260
+ var rounded = Math.round(value * factor) / factor
261
+ return rounded.toFixed(places).replace(/\.?0+$/, "")
262
+ }
263
+
264
+ function detectDisplayScale(quota, nextRefill) {
265
+ var maxAbs = Math.max(
266
+ Math.abs(quota.maximum || 0),
267
+ Math.abs(quota.used || 0),
268
+ Math.abs(quota.remaining || 0)
269
+ )
270
+
271
+ if (nextRefill && nextRefill.tariff && typeof nextRefill.tariff === "object") {
272
+ var tariffAmount = toNumber(nextRefill.tariff.amount)
273
+ if (tariffAmount !== null) {
274
+ maxAbs = Math.max(maxAbs, Math.abs(tariffAmount))
275
+ }
276
+ }
277
+
278
+ if (maxAbs >= CREDIT_UNIT_SCALE) return CREDIT_UNIT_SCALE
279
+ return 1
280
+ }
281
+
282
+ function probe(ctx) {
283
+ var paths = buildQuotaPaths(ctx)
284
+ var states = []
285
+
286
+ for (var i = 0; i < paths.length; i += 1) {
287
+ var state = readQuotaState(ctx, paths[i])
288
+ if (state) states.push(state)
289
+ }
290
+
291
+ if (states.length === 0) {
292
+ throw paths.length > 0
293
+ ? "JetBrains AI Assistant quota data unavailable. Open AI Assistant once and try again."
294
+ : "JetBrains AI Assistant not detected. Open a JetBrains IDE with AI Assistant enabled."
295
+ }
296
+
297
+ var chosen = pickBestState(ctx, states)
298
+ var quota = chosen.quota
299
+ var scale = detectDisplayScale(quota, chosen.nextRefill)
300
+ var usedPercent = (quota.used / quota.maximum) * 100
301
+ if (!Number.isFinite(usedPercent)) usedPercent = 0
302
+ usedPercent = clamp(usedPercent, 0, 100)
303
+ var line = {
304
+ label: "Quota",
305
+ used: usedPercent,
306
+ limit: 100,
307
+ format: { kind: "percent" },
308
+ }
309
+
310
+ var resetSource = null
311
+ if (chosen.nextRefill && chosen.nextRefill.next) {
312
+ resetSource = chosen.nextRefill.next
313
+ } else if (quota.until) {
314
+ resetSource = quota.until
315
+ }
316
+
317
+ var resetsAt = ctx.util.toIso(resetSource)
318
+ if (resetsAt) line.resetsAt = resetsAt
319
+
320
+ var duration = null
321
+ if (
322
+ chosen.nextRefill &&
323
+ chosen.nextRefill.tariff &&
324
+ typeof chosen.nextRefill.tariff === "object"
325
+ ) {
326
+ duration = parseIsoDurationMs(chosen.nextRefill.tariff.duration)
327
+ }
328
+ if (duration) line.periodDurationMs = duration
329
+
330
+ var lines = [ctx.line.progress(line)]
331
+
332
+ lines.push(
333
+ ctx.line.text({
334
+ label: "Used",
335
+ value:
336
+ scale > 1
337
+ ? formatDecimal(quota.used / scale, 2) + " / " + formatDecimal(quota.maximum / scale, 2) + " credits"
338
+ : String(quota.used),
339
+ })
340
+ )
341
+
342
+ if (quota.remaining !== null) {
343
+ lines.push(
344
+ ctx.line.text({
345
+ label: "Remaining",
346
+ value: scale > 1 ? formatDecimal(quota.remaining / scale, 2) + " credits" : String(quota.remaining),
347
+ })
348
+ )
349
+ }
350
+
351
+ ctx.host.log.info("quota loaded from " + chosen.path)
352
+
353
+ return { lines: lines }
354
+ }
355
+
356
+ globalThis.__openusage_plugin = { id: "jetbrains-ai-assistant", probe: probe }
357
+ })()
@@ -0,0 +1,17 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "id": "jetbrains-ai-assistant",
4
+ "name": "JetBrains AI Assistant",
5
+ "version": "0.0.1",
6
+ "entry": "plugin.js",
7
+ "icon": "icon.svg",
8
+ "brandColor": "#7d5fe6",
9
+ "cli": {
10
+ "category": "ide"
11
+ },
12
+ "lines": [
13
+ { "type": "progress", "label": "Quota", "scope": "overview", "primaryOrder": 1 },
14
+ { "type": "text", "label": "Used", "scope": "detail" },
15
+ { "type": "text", "label": "Remaining", "scope": "detail" }
16
+ ]
17
+ }