@slkiser/opencode-quota 1.2.0 → 1.4.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/README.md +171 -74
- package/dist/lib/api-key-resolver.d.ts +83 -0
- package/dist/lib/api-key-resolver.d.ts.map +1 -0
- package/dist/lib/api-key-resolver.js +113 -0
- package/dist/lib/api-key-resolver.js.map +1 -0
- package/dist/lib/chutes-config.d.ts +8 -7
- package/dist/lib/chutes-config.d.ts.map +1 -1
- package/dist/lib/chutes-config.js +32 -128
- package/dist/lib/chutes-config.js.map +1 -1
- package/dist/lib/chutes.d.ts.map +1 -1
- package/dist/lib/chutes.js +1 -17
- package/dist/lib/chutes.js.map +1 -1
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js +1 -48
- package/dist/lib/config.js.map +1 -1
- package/dist/lib/copilot.d.ts.map +1 -1
- package/dist/lib/copilot.js +1 -24
- package/dist/lib/copilot.js.map +1 -1
- package/dist/lib/env-template.d.ts +25 -0
- package/dist/lib/env-template.d.ts.map +1 -0
- package/dist/lib/env-template.js +32 -0
- package/dist/lib/env-template.js.map +1 -0
- package/dist/lib/firmware-config.d.ts +1 -7
- package/dist/lib/firmware-config.d.ts.map +1 -1
- package/dist/lib/firmware-config.js +26 -148
- package/dist/lib/firmware-config.js.map +1 -1
- package/dist/lib/firmware.d.ts +22 -0
- package/dist/lib/firmware.d.ts.map +1 -1
- package/dist/lib/firmware.js +96 -23
- package/dist/lib/firmware.js.map +1 -1
- package/dist/lib/format-utils.d.ts +56 -0
- package/dist/lib/format-utils.d.ts.map +1 -0
- package/dist/lib/format-utils.js +101 -0
- package/dist/lib/format-utils.js.map +1 -0
- package/dist/lib/format.d.ts.map +1 -1
- package/dist/lib/format.js +2 -67
- package/dist/lib/format.js.map +1 -1
- package/dist/lib/google.d.ts.map +1 -1
- package/dist/lib/google.js +2 -24
- package/dist/lib/google.js.map +1 -1
- package/dist/lib/http.d.ts +14 -0
- package/dist/lib/http.d.ts.map +1 -0
- package/dist/lib/http.js +34 -0
- package/dist/lib/http.js.map +1 -0
- package/dist/lib/jsonc.d.ts +25 -0
- package/dist/lib/jsonc.d.ts.map +1 -0
- package/dist/lib/jsonc.js +73 -0
- package/dist/lib/jsonc.js.map +1 -0
- package/dist/lib/markdown-table.d.ts +7 -0
- package/dist/lib/markdown-table.d.ts.map +1 -1
- package/dist/lib/markdown-table.js +76 -9
- package/dist/lib/markdown-table.js.map +1 -1
- package/dist/lib/openai.d.ts.map +1 -1
- package/dist/lib/openai.js +1 -17
- package/dist/lib/openai.js.map +1 -1
- package/dist/lib/opencode-storage.d.ts +27 -0
- package/dist/lib/opencode-storage.d.ts.map +1 -1
- package/dist/lib/opencode-storage.js +67 -0
- package/dist/lib/opencode-storage.js.map +1 -1
- package/dist/lib/quota-command-format.d.ts.map +1 -1
- package/dist/lib/quota-command-format.js +5 -50
- package/dist/lib/quota-command-format.js.map +1 -1
- package/dist/lib/quota-stats-format.d.ts.map +1 -1
- package/dist/lib/quota-stats-format.js +9 -3
- package/dist/lib/quota-stats-format.js.map +1 -1
- package/dist/lib/quota-stats.d.ts +1 -0
- package/dist/lib/quota-stats.d.ts.map +1 -1
- package/dist/lib/quota-stats.js +15 -5
- package/dist/lib/quota-stats.js.map +1 -1
- package/dist/lib/quota-status.d.ts +7 -0
- package/dist/lib/quota-status.d.ts.map +1 -1
- package/dist/lib/quota-status.js +10 -0
- package/dist/lib/quota-status.js.map +1 -1
- package/dist/lib/toast-format-grouped.d.ts.map +1 -1
- package/dist/lib/toast-format-grouped.js +1 -66
- package/dist/lib/toast-format-grouped.js.map +1 -1
- package/dist/plugin.d.ts.map +1 -1
- package/dist/plugin.js +116 -162
- package/dist/plugin.js.map +1 -1
- package/dist/providers/firmware.d.ts.map +1 -1
- package/dist/providers/firmware.js +40 -7
- package/dist/providers/firmware.js.map +1 -1
- package/package.json +1 -1
|
@@ -7,79 +7,9 @@
|
|
|
7
7
|
* - Supports {env:VAR_NAME} syntax for environment variable references
|
|
8
8
|
* 3. auth.json: firmware.key (legacy/fallback)
|
|
9
9
|
*/
|
|
10
|
-
import {
|
|
11
|
-
import { readFile } from "fs/promises";
|
|
12
|
-
import { homedir } from "os";
|
|
13
|
-
import { join } from "path";
|
|
10
|
+
import { resolveEnvTemplate } from "./env-template.js";
|
|
14
11
|
import { readAuthFile } from "./opencode-auth.js";
|
|
15
|
-
|
|
16
|
-
* Strip JSONC comments (// and /* ... */) from a string.
|
|
17
|
-
* This is a simple implementation that handles common cases.
|
|
18
|
-
*/
|
|
19
|
-
function stripJsonComments(content) {
|
|
20
|
-
let result = "";
|
|
21
|
-
let i = 0;
|
|
22
|
-
let inString = false;
|
|
23
|
-
let stringChar = "";
|
|
24
|
-
while (i < content.length) {
|
|
25
|
-
const char = content[i];
|
|
26
|
-
const nextChar = content[i + 1];
|
|
27
|
-
// Handle string boundaries
|
|
28
|
-
if ((char === '"' || char === "'") && (i === 0 || content[i - 1] !== "\\")) {
|
|
29
|
-
if (!inString) {
|
|
30
|
-
inString = true;
|
|
31
|
-
stringChar = char;
|
|
32
|
-
}
|
|
33
|
-
else if (char === stringChar) {
|
|
34
|
-
inString = false;
|
|
35
|
-
}
|
|
36
|
-
result += char;
|
|
37
|
-
i++;
|
|
38
|
-
continue;
|
|
39
|
-
}
|
|
40
|
-
// Skip comments only when not in a string
|
|
41
|
-
if (!inString) {
|
|
42
|
-
// Single-line comment
|
|
43
|
-
if (char === "/" && nextChar === "/") {
|
|
44
|
-
// Skip until end of line
|
|
45
|
-
while (i < content.length && content[i] !== "\n") {
|
|
46
|
-
i++;
|
|
47
|
-
}
|
|
48
|
-
continue;
|
|
49
|
-
}
|
|
50
|
-
// Multi-line comment
|
|
51
|
-
if (char === "/" && nextChar === "*") {
|
|
52
|
-
i += 2;
|
|
53
|
-
while (i < content.length - 1 && !(content[i] === "*" && content[i + 1] === "/")) {
|
|
54
|
-
i++;
|
|
55
|
-
}
|
|
56
|
-
i += 2; // Skip closing */
|
|
57
|
-
continue;
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
result += char;
|
|
61
|
-
i++;
|
|
62
|
-
}
|
|
63
|
-
return result;
|
|
64
|
-
}
|
|
65
|
-
/**
|
|
66
|
-
* Parse JSON or JSONC content
|
|
67
|
-
*/
|
|
68
|
-
function parseJsonOrJsonc(content, isJsonc) {
|
|
69
|
-
const toParse = isJsonc ? stripJsonComments(content) : content;
|
|
70
|
-
return JSON.parse(toParse);
|
|
71
|
-
}
|
|
72
|
-
/**
|
|
73
|
-
* Resolve {env:VAR_NAME} syntax in a string value
|
|
74
|
-
*/
|
|
75
|
-
function resolveEnvTemplate(value) {
|
|
76
|
-
const match = value.match(/^\{env:([^}]+)\}$/);
|
|
77
|
-
if (!match)
|
|
78
|
-
return value;
|
|
79
|
-
const envVar = match[1];
|
|
80
|
-
const envValue = process.env[envVar];
|
|
81
|
-
return envValue && envValue.trim().length > 0 ? envValue.trim() : null;
|
|
82
|
-
}
|
|
12
|
+
import { resolveApiKey, getApiKeyDiagnostics, } from "./api-key-resolver.js";
|
|
83
13
|
/**
|
|
84
14
|
* Extract firmware API key from opencode config object
|
|
85
15
|
*
|
|
@@ -105,35 +35,19 @@ function extractFirmwareKeyFromConfig(config) {
|
|
|
105
35
|
return resolveEnvTemplate(apiKey.trim());
|
|
106
36
|
}
|
|
107
37
|
/**
|
|
108
|
-
*
|
|
109
|
-
*/
|
|
110
|
-
export function getOpencodeConfigCandidatePaths() {
|
|
111
|
-
const cwd = process.cwd();
|
|
112
|
-
const configBaseDir = process.env.XDG_CONFIG_HOME || join(homedir(), ".config");
|
|
113
|
-
// Order: local overrides first, then global fallback
|
|
114
|
-
// Check both .json and .jsonc variants
|
|
115
|
-
return [
|
|
116
|
-
{ path: join(cwd, "opencode.jsonc"), isJsonc: true },
|
|
117
|
-
{ path: join(cwd, "opencode.json"), isJsonc: false },
|
|
118
|
-
{ path: join(configBaseDir, "opencode", "opencode.jsonc"), isJsonc: true },
|
|
119
|
-
{ path: join(configBaseDir, "opencode", "opencode.json"), isJsonc: false },
|
|
120
|
-
];
|
|
121
|
-
}
|
|
122
|
-
/**
|
|
123
|
-
* Read and parse opencode config file
|
|
38
|
+
* Extract firmware API key from auth.json
|
|
124
39
|
*/
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
if (!existsSync(filePath))
|
|
128
|
-
return null;
|
|
129
|
-
const content = await readFile(filePath, "utf-8");
|
|
130
|
-
const config = parseJsonOrJsonc(content, isJsonc);
|
|
131
|
-
return { config, path: filePath, isJsonc };
|
|
132
|
-
}
|
|
133
|
-
catch {
|
|
40
|
+
function extractFirmwareKeyFromAuth(auth) {
|
|
41
|
+
if (!auth || typeof auth !== "object")
|
|
134
42
|
return null;
|
|
43
|
+
const fw = auth.firmware;
|
|
44
|
+
if (fw && fw.type === "api" && fw.key && fw.key.trim().length > 0) {
|
|
45
|
+
return fw.key.trim();
|
|
135
46
|
}
|
|
47
|
+
return null;
|
|
136
48
|
}
|
|
49
|
+
// Re-export for consumers that need path info
|
|
50
|
+
export { getOpencodeConfigCandidatePaths } from "./api-key-resolver.js";
|
|
137
51
|
/**
|
|
138
52
|
* Resolve Firmware API key from all available sources.
|
|
139
53
|
*
|
|
@@ -145,36 +59,17 @@ async function readOpencodeConfig(filePath, isJsonc) {
|
|
|
145
59
|
* @returns API key and source, or null if not found
|
|
146
60
|
*/
|
|
147
61
|
export async function resolveFirmwareApiKey() {
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
for (const candidate of candidates) {
|
|
160
|
-
const result = await readOpencodeConfig(candidate.path, candidate.isJsonc);
|
|
161
|
-
if (!result)
|
|
162
|
-
continue;
|
|
163
|
-
const key = extractFirmwareKeyFromConfig(result.config);
|
|
164
|
-
if (key) {
|
|
165
|
-
return {
|
|
166
|
-
key,
|
|
167
|
-
source: result.isJsonc ? "opencode.jsonc" : "opencode.json",
|
|
168
|
-
};
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
// 3. Fallback to auth.json
|
|
172
|
-
const auth = await readAuthFile();
|
|
173
|
-
const fw = auth?.firmware;
|
|
174
|
-
if (fw && fw.type === "api" && fw.key && fw.key.trim().length > 0) {
|
|
175
|
-
return { key: fw.key.trim(), source: "auth.json" };
|
|
176
|
-
}
|
|
177
|
-
return null;
|
|
62
|
+
return resolveApiKey({
|
|
63
|
+
envVars: [
|
|
64
|
+
{ name: "FIRMWARE_AI_API_KEY", source: "env:FIRMWARE_AI_API_KEY" },
|
|
65
|
+
{ name: "FIRMWARE_API_KEY", source: "env:FIRMWARE_API_KEY" },
|
|
66
|
+
],
|
|
67
|
+
extractFromConfig: extractFirmwareKeyFromConfig,
|
|
68
|
+
configJsonSource: "opencode.json",
|
|
69
|
+
configJsoncSource: "opencode.jsonc",
|
|
70
|
+
extractFromAuth: extractFirmwareKeyFromAuth,
|
|
71
|
+
authSource: "auth.json",
|
|
72
|
+
}, readAuthFile);
|
|
178
73
|
}
|
|
179
74
|
/**
|
|
180
75
|
* Check if a Firmware API key is configured in any source
|
|
@@ -187,26 +82,9 @@ export async function hasFirmwareApiKey() {
|
|
|
187
82
|
* Get diagnostic info about firmware API key configuration
|
|
188
83
|
*/
|
|
189
84
|
export async function getFirmwareKeyDiagnostics() {
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
}
|
|
195
|
-
if (process.env.FIRMWARE_API_KEY !== undefined) {
|
|
196
|
-
checkedPaths.push("env:FIRMWARE_API_KEY");
|
|
197
|
-
}
|
|
198
|
-
// Track config files checked
|
|
199
|
-
const candidates = getOpencodeConfigCandidatePaths();
|
|
200
|
-
for (const candidate of candidates) {
|
|
201
|
-
if (existsSync(candidate.path)) {
|
|
202
|
-
checkedPaths.push(candidate.path);
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
const result = await resolveFirmwareApiKey();
|
|
206
|
-
return {
|
|
207
|
-
configured: result !== null,
|
|
208
|
-
source: result?.source ?? null,
|
|
209
|
-
checkedPaths,
|
|
210
|
-
};
|
|
85
|
+
return getApiKeyDiagnostics({
|
|
86
|
+
envVarNames: ["FIRMWARE_AI_API_KEY", "FIRMWARE_API_KEY"],
|
|
87
|
+
resolve: resolveFirmwareApiKey,
|
|
88
|
+
});
|
|
211
89
|
}
|
|
212
90
|
//# sourceMappingURL=firmware-config.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"firmware-config.js","sourceRoot":"","sources":["../../src/lib/firmware-config.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"firmware-config.js","sourceRoot":"","sources":["../../src/lib/firmware-config.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AACvD,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EACL,aAAa,EACb,oBAAoB,GAGrB,MAAM,uBAAuB,CAAC;AAgB/B;;;;GAIG;AACH,SAAS,4BAA4B,CAAC,MAAe;IACnD,IAAI,CAAC,MAAM,IAAI,OAAO,MAAM,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC;IAEvD,MAAM,IAAI,GAAG,MAAiC,CAAC;IAC/C,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC;IAC/B,IAAI,CAAC,QAAQ,IAAI,OAAO,QAAQ,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC;IAE3D,MAAM,QAAQ,GAAI,QAAoC,CAAC,QAAQ,CAAC;IAChE,IAAI,CAAC,QAAQ,IAAI,OAAO,QAAQ,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC;IAE3D,MAAM,OAAO,GAAI,QAAoC,CAAC,OAAO,CAAC;IAC9D,IAAI,CAAC,OAAO,IAAI,OAAO,OAAO,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC;IAEzD,MAAM,MAAM,GAAI,OAAmC,CAAC,MAAM,CAAC;IAC3D,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,CAAC,IAAI,EAAE,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IAE1E,gCAAgC;IAChC,OAAO,kBAAkB,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC;AAC3C,CAAC;AAED;;GAEG;AACH,SAAS,0BAA0B,CAAC,IAAa;IAC/C,IAAI,CAAC,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC;IACnD,MAAM,EAAE,GAAI,IAAgC,CAAC,QAEhC,CAAC;IACd,IAAI,EAAE,IAAI,EAAE,CAAC,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC,GAAG,IAAI,EAAE,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAClE,OAAO,EAAE,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC;IACvB,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,8CAA8C;AAC9C,OAAO,EAAE,+BAA+B,EAAE,MAAM,uBAAuB,CAAC;AAExE;;;;;;;;;GASG;AACH,MAAM,CAAC,KAAK,UAAU,qBAAqB;IACzC,OAAO,aAAa,CAClB;QACE,OAAO,EAAE;YACP,EAAE,IAAI,EAAE,qBAAqB,EAAE,MAAM,EAAE,yBAAyB,EAAE;YAClE,EAAE,IAAI,EAAE,kBAAkB,EAAE,MAAM,EAAE,sBAAsB,EAAE;SAC7D;QACD,iBAAiB,EAAE,4BAA4B;QAC/C,gBAAgB,EAAE,eAAe;QACjC,iBAAiB,EAAE,gBAAgB;QACnC,eAAe,EAAE,0BAA0B;QAC3C,UAAU,EAAE,WAAW;KACxB,EACD,YAAY,CACb,CAAC;AACJ,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB;IACrC,MAAM,MAAM,GAAG,MAAM,qBAAqB,EAAE,CAAC;IAC7C,OAAO,MAAM,KAAK,IAAI,CAAC;AACzB,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,yBAAyB;IAK7C,OAAO,oBAAoB,CAAoB;QAC7C,WAAW,EAAE,CAAC,qBAAqB,EAAE,kBAAkB,CAAC;QACxD,OAAO,EAAE,qBAAqB;KAC/B,CAAC,CAAC;AACL,CAAC"}
|
package/dist/lib/firmware.d.ts
CHANGED
|
@@ -5,12 +5,34 @@
|
|
|
5
5
|
* and queries: https://app.firmware.ai/api/v1/quota
|
|
6
6
|
*/
|
|
7
7
|
import type { QuotaError } from "./types.js";
|
|
8
|
+
/** Single window quota info */
|
|
9
|
+
export interface FirmwareWindowQuota {
|
|
10
|
+
percentRemaining: number;
|
|
11
|
+
resetTimeIso?: string;
|
|
12
|
+
}
|
|
8
13
|
export type FirmwareResult = {
|
|
9
14
|
success: true;
|
|
15
|
+
/** Back-compat: worst of 5h window and weekly (for classic toast) */
|
|
10
16
|
percentRemaining: number;
|
|
11
17
|
resetTimeIso?: string;
|
|
18
|
+
/** Individual windows for grouped display */
|
|
19
|
+
windows: {
|
|
20
|
+
window: FirmwareWindowQuota;
|
|
21
|
+
weekly: FirmwareWindowQuota;
|
|
22
|
+
};
|
|
23
|
+
/** Manual resets available this week (0-2) */
|
|
24
|
+
windowResetsRemaining: number;
|
|
25
|
+
} | QuotaError | null;
|
|
26
|
+
export type FirmwareResetWindowResult = {
|
|
27
|
+
success: true;
|
|
28
|
+
windowResetsRemaining?: number;
|
|
12
29
|
} | QuotaError | null;
|
|
13
30
|
export declare function hasFirmwareApiKeyConfigured(): Promise<boolean>;
|
|
14
31
|
export { getFirmwareKeyDiagnostics, type FirmwareKeySource } from "./firmware-config.js";
|
|
15
32
|
export declare function queryFirmwareQuota(): Promise<FirmwareResult>;
|
|
33
|
+
/**
|
|
34
|
+
* Manually reset the 5-hour spending window.
|
|
35
|
+
* Consumes one of the 2 weekly resets available.
|
|
36
|
+
*/
|
|
37
|
+
export declare function resetFirmwareQuotaWindow(): Promise<FirmwareResetWindowResult>;
|
|
16
38
|
//# sourceMappingURL=firmware.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"firmware.d.ts","sourceRoot":"","sources":["../../src/lib/firmware.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"firmware.d.ts","sourceRoot":"","sources":["../../src/lib/firmware.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAkB7C,+BAA+B;AAC/B,MAAM,WAAW,mBAAmB;IAClC,gBAAgB,EAAE,MAAM,CAAC;IACzB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AA6BD,MAAM,MAAM,cAAc,GACtB;IACE,OAAO,EAAE,IAAI,CAAC;IACd,qEAAqE;IACrE,gBAAgB,EAAE,MAAM,CAAC;IACzB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,6CAA6C;IAC7C,OAAO,EAAE;QACP,MAAM,EAAE,mBAAmB,CAAC;QAC5B,MAAM,EAAE,mBAAmB,CAAC;KAC7B,CAAC;IACF,8CAA8C;IAC9C,qBAAqB,EAAE,MAAM,CAAC;CAC/B,GACD,UAAU,GACV,IAAI,CAAC;AAET,MAAM,MAAM,yBAAyB,GACjC;IAAE,OAAO,EAAE,IAAI,CAAC;IAAC,qBAAqB,CAAC,EAAE,MAAM,CAAA;CAAE,GACjD,UAAU,GACV,IAAI,CAAC;AAKT,wBAAsB,2BAA2B,IAAI,OAAO,CAAC,OAAO,CAAC,CAEpE;AAED,OAAO,EAAE,yBAAyB,EAAE,KAAK,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAEzF,wBAAsB,kBAAkB,IAAI,OAAO,CAAC,cAAc,CAAC,CA2ElE;AAED;;;GAGG;AACH,wBAAsB,wBAAwB,IAAI,OAAO,CAAC,yBAAyB,CAAC,CAsDnF"}
|
package/dist/lib/firmware.js
CHANGED
|
@@ -4,28 +4,21 @@
|
|
|
4
4
|
* Resolves API key from multiple sources (env vars, opencode.json, auth.json)
|
|
5
5
|
* and queries: https://app.firmware.ai/api/v1/quota
|
|
6
6
|
*/
|
|
7
|
-
import {
|
|
7
|
+
import { fetchWithTimeout } from "./http.js";
|
|
8
8
|
import { resolveFirmwareApiKey, hasFirmwareApiKey, } from "./firmware-config.js";
|
|
9
9
|
function clampPercent(n) {
|
|
10
10
|
if (!Number.isFinite(n))
|
|
11
11
|
return 0;
|
|
12
12
|
return Math.max(0, Math.min(100, Math.round(n)));
|
|
13
13
|
}
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
throw new Error(`Request timeout after ${Math.round(REQUEST_TIMEOUT_MS / 1000)}s`);
|
|
23
|
-
}
|
|
24
|
-
throw err;
|
|
25
|
-
}
|
|
26
|
-
finally {
|
|
27
|
-
clearTimeout(timeoutId);
|
|
28
|
-
}
|
|
14
|
+
/**
|
|
15
|
+
* Strip control characters (ANSI escapes, etc.) from error text
|
|
16
|
+
* to prevent terminal injection when displayed in TUI output.
|
|
17
|
+
*/
|
|
18
|
+
function sanitizeErrorText(text) {
|
|
19
|
+
// Remove ANSI escape sequences and other control characters (except newline/tab)
|
|
20
|
+
// eslint-disable-next-line no-control-regex
|
|
21
|
+
return text.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]|\x1B\[[0-9;]*[A-Za-z]/g, "");
|
|
29
22
|
}
|
|
30
23
|
async function readFirmwareAuth() {
|
|
31
24
|
const result = await resolveFirmwareApiKey();
|
|
@@ -34,6 +27,7 @@ async function readFirmwareAuth() {
|
|
|
34
27
|
return { type: "api", key: result.key, source: result.source };
|
|
35
28
|
}
|
|
36
29
|
const FIRMWARE_QUOTA_URL = "https://app.firmware.ai/api/v1/quota";
|
|
30
|
+
const FIRMWARE_RESET_WINDOW_URL = "https://app.firmware.ai/api/v1/quota/reset-window";
|
|
37
31
|
export async function hasFirmwareApiKeyConfigured() {
|
|
38
32
|
return await hasFirmwareApiKey();
|
|
39
33
|
}
|
|
@@ -54,18 +48,97 @@ export async function queryFirmwareQuota() {
|
|
|
54
48
|
const text = await resp.text();
|
|
55
49
|
return {
|
|
56
50
|
success: false,
|
|
57
|
-
error: `Firmware API error ${resp.status}: ${text.slice(0, 120)}`,
|
|
51
|
+
error: `Firmware API error ${resp.status}: ${sanitizeErrorText(text.slice(0, 120))}`,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
const data = (await resp.json());
|
|
55
|
+
// Parse 5-hour window
|
|
56
|
+
const windowUsed = typeof data.windowUsed === "number" ? data.windowUsed : NaN;
|
|
57
|
+
const windowPercentRemaining = clampPercent(100 - windowUsed * 100);
|
|
58
|
+
const windowResetIso = typeof data.windowReset === "string" && data.windowReset.length > 0
|
|
59
|
+
? data.windowReset
|
|
60
|
+
: undefined;
|
|
61
|
+
// Parse weekly
|
|
62
|
+
const weeklyUsed = typeof data.weeklyUsed === "number" ? data.weeklyUsed : NaN;
|
|
63
|
+
const weeklyPercentRemaining = clampPercent(100 - weeklyUsed * 100);
|
|
64
|
+
const weeklyResetIso = typeof data.weeklyReset === "string" && data.weeklyReset.length > 0
|
|
65
|
+
? data.weeklyReset
|
|
66
|
+
: undefined;
|
|
67
|
+
// Parse resets remaining (use trunc to avoid surprising rounding)
|
|
68
|
+
const windowResetsRemaining = typeof data.windowResetsRemaining === "number"
|
|
69
|
+
? Math.max(0, Math.min(2, Math.trunc(data.windowResetsRemaining)))
|
|
70
|
+
: 0;
|
|
71
|
+
// Back-compat: use worst window for classic display
|
|
72
|
+
const windowQuota = {
|
|
73
|
+
percentRemaining: windowPercentRemaining,
|
|
74
|
+
resetTimeIso: windowResetIso,
|
|
75
|
+
};
|
|
76
|
+
const weeklyQuota = {
|
|
77
|
+
percentRemaining: weeklyPercentRemaining,
|
|
78
|
+
resetTimeIso: weeklyResetIso,
|
|
79
|
+
};
|
|
80
|
+
// Worst window for classic mode
|
|
81
|
+
const isWindowWorse = windowPercentRemaining <= weeklyPercentRemaining;
|
|
82
|
+
const worst = isWindowWorse ? windowQuota : weeklyQuota;
|
|
83
|
+
return {
|
|
84
|
+
success: true,
|
|
85
|
+
percentRemaining: worst.percentRemaining,
|
|
86
|
+
resetTimeIso: worst.resetTimeIso,
|
|
87
|
+
windows: {
|
|
88
|
+
window: windowQuota,
|
|
89
|
+
weekly: weeklyQuota,
|
|
90
|
+
},
|
|
91
|
+
windowResetsRemaining,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
catch (err) {
|
|
95
|
+
return {
|
|
96
|
+
success: false,
|
|
97
|
+
error: err instanceof Error ? err.message : String(err),
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Manually reset the 5-hour spending window.
|
|
103
|
+
* Consumes one of the 2 weekly resets available.
|
|
104
|
+
*/
|
|
105
|
+
export async function resetFirmwareQuotaWindow() {
|
|
106
|
+
const auth = await readFirmwareAuth();
|
|
107
|
+
if (!auth)
|
|
108
|
+
return null;
|
|
109
|
+
try {
|
|
110
|
+
const resp = await fetchWithTimeout(FIRMWARE_RESET_WINDOW_URL, {
|
|
111
|
+
method: "POST",
|
|
112
|
+
headers: {
|
|
113
|
+
Authorization: `Bearer ${auth.key}`,
|
|
114
|
+
"User-Agent": "OpenCode-Quota-Toast/1.0",
|
|
115
|
+
},
|
|
116
|
+
});
|
|
117
|
+
if (!resp.ok) {
|
|
118
|
+
const text = await resp.text();
|
|
119
|
+
// Try to parse error JSON for better message
|
|
120
|
+
try {
|
|
121
|
+
const errorData = JSON.parse(text);
|
|
122
|
+
if (errorData.message) {
|
|
123
|
+
return {
|
|
124
|
+
success: false,
|
|
125
|
+
error: sanitizeErrorText(errorData.message),
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
catch {
|
|
130
|
+
// Not JSON, use raw text
|
|
131
|
+
}
|
|
132
|
+
return {
|
|
133
|
+
success: false,
|
|
134
|
+
error: `Firmware API error ${resp.status}: ${sanitizeErrorText(text.slice(0, 120))}`,
|
|
58
135
|
};
|
|
59
136
|
}
|
|
137
|
+
// Parse success response
|
|
60
138
|
const data = (await resp.json());
|
|
61
|
-
// Firmware returns used ratio [0..1]. We convert to remaining %.
|
|
62
|
-
const used = typeof data.used === "number" ? data.used : NaN;
|
|
63
|
-
const percentRemaining = clampPercent(100 - used * 100);
|
|
64
|
-
const resetIso = typeof data.reset === "string" && data.reset.length > 0 ? data.reset : undefined;
|
|
65
139
|
return {
|
|
66
140
|
success: true,
|
|
67
|
-
|
|
68
|
-
resetTimeIso: resetIso,
|
|
141
|
+
windowResetsRemaining: typeof data.windowResetsRemaining === "number" ? data.windowResetsRemaining : undefined,
|
|
69
142
|
};
|
|
70
143
|
}
|
|
71
144
|
catch (err) {
|
package/dist/lib/firmware.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"firmware.js","sourceRoot":"","sources":["../../src/lib/firmware.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"firmware.js","sourceRoot":"","sources":["../../src/lib/firmware.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared formatting utilities for quota display.
|
|
3
|
+
*
|
|
4
|
+
* These primitives are used by:
|
|
5
|
+
* - format.ts (classic toast)
|
|
6
|
+
* - toast-format-grouped.ts (grouped toast)
|
|
7
|
+
* - quota-command-format.ts (/quota command)
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Clamp a number to an integer within [min, max].
|
|
11
|
+
*/
|
|
12
|
+
export declare function clampInt(n: number, min: number, max: number): number;
|
|
13
|
+
/**
|
|
14
|
+
* Pad string to width, truncating if too long, adding spaces on right if too short.
|
|
15
|
+
*/
|
|
16
|
+
export declare function padRight(str: string, width: number): string;
|
|
17
|
+
/**
|
|
18
|
+
* Pad string to width, truncating from start if too long, adding spaces on left if too short.
|
|
19
|
+
*/
|
|
20
|
+
export declare function padLeft(str: string, width: number): string;
|
|
21
|
+
/**
|
|
22
|
+
* Render a progress bar of filled/empty blocks.
|
|
23
|
+
*/
|
|
24
|
+
export declare function bar(percentRemaining: number, width: number): string;
|
|
25
|
+
/**
|
|
26
|
+
* Format a token count with K/M suffix for compactness.
|
|
27
|
+
*
|
|
28
|
+
* Examples:
|
|
29
|
+
* - 500 -> "500"
|
|
30
|
+
* - 1500 -> "1.5K"
|
|
31
|
+
* - 15000 -> "15K"
|
|
32
|
+
* - 1500000 -> "1.5M"
|
|
33
|
+
*/
|
|
34
|
+
export declare function formatTokenCount(count: number): string;
|
|
35
|
+
/**
|
|
36
|
+
* Shorten model name for compact display.
|
|
37
|
+
*
|
|
38
|
+
* Removes common prefixes/suffixes before truncating with ellipsis.
|
|
39
|
+
*/
|
|
40
|
+
export declare function shortenModelName(name: string, maxLen: number): string;
|
|
41
|
+
export interface FormatResetCountdownOptions {
|
|
42
|
+
/**
|
|
43
|
+
* String to return when ISO timestamp is missing/undefined.
|
|
44
|
+
* - Classic toast uses "-"
|
|
45
|
+
* - Grouped toast uses ""
|
|
46
|
+
*/
|
|
47
|
+
missing?: string;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Format a reset countdown for toast display.
|
|
51
|
+
*
|
|
52
|
+
* Returns human-readable time like "2d 5h" or "3h 45m".
|
|
53
|
+
* When reset time is in the past or invalid, returns "reset".
|
|
54
|
+
*/
|
|
55
|
+
export declare function formatResetCountdown(iso?: string, opts?: FormatResetCountdownOptions): string;
|
|
56
|
+
//# sourceMappingURL=format-utils.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"format-utils.d.ts","sourceRoot":"","sources":["../../src/lib/format-utils.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH;;GAEG;AACH,wBAAgB,QAAQ,CAAC,CAAC,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAEpE;AAED;;GAEG;AACH,wBAAgB,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAG3D;AAED;;GAEG;AACH,wBAAgB,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAG1D;AAED;;GAEG;AACH,wBAAgB,GAAG,CAAC,gBAAgB,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAKnE;AAED;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAWtD;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAUrE;AAED,MAAM,WAAW,2BAA2B;IAC1C;;;;OAIG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,GAAG,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,2BAA2B,GAAG,MAAM,CAc7F"}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared formatting utilities for quota display.
|
|
3
|
+
*
|
|
4
|
+
* These primitives are used by:
|
|
5
|
+
* - format.ts (classic toast)
|
|
6
|
+
* - toast-format-grouped.ts (grouped toast)
|
|
7
|
+
* - quota-command-format.ts (/quota command)
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Clamp a number to an integer within [min, max].
|
|
11
|
+
*/
|
|
12
|
+
export function clampInt(n, min, max) {
|
|
13
|
+
return Math.max(min, Math.min(max, Math.trunc(n)));
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Pad string to width, truncating if too long, adding spaces on right if too short.
|
|
17
|
+
*/
|
|
18
|
+
export function padRight(str, width) {
|
|
19
|
+
if (str.length >= width)
|
|
20
|
+
return str.slice(0, width);
|
|
21
|
+
return str + " ".repeat(width - str.length);
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Pad string to width, truncating from start if too long, adding spaces on left if too short.
|
|
25
|
+
*/
|
|
26
|
+
export function padLeft(str, width) {
|
|
27
|
+
if (str.length >= width)
|
|
28
|
+
return str.slice(str.length - width);
|
|
29
|
+
return " ".repeat(width - str.length) + str;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Render a progress bar of filled/empty blocks.
|
|
33
|
+
*/
|
|
34
|
+
export function bar(percentRemaining, width) {
|
|
35
|
+
const p = clampInt(percentRemaining, 0, 100);
|
|
36
|
+
const filled = Math.round((p / 100) * width);
|
|
37
|
+
const empty = width - filled;
|
|
38
|
+
return "█".repeat(filled) + "░".repeat(empty);
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Format a token count with K/M suffix for compactness.
|
|
42
|
+
*
|
|
43
|
+
* Examples:
|
|
44
|
+
* - 500 -> "500"
|
|
45
|
+
* - 1500 -> "1.5K"
|
|
46
|
+
* - 15000 -> "15K"
|
|
47
|
+
* - 1500000 -> "1.5M"
|
|
48
|
+
*/
|
|
49
|
+
export function formatTokenCount(count) {
|
|
50
|
+
if (count >= 1_000_000) {
|
|
51
|
+
return `${(count / 1_000_000).toFixed(1)}M`;
|
|
52
|
+
}
|
|
53
|
+
if (count >= 10_000) {
|
|
54
|
+
return `${(count / 1_000).toFixed(0)}K`;
|
|
55
|
+
}
|
|
56
|
+
if (count >= 1_000) {
|
|
57
|
+
return `${(count / 1_000).toFixed(1)}K`;
|
|
58
|
+
}
|
|
59
|
+
return String(count);
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Shorten model name for compact display.
|
|
63
|
+
*
|
|
64
|
+
* Removes common prefixes/suffixes before truncating with ellipsis.
|
|
65
|
+
*/
|
|
66
|
+
export function shortenModelName(name, maxLen) {
|
|
67
|
+
if (name.length <= maxLen)
|
|
68
|
+
return name;
|
|
69
|
+
// Remove common prefixes/suffixes
|
|
70
|
+
let s = name
|
|
71
|
+
.replace(/^antigravity-/i, "")
|
|
72
|
+
.replace(/-thinking$/i, "")
|
|
73
|
+
.replace(/-preview$/i, "");
|
|
74
|
+
if (s.length <= maxLen)
|
|
75
|
+
return s;
|
|
76
|
+
// Truncate with ellipsis
|
|
77
|
+
return s.slice(0, maxLen - 1) + "\u2026";
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Format a reset countdown for toast display.
|
|
81
|
+
*
|
|
82
|
+
* Returns human-readable time like "2d 5h" or "3h 45m".
|
|
83
|
+
* When reset time is in the past or invalid, returns "reset".
|
|
84
|
+
*/
|
|
85
|
+
export function formatResetCountdown(iso, opts) {
|
|
86
|
+
if (!iso)
|
|
87
|
+
return opts?.missing ?? "";
|
|
88
|
+
const resetDate = new Date(iso);
|
|
89
|
+
const now = new Date();
|
|
90
|
+
const diffMs = resetDate.getTime() - now.getTime();
|
|
91
|
+
if (!Number.isFinite(diffMs) || diffMs <= 0)
|
|
92
|
+
return "reset";
|
|
93
|
+
const diffMinutes = Math.floor(diffMs / 60000);
|
|
94
|
+
const days = Math.floor(diffMinutes / 1440);
|
|
95
|
+
const hours = Math.floor((diffMinutes % 1440) / 60);
|
|
96
|
+
const minutes = diffMinutes % 60;
|
|
97
|
+
if (days > 0)
|
|
98
|
+
return `${days}d ${hours}h`;
|
|
99
|
+
return `${hours}h ${minutes}m`;
|
|
100
|
+
}
|
|
101
|
+
//# sourceMappingURL=format-utils.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"format-utils.js","sourceRoot":"","sources":["../../src/lib/format-utils.ts"],"names":[],"mappings":"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"}
|
package/dist/lib/format.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"format.d.ts","sourceRoot":"","sources":["../../src/lib/format.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,eAAe,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"format.d.ts","sourceRoot":"","sources":["../../src/lib/format.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,eAAe,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAYxF,wBAAgB,eAAe,CAAC,MAAM,EAAE;IACtC,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE;QACP,QAAQ,EAAE,MAAM,CAAC;QACjB,QAAQ,EAAE,MAAM,CAAC;QACjB,MAAM,EAAE,MAAM,CAAC;KAChB,CAAC;IACF,OAAO,CAAC,EAAE,eAAe,EAAE,CAAC;IAC5B,MAAM,CAAC,EAAE,eAAe,EAAE,CAAC;IAC3B,KAAK,CAAC,EAAE,SAAS,GAAG,SAAS,CAAC;IAC9B,aAAa,CAAC,EAAE,iBAAiB,CAAC;CACnC,GAAG,MAAM,CAyFT"}
|