@latentminds/pi-quotas 0.1.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/LICENSE +21 -0
- package/README.md +94 -0
- package/package.json +44 -0
- package/src/config.ts +216 -0
- package/src/extensions/command-quotas/command.ts +122 -0
- package/src/extensions/command-quotas/components/quotas-display.ts +209 -0
- package/src/extensions/command-quotas/index.ts +1 -0
- package/src/extensions/command-quotas/provider-commands.test.ts +34 -0
- package/src/extensions/command-quotas/provider-commands.ts +32 -0
- package/src/extensions/core/index.ts +95 -0
- package/src/extensions/quota-warnings/index.ts +128 -0
- package/src/extensions/usage-status/format-status.test.ts +104 -0
- package/src/extensions/usage-status/format-status.ts +80 -0
- package/src/extensions/usage-status/index.ts +163 -0
- package/src/index.ts +4 -0
- package/src/lib/quotas.ts +96 -0
- package/src/providers/fetch.test.ts +137 -0
- package/src/providers/fetch.ts +245 -0
- package/src/providers/parse.test.ts +256 -0
- package/src/providers/providers.ts +245 -0
- package/src/types/quotas.ts +31 -0
- package/src/utils/quotas-severity.test.ts +111 -0
- package/src/utils/quotas-severity.ts +151 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025-2026 Latent Minds Pty Ltd
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="docs/latent-minds@2x.png" alt="Latent Minds" width="320" />
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
# @latentminds/pi-quotas
|
|
6
|
+
|
|
7
|
+
Quota monitoring for the [Pi coding agent](https://github.com/mariozechner/pi). Shows remaining usage and rate limits for Anthropic, OpenAI Codex, and GitHub Copilot — directly in your Pi session.
|
|
8
|
+
|
|
9
|
+
## Screenshots
|
|
10
|
+
|
|
11
|
+
| `/quotas` dashboard | Footer status |
|
|
12
|
+
|---|---|
|
|
13
|
+
|  |  |
|
|
14
|
+
|
|
15
|
+
## Install
|
|
16
|
+
|
|
17
|
+
**From npm** (recommended):
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
pi install npm:@latentminds/pi-quotas
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
**From source:**
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
git clone https://github.com/latentminds-ai/pi-quotas.git
|
|
27
|
+
pi install ./pi-quotas
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
**Try without installing:**
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
pi -e npm:@latentminds/pi-quotas
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Commands
|
|
37
|
+
|
|
38
|
+
| Command | Description |
|
|
39
|
+
|---------|-------------|
|
|
40
|
+
| `/quotas` | Combined quota dashboard for all providers |
|
|
41
|
+
| `/anthropic:quotas` | Anthropic quotas only |
|
|
42
|
+
| `/codex:quotas` | OpenAI Codex quotas only |
|
|
43
|
+
| `/github:quotas` | GitHub Copilot quotas only |
|
|
44
|
+
| `/quotas:settings` | Toggle individual features on or off |
|
|
45
|
+
|
|
46
|
+
## Features
|
|
47
|
+
|
|
48
|
+
### Quota dashboard
|
|
49
|
+
|
|
50
|
+
Run `/quotas` to open a bordered TUI view showing all providers side by side, with progress bars, used/remaining counts, and reset times. Press `r` to refresh, `q` or `Esc` to close.
|
|
51
|
+
|
|
52
|
+
### Footer status widget
|
|
53
|
+
|
|
54
|
+
When your active model is from a supported provider, the Pi footer shows real-time quota headroom — updated every 60 seconds and on each turn. Colours shift from green → amber → red as usage climbs.
|
|
55
|
+
|
|
56
|
+
### Quota warnings
|
|
57
|
+
|
|
58
|
+
Automatic notifications when projected usage is on track to exceed limits before the window resets. Warnings escalate from `warning` → `high` → `critical` based on your consumption pace.
|
|
59
|
+
|
|
60
|
+
### Per-feature toggles
|
|
61
|
+
|
|
62
|
+
Use `/quotas:settings` to enable or disable:
|
|
63
|
+
- Combined `/quotas` command
|
|
64
|
+
- Per-provider commands (`/anthropic:quotas`, `/codex:quotas`, `/github:quotas`)
|
|
65
|
+
- Footer status widget
|
|
66
|
+
- Quota warning notifications
|
|
67
|
+
|
|
68
|
+
Settings can be saved globally (`~/.pi/agent/extensions/quotas.json`) or per-project (`.pi/quotas.json`). Run `/reload` after changing command visibility.
|
|
69
|
+
|
|
70
|
+
## Supported providers
|
|
71
|
+
|
|
72
|
+
| Provider | Windows | Details |
|
|
73
|
+
|----------|---------|---------|
|
|
74
|
+
| Anthropic | 5h, 7d, per-model 7d, extra usage | Utilization percentages; optional overage budget in local currency |
|
|
75
|
+
| OpenAI Codex | 5h, 7d, credits, spend cap | Rate-limit percentages; credit balance; spend-cap reached/OK |
|
|
76
|
+
| GitHub Copilot | Premium/chat/completions per month | Remaining/entitlement counts with overage indicators |
|
|
77
|
+
|
|
78
|
+
## Credentials
|
|
79
|
+
|
|
80
|
+
pi-quotas reads existing Pi auth entries from `~/.pi/agent/auth.json`:
|
|
81
|
+
|
|
82
|
+
- `anthropic` — Anthropic OAuth token
|
|
83
|
+
- `openai-codex` — Codex access token (also reads `~/.codex/auth.json` for the account ID)
|
|
84
|
+
- `github-copilot` — GitHub Copilot OAuth token (falls back to `gh auth token` if needed)
|
|
85
|
+
|
|
86
|
+
No additional setup is required — if Pi can use the provider, pi-quotas can check its quotas.
|
|
87
|
+
|
|
88
|
+
## Requirements
|
|
89
|
+
|
|
90
|
+
- [Pi](https://github.com/mariozechner/pi) >= 0.61.0
|
|
91
|
+
|
|
92
|
+
## License
|
|
93
|
+
|
|
94
|
+
[MIT](LICENSE) © Latent Minds Pty Ltd
|
package/package.json
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@latentminds/pi-quotas",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"license": "MIT",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"private": false,
|
|
7
|
+
"author": "Markus Strazds <markus@latentminds.ai>",
|
|
8
|
+
"keywords": ["pi-package", "pi-extension", "pi"],
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "https://github.com/latentminds-ai/pi-quotas"
|
|
12
|
+
},
|
|
13
|
+
"pi": {
|
|
14
|
+
"extensions": [
|
|
15
|
+
"./src/extensions/core/index.ts",
|
|
16
|
+
"./src/extensions/command-quotas/index.ts",
|
|
17
|
+
"./src/extensions/usage-status/index.ts",
|
|
18
|
+
"./src/extensions/quota-warnings/index.ts"
|
|
19
|
+
],
|
|
20
|
+
"image": "https://raw.githubusercontent.com/latentminds-ai/pi-quotas/main/docs/quotas-dashboard.png"
|
|
21
|
+
},
|
|
22
|
+
"files": ["src", "README.md", "LICENSE"],
|
|
23
|
+
"peerDependencies": {
|
|
24
|
+
"@mariozechner/pi-coding-agent": "*",
|
|
25
|
+
"@mariozechner/pi-tui": "*"
|
|
26
|
+
},
|
|
27
|
+
"peerDependenciesMeta": {
|
|
28
|
+
"@mariozechner/pi-coding-agent": { "optional": true },
|
|
29
|
+
"@mariozechner/pi-tui": { "optional": true }
|
|
30
|
+
},
|
|
31
|
+
"devDependencies": {
|
|
32
|
+
"@mariozechner/pi-coding-agent": "0.61.0",
|
|
33
|
+
"@mariozechner/pi-tui": "0.61.0",
|
|
34
|
+
"@sinclair/typebox": "^0.34.48",
|
|
35
|
+
"@types/node": "^25.0.10",
|
|
36
|
+
"typescript": "^5.9.3",
|
|
37
|
+
"vitest": "^4.0.18"
|
|
38
|
+
},
|
|
39
|
+
"scripts": {
|
|
40
|
+
"typecheck": "tsc --noEmit",
|
|
41
|
+
"test": "vitest run",
|
|
42
|
+
"test:watch": "vitest"
|
|
43
|
+
}
|
|
44
|
+
}
|
package/src/config.ts
ADDED
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
4
|
+
import { homedir } from "node:os";
|
|
5
|
+
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
6
|
+
import pkg from "../package.json" with { type: "json" };
|
|
7
|
+
|
|
8
|
+
export type QuotasFeatureId =
|
|
9
|
+
| "quotasCommand"
|
|
10
|
+
| "providerCommands"
|
|
11
|
+
| "usageStatus"
|
|
12
|
+
| "quotaWarnings";
|
|
13
|
+
|
|
14
|
+
export const QUOTAS_EXTENSIONS_REQUEST_EVENT =
|
|
15
|
+
"quotas:extensions:request" as const;
|
|
16
|
+
export const QUOTAS_EXTENSIONS_REGISTER_EVENT =
|
|
17
|
+
"quotas:extensions:register" as const;
|
|
18
|
+
export const QUOTAS_CONFIG_UPDATED_EVENT =
|
|
19
|
+
"quotas:config:updated" as const;
|
|
20
|
+
|
|
21
|
+
export interface QuotasExtensionsRegisterPayload {
|
|
22
|
+
feature: QuotasFeatureId;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface QuotasConfig {
|
|
26
|
+
configVersion?: string;
|
|
27
|
+
quotasCommand?: boolean;
|
|
28
|
+
providerCommands?: boolean;
|
|
29
|
+
usageStatus?: boolean;
|
|
30
|
+
quotaWarnings?: boolean;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface ResolvedQuotasConfig {
|
|
34
|
+
configVersion: string;
|
|
35
|
+
quotasCommand: boolean;
|
|
36
|
+
providerCommands: boolean;
|
|
37
|
+
usageStatus: boolean;
|
|
38
|
+
quotaWarnings: boolean;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const DEFAULT_CONFIG: ResolvedQuotasConfig = {
|
|
42
|
+
configVersion: pkg.version,
|
|
43
|
+
quotasCommand: true,
|
|
44
|
+
providerCommands: true,
|
|
45
|
+
usageStatus: true,
|
|
46
|
+
quotaWarnings: true,
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
let pendingMigrationNotice = false;
|
|
50
|
+
|
|
51
|
+
function markMigrationNoticePending(): void {
|
|
52
|
+
pendingMigrationNotice = true;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function hasPendingMigrationNotice(): boolean {
|
|
56
|
+
return pendingMigrationNotice;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function clearPendingMigrationNotice(): void {
|
|
60
|
+
pendingMigrationNotice = false;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
class QuotasConfigStore {
|
|
64
|
+
private config: ResolvedQuotasConfig = DEFAULT_CONFIG;
|
|
65
|
+
private cwd = process.cwd();
|
|
66
|
+
|
|
67
|
+
private globalPath(): string {
|
|
68
|
+
return join(homedir(), ".pi", "agent", "extensions", "quotas.json");
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
private localPath(): string {
|
|
72
|
+
return join(this.cwd, ".pi", "quotas.json");
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
private resolve(input?: QuotasConfig): ResolvedQuotasConfig {
|
|
76
|
+
return {
|
|
77
|
+
configVersion: input?.configVersion ?? DEFAULT_CONFIG.configVersion,
|
|
78
|
+
quotasCommand: input?.quotasCommand ?? DEFAULT_CONFIG.quotasCommand,
|
|
79
|
+
providerCommands: input?.providerCommands ?? DEFAULT_CONFIG.providerCommands,
|
|
80
|
+
usageStatus: input?.usageStatus ?? DEFAULT_CONFIG.usageStatus,
|
|
81
|
+
quotaWarnings: input?.quotaWarnings ?? DEFAULT_CONFIG.quotaWarnings,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
private async readConfig(path: string): Promise<QuotasConfig | undefined> {
|
|
86
|
+
try {
|
|
87
|
+
const data = JSON.parse(await readFile(path, "utf8")) as QuotasConfig;
|
|
88
|
+
return data;
|
|
89
|
+
} catch {
|
|
90
|
+
return undefined;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
async load(cwd = process.cwd()): Promise<void> {
|
|
95
|
+
this.cwd = cwd;
|
|
96
|
+
const global = await this.readConfig(this.globalPath());
|
|
97
|
+
const local = await this.readConfig(this.localPath());
|
|
98
|
+
const merged = { ...global, ...local };
|
|
99
|
+
if (!global && !local) markMigrationNoticePending();
|
|
100
|
+
this.config = this.resolve(merged);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
getConfig(): ResolvedQuotasConfig {
|
|
104
|
+
return this.config;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
hasConfig(scope: "global" | "local"): boolean {
|
|
108
|
+
const path = scope === "global" ? this.globalPath() : this.localPath();
|
|
109
|
+
return existsSync(path);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
async save(scope: "global" | "local", config: QuotasConfig): Promise<void> {
|
|
113
|
+
const path = scope === "global" ? this.globalPath() : this.localPath();
|
|
114
|
+
await mkdir(dirname(path), { recursive: true });
|
|
115
|
+
await writeFile(path, JSON.stringify(this.resolve(config), null, 2) + "\n", "utf8");
|
|
116
|
+
await this.load(this.cwd);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export const configLoader = new QuotasConfigStore();
|
|
121
|
+
|
|
122
|
+
export async function seedQuotasConfigIfMissing(): Promise<void> {
|
|
123
|
+
if (configLoader.hasConfig("global") || configLoader.hasConfig("local")) return;
|
|
124
|
+
markMigrationNoticePending();
|
|
125
|
+
try {
|
|
126
|
+
await configLoader.save("global", DEFAULT_CONFIG);
|
|
127
|
+
} catch {
|
|
128
|
+
// ignore
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export interface QuotasConfigUpdatedPayload {
|
|
133
|
+
config: ResolvedQuotasConfig;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export function emitQuotasConfigUpdated(pi: ExtensionAPI): void {
|
|
137
|
+
pi.events.emit(QUOTAS_CONFIG_UPDATED_EVENT, {
|
|
138
|
+
config: configLoader.getConfig(),
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const FEATURE_META: Array<{
|
|
143
|
+
id: QuotasFeatureId;
|
|
144
|
+
label: string;
|
|
145
|
+
description: string;
|
|
146
|
+
}> = [
|
|
147
|
+
{
|
|
148
|
+
id: "quotasCommand",
|
|
149
|
+
label: "Combined quotas command",
|
|
150
|
+
description: "Toggle the `/quotas` command",
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
id: "providerCommands",
|
|
154
|
+
label: "Provider quota commands",
|
|
155
|
+
description: "Toggle `/anthropic:quotas`, `/codex:quotas`, and `/github:quotas`",
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
id: "usageStatus",
|
|
159
|
+
label: "Usage status",
|
|
160
|
+
description: "Toggle footer quota status for the active provider",
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
id: "quotaWarnings",
|
|
164
|
+
label: "Quota warnings",
|
|
165
|
+
description: "Toggle projected-usage warning notifications",
|
|
166
|
+
},
|
|
167
|
+
];
|
|
168
|
+
|
|
169
|
+
export function registerQuotasSettings(
|
|
170
|
+
pi: ExtensionAPI,
|
|
171
|
+
getLoadedFeatures: () => Set<QuotasFeatureId>,
|
|
172
|
+
): void {
|
|
173
|
+
pi.registerCommand("quotas:settings", {
|
|
174
|
+
description: "Configure quota extension settings",
|
|
175
|
+
handler: async (_args, ctx) => {
|
|
176
|
+
await configLoader.load(ctx.cwd);
|
|
177
|
+
const scopeChoice = await ctx.ui.select("Save settings to", [
|
|
178
|
+
"global",
|
|
179
|
+
"local",
|
|
180
|
+
"cancel",
|
|
181
|
+
]);
|
|
182
|
+
if (!scopeChoice || scopeChoice === "cancel") return;
|
|
183
|
+
const scope = scopeChoice as "global" | "local";
|
|
184
|
+
const draft: ResolvedQuotasConfig = { ...configLoader.getConfig() };
|
|
185
|
+
|
|
186
|
+
while (true) {
|
|
187
|
+
const choices = FEATURE_META.map((feature) => {
|
|
188
|
+
const loaded = getLoadedFeatures().has(feature.id) ? "" : " (not loaded)";
|
|
189
|
+
const enabled = draft[feature.id] ? "enabled" : "disabled";
|
|
190
|
+
return `${feature.label}: ${enabled}${loaded}`;
|
|
191
|
+
});
|
|
192
|
+
choices.push("Save and exit", "Cancel");
|
|
193
|
+
|
|
194
|
+
const selected = await ctx.ui.select("Quotas Settings", choices);
|
|
195
|
+
if (!selected || selected === "Cancel") return;
|
|
196
|
+
if (selected === "Save and exit") {
|
|
197
|
+
await configLoader.save(scope, draft);
|
|
198
|
+
emitQuotasConfigUpdated(pi);
|
|
199
|
+
ctx.ui.notify(
|
|
200
|
+
"Quota settings saved. Run /reload to fully apply command visibility changes.",
|
|
201
|
+
"info",
|
|
202
|
+
);
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const feature = FEATURE_META.find((item) => selected.startsWith(item.label));
|
|
207
|
+
if (!feature) continue;
|
|
208
|
+
if (!getLoadedFeatures().has(feature.id)) {
|
|
209
|
+
ctx.ui.notify(`${feature.label} is not loaded by Pi in this session.`, "warning");
|
|
210
|
+
continue;
|
|
211
|
+
}
|
|
212
|
+
draft[feature.id] = !draft[feature.id];
|
|
213
|
+
}
|
|
214
|
+
},
|
|
215
|
+
});
|
|
216
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import type { ExtensionAPI, ExtensionCommandContext } from "@mariozechner/pi-coding-agent";
|
|
2
|
+
import {
|
|
3
|
+
QUOTAS_EXTENSIONS_REGISTER_EVENT,
|
|
4
|
+
QUOTAS_EXTENSIONS_REQUEST_EVENT,
|
|
5
|
+
configLoader,
|
|
6
|
+
} from "../../config.js";
|
|
7
|
+
import {
|
|
8
|
+
fetchAllProviderQuotas,
|
|
9
|
+
fetchProviderQuotas,
|
|
10
|
+
SUPPORTED_PROVIDERS,
|
|
11
|
+
} from "../../lib/quotas.js";
|
|
12
|
+
import type { QuotasResult, SupportedQuotaProvider } from "../../types/quotas.js";
|
|
13
|
+
import { QuotasComponent } from "./components/quotas-display.js";
|
|
14
|
+
import { getProviderCommandInfo } from "./provider-commands.js";
|
|
15
|
+
|
|
16
|
+
type Snapshot = { provider: SupportedQuotaProvider; result: QuotasResult };
|
|
17
|
+
|
|
18
|
+
async function openQuotaView(
|
|
19
|
+
title: string,
|
|
20
|
+
loadSnapshots: (force: boolean, signal?: AbortSignal) => Promise<Snapshot[]>,
|
|
21
|
+
ctx: ExtensionCommandContext,
|
|
22
|
+
): Promise<void> {
|
|
23
|
+
const result = await ctx.ui.custom<null>((tui, theme, _kb, done) => {
|
|
24
|
+
const controller = new AbortController();
|
|
25
|
+
const component = new QuotasComponent(
|
|
26
|
+
theme,
|
|
27
|
+
tui,
|
|
28
|
+
title,
|
|
29
|
+
() => {
|
|
30
|
+
controller.abort();
|
|
31
|
+
done(null);
|
|
32
|
+
},
|
|
33
|
+
() => {
|
|
34
|
+
component.setState({ type: "loading" });
|
|
35
|
+
tui.requestRender();
|
|
36
|
+
void load(true);
|
|
37
|
+
},
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
async function load(force = false): Promise<void> {
|
|
41
|
+
const snapshots = await loadSnapshots(force, controller.signal);
|
|
42
|
+
if (controller.signal.aborted) return;
|
|
43
|
+
component.setState({ type: "loaded", snapshots });
|
|
44
|
+
tui.requestRender();
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
void load();
|
|
48
|
+
|
|
49
|
+
return {
|
|
50
|
+
render: (width: number) => component.render(width),
|
|
51
|
+
invalidate: () => component.invalidate(),
|
|
52
|
+
handleInput: (data: string) => component.handleInput(data),
|
|
53
|
+
dispose: () => {
|
|
54
|
+
controller.abort();
|
|
55
|
+
component.destroy();
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
if (result === undefined) {
|
|
61
|
+
const snapshots = await loadSnapshots(true);
|
|
62
|
+
ctx.ui.notify(JSON.stringify(snapshots, null, 2), "info");
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function registerQuotasCommands(pi: ExtensionAPI): void {
|
|
67
|
+
pi.registerCommand("quotas", {
|
|
68
|
+
description: "Display remaining quotas for Anthropic, Codex, and GitHub Copilot",
|
|
69
|
+
handler: async (_args, ctx) => {
|
|
70
|
+
if (!configLoader.getConfig().quotasCommand) {
|
|
71
|
+
ctx.ui.notify("/quotas is disabled. Re-enable it in /quotas:settings.", "warning");
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
await openQuotaView(
|
|
75
|
+
"Provider Quotas",
|
|
76
|
+
(force, signal) => fetchAllProviderQuotas(ctx.modelRegistry.authStorage, { force, signal }),
|
|
77
|
+
ctx,
|
|
78
|
+
);
|
|
79
|
+
},
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
for (const provider of SUPPORTED_PROVIDERS) {
|
|
83
|
+
const info = getProviderCommandInfo(provider);
|
|
84
|
+
pi.registerCommand(info.commandName, {
|
|
85
|
+
description: `Display remaining ${info.title.toLowerCase()}`,
|
|
86
|
+
handler: async (_args, ctx) => {
|
|
87
|
+
if (!configLoader.getConfig().providerCommands) {
|
|
88
|
+
ctx.ui.notify(`${info.commandName} is disabled. Re-enable it in /quotas:settings.`, "warning");
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
await openQuotaView(
|
|
92
|
+
info.title,
|
|
93
|
+
async (force, signal) => [
|
|
94
|
+
{
|
|
95
|
+
provider,
|
|
96
|
+
result: await fetchProviderQuotas(ctx.modelRegistry.authStorage, provider, { force, signal }),
|
|
97
|
+
},
|
|
98
|
+
],
|
|
99
|
+
ctx,
|
|
100
|
+
);
|
|
101
|
+
},
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export default async function (pi: ExtensionAPI) {
|
|
107
|
+
await configLoader.load();
|
|
108
|
+
|
|
109
|
+
const config = configLoader.getConfig();
|
|
110
|
+
if (config.quotasCommand || config.providerCommands) {
|
|
111
|
+
registerQuotasCommands(pi);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
pi.events.on(QUOTAS_EXTENSIONS_REQUEST_EVENT, () => {
|
|
115
|
+
if (configLoader.getConfig().quotasCommand) {
|
|
116
|
+
pi.events.emit(QUOTAS_EXTENSIONS_REGISTER_EVENT, { feature: "quotasCommand" });
|
|
117
|
+
}
|
|
118
|
+
if (configLoader.getConfig().providerCommands) {
|
|
119
|
+
pi.events.emit(QUOTAS_EXTENSIONS_REGISTER_EVENT, { feature: "providerCommands" });
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
}
|