diff --git a/packages/opencode/src/permission/next.ts b/packages/opencode/src/permission/next.ts index f95aaf34525..2481f104ed1 100644 --- a/packages/opencode/src/permission/next.ts +++ b/packages/opencode/src/permission/next.ts @@ -7,11 +7,20 @@ import { Storage } from "@/storage/storage" import { fn } from "@/util/fn" import { Log } from "@/util/log" import { Wildcard } from "@/util/wildcard" +import os from "os" import z from "zod" export namespace PermissionNext { const log = Log.create({ service: "permission" }) + function expand(pattern: string): string { + if (pattern.startsWith("~/")) return os.homedir() + pattern.slice(1) + if (pattern === "~") return os.homedir() + if (pattern.startsWith("$HOME/")) return os.homedir() + pattern.slice(5) + if (pattern.startsWith("$HOME")) return os.homedir() + pattern.slice(5) + return pattern + } + export const Action = z.enum(["allow", "deny", "ask"]).meta({ ref: "PermissionAction", }) @@ -44,7 +53,9 @@ export namespace PermissionNext { }) continue } - ruleset.push(...Object.entries(value).map(([pattern, action]) => ({ permission: key, pattern, action }))) + ruleset.push( + ...Object.entries(value).map(([pattern, action]) => ({ permission: key, pattern: expand(pattern), action })), + ) } return ruleset } diff --git a/packages/opencode/test/permission/next.test.ts b/packages/opencode/test/permission/next.test.ts index 68dc653de6d..29f1efa4019 100644 --- a/packages/opencode/test/permission/next.test.ts +++ b/packages/opencode/test/permission/next.test.ts @@ -1,4 +1,5 @@ import { test, expect } from "bun:test" +import os from "os" import { PermissionNext } from "../../src/permission/next" import { Instance } from "../../src/project/instance" import { Storage } from "../../src/storage/storage" @@ -38,6 +39,43 @@ test("fromConfig - empty object", () => { expect(result).toEqual([]) }) +test("fromConfig - expands tilde to home directory", () => { + const result = PermissionNext.fromConfig({ external_directory: { "~/projects/*": "allow" } }) + expect(result).toEqual([{ permission: "external_directory", pattern: `${os.homedir()}/projects/*`, action: "allow" }]) +}) + +test("fromConfig - expands $HOME to home directory", () => { + const result = PermissionNext.fromConfig({ external_directory: { "$HOME/projects/*": "allow" } }) + expect(result).toEqual([{ permission: "external_directory", pattern: `${os.homedir()}/projects/*`, action: "allow" }]) +}) + +test("fromConfig - expands $HOME without trailing slash", () => { + const result = PermissionNext.fromConfig({ external_directory: { $HOME: "allow" } }) + expect(result).toEqual([{ permission: "external_directory", pattern: os.homedir(), action: "allow" }]) +}) + +test("fromConfig - does not expand tilde in middle of path", () => { + const result = PermissionNext.fromConfig({ external_directory: { "/some/~/path": "allow" } }) + expect(result).toEqual([{ permission: "external_directory", pattern: "/some/~/path", action: "allow" }]) +}) + +test("fromConfig - expands exact tilde to home directory", () => { + const result = PermissionNext.fromConfig({ external_directory: { "~": "allow" } }) + expect(result).toEqual([{ permission: "external_directory", pattern: os.homedir(), action: "allow" }]) +}) + +test("evaluate - matches expanded tilde pattern", () => { + const ruleset = PermissionNext.fromConfig({ external_directory: { "~/projects/*": "allow" } }) + const result = PermissionNext.evaluate("external_directory", `${os.homedir()}/projects/file.txt`, ruleset) + expect(result.action).toBe("allow") +}) + +test("evaluate - matches expanded $HOME pattern", () => { + const ruleset = PermissionNext.fromConfig({ external_directory: { "$HOME/projects/*": "allow" } }) + const result = PermissionNext.evaluate("external_directory", `${os.homedir()}/projects/file.txt`, ruleset) + expect(result.action).toBe("allow") +}) + // merge tests test("merge - simple concatenation", () => { diff --git a/packages/web/src/content/docs/permissions.mdx b/packages/web/src/content/docs/permissions.mdx index 4df3841e34a..229cb264e64 100644 --- a/packages/web/src/content/docs/permissions.mdx +++ b/packages/web/src/content/docs/permissions.mdx @@ -78,6 +78,14 @@ Permission patterns use simple wildcard matching: - `?` matches exactly one character - All other characters match literally +### Home Directory Expansion + +You can use `~` or `$HOME` at the start of a pattern to reference your home directory. This is particularly useful for `external_directory` rules. + +- `~/projects/*` -> `/Users/username/projects/*` +- `$HOME/projects/*` -> `/Users/username/projects/*` +- `~` -> `/Users/username` + --- ## Available Permissions