@gotgenes/pi-permission-system 0.7.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.
@@ -0,0 +1,203 @@
1
+ import {
2
+ existsSync,
3
+ mkdirSync,
4
+ readFileSync,
5
+ renameSync,
6
+ unlinkSync,
7
+ writeFileSync,
8
+ } from "node:fs";
9
+ import { dirname, join } from "node:path";
10
+ import { fileURLToPath } from "node:url";
11
+
12
+ import { toRecord } from "./common.js";
13
+
14
+ export const EXTENSION_ID = "pi-permission-system";
15
+
16
+ export interface PermissionSystemExtensionConfig {
17
+ debugLog: boolean;
18
+ permissionReviewLog: boolean;
19
+ yoloMode: boolean;
20
+ }
21
+
22
+ export interface PermissionSystemConfigLoadResult {
23
+ config: PermissionSystemExtensionConfig;
24
+ created: boolean;
25
+ warning?: string;
26
+ }
27
+
28
+ export interface PermissionSystemConfigSaveResult {
29
+ success: boolean;
30
+ error?: string;
31
+ }
32
+
33
+ export const DEFAULT_EXTENSION_CONFIG: PermissionSystemExtensionConfig = {
34
+ debugLog: false,
35
+ permissionReviewLog: true,
36
+ yoloMode: false,
37
+ };
38
+
39
+ export function resolveExtensionRoot(moduleUrl = import.meta.url): string {
40
+ return join(dirname(fileURLToPath(moduleUrl)), "..");
41
+ }
42
+
43
+ export const EXTENSION_ROOT = resolveExtensionRoot();
44
+ export const CONFIG_PATH = join(EXTENSION_ROOT, "config.json");
45
+ export const LOGS_DIR = join(EXTENSION_ROOT, "logs");
46
+ export const DEBUG_LOG_PATH = join(LOGS_DIR, `${EXTENSION_ID}-debug.jsonl`);
47
+ export const PERMISSION_REVIEW_LOG_PATH = join(
48
+ LOGS_DIR,
49
+ `${EXTENSION_ID}-permission-review.jsonl`,
50
+ );
51
+
52
+ function cloneDefaultConfig(): PermissionSystemExtensionConfig {
53
+ return {
54
+ debugLog: DEFAULT_EXTENSION_CONFIG.debugLog,
55
+ permissionReviewLog: DEFAULT_EXTENSION_CONFIG.permissionReviewLog,
56
+ yoloMode: DEFAULT_EXTENSION_CONFIG.yoloMode,
57
+ };
58
+ }
59
+
60
+ function createDefaultConfigContent(): string {
61
+ return `${JSON.stringify(DEFAULT_EXTENSION_CONFIG, null, 2)}\n`;
62
+ }
63
+
64
+ const PERMISSION_POLICY_KEYS: ReadonlySet<string> = new Set([
65
+ "defaultPolicy",
66
+ "tools",
67
+ "bash",
68
+ "mcp",
69
+ "skills",
70
+ "special",
71
+ "external_directory",
72
+ "doom_loop",
73
+ ]);
74
+
75
+ export function detectMisplacedPermissionKeys(
76
+ raw: Record<string, unknown>,
77
+ ): string[] {
78
+ return Object.keys(raw).filter((key) => PERMISSION_POLICY_KEYS.has(key));
79
+ }
80
+
81
+ export function normalizePermissionSystemConfig(
82
+ raw: unknown,
83
+ ): PermissionSystemExtensionConfig {
84
+ const record = toRecord(raw);
85
+ return {
86
+ debugLog: record.debugLog === true,
87
+ permissionReviewLog: record.permissionReviewLog !== false,
88
+ yoloMode: record.yoloMode === true,
89
+ };
90
+ }
91
+
92
+ function ensureConfigDirectory(configPath: string): void {
93
+ mkdirSync(dirname(configPath), { recursive: true });
94
+ }
95
+
96
+ export function ensurePermissionSystemConfig(configPath = CONFIG_PATH): {
97
+ created: boolean;
98
+ warning?: string;
99
+ } {
100
+ if (existsSync(configPath)) {
101
+ return { created: false };
102
+ }
103
+
104
+ try {
105
+ ensureConfigDirectory(configPath);
106
+ writeFileSync(configPath, createDefaultConfigContent(), "utf-8");
107
+ return { created: true };
108
+ } catch (error) {
109
+ const message = error instanceof Error ? error.message : String(error);
110
+ return {
111
+ created: false,
112
+ warning: `Failed to initialize permission-system config at '${configPath}': ${message}`,
113
+ };
114
+ }
115
+ }
116
+
117
+ export function loadPermissionSystemConfig(
118
+ configPath = CONFIG_PATH,
119
+ ): PermissionSystemConfigLoadResult {
120
+ const ensureResult = ensurePermissionSystemConfig(configPath);
121
+
122
+ try {
123
+ const raw = readFileSync(configPath, "utf-8");
124
+ const parsed = JSON.parse(raw) as unknown;
125
+ const config = normalizePermissionSystemConfig(parsed);
126
+ const misplacedKeys = detectMisplacedPermissionKeys(toRecord(parsed));
127
+
128
+ const warnings: string[] = [];
129
+ if (ensureResult.warning) {
130
+ warnings.push(ensureResult.warning);
131
+ }
132
+ if (misplacedKeys.length > 0) {
133
+ warnings.push(
134
+ `config.json contains permission-rule keys that are ignored here: ${misplacedKeys.join(", ")}.\n` +
135
+ "Permission rules belong in ~/.pi/agent/pi-permissions.jsonc, " +
136
+ "<project>/.pi/agent/pi-permissions.jsonc, or per-agent frontmatter.\n" +
137
+ "See config/config.example.json for the keys config.json supports.",
138
+ );
139
+ }
140
+
141
+ return {
142
+ config,
143
+ created: ensureResult.created,
144
+ warning: warnings.length > 0 ? warnings.join("\n") : undefined,
145
+ };
146
+ } catch (error) {
147
+ const message = error instanceof Error ? error.message : String(error);
148
+ return {
149
+ config: cloneDefaultConfig(),
150
+ created: ensureResult.created,
151
+ warning:
152
+ ensureResult.warning ??
153
+ `Failed to read permission-system config at '${configPath}': ${message}`,
154
+ };
155
+ }
156
+ }
157
+
158
+ export function savePermissionSystemConfig(
159
+ config: PermissionSystemExtensionConfig,
160
+ configPath = CONFIG_PATH,
161
+ ): PermissionSystemConfigSaveResult {
162
+ const normalized = normalizePermissionSystemConfig(config);
163
+ const tmpPath = `${configPath}.tmp`;
164
+
165
+ try {
166
+ ensureConfigDirectory(configPath);
167
+ writeFileSync(tmpPath, `${JSON.stringify(normalized, null, 2)}\n`, "utf-8");
168
+ renameSync(tmpPath, configPath);
169
+ return { success: true };
170
+ } catch (error) {
171
+ try {
172
+ if (existsSync(tmpPath)) {
173
+ unlinkSync(tmpPath);
174
+ }
175
+ } catch {
176
+ // Ignore cleanup failures.
177
+ }
178
+
179
+ const message = error instanceof Error ? error.message : String(error);
180
+ return {
181
+ success: false,
182
+ error: `Failed to save permission-system config at '${configPath}': ${message}`,
183
+ };
184
+ }
185
+ }
186
+
187
+ export function getPermissionSystemConfigPath(
188
+ configPath = CONFIG_PATH,
189
+ ): string {
190
+ return configPath;
191
+ }
192
+
193
+ export function ensurePermissionSystemLogsDirectory(
194
+ logsDir = LOGS_DIR,
195
+ ): string | undefined {
196
+ try {
197
+ mkdirSync(logsDir, { recursive: true });
198
+ return undefined;
199
+ } catch (error) {
200
+ const message = error instanceof Error ? error.message : String(error);
201
+ return `Failed to create permission-system log directory '${logsDir}': ${message}`;
202
+ }
203
+ }