diff --git a/packages/opencode/src/cli/cmd/run.ts b/packages/opencode/src/cli/cmd/run.ts
index fad09c3a7add..f7787e970577 100644
--- a/packages/opencode/src/cli/cmd/run.ts
+++ b/packages/opencode/src/cli/cmd/run.ts
@@ -233,7 +233,14 @@ export const RunCommand = effectCmd({
hidden: true,
describe: "cap visible interactive replay to the newest N messages",
})
- .option("dangerously-skip-permissions", {
+ .option("interactive", {
+ alias: ["i"],
+ type: "boolean",
+ describe: "run in direct interactive split-footer mode",
+ default: false,
+ })
+ .option("auto", {
+ alias: ["yolo", "dangerously-skip-permissions"],
type: "boolean",
describe: "auto-approve permissions that are not explicitly denied (dangerous!)",
default: false,
@@ -780,7 +787,7 @@ export const RunCommand = effectCmd({
const permission = event.properties
if (permission.sessionID !== sessionID) continue
- if (args["dangerously-skip-permissions"]) {
+ if (args.auto) {
await client.permission.reply({
requestID: permission.id,
reply: "once",
@@ -981,9 +988,12 @@ export async function runMini(input: MiniCommandInput) {
variant: undefined,
thinking: undefined,
mini: true,
+ interactive: false,
replay: input.replay ?? true,
"replay-limit": input.replayLimit,
replayLimit: input.replayLimit,
+ auto: false,
+ yolo: false,
"dangerously-skip-permissions": false,
dangerouslySkipPermissions: false,
demo: input.demo ?? false,
diff --git a/packages/opencode/src/cli/cmd/tui.ts b/packages/opencode/src/cli/cmd/tui.ts
index a4443d2b03d9..7b92919d6185 100644
--- a/packages/opencode/src/cli/cmd/tui.ts
+++ b/packages/opencode/src/cli/cmd/tui.ts
@@ -105,6 +105,12 @@ export const TuiThreadCommand = cmd({
type: "string",
describe: "agent to use",
})
+ .option("auto", {
+ alias: ["yolo", "dangerously-skip-permissions"],
+ type: "boolean",
+ describe: "auto-approve permissions that are not explicitly denied (dangerous!)",
+ default: false,
+ })
.option("mini", {
type: "boolean",
describe: "start the minimal interactive interface",
@@ -272,6 +278,7 @@ export const TuiThreadCommand = cmd({
model: args.model,
prompt,
fork: args.fork,
+ auto: args.auto,
},
}),
)
diff --git a/packages/tui/src/app.tsx b/packages/tui/src/app.tsx
index fecf71b6c8c8..5788a947a11d 100644
--- a/packages/tui/src/app.tsx
+++ b/packages/tui/src/app.tsx
@@ -37,6 +37,7 @@ import { SyncProvider, useSync } from "./context/sync"
import { DataProvider } from "./context/data"
import { LocationProvider } from "./context/location"
import { LocalProvider, useLocal } from "./context/local"
+import { PermissionProvider } from "./context/permission"
import { DialogModel } from "./component/dialog-model"
import { useConnected } from "./component/use-connected"
import { DialogMcp } from "./component/dialog-mcp"
@@ -296,34 +297,36 @@ export const run = Effect.fn("Tui.run")(function* (input: TuiInput) {
headers={input.headers}
events={input.events}
>
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
@@ -926,6 +929,16 @@ function App(props: { onSnapshot?: () => Promise; pluginHost: TuiPlugi
dialog.clear()
},
},
+ {
+ name: "permission.mode",
+ title:
+ local.permission.mode === "auto" ? "Disable auto-approve permissions" : "Enable auto-approve permissions",
+ category: "System",
+ run: () => {
+ local.permission.toggle()
+ dialog.clear()
+ },
+ },
].map((command) => ({
namespace: "palette",
...command,
diff --git a/packages/tui/src/component/prompt/index.tsx b/packages/tui/src/component/prompt/index.tsx
index 0b87df9241b6..0fb04c5c108b 100644
--- a/packages/tui/src/component/prompt/index.tsx
+++ b/packages/tui/src/component/prompt/index.tsx
@@ -1442,6 +1442,9 @@ export function Prompt(props: PromptProps) {
{store.mode === "shell" ? "Shell" : Locale.titlecase(agent().name)}
+
+ auto
+
ยท
diff --git a/packages/tui/src/context/args.tsx b/packages/tui/src/context/args.tsx
index 8a229ffaba69..9a528f2f0ece 100644
--- a/packages/tui/src/context/args.tsx
+++ b/packages/tui/src/context/args.tsx
@@ -7,6 +7,7 @@ export interface Args {
continue?: boolean
sessionID?: string
fork?: boolean
+ auto?: boolean
}
export const { use: useArgs, provider: ArgsProvider } = createSimpleContext({
diff --git a/packages/tui/src/context/local.tsx b/packages/tui/src/context/local.tsx
index 39e7bc111d55..a05d315166ed 100644
--- a/packages/tui/src/context/local.tsx
+++ b/packages/tui/src/context/local.tsx
@@ -12,6 +12,7 @@ import { readJson, writeJsonAtomic } from "../util/persistence"
import { useTheme } from "./theme"
import { useToast } from "../ui/toast"
import { useRoute } from "./route"
+import { usePermission } from "./permission"
export type LocalTheme = {
secondary: RGBA
@@ -56,6 +57,9 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
const theme = useTheme().theme
const route = useRoute()
const paths = useTuiPaths()
+ const args = useArgs()
+ const event = useEvent()
+ const permission = usePermission()
function isModelValid(model: { providerID: string; modelID: string }) {
const provider = sync.data.provider.find((item) => item.id === model.providerID)
@@ -190,7 +194,6 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
if (state.pending) save()
})
- const args = useArgs()
const fallbackModel = createMemo(() => {
if (args.model) {
const { providerID, modelID } = parseModel(args.model)
@@ -446,8 +449,6 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
if (state.pending) save()
})
- const event = useEvent()
-
const slots = createMemo(() => {
const existing = new Set(sync.data.session.filter((x) => x.parentID === undefined).map((x) => x.id))
return sessionStore.pinned.filter((id) => existing.has(id)).slice(0, 9)
@@ -534,6 +535,7 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
agent,
mcp,
session,
+ permission,
}
return result
},
diff --git a/packages/tui/src/context/permission.tsx b/packages/tui/src/context/permission.tsx
new file mode 100644
index 000000000000..13a6ba822339
--- /dev/null
+++ b/packages/tui/src/context/permission.tsx
@@ -0,0 +1,26 @@
+import { createStore } from "solid-js/store"
+import { useArgs } from "./args"
+import { createSimpleContext } from "./helper"
+
+export type PermissionMode = "auto" | "normal"
+
+export const { use: usePermission, provider: PermissionProvider } = createSimpleContext({
+ name: "Permission",
+ init: () => {
+ const args = useArgs()
+ const [store, setStore] = createStore<{ mode: PermissionMode }>({
+ mode: args.auto ? "auto" : "normal",
+ })
+ return {
+ get mode() {
+ return store.mode
+ },
+ set(mode: PermissionMode) {
+ setStore("mode", mode)
+ },
+ toggle() {
+ setStore("mode", (mode) => (mode === "auto" ? "normal" : "auto"))
+ },
+ }
+ },
+})
diff --git a/packages/tui/src/context/sync.tsx b/packages/tui/src/context/sync.tsx
index 03db8784de49..d0511c5183e4 100644
--- a/packages/tui/src/context/sync.tsx
+++ b/packages/tui/src/context/sync.tsx
@@ -31,6 +31,7 @@ import { useArgs } from "./args"
import { batch, onMount } from "solid-js"
import path from "path"
import { useKV } from "./kv"
+import { usePermission } from "./permission"
const emptyConsoleState: ConsoleState = {
consoleManagedProviders: [],
@@ -59,6 +60,7 @@ export const {
init: () => {
const startup = useTuiStartup()
const kv = useKV()
+ const permission = usePermission()
const [store, setStore] = createStore<{
status: "loading" | "partial" | "complete"
provider: Provider[]
@@ -165,7 +167,7 @@ export const {
.then((x) => (x.data ?? []).toSorted((a, b) => a.id.localeCompare(b.id)))
}
- event.subscribe((event, { workspace }) => {
+ event.subscribe((event, { directory, workspace }) => {
switch (event.type) {
case "server.instance.disposed":
void bootstrap()
@@ -187,6 +189,15 @@ export const {
case "permission.asked": {
const request = event.properties
+ if (permission.mode === "auto") {
+ void sdk.client.permission.reply({
+ requestID: request.id,
+ reply: "once",
+ directory,
+ workspace,
+ })
+ break
+ }
const requests = store.permission[request.sessionID]
if (!requests) {
setStore("permission", request.sessionID, [request])
diff --git a/packages/tui/test/cli/cmd/tui/sync-fixture.tsx b/packages/tui/test/cli/cmd/tui/sync-fixture.tsx
index 5b7872ee3070..f81c4401a61a 100644
--- a/packages/tui/test/cli/cmd/tui/sync-fixture.tsx
+++ b/packages/tui/test/cli/cmd/tui/sync-fixture.tsx
@@ -6,6 +6,7 @@ import { KVProvider, useKV } from "../../../../src/context/kv"
import { ProjectProvider, useProject } from "../../../../src/context/project"
import { SDKProvider } from "../../../../src/context/sdk"
import { SyncProvider, useSync } from "../../../../src/context/sync"
+import { PermissionProvider } from "../../../../src/context/permission"
import { ExitProvider } from "../../../../src/context/exit"
import { createEventSource, createFetch, type FetchHandler, directory } from "../../../fixture/tui-sdk"
import { TestTuiContexts } from "../../../fixture/tui-environment"
@@ -48,13 +49,15 @@ export async function mount(override?: FetchHandler, state?: string) {
-
- {}}>
-
-
-
-
-
+
+
+ {}}>
+
+
+
+
+
+
diff --git a/packages/web/src/content/docs/cli.mdx b/packages/web/src/content/docs/cli.mdx
index 0f9e5e3b1f48..b44a27f4e1d2 100644
--- a/packages/web/src/content/docs/cli.mdx
+++ b/packages/web/src/content/docs/cli.mdx
@@ -37,6 +37,7 @@ opencode [project]
| {"--prompt"} | | Prompt to use |
| {"--model"} | `-m` | Model to use in the form of provider/model |
| {"--agent"} | | Agent to use |
+| {"--auto"} | | Auto-approve permissions that are not explicitly denied |
| {"--port"} | | Port to listen on |
| {"--hostname"} | | Hostname to listen on |
| {"--mdns"} | | Enable mDNS discovery |
@@ -380,7 +381,7 @@ opencode run --attach http://localhost:4096 "Explain async/await in JavaScript"
| {"--port"} | | Port for the local server (defaults to random port) |
| {"--variant"} | | Model variant (provider-specific reasoning effort) |
| {"--thinking"} | | Show thinking blocks |
-| {"--dangerously-skip-permissions"} | | Auto-approve permissions that are not explicitly denied (dangerous!) |
+| {"--auto"} | | Auto-approve permissions that are not explicitly denied |
---
diff --git a/packages/web/src/content/docs/permissions.mdx b/packages/web/src/content/docs/permissions.mdx
index 19182e27491b..ccc4034dc696 100644
--- a/packages/web/src/content/docs/permissions.mdx
+++ b/packages/web/src/content/docs/permissions.mdx
@@ -19,6 +19,26 @@ Each permission rule resolves to one of:
---
+## Auto mode
+
+Start OpenCode with `--auto` to automatically approve permission requests that are not explicitly denied.
+
+```bash
+opencode --auto
+```
+
+You can also use auto mode with [`opencode run`](/docs/cli#run).
+
+```bash
+opencode run --auto "Refactor this module"
+```
+
+Explicit `"deny"` rules are still enforced. Auto mode only changes requests that would otherwise ask for approval.
+
+In the TUI, open the command palette and select **Enable auto-approve permissions** or **Disable auto-approve permissions** to change modes. When auto mode is active, the prompt displays a muted `auto` indicator next to the current agent.
+
+---
+
## Configuration
You can set permissions globally (with `*`), and override specific tools.