@hashimkarim/usagestat-linux-x64-gnu 2.0.0-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (225) hide show
  1. package/LICENSE +57744 -0
  2. package/native-manifest.json +1414 -0
  3. package/package.json +254 -0
  4. package/plugins/abacus-ai/icon.svg +18 -0
  5. package/plugins/abacus-ai/plugin.js +121 -0
  6. package/plugins/abacus-ai/plugin.json +17 -0
  7. package/plugins/alibaba/icon-color.svg +1 -0
  8. package/plugins/alibaba/icon.svg +1 -0
  9. package/plugins/alibaba/plugin.js +322 -0
  10. package/plugins/alibaba/plugin.json +20 -0
  11. package/plugins/alibaba-token-plan/icon-color.svg +1 -0
  12. package/plugins/alibaba-token-plan/icon.svg +1 -0
  13. package/plugins/alibaba-token-plan/plugin.js +544 -0
  14. package/plugins/alibaba-token-plan/plugin.json +19 -0
  15. package/plugins/amp/icon-color.svg +1 -0
  16. package/plugins/amp/icon-conflict.svg +6 -0
  17. package/plugins/amp/icon.svg +1 -0
  18. package/plugins/amp/plugin.js +175 -0
  19. package/plugins/amp/plugin.json +16 -0
  20. package/plugins/antigravity/icon-color.svg +1 -0
  21. package/plugins/antigravity/icon-conflict.svg +3 -0
  22. package/plugins/antigravity/icon.svg +1 -0
  23. package/plugins/antigravity/plugin.js +739 -0
  24. package/plugins/antigravity/plugin.json +31 -0
  25. package/plugins/antigravity-cli/icon.svg +3 -0
  26. package/plugins/antigravity-cli/plugin.js +570 -0
  27. package/plugins/antigravity-cli/plugin.json +16 -0
  28. package/plugins/antigravity-ide/icon.svg +3 -0
  29. package/plugins/antigravity-ide/plugin.js +257 -0
  30. package/plugins/antigravity-ide/plugin.json +29 -0
  31. package/plugins/augment/icon.svg +14 -0
  32. package/plugins/augment/plugin.js +59 -0
  33. package/plugins/augment/plugin.json +18 -0
  34. package/plugins/aws-bedrock/icon-color.svg +1 -0
  35. package/plugins/aws-bedrock/icon.svg +1 -0
  36. package/plugins/aws-bedrock/plugin.js +206 -0
  37. package/plugins/aws-bedrock/plugin.json +18 -0
  38. package/plugins/azure-openai/icon-color.svg +1 -0
  39. package/plugins/azure-openai/icon.svg +1 -0
  40. package/plugins/azure-openai/plugin.js +145 -0
  41. package/plugins/azure-openai/plugin.json +17 -0
  42. package/plugins/chutes/icon.svg +7 -0
  43. package/plugins/chutes/plugin.js +236 -0
  44. package/plugins/chutes/plugin.json +19 -0
  45. package/plugins/claude/icon-color.svg +1 -0
  46. package/plugins/claude/icon.svg +1 -0
  47. package/plugins/claude/plugin.js +1723 -0
  48. package/plugins/claude/plugin.json +30 -0
  49. package/plugins/codebuff/icon.svg +92 -0
  50. package/plugins/codebuff/plugin.js +113 -0
  51. package/plugins/codebuff/plugin.json +17 -0
  52. package/plugins/codex/icon-color.svg +5 -0
  53. package/plugins/codex/icon.svg +1 -0
  54. package/plugins/codex/plugin.js +1009 -0
  55. package/plugins/codex/plugin.json +82 -0
  56. package/plugins/command-code/icon.svg +3 -0
  57. package/plugins/command-code/plugin.js +184 -0
  58. package/plugins/command-code/plugin.json +20 -0
  59. package/plugins/copilot/icon-color.svg +1 -0
  60. package/plugins/copilot/icon.svg +1 -0
  61. package/plugins/copilot/plugin.js +684 -0
  62. package/plugins/copilot/plugin.json +21 -0
  63. package/plugins/crof/icon.svg +3 -0
  64. package/plugins/crof/plugin.js +53 -0
  65. package/plugins/crof/plugin.json +17 -0
  66. package/plugins/crofai/icon.svg +16 -0
  67. package/plugins/crofai/plugin.js +81 -0
  68. package/plugins/crofai/plugin.json +18 -0
  69. package/plugins/cursor/icon-conflict.svg +3 -0
  70. package/plugins/cursor/icon.svg +1 -0
  71. package/plugins/cursor/plugin.js +1379 -0
  72. package/plugins/cursor/plugin.json +23 -0
  73. package/plugins/cursor-nightly/icon.png +0 -0
  74. package/plugins/cursor-nightly/plugin.js +1379 -0
  75. package/plugins/cursor-nightly/plugin.json +23 -0
  76. package/plugins/deepgram/icon.svg +1 -0
  77. package/plugins/deepgram/plugin.js +20 -0
  78. package/plugins/deepgram/plugin.json +17 -0
  79. package/plugins/deepseek/icon-color.svg +1 -0
  80. package/plugins/deepseek/icon.svg +1 -0
  81. package/plugins/deepseek/plugin.js +71 -0
  82. package/plugins/deepseek/plugin.json +17 -0
  83. package/plugins/devin/icon.svg +5 -0
  84. package/plugins/devin/plugin.js +341 -0
  85. package/plugins/devin/plugin.json +16 -0
  86. package/plugins/doubao/icon-color.svg +1 -0
  87. package/plugins/doubao/icon.svg +1 -0
  88. package/plugins/doubao/plugin.js +92 -0
  89. package/plugins/doubao/plugin.json +17 -0
  90. package/plugins/droid/icon.svg +1 -0
  91. package/plugins/droid/plugin.json +20 -0
  92. package/plugins/elevenlabs/icon.svg +4 -0
  93. package/plugins/elevenlabs/plugin.js +48 -0
  94. package/plugins/elevenlabs/plugin.json +18 -0
  95. package/plugins/factory/icon-conflict.svg +1 -0
  96. package/plugins/factory/icon.svg +1 -0
  97. package/plugins/factory/plugin.js +487 -0
  98. package/plugins/factory/plugin.json +15 -0
  99. package/plugins/fireworks-ai/icon.svg +6 -0
  100. package/plugins/fireworks-ai/plugin.js +370 -0
  101. package/plugins/fireworks-ai/plugin.json +19 -0
  102. package/plugins/gemini/icon-color.svg +1 -0
  103. package/plugins/gemini/icon.svg +1 -0
  104. package/plugins/gemini/plugin.js +460 -0
  105. package/plugins/gemini/plugin.json +16 -0
  106. package/plugins/grok/icon.svg +1 -0
  107. package/plugins/grok/plugin.js +297 -0
  108. package/plugins/grok/plugin.json +19 -0
  109. package/plugins/groqcloud/icon.svg +1 -0
  110. package/plugins/groqcloud/plugin.js +117 -0
  111. package/plugins/groqcloud/plugin.json +19 -0
  112. package/plugins/jetbrains-ai-assistant/icon-color.svg +14 -0
  113. package/plugins/jetbrains-ai-assistant/icon-conflict.svg +3 -0
  114. package/plugins/jetbrains-ai-assistant/icon.svg +14 -0
  115. package/plugins/jetbrains-ai-assistant/plugin.js +313 -0
  116. package/plugins/jetbrains-ai-assistant/plugin.json +16 -0
  117. package/plugins/kilo/icon.svg +1 -0
  118. package/plugins/kilo/plugin.js +111 -0
  119. package/plugins/kilo/plugin.json +19 -0
  120. package/plugins/kimi/icon-color.svg +1 -0
  121. package/plugins/kimi/icon-conflict.svg +3 -0
  122. package/plugins/kimi/icon.svg +1 -0
  123. package/plugins/kimi/plugin.js +358 -0
  124. package/plugins/kimi/plugin.json +15 -0
  125. package/plugins/kimi-k2/icon-color.svg +1 -0
  126. package/plugins/kimi-k2/icon.svg +1 -0
  127. package/plugins/kimi-k2/plugin.js +58 -0
  128. package/plugins/kimi-k2/plugin.json +18 -0
  129. package/plugins/kiro/icon-color.svg +5 -0
  130. package/plugins/kiro/icon-conflict.svg +1 -0
  131. package/plugins/kiro/icon.svg +4 -0
  132. package/plugins/kiro/plugin.js +390 -0
  133. package/plugins/kiro/plugin.json +16 -0
  134. package/plugins/litellm/icon.svg +16 -0
  135. package/plugins/litellm/plugin.js +199 -0
  136. package/plugins/litellm/plugin.json +16 -0
  137. package/plugins/llm-proxy/icon.svg +1 -0
  138. package/plugins/llm-proxy/plugin.js +40 -0
  139. package/plugins/llm-proxy/plugin.json +17 -0
  140. package/plugins/manus/icon.svg +6 -0
  141. package/plugins/manus/plugin.js +50 -0
  142. package/plugins/manus/plugin.json +17 -0
  143. package/plugins/mimo/icon.svg +1 -0
  144. package/plugins/mimo/plugin.js +177 -0
  145. package/plugins/mimo/plugin.json +19 -0
  146. package/plugins/minimax/icon-color.svg +1 -0
  147. package/plugins/minimax/icon-conflict.svg +4 -0
  148. package/plugins/minimax/icon.svg +1 -0
  149. package/plugins/minimax/plugin.js +453 -0
  150. package/plugins/minimax/plugin.json +14 -0
  151. package/plugins/mistral/icon-color.svg +1 -0
  152. package/plugins/mistral/icon.svg +1 -0
  153. package/plugins/mistral/plugin.js +152 -0
  154. package/plugins/mistral/plugin.json +18 -0
  155. package/plugins/moonshot/icon.svg +1 -0
  156. package/plugins/moonshot/plugin.js +43 -0
  157. package/plugins/moonshot/plugin.json +16 -0
  158. package/plugins/nanogpt/icon-color.svg +17 -0
  159. package/plugins/nanogpt/icon.svg +10 -0
  160. package/plugins/nanogpt/plugin.js +80 -0
  161. package/plugins/nanogpt/plugin.json +17 -0
  162. package/plugins/neuralwatt/icon.svg +3 -0
  163. package/plugins/neuralwatt/plugin.js +152 -0
  164. package/plugins/neuralwatt/plugin.json +17 -0
  165. package/plugins/ollama/icon.svg +1 -0
  166. package/plugins/ollama/plugin.js +92 -0
  167. package/plugins/ollama/plugin.json +20 -0
  168. package/plugins/openai-api/icon.svg +1 -0
  169. package/plugins/openai-api/plugin.js +533 -0
  170. package/plugins/openai-api/plugin.json +26 -0
  171. package/plugins/opencode/icon.svg +1 -0
  172. package/plugins/opencode/plugin.js +342 -0
  173. package/plugins/opencode/plugin.json +19 -0
  174. package/plugins/opencode-go/icon-conflict.svg +1 -0
  175. package/plugins/opencode-go/icon.svg +1 -0
  176. package/plugins/opencode-go/plugin.js +617 -0
  177. package/plugins/opencode-go/plugin.json +50 -0
  178. package/plugins/openrouter/icon.svg +1 -0
  179. package/plugins/openrouter/plugin.js +156 -0
  180. package/plugins/openrouter/plugin.json +8 -0
  181. package/plugins/perplexity/icon-color.svg +1 -0
  182. package/plugins/perplexity/icon-conflict.svg +1 -0
  183. package/plugins/perplexity/icon.svg +1 -0
  184. package/plugins/perplexity/plugin.js +425 -0
  185. package/plugins/perplexity/plugin.json +19 -0
  186. package/plugins/poe/icon.svg +1 -0
  187. package/plugins/poe/plugin.js +190 -0
  188. package/plugins/poe/plugin.json +19 -0
  189. package/plugins/stepfun/icon-color.svg +1 -0
  190. package/plugins/stepfun/icon.svg +1 -0
  191. package/plugins/stepfun/plugin.js +155 -0
  192. package/plugins/stepfun/plugin.json +18 -0
  193. package/plugins/synthetic/icon-conflict.svg +12 -0
  194. package/plugins/synthetic/icon.svg +14 -0
  195. package/plugins/synthetic/plugin.js +331 -0
  196. package/plugins/synthetic/plugin.json +23 -0
  197. package/plugins/t3chat/icon-color.svg +28 -0
  198. package/plugins/t3chat/icon.svg +4 -0
  199. package/plugins/t3chat/plugin.js +243 -0
  200. package/plugins/t3chat/plugin.json +20 -0
  201. package/plugins/venice/icon-color.svg +1 -0
  202. package/plugins/venice/icon.svg +1 -0
  203. package/plugins/venice/plugin.js +69 -0
  204. package/plugins/venice/plugin.json +16 -0
  205. package/plugins/vertex-ai/icon-color.svg +1 -0
  206. package/plugins/vertex-ai/icon.svg +1 -0
  207. package/plugins/vertex-ai/plugin.js +172 -0
  208. package/plugins/vertex-ai/plugin.json +18 -0
  209. package/plugins/warp/icon-color.svg +10 -0
  210. package/plugins/warp/icon.svg +6 -0
  211. package/plugins/warp/plugin.js +108 -0
  212. package/plugins/warp/plugin.json +18 -0
  213. package/plugins/windsurf/icon-conflict.svg +3 -0
  214. package/plugins/windsurf/icon.svg +1 -0
  215. package/plugins/windsurf/plugin.js +208 -0
  216. package/plugins/windsurf/plugin.json +16 -0
  217. package/plugins/zai/icon-conflict.svg +5 -0
  218. package/plugins/zai/icon.svg +1 -0
  219. package/plugins/zai/plugin.js +188 -0
  220. package/plugins/zai/plugin.json +16 -0
  221. package/plugins/zed/icon.svg +4 -0
  222. package/plugins/zed/plugin.js +270 -0
  223. package/plugins/zed/plugin.json +19 -0
  224. package/usagestat +0 -0
  225. package/usagestatd +0 -0
@@ -0,0 +1,390 @@
1
+ (function () {
2
+ const STATE_KEY = "kiro.kiroAgent"
3
+ const LOG_FILE_NAME = "q-client.log"
4
+ const TOKEN_PATH = "~/.aws/sso/cache/kiro-auth-token.json"
5
+ const REFRESH_URL = "https://prod.us-east-1.auth.desktop.kiro.dev/refreshToken"
6
+ const LIVE_STALE_MS = 15 * 60 * 1000
7
+ const REFRESH_BUFFER_MS = 10 * 60 * 1000
8
+ const DEFAULT_REGION = "us-east-1"
9
+ const COUNT_FORMAT = { kind: "count", suffix: "credits" }
10
+ const LOGIN_HINT = "Open Kiro and sign in, then try again."
11
+ const SESSION_HINT = "Kiro session expired. Open Kiro and sign in again."
12
+ const DATA_HINT = "Kiro usage data unavailable. Open the Kiro account dashboard once and try again."
13
+
14
+ function appPath(ctx, suffix) {
15
+ const settings = (ctx.provider && ctx.provider.settings) || {}
16
+ const root = typeof settings.userDataDir === "string" && settings.userDataDir
17
+ ? settings.userDataDir
18
+ : ctx.host.fs.appSupportPath("Kiro")
19
+ if (!root) throw { code: "failed", message: "Kiro data directory unavailable. Set settings.userDataDir to the IDE user data directory." }
20
+ return root.replace(/[\\/]+$/, "") + "/" + suffix
21
+ }
22
+
23
+ function num(value) {
24
+ if (typeof value === "number") return Number.isFinite(value) ? value : null
25
+ if (typeof value !== "string" || !value.trim()) return null
26
+ const parsed = Number(value)
27
+ return Number.isFinite(parsed) ? parsed : null
28
+ }
29
+ function first() {
30
+ for (let i = 0; i < arguments.length; i += 1) {
31
+ const value = num(arguments[i])
32
+ if (value !== null) return value
33
+ }
34
+ return null
35
+ }
36
+ function iso(ctx, value) {
37
+ const normalized = ctx.util.toIso(value)
38
+ return typeof normalized === "string" && normalized ? normalized : null
39
+ }
40
+ function title(value) {
41
+ const trimmed = String(value || "").trim()
42
+ return trimmed
43
+ ? trimmed
44
+ .toLowerCase()
45
+ .split(/\s+/)
46
+ .map((part) => part.charAt(0).toUpperCase() + part.slice(1))
47
+ .join(" ")
48
+ : null
49
+ }
50
+ function sanitizeAuth(token) {
51
+ if (!token || typeof token !== "object") return null
52
+ if (token.provider === "Google" || token.provider === "Github") return { ...token, authMethod: "social" }
53
+ if (token.provider === "ExternalIdp") return { ...token, authMethod: "external_idp" }
54
+ if (token.provider === "Enterprise" || token.provider === "BuilderId" || token.provider === "Internal") {
55
+ return { ...token, authMethod: "IdC" }
56
+ }
57
+ return token
58
+ }
59
+ function readJsonFile(ctx, path, label) {
60
+ if (!ctx.host.fs.exists(path)) return null
61
+ try {
62
+ return ctx.util.tryParseJson(ctx.host.fs.readText(path))
63
+ } catch (e) {
64
+ ctx.host.log.warn(label + " read failed: " + String(e))
65
+ return null
66
+ }
67
+ }
68
+ function loadAuthState(ctx) {
69
+ const parsed = readJsonFile(ctx, TOKEN_PATH, "auth token")
70
+ if (!parsed || typeof parsed !== "object") return null
71
+ const token = sanitizeAuth(parsed)
72
+ return token && (token.refreshToken || token.accessToken) ? { path: TOKEN_PATH, token } : null
73
+ }
74
+ function saveAuthState(ctx, authState) {
75
+ try {
76
+ ctx.host.fs.writeText(authState.path, JSON.stringify(authState.token, null, 2))
77
+ return true
78
+ } catch (e) {
79
+ ctx.host.log.warn("failed to persist refreshed Kiro auth: " + String(e))
80
+ return false
81
+ }
82
+ }
83
+ function loadProfileArn(ctx, authState) {
84
+ const fromToken = authState && authState.token && authState.token.profileArn
85
+ if (typeof fromToken === "string" && fromToken) return fromToken
86
+ const parsed = readJsonFile(ctx, appPath(ctx, "User/globalStorage/kiro.kiroagent/profile.json"), "profile")
87
+ return parsed && typeof parsed.arn === "string" && parsed.arn.trim() ? parsed.arn.trim() : null
88
+ }
89
+ function regionFromArn(profileArn) {
90
+ const parts = String(profileArn || "").split(":")
91
+ return parts.length > 3 && parts[3] ? parts[3] : DEFAULT_REGION
92
+ }
93
+ function readStateValue(ctx, key) {
94
+ try {
95
+ const sql = "SELECT value FROM ItemTable WHERE key = '" + String(key).replace(/'/g, "''") + "' LIMIT 1;"
96
+ const rows = ctx.util.tryParseJson(ctx.host.sqlite.query(appPath(ctx, "User/globalStorage/state.vscdb"), sql))
97
+ return Array.isArray(rows) && rows.length && typeof rows[0].value === "string" ? rows[0].value : null
98
+ } catch (e) {
99
+ ctx.host.log.warn("Kiro sqlite read failed: " + String(e))
100
+ return null
101
+ }
102
+ }
103
+ function normalizePool(ctx, raw, config) {
104
+ if (!raw || typeof raw !== "object") return null
105
+ if (config.statusKey) {
106
+ const status = raw[config.statusKey]
107
+ if (status && !config.allowedStatuses.includes(status)) return null
108
+ }
109
+ const currentUsage = first(raw[config.preciseCurrent], raw[config.current])
110
+ const usageLimit = first(raw[config.preciseLimit], raw[config.limit])
111
+ if (currentUsage === null || usageLimit === null || usageLimit <= 0) return null
112
+ return {
113
+ currentUsage,
114
+ usageLimit,
115
+ expiryDate: iso(ctx, raw[config.expiryA] || raw[config.expiryB]),
116
+ displayName: typeof raw.displayName === "string" && raw.displayName.trim() ? raw.displayName.trim() : null,
117
+ }
118
+ }
119
+ function normalizeBreakdown(ctx, raw) {
120
+ if (!raw || typeof raw !== "object") return null
121
+ const currentUsage = first(raw.currentUsageWithPrecision, raw.currentUsage)
122
+ const usageLimit = first(raw.usageLimitWithPrecision, raw.usageLimit)
123
+ if (currentUsage === null || usageLimit === null || usageLimit <= 0) return null
124
+ const bonuses = Array.isArray(raw.bonuses)
125
+ ? raw.bonuses
126
+ .map((item) =>
127
+ normalizePool(ctx, item, {
128
+ current: "currentUsage",
129
+ preciseCurrent: null,
130
+ limit: "usageLimit",
131
+ preciseLimit: null,
132
+ expiryA: "expiresAt",
133
+ expiryB: "expiryDate",
134
+ statusKey: "status",
135
+ allowedStatuses: ["ACTIVE", "EXHAUSTED"],
136
+ })
137
+ )
138
+ .filter(Boolean)
139
+ : []
140
+
141
+ return {
142
+ type: typeof raw.resourceType === "string" ? raw.resourceType : raw.type,
143
+ currentUsage,
144
+ usageLimit,
145
+ resetDate: iso(ctx, raw.nextDateReset || raw.resetDate),
146
+ freeTrialUsage: normalizePool(ctx, raw.freeTrialInfo || raw.freeTrialUsage, {
147
+ current: "currentUsage",
148
+ preciseCurrent: "currentUsageWithPrecision",
149
+ limit: "usageLimit",
150
+ preciseLimit: "usageLimitWithPrecision",
151
+ expiryA: "freeTrialExpiry",
152
+ expiryB: "expiryDate",
153
+ statusKey: "freeTrialStatus",
154
+ allowedStatuses: ["ACTIVE"],
155
+ }),
156
+ bonuses,
157
+ }
158
+ }
159
+ function normalizeCachedState(ctx) {
160
+ const parsed = ctx.util.tryParseJson(readStateValue(ctx, STATE_KEY))
161
+ const usageState = parsed && parsed["kiro.resourceNotifications.usageState"]
162
+ if (!usageState || !Array.isArray(usageState.usageBreakdowns)) return null
163
+ const usageBreakdowns = usageState.usageBreakdowns.map((item) => normalizeBreakdown(ctx, item)).filter(Boolean)
164
+ return usageBreakdowns.length
165
+ ? { usageBreakdowns, timestampMs: first(usageState.timestamp), plan: null, overageEnabled: null }
166
+ : null
167
+ }
168
+ function normalizeApiSnapshot(ctx, raw, timestampMs) {
169
+ if (!raw || typeof raw !== "object") return null
170
+ return {
171
+ usageBreakdowns: Array.isArray(raw.usageBreakdownList)
172
+ ? raw.usageBreakdownList.map((item) => normalizeBreakdown(ctx, item)).filter(Boolean)
173
+ : [],
174
+ timestampMs: timestampMs !== null ? timestampMs : null,
175
+ plan: title(raw.subscriptionInfo && raw.subscriptionInfo.subscriptionTitle),
176
+ overageEnabled: raw.overageConfiguration ? raw.overageConfiguration.overageStatus === "ENABLED" : null,
177
+ }
178
+ }
179
+ function parseUsageLogText(ctx, text) {
180
+ const lines = String(text || "").split(/\r?\n/)
181
+ for (let i = lines.length - 1; i >= 0; i -= 1) {
182
+ const line = lines[i]
183
+ if (line.indexOf('"commandName":"GetUsageLimitsCommand"') === -1) continue
184
+ const jsonStart = line.indexOf("{")
185
+ if (jsonStart === -1) continue
186
+ const parsed = ctx.util.tryParseJson(line.slice(jsonStart))
187
+ if (!parsed || !parsed.output) continue
188
+ const loggedAt = line.slice(0, jsonStart).trim().split(" [")[0]
189
+ return normalizeApiSnapshot(ctx, parsed.output, loggedAt ? ctx.util.parseDateMs(loggedAt.replace(" ", "T")) : null)
190
+ }
191
+ return null
192
+ }
193
+ function loadLoggedState(ctx) {
194
+ const logsRoot = appPath(ctx, "logs")
195
+ let sessions = []
196
+ try {
197
+ sessions = ctx.host.fs.listDir(logsRoot).slice().sort().reverse()
198
+ } catch {
199
+ return null
200
+ }
201
+ for (let i = 0; i < sessions.length && i < 12; i += 1) {
202
+ const sessionRoot = logsRoot + "/" + sessions[i]
203
+ let windows = []
204
+ try {
205
+ windows = ctx.host.fs.listDir(sessionRoot).slice().sort().reverse()
206
+ } catch {
207
+ continue
208
+ }
209
+ for (let j = 0; j < windows.length; j += 1) {
210
+ const logPath = sessionRoot + "/" + windows[j] + "/exthost/kiro.kiroAgent/" + LOG_FILE_NAME
211
+ if (!ctx.host.fs.exists(logPath)) continue
212
+ try {
213
+ const snapshot = parseUsageLogText(ctx, ctx.host.fs.readText(logPath))
214
+ if (snapshot) return snapshot
215
+ } catch (e) {
216
+ ctx.host.log.warn("failed to parse Kiro usage log: " + String(e))
217
+ }
218
+ }
219
+ }
220
+ return null
221
+ }
222
+ function buildUserAgent(ctx) {
223
+ return "OpenUsage/" + String(ctx.app && ctx.app.version ? ctx.app.version : "0.0.0")
224
+ }
225
+ function needsRefresh(ctx, authState, nowMs) {
226
+ return ctx.util.needsRefreshByExpiry({
227
+ nowMs,
228
+ expiresAtMs: ctx.util.parseDateMs(authState.token && authState.token.expiresAt),
229
+ bufferMs: REFRESH_BUFFER_MS,
230
+ })
231
+ }
232
+ function buildUsageHeaders(ctx, authState, accessToken) {
233
+ const headers = { Authorization: "Bearer " + accessToken, Accept: "application/json", "User-Agent": buildUserAgent(ctx) }
234
+ if (authState.token && authState.token.authMethod === "external_idp") headers.TokenType = "EXTERNAL_IDP"
235
+ if (authState.token && authState.token.provider === "Internal") headers["redirect-for-internal"] = "true"
236
+ return headers
237
+ }
238
+ function refreshAccessToken(ctx, authState, nowMs) {
239
+ if (!authState.token || !authState.token.refreshToken) throw SESSION_HINT
240
+ const { resp, json } = ctx.util.requestJson({
241
+ method: "POST",
242
+ url: REFRESH_URL,
243
+ headers: { "Content-Type": "application/json", "User-Agent": buildUserAgent(ctx) },
244
+ bodyText: JSON.stringify({ refreshToken: authState.token.refreshToken }),
245
+ timeoutMs: 15000,
246
+ })
247
+ if (ctx.util.isAuthStatus(resp.status)) throw SESSION_HINT
248
+ if (resp.status < 200 || resp.status >= 300 || !json || typeof json.accessToken !== "string" || !json.accessToken) {
249
+ ctx.host.log.warn("Kiro token refresh failed: HTTP " + resp.status)
250
+ return null
251
+ }
252
+ const expiresIn = first(json.expiresIn, json.expires_in)
253
+ authState.token = sanitizeAuth({
254
+ ...authState.token,
255
+ accessToken: json.accessToken,
256
+ refreshToken: typeof json.refreshToken === "string" && json.refreshToken ? json.refreshToken : authState.token.refreshToken,
257
+ profileArn: typeof json.profileArn === "string" && json.profileArn ? json.profileArn : authState.token.profileArn,
258
+ expiresAt: expiresIn !== null && expiresIn > 0 ? new Date(nowMs + expiresIn * 1000).toISOString() : authState.token.expiresAt,
259
+ })
260
+ saveAuthState(ctx, authState)
261
+ return authState.token.accessToken
262
+ }
263
+ function fetchLiveState(ctx, authState, nowMs) {
264
+ const profileArn = loadProfileArn(ctx, authState)
265
+ if (!profileArn) return null
266
+ const url =
267
+ "https://q." +
268
+ regionFromArn(profileArn) +
269
+ ".amazonaws.com/getUsageLimits?origin=" +
270
+ encodeURIComponent("AI_EDITOR") +
271
+ "&profileArn=" +
272
+ encodeURIComponent(profileArn) +
273
+ "&resourceType=" +
274
+ encodeURIComponent("AGENTIC_REQUEST")
275
+
276
+ let accessToken = authState.token && authState.token.accessToken
277
+ if (!accessToken || needsRefresh(ctx, authState, nowMs)) {
278
+ const refreshed = refreshAccessToken(ctx, authState, nowMs)
279
+ if (refreshed) accessToken = refreshed
280
+ }
281
+ if (!accessToken) throw SESSION_HINT
282
+
283
+ const resp = ctx.util.retryOnceOnAuth({
284
+ request: (tokenOverride) =>
285
+ ctx.util.request({
286
+ method: "GET",
287
+ url,
288
+ headers: buildUsageHeaders(ctx, authState, tokenOverride || accessToken),
289
+ timeoutMs: 15000,
290
+ }),
291
+ refresh: () => refreshAccessToken(ctx, authState, nowMs),
292
+ })
293
+
294
+ if (ctx.util.isAuthStatus(resp.status)) throw SESSION_HINT
295
+ if (resp.status < 200 || resp.status >= 300) {
296
+ ctx.host.log.warn("Kiro live usage request failed: HTTP " + resp.status)
297
+ return null
298
+ }
299
+ const parsed = ctx.util.tryParseJson(resp.bodyText)
300
+ if (!parsed) {
301
+ ctx.host.log.warn("Kiro live usage response invalid JSON")
302
+ return null
303
+ }
304
+ return normalizeApiSnapshot(ctx, parsed, nowMs)
305
+ }
306
+ function shouldTryLive(localState, loggedState, nowMs) {
307
+ return !localState || !loggedState || !loggedState.plan || localState.timestampMs === null || nowMs - localState.timestampMs > LIVE_STALE_MS
308
+ }
309
+ function mergeSnapshots(localState, loggedState, liveState, nowMs) {
310
+ const usageSource =
311
+ liveState && liveState.usageBreakdowns.length
312
+ ? liveState
313
+ : localState && localState.usageBreakdowns.length
314
+ ? localState
315
+ : loggedState && loggedState.usageBreakdowns.length
316
+ ? loggedState
317
+ : null
318
+ if (!usageSource) return null
319
+ return {
320
+ plan: (liveState && liveState.plan) || (loggedState && loggedState.plan) || null,
321
+ overageEnabled:
322
+ liveState && liveState.overageEnabled !== null
323
+ ? liveState.overageEnabled
324
+ : loggedState && loggedState.overageEnabled !== null
325
+ ? loggedState.overageEnabled
326
+ : null,
327
+ usageBreakdowns: usageSource.usageBreakdowns,
328
+ timestampMs: usageSource.timestampMs !== null ? usageSource.timestampMs : nowMs,
329
+ }
330
+ }
331
+ function pickPrimaryBreakdown(usageBreakdowns) {
332
+ for (let i = 0; i < usageBreakdowns.length; i += 1) if (usageBreakdowns[i].type === "CREDIT") return usageBreakdowns[i]
333
+ return usageBreakdowns.length ? usageBreakdowns[0] : null
334
+ }
335
+ function pickBonusUsage(primary) {
336
+ return !primary ? null : primary.freeTrialUsage && primary.freeTrialUsage.usageLimit > 0 ? primary.freeTrialUsage : primary.bonuses && primary.bonuses.length ? primary.bonuses[0] : null
337
+ }
338
+ function formatAge(nowMs, timestampMs) {
339
+ if (!Number.isFinite(nowMs) || !Number.isFinite(timestampMs)) return null
340
+ const diffMs = Math.max(0, nowMs - timestampMs)
341
+ if (diffMs < 60000) return "Just now"
342
+ const minutes = Math.floor(diffMs / 60000)
343
+ if (minutes < 60) return minutes + "m ago"
344
+ const hours = Math.floor(minutes / 60)
345
+ return hours < 48 ? hours + "h ago" : Math.floor(hours / 24) + "d ago"
346
+ }
347
+ function buildOutput(ctx, snapshot, nowMs) {
348
+ const primary = pickPrimaryBreakdown(snapshot.usageBreakdowns)
349
+ if (!primary) throw DATA_HINT
350
+ const lines = [ctx.line.progress({ label: "Credits", used: primary.currentUsage, limit: primary.usageLimit, format: COUNT_FORMAT, resetsAt: primary.resetDate || undefined })]
351
+ const bonusUsage = pickBonusUsage(primary)
352
+ if (bonusUsage) {
353
+ lines.push(
354
+ ctx.line.progress({
355
+ label: "Bonus Credits",
356
+ used: bonusUsage.currentUsage,
357
+ limit: bonusUsage.usageLimit,
358
+ format: COUNT_FORMAT,
359
+ resetsAt: bonusUsage.expiryDate || undefined,
360
+ })
361
+ )
362
+ }
363
+ if (snapshot.overageEnabled !== null) lines.push(ctx.line.badge({ label: "Overages", text: snapshot.overageEnabled ? "Enabled" : "Disabled" }))
364
+ return { plan: snapshot.plan || undefined, lines }
365
+ }
366
+ function probe(ctx) {
367
+ const nowMs = ctx.util.parseDateMs(ctx.nowIso) || Date.now()
368
+ const authState = loadAuthState(ctx)
369
+ if (!authState || !authState.token || !authState.token.refreshToken) throw LOGIN_HINT
370
+ const localState = normalizeCachedState(ctx)
371
+ const loggedState = loadLoggedState(ctx)
372
+ let liveState = null
373
+ let liveError = null
374
+ if (shouldTryLive(localState, loggedState, nowMs)) {
375
+ try {
376
+ liveState = fetchLiveState(ctx, authState, nowMs)
377
+ } catch (e) {
378
+ liveError = e
379
+ ctx.host.log.warn("Kiro live fallback failed: " + String(e))
380
+ }
381
+ }
382
+ const snapshot = mergeSnapshots(localState, loggedState, liveState, nowMs)
383
+ if (!snapshot) {
384
+ if (typeof liveError === "string" && liveError) throw liveError
385
+ throw DATA_HINT
386
+ }
387
+ return buildOutput(ctx, snapshot, nowMs)
388
+ }
389
+ globalThis.__openusage_plugin = { id: "kiro", probe }
390
+ })()
@@ -0,0 +1,16 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "id": "kiro",
4
+ "name": "Kiro",
5
+ "version": "0.0.1",
6
+ "entry": "plugin.js",
7
+ "icon": "icon.svg",
8
+ "brandColor": "#C09CFF",
9
+ "supportedModes": ["oauth", "local"],
10
+ "autoMode": "local",
11
+ "lines": [
12
+ { "type": "progress", "label": "Credits", "scope": "overview", "primaryOrder": 1 },
13
+ { "type": "progress", "label": "Bonus Credits", "scope": "overview" },
14
+ { "type": "badge", "label": "Overages", "scope": "detail" }
15
+ ]
16
+ }
@@ -0,0 +1,16 @@
1
+ <svg viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg">
2
+ <g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round">
3
+ <path d="M8.1 12.4C12.5 8.8 17.7 6.3 22.4 5.7c0-.5.5-.7 1-.8 1.8-.4 4.1-.5 5.8-.5.6 0 1 .4 1 1v19c0 .6-.4 1-1 1H10c-.7 0-1.3 0-1.9-.1V12.4Z" stroke-width="1.35"/>
4
+ <path d="M8.1 12.4C5 14.8 3 17 2.8 19.2c-.2 1.9 1.8 3.5 5.3 4.5V12.4Z" fill="currentColor" stroke-width="1.35"/>
5
+ <path d="M10.6 19.5h3.7" stroke-width="1.35"/>
6
+ <path d="M20.6 25.5l-1.3 2.6c.5.3 1.1.4 1.7.2.9-.2 1.6-1.2 1.7-2.8" stroke-width="1.2"/>
7
+ <path d="M25.3 25.5c.1 1.8 1.2 3 2.6 3s2.5-1.2 2.6-3" stroke-width="1.2"/>
8
+ <path d="M25.3 15.5h2.9c.5 0 .9.4.9.9v6.9c0 .5-.4.9-.9.9h-2.9c-.5 0-.9-.4-.9-.9v-6.9c0-.5.4-.9.9-.9Z" stroke-width="1.2"/>
9
+ <path d="M17.3 22.6h7.1m4.7 0h1.1" stroke-width="1.05"/>
10
+ </g>
11
+ <path d="M12.9 13.1c4.2-2.3 8.3-3.5 12.7-3.6.8.2 1.8 1.5 2.4 3.1-5.3.2-10.3.8-15.2 2.6-1.2.4-1.5-1.2.1-2.1Z" fill="currentColor"/>
12
+ <path d="M20.4 16.5h4v5.4h-6.6l2.6-5.4Z" fill="currentColor"/>
13
+ <path d="M29.1 16.5h1.1v5.4h-1.1v-5.4Z" fill="currentColor"/>
14
+ <path d="M10.9 25.5h9.5l-1.6 2.3H10c-.8 0-1.1-.7-.7-1.3l1.6-1Z" fill="currentColor"/>
15
+ <path d="M25.8 25.5h4c-.1 1.2-.9 2.1-2 2.1s-1.9-.9-2-2.1Z" fill="currentColor"/>
16
+ </svg>
@@ -0,0 +1,199 @@
1
+ (function () {
2
+ function env(ctx, name) {
3
+ try {
4
+ var value = ctx.host.env.get(name);
5
+ return typeof value === "string" && value.trim() ? value.trim() : null;
6
+ } catch (_) {
7
+ return null;
8
+ }
9
+ }
10
+
11
+ function apiKey(ctx) {
12
+ var configured = ctx.provider && typeof ctx.provider.apiKey === "string" ? ctx.provider.apiKey.trim() : "";
13
+ return configured || env(ctx, "LITELLM_API_KEY");
14
+ }
15
+
16
+ function setting(ctx, names) {
17
+ var settings = ctx.provider && ctx.provider.settings ? ctx.provider.settings : {};
18
+ for (var i = 0; i < names.length; i++) {
19
+ var value = settings[names[i]];
20
+ if (typeof value === "string" && value.trim()) return value.trim();
21
+ }
22
+ return null;
23
+ }
24
+
25
+ function baseUrl(ctx) {
26
+ var configured = setting(ctx, ["enterpriseHost", "baseUrl", "baseURL", "apiUrl", "apiURL"]);
27
+ var base = configured || env(ctx, "LITELLM_BASE_URL");
28
+ if (!base) throw "Missing LiteLLM base URL. Set LITELLM_BASE_URL or provider settings.enterpriseHost.";
29
+ base = base.trim().replace(/\/+$/, "");
30
+ if (base.toLowerCase().endsWith("/v1")) base = base.slice(0, -3).replace(/\/+$/, "");
31
+ if (!/^https?:\/\//i.test(base)) throw "LiteLLM base URL is invalid.";
32
+ return base;
33
+ }
34
+
35
+ function numberValue(value) {
36
+ if (typeof value === "number" && Number.isFinite(value)) return value;
37
+ if (typeof value === "string" && value.trim()) {
38
+ var parsed = Number(value.replace(/[$,]/g, ""));
39
+ if (Number.isFinite(parsed)) return parsed;
40
+ }
41
+ return null;
42
+ }
43
+
44
+ function nonEmpty(value) {
45
+ return typeof value === "string" && value.trim() ? value.trim() : null;
46
+ }
47
+
48
+ function parseDate(value) {
49
+ if (typeof value !== "string" || !value.trim()) return null;
50
+ var ms = Date.parse(value);
51
+ return Number.isFinite(ms) ? new Date(ms) : null;
52
+ }
53
+
54
+ function requestJson(ctx, base, path, key) {
55
+ var resp = ctx.util.request({
56
+ method: "GET",
57
+ url: base + path,
58
+ headers: { Authorization: "Bearer " + key, Accept: "application/json" },
59
+ timeoutMs: 15000,
60
+ });
61
+ if (ctx.util.isAuthStatus(resp.status)) throw "LiteLLM API key was rejected.";
62
+ if (resp.status < 200 || resp.status >= 300) {
63
+ throw "LiteLLM API request failed (HTTP " + resp.status + ").";
64
+ }
65
+ var json = ctx.util.tryParseJson(resp.bodyText);
66
+ if (!json) throw "LiteLLM response was not valid JSON.";
67
+ return json;
68
+ }
69
+
70
+ function keyInfo(json) {
71
+ var info = json && json.info ? json.info : json;
72
+ var userID = nonEmpty(info && (info.user_id || info.userID));
73
+ var teamID = nonEmpty(info && (info.team_id || info.teamID));
74
+ if (!userID && !teamID) throw "LiteLLM key info did not include a user_id or team_id.";
75
+ return {
76
+ userID: userID,
77
+ teamID: teamID,
78
+ keyName: nonEmpty(info.key_name || info.keyName),
79
+ spendUSD: numberValue(info.spend) || 0,
80
+ expiresAt: parseDate(info.expires),
81
+ };
82
+ }
83
+
84
+ function findTeam(teams, teamID) {
85
+ if (!Array.isArray(teams) || !teamID) return null;
86
+ for (var i = 0; i < teams.length; i++) {
87
+ if (String(teams[i].team_id || teams[i].teamID || "") === teamID) return teams[i];
88
+ }
89
+ return null;
90
+ }
91
+
92
+ function usd(value) {
93
+ return "$" + (Number(value) || 0).toFixed(2);
94
+ }
95
+
96
+ function spendDetail(spend, budget, prefix) {
97
+ var value = budget != null && budget > 0 ? usd(spend) + " / " + usd(budget) : usd(spend);
98
+ return prefix ? prefix + ": " + value : value;
99
+ }
100
+
101
+ function spendLabel(label) {
102
+ return label.replace(/\s+budget$/i, " spend");
103
+ }
104
+
105
+ function addBudget(lines, label, spend, budget, resetAt, detailPrefix) {
106
+ if (budget != null && budget > 0) {
107
+ var line = ctxLineProgress(label, (spend / budget) * 100);
108
+ if (resetAt) line.resetsAt = resetAt.toISOString();
109
+ line.detail = spendDetail(spend, budget, detailPrefix);
110
+ lines.push(line);
111
+ return true;
112
+ } else if (spend > 0) {
113
+ lines.push({ type: "text", label: spendLabel(label), value: spendDetail(spend, null, detailPrefix) });
114
+ return true;
115
+ }
116
+ return false;
117
+ }
118
+
119
+ function ctxLineProgress(label, percent) {
120
+ return {
121
+ type: "progress",
122
+ label: label,
123
+ used: Math.max(0, Math.min(100, percent)),
124
+ limit: 100,
125
+ format: { kind: "percent" },
126
+ };
127
+ }
128
+
129
+ function userSnapshot(ctx, base, key, info) {
130
+ var encoded = encodeURIComponent(info.userID);
131
+ var json = requestJson(ctx, base, "/user/info?user_id=" + encoded, key);
132
+ var user = json.user_info || json.userInfo || json;
133
+ var responseID = nonEmpty(user.user_id || user.userID || json.user_id || json.userID);
134
+ if (responseID && responseID !== info.userID) throw "LiteLLM user_id did not match /key/info.";
135
+ var team = findTeam(json.teams, info.teamID);
136
+ return {
137
+ userID: info.userID,
138
+ email: nonEmpty(user.user_email || user.userEmail || user.user_alias || user.userAlias || (user.metadata && user.metadata.preferred_username)),
139
+ personalSpendUSD: numberValue(user.spend) || 0,
140
+ personalBudgetUSD: numberValue(user.max_budget || user.maxBudget),
141
+ personalResetAt: parseDate(user.budget_reset_at || user.budgetResetAt),
142
+ team: team ? {
143
+ id: String(team.team_id || team.teamID),
144
+ alias: nonEmpty(team.team_alias || team.teamAlias),
145
+ spendUSD: numberValue(team.spend) || 0,
146
+ budgetUSD: numberValue(team.max_budget || team.maxBudget),
147
+ resetAt: parseDate(team.budget_reset_at || team.budgetResetAt),
148
+ } : null,
149
+ keyName: info.keyName,
150
+ keyExpiresAt: info.expiresAt,
151
+ };
152
+ }
153
+
154
+ function teamSnapshot(ctx, base, key, info) {
155
+ var encoded = encodeURIComponent(info.teamID);
156
+ var json = requestJson(ctx, base, "/team/info?team_id=" + encoded, key);
157
+ var team = json.team_info || json.teamInfo || json;
158
+ var responseID = nonEmpty(team.team_id || team.teamID || json.team_id || json.teamID);
159
+ if (responseID && responseID !== info.teamID) throw "LiteLLM team_id did not match /key/info.";
160
+ return {
161
+ userID: null,
162
+ email: null,
163
+ personalSpendUSD: 0,
164
+ personalBudgetUSD: null,
165
+ personalResetAt: null,
166
+ team: {
167
+ id: info.teamID,
168
+ alias: nonEmpty(team.team_alias || team.teamAlias),
169
+ spendUSD: numberValue(team.spend) || 0,
170
+ budgetUSD: numberValue(team.max_budget || team.maxBudget),
171
+ resetAt: parseDate(team.budget_reset_at || team.budgetResetAt),
172
+ },
173
+ keyName: info.keyName,
174
+ keyExpiresAt: info.expiresAt,
175
+ };
176
+ }
177
+
178
+ function linesFor(snapshot) {
179
+ var lines = [];
180
+ addBudget(lines, "Personal budget", snapshot.personalSpendUSD, snapshot.personalBudgetUSD, snapshot.personalResetAt);
181
+ if (snapshot.team) addBudget(lines, "Team budget", snapshot.team.spendUSD, snapshot.team.budgetUSD, snapshot.team.resetAt, snapshot.team.alias ? "Team " + snapshot.team.alias : "Team");
182
+ if (!lines.length) lines.push({ type: "text", label: "Spend", value: usd(snapshot.personalSpendUSD || (snapshot.team && snapshot.team.spendUSD) || 0) });
183
+ if (snapshot.keyName) lines.push({ type: "text", label: "Key", value: snapshot.keyName });
184
+ if (snapshot.keyExpiresAt) lines.push({ type: "text", label: "Key Expires", value: snapshot.keyExpiresAt.toISOString().slice(0, 10) });
185
+ return lines;
186
+ }
187
+
188
+ function probe(ctx) {
189
+ var key = apiKey(ctx);
190
+ if (!key) throw "Missing LiteLLM API key. Set LITELLM_API_KEY or provider apiKey.";
191
+ var base = baseUrl(ctx);
192
+ var info = keyInfo(requestJson(ctx, base, "/key/info", key));
193
+ var snapshot = info.userID ? userSnapshot(ctx, base, key, info) : teamSnapshot(ctx, base, key, info);
194
+ var plan = snapshot.team && snapshot.team.alias ? snapshot.team.alias : null;
195
+ return { displayName: "LiteLLM", source: "api", plan: plan, lines: linesFor(snapshot) };
196
+ }
197
+
198
+ globalThis.__openusage_plugin = { id: "litellm", probe: probe };
199
+ })();
@@ -0,0 +1,16 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "id": "litellm",
4
+ "name": "LiteLLM",
5
+ "version": "0.0.1",
6
+ "entry": "plugin.js",
7
+ "icon": "icon.svg",
8
+ "brandColor": "#111827",
9
+ "supportedModes": ["api"],
10
+ "autoMode": "api",
11
+ "lines": [
12
+ { "type": "progress", "label": "Personal budget", "scope": "overview", "primaryOrder": 1 },
13
+ { "type": "progress", "label": "Team budget", "scope": "overview", "primaryOrder": 2 },
14
+ { "type": "text", "label": "Spend", "scope": "detail" }
15
+ ]
16
+ }
@@ -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>OpenAI</title><path d="M9.205 8.658v-2.26c0-.19.072-.333.238-.428l4.543-2.616c.619-.357 1.356-.523 2.117-.523 2.854 0 4.662 2.212 4.662 4.566 0 .167 0 .357-.024.547l-4.71-2.759a.797.797 0 00-.856 0l-5.97 3.473zm10.609 8.8V12.06c0-.333-.143-.57-.429-.737l-5.97-3.473 1.95-1.118a.433.433 0 01.476 0l4.543 2.617c1.309.76 2.189 2.378 2.189 3.948 0 1.808-1.07 3.473-2.76 4.163zM7.802 12.703l-1.95-1.142c-.167-.095-.239-.238-.239-.428V5.899c0-2.545 1.95-4.472 4.591-4.472 1 0 1.927.333 2.712.928L8.23 5.067c-.285.166-.428.404-.428.737v6.898zM12 15.128l-2.795-1.57v-3.33L12 8.658l2.795 1.57v3.33L12 15.128zm1.796 7.23c-1 0-1.927-.332-2.712-.927l4.686-2.712c.285-.166.428-.404.428-.737v-6.898l1.974 1.142c.167.095.238.238.238.428v5.233c0 2.545-1.974 4.472-4.614 4.472zm-5.637-5.303l-4.544-2.617c-1.308-.761-2.188-2.378-2.188-3.948A4.482 4.482 0 014.21 6.327v5.423c0 .333.143.571.428.738l5.947 3.449-1.95 1.118a.432.432 0 01-.476 0zm-.262 3.9c-2.688 0-4.662-2.021-4.662-4.519 0-.19.024-.38.047-.57l4.686 2.71c.286.167.571.167.856 0l5.97-3.448v2.26c0 .19-.07.333-.237.428l-4.543 2.616c-.619.357-1.356.523-2.117.523zm5.899 2.83a5.947 5.947 0 005.827-4.756C22.287 18.339 24 15.84 24 13.296c0-1.665-.713-3.282-1.998-4.448.119-.5.19-.999.19-1.498 0-3.401-2.759-5.947-5.946-5.947-.642 0-1.26.095-1.88.31A5.962 5.962 0 0010.205 0a5.947 5.947 0 00-5.827 4.757C1.713 5.447 0 7.945 0 10.49c0 1.666.713 3.283 1.998 4.448-.119.5-.19 1-.19 1.499 0 3.401 2.759 5.946 5.946 5.946.642 0 1.26-.095 1.88-.309a5.96 5.96 0 004.162 1.713z"></path></svg>