@diegopetrucci/pi-extensions 0.1.9 → 0.1.11
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/notify/README.md +19 -3
- package/extensions/notify/index.ts +1 -1
- package/extensions/notify/notify.example.json +1 -1
- package/extensions/notify/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.11
|
|
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": {
|
|
@@ -24,12 +24,13 @@ This started from the original `notify.ts` example in [`badlogic/pi-mono`](https
|
|
|
24
24
|
- Linux sound playback via `canberra-gtk-play` or `paplay`
|
|
25
25
|
- Windows beep via `powershell.exe`
|
|
26
26
|
|
|
27
|
-
By default,
|
|
27
|
+
By default, these channels are enabled:
|
|
28
28
|
|
|
29
29
|
- terminal notification
|
|
30
30
|
- desktop notification
|
|
31
31
|
- bell
|
|
32
|
-
|
|
32
|
+
|
|
33
|
+
Sound remains available as an opt-in option via config.
|
|
33
34
|
|
|
34
35
|
The extension automatically picks the appropriate backend for the current environment.
|
|
35
36
|
|
|
@@ -80,7 +81,7 @@ Example:
|
|
|
80
81
|
"terminal": true,
|
|
81
82
|
"desktop": true,
|
|
82
83
|
"bell": true,
|
|
83
|
-
"sound":
|
|
84
|
+
"sound": false
|
|
84
85
|
},
|
|
85
86
|
"terminal": {
|
|
86
87
|
"backend": "auto"
|
|
@@ -99,6 +100,20 @@ Example:
|
|
|
99
100
|
}
|
|
100
101
|
```
|
|
101
102
|
|
|
103
|
+
### Enable sound
|
|
104
|
+
|
|
105
|
+
Minimal example:
|
|
106
|
+
|
|
107
|
+
```json
|
|
108
|
+
{
|
|
109
|
+
"channels": {
|
|
110
|
+
"sound": true
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
You can also customize the sound backend and options if needed.
|
|
116
|
+
|
|
102
117
|
### Config fields
|
|
103
118
|
|
|
104
119
|
- `enabled`: master on/off switch
|
|
@@ -123,3 +138,4 @@ Example:
|
|
|
123
138
|
- Hooks the `agent_end` event.
|
|
124
139
|
- Default message is `Pi` / `Ready for input`.
|
|
125
140
|
- Terminal, desktop, bell, and sound channels can be enabled independently.
|
|
141
|
+
- To opt into sound playback, set `channels.sound` to `true`.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@diegopetrucci/pi-extensions",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.11",
|
|
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",
|