@howaboua/opencode-usage-plugin 0.1.3 → 0.1.4-dev.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.
- package/dist/providers/proxy/index.d.ts.map +1 -1
- package/dist/providers/proxy/index.js +48 -0
- package/dist/types.d.ts +2 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/ui/formatters/copilot.d.ts +7 -0
- package/dist/ui/formatters/copilot.d.ts.map +1 -0
- package/dist/ui/formatters/copilot.js +21 -0
- package/dist/ui/formatters/proxy.d.ts +7 -0
- package/dist/ui/formatters/proxy.d.ts.map +1 -0
- package/dist/ui/formatters/proxy.js +31 -0
- package/dist/ui/formatters/shared.d.ts +10 -0
- package/dist/ui/formatters/shared.d.ts.map +1 -0
- package/dist/ui/formatters/shared.js +63 -0
- package/dist/ui/status.d.ts +1 -0
- package/dist/ui/status.d.ts.map +1 -1
- package/dist/ui/status.js +34 -184
- package/dist/usage/auth/loader.d.ts +21 -0
- package/dist/usage/auth/loader.d.ts.map +1 -0
- package/dist/usage/auth/loader.js +68 -0
- package/dist/usage/fetch.d.ts +4 -6
- package/dist/usage/fetch.d.ts.map +1 -1
- package/dist/usage/fetch.js +59 -138
- package/dist/usage/registry.d.ts +5 -0
- package/dist/usage/registry.d.ts.map +1 -1
- package/dist/usage/registry.js +11 -2
- package/dist/utils/paths.d.ts.map +1 -1
- package/dist/utils/paths.js +5 -22
- package/package.json +4 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/providers/proxy/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAA;AAM5C,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAA;AAC5C,OAAO,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAA;AAC1C,OAAO,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAA;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/providers/proxy/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAA;AAM5C,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAA;AAC5C,OAAO,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAA;AAC1C,OAAO,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAA;AAkM5C,eAAO,MAAM,aAAa,EAAE,aAAa,CAAC,IAAI,CAwB7C,CAAA"}
|
|
@@ -39,6 +39,49 @@ function normalizeTier(tier) {
|
|
|
39
39
|
return "paid";
|
|
40
40
|
return "free";
|
|
41
41
|
}
|
|
42
|
+
function parseQuotaGroupsFromAggregation(quotaGroups) {
|
|
43
|
+
if (!quotaGroups)
|
|
44
|
+
return [];
|
|
45
|
+
const tiers = {
|
|
46
|
+
paid: new Map(),
|
|
47
|
+
free: new Map(),
|
|
48
|
+
};
|
|
49
|
+
for (const [groupName, groupData] of Object.entries(quotaGroups)) {
|
|
50
|
+
const mappedName = GROUP_MAPPING[groupName];
|
|
51
|
+
if (!mappedName)
|
|
52
|
+
continue;
|
|
53
|
+
const windows = groupData.windows || {};
|
|
54
|
+
const windowPriority = ["daily", "5h", "1h", "15m"];
|
|
55
|
+
let bestWindowName = null;
|
|
56
|
+
for (const windowName of windowPriority) {
|
|
57
|
+
if (windows[windowName]) {
|
|
58
|
+
bestWindowName = windowName;
|
|
59
|
+
break;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
if (!bestWindowName && Object.keys(windows).length > 0) {
|
|
63
|
+
bestWindowName = Object.keys(windows)[0];
|
|
64
|
+
}
|
|
65
|
+
if (!bestWindowName)
|
|
66
|
+
continue;
|
|
67
|
+
const window = windows[bestWindowName];
|
|
68
|
+
// Since these are already aggregated by provider, we split by tier if possible.
|
|
69
|
+
// However, the aggregate API provides tier-agnostic totals.
|
|
70
|
+
// For now, we put them into "paid" as the default high-level view.
|
|
71
|
+
tiers.paid.set(mappedName, {
|
|
72
|
+
name: mappedName,
|
|
73
|
+
remaining: window.total_remaining,
|
|
74
|
+
max: window.total_max,
|
|
75
|
+
remainingPct: Math.round(window.remaining_pct),
|
|
76
|
+
resetTime: null, // Aggregated windows don't have a single reset_at
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
const result = [];
|
|
80
|
+
if (tiers.paid.size > 0) {
|
|
81
|
+
result.push({ tier: "paid", quotaGroups: sortQuotaGroups(Array.from(tiers.paid.values())) });
|
|
82
|
+
}
|
|
83
|
+
return result;
|
|
84
|
+
}
|
|
42
85
|
function parseQuotaGroupsFromCredential(groupUsage) {
|
|
43
86
|
if (!groupUsage)
|
|
44
87
|
return [];
|
|
@@ -72,6 +115,11 @@ function parseQuotaGroupsFromCredential(groupUsage) {
|
|
|
72
115
|
return Array.from(result.values());
|
|
73
116
|
}
|
|
74
117
|
function aggregateByProvider(provider) {
|
|
118
|
+
// Try aggregated quota groups first
|
|
119
|
+
if (provider.quota_groups && Object.keys(provider.quota_groups).length > 0) {
|
|
120
|
+
return parseQuotaGroupsFromAggregation(provider.quota_groups);
|
|
121
|
+
}
|
|
122
|
+
// Fallback to manual aggregation of credentials
|
|
75
123
|
const tiers = {
|
|
76
124
|
paid: new Map(),
|
|
77
125
|
free: new Map(),
|
package/dist/types.d.ts
CHANGED
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,eAAO,MAAM,SAAS,0IAcZ,CAAA;AAEV,MAAM,MAAM,QAAQ,GAAG,CAAC,OAAO,SAAS,CAAC,CAAC,MAAM,CAAC,CAAA;AAEjD,MAAM,WAAW,eAAe;IAC9B,WAAW,EAAE,MAAM,CAAA;IACnB,aAAa,EAAE,MAAM,GAAG,IAAI,CAAA;IAC5B,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;CACxB;AAED,MAAM,WAAW,eAAe;IAC9B,UAAU,EAAE,OAAO,CAAA;IACnB,SAAS,EAAE,OAAO,CAAA;IAClB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;CACvB;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,MAAM,CAAA;IACb,gBAAgB,EAAE,MAAM,CAAA;IACxB,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACzB,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,gBAAgB,CAAC,EAAE,MAAM,CAAA;CAC1B;AAED,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAA;IACZ,SAAS,EAAE,MAAM,CAAA;IACjB,GAAG,EAAE,MAAM,CAAA;IACX,YAAY,EAAE,MAAM,CAAA;IACpB,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAC1B;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,GAAG,MAAM,CAAA;IACrB,WAAW,EAAE,eAAe,EAAE,CAAA;CAC/B;AAED,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,aAAa,EAAE,CAAA;CACvB;AAED,MAAM,WAAW,UAAU;IACzB,SAAS,EAAE,iBAAiB,EAAE,CAAA;IAC9B,gBAAgB,EAAE,MAAM,CAAA;IACxB,iBAAiB,EAAE,MAAM,CAAA;IACzB,UAAU,EAAE,MAAM,CAAA;CACnB;AAED,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,SAAS,CAAC,EAAE;QACV,MAAM,CAAC,EAAE,OAAO,CAAA;QAChB,KAAK,CAAC,EAAE,OAAO,CAAA;QACf,OAAO,CAAC,EAAE,OAAO,CAAA;KAClB,CAAA;CACF;AAED,MAAM,WAAW,aAAa;IAC5B,SAAS,EAAE,MAAM,CAAA;IACjB,QAAQ,EAAE,MAAM,CAAA;IAChB,QAAQ,EAAE,QAAQ,GAAG,IAAI,CAAA;IACzB,OAAO,EAAE,eAAe,GAAG,IAAI,CAAA;IAC/B,SAAS,EAAE,eAAe,GAAG,IAAI,CAAA;IACjC,UAAU,EAAE,eAAe,GAAG,IAAI,CAAA;IAClC,OAAO,EAAE,eAAe,GAAG,IAAI,CAAA;IAC/B,UAAU,CAAC,EAAE,UAAU,CAAA;IACvB,YAAY,CAAC,EAAE,YAAY,CAAA;IAC3B,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,CAAC,EAAE,OAAO,CAAA;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,eAAO,MAAM,SAAS,0IAcZ,CAAA;AAEV,MAAM,MAAM,QAAQ,GAAG,CAAC,OAAO,SAAS,CAAC,CAAC,MAAM,CAAC,CAAA;AAEjD,MAAM,WAAW,eAAe;IAC9B,WAAW,EAAE,MAAM,CAAA;IACnB,aAAa,EAAE,MAAM,GAAG,IAAI,CAAA;IAC5B,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;CACxB;AAED,MAAM,WAAW,eAAe;IAC9B,UAAU,EAAE,OAAO,CAAA;IACnB,SAAS,EAAE,OAAO,CAAA;IAClB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;CACvB;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,MAAM,CAAA;IACb,gBAAgB,EAAE,MAAM,CAAA;IACxB,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACzB,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,gBAAgB,CAAC,EAAE,MAAM,CAAA;CAC1B;AAED,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAA;IACZ,SAAS,EAAE,MAAM,CAAA;IACjB,GAAG,EAAE,MAAM,CAAA;IACX,YAAY,EAAE,MAAM,CAAA;IACpB,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAC1B;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,GAAG,MAAM,CAAA;IACrB,WAAW,EAAE,eAAe,EAAE,CAAA;CAC/B;AAED,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,aAAa,EAAE,CAAA;CACvB;AAED,MAAM,WAAW,UAAU;IACzB,SAAS,EAAE,iBAAiB,EAAE,CAAA;IAC9B,gBAAgB,EAAE,MAAM,CAAA;IACxB,iBAAiB,EAAE,MAAM,CAAA;IACzB,UAAU,EAAE,MAAM,CAAA;CACnB;AAED,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,SAAS,CAAC,EAAE;QACV,MAAM,CAAC,EAAE,OAAO,CAAA;QAChB,KAAK,CAAC,EAAE,OAAO,CAAA;QACf,OAAO,CAAC,EAAE,OAAO,CAAA;KAClB,CAAA;CACF;AAED,MAAM,WAAW,aAAa;IAC5B,SAAS,EAAE,MAAM,CAAA;IACjB,QAAQ,EAAE,MAAM,CAAA;IAChB,QAAQ,EAAE,QAAQ,GAAG,IAAI,CAAA;IACzB,OAAO,EAAE,eAAe,GAAG,IAAI,CAAA;IAC/B,SAAS,EAAE,eAAe,GAAG,IAAI,CAAA;IACjC,UAAU,EAAE,eAAe,GAAG,IAAI,CAAA;IAClC,OAAO,EAAE,eAAe,GAAG,IAAI,CAAA;IAC/B,UAAU,CAAC,EAAE,UAAU,CAAA;IACvB,YAAY,CAAC,EAAE,YAAY,CAAA;IAC3B,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,cAAc,CAAC,EAAE,MAAM,EAAE,CAAA;CAC1B;AAED,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAA;IACV,SAAS,EAAE,MAAM,CAAA;IACjB,QAAQ,EAAE,MAAM,CAAA;IAChB,KAAK,EAAE,MAAM,CAAA;IACb,SAAS,EAAE,MAAM,CAAA;IACjB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,WAAW,EAAE,MAAM,CAAA;IACnB,YAAY,EAAE,MAAM,CAAA;IACpB,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Formats GitHub Copilot usage snapshots.
|
|
3
|
+
* Handles chat and completion quotas with progress bars and reset times.
|
|
4
|
+
*/
|
|
5
|
+
import type { UsageSnapshot } from "../../types";
|
|
6
|
+
export declare function formatCopilotSnapshot(snapshot: UsageSnapshot): string[];
|
|
7
|
+
//# sourceMappingURL=copilot.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"copilot.d.ts","sourceRoot":"","sources":["../../../src/ui/formatters/copilot.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAGhD,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,aAAa,GAAG,MAAM,EAAE,CAkBvE"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Formats GitHub Copilot usage snapshots.
|
|
3
|
+
* Handles chat and completion quotas with progress bars and reset times.
|
|
4
|
+
*/
|
|
5
|
+
import { formatBar, formatResetSuffixISO, formatMissingSnapshot } from "./shared";
|
|
6
|
+
export function formatCopilotSnapshot(snapshot) {
|
|
7
|
+
const copilot = snapshot.copilotQuota;
|
|
8
|
+
if (!copilot)
|
|
9
|
+
return formatMissingSnapshot(snapshot);
|
|
10
|
+
const lines = ["→ [GITHUB] Copilot"];
|
|
11
|
+
const reset = copilot.resetTime ? formatResetSuffixISO(copilot.resetTime) : "";
|
|
12
|
+
const total = copilot.total === -1 ? "∞" : copilot.total.toString();
|
|
13
|
+
lines.push(` ${"Chat:".padEnd(13)} ${formatBar(copilot.percentRemaining)} ${copilot.used}/${total}${reset}`);
|
|
14
|
+
if (copilot.completionsUsed !== undefined && copilot.completionsTotal !== undefined) {
|
|
15
|
+
const pct = copilot.completionsTotal > 0
|
|
16
|
+
? Math.round((copilot.completionsUsed / copilot.completionsTotal) * 100)
|
|
17
|
+
: 0;
|
|
18
|
+
lines.push(` ${"Completions:".padEnd(13)} ${formatBar(pct)} ${copilot.completionsUsed}/${copilot.completionsTotal}`);
|
|
19
|
+
}
|
|
20
|
+
return lines;
|
|
21
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Formats Mirrowel Proxy usage snapshots.
|
|
3
|
+
* Handles multi-provider and multi-tier quota reporting with progress bars.
|
|
4
|
+
*/
|
|
5
|
+
import type { UsageSnapshot } from "../../types";
|
|
6
|
+
export declare function formatProxySnapshot(snapshot: UsageSnapshot): string[];
|
|
7
|
+
//# sourceMappingURL=proxy.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"proxy.d.ts","sourceRoot":"","sources":["../../../src/ui/formatters/proxy.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAGhD,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,aAAa,GAAG,MAAM,EAAE,CAcrE"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Formats Mirrowel Proxy usage snapshots.
|
|
3
|
+
* Handles multi-provider and multi-tier quota reporting with progress bars.
|
|
4
|
+
*/
|
|
5
|
+
import { formatBar, formatResetSuffixISO, formatMissingSnapshot } from "./shared";
|
|
6
|
+
export function formatProxySnapshot(snapshot) {
|
|
7
|
+
const proxy = snapshot.proxyQuota;
|
|
8
|
+
if (!proxy?.providers?.length)
|
|
9
|
+
return formatMissingSnapshot(snapshot);
|
|
10
|
+
const lines = ["→ [Google] Mirrowel Proxy"];
|
|
11
|
+
for (const provider of proxy.providers) {
|
|
12
|
+
const providerLines = formatProxyProvider(provider);
|
|
13
|
+
if (providerLines.length) {
|
|
14
|
+
lines.push("", ` ${provider.name}:`, ...providerLines);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
return lines;
|
|
18
|
+
}
|
|
19
|
+
function formatProxyProvider(provider) {
|
|
20
|
+
const lines = [];
|
|
21
|
+
for (const tier of provider.tiers) {
|
|
22
|
+
if (!tier.quotaGroups?.length)
|
|
23
|
+
continue;
|
|
24
|
+
lines.push(` ${tier.tier === "paid" ? "Paid" : "Free"}:`);
|
|
25
|
+
for (const group of tier.quotaGroups) {
|
|
26
|
+
const reset = group.resetTime ? formatResetSuffixISO(group.resetTime) : "";
|
|
27
|
+
lines.push(` ${group.name.padEnd(9)} ${formatBar(group.remainingPct)} ${group.remaining}/${group.max}${reset}`);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return lines;
|
|
31
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Formats various usage snapshots (Proxy, Copilot, Codex) into human-readable text.
|
|
3
|
+
* Provides specialized formatting for progress bars, reset times, and missing data states.
|
|
4
|
+
*/
|
|
5
|
+
import type { UsageSnapshot } from "../../types";
|
|
6
|
+
export declare function formatBar(pct: number): string;
|
|
7
|
+
export declare function formatResetSuffix(resetAt: number | null): string;
|
|
8
|
+
export declare function formatResetSuffixISO(iso: string): string;
|
|
9
|
+
export declare function formatMissingSnapshot(snapshot: UsageSnapshot): string[];
|
|
10
|
+
//# sourceMappingURL=shared.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shared.d.ts","sourceRoot":"","sources":["../../../src/ui/formatters/shared.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAEhD,wBAAgB,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAK7C;AAED,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,CAGhE;AAED,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAKxD;AAWD,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,aAAa,GAAG,MAAM,EAAE,CAgBvE"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Formats various usage snapshots (Proxy, Copilot, Codex) into human-readable text.
|
|
3
|
+
* Provides specialized formatting for progress bars, reset times, and missing data states.
|
|
4
|
+
*/
|
|
5
|
+
import { platform, homedir } from "os";
|
|
6
|
+
import { join } from "path";
|
|
7
|
+
export function formatBar(pct) {
|
|
8
|
+
const clamped = Math.max(0, Math.min(100, pct));
|
|
9
|
+
const size = 15;
|
|
10
|
+
const filled = Math.round((clamped / 100) * size);
|
|
11
|
+
return `${"█".repeat(filled)}${"░".repeat(size - filled)}`;
|
|
12
|
+
}
|
|
13
|
+
export function formatResetSuffix(resetAt) {
|
|
14
|
+
if (!resetAt)
|
|
15
|
+
return "";
|
|
16
|
+
return ` (resets in ${formatTimeDelta(resetAt)})`;
|
|
17
|
+
}
|
|
18
|
+
export function formatResetSuffixISO(iso) {
|
|
19
|
+
try {
|
|
20
|
+
const at = Math.floor(new Date(iso).getTime() / 1000);
|
|
21
|
+
return ` (resets in ${formatTimeDelta(at)})`;
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
return "";
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
function formatTimeDelta(at) {
|
|
28
|
+
const diff = at - Math.floor(Date.now() / 1000);
|
|
29
|
+
if (diff <= 0)
|
|
30
|
+
return "now";
|
|
31
|
+
if (diff < 60)
|
|
32
|
+
return `${diff}s`;
|
|
33
|
+
if (diff < 3600)
|
|
34
|
+
return `${Math.ceil(diff / 60)}m`;
|
|
35
|
+
if (diff < 84400)
|
|
36
|
+
return `${Math.round(diff / 3600)}h`;
|
|
37
|
+
return `${Math.round(diff / 86400)}d`;
|
|
38
|
+
}
|
|
39
|
+
export function formatMissingSnapshot(snapshot) {
|
|
40
|
+
const { provider } = snapshot;
|
|
41
|
+
const configPath = getConfigPath();
|
|
42
|
+
const instructions = {
|
|
43
|
+
codex: "if you dont have codex oauth, please set your usage-config.jsonc to openai: false",
|
|
44
|
+
proxy: "if you are not running Mirrowel's proxy, please set your usage-config.jsonc to proxy: false",
|
|
45
|
+
copilot: "if you are not running GitHub Copilot, please set your usage-config.jsonc to copilot: false"
|
|
46
|
+
};
|
|
47
|
+
const lines = [`→ [${provider.toUpperCase()}] - ${instructions[provider] || ""}`];
|
|
48
|
+
if (snapshot.missingReason)
|
|
49
|
+
lines.push("", `Reason: ${snapshot.missingReason}`);
|
|
50
|
+
if (snapshot.missingDetails?.length) {
|
|
51
|
+
lines.push("", "Details:", ...snapshot.missingDetails.map((d) => `- ${d}`));
|
|
52
|
+
}
|
|
53
|
+
return [...lines, "", `File: ${configPath}`, "", "Issue? https://github.com/IgorWarzocha/opencode-usage-plugin/issues"];
|
|
54
|
+
}
|
|
55
|
+
function getConfigPath() {
|
|
56
|
+
const home = homedir();
|
|
57
|
+
const plat = platform();
|
|
58
|
+
if (plat === "darwin")
|
|
59
|
+
return join(home, ".config", "opencode", "usage-config.jsonc");
|
|
60
|
+
if (plat === "win32")
|
|
61
|
+
return join(process.env.APPDATA || join(home, "AppData", "Roaming"), "opencode", "usage-config.jsonc");
|
|
62
|
+
return join(process.env.XDG_CONFIG_HOME || join(home, ".config"), "opencode", "usage-config.jsonc");
|
|
63
|
+
}
|
package/dist/ui/status.d.ts
CHANGED
package/dist/ui/status.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../../src/ui/status.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../../src/ui/status.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAA;AACtD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AAC7C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,UAAU,CAAA;AAK1C,KAAK,WAAW,GAAG,WAAW,CAAC,QAAQ,CAAC,CAAA;AAExC,wBAAsB,iBAAiB,CAAC,OAAO,EAAE;IAC/C,MAAM,EAAE,WAAW,CAAA;IACnB,KAAK,EAAE,UAAU,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;IACjB,IAAI,EAAE,MAAM,CAAA;CACb,GAAG,OAAO,CAAC,IAAI,CAAC,CAmBhB;AAiCD,wBAAsB,iBAAiB,CAAC,OAAO,EAAE;IAC/C,MAAM,EAAE,WAAW,CAAA;IACnB,KAAK,EAAE,UAAU,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,aAAa,EAAE,CAAA;IAC1B,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB,GAAG,OAAO,CAAC,IAAI,CAAC,CAahB"}
|
package/dist/ui/status.js
CHANGED
|
@@ -1,218 +1,68 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Renders usage snapshots into readable status text.
|
|
3
|
+
* Dispatches to specialized formatters and manages session messaging.
|
|
3
4
|
*/
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
5
|
+
import { formatProxySnapshot } from "./formatters/proxy";
|
|
6
|
+
import { formatCopilotSnapshot } from "./formatters/copilot";
|
|
7
|
+
import { formatBar, formatResetSuffix, formatMissingSnapshot } from "./formatters/shared";
|
|
6
8
|
export async function sendStatusMessage(options) {
|
|
7
|
-
// @ts-ignore
|
|
8
9
|
const bus = options.client.bus;
|
|
9
10
|
if (bus) {
|
|
10
11
|
try {
|
|
11
12
|
await bus.publish({
|
|
12
13
|
topic: "companion.projection",
|
|
13
|
-
body: {
|
|
14
|
-
key: "usage",
|
|
15
|
-
kind: "markdown",
|
|
16
|
-
content: options.text,
|
|
17
|
-
},
|
|
14
|
+
body: { key: "usage", kind: "markdown", content: options.text },
|
|
18
15
|
});
|
|
19
16
|
}
|
|
20
17
|
catch { }
|
|
21
18
|
}
|
|
22
|
-
await options.client.session
|
|
23
|
-
.prompt({
|
|
19
|
+
await options.client.session.prompt({
|
|
24
20
|
path: { id: options.sessionID },
|
|
25
|
-
body: {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
{
|
|
29
|
-
type: "text",
|
|
30
|
-
text: options.text,
|
|
31
|
-
ignored: true,
|
|
32
|
-
},
|
|
33
|
-
],
|
|
34
|
-
},
|
|
35
|
-
})
|
|
36
|
-
.catch(async () => {
|
|
37
|
-
await options.client.tui
|
|
38
|
-
.showToast({
|
|
21
|
+
body: { noReply: true, parts: [{ type: "text", text: options.text, ignored: true }] },
|
|
22
|
+
}).catch(async () => {
|
|
23
|
+
await options.client.tui.showToast({
|
|
39
24
|
body: { title: "Usage Status", message: options.text, variant: "info" },
|
|
40
|
-
})
|
|
41
|
-
.catch(() => { });
|
|
25
|
+
}).catch(() => { });
|
|
42
26
|
});
|
|
43
27
|
}
|
|
44
|
-
function formatBar(remainingPercent) {
|
|
45
|
-
const clamped = Math.max(0, Math.min(100, remainingPercent));
|
|
46
|
-
const size = 15;
|
|
47
|
-
const filled = Math.round((clamped / 100) * size);
|
|
48
|
-
const empty = size - filled;
|
|
49
|
-
return `${"█".repeat(filled)}${"░".repeat(empty)}`;
|
|
50
|
-
}
|
|
51
|
-
function formatPlanType(planType) {
|
|
52
|
-
return planType
|
|
53
|
-
.replace(/_/g, " ")
|
|
54
|
-
.split(" ")
|
|
55
|
-
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
|
|
56
|
-
.join(" ");
|
|
57
|
-
}
|
|
58
|
-
function formatResetTime(resetAt) {
|
|
59
|
-
const now = Math.floor(Date.now() / 1000);
|
|
60
|
-
const diff = resetAt - now;
|
|
61
|
-
if (diff <= 0)
|
|
62
|
-
return "now";
|
|
63
|
-
if (diff < 60)
|
|
64
|
-
return `${diff}s`;
|
|
65
|
-
if (diff < 3600)
|
|
66
|
-
return `${Math.ceil(diff / 60)}m`;
|
|
67
|
-
if (diff < 86400)
|
|
68
|
-
return `${Math.round(diff / 3600)}h`;
|
|
69
|
-
return `${Math.round(diff / 86400)}d`;
|
|
70
|
-
}
|
|
71
|
-
function formatResetSuffix(resetAt) {
|
|
72
|
-
if (!resetAt)
|
|
73
|
-
return "";
|
|
74
|
-
return ` (resets in ${formatResetTime(resetAt)})`;
|
|
75
|
-
}
|
|
76
|
-
function formatResetSuffixISO(isoString) {
|
|
77
|
-
try {
|
|
78
|
-
const resetAt = Math.floor(new Date(isoString).getTime() / 1000);
|
|
79
|
-
return ` (resets in ${formatResetTime(resetAt)})`;
|
|
80
|
-
}
|
|
81
|
-
catch {
|
|
82
|
-
return "";
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
function formatProxySnapshot(snapshot) {
|
|
86
|
-
const proxy = snapshot.proxyQuota;
|
|
87
|
-
if (!proxy)
|
|
88
|
-
return ["→ [proxy] No data"];
|
|
89
|
-
const lines = ["→ [Google] Mirrowel Proxy"];
|
|
90
|
-
for (const provider of proxy.providers) {
|
|
91
|
-
lines.push("");
|
|
92
|
-
lines.push(` ${provider.name}:`);
|
|
93
|
-
for (const tierInfo of provider.tiers) {
|
|
94
|
-
const tierLabel = tierInfo.tier === "paid" ? "Paid" : "Free";
|
|
95
|
-
lines.push(` ${tierLabel}:`);
|
|
96
|
-
for (const group of tierInfo.quotaGroups) {
|
|
97
|
-
const resetSuffix = group.resetTime ? formatResetSuffixISO(group.resetTime) : "";
|
|
98
|
-
const label = `${group.name}:`.padEnd(9);
|
|
99
|
-
lines.push(` ${label} ${formatBar(group.remainingPct)} ${group.remaining}/${group.max}${resetSuffix}`);
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
return lines;
|
|
104
|
-
}
|
|
105
|
-
function formatCopilotSnapshot(snapshot) {
|
|
106
|
-
const copilot = snapshot.copilotQuota;
|
|
107
|
-
if (!copilot)
|
|
108
|
-
return ["→ [copilot] No data"];
|
|
109
|
-
const lines = ["→ [GITHUB] Copilot"];
|
|
110
|
-
const resetSuffix = copilot.resetTime ? formatResetSuffixISO(copilot.resetTime) : "";
|
|
111
|
-
const totalLabel = copilot.total === -1 ? "∞" : copilot.total.toString();
|
|
112
|
-
const chatLabel = "Chat:".padEnd(13);
|
|
113
|
-
const chatRemaining = copilot.used;
|
|
114
|
-
const chatPct = copilot.percentRemaining;
|
|
115
|
-
lines.push(` ${chatLabel} ${formatBar(chatPct)} ${chatRemaining}/${totalLabel}${resetSuffix}`);
|
|
116
|
-
if (copilot.completionsUsed !== undefined && copilot.completionsTotal !== undefined) {
|
|
117
|
-
const compLabel = "Completions:".padEnd(13);
|
|
118
|
-
const compPct = copilot.completionsTotal > 0
|
|
119
|
-
? Math.round((copilot.completionsUsed / copilot.completionsTotal) * 100)
|
|
120
|
-
: 0;
|
|
121
|
-
lines.push(` ${compLabel} ${formatBar(compPct)} ${copilot.completionsUsed}/${copilot.completionsTotal}`);
|
|
122
|
-
}
|
|
123
|
-
return lines;
|
|
124
|
-
}
|
|
125
|
-
function getAppDataPath() {
|
|
126
|
-
const home = homedir();
|
|
127
|
-
const plat = platform();
|
|
128
|
-
if (plat === "darwin")
|
|
129
|
-
return join(home, ".config", "opencode");
|
|
130
|
-
if (plat === "win32")
|
|
131
|
-
return join(process.env.APPDATA || join(home, "AppData", "Roaming"), "opencode");
|
|
132
|
-
return join(process.env.XDG_CONFIG_HOME || join(home, ".config"), "opencode");
|
|
133
|
-
}
|
|
134
|
-
function formatMissingSnapshot(snapshot) {
|
|
135
|
-
const provider = snapshot.provider;
|
|
136
|
-
const label = provider === "codex" ? "codex" : provider === "proxy" ? "proxy" : "gh";
|
|
137
|
-
const configPath = join(getAppDataPath(), "usage-config.jsonc");
|
|
138
|
-
let providerInstruction = "";
|
|
139
|
-
if (provider === "codex") {
|
|
140
|
-
providerInstruction = "if you dont have codex oauth, please set your usage-config.jsonc to openai: false";
|
|
141
|
-
}
|
|
142
|
-
else if (provider === "proxy") {
|
|
143
|
-
providerInstruction = "if you are not running Mirrowel's proxy, please set your usage-config.jsonc to proxy: false";
|
|
144
|
-
}
|
|
145
|
-
else if (provider === "copilot") {
|
|
146
|
-
providerInstruction = "if you are not running GitHub Copilot, please set your usage-config.jsonc to copilot: false";
|
|
147
|
-
}
|
|
148
|
-
return [
|
|
149
|
-
`→ [${provider.toUpperCase()}] - ${providerInstruction}`,
|
|
150
|
-
"",
|
|
151
|
-
`The file can be found in ${configPath}. Please read the comments in the file or visit the repo for the readme.`,
|
|
152
|
-
"",
|
|
153
|
-
"If you are seeing empty usages or errors, despite having everything set up correctly, please fire an issue at https://github.com/IgorWarzocha/opencode-usage-plugin/issues - thank you!",
|
|
154
|
-
];
|
|
155
|
-
}
|
|
156
28
|
function formatSnapshot(snapshot) {
|
|
157
|
-
if (snapshot.isMissing)
|
|
29
|
+
if (snapshot.isMissing)
|
|
158
30
|
return formatMissingSnapshot(snapshot);
|
|
159
|
-
|
|
160
|
-
if (snapshot.provider === "proxy" && snapshot.proxyQuota) {
|
|
31
|
+
if (snapshot.provider === "proxy")
|
|
161
32
|
return formatProxySnapshot(snapshot);
|
|
162
|
-
|
|
163
|
-
if (snapshot.provider === "copilot" && snapshot.copilotQuota) {
|
|
33
|
+
if (snapshot.provider === "copilot")
|
|
164
34
|
return formatCopilotSnapshot(snapshot);
|
|
165
|
-
}
|
|
166
|
-
const plan = snapshot.planType ? ` (${formatPlanType(snapshot.planType)})` : "";
|
|
35
|
+
const plan = snapshot.planType ? ` (${snapshot.planType.replace(/_/g, " ").replace(/\b\w/g, c => c.toUpperCase())})` : "";
|
|
167
36
|
const lines = [`→ [${snapshot.provider.toUpperCase()}]${plan}`];
|
|
168
|
-
const
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
const
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
const codeReview = snapshot.codeReview;
|
|
181
|
-
if (codeReview) {
|
|
182
|
-
const remainingPct = 100 - codeReview.usedPercent;
|
|
183
|
-
const label = "Code Review:".padEnd(13);
|
|
184
|
-
lines.push(` ${label} ${formatBar(remainingPct)} ${remainingPct.toFixed(0)}% left${formatResetSuffix(codeReview.resetsAt)}`);
|
|
37
|
+
const metrics = [
|
|
38
|
+
{ label: "Hourly:", data: snapshot.primary },
|
|
39
|
+
{ label: "Weekly:", data: snapshot.secondary },
|
|
40
|
+
{ label: "Code Review:", data: snapshot.codeReview }
|
|
41
|
+
];
|
|
42
|
+
let hasData = false;
|
|
43
|
+
for (const m of metrics) {
|
|
44
|
+
if (m.data) {
|
|
45
|
+
const pct = 100 - m.data.usedPercent;
|
|
46
|
+
lines.push(` ${m.label.padEnd(13)} ${formatBar(pct)} ${pct.toFixed(0)}% left${formatResetSuffix(m.data.resetsAt)}`);
|
|
47
|
+
hasData = true;
|
|
48
|
+
}
|
|
185
49
|
}
|
|
186
50
|
if (snapshot.credits?.hasCredits) {
|
|
187
51
|
lines.push(` Credits: ${snapshot.credits.balance}`);
|
|
52
|
+
hasData = true;
|
|
188
53
|
}
|
|
189
|
-
return lines;
|
|
54
|
+
return hasData ? lines : formatMissingSnapshot(snapshot);
|
|
190
55
|
}
|
|
191
56
|
export async function renderUsageStatus(options) {
|
|
192
57
|
if (options.snapshots.length === 0) {
|
|
193
58
|
const filterMsg = options.filter ? ` for "${options.filter}"` : "";
|
|
194
|
-
|
|
195
|
-
client: options.client,
|
|
196
|
-
state: options.state,
|
|
197
|
-
sessionID: options.sessionID,
|
|
198
|
-
text: `▣ Usage | No data received${filterMsg}.`,
|
|
199
|
-
});
|
|
200
|
-
return;
|
|
59
|
+
return sendStatusMessage({ ...options, text: `▣ Usage | No data received${filterMsg}.` });
|
|
201
60
|
}
|
|
202
61
|
const lines = ["▣ Usage Status", ""];
|
|
203
|
-
options.snapshots.forEach((
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
lines.push(
|
|
207
|
-
if (index < options.snapshots.length - 1) {
|
|
208
|
-
lines.push("");
|
|
209
|
-
lines.push("---");
|
|
210
|
-
}
|
|
211
|
-
});
|
|
212
|
-
await sendStatusMessage({
|
|
213
|
-
client: options.client,
|
|
214
|
-
state: options.state,
|
|
215
|
-
sessionID: options.sessionID,
|
|
216
|
-
text: lines.join("\n"),
|
|
62
|
+
options.snapshots.forEach((s, i) => {
|
|
63
|
+
lines.push(...formatSnapshot(s));
|
|
64
|
+
if (i < options.snapshots.length - 1)
|
|
65
|
+
lines.push("", "---");
|
|
217
66
|
});
|
|
67
|
+
await sendStatusMessage({ ...options, text: lines.join("\n") });
|
|
218
68
|
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Utility for loading and merging authentication records from multiple system paths.
|
|
3
|
+
* Handles platform-specific path resolution and specific provider auth transformations.
|
|
4
|
+
*/
|
|
5
|
+
import z from "zod";
|
|
6
|
+
declare const authEntrySchema: z.ZodObject<{
|
|
7
|
+
type: z.ZodOptional<z.ZodString>;
|
|
8
|
+
access: z.ZodOptional<z.ZodString>;
|
|
9
|
+
refresh: z.ZodOptional<z.ZodString>;
|
|
10
|
+
enterpriseUrl: z.ZodOptional<z.ZodString>;
|
|
11
|
+
accountId: z.ZodOptional<z.ZodString>;
|
|
12
|
+
key: z.ZodOptional<z.ZodString>;
|
|
13
|
+
}, z.core.$loose>;
|
|
14
|
+
export type AuthEntry = z.infer<typeof authEntrySchema>;
|
|
15
|
+
export type AuthRecord = Record<string, AuthEntry>;
|
|
16
|
+
export declare function loadMergedAuths(): Promise<{
|
|
17
|
+
auths: AuthRecord;
|
|
18
|
+
codexDiagnostics: string[];
|
|
19
|
+
}>;
|
|
20
|
+
export {};
|
|
21
|
+
//# sourceMappingURL=loader.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"loader.d.ts","sourceRoot":"","sources":["../../../src/usage/auth/loader.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,CAAC,MAAM,KAAK,CAAA;AAGnB,QAAA,MAAM,eAAe;;;;;;;iBAOL,CAAA;AAIhB,MAAM,MAAM,SAAS,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,eAAe,CAAC,CAAA;AACvD,MAAM,MAAM,UAAU,GAAG,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,CAAA;AAElD,wBAAsB,eAAe,IAAI,OAAO,CAAC;IAC/C,KAAK,EAAE,UAAU,CAAA;IACjB,gBAAgB,EAAE,MAAM,EAAE,CAAA;CAC3B,CAAC,CAYD"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Utility for loading and merging authentication records from multiple system paths.
|
|
3
|
+
* Handles platform-specific path resolution and specific provider auth transformations.
|
|
4
|
+
*/
|
|
5
|
+
import z from "zod";
|
|
6
|
+
import { getPossibleAuthPaths } from "../../utils";
|
|
7
|
+
const authEntrySchema = z.object({
|
|
8
|
+
type: z.string().optional(),
|
|
9
|
+
access: z.string().optional(),
|
|
10
|
+
refresh: z.string().optional(),
|
|
11
|
+
enterpriseUrl: z.string().optional(),
|
|
12
|
+
accountId: z.string().optional(),
|
|
13
|
+
key: z.string().optional(),
|
|
14
|
+
}).passthrough();
|
|
15
|
+
const authRecordSchema = z.record(z.string(), authEntrySchema);
|
|
16
|
+
export async function loadMergedAuths() {
|
|
17
|
+
const possiblePaths = getPossibleAuthPaths();
|
|
18
|
+
const mergedAuth = {};
|
|
19
|
+
const codexDiagnostics = [`Auth paths checked: ${possiblePaths.join(", ")}`];
|
|
20
|
+
const orderedPaths = [...possiblePaths].reverse();
|
|
21
|
+
for (const authPath of orderedPaths) {
|
|
22
|
+
const diagnostics = await processAuthPath(authPath, mergedAuth);
|
|
23
|
+
codexDiagnostics.push(...diagnostics);
|
|
24
|
+
}
|
|
25
|
+
return { auths: mergedAuth, codexDiagnostics };
|
|
26
|
+
}
|
|
27
|
+
async function processAuthPath(authPath, mergedAuth) {
|
|
28
|
+
const diagnostics = [];
|
|
29
|
+
try {
|
|
30
|
+
const file = Bun.file(authPath);
|
|
31
|
+
if (!(await file.exists()))
|
|
32
|
+
return [`Missing auth file: ${authPath}`];
|
|
33
|
+
const data = await file.json();
|
|
34
|
+
if (typeof data !== "object" || data === null)
|
|
35
|
+
return [`Auth file is not a JSON object: ${authPath}`];
|
|
36
|
+
if (authPath.includes(".codex")) {
|
|
37
|
+
const parsed = parseCodexAuth(data, authPath);
|
|
38
|
+
if (parsed.auth)
|
|
39
|
+
Object.assign(mergedAuth, parsed.auth);
|
|
40
|
+
return parsed.diagnostics;
|
|
41
|
+
}
|
|
42
|
+
const parsed = authRecordSchema.safeParse(data);
|
|
43
|
+
if (!parsed.success)
|
|
44
|
+
return [`Auth file failed schema validation: ${authPath}`];
|
|
45
|
+
Object.assign(mergedAuth, parsed.data);
|
|
46
|
+
diagnostics.push(`Loaded auth from ${authPath}`);
|
|
47
|
+
}
|
|
48
|
+
catch (e) {
|
|
49
|
+
diagnostics.push(`Failed to read auth file ${authPath}: ${e.message}`);
|
|
50
|
+
}
|
|
51
|
+
return diagnostics;
|
|
52
|
+
}
|
|
53
|
+
function parseCodexAuth(data, path) {
|
|
54
|
+
const tokens = data.tokens;
|
|
55
|
+
if (!tokens?.access_token)
|
|
56
|
+
return { auth: null, diagnostics: [`Invalid Codex auth in ${path}`] };
|
|
57
|
+
return {
|
|
58
|
+
auth: {
|
|
59
|
+
openai: {
|
|
60
|
+
type: "oauth",
|
|
61
|
+
access: tokens.access_token,
|
|
62
|
+
accountId: tokens.account_id,
|
|
63
|
+
refresh: tokens.refresh_token,
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
diagnostics: [`Codex CLI auth loaded from ${path}`],
|
|
67
|
+
};
|
|
68
|
+
}
|
package/dist/usage/fetch.d.ts
CHANGED
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
2
|
+
* Orchestrates the fetching of usage snapshots from multiple providers.
|
|
3
|
+
* Manages provider filtering, concurrency, and fallback for missing data.
|
|
4
4
|
*/
|
|
5
5
|
import type { UsageSnapshot } from "../types";
|
|
6
|
-
import type { AuthRecord } from "./registry";
|
|
7
|
-
export declare const providerAliases: Record<string, string>;
|
|
8
|
-
export declare function resolveProviderFilter(filter?: string): string | undefined;
|
|
9
6
|
export declare function fetchUsageSnapshots(filter?: string): Promise<UsageSnapshot[]>;
|
|
10
|
-
export declare function
|
|
7
|
+
export declare function resolveProviderFilter(filter?: string): string | undefined;
|
|
8
|
+
export declare function loadAuths(): Promise<import("./auth/loader").AuthRecord>;
|
|
11
9
|
//# sourceMappingURL=fetch.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fetch.d.ts","sourceRoot":"","sources":["../../src/usage/fetch.ts"],"names":[],"mappings":"AAAA;;;GAGG;
|
|
1
|
+
{"version":3,"file":"fetch.d.ts","sourceRoot":"","sources":["../../src/usage/fetch.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AAQ7C,wBAAsB,mBAAmB,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,EAAE,CAAC,CAoCnF;AAWD,wBAAgB,qBAAqB,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAEzE;AA6BD,wBAAsB,SAAS,gDAG9B"}
|
package/dist/usage/fetch.js
CHANGED
|
@@ -1,162 +1,83 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
2
|
+
* Orchestrates the fetching of usage snapshots from multiple providers.
|
|
3
|
+
* Manages provider filtering, concurrency, and fallback for missing data.
|
|
4
4
|
*/
|
|
5
|
-
import z from "zod";
|
|
6
5
|
import { providers } from "../providers";
|
|
7
6
|
import { loadUsageConfig } from "./config";
|
|
8
|
-
import {
|
|
7
|
+
import { loadMergedAuths } from "./auth/loader";
|
|
9
8
|
import { resolveProviderAuths } from "./registry";
|
|
10
|
-
const
|
|
11
|
-
.object({
|
|
12
|
-
type: z.string().optional(),
|
|
13
|
-
access: z.string().optional(),
|
|
14
|
-
refresh: z.string().optional(),
|
|
15
|
-
enterpriseUrl: z.string().optional(),
|
|
16
|
-
accountId: z.string().optional(),
|
|
17
|
-
})
|
|
18
|
-
.passthrough();
|
|
19
|
-
const authRecordSchema = z.record(z.string(), authEntrySchema);
|
|
20
|
-
export const providerAliases = {
|
|
21
|
-
codex: "codex",
|
|
22
|
-
openai: "codex",
|
|
23
|
-
gpt: "codex",
|
|
24
|
-
proxy: "proxy",
|
|
25
|
-
agy: "proxy",
|
|
26
|
-
antigravity: "proxy",
|
|
27
|
-
gemini: "proxy",
|
|
28
|
-
copilot: "copilot",
|
|
29
|
-
gh: "copilot",
|
|
30
|
-
github: "copilot",
|
|
31
|
-
};
|
|
32
|
-
export function resolveProviderFilter(filter) {
|
|
33
|
-
if (!filter)
|
|
34
|
-
return undefined;
|
|
35
|
-
const normalized = filter.toLowerCase().trim();
|
|
36
|
-
return providerAliases[normalized];
|
|
37
|
-
}
|
|
9
|
+
const CORE_PROVIDERS = ["codex", "proxy", "copilot"];
|
|
38
10
|
export async function fetchUsageSnapshots(filter) {
|
|
39
|
-
const
|
|
40
|
-
const
|
|
41
|
-
const
|
|
42
|
-
const
|
|
11
|
+
const target = resolveFilter(filter);
|
|
12
|
+
const config = await loadUsageConfig().catch(() => null);
|
|
13
|
+
const toggles = config?.providers ?? {};
|
|
14
|
+
const isEnabled = (id) => {
|
|
43
15
|
if (id === "codex")
|
|
44
|
-
return
|
|
45
|
-
|
|
46
|
-
return providerToggles.proxy !== false;
|
|
47
|
-
if (id === "copilot")
|
|
48
|
-
return providerToggles.copilot !== false;
|
|
49
|
-
return true;
|
|
16
|
+
return toggles.openai !== false;
|
|
17
|
+
return toggles[id] !== false;
|
|
50
18
|
};
|
|
51
|
-
const auths = await
|
|
19
|
+
const { auths, codexDiagnostics } = await loadMergedAuths();
|
|
52
20
|
const entries = resolveProviderAuths(auths, null);
|
|
53
21
|
const snapshots = [];
|
|
54
|
-
const
|
|
55
|
-
const fetchedProviders = new Set();
|
|
22
|
+
const fetched = new Set();
|
|
56
23
|
const fetches = entries
|
|
57
|
-
.filter(
|
|
58
|
-
.
|
|
59
|
-
.
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
try {
|
|
64
|
-
const snapshot = await provider.fetchUsage(entry.auth);
|
|
65
|
-
if (snapshot) {
|
|
66
|
-
snapshots.push(snapshot);
|
|
67
|
-
fetchedProviders.add(entry.providerID);
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
catch {
|
|
24
|
+
.filter(e => (!target || e.providerID === target) && isEnabled(e.providerID))
|
|
25
|
+
.map(async (e) => {
|
|
26
|
+
const snap = await providers[e.providerID]?.fetchUsage?.(e.auth).catch(() => null);
|
|
27
|
+
if (snap) {
|
|
28
|
+
snapshots.push(snap);
|
|
29
|
+
fetched.add(e.providerID);
|
|
71
30
|
}
|
|
72
31
|
});
|
|
73
|
-
|
|
74
|
-
for (const id of
|
|
75
|
-
if ((!
|
|
32
|
+
// Handle special/default fetches
|
|
33
|
+
for (const id of ["proxy", "copilot"]) {
|
|
34
|
+
if ((!target || target === id) && isEnabled(id) && !fetched.has(id)) {
|
|
76
35
|
const provider = providers[id];
|
|
77
36
|
if (provider?.fetchUsage) {
|
|
78
|
-
fetches.push(provider
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
snapshots.push(snapshot);
|
|
83
|
-
fetchedProviders.add(id);
|
|
37
|
+
fetches.push(provider.fetchUsage(undefined).then(s => {
|
|
38
|
+
if (s) {
|
|
39
|
+
snapshots.push(s);
|
|
40
|
+
fetched.add(id);
|
|
84
41
|
}
|
|
85
|
-
})
|
|
86
|
-
.catch(() => { }));
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
await Promise.race([Promise.all(fetches), timeout(5000)]);
|
|
91
|
-
for (const id of coreProviders) {
|
|
92
|
-
if (isProviderEnabled(id) && !fetchedProviders.has(id)) {
|
|
93
|
-
if (!targetProvider || targetProvider === id) {
|
|
94
|
-
snapshots.push({
|
|
95
|
-
timestamp: Date.now(),
|
|
96
|
-
provider: id,
|
|
97
|
-
planType: null,
|
|
98
|
-
primary: null,
|
|
99
|
-
secondary: null,
|
|
100
|
-
codeReview: null,
|
|
101
|
-
credits: null,
|
|
102
|
-
updatedAt: Date.now(),
|
|
103
|
-
isMissing: true,
|
|
104
|
-
});
|
|
42
|
+
}).catch(() => { }));
|
|
105
43
|
}
|
|
106
44
|
}
|
|
107
45
|
}
|
|
108
|
-
|
|
46
|
+
await Promise.race([Promise.all(fetches), new Promise(r => setTimeout(r, 5000))]);
|
|
47
|
+
return appendMissingStates(snapshots, fetched, isEnabled, target, codexDiagnostics);
|
|
109
48
|
}
|
|
110
|
-
|
|
111
|
-
const
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
49
|
+
function resolveFilter(f) {
|
|
50
|
+
const aliases = {
|
|
51
|
+
codex: "codex", openai: "codex", gpt: "codex",
|
|
52
|
+
proxy: "proxy", agy: "proxy", gemini: "proxy",
|
|
53
|
+
copilot: "copilot", github: "copilot"
|
|
54
|
+
};
|
|
55
|
+
return f ? aliases[f.toLowerCase().trim()] : undefined;
|
|
56
|
+
}
|
|
57
|
+
export function resolveProviderFilter(filter) {
|
|
58
|
+
return resolveFilter(filter);
|
|
59
|
+
}
|
|
60
|
+
function appendMissingStates(snaps, fetched, isEnabled, target, diagnostics) {
|
|
61
|
+
for (const id of CORE_PROVIDERS) {
|
|
62
|
+
if (isEnabled(id) && !fetched.has(id) && (!target || target === id)) {
|
|
63
|
+
snaps.push({
|
|
64
|
+
timestamp: Date.now(),
|
|
65
|
+
provider: id,
|
|
66
|
+
planType: null,
|
|
67
|
+
primary: null,
|
|
68
|
+
secondary: null,
|
|
69
|
+
codeReview: null,
|
|
70
|
+
credits: null,
|
|
71
|
+
updatedAt: Date.now(),
|
|
72
|
+
isMissing: true,
|
|
73
|
+
missingReason: id === "codex" ? "Auth resolution failed" : undefined,
|
|
74
|
+
missingDetails: id === "codex" ? diagnostics : undefined
|
|
75
|
+
});
|
|
135
76
|
}
|
|
136
77
|
}
|
|
137
|
-
return
|
|
78
|
+
return snaps;
|
|
138
79
|
}
|
|
139
|
-
function
|
|
140
|
-
const
|
|
141
|
-
|
|
142
|
-
access_token: z.string(),
|
|
143
|
-
account_id: z.string().optional(),
|
|
144
|
-
refresh_token: z.string().optional(),
|
|
145
|
-
}),
|
|
146
|
-
});
|
|
147
|
-
const parsed = codexAuthSchema.safeParse(data);
|
|
148
|
-
if (!parsed.success)
|
|
149
|
-
return null;
|
|
150
|
-
const { access_token, account_id, refresh_token } = parsed.data.tokens;
|
|
151
|
-
return {
|
|
152
|
-
openai: {
|
|
153
|
-
type: "oauth",
|
|
154
|
-
access: access_token,
|
|
155
|
-
accountId: account_id,
|
|
156
|
-
refresh: refresh_token,
|
|
157
|
-
},
|
|
158
|
-
};
|
|
159
|
-
}
|
|
160
|
-
function timeout(ms) {
|
|
161
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
80
|
+
export async function loadAuths() {
|
|
81
|
+
const { auths } = await loadMergedAuths();
|
|
82
|
+
return auths;
|
|
162
83
|
}
|
package/dist/usage/registry.d.ts
CHANGED
|
@@ -2,17 +2,22 @@
|
|
|
2
2
|
* Resolves provider auth entries for usage snapshots.
|
|
3
3
|
*/
|
|
4
4
|
import type { CodexAuth } from "../providers/codex";
|
|
5
|
+
import type { CopilotAuthData } from "../providers/copilot/types";
|
|
5
6
|
export type AuthEntry = {
|
|
6
7
|
type?: string;
|
|
7
8
|
access?: string;
|
|
8
9
|
refresh?: string;
|
|
9
10
|
enterpriseUrl?: string;
|
|
10
11
|
accountId?: string;
|
|
12
|
+
key?: string;
|
|
11
13
|
};
|
|
12
14
|
export type AuthRecord = Record<string, AuthEntry>;
|
|
13
15
|
type ProviderAuthEntry = {
|
|
14
16
|
providerID: "codex";
|
|
15
17
|
auth: CodexAuth;
|
|
18
|
+
} | {
|
|
19
|
+
providerID: "copilot";
|
|
20
|
+
auth: CopilotAuthData;
|
|
16
21
|
};
|
|
17
22
|
export declare function resolveProviderAuths(auths: AuthRecord, usageToken: string | null): ProviderAuthEntry[];
|
|
18
23
|
export {};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/usage/registry.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAA;
|
|
1
|
+
{"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/usage/registry.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAA;AACnD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAA;AAEjE,MAAM,MAAM,SAAS,GAAG;IACtB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,GAAG,CAAC,EAAE,MAAM,CAAA;CACb,CAAA;AAED,MAAM,MAAM,UAAU,GAAG,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,CAAA;AAElD,KAAK,iBAAiB,GAClB;IAAE,UAAU,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE,SAAS,CAAA;CAAE,GACxC;IAAE,UAAU,EAAE,SAAS,CAAC;IAAC,IAAI,EAAE,eAAe,CAAA;CAAE,CAAA;AA8BpD,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI,GAAG,iBAAiB,EAAE,CActG"}
|
package/dist/usage/registry.js
CHANGED
|
@@ -7,10 +7,19 @@ const providerDescriptors = [
|
|
|
7
7
|
authKeys: ["codex", "openai"],
|
|
8
8
|
requiresOAuth: true,
|
|
9
9
|
buildAuth: (entry) => ({
|
|
10
|
-
access: entry.access,
|
|
10
|
+
access: entry.access || entry.key,
|
|
11
11
|
accountId: entry.accountId,
|
|
12
12
|
}),
|
|
13
13
|
},
|
|
14
|
+
{
|
|
15
|
+
id: "copilot",
|
|
16
|
+
authKeys: ["copilot", "github-copilot"],
|
|
17
|
+
requiresOAuth: true,
|
|
18
|
+
buildAuth: (entry) => ({
|
|
19
|
+
access: entry.access,
|
|
20
|
+
refresh: entry.refresh,
|
|
21
|
+
}),
|
|
22
|
+
},
|
|
14
23
|
];
|
|
15
24
|
export function resolveProviderAuths(auths, usageToken) {
|
|
16
25
|
const entries = [];
|
|
@@ -21,7 +30,7 @@ export function resolveProviderAuths(auths, usageToken) {
|
|
|
21
30
|
const auth = auths[matched];
|
|
22
31
|
if (!auth)
|
|
23
32
|
continue;
|
|
24
|
-
if (descriptor.requiresOAuth && auth.type && auth.type !== "oauth")
|
|
33
|
+
if (descriptor.requiresOAuth && auth.type && auth.type !== "oauth" && auth.type !== "token")
|
|
25
34
|
continue;
|
|
26
35
|
const built = descriptor.buildAuth(auth, usageToken);
|
|
27
36
|
entries.push({ providerID: descriptor.id, auth: built });
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"paths.d.ts","sourceRoot":"","sources":["../../src/utils/paths.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAMH,wBAAgB,cAAc,IAAI,MAAM,
|
|
1
|
+
{"version":3,"file":"paths.d.ts","sourceRoot":"","sources":["../../src/utils/paths.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAMH,wBAAgB,cAAc,IAAI,MAAM,CAUvC;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,IAAI,MAAM,EAAE,CAe/C;AAED;;GAEG;AACH,wBAAgB,eAAe,IAAI,MAAM,CAYxC"}
|
package/dist/utils/paths.js
CHANGED
|
@@ -8,17 +8,11 @@ import { existsSync } from "fs";
|
|
|
8
8
|
export function getAppDataPath() {
|
|
9
9
|
const plat = platform();
|
|
10
10
|
const home = homedir();
|
|
11
|
-
if (plat === "darwin") {
|
|
12
|
-
return join(home, "Library", "Application Support", "opencode");
|
|
13
|
-
}
|
|
14
11
|
if (plat === "win32") {
|
|
15
12
|
return join(process.env.APPDATA || join(home, "AppData", "Roaming"), "opencode");
|
|
16
13
|
}
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
return join(xdgData, "opencode");
|
|
20
|
-
}
|
|
21
|
-
return join(home, ".local", "share", "opencode");
|
|
14
|
+
const dataHome = process.env.XDG_DATA_HOME || join(home, ".local", "share");
|
|
15
|
+
return join(dataHome, "opencode");
|
|
22
16
|
}
|
|
23
17
|
/**
|
|
24
18
|
* Returns all possible auth file paths for the current platform.
|
|
@@ -28,24 +22,13 @@ export function getPossibleAuthPaths() {
|
|
|
28
22
|
const plat = platform();
|
|
29
23
|
const home = homedir();
|
|
30
24
|
const pathSet = new Set();
|
|
31
|
-
if (plat === "
|
|
32
|
-
// OpenCode on macOS uses Linux-style paths
|
|
33
|
-
pathSet.add(join(home, ".local", "share", "opencode", "auth.json"));
|
|
34
|
-
// Standard macOS location (fallback)
|
|
35
|
-
pathSet.add(join(home, "Library", "Application Support", "opencode", "auth.json"));
|
|
36
|
-
// Codex-specific auth (fallback)
|
|
37
|
-
pathSet.add(join(home, ".codex", "auth.json"));
|
|
38
|
-
}
|
|
39
|
-
else if (plat === "win32") {
|
|
25
|
+
if (plat === "win32") {
|
|
40
26
|
pathSet.add(join(process.env.APPDATA || join(home, "AppData", "Roaming"), "opencode", "auth.json"));
|
|
41
27
|
}
|
|
42
28
|
else {
|
|
43
29
|
// Linux/other
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
pathSet.add(join(xdgData, "opencode", "auth.json"));
|
|
47
|
-
}
|
|
48
|
-
pathSet.add(join(home, ".local", "share", "opencode", "auth.json"));
|
|
30
|
+
const dataHome = process.env.XDG_DATA_HOME || join(home, ".local", "share");
|
|
31
|
+
pathSet.add(join(dataHome, "opencode", "auth.json"));
|
|
49
32
|
pathSet.add(join(home, ".codex", "auth.json"));
|
|
50
33
|
}
|
|
51
34
|
return Array.from(pathSet);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@howaboua/opencode-usage-plugin",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4-dev.1",
|
|
4
4
|
"description": "opencode plugin for tracking AI provider usage, rate limits, and quotas",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -31,7 +31,9 @@
|
|
|
31
31
|
"scripts": {
|
|
32
32
|
"clean": "rm -rf dist",
|
|
33
33
|
"build": "npm run clean && tsc",
|
|
34
|
-
"prepublishOnly": "npm run build"
|
|
34
|
+
"prepublishOnly": "npm run build",
|
|
35
|
+
"publish:dev": "npm publish --tag dev",
|
|
36
|
+
"release:dev": "npm version prerelease --preid dev && npm publish --tag dev"
|
|
35
37
|
},
|
|
36
38
|
"publishConfig": {
|
|
37
39
|
"access": "public"
|