openusage 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/openusage +91 -0
- package/package.json +33 -0
- package/plugins/amp/icon.svg +6 -0
- package/plugins/amp/plugin.js +175 -0
- package/plugins/amp/plugin.json +20 -0
- package/plugins/amp/plugin.test.js +365 -0
- package/plugins/antigravity/icon.svg +3 -0
- package/plugins/antigravity/plugin.js +484 -0
- package/plugins/antigravity/plugin.json +17 -0
- package/plugins/antigravity/plugin.test.js +1356 -0
- package/plugins/claude/icon.svg +3 -0
- package/plugins/claude/plugin.js +565 -0
- package/plugins/claude/plugin.json +28 -0
- package/plugins/claude/plugin.test.js +1012 -0
- package/plugins/codex/icon.svg +3 -0
- package/plugins/codex/plugin.js +673 -0
- package/plugins/codex/plugin.json +30 -0
- package/plugins/codex/plugin.test.js +1071 -0
- package/plugins/copilot/icon.svg +3 -0
- package/plugins/copilot/plugin.js +264 -0
- package/plugins/copilot/plugin.json +20 -0
- package/plugins/copilot/plugin.test.js +529 -0
- package/plugins/cursor/icon.svg +3 -0
- package/plugins/cursor/plugin.js +526 -0
- package/plugins/cursor/plugin.json +24 -0
- package/plugins/cursor/plugin.test.js +1168 -0
- package/plugins/factory/icon.svg +1 -0
- package/plugins/factory/plugin.js +407 -0
- package/plugins/factory/plugin.json +19 -0
- package/plugins/factory/plugin.test.js +833 -0
- package/plugins/gemini/icon.svg +4 -0
- package/plugins/gemini/plugin.js +413 -0
- package/plugins/gemini/plugin.json +20 -0
- package/plugins/gemini/plugin.test.js +735 -0
- package/plugins/jetbrains-ai-assistant/icon.svg +3 -0
- package/plugins/jetbrains-ai-assistant/plugin.js +357 -0
- package/plugins/jetbrains-ai-assistant/plugin.json +17 -0
- package/plugins/jetbrains-ai-assistant/plugin.test.js +338 -0
- package/plugins/kimi/icon.svg +3 -0
- package/plugins/kimi/plugin.js +358 -0
- package/plugins/kimi/plugin.json +19 -0
- package/plugins/kimi/plugin.test.js +619 -0
- package/plugins/minimax/icon.svg +4 -0
- package/plugins/minimax/plugin.js +388 -0
- package/plugins/minimax/plugin.json +17 -0
- package/plugins/minimax/plugin.test.js +943 -0
- package/plugins/perplexity/icon.svg +1 -0
- package/plugins/perplexity/plugin.js +378 -0
- package/plugins/perplexity/plugin.json +15 -0
- package/plugins/perplexity/plugin.test.js +602 -0
- package/plugins/windsurf/icon.svg +3 -0
- package/plugins/windsurf/plugin.js +218 -0
- package/plugins/windsurf/plugin.json +16 -0
- package/plugins/windsurf/plugin.test.js +455 -0
- package/plugins/zai/icon.svg +5 -0
- package/plugins/zai/plugin.js +156 -0
- package/plugins/zai/plugin.json +18 -0
- package/plugins/zai/plugin.test.js +396 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg width="100" height="100" viewBox="0 0 67 65" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill="currentColor" d="M47.75 11.15a.867.867 0 0 1-.671-.806.84.84 0 0 1 .067-.362c1.688-4.007 2.433-7.213 1.23-8.555-3.183-3.56-15.952 3.52-20.024 5.919a.9.9 0 0 1-1.273-.41c-1.711-3.998-3.51-6.78-5.334-6.9-4.833-.323-8.73 13.49-9.87 17.992a.85.85 0 0 1-.459.563.9.9 0 0 1-.737.027c-4.109-1.647-7.398-2.373-8.773-1.2-3.651 3.104 3.609 15.557 6.068 19.528a.85.85 0 0 1-.11 1.031.9.9 0 0 1-.31.21C3.455 39.856.604 41.61.478 43.389c-.329 4.713 13.834 8.513 18.452 9.625q.186.046.337.163a.87.87 0 0 1 .332.642.84.84 0 0 1-.067.362c-1.688 4.007-2.433 7.214-1.23 8.555 3.183 3.561 15.954-3.519 20.025-5.917a.9.9 0 0 1 1.058.107.9.9 0 0 1 .215.302c1.711 3.997 3.509 6.779 5.334 6.9 4.833.322 8.73-13.49 9.868-17.993a.85.85 0 0 1 .168-.33.88.88 0 0 1 .659-.324.9.9 0 0 1 .371.066c4.109 1.647 7.397 2.372 8.773 1.2 3.651-3.105-3.61-15.559-6.07-19.53a.85.85 0 0 1 .111-1.03.9.9 0 0 1 .31-.21c4.1-1.67 6.952-3.424 7.075-5.203.331-4.713-13.833-8.513-18.45-9.623m-5.546-4.518c.93 1.624-3.858 12.446-7.42 20.015a.7.7 0 0 1-.28.303.71.71 0 0 1-.796-.059.7.7 0 0 1-.23-.341c-1.439-4.921-3.082-10.704-4.841-15.612a.84.84 0 0 1 .01-.594.87.87 0 0 1 .401-.446c4.392-2.34 11.908-5.446 13.156-3.266m-21.048 1.34c1.833.507 6.294 11.46 9.264 19.268a.67.67 0 0 1-.2.754.71.71 0 0 1-.794.08c-4.589-2.485-9.94-5.444-14.743-7.702a.87.87 0 0 1-.422-.427.84.84 0 0 1-.04-.591c1.414-4.679 4.471-12.063 6.935-11.383M7.243 23.433c1.664-.906 12.762 3.763 20.522 7.235.13.058.239.154.311.274a.67.67 0 0 1-.06.776.7.7 0 0 1-.35.225c-5.045 1.403-10.976 3.006-16.01 4.721a.9.9 0 0 1-.607-.01.88.88 0 0 1-.456-.391c-2.395-4.284-5.586-11.613-3.35-12.83M8.617 43.96c.519-1.788 11.752-6.14 19.758-9.035a.72.72 0 0 1 .773.195.67.67 0 0 1 .081.774c-2.548 4.475-5.582 9.694-7.898 14.377a.87.87 0 0 1-.437.413.9.9 0 0 1-.607.039c-4.797-1.37-12.37-4.36-11.67-6.763m15.855 13.568c-.93-1.623 3.859-12.446 7.42-20.014a.7.7 0 0 1 .28-.303.715.715 0 0 1 .796.059.7.7 0 0 1 .23.34c1.439 4.92 3.083 10.705 4.841 15.613a.84.84 0 0 1-.01.593.87.87 0 0 1-.402.445c-4.391 2.335-11.908 5.447-13.15 3.267zm21.049-1.34c-1.836-.506-6.297-11.461-9.266-19.269a.67.67 0 0 1 .2-.755.71.71 0 0 1 .795-.078c4.587 2.484 9.94 5.445 14.742 7.703.189.088.339.24.423.426a.84.84 0 0 1 .039.592c-1.413 4.686-4.47 12.063-6.933 11.381m13.912-15.462c-1.665.907-12.762-3.763-20.523-7.236a.7.7 0 0 1-.311-.273.67.67 0 0 1 .06-.777.7.7 0 0 1 .35-.225c5.046-1.402 10.975-3.005 16.009-4.72a.9.9 0 0 1 .609.01.88.88 0 0 1 .457.392c2.393 4.282 5.584 11.613 3.349 12.829M58.06 20.2c-.521 1.79-11.753 6.14-19.759 9.036a.72.72 0 0 1-.774-.195.67.67 0 0 1-.08-.776c2.547-4.474 5.581-9.694 7.897-14.377a.87.87 0 0 1 .437-.412.9.9 0 0 1 .607-.038c4.797 1.377 12.37 4.359 11.672 6.762"></path></svg>
|
|
@@ -0,0 +1,407 @@
|
|
|
1
|
+
(function () {
|
|
2
|
+
const AUTH_PATHS = ["~/.factory/auth.json", "~/.factory/auth.encrypted"]
|
|
3
|
+
const KEYCHAIN_SERVICES = ["Factory Token", "Factory token", "Factory Auth", "Droid Auth"]
|
|
4
|
+
const WORKOS_CLIENT_ID = "client_01HNM792M5G5G1A2THWPXKFMXB"
|
|
5
|
+
const WORKOS_AUTH_URL = "https://api.workos.com/user_management/authenticate"
|
|
6
|
+
const USAGE_URL = "https://api.factory.ai/api/organization/subscription/usage"
|
|
7
|
+
const TOKEN_REFRESH_THRESHOLD_MS = 24 * 60 * 60 * 1000 // 24 hours before expiry
|
|
8
|
+
|
|
9
|
+
function decodeHexUtf8(hex) {
|
|
10
|
+
try {
|
|
11
|
+
const bytes = []
|
|
12
|
+
for (let i = 0; i < hex.length; i += 2) {
|
|
13
|
+
bytes.push(parseInt(hex.slice(i, i + 2), 16))
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
if (typeof TextDecoder !== "undefined") {
|
|
17
|
+
try {
|
|
18
|
+
return new TextDecoder("utf-8", { fatal: false }).decode(new Uint8Array(bytes))
|
|
19
|
+
} catch {}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
let escaped = ""
|
|
23
|
+
for (const b of bytes) {
|
|
24
|
+
const h = b.toString(16)
|
|
25
|
+
escaped += "%" + (h.length === 1 ? "0" + h : h)
|
|
26
|
+
}
|
|
27
|
+
return decodeURIComponent(escaped)
|
|
28
|
+
} catch {
|
|
29
|
+
return null
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function tryParseAuthJson(ctx, text) {
|
|
34
|
+
if (!text) return null
|
|
35
|
+
const parsed = ctx.util.tryParseJson(text)
|
|
36
|
+
if (parsed !== null) return parsed
|
|
37
|
+
|
|
38
|
+
// Some keychain payloads can be returned as hex-encoded UTF-8 bytes.
|
|
39
|
+
let hex = String(text).trim()
|
|
40
|
+
if (hex.startsWith("0x") || hex.startsWith("0X")) hex = hex.slice(2)
|
|
41
|
+
if (!hex || hex.length % 2 !== 0) return null
|
|
42
|
+
if (!/^[0-9a-fA-F]+$/.test(hex)) return null
|
|
43
|
+
|
|
44
|
+
const decoded = decodeHexUtf8(hex)
|
|
45
|
+
if (!decoded) return null
|
|
46
|
+
return ctx.util.tryParseJson(decoded)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function looksLikeJwt(value) {
|
|
50
|
+
return /^[A-Za-z0-9\-_]+\.[A-Za-z0-9\-_]+\.[A-Za-z0-9\-_]+$/.test(value)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function normalizeAuthPayload(raw, opts) {
|
|
54
|
+
const allowPartial = Boolean(opts && opts.allowPartial)
|
|
55
|
+
if (!raw || typeof raw !== "object") return null
|
|
56
|
+
|
|
57
|
+
const accessToken =
|
|
58
|
+
raw.access_token ||
|
|
59
|
+
raw.accessToken ||
|
|
60
|
+
(raw.tokens && (raw.tokens.access_token || raw.tokens.accessToken))
|
|
61
|
+
|
|
62
|
+
const refreshToken =
|
|
63
|
+
raw.refresh_token ||
|
|
64
|
+
raw.refreshToken ||
|
|
65
|
+
(raw.tokens && (raw.tokens.refresh_token || raw.tokens.refreshToken))
|
|
66
|
+
|
|
67
|
+
const hasAccess = typeof accessToken === "string" && accessToken
|
|
68
|
+
const hasRefresh = typeof refreshToken === "string" && refreshToken
|
|
69
|
+
if (!hasAccess && !(allowPartial && hasRefresh)) return null
|
|
70
|
+
|
|
71
|
+
return {
|
|
72
|
+
access_token: hasAccess ? accessToken : null,
|
|
73
|
+
refresh_token: hasRefresh ? refreshToken : null,
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function parseAuthPayload(ctx, rawText, opts) {
|
|
78
|
+
const parsed = tryParseAuthJson(ctx, rawText)
|
|
79
|
+
const normalized = normalizeAuthPayload(parsed, opts)
|
|
80
|
+
if (normalized) return normalized
|
|
81
|
+
|
|
82
|
+
if (typeof parsed === "string" && looksLikeJwt(parsed)) {
|
|
83
|
+
return { access_token: parsed, refresh_token: null }
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const direct = String(rawText || "").trim()
|
|
87
|
+
if (looksLikeJwt(direct)) {
|
|
88
|
+
return { access_token: direct, refresh_token: null }
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return null
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function loadAuthFromFiles(ctx) {
|
|
95
|
+
for (const authPath of AUTH_PATHS) {
|
|
96
|
+
if (!ctx.host.fs.exists(authPath)) continue
|
|
97
|
+
|
|
98
|
+
try {
|
|
99
|
+
const text = ctx.host.fs.readText(authPath)
|
|
100
|
+
const auth = parseAuthPayload(ctx, text, { allowPartial: true })
|
|
101
|
+
if (!auth) {
|
|
102
|
+
ctx.host.log.warn("auth file exists but has no valid auth payload: " + authPath)
|
|
103
|
+
continue
|
|
104
|
+
}
|
|
105
|
+
ctx.host.log.info("auth loaded from file: " + authPath)
|
|
106
|
+
return { auth, source: "file", authPath, keychainService: null }
|
|
107
|
+
} catch (e) {
|
|
108
|
+
ctx.host.log.warn("auth file read failed: " + String(e))
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return null
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function loadAuthFromKeychain(ctx) {
|
|
116
|
+
if (!ctx.host.keychain || typeof ctx.host.keychain.readGenericPassword !== "function") {
|
|
117
|
+
return null
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
for (const service of KEYCHAIN_SERVICES) {
|
|
121
|
+
try {
|
|
122
|
+
const value = ctx.host.keychain.readGenericPassword(service)
|
|
123
|
+
if (!value) continue
|
|
124
|
+
|
|
125
|
+
const auth = parseAuthPayload(ctx, value)
|
|
126
|
+
if (!auth) {
|
|
127
|
+
ctx.host.log.warn("keychain has data but no valid auth payload: " + service)
|
|
128
|
+
continue
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
ctx.host.log.info("auth loaded from keychain: " + service)
|
|
132
|
+
return { auth, source: "keychain", authPath: null, keychainService: service }
|
|
133
|
+
} catch (e) {
|
|
134
|
+
ctx.host.log.info("keychain read failed (may not exist): " + String(e))
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return null
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function loadAuth(ctx) {
|
|
142
|
+
const fileAuth = loadAuthFromFiles(ctx)
|
|
143
|
+
if (fileAuth) return fileAuth
|
|
144
|
+
|
|
145
|
+
const keychainAuth = loadAuthFromKeychain(ctx)
|
|
146
|
+
if (keychainAuth) return keychainAuth
|
|
147
|
+
|
|
148
|
+
for (const authPath of AUTH_PATHS) {
|
|
149
|
+
if (!ctx.host.fs.exists(authPath)) {
|
|
150
|
+
ctx.host.log.warn("auth file not found: " + authPath)
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return null
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function saveAuth(ctx, authState) {
|
|
158
|
+
const auth = authState && authState.auth ? authState.auth : null
|
|
159
|
+
if (!auth) return false
|
|
160
|
+
|
|
161
|
+
try {
|
|
162
|
+
if (authState.source === "file" && authState.authPath) {
|
|
163
|
+
ctx.host.fs.writeText(authState.authPath, JSON.stringify(auth, null, 2))
|
|
164
|
+
ctx.host.log.info("auth file updated: " + authState.authPath)
|
|
165
|
+
return true
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
if (
|
|
169
|
+
authState.source === "keychain" &&
|
|
170
|
+
authState.keychainService &&
|
|
171
|
+
ctx.host.keychain &&
|
|
172
|
+
typeof ctx.host.keychain.writeGenericPassword === "function"
|
|
173
|
+
) {
|
|
174
|
+
ctx.host.keychain.writeGenericPassword(authState.keychainService, JSON.stringify(auth))
|
|
175
|
+
ctx.host.log.info("auth keychain item updated: " + authState.keychainService)
|
|
176
|
+
return true
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
ctx.host.log.warn("auth persistence skipped: unsupported source")
|
|
180
|
+
return false
|
|
181
|
+
} catch (e) {
|
|
182
|
+
ctx.host.log.warn("failed to save auth: " + String(e))
|
|
183
|
+
return false
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function needsRefresh(ctx, accessToken, nowMs) {
|
|
188
|
+
const payload = ctx.jwt.decodePayload(accessToken)
|
|
189
|
+
const expiresAtMs = payload && typeof payload.exp === "number" ? payload.exp * 1000 : null
|
|
190
|
+
return ctx.util.needsRefreshByExpiry({
|
|
191
|
+
nowMs,
|
|
192
|
+
expiresAtMs,
|
|
193
|
+
bufferMs: TOKEN_REFRESH_THRESHOLD_MS,
|
|
194
|
+
})
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function refreshToken(ctx, authState) {
|
|
198
|
+
const auth = authState.auth
|
|
199
|
+
if (!auth.refresh_token) {
|
|
200
|
+
ctx.host.log.warn("refresh skipped: no refresh token")
|
|
201
|
+
return null
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
ctx.host.log.info("attempting token refresh via WorkOS")
|
|
205
|
+
try {
|
|
206
|
+
const resp = ctx.util.request({
|
|
207
|
+
method: "POST",
|
|
208
|
+
url: WORKOS_AUTH_URL,
|
|
209
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
210
|
+
bodyText:
|
|
211
|
+
"grant_type=refresh_token" +
|
|
212
|
+
"&refresh_token=" + encodeURIComponent(auth.refresh_token) +
|
|
213
|
+
"&client_id=" + encodeURIComponent(WORKOS_CLIENT_ID),
|
|
214
|
+
timeoutMs: 15000,
|
|
215
|
+
})
|
|
216
|
+
|
|
217
|
+
if (resp.status === 400 || resp.status === 401) {
|
|
218
|
+
ctx.host.log.error("refresh failed: status=" + resp.status)
|
|
219
|
+
throw "Session expired. Run `droid` to log in again."
|
|
220
|
+
}
|
|
221
|
+
if (resp.status < 200 || resp.status >= 300) {
|
|
222
|
+
ctx.host.log.warn("refresh returned unexpected status: " + resp.status)
|
|
223
|
+
return null
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const body = ctx.util.tryParseJson(resp.bodyText)
|
|
227
|
+
if (!body) {
|
|
228
|
+
ctx.host.log.warn("refresh response not valid JSON")
|
|
229
|
+
return null
|
|
230
|
+
}
|
|
231
|
+
const newAccessToken = body.access_token
|
|
232
|
+
if (!newAccessToken) {
|
|
233
|
+
ctx.host.log.warn("refresh response missing access_token")
|
|
234
|
+
return null
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Update auth object with new tokens
|
|
238
|
+
auth.access_token = newAccessToken
|
|
239
|
+
if (body.refresh_token) {
|
|
240
|
+
auth.refresh_token = body.refresh_token
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// Save updated auth
|
|
244
|
+
saveAuth(ctx, authState)
|
|
245
|
+
ctx.host.log.info("refresh succeeded")
|
|
246
|
+
|
|
247
|
+
return newAccessToken
|
|
248
|
+
} catch (e) {
|
|
249
|
+
if (typeof e === "string") throw e
|
|
250
|
+
ctx.host.log.error("refresh exception: " + String(e))
|
|
251
|
+
return null
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function fetchUsage(ctx, accessToken) {
|
|
256
|
+
return ctx.util.request({
|
|
257
|
+
method: "POST",
|
|
258
|
+
url: USAGE_URL,
|
|
259
|
+
headers: {
|
|
260
|
+
Authorization: "Bearer " + accessToken,
|
|
261
|
+
"Content-Type": "application/json",
|
|
262
|
+
Accept: "application/json",
|
|
263
|
+
"User-Agent": "OpenUsage",
|
|
264
|
+
},
|
|
265
|
+
bodyText: JSON.stringify({ useCache: true }),
|
|
266
|
+
timeoutMs: 10000,
|
|
267
|
+
})
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function probe(ctx) {
|
|
271
|
+
const authState = loadAuth(ctx)
|
|
272
|
+
if (!authState) {
|
|
273
|
+
ctx.host.log.error("probe failed: not logged in")
|
|
274
|
+
throw "Not logged in. Run `droid` to authenticate."
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
const auth = authState.auth
|
|
278
|
+
if (!auth.access_token) {
|
|
279
|
+
ctx.host.log.error("probe failed: no access_token in auth data")
|
|
280
|
+
throw "Invalid auth file. Run `droid` to authenticate."
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
let accessToken = auth.access_token
|
|
284
|
+
|
|
285
|
+
// Check if token needs refresh
|
|
286
|
+
const nowMs = Date.now()
|
|
287
|
+
if (needsRefresh(ctx, accessToken, nowMs)) {
|
|
288
|
+
ctx.host.log.info("token near expiry, refreshing")
|
|
289
|
+
const refreshed = refreshToken(ctx, authState)
|
|
290
|
+
if (refreshed) {
|
|
291
|
+
accessToken = refreshed
|
|
292
|
+
} else {
|
|
293
|
+
ctx.host.log.warn("proactive refresh failed, trying with existing token")
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
let resp
|
|
298
|
+
let didRefresh = false
|
|
299
|
+
try {
|
|
300
|
+
resp = ctx.util.retryOnceOnAuth({
|
|
301
|
+
request: (token) => {
|
|
302
|
+
try {
|
|
303
|
+
return fetchUsage(ctx, token || accessToken)
|
|
304
|
+
} catch (e) {
|
|
305
|
+
ctx.host.log.error("usage request exception: " + String(e))
|
|
306
|
+
if (didRefresh) {
|
|
307
|
+
throw "Usage request failed after refresh. Try again."
|
|
308
|
+
}
|
|
309
|
+
throw "Usage request failed. Check your connection."
|
|
310
|
+
}
|
|
311
|
+
},
|
|
312
|
+
refresh: () => {
|
|
313
|
+
ctx.host.log.info("usage returned 401, attempting refresh")
|
|
314
|
+
didRefresh = true
|
|
315
|
+
return refreshToken(ctx, authState)
|
|
316
|
+
},
|
|
317
|
+
})
|
|
318
|
+
} catch (e) {
|
|
319
|
+
if (typeof e === "string") throw e
|
|
320
|
+
ctx.host.log.error("usage request failed: " + String(e))
|
|
321
|
+
throw "Usage request failed. Check your connection."
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
if (ctx.util.isAuthStatus(resp.status)) {
|
|
325
|
+
ctx.host.log.error("usage returned auth error after all retries: status=" + resp.status)
|
|
326
|
+
throw "Token expired. Run `droid` to log in again."
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
if (resp.status < 200 || resp.status >= 300) {
|
|
330
|
+
ctx.host.log.error("usage returned error: status=" + resp.status)
|
|
331
|
+
throw "Usage request failed (HTTP " + String(resp.status) + "). Try again later."
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
ctx.host.log.info("usage fetch succeeded")
|
|
335
|
+
|
|
336
|
+
const data = ctx.util.tryParseJson(resp.bodyText)
|
|
337
|
+
if (data === null) {
|
|
338
|
+
throw "Usage response invalid. Try again later."
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
const usage = data.usage
|
|
342
|
+
if (!usage) {
|
|
343
|
+
throw "Usage response missing data. Try again later."
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
const lines = []
|
|
347
|
+
|
|
348
|
+
// Calculate reset time and period from usage dates
|
|
349
|
+
const endDate = usage.endDate
|
|
350
|
+
const startDate = usage.startDate
|
|
351
|
+
const resetsAt = typeof endDate === "number" ? ctx.util.toIso(endDate) : null
|
|
352
|
+
const periodDurationMs = (typeof endDate === "number" && typeof startDate === "number")
|
|
353
|
+
? (endDate - startDate)
|
|
354
|
+
: null
|
|
355
|
+
|
|
356
|
+
// Standard tokens (primary line)
|
|
357
|
+
const standard = usage.standard
|
|
358
|
+
if (standard && typeof standard.totalAllowance === "number") {
|
|
359
|
+
const used = standard.orgTotalTokensUsed || 0
|
|
360
|
+
const limit = standard.totalAllowance
|
|
361
|
+
lines.push(ctx.line.progress({
|
|
362
|
+
label: "Standard",
|
|
363
|
+
used: used,
|
|
364
|
+
limit: limit,
|
|
365
|
+
format: { kind: "count", suffix: "tokens" },
|
|
366
|
+
resetsAt: resetsAt,
|
|
367
|
+
periodDurationMs: periodDurationMs,
|
|
368
|
+
}))
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// Premium tokens (detail line, only if plan includes premium)
|
|
372
|
+
const premium = usage.premium
|
|
373
|
+
if (premium && typeof premium.totalAllowance === "number" && premium.totalAllowance > 0) {
|
|
374
|
+
const used = premium.orgTotalTokensUsed || 0
|
|
375
|
+
const limit = premium.totalAllowance
|
|
376
|
+
lines.push(ctx.line.progress({
|
|
377
|
+
label: "Premium",
|
|
378
|
+
used: used,
|
|
379
|
+
limit: limit,
|
|
380
|
+
format: { kind: "count", suffix: "tokens" },
|
|
381
|
+
resetsAt: resetsAt,
|
|
382
|
+
periodDurationMs: periodDurationMs,
|
|
383
|
+
}))
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
// Infer plan from allowance
|
|
387
|
+
let plan = null
|
|
388
|
+
if (standard && typeof standard.totalAllowance === "number") {
|
|
389
|
+
const allowance = standard.totalAllowance
|
|
390
|
+
if (allowance >= 200000000) {
|
|
391
|
+
plan = "Max"
|
|
392
|
+
} else if (allowance >= 20000000) {
|
|
393
|
+
plan = "Pro"
|
|
394
|
+
} else if (allowance > 0) {
|
|
395
|
+
plan = "Basic"
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
if (lines.length === 0) {
|
|
400
|
+
lines.push(ctx.line.badge({ label: "Status", text: "No usage data", color: "#a3a3a3" }))
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
return { plan: plan, lines: lines }
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
globalThis.__openusage_plugin = { id: "factory", probe }
|
|
407
|
+
})()
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": 1,
|
|
3
|
+
"id": "factory",
|
|
4
|
+
"name": "Factory",
|
|
5
|
+
"version": "0.0.1",
|
|
6
|
+
"entry": "plugin.js",
|
|
7
|
+
"icon": "icon.svg",
|
|
8
|
+
"brandColor": "#020202",
|
|
9
|
+
"cli": {
|
|
10
|
+
"category": "cli",
|
|
11
|
+
"binaryName": "droid",
|
|
12
|
+
"installCmd": null,
|
|
13
|
+
"loginCmd": "droid auth login"
|
|
14
|
+
},
|
|
15
|
+
"lines": [
|
|
16
|
+
{ "type": "progress", "label": "Standard", "scope": "overview", "primaryOrder": 1 },
|
|
17
|
+
{ "type": "progress", "label": "Premium", "scope": "detail" }
|
|
18
|
+
]
|
|
19
|
+
}
|