@hashimkarim/usagestat-linux-x64-gnu 2.0.1 → 2.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/native-manifest.json +112 -40
- package/package.json +13 -1
- package/plugins/aixy/plugin.json +1 -1
- package/plugins/antigravity/plugin.js +1 -1
- package/plugins/antigravity-cli/plugin.js +1 -1
- package/plugins/atlascloud/plugin.json +1 -1
- package/plugins/bifrost/plugin.json +1 -1
- package/plugins/cursor/plugin.js +24 -6
- package/plugins/devpass/plugin.json +1 -1
- package/plugins/gitkraken/plugin.json +1 -1
- package/plugins/helmcode/plugin.js +1 -1
- package/plugins/helmcode/plugin.json +2 -2
- package/plugins/huggingface/plugin.json +1 -1
- package/plugins/hyper/plugin.json +1 -1
- package/plugins/lithosai/LICENSE +21 -0
- package/plugins/lithosai/icon.svg +1 -0
- package/plugins/lithosai/plugin.js +125 -0
- package/plugins/lithosai/plugin.json +25 -0
- package/plugins/llmman/plugin.json +1 -1
- package/plugins/muse/plugin.js +93 -129
- package/plugins/muse/plugin.json +2 -2
- package/plugins/museai/LICENSE +21 -0
- package/plugins/museai/icon.svg +3 -0
- package/plugins/museai/plugin.js +154 -0
- package/plugins/museai/plugin.json +25 -0
- package/plugins/nous/plugin.json +1 -1
- package/plugins/opencode-go/plugin.js +59 -22
- package/plugins/raycast/plugin.json +1 -1
- package/plugins/replicate/plugin.json +1 -1
- package/plugins/typesafe/plugin.json +1 -1
- package/plugins/v0/plugin.json +1 -1
- package/plugins/vercel/plugin.json +1 -1
- package/plugins/workbuddy/LICENSE +21 -0
- package/plugins/workbuddy/icon.svg +1 -0
- package/plugins/workbuddy/plugin.js +227 -0
- package/plugins/workbuddy/plugin.json +33 -0
- package/plugins/xkiro/plugin.json +1 -1
- package/usagestat +0 -0
- package/usagestatd +0 -0
package/plugins/nous/plugin.json
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
],
|
|
17
17
|
"upstream": {
|
|
18
18
|
"repository": "https://github.com/steipete/CodexBar",
|
|
19
|
-
"revision": "
|
|
19
|
+
"revision": "6ba06fe9ec1522dd5c59e16810115bce28aa148f",
|
|
20
20
|
"path": "Sources/CodexBarCore/Resources/Plugins/nous.js",
|
|
21
21
|
"sha256": "160f5329bf4895a061d3bed74d0476283d3abad47e071fb868d667c0c5cb485e",
|
|
22
22
|
"license": "MIT"
|
|
@@ -12,17 +12,20 @@
|
|
|
12
12
|
monthly: 60,
|
|
13
13
|
};
|
|
14
14
|
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
15
|
+
const TABLES_SQL = "SELECT name FROM sqlite_master WHERE type = 'table' AND name IN ('message','session_message','credential')";
|
|
16
|
+
const CREDENTIAL_SQL = `SELECT value FROM credential
|
|
17
|
+
WHERE integration_id = 'opencode-go' AND (active IS NULL OR active = 1)
|
|
18
|
+
ORDER BY active DESC, time_updated DESC, id DESC LIMIT 1`;
|
|
19
|
+
function historySQL(tables) {
|
|
20
|
+
const sources = [];
|
|
21
|
+
if (tables.includes('session_message')) sources.push(`SELECT id, time_created, data FROM session_message
|
|
22
|
+
WHERE json_valid(data) AND (type = 'assistant' OR (type = 'compaction' AND json_extract(data,'$.status') = 'completed'))`);
|
|
23
|
+
if (tables.includes('message')) sources.push(`SELECT id, time_created, data FROM message
|
|
24
|
+
WHERE json_valid(data) AND json_extract(data,'$.role') = 'assistant'`);
|
|
25
|
+
if (!sources.length) return null;
|
|
26
|
+
return `
|
|
25
27
|
SELECT
|
|
28
|
+
id,
|
|
26
29
|
CAST(COALESCE(json_extract(data, '$.time.created'), time_created) AS INTEGER) AS createdMs,
|
|
27
30
|
json_extract(data, '$.cost') AS cost,
|
|
28
31
|
json_extract(data, '$.tokens.input') AS inputTokens,
|
|
@@ -30,11 +33,10 @@
|
|
|
30
33
|
json_extract(data, '$.tokens.cache.read') AS cacheReadTokens,
|
|
31
34
|
json_extract(data, '$.tokens.cache.write') AS cacheCreationTokens,
|
|
32
35
|
json_extract(data, '$.tokens.reasoning') AS reasoningOutputTokens
|
|
33
|
-
FROM
|
|
34
|
-
WHERE
|
|
35
|
-
AND json_extract(data, '$.providerID') = 'opencode-go'
|
|
36
|
-
AND json_extract(data, '$.role') = 'assistant'
|
|
36
|
+
FROM (${sources.join(' UNION ALL ')})
|
|
37
|
+
WHERE COALESCE(json_extract(data, '$.model.providerID'), json_extract(data, '$.providerID')) = 'opencode-go'
|
|
37
38
|
`;
|
|
39
|
+
}
|
|
38
40
|
|
|
39
41
|
function readNumber(value) {
|
|
40
42
|
if (typeof value !== "number" && typeof value !== "string") return null;
|
|
@@ -317,7 +319,7 @@
|
|
|
317
319
|
}
|
|
318
320
|
return { ok: true, rows };
|
|
319
321
|
} catch (e) {
|
|
320
|
-
ctx.host.log.warn("
|
|
322
|
+
ctx.host.log.warn("OpenCode history could not be read");
|
|
321
323
|
return { ok: false, rows: [] };
|
|
322
324
|
}
|
|
323
325
|
}
|
|
@@ -326,6 +328,30 @@
|
|
|
326
328
|
const configured = trim(ctx.provider && ctx.provider.apiKey) || env(ctx, "OPENCODE_GO_API_KEY");
|
|
327
329
|
if (configured) return configured;
|
|
328
330
|
const inline = ctx.host.env.get("OPENCODE_AUTH_CONTENT");
|
|
331
|
+
// A v2 credential table is authoritative even after logout. The one-time
|
|
332
|
+
// imported auth.json must never revive a deleted or inactive account.
|
|
333
|
+
if (!inline) {
|
|
334
|
+
const database = databasePath(ctx);
|
|
335
|
+
if (ctx.host.fs.exists(database)) {
|
|
336
|
+
try {
|
|
337
|
+
const tables = ctx.util.tryParseJson(ctx.host.sqlite.query(database, TABLES_SQL));
|
|
338
|
+
if (!Array.isArray(tables)) throw new Error('Invalid schema');
|
|
339
|
+
if (tables.some(table => table.name === 'credential')) {
|
|
340
|
+
const rows = ctx.util.tryParseJson(ctx.host.sqlite.query(database, CREDENTIAL_SQL));
|
|
341
|
+
if (!Array.isArray(rows)) throw new Error('Invalid credential result');
|
|
342
|
+
if (!rows.length) return null;
|
|
343
|
+
const entry = ctx.util.tryParseJson(rows[0].value);
|
|
344
|
+
if (!entry || typeof entry !== 'object' || Array.isArray(entry) ||
|
|
345
|
+
(entry.key !== undefined && typeof entry.key !== 'string'))
|
|
346
|
+
throw {code:'credential-malformed', message:'OpenCode credential row is malformed.'};
|
|
347
|
+
return trim(entry.key);
|
|
348
|
+
}
|
|
349
|
+
} catch (error) {
|
|
350
|
+
if (error && error.code) throw error;
|
|
351
|
+
throw {code:'credential-unavailable',message:'OpenCode credential database cannot be read. Its imported auth file was not used.'};
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
}
|
|
329
355
|
const path = inline ? null : dataPath(ctx, "auth.json");
|
|
330
356
|
if (!inline && !ctx.host.fs.exists(path)) return null;
|
|
331
357
|
|
|
@@ -334,8 +360,7 @@
|
|
|
334
360
|
const parsed = ctx.util.tryParseJson(text);
|
|
335
361
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
336
362
|
if (inline) throw {code: "credential-malformed", message: "OPENCODE_AUTH_CONTENT must contain an auth JSON object."};
|
|
337
|
-
|
|
338
|
-
return null;
|
|
363
|
+
throw {code:'credential-malformed',message:'OpenCode auth file is not valid JSON.'};
|
|
339
364
|
}
|
|
340
365
|
const entry = parsed[PROVIDER_ID];
|
|
341
366
|
if (!entry || typeof entry !== "object") return null;
|
|
@@ -343,25 +368,37 @@
|
|
|
343
368
|
return key || null;
|
|
344
369
|
} catch (e) {
|
|
345
370
|
if (e && e.code) throw e;
|
|
346
|
-
|
|
347
|
-
return null;
|
|
371
|
+
throw {code:'credential-unavailable',message:'OpenCode auth file cannot be read.'};
|
|
348
372
|
}
|
|
349
373
|
}
|
|
350
374
|
|
|
351
375
|
function hasHistory(ctx) {
|
|
352
|
-
const result =
|
|
376
|
+
const result = queryHistory(ctx, true);
|
|
353
377
|
if (!result.ok) return { ok: false, present: false };
|
|
354
378
|
return { ok: true, present: result.rows.length > 0 };
|
|
355
379
|
}
|
|
356
380
|
|
|
381
|
+
function queryHistory(ctx, probeOnly) {
|
|
382
|
+
const schema = queryRows(ctx, TABLES_SQL);
|
|
383
|
+
if (!schema.ok) return schema;
|
|
384
|
+
const sql = historySQL(schema.rows.map(table => table.name));
|
|
385
|
+
if (!sql) return {ok:true,rows:[]};
|
|
386
|
+
return queryRows(ctx, probeOnly ? 'SELECT 1 AS present FROM (' + sql + ') LIMIT 1' : sql);
|
|
387
|
+
}
|
|
388
|
+
|
|
357
389
|
function loadHistory(ctx) {
|
|
358
|
-
const result =
|
|
390
|
+
const result = queryHistory(ctx, false);
|
|
359
391
|
if (!result.ok) return result;
|
|
360
392
|
|
|
361
393
|
const rows = [];
|
|
394
|
+
const seen = new Set();
|
|
362
395
|
for (let i = 0; i < result.rows.length; i += 1) {
|
|
363
396
|
const row = result.rows[i];
|
|
364
397
|
if (!row || typeof row !== "object") continue;
|
|
398
|
+
if (trim(row.id)) {
|
|
399
|
+
if (seen.has(row.id)) continue;
|
|
400
|
+
seen.add(row.id);
|
|
401
|
+
}
|
|
365
402
|
const createdMs = readNumber(row.createdMs);
|
|
366
403
|
const cost = readNumber(row.cost);
|
|
367
404
|
if (createdMs === null || createdMs <= 0 || createdMs > Date.parse(ctx.nowIso)) continue;
|
|
@@ -608,7 +645,7 @@
|
|
|
608
645
|
if (!cookie) throw "OpenCode Go session cookie is missing.";
|
|
609
646
|
return fetchWebResult(ctx, cookie);
|
|
610
647
|
}
|
|
611
|
-
const authKey = loadAuthKey(ctx);
|
|
648
|
+
const authKey = source === 'local' && !ctx.host.env.get('OPENCODE_AUTH_CONTENT') ? null : loadAuthKey(ctx);
|
|
612
649
|
if (source === "api" && !authKey) throw "OpenCode Go API key missing. Set OPENCODE_GO_API_KEY.";
|
|
613
650
|
if (authKey && source !== "local") {
|
|
614
651
|
const result = fetchApiResult(ctx, authKey);
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
],
|
|
17
17
|
"upstream": {
|
|
18
18
|
"repository": "https://github.com/steipete/CodexBar",
|
|
19
|
-
"revision": "
|
|
19
|
+
"revision": "6ba06fe9ec1522dd5c59e16810115bce28aa148f",
|
|
20
20
|
"path": "Sources/CodexBarCore/Resources/Plugins/raycast.js",
|
|
21
21
|
"sha256": "df8f6d185cb67fd9ec9b622d2321a6d4e5395576b9ab54b1725aac272c95ecb7",
|
|
22
22
|
"license": "MIT"
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
],
|
|
17
17
|
"upstream": {
|
|
18
18
|
"repository": "https://github.com/steipete/CodexBar",
|
|
19
|
-
"revision": "
|
|
19
|
+
"revision": "6ba06fe9ec1522dd5c59e16810115bce28aa148f",
|
|
20
20
|
"path": "Sources/CodexBarCore/Resources/Plugins/replicate.js",
|
|
21
21
|
"sha256": "d240c36fa142ee91ce90ae52a2c6814613d2d5533a00c8a4c7ecec2f9386006c",
|
|
22
22
|
"license": "MIT"
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
],
|
|
17
17
|
"upstream": {
|
|
18
18
|
"repository": "https://github.com/steipete/CodexBar",
|
|
19
|
-
"revision": "
|
|
19
|
+
"revision": "6ba06fe9ec1522dd5c59e16810115bce28aa148f",
|
|
20
20
|
"path": "Sources/CodexBarCore/Resources/Plugins/typesafe.js",
|
|
21
21
|
"sha256": "eb3941346a966b3f5f9e0010f17edaeed7dfb6c8b0331319a86f26da10d51980",
|
|
22
22
|
"license": "MIT"
|
package/plugins/v0/plugin.json
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
],
|
|
17
17
|
"upstream": {
|
|
18
18
|
"repository": "https://github.com/steipete/CodexBar",
|
|
19
|
-
"revision": "
|
|
19
|
+
"revision": "6ba06fe9ec1522dd5c59e16810115bce28aa148f",
|
|
20
20
|
"path": "Sources/CodexBarCore/Resources/Plugins/v0.js",
|
|
21
21
|
"sha256": "5f54a9329dca35d59d76e427c34d4d9db8d3400d0b38c5fcee8899928a900019",
|
|
22
22
|
"license": "MIT"
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
],
|
|
16
16
|
"upstream": {
|
|
17
17
|
"repository": "https://github.com/steipete/CodexBar",
|
|
18
|
-
"revision": "
|
|
18
|
+
"revision": "6ba06fe9ec1522dd5c59e16810115bce28aa148f",
|
|
19
19
|
"path": "Sources/CodexBarCore/Resources/Plugins/vercel.js",
|
|
20
20
|
"sha256": "27b39f42709e93a2cfbf850171f198069553c99967f9b7ba6d16614862cb084a",
|
|
21
21
|
"license": "MIT"
|
|
@@ -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 xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="currentColor" d="M2 5h3.2l2.3 9.2L10.2 5h3.6l2.7 9.2L18.8 5H22l-4 14h-3.2L12 9.6 9.2 19H6z"/></svg>
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
// Generated by tools/vendor-codexbar-providers.cjs; see LICENSE.
|
|
2
|
+
globalThis.__usagestat_bundled_options = {"labels":["Credits","Credits","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: "workbuddy",
|
|
12
|
+
name: "WorkBuddy",
|
|
13
|
+
endpoints: ["https://www.workbuddy.cn"],
|
|
14
|
+
settings: [
|
|
15
|
+
{ key: "webTimeoutSeconds", title: "Web timeout", type: "plain" },
|
|
16
|
+
{ key: "resetDeadlineMillis", title: "Reset lookup deadline", type: "plain" },
|
|
17
|
+
{ key: "chromeMajorVersion", title: "Chrome major version", type: "plain" },
|
|
18
|
+
],
|
|
19
|
+
capabilities: ["browser-cookies", "http-status"],
|
|
20
|
+
cookieDomains: ["workbuddy.cn", "www.workbuddy.cn"],
|
|
21
|
+
cookiePolicy: {
|
|
22
|
+
selection: "request-url",
|
|
23
|
+
cache: "validated-single-entry",
|
|
24
|
+
imports: "access-gated",
|
|
25
|
+
},
|
|
26
|
+
async fetchUsage(ctx) {
|
|
27
|
+
const origin = "https://www.workbuddy.cn";
|
|
28
|
+
const domain = "www.workbuddy.cn";
|
|
29
|
+
const policy = ctx.browser.availability(domain);
|
|
30
|
+
if (policy === "off") throw ctx.fail.missingCredential("WorkBuddy cookies are disabled.");
|
|
31
|
+
const timeoutRaw = Number(_nullishCoalesce(ctx.settings.get("webTimeoutSeconds"), () => "15"));
|
|
32
|
+
const timeoutSeconds = Number.isFinite(timeoutRaw) ? Math.min(30, Math.max(1, Math.round(timeoutRaw))) : 15;
|
|
33
|
+
// WorkBuddy binds the session to the browser User-Agent, and Chrome's reduced UA varies only by major version.
|
|
34
|
+
// The previous major covers an installed update that Chrome has not relaunched into yet.
|
|
35
|
+
const major = Number(_nullishCoalesce(ctx.settings.get("chromeMajorVersion"), () => ""));
|
|
36
|
+
const userAgent = (version) =>
|
|
37
|
+
`Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/${version}.0.0.0 Safari/537.36`;
|
|
38
|
+
const userAgents =
|
|
39
|
+
Number.isSafeInteger(major) && major > 1 ? [userAgent(major), userAgent(major - 1)] : [undefined];
|
|
40
|
+
|
|
41
|
+
const fail = (field) => {
|
|
42
|
+
throw ctx.fail.parseFailure(`Invalid WorkBuddy billing response: ${field}`);
|
|
43
|
+
};
|
|
44
|
+
const object = (value, field) =>
|
|
45
|
+
value !== null && typeof value === "object" && !Array.isArray(value) ? value : fail(field);
|
|
46
|
+
// Billing amounts arrive as decimal strings ("500"); numbers are accepted for robustness.
|
|
47
|
+
const amount = (value, field) => {
|
|
48
|
+
if (typeof value === "number") return Number.isFinite(value) && value >= 0 ? value : fail(field);
|
|
49
|
+
if (typeof value !== "string" || !/^\d+(?:\.\d+)?$/.test(value.trim())) return fail(field);
|
|
50
|
+
const parsed = Number(value.trim());
|
|
51
|
+
return Number.isFinite(parsed) ? parsed : fail(field);
|
|
52
|
+
};
|
|
53
|
+
const text = (value) => (typeof value === "string" ? value.trim() || undefined : undefined);
|
|
54
|
+
const format = (value) => ctx.format.number(value, { maximumFractionDigits: 2 });
|
|
55
|
+
const decode = (bodyText, field) => {
|
|
56
|
+
let value;
|
|
57
|
+
try {
|
|
58
|
+
value = JSON.parse(bodyText);
|
|
59
|
+
} catch (error) {
|
|
60
|
+
void error;
|
|
61
|
+
return fail(field);
|
|
62
|
+
}
|
|
63
|
+
return object(value, field);
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const sessionExpired = ctx.fail.authenticationExpired(
|
|
67
|
+
"WorkBuddy session expired. Sign in at www.workbuddy.cn or paste a fresh Cookie header.",
|
|
68
|
+
);
|
|
69
|
+
const post = async (session, agent, path, body, requestTimeout = timeoutSeconds) => {
|
|
70
|
+
const headers = {
|
|
71
|
+
Accept: "application/json",
|
|
72
|
+
Origin: origin,
|
|
73
|
+
Referer: `${origin}/profile/plans-usage`,
|
|
74
|
+
};
|
|
75
|
+
if (agent) headers["User-Agent"] = agent;
|
|
76
|
+
const response = await ctx.http.post(`${origin}${path}`, {
|
|
77
|
+
body,
|
|
78
|
+
timeoutSeconds: requestTimeout,
|
|
79
|
+
cookieSession: session.id,
|
|
80
|
+
headers,
|
|
81
|
+
});
|
|
82
|
+
if (response.status === 401) throw sessionExpired;
|
|
83
|
+
if (response.status === 403) throw ctx.fail.permissionDenied("WorkBuddy denied access to billing data.");
|
|
84
|
+
if (response.status === 429) throw ctx.fail.rateLimited("WorkBuddy billing requests are rate limited.");
|
|
85
|
+
if (response.status >= 500) {
|
|
86
|
+
throw ctx.fail.providerUnavailable(`WorkBuddy billing API returned HTTP ${response.status}.`);
|
|
87
|
+
}
|
|
88
|
+
if (response.status !== 200) throw ctx.fail.apiFailure(`WorkBuddy billing API returned HTTP ${response.status}.`);
|
|
89
|
+
const root = decode(response.bodyText, "expected a JSON object");
|
|
90
|
+
if (root.code !== 0) {
|
|
91
|
+
throw ctx.fail.apiFailure(`WorkBuddy billing API returned code ${String(root.code)}.`);
|
|
92
|
+
}
|
|
93
|
+
return object(root.data, "data");
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
// Cycle timestamps carry no zone. The billing cycle follows China Standard Time calendar months.
|
|
97
|
+
const cycleEnd = (value) => {
|
|
98
|
+
const raw = text(value);
|
|
99
|
+
if (!raw) return undefined;
|
|
100
|
+
const match = /^(\d{4}-\d{2}-\d{2}) (\d{2}:\d{2}:\d{2})$/.exec(raw);
|
|
101
|
+
if (!match) return undefined;
|
|
102
|
+
const end = ctx.date.iso(`${match[1]}T${match[2]}+08:00`);
|
|
103
|
+
// The dashboard refreshes one second after the inclusive end time.
|
|
104
|
+
return new Date(end.getTime() + 1000);
|
|
105
|
+
};
|
|
106
|
+
// Package codes the WorkBuddy client sends for its paid and free listings (WorkBuddy 5.6.2).
|
|
107
|
+
const paidPackageCodes = [
|
|
108
|
+
"TCACA_code_002_AkiJS3ZHF5",
|
|
109
|
+
"TCACA_code_005_maRGyrHhw1",
|
|
110
|
+
"TCACA_code_003_FAnt7lcmRT",
|
|
111
|
+
"TCACA_code_023_4xbGhMrE6q",
|
|
112
|
+
"TCACA_code_026_BaESVICNoi",
|
|
113
|
+
"TCACA_code_027_0FCGVA6vSa",
|
|
114
|
+
"TCACA_code_009_0XmEQc2xOf",
|
|
115
|
+
"TCACA_code_038_OhvqZtiPKr",
|
|
116
|
+
"TCACA_code_036_lupO5WgNdG",
|
|
117
|
+
];
|
|
118
|
+
const freePackageCodes = [
|
|
119
|
+
"TCACA_code_001_PqouKr6QWV",
|
|
120
|
+
"TCACA_code_008_cfWoLwvjU4",
|
|
121
|
+
"TCACA_code_035_ArVxJcGDsm",
|
|
122
|
+
"TCACA_code_006_DbXS0lrypC",
|
|
123
|
+
"TCACA_code_039_KRcQj7wUat",
|
|
124
|
+
"TCACA_code_040_mi9rCYg46x",
|
|
125
|
+
"TCACA_code_007_nzdH5h4Nl0",
|
|
126
|
+
"TCACA_code_028_NtpWi0jzXs",
|
|
127
|
+
"TCACA_code_037_WxOD3MpI2o",
|
|
128
|
+
"TCACA_code_029_6wCGEWquYy",
|
|
129
|
+
"TCACA_code_030_BjSt89qTvr",
|
|
130
|
+
];
|
|
131
|
+
// Reset time is optional: a failed or unexpected package listing must not discard the balance.
|
|
132
|
+
const nextReset = async (session, agent) => {
|
|
133
|
+
const now = ctx.date.now().getTime();
|
|
134
|
+
// Share five seconds across listings, retaining the host's margin for publishing the balance.
|
|
135
|
+
const hostDeadline = Number(ctx.settings.get("resetDeadlineMillis"));
|
|
136
|
+
const deadline = Math.min(Date.now() + 5000, hostDeadline > 0 ? hostDeadline : Infinity);
|
|
137
|
+
let earliest;
|
|
138
|
+
for (const [path, codes] of [
|
|
139
|
+
["/billing/meter/get-user-resource-paid-packages", paidPackageCodes],
|
|
140
|
+
["/billing/meter/get-user-resource-free-packages", freePackageCodes],
|
|
141
|
+
]) {
|
|
142
|
+
const budget = (deadline - Date.now()) / 1000;
|
|
143
|
+
if (budget < 1) break;
|
|
144
|
+
try {
|
|
145
|
+
// The listings reject requests without package codes (HTTP 400, code 10001). Status 0 is valid, 3 used up.
|
|
146
|
+
const page = await post(
|
|
147
|
+
session,
|
|
148
|
+
agent,
|
|
149
|
+
path,
|
|
150
|
+
{ PageNumber: 1, PageSize: 100, PackageCodes: codes, Status: [0, 3] },
|
|
151
|
+
Math.min(timeoutSeconds, budget),
|
|
152
|
+
);
|
|
153
|
+
if (!Array.isArray(page.Accounts)) continue;
|
|
154
|
+
for (const raw of page.Accounts) {
|
|
155
|
+
if (raw === null || typeof raw !== "object" || Array.isArray(raw)) continue;
|
|
156
|
+
const account = raw;
|
|
157
|
+
if (account.CapacityUnit !== "credits") continue;
|
|
158
|
+
const end = cycleEnd(account.CycleEndTime);
|
|
159
|
+
if (end && end.getTime() > now && (!earliest || end.getTime() < earliest.getTime())) earliest = end;
|
|
160
|
+
}
|
|
161
|
+
} catch (error) {
|
|
162
|
+
const failure = error;
|
|
163
|
+
if (failure.transportClass === "cancelled") throw error;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
return earliest;
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
let rejected = false;
|
|
170
|
+
for await (const session of ctx.browser.sessions(domain)) {
|
|
171
|
+
let summary;
|
|
172
|
+
let agent;
|
|
173
|
+
for (const candidate of userAgents) {
|
|
174
|
+
try {
|
|
175
|
+
summary = await post(session, candidate, "/billing/meter/get-user-resource-summary", {});
|
|
176
|
+
agent = candidate;
|
|
177
|
+
break;
|
|
178
|
+
} catch (error) {
|
|
179
|
+
if (error !== sessionExpired) throw error;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
if (!summary) {
|
|
183
|
+
rejected = true;
|
|
184
|
+
ctx.browser.rejectCookie(domain, session);
|
|
185
|
+
if (policy === "manual") break;
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if (!Array.isArray(summary.Packages)) return fail("Packages");
|
|
190
|
+
// The host caches an imported session only after the billing API accepts it.
|
|
191
|
+
if (policy !== "manual") ctx.browser.acceptCookie(domain, session);
|
|
192
|
+
let total = 0;
|
|
193
|
+
let remaining = 0;
|
|
194
|
+
let frozen = 0;
|
|
195
|
+
for (const raw of summary.Packages) {
|
|
196
|
+
const item = object(raw, "package");
|
|
197
|
+
if (item.CapacityUnit !== "credits") continue;
|
|
198
|
+
total += amount(item.CycleTotalCapacity, "CycleTotalCapacity");
|
|
199
|
+
remaining += amount(item.CycleRemainCapacity, "CycleRemainCapacity");
|
|
200
|
+
if (item.CycleFrozenCapacity !== undefined) frozen += amount(item.CycleFrozenCapacity, "CycleFrozenCapacity");
|
|
201
|
+
}
|
|
202
|
+
const plan = text(summary.SubscriptionPackageName);
|
|
203
|
+
// Frozen credits are listed as reported; the meter uses the remaining amount the dashboard shows.
|
|
204
|
+
const rows = frozen > 0 ? [{ label: "Reserved", value: format(frozen) }] : [];
|
|
205
|
+
if (total <= 0) {
|
|
206
|
+
rows.unshift({ label: "Left", value: format(remaining) }, { label: "Total", value: format(total) });
|
|
207
|
+
}
|
|
208
|
+
return {
|
|
209
|
+
primary:
|
|
210
|
+
total > 0
|
|
211
|
+
? {
|
|
212
|
+
usedPercent: ctx.pct(Math.max(0, total - remaining), total),
|
|
213
|
+
resetsAt: await nextReset(session, agent),
|
|
214
|
+
resetDescription: `${format(remaining)} / ${format(total)} credits left`,
|
|
215
|
+
}
|
|
216
|
+
: undefined,
|
|
217
|
+
details: rows.length ? [{ title: "Credits", rows }] : undefined,
|
|
218
|
+
identity: plan ? { loginMethod: plan } : undefined,
|
|
219
|
+
dataConfidence: "exact",
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
if (rejected) throw sessionExpired;
|
|
223
|
+
throw ctx.fail.missingCredential(
|
|
224
|
+
`No WorkBuddy session cookies found. Sign in at www.workbuddy.cn (${ctx.browser.supportedBrowsers}) or paste a Cookie header.`,
|
|
225
|
+
);
|
|
226
|
+
},
|
|
227
|
+
});
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": 1,
|
|
3
|
+
"id": "workbuddy",
|
|
4
|
+
"name": "WorkBuddy",
|
|
5
|
+
"version": "0.1.0",
|
|
6
|
+
"entry": "plugin.js",
|
|
7
|
+
"enabledByDefault": false,
|
|
8
|
+
"supportedModes": [
|
|
9
|
+
"web"
|
|
10
|
+
],
|
|
11
|
+
"autoMode": "web",
|
|
12
|
+
"usageDashboardUrl": "https://www.workbuddy.cn",
|
|
13
|
+
"webUrl": "https://workbuddy.cn",
|
|
14
|
+
"envVars": [
|
|
15
|
+
"WORKBUDDY_COOKIE"
|
|
16
|
+
],
|
|
17
|
+
"setupHelp": "Use a Cookie header from www.workbuddy.cn and the full User-Agent from the same browser. Set Browser User-Agent below. Desktop login files are not imported.",
|
|
18
|
+
"setupFields": [
|
|
19
|
+
{
|
|
20
|
+
"key": "browserUserAgent",
|
|
21
|
+
"title": "Browser User-Agent",
|
|
22
|
+
"type": "string",
|
|
23
|
+
"description": "Paste the full User-Agent from the same browser session that supplied the cookies. Required for WorkBuddy web readback."
|
|
24
|
+
}
|
|
25
|
+
],
|
|
26
|
+
"upstream": {
|
|
27
|
+
"repository": "https://github.com/steipete/CodexBar",
|
|
28
|
+
"revision": "6ba06fe9ec1522dd5c59e16810115bce28aa148f",
|
|
29
|
+
"path": "Sources/CodexBarCore/Resources/Plugins/workbuddy.js",
|
|
30
|
+
"sha256": "d5dbd97ac32a7ceee75f0969415c7601e17402a6cad19a163735b4747e4d2e89",
|
|
31
|
+
"license": "MIT"
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
],
|
|
16
16
|
"upstream": {
|
|
17
17
|
"repository": "https://github.com/steipete/CodexBar",
|
|
18
|
-
"revision": "
|
|
18
|
+
"revision": "6ba06fe9ec1522dd5c59e16810115bce28aa148f",
|
|
19
19
|
"path": "Sources/CodexBarCore/Resources/Plugins/xkiro.js",
|
|
20
20
|
"sha256": "03bfcb8e518577ee18dd335e4e1e1f7d629d3e5abdf2216b187b1cba8b683853",
|
|
21
21
|
"license": "MIT"
|
package/usagestat
CHANGED
|
Binary file
|
package/usagestatd
CHANGED
|
Binary file
|