@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
|
@@ -4,77 +4,12 @@
|
|
|
4
4
|
* Renders quota entries grouped by provider/account with compact bars.
|
|
5
5
|
* Designed to feel like a status dashboard while still respecting OpenCode toast width.
|
|
6
6
|
*/
|
|
7
|
-
|
|
8
|
-
return Math.max(min, Math.min(max, Math.trunc(n)));
|
|
9
|
-
}
|
|
10
|
-
function padRight(str, width) {
|
|
11
|
-
if (str.length >= width)
|
|
12
|
-
return str.slice(0, width);
|
|
13
|
-
return str + " ".repeat(width - str.length);
|
|
14
|
-
}
|
|
15
|
-
function padLeft(str, width) {
|
|
16
|
-
if (str.length >= width)
|
|
17
|
-
return str.slice(str.length - width);
|
|
18
|
-
return " ".repeat(width - str.length) + str;
|
|
19
|
-
}
|
|
20
|
-
function bar(percentRemaining, width) {
|
|
21
|
-
const p = clampInt(percentRemaining, 0, 100);
|
|
22
|
-
const filled = Math.round((p / 100) * width);
|
|
23
|
-
const empty = width - filled;
|
|
24
|
-
return "█".repeat(filled) + "░".repeat(empty);
|
|
25
|
-
}
|
|
26
|
-
function formatResetCountdown(iso) {
|
|
27
|
-
if (!iso)
|
|
28
|
-
return "";
|
|
29
|
-
const resetDate = new Date(iso);
|
|
30
|
-
const now = new Date();
|
|
31
|
-
const diffMs = resetDate.getTime() - now.getTime();
|
|
32
|
-
if (!Number.isFinite(diffMs) || diffMs <= 0)
|
|
33
|
-
return "reset";
|
|
34
|
-
const diffMinutes = Math.floor(diffMs / 60000);
|
|
35
|
-
const days = Math.floor(diffMinutes / 1440);
|
|
36
|
-
const hours = Math.floor((diffMinutes % 1440) / 60);
|
|
37
|
-
const minutes = diffMinutes % 60;
|
|
38
|
-
if (days > 0)
|
|
39
|
-
return `${days}d ${hours}h`;
|
|
40
|
-
return `${hours}h ${minutes}m`;
|
|
41
|
-
}
|
|
7
|
+
import { bar, clampInt, formatResetCountdown, formatTokenCount, padLeft, padRight, shortenModelName, } from "./format-utils.js";
|
|
42
8
|
function splitGroupName(name) {
|
|
43
9
|
// Heuristic: "Label (group)" -> group is label, label is empty.
|
|
44
10
|
// Prefer explicit group/label metadata when available.
|
|
45
11
|
return { group: name, label: "" };
|
|
46
12
|
}
|
|
47
|
-
/**
|
|
48
|
-
* Format a token count with K/M suffix for compactness
|
|
49
|
-
*/
|
|
50
|
-
function formatTokenCount(count) {
|
|
51
|
-
if (count >= 1_000_000) {
|
|
52
|
-
return `${(count / 1_000_000).toFixed(1)}M`;
|
|
53
|
-
}
|
|
54
|
-
if (count >= 10_000) {
|
|
55
|
-
return `${(count / 1_000).toFixed(0)}K`;
|
|
56
|
-
}
|
|
57
|
-
if (count >= 1_000) {
|
|
58
|
-
return `${(count / 1_000).toFixed(1)}K`;
|
|
59
|
-
}
|
|
60
|
-
return String(count);
|
|
61
|
-
}
|
|
62
|
-
/**
|
|
63
|
-
* Shorten model name for compact display
|
|
64
|
-
*/
|
|
65
|
-
function shortenModelName(name, maxLen) {
|
|
66
|
-
if (name.length <= maxLen)
|
|
67
|
-
return name;
|
|
68
|
-
// Remove common prefixes/suffixes
|
|
69
|
-
let s = name
|
|
70
|
-
.replace(/^antigravity-/i, "")
|
|
71
|
-
.replace(/-thinking$/i, "")
|
|
72
|
-
.replace(/-preview$/i, "");
|
|
73
|
-
if (s.length <= maxLen)
|
|
74
|
-
return s;
|
|
75
|
-
// Truncate with ellipsis
|
|
76
|
-
return s.slice(0, maxLen - 1) + "\u2026";
|
|
77
|
-
}
|
|
78
13
|
export function formatQuotaRowsGrouped(params) {
|
|
79
14
|
const layout = params.layout ?? { maxWidth: 50, narrowAt: 42, tinyAt: 32 };
|
|
80
15
|
const maxWidth = layout.maxWidth;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"toast-format-grouped.js","sourceRoot":"","sources":["../../src/lib/toast-format-grouped.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;
|
|
1
|
+
{"version":3,"file":"toast-format-grouped.js","sourceRoot":"","sources":["../../src/lib/toast-format-grouped.ts"],"names":[],"mappings":"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"}
|
package/dist/plugin.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAuOlD;;GAEG;AACH,eAAO,MAAM,gBAAgB,EAAE,MA48B9B,CAAC"}
|
package/dist/plugin.js
CHANGED
|
@@ -12,17 +12,16 @@ import { formatQuotaRows } from "./lib/format.js";
|
|
|
12
12
|
import { formatQuotaCommand } from "./lib/quota-command-format.js";
|
|
13
13
|
import { getProviders } from "./providers/registry.js";
|
|
14
14
|
import { tool } from "@opencode-ai/plugin";
|
|
15
|
-
import { aggregateUsage, getSessionTokenSummary } from "./lib/quota-stats.js";
|
|
15
|
+
import { aggregateUsage, getSessionTokenSummary, SessionNotFoundError } from "./lib/quota-stats.js";
|
|
16
16
|
import { formatQuotaStatsReport } from "./lib/quota-stats-format.js";
|
|
17
17
|
import { buildQuotaStatusReport } from "./lib/quota-status.js";
|
|
18
18
|
import { refreshGoogleTokensForAllAccounts } from "./lib/google.js";
|
|
19
|
+
import { resetFirmwareQuotaWindow } from "./lib/firmware.js";
|
|
19
20
|
/** All token report command specifications */
|
|
20
21
|
const TOKEN_REPORT_COMMANDS = [
|
|
21
22
|
{
|
|
22
23
|
id: "tokens_today",
|
|
23
|
-
legacyId: "quota_today",
|
|
24
24
|
template: "/tokens_today",
|
|
25
|
-
legacyTemplate: "/quota_today",
|
|
26
25
|
description: "Token + official API cost summary for today (calendar day, local timezone).",
|
|
27
26
|
title: "Tokens used (Today) (/tokens_today)",
|
|
28
27
|
metadataTitle: "Tokens used (Today)",
|
|
@@ -30,9 +29,7 @@ const TOKEN_REPORT_COMMANDS = [
|
|
|
30
29
|
},
|
|
31
30
|
{
|
|
32
31
|
id: "tokens_daily",
|
|
33
|
-
legacyId: "quota_daily",
|
|
34
32
|
template: "/tokens_daily",
|
|
35
|
-
legacyTemplate: "/quota_daily",
|
|
36
33
|
description: "Token + official API cost summary for the last 24 hours (rolling).",
|
|
37
34
|
title: "Tokens used (Last 24 Hours) (/tokens_daily)",
|
|
38
35
|
metadataTitle: "Tokens used (Last 24 Hours)",
|
|
@@ -41,9 +38,7 @@ const TOKEN_REPORT_COMMANDS = [
|
|
|
41
38
|
},
|
|
42
39
|
{
|
|
43
40
|
id: "tokens_weekly",
|
|
44
|
-
legacyId: "quota_weekly",
|
|
45
41
|
template: "/tokens_weekly",
|
|
46
|
-
legacyTemplate: "/quota_weekly",
|
|
47
42
|
description: "Token + official API cost summary for the last 7 days (rolling).",
|
|
48
43
|
title: "Tokens used (Last 7 Days) (/tokens_weekly)",
|
|
49
44
|
metadataTitle: "Tokens used (Last 7 Days)",
|
|
@@ -52,9 +47,7 @@ const TOKEN_REPORT_COMMANDS = [
|
|
|
52
47
|
},
|
|
53
48
|
{
|
|
54
49
|
id: "tokens_monthly",
|
|
55
|
-
legacyId: "quota_monthly",
|
|
56
50
|
template: "/tokens_monthly",
|
|
57
|
-
legacyTemplate: "/quota_monthly",
|
|
58
51
|
description: "Token + official API cost summary for the last 30 days (rolling).",
|
|
59
52
|
title: "Tokens used (Last 30 Days) (/tokens_monthly)",
|
|
60
53
|
metadataTitle: "Tokens used (Last 30 Days)",
|
|
@@ -63,9 +56,7 @@ const TOKEN_REPORT_COMMANDS = [
|
|
|
63
56
|
},
|
|
64
57
|
{
|
|
65
58
|
id: "tokens_all",
|
|
66
|
-
legacyId: "quota_all",
|
|
67
59
|
template: "/tokens_all",
|
|
68
|
-
legacyTemplate: "/quota_all",
|
|
69
60
|
description: "Token + official API cost summary for all locally saved OpenCode history.",
|
|
70
61
|
title: "Tokens used (All Time) (/tokens_all)",
|
|
71
62
|
metadataTitle: "Tokens used (All Time)",
|
|
@@ -75,9 +66,7 @@ const TOKEN_REPORT_COMMANDS = [
|
|
|
75
66
|
},
|
|
76
67
|
{
|
|
77
68
|
id: "tokens_session",
|
|
78
|
-
legacyId: "quota_session",
|
|
79
69
|
template: "/tokens_session",
|
|
80
|
-
legacyTemplate: "/quota_session",
|
|
81
70
|
description: "Token + official API cost summary for current session only.",
|
|
82
71
|
title: "Tokens used (Current Session) (/tokens_session)",
|
|
83
72
|
metadataTitle: "Tokens used (Current Session)",
|
|
@@ -85,9 +74,7 @@ const TOKEN_REPORT_COMMANDS = [
|
|
|
85
74
|
},
|
|
86
75
|
{
|
|
87
76
|
id: "tokens_between",
|
|
88
|
-
legacyId: "quota_between",
|
|
89
77
|
template: "/tokens_between",
|
|
90
|
-
legacyTemplate: "/quota_between",
|
|
91
78
|
description: "Token + cost report between two YYYY-MM-DD dates (local timezone, inclusive).",
|
|
92
79
|
titleForRange: (startYmd, endYmd) => {
|
|
93
80
|
const formatYmd = (ymd) => {
|
|
@@ -102,12 +89,11 @@ const TOKEN_REPORT_COMMANDS = [
|
|
|
102
89
|
kind: "between",
|
|
103
90
|
},
|
|
104
91
|
];
|
|
105
|
-
/** Build a lookup map from command ID
|
|
92
|
+
/** Build a lookup map from command ID to spec */
|
|
106
93
|
const TOKEN_REPORT_COMMANDS_BY_ID = (() => {
|
|
107
94
|
const map = new Map();
|
|
108
95
|
for (const spec of TOKEN_REPORT_COMMANDS) {
|
|
109
96
|
map.set(spec.id, spec);
|
|
110
|
-
map.set(spec.legacyId, spec);
|
|
111
97
|
}
|
|
112
98
|
return map;
|
|
113
99
|
})();
|
|
@@ -157,6 +143,8 @@ export const QuotaToastPlugin = async ({ client }) => {
|
|
|
157
143
|
let configLoaded = false;
|
|
158
144
|
let configInFlight = null;
|
|
159
145
|
let configMeta = createLoadConfigMeta();
|
|
146
|
+
// Track last session token error for /quota_status diagnostics
|
|
147
|
+
let lastSessionTokenError;
|
|
160
148
|
async function refreshConfig() {
|
|
161
149
|
if (configInFlight)
|
|
162
150
|
return configInFlight;
|
|
@@ -223,7 +211,7 @@ export const QuotaToastPlugin = async ({ client }) => {
|
|
|
223
211
|
return new Date(ymd.y, ymd.m - 1, ymd.d + 1).getTime();
|
|
224
212
|
}
|
|
225
213
|
/**
|
|
226
|
-
* Parse /
|
|
214
|
+
* Parse /tokens_between arguments. Supports:
|
|
227
215
|
* - Positional: "2026-01-01 2026-01-15"
|
|
228
216
|
* - JSON: {"starting_date":"2026-01-01","ending_date":"2026-01-15"}
|
|
229
217
|
*/
|
|
@@ -432,9 +420,25 @@ export const QuotaToastPlugin = async ({ client }) => {
|
|
|
432
420
|
totalOutput: summary.totalOutput,
|
|
433
421
|
};
|
|
434
422
|
}
|
|
423
|
+
// Clear any previous error on success
|
|
424
|
+
lastSessionTokenError = undefined;
|
|
435
425
|
}
|
|
436
|
-
catch {
|
|
437
|
-
//
|
|
426
|
+
catch (err) {
|
|
427
|
+
// Capture error for /quota_status diagnostics
|
|
428
|
+
if (err instanceof SessionNotFoundError) {
|
|
429
|
+
lastSessionTokenError = {
|
|
430
|
+
sessionID: err.sessionID,
|
|
431
|
+
error: err.message,
|
|
432
|
+
checkedPath: err.checkedPath,
|
|
433
|
+
};
|
|
434
|
+
}
|
|
435
|
+
else {
|
|
436
|
+
lastSessionTokenError = {
|
|
437
|
+
sessionID,
|
|
438
|
+
error: err instanceof Error ? err.message : String(err),
|
|
439
|
+
};
|
|
440
|
+
}
|
|
441
|
+
// Toast still displays without session tokens
|
|
438
442
|
}
|
|
439
443
|
}
|
|
440
444
|
if (entries.length > 0) {
|
|
@@ -562,9 +566,25 @@ export const QuotaToastPlugin = async ({ client }) => {
|
|
|
562
566
|
totalOutput: summary.totalOutput,
|
|
563
567
|
};
|
|
564
568
|
}
|
|
569
|
+
// Clear any previous error on success
|
|
570
|
+
lastSessionTokenError = undefined;
|
|
565
571
|
}
|
|
566
|
-
catch {
|
|
567
|
-
//
|
|
572
|
+
catch (err) {
|
|
573
|
+
// Capture error for /quota_status diagnostics
|
|
574
|
+
if (err instanceof SessionNotFoundError) {
|
|
575
|
+
lastSessionTokenError = {
|
|
576
|
+
sessionID: err.sessionID,
|
|
577
|
+
error: err.message,
|
|
578
|
+
checkedPath: err.checkedPath,
|
|
579
|
+
};
|
|
580
|
+
}
|
|
581
|
+
else {
|
|
582
|
+
lastSessionTokenError = {
|
|
583
|
+
sessionID,
|
|
584
|
+
error: err instanceof Error ? err.message : String(err),
|
|
585
|
+
};
|
|
586
|
+
}
|
|
587
|
+
// Command still returns without session tokens
|
|
568
588
|
}
|
|
569
589
|
}
|
|
570
590
|
return formatQuotaCommand({ entries, errors, sessionTokens });
|
|
@@ -633,6 +653,7 @@ export const QuotaToastPlugin = async ({ client }) => {
|
|
|
633
653
|
failures: refresh.failures,
|
|
634
654
|
}
|
|
635
655
|
: { attempted: false },
|
|
656
|
+
sessionTokenError: lastSessionTokenError,
|
|
636
657
|
});
|
|
637
658
|
}
|
|
638
659
|
// Return hook implementations
|
|
@@ -650,18 +671,16 @@ export const QuotaToastPlugin = async ({ client }) => {
|
|
|
650
671
|
template: "/quota_status",
|
|
651
672
|
description: "Diagnostics for toast + pricing + local storage (includes unknown pricing report).",
|
|
652
673
|
};
|
|
653
|
-
|
|
674
|
+
cfg.command["firmware_reset_window"] = {
|
|
675
|
+
template: "/firmware_reset_window",
|
|
676
|
+
description: "Manually reset your Firmware 5-hour spending window (consumes 1 of 2 weekly resets).",
|
|
677
|
+
};
|
|
678
|
+
// Register token report commands (/tokens_*)
|
|
654
679
|
for (const spec of TOKEN_REPORT_COMMANDS) {
|
|
655
|
-
// Primary command (/tokens_*)
|
|
656
680
|
cfg.command[spec.id] = {
|
|
657
681
|
template: spec.template,
|
|
658
682
|
description: spec.description,
|
|
659
683
|
};
|
|
660
|
-
// Legacy alias (/quota_*) for backwards compatibility
|
|
661
|
-
cfg.command[spec.legacyId] = {
|
|
662
|
-
template: spec.legacyTemplate,
|
|
663
|
-
description: `${spec.description} (Legacy alias for /${spec.id})`,
|
|
664
|
-
};
|
|
665
684
|
}
|
|
666
685
|
},
|
|
667
686
|
"command.execute.before": async (input) => {
|
|
@@ -701,7 +720,7 @@ export const QuotaToastPlugin = async ({ client }) => {
|
|
|
701
720
|
throw new Error("__QUOTA_COMMAND_HANDLED__");
|
|
702
721
|
}
|
|
703
722
|
const untilMs = Date.now();
|
|
704
|
-
// Handle token report commands
|
|
723
|
+
// Handle token report commands (/tokens_*)
|
|
705
724
|
if (isTokenReportCommand(cmd)) {
|
|
706
725
|
const spec = TOKEN_REPORT_COMMANDS_BY_ID.get(cmd);
|
|
707
726
|
if (spec.kind === "between") {
|
|
@@ -777,74 +796,51 @@ export const QuotaToastPlugin = async ({ client }) => {
|
|
|
777
796
|
await injectRawOutput(sessionID, out);
|
|
778
797
|
throw new Error("__QUOTA_COMMAND_HANDLED__");
|
|
779
798
|
}
|
|
799
|
+
// Handle /firmware_reset_window (reset 5-hour spending window)
|
|
800
|
+
if (cmd === "firmware_reset_window") {
|
|
801
|
+
const raw = input.arguments?.trim() || "";
|
|
802
|
+
// Check for confirmation: accept "confirm" as positional or {"confirm": true} as JSON
|
|
803
|
+
let confirmed = false;
|
|
804
|
+
if (raw.toLowerCase() === "confirm") {
|
|
805
|
+
confirmed = true;
|
|
806
|
+
}
|
|
807
|
+
else if (raw.startsWith("{")) {
|
|
808
|
+
try {
|
|
809
|
+
const parsed = JSON.parse(raw);
|
|
810
|
+
confirmed = parsed["confirm"] === true;
|
|
811
|
+
}
|
|
812
|
+
catch {
|
|
813
|
+
await injectRawOutput(sessionID, `Invalid JSON arguments for /firmware_reset_window\n\nTo proceed, run:\n/firmware_reset_window confirm`);
|
|
814
|
+
throw new Error("__QUOTA_COMMAND_HANDLED__");
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
// Require explicit confirmation (destructive action with limited weekly resets)
|
|
818
|
+
if (!confirmed) {
|
|
819
|
+
await injectRawOutput(sessionID, `⚠️ This will reset your Firmware 5-hour spending window.\nYou have a maximum of 2 resets per week.\n\nTo proceed, run:\n/firmware_reset_window confirm`);
|
|
820
|
+
throw new Error("__QUOTA_COMMAND_HANDLED__");
|
|
821
|
+
}
|
|
822
|
+
const result = await resetFirmwareQuotaWindow();
|
|
823
|
+
if (!result) {
|
|
824
|
+
await injectRawOutput(sessionID, "Firmware API key not configured. Cannot reset window.");
|
|
825
|
+
throw new Error("__QUOTA_COMMAND_HANDLED__");
|
|
826
|
+
}
|
|
827
|
+
if (!result.success) {
|
|
828
|
+
await injectRawOutput(sessionID, `Failed to reset Firmware window:\n${result.error}`);
|
|
829
|
+
throw new Error("__QUOTA_COMMAND_HANDLED__");
|
|
830
|
+
}
|
|
831
|
+
// Build success message
|
|
832
|
+
const resetsLeft = result.windowResetsRemaining;
|
|
833
|
+
const resetsMsg = resetsLeft !== undefined
|
|
834
|
+
? ` (${resetsLeft} reset${resetsLeft === 1 ? "" : "s"} remaining this week)`
|
|
835
|
+
: "";
|
|
836
|
+
// Fetch updated quota to show new status
|
|
837
|
+
const quotaMsg = await fetchQuotaCommandMessage("command:/firmware_reset_window", sessionID);
|
|
838
|
+
const successOutput = `✓ Firmware 5-hour window reset successful${resetsMsg}${quotaMsg ? `\n\n${quotaMsg}` : ""}`;
|
|
839
|
+
await injectRawOutput(sessionID, successOutput);
|
|
840
|
+
throw new Error("__QUOTA_COMMAND_HANDLED__");
|
|
841
|
+
}
|
|
780
842
|
},
|
|
781
843
|
tool: {
|
|
782
|
-
quota_daily: tool({
|
|
783
|
-
description: "Token + official API cost summary for the last 24 hours (rolling).",
|
|
784
|
-
args: {},
|
|
785
|
-
async execute(_args, context) {
|
|
786
|
-
const untilMs = Date.now();
|
|
787
|
-
const sinceMs = untilMs - 24 * 60 * 60 * 1000;
|
|
788
|
-
const out = await buildQuotaReport({
|
|
789
|
-
title: "Tokens used (Last 24 Hours) (/tokens_daily)",
|
|
790
|
-
sinceMs,
|
|
791
|
-
untilMs,
|
|
792
|
-
sessionID: context.sessionID,
|
|
793
|
-
});
|
|
794
|
-
context.metadata({ title: "Tokens used (Last 24 Hours)" });
|
|
795
|
-
await injectRawOutput(context.sessionID, out);
|
|
796
|
-
return ""; // Empty return - output already injected with noReply
|
|
797
|
-
},
|
|
798
|
-
}),
|
|
799
|
-
quota_weekly: tool({
|
|
800
|
-
description: "Token + official API cost summary for the last 7 days (rolling).",
|
|
801
|
-
args: {},
|
|
802
|
-
async execute(_args, context) {
|
|
803
|
-
const untilMs = Date.now();
|
|
804
|
-
const sinceMs = untilMs - 7 * 24 * 60 * 60 * 1000;
|
|
805
|
-
const out = await buildQuotaReport({
|
|
806
|
-
title: "Tokens used (Last 7 Days) (/tokens_weekly)",
|
|
807
|
-
sinceMs,
|
|
808
|
-
untilMs,
|
|
809
|
-
sessionID: context.sessionID,
|
|
810
|
-
});
|
|
811
|
-
context.metadata({ title: "Tokens used (Last 7 Days)" });
|
|
812
|
-
await injectRawOutput(context.sessionID, out);
|
|
813
|
-
return ""; // Empty return - output already injected with noReply
|
|
814
|
-
},
|
|
815
|
-
}),
|
|
816
|
-
quota_monthly: tool({
|
|
817
|
-
description: "Token + official API cost summary for the last 30 days (rolling).",
|
|
818
|
-
args: {},
|
|
819
|
-
async execute(_args, context) {
|
|
820
|
-
const untilMs = Date.now();
|
|
821
|
-
const sinceMs = untilMs - 30 * 24 * 60 * 60 * 1000;
|
|
822
|
-
const out = await buildQuotaReport({
|
|
823
|
-
title: "Tokens used (Last 30 Days) (/tokens_monthly)",
|
|
824
|
-
sinceMs,
|
|
825
|
-
untilMs,
|
|
826
|
-
sessionID: context.sessionID,
|
|
827
|
-
});
|
|
828
|
-
context.metadata({ title: "Tokens used (Last 30 Days)" });
|
|
829
|
-
await injectRawOutput(context.sessionID, out);
|
|
830
|
-
return ""; // Empty return - output already injected with noReply
|
|
831
|
-
},
|
|
832
|
-
}),
|
|
833
|
-
quota_all: tool({
|
|
834
|
-
description: "Token + official API cost summary for all locally saved OpenCode history.",
|
|
835
|
-
args: {},
|
|
836
|
-
async execute(_args, context) {
|
|
837
|
-
const out = await buildQuotaReport({
|
|
838
|
-
title: "Tokens used (All Time) (/tokens_all)",
|
|
839
|
-
sessionID: context.sessionID,
|
|
840
|
-
topModels: 12,
|
|
841
|
-
topSessions: 12,
|
|
842
|
-
});
|
|
843
|
-
context.metadata({ title: "Tokens used (All Time)" });
|
|
844
|
-
await injectRawOutput(context.sessionID, out);
|
|
845
|
-
return ""; // Empty return - output already injected with noReply
|
|
846
|
-
},
|
|
847
|
-
}),
|
|
848
844
|
quota_status: tool({
|
|
849
845
|
description: "Diagnostics for toast + pricing + local storage (includes unknown pricing report).",
|
|
850
846
|
args: {
|
|
@@ -874,81 +870,39 @@ export const QuotaToastPlugin = async ({ client }) => {
|
|
|
874
870
|
return ""; // Empty return - output already injected with noReply
|
|
875
871
|
},
|
|
876
872
|
}),
|
|
877
|
-
|
|
878
|
-
description: "
|
|
879
|
-
args: {},
|
|
880
|
-
async execute(_args, context) {
|
|
881
|
-
const now = new Date();
|
|
882
|
-
const startOfDay = new Date(now.getFullYear(), now.getMonth(), now.getDate());
|
|
883
|
-
const sinceMs = startOfDay.getTime();
|
|
884
|
-
const untilMs = now.getTime();
|
|
885
|
-
const out = await buildQuotaReport({
|
|
886
|
-
title: "Tokens used (Today) (/tokens_today)",
|
|
887
|
-
sinceMs,
|
|
888
|
-
untilMs,
|
|
889
|
-
sessionID: context.sessionID,
|
|
890
|
-
});
|
|
891
|
-
context.metadata({ title: "Tokens used (Today)" });
|
|
892
|
-
await injectRawOutput(context.sessionID, out);
|
|
893
|
-
return ""; // Empty return - output already injected with noReply
|
|
894
|
-
},
|
|
895
|
-
}),
|
|
896
|
-
quota_session: tool({
|
|
897
|
-
description: "Token + official API cost summary for current session only.",
|
|
898
|
-
args: {},
|
|
899
|
-
async execute(_args, context) {
|
|
900
|
-
const out = await buildQuotaReport({
|
|
901
|
-
title: "Tokens used (Current Session) (/tokens_session)",
|
|
902
|
-
sessionID: context.sessionID,
|
|
903
|
-
filterSessionID: context.sessionID,
|
|
904
|
-
sessionOnly: true,
|
|
905
|
-
});
|
|
906
|
-
context.metadata({ title: "Tokens used (Current Session)" });
|
|
907
|
-
await injectRawOutput(context.sessionID, out);
|
|
908
|
-
return ""; // Empty return - output already injected with noReply
|
|
909
|
-
},
|
|
910
|
-
}),
|
|
911
|
-
quota_between: tool({
|
|
912
|
-
description: "Token + official API cost summary between two YYYY-MM-DD dates (local timezone, inclusive).",
|
|
873
|
+
firmware_reset_window: tool({
|
|
874
|
+
description: "Manually reset your Firmware 5-hour spending window. Consumes 1 of 2 weekly resets. Requires confirm=true.",
|
|
913
875
|
args: {
|
|
914
|
-
|
|
915
|
-
.
|
|
916
|
-
.
|
|
917
|
-
.describe("
|
|
918
|
-
endingDate: tool.schema
|
|
919
|
-
.string()
|
|
920
|
-
.regex(/^\d{4}-\d{2}-\d{2}$/)
|
|
921
|
-
.describe("Ending date in YYYY-MM-DD format (local timezone, inclusive)"),
|
|
876
|
+
confirm: tool.schema
|
|
877
|
+
.boolean()
|
|
878
|
+
.optional()
|
|
879
|
+
.describe("Must be true to proceed (destructive action with limited weekly resets)"),
|
|
922
880
|
},
|
|
923
881
|
async execute(args, context) {
|
|
924
|
-
|
|
925
|
-
if (
|
|
926
|
-
await injectRawOutput(context.sessionID,
|
|
882
|
+
// Require explicit confirmation (destructive action with limited weekly resets)
|
|
883
|
+
if (args.confirm !== true) {
|
|
884
|
+
await injectRawOutput(context.sessionID, `⚠️ This will reset your Firmware 5-hour spending window.\nYou have a maximum of 2 resets per week.\n\nTo proceed, call with confirm: true`);
|
|
927
885
|
return "";
|
|
928
886
|
}
|
|
929
|
-
const
|
|
930
|
-
if (!
|
|
931
|
-
await injectRawOutput(context.sessionID,
|
|
887
|
+
const result = await resetFirmwareQuotaWindow();
|
|
888
|
+
if (!result) {
|
|
889
|
+
await injectRawOutput(context.sessionID, "Firmware API key not configured. Cannot reset window.");
|
|
932
890
|
return "";
|
|
933
891
|
}
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
if (endMs < startMs) {
|
|
937
|
-
await injectRawOutput(context.sessionID, `Ending date (${args.endingDate}) is before starting date (${args.startingDate}).`);
|
|
892
|
+
if (!result.success) {
|
|
893
|
+
await injectRawOutput(context.sessionID, `Failed to reset Firmware window:\n${result.error}`);
|
|
938
894
|
return "";
|
|
939
895
|
}
|
|
940
|
-
|
|
941
|
-
const
|
|
942
|
-
const
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
context.metadata({ title: "Tokens used (Date Range)" });
|
|
951
|
-
await injectRawOutput(context.sessionID, out);
|
|
896
|
+
// Build success message
|
|
897
|
+
const resetsLeft = result.windowResetsRemaining;
|
|
898
|
+
const resetsMsg = resetsLeft !== undefined
|
|
899
|
+
? ` (${resetsLeft} reset${resetsLeft === 1 ? "" : "s"} remaining this week)`
|
|
900
|
+
: "";
|
|
901
|
+
// Fetch updated quota to show new status
|
|
902
|
+
const quotaMsg = await fetchQuotaCommandMessage("tool:firmware_reset_window", context.sessionID);
|
|
903
|
+
const successOutput = `✓ Firmware 5-hour window reset successful${resetsMsg}${quotaMsg ? `\n\n${quotaMsg}` : ""}`;
|
|
904
|
+
context.metadata({ title: "Firmware Window Reset" });
|
|
905
|
+
await injectRawOutput(context.sessionID, successOutput);
|
|
952
906
|
return ""; // Empty return - output already injected with noReply
|
|
953
907
|
},
|
|
954
908
|
}),
|