pi-chatgpt-pool 0.2.0 → 0.2.1

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
@@ -20,7 +20,7 @@ Run `/chatgpt-pool` and choose **+ Add account**, or add them directly:
20
20
  /login
21
21
  ```
22
22
 
23
- `/login` lists each account as `ChatGPT (<label>)`. Sign in to each one with a different ChatGPT account. Then pick a pooled model:
23
+ `/login` lists each account as `ChatGPT (<label>)`, or under a name you choose with `/chatgpt-pool name`. Renaming keeps the account signed in. Sign in to each one with a different ChatGPT account. Then pick a pooled model:
24
24
 
25
25
  ```text
26
26
  /model chatgpt/gpt-5.6-sol
@@ -34,6 +34,7 @@ The pool offers the ChatGPT subscription models: the OpenAI models that pi also
34
34
  | --- | --- |
35
35
  | `/chatgpt-pool` | Open the pool panel (see below) |
36
36
  | `/chatgpt-pool add <label>` | Add an account slot. Sign in to it with `/login` |
37
+ | `/chatgpt-pool name <label> <name>` | Set the name the account shows in `/login`, for example `Work (jal@mastra.ai)`. Leave the name empty to go back to `ChatGPT (<label>)` |
37
38
  | `/chatgpt-pool remove <label>` | Remove an account. Run `/logout` for it first to delete its token |
38
39
  | `/chatgpt-pool spend <label>` | Spend one banked reset on that account, after you confirm |
39
40
  | `/chatgpt-pool strategy <name>` | Set the routing strategy: `fill-first`, `round-robin`, `least-used`, or `use-it-or-lose-it` |
@@ -50,6 +51,7 @@ The command autocompletes its actions and account labels.
50
51
  | `↑` `↓` or `j` `k` | Select an account |
51
52
  | `s` | Spend a banked reset on the selected account, after you confirm |
52
53
  | `c` | Clear the selected account's recorded limit |
54
+ | `n` | Set the name the selected account shows in `/login` |
53
55
  | `d` | Remove the selected account |
54
56
  | `a` | Add an account |
55
57
  | `t` | Choose the strategy |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-chatgpt-pool",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "Pool several Sign in with ChatGPT accounts in pi, with routing strategies, failover, and banked resets",
5
5
  "keywords": [
6
6
  "pi-package",
package/src/index.ts CHANGED
@@ -8,6 +8,7 @@ import {
8
8
  isStrategy,
9
9
  failoverMessage,
10
10
  limitCooldown,
11
+ loginName,
11
12
  nextAccountId,
12
13
  orderAccounts,
13
14
  parseResetAt,
@@ -79,7 +80,7 @@ function subscriptionModels(providers: readonly Provider[]): Model<Api>[] {
79
80
  }
80
81
 
81
82
  function accountProvider(openai: Provider, account: Account, onReset: ResetListener): Provider {
82
- const name = `ChatGPT (${account.label})`;
83
+ const name = loginName(account);
83
84
  const signIn = openai.auth.oauth;
84
85
  if (!signIn) throw new Error("pi's openai provider has no Sign in with ChatGPT flow; update pi to 0.99.1 or later");
85
86
  const models: Model<Api>[] = subscriptionModels(builtinProviders()).map((model) => ({
@@ -283,7 +284,7 @@ export default function chatgptPool(pi: ExtensionAPI) {
283
284
 
284
285
  const describe = ({ account, signedIn, usage, error }: AccountStatus): string => {
285
286
  const limitedUntil = account.resetsAt && account.resetsAt > Date.now() ? account.resetsAt : undefined;
286
- if (!signedIn) return `${account.label} · not signed in, run /login and pick ChatGPT (${account.label})`;
287
+ if (!signedIn) return `${account.label} · not signed in, run /login and pick ${loginName(account)}`;
287
288
  const state = limitedUntil ? `limited until ${formatReset(limitedUntil)}` : "ready";
288
289
  if (!usage) return `${account.label} · ${state} · usage unavailable${error ? ` (${error})` : ""}`;
289
290
  const windows = usage.windows.map(
@@ -305,7 +306,25 @@ export default function chatgptPool(pi: ExtensionAPI) {
305
306
  update((config) => ({ ...config, accounts: [...config.accounts, account] }));
306
307
  registerPooledModels();
307
308
  pi.registerProvider(accountProvider(openai, account, onReset));
308
- ctx.ui.notify(`Added ChatGPT (${label}). Run /login and pick it to sign in.`, "info");
309
+ ctx.ui.notify(`Added ${loginName(account)}. Run /login and pick it to sign in.`, "info");
310
+ };
311
+
312
+ const setLoginName = (ctx: ExtensionContext, account: Account, name: string) => {
313
+ const next = { ...account, name: name.trim() || undefined };
314
+ update((config) => ({
315
+ ...config,
316
+ accounts: config.accounts.map((candidate) => (candidate.id === account.id ? next : candidate)),
317
+ }));
318
+ pi.registerProvider(accountProvider(openai, next, onReset));
319
+ ctx.ui.notify(`${account.label} now shows as "${loginName(next)}" in /login. Its sign-in is unchanged.`, "info");
320
+ };
321
+
322
+ const askLoginName = async (ctx: ExtensionContext, account: Account) => {
323
+ const name = await ctx.ui.input(
324
+ `Name shown in /login for ${account.label} (empty for "ChatGPT (${account.label})")`,
325
+ loginName(account),
326
+ );
327
+ if (name !== undefined) setLoginName(ctx, account, name);
309
328
  };
310
329
 
311
330
  const removeAccount = (ctx: ExtensionContext, account: Account) => {
@@ -386,6 +405,8 @@ export default function chatgptPool(pi: ExtensionAPI) {
386
405
  } else if (status && action.type === "spend") {
387
406
  await spend(ctx, status.account, status.usage);
388
407
  reload = true;
408
+ } else if (status && action.type === "name") {
409
+ await askLoginName(ctx, status.account);
389
410
  } else if (status && action.type === "clear") {
390
411
  setResetsAt(status.account.id, undefined);
391
412
  ctx.ui.notify(`ChatGPT (${status.account.label}) is back in rotation.`, "info");
@@ -441,12 +462,13 @@ export default function chatgptPool(pi: ExtensionAPI) {
441
462
  { value: "add", label: "add", description: "Add an account slot" },
442
463
  { value: "remove", label: "remove", description: "Remove an account" },
443
464
  { value: "spend", label: "spend", description: "Spend a banked reset, after confirming" },
465
+ { value: "name", label: "name", description: "Set the name an account shows in /login" },
444
466
  { value: "reset", label: "reset", description: "Forget recorded limits" },
445
467
  { value: "strategy", label: "strategy", description: "Choose how new conversations pick an account" },
446
468
  ];
447
469
 
448
470
  pi.registerCommand("chatgpt-pool", {
449
- description: "Manage pooled ChatGPT accounts: usage, banked resets, strategy, add, remove, spend, reset",
471
+ description: "Manage pooled ChatGPT accounts: usage, banked resets, strategy, add, remove, name, spend, reset",
450
472
  getArgumentCompletions(prefix) {
451
473
  const [action, ...rest] = prefix.split(" ");
452
474
  if (rest.length === 0) return SUBCOMMANDS.filter((item) => item.value.startsWith(action));
@@ -455,7 +477,8 @@ export default function chatgptPool(pi: ExtensionAPI) {
455
477
  .filter(([name]) => name.startsWith(rest.join(" ")))
456
478
  .map(([name, description]) => ({ value: `strategy ${name}`, label: name, description }));
457
479
  }
458
- if (action !== "remove" && action !== "spend") return null;
480
+ if (action !== "remove" && action !== "spend" && action !== "name") return null;
481
+ if (action === "name" && rest.length > 1) return null;
459
482
  const partial = rest.join(" ");
460
483
  return accounts
461
484
  .filter((account) => account.label.startsWith(partial))
@@ -476,8 +499,16 @@ export default function chatgptPool(pi: ExtensionAPI) {
476
499
  return removeAccount(ctx, account);
477
500
  }
478
501
 
502
+ if (action === "name") {
503
+ const [target = "", ...nameParts] = rest;
504
+ const account = findAccount(target);
505
+ if (!account) return ctx.ui.notify("Usage: /chatgpt-pool name <label> <name shown in /login>", "warning");
506
+ if (nameParts.length > 0 || !ctx.hasUI) return setLoginName(ctx, account, nameParts.join(" "));
507
+ return askLoginName(ctx, account);
508
+ }
509
+
479
510
  if (action === "reset") {
480
- update((config) => ({ ...config, accounts: config.accounts.map(({ id, label }) => ({ id, label })) }));
511
+ update((config) => ({ ...config, accounts: config.accounts.map((account) => ({ ...account, resetsAt: undefined })) }));
481
512
  updateStatus(ctx);
482
513
  return ctx.ui.notify("Cleared recorded limits. Every signed-in account is back in rotation.", "info");
483
514
  }
@@ -502,7 +533,7 @@ export default function chatgptPool(pi: ExtensionAPI) {
502
533
  return spend(ctx, account);
503
534
  }
504
535
 
505
- if (action) return ctx.ui.notify(`Unknown action "${action}". Use add, remove, spend, reset, or strategy.`, "warning");
536
+ if (action) return ctx.ui.notify(`Unknown action "${action}". Use add, remove, name, spend, reset, or strategy.`, "warning");
506
537
  if (accounts.length === 0) {
507
538
  const label = (await ctx.ui.input("No ChatGPT accounts yet. Label for the first one", "personal"))?.trim();
508
539
  if (label) addAccount(ctx, label);
package/src/panel.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import type { Theme } from "@earendil-works/pi-coding-agent";
2
2
  import { type Component, Key, matchesKey, truncateToWidth, type TUI, visibleWidth } from "@earendil-works/pi-tui";
3
3
  import { formatIn, formatWindow, usageBar } from "./format.ts";
4
- import type { Account } from "./pool.ts";
4
+ import { type Account, loginName } from "./pool.ts";
5
5
  import type { Usage } from "./usage.ts";
6
6
 
7
7
  export type AccountStatus = { account: Account; signedIn: boolean; usage?: Usage; error?: string };
@@ -11,12 +11,13 @@ export type PanelAction =
11
11
  | { type: "refresh" }
12
12
  | { type: "add" }
13
13
  | { type: "strategy" }
14
- | { type: "spend" | "clear" | "remove"; accountId: string };
14
+ | { type: "spend" | "clear" | "remove" | "name"; accountId: string };
15
15
 
16
- const ACCOUNT_KEYS = new Map<string, "spend" | "clear" | "remove">([
16
+ const ACCOUNT_KEYS = new Map<string, "spend" | "clear" | "remove" | "name">([
17
17
  ["s", "spend"],
18
18
  ["c", "clear"],
19
19
  ["d", "remove"],
20
+ ["n", "name"],
20
21
  ]);
21
22
  const POOL_KEYS = new Map<string, "refresh" | "add" | "strategy">([
22
23
  ["r", "refresh"],
@@ -25,9 +26,9 @@ const POOL_KEYS = new Map<string, "refresh" | "add" | "strategy">([
25
26
  ]);
26
27
  const BAR_WIDTH = 16;
27
28
  const HINTS = [
28
- "↑↓ select · s spend reset · c clear limit · d remove · a add · t strategy · r refresh · esc close",
29
- "↑↓ · s spend · c clear · d remove · a add · t strategy · r refresh · esc",
30
- "s spend · c clear · d del · a add · t strat · esc",
29
+ "↑↓ select · s spend reset · c clear limit · n login name · d remove · a add · t strategy · r refresh · esc close",
30
+ "↑↓ · s spend · c clear · n name · d remove · a add · t strategy · r refresh · esc",
31
+ "s spend · c clear · n name · d del · a add · esc",
31
32
  ];
32
33
 
33
34
  export class PoolPanel implements Component {
@@ -95,7 +96,7 @@ export class PoolPanel implements Component {
95
96
  : "";
96
97
  const header = `${pointer}${name}${active} ${state}${banked}`;
97
98
 
98
- if (!signedIn) return [header, theme.fg("dim", ` run /login and pick ChatGPT (${account.label})`)];
99
+ if (!signedIn) return [header, theme.fg("dim", ` run /login and pick ${loginName(account)}`)];
99
100
  if (!usage) return [header, theme.fg("dim", ` usage unavailable${error ? `: ${error}` : ""}`)];
100
101
  return [
101
102
  header,
package/src/pool.ts CHANGED
@@ -1,4 +1,8 @@
1
- export type Account = { id: string; label: string; resetsAt?: number };
1
+ export type Account = { id: string; label: string; name?: string; resetsAt?: number };
2
+
3
+ export function loginName(account: Account): string {
4
+ return account.name || `ChatGPT (${account.label})`;
5
+ }
2
6
 
3
7
  const USAGE_LIMIT = /usage.?limit|subscription_sharing_usage_limit_exceeded|insufficient_quota|quota/i;
4
8
  const RATE_LIMIT = /rate.?limit|too many requests|\b429\b/i;