diff options
| author | Daniel Olowoniyi <[email protected]> | 2026-01-21 19:52:21 +0100 |
|---|---|---|
| committer | GitHub <[email protected]> | 2026-01-21 12:52:21 -0600 |
| commit | 2a370f8038224859713010857a90037aa8298340 (patch) | |
| tree | bb1b7cf8302ba8d936a7daf35f176823e7e3860d | |
| parent | d9f0287d74a2eed89c5cb4f1919b5944959b5317 (diff) | |
| download | opencode-2a370f8038224859713010857a90037aa8298340.tar.gz opencode-2a370f8038224859713010857a90037aa8298340.zip | |
feat: implement home directory expansion for permission patterns using `~` and `$HOME` prefixes. (#9813)
| -rw-r--r-- | packages/opencode/src/permission/next.ts | 13 | ||||
| -rw-r--r-- | packages/opencode/test/permission/next.test.ts | 38 | ||||
| -rw-r--r-- | packages/web/src/content/docs/permissions.mdx | 8 |
3 files changed, 58 insertions, 1 deletions
diff --git a/packages/opencode/src/permission/next.ts b/packages/opencode/src/permission/next.ts index f95aaf345..2481f104e 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 68dc653de..29f1efa40 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 4df3841e3..229cb264e 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 |
