pi-permission-system 0.4.9 → 0.5.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/CHANGELOG.md +11 -0
- package/README.md +9 -4
- package/package.json +71 -71
- package/src/config-modal.ts +12 -2
- package/src/extension-config.ts +170 -166
- package/src/index.ts +458 -124
- package/src/logging.ts +123 -96
- package/src/model-option-compatibility.ts +168 -168
- package/src/permission-forwarding.ts +139 -135
- package/src/permission-manager.ts +1 -1
- package/src/skill-prompt-sanitizer.ts +4 -4
- package/src/status.ts +20 -20
- package/src/types-shims.d.ts +10 -4
- package/src/zellij-modal.ts +2 -2
- package/tests/config-modal.test.ts +152 -149
- package/tests/permission-system.test.ts +393 -11
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.5.0] - 2026-05-22
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- Added `logPlaintextBashCommands` as an opt-in extension setting and settings-modal control; review logs redact raw bash command strings by default while retaining safe metadata.
|
|
14
|
+
- Added structured edit summaries for `replace`, `append`, `prepend`, `replace_text`, and `delete` payloads in permission prompts.
|
|
15
|
+
- Added skill-read enforcement for direct `read` calls under global and project Pi skill directories by inferring the skill name from the requested path when prompt entries are absent.
|
|
16
|
+
|
|
17
|
+
### Changed
|
|
18
|
+
- Hardened subagent permission forwarding with watched request directories, async request/response reads, and nonce-bound responses.
|
|
19
|
+
- Updated package metadata and lockfile version to `0.5.0` and migrated Pi peer dependency metadata to the `@earendil-works` scope.
|
|
20
|
+
|
|
10
21
|
## [0.4.9] - 2026-05-05
|
|
11
22
|
|
|
12
23
|
### Changed
|
package/README.md
CHANGED
|
@@ -95,11 +95,11 @@ If you are coming from OpenCode, you usually do **not** need to rewrite your who
|
|
|
95
95
|
- **Runtime Enforcement** — Blocks/asks/allows at tool call time with UI confirmation dialogs and readable approval summaries
|
|
96
96
|
- **Bash Command Control** — Wildcard pattern matching for granular bash command permissions
|
|
97
97
|
- **MCP Access Control** — Server and tool-level permissions for MCP operations
|
|
98
|
-
- **Skill Protection** — Controls which skills can be loaded or read from disk, including multi-block prompt sanitization
|
|
98
|
+
- **Skill Protection** — Controls which skills can be loaded or read from disk, including multi-block prompt sanitization and path-inferred reads under Pi skill directories
|
|
99
99
|
- **Per-Agent Overrides** — Agent-specific permission policies via YAML frontmatter
|
|
100
100
|
- **Subagent Permission Forwarding** — Forwards `ask` confirmations from non-UI subagents back to the main interactive session
|
|
101
101
|
- **Runtime YOLO Control** — Lets users toggle yolo mode from the settings modal and lets other extensions toggle it through the runtime API
|
|
102
|
-
- **File-Based Review Logging** — Writes permission request/denial review entries to a file by default
|
|
102
|
+
- **File-Based Review Logging** — Writes permission request/denial review entries to a file by default, with raw bash command text redacted unless `logPlaintextBashCommands` is enabled
|
|
103
103
|
- **Optional Debug Logging** — Keeps verbose extension diagnostics in a separate file when enabled in `config.json`
|
|
104
104
|
- **JSON Schema Validation** — Full schema for editor autocomplete and config validation
|
|
105
105
|
- **External Directory Guard** — Enforces `special.external_directory` for path-bearing file tools that target paths outside the active working directory
|
|
@@ -167,7 +167,7 @@ The extension integrates via Pi's lifecycle hooks:
|
|
|
167
167
|
|----------------------|-------------------------------------------------------------------------------------------|
|
|
168
168
|
| `before_agent_start` | Filters active tools, removes denied tool entries from the system prompt, and hides denied skills |
|
|
169
169
|
| `tool_call` | Enforces permissions for every tool invocation |
|
|
170
|
-
| `input` |
|
|
170
|
+
| `input` | Tracks explicit `/skill:<name>` requests so user-invoked skill loads can proceed while agent-initiated reads remain policy-gated |
|
|
171
171
|
|
|
172
172
|
**Additional behaviors:**
|
|
173
173
|
- Unknown/unregistered tools are blocked before permission checks (prevents bypass attempts)
|
|
@@ -175,8 +175,10 @@ The extension integrates via Pi's lifecycle hooks:
|
|
|
175
175
|
- Extension-provided tools like `task`, `mcp`, and third-party tools are handled through the same registered-tool permission layer instead of private built-in hardcodes
|
|
176
176
|
- When a subagent hits an `ask` permission without direct UI access, the request can be forwarded to the main interactive session for confirmation
|
|
177
177
|
- Generic extension-tool approval prompts include a bounded input preview; built-in file tools use concise human-readable summaries instead of raw multiline JSON
|
|
178
|
-
- Permission review logs include
|
|
178
|
+
- Permission review logs include redacted prompt/input metadata for auditing; raw bash command text is omitted unless `logPlaintextBashCommands` is enabled.
|
|
179
179
|
- Path-bearing file tools (`read`, `write`, `edit`, `find`, `grep`, `ls`) evaluate `special.external_directory` before their normal tool permission when an explicit path points outside `ctx.cwd`
|
|
180
|
+
- `read` calls under global and project Pi skill directories are checked against `skills` policy even when the skill entry is inferred from the path rather than an active prompt block.
|
|
181
|
+
- Structured edit payloads are summarized by operation and line count in prompts so permission decisions do not require raw multiline JSON.
|
|
180
182
|
|
|
181
183
|
## Configuration
|
|
182
184
|
|
|
@@ -192,6 +194,7 @@ The extension creates this file automatically when it is missing. It controls ex
|
|
|
192
194
|
{
|
|
193
195
|
"debugLog": false,
|
|
194
196
|
"permissionReviewLog": true,
|
|
197
|
+
"logPlaintextBashCommands": false,
|
|
195
198
|
"yoloMode": false
|
|
196
199
|
}
|
|
197
200
|
```
|
|
@@ -200,6 +203,7 @@ The extension creates this file automatically when it is missing. It controls ex
|
|
|
200
203
|
|-----|---------|-------------|
|
|
201
204
|
| `debugLog` | `false` | Enables verbose diagnostic logging to `logs/pi-permission-system-debug.jsonl` |
|
|
202
205
|
| `permissionReviewLog` | `true` | Enables the permission request/denial review log at `logs/pi-permission-system-permission-review.jsonl` |
|
|
206
|
+
| `logPlaintextBashCommands` | `false` | Opts in to storing raw bash command strings in review logs; when disabled, bash commands are redacted and only safe metadata is retained |
|
|
203
207
|
| `yoloMode` | `false` | Auto-approves `ask` results instead of prompting when yolo mode is enabled |
|
|
204
208
|
|
|
205
209
|
Both logs write to files only under the extension directory by default. Set `PI_PERMISSION_SYSTEM_LOGS_DIR` to redirect review/debug logs to a specific directory. No debug output is printed to the terminal.
|
|
@@ -430,6 +434,7 @@ Skill name patterns use `*` wildcards:
|
|
|
430
434
|
}
|
|
431
435
|
```
|
|
432
436
|
|
|
437
|
+
Skill-read enforcement also applies when a `read` path is under the global Pi skills directory (`~/.pi/agent/skills` or `PI_CODING_AGENT_DIR/skills`) or the active project's `.pi/agent/skills` directory. In that case the skill name is inferred from the path and checked against `skills` policy even if no active prompt block listed the skill; direct user `/skill:<name>` requests are allowed to proceed for that requested skill.
|
|
433
438
|
### `special`
|
|
434
439
|
|
|
435
440
|
Reserved permission checks:
|
package/package.json
CHANGED
|
@@ -1,71 +1,71 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "pi-permission-system",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Permission enforcement extension for the Pi coding agent.",
|
|
5
|
-
"type": "module",
|
|
6
|
-
"main": "./index.ts",
|
|
7
|
-
"exports": {
|
|
8
|
-
".": "./index.ts"
|
|
9
|
-
},
|
|
10
|
-
"files": [
|
|
11
|
-
"index.ts",
|
|
12
|
-
"src",
|
|
13
|
-
"tests",
|
|
14
|
-
"config/config.example.json",
|
|
15
|
-
"schemas/permissions.schema.json",
|
|
16
|
-
"README.md",
|
|
17
|
-
"CHANGELOG.md",
|
|
18
|
-
"LICENSE"
|
|
19
|
-
],
|
|
20
|
-
"scripts": {
|
|
21
|
-
"typecheck": "npx --yes -p typescript@5.7.3 tsc -p tsconfig.json --noEmit",
|
|
22
|
-
"build": "npm run typecheck",
|
|
23
|
-
"lint": "npm run typecheck",
|
|
24
|
-
"validate:artifacts": "node ./scripts/validate-artifacts.mjs",
|
|
25
|
-
"test": "bun ./tests/permission-system.test.ts && bun ./tests/config-modal.test.ts",
|
|
26
|
-
"check": "npm run lint && npm run validate:artifacts && npm run test"
|
|
27
|
-
},
|
|
28
|
-
"keywords": [
|
|
29
|
-
"pi-package",
|
|
30
|
-
"pi",
|
|
31
|
-
"pi-extension",
|
|
32
|
-
"pi-coding-agent",
|
|
33
|
-
"coding-agent",
|
|
34
|
-
"permissions",
|
|
35
|
-
"policy",
|
|
36
|
-
"access-control",
|
|
37
|
-
"authorization",
|
|
38
|
-
"security"
|
|
39
|
-
],
|
|
40
|
-
"author": "MasuRii",
|
|
41
|
-
"license": "MIT",
|
|
42
|
-
"repository": {
|
|
43
|
-
"type": "git",
|
|
44
|
-
"url": "git+https://github.com/MasuRii/pi-permission-system.git"
|
|
45
|
-
},
|
|
46
|
-
"homepage": "https://github.com/MasuRii/pi-permission-system#readme",
|
|
47
|
-
"bugs": {
|
|
48
|
-
"url": "https://github.com/MasuRii/pi-permission-system/issues"
|
|
49
|
-
},
|
|
50
|
-
"engines": {
|
|
51
|
-
"node": ">=20",
|
|
52
|
-
"bun": ">=1.1.0"
|
|
53
|
-
},
|
|
54
|
-
"publishConfig": {
|
|
55
|
-
"access": "public"
|
|
56
|
-
},
|
|
57
|
-
"pi": {
|
|
58
|
-
"extensions": [
|
|
59
|
-
"./index.ts"
|
|
60
|
-
]
|
|
61
|
-
},
|
|
62
|
-
"peerDependencies": {
|
|
63
|
-
"@
|
|
64
|
-
"@
|
|
65
|
-
"@
|
|
66
|
-
"@
|
|
67
|
-
},
|
|
68
|
-
"dependencies": {
|
|
69
|
-
"jsonc-parser": "^3.3.1"
|
|
70
|
-
}
|
|
71
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "pi-permission-system",
|
|
3
|
+
"version": "0.5.0",
|
|
4
|
+
"description": "Permission enforcement extension for the Pi coding agent.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./index.ts",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": "./index.ts"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"index.ts",
|
|
12
|
+
"src",
|
|
13
|
+
"tests",
|
|
14
|
+
"config/config.example.json",
|
|
15
|
+
"schemas/permissions.schema.json",
|
|
16
|
+
"README.md",
|
|
17
|
+
"CHANGELOG.md",
|
|
18
|
+
"LICENSE"
|
|
19
|
+
],
|
|
20
|
+
"scripts": {
|
|
21
|
+
"typecheck": "npx --yes -p typescript@5.7.3 tsc -p tsconfig.json --noEmit",
|
|
22
|
+
"build": "npm run typecheck",
|
|
23
|
+
"lint": "npm run typecheck",
|
|
24
|
+
"validate:artifacts": "node ./scripts/validate-artifacts.mjs",
|
|
25
|
+
"test": "bun ./tests/permission-system.test.ts && bun ./tests/config-modal.test.ts",
|
|
26
|
+
"check": "npm run lint && npm run validate:artifacts && npm run test"
|
|
27
|
+
},
|
|
28
|
+
"keywords": [
|
|
29
|
+
"pi-package",
|
|
30
|
+
"pi",
|
|
31
|
+
"pi-extension",
|
|
32
|
+
"pi-coding-agent",
|
|
33
|
+
"coding-agent",
|
|
34
|
+
"permissions",
|
|
35
|
+
"policy",
|
|
36
|
+
"access-control",
|
|
37
|
+
"authorization",
|
|
38
|
+
"security"
|
|
39
|
+
],
|
|
40
|
+
"author": "MasuRii",
|
|
41
|
+
"license": "MIT",
|
|
42
|
+
"repository": {
|
|
43
|
+
"type": "git",
|
|
44
|
+
"url": "git+https://github.com/MasuRii/pi-permission-system.git"
|
|
45
|
+
},
|
|
46
|
+
"homepage": "https://github.com/MasuRii/pi-permission-system#readme",
|
|
47
|
+
"bugs": {
|
|
48
|
+
"url": "https://github.com/MasuRii/pi-permission-system/issues"
|
|
49
|
+
},
|
|
50
|
+
"engines": {
|
|
51
|
+
"node": ">=20",
|
|
52
|
+
"bun": ">=1.1.0"
|
|
53
|
+
},
|
|
54
|
+
"publishConfig": {
|
|
55
|
+
"access": "public"
|
|
56
|
+
},
|
|
57
|
+
"pi": {
|
|
58
|
+
"extensions": [
|
|
59
|
+
"./index.ts"
|
|
60
|
+
]
|
|
61
|
+
},
|
|
62
|
+
"peerDependencies": {
|
|
63
|
+
"@sinclair/typebox": "^0.34.49",
|
|
64
|
+
"@earendil-works/pi-ai": "^0.75.4",
|
|
65
|
+
"@earendil-works/pi-coding-agent": "^0.75.4",
|
|
66
|
+
"@earendil-works/pi-tui": "^0.75.4"
|
|
67
|
+
},
|
|
68
|
+
"dependencies": {
|
|
69
|
+
"jsonc-parser": "^3.3.1"
|
|
70
|
+
}
|
|
71
|
+
}
|
package/src/config-modal.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { ExtensionAPI, ExtensionCommandContext } from "@
|
|
2
|
-
import type { SettingItem } from "@
|
|
1
|
+
import type { ExtensionAPI, ExtensionCommandContext } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import type { SettingItem } from "@earendil-works/pi-tui";
|
|
3
3
|
|
|
4
4
|
import type { PermissionSystemExtensionConfig } from "./extension-config.js";
|
|
5
5
|
import { ZellijModal, ZellijSettingsModal } from "./zellij-modal.js";
|
|
@@ -36,6 +36,13 @@ function buildSettingItems(config: PermissionSystemExtensionConfig): SettingItem
|
|
|
36
36
|
currentValue: toOnOff(config.permissionReviewLog),
|
|
37
37
|
values: ON_OFF,
|
|
38
38
|
},
|
|
39
|
+
{
|
|
40
|
+
id: "logPlaintextBashCommands",
|
|
41
|
+
label: "Plaintext bash commands in review log",
|
|
42
|
+
description: "Opt in to storing raw bash command strings; disabled stores only safe command metadata",
|
|
43
|
+
currentValue: toOnOff(config.logPlaintextBashCommands),
|
|
44
|
+
values: ON_OFF,
|
|
45
|
+
},
|
|
39
46
|
{
|
|
40
47
|
id: "debugLog",
|
|
41
48
|
label: "Debug logging",
|
|
@@ -56,6 +63,8 @@ function applySetting(
|
|
|
56
63
|
return { ...config, yoloMode: value === "on" };
|
|
57
64
|
case "permissionReviewLog":
|
|
58
65
|
return { ...config, permissionReviewLog: value === "on" };
|
|
66
|
+
case "logPlaintextBashCommands":
|
|
67
|
+
return { ...config, logPlaintextBashCommands: value === "on" };
|
|
59
68
|
case "debugLog":
|
|
60
69
|
return { ...config, debugLog: value === "on" };
|
|
61
70
|
default:
|
|
@@ -66,6 +75,7 @@ function applySetting(
|
|
|
66
75
|
function syncSettingValues(settingsList: SettingValueSyncTarget, config: PermissionSystemExtensionConfig): void {
|
|
67
76
|
settingsList.updateValue("yoloMode", toOnOff(config.yoloMode));
|
|
68
77
|
settingsList.updateValue("permissionReviewLog", toOnOff(config.permissionReviewLog));
|
|
78
|
+
settingsList.updateValue("logPlaintextBashCommands", toOnOff(config.logPlaintextBashCommands));
|
|
69
79
|
settingsList.updateValue("debugLog", toOnOff(config.debugLog));
|
|
70
80
|
}
|
|
71
81
|
|
package/src/extension-config.ts
CHANGED
|
@@ -1,166 +1,170 @@
|
|
|
1
|
-
import { existsSync, mkdirSync, readFileSync, renameSync, unlinkSync, writeFileSync } from "node:fs";
|
|
2
|
-
import { dirname, join } from "node:path";
|
|
3
|
-
import { fileURLToPath } from "node:url";
|
|
4
|
-
|
|
5
|
-
import { toRecord } from "./common.js";
|
|
6
|
-
import { formatJsoncConfigLoadWarning, parseJsoncConfig } from "./jsonc-config.js";
|
|
7
|
-
|
|
8
|
-
export const EXTENSION_ID = "pi-permission-system";
|
|
9
|
-
|
|
10
|
-
export interface PermissionSystemExtensionConfig {
|
|
11
|
-
debugLog: boolean;
|
|
12
|
-
permissionReviewLog: boolean;
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
export const
|
|
40
|
-
export const
|
|
41
|
-
export const
|
|
42
|
-
export const
|
|
43
|
-
export const
|
|
44
|
-
|
|
45
|
-
export
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
}
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, renameSync, unlinkSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
|
|
5
|
+
import { toRecord } from "./common.js";
|
|
6
|
+
import { formatJsoncConfigLoadWarning, parseJsoncConfig } from "./jsonc-config.js";
|
|
7
|
+
|
|
8
|
+
export const EXTENSION_ID = "pi-permission-system";
|
|
9
|
+
|
|
10
|
+
export interface PermissionSystemExtensionConfig {
|
|
11
|
+
debugLog: boolean;
|
|
12
|
+
permissionReviewLog: boolean;
|
|
13
|
+
logPlaintextBashCommands: boolean;
|
|
14
|
+
yoloMode: boolean;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface PermissionSystemConfigLoadResult {
|
|
18
|
+
config: PermissionSystemExtensionConfig;
|
|
19
|
+
created: boolean;
|
|
20
|
+
warning?: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface PermissionSystemConfigSaveResult {
|
|
24
|
+
success: boolean;
|
|
25
|
+
error?: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export const DEFAULT_EXTENSION_CONFIG: PermissionSystemExtensionConfig = {
|
|
29
|
+
debugLog: false,
|
|
30
|
+
permissionReviewLog: true,
|
|
31
|
+
logPlaintextBashCommands: false,
|
|
32
|
+
yoloMode: false,
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export function resolveExtensionRoot(moduleUrl = import.meta.url): string {
|
|
36
|
+
return join(dirname(fileURLToPath(moduleUrl)), "..");
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export const EXTENSION_ROOT = resolveExtensionRoot();
|
|
40
|
+
export const CONFIG_PATH = join(EXTENSION_ROOT, "config.json");
|
|
41
|
+
export const LOGS_DIR = join(EXTENSION_ROOT, "logs");
|
|
42
|
+
export const DEBUG_LOG_PATH = join(LOGS_DIR, `${EXTENSION_ID}-debug.jsonl`);
|
|
43
|
+
export const PERMISSION_REVIEW_LOG_PATH = join(LOGS_DIR, `${EXTENSION_ID}-permission-review.jsonl`);
|
|
44
|
+
export const CONFIG_PATH_ENV_KEY = "PI_PERMISSION_SYSTEM_CONFIG_PATH";
|
|
45
|
+
export const LOGS_DIR_ENV_KEY = "PI_PERMISSION_SYSTEM_LOGS_DIR";
|
|
46
|
+
|
|
47
|
+
export function getPermissionSystemConfigPath(configPath?: string): string {
|
|
48
|
+
const overridePath = process.env[CONFIG_PATH_ENV_KEY]?.trim();
|
|
49
|
+
return configPath || overridePath || CONFIG_PATH;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function getPermissionSystemLogsDir(logsDir?: string): string {
|
|
53
|
+
const overrideDir = process.env[LOGS_DIR_ENV_KEY]?.trim();
|
|
54
|
+
return logsDir || overrideDir || LOGS_DIR;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function getPermissionSystemDebugLogPath(logsDir = getPermissionSystemLogsDir()): string {
|
|
58
|
+
return join(logsDir, `${EXTENSION_ID}-debug.jsonl`);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function getPermissionSystemReviewLogPath(logsDir = getPermissionSystemLogsDir()): string {
|
|
62
|
+
return join(logsDir, `${EXTENSION_ID}-permission-review.jsonl`);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function cloneDefaultConfig(): PermissionSystemExtensionConfig {
|
|
66
|
+
return {
|
|
67
|
+
debugLog: DEFAULT_EXTENSION_CONFIG.debugLog,
|
|
68
|
+
permissionReviewLog: DEFAULT_EXTENSION_CONFIG.permissionReviewLog,
|
|
69
|
+
logPlaintextBashCommands: DEFAULT_EXTENSION_CONFIG.logPlaintextBashCommands,
|
|
70
|
+
yoloMode: DEFAULT_EXTENSION_CONFIG.yoloMode,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function createDefaultConfigContent(): string {
|
|
75
|
+
return `${JSON.stringify(DEFAULT_EXTENSION_CONFIG, null, 2)}\n`;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function normalizePermissionSystemConfig(raw: unknown): PermissionSystemExtensionConfig {
|
|
79
|
+
const record = toRecord(raw);
|
|
80
|
+
return {
|
|
81
|
+
debugLog: record.debugLog === true,
|
|
82
|
+
permissionReviewLog: record.permissionReviewLog !== false,
|
|
83
|
+
logPlaintextBashCommands: record.logPlaintextBashCommands === true,
|
|
84
|
+
yoloMode: record.yoloMode === true,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function ensureConfigDirectory(configPath: string): void {
|
|
89
|
+
mkdirSync(dirname(configPath), { recursive: true });
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function ensurePermissionSystemConfig(configPath = getPermissionSystemConfigPath()): { created: boolean; warning?: string } {
|
|
93
|
+
if (existsSync(configPath)) {
|
|
94
|
+
return { created: false };
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
try {
|
|
98
|
+
ensureConfigDirectory(configPath);
|
|
99
|
+
writeFileSync(configPath, createDefaultConfigContent(), "utf-8");
|
|
100
|
+
return { created: true };
|
|
101
|
+
} catch (error) {
|
|
102
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
103
|
+
return {
|
|
104
|
+
created: false,
|
|
105
|
+
warning: `Failed to initialize permission-system config at '${configPath}': ${message}`,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export function loadPermissionSystemConfig(configPath = getPermissionSystemConfigPath()): PermissionSystemConfigLoadResult {
|
|
111
|
+
const ensureResult = ensurePermissionSystemConfig(configPath);
|
|
112
|
+
|
|
113
|
+
try {
|
|
114
|
+
const raw = readFileSync(configPath, "utf-8");
|
|
115
|
+
const parsed = parseJsoncConfig(raw, configPath, "permission-system config");
|
|
116
|
+
const config = normalizePermissionSystemConfig(parsed);
|
|
117
|
+
return {
|
|
118
|
+
config,
|
|
119
|
+
created: ensureResult.created,
|
|
120
|
+
warning: ensureResult.warning,
|
|
121
|
+
};
|
|
122
|
+
} catch (error) {
|
|
123
|
+
return {
|
|
124
|
+
config: cloneDefaultConfig(),
|
|
125
|
+
created: ensureResult.created,
|
|
126
|
+
warning: ensureResult.warning
|
|
127
|
+
?? formatJsoncConfigLoadWarning(configPath, error, "permission-system config", "using default extension config")
|
|
128
|
+
?? undefined,
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export function savePermissionSystemConfig(
|
|
134
|
+
config: PermissionSystemExtensionConfig,
|
|
135
|
+
configPath = getPermissionSystemConfigPath(),
|
|
136
|
+
): PermissionSystemConfigSaveResult {
|
|
137
|
+
const normalized = normalizePermissionSystemConfig(config);
|
|
138
|
+
const tmpPath = `${configPath}.tmp`;
|
|
139
|
+
|
|
140
|
+
try {
|
|
141
|
+
ensureConfigDirectory(configPath);
|
|
142
|
+
writeFileSync(tmpPath, `${JSON.stringify(normalized, null, 2)}\n`, "utf-8");
|
|
143
|
+
renameSync(tmpPath, configPath);
|
|
144
|
+
return { success: true };
|
|
145
|
+
} catch (error) {
|
|
146
|
+
try {
|
|
147
|
+
if (existsSync(tmpPath)) {
|
|
148
|
+
unlinkSync(tmpPath);
|
|
149
|
+
}
|
|
150
|
+
} catch {
|
|
151
|
+
// Ignore cleanup failures.
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
155
|
+
return {
|
|
156
|
+
success: false,
|
|
157
|
+
error: `Failed to save permission-system config at '${configPath}': ${message}`,
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export function ensurePermissionSystemLogsDirectory(logsDir = getPermissionSystemLogsDir()): string | undefined {
|
|
163
|
+
try {
|
|
164
|
+
mkdirSync(logsDir, { recursive: true });
|
|
165
|
+
return undefined;
|
|
166
|
+
} catch (error) {
|
|
167
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
168
|
+
return `Failed to create permission-system log directory '${logsDir}': ${message}`;
|
|
169
|
+
}
|
|
170
|
+
}
|