diff --git a/packages/opencode/src/agent/agent.ts b/packages/opencode/src/agent/agent.ts
index e30d05e935..43e7a2de34 100644
--- a/packages/opencode/src/agent/agent.ts
+++ b/packages/opencode/src/agent/agent.ts
@@ -64,6 +64,7 @@ export namespace Agent {
question: "deny",
plan_enter: "deny",
plan_exit: "deny",
+ edit: "ask",
// mirrors github.com/github/gitignore Node.gitignore pattern for .env files
read: {
"*": "allow",
diff --git a/packages/opencode/src/cli/cmd/run.ts b/packages/opencode/src/cli/cmd/run.ts
index 85b5689daa..ee75bcb933 100644
--- a/packages/opencode/src/cli/cmd/run.ts
+++ b/packages/opencode/src/cli/cmd/run.ts
@@ -370,6 +370,11 @@ export const RunCommand = cmd({
action: "deny",
pattern: "*",
},
+ {
+ permission: "edit",
+ action: "allow",
+ pattern: "*",
+ },
]
function title() {
diff --git a/packages/opencode/src/cli/cmd/tui/app.tsx b/packages/opencode/src/cli/cmd/tui/app.tsx
index 8bb17ff133..4b997418bf 100644
--- a/packages/opencode/src/cli/cmd/tui/app.tsx
+++ b/packages/opencode/src/cli/cmd/tui/app.tsx
@@ -480,6 +480,7 @@ function App() {
{
title: "Toggle MCPs",
value: "mcp.list",
+ search: "toggle mcps",
category: "Agent",
slash: {
name: "mcps",
@@ -555,8 +556,9 @@ function App() {
category: "System",
},
{
- title: "Toggle appearance",
+ title: mode() === "dark" ? "Light mode" : "Dark mode",
value: "theme.switch_mode",
+ search: "toggle appearance",
onSelect: (dialog) => {
setMode(mode() === "dark" ? "light" : "dark")
dialog.clear()
@@ -595,6 +597,7 @@ function App() {
},
{
title: "Toggle debug panel",
+ search: "toggle debug",
category: "System",
value: "app.debug",
onSelect: (dialog) => {
@@ -604,6 +607,7 @@ function App() {
},
{
title: "Toggle console",
+ search: "toggle console",
category: "System",
value: "app.console",
onSelect: (dialog) => {
@@ -644,6 +648,7 @@ function App() {
{
title: terminalTitleEnabled() ? "Disable terminal title" : "Enable terminal title",
value: "terminal.title.toggle",
+ search: "toggle terminal title",
keybind: "terminal_title_toggle",
category: "System",
onSelect: (dialog) => {
@@ -659,6 +664,7 @@ function App() {
{
title: kv.get("animations_enabled", true) ? "Disable animations" : "Enable animations",
value: "app.toggle.animations",
+ search: "toggle animations",
category: "System",
onSelect: (dialog) => {
kv.set("animations_enabled", !kv.get("animations_enabled", true))
@@ -668,6 +674,7 @@ function App() {
{
title: kv.get("diff_wrap_mode", "word") === "word" ? "Disable diff wrapping" : "Enable diff wrapping",
value: "app.toggle.diffwrap",
+ search: "toggle diff wrapping",
category: "System",
onSelect: (dialog) => {
const current = kv.get("diff_wrap_mode", "word")
diff --git a/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx b/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx
index 249a48d358..2142df063f 100644
--- a/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx
+++ b/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx
@@ -79,6 +79,7 @@ export function Prompt(props: PromptProps) {
const renderer = useRenderer()
const { theme, syntax } = useTheme()
const kv = useKV()
+ const [autoaccept, setAutoaccept] = kv.signal<"none" | "edit">("permission_auto_accept", "edit")
function promptModelWarning() {
toast.show({
@@ -172,6 +173,17 @@ export function Prompt(props: PromptProps) {
command.register(() => {
return [
+ {
+ title: autoaccept() === "none" ? "Enable autoedit" : "Disable autoedit",
+ value: "permission.auto_accept.toggle",
+ search: "toggle permissions",
+ keybind: "permission_auto_accept_toggle",
+ category: "Agent",
+ onSelect: (dialog) => {
+ setAutoaccept(() => (autoaccept() === "none" ? "edit" : "none"))
+ dialog.clear()
+ },
+ },
{
title: "Clear prompt",
value: "prompt.clear",
@@ -1010,23 +1022,30 @@ export function Prompt(props: PromptProps) {
cursorColor={theme.text}
syntaxStyle={syntax()}
/>
-
-
- {store.mode === "shell" ? "Shell" : Locale.titlecase(local.agent.current().name)}{" "}
-
-
-
-
- {local.model.parsed().model}
-
- {local.model.parsed().provider}
-
- ·
-
- {local.model.variant.current()}
+
+
+
+ {store.mode === "shell" ? "Shell" : Locale.titlecase(local.agent.current().name)}{" "}
+
+
+
+
+ {local.model.parsed().model}
-
-
+ {local.model.parsed().provider}
+
+ ·
+
+ {local.model.variant.current()}
+
+
+
+
+
+
+
+ autoedit
+
diff --git a/packages/opencode/src/cli/cmd/tui/context/sync.tsx b/packages/opencode/src/cli/cmd/tui/context/sync.tsx
index 3b296a927a..b629d3f8a0 100644
--- a/packages/opencode/src/cli/cmd/tui/context/sync.tsx
+++ b/packages/opencode/src/cli/cmd/tui/context/sync.tsx
@@ -25,6 +25,7 @@ import { createSimpleContext } from "./helper"
import type { Snapshot } from "@/snapshot"
import { useExit } from "./exit"
import { useArgs } from "./args"
+import { useKV } from "./kv"
import { batch, onMount } from "solid-js"
import { Log } from "@/util/log"
import type { Path } from "@opencode-ai/sdk"
@@ -106,6 +107,8 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({
})
const sdk = useSDK()
+ const kv = useKV()
+ const [autoaccept] = kv.signal<"none" | "edit">("permission_auto_accept", "edit")
async function syncWorkspaces() {
const result = await sdk.client.experimental.workspace.list().catch(() => undefined)
@@ -136,6 +139,13 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({
case "permission.asked": {
const request = event.properties
+ if (autoaccept() === "edit" && request.permission === "edit") {
+ sdk.client.permission.reply({
+ reply: "once",
+ requestID: request.id,
+ })
+ break
+ }
const requests = store.permission[request.sessionID]
if (!requests) {
setStore("permission", request.sessionID, [request])
@@ -451,6 +461,7 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({
get ready() {
return store.status !== "loading"
},
+
session: {
get(sessionID: string) {
const match = Binary.search(store.session, sessionID, (s) => s.id)
diff --git a/packages/opencode/src/cli/cmd/tui/routes/home.tsx b/packages/opencode/src/cli/cmd/tui/routes/home.tsx
index e76e165b26..39b9b3326f 100644
--- a/packages/opencode/src/cli/cmd/tui/routes/home.tsx
+++ b/packages/opencode/src/cli/cmd/tui/routes/home.tsx
@@ -47,6 +47,7 @@ export function Home() {
{
title: tipsHidden() ? "Show tips" : "Hide tips",
value: "tips.toggle",
+ search: "toggle tips",
keybind: "tips_toggle",
category: "System",
onSelect: (dialog) => {
diff --git a/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx b/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx
index 7b54ec2875..f8efba06e5 100644
--- a/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx
+++ b/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx
@@ -568,6 +568,7 @@ export function Session() {
{
title: sidebarVisible() ? "Hide sidebar" : "Show sidebar",
value: "session.sidebar.toggle",
+ search: "toggle sidebar",
keybind: "sidebar_toggle",
category: "Session",
onSelect: (dialog) => {
@@ -582,6 +583,7 @@ export function Session() {
{
title: conceal() ? "Disable code concealment" : "Enable code concealment",
value: "session.toggle.conceal",
+ search: "toggle code concealment",
keybind: "messages_toggle_conceal" as any,
category: "Session",
onSelect: (dialog) => {
@@ -592,6 +594,7 @@ export function Session() {
{
title: showTimestamps() ? "Hide timestamps" : "Show timestamps",
value: "session.toggle.timestamps",
+ search: "toggle timestamps",
category: "Session",
slash: {
name: "timestamps",
@@ -605,6 +608,7 @@ export function Session() {
{
title: showThinking() ? "Hide thinking" : "Show thinking",
value: "session.toggle.thinking",
+ search: "toggle thinking",
keybind: "display_thinking",
category: "Session",
slash: {
@@ -619,6 +623,7 @@ export function Session() {
{
title: showDetails() ? "Hide tool details" : "Show tool details",
value: "session.toggle.actions",
+ search: "toggle tool details",
keybind: "tool_details",
category: "Session",
onSelect: (dialog) => {
@@ -627,8 +632,9 @@ export function Session() {
},
},
{
- title: "Toggle session scrollbar",
+ title: showScrollbar() ? "Hide session scrollbar" : "Show session scrollbar",
value: "session.toggle.scrollbar",
+ search: "toggle session scrollbar",
keybind: "scrollbar_toggle",
category: "Session",
onSelect: (dialog) => {
diff --git a/packages/opencode/src/cli/cmd/tui/ui/dialog-select.tsx b/packages/opencode/src/cli/cmd/tui/ui/dialog-select.tsx
index 151f73cf7c..e3152bbd70 100644
--- a/packages/opencode/src/cli/cmd/tui/ui/dialog-select.tsx
+++ b/packages/opencode/src/cli/cmd/tui/ui/dialog-select.tsx
@@ -34,6 +34,7 @@ export interface DialogSelectOption {
title: string
value: T
description?: string
+ search?: string
footer?: JSX.Element | string
category?: string
disabled?: boolean
@@ -85,8 +86,8 @@ export function DialogSelect(props: DialogSelectProps) {
// users typically search by the item name, and not its category.
const result = fuzzysort
.go(needle, options, {
- keys: ["title", "category"],
- scoreFn: (r) => r[0].score * 2 + r[1].score,
+ keys: ["title", "category", "search"],
+ scoreFn: (r) => r[0].score * 2 + r[1].score + r[2].score,
})
.map((x) => x.obj)
diff --git a/packages/opencode/src/config/config.ts b/packages/opencode/src/config/config.ts
index 47afdfd7d0..abb772ab22 100644
--- a/packages/opencode/src/config/config.ts
+++ b/packages/opencode/src/config/config.ts
@@ -858,7 +858,12 @@ export namespace Config {
command_list: z.string().optional().default("ctrl+p").describe("List available commands"),
agent_list: z.string().optional().default("a").describe("List agents"),
agent_cycle: z.string().optional().default("tab").describe("Next agent"),
- agent_cycle_reverse: z.string().optional().default("shift+tab").describe("Previous agent"),
+ agent_cycle_reverse: z.string().optional().default("none").describe("Previous agent"),
+ permission_auto_accept_toggle: z
+ .string()
+ .optional()
+ .default("shift+tab")
+ .describe("Toggle auto-accept mode for permissions"),
variant_cycle: z.string().optional().default("ctrl+t").describe("Cycle model variants"),
input_clear: z.string().optional().default("ctrl+c").describe("Clear input field"),
input_paste: z.string().optional().default("ctrl+v").describe("Paste from clipboard"),
diff --git a/packages/opencode/test/agent/agent.test.ts b/packages/opencode/test/agent/agent.test.ts
index 60c8e57c92..c298e0d9eb 100644
--- a/packages/opencode/test/agent/agent.test.ts
+++ b/packages/opencode/test/agent/agent.test.ts
@@ -38,7 +38,7 @@ test("build agent has correct default properties", async () => {
expect(build).toBeDefined()
expect(build?.mode).toBe("primary")
expect(build?.native).toBe(true)
- expect(evalPerm(build, "edit")).toBe("allow")
+ expect(evalPerm(build, "edit")).toBe("ask")
expect(evalPerm(build, "bash")).toBe("allow")
},
})
@@ -217,8 +217,8 @@ test("agent permission config merges with defaults", async () => {
expect(build).toBeDefined()
// Specific pattern is denied
expect(PermissionNext.evaluate("bash", "rm -rf *", build!.permission).action).toBe("deny")
- // Edit still allowed
- expect(evalPerm(build, "edit")).toBe("allow")
+ // Edit still asks (default behavior)
+ expect(evalPerm(build, "edit")).toBe("ask")
},
})
})
diff --git a/packages/sdk/js/src/v2/gen/types.gen.ts b/packages/sdk/js/src/v2/gen/types.gen.ts
index ec797f2ba8..20ecf168aa 100644
--- a/packages/sdk/js/src/v2/gen/types.gen.ts
+++ b/packages/sdk/js/src/v2/gen/types.gen.ts
@@ -1009,6 +1009,392 @@ export type GlobalEvent = {
payload: Event
}
+/**
+ * Custom keybind configurations
+ */
+export type KeybindsConfig = {
+ /**
+ * Leader key for keybind combinations
+ */
+ leader?: string
+ /**
+ * Exit the application
+ */
+ app_exit?: string
+ /**
+ * Open external editor
+ */
+ editor_open?: string
+ /**
+ * List available themes
+ */
+ theme_list?: string
+ /**
+ * Toggle sidebar
+ */
+ sidebar_toggle?: string
+ /**
+ * Toggle session scrollbar
+ */
+ scrollbar_toggle?: string
+ /**
+ * Toggle username visibility
+ */
+ username_toggle?: string
+ /**
+ * View status
+ */
+ status_view?: string
+ /**
+ * Export session to editor
+ */
+ session_export?: string
+ /**
+ * Create a new session
+ */
+ session_new?: string
+ /**
+ * List all sessions
+ */
+ session_list?: string
+ /**
+ * Show session timeline
+ */
+ session_timeline?: string
+ /**
+ * Fork session from message
+ */
+ session_fork?: string
+ /**
+ * Rename session
+ */
+ session_rename?: string
+ /**
+ * Delete session
+ */
+ session_delete?: string
+ /**
+ * Delete stash entry
+ */
+ stash_delete?: string
+ /**
+ * Open provider list from model dialog
+ */
+ model_provider_list?: string
+ /**
+ * Toggle model favorite status
+ */
+ model_favorite_toggle?: string
+ /**
+ * Share current session
+ */
+ session_share?: string
+ /**
+ * Unshare current session
+ */
+ session_unshare?: string
+ /**
+ * Interrupt current session
+ */
+ session_interrupt?: string
+ /**
+ * Compact the session
+ */
+ session_compact?: string
+ /**
+ * Scroll messages up by one page
+ */
+ messages_page_up?: string
+ /**
+ * Scroll messages down by one page
+ */
+ messages_page_down?: string
+ /**
+ * Scroll messages up by one line
+ */
+ messages_line_up?: string
+ /**
+ * Scroll messages down by one line
+ */
+ messages_line_down?: string
+ /**
+ * Scroll messages up by half page
+ */
+ messages_half_page_up?: string
+ /**
+ * Scroll messages down by half page
+ */
+ messages_half_page_down?: string
+ /**
+ * Navigate to first message
+ */
+ messages_first?: string
+ /**
+ * Navigate to last message
+ */
+ messages_last?: string
+ /**
+ * Navigate to next message
+ */
+ messages_next?: string
+ /**
+ * Navigate to previous message
+ */
+ messages_previous?: string
+ /**
+ * Navigate to last user message
+ */
+ messages_last_user?: string
+ /**
+ * Copy message
+ */
+ messages_copy?: string
+ /**
+ * Undo message
+ */
+ messages_undo?: string
+ /**
+ * Redo message
+ */
+ messages_redo?: string
+ /**
+ * Toggle code block concealment in messages
+ */
+ messages_toggle_conceal?: string
+ /**
+ * Toggle tool details visibility
+ */
+ tool_details?: string
+ /**
+ * List available models
+ */
+ model_list?: string
+ /**
+ * Next recently used model
+ */
+ model_cycle_recent?: string
+ /**
+ * Previous recently used model
+ */
+ model_cycle_recent_reverse?: string
+ /**
+ * Next favorite model
+ */
+ model_cycle_favorite?: string
+ /**
+ * Previous favorite model
+ */
+ model_cycle_favorite_reverse?: string
+ /**
+ * List available commands
+ */
+ command_list?: string
+ /**
+ * List agents
+ */
+ agent_list?: string
+ /**
+ * Next agent
+ */
+ agent_cycle?: string
+ /**
+ * Previous agent
+ */
+ agent_cycle_reverse?: string
+ /**
+ * Toggle auto-accept mode for permissions
+ */
+ permission_auto_accept_toggle?: string
+ /**
+ * Cycle model variants
+ */
+ variant_cycle?: string
+ /**
+ * Clear input field
+ */
+ input_clear?: string
+ /**
+ * Paste from clipboard
+ */
+ input_paste?: string
+ /**
+ * Submit input
+ */
+ input_submit?: string
+ /**
+ * Insert newline in input
+ */
+ input_newline?: string
+ /**
+ * Move cursor left in input
+ */
+ input_move_left?: string
+ /**
+ * Move cursor right in input
+ */
+ input_move_right?: string
+ /**
+ * Move cursor up in input
+ */
+ input_move_up?: string
+ /**
+ * Move cursor down in input
+ */
+ input_move_down?: string
+ /**
+ * Select left in input
+ */
+ input_select_left?: string
+ /**
+ * Select right in input
+ */
+ input_select_right?: string
+ /**
+ * Select up in input
+ */
+ input_select_up?: string
+ /**
+ * Select down in input
+ */
+ input_select_down?: string
+ /**
+ * Move to start of line in input
+ */
+ input_line_home?: string
+ /**
+ * Move to end of line in input
+ */
+ input_line_end?: string
+ /**
+ * Select to start of line in input
+ */
+ input_select_line_home?: string
+ /**
+ * Select to end of line in input
+ */
+ input_select_line_end?: string
+ /**
+ * Move to start of visual line in input
+ */
+ input_visual_line_home?: string
+ /**
+ * Move to end of visual line in input
+ */
+ input_visual_line_end?: string
+ /**
+ * Select to start of visual line in input
+ */
+ input_select_visual_line_home?: string
+ /**
+ * Select to end of visual line in input
+ */
+ input_select_visual_line_end?: string
+ /**
+ * Move to start of buffer in input
+ */
+ input_buffer_home?: string
+ /**
+ * Move to end of buffer in input
+ */
+ input_buffer_end?: string
+ /**
+ * Select to start of buffer in input
+ */
+ input_select_buffer_home?: string
+ /**
+ * Select to end of buffer in input
+ */
+ input_select_buffer_end?: string
+ /**
+ * Delete line in input
+ */
+ input_delete_line?: string
+ /**
+ * Delete to end of line in input
+ */
+ input_delete_to_line_end?: string
+ /**
+ * Delete to start of line in input
+ */
+ input_delete_to_line_start?: string
+ /**
+ * Backspace in input
+ */
+ input_backspace?: string
+ /**
+ * Delete character in input
+ */
+ input_delete?: string
+ /**
+ * Undo in input
+ */
+ input_undo?: string
+ /**
+ * Redo in input
+ */
+ input_redo?: string
+ /**
+ * Move word forward in input
+ */
+ input_word_forward?: string
+ /**
+ * Move word backward in input
+ */
+ input_word_backward?: string
+ /**
+ * Select word forward in input
+ */
+ input_select_word_forward?: string
+ /**
+ * Select word backward in input
+ */
+ input_select_word_backward?: string
+ /**
+ * Delete word forward in input
+ */
+ input_delete_word_forward?: string
+ /**
+ * Delete word backward in input
+ */
+ input_delete_word_backward?: string
+ /**
+ * Previous history item
+ */
+ history_previous?: string
+ /**
+ * Next history item
+ */
+ history_next?: string
+ /**
+ * Next child session
+ */
+ session_child_cycle?: string
+ /**
+ * Previous child session
+ */
+ session_child_cycle_reverse?: string
+ /**
+ * Go to parent session
+ */
+ session_parent?: string
+ /**
+ * Suspend terminal
+ */
+ terminal_suspend?: string
+ /**
+ * Toggle terminal title
+ */
+ terminal_title_toggle?: string
+ /**
+ * Toggle tips on home screen
+ */
+ tips_toggle?: string
+ /**
+ * Toggle thinking blocks visibility
+ */
+ display_thinking?: string
+}
+
/**
* Log level
*/