|
|
@@ -0,0 +1,353 @@
|
|
|
+<!--
|
|
|
+ Built-in skill. Name and description are registered in code at
|
|
|
+ packages/opencode/src/skill/index.ts (see SKILL_NAME and SKILL_DESCRIPTION).
|
|
|
+ The body below becomes the skill's content.
|
|
|
+-->
|
|
|
+
|
|
|
+# Customizing opencode
|
|
|
+
|
|
|
+opencode validates its own config strictly and refuses to start when a field
|
|
|
+is wrong. The shapes below are the accepted shapes. When in doubt, fetch
|
|
|
+`https://opencode.ai/config.json` (the JSON Schema) and validate against it.
|
|
|
+
|
|
|
+Every `opencode.json` should declare `"$schema": "https://opencode.ai/config.json"`
|
|
|
+so the user's editor catches mistakes as they type.
|
|
|
+
|
|
|
+## Where files live
|
|
|
+
|
|
|
+| Scope | Path |
|
|
|
+|---|---|
|
|
|
+| Project config | `./opencode.json`, `./opencode.jsonc`, or `.opencode/opencode.json` (opencode walks up from the cwd to the worktree root) |
|
|
|
+| Global config | `~/.config/opencode/opencode.json` (NOT `~/.opencode/`) |
|
|
|
+| Project agents | `.opencode/agent/<name>.md` or `.opencode/agents/<name>.md` |
|
|
|
+| Global agents | `~/.config/opencode/agent(s)/<name>.md` |
|
|
|
+| Project skills | `.opencode/skill(s)/<name>/SKILL.md` |
|
|
|
+| Global skills | `~/.config/opencode/skill(s)/<name>/SKILL.md` |
|
|
|
+| External skills (auto-loaded) | `~/.claude/skills/<name>/SKILL.md`, `~/.agents/skills/<name>/SKILL.md` |
|
|
|
+
|
|
|
+Configs from each scope are deep-merged. Project overrides global. Unknown
|
|
|
+top-level keys in `opencode.json` are rejected with `ConfigInvalidError`.
|
|
|
+
|
|
|
+## opencode.json
|
|
|
+
|
|
|
+Every field is optional.
|
|
|
+
|
|
|
+```json
|
|
|
+{
|
|
|
+ "$schema": "https://opencode.ai/config.json",
|
|
|
+ "username": "string",
|
|
|
+ "model": "provider/model-id",
|
|
|
+ "small_model": "provider/model-id",
|
|
|
+ "default_agent": "agent-name",
|
|
|
+ "shell": "/bin/zsh",
|
|
|
+ "logLevel": "DEBUG" | "INFO" | "WARN" | "ERROR",
|
|
|
+ "share": "manual" | "auto" | "disabled",
|
|
|
+ "autoupdate": true | false | "notify",
|
|
|
+ "snapshot": true,
|
|
|
+ "instructions": ["AGENTS.md", "docs/style.md"],
|
|
|
+
|
|
|
+ "skills": {
|
|
|
+ "paths": [".opencode/skills", "/abs/path/to/skills"],
|
|
|
+ "urls": ["https://example.com/.well-known/skills/"]
|
|
|
+ },
|
|
|
+
|
|
|
+ "agent": {
|
|
|
+ "my-agent": {
|
|
|
+ "model": "anthropic/claude-sonnet-4-6",
|
|
|
+ "mode": "subagent",
|
|
|
+ "description": "...",
|
|
|
+ "permission": { "edit": "deny" }
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ "command": {
|
|
|
+ "deploy": { "description": "...", "prompt": "..." }
|
|
|
+ },
|
|
|
+
|
|
|
+ "provider": {
|
|
|
+ "anthropic": { "options": { "apiKey": "..." } }
|
|
|
+ },
|
|
|
+ "disabled_providers": ["openai"],
|
|
|
+ "enabled_providers": ["anthropic"],
|
|
|
+
|
|
|
+ "mcp": {
|
|
|
+ "playwright": {
|
|
|
+ "type": "local",
|
|
|
+ "command": ["npx", "-y", "@playwright/mcp"],
|
|
|
+ "enabled": true,
|
|
|
+ "env": {}
|
|
|
+ },
|
|
|
+ "remote-thing": {
|
|
|
+ "type": "remote",
|
|
|
+ "url": "https://...",
|
|
|
+ "headers": { "Authorization": "Bearer ..." }
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ "plugin": [
|
|
|
+ "opencode-gemini-auth",
|
|
|
+ "opencode-foo@1.2.3",
|
|
|
+ "./local-plugin.ts",
|
|
|
+ ["opencode-bar", { "option": "value" }]
|
|
|
+ ],
|
|
|
+
|
|
|
+ "permission": {
|
|
|
+ "edit": "deny",
|
|
|
+ "bash": { "git *": "allow", "*": "ask" }
|
|
|
+ },
|
|
|
+
|
|
|
+ "formatter": false,
|
|
|
+ "lsp": false,
|
|
|
+
|
|
|
+ "experimental": {
|
|
|
+ "primary_tools": ["edit"],
|
|
|
+ "mcp_timeout": 30000
|
|
|
+ },
|
|
|
+
|
|
|
+ "tool_output": { "max_lines": 200, "max_bytes": 8192 },
|
|
|
+
|
|
|
+ "compaction": { "auto": true, "tail_turns": 15 }
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+Shape notes worth being explicit about:
|
|
|
+
|
|
|
+- `model` always carries a provider prefix: `"anthropic/claude-sonnet-4-6"`.
|
|
|
+- `skills` is an object with `paths` and/or `urls`, not an array.
|
|
|
+- `agent` is an object keyed by agent name, not an array.
|
|
|
+- `plugin` is an array of strings or `[name, options]` tuples, not an object.
|
|
|
+- `mcp[name].command` is an array of strings, never a single string. `type` is required.
|
|
|
+- `permission` is either a string action or an object keyed by tool name.
|
|
|
+
|
|
|
+## Skills
|
|
|
+
|
|
|
+opencode's skill loader scans for `**/SKILL.md` inside skill directories. The
|
|
|
+file is named `SKILL.md` exactly, and lives in its own folder named after the
|
|
|
+skill:
|
|
|
+
|
|
|
+```
|
|
|
+.opencode/skills/my-skill/SKILL.md
|
|
|
+```
|
|
|
+
|
|
|
+Frontmatter:
|
|
|
+
|
|
|
+```markdown
|
|
|
+---
|
|
|
+name: my-skill
|
|
|
+description: One sentence covering what this skill does AND when to trigger it. Front-load the literal keywords or filenames the user is likely to say.
|
|
|
+---
|
|
|
+
|
|
|
+# My Skill
|
|
|
+
|
|
|
+(skill body in markdown: instructions, examples, references)
|
|
|
+```
|
|
|
+
|
|
|
+- `name` is required, lowercase hyphen-separated, up to 64 chars, and matches the folder name.
|
|
|
+- `description` is effectively required: skills without one are filtered out and never surfaced to the model. Cover both *what* the skill does and *when* to use it. Write in third person ("Use when...", not "I help with..."). Front-load concrete trigger keywords and filenames; gate with "Use ONLY when..." if the skill should stay quiet on adjacent topics.
|
|
|
+- Optional: `license`, `compatibility`, `metadata` (string-string map).
|
|
|
+
|
|
|
+Register skills from non-default locations via `skills.paths` (scanned
|
|
|
+recursively for `**/SKILL.md`) and `skills.urls` (each URL serves a list of
|
|
|
+skills).
|
|
|
+
|
|
|
+## Agents
|
|
|
+
|
|
|
+Two ways to define an agent. Use the file form for anything non-trivial.
|
|
|
+
|
|
|
+### Inline (in `opencode.json`)
|
|
|
+
|
|
|
+```json
|
|
|
+{
|
|
|
+ "agent": {
|
|
|
+ "my-reviewer": {
|
|
|
+ "description": "Reviews PRs for style violations.",
|
|
|
+ "mode": "subagent",
|
|
|
+ "model": "anthropic/claude-sonnet-4-6",
|
|
|
+ "permission": { "edit": "deny", "bash": "ask" },
|
|
|
+ "prompt": "You are a strict PR reviewer..."
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+### File
|
|
|
+
|
|
|
+```
|
|
|
+.opencode/agent/my-reviewer.md OR .opencode/agents/my-reviewer.md
|
|
|
+```
|
|
|
+
|
|
|
+```markdown
|
|
|
+---
|
|
|
+description: Reviews PRs for style violations.
|
|
|
+mode: subagent
|
|
|
+model: anthropic/claude-sonnet-4-6
|
|
|
+permission:
|
|
|
+ edit: deny
|
|
|
+ bash: ask
|
|
|
+---
|
|
|
+
|
|
|
+You are a strict PR reviewer. Focus on...
|
|
|
+```
|
|
|
+
|
|
|
+The file body becomes the agent's `prompt`. Do not also put `prompt:` in the
|
|
|
+frontmatter.
|
|
|
+
|
|
|
+`mode` is one of `"primary"`, `"subagent"`, `"all"`.
|
|
|
+
|
|
|
+Allowed top-level frontmatter fields: `name, model, variant, description, mode,
|
|
|
+hidden, color, steps, options, permission, disable, temperature, top_p`. Any
|
|
|
+unknown field is silently routed into `options`.
|
|
|
+
|
|
|
+To disable a built-in agent: `agent: { build: { disable: true } }`, or in a
|
|
|
+file, `disable: true` in frontmatter.
|
|
|
+
|
|
|
+`default_agent` must point to a non-hidden, primary-mode agent.
|
|
|
+
|
|
|
+### Built-in agents
|
|
|
+
|
|
|
+opencode ships with `build`, `plan`, `general`, `explore`, plus optionally
|
|
|
+`scout` (gated on `OPENCODE_EXPERIMENTAL_SCOUT`). Hidden internal agents:
|
|
|
+`compaction`, `title`, `summary`. To override a built-in's fields, define the
|
|
|
+same key in `agent: { <name>: { ... } }`.
|
|
|
+
|
|
|
+## Plugins
|
|
|
+
|
|
|
+`plugin:` is an array. Each entry is one of:
|
|
|
+
|
|
|
+```json
|
|
|
+"plugin": [
|
|
|
+ "opencode-gemini-auth", // npm spec, latest
|
|
|
+ "opencode-foo@1.2.3", // npm spec, pinned
|
|
|
+ "./local-plugin.ts", // file path, relative to the declaring config
|
|
|
+ "file:///abs/path/plugin.js", // file URL
|
|
|
+ ["opencode-bar", { "key": "val" }] // tuple form with options
|
|
|
+]
|
|
|
+```
|
|
|
+
|
|
|
+Auto-discovered plugins (no config entry needed): any `*.ts` or `*.js` file in
|
|
|
+`.opencode/plugin/` or `.opencode/plugins/`.
|
|
|
+
|
|
|
+A plugin module exports `default` (or any named export) of type
|
|
|
+`Plugin = (input: PluginInput, options?) => Promise<Hooks>`. The export is a
|
|
|
+function, not a plain object literal, and the function returns an object
|
|
|
+(return `{}` if there is nothing to register).
|
|
|
+
|
|
|
+```ts
|
|
|
+import type { Plugin } from "@opencode-ai/plugin"
|
|
|
+
|
|
|
+export default (async ({ client, project, directory, $ }) => {
|
|
|
+ return {
|
|
|
+ config: (cfg) => {
|
|
|
+ // cfg is the live merged config; mutate fields here.
|
|
|
+ },
|
|
|
+ "tool.execute.before": async (input, output) => {
|
|
|
+ // mutate output.args before the tool runs
|
|
|
+ },
|
|
|
+ }
|
|
|
+}) satisfies Plugin
|
|
|
+```
|
|
|
+
|
|
|
+Hook surface (mutate `output` in place; return `void`):
|
|
|
+- `event(input)`: every bus event
|
|
|
+- `config(cfg)`: once on init with the merged config
|
|
|
+- `chat.message`, `chat.params`, `chat.headers`
|
|
|
+- `tool.execute.before`, `tool.execute.after`
|
|
|
+- `tool.definition`
|
|
|
+- `command.execute.before`
|
|
|
+- `shell.env`
|
|
|
+- `permission.ask`
|
|
|
+- `experimental.chat.messages.transform`, `experimental.chat.system.transform`,
|
|
|
+ `experimental.session.compacting`, `experimental.compaction.autocontinue`,
|
|
|
+ `experimental.text.complete`
|
|
|
+
|
|
|
+Special object-shaped (not callbacks): `tool: { my_tool: { ... } }`,
|
|
|
+`auth: { ... }`, `provider: { ... }`.
|
|
|
+
|
|
|
+## MCP servers
|
|
|
+
|
|
|
+`mcp:` is an object keyed by server name. Each server is discriminated by
|
|
|
+`type`:
|
|
|
+
|
|
|
+```json
|
|
|
+{
|
|
|
+ "mcp": {
|
|
|
+ "playwright": {
|
|
|
+ "type": "local",
|
|
|
+ "command": ["npx", "-y", "@playwright/mcp"],
|
|
|
+ "enabled": true,
|
|
|
+ "env": { "BROWSER": "chromium" }
|
|
|
+ },
|
|
|
+ "github": {
|
|
|
+ "type": "remote",
|
|
|
+ "url": "https://...",
|
|
|
+ "enabled": true,
|
|
|
+ "headers": { "Authorization": "Bearer ${GITHUB_TOKEN}" }
|
|
|
+ },
|
|
|
+ "old-server": { "enabled": false }
|
|
|
+ }
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+`command` is an array of strings. `type` is required. Use `enabled: false` to
|
|
|
+disable a server inherited from a parent config.
|
|
|
+
|
|
|
+## Permissions
|
|
|
+
|
|
|
+```json
|
|
|
+"permission": {
|
|
|
+ "edit": "deny",
|
|
|
+ "bash": { "git *": "allow", "rm *": "deny", "*": "ask" },
|
|
|
+ "external_directory": { "~/secrets/**": "deny", "*": "allow" }
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+Actions: `"allow"`, `"ask"`, `"deny"`.
|
|
|
+
|
|
|
+Per-tool value forms: `"allow"` shorthand (treated as `{"*": "allow"}`), or an
|
|
|
+object `{ pattern: action }`. Within an object, **insertion order matters**.
|
|
|
+opencode evaluates the LAST matching rule, so put broad rules first and narrow
|
|
|
+rules last.
|
|
|
+
|
|
|
+`permission: "allow"` (a string at the top level) is shorthand for "allow
|
|
|
+everything" and is rarely what the user wants.
|
|
|
+
|
|
|
+Known permission keys: `read, edit, glob, grep, list, bash, task,
|
|
|
+external_directory, todowrite, question, webfetch, websearch, codesearch,
|
|
|
+repo_clone, repo_overview, lsp, doom_loop, skill`. Some of these (`todowrite,
|
|
|
+question, webfetch, websearch, codesearch, doom_loop`) only accept a flat
|
|
|
+action, not a per-pattern object.
|
|
|
+
|
|
|
+`external_directory` patterns are filesystem paths (use `~/`, absolute paths,
|
|
|
+or globs like `~/projects/**`).
|
|
|
+
|
|
|
+Per-agent `permission:` overrides top-level `permission:`. Plan Mode lives on
|
|
|
+the `plan` agent's permission ruleset (`edit: deny *`).
|
|
|
+
|
|
|
+## Escape hatches
|
|
|
+
|
|
|
+When a user's config is broken and opencode won't start, these env vars help:
|
|
|
+
|
|
|
+- `OPENCODE_DISABLE_PROJECT_CONFIG=1`: skip the project's local `opencode.json`
|
|
|
+ and start from globals only. Run from the project directory, opencode loads,
|
|
|
+ the user edits the broken file, then they restart without the flag.
|
|
|
+- `OPENCODE_CONFIG=/path/to/file.json`: load an additional explicit config.
|
|
|
+- `OPENCODE_CONFIG_CONTENT='{"$schema":"https://opencode.ai/config.json"}'`:
|
|
|
+ inject inline JSON as a final local-scope merge.
|
|
|
+- `OPENCODE_DISABLE_DEFAULT_PLUGINS=1`: skip default plugins.
|
|
|
+- `OPENCODE_PURE=1`: skip external plugins entirely.
|
|
|
+- `OPENCODE_DISABLE_EXTERNAL_SKILLS=1`,
|
|
|
+ `OPENCODE_DISABLE_CLAUDE_CODE_SKILLS=1`: skip the external skill scans under
|
|
|
+ `~/.claude/` and `~/.agents/`.
|
|
|
+
|
|
|
+## When proposing edits
|
|
|
+
|
|
|
+- Validate against the schema before writing. If you are unsure of a field's
|
|
|
+ exact shape, fetch `https://opencode.ai/config.json` rather than guessing.
|
|
|
+- Preserve `$schema` and any existing fields the user did not ask to change.
|
|
|
+- For agent, skill, and plugin definitions, prefer creating new files in the
|
|
|
+ correct location over inlining everything in `opencode.json`.
|
|
|
+- If the user's existing config is malformed, point them at the env-var escape
|
|
|
+ hatch above so they can edit from inside opencode without breaking their
|
|
|
+ session.
|
|
|
+- opencode hard-fails on invalid config by design. There is no graceful
|
|
|
+ degradation, so get the shape right the first time.
|