@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.
Files changed (83) hide show
  1. package/README.md +171 -74
  2. package/dist/lib/api-key-resolver.d.ts +83 -0
  3. package/dist/lib/api-key-resolver.d.ts.map +1 -0
  4. package/dist/lib/api-key-resolver.js +113 -0
  5. package/dist/lib/api-key-resolver.js.map +1 -0
  6. package/dist/lib/chutes-config.d.ts +8 -7
  7. package/dist/lib/chutes-config.d.ts.map +1 -1
  8. package/dist/lib/chutes-config.js +32 -128
  9. package/dist/lib/chutes-config.js.map +1 -1
  10. package/dist/lib/chutes.d.ts.map +1 -1
  11. package/dist/lib/chutes.js +1 -17
  12. package/dist/lib/chutes.js.map +1 -1
  13. package/dist/lib/config.d.ts.map +1 -1
  14. package/dist/lib/config.js +1 -48
  15. package/dist/lib/config.js.map +1 -1
  16. package/dist/lib/copilot.d.ts.map +1 -1
  17. package/dist/lib/copilot.js +1 -24
  18. package/dist/lib/copilot.js.map +1 -1
  19. package/dist/lib/env-template.d.ts +25 -0
  20. package/dist/lib/env-template.d.ts.map +1 -0
  21. package/dist/lib/env-template.js +32 -0
  22. package/dist/lib/env-template.js.map +1 -0
  23. package/dist/lib/firmware-config.d.ts +1 -7
  24. package/dist/lib/firmware-config.d.ts.map +1 -1
  25. package/dist/lib/firmware-config.js +26 -148
  26. package/dist/lib/firmware-config.js.map +1 -1
  27. package/dist/lib/firmware.d.ts +22 -0
  28. package/dist/lib/firmware.d.ts.map +1 -1
  29. package/dist/lib/firmware.js +96 -23
  30. package/dist/lib/firmware.js.map +1 -1
  31. package/dist/lib/format-utils.d.ts +56 -0
  32. package/dist/lib/format-utils.d.ts.map +1 -0
  33. package/dist/lib/format-utils.js +101 -0
  34. package/dist/lib/format-utils.js.map +1 -0
  35. package/dist/lib/format.d.ts.map +1 -1
  36. package/dist/lib/format.js +2 -67
  37. package/dist/lib/format.js.map +1 -1
  38. package/dist/lib/google.d.ts.map +1 -1
  39. package/dist/lib/google.js +2 -24
  40. package/dist/lib/google.js.map +1 -1
  41. package/dist/lib/http.d.ts +14 -0
  42. package/dist/lib/http.d.ts.map +1 -0
  43. package/dist/lib/http.js +34 -0
  44. package/dist/lib/http.js.map +1 -0
  45. package/dist/lib/jsonc.d.ts +25 -0
  46. package/dist/lib/jsonc.d.ts.map +1 -0
  47. package/dist/lib/jsonc.js +73 -0
  48. package/dist/lib/jsonc.js.map +1 -0
  49. package/dist/lib/markdown-table.d.ts +7 -0
  50. package/dist/lib/markdown-table.d.ts.map +1 -1
  51. package/dist/lib/markdown-table.js +76 -9
  52. package/dist/lib/markdown-table.js.map +1 -1
  53. package/dist/lib/openai.d.ts.map +1 -1
  54. package/dist/lib/openai.js +1 -17
  55. package/dist/lib/openai.js.map +1 -1
  56. package/dist/lib/opencode-storage.d.ts +27 -0
  57. package/dist/lib/opencode-storage.d.ts.map +1 -1
  58. package/dist/lib/opencode-storage.js +67 -0
  59. package/dist/lib/opencode-storage.js.map +1 -1
  60. package/dist/lib/quota-command-format.d.ts.map +1 -1
  61. package/dist/lib/quota-command-format.js +5 -50
  62. package/dist/lib/quota-command-format.js.map +1 -1
  63. package/dist/lib/quota-stats-format.d.ts.map +1 -1
  64. package/dist/lib/quota-stats-format.js +9 -3
  65. package/dist/lib/quota-stats-format.js.map +1 -1
  66. package/dist/lib/quota-stats.d.ts +1 -0
  67. package/dist/lib/quota-stats.d.ts.map +1 -1
  68. package/dist/lib/quota-stats.js +15 -5
  69. package/dist/lib/quota-stats.js.map +1 -1
  70. package/dist/lib/quota-status.d.ts +7 -0
  71. package/dist/lib/quota-status.d.ts.map +1 -1
  72. package/dist/lib/quota-status.js +10 -0
  73. package/dist/lib/quota-status.js.map +1 -1
  74. package/dist/lib/toast-format-grouped.d.ts.map +1 -1
  75. package/dist/lib/toast-format-grouped.js +1 -66
  76. package/dist/lib/toast-format-grouped.js.map +1 -1
  77. package/dist/plugin.d.ts.map +1 -1
  78. package/dist/plugin.js +116 -162
  79. package/dist/plugin.js.map +1 -1
  80. package/dist/providers/firmware.d.ts.map +1 -1
  81. package/dist/providers/firmware.js +40 -7
  82. package/dist/providers/firmware.js.map +1 -1
  83. 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
- function clampInt(n, min, max) {
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":"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"}
1
+ {"version":3,"file":"toast-format-grouped.js","sourceRoot":"","sources":["../../src/lib/toast-format-grouped.ts"],"names":[],"mappings":"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"}
@@ -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;AAkQlD;;GAEG;AACH,eAAO,MAAM,gBAAgB,EAAE,MAu9B9B,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 (both new and legacy) to spec */
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 /quota_between arguments. Supports:
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
- // Ignore errors fetching session tokens - it's a nice-to-have
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
- // Ignore errors fetching session tokens - it's a nice-to-have
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
- // Register token report commands (primary /tokens_* and legacy /quota_* aliases)
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 generically (both /tokens_* and legacy /quota_* aliases)
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
- quota_today: tool({
878
- description: "Token + official API cost summary for today (calendar day, local timezone).",
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
- startingDate: tool.schema
915
- .string()
916
- .regex(/^\d{4}-\d{2}-\d{2}$/)
917
- .describe("Starting date in YYYY-MM-DD format (local timezone)"),
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
- const startYmd = parseYyyyMmDd(args.startingDate);
925
- if (!startYmd) {
926
- await injectRawOutput(context.sessionID, `Invalid starting date: "${args.startingDate}". Expected YYYY-MM-DD.`);
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 endYmd = parseYyyyMmDd(args.endingDate);
930
- if (!endYmd) {
931
- await injectRawOutput(context.sessionID, `Invalid ending date: "${args.endingDate}". Expected YYYY-MM-DD.`);
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
- const startMs = startOfLocalDayMs(startYmd);
935
- const endMs = startOfLocalDayMs(endYmd);
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
- const sinceMs = startMs;
941
- const untilMs = startOfNextLocalDayMs(endYmd); // Exclusive upper bound for inclusive end date
942
- const startStr = formatYmd(startYmd);
943
- const endStr = formatYmd(endYmd);
944
- const out = await buildQuotaReport({
945
- title: `Tokens used (${startStr} .. ${endStr}) (/tokens_between)`,
946
- sinceMs,
947
- untilMs,
948
- sessionID: context.sessionID,
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
  }),