@astrosheep/pi-quota 0.5.3 → 0.5.5

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 CHANGED
@@ -1,12 +1,12 @@
1
1
  # @astrosheep/pi-quota
2
2
 
3
- Compact quota status for Pi: Codex, Kimi and OpenCode Go work out of the box; new-api, DeepSeek and Sub2API use explicit provider bindings.
3
+ Compact quota status for Pi: Codex, Kimi, OpenCode Go and DeepSeek work out of the box; new-api and Sub2API use explicit provider bindings.
4
4
 
5
5
  ```bash
6
6
  pi install npm:@astrosheep/pi-quota
7
7
  ```
8
8
 
9
- Reload Pi after installing or changing settings. Use `/usage` for a persistent, context-free snapshot covering every configured provider with a quota adapter, including the current one. Automatic footer polling never adds chat items.
9
+ Reload Pi after installing or changing settings. Use `/usage` for a persistent, context-free snapshot of providers with available quota data, including the current one. Use `/usage --all` to inspect provider errors. Automatic footer polling never adds chat items.
10
10
 
11
11
  ## What it shows
12
12
 
@@ -18,7 +18,7 @@ Reload Pi after installing or changing settings. Use `/usage` for a persistent,
18
18
 
19
19
  A percentage always means **remaining**, not used. Unknown is `[?] ?`; an expired reset is `↺ due` with the stale percentage hidden until the next successful query. A wallet balance never becomes a percentage. Historical spend is not a quota.
20
20
 
21
- The footer shows at most two windows and `+N` for additional ones, for the current provider only. `/usage` queries all configured supported providers and includes all windows, grouped by provider; it does not open a modal or send messages to the model. Snapshots remain frozen at capture time. Errors replace stale values and are shown per provider.
21
+ The footer shows at most two windows and `+N` for additional ones, for the current provider only. `/usage` queries all configured supported providers and shows successful results with all windows, grouped by provider; it does not open a modal or send messages to the model. Snapshots remain frozen at capture time. Query failures replace stale values in the footer and appear in `/usage --all`.
22
22
 
23
23
  ## Configure providers
24
24
 
@@ -36,7 +36,7 @@ Add bindings in global `~/.pi/agent/settings.json` under `quotaUsage` (not proje
36
36
  }
37
37
  ```
38
38
 
39
- Use the **exact Pi provider ID**. Only selected, explicitly bound providers are queried; built-in `openai-codex`, `kimi-coding` and `opencode-go` cannot be overridden. Unknown keys or adapter options are rejected. The API key and model `baseUrl` come from Pi's active provider auth; do not put model API keys in `quotaUsage`.
39
+ Use the **exact Pi provider ID**. Built-in providers and explicitly bound providers are queried; explicit bindings replace a built-in adapter with the same ID. Unknown keys or adapter options are rejected. The API key and model `baseUrl` come from Pi's active provider auth; do not put model API keys in `quotaUsage`.
40
40
 
41
41
  | Adapter | Endpoint | Result |
42
42
  |---|---|---|
@@ -63,7 +63,7 @@ Pi model/auth → adapter → QuotaSnapshot → controller → footer / /usage /
63
63
 
64
64
  `QuotaSnapshot` separates `balance` (wallet), `allowance` (finite key quota), `windows` (renewable limits), and optional `spend` (Today/Lifetime actual charge). This prevents unrelated amounts from sharing an ambiguous `Used` label. Custom adapters can be passed to `createQuotaExtension({ adapters: [...] })`; see `examples/custom-provider.ts`. Provider aliases are never inferred.
65
65
 
66
- The extension publishes structured state on `quota-bar:state:v1`. Use `createQuotaExtension({ footer: false })` to consume it without this extension's footer. Normal queries are throttled, cancellable, timeout-bounded and backed off after errors. `/usage` forces a fresh query for every configured supported provider. Switching provider discards old footer values immediately.
66
+ The extension publishes structured state on `quota-bar:state:v1`. Use `createQuotaExtension({ footer: false })` to consume it without this extension's footer. Normal queries are throttled, cancellable, timeout-bounded and backed off after errors. `/usage` forces a fresh query for every configured supported provider, then omits failed results from its default view. Switching provider discards old footer values immediately.
67
67
 
68
68
  ## Development
69
69
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@astrosheep/pi-quota",
3
- "version": "0.5.3",
3
+ "version": "0.5.5",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
package/src/bar/card.ts CHANGED
@@ -24,7 +24,7 @@ export function captureAllUsage(states: readonly QuotaState[], now = Date.now())
24
24
 
25
25
  export function usageLines(card: UsageCard, paint?: Paint, barWidth = 20): string[] {
26
26
  if (card.version === 1) return renderUsage(card.state, paint, card.capturedAt, barWidth);
27
- if (card.states.length === 0) return ['No supported quota providers available.'];
27
+ if (card.states.length === 0) return ['No quota data available. Run /usage --all for details.'];
28
28
  return card.states.flatMap((state, index) => [
29
29
  ...(index ? ['', ''] : []), ...renderUsage(state, paint, card.capturedAt, barWidth),
30
30
  ]);
package/src/bar/quota.ts CHANGED
@@ -46,11 +46,9 @@ export function renderFooter(state: QuotaState, paint: Paint = plain, now = Date
46
46
  return renderBar([quotaElement(state, now)], paint);
47
47
  }
48
48
 
49
- const displayLabel: Record<string, string> = { deepseek: 'DeepSeek' };
50
-
51
49
  export function renderUsage(state: QuotaState, paint: Paint = plain, now = Date.now(), barWidth = 16): string[] {
52
50
  if (state.kind === 'hidden') return ['No quota adapter for the current provider.'];
53
- const title = displayLabel[state.provider] ?? state.label;
51
+ const title = state.label;
54
52
  const indent = (line: string) => line ? ` ${line}` : line;
55
53
  if (state.kind === 'loading') return [title, '', indent('Loading…')];
56
54
  if (state.kind === 'error') return [title, '', indent(errorText[state.code])];
package/src/extension.ts CHANGED
@@ -16,7 +16,8 @@ const STATUS_KEY = 'quota-bar';
16
16
  const USAGE_WIDGET = 'quota-bar:usage';
17
17
 
18
18
  export interface QuotaExtensionOptions {
19
- adapters?: readonly QuotaAdapter[]; // Extra adapters; duplicate IDs are rejected.
19
+ // Explicit adapters replace matching built-ins; duplicate explicit IDs are rejected.
20
+ adapters?: readonly QuotaAdapter[];
20
21
  providers?: readonly string[]; // Extra configured quota providers to include in /usage
21
22
  footer?: boolean; // false: query + /usage + structured events only
22
23
  intervalMs?: number;
@@ -25,8 +26,13 @@ export interface QuotaExtensionOptions {
25
26
 
26
27
  export function createQuotaExtension(options: QuotaExtensionOptions = {}) {
27
28
  return (pi: ExtensionAPI): void => {
28
- const registry = new AdapterRegistry([codexAdapter, kimiAdapter, openCodeGoAdapter,
29
- createDeepSeekAdapter('deepseek'), ...(options.adapters ?? [])]);
29
+ const explicitAdapters = options.adapters ?? [];
30
+ const explicitProviderIds = new Set(explicitAdapters.map(adapter => adapter.provider));
31
+ // Settings are deliberate user bindings, so they take precedence over defaults.
32
+ // AdapterRegistry still detects an ambiguous duplicate among explicit adapters.
33
+ const registry = new AdapterRegistry([
34
+ codexAdapter, kimiAdapter, openCodeGoAdapter, createDeepSeekAdapter('deepseek'),
35
+ ].filter(adapter => !explicitProviderIds.has(adapter.provider)).concat(explicitAdapters));
30
36
  let current: ExtensionContext | undefined;
31
37
  let selectedProvider: string | undefined;
32
38
  let timer: ReturnType<typeof setInterval> | undefined;
@@ -161,8 +167,8 @@ export function createQuotaExtension(options: QuotaExtensionOptions = {}) {
161
167
  });
162
168
 
163
169
  pi.registerCommand('usage', {
164
- description: 'Query quota for every configured supported provider and add snapshots to the chat history',
165
- handler: async (_args, ctx) => {
170
+ description: 'Show available quotas; use /usage --all to include provider errors',
171
+ handler: async (args, ctx) => {
166
172
  if (!ctx.hasUI) return;
167
173
  bind(ctx);
168
174
  const generation = ++commandGeneration;
@@ -174,7 +180,9 @@ export function createQuotaExtension(options: QuotaExtensionOptions = {}) {
174
180
  // Never append via a stale Pi runtime, or label a replacement model's data
175
181
  // as the result of the original command. A newer command owns its result.
176
182
  if (!current || generation !== commandGeneration) return;
177
- const card = captureAllUsage(states);
183
+ const visibleStates = args.trim() === '--all'
184
+ ? states : states.filter(state => state.kind === 'ready');
185
+ const card = captureAllUsage(visibleStates);
178
186
  pi.appendEntry<UsageCard>(USAGE_ENTRY, card);
179
187
  // Custom entry renderers are TUI-only; RPC clients still get readable output.
180
188
  if (ctx.mode !== 'tui') {
@@ -24,7 +24,7 @@ export function parseDeepSeekBalance(payload: unknown): AccountBalance {
24
24
 
25
25
  export function createDeepSeekAdapter(provider: string): QuotaAdapter {
26
26
  return {
27
- provider, label: provider,
27
+ provider, label: provider === 'deepseek' ? 'DeepSeek' : provider,
28
28
  async query(context) {
29
29
  const auth = await officialAuth(context, DEEPSEEK_ORIGIN);
30
30
  const payload = await context.getJson(`${DEEPSEEK_ORIGIN}/user/balance`,