pi-permissions 1.0.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Nico Avanzini
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,100 @@
1
+ # pi-permissions
2
+
3
+ A [pi](https://github.com/badlogic/pi) extension that provides configurable allow/deny permission rules for tool calls — similar to Claude's permission system.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ # From npm
9
+ pi install npm:pi-permissions
10
+
11
+ # From GitHub
12
+ pi install https://github.com/NicoAvanzDev/pi-permissions
13
+
14
+ # Project-local (shared with team)
15
+ pi install -l https://github.com/NicoAvanzDev/pi-permissions
16
+
17
+ # Try without installing
18
+ pi -e npm:pi-permissions
19
+ ```
20
+
21
+ ## Configuration
22
+
23
+ Create a `permissions.json` file in one of these locations:
24
+
25
+ | Location | Scope |
26
+ |----------|-------|
27
+ | `.pi/permissions.json` | Project-local (checked first) |
28
+ | `~/.pi/agent/permissions.json` | Global fallback |
29
+
30
+ ### Example
31
+
32
+ ```json
33
+ {
34
+ "permissions": {
35
+ "allow": [
36
+ "Bash(npm run *)",
37
+ "Bash(npm test *)",
38
+ "Bash(git commit *)",
39
+ "Bash(git diff *)",
40
+ "Bash(git status)",
41
+ "Bash(git log *)",
42
+ "Bash(* --version)",
43
+ "Bash(* --help *)"
44
+ ],
45
+ "deny": [
46
+ "Bash(git push *)",
47
+ "Bash(git add .)",
48
+ "Bash(rm -rf *)",
49
+ "Write(*.env)",
50
+ "Write(.env.*)",
51
+ "Edit(*.env)"
52
+ ]
53
+ }
54
+ }
55
+ ```
56
+
57
+ ## Rule Format
58
+
59
+ Rules use the format `Tool(pattern)` where:
60
+
61
+ - **Tool** — the pi tool name: `Bash`, `Write`, `Edit`, `Read`
62
+ - **pattern** — a glob pattern where `*` matches any characters
63
+
64
+ ### Supported Tools
65
+
66
+ | Tool | What the pattern matches against |
67
+ |------|----------------------------------|
68
+ | `Bash(pattern)` | The bash command string |
69
+ | `Write(pattern)` | The file path being written |
70
+ | `Edit(pattern)` | The file path being edited |
71
+ | `Read(pattern)` | The file path being read |
72
+
73
+ ### Evaluation Logic
74
+
75
+ 1. **Deny rules are checked first** — if any deny rule matches, the call is blocked
76
+ 2. **Allow rules are checked next** — if allow rules exist for the tool, the call must match at least one
77
+ 3. **No rules = no restrictions** — if no allow rules exist for a tool, all calls pass (unless denied)
78
+
79
+ This means:
80
+ - Use **deny** to block specific dangerous commands
81
+ - Use **allow** to whitelist only approved commands (everything else is blocked)
82
+ - **Deny always wins** over allow
83
+
84
+ ### Pattern Examples
85
+
86
+ | Pattern | Matches | Doesn't match |
87
+ |---------|---------|---------------|
88
+ | `Bash(git push *)` | `git push origin main` | `git pull` |
89
+ | `Bash(npm run *)` | `npm run build`, `npm run test` | `npm install` |
90
+ | `Bash(* --version)` | `node --version`, `npm --version` | `node index.js` |
91
+ | `Write(*.env)` | `.env`, `app.env` | `.env.example` |
92
+ | `Write(secrets/*)` | `secrets/key.pem` | `src/secrets.ts` |
93
+
94
+ ## Reloading
95
+
96
+ After editing `permissions.json`, type `/reload` in pi to apply the changes.
97
+
98
+ ## License
99
+
100
+ MIT
@@ -0,0 +1,59 @@
1
+ import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
2
+ import { readFile } from "node:fs/promises";
3
+ import { join } from "node:path";
4
+ import type { PermissionsConfig } from "./rules.ts";
5
+ import { evaluate, parseRules } from "./rules.ts";
6
+ import type { ParsedRule } from "./rules.ts";
7
+
8
+ async function loadConfig(cwd: string): Promise<PermissionsConfig> {
9
+ const paths = [join(cwd, ".pi", "permissions.json")];
10
+
11
+ const home = process.env.HOME || process.env.USERPROFILE || "";
12
+ if (home) {
13
+ paths.push(join(home, ".pi", "agent", "permissions.json"));
14
+ }
15
+
16
+ for (const configPath of paths) {
17
+ try {
18
+ const content = await readFile(configPath, "utf-8");
19
+ return JSON.parse(content) as PermissionsConfig;
20
+ } catch {
21
+ // File not found or invalid, try next
22
+ }
23
+ }
24
+ return {};
25
+ }
26
+
27
+ export default function (pi: ExtensionAPI) {
28
+ let allowRules: ParsedRule[] = [];
29
+ let denyRules: ParsedRule[] = [];
30
+
31
+ async function reloadRules(cwd: string) {
32
+ const config = await loadConfig(cwd);
33
+ allowRules = parseRules(config.permissions?.allow ?? []);
34
+ denyRules = parseRules(config.permissions?.deny ?? []);
35
+ }
36
+
37
+ pi.on("session_start", async (_event, ctx) => {
38
+ await reloadRules(ctx.cwd);
39
+ const total = allowRules.length + denyRules.length;
40
+ if (total > 0) {
41
+ ctx.ui.notify(
42
+ `Permissions loaded: ${allowRules.length} allow, ${denyRules.length} deny rules`,
43
+ "info",
44
+ );
45
+ }
46
+ });
47
+
48
+ pi.on("tool_call", async (event) => {
49
+ const result = evaluate(
50
+ event.toolName,
51
+ event.input as Record<string, unknown>,
52
+ allowRules,
53
+ denyRules,
54
+ );
55
+ if (result.blocked) {
56
+ return { block: true, reason: result.reason! };
57
+ }
58
+ });
59
+ }
package/package.json ADDED
@@ -0,0 +1,46 @@
1
+ {
2
+ "name": "pi-permissions",
3
+ "version": "1.0.0",
4
+ "description": "Configurable allow/deny permission rules for pi tool calls — control which bash commands, file reads, writes, and edits the agent can perform.",
5
+ "keywords": [
6
+ "pi-package",
7
+ "pi-extension",
8
+ "permissions",
9
+ "security",
10
+ "allow-deny"
11
+ ],
12
+ "homepage": "https://github.com/NicoAvanzDev/pi-permissions#readme",
13
+ "bugs": {
14
+ "url": "https://github.com/NicoAvanzDev/pi-permissions/issues"
15
+ },
16
+ "license": "MIT",
17
+ "repository": {
18
+ "type": "git",
19
+ "url": "git+https://github.com/NicoAvanzDev/pi-permissions.git"
20
+ },
21
+ "files": [
22
+ "extensions/permissions.ts",
23
+ "README.md"
24
+ ],
25
+ "type": "module",
26
+ "scripts": {
27
+ "build": "echo 'nothing to build'",
28
+ "test": "vitest run",
29
+ "lint": "oxlint",
30
+ "fmt": "oxfmt \"**/*.ts\"",
31
+ "fmt:check": "oxfmt --check \"**/*.ts\"",
32
+ "check": "vitest run && oxlint && oxfmt --check \"**/*.ts\"",
33
+ "prepublishOnly": "npm pack --dry-run"
34
+ },
35
+ "devDependencies": {
36
+ "oxfmt": "^0.41.0",
37
+ "oxlint": "^1.56.0",
38
+ "vitest": "^4.1.0"
39
+ },
40
+ "peerDependencies": {
41
+ "@mariozechner/pi-coding-agent": "*"
42
+ },
43
+ "pi": {
44
+ "extensions": ["./extensions"]
45
+ }
46
+ }