@diegopetrucci/pi-extensions 0.1.74 → 0.1.78

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.
Files changed (108) hide show
  1. package/LICENSE +2 -0
  2. package/README.md +17 -12
  3. package/extensions/agent-workflow-audit/README.md +1 -1
  4. package/extensions/agent-workflow-audit/index.ts +1631 -1009
  5. package/extensions/agent-workflow-audit/package.json +1 -1
  6. package/extensions/annotate-git-diff/README.md +3 -1
  7. package/extensions/annotate-git-diff/clipboard.ts +109 -100
  8. package/extensions/annotate-git-diff/git.ts +846 -768
  9. package/extensions/annotate-git-diff/glimpseui.d.ts +87 -78
  10. package/extensions/annotate-git-diff/index.ts +712 -671
  11. package/extensions/annotate-git-diff/package.json +1 -1
  12. package/extensions/annotate-git-diff/prompt.ts +45 -45
  13. package/extensions/annotate-git-diff/quiet-glimpse.ts +161 -155
  14. package/extensions/annotate-git-diff/review-server.ts +337 -311
  15. package/extensions/annotate-git-diff/types.ts +132 -132
  16. package/extensions/annotate-git-diff/ui.ts +27 -27
  17. package/extensions/annotate-git-diff/watch.ts +74 -74
  18. package/extensions/annotate-git-diff/web/app.js +1947 -1884
  19. package/extensions/annotate-git-diff/web/index.html +5 -6
  20. package/extensions/annotate-last-message/README.md +1 -1
  21. package/extensions/annotate-last-message/glimpseui.d.ts +87 -78
  22. package/extensions/annotate-last-message/index.ts +210 -206
  23. package/extensions/annotate-last-message/package.json +1 -1
  24. package/extensions/annotate-last-message/prompt.ts +87 -66
  25. package/extensions/annotate-last-message/quiet-glimpse.ts +161 -155
  26. package/extensions/annotate-last-message/session.ts +94 -89
  27. package/extensions/annotate-last-message/theme.ts +199 -193
  28. package/extensions/annotate-last-message/types.ts +23 -23
  29. package/extensions/annotate-last-message/ui.ts +27 -25
  30. package/extensions/annotate-last-message/web/app.js +4 -7
  31. package/extensions/annotate-last-message/web/index.html +3 -3
  32. package/extensions/annotate-last-message/web/md-renderer.js +1 -1
  33. package/extensions/brrr/README.md +1 -1
  34. package/extensions/brrr/index.ts +213 -191
  35. package/extensions/brrr/package.json +1 -1
  36. package/extensions/claude-fast/README.md +5 -79
  37. package/extensions/claude-fast/index.ts +31 -289
  38. package/extensions/claude-fast/package.json +2 -3
  39. package/extensions/code-reviewer/README.md +6 -6
  40. package/extensions/code-reviewer/index.ts +1825 -1373
  41. package/extensions/code-reviewer/package.json +1 -1
  42. package/extensions/confirm-destructive/README.md +1 -1
  43. package/extensions/confirm-destructive/index.ts +50 -52
  44. package/extensions/confirm-destructive/package.json +1 -1
  45. package/extensions/context-cap/README.md +9 -9
  46. package/extensions/context-cap/index.ts +313 -305
  47. package/extensions/context-cap/package.json +2 -2
  48. package/extensions/context-inspector/index.ts +1211 -1064
  49. package/extensions/context-inspector/package.json +1 -1
  50. package/extensions/contrarian/README.md +4 -3
  51. package/extensions/contrarian/index.ts +1863 -1737
  52. package/extensions/contrarian/package.json +1 -1
  53. package/extensions/dirty-repo-guard/index.ts +37 -41
  54. package/extensions/dirty-repo-guard/package.json +1 -1
  55. package/extensions/dynamic-context-pruning/README.md +11 -43
  56. package/extensions/dynamic-context-pruning/index.ts +2131 -2105
  57. package/extensions/dynamic-context-pruning/package.json +1 -1
  58. package/extensions/fast/README.md +52 -14
  59. package/extensions/fast/index.ts +513 -317
  60. package/extensions/fast/package.json +1 -1
  61. package/extensions/git-footer/README.md +1 -1
  62. package/extensions/git-footer/index.ts +458 -411
  63. package/extensions/git-footer/package.json +2 -3
  64. package/extensions/gnosis/index.ts +180 -130
  65. package/extensions/gnosis/package.json +1 -1
  66. package/extensions/inline-bash/index.ts +87 -103
  67. package/extensions/inline-bash/package.json +1 -1
  68. package/extensions/librarian/README.md +1 -1
  69. package/extensions/librarian/index.ts +1877 -1419
  70. package/extensions/librarian/package.json +1 -1
  71. package/extensions/minimal-footer/README.md +5 -5
  72. package/extensions/minimal-footer/index.ts +987 -1012
  73. package/extensions/minimal-footer/minimal-footer.example.json +1 -1
  74. package/extensions/minimal-footer/openai-usage.ts +195 -190
  75. package/extensions/minimal-footer/package.json +1 -1
  76. package/extensions/notify/index.ts +222 -230
  77. package/extensions/notify/package.json +1 -1
  78. package/extensions/openai-fast/README.md +5 -73
  79. package/extensions/openai-fast/index.ts +32 -272
  80. package/extensions/openai-fast/package.json +2 -3
  81. package/extensions/oracle/README.md +4 -3
  82. package/extensions/oracle/index.ts +1740 -1627
  83. package/extensions/oracle/package.json +1 -1
  84. package/extensions/permission-gate/README.md +3 -0
  85. package/extensions/permission-gate/index.ts +785 -612
  86. package/extensions/permission-gate/package.json +1 -1
  87. package/extensions/permission-gate/powershell-safety.ts +840 -793
  88. package/extensions/project-mcp-json/.pi-fleet-tested-version +1 -0
  89. package/extensions/project-mcp-json/README.md +199 -0
  90. package/extensions/project-mcp-json/config.ts +564 -0
  91. package/extensions/project-mcp-json/index.ts +119 -0
  92. package/extensions/project-mcp-json/package.json +38 -0
  93. package/extensions/quiet-tools/.pi-fleet-tested-version +1 -1
  94. package/extensions/quiet-tools/README.md +30 -19
  95. package/extensions/quiet-tools/index.ts +376 -291
  96. package/extensions/quiet-tools/package.json +2 -2
  97. package/extensions/review/README.md +2 -2
  98. package/extensions/review/index.ts +1745 -1872
  99. package/extensions/review/package.json +1 -1
  100. package/extensions/todo/index.ts +280 -280
  101. package/extensions/todo/package.json +1 -1
  102. package/extensions/triage-comments/README.md +6 -4
  103. package/extensions/triage-comments/index.ts +2620 -1888
  104. package/extensions/triage-comments/package.json +1 -1
  105. package/package.json +9 -6
  106. package/assets/social-preview.png +0 -0
  107. package/extensions/claude-fast/claude-fast.example.json +0 -4
  108. package/extensions/openai-fast/openai-fast.example.json +0 -4
@@ -25,308 +25,300 @@ type DesktopBackend = "auto" | "macos" | "linux" | "windows-toast" | "none";
25
25
  type SoundBackend = "auto" | "macos" | "linux" | "windows-beep" | "command" | "none";
26
26
 
27
27
  type ProjectConfigContext = {
28
- cwd: string;
29
- isProjectTrusted?: () => boolean;
28
+ cwd: string;
29
+ isProjectTrusted?: () => boolean;
30
30
  };
31
31
 
32
32
  interface NotifyConfig {
33
- enabled: boolean;
34
- onlyWhenInteractive: boolean;
35
- title: string;
36
- body: string;
37
- channels: {
38
- terminal: boolean;
39
- desktop: boolean;
40
- bell: boolean;
41
- sound: boolean;
42
- };
43
- terminal: {
44
- backend: TerminalBackend;
45
- tmuxPassthrough: TmuxPassthrough;
46
- };
47
- desktop: {
48
- backend: DesktopBackend;
49
- };
50
- sound: {
51
- backend: SoundBackend;
52
- name: string;
53
- linuxSoundId: string;
54
- frequencyHz: number;
55
- durationMs: number;
56
- command: string;
57
- };
33
+ enabled: boolean;
34
+ onlyWhenInteractive: boolean;
35
+ title: string;
36
+ body: string;
37
+ channels: {
38
+ terminal: boolean;
39
+ desktop: boolean;
40
+ bell: boolean;
41
+ sound: boolean;
42
+ };
43
+ terminal: {
44
+ backend: TerminalBackend;
45
+ tmuxPassthrough: TmuxPassthrough;
46
+ };
47
+ desktop: {
48
+ backend: DesktopBackend;
49
+ };
50
+ sound: {
51
+ backend: SoundBackend;
52
+ name: string;
53
+ linuxSoundId: string;
54
+ frequencyHz: number;
55
+ durationMs: number;
56
+ command: string;
57
+ };
58
58
  }
59
59
 
60
60
  const DEFAULT_CONFIG: NotifyConfig = {
61
- enabled: true,
62
- onlyWhenInteractive: true,
63
- title: "Pi",
64
- body: "Ready for input",
65
- channels: {
66
- terminal: true,
67
- desktop: true,
68
- bell: true,
69
- sound: false,
70
- },
71
- terminal: {
72
- backend: "auto",
73
- tmuxPassthrough: "auto",
74
- },
75
- desktop: {
76
- backend: "auto",
77
- },
78
- sound: {
79
- backend: "auto",
80
- name: "Glass",
81
- linuxSoundId: "complete",
82
- frequencyHz: 1000,
83
- durationMs: 250,
84
- command: "",
85
- },
61
+ enabled: true,
62
+ onlyWhenInteractive: true,
63
+ title: "Pi",
64
+ body: "Ready for input",
65
+ channels: {
66
+ terminal: true,
67
+ desktop: true,
68
+ bell: true,
69
+ sound: false,
70
+ },
71
+ terminal: {
72
+ backend: "auto",
73
+ tmuxPassthrough: "auto",
74
+ },
75
+ desktop: {
76
+ backend: "auto",
77
+ },
78
+ sound: {
79
+ backend: "auto",
80
+ name: "Glass",
81
+ linuxSoundId: "complete",
82
+ frequencyHz: 1000,
83
+ durationMs: 250,
84
+ command: "",
85
+ },
86
86
  };
87
87
 
88
88
  function readConfigFile(path: string): Partial<NotifyConfig> {
89
- if (!existsSync(path)) return {};
90
-
91
- try {
92
- return JSON.parse(readFileSync(path, "utf-8")) as Partial<NotifyConfig>;
93
- } catch (error) {
94
- console.error(`Warning: Could not parse ${path}: ${error}`);
95
- return {};
96
- }
89
+ if (!existsSync(path)) return {};
90
+
91
+ try {
92
+ return JSON.parse(readFileSync(path, "utf-8")) as Partial<NotifyConfig>;
93
+ } catch (error) {
94
+ console.error(`Warning: Could not parse ${path}: ${error}`);
95
+ return {};
96
+ }
97
97
  }
98
98
 
99
99
  function mergeConfig(base: NotifyConfig, overrides: Partial<NotifyConfig>): NotifyConfig {
100
- return {
101
- ...base,
102
- ...overrides,
103
- channels: {
104
- ...base.channels,
105
- ...overrides.channels,
106
- },
107
- terminal: {
108
- ...base.terminal,
109
- ...overrides.terminal,
110
- },
111
- desktop: {
112
- ...base.desktop,
113
- ...overrides.desktop,
114
- },
115
- sound: {
116
- ...base.sound,
117
- ...overrides.sound,
118
- },
119
- };
100
+ return {
101
+ ...base,
102
+ ...overrides,
103
+ channels: {
104
+ ...base.channels,
105
+ ...overrides.channels,
106
+ },
107
+ terminal: {
108
+ ...base.terminal,
109
+ ...overrides.terminal,
110
+ },
111
+ desktop: {
112
+ ...base.desktop,
113
+ ...overrides.desktop,
114
+ },
115
+ sound: {
116
+ ...base.sound,
117
+ ...overrides.sound,
118
+ },
119
+ };
120
120
  }
121
121
 
122
122
  function canReadProjectConfig(ctx: ProjectConfigContext): boolean {
123
- return typeof ctx.isProjectTrusted === "function" && ctx.isProjectTrusted();
123
+ return typeof ctx.isProjectTrusted === "function" && ctx.isProjectTrusted();
124
124
  }
125
125
 
126
126
  function loadConfig(ctx: ProjectConfigContext): NotifyConfig {
127
- const globalConfig = readConfigFile(join(getAgentDir(), "extensions", "notify.json"));
128
- const projectConfig = canReadProjectConfig(ctx)
129
- ? readConfigFile(join(ctx.cwd, CONFIG_DIR_NAME, "notify.json"))
130
- : {};
131
- return mergeConfig(mergeConfig(DEFAULT_CONFIG, globalConfig), projectConfig);
127
+ const globalConfig = readConfigFile(join(getAgentDir(), "extensions", "notify.json"));
128
+ const projectConfig = canReadProjectConfig(ctx)
129
+ ? readConfigFile(join(ctx.cwd, CONFIG_DIR_NAME, "notify.json"))
130
+ : {};
131
+ return mergeConfig(mergeConfig(DEFAULT_CONFIG, globalConfig), projectConfig);
132
132
  }
133
133
 
134
134
  function powershellString(value: string): string {
135
- return `'${value.replace(/'/g, "''")}'`;
135
+ return `'${value.replace(/'/g, "''")}'`;
136
136
  }
137
137
 
138
138
  function windowsToastScript(title: string, body: string): string {
139
- const type = "Windows.UI.Notifications";
140
- const mgr = `[${type}.ToastNotificationManager, ${type}, ContentType = WindowsRuntime]`;
141
- const template = `[${type}.ToastTemplateType]::ToastText01`;
142
- const toast = `[${type}.ToastNotification]::new($xml)`;
143
- return [
144
- `${mgr} > $null`,
145
- `$xml = [${type}.ToastNotificationManager]::GetTemplateContent(${template})`,
146
- `$xml.GetElementsByTagName('text')[0].AppendChild($xml.CreateTextNode(${powershellString(body)})) > $null`,
147
- `[${type}.ToastNotificationManager]::CreateToastNotifier(${powershellString(title)}).Show(${toast})`,
148
- ].join("; ");
139
+ const type = "Windows.UI.Notifications";
140
+ const mgr = `[${type}.ToastNotificationManager, ${type}, ContentType = WindowsRuntime]`;
141
+ const template = `[${type}.ToastTemplateType]::ToastText01`;
142
+ const toast = `[${type}.ToastNotification]::new($xml)`;
143
+ return [
144
+ `${mgr} > $null`,
145
+ `$xml = [${type}.ToastNotificationManager]::GetTemplateContent(${template})`,
146
+ `$xml.GetElementsByTagName('text')[0].AppendChild($xml.CreateTextNode(${powershellString(body)})) > $null`,
147
+ `[${type}.ToastNotificationManager]::CreateToastNotifier(${powershellString(title)}).Show(${toast})`,
148
+ ].join("; ");
149
149
  }
150
150
 
151
- /**
152
- * Inside tmux, terminal notifications are written to tmux, not to the terminal
153
- * that can act on them: tmux is the pane's emulator and parses the sequence
154
- * itself. tmux has no OSC 777 or OSC 99 handler (checked in 3.7b, whose OSC
155
- * dispatch covers 4/7/8/9;4/10/11/12/52/104/133) and does not forward OSC codes
156
- * it does not recognise, so both backends are silently dropped.
157
- *
158
- * tmux's DCS passthrough asks it to write a payload to the outer terminal
159
- * verbatim. Every ESC inside the payload must be doubled.
160
- *
161
- * Users must also allow passthrough in tmux.conf:
162
- *
163
- * set -g allow-passthrough all
164
- *
165
- * `all` rather than `on`, because `on` only honours passthrough from panes that
166
- * are currently visible - which drops exactly the notification that matters,
167
- * the one fired while the user is looking at a different window. When
168
- * passthrough is disabled entirely, the wrapped sequence is dropped just like
169
- * the bare one, so wrapping is never a regression.
170
- */
151
+ /** tmux drops unrecognized OSC, so wrap in DCS passthrough with doubled ESC. */
171
152
  function wrapForTmux(sequence: string): string {
172
- return `\x1bPtmux;${sequence.replaceAll("\x1b", "\x1b\x1b")}\x1b\\`;
153
+ return `\x1bPtmux;${sequence.replaceAll("\x1b", "\x1b\x1b")}\x1b\\`;
173
154
  }
174
155
 
175
156
  function shouldWrapForTmux(config: NotifyConfig): boolean {
176
- if (config.terminal.tmuxPassthrough === "never") return false;
177
- if (config.terminal.tmuxPassthrough === "always") return true;
178
- return Boolean(process.env.TMUX);
157
+ if (config.terminal.tmuxPassthrough === "never") return false;
158
+ if (config.terminal.tmuxPassthrough === "always") return true;
159
+ return Boolean(process.env.TMUX);
179
160
  }
180
161
 
181
162
  function writeTerminalSequence(sequence: string, wrap: boolean): void {
182
- process.stdout.write(wrap ? wrapForTmux(sequence) : sequence);
163
+ process.stdout.write(wrap ? wrapForTmux(sequence) : sequence);
183
164
  }
184
165
 
185
166
  function notifyOSC777(title: string, body: string, wrap: boolean): void {
186
- writeTerminalSequence(`\x1b]777;notify;${title};${body}\x07`, wrap);
167
+ writeTerminalSequence(`\x1b]777;notify;${title};${body}\x07`, wrap);
187
168
  }
188
169
 
189
170
  function notifyOSC99(title: string, body: string, wrap: boolean): void {
190
- writeTerminalSequence(`\x1b]99;i=1:d=0;${title}\x1b\\`, wrap);
191
- writeTerminalSequence(`\x1b]99;i=1:p=body;${body}\x1b\\`, wrap);
171
+ writeTerminalSequence(`\x1b]99;i=1:d=0;${title}\x1b\\`, wrap);
172
+ writeTerminalSequence(`\x1b]99;i=1:p=body;${body}\x1b\\`, wrap);
192
173
  }
193
174
 
194
175
  function ringBell(): void {
195
- process.stdout.write("\x07");
176
+ process.stdout.write("\x07");
196
177
  }
197
178
 
198
179
  function runCommand(command: string, args: string[]): Promise<boolean> {
199
- return new Promise((resolve) => {
200
- execFile(command, args, (error) => resolve(!error));
201
- });
180
+ return new Promise((resolve) => {
181
+ execFile(command, args, (error) => resolve(!error));
182
+ });
202
183
  }
203
184
 
204
185
  function runShellCommand(command: string): Promise<boolean> {
205
- if (process.platform === "win32") {
206
- return runCommand("cmd.exe", ["/d", "/s", "/c", command]);
207
- }
186
+ if (process.platform === "win32") {
187
+ return runCommand("cmd.exe", ["/d", "/s", "/c", command]);
188
+ }
208
189
 
209
- return runCommand(process.env.SHELL || "/bin/sh", ["-lc", command]);
190
+ return runCommand(process.env.SHELL || "/bin/sh", ["-lc", command]);
210
191
  }
211
192
 
212
193
  function detectTerminalBackend(config: NotifyConfig): Exclude<TerminalBackend, "auto"> {
213
- if (config.terminal.backend !== "auto") return config.terminal.backend;
214
- if (process.env.KITTY_WINDOW_ID) return "osc99";
215
- return "osc777";
194
+ if (config.terminal.backend !== "auto") return config.terminal.backend;
195
+ if (process.env.KITTY_WINDOW_ID) return "osc99";
196
+ return "osc777";
216
197
  }
217
198
 
218
199
  function detectDesktopBackend(config: NotifyConfig): Exclude<DesktopBackend, "auto"> {
219
- if (config.desktop.backend !== "auto") return config.desktop.backend;
220
- if (process.env.WT_SESSION || process.env.WSL_DISTRO_NAME) return "windows-toast";
221
- if (process.platform === "darwin") return "macos";
222
- if (process.platform === "linux") return "linux";
223
- if (process.platform === "win32") return "windows-toast";
224
- return "none";
200
+ if (config.desktop.backend !== "auto") return config.desktop.backend;
201
+ if (process.env.WT_SESSION || process.env.WSL_DISTRO_NAME) return "windows-toast";
202
+ if (process.platform === "darwin") return "macos";
203
+ if (process.platform === "linux") return "linux";
204
+ if (process.platform === "win32") return "windows-toast";
205
+ return "none";
225
206
  }
226
207
 
227
208
  function detectSoundBackend(config: NotifyConfig): Exclude<SoundBackend, "auto"> {
228
- if (config.sound.backend !== "auto") return config.sound.backend;
229
- if (process.env.WT_SESSION || process.platform === "win32" || process.env.WSL_DISTRO_NAME) return "windows-beep";
230
- if (process.platform === "darwin") return "macos";
231
- if (process.platform === "linux") return "linux";
232
- return "none";
209
+ if (config.sound.backend !== "auto") return config.sound.backend;
210
+ if (process.env.WT_SESSION || process.platform === "win32" || process.env.WSL_DISTRO_NAME)
211
+ return "windows-beep";
212
+ if (process.platform === "darwin") return "macos";
213
+ if (process.platform === "linux") return "linux";
214
+ return "none";
233
215
  }
234
216
 
235
217
  function sendTerminalNotification(
236
- title: string,
237
- body: string,
238
- backend: Exclude<TerminalBackend, "auto">,
239
- wrap: boolean,
218
+ title: string,
219
+ body: string,
220
+ backend: Exclude<TerminalBackend, "auto">,
221
+ wrap: boolean,
240
222
  ): void {
241
- if (backend === "osc99") {
242
- notifyOSC99(title, body, wrap);
243
- return;
244
- }
245
- if (backend === "osc777") {
246
- notifyOSC777(title, body, wrap);
247
- }
223
+ if (backend === "osc99") {
224
+ notifyOSC99(title, body, wrap);
225
+ return;
226
+ }
227
+ if (backend === "osc777") {
228
+ notifyOSC777(title, body, wrap);
229
+ }
248
230
  }
249
231
 
250
232
  function appleScriptString(value: string): string {
251
- return JSON.stringify(value);
233
+ return JSON.stringify(value);
252
234
  }
253
235
 
254
236
  function sendDesktopNotification(
255
- title: string,
256
- body: string,
257
- backend: Exclude<DesktopBackend, "auto">,
237
+ title: string,
238
+ body: string,
239
+ backend: Exclude<DesktopBackend, "auto">,
258
240
  ): Promise<boolean> {
259
- if (backend === "windows-toast") {
260
- return runCommand("powershell.exe", ["-NoProfile", "-Command", windowsToastScript(title, body)]);
261
- }
262
- if (backend === "macos") {
263
- return runCommand("osascript", ["-e", `display notification ${appleScriptString(body)} with title ${appleScriptString(title)}`]);
264
- }
265
- if (backend === "linux") {
266
- return runCommand("notify-send", [title, body]);
267
- }
268
- return Promise.resolve(false);
241
+ if (backend === "windows-toast") {
242
+ return runCommand("powershell.exe", [
243
+ "-NoProfile",
244
+ "-Command",
245
+ windowsToastScript(title, body),
246
+ ]);
247
+ }
248
+ if (backend === "macos") {
249
+ return runCommand("osascript", [
250
+ "-e",
251
+ `display notification ${appleScriptString(body)} with title ${appleScriptString(title)}`,
252
+ ]);
253
+ }
254
+ if (backend === "linux") {
255
+ return runCommand("notify-send", [title, body]);
256
+ }
257
+ return Promise.resolve(false);
269
258
  }
270
259
 
271
- async function playSound(config: NotifyConfig, backend: Exclude<SoundBackend, "auto">): Promise<boolean> {
272
- if (backend === "command") {
273
- if (!config.sound.command.trim()) return false;
274
- return runShellCommand(config.sound.command);
275
- }
276
-
277
- if (backend === "windows-beep") {
278
- return runCommand("powershell.exe", [
279
- "-NoProfile",
280
- "-Command",
281
- `[console]::beep(${config.sound.frequencyHz}, ${config.sound.durationMs})`,
282
- ]);
283
- }
284
-
285
- if (backend === "macos") {
286
- return runCommand("afplay", [`/System/Library/Sounds/${config.sound.name}.aiff`]);
287
- }
288
-
289
- if (backend === "linux") {
290
- const soundId = config.sound.linuxSoundId;
291
- const viaCanberra = await runCommand("canberra-gtk-play", ["-i", soundId]);
292
- if (viaCanberra) return true;
293
- return runCommand("paplay", [`/usr/share/sounds/freedesktop/stereo/${soundId}.oga`]);
294
- }
295
-
296
- return false;
260
+ async function playSound(
261
+ config: NotifyConfig,
262
+ backend: Exclude<SoundBackend, "auto">,
263
+ ): Promise<boolean> {
264
+ if (backend === "command") {
265
+ if (!config.sound.command.trim()) return false;
266
+ return runShellCommand(config.sound.command);
267
+ }
268
+
269
+ if (backend === "windows-beep") {
270
+ return runCommand("powershell.exe", [
271
+ "-NoProfile",
272
+ "-Command",
273
+ `[console]::beep(${config.sound.frequencyHz}, ${config.sound.durationMs})`,
274
+ ]);
275
+ }
276
+
277
+ if (backend === "macos") {
278
+ return runCommand("afplay", [`/System/Library/Sounds/${config.sound.name}.aiff`]);
279
+ }
280
+
281
+ if (backend === "linux") {
282
+ const soundId = config.sound.linuxSoundId;
283
+ const viaCanberra = await runCommand("canberra-gtk-play", ["-i", soundId]);
284
+ if (viaCanberra) return true;
285
+ return runCommand("paplay", [`/usr/share/sounds/freedesktop/stereo/${soundId}.oga`]);
286
+ }
287
+
288
+ return false;
297
289
  }
298
290
 
299
291
  export default function notifyExtension(pi: ExtensionAPI) {
300
- pi.on("agent_settled", async (_event, ctx) => {
301
- const config = loadConfig(ctx);
302
- if (!config.enabled) return;
303
- if (config.onlyWhenInteractive && !ctx.hasUI) return;
304
-
305
- const tasks: Array<Promise<unknown>> = [];
306
-
307
- if (config.channels.terminal) {
308
- sendTerminalNotification(
309
- config.title,
310
- config.body,
311
- detectTerminalBackend(config),
312
- shouldWrapForTmux(config),
313
- );
314
- }
315
-
316
- if (config.channels.desktop) {
317
- tasks.push(sendDesktopNotification(config.title, config.body, detectDesktopBackend(config)));
318
- }
319
-
320
- if (config.channels.bell) {
321
- ringBell();
322
- }
323
-
324
- if (config.channels.sound) {
325
- tasks.push(playSound(config, detectSoundBackend(config)));
326
- }
327
-
328
- if (tasks.length > 0) {
329
- await Promise.allSettled(tasks);
330
- }
331
- });
292
+ pi.on("agent_settled", async (_event, ctx) => {
293
+ const config = loadConfig(ctx);
294
+ if (!config.enabled) return;
295
+ if (config.onlyWhenInteractive && !ctx.hasUI) return;
296
+
297
+ const tasks: Array<Promise<unknown>> = [];
298
+
299
+ if (config.channels.terminal) {
300
+ sendTerminalNotification(
301
+ config.title,
302
+ config.body,
303
+ detectTerminalBackend(config),
304
+ shouldWrapForTmux(config),
305
+ );
306
+ }
307
+
308
+ if (config.channels.desktop) {
309
+ tasks.push(sendDesktopNotification(config.title, config.body, detectDesktopBackend(config)));
310
+ }
311
+
312
+ if (config.channels.bell) {
313
+ ringBell();
314
+ }
315
+
316
+ if (config.channels.sound) {
317
+ tasks.push(playSound(config, detectSoundBackend(config)));
318
+ }
319
+
320
+ if (tasks.length > 0) {
321
+ await Promise.allSettled(tasks);
322
+ }
323
+ });
332
324
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@diegopetrucci/pi-notify",
3
- "version": "0.1.18",
3
+ "version": "0.1.19",
4
4
  "description": "A pi extension that sends a notification when the agent is ready for input.",
5
5
  "keywords": [
6
6
  "pi-package",
@@ -1,83 +1,21 @@
1
1
  # openai-fast
2
2
 
3
- A pi extension that enables OpenAI Codex Fast mode for ChatGPT-auth GPT-5.5 and GPT-5.6 Codex variants (`gpt-5.6-sol`, `gpt-5.6-terra`, `gpt-5.6-luna`).
3
+ Deprecated standalone leftover. This package no longer enables Codex Fast mode, registers `/fast`, or reads `openai-fast.json`. On startup it clears a leftover `openai-fast` footer status and removes `fast-mode-2026-02-01` from the current model header. It does not inject Fast fields into requests.
4
4
 
5
- This package is standalone-only and is not auto-loaded by the `@diegopetrucci/pi-extensions` collection package. The collection uses the unified [`fast`](../fast) extension instead.
5
+ Fast mode lives in the unified [`fast`](../fast) extension. Install `@diegopetrucci/pi-fast`, or install the collection, which loads `extensions/fast` only.
6
6
 
7
- When active, the extension injects this into eligible OpenAI Codex request payloads:
8
-
9
- ```json
10
- {
11
- "service_tier": "priority"
12
- }
13
- ```
14
-
15
- The user-facing feature is OpenAI Codex **Fast mode**. The wire value is `priority` because current Codex clients map Fast mode to the OpenAI priority service tier.
16
-
17
- ## Eligibility
18
-
19
- Fast mode is only injected when all of these are true:
20
-
21
- - The current provider is `openai-codex`.
22
- - The current API is `openai-codex-responses`.
23
- - The current model is `gpt-5.5`, `gpt-5.6-sol`, `gpt-5.6-terra`, or `gpt-5.6-luna`.
24
- - The provider is using ChatGPT OAuth/subscription auth, not API-key auth.
25
- - The request payload does not already include `service_tier`.
26
-
27
- ## Commands
28
-
29
- ```text
30
- /fast
31
- ```
32
-
33
- Run `/fast` to toggle Fast mode on or off for the current session/runtime. The command reports the new state in chat, and the footer shows `fast` while Fast mode is active for an eligible model.
34
-
35
- The extension defaults to off so installing the full collection does not accidentally spend Fast-mode credits.
36
-
37
- ## Config
38
-
39
- Optional global config:
40
-
41
- ```text
42
- ~/<pi-config-dir>/agent/extensions/openai-fast.json
43
- ```
44
-
45
- Optional project config:
46
-
47
- ```text
48
- <project>/<pi-config-dir>/openai-fast.json
49
- ```
50
-
51
- Here `<pi-config-dir>` is Pi's runtime config directory name (`CONFIG_DIR_NAME`; `.pi` by default). Project config overrides global config after Pi reports that the project is trusted.
52
-
53
- ```json
54
- {
55
- "enabled": false,
56
- "showStatus": true
57
- }
58
- ```
59
-
60
- - `enabled`: default Fast-mode state when there is no session override.
61
- - `showStatus`: show a compact `fast` status when Fast mode is active for the current model.
7
+ This package is standalone-only and is not auto-loaded by the `@diegopetrucci/pi-extensions` collection package.
62
8
 
63
9
  ## Install
64
10
 
65
- ### Standalone npm package
66
-
67
11
  ```bash
68
12
  pi install npm:@diegopetrucci/pi-openai-fast
69
13
  ```
70
14
 
71
- ### Collection package
72
-
73
- ```bash
74
- pi install npm:@diegopetrucci/pi-extensions
75
- ```
76
-
77
- ### GitHub package
15
+ Installing this package does not enable Fast mode. For the working extension:
78
16
 
79
17
  ```bash
80
- pi install git:github.com/diegopetrucci/pi-extensions
18
+ pi install npm:@diegopetrucci/pi-fast
81
19
  ```
82
20
 
83
21
  Then reload pi:
@@ -85,9 +23,3 @@ Then reload pi:
85
23
  ```text
86
24
  /reload
87
25
  ```
88
-
89
- ## Notes
90
-
91
- - This extension intentionally does not affect API-key OpenAI models.
92
- - Pi may only account Fast-mode cost correctly when the backend reports `service_tier: "priority"` in the streamed response. The extension does not patch usage totals to avoid double-counting.
93
- - If pi adds first-class service-tier support later, this extension skips payloads that already contain `service_tier`.