@diegopetrucci/pi-extensions 0.1.55 → 0.1.57

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 (76) hide show
  1. package/.pi-fleet-tested-version +1 -1
  2. package/README.md +2 -2
  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/package.json +2 -2
  7. package/extensions/annotate-last-message/.pi-fleet-tested-version +1 -1
  8. package/extensions/annotate-last-message/package.json +1 -1
  9. package/extensions/brrr/.pi-fleet-tested-version +1 -1
  10. package/extensions/brrr/README.md +3 -3
  11. package/extensions/brrr/index.ts +26 -6
  12. package/extensions/brrr/package.json +1 -1
  13. package/extensions/claude-fast/.pi-fleet-tested-version +1 -1
  14. package/extensions/claude-fast/README.md +3 -3
  15. package/extensions/claude-fast/index.ts +1 -2
  16. package/extensions/claude-fast/package.json +1 -1
  17. package/extensions/code-reviewer/.pi-fleet-tested-version +1 -1
  18. package/extensions/code-reviewer/index.ts +27 -19
  19. package/extensions/code-reviewer/package.json +1 -1
  20. package/extensions/confirm-destructive/.pi-fleet-tested-version +1 -1
  21. package/extensions/confirm-destructive/package.json +1 -1
  22. package/extensions/context-cap/.pi-fleet-tested-version +1 -1
  23. package/extensions/context-cap/package.json +1 -1
  24. package/extensions/context-inspector/.pi-fleet-tested-version +1 -1
  25. package/extensions/context-inspector/README.md +4 -2
  26. package/extensions/context-inspector/index.ts +10 -4
  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 +96 -92
  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/git-footer/.pi-fleet-tested-version +1 -1
  36. package/extensions/git-footer/package.json +1 -1
  37. package/extensions/gnosis/.pi-fleet-tested-version +1 -1
  38. package/extensions/gnosis/package.json +1 -1
  39. package/extensions/illustrations-to-explain-things/.pi-fleet-tested-version +1 -1
  40. package/extensions/illustrations-to-explain-things/package.json +1 -1
  41. package/extensions/inline-bash/.pi-fleet-tested-version +1 -1
  42. package/extensions/inline-bash/package.json +1 -1
  43. package/extensions/librarian/.pi-fleet-tested-version +1 -1
  44. package/extensions/librarian/README.md +1 -1
  45. package/extensions/librarian/index.ts +39 -8
  46. package/extensions/librarian/package.json +1 -1
  47. package/extensions/minimal-footer/.pi-fleet-tested-version +1 -1
  48. package/extensions/minimal-footer/README.md +3 -3
  49. package/extensions/minimal-footer/index.ts +1 -1
  50. package/extensions/minimal-footer/package.json +1 -1
  51. package/extensions/notify/.pi-fleet-tested-version +1 -1
  52. package/extensions/notify/README.md +3 -3
  53. package/extensions/notify/index.ts +4 -6
  54. package/extensions/notify/package.json +1 -1
  55. package/extensions/openai-fast/.pi-fleet-tested-version +1 -1
  56. package/extensions/openai-fast/README.md +5 -5
  57. package/extensions/openai-fast/index.ts +11 -5
  58. package/extensions/openai-fast/package.json +2 -2
  59. package/extensions/oracle/.pi-fleet-tested-version +1 -1
  60. package/extensions/oracle/index.ts +97 -90
  61. package/extensions/oracle/package.json +1 -1
  62. package/extensions/permission-gate/.pi-fleet-tested-version +1 -1
  63. package/extensions/permission-gate/README.md +11 -4
  64. package/extensions/permission-gate/index.ts +156 -17
  65. package/extensions/permission-gate/package.json +5 -3
  66. package/extensions/quiet-tools/.pi-fleet-tested-version +1 -1
  67. package/extensions/quiet-tools/package.json +1 -1
  68. package/extensions/review/.pi-fleet-tested-version +1 -1
  69. package/extensions/review/README.md +1 -1
  70. package/extensions/review/index.ts +4 -4
  71. package/extensions/review/package.json +1 -1
  72. package/extensions/todo/.pi-fleet-tested-version +1 -1
  73. package/extensions/todo/package.json +1 -1
  74. package/extensions/triage-comments/.pi-fleet-tested-version +1 -1
  75. package/extensions/triage-comments/package.json +1 -1
  76. package/package.json +9 -6
@@ -1 +1 @@
1
- 0.80.3
1
+ 0.80.6
@@ -1,6 +1,6 @@
1
1
  # permission-gate
2
2
 
3
- A small pi extension that prompts for confirmation before running potentially dangerous bash commands.
3
+ A small pi extension that prompts for confirmation before running potentially dangerous bash commands or writing to protected paths.
4
4
 
5
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
 
@@ -9,8 +9,14 @@ This is adapted from the original `permission-gate.ts` example in [`earendil-wor
9
9
  - `rm -rf`
10
10
  - `sudo`
11
11
  - `chmod` / `chown` with `777`
12
+ - direct `write` / `edit` calls touching normalized protected paths:
13
+ - exact `.git` path segments
14
+ - exact `node_modules` path segments
15
+ - secret-bearing `.env` files such as `.env` and `.env.production`
12
16
 
13
- If pi is running without an interactive UI, it blocks matching commands by default.
17
+ Safe `.env` templates/examples such as `.env.example` and `.env.production.template` are allowed.
18
+
19
+ If pi is running without an interactive UI, it blocks matching commands and protected path writes by default.
14
20
 
15
21
  ## Install
16
22
 
@@ -41,5 +47,6 @@ Then reload pi:
41
47
  ## Notes
42
48
 
43
49
  - Hooks the `tool_call` event.
44
- - Only inspects the `bash` tool.
45
- - Prompts with a simple `Yes` / `No` selector before allowing dangerous commands.
50
+ - Inspects `bash`, `write`, and `edit` tool calls.
51
+ - Normalizes relative/absolute paths before matching so traversal tricks do not bypass the guard.
52
+ - Prompts with a simple `Yes` / `No` selector before allowing dangerous commands or protected path writes.
@@ -1,36 +1,175 @@
1
1
  /**
2
2
  * Permission Gate Extension
3
3
  *
4
- * Prompts for confirmation before running potentially dangerous bash commands.
5
- * Patterns checked: rm -rf, sudo, chmod/chown 777
4
+ * Prompts for confirmation before running potentially dangerous bash commands
5
+ * or modifying protected paths via write/edit.
6
+ * Protected paths include exact .git and node_modules path segments plus
7
+ * secret-bearing .env files (excluding example/template variants).
6
8
  */
7
9
 
10
+ import * as path from "node:path";
11
+
8
12
  import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
9
13
 
10
- export default function (pi: ExtensionAPI) {
11
- const dangerousPatterns = [/\brm\s+(-rf?|--recursive)/i, /\bsudo\b/i, /\b(chmod|chown)\b.*777/i];
14
+ const dangerousPatterns = [/\brm\s+(-rf?|--recursive)/i, /\bsudo\b/i, /\b(chmod|chown)\b.*777/i];
15
+ const protectedPathSegments = new Set([".git", "node_modules"]);
16
+ const safeEnvSuffixes = new Set(["example", "examples", "template", "templates"]);
17
+
18
+ type GuardDecision = { block: true; reason: string } | undefined;
19
+ type NormalizedPath = {
20
+ original: string;
21
+ displayPath: string;
22
+ segments: string[];
23
+ basename: string;
24
+ };
25
+
26
+ type WriteInput = { path: string; content: string };
27
+ type EditInput = { path: string; edits: Array<{ oldText: string; newText: string }> };
28
+
29
+ function isRecord(value: unknown): value is Record<string, unknown> {
30
+ return !!value && typeof value === "object" && !Array.isArray(value);
31
+ }
32
+
33
+ function normalizeToolPath(rawPath: string): NormalizedPath | undefined {
34
+ const trimmed = rawPath.trim();
35
+ if (!trimmed) return undefined;
36
+
37
+ // Pi's built-in path tools treat a leading @ as path syntax and strip it
38
+ // before execution, so inspect the same effective target.
39
+ const withoutPathSigil = trimmed.startsWith("@") ? trimmed.slice(1) : trimmed;
40
+ if (!withoutPathSigil) return undefined;
41
+
42
+ const withPosixSeparators = withoutPathSigil.replace(/\\+/g, "/");
43
+ const normalizedPath = path.posix.normalize(
44
+ withPosixSeparators.startsWith("/") ? withPosixSeparators : path.posix.join("/", withPosixSeparators),
45
+ );
46
+ const segments = normalizedPath.split("/").filter(Boolean);
47
+ const basename = segments.at(-1) ?? "";
48
+
49
+ return {
50
+ original: rawPath,
51
+ displayPath: withPosixSeparators,
52
+ segments,
53
+ basename,
54
+ };
55
+ }
56
+
57
+ function isSafeEnvTemplate(basename: string): boolean {
58
+ const normalizedBasename = basename.toLowerCase();
59
+ if (!normalizedBasename.startsWith(".env.")) return false;
60
+ const terminalSuffix = normalizedBasename.split(".").at(-1) ?? "";
61
+ return safeEnvSuffixes.has(terminalSuffix);
62
+ }
63
+
64
+ function isProtectedEnvFile(basename: string): boolean {
65
+ const normalizedBasename = basename.toLowerCase();
66
+ if (normalizedBasename === ".env") return true;
67
+ if (!normalizedBasename.startsWith(".env.")) return false;
68
+ return !isSafeEnvTemplate(normalizedBasename);
69
+ }
70
+
71
+ function isProtectedPath(normalizedPath: NormalizedPath): boolean {
72
+ if (normalizedPath.segments.some((segment) => protectedPathSegments.has(segment.toLowerCase()))) return true;
73
+ return isProtectedEnvFile(normalizedPath.basename);
74
+ }
75
+
76
+ function validateWriteInput(input: unknown): WriteInput | undefined {
77
+ if (!isRecord(input)) return undefined;
78
+ if (typeof input.path !== "string" || typeof input.content !== "string") return undefined;
79
+ if (!normalizeToolPath(input.path)) return undefined;
80
+ return { path: input.path, content: input.content };
81
+ }
82
+
83
+ function validateEditInput(input: unknown): EditInput | undefined {
84
+ if (!isRecord(input)) return undefined;
85
+ if (typeof input.path !== "string" || !Array.isArray(input.edits)) return undefined;
86
+ if (!normalizeToolPath(input.path)) return undefined;
12
87
 
88
+ const edits = input.edits;
89
+ if (
90
+ edits.some(
91
+ (edit) =>
92
+ !isRecord(edit) || typeof edit.oldText !== "string" || typeof edit.newText !== "string",
93
+ )
94
+ ) {
95
+ return undefined;
96
+ }
97
+
98
+ return {
99
+ path: input.path,
100
+ edits: edits as Array<{ oldText: string; newText: string }>,
101
+ };
102
+ }
103
+
104
+ async function confirmProtectedPathAction(
105
+ toolName: "write" | "edit",
106
+ normalizedPath: NormalizedPath,
107
+ ctx: { hasUI?: boolean; ui?: { select(prompt: string, options: string[]): Promise<string | undefined> } },
108
+ ): Promise<GuardDecision> {
109
+ if (!isProtectedPath(normalizedPath)) return undefined;
110
+ if (!ctx.hasUI || !ctx.ui) {
111
+ return { block: true, reason: `Protected path blocked (${toolName} without UI confirmation): ${normalizedPath.displayPath}` };
112
+ }
113
+
114
+ const choice = await ctx.ui.select(
115
+ `⚠️ Protected path ${toolName} request:\n\n ${normalizedPath.displayPath}\n\nAllow?`,
116
+ ["Yes", "No"],
117
+ );
118
+
119
+ if (choice !== "Yes") {
120
+ return { block: true, reason: "Blocked by user" };
121
+ }
122
+
123
+ return undefined;
124
+ }
125
+
126
+ export default function (pi: ExtensionAPI) {
13
127
  pi.on("tool_call", async (event, ctx) => {
14
- if (event.toolName !== "bash") return undefined;
128
+ if (event.toolName === "bash") {
129
+ const command = event.input?.command;
130
+ if (typeof command !== "string" || command.trim() === "") {
131
+ return { block: true, reason: "Malformed bash command blocked" };
132
+ }
15
133
 
16
- const command = event.input?.command;
17
- if (typeof command !== "string" || command.trim() === "") {
18
- return { block: true, reason: "Malformed bash command blocked" };
19
- }
134
+ const isDangerous = dangerousPatterns.some((p) => p.test(command));
135
+
136
+ if (isDangerous) {
137
+ if (!ctx.hasUI) {
138
+ return { block: true, reason: "Dangerous command blocked (no UI for confirmation)" };
139
+ }
20
140
 
21
- const isDangerous = dangerousPatterns.some((p) => p.test(command));
141
+ const choice = await ctx.ui.select(`⚠️ Dangerous command:\n\n ${command}\n\nAllow?`, ["Yes", "No"]);
22
142
 
23
- if (isDangerous) {
24
- if (!ctx.hasUI) {
25
- // In non-interactive mode, block by default
26
- return { block: true, reason: "Dangerous command blocked (no UI for confirmation)" };
143
+ if (choice !== "Yes") {
144
+ return { block: true, reason: "Blocked by user" };
145
+ }
27
146
  }
28
147
 
29
- const choice = await ctx.ui.select(`⚠️ Dangerous command:\n\n ${command}\n\nAllow?`, ["Yes", "No"]);
148
+ return undefined;
149
+ }
30
150
 
31
- if (choice !== "Yes") {
32
- return { block: true, reason: "Blocked by user" };
151
+ if (event.toolName === "write") {
152
+ const input = validateWriteInput(event.input);
153
+ if (!input) {
154
+ return { block: true, reason: "Malformed write input blocked" };
155
+ }
156
+ const normalizedPath = normalizeToolPath(input.path);
157
+ if (!normalizedPath) {
158
+ return { block: true, reason: "Malformed write input blocked" };
159
+ }
160
+ return confirmProtectedPathAction("write", normalizedPath, ctx);
161
+ }
162
+
163
+ if (event.toolName === "edit") {
164
+ const input = validateEditInput(event.input);
165
+ if (!input) {
166
+ return { block: true, reason: "Malformed edit input blocked" };
167
+ }
168
+ const normalizedPath = normalizeToolPath(input.path);
169
+ if (!normalizedPath) {
170
+ return { block: true, reason: "Malformed edit input blocked" };
33
171
  }
172
+ return confirmProtectedPathAction("edit", normalizedPath, ctx);
34
173
  }
35
174
 
36
175
  return undefined;
@@ -1,12 +1,14 @@
1
1
  {
2
2
  "name": "@diegopetrucci/pi-permission-gate",
3
- "version": "0.1.5",
4
- "description": "A pi extension that prompts before dangerous bash commands.",
3
+ "version": "0.1.7",
4
+ "description": "A pi extension that prompts before dangerous bash commands and protected file writes.",
5
5
  "keywords": [
6
6
  "pi-package",
7
7
  "pi",
8
8
  "security",
9
- "bash"
9
+ "bash",
10
+ "permissions",
11
+ "file-safety"
10
12
  ],
11
13
  "license": "MIT",
12
14
  "repository": {
@@ -1 +1 @@
1
- 0.80.3
1
+ 0.80.6
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@diegopetrucci/pi-quiet-tools",
3
- "version": "0.1.5",
3
+ "version": "0.1.6",
4
4
  "description": "A pi extension that visually compacts collapsed built-in tool rows in the TUI without changing tool results sent to the model.",
5
5
  "keywords": [
6
6
  "pi-package",
@@ -1 +1 @@
1
- 0.80.3
1
+ 0.80.6
@@ -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 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.
34
+ - If the project is trusted and a `REVIEW_GUIDELINES.md` file exists next to the repo's `<pi-config-dir>` directory, its contents are appended to the review prompt. `<pi-config-dir>` is Pi's runtime config directory name (`CONFIG_DIR_NAME`; `.pi` by default).
35
35
 
36
36
  ## License and attribution
37
37
 
@@ -29,14 +29,14 @@
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 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.
32
+ * - If the project is trusted and a REVIEW_GUIDELINES.md file exists next to
33
+ * the project config directory, 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
  */
37
37
 
38
38
  import type { ExtensionAPI, ExtensionContext, ExtensionCommandContext } from "@earendil-works/pi-coding-agent";
39
- import { DynamicBorder, BorderedLoader } from "@earendil-works/pi-coding-agent";
39
+ import { CONFIG_DIR_NAME, DynamicBorder, BorderedLoader } from "@earendil-works/pi-coding-agent";
40
40
  import {
41
41
  Container,
42
42
  fuzzyFilter,
@@ -49,7 +49,6 @@ 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
53
  // State to track fresh session review (where we branched from).
55
54
  // Module-level state means only one review can be active at a time.
@@ -1089,6 +1088,7 @@ export const __test__ = {
1089
1088
  parsePrReference,
1090
1089
  parseReviewPaths,
1091
1090
  REVIEW_SUMMARY_PROMPT,
1091
+ loadProjectReviewGuidelines,
1092
1092
  resetReviewRuntimeState,
1093
1093
  tokenizeArgs,
1094
1094
  };
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@diegopetrucci/pi-review",
3
- "version": "0.1.5",
3
+ "version": "0.1.7",
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",
@@ -1 +1 @@
1
- 0.80.3
1
+ 0.80.6
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@diegopetrucci/pi-todo",
3
- "version": "0.1.4",
3
+ "version": "0.1.5",
4
4
  "description": "A pi extension that adds a branch-aware todo tool and /todos viewer.",
5
5
  "keywords": [
6
6
  "pi-package",
@@ -1 +1 @@
1
- 0.80.3
1
+ 0.80.6
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@diegopetrucci/pi-triage-comments",
3
- "version": "0.1.5",
3
+ "version": "0.1.6",
4
4
  "description": "A pi extension that adds /triage-comments and a read-only triage_comments subagent tool for review-comment triage.",
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.55",
3
+ "version": "0.1.57",
4
4
  "description": "A collection of pi extensions and skills for annotation UIs, context management, workflow audits, contrarian review, 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",
@@ -71,17 +71,20 @@
71
71
  ]
72
72
  },
73
73
  "devDependencies": {
74
- "@earendil-works/pi-ai": "^0.80.3",
75
- "@earendil-works/pi-coding-agent": "^0.80.3",
76
- "@earendil-works/pi-tui": "^0.80.3",
74
+ "@earendil-works/pi-ai": "^0.80.6",
75
+ "@earendil-works/pi-coding-agent": "^0.80.6",
76
+ "@earendil-works/pi-tui": "^0.80.6",
77
77
  "@types/node": "^25.9.1",
78
78
  "husky": "^9.1.7",
79
- "typebox": "^1.1.38",
79
+ "typebox": "^1.3.6",
80
80
  "typescript": "^6.0.3"
81
81
  },
82
82
  "dependencies": {
83
- "@tailwindcss/browser": "^4.3.0",
83
+ "@tailwindcss/browser": "^4.3.2",
84
84
  "glimpseui": "^0.8.1",
85
85
  "monaco-editor": "0.52.2"
86
+ },
87
+ "overrides": {
88
+ "protobufjs": "^7.6.4"
86
89
  }
87
90
  }