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 +3 -1
- package/package.json +1 -1
- package/src/index.ts +38 -7
- package/src/panel.ts +8 -7
- package/src/pool.ts +5 -1
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
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 =
|
|
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
|
|
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
|
|
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((
|
|
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
|
|
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 ·
|
|
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
|
|
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;
|