Compare commits

...
15 Commits
Author SHA1 Message Date
Dax Raad 085c0e4e2b respect go.work when spawning LSP
publish / publish (push) Has been cancelled
2025-07-09 22:54:47 -04:00
Dax Raad 8404a97c3e better detection of prettier formatter
publish / publish (push) Has been cancelled
2025-07-09 22:37:31 -04:00
Dax Raad 0ee3b1ede2 do not wait for LSP to be fully ready
publish / publish (push) Has been cancelled
2025-07-09 21:59:38 -04:00
Dax Raad a826936702 modes concept 2025-07-09 21:59:38 -04:00
Jay V fd4a5d5a63 docs: share doc edit 2025-07-09 20:26:31 -04:00
Jay V 69cf1d7b7e docs: share doc 2025-07-09 20:24:09 -04:00
Jay V 8e0a1d1167 docs: edit troubleshooting 2025-07-09 19:55:14 -04:00
Timo Clasen f22021187d feat(tui): treat pasted text file paths as file references (#809) 2025-07-09 18:37:39 -05:00
Jay V febecc348a docs: enterprise doc 2025-07-09 15:46:57 -04:00
Jay V c5ccfc3e94 docs: share page last part fix 2025-07-09 15:46:57 -04:00
Mike WallioandDax Raad 1f6efc6b94 Add gpt-4.1 beast prompt (#778)
publish / publish (push) Has been cancelled
Co-authored-by: Dax Raad <d@ironbay.co>
2025-07-09 12:11:54 -04:00
Frank Denis 727fe6f942 LSP: fix SimpleRoots to actually search in the root directory (#795) 2025-07-09 10:35:06 -05:00
Dax Raad a91e79382e ci: remove checked in config.schema.json 2025-07-09 11:30:42 -04:00
Dax Raad 5c626e0a2f ci: generate config schema as part of build 2025-07-09 11:25:58 -04:00
adamdottv 8e9e383219 chore: troubleshooting docs 2025-07-09 10:12:36 -05:00
29 changed files with 720 additions and 714 deletions
+1 -1
View File
@@ -9,7 +9,7 @@
</p> </p>
<p align="center">AI coding agent, built for the terminal.</p> <p align="center">AI coding agent, built for the terminal.</p>
<p align="center"> <p align="center">
<a href="https://discord.gg/opencode"><img alt="Discord" src="https://img.shields.io/discord/1391832426048651334?style=flat-square&label=discord" /></a> <a href="https://opencode.ai/discord"><img alt="Discord" src="https://img.shields.io/discord/1391832426048651334?style=flat-square&label=discord" /></a>
<a href="https://www.npmjs.com/package/opencode-ai"><img alt="npm" src="https://img.shields.io/npm/v/opencode-ai?style=flat-square" /></a> <a href="https://www.npmjs.com/package/opencode-ai"><img alt="npm" src="https://img.shields.io/npm/v/opencode-ai?style=flat-square" /></a>
<a href="https://github.com/sst/opencode/actions/workflows/publish.yml"><img alt="Build status" src="https://img.shields.io/github/actions/workflow/status/sst/opencode/publish.yml?style=flat-square&branch=dev" /></a> <a href="https://github.com/sst/opencode/actions/workflows/publish.yml"><img alt="Build status" src="https://img.shields.io/github/actions/workflow/status/sst/opencode/publish.yml?style=flat-square&branch=dev" /></a>
</p> </p>
+2 -2
View File
@@ -83,7 +83,7 @@
"sharp": "0.32.5", "sharp": "0.32.5",
"shiki": "3.4.2", "shiki": "3.4.2",
"solid-js": "1.9.7", "solid-js": "1.9.7",
"toolbeam-docs-theme": "0.4.1", "toolbeam-docs-theme": "0.4.3",
}, },
"devDependencies": { "devDependencies": {
"@types/node": "catalog:", "@types/node": "catalog:",
@@ -1531,7 +1531,7 @@
"token-types": ["token-types@6.0.0", "", { "dependencies": { "@tokenizer/token": "^0.3.0", "ieee754": "^1.2.1" } }, "sha512-lbDrTLVsHhOMljPscd0yitpozq7Ga2M5Cvez5AjGg8GASBjtt6iERCAJ93yommPmz62fb45oFIXHEZ3u9bfJEA=="], "token-types": ["token-types@6.0.0", "", { "dependencies": { "@tokenizer/token": "^0.3.0", "ieee754": "^1.2.1" } }, "sha512-lbDrTLVsHhOMljPscd0yitpozq7Ga2M5Cvez5AjGg8GASBjtt6iERCAJ93yommPmz62fb45oFIXHEZ3u9bfJEA=="],
"toolbeam-docs-theme": ["toolbeam-docs-theme@0.4.1", "", { "peerDependencies": { "@astrojs/starlight": "^0.34.3", "astro": "^5.7.13" } }, "sha512-lTI4dHZaVNQky29m7sb36Oy4tWPwxsCuFxFjF8hgGW0vpV+S6qPvI9SwsJFvdE/OHO5DoI7VMbryV1pxZHkkHQ=="], "toolbeam-docs-theme": ["toolbeam-docs-theme@0.4.3", "", { "peerDependencies": { "@astrojs/starlight": "^0.34.3", "astro": "^5.7.13" } }, "sha512-3um/NsSq4xFeKbKrNGPHIzfTixwnEVvroqA8Q+lecnYHHJ5TtiYTggHDqewOW+I67t0J1IVBwVKUPjxiQfIcog=="],
"tr46": ["tr46@0.0.3", "", {}, "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="], "tr46": ["tr46@0.0.3", "", {}, "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="],
-556
View File
@@ -1,556 +0,0 @@
{
"type": "object",
"properties": {
"$schema": {
"type": "string",
"description": "JSON schema reference for configuration validation"
},
"theme": {
"type": "string",
"description": "Theme name to use for the interface"
},
"keybinds": {
"type": "object",
"properties": {
"leader": {
"type": "string",
"default": "ctrl+x",
"description": "Leader key for keybind combinations\n\ndefault: `ctrl+x`",
"examples": [
"ctrl+x"
]
},
"app_help": {
"type": "string",
"default": "<leader>h",
"description": "Show help dialog\n\ndefault: `<leader>h`",
"examples": [
"<leader>h"
]
},
"editor_open": {
"type": "string",
"default": "<leader>e",
"description": "Open external editor\n\ndefault: `<leader>e`",
"examples": [
"<leader>e"
]
},
"session_new": {
"type": "string",
"default": "<leader>n",
"description": "Create a new session\n\ndefault: `<leader>n`",
"examples": [
"<leader>n"
]
},
"session_list": {
"type": "string",
"default": "<leader>l",
"description": "List all sessions\n\ndefault: `<leader>l`",
"examples": [
"<leader>l"
]
},
"session_share": {
"type": "string",
"default": "<leader>s",
"description": "Share current session\n\ndefault: `<leader>s`",
"examples": [
"<leader>s"
]
},
"session_unshare": {
"type": "string",
"default": "<leader>u",
"description": "Unshare current session\n\ndefault: `<leader>u`",
"examples": [
"<leader>u"
]
},
"session_interrupt": {
"type": "string",
"default": "esc",
"description": "Interrupt current session\n\ndefault: `esc`",
"examples": [
"esc"
]
},
"session_compact": {
"type": "string",
"default": "<leader>c",
"description": "Compact the session\n\ndefault: `<leader>c`",
"examples": [
"<leader>c"
]
},
"tool_details": {
"type": "string",
"default": "<leader>d",
"description": "Toggle tool details\n\ndefault: `<leader>d`",
"examples": [
"<leader>d"
]
},
"model_list": {
"type": "string",
"default": "<leader>m",
"description": "List available models\n\ndefault: `<leader>m`",
"examples": [
"<leader>m"
]
},
"theme_list": {
"type": "string",
"default": "<leader>t",
"description": "List available themes\n\ndefault: `<leader>t`",
"examples": [
"<leader>t"
]
},
"file_list": {
"type": "string",
"default": "<leader>f",
"description": "List files\n\ndefault: `<leader>f`",
"examples": [
"<leader>f"
]
},
"file_close": {
"type": "string",
"default": "esc",
"description": "Close file\n\ndefault: `esc`",
"examples": [
"esc"
]
},
"file_search": {
"type": "string",
"default": "<leader>/",
"description": "Search file\n\ndefault: `<leader>/`",
"examples": [
"<leader>/"
]
},
"file_diff_toggle": {
"type": "string",
"default": "<leader>v",
"description": "Split/unified diff\n\ndefault: `<leader>v`",
"examples": [
"<leader>v"
]
},
"project_init": {
"type": "string",
"default": "<leader>i",
"description": "Create/update AGENTS.md\n\ndefault: `<leader>i`",
"examples": [
"<leader>i"
]
},
"input_clear": {
"type": "string",
"default": "ctrl+c",
"description": "Clear input field\n\ndefault: `ctrl+c`",
"examples": [
"ctrl+c"
]
},
"input_paste": {
"type": "string",
"default": "ctrl+v",
"description": "Paste from clipboard\n\ndefault: `ctrl+v`",
"examples": [
"ctrl+v"
]
},
"input_submit": {
"type": "string",
"default": "enter",
"description": "Submit input\n\ndefault: `enter`",
"examples": [
"enter"
]
},
"input_newline": {
"type": "string",
"default": "shift+enter,ctrl+j",
"description": "Insert newline in input\n\ndefault: `shift+enter,ctrl+j`",
"examples": [
"shift+enter,ctrl+j"
]
},
"messages_page_up": {
"type": "string",
"default": "pgup",
"description": "Scroll messages up by one page\n\ndefault: `pgup`",
"examples": [
"pgup"
]
},
"messages_page_down": {
"type": "string",
"default": "pgdown",
"description": "Scroll messages down by one page\n\ndefault: `pgdown`",
"examples": [
"pgdown"
]
},
"messages_half_page_up": {
"type": "string",
"default": "ctrl+alt+u",
"description": "Scroll messages up by half page\n\ndefault: `ctrl+alt+u`",
"examples": [
"ctrl+alt+u"
]
},
"messages_half_page_down": {
"type": "string",
"default": "ctrl+alt+d",
"description": "Scroll messages down by half page\n\ndefault: `ctrl+alt+d`",
"examples": [
"ctrl+alt+d"
]
},
"messages_previous": {
"type": "string",
"default": "ctrl+up",
"description": "Navigate to previous message\n\ndefault: `ctrl+up`",
"examples": [
"ctrl+up"
]
},
"messages_next": {
"type": "string",
"default": "ctrl+down",
"description": "Navigate to next message\n\ndefault: `ctrl+down`",
"examples": [
"ctrl+down"
]
},
"messages_first": {
"type": "string",
"default": "ctrl+g",
"description": "Navigate to first message\n\ndefault: `ctrl+g`",
"examples": [
"ctrl+g"
]
},
"messages_last": {
"type": "string",
"default": "ctrl+alt+g",
"description": "Navigate to last message\n\ndefault: `ctrl+alt+g`",
"examples": [
"ctrl+alt+g"
]
},
"messages_layout_toggle": {
"type": "string",
"default": "<leader>p",
"description": "Toggle layout\n\ndefault: `<leader>p`",
"examples": [
"<leader>p"
]
},
"messages_copy": {
"type": "string",
"default": "<leader>y",
"description": "Copy message\n\ndefault: `<leader>y`",
"examples": [
"<leader>y"
]
},
"messages_revert": {
"type": "string",
"default": "<leader>r",
"description": "Revert message\n\ndefault: `<leader>r`",
"examples": [
"<leader>r"
]
},
"app_exit": {
"type": "string",
"default": "ctrl+c,<leader>q",
"description": "Exit the application\n\ndefault: `ctrl+c,<leader>q`",
"examples": [
"ctrl+c,<leader>q"
]
}
},
"additionalProperties": false,
"description": "Custom keybind configurations"
},
"autoshare": {
"type": "boolean",
"description": "Share newly created sessions automatically"
},
"autoupdate": {
"type": "boolean",
"description": "Automatically update to the latest version"
},
"disabled_providers": {
"type": "array",
"items": {
"type": "string"
},
"description": "Disable providers that are loaded automatically"
},
"model": {
"type": "string",
"description": "Model to use in the format of provider/model, eg anthropic/claude-2"
},
"log_level": {
"type": "string",
"enum": [
"DEBUG",
"INFO",
"WARN",
"ERROR"
],
"description": "Minimum log level to write to log files"
},
"provider": {
"type": "object",
"additionalProperties": {
"type": "object",
"properties": {
"api": {
"type": "string"
},
"name": {
"type": "string"
},
"env": {
"type": "array",
"items": {
"type": "string"
}
},
"id": {
"type": "string"
},
"npm": {
"type": "string"
},
"models": {
"type": "object",
"additionalProperties": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"name": {
"type": "string"
},
"release_date": {
"type": "string"
},
"attachment": {
"type": "boolean"
},
"reasoning": {
"type": "boolean"
},
"temperature": {
"type": "boolean"
},
"tool_call": {
"type": "boolean"
},
"cost": {
"type": "object",
"properties": {
"input": {
"type": "number"
},
"output": {
"type": "number"
},
"cache_read": {
"type": "number"
},
"cache_write": {
"type": "number"
}
},
"required": [
"input",
"output"
],
"additionalProperties": false
},
"limit": {
"type": "object",
"properties": {
"context": {
"type": "number"
},
"output": {
"type": "number"
}
},
"required": [
"context",
"output"
],
"additionalProperties": false
},
"options": {
"type": "object",
"additionalProperties": {}
}
},
"additionalProperties": false
}
},
"options": {
"type": "object",
"additionalProperties": {}
}
},
"required": [
"models"
],
"additionalProperties": false
},
"description": "Custom provider configurations and model overrides"
},
"mcp": {
"type": "object",
"additionalProperties": {
"anyOf": [
{
"type": "object",
"properties": {
"type": {
"type": "string",
"const": "local",
"description": "Type of MCP server connection"
},
"command": {
"type": "array",
"items": {
"type": "string"
},
"description": "Command and arguments to run the MCP server"
},
"environment": {
"type": "object",
"additionalProperties": {
"type": "string"
},
"description": "Environment variables to set when running the MCP server"
},
"enabled": {
"type": "boolean",
"description": "Enable or disable the MCP server on startup"
}
},
"required": [
"type",
"command"
],
"additionalProperties": false
},
{
"type": "object",
"properties": {
"type": {
"type": "string",
"const": "remote",
"description": "Type of MCP server connection"
},
"url": {
"type": "string",
"description": "URL of the remote MCP server"
},
"enabled": {
"type": "boolean",
"description": "Enable or disable the MCP server on startup"
}
},
"required": [
"type",
"url"
],
"additionalProperties": false
}
]
},
"description": "MCP (Model Context Protocol) server configurations"
},
"instructions": {
"type": "array",
"items": {
"type": "string"
},
"description": "Additional instruction files or patterns to include"
},
"experimental": {
"type": "object",
"properties": {
"hook": {
"type": "object",
"properties": {
"file_edited": {
"type": "object",
"additionalProperties": {
"type": "array",
"items": {
"type": "object",
"properties": {
"command": {
"type": "array",
"items": {
"type": "string"
}
},
"environment": {
"type": "object",
"additionalProperties": {
"type": "string"
}
}
},
"required": [
"command"
],
"additionalProperties": false
}
}
},
"session_completed": {
"type": "array",
"items": {
"type": "object",
"properties": {
"command": {
"type": "array",
"items": {
"type": "string"
}
},
"environment": {
"type": "object",
"additionalProperties": {
"type": "string"
}
}
},
"required": [
"command"
],
"additionalProperties": false
}
}
},
"additionalProperties": false
}
},
"additionalProperties": false
}
},
"additionalProperties": false,
"$schema": "http://json-schema.org/draft-07/schema#"
}
+3 -1
View File
@@ -4,6 +4,8 @@ import "zod-openapi/extend"
import { Config } from "../src/config/config" import { Config } from "../src/config/config"
import { zodToJsonSchema } from "zod-to-json-schema" import { zodToJsonSchema } from "zod-to-json-schema"
const file = process.argv[2]
const result = zodToJsonSchema(Config.Info, { const result = zodToJsonSchema(Config.Info, {
/** /**
* We'll use the `default` values of the field as the only value in `examples`. * We'll use the `default` values of the field as the only value in `examples`.
@@ -30,4 +32,4 @@ const result = zodToJsonSchema(Config.Info, {
return jsonSchema return jsonSchema
}, },
}) })
await Bun.write("config.schema.json", JSON.stringify(result, null, 2)) await Bun.write(file, JSON.stringify(result, null, 2))
+19
View File
@@ -55,6 +55,17 @@ export namespace Config {
export const Mcp = z.discriminatedUnion("type", [McpLocal, McpRemote]) export const Mcp = z.discriminatedUnion("type", [McpLocal, McpRemote])
export type Mcp = z.infer<typeof Mcp> export type Mcp = z.infer<typeof Mcp>
export const Mode = z
.object({
model: z.string().optional(),
prompt: z.string().optional(),
tools: z.record(z.string(), z.boolean()).optional(),
})
.openapi({
ref: "ModeConfig",
})
export type Mode = z.infer<typeof Mode>
export const Keybinds = z export const Keybinds = z
.object({ .object({
leader: z.string().optional().default("ctrl+x").describe("Leader key for keybind combinations"), leader: z.string().optional().default("ctrl+x").describe("Leader key for keybind combinations"),
@@ -99,6 +110,7 @@ export namespace Config {
.openapi({ .openapi({
ref: "KeybindsConfig", ref: "KeybindsConfig",
}) })
export const Info = z export const Info = z
.object({ .object({
$schema: z.string().optional().describe("JSON schema reference for configuration validation"), $schema: z.string().optional().describe("JSON schema reference for configuration validation"),
@@ -108,6 +120,13 @@ export namespace Config {
autoupdate: z.boolean().optional().describe("Automatically update to the latest version"), autoupdate: z.boolean().optional().describe("Automatically update to the latest version"),
disabled_providers: z.array(z.string()).optional().describe("Disable providers that are loaded automatically"), disabled_providers: z.array(z.string()).optional().describe("Disable providers that are loaded automatically"),
model: z.string().describe("Model to use in the format of provider/model, eg anthropic/claude-2").optional(), model: z.string().describe("Model to use in the format of provider/model, eg anthropic/claude-2").optional(),
mode: z
.object({
build: Mode.optional(),
plan: Mode.optional(),
})
.catchall(Mode)
.optional(),
log_level: Log.Level.optional().describe("Minimum log level to write to log files"), log_level: Log.Level.optional().describe("Minimum log level to write to log files"),
provider: z provider: z
.record( .record(
+8 -17
View File
@@ -1,5 +1,7 @@
import { App } from "../app/app" import { App } from "../app/app"
import { BunProc } from "../bun" import { BunProc } from "../bun"
import { Filesystem } from "../util/filesystem"
import path from "path"
export interface Info { export interface Info {
name: string name: string
@@ -29,7 +31,7 @@ export const mix: Info = {
export const prettier: Info = { export const prettier: Info = {
name: "prettier", name: "prettier",
command: [BunProc.which(), "run", "prettier", "--write", "$FILE"], command: [BunProc.which(), "x", "prettier", "--write", "$FILE"],
environment: { environment: {
BUN_BE_BUN: "1", BUN_BE_BUN: "1",
}, },
@@ -62,23 +64,12 @@ export const prettier: Info = {
".gql", ".gql",
], ],
async enabled() { async enabled() {
// this is more complicated because we only want to use prettier if it's const app = App.info()
// being used with the current project const nms = await Filesystem.findUp("node_modules", app.path.cwd, app.path.root)
try { for (const item of nms) {
const proc = Bun.spawn({ if (await Bun.file(path.join(item, ".bin", "prettier")).exists()) return true
cmd: [BunProc.which(), "run", "prettier", "--version"],
cwd: App.info().path.cwd,
env: {
BUN_BE_BUN: "1",
},
stdout: "ignore",
stderr: "ignore",
})
const exit = await proc.exited
return exit === 0
} catch {
return false
} }
return false
}, },
} }
+1 -3
View File
@@ -184,9 +184,7 @@ export namespace LSPClient {
}, },
} }
if (input.server.onInitialized) { if (input.server.onInitialized) input.server.onInitialized(result)
await input.server.onInitialized(result)
}
l.info("initialized") l.info("initialized")
return result return result
+111 -3
View File
@@ -24,9 +24,8 @@ export namespace LSPServer {
const SimpleRoots = (patterns: string[]): RootsFunction => { const SimpleRoots = (patterns: string[]): RootsFunction => {
return async (app) => { return async (app) => {
const glob = `**/*/{${patterns.join(",")}}`
const files = await Ripgrep.files({ const files = await Ripgrep.files({
glob: [glob], glob: patterns.map((p) => `**/${p}`),
cwd: app.path.root, cwd: app.path.root,
}) })
const dirs = files.map((file) => path.dirname(file)) const dirs = files.map((file) => path.dirname(file))
@@ -86,7 +85,11 @@ export namespace LSPServer {
export const Gopls: Info = { export const Gopls: Info = {
id: "golang", id: "golang",
roots: SimpleRoots(["go.mod", "go.sum"]), roots: async (app) => {
const work = await SimpleRoots(["go.work"])(app)
if (work.length > 0) return work
return SimpleRoots(["go.mod", "go.sum"])(app)
},
extensions: [".go"], extensions: [".go"],
async spawn(_, root) { async spawn(_, root) {
let bin = Bun.which("gopls", { let bin = Bun.which("gopls", {
@@ -239,4 +242,109 @@ export namespace LSPServer {
} }
}, },
} }
export const Zls: Info = {
id: "zls",
extensions: [".zig", ".zon"],
roots: SimpleRoots(["build.zig"]),
async spawn(_, root) {
let bin = Bun.which("zls", {
PATH: process.env["PATH"] + ":" + Global.Path.bin,
})
if (!bin) {
const zig = Bun.which("zig")
if (!zig) {
log.error("Zig is required to use zls. Please install Zig first.")
return
}
log.info("downloading zls from GitHub releases")
const releaseResponse = await fetch("https://api.github.com/repos/zigtools/zls/releases/latest")
if (!releaseResponse.ok) {
log.error("Failed to fetch zls release info")
return
}
const release = await releaseResponse.json()
const platform = process.platform
const arch = process.arch
let assetName = ""
let zlsArch: string = arch
if (arch === "arm64") zlsArch = "aarch64"
else if (arch === "x64") zlsArch = "x86_64"
else if (arch === "ia32") zlsArch = "x86"
let zlsPlatform: string = platform
if (platform === "darwin") zlsPlatform = "macos"
else if (platform === "win32") zlsPlatform = "windows"
const ext = platform === "win32" ? "zip" : "tar.xz"
assetName = `zls-${zlsArch}-${zlsPlatform}.${ext}`
const supportedCombos = [
"zls-x86_64-linux.tar.xz",
"zls-x86_64-macos.tar.xz",
"zls-x86_64-windows.zip",
"zls-aarch64-linux.tar.xz",
"zls-aarch64-macos.tar.xz",
"zls-aarch64-windows.zip",
"zls-x86-linux.tar.xz",
"zls-x86-windows.zip",
]
if (!supportedCombos.includes(assetName)) {
log.error(`Platform ${platform} and architecture ${arch} is not supported by zls`)
return
}
const asset = release.assets.find((a: any) => a.name === assetName)
if (!asset) {
log.error(`Could not find asset ${assetName} in latest zls release`)
return
}
const downloadUrl = asset.browser_download_url
const downloadResponse = await fetch(downloadUrl)
if (!downloadResponse.ok) {
log.error("Failed to download zls")
return
}
const tempPath = path.join(Global.Path.bin, assetName)
await Bun.file(tempPath).write(downloadResponse)
if (ext === "zip") {
await $`unzip -o -q ${tempPath}`.cwd(Global.Path.bin).nothrow()
} else {
await $`tar -xf ${tempPath}`.cwd(Global.Path.bin).nothrow()
}
await fs.rm(tempPath, { force: true })
bin = path.join(Global.Path.bin, "zls" + (platform === "win32" ? ".exe" : ""))
if (!(await Bun.file(bin).exists())) {
log.error("Failed to extract zls binary")
return
}
if (platform !== "win32") {
await $`chmod +x ${bin}`.nothrow()
}
log.info(`installed zls`, { bin })
}
return {
process: spawn(bin, {
cwd: root,
}),
}
},
}
} }
+21
View File
@@ -16,6 +16,7 @@ import { Config } from "../config/config"
import { File } from "../file" import { File } from "../file"
import { LSP } from "../lsp" import { LSP } from "../lsp"
import { MessageV2 } from "../session/message-v2" import { MessageV2 } from "../session/message-v2"
import { Mode } from "../session/mode"
const ERRORS = { const ERRORS = {
400: { 400: {
@@ -681,6 +682,26 @@ export namespace Server {
return c.json(true) return c.json(true)
}, },
) )
.get(
"/mode",
describeRoute({
description: "List all modes",
responses: {
200: {
description: "List of modes",
content: {
"application/json": {
schema: resolver(Mode.Info.array()),
},
},
},
},
}),
async (c) => {
const modes = await Mode.list()
return c.json(modes)
},
)
return result return result
} }
+22 -5
View File
@@ -15,6 +15,7 @@ import {
} from "ai" } from "ai"
import PROMPT_INITIALIZE from "../session/prompt/initialize.txt" import PROMPT_INITIALIZE from "../session/prompt/initialize.txt"
import PROMPT_PLAN from "../session/prompt/plan.txt"
import { App } from "../app/app" import { App } from "../app/app"
import { Bus } from "../bus" import { Bus } from "../bus"
@@ -29,12 +30,12 @@ import type { ModelsDev } from "../provider/models"
import { Share } from "../share/share" import { Share } from "../share/share"
import { Snapshot } from "../snapshot" import { Snapshot } from "../snapshot"
import { Storage } from "../storage/storage" import { Storage } from "../storage/storage"
import type { Tool } from "../tool/tool"
import { Log } from "../util/log" import { Log } from "../util/log"
import { NamedError } from "../util/error" import { NamedError } from "../util/error"
import { SystemPrompt } from "./system" import { SystemPrompt } from "./system"
import { FileTime } from "../file/time" import { FileTime } from "../file/time"
import { MessageV2 } from "./message-v2" import { MessageV2 } from "./message-v2"
import { Mode } from "./mode"
export namespace Session { export namespace Session {
const log = Log.create({ service: "session" }) const log = Log.create({ service: "session" })
@@ -281,13 +282,13 @@ export namespace Session {
sessionID: string sessionID: string
providerID: string providerID: string
modelID: string modelID: string
mode?: string
parts: MessageV2.UserPart[] parts: MessageV2.UserPart[]
system?: string[]
tools?: Tool.Info[]
}) { }) {
using abort = lock(input.sessionID) using abort = lock(input.sessionID)
const l = log.clone().tag("session", input.sessionID) const l = log.clone().tag("session", input.sessionID)
l.info("chatting") l.info("chatting")
const model = await Provider.getModel(input.providerID, input.modelID) const model = await Provider.getModel(input.providerID, input.modelID)
let msgs = await messages(input.sessionID) let msgs = await messages(input.sessionID)
const session = await get(input.sessionID) const session = await get(input.sessionID)
@@ -364,6 +365,7 @@ export namespace Session {
return [ return [
{ {
type: "text", type: "text",
synthetic: true,
text: ["Called the Read tool on " + url.pathname, "<results>", text, "</results>"].join("\n"), text: ["Called the Read tool on " + url.pathname, "<results>", text, "</results>"].join("\n"),
}, },
] ]
@@ -373,6 +375,7 @@ export namespace Session {
{ {
type: "text", type: "text",
text: `Called the Read tool with the following input: {\"filePath\":\"${url.pathname}\"}`, text: `Called the Read tool with the following input: {\"filePath\":\"${url.pathname}\"}`,
synthetic: true,
}, },
{ {
type: "file", type: "file",
@@ -386,6 +389,14 @@ export namespace Session {
return [part] return [part]
}), }),
).then((x) => x.flat()) ).then((x) => x.flat())
if (input.mode === "plan")
input.parts.push({
type: "text",
text: PROMPT_PLAN,
synthetic: true,
})
if (msgs.length === 0 && !session.parentID) { if (msgs.length === 0 && !session.parentID) {
generateText({ generateText({
maxOutputTokens: input.providerID === "google" ? 1024 : 20, maxOutputTokens: input.providerID === "google" ? 1024 : 20,
@@ -431,9 +442,13 @@ export namespace Session {
await updateMessage(msg) await updateMessage(msg)
msgs.push(msg) msgs.push(msg)
const system = input.system ?? SystemPrompt.provider(input.providerID) const mode = await Mode.get(input.mode ?? "build")
let system = mode.prompt ? [mode.prompt] : SystemPrompt.provider(input.providerID, input.modelID)
system.push(...(await SystemPrompt.environment())) system.push(...(await SystemPrompt.environment()))
system.push(...(await SystemPrompt.custom())) system.push(...(await SystemPrompt.custom()))
// max 2 system prompt messages for caching purposes
const [first, ...rest] = system
system = [first, rest.join("\n")]
const next: MessageV2.Info = { const next: MessageV2.Info = {
id: Identifier.ascending("message"), id: Identifier.ascending("message"),
@@ -462,7 +477,8 @@ export namespace Session {
const tools: Record<string, AITool> = {} const tools: Record<string, AITool> = {}
for (const item of await Provider.tools(input.providerID)) { for (const item of await Provider.tools(input.providerID)) {
tools[item.id.replaceAll(".", "_")] = tool({ if (mode.tools[item.id] === false) continue
tools[item.id] = tool({
id: item.id as any, id: item.id as any,
description: item.description, description: item.description,
inputSchema: item.parameters as ZodSchema, inputSchema: item.parameters as ZodSchema,
@@ -494,6 +510,7 @@ export namespace Session {
} }
for (const [key, item] of Object.entries(await MCP.tools())) { for (const [key, item] of Object.entries(await MCP.tools())) {
if (mode.tools[key] === false) continue
const execute = item.execute const execute = item.execute
if (!execute) continue if (!execute) continue
item.execute = async (args, opts) => { item.execute = async (args, opts) => {
@@ -76,6 +76,7 @@ export namespace MessageV2 {
.object({ .object({
type: z.literal("text"), type: z.literal("text"),
text: z.string(), text: z.string(),
synthetic: z.boolean().optional(),
}) })
.openapi({ .openapi({
ref: "TextPart", ref: "TextPart",
+69
View File
@@ -0,0 +1,69 @@
import { mergeDeep } from "remeda"
import { App } from "../app/app"
import { Config } from "../config/config"
import z from "zod"
export namespace Mode {
export const Info = z
.object({
name: z.string(),
model: z
.object({
modelID: z.string(),
providerID: z.string(),
})
.optional(),
prompt: z.string().optional(),
tools: z.record(z.boolean()),
})
.openapi({
ref: "Mode",
})
export type Info = z.infer<typeof Info>
const state = App.state("mode", async () => {
const cfg = await Config.get()
const mode = mergeDeep(
{
build: {},
plan: {
tools: {
write: false,
edit: false,
patch: false,
},
},
},
cfg.mode ?? {},
)
const result: Record<string, Info> = {}
for (const [key, value] of Object.entries(mode)) {
let item = result[key]
if (!item)
item = result[key] = {
name: key,
tools: {},
}
const model = value.model ?? cfg.model
if (model) {
const [providerID, ...rest] = model.split("/")
const modelID = rest.join("/")
item.model = {
modelID,
providerID,
}
}
if (value.prompt) item.prompt = await Bun.file(value.prompt).text()
if (value.tools) item.tools = value.tools
}
return result
})
export async function get(mode: string) {
return state().then((x) => x[mode])
}
export async function list() {
return state().then((x) => Object.values(x))
}
}
@@ -0,0 +1,95 @@
You are an agent known as opencode - please keep going until the users query is completely resolved, before ending your turn and yielding back to the user.
Your thinking should be thorough and so it's fine if it's very long. However, avoid unnecessary repetition and verbosity. You should be concise, but thorough.
You MUST iterate and keep going until the problem is solved.
I want you to fully solve this autonomously before coming back to me.
Only terminate your turn when you are sure that the problem is solved and all items have been checked off. Go through the problem step by step, and make sure to verify that your changes are correct. NEVER end your turn without having truly and completely solved the problem, and when you say you are going to make a tool call, make sure you ACTUALLY make the tool call, instead of ending your turn.
Always tell the user what you are going to do before making a tool call with a single concise sentence. This will help them understand what you are doing and why.
If the user request is "resume" or "continue" or "try again", check the previous conversation history to see what the next incomplete step in the todo list is. Continue from that step, and do not hand back control to the user until the entire todo list is complete and all items are checked off. Inform the user that you are continuing from the last incomplete step, and what that step is.
Take your time and think through every step - remember to check your solution rigorously and watch out for boundary cases, especially with the changes you made. Your solution must be perfect. If not, continue working on it. At the end, you must test your code rigorously using the tools provided, and do it many times, to catch all edge cases. If it is not robust, iterate more and make it perfect. Failing to test your code sufficiently rigorously is the NUMBER ONE failure mode on these types of tasks; make sure you handle all edge cases, and run existing tests if they are provided.
You MUST plan extensively before each function call, and reflect extensively on the outcomes of the previous function calls. DO NOT do this entire process by making function calls only, as this can impair your ability to solve the problem and think insightfully.
# Workflow
1. Understand the problem deeply. Carefully read the issue and think critically about what is required.
2. Investigate the codebase. Explore relevant files, search for key functions, and gather context.
3. Develop a clear, step-by-step plan. Break down the fix into manageable, incremental steps. Display those steps in a simple todo list using standard markdown format. Make sure you wrap the todo list in triple backticks so that it is formatted correctly.
4. Implement the fix incrementally. Make small, testable code changes.
5. Debug as needed. Use debugging techniques to isolate and resolve issues.
6. Test frequently. Run tests after each change to verify correctness.
7. Iterate until the root cause is fixed and all tests pass.
8. Reflect and validate comprehensively. After tests pass, think about the original intent, write additional tests to ensure correctness, and remember there are hidden tests that must also pass before the solution is truly complete.
Refer to the detailed sections below for more information on each step.
## 1. Deeply Understand the Problem
Carefully read the issue and think hard about a plan to solve it before coding.
## 2. Codebase Investigation
- Explore relevant files and directories.
- Search for key functions, classes, or variables related to the issue.
- Read and understand relevant code snippets.
- Identify the root cause of the problem.
- Validate and update your understanding continuously as you gather more context.
## 3. Fetch Provided URLs
- If the user provides a URL, use the `functions.fetch_webpage` tool to retrieve the content of the provided URL.
- After fetching, review the content returned by the fetch tool.
- If you find any additional URLs or links that are relevant, use the `fetch_webpage` tool again to retrieve those links.
- Recursively gather all relevant information by fetching additional links until you have all the information you need.
## 4. Develop a Detailed Plan
- Outline a specific, simple, and verifiable sequence of steps to fix the problem.
- Create a todo list in markdown format to track your progress.
- Each time you complete a step, check it off using `[x]` syntax.
- Each time you check off a step, display the updated todo list to the user.
- Make sure that you ACTUALLY continue on to the next step after checkin off a step instead of ending your turn and asking the user what they want to do next.
## 5. Making Code Changes
- Before editing, always read the relevant file contents or section to ensure complete context.
- Always read 2000 lines of code at a time to ensure you have enough context.
- If a patch is not applied correctly, attempt to reapply it.
- Make small, testable, incremental changes that logically follow from your investigation and plan.
## 6. Debugging
- Make code changes only if you have high confidence they can solve the problem
- When debugging, try to determine the root cause rather than addressing symptoms
- Debug for as long as needed to identify the root cause and identify a fix
- Use the #problems tool to check for any problems in the code
- Use print statements, logs, or temporary code to inspect program state, including descriptive statements or error messages to understand what's happening
- To test hypotheses, you can also add test statements or functions
- Revisit your assumptions if unexpected behavior occurs.
# Fetch Webpage
Use the `webfetch` tool when the user provides a URL. Follow these steps exactly.
1. Use the `webfetch` tool to retrieve the content of the provided URL.
2. After fetching, review the content returned by the fetch tool.
3. If you find any additional URLs or links that are relevant, use the `webfetch` tool again to retrieve those links.
4. Go back to step 2 and repeat until you have all the information you need.
IMPORTANT: Recursively fetching links is crucial. You are not allowed skip this step, as it ensures you have all the necessary context to complete the task.
# How to create a Todo List
Use the following format to create a todo list:
```markdown
- [ ] Step 1: Description of the first step
- [ ] Step 2: Description of the second step
- [ ] Step 3: Description of the third step
```
Do not ever use HTML tags or any other formatting for the todo list, as it will not be rendered correctly. Always use the markdown format shown above.
# Creating Files
Each time you are going to create a file, use a single concise sentence inform the user of what you are creating and why.
# Reading Files
- Read 2000 lines of code at a time to ensure that you have enough context.
- Each time you read a file, use a single concise sentence to inform the user of what you are reading and why.
@@ -0,0 +1,3 @@
<system-reminder>
Plan mode is active. The user indicated that they do not want you to execute yet -- you MUST NOT make any edits, run any non-readonly tools (including changing configs or making commits), or otherwise make any changes to the system. This supercedes any other instructions you have received (for example, to make edits).
</system-reminder>
+5 -12
View File
@@ -7,23 +7,16 @@ import path from "path"
import os from "os" import os from "os"
import PROMPT_ANTHROPIC from "./prompt/anthropic.txt" import PROMPT_ANTHROPIC from "./prompt/anthropic.txt"
import PROMPT_BEAST from "./prompt/beast.txt"
import PROMPT_ANTHROPIC_SPOOF from "./prompt/anthropic_spoof.txt" import PROMPT_ANTHROPIC_SPOOF from "./prompt/anthropic_spoof.txt"
import PROMPT_SUMMARIZE from "./prompt/summarize.txt" import PROMPT_SUMMARIZE from "./prompt/summarize.txt"
import PROMPT_TITLE from "./prompt/title.txt" import PROMPT_TITLE from "./prompt/title.txt"
export namespace SystemPrompt { export namespace SystemPrompt {
export function provider(providerID: string) { export function provider(providerID: string, modelID: string) {
const result = [] if (providerID === "anthropic") return [PROMPT_ANTHROPIC_SPOOF.trim(), PROMPT_ANTHROPIC]
switch (providerID) { if (modelID.includes("gpt-")) return [PROMPT_BEAST]
case "anthropic": return [PROMPT_ANTHROPIC]
result.push(PROMPT_ANTHROPIC_SPOOF.trim())
result.push(PROMPT_ANTHROPIC)
break
default:
result.push(PROMPT_ANTHROPIC)
break
}
return result
} }
export async function environment() { export async function environment() {
+2
View File
@@ -16,6 +16,8 @@ export const IGNORE_PATTERNS = [
"obj/", "obj/",
".idea/", ".idea/",
".vscode/", ".vscode/",
".zig-cache/",
"zig-out",
] ]
const LIMIT = 100 const LIMIT = 100
+1 -1
View File
@@ -71,7 +71,7 @@ export const ReadTool = Tool.define({
output += "\n</file>" output += "\n</file>"
// just warms the lsp client // just warms the lsp client
await LSP.touchFile(filePath, false) LSP.touchFile(filePath, false)
FileTime.read(ctx.sessionID, filePath) FileTime.read(ctx.sessionID, filePath)
return { return {
@@ -96,7 +96,16 @@ func (m *editorComponent) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
case ".pdf": case ".pdf":
mediaType = "application/pdf" mediaType = "application/pdf"
default: default:
mediaType = "text/plain" attachment := &textarea.Attachment{
ID: uuid.NewString(),
Display: "@" + filePath,
URL: fmt.Sprintf("file://./%s", filePath),
Filename: filePath,
MediaType: "text/plain",
}
m.textarea.InsertAttachment(attachment)
m.textarea.InsertString(" ")
return m, nil
} }
fileBytes, err := os.ReadFile(filePath) fileBytes, err := os.ReadFile(filePath)
+19 -1
View File
@@ -7,6 +7,7 @@ import theme from "toolbeam-docs-theme"
import config from "./config.mjs" import config from "./config.mjs"
import { rehypeHeadingIds } from "@astrojs/markdown-remark" import { rehypeHeadingIds } from "@astrojs/markdown-remark"
import rehypeAutolinkHeadings from "rehype-autolink-headings" import rehypeAutolinkHeadings from "rehype-autolink-headings"
import { spawnSync } from "child_process"
const github = "https://github.com/sst/opencode" const github = "https://github.com/sst/opencode"
@@ -26,7 +27,9 @@ export default defineConfig({
markdown: { markdown: {
rehypePlugins: [rehypeHeadingIds, [rehypeAutolinkHeadings, { behavior: "wrap" }]], rehypePlugins: [rehypeHeadingIds, [rehypeAutolinkHeadings, { behavior: "wrap" }]],
}, },
build: {},
integrations: [ integrations: [
configSchema(),
solidJs(), solidJs(),
starlight({ starlight({
title: "opencode", title: "opencode",
@@ -60,11 +63,14 @@ export default defineConfig({
"docs", "docs",
"docs/cli", "docs/cli",
"docs/rules", "docs/rules",
"docs/share",
"docs/config", "docs/config",
"docs/models", "docs/models",
"docs/themes", "docs/themes",
"docs/keybinds", "docs/keybinds",
"docs/enterprise",
"docs/mcp-servers", "docs/mcp-servers",
"docs/troubleshooting",
], ],
components: { components: {
Hero: "./src/components/Hero.astro", Hero: "./src/components/Hero.astro",
@@ -79,6 +85,18 @@ export default defineConfig({
}), }),
], ],
redirects: { redirects: {
"/discord": "https://discord.gg/invite/opencode", "/discord": "https://discord.gg/opencode",
}, },
}) })
function configSchema() {
return {
name: "configSchema",
hooks: {
"astro:build:done": async () => {
console.log("generating config schema")
spawnSync("../opencode/script/schema.ts", ["./dist/config.json"])
},
},
}
}
+1 -1
View File
@@ -6,7 +6,7 @@ export default {
: `https://${stage}.opencode.ai`, : `https://${stage}.opencode.ai`,
socialCard: "https://social-cards.sst.dev", socialCard: "https://social-cards.sst.dev",
github: "https://github.com/sst/opencode", github: "https://github.com/sst/opencode",
discord: "https://discord.gg/opencode", discord: "https://opencode.ai/discord",
headerLinks: [ headerLinks: [
{ name: "Home", url: "/" }, { name: "Home", url: "/" },
{ name: "Docs", url: "/docs/" }, { name: "Docs", url: "/docs/" },
+1 -1
View File
@@ -30,7 +30,7 @@
"sharp": "0.32.5", "sharp": "0.32.5",
"shiki": "3.4.2", "shiki": "3.4.2",
"solid-js": "1.9.7", "solid-js": "1.9.7",
"toolbeam-docs-theme": "0.4.1" "toolbeam-docs-theme": "0.4.3"
}, },
"devDependencies": { "devDependencies": {
"opencode": "workspace:*", "opencode": "workspace:*",
-32
View File
@@ -1,32 +0,0 @@
import { type JSX, splitProps, createResource } from "solid-js"
import { codeToHtml } from "shiki"
import styles from "./codeblock.module.css"
import { transformerNotationDiff } from "@shikijs/transformers"
interface CodeBlockProps extends JSX.HTMLAttributes<HTMLDivElement> {
code: string
lang?: string
}
function CodeBlock(props: CodeBlockProps) {
const [local, rest] = splitProps(props, ["code", "lang"])
const [html] = createResource(
() => [local.code, local.lang],
async ([code, lang]) => {
// TODO: For testing delays
// await new Promise((resolve) => setTimeout(resolve, 3000))
return (await codeToHtml(code || "", {
lang: lang || "text",
themes: {
light: "github-light",
dark: "github-dark",
},
transformers: [transformerNotationDiff()],
})) as string
},
)
return <div innerHTML={html()} class={styles.codeblock} {...rest}></div>
}
export default CodeBlock
+40 -45
View File
@@ -1,13 +1,4 @@
import { import { For, Show, onMount, Suspense, onCleanup, createMemo, createSignal, SuspenseList } from "solid-js"
For,
Show,
onMount,
Suspense,
onCleanup,
createMemo,
createSignal,
SuspenseList,
} from "solid-js"
import { DateTime } from "luxon" import { DateTime } from "luxon"
import { createStore, reconcile } from "solid-js/store" import { createStore, reconcile } from "solid-js/store"
import { IconArrowDown } from "./icons" import { IconArrowDown } from "./icons"
@@ -277,7 +268,6 @@ export default function Share(props: {
} }
} }
} }
console.log(result.messages)
return result return result
}) })
@@ -327,42 +317,47 @@ export default function Share(props: {
<div class={styles.parts}> <div class={styles.parts}>
<SuspenseList revealOrder="forwards"> <SuspenseList revealOrder="forwards">
<For each={data().messages}> <For each={data().messages}>
{(msg, msgIndex) => ( {(msg, msgIndex) => {
<Suspense> const filteredParts = createMemo(() =>
<For msg.parts.filter((x, index) => {
each={msg.parts.filter((x, index) => { if (x.type === "step-start" && index > 0) return false
if (x.type === "step-start" && index > 0) return false if (x.type === "tool" && x.tool === "todoread") return false
if (x.type === "tool" && x.tool === "todoread") return false if (x.type === "text" && !x.text) return false
if (x.type === "text" && !x.text) return false if (x.type === "tool" && (x.state.status === "pending" || x.state.status === "running"))
if (x.type === "tool" && (x.state.status === "pending" || x.state.status === "running")) return false
return false return true
return true })
})} )
>
{(part, partIndex) => {
const last = createMemo(
() => data().messages.length === msgIndex() + 1 && msg.parts.length === partIndex() + 1,
)
onMount(() => { return (
const hash = window.location.hash.slice(1) <Suspense>
// Wait till all parts are loaded <For each={filteredParts()}>
if ( {(part, partIndex) => {
hash !== "" && const last = createMemo(
!hasScrolledToAnchor && () =>
msg.parts.length === partIndex() + 1 && data().messages.length === msgIndex() + 1 && filteredParts().length === partIndex() + 1,
data().messages.length === msgIndex() + 1 )
) {
hasScrolledToAnchor = true
scrollToAnchor(hash)
}
})
return <Part last={last()} part={part} index={partIndex()} message={msg} /> onMount(() => {
}} const hash = window.location.hash.slice(1)
</For> // Wait till all parts are loaded
</Suspense> if (
)} hash !== "" &&
!hasScrolledToAnchor &&
filteredParts().length === partIndex() + 1 &&
data().messages.length === msgIndex() + 1
) {
hasScrolledToAnchor = true
scrollToAnchor(hash)
}
})
return <Part last={last()} part={part} index={partIndex()} message={msg} />
}}
</For>
</Suspense>
)
}}
</For> </For>
</SuspenseList> </SuspenseList>
<div data-section="part" data-part-type="summary"> <div data-section="part" data-part-type="summary">
@@ -1,10 +0,0 @@
.codeblock {
pre {
--shiki-dark-bg: var(--sl-color-bg-surface) !important;
background-color: var(--sl-color-bg-surface) !important;
span {
white-space: break-spaces;
}
}
}
@@ -127,3 +127,5 @@ The opencode CLI takes the following flags.
| `--help` | `-h` | Display help | | `--help` | `-h` | Display help |
| `--version` | | Print version number | | `--version` | | Print version number |
| `--print-logs` | | Print logs to stderr | | `--print-logs` | | Print logs to stderr |
| `--prompt` | `-p` | Prompt to use |
| `--model` | `-m` | Model to use in the form of provider/model |
+30 -22
View File
@@ -63,6 +63,36 @@ You can configure the theme you want to use in your opencode config through the
--- ---
### Logging
Logs are written to:
- **macOS/Linux**: `~/.local/share/opencode/log/`
- **Windows**: `%APPDATA%\opencode\log\`
You can configure the minimum log level through the `log_level` option.
```json title="opencode.json"
{
"$schema": "https://opencode.ai/config.json",
"log_level": "INFO"
}
```
With the following options:
| Level | Description |
| ----- | ----------- |
| `DEBUG` | All messages including debug information |
| `INFO` | Informational messages and above |
| `WARN` | Warnings and errors only |
| `ERROR` | Errors only |
The **default** log level is `INFO`. If you are running opencode locally in
development mode it's set to `DEBUG`.
---
### Keybinds ### Keybinds
You can customize your keybinds through the `keybinds` option. You can customize your keybinds through the `keybinds` option.
@@ -93,28 +123,6 @@ You can configure MCP servers you want to use through the `mcp` option.
--- ---
### Logging
You can configure the minimum log level through the `log_level` option. This controls which log messages are written to the log files.
```json title="opencode.json"
{
"$schema": "https://opencode.ai/config.json",
"log_level": "INFO"
}
```
Available log levels are:
- `DEBUG` - All messages including debug information
- `INFO` - Informational messages and above (default)
- `WARN` - Warnings and errors only
- `ERROR` - Errors only
The default log level is `INFO` in production and `DEBUG` in development mode.
---
### Disabled providers ### Disabled providers
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. 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.
@@ -0,0 +1,63 @@
---
title: Enterprise
description: Using opencode in your organization.
---
opencode does not store any of your code or context data. This makes it easy for
you to use opencode at your organization.
To get started, we recommend:
1. Do a trial internally with your team.
2. [**Contact us**](mailto:hello@sst.dev) to discuss pricing and implementation options.
---
## Trial
Since opencode is open source and does not store any of your code or context data, your developers can simply [get started](/docs/) and carry out a trial.
---
### Data handling
**opencode does not store your code or context data.** All processing happens locally or through direct API calls to your AI provider.
The only caveat here is the optional `/share` feature that must be manually enabled.
---
#### Sharing conversations
If a user enables the `/share` feature, the conversation and the data associated with it are sent to the service we use to host these shares pages at opencode.ai.
The data is currently served through our CDN's edge network, and is cached on the edge near your users.
---
### Code ownership
**You own all code produced by opencode.** There are no licensing restrictions or ownership claims.
---
## Deployment
Once you have completed your trial and you are ready to self-host opencode at
your organization, you can [**contact us**](mailto:hello@sst.dev) to discuss
pricing and implementation options.
---
### SSO
SSO integration can be implemented for enterprise deployments after your trial. Currently users manage and configure individual API keys locally.
This can be switched to a centralized authentication system that your organization uses.
---
### Self-hosting
The share feature can be self-hosted and the share pages can be made accessible
only after the user has been authenticated.
@@ -0,0 +1,103 @@
---
title: Share
description: Share your opencode conversations.
---
opencode's share feature allows you to create public links to your opencode conversations, so you can collaborate with teammates or get help from others.
:::note
Shared conversations are publicly accessible to anyone with the link.
:::
---
## How it works
When you share a conversation, opencode:
1. Creates a unique public URL for your session
2. Syncs your conversation history to our servers
3. Makes the conversation accessible via the shareable link — `opencode.ai/s/<share-id>`
---
## Sharing
You can manually share a conversation or enable automatic sharing for all new conversations.
---
### Manual
Use the `/share` command in any conversation to create a shareable link:
```
/share
```
This will generate a unique URL that'll be copied to your clipboard.
---
### Autoshare
You can enable automatic sharing for all new conversations through the `autoshare` option in your [config file](/docs/config).
```json title="opencode.json"
{
"$schema": "https://opencode.ai/config.json",
"autoshare": true
}
```
By default, `autoshare` is disabled.
---
## Unsharing
To stop sharing a conversation and remove it from public access:
```
/unshare
```
This will remove the share link and delete the data related to the conversation.
---
## Privacy
There are a few things to keep in mind when sharing a conversation.
---
### Data retention
Shared conversations remain accessible until you explicitly unshare them. This
includes:
- Full conversation history
- All messages and responses
- Session metadata
---
### Recommendations
- Only share conversations that don't contain sensitive information
- Review conversation content before sharing
- Unshare conversations when collaboration is complete
- Avoid sharing conversations with proprietary code or confidential data
---
## For enterprises
For enterprise deployments, the share feature can be:
- **Self-hosted** on your own infrastructure
- **Restricted** to authenticated users only
- **Disabled** entirely for security compliance
[Learn more](/docs/enterprise) about using opencode in your organization.
@@ -0,0 +1,87 @@
---
title: Troubleshooting
description: Common issues and how to resolve them.
---
To debug any issues with opencode, you can check the logs or the session data
that it stores locally.
---
### Logs
Log files are written to:
- **macOS/Linux**: `~/.local/share/opencode/log/`
- **Windows**: `%APPDATA%\opencode\log\`
Log files are named with timestamps (e.g., `2025-01-09T123456.log`) and the most recent 10 log files are kept.
You can configure the log level in your [config file](/docs/config#logging) to get more detailed debug information.
---
### Storage
opencode stores session data and other application data on disk at:
- **macOS/Linux**: `~/.local/share/opencode/`
- **Windows**: `%APPDATA%\opencode\`
This directory contains:
- `auth.json` - Authentication data like API keys, OAuth tokens
- `log/` - Application logs
- `project/` - Project-specific data like session and message data
- If the project is within a Git repo, it is stored in `./<project-slug>/storage/`
- If it is not a Git repo, it is stored in `./global/storage/`
---
## Getting help
If you're experiencing issues with opencode:
1. **Report issues on GitHub**
The best way to report bugs or request features is through our GitHub repository:
[**github.com/sst/opencode/issues**](https://github.com/sst/opencode/issues)
Before creating a new issue, search existing issues to see if your problem has already been reported.
2. **Join our Discord**
For real-time help and community discussion, join our Discord server:
[**opencode.ai/discord**](https://opencode.ai/discord)
---
## Common issues
Here are some common issues and how to resolve them.
---
### opencode won't start
1. Check the logs for error messages
2. Try running with `--print-logs` to see output in the terminal
3. Ensure you have the latest version with `opencode upgrade`
---
### Authentication issues
1. Try re-authenticating with `opencode auth login <provider>`
2. Check that your API keys are valid
3. Ensure your network allows connections to the provider's API
---
### Model not available
1. Check that you've authenticated with the provider
2. Verify the model name in your config is correct
3. Some models may require specific access or subscriptions