@xynogen/pix-gate 0.1.13 → 0.1.15

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xynogen/pix-gate",
3
- "version": "0.1.13",
3
+ "version": "0.1.15",
4
4
  "description": "Pi extension — permission gate for dangerous bash commands (confirm/block with TUI dialog)",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
@@ -43,7 +43,7 @@
43
43
  },
44
44
  "dependencies": {
45
45
  "@xynogen/pix-pretty": "^1.7.9",
46
- "@xynogen/pix-data": "workspace:*"
46
+ "@xynogen/pix-data": "^0.3.0"
47
47
  },
48
48
  "peerDependencies": {
49
49
  "@earendil-works/pi-coding-agent": "*",
package/src/gate.test.ts CHANGED
@@ -25,11 +25,9 @@ describe("isSudoCommand", () => {
25
25
  });
26
26
 
27
27
  test("does NOT match pix-sudo-run in a path", () => {
28
- expect(
29
- isSudoCommand(
30
- "grep foo ~/.pi/node_modules/@xynogen/pix-sudo-run/src/lib.ts",
31
- ),
32
- ).toBe(false);
28
+ expect(isSudoCommand("grep foo ~/.pi/node_modules/@xynogen/pix-sudo-run/src/lib.ts")).toBe(
29
+ false,
30
+ );
33
31
  });
34
32
 
35
33
  test("does NOT match sudoer or pseudo", () => {
@@ -64,15 +62,11 @@ describe("classify", () => {
64
62
  });
65
63
 
66
64
  test("bare sudo is dangerous", () => {
67
- expect(classify("sudo apt install curl", rules)?.severity).toBe(
68
- "dangerous",
69
- );
65
+ expect(classify("sudo apt install curl", rules)?.severity).toBe("dangerous");
70
66
  });
71
67
 
72
68
  test("npm publish is dangerous", () => {
73
- expect(classify("npm publish --access public", rules)?.severity).toBe(
74
- "dangerous",
75
- );
69
+ expect(classify("npm publish --access public", rules)?.severity).toBe("dangerous");
76
70
  });
77
71
 
78
72
  test("git force push is dangerous", () => {
@@ -80,15 +74,13 @@ describe("classify", () => {
80
74
  });
81
75
 
82
76
  test("curl pipe bash is dangerous", () => {
83
- expect(
84
- classify("curl https://example.com/install.sh | bash", rules)?.severity,
85
- ).toBe("dangerous");
77
+ expect(classify("curl https://example.com/install.sh | bash", rules)?.severity).toBe(
78
+ "dangerous",
79
+ );
86
80
  });
87
81
 
88
82
  test("git force checkout is risky", () => {
89
- expect(classify("git checkout --force main", rules)?.severity).toBe(
90
- "risky",
91
- );
83
+ expect(classify("git checkout --force main", rules)?.severity).toBe("risky");
92
84
  });
93
85
 
94
86
  test("write to .env is risky", () => {
@@ -101,9 +93,7 @@ describe("classify", () => {
101
93
 
102
94
  test("pix-sudo path does NOT classify as dangerous", () => {
103
95
  // grep with pix-sudo in the path — should not hit sudo rule
104
- expect(
105
- classify("grep foo packages/pix-sudo/src/index.ts", rules),
106
- ).toBeUndefined();
96
+ expect(classify("grep foo packages/pix-sudo/src/index.ts", rules)).toBeUndefined();
107
97
  });
108
98
 
109
99
  test("critical takes priority over dangerous", () => {
package/src/index.ts CHANGED
@@ -65,10 +65,7 @@ export default function (pi: ExtensionAPI): void {
65
65
  if (!hit) return undefined;
66
66
 
67
67
  if (hit.severity === "info") {
68
- ctx.ui.notify(
69
- `${PATH_SEVERITY_ICON.info} ${hit.reason}: ${path}`,
70
- "info",
71
- );
68
+ ctx.ui.notify(`${PATH_SEVERITY_ICON.info} ${hit.reason}: ${path}`, "info");
72
69
  return undefined;
73
70
  }
74
71
 
@@ -187,10 +184,7 @@ export default function (pi: ExtensionAPI): void {
187
184
  }
188
185
 
189
186
  if (!decision.approved) {
190
- ctx.ui.notify(
191
- `${highest.icon} ${decision.reason}: ${highest.detail}`,
192
- "warning",
193
- );
187
+ ctx.ui.notify(`${highest.icon} ${decision.reason}: ${highest.detail}`, "warning");
194
188
  return { block: true, reason: `[${highest.label}] ${decision.reason}` };
195
189
  }
196
190
 
@@ -202,10 +196,7 @@ export default function (pi: ExtensionAPI): void {
202
196
  : "accent";
203
197
  ctx.ui.notify(
204
198
  `${highest.icon} ` +
205
- ctx.ui.theme.fg(
206
- severityColor,
207
- `Approved ${highest.label.toLowerCase()} command`,
208
- ),
199
+ ctx.ui.theme.fg(severityColor, `Approved ${highest.label.toLowerCase()} command`),
209
200
  "info",
210
201
  );
211
202
  return undefined;
package/src/lib.ts CHANGED
@@ -32,8 +32,7 @@ export const DEFAULT_RULES: Rule[] = [
32
32
  reason: "rm -rf on /",
33
33
  },
34
34
  {
35
- pattern:
36
- /\brm\s+(-[a-z]*r[a-z]*f|-[a-z]*f[a-z]*r)\s+(~|\$HOME)(\s|$|\/\s*$)/i,
35
+ pattern: /\brm\s+(-[a-z]*r[a-z]*f|-[a-z]*f[a-z]*r)\s+(~|\$HOME)(\s|$|\/\s*$)/i,
37
36
  severity: "critical",
38
37
  reason: "rm -rf on $HOME",
39
38
  },
@@ -171,9 +170,7 @@ export function buildRules(cfg: UserConfig): {
171
170
  export function classify(command: string, rules: Rule[]): Rule | undefined {
172
171
  const order: Severity[] = ["critical", "dangerous", "risky"];
173
172
  for (const sev of order) {
174
- const hit = rules.find(
175
- (r) => r.severity === sev && r.pattern.test(command),
176
- );
173
+ const hit = rules.find((r) => r.severity === sev && r.pattern.test(command));
177
174
  if (hit) return hit;
178
175
  }
179
176
  return undefined;
package/src/prompt.ts CHANGED
@@ -6,10 +6,7 @@
6
6
  */
7
7
 
8
8
  import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
9
- import {
10
- type OverlayResult,
11
- showOverlay,
12
- } from "@xynogen/pix-pretty/gate-overlay";
9
+ import { type OverlayResult, showOverlay } from "@xynogen/pix-pretty/gate-overlay";
13
10
  import type { PathRule, Rule } from "./lib.ts";
14
11
 
15
12
  export interface GateDecision {
@@ -106,22 +103,17 @@ export async function promptPathDecision(
106
103
  },
107
104
  ];
108
105
 
109
- const result: OverlayResult = await showOverlay(
110
- ui as Parameters<typeof showOverlay>[0],
111
- {
112
- mode: "confirm",
113
- title: `${icon} ${label} — ${hit.reason}`,
114
- body: [`${op.toUpperCase()} ${path}`],
115
- accent,
116
- timeoutMs: PATH_TIMEOUT_MS[hit.severity],
117
- choices,
118
- },
119
- );
106
+ const result: OverlayResult = await showOverlay(ui as Parameters<typeof showOverlay>[0], {
107
+ mode: "confirm",
108
+ title: `${icon} ${label} — ${hit.reason}`,
109
+ body: [`${op.toUpperCase()} ${path}`],
110
+ accent,
111
+ timeoutMs: PATH_TIMEOUT_MS[hit.severity],
112
+ choices,
113
+ });
120
114
 
121
- if (result.action === "approved")
122
- return { approved: true, reason: "Approved" };
123
- if (result.action === "timeout")
124
- return { approved: false, reason: "Timed out" };
115
+ if (result.action === "approved") return { approved: true, reason: "Approved" };
116
+ if (result.action === "timeout") return { approved: false, reason: "Timed out" };
125
117
  return { approved: false, reason: "Blocked by user" };
126
118
  }
127
119
 
@@ -152,8 +144,7 @@ export async function promptMergedGateDecision(
152
144
  // Sort by tier desc so the highest-severity concern drives dialog behavior.
153
145
  const sorted = [...concerns].sort((a, b) => b.tier - a.tier);
154
146
  const highest = sorted[0];
155
- if (!highest)
156
- throw new Error("promptMergedGateDecision requires at least one concern");
147
+ if (!highest) throw new Error("promptMergedGateDecision requires at least one concern");
157
148
  const isRestrictive = highest.tier >= SEVERITY_TIER.block; // block or critical
158
149
 
159
150
  // Title: unique severity types, joined with " + "
@@ -166,16 +157,11 @@ export async function promptMergedGateDecision(
166
157
  uniqueLabels.push(key);
167
158
  }
168
159
  }
169
- const titleSuffix =
170
- command.length > 55 ? `${command.slice(0, 52)}…` : command;
160
+ const titleSuffix = command.length > 55 ? `${command.slice(0, 52)}…` : command;
171
161
  const title = `${uniqueLabels.join(" + ")} — ${titleSuffix}`;
172
162
 
173
163
  // Body: command + concern list
174
- const body = [
175
- command,
176
- "",
177
- ...sorted.map((c) => `${c.icon} ${c.label} — ${c.detail}`),
178
- ];
164
+ const body = [command, "", ...sorted.map((c) => `${c.icon} ${c.label} — ${c.detail}`)];
179
165
 
180
166
  const accent =
181
167
  highest.tier >= SEVERITY_TIER.critical
@@ -215,15 +201,17 @@ export async function promptMergedGateDecision(
215
201
  },
216
202
  ];
217
203
 
218
- const result: OverlayResult = await showOverlay(
219
- ui as Parameters<typeof showOverlay>[0],
220
- { mode: "confirm", title, body, accent, timeoutMs, choices },
221
- );
204
+ const result: OverlayResult = await showOverlay(ui as Parameters<typeof showOverlay>[0], {
205
+ mode: "confirm",
206
+ title,
207
+ body,
208
+ accent,
209
+ timeoutMs,
210
+ choices,
211
+ });
222
212
 
223
- if (result.action === "approved")
224
- return { approved: true, reason: "Approved" };
225
- if (result.action === "timeout")
226
- return { approved: false, reason: "Timed out" };
213
+ if (result.action === "approved") return { approved: true, reason: "Approved" };
214
+ if (result.action === "timeout") return { approved: false, reason: "Timed out" };
227
215
  return { approved: false, reason: "Blocked by user" };
228
216
  }
229
217
 
@@ -270,21 +258,16 @@ export async function promptGateDecision(
270
258
  },
271
259
  ];
272
260
 
273
- const result: OverlayResult = await showOverlay(
274
- ui as Parameters<typeof showOverlay>[0],
275
- {
276
- mode: "confirm",
277
- title: `${icon} ${label} — ${hit.reason}`,
278
- body: [command],
279
- accent,
280
- timeoutMs: TIMEOUT_MS[hit.severity],
281
- choices,
282
- },
283
- );
261
+ const result: OverlayResult = await showOverlay(ui as Parameters<typeof showOverlay>[0], {
262
+ mode: "confirm",
263
+ title: `${icon} ${label} — ${hit.reason}`,
264
+ body: [command],
265
+ accent,
266
+ timeoutMs: TIMEOUT_MS[hit.severity],
267
+ choices,
268
+ });
284
269
 
285
- if (result.action === "approved")
286
- return { approved: true, reason: "Approved" };
287
- if (result.action === "timeout")
288
- return { approved: false, reason: "Timed out" };
270
+ if (result.action === "approved") return { approved: true, reason: "Approved" };
271
+ if (result.action === "timeout") return { approved: false, reason: "Timed out" };
289
272
  return { approved: false, reason: "Blocked by user" };
290
273
  }