Skip to content

Commit 4956ee3

Browse files
committed
tui: add escape key handling to permission dialogs for better keyboard navigation
1 parent 1261b7d commit 4956ee3

File tree

3 files changed

+14
-32
lines changed

3 files changed

+14
-32
lines changed

packages/opencode/src/cli/cmd/tui/routes/session/permission.tsx

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { createStore } from "solid-js/store"
22
import { createMemo, For, Match, Show, Switch } from "solid-js"
33
import { useKeyboard, useTerminalDimensions, type JSX } from "@opentui/solid"
4+
import { useKeybind } from "../../context/keybind"
45
import { useTheme } from "../../context/theme"
56
import type { PermissionRequest } from "@opencode-ai/sdk/v2"
67
import { useSDK } from "../../context/sdk"
@@ -145,6 +146,7 @@ export function PermissionPrompt(props: { request: PermissionRequest }) {
145146
</Switch>
146147
}
147148
options={{ confirm: "Confirm", cancel: "Cancel" }}
149+
escapeKey="cancel"
148150
onSelect={(option) => {
149151
setStore("always", false)
150152
if (option === "cancel") return
@@ -199,7 +201,7 @@ export function PermissionPrompt(props: { request: PermissionRequest }) {
199201
<TextBody icon="◇" title={`Exa Code Search "` + (input().query ?? "") + `"`} />
200202
</Match>
201203
<Match when={props.request.permission === "external_directory"}>
202-
<TextBody icon="" title={`Access external directory ` + normalizePath(input().path as string)} />
204+
<TextBody icon="" title={`Access external directory ` + normalizePath(input().path as string)} />
203205
</Match>
204206
<Match when={props.request.permission === "doom_loop"}>
205207
<TextBody icon="⟳" title="Continue after repeated failures" />
@@ -210,6 +212,7 @@ export function PermissionPrompt(props: { request: PermissionRequest }) {
210212
</Switch>
211213
}
212214
options={{ once: "Allow once", always: "Allow always", reject: "Reject" }}
215+
escapeKey="reject"
213216
onSelect={(option) => {
214217
if (option === "always") {
215218
setStore("always", true)
@@ -230,9 +233,11 @@ function Prompt<const T extends Record<string, string>>(props: {
230233
title: string
231234
body: JSX.Element
232235
options: T
236+
escapeKey?: keyof T
233237
onSelect: (option: keyof T) => void
234238
}) {
235239
const { theme } = useTheme()
240+
const keybind = useKeybind()
236241
const keys = Object.keys(props.options) as (keyof T)[]
237242
const [store, setStore] = createStore({
238243
selected: keys[0],
@@ -257,6 +262,11 @@ function Prompt<const T extends Record<string, string>>(props: {
257262
evt.preventDefault()
258263
props.onSelect(store.selected)
259264
}
265+
266+
if (props.escapeKey && (evt.name === "escape" || keybind.match("app_exit", evt))) {
267+
evt.preventDefault()
268+
props.onSelect(props.escapeKey)
269+
}
260270
})
261271

262272
return (

packages/opencode/src/server/server.ts

Lines changed: 1 addition & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -2721,39 +2721,10 @@ export namespace Server {
27212721
const response = await proxy(`https://app.opencode.ai${path}`, {
27222722
...c.req,
27232723
headers: {
2724+
...c.req.raw.headers,
27242725
host: "app.opencode.ai",
27252726
},
27262727
})
2727-
// Cloudflare doesn't return Content-Type for static assets, so we need to add it
2728-
const mimeTypes: Record<string, string> = {
2729-
".js": "application/javascript",
2730-
".mjs": "application/javascript",
2731-
".css": "text/css",
2732-
".json": "application/json",
2733-
".wasm": "application/wasm",
2734-
".svg": "image/svg+xml",
2735-
".png": "image/png",
2736-
".jpg": "image/jpeg",
2737-
".jpeg": "image/jpeg",
2738-
".gif": "image/gif",
2739-
".ico": "image/x-icon",
2740-
".webp": "image/webp",
2741-
".woff": "font/woff",
2742-
".woff2": "font/woff2",
2743-
".ttf": "font/ttf",
2744-
".eot": "application/vnd.ms-fontobject",
2745-
}
2746-
for (const [ext, mime] of Object.entries(mimeTypes)) {
2747-
if (path.endsWith(ext)) {
2748-
const headers = new Headers(response.headers)
2749-
headers.set("Content-Type", mime)
2750-
return new Response(response.body, {
2751-
status: response.status,
2752-
statusText: response.statusText,
2753-
headers,
2754-
})
2755-
}
2756-
}
27572728
return response
27582729
}),
27592730
)

packages/opencode/src/tool/bash.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import z from "zod"
22
import { spawn } from "child_process"
33
import { Tool } from "./tool"
4+
import path from "path"
45
import DESCRIPTION from "./bash.txt"
56
import { Log } from "../util/log"
67
import { Instance } from "../project/instance"
@@ -136,7 +137,7 @@ export const BashTool = Tool.define("bash", async () => {
136137
await ctx.ask({
137138
permission: "external_directory",
138139
patterns: Array.from(directories),
139-
always: Array.from(directories).map((x) => x + "*"),
140+
always: Array.from(directories).map((x) => path.dirname(x) + "*"),
140141
metadata: {},
141142
})
142143
}

0 commit comments

Comments
 (0)