fix: fix permissions paths (#92)

This commit is contained in:
Charles Packer
2025-11-16 21:12:01 -08:00
committed by GitHub
parent 754db3b273
commit cedbff11a2
4 changed files with 41 additions and 26 deletions

1
.gitignore vendored
View File

@@ -1,6 +1,5 @@
# Letta Code local settings # Letta Code local settings
.letta/settings.local.json .letta/settings.local.json
.letta
# User-defined skills # User-defined skills
.skills .skills

View File

@@ -89,7 +89,7 @@ function analyzeReadApproval(
const displayPath = dirPath.replace(require("node:os").homedir(), "~"); const displayPath = dirPath.replace(require("node:os").homedir(), "~");
return { return {
recommendedRule: `Read(${dirPath}/**)`, recommendedRule: `Read(/${dirPath}/**)`,
ruleDescription: `reading from ${displayPath}/`, ruleDescription: `reading from ${displayPath}/`,
approveAlwaysText: `Yes, allow reading from ${displayPath}/ in this project`, approveAlwaysText: `Yes, allow reading from ${displayPath}/ in this project`,
defaultScope: "project", defaultScope: "project",
@@ -98,9 +98,13 @@ function analyzeReadApproval(
}; };
} }
// Inside working directory - shouldn't normally be asked, but offer session approval // Inside working directory - use relative path
const relativePath = absolutePath.slice(workingDir.length + 1);
const relativeDir = dirname(relativePath);
const pattern = relativeDir === "." ? "**" : `${relativeDir}/**`;
return { return {
recommendedRule: `Read(${workingDir}/**)`, recommendedRule: `Read(${pattern})`,
ruleDescription: "reading project files", ruleDescription: "reading project files",
approveAlwaysText: "Yes, allow reading project files during this session", approveAlwaysText: "Yes, allow reading project files during this session",
defaultScope: "session", defaultScope: "session",
@@ -139,12 +143,26 @@ function analyzeEditApproval(
// Can offer project-level for specific directories // Can offer project-level for specific directories
const absolutePath = resolve(workingDir, filePath); const absolutePath = resolve(workingDir, filePath);
const dirPath = dirname(absolutePath); const dirPath = dirname(absolutePath);
const relativeDirPath = dirPath.startsWith(workingDir)
? dirPath.slice(workingDir.length + 1) // If outside working directory, use absolute path with // prefix
: dirPath; if (!dirPath.startsWith(workingDir)) {
const displayPath = dirPath.replace(require("node:os").homedir(), "~");
return {
recommendedRule: `Edit(/${dirPath}/**)`,
ruleDescription: `editing files in ${displayPath}/`,
approveAlwaysText: `Yes, allow editing files in ${displayPath}/ in this project`,
defaultScope: "project",
allowPersistence: true,
safetyLevel: "safe",
};
}
// Inside working directory, use relative path
const relativeDirPath = dirPath.slice(workingDir.length + 1);
const pattern = relativeDirPath === "" ? "**" : `${relativeDirPath}/**`;
return { return {
recommendedRule: `Edit(${relativeDirPath}/**)`, recommendedRule: `Edit(${pattern})`,
ruleDescription: `editing files in ${relativeDirPath || "project"}/`, ruleDescription: `editing files in ${relativeDirPath || "project"}/`,
approveAlwaysText: `Yes, allow editing files in ${relativeDirPath || "project"}/ in this project`, approveAlwaysText: `Yes, allow editing files in ${relativeDirPath || "project"}/ in this project`,
defaultScope: "project", defaultScope: "project",
@@ -307,7 +325,7 @@ function analyzeBashApproval(
} }
// Handle complex piped/chained commands (cd /path && git diff | head) // Handle complex piped/chained commands (cd /path && git diff | head)
// Extract the most significant command and generate a wildcard pattern // Strip out cd commands and extract the actual command
if ( if (
command.includes("&&") || command.includes("&&") ||
command.includes("|") || command.includes("|") ||
@@ -321,6 +339,11 @@ function analyzeBashApproval(
const segmentBase = segmentParts[0] || ""; const segmentBase = segmentParts[0] || "";
const segmentArg = segmentParts[1] || ""; const segmentArg = segmentParts[1] || "";
// Skip cd commands - we want the actual command
if (segmentBase === "cd") {
continue;
}
// Check if this segment is git command // Check if this segment is git command
if (segmentBase === "git") { if (segmentBase === "git") {
const gitSubcommand = segmentArg; const gitSubcommand = segmentArg;
@@ -331,15 +354,10 @@ function analyzeBashApproval(
safeGitCommands.includes(gitSubcommand) || safeGitCommands.includes(gitSubcommand) ||
writeGitCommands.includes(gitSubcommand) writeGitCommands.includes(gitSubcommand)
) { ) {
// Generate wildcard pattern that includes the leading commands
// e.g., "cd /path && git diff:*"
const beforeGit = command.substring(0, command.indexOf("git"));
const pattern = `${beforeGit}git ${gitSubcommand}:*`;
return { return {
recommendedRule: `Bash(${pattern})`, recommendedRule: `Bash(git ${gitSubcommand}:*)`,
ruleDescription: `'${beforeGit}git ${gitSubcommand}' commands`, ruleDescription: `'git ${gitSubcommand}' commands`,
approveAlwaysText: `Yes, and don't ask again for '${beforeGit}git ${gitSubcommand}' commands in this project`, approveAlwaysText: `Yes, and don't ask again for 'git ${gitSubcommand}' commands in this project`,
defaultScope: "project", defaultScope: "project",
allowPersistence: true, allowPersistence: true,
safetyLevel: safeGitCommands.includes(gitSubcommand) safetyLevel: safeGitCommands.includes(gitSubcommand)
@@ -438,7 +456,7 @@ function analyzeSearchApproval(
const displayPath = absolutePath.replace(require("node:os").homedir(), "~"); const displayPath = absolutePath.replace(require("node:os").homedir(), "~");
return { return {
recommendedRule: `${toolName}(${absolutePath}/**)`, recommendedRule: `${toolName}(/${absolutePath}/**)`,
ruleDescription: `searching in ${displayPath}/`, ruleDescription: `searching in ${displayPath}/`,
approveAlwaysText: `Yes, allow searching in ${displayPath}/ in this project`, approveAlwaysText: `Yes, allow searching in ${displayPath}/ in this project`,
defaultScope: "project", defaultScope: "project",
@@ -448,7 +466,7 @@ function analyzeSearchApproval(
} }
return { return {
recommendedRule: `${toolName}(${workingDir}/**)`, recommendedRule: `${toolName}(**)`,
ruleDescription: "searching project files", ruleDescription: "searching project files",
approveAlwaysText: "Yes, allow searching project files during this session", approveAlwaysText: "Yes, allow searching project files during this session",
defaultScope: "session", defaultScope: "session",

View File

@@ -216,7 +216,7 @@ test("Read outside working directory suggests directory pattern", () => {
"/Users/test/project", "/Users/test/project",
); );
expect(context.recommendedRule).toBe("Read(/Users/test/docs/**)"); expect(context.recommendedRule).toBe("Read(//Users/test/docs/**)");
expect(context.approveAlwaysText).toContain("/Users/test/docs/"); expect(context.approveAlwaysText).toContain("/Users/test/docs/");
expect(context.defaultScope).toBe("project"); expect(context.defaultScope).toBe("project");
expect(context.safetyLevel).toBe("safe"); expect(context.safetyLevel).toBe("safe");
@@ -283,7 +283,7 @@ test("Glob outside working directory suggests directory pattern", () => {
"/Users/test/project", "/Users/test/project",
); );
expect(context.recommendedRule).toContain("Glob(/Users/test/docs/**)"); expect(context.recommendedRule).toContain("Glob(//Users/test/docs/**)");
expect(context.approveAlwaysText).toContain("/Users/test/docs/"); expect(context.approveAlwaysText).toContain("/Users/test/docs/");
}); });
@@ -296,7 +296,7 @@ test("Grep outside working directory suggests directory pattern", () => {
"/Users/test/project", "/Users/test/project",
); );
expect(context.recommendedRule).toContain("Grep(/Users/test/docs/**)"); expect(context.recommendedRule).toContain("Grep(//Users/test/docs/**)");
expect(context.approveAlwaysText).toContain("/Users/test/docs/"); expect(context.approveAlwaysText).toContain("/Users/test/docs/");
}); });
@@ -373,9 +373,7 @@ test("Long complex bash commands should generate smart wildcard patterns", () =>
); );
// Should extract "git diff" pattern, not save full command // Should extract "git diff" pattern, not save full command
expect(context.recommendedRule).toBe( expect(context.recommendedRule).toBe("Bash(git diff:*)");
"Bash(cd /Users/test/project && git diff:*)",
);
// Button text should reflect the wildcard pattern // Button text should reflect the wildcard pattern
expect(context.approveAlwaysText).not.toContain("..."); expect(context.approveAlwaysText).not.toContain("...");
}); });

View File

@@ -109,7 +109,7 @@ test("Read outside working directory suggests directory pattern", () => {
"/Users/test/project", "/Users/test/project",
); );
expect(context.recommendedRule).toBe("Read(/Users/test/docs/**)"); expect(context.recommendedRule).toBe("Read(//Users/test/docs/**)");
expect(context.approveAlwaysText).toContain("/Users/test/docs/"); expect(context.approveAlwaysText).toContain("/Users/test/docs/");
expect(context.defaultScope).toBe("project"); expect(context.defaultScope).toBe("project");
}); });