pi-cliproxy-usage 0.1.1 → 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 +17 -8
- package/index.ts +15 -3
- package/package.json +14 -4
- 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
|
@@ -2,13 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
Compact CLIProxyAPI account usage meters for Pi Coding Agent.
|
|
4
4
|
|
|
5
|
-
Code layout:
|
|
6
|
-
|
|
7
|
-
- `index.ts` — Pi lifecycle and command wiring
|
|
8
|
-
- `src/settings.ts` / `src/settings-ui.ts` — persisted and interactive settings
|
|
9
|
-
- `src/usage.ts` / `src/parsers.ts` — account discovery, provider requests, response mapping
|
|
10
|
-
- `src/ui.ts` — widget rendering and text formatting
|
|
11
|
-
|
|
12
5
|
Shows one colored line below editor per enabled account:
|
|
13
6
|
|
|
14
7
|
```text
|
|
@@ -19,12 +12,19 @@ Shows one colored line below editor per enabled account:
|
|
|
19
12
|
|
|
20
13
|
Percentages and filled bars show usage **consumed**. Colors shift green → yellow at 70% → red at 90%. Codex intentionally shows Session only; weekly meter is omitted.
|
|
21
14
|
|
|
15
|
+
## Requirements
|
|
16
|
+
|
|
17
|
+
- [Pi Coding Agent](https://github.com/earendil-works/pi)
|
|
18
|
+
- [CLIProxyAPI](https://github.com/router-for-me/CLIProxyAPI) with at least one Claude, Codex, or Grok account configured
|
|
19
|
+
|
|
22
20
|
## Install
|
|
23
21
|
|
|
24
22
|
```bash
|
|
25
23
|
pi install npm:pi-cliproxy-usage
|
|
26
24
|
```
|
|
27
25
|
|
|
26
|
+
After installing or updating, run `/reload` in Pi.
|
|
27
|
+
|
|
28
28
|
Or test directly:
|
|
29
29
|
|
|
30
30
|
```bash
|
|
@@ -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
|
|
|
@@ -62,3 +63,11 @@ Accepted values: `accountsDir` is a non-empty string, `refreshMinutes` is an int
|
|
|
62
63
|
- `/cliproxy-usage config` — compatibility alias for `settings`
|
|
63
64
|
|
|
64
65
|
Token refresh is deliberately left to CLIProxyAPI. If provider returns `401`/`403`, let CLIProxyAPI refresh account or log in again.
|
|
66
|
+
|
|
67
|
+
## Screenshot
|
|
68
|
+
|
|
69
|
+

|
|
70
|
+
|
|
71
|
+
## Support
|
|
72
|
+
|
|
73
|
+
Report bugs and request features in [GitHub Issues](https://github.com/Villoh/pi-cliproxy-usage/issues).
|
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
|
@@ -1,17 +1,27 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-cliproxy-usage",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "Compact CLIProxyAPI account usage meters for Pi Coding Agent",
|
|
5
5
|
"type": "module",
|
|
6
|
-
"keywords": [
|
|
6
|
+
"keywords": [
|
|
7
|
+
"pi-package",
|
|
8
|
+
"cliproxyapi",
|
|
9
|
+
"usage"
|
|
10
|
+
],
|
|
7
11
|
"license": "MIT",
|
|
8
12
|
"repository": {
|
|
9
13
|
"type": "git",
|
|
10
14
|
"url": "git+https://github.com/Villoh/pi-cliproxy-usage.git"
|
|
11
15
|
},
|
|
12
|
-
"files": [
|
|
16
|
+
"files": [
|
|
17
|
+
"index.ts",
|
|
18
|
+
"src",
|
|
19
|
+
"README.md"
|
|
20
|
+
],
|
|
13
21
|
"pi": {
|
|
14
|
-
"extensions": [
|
|
22
|
+
"extensions": [
|
|
23
|
+
"./index.ts"
|
|
24
|
+
]
|
|
15
25
|
},
|
|
16
26
|
"peerDependencies": {
|
|
17
27
|
"@earendil-works/pi-coding-agent": "*",
|
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" },
|