@diegopetrucci/pi-extensions 0.1.12 → 0.1.14
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 -3
- package/extensions/confirm-destructive/README.md +1 -1
- package/extensions/confirm-destructive/index.ts +1 -1
- package/extensions/confirm-destructive/package.json +2 -2
- package/extensions/minimal-footer/README.md +100 -1
- package/extensions/minimal-footer/index.ts +196 -12
- package/extensions/minimal-footer/minimal-footer.example.json +26 -0
- package/extensions/minimal-footer/openai-usage.ts +18 -4
- package/extensions/minimal-footer/package.json +4 -3
- package/extensions/notify/README.md +1 -1
- package/extensions/notify/index.ts +2 -2
- package/extensions/notify/package.json +2 -2
- package/extensions/oracle/README.md +1 -1
- package/extensions/oracle/index.ts +26 -13
- package/extensions/oracle/package.json +5 -3
- package/extensions/permission-gate/README.md +1 -1
- package/extensions/permission-gate/index.ts +1 -1
- package/extensions/permission-gate/package.json +2 -2
- package/package.json +5 -3
package/README.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# pi-extensions
|
|
2
2
|
|
|
3
|
-
A collection of [pi](https://github.com/
|
|
3
|
+
A collection of [pi](https://github.com/earendil-works/pi-mono) agent extensions I made:
|
|
4
4
|
|
|
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.
|
|
5
|
+
- [`minimal-footer`](./extensions/minimal-footer): Replaces pi's built-in footer with a minimal configurable two-line layout: branch/repo on the first line, context/model on the second, optional `DUMB ZONE`, plus OpenAI Codex 5-hour and 7-day usage when available.
|
|
6
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
7
|
- [`permission-gate`](./extensions/permission-gate): Prompts for confirmation before dangerous bash commands like `rm -rf`, `sudo`, and `chmod 777`.
|
|
8
8
|
- [`confirm-destructive`](./extensions/confirm-destructive): Confirms before destructive session actions like clear, switch, and fork.
|
|
@@ -21,7 +21,7 @@ pi install npm:@diegopetrucci/pi-extensions
|
|
|
21
21
|
Or pin the GitHub package to this release:
|
|
22
22
|
|
|
23
23
|
```bash
|
|
24
|
-
pi install git:github.com/diegopetrucci/pi-extensions@v0.1.
|
|
24
|
+
pi install git:github.com/diegopetrucci/pi-extensions@v0.1.14
|
|
25
25
|
```
|
|
26
26
|
|
|
27
27
|
Or a specific extension:
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
A small pi extension that asks for confirmation before destructive session actions.
|
|
4
4
|
|
|
5
|
-
This is adapted from the original `confirm-destructive.ts` example in [`
|
|
5
|
+
This is adapted from the original `confirm-destructive.ts` example in [`earendil-works/pi-mono`](https://github.com/earendil-works/pi-mono/blob/main/packages/coding-agent/examples/extensions/confirm-destructive.ts) and kept basically the same.
|
|
6
6
|
|
|
7
7
|
## What it checks
|
|
8
8
|
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* Demonstrates how to cancel session events using the before_* events.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import type { ExtensionAPI, SessionBeforeSwitchEvent, SessionMessageEntry } from "@
|
|
8
|
+
import type { ExtensionAPI, SessionBeforeSwitchEvent, SessionMessageEntry } from "@earendil-works/pi-coding-agent";
|
|
9
9
|
|
|
10
10
|
export default function (pi: ExtensionAPI) {
|
|
11
11
|
pi.on("session_before_switch", async (event: SessionBeforeSwitchEvent, ctx) => {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@diegopetrucci/pi-confirm-destructive",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "A pi extension that confirms destructive session actions.",
|
|
5
5
|
"keywords": ["pi-package", "pi", "session", "safety"],
|
|
6
6
|
"license": "MIT",
|
|
@@ -22,6 +22,6 @@
|
|
|
22
22
|
]
|
|
23
23
|
},
|
|
24
24
|
"peerDependencies": {
|
|
25
|
-
"@
|
|
25
|
+
"@earendil-works/pi-coding-agent": "*"
|
|
26
26
|
}
|
|
27
27
|
}
|
|
@@ -9,6 +9,7 @@ It replaces pi's built-in footer with a cleaner two-line layout that focuses on
|
|
|
9
9
|
- current git branch
|
|
10
10
|
- current repo name
|
|
11
11
|
- current context percentage
|
|
12
|
+
- red `DUMB ZONE` indicator when context usage is above 200k tokens
|
|
12
13
|
- current model and thinking level
|
|
13
14
|
- OpenAI Codex 5-hour and 7-day usage when available
|
|
14
15
|
|
|
@@ -28,6 +29,12 @@ fix/remove-detached-image-tasks SendItToMy
|
|
|
28
29
|
44.1% gpt-5.4 high
|
|
29
30
|
```
|
|
30
31
|
|
|
32
|
+
When context usage is above 200k tokens, the bottom-left line includes a red warning:
|
|
33
|
+
|
|
34
|
+
```text
|
|
35
|
+
44.1% · DUMB ZONE
|
|
36
|
+
```
|
|
37
|
+
|
|
31
38
|
When using `openai-codex`, the bottom-left line also includes subscription usage:
|
|
32
39
|
|
|
33
40
|
```text
|
|
@@ -62,11 +69,102 @@ Then reload pi:
|
|
|
62
69
|
/reload
|
|
63
70
|
```
|
|
64
71
|
|
|
72
|
+
## Configuration
|
|
73
|
+
|
|
74
|
+
Config files are merged, with project config overriding global config:
|
|
75
|
+
|
|
76
|
+
- `~/.pi/agent/extensions/minimal-footer.json`
|
|
77
|
+
- `<project>/.pi/minimal-footer.json`
|
|
78
|
+
|
|
79
|
+
A ready-to-copy sample file is included at [`minimal-footer.example.json`](./minimal-footer.example.json).
|
|
80
|
+
|
|
81
|
+
Example:
|
|
82
|
+
|
|
83
|
+
```json
|
|
84
|
+
{
|
|
85
|
+
"context": {
|
|
86
|
+
"showPercent": true,
|
|
87
|
+
"dumbZone": {
|
|
88
|
+
"enabled": true,
|
|
89
|
+
"thresholdTokens": 200000,
|
|
90
|
+
"label": "DUMB ZONE",
|
|
91
|
+
"color": "error"
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
"codexUsage": {
|
|
95
|
+
"enabled": true,
|
|
96
|
+
"cacheTtlMs": 300000,
|
|
97
|
+
"requestTimeoutMs": 10000,
|
|
98
|
+
"windows": {
|
|
99
|
+
"primary": {
|
|
100
|
+
"enabled": true,
|
|
101
|
+
"label": "5h"
|
|
102
|
+
},
|
|
103
|
+
"secondary": {
|
|
104
|
+
"enabled": true,
|
|
105
|
+
"label": "7d"
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
Disable `DUMB ZONE`:
|
|
113
|
+
|
|
114
|
+
```json
|
|
115
|
+
{
|
|
116
|
+
"context": {
|
|
117
|
+
"dumbZone": {
|
|
118
|
+
"enabled": false
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
Disable OpenAI Codex session-limit usage entirely:
|
|
125
|
+
|
|
126
|
+
```json
|
|
127
|
+
{
|
|
128
|
+
"codexUsage": {
|
|
129
|
+
"enabled": false
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
Disable one session-limit window:
|
|
135
|
+
|
|
136
|
+
```json
|
|
137
|
+
{
|
|
138
|
+
"codexUsage": {
|
|
139
|
+
"windows": {
|
|
140
|
+
"secondary": {
|
|
141
|
+
"enabled": false
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### Config fields
|
|
149
|
+
|
|
150
|
+
- `context.showPercent`: show the context percentage
|
|
151
|
+
- `context.dumbZone.enabled`: show `DUMB ZONE` when context tokens exceed the threshold
|
|
152
|
+
- `context.dumbZone.thresholdTokens`: token threshold for `DUMB ZONE`
|
|
153
|
+
- `context.dumbZone.label`: warning text
|
|
154
|
+
- `context.dumbZone.color`: theme color for the warning (`error`, `warning`, `accent`, `text`, or `dim`)
|
|
155
|
+
- `codexUsage.enabled`: show OpenAI Codex session-limit usage when using `openai-codex`
|
|
156
|
+
- `codexUsage.cacheTtlMs`: in-memory usage cache duration
|
|
157
|
+
- `codexUsage.requestTimeoutMs`: usage request timeout
|
|
158
|
+
- `codexUsage.windows.primary.enabled`: show the primary usage window
|
|
159
|
+
- `codexUsage.windows.primary.label`: label for the primary usage window
|
|
160
|
+
- `codexUsage.windows.secondary.enabled`: show the secondary usage window
|
|
161
|
+
- `codexUsage.windows.secondary.label`: label for the secondary usage window
|
|
162
|
+
|
|
65
163
|
## What it shows
|
|
66
164
|
|
|
67
165
|
- **Top left:** current git branch
|
|
68
166
|
- **Top right:** current repo directory name
|
|
69
|
-
- **Bottom left:** current context usage percentage
|
|
167
|
+
- **Bottom left:** current context usage percentage, plus red `DUMB ZONE` above 200k context tokens
|
|
70
168
|
- **Bottom left on `openai-codex`:** current context usage percentage plus 5-hour and 7-day Codex usage
|
|
71
169
|
- **Bottom right:** model id and thinking level
|
|
72
170
|
|
|
@@ -79,6 +177,7 @@ This extension also lives inside the broader [`pi-extensions`](../../README.md)
|
|
|
79
177
|
- Replaces pi's built-in footer entirely.
|
|
80
178
|
- Uses pi footer data for git branch updates.
|
|
81
179
|
- Shows only context percentage, not context window size.
|
|
180
|
+
- Shows `DUMB ZONE` only while context usage is above 200k tokens.
|
|
82
181
|
- Shows the model id rather than a provider-specific display label.
|
|
83
182
|
- For `openai-codex`, reads pi's stored OAuth login and fetches usage from ChatGPT's backend usage endpoint.
|
|
84
183
|
- Usage is cached briefly in memory and refreshed after turns.
|
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { basename, dirname, join } from "node:path";
|
|
2
3
|
import {
|
|
3
4
|
AuthStorage,
|
|
5
|
+
getAgentDir,
|
|
4
6
|
type ExtensionAPI,
|
|
5
7
|
type ExtensionContext,
|
|
6
|
-
} from "@
|
|
7
|
-
import { truncateToWidth, visibleWidth } from "@
|
|
8
|
+
} from "@earendil-works/pi-coding-agent";
|
|
9
|
+
import { truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
|
|
8
10
|
import {
|
|
9
11
|
fetchOpenAICodexUsage,
|
|
10
12
|
formatUsageSummary,
|
|
@@ -12,11 +14,77 @@ import {
|
|
|
12
14
|
type UsageSnapshot,
|
|
13
15
|
} from "./openai-usage";
|
|
14
16
|
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
+
const DEFAULT_CONFIG: MinimalFooterConfig = {
|
|
18
|
+
context: {
|
|
19
|
+
showPercent: true,
|
|
20
|
+
dumbZone: {
|
|
21
|
+
enabled: true,
|
|
22
|
+
thresholdTokens: 200_000,
|
|
23
|
+
label: "DUMB ZONE",
|
|
24
|
+
color: "error",
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
codexUsage: {
|
|
28
|
+
enabled: true,
|
|
29
|
+
cacheTtlMs: 5 * 60 * 1000,
|
|
30
|
+
requestTimeoutMs: 10 * 1000,
|
|
31
|
+
windows: {
|
|
32
|
+
primary: {
|
|
33
|
+
enabled: true,
|
|
34
|
+
label: "5h",
|
|
35
|
+
},
|
|
36
|
+
secondary: {
|
|
37
|
+
enabled: true,
|
|
38
|
+
label: "7d",
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const DUMB_ZONE_COLORS = new Set<DumbZoneColor>([
|
|
45
|
+
"error",
|
|
46
|
+
"warning",
|
|
47
|
+
"accent",
|
|
48
|
+
"text",
|
|
49
|
+
"dim",
|
|
50
|
+
]);
|
|
51
|
+
|
|
52
|
+
type RecursivePartial<T> = {
|
|
53
|
+
[P in keyof T]?: T[P] extends object ? RecursivePartial<T[P]> : T[P];
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
type DumbZoneColor = "error" | "warning" | "accent" | "text" | "dim";
|
|
57
|
+
|
|
58
|
+
interface MinimalFooterConfig {
|
|
59
|
+
context: {
|
|
60
|
+
showPercent: boolean;
|
|
61
|
+
dumbZone: {
|
|
62
|
+
enabled: boolean;
|
|
63
|
+
thresholdTokens: number;
|
|
64
|
+
label: string;
|
|
65
|
+
color: DumbZoneColor;
|
|
66
|
+
};
|
|
67
|
+
};
|
|
68
|
+
codexUsage: {
|
|
69
|
+
enabled: boolean;
|
|
70
|
+
cacheTtlMs: number;
|
|
71
|
+
requestTimeoutMs: number;
|
|
72
|
+
windows: {
|
|
73
|
+
primary: {
|
|
74
|
+
enabled: boolean;
|
|
75
|
+
label: string;
|
|
76
|
+
};
|
|
77
|
+
secondary: {
|
|
78
|
+
enabled: boolean;
|
|
79
|
+
label: string;
|
|
80
|
+
};
|
|
81
|
+
};
|
|
82
|
+
};
|
|
83
|
+
}
|
|
17
84
|
|
|
18
85
|
type UsageSessionState = {
|
|
19
86
|
authStorage: AuthStorage;
|
|
87
|
+
config: MinimalFooterConfig;
|
|
20
88
|
snapshot?: UsageSnapshot;
|
|
21
89
|
lastFetchedAt?: number;
|
|
22
90
|
loading: boolean;
|
|
@@ -25,6 +93,115 @@ type UsageSessionState = {
|
|
|
25
93
|
requestRender?: () => void;
|
|
26
94
|
};
|
|
27
95
|
|
|
96
|
+
function readConfigFile(path: string): RecursivePartial<MinimalFooterConfig> {
|
|
97
|
+
if (!existsSync(path)) return {};
|
|
98
|
+
|
|
99
|
+
try {
|
|
100
|
+
return JSON.parse(readFileSync(path, "utf-8")) as RecursivePartial<MinimalFooterConfig>;
|
|
101
|
+
} catch (error) {
|
|
102
|
+
console.error(`Warning: Could not parse ${path}: ${error}`);
|
|
103
|
+
return {};
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function mergeConfig(
|
|
108
|
+
base: MinimalFooterConfig,
|
|
109
|
+
overrides: RecursivePartial<MinimalFooterConfig>,
|
|
110
|
+
): MinimalFooterConfig {
|
|
111
|
+
const context = overrides.context;
|
|
112
|
+
const dumbZone = context?.dumbZone;
|
|
113
|
+
const codexUsage = overrides.codexUsage;
|
|
114
|
+
const primaryWindow = codexUsage?.windows?.primary;
|
|
115
|
+
const secondaryWindow = codexUsage?.windows?.secondary;
|
|
116
|
+
|
|
117
|
+
return {
|
|
118
|
+
context: {
|
|
119
|
+
showPercent: normalizeBoolean(context?.showPercent, base.context.showPercent),
|
|
120
|
+
dumbZone: {
|
|
121
|
+
enabled: normalizeBoolean(dumbZone?.enabled, base.context.dumbZone.enabled),
|
|
122
|
+
thresholdTokens: normalizeNonNegativeNumber(
|
|
123
|
+
dumbZone?.thresholdTokens,
|
|
124
|
+
base.context.dumbZone.thresholdTokens,
|
|
125
|
+
),
|
|
126
|
+
label: normalizeLabel(dumbZone?.label, base.context.dumbZone.label),
|
|
127
|
+
color: normalizeDumbZoneColor(dumbZone?.color, base.context.dumbZone.color),
|
|
128
|
+
},
|
|
129
|
+
},
|
|
130
|
+
codexUsage: {
|
|
131
|
+
enabled: normalizeBoolean(codexUsage?.enabled, base.codexUsage.enabled),
|
|
132
|
+
cacheTtlMs: normalizeNonNegativeNumber(
|
|
133
|
+
codexUsage?.cacheTtlMs,
|
|
134
|
+
base.codexUsage.cacheTtlMs,
|
|
135
|
+
),
|
|
136
|
+
requestTimeoutMs: normalizePositiveNumber(
|
|
137
|
+
codexUsage?.requestTimeoutMs,
|
|
138
|
+
base.codexUsage.requestTimeoutMs,
|
|
139
|
+
),
|
|
140
|
+
windows: {
|
|
141
|
+
primary: {
|
|
142
|
+
enabled: normalizeBoolean(
|
|
143
|
+
primaryWindow?.enabled,
|
|
144
|
+
base.codexUsage.windows.primary.enabled,
|
|
145
|
+
),
|
|
146
|
+
label: normalizeLabel(primaryWindow?.label, base.codexUsage.windows.primary.label),
|
|
147
|
+
},
|
|
148
|
+
secondary: {
|
|
149
|
+
enabled: normalizeBoolean(
|
|
150
|
+
secondaryWindow?.enabled,
|
|
151
|
+
base.codexUsage.windows.secondary.enabled,
|
|
152
|
+
),
|
|
153
|
+
label: normalizeLabel(secondaryWindow?.label, base.codexUsage.windows.secondary.label),
|
|
154
|
+
},
|
|
155
|
+
},
|
|
156
|
+
},
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function normalizeBoolean(value: unknown, fallback: boolean): boolean {
|
|
161
|
+
return typeof value === "boolean" ? value : fallback;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function normalizeNonNegativeNumber(value: unknown, fallback: number): number {
|
|
165
|
+
return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : fallback;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function normalizePositiveNumber(value: unknown, fallback: number): number {
|
|
169
|
+
return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : fallback;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function normalizeLabel(value: unknown, fallback: string): string {
|
|
173
|
+
return typeof value === "string" && value.trim() ? value.trim() : fallback;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function normalizeDumbZoneColor(value: unknown, fallback: DumbZoneColor): DumbZoneColor {
|
|
177
|
+
return DUMB_ZONE_COLORS.has(value as DumbZoneColor) ? (value as DumbZoneColor) : fallback;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function findProjectConfigPath(cwd: string): string {
|
|
181
|
+
let current = cwd;
|
|
182
|
+
while (true) {
|
|
183
|
+
const candidate = join(current, ".pi", "minimal-footer.json");
|
|
184
|
+
if (existsSync(candidate)) return candidate;
|
|
185
|
+
|
|
186
|
+
const parent = dirname(current);
|
|
187
|
+
if (parent === current) return join(cwd, ".pi", "minimal-footer.json");
|
|
188
|
+
current = parent;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function loadConfig(cwd: string): MinimalFooterConfig {
|
|
193
|
+
const globalConfig = readConfigFile(join(getAgentDir(), "extensions", "minimal-footer.json"));
|
|
194
|
+
const projectConfig = readConfigFile(findProjectConfigPath(cwd));
|
|
195
|
+
return mergeConfig(mergeConfig(DEFAULT_CONFIG, globalConfig), projectConfig);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function shouldShowCodexUsage(config: MinimalFooterConfig): boolean {
|
|
199
|
+
return (
|
|
200
|
+
config.codexUsage.enabled &&
|
|
201
|
+
(config.codexUsage.windows.primary.enabled || config.codexUsage.windows.secondary.enabled)
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
|
|
28
205
|
function clearUsageState(state: UsageSessionState): void {
|
|
29
206
|
state.snapshot = undefined;
|
|
30
207
|
state.lastFetchedAt = undefined;
|
|
@@ -37,7 +214,8 @@ async function refreshUsageIfNeeded(
|
|
|
37
214
|
state: UsageSessionState,
|
|
38
215
|
force = false,
|
|
39
216
|
): Promise<void> {
|
|
40
|
-
|
|
217
|
+
const config = state.config;
|
|
218
|
+
if (!shouldShowCodexUsage(config) || !isOpenAICodexProvider(ctx.model?.provider)) {
|
|
41
219
|
clearUsageState(state);
|
|
42
220
|
state.requestRender?.();
|
|
43
221
|
return;
|
|
@@ -47,7 +225,7 @@ async function refreshUsageIfNeeded(
|
|
|
47
225
|
if (
|
|
48
226
|
!force &&
|
|
49
227
|
state.lastFetchedAt &&
|
|
50
|
-
now - state.lastFetchedAt <
|
|
228
|
+
now - state.lastFetchedAt < config.codexUsage.cacheTtlMs
|
|
51
229
|
) {
|
|
52
230
|
return;
|
|
53
231
|
}
|
|
@@ -61,7 +239,7 @@ async function refreshUsageIfNeeded(
|
|
|
61
239
|
state.inflight = (async () => {
|
|
62
240
|
try {
|
|
63
241
|
const snapshot = await fetchOpenAICodexUsage(state.authStorage, {
|
|
64
|
-
timeoutMs:
|
|
242
|
+
timeoutMs: config.codexUsage.requestTimeoutMs,
|
|
65
243
|
});
|
|
66
244
|
if (snapshot) {
|
|
67
245
|
state.snapshot = snapshot;
|
|
@@ -90,6 +268,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
90
268
|
pi.on("session_start", (_event, ctx) => {
|
|
91
269
|
const state: UsageSessionState = {
|
|
92
270
|
authStorage: AuthStorage.create(),
|
|
271
|
+
config: loadConfig(ctx.cwd),
|
|
93
272
|
loading: false,
|
|
94
273
|
};
|
|
95
274
|
states.set(ctx.sessionManager, state);
|
|
@@ -111,10 +290,11 @@ export default function (pi: ExtensionAPI) {
|
|
|
111
290
|
|
|
112
291
|
const usage = ctx.getContextUsage();
|
|
113
292
|
const context = usage?.percent == null ? "?" : `${usage.percent.toFixed(1)}%`;
|
|
114
|
-
const
|
|
115
|
-
|
|
293
|
+
const dumbZone = state.config.context.dumbZone;
|
|
294
|
+
const inDumbZone = dumbZone.enabled && (usage?.tokens ?? 0) > dumbZone.thresholdTokens;
|
|
295
|
+
const usageSummary = shouldShowCodexUsage(state.config) && isOpenAICodexProvider(ctx.model?.provider)
|
|
296
|
+
? formatUsageSummary(state.snapshot, state.config.codexUsage.windows)
|
|
116
297
|
: undefined;
|
|
117
|
-
const contextText = usageSummary ? `${context} · ${usageSummary}` : context;
|
|
118
298
|
|
|
119
299
|
const model = ctx.model?.id ?? "no-model";
|
|
120
300
|
const thinking = pi.getThinkingLevel();
|
|
@@ -122,7 +302,11 @@ export default function (pi: ExtensionAPI) {
|
|
|
122
302
|
|
|
123
303
|
const branchStyled = theme.fg("dim", branch);
|
|
124
304
|
const repoStyled = theme.fg("dim", repo);
|
|
125
|
-
const
|
|
305
|
+
const contextParts: string[] = [];
|
|
306
|
+
if (state.config.context.showPercent) contextParts.push(theme.fg("dim", context));
|
|
307
|
+
if (inDumbZone) contextParts.push(theme.fg(dumbZone.color, dumbZone.label));
|
|
308
|
+
if (usageSummary) contextParts.push(theme.fg("dim", usageSummary));
|
|
309
|
+
const contextStyled = contextParts.join(theme.fg("dim", " · "));
|
|
126
310
|
const modelStyled = theme.fg("dim", modelText);
|
|
127
311
|
|
|
128
312
|
const renderSplitLine = (left: string, right: string): string => {
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"context": {
|
|
3
|
+
"showPercent": true,
|
|
4
|
+
"dumbZone": {
|
|
5
|
+
"enabled": true,
|
|
6
|
+
"thresholdTokens": 200000,
|
|
7
|
+
"label": "DUMB ZONE",
|
|
8
|
+
"color": "error"
|
|
9
|
+
}
|
|
10
|
+
},
|
|
11
|
+
"codexUsage": {
|
|
12
|
+
"enabled": true,
|
|
13
|
+
"cacheTtlMs": 300000,
|
|
14
|
+
"requestTimeoutMs": 10000,
|
|
15
|
+
"windows": {
|
|
16
|
+
"primary": {
|
|
17
|
+
"enabled": true,
|
|
18
|
+
"label": "5h"
|
|
19
|
+
},
|
|
20
|
+
"secondary": {
|
|
21
|
+
"enabled": true,
|
|
22
|
+
"label": "7d"
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AuthStorage } from "@
|
|
1
|
+
import { AuthStorage } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
|
|
3
3
|
const PROVIDER_ID = "openai-codex";
|
|
4
4
|
|
|
@@ -25,6 +25,17 @@ export interface UsageSnapshot {
|
|
|
25
25
|
fetchedAt: number;
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
+
export interface UsageSummaryWindowsConfig {
|
|
29
|
+
primary: {
|
|
30
|
+
enabled: boolean;
|
|
31
|
+
label: string;
|
|
32
|
+
};
|
|
33
|
+
secondary: {
|
|
34
|
+
enabled: boolean;
|
|
35
|
+
label: string;
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
28
39
|
function normalizeUsedPercent(value?: number): number | undefined {
|
|
29
40
|
if (typeof value !== "number" || !Number.isFinite(value)) return undefined;
|
|
30
41
|
return Math.min(100, Math.max(0, value));
|
|
@@ -68,15 +79,18 @@ export function isOpenAICodexProvider(provider?: string): boolean {
|
|
|
68
79
|
return provider === PROVIDER_ID;
|
|
69
80
|
}
|
|
70
81
|
|
|
71
|
-
export function formatUsageSummary(
|
|
82
|
+
export function formatUsageSummary(
|
|
83
|
+
snapshot: UsageSnapshot | undefined,
|
|
84
|
+
windows: UsageSummaryWindowsConfig,
|
|
85
|
+
): string | undefined {
|
|
72
86
|
if (!snapshot) return undefined;
|
|
73
87
|
|
|
74
88
|
const primary = formatUsagePercent(snapshot.primary?.usedPercent);
|
|
75
89
|
const secondary = formatUsagePercent(snapshot.secondary?.usedPercent);
|
|
76
90
|
const parts: string[] = [];
|
|
77
91
|
|
|
78
|
-
if (primary) parts.push(
|
|
79
|
-
if (secondary) parts.push(
|
|
92
|
+
if (windows.primary.enabled && primary) parts.push(`${windows.primary.label} ${primary}`);
|
|
93
|
+
if (windows.secondary.enabled && secondary) parts.push(`${windows.secondary.label} ${secondary}`);
|
|
80
94
|
|
|
81
95
|
return parts.length > 0 ? parts.join(" · ") : undefined;
|
|
82
96
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@diegopetrucci/pi-minimal-footer",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.6",
|
|
4
4
|
"description": "A minimal custom footer for pi.",
|
|
5
5
|
"keywords": ["pi-package", "pi", "terminal", "footer"],
|
|
6
6
|
"license": "MIT",
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
"files": [
|
|
13
13
|
"index.ts",
|
|
14
14
|
"openai-usage.ts",
|
|
15
|
+
"minimal-footer.example.json",
|
|
15
16
|
"README.md"
|
|
16
17
|
],
|
|
17
18
|
"publishConfig": {
|
|
@@ -24,7 +25,7 @@
|
|
|
24
25
|
"image": "https://raw.githubusercontent.com/diegopetrucci/pi-extensions/main/assets/minimal-footer-preview.png"
|
|
25
26
|
},
|
|
26
27
|
"peerDependencies": {
|
|
27
|
-
"@
|
|
28
|
-
"@
|
|
28
|
+
"@earendil-works/pi-coding-agent": "*",
|
|
29
|
+
"@earendil-works/pi-tui": "*"
|
|
29
30
|
}
|
|
30
31
|
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
A pi extension that sends notifications when the agent finishes and is waiting for input.
|
|
4
4
|
|
|
5
|
-
This started from the original `notify.ts` example in [`
|
|
5
|
+
This started from the original `notify.ts` example in [`earendil-works/pi-mono`](https://github.com/earendil-works/pi-mono/blob/main/packages/coding-agent/examples/extensions/notify.ts), but now supports multiple notification channels and JSON configuration.
|
|
6
6
|
|
|
7
7
|
## Supported notification channels
|
|
8
8
|
|
|
@@ -16,8 +16,8 @@
|
|
|
16
16
|
import { execFile } from "node:child_process";
|
|
17
17
|
import { existsSync, readFileSync } from "node:fs";
|
|
18
18
|
import { join } from "node:path";
|
|
19
|
-
import type { ExtensionAPI } from "@
|
|
20
|
-
import { getAgentDir } from "@
|
|
19
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
20
|
+
import { getAgentDir } from "@earendil-works/pi-coding-agent";
|
|
21
21
|
|
|
22
22
|
type TerminalBackend = "auto" | "osc777" | "osc99" | "none";
|
|
23
23
|
type DesktopBackend = "auto" | "macos" | "linux" | "windows-toast" | "none";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@diegopetrucci/pi-notify",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "A pi extension that sends a notification when the agent is ready for input.",
|
|
5
5
|
"keywords": ["pi-package", "pi", "notification", "terminal"],
|
|
6
6
|
"license": "MIT",
|
|
@@ -23,6 +23,6 @@
|
|
|
23
23
|
]
|
|
24
24
|
},
|
|
25
25
|
"peerDependencies": {
|
|
26
|
-
"@
|
|
26
|
+
"@earendil-works/pi-coding-agent": "*"
|
|
27
27
|
}
|
|
28
28
|
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|

|
|
4
4
|
|
|
5
|
-
An Amp-style oracle for [pi](https://github.com/
|
|
5
|
+
An Amp-style oracle for [pi](https://github.com/earendil-works/pi-mono).
|
|
6
6
|
|
|
7
7
|
It adds an `oracle` tool that spins up a separate read-only pi subprocess and sends it to the strongest reasoning model available on the **same provider/subscription** the user is currently using.
|
|
8
8
|
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
2
|
import { existsSync } from "node:fs";
|
|
3
3
|
import { basename } from "node:path";
|
|
4
|
-
import { StringEnum } from "@
|
|
5
|
-
import { getMarkdownTheme, type ExtensionAPI } from "@
|
|
6
|
-
import { Container, Markdown, Spacer, Text } from "@
|
|
7
|
-
import { Type } from "
|
|
4
|
+
import { StringEnum } from "@earendil-works/pi-ai";
|
|
5
|
+
import { getMarkdownTheme, type ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
6
|
+
import { Container, Markdown, Spacer, Text } from "@earendil-works/pi-tui";
|
|
7
|
+
import { Type } from "typebox";
|
|
8
8
|
|
|
9
9
|
type ThinkingLevel = "off" | "minimal" | "low" | "medium" | "high" | "xhigh";
|
|
10
10
|
|
|
@@ -171,14 +171,6 @@ const PROVIDER_MODEL_PREFERENCES: Record<string, string[]> = {
|
|
|
171
171
|
"gemini-2.5-flash-lite-preview",
|
|
172
172
|
"gemini-2.5-flash-lite",
|
|
173
173
|
],
|
|
174
|
-
"google-antigravity": [
|
|
175
|
-
"claude-opus-4-6-thinking",
|
|
176
|
-
"claude-sonnet-4-5-thinking",
|
|
177
|
-
"gemini-3.1-pro-low",
|
|
178
|
-
"gemini-3-flash",
|
|
179
|
-
"gemini-2.0-flash",
|
|
180
|
-
],
|
|
181
|
-
"google-gemini-cli": ["gemini-3-pro-preview", "gemini-2.5-pro", "gemini-1.5-flash"],
|
|
182
174
|
"google-vertex": [
|
|
183
175
|
"gemini-3.1-pro-preview-customtools",
|
|
184
176
|
"gemini-3.1-pro-preview",
|
|
@@ -335,6 +327,28 @@ const PROVIDER_MODEL_PREFERENCES: Record<string, string[]> = {
|
|
|
335
327
|
"grok-3-mini-fast",
|
|
336
328
|
"grok-3-latest",
|
|
337
329
|
],
|
|
330
|
+
xiaomi: ["mimo-v2.5-pro", "mimo-v2.5", "mimo-v2-pro", "mimo-v2-omni", "mimo-v2-flash"],
|
|
331
|
+
"xiaomi-token-plan-ams": [
|
|
332
|
+
"mimo-v2.5-pro",
|
|
333
|
+
"mimo-v2.5",
|
|
334
|
+
"mimo-v2-pro",
|
|
335
|
+
"mimo-v2-omni",
|
|
336
|
+
"mimo-v2-flash",
|
|
337
|
+
],
|
|
338
|
+
"xiaomi-token-plan-cn": [
|
|
339
|
+
"mimo-v2.5-pro",
|
|
340
|
+
"mimo-v2.5",
|
|
341
|
+
"mimo-v2-pro",
|
|
342
|
+
"mimo-v2-omni",
|
|
343
|
+
"mimo-v2-flash",
|
|
344
|
+
],
|
|
345
|
+
"xiaomi-token-plan-sgp": [
|
|
346
|
+
"mimo-v2.5-pro",
|
|
347
|
+
"mimo-v2.5",
|
|
348
|
+
"mimo-v2-pro",
|
|
349
|
+
"mimo-v2-omni",
|
|
350
|
+
"mimo-v2-flash",
|
|
351
|
+
],
|
|
338
352
|
zai: [
|
|
339
353
|
"glm-5.1",
|
|
340
354
|
"glm-5-turbo",
|
|
@@ -348,7 +362,6 @@ const PROVIDER_MODEL_PREFERENCES: Record<string, string[]> = {
|
|
|
348
362
|
],
|
|
349
363
|
moonshotai: ["kimi-k2.6", "kimi-k2-thinking-turbo", "kimi-k2-thinking", "kimi-k2.5"],
|
|
350
364
|
"moonshotai-cn": ["kimi-k2.6", "kimi-k2-thinking-turbo", "kimi-k2-thinking", "kimi-k2.5"],
|
|
351
|
-
"gemini-cli": ["gemini-3-pro-preview", "gemini-2.5-pro", "gemini-1.5-flash"],
|
|
352
365
|
};
|
|
353
366
|
|
|
354
367
|
const ORACLE_SYSTEM_PROMPT = [
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@diegopetrucci/pi-oracle",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.7",
|
|
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",
|
|
@@ -23,7 +23,9 @@
|
|
|
23
23
|
"image": "https://raw.githubusercontent.com/diegopetrucci/pi-extensions/main/assets/oracle-preview.svg"
|
|
24
24
|
},
|
|
25
25
|
"peerDependencies": {
|
|
26
|
-
"@
|
|
27
|
-
"@
|
|
26
|
+
"@earendil-works/pi-ai": "*",
|
|
27
|
+
"@earendil-works/pi-coding-agent": "*",
|
|
28
|
+
"@earendil-works/pi-tui": "*",
|
|
29
|
+
"typebox": "*"
|
|
28
30
|
}
|
|
29
31
|
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
A small pi extension that prompts for confirmation before running potentially dangerous bash commands.
|
|
4
4
|
|
|
5
|
-
This is adapted from the original `permission-gate.ts` example in [`
|
|
5
|
+
This is adapted from the original `permission-gate.ts` example in [`earendil-works/pi-mono`](https://github.com/earendil-works/pi-mono/blob/main/packages/coding-agent/examples/extensions/permission-gate.ts) and kept basically the same.
|
|
6
6
|
|
|
7
7
|
## What it checks
|
|
8
8
|
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* Patterns checked: rm -rf, sudo, chmod/chown 777
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import type { ExtensionAPI } from "@
|
|
8
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
9
9
|
|
|
10
10
|
export default function (pi: ExtensionAPI) {
|
|
11
11
|
const dangerousPatterns = [/\brm\s+(-rf?|--recursive)/i, /\bsudo\b/i, /\b(chmod|chown)\b.*777/i];
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@diegopetrucci/pi-permission-gate",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "A pi extension that prompts before dangerous bash commands.",
|
|
5
5
|
"keywords": ["pi-package", "pi", "security", "bash"],
|
|
6
6
|
"license": "MIT",
|
|
@@ -22,6 +22,6 @@
|
|
|
22
22
|
]
|
|
23
23
|
},
|
|
24
24
|
"peerDependencies": {
|
|
25
|
-
"@
|
|
25
|
+
"@earendil-works/pi-coding-agent": "*"
|
|
26
26
|
}
|
|
27
27
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@diegopetrucci/pi-extensions",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.14",
|
|
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",
|
|
@@ -21,8 +21,10 @@
|
|
|
21
21
|
"access": "public"
|
|
22
22
|
},
|
|
23
23
|
"peerDependencies": {
|
|
24
|
-
"@
|
|
25
|
-
"@
|
|
24
|
+
"@earendil-works/pi-ai": "*",
|
|
25
|
+
"@earendil-works/pi-coding-agent": "*",
|
|
26
|
+
"@earendil-works/pi-tui": "*",
|
|
27
|
+
"typebox": "*"
|
|
26
28
|
},
|
|
27
29
|
"pi": {
|
|
28
30
|
"extensions": [
|