summaryrefslogtreecommitdiffhomepage
path: root/packages
diff options
context:
space:
mode:
authorDaniel Olowoniyi <[email protected]>2026-01-21 19:52:21 +0100
committerGitHub <[email protected]>2026-01-21 12:52:21 -0600
commit2a370f8038224859713010857a90037aa8298340 (patch)
treebb1b7cf8302ba8d936a7daf35f176823e7e3860d /packages
parentd9f0287d74a2eed89c5cb4f1919b5944959b5317 (diff)
downloadopencode-2a370f8038224859713010857a90037aa8298340.tar.gz
opencode-2a370f8038224859713010857a90037aa8298340.zip
feat: implement home directory expansion for permission patterns using `~` and `$HOME` prefixes. (#9813)
Diffstat (limited to 'packages')
-rw-r--r--packages/opencode/src/permission/next.ts13
-rw-r--r--packages/opencode/test/permission/next.test.ts38
-rw-r--r--packages/web/src/content/docs/permissions.mdx8
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