@diegopetrucci/pi-extensions 0.1.10 → 0.1.12
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 +13 -121
- package/assets/minimal-footer-preview.png +0 -0
- package/extensions/minimal-footer/README.md +10 -0
- package/extensions/minimal-footer/index.ts +115 -4
- package/extensions/minimal-footer/openai-usage.ts +121 -0
- package/extensions/minimal-footer/package.json +2 -1
- package/extensions/oracle/README.md +2 -2
- package/extensions/oracle/index.ts +128 -8
- package/extensions/oracle/package.json +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,39 +1,33 @@
|
|
|
1
1
|
# pi-extensions
|
|
2
2
|
|
|
3
|
-
A collection of [pi](https://github.com/badlogic/pi-mono) agent extensions I made
|
|
3
|
+
A collection of [pi](https://github.com/badlogic/pi-mono) agent extensions I made:
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
- [`minimal-footer`](./extensions/minimal-footer): Replaces pi's built-in footer with a minimal two-line layout: branch/repo on the first line, context/model on the second, plus OpenAI Codex 5-hour and 7-day usage when available.
|
|
6
|
+
- [`oracle`](./extensions/oracle): Adds an Amp-style read-only oracle tool that auto-selects the strongest reasoning model on the current provider/subscription, covers pi’s built-in providers with hardcoded rankings, sets reasoning to xhigh by default, and shows live status while running.
|
|
7
|
+
- [`permission-gate`](./extensions/permission-gate): Prompts for confirmation before dangerous bash commands like `rm -rf`, `sudo`, and `chmod 777`.
|
|
8
|
+
- [`confirm-destructive`](./extensions/confirm-destructive): Confirms before destructive session actions like clear, switch, and fork.
|
|
9
|
+
- [`notify`](./extensions/notify): Sends configurable terminal, desktop, bell, and sound notifications when pi finishes and is ready for input.
|
|
6
10
|
|
|
7
|
-
|
|
8
|
-
|---|---|
|
|
9
|
-
| [`minimal-footer`](./extensions/minimal-footer) | Replaces pi's built-in footer with a minimal two-line layout: branch/repo on the first line, context/model on the second. |
|
|
10
|
-
| [`oracle`](./extensions/oracle) | Adds an Amp-style read-only oracle tool that auto-selects the strongest reasoning model on the current provider/subscription, covers pi’s built-in providers with hardcoded rankings, sets reasoning to xhigh by default, and shows live status while running. |
|
|
11
|
-
| [`permission-gate`](./extensions/permission-gate) | Prompts for confirmation before dangerous bash commands like `rm -rf`, `sudo`, and `chmod 777`. |
|
|
12
|
-
| [`confirm-destructive`](./extensions/confirm-destructive) | Confirms before destructive session actions like clear, switch, and fork. |
|
|
13
|
-
| [`notify`](./extensions/notify) | Sends configurable terminal, desktop, bell, and sound notifications when pi finishes and is ready for input. |
|
|
11
|
+
(For the full list of pi extensions I use, [check out my dotfiles](https://github.com/diegopetrucci/dot/blob/main/.pi/agent/settings.json).)
|
|
14
12
|
|
|
15
13
|
## Install
|
|
16
14
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
Install the repo as a pi package:
|
|
15
|
+
Full collection:
|
|
20
16
|
|
|
21
17
|
```bash
|
|
22
|
-
pi install
|
|
18
|
+
pi install npm:@diegopetrucci/pi-extensions
|
|
23
19
|
```
|
|
24
20
|
|
|
25
|
-
Or pin to
|
|
21
|
+
Or pin the GitHub package to this release:
|
|
26
22
|
|
|
27
23
|
```bash
|
|
28
|
-
pi install git:github.com/diegopetrucci/pi-extensions@v0.1.
|
|
24
|
+
pi install git:github.com/diegopetrucci/pi-extensions@v0.1.12
|
|
29
25
|
```
|
|
30
26
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
Install the full collection from npm:
|
|
27
|
+
Or a specific extension:
|
|
34
28
|
|
|
35
29
|
```bash
|
|
36
|
-
pi install npm:@diegopetrucci/pi-
|
|
30
|
+
pi install npm:@diegopetrucci/pi-oracle
|
|
37
31
|
```
|
|
38
32
|
|
|
39
33
|
Then reload pi:
|
|
@@ -41,105 +35,3 @@ Then reload pi:
|
|
|
41
35
|
```text
|
|
42
36
|
/reload
|
|
43
37
|
```
|
|
44
|
-
|
|
45
|
-
## Install only one extension
|
|
46
|
-
|
|
47
|
-
If you only want one extension, you have two options.
|
|
48
|
-
|
|
49
|
-
### Option 1: install the standalone npm package
|
|
50
|
-
|
|
51
|
-
```bash
|
|
52
|
-
pi install npm:@diegopetrucci/pi-minimal-footer
|
|
53
|
-
```
|
|
54
|
-
|
|
55
|
-
```bash
|
|
56
|
-
pi install npm:@diegopetrucci/pi-oracle
|
|
57
|
-
```
|
|
58
|
-
|
|
59
|
-
```bash
|
|
60
|
-
pi install npm:@diegopetrucci/pi-permission-gate
|
|
61
|
-
```
|
|
62
|
-
|
|
63
|
-
```bash
|
|
64
|
-
pi install npm:@diegopetrucci/pi-confirm-destructive
|
|
65
|
-
```
|
|
66
|
-
|
|
67
|
-
```bash
|
|
68
|
-
pi install npm:@diegopetrucci/pi-notify
|
|
69
|
-
```
|
|
70
|
-
|
|
71
|
-
### Option 2: filter the repo package
|
|
72
|
-
|
|
73
|
-
If you prefer the collection package, you can filter it in your pi settings.
|
|
74
|
-
|
|
75
|
-
Minimal footer only:
|
|
76
|
-
|
|
77
|
-
```json
|
|
78
|
-
{
|
|
79
|
-
"packages": [
|
|
80
|
-
{
|
|
81
|
-
"source": "npm:@diegopetrucci/pi-extensions",
|
|
82
|
-
"extensions": ["extensions/minimal-footer/index.ts"]
|
|
83
|
-
}
|
|
84
|
-
]
|
|
85
|
-
}
|
|
86
|
-
```
|
|
87
|
-
|
|
88
|
-
Oracle only:
|
|
89
|
-
|
|
90
|
-
```json
|
|
91
|
-
{
|
|
92
|
-
"packages": [
|
|
93
|
-
{
|
|
94
|
-
"source": "npm:@diegopetrucci/pi-extensions",
|
|
95
|
-
"extensions": ["extensions/oracle/index.ts"]
|
|
96
|
-
}
|
|
97
|
-
]
|
|
98
|
-
}
|
|
99
|
-
```
|
|
100
|
-
|
|
101
|
-
Permission gate only:
|
|
102
|
-
|
|
103
|
-
```json
|
|
104
|
-
{
|
|
105
|
-
"packages": [
|
|
106
|
-
{
|
|
107
|
-
"source": "npm:@diegopetrucci/pi-extensions",
|
|
108
|
-
"extensions": ["extensions/permission-gate/index.ts"]
|
|
109
|
-
}
|
|
110
|
-
]
|
|
111
|
-
}
|
|
112
|
-
```
|
|
113
|
-
|
|
114
|
-
Confirm destructive only:
|
|
115
|
-
|
|
116
|
-
```json
|
|
117
|
-
{
|
|
118
|
-
"packages": [
|
|
119
|
-
{
|
|
120
|
-
"source": "npm:@diegopetrucci/pi-extensions",
|
|
121
|
-
"extensions": ["extensions/confirm-destructive/index.ts"]
|
|
122
|
-
}
|
|
123
|
-
]
|
|
124
|
-
}
|
|
125
|
-
```
|
|
126
|
-
|
|
127
|
-
Notify only:
|
|
128
|
-
|
|
129
|
-
```json
|
|
130
|
-
{
|
|
131
|
-
"packages": [
|
|
132
|
-
{
|
|
133
|
-
"source": "npm:@diegopetrucci/pi-extensions",
|
|
134
|
-
"extensions": ["extensions/notify/index.ts"]
|
|
135
|
-
}
|
|
136
|
-
]
|
|
137
|
-
}
|
|
138
|
-
```
|
|
139
|
-
|
|
140
|
-
## npm publishing
|
|
141
|
-
|
|
142
|
-
The repo is set up to support both:
|
|
143
|
-
|
|
144
|
-
- the collection package: `@diegopetrucci/pi-extensions`
|
|
145
|
-
- standalone extension packages like `@diegopetrucci/pi-minimal-footer`, `@diegopetrucci/pi-oracle`, `@diegopetrucci/pi-permission-gate`, `@diegopetrucci/pi-confirm-destructive`, and `@diegopetrucci/pi-notify`
|
|
Binary file
|
|
@@ -10,6 +10,7 @@ It replaces pi's built-in footer with a cleaner two-line layout that focuses on
|
|
|
10
10
|
- current repo name
|
|
11
11
|
- current context percentage
|
|
12
12
|
- current model and thinking level
|
|
13
|
+
- OpenAI Codex 5-hour and 7-day usage when available
|
|
13
14
|
|
|
14
15
|
## Layout
|
|
15
16
|
|
|
@@ -27,6 +28,12 @@ fix/remove-detached-image-tasks SendItToMy
|
|
|
27
28
|
44.1% gpt-5.4 high
|
|
28
29
|
```
|
|
29
30
|
|
|
31
|
+
When using `openai-codex`, the bottom-left line also includes subscription usage:
|
|
32
|
+
|
|
33
|
+
```text
|
|
34
|
+
44.1% · 5h 12% · 7d 38%
|
|
35
|
+
```
|
|
36
|
+
|
|
30
37
|
On narrow terminals it falls back to one item per line.
|
|
31
38
|
|
|
32
39
|
## Install
|
|
@@ -60,6 +67,7 @@ Then reload pi:
|
|
|
60
67
|
- **Top left:** current git branch
|
|
61
68
|
- **Top right:** current repo directory name
|
|
62
69
|
- **Bottom left:** current context usage percentage
|
|
70
|
+
- **Bottom left on `openai-codex`:** current context usage percentage plus 5-hour and 7-day Codex usage
|
|
63
71
|
- **Bottom right:** model id and thinking level
|
|
64
72
|
|
|
65
73
|
## Publishing notes
|
|
@@ -72,3 +80,5 @@ This extension also lives inside the broader [`pi-extensions`](../../README.md)
|
|
|
72
80
|
- Uses pi footer data for git branch updates.
|
|
73
81
|
- Shows only context percentage, not context window size.
|
|
74
82
|
- Shows the model id rather than a provider-specific display label.
|
|
83
|
+
- For `openai-codex`, reads pi's stored OAuth login and fetches usage from ChatGPT's backend usage endpoint.
|
|
84
|
+
- Usage is cached briefly in memory and refreshed after turns.
|
|
@@ -1,14 +1,109 @@
|
|
|
1
1
|
import { basename } from "node:path";
|
|
2
|
-
import
|
|
2
|
+
import {
|
|
3
|
+
AuthStorage,
|
|
4
|
+
type ExtensionAPI,
|
|
5
|
+
type ExtensionContext,
|
|
6
|
+
} from "@mariozechner/pi-coding-agent";
|
|
3
7
|
import { truncateToWidth, visibleWidth } from "@mariozechner/pi-tui";
|
|
8
|
+
import {
|
|
9
|
+
fetchOpenAICodexUsage,
|
|
10
|
+
formatUsageSummary,
|
|
11
|
+
isOpenAICodexProvider,
|
|
12
|
+
type UsageSnapshot,
|
|
13
|
+
} from "./openai-usage";
|
|
14
|
+
|
|
15
|
+
const USAGE_CACHE_TTL_MS = 5 * 60 * 1000;
|
|
16
|
+
const USAGE_REQUEST_TIMEOUT_MS = 10 * 1000;
|
|
17
|
+
|
|
18
|
+
type UsageSessionState = {
|
|
19
|
+
authStorage: AuthStorage;
|
|
20
|
+
snapshot?: UsageSnapshot;
|
|
21
|
+
lastFetchedAt?: number;
|
|
22
|
+
loading: boolean;
|
|
23
|
+
error?: string;
|
|
24
|
+
inflight?: Promise<void>;
|
|
25
|
+
requestRender?: () => void;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
function clearUsageState(state: UsageSessionState): void {
|
|
29
|
+
state.snapshot = undefined;
|
|
30
|
+
state.lastFetchedAt = undefined;
|
|
31
|
+
state.loading = false;
|
|
32
|
+
state.error = undefined;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async function refreshUsageIfNeeded(
|
|
36
|
+
ctx: ExtensionContext,
|
|
37
|
+
state: UsageSessionState,
|
|
38
|
+
force = false,
|
|
39
|
+
): Promise<void> {
|
|
40
|
+
if (!isOpenAICodexProvider(ctx.model?.provider)) {
|
|
41
|
+
clearUsageState(state);
|
|
42
|
+
state.requestRender?.();
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const now = Date.now();
|
|
47
|
+
if (
|
|
48
|
+
!force &&
|
|
49
|
+
state.lastFetchedAt &&
|
|
50
|
+
now - state.lastFetchedAt < USAGE_CACHE_TTL_MS
|
|
51
|
+
) {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (state.inflight) {
|
|
56
|
+
return state.inflight;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
state.loading = true;
|
|
60
|
+
state.requestRender?.();
|
|
61
|
+
state.inflight = (async () => {
|
|
62
|
+
try {
|
|
63
|
+
const snapshot = await fetchOpenAICodexUsage(state.authStorage, {
|
|
64
|
+
timeoutMs: USAGE_REQUEST_TIMEOUT_MS,
|
|
65
|
+
});
|
|
66
|
+
if (snapshot) {
|
|
67
|
+
state.snapshot = snapshot;
|
|
68
|
+
state.lastFetchedAt = snapshot.fetchedAt;
|
|
69
|
+
state.error = undefined;
|
|
70
|
+
} else {
|
|
71
|
+
state.snapshot = undefined;
|
|
72
|
+
state.lastFetchedAt = Date.now();
|
|
73
|
+
state.error = undefined;
|
|
74
|
+
}
|
|
75
|
+
} catch (error) {
|
|
76
|
+
state.error = error instanceof Error ? error.message : String(error);
|
|
77
|
+
} finally {
|
|
78
|
+
state.loading = false;
|
|
79
|
+
state.inflight = undefined;
|
|
80
|
+
state.requestRender?.();
|
|
81
|
+
}
|
|
82
|
+
})();
|
|
83
|
+
|
|
84
|
+
return state.inflight;
|
|
85
|
+
}
|
|
4
86
|
|
|
5
87
|
export default function (pi: ExtensionAPI) {
|
|
6
|
-
|
|
88
|
+
const states = new WeakMap<object, UsageSessionState>();
|
|
89
|
+
|
|
90
|
+
pi.on("session_start", (_event, ctx) => {
|
|
91
|
+
const state: UsageSessionState = {
|
|
92
|
+
authStorage: AuthStorage.create(),
|
|
93
|
+
loading: false,
|
|
94
|
+
};
|
|
95
|
+
states.set(ctx.sessionManager, state);
|
|
96
|
+
|
|
7
97
|
ctx.ui.setFooter((tui, theme, footerData) => {
|
|
8
98
|
const unsub = footerData.onBranchChange(() => tui.requestRender());
|
|
99
|
+
state.requestRender = () => tui.requestRender();
|
|
100
|
+
void refreshUsageIfNeeded(ctx, state);
|
|
9
101
|
|
|
10
102
|
return {
|
|
11
|
-
dispose
|
|
103
|
+
dispose() {
|
|
104
|
+
if (state.requestRender) state.requestRender = undefined;
|
|
105
|
+
unsub();
|
|
106
|
+
},
|
|
12
107
|
invalidate() {},
|
|
13
108
|
render(width: number): string[] {
|
|
14
109
|
const repo = basename(ctx.cwd);
|
|
@@ -16,6 +111,10 @@ export default function (pi: ExtensionAPI) {
|
|
|
16
111
|
|
|
17
112
|
const usage = ctx.getContextUsage();
|
|
18
113
|
const context = usage?.percent == null ? "?" : `${usage.percent.toFixed(1)}%`;
|
|
114
|
+
const usageSummary = isOpenAICodexProvider(ctx.model?.provider)
|
|
115
|
+
? formatUsageSummary(state.snapshot)
|
|
116
|
+
: undefined;
|
|
117
|
+
const contextText = usageSummary ? `${context} · ${usageSummary}` : context;
|
|
19
118
|
|
|
20
119
|
const model = ctx.model?.id ?? "no-model";
|
|
21
120
|
const thinking = pi.getThinkingLevel();
|
|
@@ -23,7 +122,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
23
122
|
|
|
24
123
|
const branchStyled = theme.fg("dim", branch);
|
|
25
124
|
const repoStyled = theme.fg("dim", repo);
|
|
26
|
-
const contextStyled = theme.fg("dim",
|
|
125
|
+
const contextStyled = theme.fg("dim", contextText);
|
|
27
126
|
const modelStyled = theme.fg("dim", modelText);
|
|
28
127
|
|
|
29
128
|
const renderSplitLine = (left: string, right: string): string => {
|
|
@@ -51,4 +150,16 @@ export default function (pi: ExtensionAPI) {
|
|
|
51
150
|
};
|
|
52
151
|
});
|
|
53
152
|
});
|
|
153
|
+
|
|
154
|
+
pi.on("model_select", (_event, ctx) => {
|
|
155
|
+
const state = states.get(ctx.sessionManager);
|
|
156
|
+
if (!state) return;
|
|
157
|
+
void refreshUsageIfNeeded(ctx, state, true);
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
pi.on("turn_end", (_event, ctx) => {
|
|
161
|
+
const state = states.get(ctx.sessionManager);
|
|
162
|
+
if (!state) return;
|
|
163
|
+
void refreshUsageIfNeeded(ctx, state);
|
|
164
|
+
});
|
|
54
165
|
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { AuthStorage } from "@mariozechner/pi-coding-agent";
|
|
2
|
+
|
|
3
|
+
const PROVIDER_ID = "openai-codex";
|
|
4
|
+
|
|
5
|
+
interface WhamUsageWindow {
|
|
6
|
+
reset_at?: number;
|
|
7
|
+
used_percent?: number;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
interface WhamUsageResponse {
|
|
11
|
+
rate_limit?: {
|
|
12
|
+
primary_window?: WhamUsageWindow;
|
|
13
|
+
secondary_window?: WhamUsageWindow;
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface UsageWindow {
|
|
18
|
+
usedPercent?: number;
|
|
19
|
+
resetAt?: number;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface UsageSnapshot {
|
|
23
|
+
primary?: UsageWindow;
|
|
24
|
+
secondary?: UsageWindow;
|
|
25
|
+
fetchedAt: number;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function normalizeUsedPercent(value?: number): number | undefined {
|
|
29
|
+
if (typeof value !== "number" || !Number.isFinite(value)) return undefined;
|
|
30
|
+
return Math.min(100, Math.max(0, value));
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function normalizeResetAt(value?: number): number | undefined {
|
|
34
|
+
if (typeof value !== "number" || !Number.isFinite(value)) return undefined;
|
|
35
|
+
return value * 1000;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function parseUsageWindow(window?: WhamUsageWindow): UsageWindow | undefined {
|
|
39
|
+
if (!window) return undefined;
|
|
40
|
+
const usedPercent = normalizeUsedPercent(window.used_percent);
|
|
41
|
+
const resetAt = normalizeResetAt(window.reset_at);
|
|
42
|
+
if (usedPercent === undefined && resetAt === undefined) return undefined;
|
|
43
|
+
return { usedPercent, resetAt };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function parseUsageSnapshot(data: WhamUsageResponse): Omit<UsageSnapshot, "fetchedAt"> {
|
|
47
|
+
return {
|
|
48
|
+
primary: parseUsageWindow(data.rate_limit?.primary_window),
|
|
49
|
+
secondary: parseUsageWindow(data.rate_limit?.secondary_window),
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function getOAuthAccountId(authStorage: AuthStorage): string | undefined {
|
|
54
|
+
const credential = authStorage.get(PROVIDER_ID);
|
|
55
|
+
if (!credential || credential.type !== "oauth") return undefined;
|
|
56
|
+
const accountId = (credential as { accountId?: unknown }).accountId;
|
|
57
|
+
return typeof accountId === "string" && accountId.trim()
|
|
58
|
+
? accountId.trim()
|
|
59
|
+
: undefined;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function formatUsagePercent(value?: number): string | undefined {
|
|
63
|
+
if (typeof value !== "number" || !Number.isFinite(value)) return undefined;
|
|
64
|
+
return `${Math.round(value)}%`;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function isOpenAICodexProvider(provider?: string): boolean {
|
|
68
|
+
return provider === PROVIDER_ID;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function formatUsageSummary(snapshot?: UsageSnapshot): string | undefined {
|
|
72
|
+
if (!snapshot) return undefined;
|
|
73
|
+
|
|
74
|
+
const primary = formatUsagePercent(snapshot.primary?.usedPercent);
|
|
75
|
+
const secondary = formatUsagePercent(snapshot.secondary?.usedPercent);
|
|
76
|
+
const parts: string[] = [];
|
|
77
|
+
|
|
78
|
+
if (primary) parts.push(`5h ${primary}`);
|
|
79
|
+
if (secondary) parts.push(`7d ${secondary}`);
|
|
80
|
+
|
|
81
|
+
return parts.length > 0 ? parts.join(" · ") : undefined;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export async function fetchOpenAICodexUsage(
|
|
85
|
+
authStorage: AuthStorage,
|
|
86
|
+
options?: { timeoutMs?: number },
|
|
87
|
+
): Promise<UsageSnapshot | undefined> {
|
|
88
|
+
const accessToken = await authStorage.getApiKey(PROVIDER_ID, {
|
|
89
|
+
includeFallback: false,
|
|
90
|
+
});
|
|
91
|
+
if (!accessToken) return undefined;
|
|
92
|
+
|
|
93
|
+
authStorage.reload();
|
|
94
|
+
const accountId = getOAuthAccountId(authStorage);
|
|
95
|
+
const controller = new AbortController();
|
|
96
|
+
const timeoutMs = options?.timeoutMs ?? 10_000;
|
|
97
|
+
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
98
|
+
|
|
99
|
+
try {
|
|
100
|
+
const headers: Record<string, string> = {
|
|
101
|
+
Authorization: `Bearer ${accessToken}`,
|
|
102
|
+
Accept: "application/json",
|
|
103
|
+
};
|
|
104
|
+
if (accountId) {
|
|
105
|
+
headers["ChatGPT-Account-Id"] = accountId;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const response = await fetch("https://chatgpt.com/backend-api/wham/usage", {
|
|
109
|
+
headers,
|
|
110
|
+
signal: controller.signal,
|
|
111
|
+
});
|
|
112
|
+
if (!response.ok) {
|
|
113
|
+
throw new Error(`Usage request failed: ${response.status}`);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const data = (await response.json()) as WhamUsageResponse;
|
|
117
|
+
return { ...parseUsageSnapshot(data), fetchedAt: Date.now() };
|
|
118
|
+
} finally {
|
|
119
|
+
clearTimeout(timeout);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@diegopetrucci/pi-minimal-footer",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"description": "A minimal custom footer for pi.",
|
|
5
5
|
"keywords": ["pi-package", "pi", "terminal", "footer"],
|
|
6
6
|
"license": "MIT",
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
},
|
|
12
12
|
"files": [
|
|
13
13
|
"index.ts",
|
|
14
|
+
"openai-usage.ts",
|
|
14
15
|
"README.md"
|
|
15
16
|
],
|
|
16
17
|
"publishConfig": {
|
|
@@ -27,7 +27,7 @@ By default, the extension:
|
|
|
27
27
|
4. tries a provider-specific hardcoded priority list first
|
|
28
28
|
5. falls back to a heuristic that favors stronger tiers like `opus`, `pro`, newer versions, and penalizes `mini`, `flash`, `haiku`, `spark`, etc.
|
|
29
29
|
|
|
30
|
-
The hardcoded rankings now cover pi's built-in provider set, including OpenAI/Codex, Anthropic, Google variants, GitHub Copilot, Bedrock, Azure OpenAI Responses, Groq, Hugging Face, Kimi, MiniMax, Mistral, OpenCode, OpenRouter, Vercel AI Gateway, xAI, ZAI, and Cerebras.
|
|
30
|
+
The hardcoded rankings now cover pi's built-in provider set, including OpenAI/Codex, Anthropic, Google variants, GitHub Copilot, Bedrock, Azure OpenAI Responses, Cloudflare, DeepSeek, Fireworks, Groq, Hugging Face, Kimi/Moonshot, MiniMax, Mistral, OpenCode, OpenRouter, Vercel AI Gateway, xAI, ZAI, and Cerebras.
|
|
31
31
|
|
|
32
32
|
If no reasoning model exists on the current provider, it falls back to the best available model on that provider.
|
|
33
33
|
|
|
@@ -43,7 +43,7 @@ Use `/oracle-model` inside pi to see what it would pick right now.
|
|
|
43
43
|
|
|
44
44
|
See also:
|
|
45
45
|
- [Oracle provider matrix](../../docs/oracle-provider-matrix.md)
|
|
46
|
-
- [v0.1.
|
|
46
|
+
- [v0.1.12 release notes](../../docs/release-notes-v0.1.12.md)
|
|
47
47
|
|
|
48
48
|
## Install
|
|
49
49
|
|
|
@@ -72,11 +72,16 @@ const PROVIDER_MODEL_PREFERENCES: Record<string, string[]> = {
|
|
|
72
72
|
"claude-sonnet-4-6",
|
|
73
73
|
"claude-sonnet-4-5",
|
|
74
74
|
"claude-sonnet-4",
|
|
75
|
+
"deepseek.v3.2",
|
|
75
76
|
"deepseek.r1",
|
|
76
77
|
"kimi-k2.5",
|
|
78
|
+
"minimax-m2.5",
|
|
77
79
|
"minimax-m2.1",
|
|
80
|
+
"zai.glm-5",
|
|
78
81
|
],
|
|
79
82
|
anthropic: [
|
|
83
|
+
"claude-opus-4-7",
|
|
84
|
+
"claude-opus-4.7",
|
|
80
85
|
"claude-opus-4-6",
|
|
81
86
|
"claude-opus-4.6",
|
|
82
87
|
"claude-opus-4-5",
|
|
@@ -94,8 +99,13 @@ const PROVIDER_MODEL_PREFERENCES: Record<string, string[]> = {
|
|
|
94
99
|
"claude-3-7-sonnet",
|
|
95
100
|
],
|
|
96
101
|
"azure-openai-responses": [
|
|
102
|
+
"gpt-5.5-pro",
|
|
103
|
+
"gpt-5.5",
|
|
97
104
|
"gpt-5.4-pro",
|
|
105
|
+
"gpt-5.4",
|
|
106
|
+
"gpt-5.3-codex",
|
|
98
107
|
"gpt-5-pro",
|
|
108
|
+
"gpt-5.2-pro",
|
|
99
109
|
"gpt-5.2",
|
|
100
110
|
"gpt-5.2-codex",
|
|
101
111
|
"gpt-5.1-codex-max",
|
|
@@ -106,20 +116,53 @@ const PROVIDER_MODEL_PREFERENCES: Record<string, string[]> = {
|
|
|
106
116
|
"gpt-5.4-mini",
|
|
107
117
|
"gpt-5-mini",
|
|
108
118
|
],
|
|
109
|
-
cerebras: ["zai-glm-4.7", "llama3.1-8b"],
|
|
119
|
+
cerebras: ["gpt-oss-120b", "zai-glm-4.7", "llama3.1-8b"],
|
|
120
|
+
"cloudflare-ai-gateway": [
|
|
121
|
+
"claude-opus-4-7",
|
|
122
|
+
"claude-opus-4-6",
|
|
123
|
+
"claude-opus-4-5",
|
|
124
|
+
"gpt-5.5",
|
|
125
|
+
"gpt-5.4",
|
|
126
|
+
"gpt-5.3-codex",
|
|
127
|
+
"workers-ai/@cf/moonshotai/kimi-k2.6",
|
|
128
|
+
"workers-ai/@cf/nvidia/nemotron-3-120b-a12b",
|
|
129
|
+
"workers-ai/@cf/zai-org/glm-4.7-flash",
|
|
130
|
+
],
|
|
131
|
+
"cloudflare-workers-ai": [
|
|
132
|
+
"@cf/moonshotai/kimi-k2.6",
|
|
133
|
+
"@cf/nvidia/nemotron-3-120b-a12b",
|
|
134
|
+
"@cf/moonshotai/kimi-k2.5",
|
|
135
|
+
"@cf/openai/gpt-oss-120b",
|
|
136
|
+
"@cf/zai-org/glm-4.7-flash",
|
|
137
|
+
],
|
|
138
|
+
deepseek: ["deepseek-v4-pro", "deepseek-v4-flash"],
|
|
139
|
+
fireworks: [
|
|
140
|
+
"accounts/fireworks/models/deepseek-v4-pro",
|
|
141
|
+
"accounts/fireworks/models/kimi-k2p6",
|
|
142
|
+
"accounts/fireworks/models/glm-5p1",
|
|
143
|
+
"accounts/fireworks/models/minimax-m2p7",
|
|
144
|
+
"accounts/fireworks/models/qwen3p6-plus",
|
|
145
|
+
"accounts/fireworks/models/gpt-oss-120b",
|
|
146
|
+
],
|
|
110
147
|
"github-copilot": [
|
|
111
148
|
"claude-opus-4.7",
|
|
149
|
+
"claude-opus-4.6",
|
|
112
150
|
"claude-opus-4.5",
|
|
151
|
+
"gpt-5.5",
|
|
152
|
+
"gpt-5.4",
|
|
153
|
+
"gpt-5.3-codex",
|
|
113
154
|
"gpt-5.2",
|
|
114
155
|
"gpt-5.1-codex-max",
|
|
115
156
|
"gpt-5.1",
|
|
116
157
|
"gpt-5",
|
|
117
|
-
"
|
|
158
|
+
"gemini-3.1-pro-preview",
|
|
118
159
|
"gemini-3-pro-preview",
|
|
160
|
+
"claude-sonnet-4.6",
|
|
119
161
|
"claude-sonnet-4.5",
|
|
120
162
|
"gemini-2.5-pro",
|
|
121
163
|
],
|
|
122
164
|
google: [
|
|
165
|
+
"gemini-3.1-pro-preview-customtools",
|
|
123
166
|
"gemini-3.1-pro-preview",
|
|
124
167
|
"gemini-3-pro-preview",
|
|
125
168
|
"gemini-2.5-pro-preview",
|
|
@@ -138,6 +181,7 @@ const PROVIDER_MODEL_PREFERENCES: Record<string, string[]> = {
|
|
|
138
181
|
"google-gemini-cli": ["gemini-3-pro-preview", "gemini-2.5-pro", "gemini-1.5-flash"],
|
|
139
182
|
"google-vertex": [
|
|
140
183
|
"gemini-3.1-pro-preview-customtools",
|
|
184
|
+
"gemini-3.1-pro-preview",
|
|
141
185
|
"gemini-3-pro-preview",
|
|
142
186
|
"gemini-2.5-pro",
|
|
143
187
|
"gemini-2.5-flash-lite",
|
|
@@ -152,16 +196,23 @@ const PROVIDER_MODEL_PREFERENCES: Record<string, string[]> = {
|
|
|
152
196
|
],
|
|
153
197
|
huggingface: [
|
|
154
198
|
"zai-org/GLM-5.1",
|
|
199
|
+
"deepseek-ai/DeepSeek-V4-Pro",
|
|
200
|
+
"moonshotai/Kimi-K2.6",
|
|
201
|
+
"MiniMaxAI/MiniMax-M2.7",
|
|
202
|
+
"Qwen/Qwen3.5-397B-A17B",
|
|
155
203
|
"Qwen/Qwen3-235B-A22B-Thinking-2507",
|
|
156
204
|
"moonshotai/Kimi-K2.5",
|
|
157
205
|
"deepseek-ai/DeepSeek-V3.2",
|
|
158
206
|
"MiniMaxAI/MiniMax-M2.5",
|
|
159
207
|
"Qwen/Qwen3-Coder-Next",
|
|
160
208
|
],
|
|
161
|
-
"kimi-coding": ["kimi-k2-thinking"],
|
|
162
|
-
minimax: ["MiniMax-M2.7-highspeed"],
|
|
163
|
-
"minimax-cn": ["MiniMax-M2.7-highspeed"],
|
|
209
|
+
"kimi-coding": ["k2p6", "kimi-k2-thinking", "kimi-for-coding"],
|
|
210
|
+
minimax: ["MiniMax-M2.7-highspeed", "MiniMax-M2.7"],
|
|
211
|
+
"minimax-cn": ["MiniMax-M2.7-highspeed", "MiniMax-M2.7"],
|
|
164
212
|
mistral: [
|
|
213
|
+
"mistral-medium-2604",
|
|
214
|
+
"mistral-medium-3.5",
|
|
215
|
+
"mistral-medium-latest",
|
|
165
216
|
"magistral-medium-latest",
|
|
166
217
|
"devstral-medium-latest",
|
|
167
218
|
"mistral-large-latest",
|
|
@@ -171,8 +222,13 @@ const PROVIDER_MODEL_PREFERENCES: Record<string, string[]> = {
|
|
|
171
222
|
"devstral-2512",
|
|
172
223
|
],
|
|
173
224
|
openai: [
|
|
225
|
+
"gpt-5.5-pro",
|
|
226
|
+
"gpt-5.5",
|
|
174
227
|
"gpt-5.4-pro",
|
|
228
|
+
"gpt-5.4",
|
|
229
|
+
"gpt-5.3-codex",
|
|
175
230
|
"gpt-5-pro",
|
|
231
|
+
"gpt-5.2-pro",
|
|
176
232
|
"gpt-5.2",
|
|
177
233
|
"gpt-5.2-codex",
|
|
178
234
|
"gpt-5.1-codex-max",
|
|
@@ -184,6 +240,7 @@ const PROVIDER_MODEL_PREFERENCES: Record<string, string[]> = {
|
|
|
184
240
|
"gpt-5-mini",
|
|
185
241
|
],
|
|
186
242
|
"openai-codex": [
|
|
243
|
+
"gpt-5.5",
|
|
187
244
|
"gpt-5.4",
|
|
188
245
|
"gpt-5.3-codex",
|
|
189
246
|
"gpt-5.2",
|
|
@@ -193,41 +250,104 @@ const PROVIDER_MODEL_PREFERENCES: Record<string, string[]> = {
|
|
|
193
250
|
"big-pickle",
|
|
194
251
|
],
|
|
195
252
|
opencode: [
|
|
253
|
+
"gpt-5.5-pro",
|
|
254
|
+
"gpt-5.5",
|
|
255
|
+
"gpt-5.4-pro",
|
|
196
256
|
"gpt-5.4",
|
|
197
257
|
"claude-opus-4-7",
|
|
258
|
+
"claude-opus-4-6",
|
|
198
259
|
"claude-opus-4-5",
|
|
260
|
+
"gpt-5.3-codex",
|
|
199
261
|
"gpt-5.2-codex",
|
|
200
262
|
"gpt-5.1-codex",
|
|
263
|
+
"gemini-3.1-pro",
|
|
264
|
+
"glm-5.1",
|
|
201
265
|
"glm-5",
|
|
266
|
+
"kimi-k2.6",
|
|
202
267
|
"kimi-k2.5",
|
|
268
|
+
"qwen3.6-plus",
|
|
203
269
|
"qwen3.5-plus",
|
|
270
|
+
"minimax-m2.7",
|
|
204
271
|
"minimax-m2.5-free",
|
|
205
272
|
],
|
|
206
|
-
"opencode-go": [
|
|
273
|
+
"opencode-go": [
|
|
274
|
+
"deepseek-v4-pro",
|
|
275
|
+
"glm-5.1",
|
|
276
|
+
"qwen3.6-plus",
|
|
277
|
+
"mimo-v2.5-pro",
|
|
278
|
+
"mimo-v2-pro",
|
|
279
|
+
"minimax-m2.7",
|
|
280
|
+
"kimi-k2.6",
|
|
281
|
+
"kimi-k2.5",
|
|
282
|
+
],
|
|
207
283
|
openrouter: [
|
|
284
|
+
"anthropic/claude-opus-4.7",
|
|
208
285
|
"anthropic/claude-opus-4.6-fast",
|
|
286
|
+
"anthropic/claude-opus-4.6",
|
|
209
287
|
"anthropic/claude-opus-4.5",
|
|
210
288
|
"anthropic/claude-opus-4",
|
|
289
|
+
"openai/gpt-5.5-pro",
|
|
290
|
+
"openai/gpt-5.5",
|
|
291
|
+
"openai/gpt-5.4-pro",
|
|
292
|
+
"openai/gpt-5.4",
|
|
211
293
|
"google/gemini-3.1-pro-preview-customtools",
|
|
294
|
+
"google/gemini-3.1-pro-preview",
|
|
212
295
|
"google/gemini-2.5-pro",
|
|
296
|
+
"moonshotai/kimi-k2.6",
|
|
213
297
|
"moonshotai/kimi-k2-thinking",
|
|
298
|
+
"deepseek/deepseek-v4-pro",
|
|
214
299
|
"deepseek/deepseek-r1",
|
|
215
300
|
"deepseek/deepseek-v3.2",
|
|
301
|
+
"minimax/minimax-m2.7",
|
|
216
302
|
"minimax/minimax-m2.1",
|
|
303
|
+
"z-ai/glm-5.1",
|
|
217
304
|
],
|
|
218
305
|
"vercel-ai-gateway": [
|
|
306
|
+
"anthropic/claude-opus-4.7",
|
|
219
307
|
"anthropic/claude-opus-4.6",
|
|
308
|
+
"anthropic/claude-opus-4.5",
|
|
220
309
|
"anthropic/claude-opus-4.1",
|
|
221
310
|
"anthropic/claude-sonnet-4.6",
|
|
311
|
+
"openai/gpt-5.5-pro",
|
|
312
|
+
"openai/gpt-5.5",
|
|
313
|
+
"openai/gpt-5.4-pro",
|
|
314
|
+
"openai/gpt-5.4",
|
|
222
315
|
"openai/gpt-5.1-codex",
|
|
223
316
|
"openai/gpt-5-codex",
|
|
317
|
+
"moonshotai/kimi-k2.6",
|
|
224
318
|
"moonshotai/kimi-k2-thinking",
|
|
319
|
+
"deepseek/deepseek-v4-pro",
|
|
225
320
|
"deepseek/deepseek-v3.2-thinking",
|
|
321
|
+
"alibaba/qwen3.5-plus",
|
|
226
322
|
"alibaba/qwen3-max-thinking",
|
|
323
|
+
"google/gemini-3.1-pro-preview",
|
|
227
324
|
"google/gemini-3-flash",
|
|
325
|
+
"xai/grok-4.3",
|
|
326
|
+
"zai/glm-5.1",
|
|
327
|
+
],
|
|
328
|
+
xai: [
|
|
329
|
+
"grok-4.3",
|
|
330
|
+
"grok-4.20-0309-reasoning",
|
|
331
|
+
"grok-4-1-fast",
|
|
332
|
+
"grok-4-fast",
|
|
333
|
+
"grok-4",
|
|
334
|
+
"grok-3-mini-latest",
|
|
335
|
+
"grok-3-mini-fast",
|
|
336
|
+
"grok-3-latest",
|
|
337
|
+
],
|
|
338
|
+
zai: [
|
|
339
|
+
"glm-5.1",
|
|
340
|
+
"glm-5-turbo",
|
|
341
|
+
"glm-5v-turbo",
|
|
342
|
+
"glm-5",
|
|
343
|
+
"glm-4.7",
|
|
344
|
+
"glm-4.7-flash",
|
|
345
|
+
"glm-4.6v",
|
|
346
|
+
"glm-4.5v",
|
|
347
|
+
"glm-4.5-air",
|
|
228
348
|
],
|
|
229
|
-
|
|
230
|
-
|
|
349
|
+
moonshotai: ["kimi-k2.6", "kimi-k2-thinking-turbo", "kimi-k2-thinking", "kimi-k2.5"],
|
|
350
|
+
"moonshotai-cn": ["kimi-k2.6", "kimi-k2-thinking-turbo", "kimi-k2-thinking", "kimi-k2.5"],
|
|
231
351
|
"gemini-cli": ["gemini-3-pro-preview", "gemini-2.5-pro", "gemini-1.5-flash"],
|
|
232
352
|
};
|
|
233
353
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@diegopetrucci/pi-oracle",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.6",
|
|
4
4
|
"description": "An Amp-style oracle extension for pi that consults the strongest reasoning model on your current provider.",
|
|
5
5
|
"keywords": ["pi-package", "pi", "oracle", "reasoning", "subagent"],
|
|
6
6
|
"license": "MIT",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@diegopetrucci/pi-extensions",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.12",
|
|
4
4
|
"description": "A collection of pi extensions, including a minimal custom footer, an Amp-style oracle, a permission gate for dangerous bash commands, confirm-before-destructive session actions, and terminal notifications when pi is ready for input.",
|
|
5
5
|
"keywords": ["pi-package", "pi", "terminal", "agent"],
|
|
6
6
|
"license": "MIT",
|