Allow reading from skills directories in permission gate
Permission gate now auto-allows reading files from configured skills
directories (e.g. ~/.claude/skills) in addition to tracked files.
This enables seamless access to skill documentation and examples.
Assisted-by: Claude Sonnet 4.0 via Claude Code
diff --git a/.pi/agent/extensions/permission-gate.ts b/.pi/agent/extensions/permission-gate.ts
index 79e55cb4..05734a7b 100644
--- a/.pi/agent/extensions/permission-gate.ts
+++ b/.pi/agent/extensions/permission-gate.ts
@@ -3,16 +3,20 @@
*
* Requires explicit user confirmation before every tool call.
* Auto-allows read-only operations (read tool, non-destructive jj commands).
+ * Auto-allows reading from tracked files and skills directories.
* Shows tool name and a summary of the arguments, then asks to allow or block.
*/
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
import { execSync } from "node:child_process";
import * as path from "node:path";
+import * as fs from "node:fs";
+import * as os from "node:os";
const ALLOWED_JJ_SUBCOMMANDS = ["diff", "log", "show", "status"];
let trackedFiles: Set<string> | null = null;
+let skillsDirs: string[] | null = null;
function getTrackedFiles(cwd: string): Set<string> {
if (trackedFiles) return trackedFiles;
@@ -25,10 +29,33 @@ function getTrackedFiles(cwd: string): Set<string> {
return trackedFiles;
}
+function getSkillsDirectories(cwd: string): string[] {
+ if (skillsDirs) return skillsDirs;
+ try {
+ const settingsPath = path.join(cwd, ".pi", "agent", "settings.json");
+ const settings = JSON.parse(fs.readFileSync(settingsPath, "utf-8"));
+ skillsDirs = (settings.skills || []).map((skillPath: string) => {
+ // Handle ~ expansion
+ if (skillPath.startsWith("~/")) {
+ return path.resolve(os.homedir(), skillPath.slice(2));
+ }
+ return path.resolve(cwd, skillPath);
+ });
+ } catch {
+ skillsDirs = [];
+ }
+ return skillsDirs;
+}
+
+function isInSkillsDirectory(filePath: string, cwd: string): boolean {
+ const skillsDirs = getSkillsDirectories(cwd);
+ return skillsDirs.some(skillsDir => filePath.startsWith(skillsDir + path.sep));
+}
+
function isAllowed(toolName: string, input: Record<string, unknown>, cwd: string): boolean {
if (toolName === "read") {
const filePath = path.resolve(cwd, String(input.path ?? ""));
- return getTrackedFiles(cwd).has(filePath);
+ return getTrackedFiles(cwd).has(filePath) || isInSkillsDirectory(filePath, cwd);
}
if (toolName === "bash") {
@@ -50,6 +77,12 @@ export default function (pi: ExtensionAPI) {
trackedFiles = null;
}
}
+ if (event.toolName === "write" || event.toolName === "edit") {
+ const filePath = String(event.input.path ?? "");
+ if (filePath.includes("settings.json")) {
+ skillsDirs = null;
+ }
+ }
});
pi.on("tool_call", async (event, ctx) => {
diff --git a/.pi/agent/settings.json b/.pi/agent/settings.json
index 234ce17a..36e43037 100644
--- a/.pi/agent/settings.json
+++ b/.pi/agent/settings.json
@@ -1,7 +1,7 @@
{
"lastChangelogVersion": "0.52.6",
"defaultProvider": "anthropic",
- "defaultModel": "claude-sonnet-4-0",
+ "defaultModel": "claude-opus-4-6",
"skills": [
"~/.claude/skills"
],