diff --git a/packages/opencode/src/cli/cmd/run.ts b/packages/opencode/src/cli/cmd/run.ts index 6f4508cb0b..425aef9478 100644 --- a/packages/opencode/src/cli/cmd/run.ts +++ b/packages/opencode/src/cli/cmd/run.ts @@ -228,7 +228,8 @@ export const RunCommand = effectCmd({ describe: "run in direct interactive split-footer mode", default: false, }) - .option("dangerously-skip-permissions", { + .option("yolo", { + alias: ["dangerously-skip-permissions"], type: "boolean", describe: "auto-approve permissions that are not explicitly denied (dangerous!)", default: false, @@ -732,7 +733,7 @@ export const RunCommand = effectCmd({ const permission = event.properties if (permission.sessionID !== sessionID) continue - if (args["dangerously-skip-permissions"]) { + if (args.yolo) { await client.permission.reply({ requestID: permission.id, reply: "once", diff --git a/packages/opencode/src/cli/cmd/tui.ts b/packages/opencode/src/cli/cmd/tui.ts index 68941e976a..6364d153cb 100644 --- a/packages/opencode/src/cli/cmd/tui.ts +++ b/packages/opencode/src/cli/cmd/tui.ts @@ -103,6 +103,12 @@ export const TuiThreadCommand = cmd({ .option("agent", { type: "string", describe: "agent to use", + }) + .option("yolo", { + alias: ["dangerously-skip-permissions"], + type: "boolean", + describe: "auto-approve permissions that are not explicitly denied (dangerous!)", + default: false, }), handler: async (args) => { const unguard = win32InstallCtrlCGuard() diff --git a/packages/opencode/src/effect/runtime-flags.ts b/packages/opencode/src/effect/runtime-flags.ts index 58dc50d027..650896b268 100644 --- a/packages/opencode/src/effect/runtime-flags.ts +++ b/packages/opencode/src/effect/runtime-flags.ts @@ -15,6 +15,10 @@ const enabledByExperimental = (name: string) => export class Service extends ConfigService.Service()("@opencode/RuntimeFlags", { autoShare: bool("OPENCODE_AUTO_SHARE"), + yolo: Config.all({ + primary: bool("OPENCODE_YOLO"), + legacy: bool("OPENCODE_DANGEROUSLY_SKIP_PERMISSIONS"), + }).pipe(Config.map((flags) => flags.primary || flags.legacy)), pure: bool("OPENCODE_PURE"), disableDefaultPlugins: bool("OPENCODE_DISABLE_DEFAULT_PLUGINS"), disableEmbeddedWebUi: bool("OPENCODE_DISABLE_EMBEDDED_WEB_UI"), diff --git a/packages/opencode/src/index.ts b/packages/opencode/src/index.ts index 13540a73a3..0f93a3b5b4 100644 --- a/packages/opencode/src/index.ts +++ b/packages/opencode/src/index.ts @@ -66,6 +66,7 @@ const cli = yargs(args) .middleware(async (opts) => { if (opts.printLogs) process.env.OPENCODE_PRINT_LOGS = "1" if (opts.logLevel) process.env.OPENCODE_LOG_LEVEL = opts.logLevel + if (opts.yolo) process.env.OPENCODE_YOLO = "1" if (opts.pure) { process.env.OPENCODE_PURE = "1" } diff --git a/packages/opencode/src/permission/index.ts b/packages/opencode/src/permission/index.ts index cd1f935ada..66bbe9851e 100644 --- a/packages/opencode/src/permission/index.ts +++ b/packages/opencode/src/permission/index.ts @@ -7,6 +7,7 @@ import os from "os" import { PermissionV1 } from "@opencode-ai/core/v1/permission" import { EventV2Bridge } from "@/event-v2-bridge" import { EventV2 } from "@opencode-ai/core/event" +import { RuntimeFlags } from "@/effect/runtime-flags" export const Event = { Asked: EventV2.define({ type: "permission.asked", schema: PermissionV1.Request.fields }), @@ -54,6 +55,7 @@ export const layer = Layer.effect( Service, Effect.gen(function* () { const events = yield* EventV2Bridge.Service + const flags = yield* RuntimeFlags.Service const state = yield* InstanceState.make( Effect.fn("Permission.state")(function* (ctx) { void ctx @@ -89,6 +91,7 @@ export const layer = Layer.effect( }) } if (rule.action === "allow") continue + if (flags.yolo) continue needsAsk = true } @@ -223,8 +226,11 @@ export function disabled(tools: string[], ruleset: PermissionV1.Ruleset): Set { const topLevel = yield* opencode.spawn(["--help"], { env: SNAPSHOT_ENV }) expect(topLevel.exitCode).toBe(0) expect(topLevel.stderr.endsWith(EOL)).toBe(true) + expect(topLevel.stderr).toContain("--yolo") + expect(topLevel.stderr).toContain("--dangerously-skip-permissions") const argvs: Array = [...TOP_LEVEL.map((c) => [c] as const), ...SUBCOMMANDS] diff --git a/packages/opencode/test/effect/runtime-flags.test.ts b/packages/opencode/test/effect/runtime-flags.test.ts index 2e1226b38b..b42e5b9bab 100644 --- a/packages/opencode/test/effect/runtime-flags.test.ts +++ b/packages/opencode/test/effect/runtime-flags.test.ts @@ -14,6 +14,7 @@ describe("RuntimeFlags", () => { const flags = yield* readFlags.pipe(Effect.provide(fromConfig({}))) expect(flags.autoShare).toBe(false) + expect(flags.yolo).toBe(false) }), ) @@ -23,6 +24,7 @@ describe("RuntimeFlags", () => { Effect.provide( fromConfig({ OPENCODE_PURE: "true", + OPENCODE_YOLO: "true", OPENCODE_DISABLE_DEFAULT_PLUGINS: "true", OPENCODE_AUTO_SHARE: "true", OPENCODE_DISABLE_EMBEDDED_WEB_UI: "true", @@ -39,6 +41,7 @@ describe("RuntimeFlags", () => { ) expect(flags.pure).toBe(true) + expect(flags.yolo).toBe(true) expect(flags.autoShare).toBe(true) expect(flags.disableDefaultPlugins).toBe(true) expect(flags.disableEmbeddedWebUi).toBe(true) @@ -78,6 +81,14 @@ describe("RuntimeFlags", () => { }), ) + it.effect("supports the legacy dangerous permission environment variable", () => + Effect.gen(function* () { + const flags = yield* readFlags.pipe(Effect.provide(fromConfig({ OPENCODE_DANGEROUSLY_SKIP_PERMISSIONS: "true" }))) + + expect(flags.yolo).toBe(true) + }), + ) + it.effect("enables native LLM via dedicated flag only", () => Effect.gen(function* () { const explicit = yield* readFlags.pipe(Effect.provide(fromConfig({ OPENCODE_EXPERIMENTAL_NATIVE_LLM: "true" }))) diff --git a/packages/opencode/test/permission/next.test.ts b/packages/opencode/test/permission/next.test.ts index e784350055..b646244ee1 100644 --- a/packages/opencode/test/permission/next.test.ts +++ b/packages/opencode/test/permission/next.test.ts @@ -11,16 +11,21 @@ import { InstanceStore } from "../../src/project/instance-store" import { TestInstance, tmpdirScoped } from "../fixture/fixture" import { testEffect } from "../lib/effect" import { MessageID, SessionID } from "../../src/session/schema" +import { RuntimeFlags } from "../../src/effect/runtime-flags" const events = EventV2Bridge.defaultLayer const noopBootstrap = Layer.succeed(InstanceBootstrap.Service, InstanceBootstrap.Service.of({ run: Effect.void })) -const env = Layer.mergeAll( - Permission.layer.pipe(Layer.provide(Database.defaultLayer), Layer.provide(events)), - events, - CrossSpawnSpawner.defaultLayer, - InstanceStore.defaultLayer.pipe(Layer.provide(noopBootstrap)), -) -const it = testEffect(env) +const makeEnv = (flags: Partial = {}) => { + const runtimeFlags = RuntimeFlags.layer(flags) + return Layer.mergeAll( + Permission.layer.pipe(Layer.provide(Database.defaultLayer), Layer.provide(events), Layer.provide(runtimeFlags)), + events, + CrossSpawnSpawner.defaultLayer, + InstanceStore.defaultLayer.pipe(Layer.provide(noopBootstrap)), + ) +} +const it = testEffect(makeEnv()) +const yoloIt = testEffect(makeEnv({ yolo: true })) const rejectAll = (message?: string) => Effect.gen(function* () { @@ -613,6 +618,39 @@ it.instance( { git: true }, ) +yoloIt.instance( + "ask - resolves ask rules when yolo mode is enabled", + () => + ask({ + sessionID: SessionID.make("session_test"), + permission: "bash", + patterns: ["ls"], + metadata: {}, + always: [], + ruleset: [{ permission: "bash", pattern: "*", action: "ask" }], + }), + { git: true }, +) + +yoloIt.instance( + "ask - preserves deny rules when yolo mode is enabled", + () => + Effect.gen(function* () { + const err = yield* fail( + ask({ + sessionID: SessionID.make("session_test"), + permission: "bash", + patterns: ["rm -rf /"], + metadata: {}, + always: [], + ruleset: [{ permission: "bash", pattern: "*", action: "deny" }], + }), + ) + expect(err).toBeInstanceOf(PermissionV1.DeniedError) + }), + { git: true }, +) + it.instance( "ask - adds request to pending list", () => diff --git a/packages/web/src/content/docs/cli.mdx b/packages/web/src/content/docs/cli.mdx index 0f9e5e3b1f..213d26dd06 100644 --- a/packages/web/src/content/docs/cli.mdx +++ b/packages/web/src/content/docs/cli.mdx @@ -29,19 +29,21 @@ opencode [project] #### Flags -| Flag | Short | Description | -| ------------------------------------------- | ----- | ----------------------------------------------------------------------- | -| {"--continue"} | `-c` | Continue the last session | -| {"--session"} | `-s` | Session ID to continue | -| {"--fork"} | | Fork the session when continuing (use with `--continue` or `--session`) | -| {"--prompt"} | | Prompt to use | -| {"--model"} | `-m` | Model to use in the form of provider/model | -| {"--agent"} | | Agent to use | -| {"--port"} | | Port to listen on | -| {"--hostname"} | | Hostname to listen on | -| {"--mdns"} | | Enable mDNS discovery | -| {"--mdns-domain"} | | Custom mDNS domain name | -| {"--cors"} | | Additional browser origin(s) to allow CORS | +| Flag | Short | Description | +| ------------------------------------------------------------ | ----- | ----------------------------------------------------------------------- | +| {"--continue"} | `-c` | Continue the last session | +| {"--session"} | `-s` | Session ID to continue | +| {"--fork"} | | Fork the session when continuing (use with `--continue` or `--session`) | +| {"--prompt"} | | Prompt to use | +| {"--model"} | `-m` | Model to use in the form of provider/model | +| {"--agent"} | | Agent to use | +| {"--port"} | | Port to listen on | +| {"--hostname"} | | Hostname to listen on | +| {"--mdns"} | | Enable mDNS discovery | +| {"--mdns-domain"} | | Custom mDNS domain name | +| {"--cors"} | | Additional browser origin(s) to allow CORS | +| {"--yolo"} | | Auto-approve permissions not explicitly denied | +| {"--dangerously-skip-permissions"} | | Legacy alias for `--yolo` | --- @@ -380,7 +382,8 @@ 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!) | +| {"--yolo"} | | Auto-approve permissions that are not explicitly denied | +| {"--dangerously-skip-permissions"} | | Legacy alias for `--yolo` | --- @@ -676,33 +679,35 @@ The opencode CLI takes the following global flags. OpenCode can be configured using environment variables. -| Variable | Type | Description | -| ------------------------------------- | ------- | ------------------------------------------------- | -| `OPENCODE_AUTO_SHARE` | boolean | Automatically share sessions | -| `OPENCODE_GIT_BASH_PATH` | string | Path to Git Bash executable on Windows | -| `OPENCODE_CONFIG` | string | Path to config file | -| `OPENCODE_TUI_CONFIG` | string | Path to TUI config file | -| `OPENCODE_CONFIG_DIR` | string | Path to config directory | -| `OPENCODE_CONFIG_CONTENT` | string | Inline json config content | -| `OPENCODE_DISABLE_AUTOUPDATE` | boolean | Disable automatic update checks | -| `OPENCODE_DISABLE_PRUNE` | boolean | Disable pruning of old data | -| `OPENCODE_DISABLE_TERMINAL_TITLE` | boolean | Disable automatic terminal title updates | -| `OPENCODE_PERMISSION` | string | Inlined json permissions config | -| `OPENCODE_DISABLE_DEFAULT_PLUGINS` | boolean | Disable default plugins | -| `OPENCODE_DISABLE_LSP_DOWNLOAD` | boolean | Disable automatic LSP server downloads | -| `OPENCODE_ENABLE_EXPERIMENTAL_MODELS` | boolean | Enable experimental models | -| `OPENCODE_DISABLE_AUTOCOMPACT` | boolean | Disable automatic context compaction | -| `OPENCODE_DISABLE_CLAUDE_CODE` | boolean | Disable reading from `.claude` (prompt + skills) | -| `OPENCODE_DISABLE_CLAUDE_CODE_PROMPT` | boolean | Disable reading `~/.claude/CLAUDE.md` | -| `OPENCODE_DISABLE_CLAUDE_CODE_SKILLS` | boolean | Disable loading `.claude/skills` | -| `OPENCODE_DISABLE_MODELS_FETCH` | boolean | Disable fetching models from remote sources | -| `OPENCODE_DISABLE_MOUSE` | boolean | Disable mouse capture in the TUI | -| `OPENCODE_FAKE_VCS` | string | Fake VCS provider for testing purposes | -| `OPENCODE_CLIENT` | string | Client identifier (defaults to `cli`) | -| `OPENCODE_ENABLE_EXA` | boolean | Enable Exa web search tools | -| `OPENCODE_SERVER_PASSWORD` | string | Enable basic auth for `serve`/`web` | -| `OPENCODE_SERVER_USERNAME` | string | Override basic auth username (default `opencode`) | -| `OPENCODE_MODELS_URL` | string | Custom URL for fetching models configuration | +| Variable | Type | Description | +| --------------------------------------- | ------- | ------------------------------------------------- | +| `OPENCODE_AUTO_SHARE` | boolean | Automatically share sessions | +| `OPENCODE_GIT_BASH_PATH` | string | Path to Git Bash executable on Windows | +| `OPENCODE_CONFIG` | string | Path to config file | +| `OPENCODE_TUI_CONFIG` | string | Path to TUI config file | +| `OPENCODE_CONFIG_DIR` | string | Path to config directory | +| `OPENCODE_CONFIG_CONTENT` | string | Inline json config content | +| `OPENCODE_DISABLE_AUTOUPDATE` | boolean | Disable automatic update checks | +| `OPENCODE_DISABLE_PRUNE` | boolean | Disable pruning of old data | +| `OPENCODE_DISABLE_TERMINAL_TITLE` | boolean | Disable automatic terminal title updates | +| `OPENCODE_PERMISSION` | string | Inlined json permissions config | +| `OPENCODE_YOLO` | boolean | Auto-approve permissions not explicitly denied | +| `OPENCODE_DANGEROUSLY_SKIP_PERMISSIONS` | boolean | Legacy alias for `OPENCODE_YOLO` | +| `OPENCODE_DISABLE_DEFAULT_PLUGINS` | boolean | Disable default plugins | +| `OPENCODE_DISABLE_LSP_DOWNLOAD` | boolean | Disable automatic LSP server downloads | +| `OPENCODE_ENABLE_EXPERIMENTAL_MODELS` | boolean | Enable experimental models | +| `OPENCODE_DISABLE_AUTOCOMPACT` | boolean | Disable automatic context compaction | +| `OPENCODE_DISABLE_CLAUDE_CODE` | boolean | Disable reading from `.claude` (prompt + skills) | +| `OPENCODE_DISABLE_CLAUDE_CODE_PROMPT` | boolean | Disable reading `~/.claude/CLAUDE.md` | +| `OPENCODE_DISABLE_CLAUDE_CODE_SKILLS` | boolean | Disable loading `.claude/skills` | +| `OPENCODE_DISABLE_MODELS_FETCH` | boolean | Disable fetching models from remote sources | +| `OPENCODE_DISABLE_MOUSE` | boolean | Disable mouse capture in the TUI | +| `OPENCODE_FAKE_VCS` | string | Fake VCS provider for testing purposes | +| `OPENCODE_CLIENT` | string | Client identifier (defaults to `cli`) | +| `OPENCODE_ENABLE_EXA` | boolean | Enable Exa web search tools | +| `OPENCODE_SERVER_PASSWORD` | string | Enable basic auth for `serve`/`web` | +| `OPENCODE_SERVER_USERNAME` | string | Override basic auth username (default `opencode`) | +| `OPENCODE_MODELS_URL` | string | Custom URL for fetching models configuration | ---