@aliou/pi-guardrails 0.7.0 → 0.7.2

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
@@ -68,7 +68,6 @@ Configs without a `version` field are automatically migrated on first load. The
68
68
 
69
69
  ```json
70
70
  {
71
- "version": "0.7.0-20260204",
72
71
  "enabled": true,
73
72
  "features": {
74
73
  "protectEnvFiles": true,
@@ -78,12 +77,17 @@ Configs without a `version` field are automatically migrated on first load. The
78
77
  "protectedPatterns": [
79
78
  { "pattern": ".env" },
80
79
  { "pattern": ".env.local" },
81
- { "pattern": ".env.production" }
80
+ { "pattern": ".env.production" },
81
+ { "pattern": ".env.prod" },
82
+ { "pattern": ".dev.vars" }
82
83
  ],
83
84
  "allowedPatterns": [
84
85
  { "pattern": ".env.example" },
85
86
  { "pattern": ".env.sample" },
86
- { "pattern": "*.example.env" }
87
+ { "pattern": ".env.test" },
88
+ { "pattern": "*.example.env" },
89
+ { "pattern": "*.sample.env" },
90
+ { "pattern": "*.test.env" }
87
91
  ],
88
92
  "protectedDirectories": [],
89
93
  "protectedTools": ["read", "write", "edit", "bash", "grep", "find", "ls"],
@@ -103,7 +107,7 @@ Configs without a `version` field are automatically migrated on first load. The
103
107
  }
104
108
  ```
105
109
 
106
- All fields are optional. Missing fields use defaults shown above.
110
+ All fields are optional. Missing fields use defaults shown above. The default patterns listed here may change between versions. Check the source code or run `/guardrails:settings` to see the current defaults and update them to your liking.
107
111
 
108
112
  ### Pattern Format
109
113
 
@@ -10,6 +10,7 @@ import {
10
10
  wrapTextWithAnsi,
11
11
  } from "@mariozechner/pi-tui";
12
12
  import type { DangerousPattern, ResolvedConfig } from "../config";
13
+ import { configLoader } from "../config";
13
14
  import { emitBlocked, emitDangerous } from "../utils/events";
14
15
  import {
15
16
  type CompiledPattern,
@@ -221,70 +222,100 @@ export function setupPermissionGateHook(
221
222
  return { block: true, reason };
222
223
  }
223
224
 
224
- const proceed = await ctx.ui.custom<boolean>((_tui, theme, _kb, done) => {
225
- const container = new Container();
226
- const redBorder = (s: string) => theme.fg("error", s);
225
+ type ConfirmResult = "allow" | "allow-session" | "deny";
227
226
 
228
- container.addChild(new DynamicBorder(redBorder));
229
- container.addChild(
230
- new Text(
231
- theme.fg("error", theme.bold("Dangerous Command Detected")),
232
- 1,
233
- 0,
234
- ),
235
- );
236
- container.addChild(new Spacer(1));
237
- container.addChild(
238
- new Text(
239
- theme.fg("warning", `This command contains ${description}:`),
240
- 1,
241
- 0,
242
- ),
243
- );
244
- container.addChild(new Spacer(1));
245
- container.addChild(
246
- new DynamicBorder((s: string) => theme.fg("muted", s)),
247
- );
248
- const commandText = new Text("", 1, 0);
249
- container.addChild(commandText);
250
- container.addChild(
251
- new DynamicBorder((s: string) => theme.fg("muted", s)),
252
- );
253
- container.addChild(new Spacer(1));
254
- container.addChild(
255
- new Text(theme.fg("text", "Allow execution?"), 1, 0),
256
- );
257
- container.addChild(new Spacer(1));
258
- container.addChild(
259
- new Text(theme.fg("dim", "y/enter: allow • n/esc: deny"), 1, 0),
260
- );
261
- container.addChild(new DynamicBorder(redBorder));
227
+ const result = await ctx.ui.custom<ConfirmResult>(
228
+ (_tui, theme, _kb, done) => {
229
+ const container = new Container();
230
+ const redBorder = (s: string) => theme.fg("error", s);
262
231
 
263
- return {
264
- render: (width: number) => {
265
- const wrappedCommand = wrapTextWithAnsi(
266
- theme.fg("text", command),
267
- width - 4,
268
- ).join("\n");
269
- commandText.setText(wrappedCommand);
270
- return container.render(width);
271
- },
272
- invalidate: () => container.invalidate(),
273
- handleInput: (data: string) => {
274
- if (matchesKey(data, Key.enter) || data === "y" || data === "Y") {
275
- done(true);
276
- } else if (
277
- matchesKey(data, Key.escape) ||
278
- data === "n" ||
279
- data === "N"
280
- ) {
281
- done(false);
282
- }
232
+ container.addChild(new DynamicBorder(redBorder));
233
+ container.addChild(
234
+ new Text(
235
+ theme.fg("error", theme.bold("Dangerous Command Detected")),
236
+ 1,
237
+ 0,
238
+ ),
239
+ );
240
+ container.addChild(new Spacer(1));
241
+ container.addChild(
242
+ new Text(
243
+ theme.fg("warning", `This command contains ${description}:`),
244
+ 1,
245
+ 0,
246
+ ),
247
+ );
248
+ container.addChild(new Spacer(1));
249
+ container.addChild(
250
+ new DynamicBorder((s: string) => theme.fg("muted", s)),
251
+ );
252
+ const commandText = new Text("", 1, 0);
253
+ container.addChild(commandText);
254
+ container.addChild(
255
+ new DynamicBorder((s: string) => theme.fg("muted", s)),
256
+ );
257
+ container.addChild(new Spacer(1));
258
+ container.addChild(
259
+ new Text(theme.fg("text", "Allow execution?"), 1, 0),
260
+ );
261
+ container.addChild(new Spacer(1));
262
+ container.addChild(
263
+ new Text(
264
+ theme.fg(
265
+ "dim",
266
+ "y/enter: allow • a: allow for session • n/esc: deny",
267
+ ),
268
+ 1,
269
+ 0,
270
+ ),
271
+ );
272
+ container.addChild(new DynamicBorder(redBorder));
273
+
274
+ return {
275
+ render: (width: number) => {
276
+ const wrappedCommand = wrapTextWithAnsi(
277
+ theme.fg("text", command),
278
+ width - 4,
279
+ ).join("\n");
280
+ commandText.setText(wrappedCommand);
281
+ return container.render(width);
282
+ },
283
+ invalidate: () => container.invalidate(),
284
+ handleInput: (data: string) => {
285
+ if (matchesKey(data, Key.enter) || data === "y" || data === "Y") {
286
+ done("allow");
287
+ } else if (data === "a" || data === "A") {
288
+ done("allow-session");
289
+ } else if (
290
+ matchesKey(data, Key.escape) ||
291
+ data === "n" ||
292
+ data === "N"
293
+ ) {
294
+ done("deny");
295
+ }
296
+ },
297
+ };
298
+ },
299
+ );
300
+
301
+ if (result === "allow-session") {
302
+ // Save command as allowed in memory scope (session-only).
303
+ // Spread the resolved allowed patterns and append the new one.
304
+ const resolved = configLoader.getConfig();
305
+ await configLoader.save("memory", {
306
+ permissionGate: {
307
+ allowedPatterns: [
308
+ ...resolved.permissionGate.allowedPatterns,
309
+ { pattern: command },
310
+ ],
283
311
  },
284
- };
285
- });
312
+ });
313
+
314
+ // Update the local cache so it takes effect immediately
315
+ allowedPatterns.push(...compileCommandPatterns([{ pattern: command }]));
316
+ }
286
317
 
287
- if (!proceed) {
318
+ if (result === "deny") {
288
319
  emitBlocked(pi, {
289
320
  feature: "permissionGate",
290
321
  toolName: "bash",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aliou/pi-guardrails",
3
- "version": "0.7.0",
3
+ "version": "0.7.2",
4
4
  "type": "module",
5
5
  "private": false,
6
6
  "keywords": [
@@ -32,8 +32,8 @@
32
32
  "README.md"
33
33
  ],
34
34
  "dependencies": {
35
- "@aliou/pi-utils-settings": "^0.2.0",
36
- "@aliou/sh": "^0.1.0"
35
+ "@aliou/sh": "^0.1.0",
36
+ "@aliou/pi-utils-settings": "^0.3.0"
37
37
  },
38
38
  "peerDependencies": {
39
39
  "@mariozechner/pi-coding-agent": ">=0.51.0"