@victor-software-house/pi-multicodex 2.1.5 → 2.2.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/commands.ts +251 -57
- package/package.json +1 -1
package/commands.ts
CHANGED
|
@@ -5,14 +5,15 @@ import type {
|
|
|
5
5
|
ExtensionAPI,
|
|
6
6
|
ExtensionCommandContext,
|
|
7
7
|
} from "@mariozechner/pi-coding-agent";
|
|
8
|
-
import {
|
|
8
|
+
import { DynamicBorder, rawKeyHint } from "@mariozechner/pi-coding-agent";
|
|
9
9
|
import {
|
|
10
10
|
type AutocompleteItem,
|
|
11
11
|
Container,
|
|
12
|
-
|
|
12
|
+
getKeybindings,
|
|
13
13
|
matchesKey,
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
Spacer,
|
|
15
|
+
truncateToWidth,
|
|
16
|
+
visibleWidth,
|
|
16
17
|
} from "@mariozechner/pi-tui";
|
|
17
18
|
import { getAgentSettingsPath } from "pi-provider-utils/agent-paths";
|
|
18
19
|
import { normalizeUnknownError } from "pi-provider-utils/streams";
|
|
@@ -20,7 +21,7 @@ import type { AccountManager } from "./account-manager";
|
|
|
20
21
|
import { writeActiveTokenToAuthJson } from "./auth";
|
|
21
22
|
import { openLoginInBrowser } from "./browser";
|
|
22
23
|
import type { createUsageStatusController } from "./status";
|
|
23
|
-
import { STORAGE_FILE } from "./storage";
|
|
24
|
+
import { type Account, STORAGE_FILE } from "./storage";
|
|
24
25
|
import { formatResetAt, isUsageUntouched } from "./usage";
|
|
25
26
|
|
|
26
27
|
const SETTINGS_FILE = getAgentSettingsPath();
|
|
@@ -87,35 +88,42 @@ function parseResetTarget(value: string): ResetTarget | undefined {
|
|
|
87
88
|
return undefined;
|
|
88
89
|
}
|
|
89
90
|
|
|
90
|
-
function
|
|
91
|
+
function isPlaceholderAccount(account: Account): boolean {
|
|
92
|
+
return (
|
|
93
|
+
!account.accessToken || !account.refreshToken || account.expiresAt <= 0
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function getAccountTags(
|
|
91
98
|
accountManager: AccountManager,
|
|
92
|
-
|
|
93
|
-
): string {
|
|
94
|
-
const account = accountManager.getAccount(email);
|
|
95
|
-
if (!account) return email;
|
|
99
|
+
account: Account,
|
|
100
|
+
): string[] {
|
|
96
101
|
const usage = accountManager.getCachedUsage(account.email);
|
|
97
102
|
const active = accountManager.getActiveAccount();
|
|
98
103
|
const manual = accountManager.getManualAccount();
|
|
99
104
|
const quotaHit =
|
|
100
105
|
account.quotaExhaustedUntil && account.quotaExhaustedUntil > Date.now();
|
|
101
|
-
const untouched = isUsageUntouched(usage) ? "untouched" : null;
|
|
102
106
|
const imported = account.importSource
|
|
103
107
|
? account.importMode === "synthetic"
|
|
104
108
|
? "pi auth only"
|
|
105
109
|
: "pi auth"
|
|
106
110
|
: null;
|
|
107
|
-
|
|
108
|
-
const tags = [
|
|
111
|
+
return [
|
|
109
112
|
active?.email === account.email ? "active" : null,
|
|
110
113
|
manual?.email === account.email ? "manual" : null,
|
|
111
|
-
reauth,
|
|
114
|
+
account.needsReauth ? "needs reauth" : null,
|
|
115
|
+
isPlaceholderAccount(account) ? "placeholder" : null,
|
|
112
116
|
quotaHit ? "quota" : null,
|
|
113
|
-
untouched,
|
|
117
|
+
isUsageUntouched(usage) ? "untouched" : null,
|
|
114
118
|
imported,
|
|
115
|
-
]
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
+
].filter((value): value is string => Boolean(value));
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function formatUsageSummary(
|
|
123
|
+
accountManager: AccountManager,
|
|
124
|
+
account: Account,
|
|
125
|
+
): string {
|
|
126
|
+
const usage = accountManager.getCachedUsage(account.email);
|
|
119
127
|
const primaryUsed = usage?.primary?.usedPercent;
|
|
120
128
|
const secondaryUsed = usage?.secondary?.usedPercent;
|
|
121
129
|
const primaryReset = usage?.primary?.resetAt;
|
|
@@ -124,8 +132,18 @@ function formatAccountStatusLine(
|
|
|
124
132
|
primaryUsed === undefined ? "unknown" : `${Math.round(primaryUsed)}%`;
|
|
125
133
|
const secondaryLabel =
|
|
126
134
|
secondaryUsed === undefined ? "unknown" : `${Math.round(secondaryUsed)}%`;
|
|
127
|
-
|
|
128
|
-
|
|
135
|
+
return `5h ${primaryLabel} reset:${formatResetAt(primaryReset)} | weekly ${secondaryLabel} reset:${formatResetAt(secondaryReset)}`;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function formatAccountStatusLine(
|
|
139
|
+
accountManager: AccountManager,
|
|
140
|
+
email: string,
|
|
141
|
+
): string {
|
|
142
|
+
const account = accountManager.getAccount(email);
|
|
143
|
+
if (!account) return email;
|
|
144
|
+
const tags = getAccountTags(accountManager, account).join(", ");
|
|
145
|
+
const suffix = tags ? ` (${tags})` : "";
|
|
146
|
+
return `${account.email}${suffix} - ${formatUsageSummary(accountManager, account)}`;
|
|
129
147
|
}
|
|
130
148
|
|
|
131
149
|
function getSubcommandCompletions(prefix: string): AutocompleteItem[] | null {
|
|
@@ -344,58 +362,234 @@ async function openAccountManagementPanel(
|
|
|
344
362
|
accountManager: AccountManager,
|
|
345
363
|
): Promise<AccountPanelResult> {
|
|
346
364
|
const accounts = accountManager.getAccounts();
|
|
347
|
-
const items = accounts.map((account) => ({
|
|
348
|
-
value: account.email,
|
|
349
|
-
label: formatAccountStatusLine(accountManager, account.email),
|
|
350
|
-
}));
|
|
351
365
|
|
|
352
|
-
return ctx.ui.custom<AccountPanelResult>((
|
|
353
|
-
const
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
)
|
|
366
|
+
return ctx.ui.custom<AccountPanelResult>((tui, theme, _kb, done) => {
|
|
367
|
+
const kb = getKeybindings();
|
|
368
|
+
let selectedIndex = 0;
|
|
369
|
+
const maxVisible = 12;
|
|
370
|
+
|
|
371
|
+
function getSelectedAccount(): Account | undefined {
|
|
372
|
+
return accounts[selectedIndex];
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
function findNextIndex(from: number, direction: number): number {
|
|
376
|
+
if (accounts.length === 0) return 0;
|
|
377
|
+
return Math.max(0, Math.min(accounts.length - 1, from + direction));
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
function renderTag(text: string): string {
|
|
381
|
+
if (text === "active") {
|
|
382
|
+
return theme.fg("accent", `[${text}]`);
|
|
383
|
+
}
|
|
384
|
+
if (text === "manual") {
|
|
385
|
+
return theme.fg("warning", `[${text}]`);
|
|
386
|
+
}
|
|
387
|
+
if (text === "needs reauth") {
|
|
388
|
+
return theme.fg("error", `[${text}]`);
|
|
389
|
+
}
|
|
390
|
+
if (text === "placeholder") {
|
|
391
|
+
return theme.fg("warning", `[${text}]`);
|
|
392
|
+
}
|
|
393
|
+
if (text === "quota") {
|
|
394
|
+
return theme.fg("warning", `[${text}]`);
|
|
395
|
+
}
|
|
396
|
+
if (text === "pi auth" || text === "pi auth only") {
|
|
397
|
+
return theme.fg("success", `[${text}]`);
|
|
398
|
+
}
|
|
399
|
+
return theme.fg("muted", `[${text}]`);
|
|
400
|
+
}
|
|
367
401
|
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
402
|
+
function renderRow(
|
|
403
|
+
account: Account,
|
|
404
|
+
selected: boolean,
|
|
405
|
+
width: number,
|
|
406
|
+
): string[] {
|
|
407
|
+
const cursor = selected ? theme.fg("accent", ">") : theme.fg("dim", " ");
|
|
408
|
+
const name = selected ? theme.bold(account.email) : account.email;
|
|
409
|
+
const tags = getAccountTags(accountManager, account)
|
|
410
|
+
.map((tag) => renderTag(tag))
|
|
411
|
+
.join(" ");
|
|
412
|
+
const primary = truncateToWidth(
|
|
413
|
+
`${cursor} ${name}${tags ? ` ${tags}` : ""}`,
|
|
414
|
+
width,
|
|
415
|
+
"",
|
|
416
|
+
);
|
|
417
|
+
const summaryColor = account.needsReauth
|
|
418
|
+
? "warning"
|
|
419
|
+
: isPlaceholderAccount(account)
|
|
420
|
+
? "muted"
|
|
421
|
+
: "dim";
|
|
422
|
+
const secondary = theme.fg(
|
|
423
|
+
summaryColor,
|
|
424
|
+
formatUsageSummary(accountManager, account),
|
|
425
|
+
);
|
|
426
|
+
return [primary, truncateToWidth(` ${secondary}`, width, "")];
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
const header = {
|
|
430
|
+
invalidate() {},
|
|
431
|
+
render(width: number): string[] {
|
|
432
|
+
const title = theme.bold("MultiCodex Accounts");
|
|
433
|
+
const sep = theme.fg("muted", " · ");
|
|
434
|
+
const hints = [
|
|
435
|
+
rawKeyHint("enter", "use"),
|
|
436
|
+
rawKeyHint("u", "refresh"),
|
|
437
|
+
rawKeyHint("r", "reauth"),
|
|
438
|
+
rawKeyHint("n", "add"),
|
|
439
|
+
rawKeyHint("backspace", "remove"),
|
|
440
|
+
rawKeyHint("esc", "close"),
|
|
441
|
+
].join(sep);
|
|
442
|
+
const spacing = Math.max(
|
|
443
|
+
1,
|
|
444
|
+
width - visibleWidth(title) - visibleWidth(hints),
|
|
445
|
+
);
|
|
446
|
+
const reauthCount = accountManager.getAccountsNeedingReauth().length;
|
|
447
|
+
const placeholderCount = accounts.filter((account) =>
|
|
448
|
+
isPlaceholderAccount(account),
|
|
449
|
+
).length;
|
|
450
|
+
const status = [
|
|
451
|
+
`${accounts.length} account${accounts.length === 1 ? "" : "s"}`,
|
|
452
|
+
reauthCount > 0 ? `${reauthCount} need reauth` : undefined,
|
|
453
|
+
placeholderCount > 0
|
|
454
|
+
? `${placeholderCount} placeholder${placeholderCount === 1 ? "" : "s"}`
|
|
455
|
+
: undefined,
|
|
456
|
+
]
|
|
457
|
+
.filter(Boolean)
|
|
458
|
+
.join(" · ");
|
|
459
|
+
return [
|
|
460
|
+
truncateToWidth(`${title}${" ".repeat(spacing)}${hints}`, width, ""),
|
|
461
|
+
theme.fg("muted", status),
|
|
462
|
+
];
|
|
463
|
+
},
|
|
371
464
|
};
|
|
372
|
-
|
|
373
|
-
|
|
465
|
+
|
|
466
|
+
const list = {
|
|
467
|
+
invalidate() {},
|
|
468
|
+
render(width: number): string[] {
|
|
469
|
+
const lines: string[] = [];
|
|
470
|
+
if (accounts.length === 0) {
|
|
471
|
+
return [theme.fg("muted", " No managed accounts")];
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
const visibleRows = Math.max(1, Math.floor(maxVisible / 2));
|
|
475
|
+
const startIndex = Math.max(
|
|
476
|
+
0,
|
|
477
|
+
Math.min(
|
|
478
|
+
selectedIndex - Math.floor(visibleRows / 2),
|
|
479
|
+
Math.max(0, accounts.length - visibleRows),
|
|
480
|
+
),
|
|
481
|
+
);
|
|
482
|
+
const endIndex = Math.min(accounts.length, startIndex + visibleRows);
|
|
483
|
+
|
|
484
|
+
for (let index = startIndex; index < endIndex; index++) {
|
|
485
|
+
const account = accounts[index];
|
|
486
|
+
if (!account) continue;
|
|
487
|
+
lines.push(...renderRow(account, index === selectedIndex, width));
|
|
488
|
+
if (index < endIndex - 1) {
|
|
489
|
+
lines.push("");
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
const selected = getSelectedAccount();
|
|
494
|
+
if (selected) {
|
|
495
|
+
lines.push("");
|
|
496
|
+
const detail = isPlaceholderAccount(selected)
|
|
497
|
+
? `selected: ${selected.email} · restored placeholder, re-auth required`
|
|
498
|
+
: `selected: ${selected.email}`;
|
|
499
|
+
lines.push(truncateToWidth(theme.fg("dim", detail), width, ""));
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
const current = selectedIndex + 1;
|
|
503
|
+
lines.push(
|
|
504
|
+
theme.fg(
|
|
505
|
+
"dim",
|
|
506
|
+
` ${current}/${accounts.length} visible account rows`,
|
|
507
|
+
),
|
|
508
|
+
);
|
|
509
|
+
return lines;
|
|
510
|
+
},
|
|
511
|
+
};
|
|
512
|
+
|
|
513
|
+
const container = new Container();
|
|
514
|
+
container.addChild(new Spacer(1));
|
|
515
|
+
container.addChild(new DynamicBorder());
|
|
516
|
+
container.addChild(new Spacer(1));
|
|
517
|
+
container.addChild(header);
|
|
518
|
+
container.addChild(new Spacer(1));
|
|
519
|
+
container.addChild(list);
|
|
520
|
+
container.addChild(new Spacer(1));
|
|
521
|
+
container.addChild(new DynamicBorder());
|
|
374
522
|
|
|
375
523
|
return {
|
|
376
|
-
render
|
|
377
|
-
|
|
378
|
-
|
|
524
|
+
render(width: number) {
|
|
525
|
+
return container.render(width);
|
|
526
|
+
},
|
|
527
|
+
invalidate() {
|
|
528
|
+
container.invalidate();
|
|
529
|
+
},
|
|
530
|
+
handleInput(data: string) {
|
|
531
|
+
if (kb.matches(data, "tui.select.up")) {
|
|
532
|
+
selectedIndex = findNextIndex(selectedIndex, -1);
|
|
533
|
+
tui.requestRender();
|
|
534
|
+
return;
|
|
535
|
+
}
|
|
536
|
+
if (kb.matches(data, "tui.select.down")) {
|
|
537
|
+
selectedIndex = findNextIndex(selectedIndex, 1);
|
|
538
|
+
tui.requestRender();
|
|
539
|
+
return;
|
|
540
|
+
}
|
|
541
|
+
if (kb.matches(data, "tui.select.pageUp")) {
|
|
542
|
+
selectedIndex = findNextIndex(selectedIndex, -5);
|
|
543
|
+
tui.requestRender();
|
|
544
|
+
return;
|
|
545
|
+
}
|
|
546
|
+
if (kb.matches(data, "tui.select.pageDown")) {
|
|
547
|
+
selectedIndex = findNextIndex(selectedIndex, 5);
|
|
548
|
+
tui.requestRender();
|
|
549
|
+
return;
|
|
550
|
+
}
|
|
551
|
+
if (
|
|
552
|
+
kb.matches(data, "tui.select.cancel") ||
|
|
553
|
+
matchesKey(data, "ctrl+c")
|
|
554
|
+
) {
|
|
555
|
+
done(undefined);
|
|
556
|
+
return;
|
|
557
|
+
}
|
|
558
|
+
if (
|
|
559
|
+
data === "\r" ||
|
|
560
|
+
data === "\n" ||
|
|
561
|
+
kb.matches(data, "tui.select.confirm")
|
|
562
|
+
) {
|
|
563
|
+
const selected = getSelectedAccount();
|
|
564
|
+
if (selected) {
|
|
565
|
+
done({ action: "select", email: selected.email });
|
|
566
|
+
}
|
|
567
|
+
return;
|
|
568
|
+
}
|
|
379
569
|
if (data.toLowerCase() === "n") {
|
|
380
570
|
done({ action: "add" });
|
|
381
571
|
return;
|
|
382
572
|
}
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
573
|
+
if (data.toLowerCase() === "u") {
|
|
574
|
+
const selected = getSelectedAccount();
|
|
575
|
+
if (selected) {
|
|
576
|
+
done({ action: "refresh", email: selected.email });
|
|
577
|
+
}
|
|
386
578
|
return;
|
|
387
579
|
}
|
|
388
|
-
if (
|
|
389
|
-
|
|
580
|
+
if (data.toLowerCase() === "r") {
|
|
581
|
+
const selected = getSelectedAccount();
|
|
582
|
+
if (selected) {
|
|
583
|
+
done({ action: "reauth", email: selected.email });
|
|
584
|
+
}
|
|
390
585
|
return;
|
|
391
586
|
}
|
|
392
|
-
if (matchesKey(data,
|
|
587
|
+
if (matchesKey(data, "backspace")) {
|
|
588
|
+
const selected = getSelectedAccount();
|
|
393
589
|
if (selected) {
|
|
394
|
-
done({ action: "remove", email: selected.
|
|
590
|
+
done({ action: "remove", email: selected.email });
|
|
395
591
|
}
|
|
396
|
-
return;
|
|
397
592
|
}
|
|
398
|
-
selectList.handleInput(data);
|
|
399
593
|
},
|
|
400
594
|
};
|
|
401
595
|
});
|