@aliou/pi-guardrails 0.7.1 → 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.
@@ -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.1",
3
+ "version": "0.7.2",
4
4
  "type": "module",
5
5
  "private": false,
6
6
  "keywords": [
@@ -33,7 +33,7 @@
33
33
  ],
34
34
  "dependencies": {
35
35
  "@aliou/sh": "^0.1.0",
36
- "@aliou/pi-utils-settings": "^0.2.1"
36
+ "@aliou/pi-utils-settings": "^0.3.0"
37
37
  },
38
38
  "peerDependencies": {
39
39
  "@mariozechner/pi-coding-agent": ">=0.51.0"