@diegopetrucci/pi-extensions 0.1.46 → 0.1.47

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.
@@ -35,6 +35,8 @@ Config files are merged, with project config overriding global config:
35
35
  - `~/.pi/agent/extensions/brrr.json`
36
36
  - `<project>/.pi/brrr.json`
37
37
 
38
+ Project config is only read after Pi reports that the project is trusted.
39
+
38
40
  The default config expects your webhook in `BRRR_WEBHOOK_URL`:
39
41
 
40
42
  ```bash
@@ -6,7 +6,7 @@
6
6
  *
7
7
  * Config files (project overrides global):
8
8
  * - ~/.pi/agent/extensions/brrr.json
9
- * - <cwd>/.pi/brrr.json
9
+ * - <cwd>/.pi/brrr.json, when the project is trusted
10
10
  */
11
11
 
12
12
  import { execFile } from "node:child_process";
@@ -16,6 +16,7 @@ import { promisify } from "node:util";
16
16
  import { getAgentDir, type ExtensionAPI, type ExtensionCommandContext } from "@earendil-works/pi-coding-agent";
17
17
 
18
18
  const execFileAsync = promisify(execFile);
19
+ const CONFIG_DIR_NAME = ".pi";
19
20
 
20
21
  interface BrrrConfig {
21
22
  enabled: boolean;
@@ -40,6 +41,11 @@ interface BrrrPayload {
40
41
  image_url?: string;
41
42
  }
42
43
 
44
+ type ProjectConfigContext = {
45
+ cwd: string;
46
+ isProjectTrusted?: () => boolean;
47
+ };
48
+
43
49
  const DEFAULT_CONFIG: BrrrConfig = {
44
50
  enabled: true,
45
51
  onlyWhenInteractive: true,
@@ -71,9 +77,15 @@ function mergeConfig(base: BrrrConfig, overrides: Partial<BrrrConfig>): BrrrConf
71
77
  };
72
78
  }
73
79
 
74
- function loadConfig(cwd: string): BrrrConfig {
80
+ function canReadProjectConfig(ctx: ProjectConfigContext): boolean {
81
+ return typeof ctx.isProjectTrusted === "function" && ctx.isProjectTrusted();
82
+ }
83
+
84
+ function loadConfig(ctx: ProjectConfigContext): BrrrConfig {
75
85
  const globalConfig = readConfigFile(join(getAgentDir(), "extensions", "brrr.json"));
76
- const projectConfig = readConfigFile(join(cwd, ".pi", "brrr.json"));
86
+ const projectConfig = canReadProjectConfig(ctx)
87
+ ? readConfigFile(join(ctx.cwd, CONFIG_DIR_NAME, "brrr.json"))
88
+ : {};
77
89
  return mergeConfig(mergeConfig(DEFAULT_CONFIG, globalConfig), projectConfig);
78
90
  }
79
91
 
@@ -219,13 +231,13 @@ export default function brrrExtension(pi: ExtensionAPI) {
219
231
  pi.registerCommand("brrr", {
220
232
  description: "Show brrr notification status",
221
233
  handler: async (_args, ctx) => {
222
- const config = loadConfig(ctx.cwd);
234
+ const config = loadConfig(ctx);
223
235
  notify(ctx, describeConfig(config, resolveWebhook(config.webhook)));
224
236
  },
225
237
  });
226
238
 
227
239
  pi.on("agent_end", async (event, ctx) => {
228
- const config = loadConfig(ctx.cwd);
240
+ const config = loadConfig(ctx);
229
241
  if (!config.enabled) return;
230
242
  if (config.onlyWhenInteractive && !ctx.hasUI) return;
231
243
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@diegopetrucci/pi-brrr",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "description": "A pi extension that sends brrr push notifications when pi is ready for input.",
5
5
  "keywords": [
6
6
  "pi-package",
@@ -51,7 +51,7 @@ Optional project config:
51
51
  .pi/claude-fast.json
52
52
  ```
53
53
 
54
- Project config overrides global config.
54
+ Project config overrides global config after Pi reports that the project is trusted.
55
55
 
56
56
  ```json
57
57
  {
@@ -6,6 +6,8 @@ import {
6
6
  type ExtensionContext,
7
7
  } from "@earendil-works/pi-coding-agent";
8
8
 
9
+ const CONFIG_DIR_NAME = ".pi";
10
+
9
11
  const EXTENSION_ID = "claude-fast";
10
12
  const PROVIDER_ID = "anthropic";
11
13
  const API_ID = "anthropic-messages";
@@ -35,6 +37,11 @@ type SessionState = {
35
37
  lastInjectedModel?: string;
36
38
  };
37
39
 
40
+ type ProjectConfigContext = {
41
+ cwd: string;
42
+ isProjectTrusted?: () => boolean;
43
+ };
44
+
38
45
  type RecursivePartial<T> = {
39
46
  [P in keyof T]?: T[P] extends object ? RecursivePartial<T[P]> : T[P];
40
47
  };
@@ -77,21 +84,25 @@ function mergeConfig(
77
84
  };
78
85
  }
79
86
 
87
+ function canReadProjectConfig(ctx: ProjectConfigContext): boolean {
88
+ return typeof ctx.isProjectTrusted === "function" && ctx.isProjectTrusted();
89
+ }
90
+
80
91
  function findProjectConfigPath(cwd: string): string {
81
92
  let current = cwd;
82
93
  while (true) {
83
- const candidate = join(current, ".pi", "claude-fast.json");
94
+ const candidate = join(current, CONFIG_DIR_NAME, "claude-fast.json");
84
95
  if (existsSync(candidate)) return candidate;
85
96
 
86
97
  const parent = dirname(current);
87
- if (parent === current) return join(cwd, ".pi", "claude-fast.json");
98
+ if (parent === current) return join(cwd, CONFIG_DIR_NAME, "claude-fast.json");
88
99
  current = parent;
89
100
  }
90
101
  }
91
102
 
92
- function loadConfig(cwd: string): ClaudeFastConfig {
103
+ function loadConfig(ctx: ProjectConfigContext): ClaudeFastConfig {
93
104
  const globalConfig = readConfigFile(join(getAgentDir(), "extensions", "claude-fast.json"));
94
- const projectConfig = readConfigFile(findProjectConfigPath(cwd));
105
+ const projectConfig = canReadProjectConfig(ctx) ? readConfigFile(findProjectConfigPath(ctx.cwd)) : {};
95
106
  return mergeConfig(mergeConfig(DEFAULT_CONFIG, globalConfig), projectConfig);
96
107
  }
97
108
 
@@ -235,7 +246,7 @@ export default function claudeFastExtension(pi: ExtensionAPI) {
235
246
  let state = states.get(ctx.sessionManager);
236
247
  if (!state) {
237
248
  state = {
238
- config: loadConfig(ctx.cwd),
249
+ config: loadConfig(ctx),
239
250
  override: "auto",
240
251
  };
241
252
  states.set(ctx.sessionManager, state);
@@ -245,7 +256,7 @@ export default function claudeFastExtension(pi: ExtensionAPI) {
245
256
 
246
257
  pi.on("session_start", (_event, ctx) => {
247
258
  const state: SessionState = {
248
- config: loadConfig(ctx.cwd),
259
+ config: loadConfig(ctx),
249
260
  override: "auto",
250
261
  };
251
262
  states.set(ctx.sessionManager, state);
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@diegopetrucci/pi-claude-fast",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "description": "A pi extension that enables Anthropic Claude Fast mode for supported Claude Opus models by injecting speed=fast.",
5
5
  "keywords": [
6
6
  "pi-package",
@@ -76,6 +76,8 @@ Config files are merged, with project config overriding global config:
76
76
  - `~/.pi/agent/extensions/minimal-footer.json`
77
77
  - `<project>/.pi/minimal-footer.json`
78
78
 
79
+ Project config is only read after Pi reports that the project is trusted.
80
+
79
81
  A ready-to-copy sample file is included at [`minimal-footer.example.json`](./minimal-footer.example.json).
80
82
 
81
83
  Example:
@@ -14,6 +14,8 @@ import {
14
14
  type UsageSnapshot,
15
15
  } from "./openai-usage";
16
16
 
17
+ const CONFIG_DIR_NAME = ".pi";
18
+
17
19
  const DEFAULT_CONFIG: MinimalFooterConfig = {
18
20
  context: {
19
21
  showPercent: true,
@@ -93,6 +95,11 @@ type UsageSessionState = {
93
95
  requestRender?: () => void;
94
96
  };
95
97
 
98
+ type ProjectConfigContext = {
99
+ cwd: string;
100
+ isProjectTrusted?: () => boolean;
101
+ };
102
+
96
103
  function readConfigFile(path: string): RecursivePartial<MinimalFooterConfig> {
97
104
  if (!existsSync(path)) return {};
98
105
 
@@ -177,21 +184,25 @@ function normalizeDumbZoneColor(value: unknown, fallback: DumbZoneColor): DumbZo
177
184
  return DUMB_ZONE_COLORS.has(value as DumbZoneColor) ? (value as DumbZoneColor) : fallback;
178
185
  }
179
186
 
187
+ function canReadProjectConfig(ctx: ProjectConfigContext): boolean {
188
+ return typeof ctx.isProjectTrusted === "function" && ctx.isProjectTrusted();
189
+ }
190
+
180
191
  function findProjectConfigPath(cwd: string): string {
181
192
  let current = cwd;
182
193
  while (true) {
183
- const candidate = join(current, ".pi", "minimal-footer.json");
194
+ const candidate = join(current, CONFIG_DIR_NAME, "minimal-footer.json");
184
195
  if (existsSync(candidate)) return candidate;
185
196
 
186
197
  const parent = dirname(current);
187
- if (parent === current) return join(cwd, ".pi", "minimal-footer.json");
198
+ if (parent === current) return join(cwd, CONFIG_DIR_NAME, "minimal-footer.json");
188
199
  current = parent;
189
200
  }
190
201
  }
191
202
 
192
- function loadConfig(cwd: string): MinimalFooterConfig {
203
+ function loadConfig(ctx: ProjectConfigContext): MinimalFooterConfig {
193
204
  const globalConfig = readConfigFile(join(getAgentDir(), "extensions", "minimal-footer.json"));
194
- const projectConfig = readConfigFile(findProjectConfigPath(cwd));
205
+ const projectConfig = canReadProjectConfig(ctx) ? readConfigFile(findProjectConfigPath(ctx.cwd)) : {};
195
206
  return mergeConfig(mergeConfig(DEFAULT_CONFIG, globalConfig), projectConfig);
196
207
  }
197
208
 
@@ -268,7 +279,7 @@ export default function (pi: ExtensionAPI) {
268
279
  pi.on("session_start", (_event, ctx) => {
269
280
  const state: UsageSessionState = {
270
281
  authStorage: AuthStorage.create(),
271
- config: loadConfig(ctx.cwd),
282
+ config: loadConfig(ctx),
272
283
  loading: false,
273
284
  };
274
285
  states.set(ctx.sessionManager, state);
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@diegopetrucci/pi-minimal-footer",
3
- "version": "0.1.8",
3
+ "version": "0.1.9",
4
4
  "description": "A minimal custom footer for pi.",
5
5
  "keywords": [
6
6
  "pi-package",
@@ -67,6 +67,8 @@ Config files are merged, with project config overriding global config:
67
67
  - `~/.pi/agent/extensions/notify.json`
68
68
  - `<project>/.pi/notify.json`
69
69
 
70
+ Project config is only read after Pi reports that the project is trusted.
71
+
70
72
  A ready-to-copy sample file is included at [`notify.example.json`](./notify.example.json).
71
73
 
72
74
  Example:
@@ -10,7 +10,7 @@
10
10
  *
11
11
  * Config files (project overrides global):
12
12
  * - ~/.pi/agent/extensions/notify.json
13
- * - <cwd>/.pi/notify.json
13
+ * - <cwd>/.pi/notify.json, when the project is trusted
14
14
  */
15
15
 
16
16
  import { execFile } from "node:child_process";
@@ -19,10 +19,17 @@ import { join } from "node:path";
19
19
  import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
20
20
  import { getAgentDir } from "@earendil-works/pi-coding-agent";
21
21
 
22
+ const CONFIG_DIR_NAME = ".pi";
23
+
22
24
  type TerminalBackend = "auto" | "osc777" | "osc99" | "none";
23
25
  type DesktopBackend = "auto" | "macos" | "linux" | "windows-toast" | "none";
24
26
  type SoundBackend = "auto" | "macos" | "linux" | "windows-beep" | "command" | "none";
25
27
 
28
+ type ProjectConfigContext = {
29
+ cwd: string;
30
+ isProjectTrusted?: () => boolean;
31
+ };
32
+
26
33
  interface NotifyConfig {
27
34
  enabled: boolean;
28
35
  onlyWhenInteractive: boolean;
@@ -111,9 +118,15 @@ function mergeConfig(base: NotifyConfig, overrides: Partial<NotifyConfig>): Noti
111
118
  };
112
119
  }
113
120
 
114
- function loadConfig(cwd: string): NotifyConfig {
121
+ function canReadProjectConfig(ctx: ProjectConfigContext): boolean {
122
+ return typeof ctx.isProjectTrusted === "function" && ctx.isProjectTrusted();
123
+ }
124
+
125
+ function loadConfig(ctx: ProjectConfigContext): NotifyConfig {
115
126
  const globalConfig = readConfigFile(join(getAgentDir(), "extensions", "notify.json"));
116
- const projectConfig = readConfigFile(join(cwd, ".pi", "notify.json"));
127
+ const projectConfig = canReadProjectConfig(ctx)
128
+ ? readConfigFile(join(ctx.cwd, CONFIG_DIR_NAME, "notify.json"))
129
+ : {};
117
130
  return mergeConfig(mergeConfig(DEFAULT_CONFIG, globalConfig), projectConfig);
118
131
  }
119
132
 
@@ -245,7 +258,7 @@ async function playSound(config: NotifyConfig, backend: Exclude<SoundBackend, "a
245
258
 
246
259
  export default function notifyExtension(pi: ExtensionAPI) {
247
260
  pi.on("agent_end", async (_event, ctx) => {
248
- const config = loadConfig(ctx.cwd);
261
+ const config = loadConfig(ctx);
249
262
  if (!config.enabled) return;
250
263
  if (config.onlyWhenInteractive && !ctx.hasUI) return;
251
264
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@diegopetrucci/pi-notify",
3
- "version": "0.1.5",
3
+ "version": "0.1.6",
4
4
  "description": "A pi extension that sends a notification when the agent is ready for input.",
5
5
  "keywords": [
6
6
  "pi-package",
@@ -46,7 +46,7 @@ Optional project config:
46
46
  .pi/openai-fast.json
47
47
  ```
48
48
 
49
- Project config overrides global config.
49
+ Project config overrides global config after Pi reports that the project is trusted.
50
50
 
51
51
  ```json
52
52
  {
@@ -6,6 +6,8 @@ import {
6
6
  type ExtensionContext,
7
7
  } from "@earendil-works/pi-coding-agent";
8
8
 
9
+ const CONFIG_DIR_NAME = ".pi";
10
+
9
11
  const EXTENSION_ID = "openai-fast";
10
12
  const PROVIDER_ID = "openai-codex";
11
13
  const API_ID = "openai-codex-responses";
@@ -33,6 +35,11 @@ type SessionState = {
33
35
  lastInjectedModel?: string;
34
36
  };
35
37
 
38
+ type ProjectConfigContext = {
39
+ cwd: string;
40
+ isProjectTrusted?: () => boolean;
41
+ };
42
+
36
43
  type RecursivePartial<T> = {
37
44
  [P in keyof T]?: T[P] extends object ? RecursivePartial<T[P]> : T[P];
38
45
  };
@@ -71,21 +78,25 @@ function mergeConfig(
71
78
  };
72
79
  }
73
80
 
81
+ function canReadProjectConfig(ctx: ProjectConfigContext): boolean {
82
+ return typeof ctx.isProjectTrusted === "function" && ctx.isProjectTrusted();
83
+ }
84
+
74
85
  function findProjectConfigPath(cwd: string): string {
75
86
  let current = cwd;
76
87
  while (true) {
77
- const candidate = join(current, ".pi", "openai-fast.json");
88
+ const candidate = join(current, CONFIG_DIR_NAME, "openai-fast.json");
78
89
  if (existsSync(candidate)) return candidate;
79
90
 
80
91
  const parent = dirname(current);
81
- if (parent === current) return join(cwd, ".pi", "openai-fast.json");
92
+ if (parent === current) return join(cwd, CONFIG_DIR_NAME, "openai-fast.json");
82
93
  current = parent;
83
94
  }
84
95
  }
85
96
 
86
- function loadConfig(cwd: string): OpenAIFastConfig {
97
+ function loadConfig(ctx: ProjectConfigContext): OpenAIFastConfig {
87
98
  const globalConfig = readConfigFile(join(getAgentDir(), "extensions", "openai-fast.json"));
88
- const projectConfig = readConfigFile(findProjectConfigPath(cwd));
99
+ const projectConfig = canReadProjectConfig(ctx) ? readConfigFile(findProjectConfigPath(ctx.cwd)) : {};
89
100
  return mergeConfig(mergeConfig(DEFAULT_CONFIG, globalConfig), projectConfig);
90
101
  }
91
102
 
@@ -211,7 +222,7 @@ export default function openAIFastExtension(pi: ExtensionAPI) {
211
222
  let state = states.get(ctx.sessionManager);
212
223
  if (!state) {
213
224
  state = {
214
- config: loadConfig(ctx.cwd),
225
+ config: loadConfig(ctx),
215
226
  override: "auto",
216
227
  };
217
228
  states.set(ctx.sessionManager, state);
@@ -221,7 +232,7 @@ export default function openAIFastExtension(pi: ExtensionAPI) {
221
232
 
222
233
  pi.on("session_start", (_event, ctx) => {
223
234
  const state: SessionState = {
224
- config: loadConfig(ctx.cwd),
235
+ config: loadConfig(ctx),
225
236
  override: "auto",
226
237
  };
227
238
  states.set(ctx.sessionManager, state);
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@diegopetrucci/pi-openai-fast",
3
- "version": "0.1.4",
3
+ "version": "0.1.5",
4
4
  "description": "A pi extension that enables OpenAI Codex Fast mode for ChatGPT-auth GPT-5.4 and GPT-5.5 by injecting the priority service tier.",
5
5
  "keywords": [
6
6
  "pi-package",
@@ -31,7 +31,7 @@ Then reload pi:
31
31
 
32
32
  - Behavior is intentionally kept equivalent to the upstream source, with only packaging/attribution changes for this repository.
33
33
  - PR review requires `gh` access and a clean working tree for tracked files.
34
- - If a `REVIEW_GUIDELINES.md` file exists next to the repo's `.pi` directory, its contents are appended to the review prompt.
34
+ - If the project is trusted and a `REVIEW_GUIDELINES.md` file exists next to the repo's `.pi` directory, its contents are appended to the review prompt.
35
35
 
36
36
  ## License and attribution
37
37
 
@@ -29,8 +29,8 @@
29
29
  * - `/review --extra "focus on performance regressions"` - add extra review instruction (works with any mode)
30
30
  *
31
31
  * Project-specific review guidelines:
32
- * - If a REVIEW_GUIDELINES.md file exists in the same directory as .pi,
33
- * its contents are appended to the review prompt.
32
+ * - If the project is trusted and a REVIEW_GUIDELINES.md file exists in the
33
+ * same directory as .pi, its contents are appended to the review prompt.
34
34
  *
35
35
  * Note: PR review requires a clean working tree (no uncommitted changes to tracked files).
36
36
  */
@@ -49,6 +49,8 @@ import {
49
49
  import path from "node:path";
50
50
  import { promises as fs } from "node:fs";
51
51
 
52
+ const CONFIG_DIR_NAME = ".pi";
53
+
52
54
  // State to track fresh session review (where we branched from).
53
55
  // Module-level state means only one review can be active at a time.
54
56
  // This is intentional - the UI and /end-review command assume a single active review.
@@ -75,6 +77,15 @@ type ReviewSettingsState = {
75
77
  customInstructions?: string;
76
78
  };
77
79
 
80
+ type ProjectConfigContext = {
81
+ cwd: string;
82
+ isProjectTrusted?: () => boolean;
83
+ };
84
+
85
+ function canReadProjectConfig(ctx: ProjectConfigContext): boolean {
86
+ return typeof ctx.isProjectTrusted === "function" && ctx.isProjectTrusted();
87
+ }
88
+
78
89
  function setReviewWidget(ctx: ExtensionContext, active: boolean) {
79
90
  if (!ctx.hasUI) return;
80
91
  if (!active) {
@@ -501,7 +512,7 @@ async function loadProjectReviewGuidelines(cwd: string): Promise<string | null>
501
512
  let currentDir = path.resolve(cwd);
502
513
 
503
514
  while (true) {
504
- const piDir = path.join(currentDir, ".pi");
515
+ const piDir = path.join(currentDir, CONFIG_DIR_NAME);
505
516
  const guidelinesPath = path.join(currentDir, "REVIEW_GUIDELINES.md");
506
517
 
507
518
  const piStats = await fs.stat(piDir).catch(() => null);
@@ -1420,7 +1431,7 @@ export default function reviewExtension(pi: ExtensionAPI) {
1420
1431
  includeLocalChanges: options?.includeLocalChanges === true,
1421
1432
  });
1422
1433
  const hint = getUserFacingHint(target);
1423
- const projectGuidelines = await loadProjectReviewGuidelines(ctx.cwd);
1434
+ const projectGuidelines = canReadProjectConfig(ctx) ? await loadProjectReviewGuidelines(ctx.cwd) : null;
1424
1435
 
1425
1436
  // Combine the review rubric with the specific prompt
1426
1437
  let fullPrompt = `${REVIEW_RUBRIC}\n\n---\n\nPlease perform a code review with the following focus:\n\n${prompt}`;
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@diegopetrucci/pi-review",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "description": "A standalone pi extension that adds /review and /end-review commands adapted from mitsuhiko/agent-stuff.",
5
5
  "keywords": [
6
6
  "pi-package",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@diegopetrucci/pi-extensions",
3
- "version": "0.1.46",
3
+ "version": "0.1.47",
4
4
  "description": "A collection of pi extensions and skills for annotation UIs, context management, workflow audits, review-comment triage, notifications, brrr push alerts, safety guards, GitHub research, repo-local knowledge, todos, tool rendering, model/provider helpers, and Xiaohei-style article illustrations.",
5
5
  "keywords": [
6
6
  "pi-package",