@diegopetrucci/pi-extensions 0.1.65 → 0.1.66

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 (69) hide show
  1. package/.pi-fleet-tested-version +1 -1
  2. package/README.md +1 -1
  3. package/extensions/agent-workflow-audit/.pi-fleet-tested-version +1 -1
  4. package/extensions/agent-workflow-audit/package.json +1 -1
  5. package/extensions/annotate-git-diff/.pi-fleet-tested-version +1 -1
  6. package/extensions/annotate-git-diff/README.md +3 -0
  7. package/extensions/annotate-git-diff/index.ts +49 -5
  8. package/extensions/annotate-git-diff/package.json +2 -1
  9. package/extensions/annotate-git-diff/quiet-glimpse.ts +24 -3
  10. package/extensions/annotate-git-diff/review-server.ts +379 -0
  11. package/extensions/annotate-git-diff/ui.ts +17 -41
  12. package/extensions/annotate-last-message/.pi-fleet-tested-version +1 -1
  13. package/extensions/annotate-last-message/index.ts +28 -2
  14. package/extensions/annotate-last-message/package.json +1 -1
  15. package/extensions/annotate-last-message/quiet-glimpse.ts +24 -3
  16. package/extensions/brrr/.pi-fleet-tested-version +1 -1
  17. package/extensions/brrr/package.json +1 -1
  18. package/extensions/claude-fast/.pi-fleet-tested-version +1 -1
  19. package/extensions/claude-fast/package.json +1 -1
  20. package/extensions/code-reviewer/.pi-fleet-tested-version +1 -1
  21. package/extensions/code-reviewer/package.json +1 -1
  22. package/extensions/confirm-destructive/.pi-fleet-tested-version +1 -1
  23. package/extensions/confirm-destructive/package.json +1 -1
  24. package/extensions/context-cap/.pi-fleet-tested-version +1 -1
  25. package/extensions/context-cap/package.json +1 -1
  26. package/extensions/context-inspector/.pi-fleet-tested-version +1 -1
  27. package/extensions/context-inspector/package.json +1 -1
  28. package/extensions/contrarian/.pi-fleet-tested-version +1 -1
  29. package/extensions/contrarian/index.ts +46 -17
  30. package/extensions/contrarian/package.json +1 -1
  31. package/extensions/dirty-repo-guard/.pi-fleet-tested-version +1 -1
  32. package/extensions/dirty-repo-guard/package.json +1 -1
  33. package/extensions/dynamic-context-pruning/.pi-fleet-tested-version +1 -1
  34. package/extensions/dynamic-context-pruning/package.json +1 -1
  35. package/extensions/fast/.pi-fleet-tested-version +1 -1
  36. package/extensions/fast/package.json +1 -1
  37. package/extensions/git-footer/.pi-fleet-tested-version +1 -1
  38. package/extensions/git-footer/package.json +1 -1
  39. package/extensions/gnosis/.pi-fleet-tested-version +1 -1
  40. package/extensions/gnosis/package.json +1 -1
  41. package/extensions/illustrations-to-explain-things/.pi-fleet-tested-version +1 -1
  42. package/extensions/illustrations-to-explain-things/package.json +1 -1
  43. package/extensions/inline-bash/.pi-fleet-tested-version +1 -1
  44. package/extensions/inline-bash/package.json +1 -1
  45. package/extensions/librarian/.pi-fleet-tested-version +1 -1
  46. package/extensions/librarian/package.json +1 -1
  47. package/extensions/minimal-footer/.pi-fleet-tested-version +1 -1
  48. package/extensions/minimal-footer/package.json +1 -1
  49. package/extensions/notify/.pi-fleet-tested-version +1 -1
  50. package/extensions/notify/package.json +1 -1
  51. package/extensions/openai-fast/.pi-fleet-tested-version +1 -1
  52. package/extensions/openai-fast/package.json +1 -1
  53. package/extensions/oracle/.pi-fleet-tested-version +1 -1
  54. package/extensions/oracle/index.ts +46 -17
  55. package/extensions/oracle/package.json +1 -1
  56. package/extensions/permission-gate/.pi-fleet-tested-version +1 -1
  57. package/extensions/permission-gate/README.md +11 -3
  58. package/extensions/permission-gate/index.ts +70 -7
  59. package/extensions/permission-gate/package.json +4 -2
  60. package/extensions/permission-gate/powershell-safety.ts +1006 -0
  61. package/extensions/quiet-tools/.pi-fleet-tested-version +1 -1
  62. package/extensions/quiet-tools/package.json +1 -1
  63. package/extensions/review/.pi-fleet-tested-version +1 -1
  64. package/extensions/review/package.json +1 -1
  65. package/extensions/todo/.pi-fleet-tested-version +1 -1
  66. package/extensions/todo/package.json +1 -1
  67. package/extensions/triage-comments/.pi-fleet-tested-version +1 -1
  68. package/extensions/triage-comments/package.json +1 -1
  69. package/package.json +4 -4
@@ -1 +1 @@
1
- 0.84.2
1
+ 0.84.4
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@diegopetrucci/pi-inline-bash",
3
- "version": "0.1.10",
3
+ "version": "0.1.11",
4
4
  "description": "A pi extension that expands inline bash commands in user prompts.",
5
5
  "keywords": [
6
6
  "pi-package",
@@ -1 +1 @@
1
- 0.84.2
1
+ 0.84.4
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@diegopetrucci/pi-librarian",
3
- "version": "0.1.15",
3
+ "version": "0.1.16",
4
4
  "description": "A pi GitHub research scout with a toggleable local repo checkout cache under the user's OS cache directory.",
5
5
  "keywords": [
6
6
  "pi-package",
@@ -1 +1 @@
1
- 0.84.2
1
+ 0.84.4
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@diegopetrucci/pi-minimal-footer",
3
- "version": "0.1.19",
3
+ "version": "0.1.20",
4
4
  "description": "A minimal custom footer for pi.",
5
5
  "keywords": [
6
6
  "pi-package",
@@ -1 +1 @@
1
- 0.84.2
1
+ 0.84.4
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@diegopetrucci/pi-notify",
3
- "version": "0.1.17",
3
+ "version": "0.1.18",
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 +1 @@
1
- 0.84.2
1
+ 0.84.4
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@diegopetrucci/pi-openai-fast",
3
- "version": "0.1.16",
3
+ "version": "0.1.17",
4
4
  "description": "A pi extension that enables OpenAI Codex Fast mode for ChatGPT-auth GPT-5.5 and GPT-5.6 Codex variants by injecting the priority service tier.",
5
5
  "keywords": [
6
6
  "pi-package",
@@ -1 +1 @@
1
- 0.84.2
1
+ 0.84.4
@@ -115,6 +115,9 @@ const PROVIDER_MODEL_PREFERENCES: Record<string, string[]> = {
115
115
  "claude-opus-4-5",
116
116
  "claude-opus-4-1",
117
117
  "claude-opus-4",
118
+ "openai.gpt-5.6-sol",
119
+ "openai.gpt-5.6-terra",
120
+ "openai.gpt-5.6-luna",
118
121
  "claude-sonnet-5",
119
122
  "claude-sonnet-4-6",
120
123
  "claude-sonnet-4-5",
@@ -125,6 +128,7 @@ const PROVIDER_MODEL_PREFERENCES: Record<string, string[]> = {
125
128
  "minimax-m2.5",
126
129
  "minimax-m2.1",
127
130
  "zai.glm-5",
131
+ "xai.grok-4.6",
128
132
  ],
129
133
  anthropic: [
130
134
  "claude-opus-5",
@@ -160,6 +164,7 @@ const PROVIDER_MODEL_PREFERENCES: Record<string, string[]> = {
160
164
  baseten: [
161
165
  "moonshotai/Kimi-K3",
162
166
  "moonshotai/Kimi-K2.7-Code",
167
+ "zai-org/GLM-5.3",
163
168
  "deepseek-ai/DeepSeek-V4-Pro-0813",
164
169
  "deepseek-ai/DeepSeek-V4-Pro",
165
170
  "zai-org/GLM-5.2",
@@ -176,43 +181,49 @@ const PROVIDER_MODEL_PREFERENCES: Record<string, string[]> = {
176
181
  "zai-org/GLM-5.2-Fast",
177
182
  "zai-org/GLM-4.7",
178
183
  ],
179
- cerebras: ["gpt-oss-120b", "zai-glm-4.7", "gemma-4-31b"],
184
+ cerebras: ["gpt-oss-120b", "gemma-4-31b"],
180
185
  "cloudflare-ai-gateway": [
181
186
  "claude-fable-5",
182
- "claude-opus-4-7",
183
- "claude-opus-4-6",
184
- "claude-opus-4-5",
187
+ "claude-opus-5",
188
+ "claude-opus-4.8",
189
+ "claude-opus-4.7",
190
+ "claude-opus-4.6",
191
+ "claude-opus-4.5",
185
192
  "gpt-5.5",
186
193
  "gpt-5.4",
187
- "gpt-5.3-codex",
194
+ "workers-ai/@cf/moonshotai/kimi-k2.7-code",
195
+ "workers-ai/@cf/deepseek-ai/deepseek-v4-pro-0813",
196
+ "workers-ai/@cf/zai-org/glm-5.3",
197
+ "workers-ai/@cf/zai-org/glm-5.2",
188
198
  "workers-ai/@cf/moonshotai/kimi-k2.6",
189
199
  "workers-ai/@cf/nvidia/nemotron-3-120b-a12b",
200
+ "workers-ai/@cf/openai/gpt-oss-120b",
190
201
  "workers-ai/@cf/zai-org/glm-4.7-flash",
191
202
  ],
192
203
  "cloudflare-workers-ai": [
193
204
  "@cf/moonshotai/kimi-k2.7-code",
205
+ "@cf/deepseek-ai/deepseek-v4-pro-0813",
206
+ "@cf/zai-org/glm-5.3",
194
207
  "@cf/zai-org/glm-5.2",
195
208
  "@cf/moonshotai/kimi-k2.6",
196
209
  "@cf/nvidia/nemotron-3-120b-a12b",
197
210
  "@cf/openai/gpt-oss-120b",
211
+ "@cf/qwen/qwen3.8-27b",
198
212
  "@cf/qwen/qwen3-30b-a3b-fp8",
199
213
  "@cf/zai-org/glm-4.7-flash",
200
214
  ],
201
215
  deepseek: ["deepseek-v4-pro", "deepseek-v4-flash"],
202
216
  fireworks: [
217
+ "accounts/fireworks/models/kimi-k3",
218
+ "accounts/fireworks/routers/kimi-k3-fast",
203
219
  "accounts/fireworks/models/deepseek-v4-pro",
204
220
  "accounts/fireworks/models/kimi-k2p7-code",
205
- "accounts/fireworks/routers/kimi-k2p7-code-fast",
206
221
  "accounts/fireworks/models/glm-5p2",
207
222
  "accounts/fireworks/routers/glm-5p2-fast",
208
223
  "accounts/fireworks/models/minimax-m3",
209
224
  "accounts/fireworks/models/kimi-k2p6",
210
- "accounts/fireworks/routers/kimi-k2p6-turbo",
211
- "accounts/fireworks/routers/kimi-k2p6-fast",
212
- "accounts/fireworks/models/minimax-m2p7",
213
225
  "accounts/fireworks/models/qwen3p7-plus",
214
226
  "accounts/fireworks/models/gpt-oss-120b",
215
- "accounts/fireworks/models/gpt-oss-20b",
216
227
  ],
217
228
  "github-copilot": [
218
229
  "claude-opus-5",
@@ -221,6 +232,9 @@ const PROVIDER_MODEL_PREFERENCES: Record<string, string[]> = {
221
232
  "claude-opus-4.7",
222
233
  "claude-opus-4.6",
223
234
  "claude-opus-4.5",
235
+ "gpt-5.6-sol",
236
+ "gpt-5.6-terra",
237
+ "gpt-5.6-luna",
224
238
  "gpt-5.5",
225
239
  "gpt-5.4",
226
240
  "gpt-5.3-codex",
@@ -232,6 +246,7 @@ const PROVIDER_MODEL_PREFERENCES: Record<string, string[]> = {
232
246
  "claude-sonnet-4.6",
233
247
  "claude-sonnet-4.5",
234
248
  "gpt-5-mini",
249
+ "kimi-k3",
235
250
  "kimi-k2.7-code",
236
251
  ],
237
252
  google: [
@@ -260,7 +275,10 @@ const PROVIDER_MODEL_PREFERENCES: Record<string, string[]> = {
260
275
  "openai/gpt-oss-20b",
261
276
  ],
262
277
  huggingface: [
278
+ "moonshotai/Kimi-K3",
263
279
  "zai-org/GLM-5.1",
280
+ "zai-org/GLM-5.3-Flash",
281
+ "deepseek-ai/DeepSeek-V4-Pro-0813",
264
282
  "deepseek-ai/DeepSeek-V4-Pro",
265
283
  "moonshotai/Kimi-K2.6",
266
284
  "MiniMaxAI/MiniMax-M2.7",
@@ -285,6 +303,7 @@ const PROVIDER_MODEL_PREFERENCES: Record<string, string[]> = {
285
303
  "mistral-medium-2508",
286
304
  "mistral-small-2603",
287
305
  "devstral-2512",
306
+ "zai-glm-5-2",
288
307
  ],
289
308
  openai: [
290
309
  "gpt-5.6-sol",
@@ -334,6 +353,7 @@ const PROVIDER_MODEL_PREFERENCES: Record<string, string[]> = {
334
353
  "glm-5.2",
335
354
  "glm-5.1",
336
355
  "glm-5",
356
+ "kimi-k3",
337
357
  "kimi-k2.7-code",
338
358
  "kimi-k2.6",
339
359
  "kimi-k2.5",
@@ -344,6 +364,7 @@ const PROVIDER_MODEL_PREFERENCES: Record<string, string[]> = {
344
364
  ],
345
365
  "opencode-go": [
346
366
  "deepseek-v4-pro",
367
+ "glm-5.3",
347
368
  "glm-5.2",
348
369
  "glm-5.1",
349
370
  "qwen3.7-max",
@@ -353,6 +374,7 @@ const PROVIDER_MODEL_PREFERENCES: Record<string, string[]> = {
353
374
  "mimo-v2.5",
354
375
  "minimax-m3",
355
376
  "minimax-m2.7",
377
+ "kimi-k3",
356
378
  "kimi-k2.7-code",
357
379
  "kimi-k2.6",
358
380
  ],
@@ -381,6 +403,7 @@ const PROVIDER_MODEL_PREFERENCES: Record<string, string[]> = {
381
403
  "~google/gemini-pro-latest",
382
404
  "google/gemini-3.5-flash",
383
405
  "google/gemini-2.5-pro",
406
+ "moonshotai/kimi-k3",
384
407
  "moonshotai/kimi-k2.7-code",
385
408
  "~moonshotai/kimi-latest",
386
409
  "moonshotai/kimi-k2.6",
@@ -393,6 +416,7 @@ const PROVIDER_MODEL_PREFERENCES: Record<string, string[]> = {
393
416
  "minimax/minimax-m3",
394
417
  "minimax/minimax-m2.7",
395
418
  "minimax/minimax-m2.1",
419
+ "z-ai/glm-5.3",
396
420
  "z-ai/glm-5.2",
397
421
  "z-ai/glm-5.1",
398
422
  ],
@@ -427,6 +451,8 @@ const PROVIDER_MODEL_PREFERENCES: Record<string, string[]> = {
427
451
  ],
428
452
  together: [
429
453
  "deepseek-ai/DeepSeek-V4-Pro",
454
+ "moonshotai/Kimi-K3",
455
+ "zai-org/GLM-5.3-Flash",
430
456
  "zai-org/GLM-5.2",
431
457
  "moonshotai/Kimi-K2.7-Code",
432
458
  "moonshotai/Kimi-K2.6",
@@ -459,6 +485,7 @@ const PROVIDER_MODEL_PREFERENCES: Record<string, string[]> = {
459
485
  "openai/gpt-5.2-codex",
460
486
  "openai/gpt-5.1-codex",
461
487
  "openai/gpt-5-codex",
488
+ "moonshotai/kimi-k3",
462
489
  "moonshotai/kimi-k2.7-code",
463
490
  "moonshotai/kimi-k2.7-code-highspeed",
464
491
  "moonshotai/kimi-k2.6",
@@ -476,6 +503,7 @@ const PROVIDER_MODEL_PREFERENCES: Record<string, string[]> = {
476
503
  "xai/grok-4.5",
477
504
  "xai/grok-4.20-reasoning",
478
505
  "minimax/minimax-m3",
506
+ "zai/glm-5.3",
479
507
  "zai/glm-5.2",
480
508
  "zai/glm-5.1",
481
509
  ],
@@ -485,15 +513,16 @@ const PROVIDER_MODEL_PREFERENCES: Record<string, string[]> = {
485
513
  "grok-4.3",
486
514
  "grok-build-0.1",
487
515
  ],
488
- xiaomi: ["mimo-v2.5-pro", "mimo-v2.5", "mimo-v2-pro", "mimo-v2-omni", "mimo-v2-flash"],
489
- "xiaomi-token-plan-ams": ["mimo-v2.5-pro", "mimo-v2.5", "mimo-v2-pro"],
490
- "xiaomi-token-plan-cn": ["mimo-v2.5-pro", "mimo-v2.5", "mimo-v2-pro"],
491
- "xiaomi-token-plan-sgp": ["mimo-v2.5-pro", "mimo-v2.5", "mimo-v2-pro"],
516
+ xiaomi: ["mimo-v2.5-pro-ultraspeed", "mimo-v2.5-pro", "mimo-v2.5"],
517
+ "xiaomi-token-plan-ams": ["mimo-v2.5-pro", "mimo-v2.5"],
518
+ "xiaomi-token-plan-cn": ["mimo-v2.5-pro", "mimo-v2.5"],
519
+ "xiaomi-token-plan-sgp": ["mimo-v2.5-pro", "mimo-v2.5"],
492
520
  nvidia: [
493
521
  "nvidia/nemotron-3-ultra-550b-a55b",
494
522
  "nvidia/nemotron-3-super-120b-a12b",
523
+ "moonshotai/kimi-k3",
524
+ "deepseek-ai/deepseek-v4-pro-0813",
495
525
  "moonshotai/kimi-k2.6",
496
- "z-ai/glm-5.2",
497
526
  "minimaxai/minimax-m3",
498
527
  "openai/gpt-oss-120b",
499
528
  "nvidia/nemotron-3-nano-omni-30b-a3b-reasoning",
@@ -512,8 +541,8 @@ const PROVIDER_MODEL_PREFERENCES: Record<string, string[]> = {
512
541
  "glm-5",
513
542
  "glm-4.7",
514
543
  ],
515
- moonshotai: ["kimi-k2.7-code", "kimi-k2.7-code-highspeed", "kimi-k2.6", "kimi-k2-thinking-turbo", "kimi-k2-thinking", "kimi-k2.5"],
516
- "moonshotai-cn": ["kimi-k2.7-code", "kimi-k2.7-code-highspeed", "kimi-k2.6", "kimi-k2-thinking-turbo", "kimi-k2-thinking", "kimi-k2.5"],
544
+ moonshotai: ["kimi-k3", "kimi-k2.7-code", "kimi-k2.7-code-highspeed", "kimi-k2.6", "kimi-k2-thinking-turbo", "kimi-k2-thinking", "kimi-k2.5"],
545
+ "moonshotai-cn": ["kimi-k3", "kimi-k2.7-code", "kimi-k2.7-code-highspeed", "kimi-k2.6", "kimi-k2-thinking-turbo", "kimi-k2-thinking", "kimi-k2.5"],
517
546
  };
518
547
 
519
548
  const ORACLE_SYSTEM_PROMPT = [
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@diegopetrucci/pi-oracle",
3
- "version": "0.1.25",
3
+ "version": "0.1.26",
4
4
  "description": "An Amp-style oracle extension for pi that consults the strongest reasoning model on your current provider.",
5
5
  "keywords": [
6
6
  "pi-package",
@@ -1 +1 @@
1
- 0.84.2
1
+ 0.84.4
@@ -1,12 +1,13 @@
1
1
  # permission-gate
2
2
 
3
- A small pi extension that prompts for confirmation before running potentially dangerous bash commands or writing to protected paths.
3
+ A small pi extension that prompts for confirmation before running potentially dangerous bash or PowerShell commands or writing to protected paths.
4
4
 
5
- This is adapted from the original `permission-gate.ts` example in [`earendil-works/pi`](https://github.com/earendil-works/pi/blob/main/packages/coding-agent/examples/extensions/permission-gate.ts) and kept basically the same.
5
+ This started from the original `permission-gate.ts` example in [`earendil-works/pi`](https://github.com/earendil-works/pi/blob/main/packages/coding-agent/examples/extensions/permission-gate.ts) and adds conservative shell and path hardening.
6
6
 
7
7
  ## What it checks
8
8
 
9
9
  - `rm -rf`
10
+ - PowerShell `Remove-Item -Recurse -Force` and its standard aliases, including module-qualified, interpolated, splatted, and backtick-escaped forms
10
11
  - `sudo`
11
12
  - `chmod` / `chown` with `777`
12
13
  - direct `write` / `edit` calls touching normalized protected paths:
@@ -18,6 +19,12 @@ Safe `.env` templates/examples such as `.env.example` and `.env.production.templ
18
19
 
19
20
  If pi is running without an interactive UI, it blocks matching commands and protected path writes by default.
20
21
 
22
+ For Pi's built-in local Windows PowerShell tool, commands that pass the fast lexical checks are also parsed with the same PowerShell installation's AST parser before execution. Malformed input, parser failures, `Remove-Item` parameter splatting, command-resolution changes (aliases/modules/providers/direct function or filter definitions), known script/process/job/member invocation wrappers, computed member calls, and analyzer inputs above 16,000 UTF-8 bytes are treated conservatively and require confirmation (or are blocked when no UI is available). Computed targets passed to provider-capable mutation commands also require confirmation because the target could resolve to `Alias:` or `Function:`. Literal quoted/commented examples, including literal here-strings, stay benign. Definitely enabled `-WhatIf` removals (`-WhatIf`, `-WhatIf:$true`, or `-WhatIf:1`) remain non-destructive and are allowed; computed switch values require confirmation.
23
+
24
+ Non-Windows and custom/remote `powershell` tools receive the conservative lexical checks, but only Pi's built-in local Windows tool can be verified with its actual parser. Pi extensions are trusted code: tool overrides, remote operations, spawn hooks, and mutations made by handlers loaded after this gate are outside its boundary and must enforce their own final-input policy.
25
+
26
+ This is a targeted confirmation guard for the command classes listed above, not a shell sandbox or a proof that every unlisted executable/API is harmless.
27
+
21
28
  ## Install
22
29
 
23
30
  ### Standalone npm package
@@ -47,6 +54,7 @@ Then reload pi:
47
54
  ## Notes
48
55
 
49
56
  - Hooks the `tool_call` event.
50
- - Inspects `bash`, `write`, and `edit` tool calls.
57
+ - Inspects `bash`, `powershell`, `write`, and `edit` tool calls.
58
+ - Validates shell timeouts as well as command strings and fails closed on malformed calls.
51
59
  - Normalizes relative/absolute paths before matching so traversal tricks do not bypass the guard.
52
60
  - Prompts with a simple `Yes` / `No` selector before allowing dangerous commands or protected path writes.
@@ -1,8 +1,8 @@
1
1
  /**
2
2
  * Permission Gate Extension
3
3
  *
4
- * Prompts for confirmation before running potentially dangerous bash commands
5
- * or modifying protected paths via write/edit.
4
+ * Prompts for confirmation before running potentially dangerous bash or
5
+ * PowerShell commands, or modifying protected paths via write/edit.
6
6
  * Protected paths include exact .git and node_modules path segments plus
7
7
  * secret-bearing .env files (excluding example/template variants).
8
8
  */
@@ -11,10 +11,19 @@ import * as path from "node:path";
11
11
 
12
12
  import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
13
13
 
14
+ import {
15
+ analyzePowerShellLexically,
16
+ buildPowerShellAstCommand,
17
+ MAX_POWERSHELL_ANALYSIS_SOURCE_BYTES,
18
+ parsePowerShellAstOutput,
19
+ } from "./powershell-safety.ts";
20
+
14
21
  const dangerousPatterns = [/\bsudo\b/i, /\b(chmod|chown)\b.*777/i];
15
22
  const protectedPathSegments = new Set([".git", "node_modules"]);
16
23
  const shellCommandSeparators = new Set([";", "&", "&&", "||", "|", "\n", "(", ")", "{", "}"]);
17
24
  const safeEnvSuffixes = new Set(["example", "examples", "template", "templates"]);
25
+ const MAX_SHELL_TIMEOUT_SECONDS = 2_147_483_647 / 1000;
26
+ const POWERSHELL_ANALYSIS_TIMEOUT_MS = 5_000;
18
27
 
19
28
  type GuardDecision = { block: true; reason: string } | undefined;
20
29
  type NormalizedPath = {
@@ -30,6 +39,7 @@ type ShellToken = {
30
39
 
31
40
  type WriteInput = { path: string; content: string };
32
41
  type EditInput = { path: string; edits: Array<{ oldText: string; newText: string }> };
42
+ type ShellInput = { command: string; timeout?: number };
33
43
 
34
44
  function isRecord(value: unknown): value is Record<string, unknown> {
35
45
  return !!value && typeof value === "object" && !Array.isArray(value);
@@ -524,6 +534,55 @@ function hasDangerousRecursiveRm(command: string): boolean {
524
534
  return evaluateTokens(currentCommand);
525
535
  }
526
536
 
537
+ function validateShellInput(input: unknown): ShellInput | undefined {
538
+ if (!isRecord(input) || typeof input.command !== "string" || input.command.trim() === "") return undefined;
539
+ if (
540
+ input.timeout !== undefined &&
541
+ (typeof input.timeout !== "number" ||
542
+ !Number.isFinite(input.timeout) ||
543
+ input.timeout <= 0 ||
544
+ input.timeout > MAX_SHELL_TIMEOUT_SECONDS)
545
+ ) {
546
+ return undefined;
547
+ }
548
+ return input.timeout === undefined
549
+ ? { command: input.command }
550
+ : { command: input.command, timeout: input.timeout };
551
+ }
552
+
553
+ async function hasDangerousPowerShellCommand(
554
+ pi: ExtensionAPI,
555
+ command: string,
556
+ signal?: AbortSignal,
557
+ ): Promise<boolean> {
558
+ // Keep both lexical work and Windows' base64-expanded -Command transport
559
+ // bounded below the platform command-line limit. Oversized input is
560
+ // deliberately treated as requiring confirmation rather than failing open.
561
+ if (Buffer.byteLength(command, "utf8") > MAX_POWERSHELL_ANALYSIS_SOURCE_BYTES) return true;
562
+
563
+ const lexicalAnalysis = analyzePowerShellLexically(command);
564
+ if (lexicalAnalysis.risky) return true;
565
+
566
+ // Pi only exposes its built-in PowerShell tool on Windows. On that platform,
567
+ // ask PowerShell's own parser for the final word so ordinary syntax features
568
+ // (module qualification, interpolation, splatting, and backtick escapes) cannot
569
+ // bypass the fallback lexer. Parser failures and malformed output fail closed.
570
+ if (process.platform !== "win32") return false;
571
+
572
+ try {
573
+ const { getPowerShellConfig } = await import("@earendil-works/pi-coding-agent");
574
+ const config = getPowerShellConfig();
575
+ const result = await pi.exec(config.shell, [...config.args, buildPowerShellAstCommand(command)], {
576
+ timeout: POWERSHELL_ANALYSIS_TIMEOUT_MS,
577
+ signal,
578
+ });
579
+ if (result.code !== 0 || result.killed) return true;
580
+ return parsePowerShellAstOutput(result.stdout).risky;
581
+ } catch {
582
+ return true;
583
+ }
584
+ }
585
+
527
586
  function validateWriteInput(input: unknown): WriteInput | undefined {
528
587
  if (!isRecord(input)) return undefined;
529
588
  if (typeof input.path !== "string" || typeof input.content !== "string") return undefined;
@@ -576,13 +635,17 @@ async function confirmProtectedPathAction(
576
635
 
577
636
  export default function (pi: ExtensionAPI) {
578
637
  pi.on("tool_call", async (event, ctx) => {
579
- if (event.toolName === "bash") {
580
- const command = event.input?.command;
581
- if (typeof command !== "string" || command.trim() === "") {
582
- return { block: true, reason: "Malformed bash command blocked" };
638
+ if (event.toolName === "bash" || event.toolName === "powershell") {
639
+ const shellToolName = event.toolName;
640
+ const input = validateShellInput(event.input);
641
+ if (!input) {
642
+ return { block: true, reason: `Malformed ${shellToolName} command blocked` };
583
643
  }
644
+ const { command } = input;
584
645
 
585
- const isDangerous = hasDangerousRecursiveRm(command) || dangerousPatterns.some((p) => p.test(command));
646
+ const isDangerous = shellToolName === "powershell"
647
+ ? await hasDangerousPowerShellCommand(pi, command, ctx.signal)
648
+ : hasDangerousRecursiveRm(command) || dangerousPatterns.some((pattern) => pattern.test(command));
586
649
 
587
650
  if (isDangerous) {
588
651
  if (!ctx.hasUI) {
@@ -1,12 +1,13 @@
1
1
  {
2
2
  "name": "@diegopetrucci/pi-permission-gate",
3
- "version": "0.1.13",
4
- "description": "A pi extension that prompts before dangerous bash commands and protected file writes.",
3
+ "version": "0.1.14",
4
+ "description": "A pi extension that prompts before dangerous shell commands and protected file writes.",
5
5
  "keywords": [
6
6
  "pi-package",
7
7
  "pi",
8
8
  "security",
9
9
  "bash",
10
+ "powershell",
10
11
  "permissions",
11
12
  "file-safety"
12
13
  ],
@@ -18,6 +19,7 @@
18
19
  },
19
20
  "files": [
20
21
  "index.ts",
22
+ "powershell-safety.ts",
21
23
  "README.md",
22
24
  ".pi-fleet-tested-version"
23
25
  ],