pi-cliproxy-usage 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 -2
- package/index.ts +15 -3
- package/package.json +1 -1
- package/src/settings-ui.ts +15 -3
- package/src/settings.ts +10 -0
- package/src/types.ts +1 -0
- package/src/ui.ts +34 -4
package/README.md
CHANGED
|
@@ -41,6 +41,7 @@ Older `~/.pi/agent/extensions/pi-cliproxy-usage/config.json` files migrate autom
|
|
|
41
41
|
{
|
|
42
42
|
"accountsDir": "~/.cli-proxy-api",
|
|
43
43
|
"refreshMinutes": 5,
|
|
44
|
+
"maxVisibleAccounts": 4,
|
|
44
45
|
"providers": {
|
|
45
46
|
"claude": true,
|
|
46
47
|
"codex": true,
|
|
@@ -51,7 +52,7 @@ Older `~/.pi/agent/extensions/pi-cliproxy-usage/config.json` files migrate autom
|
|
|
51
52
|
|
|
52
53
|
Extension reads top-level CLIProxyAPI auth JSON files with `type` equal to `claude`, `codex`, or `xai`. Disabled auth files are skipped. Credentials stay local and are sent only to official provider usage endpoints documented by OpenUsage.
|
|
53
54
|
|
|
54
|
-
Accepted values: `accountsDir` is a non-empty string
|
|
55
|
+
Accepted values: `accountsDir` is a non-empty string; `refreshMinutes` and `maxVisibleAccounts` are integers of at least `1`; provider values are booleans. Widget prioritizes errors, then accounts with highest usage, and shows an overflow row when more accounts exist. Invalid values are ignored with a warning. Unknown fields are preserved when saving.
|
|
55
56
|
|
|
56
57
|
## Commands
|
|
57
58
|
|
|
@@ -65,7 +66,7 @@ Token refresh is deliberately left to CLIProxyAPI. If provider returns `401`/`40
|
|
|
65
66
|
|
|
66
67
|
## Screenshot
|
|
67
68
|
|
|
68
|
-

|
|
69
70
|
|
|
70
71
|
## Support
|
|
71
72
|
|
package/index.ts
CHANGED
|
@@ -31,7 +31,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
31
31
|
ctx.ui.notify(loaded.warnings.join("; "), "warning");
|
|
32
32
|
}
|
|
33
33
|
const items = await readAccounts(loaded.settings);
|
|
34
|
-
renderUsage(ctx, items);
|
|
34
|
+
renderUsage(ctx, items, loaded.settings.maxVisibleAccounts);
|
|
35
35
|
if (notify) {
|
|
36
36
|
ctx.ui.notify(
|
|
37
37
|
formatDetails(items),
|
|
@@ -48,8 +48,19 @@ export default function (pi: ExtensionAPI) {
|
|
|
48
48
|
timer.unref?.();
|
|
49
49
|
};
|
|
50
50
|
|
|
51
|
-
const applySettings = async (
|
|
51
|
+
const applySettings = async (
|
|
52
|
+
ctx: ExtensionContext,
|
|
53
|
+
settings: Settings,
|
|
54
|
+
changedId: string,
|
|
55
|
+
) => {
|
|
52
56
|
scheduleRefresh(ctx, settings.refreshMinutes);
|
|
57
|
+
// Only refetch when something that affects the fetched data changed.
|
|
58
|
+
// Interval/display-only changes (refreshMinutes, maxVisibleAccounts) must
|
|
59
|
+
// not trigger a request, or every settings tweak burns an API call and
|
|
60
|
+
// risks rate limiting.
|
|
61
|
+
if (changedId === "refreshMinutes" || changedId === "maxVisibleAccounts") {
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
53
64
|
await refresh(ctx);
|
|
54
65
|
};
|
|
55
66
|
|
|
@@ -64,6 +75,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
64
75
|
`Settings: ${loaded.path}`,
|
|
65
76
|
`Accounts: ${loaded.settings.accountsDir}`,
|
|
66
77
|
`Refresh: ${loaded.settings.refreshMinutes} min`,
|
|
78
|
+
`Visible accounts: ${loaded.settings.maxVisibleAccounts}`,
|
|
67
79
|
`Providers: ${providers || "none"}`,
|
|
68
80
|
`Source: ${Object.keys(loaded.raw).length ? "settings file" : "defaults"}`,
|
|
69
81
|
].join("\n"),
|
|
@@ -103,7 +115,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
103
115
|
ctx,
|
|
104
116
|
SETTINGS_PATH,
|
|
105
117
|
LEGACY_SETTINGS_PATH,
|
|
106
|
-
(settings) => applySettings(ctx, settings),
|
|
118
|
+
(settings, changedId) => applySettings(ctx, settings, changedId),
|
|
107
119
|
);
|
|
108
120
|
}
|
|
109
121
|
if (action === "status") return showStatus(ctx);
|
package/package.json
CHANGED
package/src/settings-ui.ts
CHANGED
|
@@ -18,7 +18,7 @@ export async function showSettings(
|
|
|
18
18
|
ctx: ExtensionCommandContext,
|
|
19
19
|
settingsPath: string,
|
|
20
20
|
legacySettingsPath: string,
|
|
21
|
-
onChange: (settings: Settings) => Promise<void>,
|
|
21
|
+
onChange: (settings: Settings, changedId: string) => Promise<void>,
|
|
22
22
|
): Promise<void> {
|
|
23
23
|
if (ctx.mode !== "tui") {
|
|
24
24
|
if (ctx.hasUI)
|
|
@@ -55,11 +55,18 @@ export async function showSettings(
|
|
|
55
55
|
},
|
|
56
56
|
{
|
|
57
57
|
id: "refreshMinutes",
|
|
58
|
-
label: "Refresh interval",
|
|
58
|
+
label: "Refresh interval (min)",
|
|
59
59
|
description: "Minutes between automatic usage refreshes",
|
|
60
60
|
currentValue: String(settings.refreshMinutes),
|
|
61
61
|
values: ["1", "5", "10", "15", "30", "60"],
|
|
62
62
|
},
|
|
63
|
+
{
|
|
64
|
+
id: "maxVisibleAccounts",
|
|
65
|
+
label: "Visible accounts",
|
|
66
|
+
description: "Maximum account rows shown below editor",
|
|
67
|
+
currentValue: String(settings.maxVisibleAccounts),
|
|
68
|
+
values: ["1", "2", "3", "4", "5", "10"],
|
|
69
|
+
},
|
|
63
70
|
...(["claude", "codex", "grok"] as const).map((provider) => ({
|
|
64
71
|
id: provider,
|
|
65
72
|
label: `${provider[0]?.toUpperCase()}${provider.slice(1)}`,
|
|
@@ -84,6 +91,9 @@ export async function showSettings(
|
|
|
84
91
|
const previous = structuredClone(settings);
|
|
85
92
|
if (id === "accountsDir") settings.accountsDir = value;
|
|
86
93
|
if (id === "refreshMinutes") settings.refreshMinutes = Number(value);
|
|
94
|
+
if (id === "maxVisibleAccounts") {
|
|
95
|
+
settings.maxVisibleAccounts = Number(value);
|
|
96
|
+
}
|
|
87
97
|
if (providerIds.has(id as ProviderName)) {
|
|
88
98
|
settings.providers[id as ProviderName] = value === "enabled";
|
|
89
99
|
}
|
|
@@ -91,7 +101,7 @@ export async function showSettings(
|
|
|
91
101
|
saveQueue = saveQueue
|
|
92
102
|
.then(async () => {
|
|
93
103
|
raw = await saveSettings(next, raw, settingsPath);
|
|
94
|
-
await onChange(next);
|
|
104
|
+
await onChange(next, id);
|
|
95
105
|
})
|
|
96
106
|
.catch((error) => {
|
|
97
107
|
settings = previous;
|
|
@@ -99,6 +109,8 @@ export async function showSettings(
|
|
|
99
109
|
if (id === "accountsDir") previousValue = previous.accountsDir;
|
|
100
110
|
else if (id === "refreshMinutes") {
|
|
101
111
|
previousValue = String(previous.refreshMinutes);
|
|
112
|
+
} else if (id === "maxVisibleAccounts") {
|
|
113
|
+
previousValue = String(previous.maxVisibleAccounts);
|
|
102
114
|
} else {
|
|
103
115
|
previousValue = previous.providers[id as ProviderName]
|
|
104
116
|
? "enabled"
|
package/src/settings.ts
CHANGED
|
@@ -5,6 +5,7 @@ import type { Settings } from "./types.js";
|
|
|
5
5
|
export const DEFAULT_SETTINGS: Settings = {
|
|
6
6
|
accountsDir: "~/.cli-proxy-api",
|
|
7
7
|
refreshMinutes: 5,
|
|
8
|
+
maxVisibleAccounts: 4,
|
|
8
9
|
providers: { claude: true, codex: true, grok: true },
|
|
9
10
|
};
|
|
10
11
|
|
|
@@ -47,6 +48,15 @@ export function normalizeSettings(value: unknown): {
|
|
|
47
48
|
} else if (value.refreshMinutes !== undefined) {
|
|
48
49
|
warnings.push("ignored invalid refreshMinutes");
|
|
49
50
|
}
|
|
51
|
+
if (
|
|
52
|
+
typeof value.maxVisibleAccounts === "number" &&
|
|
53
|
+
Number.isInteger(value.maxVisibleAccounts) &&
|
|
54
|
+
value.maxVisibleAccounts > 0
|
|
55
|
+
) {
|
|
56
|
+
settings.maxVisibleAccounts = value.maxVisibleAccounts;
|
|
57
|
+
} else if (value.maxVisibleAccounts !== undefined) {
|
|
58
|
+
warnings.push("ignored invalid maxVisibleAccounts");
|
|
59
|
+
}
|
|
50
60
|
for (const provider of ["claude", "codex", "grok"] as const) {
|
|
51
61
|
if (typeof providers[provider] === "boolean") {
|
|
52
62
|
settings.providers[provider] = providers[provider];
|
package/src/types.ts
CHANGED
package/src/ui.ts
CHANGED
|
@@ -13,8 +13,7 @@ const PROVIDER_LABELS: Record<ProviderName, string> = {
|
|
|
13
13
|
};
|
|
14
14
|
|
|
15
15
|
function accountLabel(label: string): string {
|
|
16
|
-
|
|
17
|
-
return local.slice(0, 18);
|
|
16
|
+
return label;
|
|
18
17
|
}
|
|
19
18
|
|
|
20
19
|
export function usageBar(used: number, width = 10): string {
|
|
@@ -80,18 +79,37 @@ export function clearUsage(ctx: UiContext): void {
|
|
|
80
79
|
ctx.ui.setWidget("cliproxy-usage", undefined);
|
|
81
80
|
}
|
|
82
81
|
|
|
83
|
-
export function renderUsage(
|
|
82
|
+
export function renderUsage(
|
|
83
|
+
ctx: UiContext,
|
|
84
|
+
items: AccountUsage[],
|
|
85
|
+
maxVisibleAccounts: number,
|
|
86
|
+
): void {
|
|
84
87
|
ctx.ui.setStatus("cliproxy-usage", undefined);
|
|
85
88
|
if (!items.length) {
|
|
86
89
|
ctx.ui.setWidget("cliproxy-usage", undefined);
|
|
87
90
|
return;
|
|
88
91
|
}
|
|
92
|
+
const visibleItems = items
|
|
93
|
+
.map((item, index) => ({
|
|
94
|
+
item,
|
|
95
|
+
index,
|
|
96
|
+
priority: item.error
|
|
97
|
+
? Number.POSITIVE_INFINITY
|
|
98
|
+
: Math.max(item.session?.used ?? -1, item.weekly?.used ?? -1),
|
|
99
|
+
}))
|
|
100
|
+
.sort(
|
|
101
|
+
(left, right) =>
|
|
102
|
+
right.priority - left.priority || left.index - right.index,
|
|
103
|
+
)
|
|
104
|
+
.slice(0, maxVisibleAccounts)
|
|
105
|
+
.map(({ item }) => item);
|
|
106
|
+
const hiddenCount = items.length - visibleItems.length;
|
|
89
107
|
ctx.ui.setWidget(
|
|
90
108
|
"cliproxy-usage",
|
|
91
109
|
(_tui: unknown, theme: Theme) => ({
|
|
92
110
|
invalidate() {},
|
|
93
111
|
render(width: number): string[] {
|
|
94
|
-
|
|
112
|
+
const lines = visibleItems.map((item) => {
|
|
95
113
|
const providerColor =
|
|
96
114
|
item.provider === "claude"
|
|
97
115
|
? "warning"
|
|
@@ -127,6 +145,18 @@ export function renderUsage(ctx: UiContext, items: AccountUsage[]): void {
|
|
|
127
145
|
width,
|
|
128
146
|
);
|
|
129
147
|
});
|
|
148
|
+
if (hiddenCount) {
|
|
149
|
+
lines.push(
|
|
150
|
+
truncateAnsi(
|
|
151
|
+
theme.fg(
|
|
152
|
+
"dim",
|
|
153
|
+
`… ${hiddenCount} more account${hiddenCount === 1 ? "" : "s"} · /cliproxy-usage for details`,
|
|
154
|
+
),
|
|
155
|
+
width,
|
|
156
|
+
),
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
return lines;
|
|
130
160
|
},
|
|
131
161
|
}),
|
|
132
162
|
{ placement: "belowEditor" },
|