shariq-pi-extensions 0.2.25 → 0.2.26

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.
@@ -10,7 +10,7 @@ Local persistent Pi provider for Google Antigravity-compatible models.
10
10
 
11
11
  The provider includes an IPv4 OAuth token-exchange fallback for Node environments where the default request fails. Its curated catalog follows current Antigravity model identifiers and runtime behavior.
12
12
 
13
- Successful logins are added to `<agent-dir>/antigravity/accounts.json`, written with owner-only permissions. A missing account file triggers one-time migration of an existing Pi OAuth credential; after that, the valid account file is authoritative, so removing an account cannot be undone by a stale credential in `auth.json`. Requests select the least recently used eligible account, skip disabled/cooling/exhausted accounts, refresh expiring OAuth tokens, and rotate to another account when an auth, rate, quota, or capacity failure occurs before response streaming begins. Cached per-model remaining quota and reset times guide selection; `/antigravity` refreshes the authoritative catalog, uses `d` to reversibly enable or disable an account, and uses `x` plus confirmation to remove one permanently.
13
+ Successful logins are added to `<agent-dir>/antigravity/accounts.json`, written with owner-only permissions. A missing account file triggers one-time migration of an existing Pi OAuth credential; after that, the valid account file is authoritative, so removing an account cannot be undone by a stale credential in `auth.json`. Requests select the least recently used eligible account, skip disabled/cooling/exhausted accounts, refresh expiring OAuth tokens, and rotate to another account when an auth, rate, quota, or capacity failure occurs before response streaming begins. Cached per-model remaining quota and reset times guide selection; `/antigravity` refreshes the authoritative catalog, uses `d` to reversibly enable or disable an account, and uses `x` twice to confirm and remove one permanently; Escape cancels an armed removal.
14
14
 
15
15
  Current public model IDs:
16
16
  - `antigravity/gemini-3.7-flash`
@@ -71,6 +71,7 @@ function compactGroup(label: string, entries: AntigravityQuotaEntry[]) {
71
71
  export class AntigravityDashboard implements Component {
72
72
  private selected = 0;
73
73
  private refreshing = false;
74
+ private pendingRemoval: { id: string; label: string } | undefined;
74
75
  private closed = false;
75
76
  private readonly tui: TUI;
76
77
  private readonly theme: Theme;
@@ -132,21 +133,36 @@ export class AntigravityDashboard implements Component {
132
133
  handleInput(data: string) {
133
134
  const accounts = this.snapshot.accounts;
134
135
  if (this.keys.matches(data, "tui.select.cancel")) {
136
+ if (this.pendingRemoval) {
137
+ this.pendingRemoval = undefined;
138
+ this.tui.requestRender();
139
+ return;
140
+ }
135
141
  this.closed = true;
136
142
  this.done();
137
143
  return;
138
144
  }
139
145
  if (this.keys.matches(data, "tui.select.up") || data === "k") {
146
+ this.pendingRemoval = undefined;
140
147
  if (accounts.length) this.selected = (this.selected - 1 + accounts.length) % accounts.length;
141
148
  } else if (this.keys.matches(data, "tui.select.down") || data === "j") {
149
+ this.pendingRemoval = undefined;
142
150
  if (accounts.length) this.selected = (this.selected + 1) % accounts.length;
143
151
  } else if (data === "r") {
152
+ this.pendingRemoval = undefined;
144
153
  this.startRefresh(true);
145
154
  } else if ((data === "d" || data === "x") && accounts[this.selected] && !this.refreshing) {
146
155
  const account = accounts[this.selected]!;
156
+ const label = account.email || `account-${account.id.slice(0, 6)}`;
157
+ if (data === "x" && this.pendingRemoval?.id !== account.id) {
158
+ this.pendingRemoval = { id: account.id, label };
159
+ this.tui.requestRender();
160
+ return;
161
+ }
162
+ this.pendingRemoval = undefined;
147
163
  this.refreshing = true;
148
164
  const action = data === "x"
149
- ? this.removeAccount(account.id, account.email || `account-${account.id.slice(0, 6)}`)
165
+ ? this.removeAccount(account.id, label)
150
166
  : this.toggleAccount(account.id, account.disabled === true);
151
167
  void action
152
168
  .then((snapshot) => this.replaceSnapshot(snapshot))
@@ -172,9 +188,11 @@ export class AntigravityDashboard implements Component {
172
188
  const active = accounts.filter((account) => account.active).length;
173
189
  const right = this.refreshing
174
190
  ? this.theme.fg("warning", "refreshing…")
175
- : this.snapshot.warning
176
- ? this.theme.fg("warning", oneLine(this.snapshot.warning))
177
- : this.theme.fg("muted", `${active}/${accounts.length} active · ${this.snapshot.modelCount} models`);
191
+ : this.pendingRemoval
192
+ ? this.theme.fg("warning", `press x again to remove ${oneLine(this.pendingRemoval.label)}`)
193
+ : this.snapshot.warning
194
+ ? this.theme.fg("warning", oneLine(this.snapshot.warning))
195
+ : this.theme.fg("muted", `${active}/${accounts.length} active · ${this.snapshot.modelCount} models`);
178
196
  const title = ` ${this.theme.fg("accent", this.theme.bold("◆ ANTIGRAVITY"))} ${this.theme.fg("dim", `· ${this.snapshot.authentication}`)}`;
179
197
  const lines = width >= 64
180
198
  ? [joinSides(title, `${right} `, width)]
@@ -194,7 +212,10 @@ export class AntigravityDashboard implements Component {
194
212
  for (let row = 0; row < bodyHeight; row++) lines.push(this.theme.fg("border", "│") + padLine(list[row] ?? "", inner) + this.theme.fg("border", "│"));
195
213
  }
196
214
  lines.push(frameBottom(this.theme, width));
197
- lines.push(truncateToWidth(`${this.theme.fg("accent", " ↑↓ / j k")} ${this.theme.fg("dim", "select")} ${this.theme.fg("accent", "r")} ${this.theme.fg("dim", "refresh")} ${this.theme.fg("accent", "d")} ${this.theme.fg("dim", "enable/disable")} ${this.theme.fg("accent", "x")} ${this.theme.fg("dim", "remove")} ${this.theme.fg("accent", "esc")} ${this.theme.fg("dim", "close")}`, width, ""));
215
+ const controls = this.pendingRemoval
216
+ ? `${this.theme.fg("warning", " x")} ${this.theme.fg("warning", "confirm permanent removal")} ${this.theme.fg("accent", "esc")} ${this.theme.fg("dim", "cancel")}`
217
+ : `${this.theme.fg("accent", " ↑↓ / j k")} ${this.theme.fg("dim", "select")} ${this.theme.fg("accent", "r")} ${this.theme.fg("dim", "refresh")} ${this.theme.fg("accent", "d")} ${this.theme.fg("dim", "enable/disable")} ${this.theme.fg("accent", "x")} ${this.theme.fg("dim", "remove")} ${this.theme.fg("accent", "esc")} ${this.theme.fg("dim", "close")}`;
218
+ lines.push(truncateToWidth(controls, width, ""));
198
219
  return lines.map((line) => truncateToWidth(line, width, ""));
199
220
  }
200
221
 
@@ -135,9 +135,7 @@ export default function antigravityProviderExtension(pi: ExtensionAPI) {
135
135
  if (enabled) await refreshAntigravityQuotas({ force: true, signal: ctx.signal });
136
136
  return dashboardSnapshot(ctx);
137
137
  },
138
- async (id, label) => {
139
- const confirmed = await ctx.ui.confirm("Remove Antigravity account?", `Permanently remove ${label} from this Pi installation?`);
140
- if (!confirmed) return dashboardSnapshot(ctx);
138
+ async (id) => {
141
139
  removeAntigravityAccount(id);
142
140
  await reconcileStoredCredential(ctx);
143
141
  return dashboardSnapshot(ctx);
@@ -14,4 +14,4 @@ Alternatively choose Pi's top-level **Use an API key** authentication method to
14
14
 
15
15
  Use `/logout` to remove the active Factory authentication selection. The configured key file is intentionally not deleted by logout.
16
16
 
17
- Open `/factory` to inspect every account, its Standard/Core usage, and rotation status. File-backed keys remain visible when disabled: select one and press `d` to enable/disable it, or press `x` and confirm to remove it permanently from `api-keys.json`. Environment-provided keys are read-only. For a smoke test, use the current `factory/kimi-k3` model.
17
+ Open `/factory` to inspect every account, its Standard/Core usage, and rotation status. File-backed keys remain visible when disabled: select one and press `d` to enable/disable it, or press `x` twice to confirm permanent removal from `api-keys.json`; Escape cancels an armed removal. Environment-provided keys are read-only. For a smoke test, use the current `factory/kimi-k3` model.
@@ -59,7 +59,7 @@ There are no `-oauth` or `-api-key` model duplicates. The user-curated catalog e
59
59
  /factory
60
60
  ```
61
61
 
62
- `/factory` opens a full-width account dashboard combining provider status, authentication, Droid/model metadata, rotation cooldowns, and separate Standard and Droid Core usage for every credential. Percentages are explicitly labeled as **used**. Navigate with the arrow keys or `j`/`k`, press `r` to force-refresh every account, use `d` to reversibly enable or disable a rotating file-backed key, use `x` plus confirmation to remove one permanently, and press Escape to close. Environment variables and single credentials managed by Pi auth remain read-only in this dashboard.
62
+ `/factory` opens a full-width account dashboard combining provider status, authentication, Droid/model metadata, rotation cooldowns, and separate Standard and Droid Core usage for every credential. Percentages are explicitly labeled as **used**. Navigate with the arrow keys or `j`/`k`, press `r` to force-refresh every account, use `d` to reversibly enable or disable a rotating file-backed key, press `x` twice to confirm and remove one permanently, and press Escape to cancel an armed removal or close the dashboard. Environment variables and single credentials managed by Pi auth remain read-only in this dashboard.
63
63
 
64
64
  Usage is never estimated from Pi token counts: Factory's API already applies model multipliers and cache-hit discounts. Cached records refresh at most every 15 minutes during normal sessions and after Factory runs; the manual command is the explicit force-refresh path.
65
65
 
@@ -119,6 +119,7 @@ function stateColor(state: string) {
119
119
  export class FactoryDashboard implements Component {
120
120
  private selected = 0;
121
121
  private refreshing = false;
122
+ private pendingRemoval: { id: string; label: string } | undefined;
122
123
  private closed = false;
123
124
  private readonly tui: TUI;
124
125
  private readonly theme: Theme;
@@ -190,20 +191,34 @@ export class FactoryDashboard implements Component {
190
191
  handleInput(data: string) {
191
192
  const accounts = this.snapshot.accounts;
192
193
  if (this.keys.matches(data, "tui.select.cancel")) {
194
+ if (this.pendingRemoval) {
195
+ this.pendingRemoval = undefined;
196
+ this.tui.requestRender();
197
+ return;
198
+ }
193
199
  this.closed = true;
194
200
  this.done();
195
201
  return;
196
202
  }
197
203
  if (this.keys.matches(data, "tui.select.up") || data === "k") {
204
+ this.pendingRemoval = undefined;
198
205
  if (accounts.length) {
199
206
  this.selected = (this.selected - 1 + accounts.length) % accounts.length;
200
207
  }
201
208
  } else if (this.keys.matches(data, "tui.select.down") || data === "j") {
209
+ this.pendingRemoval = undefined;
202
210
  if (accounts.length) this.selected = (this.selected + 1) % accounts.length;
203
211
  } else if (data === "r") {
212
+ this.pendingRemoval = undefined;
204
213
  this.startRefresh(true);
205
214
  } else if ((data === "d" || data === "x") && accounts[this.selected]?.editable && !this.refreshing) {
206
215
  const account = accounts[this.selected]!;
216
+ if (data === "x" && this.pendingRemoval?.id !== account.id) {
217
+ this.pendingRemoval = { id: account.id, label: account.label };
218
+ this.tui.requestRender();
219
+ return;
220
+ }
221
+ this.pendingRemoval = undefined;
207
222
  this.refreshing = true;
208
223
  const action = data === "x"
209
224
  ? this.removeAccount(account.id, account.label)
@@ -232,9 +247,11 @@ export class FactoryDashboard implements Component {
232
247
  const ready = `${this.snapshot.active}/${this.snapshot.configured} active`;
233
248
  const headerRight = this.refreshing
234
249
  ? this.theme.fg("warning", "refreshing…")
235
- : this.snapshot.warning
236
- ? this.theme.fg("warning", oneLine(this.snapshot.warning))
237
- : this.theme.fg("muted", `${ready} · ${this.snapshot.modelCount} models · Droid ${this.snapshot.version}`);
250
+ : this.pendingRemoval
251
+ ? this.theme.fg("warning", `press x again to remove ${oneLine(this.pendingRemoval.label)}`)
252
+ : this.snapshot.warning
253
+ ? this.theme.fg("warning", oneLine(this.snapshot.warning))
254
+ : this.theme.fg("muted", `${ready} · ${this.snapshot.modelCount} models · Droid ${this.snapshot.version}`);
238
255
  const title = ` ${this.theme.fg("accent", this.theme.bold("◆ FACTORY"))} ${this.theme.fg("dim", `· ${this.snapshot.authentication}`)}`;
239
256
  const lines = width >= 60
240
257
  ? [joinSides(title, `${headerRight} `, width)]
@@ -268,13 +285,10 @@ export class FactoryDashboard implements Component {
268
285
  }
269
286
  }
270
287
  lines.push(frameBottom(this.theme, width));
271
- lines.push(
272
- truncateToWidth(
273
- `${this.theme.fg("accent", " ↑↓ / j k")} ${this.theme.fg("dim", "select")} ${this.theme.fg("accent", "r")} ${this.theme.fg("dim", "refresh all")} ${this.theme.fg("accent", "d")} ${this.theme.fg("dim", "enable/disable")} ${this.theme.fg("accent", "x")} ${this.theme.fg("dim", "remove")} ${this.theme.fg("accent", "esc")} ${this.theme.fg("dim", "close")}`,
274
- width,
275
- "",
276
- ),
277
- );
288
+ const controls = this.pendingRemoval
289
+ ? `${this.theme.fg("warning", " x")} ${this.theme.fg("warning", "confirm permanent removal")} ${this.theme.fg("accent", "esc")} ${this.theme.fg("dim", "cancel")}`
290
+ : `${this.theme.fg("accent", " ↑↓ / j k")} ${this.theme.fg("dim", "select")} ${this.theme.fg("accent", "r")} ${this.theme.fg("dim", "refresh all")} ${this.theme.fg("accent", "d")} ${this.theme.fg("dim", "enable/disable")} ${this.theme.fg("accent", "x")} ${this.theme.fg("dim", "remove")} ${this.theme.fg("accent", "esc")} ${this.theme.fg("dim", "close")}`;
291
+ lines.push(truncateToWidth(controls, width, ""));
278
292
  return lines.map((line) => truncateToWidth(line, width, ""));
279
293
  }
280
294
 
@@ -223,9 +223,7 @@ export default async function factoryExtension(pi: ExtensionAPI) {
223
223
  if (enabled) await refreshLimits(ctx, true);
224
224
  return dashboardSnapshot(ctx);
225
225
  },
226
- async (id, label) => {
227
- const confirmed = await ctx.ui.confirm("Remove Factory account?", `Permanently remove ${label} from the rotating-key file?`);
228
- if (!confirmed) return dashboardSnapshot(ctx);
226
+ async (id) => {
229
227
  if (!removeFactoryApiKey(id)) throw new Error("This Factory credential is not editable from the rotating-key file.");
230
228
  return dashboardSnapshot(ctx);
231
229
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shariq-pi-extensions",
3
- "version": "0.2.25",
3
+ "version": "0.2.26",
4
4
  "description": "Cross-platform extension suite for the Pi coding agent.",
5
5
  "license": "MIT",
6
6
  "author": "Shariq Riaz",