@hashimkarim/usagestat-linux-x64-gnu 2.0.0-alpha.3 → 2.0.0-alpha.4

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 (142) hide show
  1. package/native-manifest.json +731 -65
  2. package/package.json +112 -1
  3. package/plugins/UPSTREAM-LICENSES.md +6 -2
  4. package/plugins/_provider-icons/LICENSE +21 -0
  5. package/plugins/_provider-icons/NOTICE +12 -0
  6. package/plugins/_provider-icons/licenses/CodexBar-MIT.txt +21 -0
  7. package/plugins/_provider-icons/licenses/LobeHub-MIT.txt +21 -0
  8. package/plugins/_provider-icons/licenses/UsageStat-Bar-MIT.txt +21 -0
  9. package/plugins/_provider-icons/manifest.json +1219 -0
  10. package/plugins/_provider-icons/provenance.json +269 -0
  11. package/plugins/_provider-icons/source.json +4 -0
  12. package/plugins/abacus-ai/icon-color.svg +18 -0
  13. package/plugins/aixy/LICENSE +21 -0
  14. package/plugins/aixy/plugin.js +261 -0
  15. package/plugins/aixy/plugin.json +24 -0
  16. package/plugins/antigravity/plugin.js +136 -46
  17. package/plugins/antigravity-cli/icon-color.svg +1 -0
  18. package/plugins/antigravity-cli/icon.svg +1 -3
  19. package/plugins/antigravity-cli/plugin.js +11 -15
  20. package/plugins/antigravity-ide/icon-color.svg +1 -0
  21. package/plugins/antigravity-ide/icon.svg +1 -3
  22. package/plugins/atlascloud/LICENSE +21 -0
  23. package/plugins/atlascloud/icon-color.svg +1 -0
  24. package/plugins/atlascloud/icon.svg +1 -0
  25. package/plugins/atlascloud/plugin.js +49 -0
  26. package/plugins/atlascloud/plugin.json +23 -0
  27. package/plugins/augment/icon-color.svg +14 -0
  28. package/plugins/bifrost/LICENSE +21 -0
  29. package/plugins/bifrost/plugin.js +361 -0
  30. package/plugins/bifrost/plugin.json +23 -0
  31. package/plugins/claude/plugin.js +302 -152
  32. package/plugins/claude/plugin.json +1 -1
  33. package/plugins/clinepass/plugin.js +19 -3
  34. package/plugins/codebuff/icon-color.svg +92 -0
  35. package/plugins/coderabbit/plugin.js +25 -0
  36. package/plugins/coderabbit/plugin.json +12 -0
  37. package/plugins/codex/plugin.js +1 -0
  38. package/plugins/command-code/icon-color.svg +3 -0
  39. package/plugins/command-code/plugin.js +7 -3
  40. package/plugins/copilot/icon.svg +1 -1
  41. package/plugins/crof/icon-color.svg +3 -0
  42. package/plugins/crofai/icon-color.svg +3 -0
  43. package/plugins/crofai/icon.svg +2 -15
  44. package/plugins/cursor/icon-color.svg +1 -0
  45. package/plugins/deepinfra/icon-color.svg +1 -0
  46. package/plugins/deepinfra/icon.svg +1 -4
  47. package/plugins/deepseek/plugin.js +150 -59
  48. package/plugins/deepseek/plugin.json +3 -1
  49. package/plugins/devin/plugin.js +3 -0
  50. package/plugins/devpass/LICENSE +21 -0
  51. package/plugins/devpass/plugin.js +115 -0
  52. package/plugins/devpass/plugin.json +23 -0
  53. package/plugins/elevenlabs/icon-color.svg +4 -0
  54. package/plugins/factory/icon-color.svg +1 -0
  55. package/plugins/fireworks-ai/icon-color.svg +1 -0
  56. package/plugins/fireworks-ai/icon.svg +1 -6
  57. package/plugins/gemini-apps/LICENSE +22 -0
  58. package/plugins/gemini-apps/icon-color.svg +1 -0
  59. package/plugins/gemini-apps/icon.svg +1 -0
  60. package/plugins/gemini-apps/plugin.js +470 -0
  61. package/plugins/gemini-apps/plugin.json +19 -0
  62. package/plugins/gitkraken/LICENSE +21 -0
  63. package/plugins/gitkraken/plugin.js +153 -0
  64. package/plugins/gitkraken/plugin.json +24 -0
  65. package/plugins/grok/icon-color.svg +1 -0
  66. package/plugins/groqcloud/icon-color.svg +1 -0
  67. package/plugins/helmcode/LICENSE +21 -0
  68. package/plugins/helmcode/plugin.js +188 -0
  69. package/plugins/helmcode/plugin.json +24 -0
  70. package/plugins/huggingface/LICENSE +21 -0
  71. package/plugins/huggingface/icon-color.svg +1 -0
  72. package/plugins/huggingface/icon.svg +1 -0
  73. package/plugins/huggingface/plugin.js +293 -0
  74. package/plugins/huggingface/plugin.json +25 -0
  75. package/plugins/hyper/LICENSE +21 -0
  76. package/plugins/hyper/plugin.js +131 -0
  77. package/plugins/hyper/plugin.json +26 -0
  78. package/plugins/kilo/icon-color.svg +1 -0
  79. package/plugins/litellm/plugin.js +54 -3
  80. package/plugins/llmman/LICENSE +21 -0
  81. package/plugins/llmman/plugin.js +133 -0
  82. package/plugins/llmman/plugin.json +23 -0
  83. package/plugins/longcat/icon-color.svg +1 -0
  84. package/plugins/longcat/icon.svg +1 -4
  85. package/plugins/manus/icon-color.svg +6 -0
  86. package/plugins/mimo/icon-color.svg +4 -0
  87. package/plugins/mimo/icon.svg +4 -1
  88. package/plugins/mistral/plugin.js +110 -5
  89. package/plugins/moonshot/icon-color.svg +1 -0
  90. package/plugins/muse/LICENSE +21 -0
  91. package/plugins/muse/plugin.js +319 -0
  92. package/plugins/muse/plugin.json +26 -0
  93. package/plugins/nous/LICENSE +21 -0
  94. package/plugins/nous/icon-color.svg +1 -0
  95. package/plugins/nous/icon.svg +1 -0
  96. package/plugins/nous/plugin.js +124 -0
  97. package/plugins/nous/plugin.json +24 -0
  98. package/plugins/ollama/icon-color.svg +1 -0
  99. package/plugins/openai-api/icon-color.svg +1 -0
  100. package/plugins/opencode/icon-color.svg +1 -0
  101. package/plugins/opencode-go/icon-color.svg +1 -0
  102. package/plugins/opencode-go/plugin.js +30 -12
  103. package/plugins/openrouter/icon-color.svg +1 -0
  104. package/plugins/pi/plugin.js +113 -0
  105. package/plugins/pi/plugin.json +11 -0
  106. package/plugins/qwencloud/icon-color.svg +1 -0
  107. package/plugins/qwencloud/icon.svg +1 -4
  108. package/plugins/raycast/LICENSE +21 -0
  109. package/plugins/raycast/plugin.js +174 -0
  110. package/plugins/raycast/plugin.json +24 -0
  111. package/plugins/replicate/LICENSE +21 -0
  112. package/plugins/replicate/icon-color.svg +1 -0
  113. package/plugins/replicate/icon.svg +1 -0
  114. package/plugins/replicate/plugin.js +165 -0
  115. package/plugins/replicate/plugin.json +24 -0
  116. package/plugins/synthetic/icon-color.svg +14 -0
  117. package/plugins/typesafe/LICENSE +21 -0
  118. package/plugins/typesafe/plugin.js +225 -0
  119. package/plugins/typesafe/plugin.json +24 -0
  120. package/plugins/v0/LICENSE +21 -0
  121. package/plugins/v0/icon-color.svg +1 -0
  122. package/plugins/v0/icon.svg +1 -0
  123. package/plugins/v0/plugin.js +214 -0
  124. package/plugins/v0/plugin.json +24 -0
  125. package/plugins/venice/plugin.js +50 -1
  126. package/plugins/venice/plugin.json +2 -1
  127. package/plugins/vercel/LICENSE +21 -0
  128. package/plugins/vercel/icon-color.svg +1 -0
  129. package/plugins/vercel/icon.svg +1 -0
  130. package/plugins/vercel/plugin.js +55 -0
  131. package/plugins/vercel/plugin.json +23 -0
  132. package/plugins/windsurf/icon-color.svg +1 -0
  133. package/plugins/xai/icon-color.svg +1 -0
  134. package/plugins/xai/icon.svg +1 -7
  135. package/plugins/xkiro/LICENSE +21 -0
  136. package/plugins/xkiro/plugin.js +99 -0
  137. package/plugins/xkiro/plugin.json +23 -0
  138. package/plugins/zai/icon-color.svg +1 -0
  139. package/plugins/zenmux/icon-color.svg +1 -0
  140. package/plugins/zenmux/icon.svg +1 -3
  141. package/usagestat +0 -0
  142. package/usagestatd +0 -0
@@ -18,19 +18,22 @@
18
18
  WHERE json_valid(data)
19
19
  AND json_extract(data, '$.providerID') = 'opencode-go'
20
20
  AND json_extract(data, '$.role') = 'assistant'
21
- AND json_type(data, '$.cost') IN ('integer', 'real')
22
21
  LIMIT 1
23
22
  `;
24
23
 
25
24
  const HISTORY_ROWS_SQL = `
26
25
  SELECT
27
26
  CAST(COALESCE(json_extract(data, '$.time.created'), time_created) AS INTEGER) AS createdMs,
28
- CAST(json_extract(data, '$.cost') AS REAL) AS cost
27
+ json_extract(data, '$.cost') AS cost,
28
+ json_extract(data, '$.tokens.input') AS inputTokens,
29
+ json_extract(data, '$.tokens.output') AS outputTokens,
30
+ json_extract(data, '$.tokens.cache.read') AS cacheReadTokens,
31
+ json_extract(data, '$.tokens.cache.write') AS cacheCreationTokens,
32
+ json_extract(data, '$.tokens.reasoning') AS reasoningOutputTokens
29
33
  FROM message
30
34
  WHERE json_valid(data)
31
35
  AND json_extract(data, '$.providerID') = 'opencode-go'
32
36
  AND json_extract(data, '$.role') = 'assistant'
33
- AND json_type(data, '$.cost') IN ('integer', 'real')
34
37
  `;
35
38
 
36
39
  function readNumber(value) {
@@ -361,9 +364,15 @@
361
364
  if (!row || typeof row !== "object") continue;
362
365
  const createdMs = readNumber(row.createdMs);
363
366
  const cost = readNumber(row.cost);
364
- if (createdMs === null || createdMs <= 0) continue;
365
- if (cost === null || cost < 0) continue;
366
- rows.push({ createdMs, cost });
367
+ if (createdMs === null || createdMs <= 0 || createdMs > Date.parse(ctx.nowIso)) continue;
368
+ const entry = { createdMs, cost: cost !== null && cost >= 0 ? cost : 0, costKnown: cost !== null && cost >= 0, tokensKnown: false };
369
+ for (const key of ['inputTokens','outputTokens','cacheReadTokens','cacheCreationTokens','reasoningOutputTokens']) {
370
+ const value = readNumber(row[key]);
371
+ entry[key] = value !== null && value >= 0 ? value : 0;
372
+ if (value !== null && value >= 0 && key !== 'reasoningOutputTokens') entry.tokensKnown = true;
373
+ }
374
+ entry.totalTokens = entry.inputTokens + entry.outputTokens + entry.cacheReadTokens + entry.cacheCreationTokens;
375
+ if (entry.costKnown || entry.tokensKnown) rows.push(entry);
367
376
  }
368
377
 
369
378
  return { ok: true, rows };
@@ -623,7 +632,8 @@
623
632
 
624
633
  const rowsResult = loadHistory(ctx);
625
634
  if (rowsResult.ok && rowsResult.rows.length > 0) {
626
- const lines = buildProgressLines(ctx, rowsResult.rows, readNowMs());
635
+ const lines = rowsResult.rows.every(row => row.costKnown)
636
+ ? buildProgressLines(ctx, rowsResult.rows, readNowMs()) : [];
627
637
  for (const line of lines) line.detail = "Estimated from local API-rate costs";
628
638
  attachCostHistory(ctx, lines, rowsResult.rows);
629
639
  if (cookie && source !== "local") {
@@ -669,12 +679,20 @@
669
679
  const date = toIso(row.createdMs);
670
680
  if (!date) continue;
671
681
  const day = date.slice(0, 10);
672
- byDay[day] = (byDay[day] || 0) + row.cost;
673
- }
674
- const daily = Object.keys(byDay).sort().map((day) => ({ date: day, costUsd: byDay[day] }));
682
+ const daily = byDay[day] || (byDay[day] = { date: day, costUsd: 0, tokensKnown: true, costKnown: true,
683
+ inputTokens: 0, outputTokens: 0, cacheReadTokens: 0, cacheCreationTokens: 0, reasoningOutputTokens: 0, totalTokens: 0, requests: 0 });
684
+ daily.costUsd += row.cost;
685
+ daily.costKnown = daily.costKnown && row.costKnown;
686
+ daily.tokensKnown = daily.tokensKnown && row.tokensKnown;
687
+ daily.requests++;
688
+ for (const key of ['inputTokens','outputTokens','cacheReadTokens','cacheCreationTokens','reasoningOutputTokens','totalTokens']) daily[key] += row[key];
689
+ }
690
+ const daily = Object.keys(byDay).sort().map((day) => byDay[day]);
675
691
  if (!daily.length) return;
676
- lines.push(ctx.line.text({ label: "Local Cost", value: "$" + daily.reduce((n, day) => n + day.costUsd, 0).toFixed(2), subtitle: "Last 30 days; estimated API-rate cost" }));
677
- lines.push(ctx.line.barChart({ label: "Cost History", points: daily.map((day) => ({ label: day.date, value: day.costUsd, valueLabel: "$" + day.costUsd.toFixed(2) })),
692
+ const total = daily.reduce((n, day) => n + day.costUsd, 0), known = daily.every(day => day.costKnown);
693
+ lines.push(ctx.line.text({ label: "Local Cost", value: known ? "$" + total.toFixed(2) : total > 0 ? "$" + total.toFixed(2) + "+" : "Unpriced", subtitle: "Last 30 days; estimated API-rate cost" }));
694
+ lines.push(ctx.line.text({ label: "Local Tokens", value: String(daily.reduce((n, day) => n + day.totalTokens, 0)) + (daily.every(day => day.tokensKnown) ? "" : "+"), subtitle: "Last 30 days" }));
695
+ lines.push(ctx.line.barChart({ label: "Cost History", points: daily.map((day) => ({ label: day.date, value: day.costUsd, valueLabel: day.costKnown ? "$" + day.costUsd.toFixed(2) : day.costUsd > 0 ? "$" + day.costUsd.toFixed(2) + "+" : "Unpriced" })),
678
696
  note: "Estimated from local OpenCode Go messages, not a subscription bill.", color: "#4d9f75" }));
679
697
  try {
680
698
  ctx.host.usageDaily.ingest({ displayName: "OpenCode Go", source: "opencode_go_local_estimated", daily });
@@ -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>
@@ -0,0 +1,113 @@
1
+ (function () {
2
+ const own = (o, k) => Object.prototype.hasOwnProperty.call(o, k);
3
+ const validCount = n => Number.isSafeInteger(n) && n >= 0;
4
+ const clean = s => typeof s === 'string' && s.trim() ? s.trim() : null;
5
+ const DAY = 86400000;
6
+ function roots(ctx) {
7
+ const settings = ctx.provider?.settings || {};
8
+ if (own(settings, 'sessionRoots')) {
9
+ if (!Array.isArray(settings.sessionRoots) || settings.sessionRoots.some(p => !clean(p))) throw new Error('Invalid Pi sessionRoots.');
10
+ return [...new Set(settings.sessionRoots)];
11
+ }
12
+ if (ctx.provider?.instanceId && ctx.provider.instanceId !== 'pi') throw new Error('Set sessionRoots for this Pi account.');
13
+ const env = k => clean(ctx.host.env.get(k));
14
+ const sessionDir = env('PI_CODING_AGENT_SESSION_DIR');
15
+ if (sessionDir) return [sessionDir];
16
+ const agentDir = env('PI_CODING_AGENT_DIR');
17
+ if (agentDir) return [agentDir.replace(/[\\/]+$/, '') + '/sessions'];
18
+ const home = ctx.host.fs.homeDir;
19
+ const config = env('PI_CONFIG_DIR') || '.omp';
20
+ if (/^(?:[\\/]|[A-Za-z]:)/.test(config) || config.split(/[\\/]/).includes('..')) throw new Error('PI_CONFIG_DIR must be inside the home directory.');
21
+ const xdg = env('XDG_DATA_HOME') || home + '/.local/share';
22
+ const profile = env('OMP_PROFILE') || env('PI_PROFILE');
23
+ if (profile && profile !== 'default') {
24
+ if (!/^[A-Za-z0-9_-]+$/.test(profile)) throw new Error('Invalid OMP profile.');
25
+ return [home + '/' + config, xdg + '/omp'].flatMap(root =>
26
+ [root + '/profiles/' + profile + '/sessions', root + '/profiles/' + profile + '/agent/sessions']);
27
+ }
28
+ return [home + '/.pi/agent/sessions', home + '/' + config + '/agent/sessions', xdg + '/omp/sessions'];
29
+ }
30
+ // API-equivalent USD estimates, reviewed against CodexBar 78ba5a1 (not subscription charges).
31
+ function cost(message, counts, date) {
32
+ const model = String(message.model || '').replace(/^(openai|anthropic)\//, '');
33
+ const [input, output, read, write] = counts;
34
+ let rate;
35
+ if (message.provider === 'openai-codex') {
36
+ if (model === 'gpt-5.4') rate = input + read > 272000 ? [5,22.5,.5,5] : [2.5,15,.25,2.5];
37
+ else if (model === 'gpt-5.4-mini') rate = [.75,4.5,.075,.75];
38
+ else if (model === 'gpt-5.4-nano') rate = [.2,1.25,.02,.2];
39
+ } else if (message.provider === 'anthropic') {
40
+ if (/^claude-sonnet-4-6(?:-\d{8})?$/.test(model)) rate = [3,15,.3,3.75];
41
+ if (/^claude-opus-4-[67](?:-\d{8})?$/.test(model)) rate = [5,25,.5,6.25];
42
+ // Earlier long-context pricing differs; keep it unpriced until its historical rate is available.
43
+ if (date < '2026-03-13' && input + read + write > 200000) return null;
44
+ }
45
+ if (!rate) return null;
46
+ const value = counts.reduce((sum, n, i) => sum + n * rate[i] / 1e6, 0);
47
+ return Number.isFinite(value) ? value : null;
48
+ }
49
+ function probe(ctx) {
50
+ const daily = new Map(), seen = new Set(), paths = new Set();
51
+ const now = Date.parse(ctx.nowIso), start = Date.now();
52
+ let bytes = 0, files = 0, complete = true;
53
+ function visit(root, depth) {
54
+ if (paths.has(root)) return;
55
+ paths.add(root);
56
+ if (depth > 6 || paths.size > 20000 || bytes > 128 * 1024 * 1024 || Date.now() - start > 10000) { complete = false; return; }
57
+ if (!root.endsWith('.jsonl')) {
58
+ let entries;
59
+ try { entries = ctx.host.fs.listDir(root); } catch (_) { if (depth || ctx.host.fs.exists(root)) complete = false; return; }
60
+ for (const name of entries) if (name !== '.' && name !== '..' && !/[\\/]/.test(name)) visit(root + '/' + name, depth + 1);
61
+ return;
62
+ }
63
+ let content;
64
+ try { content = ctx.host.fs.readTextLimited(root, 8 * 1024 * 1024); } catch (_) { complete = false; return; }
65
+ bytes += content.length; files++;
66
+ let session = root;
67
+ for (const line of content.split('\n')) {
68
+ if (!line.trim()) continue;
69
+ let entry;
70
+ try { entry = JSON.parse(line); } catch (_) { complete = false; continue; }
71
+ if (entry.type === 'session' && clean(entry.id)) session = entry.id;
72
+ const m = entry.message;
73
+ if (entry.type !== 'message' || m?.role !== 'assistant') continue;
74
+ const stamp = typeof m.timestamp === 'number' ? m.timestamp : Date.parse(entry.timestamp);
75
+ if (!Number.isFinite(stamp)) { complete = false; continue; }
76
+ if (stamp > now) continue;
77
+ const id = session + ':' + (entry.id || ctx.host.crypto.sha256(line));
78
+ if (seen.has(id)) continue;
79
+ seen.add(id);
80
+ const date = new Date(stamp).toISOString().slice(0, 10);
81
+ if (!daily.has(date)) daily.set(date, {date, inputTokens:0, outputTokens:0, cacheReadTokens:0,
82
+ cacheCreationTokens:0, totalTokens:0, costUsd:0, tokensKnown:true, costKnown:true, requests:0});
83
+ const row = daily.get(date), u = m.usage;
84
+ row.requests++;
85
+ const counts = [u?.input, u?.output, u?.cacheRead ?? 0, u?.cacheWrite ?? 0];
86
+ if (!u || !counts.every(validCount)) { row.tokensKnown = false; row.costKnown = false; continue; }
87
+ const total = counts.reduce((a, b) => a + b, 0);
88
+ if (!validCount(row.totalTokens + total)) { row.tokensKnown = false; row.costKnown = false; continue; }
89
+ ['inputTokens','outputTokens','cacheReadTokens','cacheCreationTokens'].forEach((key, i) => row[key] += counts[i]);
90
+ row.totalTokens += total;
91
+ const amount = cost(m, counts, date);
92
+ if (amount === null) row.costKnown = false; else row.costUsd += amount;
93
+ }
94
+ }
95
+ for (const root of roots(ctx)) visit(root.replace(/[\\/]+$/, ''), 0);
96
+ const rows = [...daily.values()].sort((a,b)=>a.date.localeCompare(b.date));
97
+ if (!complete) rows.forEach(row=>{row.tokensKnown=false;row.costKnown=false;});
98
+ if (rows.length) ctx.host.usageDaily.ingest({displayName:'Pi / OMP', source:'pi_transcript_estimated', daily:rows});
99
+ const lines = [];
100
+ for (const [label, days] of [['Today',1],['Last 30 Days',30]]) {
101
+ const since = new Date(Date.parse(ctx.nowIso.slice(0,10)) - (days-1)*DAY).toISOString().slice(0,10);
102
+ const selected = rows.filter(row=>row.date >= since);
103
+ if (!selected.length) continue;
104
+ const tokens = selected.reduce((sum,r)=>sum+r.totalTokens,0), usd=selected.reduce((sum,r)=>sum+r.costUsd,0);
105
+ const priced = selected.every(r=>r.costKnown), counted = selected.every(r=>r.tokensKnown);
106
+ lines.push(ctx.line.text({label, value:`${tokens}${counted?'':'+'} tokens`,
107
+ subtitle:priced ? '$'+usd.toFixed(2)+' estimated API cost' : usd>0 ? '$'+usd.toFixed(2)+'+ partial estimated API cost' : 'Pricing unavailable'}));
108
+ }
109
+ if (!complete) lines.push(ctx.line.text({label:'History',value:'Partial scan'}));
110
+ return {displayName:'Pi / OMP',source:'local',state:rows.length?'ready':'no-data',lines};
111
+ }
112
+ globalThis.__usagestat_plugin = {id:'pi',probe};
113
+ })();
@@ -0,0 +1,11 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "id": "pi",
4
+ "name": "Pi / OMP",
5
+ "version": "0.1.0",
6
+ "entry": "plugin.js",
7
+ "enabledByDefault": false,
8
+ "supportedModes": ["local"],
9
+ "autoMode": "local",
10
+ "envVars": ["PI_CODING_AGENT_DIR", "PI_CODING_AGENT_SESSION_DIR", "PI_CONFIG_DIR", "OMP_PROFILE", "PI_PROFILE"]
11
+ }
@@ -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>Qwen</title><path d="M12.604 1.34c.393.69.784 1.382 1.174 2.075a.18.18 0 00.157.091h5.552c.174 0 .322.11.446.327l1.454 2.57c.19.337.24.478.024.837-.26.43-.513.864-.76 1.3l-.367.658c-.106.196-.223.28-.04.512l2.652 4.637c.172.301.111.494-.043.77-.437.785-.882 1.564-1.335 2.34-.159.272-.352.375-.68.37-.777-.016-1.552-.01-2.327.016a.099.099 0 00-.081.05 575.097 575.097 0 01-2.705 4.74c-.169.293-.38.363-.725.364-.997.003-2.002.004-3.017.002a.537.537 0 01-.465-.271l-1.335-2.323a.09.09 0 00-.083-.049H4.982c-.285.03-.553-.001-.805-.092l-1.603-2.77a.543.543 0 01-.002-.54l1.207-2.12a.198.198 0 000-.197 550.951 550.951 0 01-1.875-3.272l-.79-1.395c-.16-.31-.173-.496.095-.965.465-.813.927-1.625 1.387-2.436.132-.234.304-.334.584-.335a338.3 338.3 0 012.589-.001.124.124 0 00.107-.063l2.806-4.895a.488.488 0 01.422-.246c.524-.001 1.053 0 1.583-.006L11.704 1c.341-.003.724.032.9.34zm-3.432.403a.06.06 0 00-.052.03L6.254 6.788a.157.157 0 01-.135.078H3.253c-.056 0-.07.025-.041.074l5.81 10.156c.025.042.013.062-.034.063l-2.795.015a.218.218 0 00-.2.116l-1.32 2.31c-.044.078-.021.118.068.118l5.716.008c.046 0 .08.02.104.061l1.403 2.454c.046.081.092.082.139 0l5.006-8.76.783-1.382a.055.055 0 01.096 0l1.424 2.53a.122.122 0 00.107.062l2.763-.02a.04.04 0 00.035-.02.041.041 0 000-.04l-2.9-5.086a.108.108 0 010-.113l.293-.507 1.12-1.977c.024-.041.012-.062-.035-.062H9.2c-.059 0-.073-.026-.043-.077l1.434-2.505a.107.107 0 000-.114L9.225 1.774a.06.06 0 00-.053-.031zm6.29 8.02c.046 0 .058.02.034.06l-.832 1.465-2.613 4.585a.056.056 0 01-.05.029.058.058 0 01-.05-.029L8.498 9.841c-.02-.034-.01-.052.028-.054l.216-.012 6.722-.012z" fill="url(#lobe-icons-qwen-_R_0_)" fill-rule="nonzero"></path><defs><linearGradient id="lobe-icons-qwen-_R_0_" x1="0%" x2="100%" y1="0%" y2="0%"><stop offset="0%" stop-color="#6336E7" stop-opacity=".84"></stop><stop offset="100%" stop-color="#6F69F7" stop-opacity=".84"></stop></linearGradient></defs></svg>
@@ -1,4 +1 @@
1
- <svg width="200" height="200" viewBox="0 0 200 200" fill="none" xmlns="http://www.w3.org/2000/svg">
2
- <path fill-rule="evenodd" clip-rule="evenodd" d="M100 12L120.5 47.5H161.5L141 83L161.5 118.5H120.5L100 154L79.5 118.5H38.5L59 83L38.5 47.5H79.5L100 12ZM100 58L113 83L100 108L87 83L100 58Z" fill="#111111"/>
3
- <path d="M100 158C100 158 138 162 138 176C138 184 128 188 100 188C72 188 62 184 62 176C62 162 100 158 100 158Z" fill="#111111"/>
4
- </svg>
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>Qwen</title><path d="M12.604 1.34c.393.69.784 1.382 1.174 2.075a.18.18 0 00.157.091h5.552c.174 0 .322.11.446.327l1.454 2.57c.19.337.24.478.024.837-.26.43-.513.864-.76 1.3l-.367.658c-.106.196-.223.28-.04.512l2.652 4.637c.172.301.111.494-.043.77-.437.785-.882 1.564-1.335 2.34-.159.272-.352.375-.68.37-.777-.016-1.552-.01-2.327.016a.099.099 0 00-.081.05 575.097 575.097 0 01-2.705 4.74c-.169.293-.38.363-.725.364-.997.003-2.002.004-3.017.002a.537.537 0 01-.465-.271l-1.335-2.323a.09.09 0 00-.083-.049H4.982c-.285.03-.553-.001-.805-.092l-1.603-2.77a.543.543 0 01-.002-.54l1.207-2.12a.198.198 0 000-.197 550.951 550.951 0 01-1.875-3.272l-.79-1.395c-.16-.31-.173-.496.095-.965.465-.813.927-1.625 1.387-2.436.132-.234.304-.334.584-.335a338.3 338.3 0 012.589-.001.124.124 0 00.107-.063l2.806-4.895a.488.488 0 01.422-.246c.524-.001 1.053 0 1.583-.006L11.704 1c.341-.003.724.032.9.34zm-3.432.403a.06.06 0 00-.052.03L6.254 6.788a.157.157 0 01-.135.078H3.253c-.056 0-.07.025-.041.074l5.81 10.156c.025.042.013.062-.034.063l-2.795.015a.218.218 0 00-.2.116l-1.32 2.31c-.044.078-.021.118.068.118l5.716.008c.046 0 .08.02.104.061l1.403 2.454c.046.081.092.082.139 0l5.006-8.76.783-1.382a.055.055 0 01.096 0l1.424 2.53a.122.122 0 00.107.062l2.763-.02a.04.04 0 00.035-.02.041.041 0 000-.04l-2.9-5.086a.108.108 0 010-.113l.293-.507 1.12-1.977c.024-.041.012-.062-.035-.062H9.2c-.059 0-.073-.026-.043-.077l1.434-2.505a.107.107 0 000-.114L9.225 1.774a.06.06 0 00-.053-.031zm6.29 8.02c.046 0 .058.02.034.06l-.832 1.465-2.613 4.585a.056.056 0 01-.05.029.058.058 0 01-.05-.029L8.498 9.841c-.02-.034-.01-.052.028-.054l.216-.012 6.722-.012z"></path></svg>
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Peter Steinberger
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,174 @@
1
+ // Generated by tools/vendor-codexbar-providers.cjs; see LICENSE.
2
+ globalThis.__usagestat_bundled_options = {"labels":["Credits","Plan","Additional"],"defaults":{},"autoMode":"web"};
3
+ function _nullishCoalesce(lhs, rhsFn) {
4
+ if (lhs != null) {
5
+ return lhs;
6
+ } else {
7
+ return rhsFn();
8
+ }
9
+ }
10
+ defineProvider({
11
+ id: "raycast",
12
+ name: "Raycast",
13
+ endpoints: ["https://www.raycast.com"],
14
+ settings: [{ key: "webTimeoutSeconds", title: "Web timeout", type: "plain" }],
15
+ capabilities: ["browser-cookies", "http-status"],
16
+ cookieDomains: ["raycast.com", "www.raycast.com"],
17
+ async fetchUsage(ctx) {
18
+ const sessionCookie = (value) => {
19
+ const match = /(?:^|;\s*)__raycast_session=([^;]*)/.exec(value);
20
+ return !!match && match[1].trim().length > 0;
21
+ };
22
+ const missingSession = () => {
23
+ throw ctx.fail.missingCredential(
24
+ "No Raycast session cookies found. Sign in at www.raycast.com/settings or paste a Cookie header.",
25
+ );
26
+ };
27
+ const domain = "www.raycast.com";
28
+ const policy = ctx.browser.availability(domain);
29
+ if (policy === "off") throw ctx.fail.missingCredential("Raycast cookies are disabled.");
30
+ const timeoutRaw = Number(_nullishCoalesce(ctx.settings.get("webTimeoutSeconds"), () => "15"));
31
+ const timeoutSeconds = Number.isFinite(timeoutRaw) ? Math.min(30, Math.max(1, Math.round(timeoutRaw))) : 15;
32
+ let response;
33
+ let rejected = false;
34
+ for await (const session of ctx.browser.sessions(domain)) {
35
+ const cookie = session.header
36
+ .split(";")
37
+ .map((part) => part.trim())
38
+ .filter((part) => /^(?:__raycast_session|csrf_token)=/.test(part))
39
+ .join("; ");
40
+ if (!sessionCookie(cookie)) {
41
+ ctx.browser.rejectCookie(domain, session);
42
+ if (policy === "manual") break;
43
+ continue;
44
+ }
45
+ const candidate = await ctx.http.get("https://www.raycast.com/frontend_api/current_user/ai_credits", {
46
+ timeoutSeconds,
47
+ headers: {
48
+ Cookie: cookie,
49
+ Accept: "application/json",
50
+ Origin: "https://www.raycast.com",
51
+ Referer: "https://www.raycast.com/settings",
52
+ "User-Agent":
53
+ "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36",
54
+ },
55
+ });
56
+ if (candidate.status === 401) {
57
+ rejected = true;
58
+ ctx.browser.rejectCookie(domain, session);
59
+ if (policy === "manual") break;
60
+ continue;
61
+ }
62
+ response = candidate;
63
+ break;
64
+ }
65
+ if (!response) {
66
+ if (rejected)
67
+ throw ctx.fail.authenticationExpired(
68
+ "Raycast website session expired. Sign in at www.raycast.com/settings or paste a fresh Cookie header.",
69
+ );
70
+ return missingSession();
71
+ }
72
+ if (response.status === 403) {
73
+ throw ctx.fail.permissionDenied("Raycast denied access to AI credits for this account.");
74
+ }
75
+ if (response.status === 429) throw ctx.fail.rateLimited("Raycast credits requests are rate limited.");
76
+ if (response.status >= 500) {
77
+ throw ctx.fail.providerUnavailable(`Raycast credits API returned HTTP ${response.status}.`);
78
+ }
79
+ if (response.status !== 200) {
80
+ throw ctx.fail.apiFailure(`Raycast credits API returned HTTP ${response.status}.`);
81
+ }
82
+
83
+ const fail = (field) => {
84
+ throw ctx.fail.parseFailure(`Invalid Raycast credits response: ${field}`);
85
+ };
86
+ const object = (value, field) => {
87
+ if (value === undefined || value === null) return {};
88
+ if (typeof value !== "object" || Array.isArray(value)) return fail(field);
89
+ return value;
90
+ };
91
+ const number = (value, field) => {
92
+ if (value === undefined || value === null) return undefined;
93
+ if (typeof value === "boolean") return fail(field);
94
+ if (typeof value === "number") {
95
+ return Number.isFinite(value) ? value : fail(field);
96
+ }
97
+ if (typeof value !== "string") return fail(field);
98
+ const trimmed = value.trim();
99
+ if (trimmed === "") return fail(field);
100
+ if (!/^[+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?$/.test(trimmed)) return fail(field);
101
+ const parsed = Number(trimmed);
102
+ return Number.isFinite(parsed) ? parsed : fail(field);
103
+ };
104
+ const text = (value) => (typeof value === "string" ? value.trim() || undefined : undefined);
105
+ // No thousands grouping. The meter footnote is this text, and a grouped number would not match the source amount.
106
+ const amount = (value) => {
107
+ if (Number.isInteger(value)) return String(value);
108
+ let text = value.toFixed(2);
109
+ while (text.endsWith("0")) text = text.slice(0, -1);
110
+ if (text.endsWith(".")) text = text.slice(0, -1);
111
+ return text;
112
+ };
113
+ const planLabel = (tier) => {
114
+ switch (tier) {
115
+ case "pro":
116
+ return "Pro";
117
+ case "pro_plus":
118
+ return "Pro+";
119
+ case "max":
120
+ return "Max";
121
+ default:
122
+ return tier;
123
+ }
124
+ };
125
+
126
+ let decoded;
127
+ try {
128
+ decoded = JSON.parse(response.bodyText);
129
+ } catch (error) {
130
+ void error;
131
+ return fail("expected JSON");
132
+ }
133
+ const root = object(decoded, "expected an object");
134
+ const remaining = number(root.remaining_balance_credits, "remaining_balance_credits");
135
+ const total = number(root.total_balance_credits, "total_balance_credits");
136
+ if (remaining === undefined && total === undefined) return fail("no credit amounts");
137
+ if (remaining !== undefined && remaining < 0) return fail("remaining_balance_credits");
138
+ if (total !== undefined && total < 0) return fail("total_balance_credits");
139
+
140
+ let renewal;
141
+ if (root.next_credits_at !== undefined && root.next_credits_at !== null) {
142
+ if (typeof root.next_credits_at !== "string") return fail("next_credits_at");
143
+ try {
144
+ renewal = ctx.date.iso(root.next_credits_at);
145
+ } catch (error) {
146
+ void error;
147
+ return fail("next_credits_at");
148
+ }
149
+ }
150
+ const funding = object(root.funding_subscription, "funding_subscription");
151
+ const plan = planLabel(text(funding.tier));
152
+ const rows = [];
153
+ if (remaining !== undefined) rows.push({ label: "Left", value: amount(remaining) });
154
+ if (total !== undefined) rows.push({ label: "Total", value: amount(total) });
155
+ // A positive total is one Credits meter. The balance lives in resetDescription, which the shared
156
+ // card already prints under the bar. Left and Total rows stay only when that meter is absent,
157
+ // so a zero allowance still has something to show.
158
+ const meter = remaining !== undefined && total !== undefined && total > 0 ? { remaining, total } : undefined;
159
+
160
+ return {
161
+ primary: meter
162
+ ? {
163
+ usedPercent: ctx.pct(Math.max(0, meter.total - meter.remaining), meter.total),
164
+ resetsAt: renewal,
165
+ resetDescription: `${amount(meter.remaining)} / ${amount(meter.total)} credits left`,
166
+ }
167
+ : undefined,
168
+ details: meter || !rows.length ? undefined : [{ title: "Credits", rows }],
169
+ subscriptionRenewsAt: meter ? undefined : renewal,
170
+ identity: plan ? { loginMethod: plan } : undefined,
171
+ dataConfidence: "exact",
172
+ };
173
+ },
174
+ });
@@ -0,0 +1,24 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "id": "raycast",
4
+ "name": "Raycast",
5
+ "version": "0.1.0",
6
+ "entry": "plugin.js",
7
+ "enabledByDefault": false,
8
+ "supportedModes": [
9
+ "web"
10
+ ],
11
+ "autoMode": "web",
12
+ "usageDashboardUrl": "https://www.raycast.com/settings",
13
+ "webUrl": "https://raycast.com",
14
+ "envVars": [
15
+ "RAYCAST_COOKIE"
16
+ ],
17
+ "upstream": {
18
+ "repository": "https://github.com/steipete/CodexBar",
19
+ "revision": "78ba5a1e65e6859da59a21b799df95ab91bc0b89",
20
+ "path": "Sources/CodexBarCore/Resources/Plugins/raycast.js",
21
+ "sha256": "1f93bd240923c8427615b2de3a40d46f167b907864c01475754b115b804c3ee4",
22
+ "license": "MIT"
23
+ }
24
+ }
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Peter Steinberger
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -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>Replicate</title><path d="M22 10.552v2.26h-7.932V22H11.54V10.552H22zM22 2v2.264H4.528V22H2V2h20zm0 4.276V8.54H9.296V22H6.768V6.276H22z"></path></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>Replicate</title><path d="M22 10.552v2.26h-7.932V22H11.54V10.552H22zM22 2v2.264H4.528V22H2V2h20zm0 4.276V8.54H9.296V22H6.768V6.276H22z"></path></svg>
@@ -0,0 +1,165 @@
1
+ // Generated by tools/vendor-codexbar-providers.cjs; see LICENSE.
2
+ globalThis.__usagestat_bundled_options = {"labels":["Spend","Spend","Additional"],"defaults":{},"autoMode":"web"};
3
+ function _optionalChain(ops) {
4
+ let lastAccessLHS = undefined;
5
+ let value = ops[0];
6
+ let i = 1;
7
+ while (i < ops.length) {
8
+ const op = ops[i];
9
+ const fn = ops[i + 1];
10
+ i += 2;
11
+ if ((op === "optionalAccess" || op === "optionalCall") && value == null) {
12
+ return undefined;
13
+ }
14
+ if (op === "access" || op === "optionalAccess") {
15
+ lastAccessLHS = value;
16
+ value = fn(value);
17
+ } else if (op === "call" || op === "optionalCall") {
18
+ value = fn((...args) => value.call(lastAccessLHS, ...args));
19
+ lastAccessLHS = undefined;
20
+ }
21
+ }
22
+ return value;
23
+ }
24
+ defineProvider({
25
+ id: "replicate",
26
+ name: "Replicate",
27
+ endpoints: ["https://replicate.com"],
28
+ settings: [],
29
+ capabilities: ["browser-cookies", "http-status"],
30
+ cookieDomains: ["replicate.com"],
31
+ async fetchUsage(ctx) {
32
+ const fail = (field) => {
33
+ throw ctx.fail.parseFailure(`Replicate billing response format changed: ${field}`);
34
+ };
35
+ const record = (value) =>
36
+ value !== null && typeof value === "object" && !Array.isArray(value) ? value : undefined;
37
+ const parse = (body) => {
38
+ try {
39
+ return JSON.parse(body);
40
+ } catch (error) {
41
+ void error;
42
+ return fail("invalid JSON");
43
+ }
44
+ };
45
+ const money = (value) => {
46
+ if (typeof value !== "string" || !/^\d+(?:\.\d+)?$/.test(value.trim())) return undefined;
47
+ const number = Number(value.trim());
48
+ return Number.isFinite(number) && number >= 0 ? number : undefined;
49
+ };
50
+ const expired = () => {
51
+ throw ctx.fail.authenticationExpired("Replicate session expired. Sign in again or paste a fresh Cookie header.");
52
+ };
53
+ const validate = (response) => {
54
+ if (response.status === 401 || response.status === 403) expired();
55
+ const rawRetry = response.headers["retry-after"];
56
+ const retry = rawRetry && /^\d+(?:\.\d+)?$/.test(rawRetry.trim()) ? Number(rawRetry) : 1;
57
+ const retryAfterSeconds = Number.isFinite(retry) ? Math.min(10, retry) : 1;
58
+ if (response.status === 429) throw ctx.fail.rateLimited("Replicate rate limit reached.", { retryAfterSeconds });
59
+ if (response.status === 408 || response.status >= 500)
60
+ throw ctx.fail.providerUnavailable("Replicate billing is unavailable.", { retryAfterSeconds });
61
+ if (response.status < 200 || response.status >= 300)
62
+ throw ctx.fail.apiFailure(`Replicate returned HTTP ${response.status}.`);
63
+ };
64
+ const cookie = await ctx.browser.cookieHeader("replicate.com");
65
+ const response = await ctx.http.get("https://replicate.com/account/billing", {
66
+ headers: { Cookie: cookie, Accept: "text/html" },
67
+ timeoutSeconds: 8,
68
+ });
69
+ validate(response);
70
+ let account;
71
+ const scripts = /<script\b([^>]*)>([\s\S]*?)<\/script\s*>/gi;
72
+ let match;
73
+ let visited = 0;
74
+ while ((match = scripts.exec(response.bodyText)) && !account && visited < 4000) {
75
+ if (
76
+ !/\bid\s*=\s*(["'])react-component-props[^"']*\1/i.test(match[1]) ||
77
+ !/\btype\s*=\s*(["'])application\/json\1/i.test(match[1])
78
+ )
79
+ continue;
80
+ let payload;
81
+ try {
82
+ payload = JSON.parse(match[2]);
83
+ } catch (error) {
84
+ void error;
85
+ continue;
86
+ }
87
+ const queue = [payload];
88
+ for (let index = 0; index < queue.length && visited < 4000; index++, visited++) {
89
+ const item = queue[index];
90
+ const object = record(item);
91
+ const candidate = record(_optionalChain([object, "optionalAccess", (_) => _.account]));
92
+ if (
93
+ candidate &&
94
+ (candidate.kind === "user" || candidate.kind === "organization") &&
95
+ typeof candidate.username === "string" &&
96
+ candidate.username.trim()
97
+ ) {
98
+ account = { kind: candidate.kind, username: candidate.username.trim() };
99
+ break;
100
+ }
101
+ const children = Array.isArray(item) ? item : object ? Object.values(object) : [];
102
+ for (const child of children) {
103
+ if (queue.length >= 4000) break;
104
+ if (child !== null && typeof child === "object") queue.push(child);
105
+ }
106
+ }
107
+ }
108
+ if (!account) {
109
+ // These two markers identify Replicate's public signed-out billing redirect.
110
+ const signInTitle = /<title>\s*Sign in\s*\|\s*Replicate\s*<\/title>/i.test(response.bodyText);
111
+ const githubLogin = /<a\b[^>]*\bhref=["']\/login\/github\/(?:\?[^"']*)?["']/i.test(response.bodyText);
112
+ if (signInTitle && githubLogin) return expired();
113
+ return fail("unrecognized billing account props");
114
+ }
115
+ const base = `https://replicate.com/api/${account.kind === "organization" ? "organizations" : "users"}/${encodeURIComponent(account.username)}`;
116
+ const invoicesResponse = await ctx.http.get(`${base}/invoices`, { headers: { Cookie: cookie }, timeoutSeconds: 8 });
117
+ validate(invoicesResponse);
118
+ const invoices = _optionalChain([
119
+ record,
120
+ "call",
121
+ (_2) => _2(parse(invoicesResponse.bodyText)),
122
+ "optionalAccess",
123
+ (_3) => _3.invoices,
124
+ ]);
125
+ if (!Array.isArray(invoices)) return fail("missing invoices");
126
+ const now = ctx.date.now().getTime();
127
+ const current = invoices.map(record).find((invoice) => {
128
+ if (!invoice || invoice.type !== "monthly-usage") return false;
129
+ if (invoice.ended_before === null || invoice.ended_before === undefined) return true;
130
+ if (typeof invoice.ended_before !== "string" || !invoice.ended_before.trim()) return false;
131
+ const end = Date.parse(invoice.ended_before);
132
+ return Number.isFinite(end) && end > now;
133
+ });
134
+ if (!current) return fail("no current monthly-usage invoice");
135
+ const used = money(current.total_cost_before_adjustments);
136
+ if (used === undefined) return fail("missing or invalid total_cost_before_adjustments");
137
+ let balance;
138
+ try {
139
+ const credit = await ctx.http.get(`${base}/unused-credit`, { headers: { Cookie: cookie }, timeoutSeconds: 2 });
140
+ if (credit.status >= 200 && credit.status < 300)
141
+ balance = money(
142
+ _optionalChain([
143
+ record,
144
+ "call",
145
+ (_4) => _4(parse(credit.bodyText)),
146
+ "optionalAccess",
147
+ (_5) => _5.unused_credit,
148
+ ]),
149
+ );
150
+ } catch (error) {
151
+ void error;
152
+ }
153
+ const rows = [{ label: "Spent this month", value: ctx.format.usd(used) }];
154
+ if (balance !== undefined) rows.push({ label: "Credit balance", value: ctx.format.usd(balance) });
155
+ return {
156
+ cost: { used, balance, currency: "USD", period: "This month" },
157
+ details: [{ title: "Billing", rows }],
158
+ identity: {
159
+ accountID: account.username,
160
+ organization: account.kind === "organization" ? account.username : undefined,
161
+ },
162
+ dataConfidence: "exact",
163
+ };
164
+ },
165
+ });