@aliou/pi-guardrails 0.2.1 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -35,15 +35,164 @@ pi install npm:@aliou/pi-guardrails
35
35
 
36
36
  ## Features
37
37
 
38
- - **prevent-brew**: Blocks Homebrew commands (project uses Nix)
38
+ - **prevent-brew**: Blocks Homebrew commands (disabled by default)
39
39
  - **protect-env-files**: Prevents access to `.env` files (except `.example`/`.sample`/`.test`)
40
40
  - **permission-gate**: Prompts for confirmation on dangerous commands
41
41
 
42
+ ## Configuration
43
+
44
+ Configuration is loaded from two optional JSON files, merged in order (project overrides global):
45
+
46
+ - **Global**: `~/.pi/agent/extensions/guardrails.json`
47
+ - **Project**: `.pi/extensions/guardrails.json`
48
+
49
+ ### Settings Command
50
+
51
+ Run `/guardrails:settings` to open an interactive settings UI with two tabs:
52
+ - **Local**: edit project-scoped config (`.pi/extensions/guardrails.json`)
53
+ - **Global**: edit global config (`~/.pi/agent/extensions/guardrails.json`)
54
+
55
+ Use `Tab` / `Shift+Tab` to switch tabs. Boolean settings can be toggled directly. Array/object settings (patterns, tools) require manual JSON editing.
56
+
57
+ ### Configuration Schema
58
+
59
+ ```json
60
+ {
61
+ "enabled": true,
62
+ "features": {
63
+ "preventBrew": false,
64
+ "protectEnvFiles": true,
65
+ "permissionGate": true
66
+ },
67
+ "envFiles": {
68
+ "protectedPatterns": ["\\.env$", "\\.env\\.local$"],
69
+ "allowedPatterns": [
70
+ "\\.(example|sample|test)\\.env$",
71
+ "\\.env\\.(example|sample|test)$"
72
+ ],
73
+ "protectedDirectories": [],
74
+ "protectedTools": ["read", "write", "edit", "bash", "grep", "find", "ls"],
75
+ "onlyBlockIfExists": true,
76
+ "blockMessage": "Accessing {file} is not allowed. ..."
77
+ },
78
+ "permissionGate": {
79
+ "patterns": [
80
+ { "pattern": "rm\\s+-rf", "description": "recursive force delete" }
81
+ ],
82
+ "customPatterns": [],
83
+ "requireConfirmation": true,
84
+ "allowedPatterns": [],
85
+ "autoDenyPatterns": []
86
+ }
87
+ }
88
+ ```
89
+
90
+ All fields are optional. Missing fields use defaults shown above.
91
+
92
+ ### Configuration Details
93
+
94
+ #### `features`
95
+
96
+ | Key | Default | Description |
97
+ |---|---|---|
98
+ | `preventBrew` | `false` | Block Homebrew install/upgrade commands |
99
+ | `protectEnvFiles` | `true` | Block access to `.env` files containing secrets |
100
+ | `permissionGate` | `true` | Prompt for confirmation on dangerous commands |
101
+
102
+ #### `envFiles`
103
+
104
+ | Key | Default | Description |
105
+ |---|---|---|
106
+ | `protectedPatterns` | `["\\.env$", "\\.env\\.local$"]` | Regex patterns for files to protect |
107
+ | `allowedPatterns` | `["\\.(example\|sample\|test)\\.env$", ...]` | Regex patterns for allowed exceptions |
108
+ | `protectedDirectories` | `[]` | Regex patterns for directories to protect |
109
+ | `protectedTools` | `["read", "write", "edit", "bash", "grep", "find", "ls"]` | Tools to intercept |
110
+ | `onlyBlockIfExists` | `true` | Only block if the file exists on disk |
111
+ | `blockMessage` | See defaults | Message shown when blocked. Supports `{file}` placeholder |
112
+
113
+ #### `permissionGate`
114
+
115
+ | Key | Default | Description |
116
+ |---|---|---|
117
+ | `patterns` | See defaults | Array of `{ pattern, description }` for dangerous commands |
118
+ | `customPatterns` | Not set | If set, replaces `patterns` entirely |
119
+ | `requireConfirmation` | `true` | Show confirmation dialog (if `false`, just warns) |
120
+ | `allowedPatterns` | `[]` | Regex patterns that bypass the gate |
121
+ | `autoDenyPatterns` | `[]` | Regex patterns that are blocked immediately without dialog |
122
+
123
+ ### Examples
124
+
125
+ Enable `prevent-brew` for a project using Nix:
126
+
127
+ ```json
128
+ {
129
+ "features": {
130
+ "preventBrew": true
131
+ }
132
+ }
133
+ ```
134
+
135
+ Add a custom dangerous command pattern:
136
+
137
+ ```json
138
+ {
139
+ "permissionGate": {
140
+ "patterns": [
141
+ { "pattern": "rm\\s+-rf", "description": "recursive force delete" },
142
+ { "pattern": "\\bsudo\\b", "description": "superuser command" },
143
+ { "pattern": "docker\\s+system\\s+prune", "description": "docker system prune" }
144
+ ]
145
+ }
146
+ }
147
+ ```
148
+
149
+ Auto-deny certain commands:
150
+
151
+ ```json
152
+ {
153
+ "permissionGate": {
154
+ "autoDenyPatterns": ["rm\\s+-rf\\s+/(?!tmp)"]
155
+ }
156
+ }
157
+ ```
158
+
159
+ ## Events
160
+
161
+ The extension emits events on the pi event bus for inter-extension communication.
162
+
163
+ ### `guardrails:blocked`
164
+
165
+ Emitted when a tool call is blocked by any guardrail.
166
+
167
+ ```typescript
168
+ interface GuardrailsBlockedEvent {
169
+ feature: "preventBrew" | "protectEnvFiles" | "permissionGate";
170
+ toolName: string;
171
+ input: Record<string, unknown>;
172
+ reason: string;
173
+ userDenied?: boolean;
174
+ }
175
+ ```
176
+
177
+ ### `guardrails:dangerous`
178
+
179
+ Emitted when a dangerous command is detected (before the confirmation dialog).
180
+
181
+ ```typescript
182
+ interface GuardrailsDangerousEvent {
183
+ command: string;
184
+ description: string;
185
+ pattern: string;
186
+ }
187
+ ```
188
+
189
+ The [presenter extension](../presenter) listens for `guardrails:dangerous` events and plays a notification sound.
190
+
42
191
  ## Hooks
43
192
 
44
193
  ### prevent-brew
45
194
 
46
- Blocks bash commands that attempt to install packages using Homebrew. Notifies the user that the project uses Nix for package management.
195
+ Blocks bash commands that attempt to install packages using Homebrew. Disabled by default. Enable via config if your project uses Nix.
47
196
 
48
197
  Blocked patterns:
49
198
  - `brew install`
@@ -62,7 +211,7 @@ Prevents accessing `.env` files that might contain secrets. Only allows access t
62
211
  - `*.sample.env`
63
212
  - `*.test.env`
64
213
 
65
- Covers tools: `read`, `write`, `edit`, `bash`, `grep`, `find`, `ls`
214
+ Covers tools: `read`, `write`, `edit`, `bash`, `grep`, `find`, `ls` (configurable).
66
215
 
67
216
  ### permission-gate
68
217
 
@@ -74,3 +223,5 @@ Prompts user confirmation before executing dangerous commands:
74
223
  - `mkfs.` (filesystem format)
75
224
  - `chmod -R 777` (insecure recursive permissions)
76
225
  - `chown -R` (recursive ownership change)
226
+
227
+ All patterns are configurable. Supports allow-lists and auto-deny lists.
@@ -0,0 +1,213 @@
1
+ import type { Component, SettingsListTheme } from "@mariozechner/pi-tui";
2
+ import {
3
+ Input,
4
+ Key,
5
+ matchesKey,
6
+ truncateToWidth,
7
+ visibleWidth,
8
+ } from "@mariozechner/pi-tui";
9
+
10
+ /**
11
+ * A submenu component for editing string arrays inside a SettingsList.
12
+ *
13
+ * Modes:
14
+ * - list: navigate items, delete with 'd', add with 'a', edit with 'e'/Enter
15
+ * - add: text input for new item, confirm with Enter, cancel with Escape
16
+ * - edit: text input pre-filled with current value, Enter saves, Escape cancels
17
+ */
18
+
19
+ export interface ArrayEditorOptions {
20
+ label: string;
21
+ items: string[];
22
+ theme: SettingsListTheme;
23
+ onSave: (items: string[]) => void;
24
+ onDone: () => void;
25
+ /** Max visible items before scrolling */
26
+ maxVisible?: number;
27
+ }
28
+
29
+ export class ArrayEditor implements Component {
30
+ private items: string[];
31
+ private label: string;
32
+ private theme: SettingsListTheme;
33
+ private onSave: (items: string[]) => void;
34
+ private onDone: () => void;
35
+ private selectedIndex = 0;
36
+ private maxVisible: number;
37
+ private mode: "list" | "add" | "edit" = "list";
38
+ private input: Input;
39
+ private editIndex = -1;
40
+
41
+ constructor(options: ArrayEditorOptions) {
42
+ this.items = [...options.items];
43
+ this.label = options.label;
44
+ this.theme = options.theme;
45
+ this.onSave = options.onSave;
46
+ this.onDone = options.onDone;
47
+ this.maxVisible = options.maxVisible ?? 10;
48
+ this.input = new Input();
49
+ this.input.onSubmit = (value: string) => {
50
+ if (this.mode === "edit") {
51
+ this.submitEdit(value);
52
+ } else {
53
+ this.submitAdd(value);
54
+ }
55
+ };
56
+ this.input.onEscape = () => {
57
+ this.mode = "list";
58
+ this.editIndex = -1;
59
+ };
60
+ }
61
+
62
+ private submitAdd(value: string) {
63
+ const trimmed = value.trim();
64
+ if (!trimmed) {
65
+ this.mode = "list";
66
+ return;
67
+ }
68
+ this.items.push(trimmed);
69
+ this.selectedIndex = this.items.length - 1;
70
+ this.save();
71
+ this.mode = "list";
72
+ this.input.setValue("");
73
+ }
74
+
75
+ private submitEdit(value: string) {
76
+ const trimmed = value.trim();
77
+ if (!trimmed) {
78
+ // Empty value = cancel edit
79
+ this.mode = "list";
80
+ this.editIndex = -1;
81
+ return;
82
+ }
83
+ this.items[this.editIndex] = trimmed;
84
+ this.save();
85
+ this.mode = "list";
86
+ this.editIndex = -1;
87
+ this.input.setValue("");
88
+ }
89
+
90
+ private deleteSelected() {
91
+ if (this.items.length === 0) return;
92
+ this.items.splice(this.selectedIndex, 1);
93
+ if (this.selectedIndex >= this.items.length) {
94
+ this.selectedIndex = Math.max(0, this.items.length - 1);
95
+ }
96
+ this.save();
97
+ }
98
+
99
+ private startEdit() {
100
+ if (this.items.length === 0) return;
101
+ this.editIndex = this.selectedIndex;
102
+ this.mode = "edit";
103
+ this.input.setValue(this.items[this.selectedIndex] as string);
104
+ }
105
+
106
+ private save() {
107
+ this.onSave([...this.items]);
108
+ }
109
+
110
+ invalidate() {}
111
+
112
+ render(width: number): string[] {
113
+ const lines: string[] = [];
114
+
115
+ // Header
116
+ lines.push(this.theme.label(` ${this.label}`, true));
117
+ lines.push("");
118
+
119
+ if (this.mode === "add" || this.mode === "edit") {
120
+ return [...lines, ...this.renderInputMode(width)];
121
+ }
122
+
123
+ return [...lines, ...this.renderListMode(width)];
124
+ }
125
+
126
+ private renderListMode(width: number): string[] {
127
+ const lines: string[] = [];
128
+
129
+ if (this.items.length === 0) {
130
+ lines.push(this.theme.hint(" (empty)"));
131
+ } else {
132
+ const startIndex = Math.max(
133
+ 0,
134
+ Math.min(
135
+ this.selectedIndex - Math.floor(this.maxVisible / 2),
136
+ this.items.length - this.maxVisible,
137
+ ),
138
+ );
139
+ const endIndex = Math.min(
140
+ startIndex + this.maxVisible,
141
+ this.items.length,
142
+ );
143
+
144
+ for (let i = startIndex; i < endIndex; i++) {
145
+ const item = this.items[i];
146
+ if (!item) continue;
147
+ const isSelected = i === this.selectedIndex;
148
+ const prefix = isSelected ? this.theme.cursor : " ";
149
+ const prefixWidth = visibleWidth(prefix);
150
+ const maxItemWidth = width - prefixWidth - 2;
151
+ const text = this.theme.value(
152
+ truncateToWidth(item, maxItemWidth, ""),
153
+ isSelected,
154
+ );
155
+ lines.push(prefix + text);
156
+ }
157
+
158
+ if (startIndex > 0 || endIndex < this.items.length) {
159
+ lines.push(
160
+ this.theme.hint(` (${this.selectedIndex + 1}/${this.items.length})`),
161
+ );
162
+ }
163
+ }
164
+
165
+ lines.push("");
166
+ lines.push(
167
+ this.theme.hint(" a: add · e/Enter: edit · d: delete · Esc: back"),
168
+ );
169
+
170
+ return lines;
171
+ }
172
+
173
+ private renderInputMode(width: number): string[] {
174
+ const lines: string[] = [];
175
+ const label = this.mode === "edit" ? " Edit item:" : " New item:";
176
+ lines.push(this.theme.hint(label));
177
+ lines.push(` ${this.input.render(width - 4).join("")}`);
178
+ lines.push("");
179
+ lines.push(this.theme.hint(" Enter: confirm · Esc: cancel"));
180
+ return lines;
181
+ }
182
+
183
+ handleInput(data: string) {
184
+ if (this.mode === "add" || this.mode === "edit") {
185
+ this.input.handleInput(data);
186
+ return;
187
+ }
188
+
189
+ // List mode
190
+ if (matchesKey(data, Key.up) || data === "k") {
191
+ if (this.items.length === 0) return;
192
+ this.selectedIndex =
193
+ this.selectedIndex === 0
194
+ ? this.items.length - 1
195
+ : this.selectedIndex - 1;
196
+ } else if (matchesKey(data, Key.down) || data === "j") {
197
+ if (this.items.length === 0) return;
198
+ this.selectedIndex =
199
+ this.selectedIndex === this.items.length - 1
200
+ ? 0
201
+ : this.selectedIndex + 1;
202
+ } else if (data === "a" || data === "A") {
203
+ this.mode = "add";
204
+ this.input.setValue("");
205
+ } else if (data === "e" || data === "E" || matchesKey(data, Key.enter)) {
206
+ this.startEdit();
207
+ } else if (data === "d" || data === "D") {
208
+ this.deleteSelected();
209
+ } else if (matchesKey(data, Key.escape)) {
210
+ this.onDone();
211
+ }
212
+ }
213
+ }
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Configuration schema for the guardrails extension.
3
+ *
4
+ * GuardrailsConfig is the user-facing schema (all fields optional).
5
+ * ResolvedConfig is the internal schema (all fields required, defaults applied).
6
+ */
7
+
8
+ export interface GuardrailsConfig {
9
+ enabled?: boolean;
10
+ features?: {
11
+ preventBrew?: boolean;
12
+ protectEnvFiles?: boolean;
13
+ permissionGate?: boolean;
14
+ };
15
+ envFiles?: {
16
+ protectedPatterns?: string[];
17
+ allowedPatterns?: string[];
18
+ protectedDirectories?: string[];
19
+ protectedTools?: string[];
20
+ onlyBlockIfExists?: boolean;
21
+ blockMessage?: string;
22
+ };
23
+ permissionGate?: {
24
+ patterns?: Array<{ pattern: string; description: string }>;
25
+ /** If set, replaces the default patterns entirely. */
26
+ customPatterns?: Array<{ pattern: string; description: string }>;
27
+ requireConfirmation?: boolean;
28
+ allowedPatterns?: string[];
29
+ autoDenyPatterns?: string[];
30
+ };
31
+ }
32
+
33
+ export interface ResolvedConfig {
34
+ enabled: boolean;
35
+ features: {
36
+ preventBrew: boolean;
37
+ protectEnvFiles: boolean;
38
+ permissionGate: boolean;
39
+ };
40
+ envFiles: {
41
+ protectedPatterns: string[];
42
+ allowedPatterns: string[];
43
+ protectedDirectories: string[];
44
+ protectedTools: string[];
45
+ onlyBlockIfExists: boolean;
46
+ blockMessage: string;
47
+ };
48
+ permissionGate: {
49
+ patterns: Array<{ pattern: string; description: string }>;
50
+ requireConfirmation: boolean;
51
+ allowedPatterns: string[];
52
+ autoDenyPatterns: string[];
53
+ };
54
+ }
package/config.ts ADDED
@@ -0,0 +1,160 @@
1
+ import { mkdir, readFile, writeFile } from "node:fs/promises";
2
+ import { homedir } from "node:os";
3
+ import { dirname, resolve } from "node:path";
4
+ import type { GuardrailsConfig, ResolvedConfig } from "./config-schema";
5
+
6
+ const GLOBAL_CONFIG_PATH = resolve(
7
+ homedir(),
8
+ ".pi/agent/extensions/guardrails.json",
9
+ );
10
+ const PROJECT_CONFIG_PATH = resolve(
11
+ process.cwd(),
12
+ ".pi/extensions/guardrails.json",
13
+ );
14
+
15
+ const DEFAULT_CONFIG: ResolvedConfig = {
16
+ enabled: true,
17
+ features: {
18
+ preventBrew: false,
19
+ protectEnvFiles: true,
20
+ permissionGate: true,
21
+ },
22
+ envFiles: {
23
+ protectedPatterns: ["\\.env$", "\\.env\\.local$"],
24
+ allowedPatterns: [
25
+ "\\.(example|sample|test)\\.env$",
26
+ "\\.env\\.(example|sample|test)$",
27
+ ],
28
+ protectedDirectories: [],
29
+ protectedTools: ["read", "write", "edit", "bash", "grep", "find", "ls"],
30
+ onlyBlockIfExists: true,
31
+ blockMessage:
32
+ "Accessing {file} is not allowed. Environment files containing secrets are protected. " +
33
+ "Explain to the user why you want to access this .env file, and if changes are needed ask the user to make them. " +
34
+ "Only .env.example, .env.sample, or .env.test files can be accessed.",
35
+ },
36
+ permissionGate: {
37
+ patterns: [
38
+ { pattern: "rm\\s+-rf", description: "recursive force delete" },
39
+ { pattern: "\\bsudo\\b", description: "superuser command" },
40
+ { pattern: ":\\s*\\|\\s*sh", description: "piped shell execution" },
41
+ { pattern: "\\bdd\\s+if=", description: "disk write operation" },
42
+ { pattern: "mkfs\\.", description: "filesystem format" },
43
+ {
44
+ pattern: "\\bchmod\\s+-R\\s+777",
45
+ description: "insecure recursive permissions",
46
+ },
47
+ {
48
+ pattern: "\\bchown\\s+-R",
49
+ description: "recursive ownership change",
50
+ },
51
+ ],
52
+ requireConfirmation: true,
53
+ allowedPatterns: [],
54
+ autoDenyPatterns: [],
55
+ },
56
+ };
57
+
58
+ class ConfigLoader {
59
+ private globalConfig: GuardrailsConfig | null = null;
60
+ private projectConfig: GuardrailsConfig | null = null;
61
+ private resolved: ResolvedConfig | null = null;
62
+
63
+ async load(): Promise<void> {
64
+ this.globalConfig = await this.loadConfigFile(GLOBAL_CONFIG_PATH);
65
+ this.projectConfig = await this.loadConfigFile(PROJECT_CONFIG_PATH);
66
+ this.resolved = this.mergeConfigs();
67
+ }
68
+
69
+ private async loadConfigFile(path: string): Promise<GuardrailsConfig | null> {
70
+ try {
71
+ const content = await readFile(path, "utf-8");
72
+ return JSON.parse(content) as GuardrailsConfig;
73
+ } catch {
74
+ return null;
75
+ }
76
+ }
77
+
78
+ private mergeConfigs(): ResolvedConfig {
79
+ const merged = structuredClone(DEFAULT_CONFIG);
80
+
81
+ if (this.globalConfig) {
82
+ this.mergeInto(merged, this.globalConfig);
83
+ }
84
+ if (this.projectConfig) {
85
+ this.mergeInto(merged, this.projectConfig);
86
+ }
87
+
88
+ // customPatterns replaces entire patterns array
89
+ if (this.projectConfig?.permissionGate?.customPatterns) {
90
+ merged.permissionGate.patterns =
91
+ this.projectConfig.permissionGate.customPatterns;
92
+ } else if (this.globalConfig?.permissionGate?.customPatterns) {
93
+ merged.permissionGate.patterns =
94
+ this.globalConfig.permissionGate.customPatterns;
95
+ }
96
+
97
+ return merged;
98
+ }
99
+
100
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
101
+ private mergeInto(target: any, source: any): void {
102
+ for (const key in source) {
103
+ if (source[key] === undefined) continue;
104
+
105
+ if (
106
+ typeof source[key] === "object" &&
107
+ !Array.isArray(source[key]) &&
108
+ source[key] !== null
109
+ ) {
110
+ if (!target[key]) target[key] = {};
111
+ this.mergeInto(target[key], source[key]);
112
+ } else {
113
+ target[key] = source[key];
114
+ }
115
+ }
116
+ }
117
+
118
+ getConfig(): ResolvedConfig {
119
+ if (!this.resolved) {
120
+ throw new Error("Config not loaded. Call load() first.");
121
+ }
122
+ return this.resolved;
123
+ }
124
+
125
+ async saveGlobal(config: GuardrailsConfig): Promise<void> {
126
+ await this.saveConfigFile(GLOBAL_CONFIG_PATH, config);
127
+ await this.load();
128
+ }
129
+
130
+ async saveProject(config: GuardrailsConfig): Promise<void> {
131
+ await this.saveConfigFile(PROJECT_CONFIG_PATH, config);
132
+ await this.load();
133
+ }
134
+
135
+ private async saveConfigFile(
136
+ path: string,
137
+ config: GuardrailsConfig,
138
+ ): Promise<void> {
139
+ await mkdir(dirname(path), { recursive: true });
140
+ await writeFile(path, JSON.stringify(config, null, 2) + "\n", "utf-8");
141
+ }
142
+
143
+ hasGlobalConfig(): boolean {
144
+ return this.globalConfig !== null;
145
+ }
146
+
147
+ hasProjectConfig(): boolean {
148
+ return this.projectConfig !== null;
149
+ }
150
+
151
+ getGlobalConfig(): GuardrailsConfig {
152
+ return this.globalConfig ?? {};
153
+ }
154
+
155
+ getProjectConfig(): GuardrailsConfig {
156
+ return this.projectConfig ?? {};
157
+ }
158
+ }
159
+
160
+ export const configLoader = new ConfigLoader();
package/events.ts ADDED
@@ -0,0 +1,32 @@
1
+ import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
2
+
3
+ export const GUARDRAILS_BLOCKED_EVENT = "guardrails:blocked";
4
+ export const GUARDRAILS_DANGEROUS_EVENT = "guardrails:dangerous";
5
+
6
+ export interface GuardrailsBlockedEvent {
7
+ feature: "preventBrew" | "protectEnvFiles" | "permissionGate";
8
+ toolName: string;
9
+ input: Record<string, unknown>;
10
+ reason: string;
11
+ userDenied?: boolean;
12
+ }
13
+
14
+ export interface GuardrailsDangerousEvent {
15
+ command: string;
16
+ description: string;
17
+ pattern: string;
18
+ }
19
+
20
+ export function emitBlocked(
21
+ pi: ExtensionAPI,
22
+ event: GuardrailsBlockedEvent,
23
+ ): void {
24
+ pi.events.emit(GUARDRAILS_BLOCKED_EVENT, event);
25
+ }
26
+
27
+ export function emitDangerous(
28
+ pi: ExtensionAPI,
29
+ event: GuardrailsDangerousEvent,
30
+ ): void {
31
+ pi.events.emit(GUARDRAILS_DANGEROUS_EVENT, event);
32
+ }
package/hooks/index.ts CHANGED
@@ -1,10 +1,11 @@
1
1
  import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
2
+ import type { ResolvedConfig } from "../config-schema";
2
3
  import { setupPermissionGateHook } from "./permission-gate";
3
4
  import { setupPreventBrewHook } from "./prevent-brew";
4
5
  import { setupProtectEnvFilesHook } from "./protect-env-files";
5
6
 
6
- export function setupGuardrailsHooks(pi: ExtensionAPI) {
7
- setupPreventBrewHook(pi);
8
- setupProtectEnvFilesHook(pi);
9
- setupPermissionGateHook(pi);
7
+ export function setupGuardrailsHooks(pi: ExtensionAPI, config: ResolvedConfig) {
8
+ setupPreventBrewHook(pi, config);
9
+ setupProtectEnvFilesHook(pi, config);
10
+ setupPermissionGateHook(pi, config);
10
11
  }