Compare commits

..

16 Commits

Author SHA1 Message Date
Dax Raad a0d2e53bde poll for models.dev changes 2025-07-31 23:47:42 -04:00
Dax Raad 851e900982 add user agent for models.dev request 2025-07-31 22:00:45 -04:00
Dax Raad 3aa6eeb426 do not mark errored tool calls as aborted 2025-07-31 21:45:40 -04:00
Dax Raad b6ee8e92f9 better guarding against bash commands that go outside of cwd 2025-07-31 21:42:30 -04:00
Frank 44211e1526 Update STATS.md 2025-07-31 21:42:05 -04:00
Dax Raad 12f84f198f improve wildcard matching for permissions 2025-07-31 20:40:05 -04:00
Dax Raad e6db1cf29d ci: ignore release commits 2025-07-31 19:57:07 -04:00
Dax Raad f07f04d969 fix escape button not canceling if retry in progress 2025-07-31 19:55:57 -04:00
Dax Raad 33d613a470 docs: sync 2025-07-31 19:50:51 -04:00
Dax Raad 0bbd7ea17b docs: formatters 2025-07-31 19:50:31 -04:00
Dax Raad 87f3166437 ignore: config 2025-07-31 19:45:44 -04:00
opencode 7665bd9439 Release v0.3.105 2025-07-31 23:41:27 +00:00
Dax Raad 30e10127f2 formatter config 2025-07-31 19:36:07 -04:00
Jay V 5e66fc2318 docs: edit premissions doc 2025-07-31 19:10:54 -04:00
opencode c1c99c7e0f Release v0.3.104 2025-07-31 23:02:36 +00:00
Dax Raad 04e3e83db3 allow disabling formatter 2025-07-31 18:56:04 -04:00
20 changed files with 260 additions and 90 deletions
+2 -1
View File
@@ -11,7 +11,8 @@
| 2025-07-05 | 32,524 (+1,916) | 58,371 (+3,613) | 90,895 (+5,529) |
| 2025-07-06 | 33,766 (+1,242) | 59,694 (+1,323) | 93,460 (+2,565) |
| 2025-07-08 | 38,052 (+4,286) | 64,468 (+4,774) | 102,520 (+9,060) |
| 2025-07-10 | 43,796 (+5,744) | 71,402 (+6,934) | 115,198 (+12,678) |
| 2025-07-09 | 40,924 (+2,872) | 67,935 (+3,467) | 108,859 (+6,339) |
| 2025-07-10 | 43,796 (+2,872) | 71,402 (+3,467) | 115,198 (+6,339) |
| 2025-07-11 | 46,982 (+3,186) | 77,462 (+6,060) | 124,444 (+9,246) |
| 2025-07-12 | 49,302 (+2,320) | 82,177 (+4,715) | 131,479 (+7,035) |
| 2025-07-13 | 50,803 (+1,501) | 86,394 (+4,217) | 137,197 (+5,718) |
+4 -4
View File
@@ -123,7 +123,7 @@
"catalog": {
"@tsconfig/node22": "22.0.2",
"@types/node": "22.13.9",
"ai": "5.0.0-beta.33",
"ai": "5.0.0-beta.34",
"hono": "4.7.10",
"remeda": "2.26.0",
"typescript": "5.8.2",
@@ -144,11 +144,11 @@
"@ai-sdk/anthropic": ["@ai-sdk/anthropic@1.2.12", "", { "dependencies": { "@ai-sdk/provider": "1.1.3", "@ai-sdk/provider-utils": "2.2.8" }, "peerDependencies": { "zod": "^3.0.0" } }, "sha512-YSzjlko7JvuiyQFmI9RN1tNZdEiZxc+6xld/0tq/VkJaHpEzGAb1yiNxxvmYVcjvfu/PcvCxAAYXmTYQQ63IHQ=="],
"@ai-sdk/gateway": ["@ai-sdk/gateway@1.0.0-beta.18", "", { "dependencies": { "@ai-sdk/provider": "2.0.0-beta.2", "@ai-sdk/provider-utils": "3.0.0-beta.9" }, "peerDependencies": { "zod": "^3.25.76 || ^4" } }, "sha512-1K5L7mY04ZwpngkDPLaiBiCivVj1h7gDiCZjAIgXtVp0S2zQ+1efnM/K/o2Pig6rUbt559rDLLalwZUgvn0vig=="],
"@ai-sdk/gateway": ["@ai-sdk/gateway@1.0.0-beta.19", "", { "dependencies": { "@ai-sdk/provider": "2.0.0-beta.2", "@ai-sdk/provider-utils": "3.0.0-beta.10" }, "peerDependencies": { "zod": "^3.25.76 || ^4" } }, "sha512-felWPMuECZRGx8xnmvH5dW3jywKTkGnw/tXN8szphGzEDr/BfxywuXijfPBG2WBUS6frPXsvSLDRdCm5W38PXA=="],
"@ai-sdk/provider": ["@ai-sdk/provider@2.0.0-beta.2", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-vqhtZA7R24q1XnmfmIb1fZSmHMIaJH1BVQ+0kFnNJgqWsc+V8i+yfetZ37gUc4fXATFmBuS/6O7+RPoHsZ2Fqg=="],
"@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.0-beta.9", "", { "dependencies": { "@ai-sdk/provider": "2.0.0-beta.2", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.3", "zod-to-json-schema": "^3.24.1" }, "peerDependencies": { "zod": "^3.25.76 || ^4" } }, "sha512-RJMeoqFA9mGo1XOE20bpVv4/ikVbZMHo00vmF4RweN7GHS+nEXU3SHFgtcp7NBG3j8W15b9MAitOBycRMYxecg=="],
"@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.0-beta.10", "", { "dependencies": { "@ai-sdk/provider": "2.0.0-beta.2", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.3", "zod-to-json-schema": "^3.24.1" }, "peerDependencies": { "zod": "^3.25.76 || ^4" } }, "sha512-e6WSsgM01au04/1L/v5daXHn00eKjPBQXl3jq3BfvQbQ1jo8Rls2pvrdkyVc25jBW4TV4Zm+tw+v6NAh5NPXMA=="],
"@ampproject/remapping": ["@ampproject/remapping@2.3.0", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw=="],
@@ -594,7 +594,7 @@
"acorn-walk": ["acorn-walk@8.3.2", "", {}, "sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A=="],
"ai": ["ai@5.0.0-beta.33", "", { "dependencies": { "@ai-sdk/gateway": "1.0.0-beta.18", "@ai-sdk/provider": "2.0.0-beta.2", "@ai-sdk/provider-utils": "3.0.0-beta.9", "@opentelemetry/api": "1.9.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4" } }, "sha512-TKDOYDRhS6kSmfbTj3lLFmS8kBx8OOHsIfhYKJBKnAPwlbkI3/byZRBty8tfKBrwsUAbSro3GB7rFeSthft37Q=="],
"ai": ["ai@5.0.0-beta.34", "", { "dependencies": { "@ai-sdk/gateway": "1.0.0-beta.19", "@ai-sdk/provider": "2.0.0-beta.2", "@ai-sdk/provider-utils": "3.0.0-beta.10", "@opentelemetry/api": "1.9.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4" } }, "sha512-AFJ4p35AxA+1KFtnoouePLaAUpoj0IxIAoq/xgIv88qzYajTg4Sac5KaV4CDHFRLoF0L2cwhlFXt/Ss/zyBKkA=="],
"ajv": ["ajv@6.12.6", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g=="],
+1 -1
View File
@@ -18,7 +18,7 @@
"catalog": {
"@types/node": "22.13.9",
"@tsconfig/node22": "22.0.2",
"ai": "5.0.0-beta.33",
"ai": "5.0.0-beta.34",
"hono": "4.7.10",
"typescript": "5.8.2",
"zod": "3.25.49",
+1
View File
@@ -104,6 +104,7 @@ if (!snapshot) {
.filter((x: string) => {
const lower = x.toLowerCase()
return (
!lower.includes("release:") &&
!lower.includes("ignore:") &&
!lower.includes("chore:") &&
!lower.includes("ci:") &&
+11
View File
@@ -278,6 +278,17 @@ export namespace Config {
.optional()
.describe("Custom provider configurations and model overrides"),
mcp: z.record(z.string(), Mcp).optional().describe("MCP (Model Context Protocol) server configurations"),
formatter: z
.record(
z.string(),
z.object({
disabled: z.boolean().optional(),
command: z.array(z.string()).optional(),
environment: z.record(z.string(), z.string()).optional(),
extensions: z.array(z.string()).optional(),
}),
)
.optional(),
instructions: z.array(z.string()).optional().describe("Additional instruction files or patterns to include"),
layout: Layout.optional().describe("@deprecated Always uses stretch layout."),
permission: z
+3 -1
View File
@@ -129,7 +129,9 @@ export const clang: Info = {
command: ["clang-format", "-i", "$FILE"],
extensions: [".c", ".cc", ".cpp", ".cxx", ".c++", ".h", ".hh", ".hpp", ".hxx", ".h++", ".ino", ".C", ".H"],
async enabled() {
return Bun.which("clang-format") !== null
const app = App.info()
const items = await Filesystem.findUp(".clang-format", app.path.cwd, app.path.root)
return items.length > 0
},
}
+25 -3
View File
@@ -5,20 +5,40 @@ import { Log } from "../util/log"
import path from "path"
import * as Formatter from "./formatter"
import { Config } from "../config/config"
import { mergeDeep } from "remeda"
export namespace Format {
const log = Log.create({ service: "format" })
const state = App.state("format", () => {
const state = App.state("format", async () => {
const enabled: Record<string, boolean> = {}
const cfg = await Config.get()
const formatters = { ...Formatter } as Record<string, Formatter.Info>
for (const [name, item] of Object.entries(cfg.formatter ?? {})) {
if (item.disabled) {
delete formatters[name]
continue
}
const result: Formatter.Info = mergeDeep(formatters[name] ?? {}, {
command: [],
extensions: [],
...item,
})
result.enabled = async () => true
result.name = name
formatters[name] = result
}
return {
enabled,
formatters,
}
})
async function isEnabled(item: Formatter.Info) {
const s = state()
const s = await state()
let status = s.enabled[item.name]
if (status === undefined) {
status = await item.enabled()
@@ -28,8 +48,10 @@ export namespace Format {
}
async function getFormatter(ext: string) {
const formatters = await state().then((x) => x.formatters)
const result = []
for (const item of Object.values(Formatter)) {
for (const item of Object.values(formatters)) {
log.info("checking", { name: item.name, ext })
if (!item.extensions.includes(ext)) continue
if (!(await isEnabled(item))) continue
result.push(item)
+10 -7
View File
@@ -50,21 +50,24 @@ export namespace ModelsDev {
export type Provider = z.infer<typeof Provider>
export async function get() {
refresh()
const file = Bun.file(filepath)
const result = await file.json().catch(() => {})
if (result) {
refresh()
return result as Record<string, Provider>
}
refresh()
if (result) return result as Record<string, Provider>
const json = await data()
return JSON.parse(json) as Record<string, Provider>
}
async function refresh() {
export async function refresh() {
const file = Bun.file(filepath)
log.info("refreshing")
const result = await fetch("https://models.dev/api.json").catch(() => {})
const result = await fetch("https://models.dev/api.json", {
headers: {
"User-Agent": "opencode",
},
}).catch(() => {})
if (result && result.ok) await Bun.write(file, result)
}
}
setInterval(() => ModelsDev.refresh(), 60 * 1000).unref()
+1 -1
View File
@@ -1115,7 +1115,7 @@ export namespace Session {
}
const p = await getParts(assistantMsg.sessionID, assistantMsg.id)
for (const part of p) {
if (part.type === "tool" && part.state.status !== "completed") {
if (part.type === "tool" && part.state.status !== "completed" && part.state.status !== "error") {
updatePart({
...part,
state: {
+10 -6
View File
@@ -5,14 +5,17 @@ import { App } from "../app/app"
import { Permission } from "../permission"
import { Config } from "../config/config"
import { Filesystem } from "../util/filesystem"
import path from "path"
import { lazy } from "../util/lazy"
import { minimatch } from "minimatch"
import { Log } from "../util/log"
import { Wildcard } from "../util/wildcard"
import { $ } from "bun"
const MAX_OUTPUT_LENGTH = 30000
const DEFAULT_TIMEOUT = 1 * 60 * 1000
const MAX_TIMEOUT = 10 * 60 * 1000
const log = Log.create({ service: "bash-tool" })
const parser = lazy(async () => {
const { default: Parser } = await import("tree-sitter")
const Bash = await import("tree-sitter-bash")
@@ -72,7 +75,8 @@ export const BashTool = Tool.define("bash", {
if (["cd", "rm", "cp", "mv", "mkdir", "touch", "chmod", "chown"].includes(command[0])) {
for (const arg of command.slice(1)) {
if (arg.startsWith("-")) continue
const resolved = path.resolve(app.path.cwd, arg)
const resolved = await $`realpath ${arg}`.text().then((x) => x.trim())
log.info("resolved path", { arg, resolved })
if (!Filesystem.contains(app.path.cwd, resolved)) {
throw new Error(
`This command references paths outside of ${app.path.cwd} so it is not allowed to be executed.`,
@@ -85,9 +89,9 @@ export const BashTool = Tool.define("bash", {
if (!needsAsk && command[0] !== "cd") {
const ask = (() => {
for (const [pattern, value] of Object.entries(permissions)) {
if (minimatch(node.text, pattern)) {
return value
}
const match = Wildcard.match(node.text, pattern)
log.info("checking", { text: node.text.trim(), pattern, match })
if (match) return value
}
return "ask"
})()
+1
View File
@@ -96,6 +96,7 @@ export const EditTool = Tool.define("edit", {
file: filePath,
})
contentNew = await file.text()
diff = trimDiff(createTwoFilesPatch(filePath, filePath, contentOld, contentNew))
})()
FileTime.read(ctx.sessionID, filePath)
+14
View File
@@ -0,0 +1,14 @@
export namespace Wildcard {
export function match(str: string, pattern: string) {
const regex = new RegExp(
"^" +
pattern
.replace(/[.+^${}()|[\]\\]/g, "\\$&") // escape special regex chars
.replace(/\*/g, ".*") // * becomes .*
.replace(/\?/g, ".") + // ? becomes .
"$",
"s", // s flag enables multiline matching
)
return regex.test(str)
}
}
+10
View File
@@ -590,6 +590,16 @@ export type Config = {
type: 'remote';
} & McpRemoteConfig);
};
formatter?: {
[key: string]: {
disabled?: boolean;
command?: Array<string>;
environment?: {
[key: string]: string;
};
extensions?: Array<string>;
};
};
/**
* Additional instruction files or patterns to include
*/
+1
View File
@@ -83,6 +83,7 @@ export default defineConfig({
"docs/keybinds",
"docs/permissions",
"docs/mcp-servers",
"docs/formatters",
],
},
],
@@ -230,6 +230,12 @@ You can also define agents using markdown files in `~/.config/opencode/agent/` o
You can disable providers that are loaded automatically through the `disabled_providers` option. This is useful when you want to prevent certain providers from being loaded even if their credentials are available.
---
### Formatters
You can configure code formatters through the `formatter` option. See [Formatters documentation](/docs/formatters) for more details.
```json title="opencode.json"
{
"$schema": "https://opencode.ai/config.json",
@@ -0,0 +1,87 @@
---
title: Formatters
description: Code formatting with opencode
---
opencode automatically formats code files after they are written or edited using language-specific formatters. This ensures consistent code style across your project without manual intervention.
## Built-in Formatters
opencode comes with several built-in formatters for popular languages and frameworks:
| Formatter | Languages/Extensions | Requirements |
| -------------- | -------------------------------------------------------------------------------------------------------- | ------------------------------------- |
| gofmt | .go | `gofmt` command available |
| mix | .ex, .exs, .eex, .heex, .leex, .neex, .sface | `mix` command available |
| prettier | .js, .jsx, .ts, .tsx, .html, .css, .md, .json, .yaml, and [more](https://prettier.io/docs/en/index.html) | `prettier` dependency in package.json |
| biome | .js, .jsx, .ts, .tsx, .html, .css, .md, .json, .yaml, and [more](https://biomejs.dev/) | `biome.json` config file |
| zig | .zig, .zon | `zig` command available |
| clang-format | .c, .cpp, .h, .hpp, .ino, and [more](https://clang.llvm.org/docs/ClangFormat.html) | `.clang-format` config file |
| ktlint | .kt, .kts | `ktlint` command available |
| ruff | .py, .pyi | `ruff` command available with config |
| rubocop | .rb, .rake, .gemspec, .ru | `rubocop` command available |
| standardrb | .rb, .rake, .gemspec, .ru | `standardrb` command available |
| htmlbeautifier | .erb, .html.erb | `htmlbeautifier` command available |
Formatters are automatically enabled when their requirements are met in your project environment.
## Configuration
You can customize formatters through the `formatter` section in your `opencode.json` configuration file.
### Disabling Formatters
To disable a specific formatter, set its `disabled` property to `true`:
```json title="opencode.json"
{
"$schema": "https://opencode.ai/config.json",
"formatter": {
"prettier": {
"disabled": true
}
}
}
```
### Custom Formatters
You can override the default formatters or add new ones by specifying the command, environment variables, and file extensions:
```json title="opencode.json"
{
"$schema": "https://opencode.ai/config.json",
"formatter": {
"custom-prettier": {
"command": ["npx", "prettier", "--write", "$FILE"],
"environment": {
"NODE_ENV": "development"
},
"extensions": [".js", ".ts", ".jsx", ".tsx"]
}
}
}
```
The `$FILE` placeholder in the command will be replaced with the path to the file being formatted.
### Configuration Options
Each formatter configuration supports these properties:
| Property | Type | Description |
| ------------- | -------- | ------------------------------------------------------- |
| `disabled` | boolean | Set to `true` to disable the formatter |
| `command` | string[] | The command to run for formatting |
| `environment` | object | Environment variables to set when running the formatter |
| `extensions` | string[] | File extensions this formatter should handle |
## How It Works
When opencode writes or edits a file, it:
1. Checks the file extension against all enabled formatters
2. Runs the appropriate formatter command on the file
3. Applies the formatting changes automatically
This process happens seamlessly in the background, ensuring your code maintains consistent formatting without requiring manual steps.
+16 -31
View File
@@ -18,6 +18,7 @@ Let's get started.
To use opencode, you'll need:
1. A modern terminal emulator like:
- [WezTerm](https://wezterm.org), cross-platform
- [Alacritty](https://alacritty.org), cross-platform
- [Ghostty](https://ghostty.org), Linux and macOS
@@ -40,26 +41,10 @@ You can also install it with the following:
- **Using Node.js**
<Tabs>
<TabItem label="npm">
```bash
npm install -g opencode-ai
```
</TabItem>
<TabItem label="Bun">
```bash
bun install -g opencode-ai
```
</TabItem>
<TabItem label="pnpm">
```bash
pnpm install -g opencode-ai
```
</TabItem>
<TabItem label="Yarn">
```bash
yarn global add opencode-ai
```
</TabItem>
<TabItem label="npm">```bash npm install -g opencode-ai ```</TabItem>
<TabItem label="Bun">```bash bun install -g opencode-ai ```</TabItem>
<TabItem label="pnpm">```bash pnpm install -g opencode-ai ```</TabItem>
<TabItem label="Yarn">```bash yarn global add opencode-ai ```</TabItem>
</Tabs>
- **Using Homebrew on macOS**
@@ -138,7 +123,7 @@ You should commit your project's `AGENTS.md` file to Git.
:::
This helps opencode understand the project structure and the coding patterns
used.
used.
---
@@ -178,22 +163,22 @@ You can ask opencode to add new features to your project. Though we first recomm
instead suggest _how_ it'll implement the feature.
Switch to it using the **Tab** key. You'll see an indicator for this in the lower right corner.
```bash frame="none" title="Switch to Plan mode"
<TAB>
```
Now let's describe what we want it to do.
```txt frame="none"
When a user deletes a note, we'd like to flag it as deleted in the database.
Then create a screen that shows all the recently deleted notes.
From this screen, the user can undelete a note or permanently delete it.
```
You want to give opencode enough details to understand what you want. It helps
to talk to it like you are talking to a junior developer on your team.
:::tip
Give opencode plenty of context and examples to help it understand what you
want.
@@ -202,7 +187,7 @@ You can ask opencode to add new features to your project. Though we first recomm
2. **Iterate on the plan**
Once it gives you a plan, you can give it feedback or add more details.
```txt frame="none"
We'd like to design this new screen using a design I've used before.
[Image #1] Take a look at this image and use it as a reference.
@@ -216,16 +201,16 @@ You can ask opencode to add new features to your project. Though we first recomm
do this by dragging and dropping an image into the terminal.
3. **Build the feature**
Once you feel comfortable with the plan, switch back to _Build mode_ by
hitting the **Tab** key again.
```bash frame="none"
<TAB>
```
And asking it to make the changes.
```bash frame="none"
Sounds good! Go ahead and make the changes.
```
@@ -271,4 +256,4 @@ Here's an [example conversation](https://opencode.ai/s/4XP1fce5) with opencode.
And that's it! You are now a pro at using opencode.
To make it your own, we recommend [picking a theme](/docs/themes), [customizing the keybinds](/docs/keybinds), or playing around with the [opencode config](/docs/config).
To make it your own, we recommend [picking a theme](/docs/themes), [customizing the keybinds](/docs/keybinds), [configuring code formatters](/docs/formatters), or playing around with the [opencode config](/docs/config).
@@ -46,6 +46,8 @@ This mode is useful when you want the AI to analyze code, suggest changes, or cr
You can switch between modes during a session using the _Tab_ key. Or your configured `switch_mode` keybind.
See also: [Formatters](/docs/formatters) for information about code formatting configuration.
---
## Configure
@@ -99,6 +101,7 @@ tools:
---
You are in code review mode. Focus on:
- Code quality and best practices
- Potential bugs and edge cases
- Performance implications
@@ -281,6 +284,7 @@ tools:
You are in debug mode. Your primary goal is to help investigate and diagnose issues.
Focus on:
- Understanding the problem through careful analysis
- Using bash commands to inspect system state
- Reading relevant files and logs
@@ -304,6 +308,7 @@ tools:
You are in refactoring mode. Focus on improving code quality without changing functionality.
Priorities:
- Improve code readability and maintainability
- Apply consistent naming conventions
- Reduce code duplication
@@ -1,57 +1,74 @@
---
title: Permissions
description: Control what AI agents can do in your codebase.
description: Control what agents can do in your codebase.
---
By default, opencode allows all operations without requiring explicit approval. The permissions system provides granular control to restrict what actions AI agents can perform in your codebase, allowing you to configure explicit approval requirements for sensitive operations like file editing, bash commands, and more.
By default, opencode **allows all operations** without requiring explicit approval.
## Configuration
The permissions system provides granular control to restrict what actions AI agents can perform in your codebase, allowing you to configure explicit approval requirements for sensitive operations like file editing, bash commands, and more.
Permissions are configured in your `opencode.json` file under the `permission` key. Here are the available options:
---
### permission.edit
## Configure
Controls whether file editing operations require user approval.
Permissions are configured in your `opencode.json` file under the `permission` key. Here are the available options.
- `"ask"` - Prompt user for approval before editing files
---
### edit
Use the `permission.edit` key to control whether file editing operations require user approval.
- `"ask"` - Prompt for approval before editing files
- `"allow"` - Allow all file editing operations without approval
### permission.bash
Controls whether bash commands require user approval. This can be configured globally or with specific patterns. Setting this to "ask" is the strictest mode, requiring approval for all bash commands.
## Configuration examples
### Basic permission configuration
```json title="opencode.json"
```json title="opencode.json" {4}
{
"$schema": "https://opencode.ai/config.json",
"permission": {
"edit": "ask",
"bash": "ask"
"edit": "ask"
}
}
```
### Advanced bash permission configuration
---
Setting bash permissions to "ask" is the strictest mode. If you want to allow specific commands without approval, you can configure them explicitly. All other commands will require approval by default:
### bash
```json title="opencode.json"
{
"$schema": "https://opencode.ai/config.json",
"permission": {
"edit": "ask",
"bash": {
"git status": "allow",
"git diff": "allow",
"npm run build": "allow",
"ls": "allow",
"pwd": "allow"
Controls whether bash commands require user approval.
:::tip
You can specify which commands you want to have run without approval.
:::
This can be configured globally or with specific patterns. Setting this to `"ask"` is the strictest mode, requiring approval for all bash commands.
For example.
- **Ask for approval for all commands**
```json title="opencode.json"
{
"$schema": "https://opencode.ai/config.json",
"permission": {
"bash": "ask"
}
}
}
```
```
This permissions system ensures that you maintain control over what AI agents can do in your codebase while providing flexibility for trusted operations.
- **Approve specific commands**
```json title="opencode.json"
{
"$schema": "https://opencode.ai/config.json",
"permission": {
"bash": {
"git status": "allow",
"git diff": "allow",
"npm run build": "allow",
"ls": "allow",
"pwd": "allow"
}
}
}
```
+1 -1
View File
@@ -16,7 +16,7 @@ await import(`../packages/sdk/js/script/publish.ts`)
// await import(`../packages/sdk/stainless/generate.ts`)
if (!snapshot) {
await $`git commit -am "Release v${version}"`
await $`git commit -am "release: v${version}"`
await $`git tag v${version}`
await $`git push origin HEAD --tags`
}