diff --git a/packages/cli/src/commands/commands.ts b/packages/cli/src/commands/commands.ts index a3ae15113f..f08884c15a 100644 --- a/packages/cli/src/commands/commands.ts +++ b/packages/cli/src/commands/commands.ts @@ -14,10 +14,23 @@ const ServerParams = { ), } +const PermissionParams = { + auto: Flag.boolean("auto").pipe( + Flag.withDescription("Auto-approve permissions that are not explicitly denied"), + Flag.withDefault(false), + ), + yolo: Flag.boolean("yolo").pipe(Flag.withDefault(false), Flag.withHidden), + dangerouslySkipPermissions: Flag.boolean("dangerously-skip-permissions").pipe( + Flag.withDefault(false), + Flag.withHidden, + ), +} + export const Commands = Spec.make(typeof OPENCODE_CLI_NAME === "string" ? OPENCODE_CLI_NAME : "opencode", { description: "OpenCode 2.0 preview command line interface", params: { ...ServerParams, + ...PermissionParams, directory: Argument.string("directory").pipe( Argument.withDescription("Directory to start OpenCode in"), Argument.optional, @@ -196,11 +209,7 @@ export const Commands = Spec.make(typeof OPENCODE_CLI_NAME === "string" ? OPENCO ), title: Flag.string("title").pipe(Flag.withDescription("Session title"), Flag.optional), thinking: Flag.boolean("thinking").pipe(Flag.withDescription("Show thinking blocks"), Flag.withDefault(false)), - auto: Flag.boolean("auto").pipe( - Flag.withDescription("Auto-approve permissions that are not explicitly denied"), - Flag.withDefault(false), - ), - yolo: Flag.boolean("yolo").pipe(Flag.withDefault(false), Flag.withHidden), + ...PermissionParams, }, }), Spec.make("service", { diff --git a/packages/cli/src/commands/handlers/default.ts b/packages/cli/src/commands/handlers/default.ts index f83a0ef7cb..e071949460 100644 --- a/packages/cli/src/commands/handlers/default.ts +++ b/packages/cli/src/commands/handlers/default.ts @@ -59,6 +59,7 @@ export default Runtime.handler(Commands, (input) => continue: input.continue, sessionID: Option.getOrUndefined(input.session), prompt: Option.getOrUndefined(input.prompt), + auto: input.auto || input.yolo || input.dangerouslySkipPermissions, }, config: { path: config.path, diff --git a/packages/cli/src/commands/handlers/run.ts b/packages/cli/src/commands/handlers/run.ts index 74a88e8e72..b86e1bfc05 100644 --- a/packages/cli/src/commands/handlers/run.ts +++ b/packages/cli/src/commands/handlers/run.ts @@ -24,7 +24,7 @@ export default Runtime.handler(Commands.commands.run, (input) => file: [...input.file], title: Option.getOrUndefined(input.title), thinking: input.thinking, - auto: input.auto || input.yolo, + auto: input.auto || input.yolo || input.dangerouslySkipPermissions, }), ) }), diff --git a/packages/tui/src/routes/session/footer.tsx b/packages/tui/src/routes/session/footer.tsx index 106cbda253..401db265c9 100644 --- a/packages/tui/src/routes/session/footer.tsx +++ b/packages/tui/src/routes/session/footer.tsx @@ -5,11 +5,13 @@ import { useDirectory } from "../../context/directory" import { useConnected } from "../../component/use-connected" import { createStore } from "solid-js/store" import { useRoute } from "../../context/route" +import { usePermission } from "../../context/permission" export function Footer() { const { themeV2 } = useTheme() const data = useData() const route = useRoute() + const permission = usePermission() const mcp = createMemo( () => (data.location.mcp.server.list() ?? []).filter((x) => x.status.status === "connected").length, ) @@ -61,7 +63,7 @@ export function Footer() { - 0}> + 0}> {permissions().length} Permission {permissions().length > 1 ? "s" : ""} diff --git a/packages/tui/src/routes/session/index.tsx b/packages/tui/src/routes/session/index.tsx index f487716944..13683ff08e 100644 --- a/packages/tui/src/routes/session/index.tsx +++ b/packages/tui/src/routes/session/index.tsx @@ -157,6 +157,7 @@ export function Session() { (sessionID) => data.session.permission.list(sessionID) ?? [], ) }) + const promptedPermissions = createMemo(() => (local.permission.mode === "auto" ? [] : permissions())) const forms = createMemo(() => { const global = data.session.form.list("global", location()) ?? [] if (session()?.parentID) return global @@ -168,7 +169,7 @@ export function Session() { open: false, tab: undefined as string | undefined, }) - const disabled = createMemo(() => permissions().length > 0 || forms().length > 0) + const disabled = createMemo(() => promptedPermissions().length > 0 || forms().length > 0) const pending = createMemo(() => { const completed = messages().findLast((x) => x.type === "assistant" && x.time.completed)?.id @@ -966,10 +967,10 @@ export function Session() { /> {null} - 0}> - + 0}> + {(_) => { - const request = permissions()[0] + const request = promptedPermissions()[0] return request ? ( ) : null @@ -2324,6 +2325,17 @@ function GenericTool(props: ToolProps) { ) } +function useToolPermission(part: () => SessionMessageAssistantTool | undefined) { + const ctx = use() + const data = useData() + const local = useLocal() + return createMemo(() => { + if (local.permission.mode === "auto") return false + const request = data.session.permission.list(ctx.sessionID)?.[0] + return request?.source?.type === "tool" && request.source.callID === part()?.id + }) +} + function InlineTool(props: { icon: string iconColor?: RGBA @@ -2338,16 +2350,10 @@ function InlineTool(props: { onClick?: () => void }) { const { themeV2 } = useTheme() - const ctx = use() - const data = useData() const renderer = useRenderer() const [hover, setHover] = createSignal(false) const [errorExpanded, setErrorExpanded] = createSignal(false) - - const permission = createMemo(() => { - const request = data.session.permission.list(ctx.sessionID)?.[0] - return request?.source?.type === "tool" && request.source.callID === props.part.id - }) + const permission = useToolPermission(() => props.part) const error = createMemo(() => (props.part.state.status === "error" ? props.part.state.error.message : undefined)) @@ -2526,15 +2532,10 @@ function BlockTool(props: BlockToolProps) { function BlockToolContent(props: BlockToolProps & { borderColor: RGBA }) { const { themeV2 } = useTheme() const ctx = use() - const data = useData() const renderer = useRenderer() const [hover, setHover] = createSignal(false) const error = createMemo(() => (props.part?.state.status === "error" ? props.part.state.error.message : undefined)) - const permission = createMemo(() => { - if (!props.part) return false - const request = data.session.permission.list(ctx.sessionID)?.[0] - return request?.source?.type === "tool" && request.source.callID === props.part.id - }) + const permission = useToolPermission(() => props.part) return ( { - const request = data.session.permission.list(ctx.sessionID)?.[0] - return request?.source?.type === "tool" && request.source.callID === props.part.id - }) + const permission = useToolPermission(() => props.part) const color = createMemo(() => (permission() ? themeV2.text.feedback.warning.default : themeV2.text.default)) const shellID = createMemo(() => stringValue(props.metadata.shellID)) const background = createMemo(() => Boolean(shellID()) && props.part.state.status !== "running")