@hashimkarim/usagestat-linux-x64-gnu 2.0.2 → 2.2.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.
- package/native-manifest.json +808 -148
- package/package.json +112 -2
- package/plugins/abliteration/LICENSE +21 -0
- package/plugins/abliteration/icon.svg +5 -0
- package/plugins/abliteration/plugin.js +89 -0
- package/plugins/abliteration/plugin.json +24 -0
- package/plugins/aerostack/LICENSE +21 -0
- package/plugins/aerostack/icon.svg +1 -0
- package/plugins/aerostack/plugin.js +94 -0
- package/plugins/aerostack/plugin.json +24 -0
- package/plugins/aixy/icon.svg +1 -0
- package/plugins/aixy/plugin.js +5 -0
- package/plugins/aixy/plugin.json +9 -1
- package/plugins/antigravity/plugin.js +19 -10
- package/plugins/antigravity-cli/plugin.js +8 -3
- package/plugins/antigravity-ide/plugin.js +8 -5
- package/plugins/atlascloud/plugin.js +5 -0
- package/plugins/atlascloud/plugin.json +2 -1
- package/plugins/bifrost/icon.svg +3 -0
- package/plugins/bifrost/plugin.js +5 -0
- package/plugins/bifrost/plugin.json +9 -1
- package/plugins/chatbox/LICENSE +21 -0
- package/plugins/chatbox/icon.svg +4 -0
- package/plugins/chatbox/plugin.js +208 -0
- package/plugins/chatbox/plugin.json +24 -0
- package/plugins/claude/plugin.js +39 -2
- package/plugins/claudeplatform/LICENSE +21 -0
- package/plugins/claudeplatform/icon.svg +3 -0
- package/plugins/claudeplatform/plugin.js +244 -0
- package/plugins/claudeplatform/plugin.json +25 -0
- package/plugins/codex/plugin.js +14 -8
- package/plugins/codex/plugin.json +11 -2
- package/plugins/cosmic/LICENSE +21 -0
- package/plugins/cosmic/icon.svg +1 -0
- package/plugins/cosmic/plugin.js +134 -0
- package/plugins/cosmic/plugin.json +32 -0
- package/plugins/cursor/plugin.js +27 -8
- package/plugins/cursor-nightly/plugin.json +1 -1
- package/plugins/devpass/icon.svg +3 -0
- package/plugins/devpass/plugin.js +5 -0
- package/plugins/devpass/plugin.json +2 -1
- package/plugins/exa/LICENSE +21 -0
- package/plugins/exa/icon-color.svg +1 -0
- package/plugins/exa/icon.svg +1 -0
- package/plugins/exa/plugin.js +75 -0
- package/plugins/exa/plugin.json +32 -0
- package/plugins/fal/LICENSE +21 -0
- package/plugins/fal/icon-color.svg +1 -0
- package/plugins/fal/icon.svg +1 -0
- package/plugins/fal/plugin.js +74 -0
- package/plugins/fal/plugin.json +24 -0
- package/plugins/flora/LICENSE +21 -0
- package/plugins/flora/icon.svg +4 -0
- package/plugins/flora/plugin.js +93 -0
- package/plugins/flora/plugin.json +32 -0
- package/plugins/futureagi/LICENSE +21 -0
- package/plugins/futureagi/icon.svg +24 -0
- package/plugins/futureagi/plugin.js +139 -0
- package/plugins/futureagi/plugin.json +32 -0
- package/plugins/gemini-apps/LICENSE +1 -2
- package/plugins/gemini-apps/plugin.js +136 -465
- package/plugins/gemini-apps/plugin.json +13 -7
- package/plugins/gitkraken/icon.svg +2 -0
- package/plugins/gitkraken/plugin.js +5 -0
- package/plugins/gitkraken/plugin.json +9 -1
- package/plugins/googledeveloper/LICENSE +21 -0
- package/plugins/googledeveloper/icon.svg +3 -0
- package/plugins/googledeveloper/plugin.js +204 -0
- package/plugins/googledeveloper/plugin.json +25 -0
- package/plugins/helmcode/icon.svg +5 -0
- package/plugins/helmcode/plugin.js +120 -55
- package/plugins/helmcode/plugin.json +10 -2
- package/plugins/huggingface/plugin.js +6 -1
- package/plugins/huggingface/plugin.json +4 -3
- package/plugins/hyper/plugin.js +53 -47
- package/plugins/hyper/plugin.json +3 -2
- package/plugins/jetbrains-ai-assistant/LICENSE +22 -0
- package/plugins/jetbrains-ai-assistant/plugin.js +502 -16
- package/plugins/jetbrains-ai-assistant/plugin.json +27 -4
- package/plugins/kimi/plugin.js +54 -14
- package/plugins/kimi/plugin.json +46 -6
- package/plugins/langdock/LICENSE +21 -0
- package/plugins/langdock/icon.svg +4 -0
- package/plugins/langdock/plugin.js +106 -0
- package/plugins/langdock/plugin.json +25 -0
- package/plugins/linkup/LICENSE +21 -0
- package/plugins/linkup/icon.svg +10 -0
- package/plugins/linkup/plugin.js +46 -0
- package/plugins/linkup/plugin.json +24 -0
- package/plugins/lithosai/plugin.js +5 -0
- package/plugins/lithosai/plugin.json +2 -1
- package/plugins/llmman/icon.svg +4 -0
- package/plugins/llmman/plugin.js +5 -0
- package/plugins/llmman/plugin.json +9 -1
- package/plugins/munito/LICENSE +21 -0
- package/plugins/munito/icon.svg +7 -0
- package/plugins/munito/plugin.js +171 -0
- package/plugins/munito/plugin.json +24 -0
- package/plugins/muse/icon.svg +3 -0
- package/plugins/muse/plugin.js +5 -0
- package/plugins/muse/plugin.json +9 -1
- package/plugins/museai/plugin.js +10 -3
- package/plugins/museai/plugin.json +3 -2
- package/plugins/nous/plugin.js +5 -0
- package/plugins/nous/plugin.json +9 -1
- package/plugins/ollama/plugin.js +58 -0
- package/plugins/opencode-go/plugin.js +21 -9
- package/plugins/opendesign/LICENSE +21 -0
- package/plugins/opendesign/icon.svg +4 -0
- package/plugins/opendesign/plugin.js +108 -0
- package/plugins/opendesign/plugin.json +25 -0
- package/plugins/parallel/LICENSE +21 -0
- package/plugins/parallel/icon.svg +11 -0
- package/plugins/parallel/plugin.js +97 -0
- package/plugins/parallel/plugin.json +25 -0
- package/plugins/perplexity/LICENSE +22 -0
- package/plugins/perplexity/plugin.js +122 -0
- package/plugins/perplexity/plugin.json +36 -7
- package/plugins/qwencloud/LICENSE +21 -0
- package/plugins/qwencloud/plugin.js +197 -0
- package/plugins/qwencloud/plugin.json +17 -0
- package/plugins/raycast/icon.svg +4 -0
- package/plugins/raycast/plugin.js +5 -0
- package/plugins/raycast/plugin.json +9 -1
- package/plugins/replicate/plugin.js +5 -0
- package/plugins/replicate/plugin.json +2 -1
- package/plugins/sailresearch/LICENSE +21 -0
- package/plugins/sailresearch/icon.svg +4 -0
- package/plugins/sailresearch/plugin.js +104 -0
- package/plugins/sailresearch/plugin.json +24 -0
- package/plugins/siliconflow/LICENSE +21 -0
- package/plugins/siliconflow/icon.svg +3 -0
- package/plugins/siliconflow/plugin.js +87 -0
- package/plugins/siliconflow/plugin.json +32 -0
- package/plugins/sofya/LICENSE +21 -0
- package/plugins/sofya/icon.svg +3 -0
- package/plugins/sofya/plugin.js +86 -0
- package/plugins/sofya/plugin.json +24 -0
- package/plugins/soniox/LICENSE +21 -0
- package/plugins/soniox/icon.svg +3 -0
- package/plugins/soniox/plugin.js +103 -0
- package/plugins/soniox/plugin.json +24 -0
- package/plugins/synthetic/plugin.js +12 -0
- package/plugins/synthetic/plugin.json +51 -9
- package/plugins/tavily/LICENSE +21 -0
- package/plugins/tavily/icon.svg +3 -0
- package/plugins/tavily/plugin.js +61 -0
- package/plugins/tavily/plugin.json +24 -0
- package/plugins/tinyapi/LICENSE +21 -0
- package/plugins/tinyapi/icon.svg +4 -0
- package/plugins/tinyapi/plugin.js +73 -0
- package/plugins/tinyapi/plugin.json +25 -0
- package/plugins/traecn/LICENSE +21 -0
- package/plugins/traecn/icon.svg +3 -0
- package/plugins/traecn/plugin.js +103 -0
- package/plugins/traecn/plugin.json +25 -0
- package/plugins/tripo/LICENSE +21 -0
- package/plugins/tripo/icon.svg +4 -0
- package/plugins/tripo/plugin.js +60 -0
- package/plugins/tripo/plugin.json +24 -0
- package/plugins/typesafe/icon.svg +4 -0
- package/plugins/typesafe/plugin.js +85 -129
- package/plugins/typesafe/plugin.json +3 -2
- package/plugins/v0/plugin.js +5 -0
- package/plugins/v0/plugin.json +9 -1
- package/plugins/vercel/plugin.js +147 -0
- package/plugins/vercel/plugin.json +34 -3
- package/plugins/workbuddy/icon.svg +1 -1
- package/plugins/workbuddy/plugin.js +110 -19
- package/plugins/workbuddy/plugin.json +32 -2
- package/plugins/xai/plugin.js +9 -0
- package/plugins/xapi/LICENSE +21 -0
- package/plugins/xapi/icon.svg +1 -0
- package/plugins/xapi/plugin.js +90 -0
- package/plugins/xapi/plugin.json +25 -0
- package/plugins/xkiro/icon.svg +1 -0
- package/plugins/xkiro/plugin.js +5 -0
- package/plugins/xkiro/plugin.json +2 -1
- package/usagestat +0 -0
- package/usagestatd +0 -0
- package/plugins/cursor-nightly/plugin.js +0 -1416
|
@@ -1,9 +1,30 @@
|
|
|
1
|
+
// Adapted from barramee27/crossusage at 78b243b798abce1270d6fed775c79e65825b2c33; see LICENSE.
|
|
1
2
|
(function () {
|
|
2
3
|
var QUOTA_FILENAME = "AIAssistantQuotaManager2.xml"
|
|
4
|
+
var JUNIE_INSTANCES_DIR = "~/.junie/instances"
|
|
5
|
+
var JUNIE_SESSIONS_DIR = "~/.junie/sessions"
|
|
3
6
|
|
|
4
7
|
// JetBrains stores AI quota in 1e-5 credit units; divide by this to get credits.
|
|
5
8
|
var CREDIT_UNIT_SCALE = 100000
|
|
6
9
|
|
|
10
|
+
// Included credits on current JetBrains plans. The Aug 2025 currency model used
|
|
11
|
+
// 10 (Pro) and 35 (Ultimate); those caps clamp a live balance that is still
|
|
12
|
+
// inside the real tariff, and top-ups can sit above either number.
|
|
13
|
+
var AI_PRO_CREDITS = 20
|
|
14
|
+
var AI_ULTIMATE_CREDITS = 70
|
|
15
|
+
var UNKNOWN_PLAN_CREDIT_FLOOR = 10
|
|
16
|
+
// An undated IDE quota file has no mtime. Sessions older than this do not
|
|
17
|
+
// outrank it; a dated idea.log is compared by its own timestamp instead.
|
|
18
|
+
var SESSION_UNDATED_LIVE_MAX_AGE_MS = 48 * 60 * 60 * 1000
|
|
19
|
+
|
|
20
|
+
var LICENSE_NAMES = {
|
|
21
|
+
AIP: "JetBrains AI Pro",
|
|
22
|
+
AIPU: "JetBrains AI Ultimate",
|
|
23
|
+
AIF: "JetBrains AI Free",
|
|
24
|
+
TRIAL: "JetBrains AI Trial",
|
|
25
|
+
JUNP: "Junie EAP",
|
|
26
|
+
}
|
|
27
|
+
|
|
7
28
|
var PRODUCT_PREFIXES = [
|
|
8
29
|
"Aqua",
|
|
9
30
|
"AndroidStudio",
|
|
@@ -30,6 +51,12 @@
|
|
|
30
51
|
return [root]
|
|
31
52
|
}
|
|
32
53
|
|
|
54
|
+
function platformLogDirs(ctx) {
|
|
55
|
+
if (ctx.app.platform === "macos" || ctx.app.platform === "darwin") return [ctx.host.fs.homeDir+"/Library/Logs/JetBrains"]
|
|
56
|
+
if (ctx.app.platform === "windows") return [ctx.host.fs.localAppDataPath("JetBrains")].filter(Boolean)
|
|
57
|
+
return ctx.host.fs.cacheDir ? [ctx.host.fs.cacheDir+"/JetBrains"] : []
|
|
58
|
+
}
|
|
59
|
+
|
|
33
60
|
function isLikelyIdeDirName(name) {
|
|
34
61
|
if (typeof name !== "string") return false
|
|
35
62
|
var trimmed = name.trim()
|
|
@@ -56,7 +83,7 @@
|
|
|
56
83
|
|
|
57
84
|
try {
|
|
58
85
|
var entries = ctx.host.fs.listDir(path)
|
|
59
|
-
return Array.isArray(entries) ? entries : []
|
|
86
|
+
return Array.isArray(entries) ? entries.slice().sort().reverse().slice(0,64) : []
|
|
60
87
|
} catch (e) {
|
|
61
88
|
ctx.host.log.warn("listDir failed for " + path + ": " + String(e))
|
|
62
89
|
return []
|
|
@@ -89,6 +116,33 @@
|
|
|
89
116
|
return paths
|
|
90
117
|
}
|
|
91
118
|
|
|
119
|
+
function buildIdeaLogPaths(ctx) {
|
|
120
|
+
var bases = platformLogDirs(ctx)
|
|
121
|
+
var paths = []
|
|
122
|
+
var seen = Object.create(null)
|
|
123
|
+
for (var b = 0; b < bases.length; b += 1) {
|
|
124
|
+
var base = bases[b]
|
|
125
|
+
var entries = safeListDir(ctx, base)
|
|
126
|
+
for (var i = 0; i < entries.length; i += 1) {
|
|
127
|
+
var dirName = entries[i]
|
|
128
|
+
if (!isLikelyIdeDirName(dirName)) continue
|
|
129
|
+
var candidates = [
|
|
130
|
+
base + "/" + dirName + "/log/idea.log",
|
|
131
|
+
base + "/" + dirName + "/idea.log",
|
|
132
|
+
]
|
|
133
|
+
for (var c = 0; c < candidates.length; c += 1) {
|
|
134
|
+
var logPath = candidates[c]
|
|
135
|
+
if (!ctx.host.fs.exists(logPath)) continue
|
|
136
|
+
if (!seen[logPath]) {
|
|
137
|
+
seen[logPath] = true
|
|
138
|
+
paths.push(logPath)
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return paths
|
|
144
|
+
}
|
|
145
|
+
|
|
92
146
|
function decodeXmlEntities(text) {
|
|
93
147
|
if (!text) return ""
|
|
94
148
|
return String(text)
|
|
@@ -110,6 +164,7 @@
|
|
|
110
164
|
}
|
|
111
165
|
|
|
112
166
|
function toNumber(value) {
|
|
167
|
+
if (value === null || value === undefined || value === "") return null
|
|
113
168
|
var n = Number(value)
|
|
114
169
|
return Number.isFinite(n) ? n : null
|
|
115
170
|
}
|
|
@@ -120,12 +175,30 @@
|
|
|
120
175
|
return value
|
|
121
176
|
}
|
|
122
177
|
|
|
178
|
+
function resolvePlanLabel(licenseName, licenseType) {
|
|
179
|
+
if (typeof licenseName === "string" && licenseName.trim()) {
|
|
180
|
+
return licenseName.trim()
|
|
181
|
+
}
|
|
182
|
+
if (typeof licenseType === "string" && licenseType.trim()) {
|
|
183
|
+
var code = licenseType.trim()
|
|
184
|
+
if (LICENSE_NAMES[code]) return LICENSE_NAMES[code]
|
|
185
|
+
return code
|
|
186
|
+
}
|
|
187
|
+
return null
|
|
188
|
+
}
|
|
189
|
+
|
|
123
190
|
function normalizeQuota(quotaInfo) {
|
|
124
191
|
if (!quotaInfo || typeof quotaInfo !== "object") return null
|
|
125
192
|
|
|
126
193
|
var maximum = toNumber(quotaInfo.maximum)
|
|
194
|
+
if (maximum === null) maximum = toNumber(quotaInfo.creditsMaximum)
|
|
195
|
+
|
|
127
196
|
var used = toNumber(quotaInfo.current)
|
|
197
|
+
if (used === null) used = toNumber(quotaInfo.creditsUsed)
|
|
198
|
+
|
|
128
199
|
var remaining = toNumber(quotaInfo.available)
|
|
200
|
+
if (remaining === null) remaining = toNumber(quotaInfo.creditsAvailable)
|
|
201
|
+
if (remaining === null) remaining = toNumber(quotaInfo.balanceLeft)
|
|
129
202
|
|
|
130
203
|
var tariff = quotaInfo.tariffQuota && typeof quotaInfo.tariffQuota === "object"
|
|
131
204
|
? quotaInfo.tariffQuota
|
|
@@ -158,6 +231,10 @@
|
|
|
158
231
|
}
|
|
159
232
|
}
|
|
160
233
|
|
|
234
|
+
if (used === null && maximum !== null && remaining !== null) {
|
|
235
|
+
used = maximum - remaining
|
|
236
|
+
}
|
|
237
|
+
|
|
161
238
|
if (remaining === null && maximum !== null && used !== null) {
|
|
162
239
|
remaining = maximum - used
|
|
163
240
|
}
|
|
@@ -167,31 +244,304 @@
|
|
|
167
244
|
used = clamp(used, 0, maximum)
|
|
168
245
|
if (remaining !== null) remaining = clamp(remaining, 0, maximum)
|
|
169
246
|
|
|
247
|
+
var until = quotaInfo.until || null
|
|
248
|
+
if (!until && quotaInfo.nextRefillMillis) {
|
|
249
|
+
var refillMs = toNumber(quotaInfo.nextRefillMillis)
|
|
250
|
+
if (refillMs !== null && refillMs > 0) {
|
|
251
|
+
until = new Date(refillMs).toISOString()
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
170
255
|
return {
|
|
171
256
|
used: used,
|
|
172
257
|
maximum: maximum,
|
|
173
258
|
remaining: remaining,
|
|
174
|
-
until:
|
|
259
|
+
until: until,
|
|
260
|
+
plan: resolvePlanLabel(quotaInfo.licenseName, quotaInfo.licenseType),
|
|
175
261
|
}
|
|
176
262
|
}
|
|
177
263
|
|
|
178
264
|
function readQuotaState(ctx, path) {
|
|
179
265
|
if (!ctx.host.fs.exists(path)) return null
|
|
180
266
|
try {
|
|
181
|
-
var xml = ctx.host.fs.
|
|
267
|
+
var xml = ctx.host.fs.readTextLimited(path, 2097152)
|
|
182
268
|
var quotaInfo = parseOptionJson(ctx, xml, "quotaInfo")
|
|
183
269
|
var nextRefill = parseOptionJson(ctx, xml, "nextRefill")
|
|
184
270
|
var quota = normalizeQuota(quotaInfo)
|
|
185
271
|
if (!quota) return null
|
|
186
|
-
return { path: path, quota: quota, nextRefill: nextRefill }
|
|
272
|
+
return { path: path, quota: quota, nextRefill: nextRefill, tier: 5 }
|
|
187
273
|
} catch (e) {
|
|
188
274
|
ctx.host.log.warn("failed reading quota state " + path + ": " + String(e))
|
|
189
275
|
return null
|
|
190
276
|
}
|
|
191
277
|
}
|
|
192
278
|
|
|
193
|
-
//
|
|
194
|
-
|
|
279
|
+
// Tier 1: Live JetBrains IDE Local JSON-RPC Bridge (~/.junie/instances/*.json)
|
|
280
|
+
function probeLiveIdeRpc(ctx) {
|
|
281
|
+
if (!ctx.host.http || typeof ctx.host.http.request !== "function") return []
|
|
282
|
+
var entries = safeListDir(ctx, JUNIE_INSTANCES_DIR)
|
|
283
|
+
var states = []
|
|
284
|
+
|
|
285
|
+
for (var i = 0; i < entries.length; i += 1) {
|
|
286
|
+
var fileName = entries[i]
|
|
287
|
+
if (typeof fileName !== "string" || !/\.json$/i.test(fileName)) continue
|
|
288
|
+
var instancePath = JUNIE_INSTANCES_DIR + "/" + fileName
|
|
289
|
+
try {
|
|
290
|
+
var raw = ctx.host.fs.readTextLimited(instancePath, 2097152)
|
|
291
|
+
var instance = ctx.util.tryParseJson(raw)
|
|
292
|
+
if (!instance || typeof instance !== "object") continue
|
|
293
|
+
var port = toNumber(instance.port)
|
|
294
|
+
var rpcToken = typeof instance.rpc_token === "string" ? instance.rpc_token.trim() : ""
|
|
295
|
+
if (!port || port <= 0 || port > 65535 || !rpcToken) continue
|
|
296
|
+
|
|
297
|
+
var resp = ctx.host.http.request({
|
|
298
|
+
method: "POST",
|
|
299
|
+
url: "http://127.0.0.1:" + String(port) + "/rpc",
|
|
300
|
+
headers: {
|
|
301
|
+
"Content-Type": "application/json",
|
|
302
|
+
Authorization: rpcToken,
|
|
303
|
+
},
|
|
304
|
+
bodyText: JSON.stringify({
|
|
305
|
+
jsonrpc: "2.0",
|
|
306
|
+
id: 1,
|
|
307
|
+
method: "JunieSystemService.getQuotaInfo",
|
|
308
|
+
params: {},
|
|
309
|
+
}),
|
|
310
|
+
timeoutMs: 1500,
|
|
311
|
+
})
|
|
312
|
+
|
|
313
|
+
if (!resp || resp.status < 200 || resp.status >= 300 || !resp.bodyText) continue
|
|
314
|
+
var payload = ctx.util.tryParseJson(resp.bodyText)
|
|
315
|
+
if (!payload || !payload.result || payload.result.error) continue
|
|
316
|
+
|
|
317
|
+
var quota = normalizeQuota(payload.result)
|
|
318
|
+
if (!quota) continue
|
|
319
|
+
|
|
320
|
+
var nextRefill = null
|
|
321
|
+
if (payload.result.nextRefillMillis) {
|
|
322
|
+
var refillMs = toNumber(payload.result.nextRefillMillis)
|
|
323
|
+
if (refillMs !== null && refillMs > 0) {
|
|
324
|
+
nextRefill = { next: new Date(refillMs).toISOString() }
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
states.push({
|
|
329
|
+
path: "rpc://127.0.0.1:" + String(port) + " (" + fileName + ")",
|
|
330
|
+
quota: quota,
|
|
331
|
+
nextRefill: nextRefill,
|
|
332
|
+
tier: 1,
|
|
333
|
+
})
|
|
334
|
+
} catch (e) {
|
|
335
|
+
ctx.host.log.warn("IDE RPC probe skipped for " + instancePath + ": " + String(e))
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
return states
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
function coerceEpochMs(ctx, value) {
|
|
343
|
+
var n = toNumber(value)
|
|
344
|
+
if (n !== null) {
|
|
345
|
+
if (n <= 0) return null
|
|
346
|
+
return n < 1000000000000 ? n * 1000 : n
|
|
347
|
+
}
|
|
348
|
+
if (typeof value === "string" && value.trim()) {
|
|
349
|
+
return ctx.util.parseDateMs(value.trim())
|
|
350
|
+
}
|
|
351
|
+
return null
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
function readObservedAtMs(ctx, value) {
|
|
355
|
+
if (!value || typeof value !== "object") return null
|
|
356
|
+
var keys = ["timestampMs", "timestamp", "timeMs", "createdAt", "updatedAt"]
|
|
357
|
+
for (var i = 0; i < keys.length; i += 1) {
|
|
358
|
+
var ms = coerceEpochMs(ctx, value[keys[i]])
|
|
359
|
+
if (ms !== null) return ms
|
|
360
|
+
}
|
|
361
|
+
return null
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
function eventObservedAtMs(ctx, parsed) {
|
|
365
|
+
var direct = readObservedAtMs(ctx, parsed)
|
|
366
|
+
if (direct !== null) return direct
|
|
367
|
+
if (!parsed || typeof parsed !== "object") return null
|
|
368
|
+
var fromEvent = readObservedAtMs(ctx, parsed.event)
|
|
369
|
+
if (fromEvent !== null) return fromEvent
|
|
370
|
+
return readObservedAtMs(ctx, parsed.completion)
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
function sessionIdObservedAtMs(sessionId) {
|
|
374
|
+
if (typeof sessionId !== "string") return null
|
|
375
|
+
var match = sessionId.match(/^session-(\d{2})(\d{2})(\d{2})-(\d{2})(\d{2})(\d{2})(?:-|$)/)
|
|
376
|
+
if (!match) return null
|
|
377
|
+
var ms = Date.parse(
|
|
378
|
+
"20" +
|
|
379
|
+
match[1] +
|
|
380
|
+
"-" +
|
|
381
|
+
match[2] +
|
|
382
|
+
"-" +
|
|
383
|
+
match[3] +
|
|
384
|
+
"T" +
|
|
385
|
+
match[4] +
|
|
386
|
+
":" +
|
|
387
|
+
match[5] +
|
|
388
|
+
":" +
|
|
389
|
+
match[6] +
|
|
390
|
+
"Z"
|
|
391
|
+
)
|
|
392
|
+
return Number.isFinite(ms) ? ms : null
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
function readJunieSessionIndexTimes(ctx) {
|
|
396
|
+
var times = Object.create(null)
|
|
397
|
+
var indexPath = JUNIE_SESSIONS_DIR + "/index.jsonl"
|
|
398
|
+
if (!ctx.host.fs.exists(indexPath)) return times
|
|
399
|
+
try {
|
|
400
|
+
var text = ctx.host.fs.readTextLimited(indexPath, 2097152)
|
|
401
|
+
if (!text) return times
|
|
402
|
+
var lines = text.split(/\r?\n/)
|
|
403
|
+
for (var i = 0; i < lines.length; i += 1) {
|
|
404
|
+
var line = lines[i].trim()
|
|
405
|
+
if (!line) continue
|
|
406
|
+
var row = ctx.util.tryParseJson(line)
|
|
407
|
+
if (!row || typeof row.sessionId !== "string") continue
|
|
408
|
+
var at = coerceEpochMs(ctx, row.updatedAt)
|
|
409
|
+
if (at === null) at = coerceEpochMs(ctx, row.createdAt)
|
|
410
|
+
if (at !== null) times[row.sessionId] = at
|
|
411
|
+
}
|
|
412
|
+
} catch (e) {
|
|
413
|
+
ctx.host.log.warn("failed reading junie session index: " + String(e))
|
|
414
|
+
}
|
|
415
|
+
return times
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
function logLineObservedAtMs(line) {
|
|
419
|
+
if (typeof line !== "string") return null
|
|
420
|
+
var match = line.match(/^(\d{4}-\d{2}-\d{2})[ T](\d{2}:\d{2}:\d{2})(?:,(\d{1,3}))?/)
|
|
421
|
+
if (!match) return null
|
|
422
|
+
var frac = match[3] || "000"
|
|
423
|
+
if (frac.length > 3) frac = frac.slice(0, 3)
|
|
424
|
+
while (frac.length < 3) frac = frac + "0"
|
|
425
|
+
// idea.log stamps are local wall time (ZoneId.systemDefault) and carry no offset.
|
|
426
|
+
// Date.parse treats a date-time without Z as local. Appending Z would read that
|
|
427
|
+
// wall clock as UTC and mis-rank it against real Unix session timestamps.
|
|
428
|
+
var ms = Date.parse(match[1] + "T" + match[2] + "." + frac)
|
|
429
|
+
return Number.isFinite(ms) ? ms : null
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
// Tier 3: Junie CLI Session Logs (~/.junie/sessions/*/events.jsonl)
|
|
433
|
+
// Each session keeps the observation time of its newest quota event so a
|
|
434
|
+
// leftover folder cannot outrank a later idea.log or XML snapshot.
|
|
435
|
+
function probeJunieSessions(ctx) {
|
|
436
|
+
var sessionDirs = safeListDir(ctx, JUNIE_SESSIONS_DIR)
|
|
437
|
+
var indexTimes = readJunieSessionIndexTimes(ctx)
|
|
438
|
+
var states = []
|
|
439
|
+
|
|
440
|
+
for (var i = 0; i < sessionDirs.length; i += 1) {
|
|
441
|
+
var sessionId = sessionDirs[i]
|
|
442
|
+
var eventsPath = JUNIE_SESSIONS_DIR + "/" + sessionId + "/events.jsonl"
|
|
443
|
+
if (!ctx.host.fs.exists(eventsPath)) continue
|
|
444
|
+
try {
|
|
445
|
+
var text = ctx.host.fs.readTextLimited(eventsPath, 2097152)
|
|
446
|
+
if (!text) continue
|
|
447
|
+
var lines = text.split(/\r?\n/)
|
|
448
|
+
for (var l = lines.length - 1; l >= 0; l -= 1) {
|
|
449
|
+
var line = lines[l].trim()
|
|
450
|
+
if (!line || line.indexOf("quota") === -1) continue
|
|
451
|
+
var parsed = ctx.util.tryParseJson(line)
|
|
452
|
+
if (!parsed || typeof parsed !== "object") continue
|
|
453
|
+
var candidate =
|
|
454
|
+
(parsed.completion && parsed.completion.quota) ||
|
|
455
|
+
parsed.quotaInfo ||
|
|
456
|
+
parsed.quota ||
|
|
457
|
+
null
|
|
458
|
+
if (!candidate) continue
|
|
459
|
+
var quota = normalizeQuota(candidate)
|
|
460
|
+
if (quota) {
|
|
461
|
+
var observedAt = eventObservedAtMs(ctx, parsed)
|
|
462
|
+
if (observedAt === null && typeof indexTimes[sessionId] === "number") {
|
|
463
|
+
observedAt = indexTimes[sessionId]
|
|
464
|
+
}
|
|
465
|
+
if (observedAt === null) observedAt = sessionIdObservedAtMs(sessionId)
|
|
466
|
+
states.push({
|
|
467
|
+
path: eventsPath,
|
|
468
|
+
quota: quota,
|
|
469
|
+
nextRefill: candidate.nextRefill || null,
|
|
470
|
+
tier: 3,
|
|
471
|
+
observedAtMs: observedAt,
|
|
472
|
+
})
|
|
473
|
+
break
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
} catch (e) {
|
|
477
|
+
ctx.host.log.warn("failed reading junie session " + eventsPath + ": " + String(e))
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
return states
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
// Tier 4: IDE Application Logs (idea.log -> QuotaManager2Impl)
|
|
485
|
+
function extractLastLogQuotaState(ctx, logPath) {
|
|
486
|
+
try {
|
|
487
|
+
var text = ctx.host.fs.readTextLimited(logPath, 2097152)
|
|
488
|
+
if (!text || text.indexOf("QuotaManager2") === -1) return null
|
|
489
|
+
var lines = text.split(/\r?\n/)
|
|
490
|
+
var latestQuota = null
|
|
491
|
+
var latestRefill = null
|
|
492
|
+
var latestObservedAt = null
|
|
493
|
+
|
|
494
|
+
for (var i = lines.length - 1; i >= 0; i -= 1) {
|
|
495
|
+
var line = lines[i]
|
|
496
|
+
if (!latestQuota && line.indexOf("New quota info") !== -1) {
|
|
497
|
+
var braceIdx = line.indexOf("{")
|
|
498
|
+
if (braceIdx !== -1) {
|
|
499
|
+
var jsonCandidate = line.slice(braceIdx).trim()
|
|
500
|
+
var parsed = ctx.util.tryParseJson(jsonCandidate)
|
|
501
|
+
if (parsed) {
|
|
502
|
+
latestQuota = normalizeQuota(parsed)
|
|
503
|
+
if (latestQuota) latestObservedAt = logLineObservedAtMs(line)
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
if (!latestRefill && line.indexOf("New next refill") !== -1) {
|
|
508
|
+
var refillBraceIdx = line.indexOf("{")
|
|
509
|
+
if (refillBraceIdx !== -1) {
|
|
510
|
+
latestRefill = ctx.util.tryParseJson(line.slice(refillBraceIdx).trim())
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
if (latestQuota && latestRefill) break
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
if (!latestQuota) return null
|
|
517
|
+
return {
|
|
518
|
+
path: logPath,
|
|
519
|
+
quota: latestQuota,
|
|
520
|
+
nextRefill: latestRefill,
|
|
521
|
+
tier: 4,
|
|
522
|
+
observedAtMs: latestObservedAt,
|
|
523
|
+
}
|
|
524
|
+
} catch (e) {
|
|
525
|
+
ctx.host.log.warn("failed reading idea.log " + logPath + ": " + String(e))
|
|
526
|
+
return null
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
function probeIdeaLogs(ctx) {
|
|
531
|
+
var logPaths = buildIdeaLogPaths(ctx)
|
|
532
|
+
var states = []
|
|
533
|
+
for (var i = 0; i < logPaths.length; i += 1) {
|
|
534
|
+
var state = extractLastLogQuotaState(ctx, logPaths[i])
|
|
535
|
+
if (state) states.push(state)
|
|
536
|
+
}
|
|
537
|
+
return states
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
// Tier 2: Junie Cloud API (~/.junie/secure_credentials.json or OS keychain -> Ingrazzio /auth/test)
|
|
541
|
+
function probeNowMs(ctx) {
|
|
542
|
+
return Date.parse(ctx.nowIso) || Date.now()
|
|
543
|
+
}
|
|
544
|
+
|
|
195
545
|
function parseIsoDurationMs(value) {
|
|
196
546
|
if (typeof value !== "string" || !value) return null
|
|
197
547
|
|
|
@@ -207,6 +557,100 @@
|
|
|
207
557
|
return null
|
|
208
558
|
}
|
|
209
559
|
|
|
560
|
+
function filterSessionStates(ctx, sessionStates, liveStates) {
|
|
561
|
+
if (!liveStates || liveStates.length === 0) return sessionStates
|
|
562
|
+
var newestLiveAt = null
|
|
563
|
+
for (var i = 0; i < liveStates.length; i += 1) {
|
|
564
|
+
var liveAt = liveStates[i].observedAtMs
|
|
565
|
+
if (typeof liveAt === "number" && (newestLiveAt === null || liveAt > newestLiveAt)) {
|
|
566
|
+
newestLiveAt = liveAt
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
var nowMs = ctx.nowIso ? ctx.util.parseDateMs(ctx.nowIso) : null
|
|
570
|
+
if (nowMs === null) nowMs = Date.now()
|
|
571
|
+
var kept = []
|
|
572
|
+
for (var s = 0; s < sessionStates.length; s += 1) {
|
|
573
|
+
var session = sessionStates[s]
|
|
574
|
+
var sessionAt = session.observedAtMs
|
|
575
|
+
if (typeof sessionAt !== "number") continue
|
|
576
|
+
if (newestLiveAt !== null) {
|
|
577
|
+
if (sessionAt >= newestLiveAt) kept.push(session)
|
|
578
|
+
continue
|
|
579
|
+
}
|
|
580
|
+
if (nowMs - sessionAt <= SESSION_UNDATED_LIVE_MAX_AGE_MS) kept.push(session)
|
|
581
|
+
}
|
|
582
|
+
return kept
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
function localSourceRank(tier) {
|
|
586
|
+
var rank = tier || 5
|
|
587
|
+
// Session logs share `until` with idea.log and XML. That timestamp is the
|
|
588
|
+
// billing-window end, so a lower tier number must not win the tie.
|
|
589
|
+
if (rank === 3) return 6
|
|
590
|
+
return rank
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
function pickBestState(ctx, states) {
|
|
594
|
+
var best = null
|
|
595
|
+
var bestMs = -Infinity
|
|
596
|
+
|
|
597
|
+
for (var i = 0; i < states.length; i += 1) {
|
|
598
|
+
var state = states[i]
|
|
599
|
+
var untilMs = null
|
|
600
|
+
|
|
601
|
+
if (state.quota.until) {
|
|
602
|
+
untilMs = ctx.util.parseDateMs(state.quota.until)
|
|
603
|
+
}
|
|
604
|
+
if (untilMs === null && state.nextRefill && state.nextRefill.next) {
|
|
605
|
+
untilMs = ctx.util.parseDateMs(state.nextRefill.next)
|
|
606
|
+
}
|
|
607
|
+
if (untilMs === null) untilMs = -Infinity
|
|
608
|
+
|
|
609
|
+
if (!best || untilMs > bestMs) {
|
|
610
|
+
best = state
|
|
611
|
+
bestMs = untilMs
|
|
612
|
+
continue
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
if (untilMs === bestMs) {
|
|
616
|
+
var stateAt = typeof state.observedAtMs === "number" ? state.observedAtMs : null
|
|
617
|
+
var bestAt = typeof best.observedAtMs === "number" ? best.observedAtMs : null
|
|
618
|
+
if (stateAt !== null && bestAt !== null && stateAt !== bestAt) {
|
|
619
|
+
if (stateAt > bestAt) {
|
|
620
|
+
best = state
|
|
621
|
+
continue
|
|
622
|
+
}
|
|
623
|
+
continue
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
var stateRank = localSourceRank(state.tier)
|
|
627
|
+
var bestRank = localSourceRank(best.tier)
|
|
628
|
+
if (stateRank < bestRank) {
|
|
629
|
+
best = state
|
|
630
|
+
continue
|
|
631
|
+
}
|
|
632
|
+
if (stateRank > bestRank) {
|
|
633
|
+
continue
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
var currentRatio =
|
|
637
|
+
state.quota.maximum > 0 ? state.quota.used / state.quota.maximum : 0
|
|
638
|
+
var bestRatio =
|
|
639
|
+
best.quota.maximum > 0 ? best.quota.used / best.quota.maximum : 0
|
|
640
|
+
if (currentRatio > bestRatio) {
|
|
641
|
+
best = state
|
|
642
|
+
continue
|
|
643
|
+
}
|
|
644
|
+
if (currentRatio === bestRatio && state.quota.used > best.quota.used) {
|
|
645
|
+
best = state
|
|
646
|
+
continue
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
return best
|
|
652
|
+
}
|
|
653
|
+
|
|
210
654
|
function formatDecimal(value, places) {
|
|
211
655
|
if (!Number.isFinite(value)) return null
|
|
212
656
|
var factor = Math.pow(10, places)
|
|
@@ -233,24 +677,64 @@
|
|
|
233
677
|
}
|
|
234
678
|
|
|
235
679
|
function probe(ctx) {
|
|
236
|
-
|
|
237
|
-
|
|
680
|
+
if (ctx.provider?.instanceId && ctx.provider.instanceId !== "jetbrains-ai-assistant")
|
|
681
|
+
throw "JetBrains local observations are available only for the default account."
|
|
238
682
|
|
|
683
|
+
// 1. Check live running JetBrains IDEs via Junie local JSON-RPC bridge first
|
|
684
|
+
var settings = ctx.provider?.settings || {}
|
|
685
|
+
var paths = buildQuotaPaths(ctx)
|
|
686
|
+
if (paths.length > 1 && !settings.configDir)
|
|
687
|
+
throw {code:"failed",message:"Multiple JetBrains IDE quota profiles found. Select settings.configDir to choose one installation."}
|
|
688
|
+
// Junie observations may span IDEs. Require an explicit opt-in, and never
|
|
689
|
+
// borrow them when an individual configuration directory was selected.
|
|
690
|
+
var live = settings.liveQuota === true && !settings.configDir
|
|
691
|
+
var rpcStates = live ? probeLiveIdeRpc(ctx) : []
|
|
692
|
+
|
|
693
|
+
// 2. Gather local XML, session, and idea.log states
|
|
694
|
+
var xmlStates = []
|
|
239
695
|
for (var i = 0; i < paths.length; i += 1) {
|
|
240
|
-
var
|
|
241
|
-
if (
|
|
696
|
+
var xmlState = readQuotaState(ctx, paths[i])
|
|
697
|
+
if (xmlState) xmlStates.push(xmlState)
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
var sessionStates = live ? probeJunieSessions(ctx) : []
|
|
701
|
+
var logStates = live ? probeIdeaLogs(ctx) : []
|
|
702
|
+
var liveStates = logStates.concat(xmlStates)
|
|
703
|
+
// Drop session snapshots that are older than the live IDE files. A shared
|
|
704
|
+
// billing-window `until` would otherwise let a leftover events.jsonl win,
|
|
705
|
+
var sessionCandidates = filterSessionStates(ctx, sessionStates, liveStates)
|
|
706
|
+
|
|
707
|
+
var localFallbackBest = pickBestState(ctx, sessionCandidates.concat(liveStates))
|
|
708
|
+
|
|
709
|
+
var chosen = null
|
|
710
|
+
if (rpcStates.length > 0) {
|
|
711
|
+
chosen = pickBestState(ctx, rpcStates)
|
|
712
|
+
// Preserve tariff duration metadata from XML if RPC response only has nextRefillMillis
|
|
713
|
+
if (
|
|
714
|
+
chosen &&
|
|
715
|
+
localFallbackBest &&
|
|
716
|
+
localFallbackBest.nextRefill &&
|
|
717
|
+
localFallbackBest.nextRefill.tariff &&
|
|
718
|
+
(!chosen.nextRefill || !chosen.nextRefill.tariff)
|
|
719
|
+
) {
|
|
720
|
+
chosen.nextRefill = {
|
|
721
|
+
next:
|
|
722
|
+
(chosen.nextRefill && chosen.nextRefill.next) ||
|
|
723
|
+
localFallbackBest.nextRefill.next ||
|
|
724
|
+
null,
|
|
725
|
+
tariff: localFallbackBest.nextRefill.tariff,
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
} else {
|
|
729
|
+
chosen = localFallbackBest
|
|
242
730
|
}
|
|
243
731
|
|
|
244
|
-
if (
|
|
732
|
+
if (!chosen) {
|
|
245
733
|
throw paths.length > 0
|
|
246
734
|
? "JetBrains AI Assistant quota data unavailable. Open AI Assistant once and try again."
|
|
247
735
|
: "JetBrains AI Assistant not detected. Open a JetBrains IDE with AI Assistant enabled."
|
|
248
736
|
}
|
|
249
737
|
|
|
250
|
-
if (states.length > 1) {
|
|
251
|
-
throw {code: "failed", message: "Multiple JetBrains IDE quota profiles found. Select settings.configDir to choose one installation."}
|
|
252
|
-
}
|
|
253
|
-
var chosen = states[0]
|
|
254
738
|
var quota = chosen.quota
|
|
255
739
|
var scale = detectDisplayScale(quota, chosen.nextRefill)
|
|
256
740
|
var usedPercent = (quota.used / quota.maximum) * 100
|
|
@@ -306,7 +790,9 @@
|
|
|
306
790
|
|
|
307
791
|
ctx.host.log.info("quota loaded from " + chosen.path)
|
|
308
792
|
|
|
309
|
-
|
|
793
|
+
var result = { lines: lines }
|
|
794
|
+
if (quota.plan) result.plan = quota.plan
|
|
795
|
+
return result
|
|
310
796
|
}
|
|
311
797
|
|
|
312
798
|
globalThis.__openusage_plugin = { id: "jetbrains-ai-assistant", probe: probe }
|
|
@@ -6,11 +6,34 @@
|
|
|
6
6
|
"entry": "plugin.js",
|
|
7
7
|
"icon": "icon.svg",
|
|
8
8
|
"brandColor": "#7d5fe6",
|
|
9
|
-
"supportedModes": [
|
|
9
|
+
"supportedModes": [
|
|
10
|
+
"local"
|
|
11
|
+
],
|
|
10
12
|
"autoMode": "local",
|
|
11
13
|
"lines": [
|
|
12
|
-
{
|
|
13
|
-
|
|
14
|
-
|
|
14
|
+
{
|
|
15
|
+
"type": "progress",
|
|
16
|
+
"label": "Quota",
|
|
17
|
+
"scope": "overview",
|
|
18
|
+
"primaryOrder": 1
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"type": "text",
|
|
22
|
+
"label": "Used",
|
|
23
|
+
"scope": "detail"
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
"type": "text",
|
|
27
|
+
"label": "Remaining",
|
|
28
|
+
"scope": "detail"
|
|
29
|
+
}
|
|
30
|
+
],
|
|
31
|
+
"setupFields": [
|
|
32
|
+
{
|
|
33
|
+
"key": "liveQuota",
|
|
34
|
+
"type": "boolean",
|
|
35
|
+
"title": "Read local Junie observations",
|
|
36
|
+
"description": "Read running IDE quotas, Junie session observations and IDE logs for the default account. Disabled when configDir selects one IDE; no cloud token refresh."
|
|
37
|
+
}
|
|
15
38
|
]
|
|
16
39
|
}
|