Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3728a12bee | ||
|
|
af07e51213 | ||
|
|
3113788c92 | ||
|
|
efb5fe6d4e | ||
|
|
54dd6c644d | ||
|
|
39ad8f2667 | ||
|
|
c4a2c84e53 | ||
|
|
44fe012812 | ||
|
|
f5e7f079ea | ||
|
|
15a8936806 | ||
|
|
4e4cff49c0 | ||
|
|
5540503bee | ||
|
|
193718034b | ||
|
|
72108c0296 |
@@ -8,6 +8,7 @@
|
||||
</a>
|
||||
</p>
|
||||
<p align="center">
|
||||
<a href="https://opencode.ai/docs"><img alt="View docs" src="https://img.shields.io/badge/view-docs-blue?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>
|
||||
</p>
|
||||
@@ -16,9 +17,7 @@
|
||||
|
||||
AI coding agent, built for the terminal.
|
||||
|
||||
**Note:** Version 0.1.x is a full rewrite, and we do not have proper documentation for it yet. Should have this out week of June 17th 2025.
|
||||
|
||||
[](https://opencode.ai)
|
||||
[](https://opencode.ai)
|
||||
|
||||
### Installation
|
||||
|
||||
@@ -34,127 +33,9 @@ paru -S opencode-bin # Arch Linux
|
||||
|
||||
> **Note:** Remove versions older than 0.1.x before installing
|
||||
|
||||
### Providers
|
||||
### Documentation
|
||||
|
||||
The recommended approach is to sign up for Claude Pro or Max, run `opencode auth login`, and select Anthropic. It's the most cost-effective way to use opencode.
|
||||
|
||||
opencode is powered by the provider list at [Models.dev](https://models.dev), so you can use `opencode auth login` to configure API keys for any provider you'd like to use. This is stored in `~/.local/share/opencode/auth.json`.
|
||||
|
||||
```bash
|
||||
$ opencode auth login
|
||||
|
||||
┌ Add credential
|
||||
│
|
||||
◆ Select provider
|
||||
│ ● Anthropic (recommended)
|
||||
│ ○ OpenAI
|
||||
│ ○ Google
|
||||
│ ○ Amazon Bedrock
|
||||
│ ○ Azure
|
||||
│ ○ DeepSeek
|
||||
│ ○ Groq
|
||||
│ ...
|
||||
└
|
||||
```
|
||||
|
||||
The Models.dev dataset is also used to detect common environment variables like `OPENAI_API_KEY` to autoload that provider.
|
||||
|
||||
If there are additional providers you want to use you can submit a PR to the [Models.dev repo](https://github.com/sst/models.dev). If configuring just for yourself check out the Config section below.
|
||||
|
||||
### Config
|
||||
|
||||
Config is optional and can be placed in the root of your repo or globally in `~/.config/opencode/config.json`. It can be checked in and shared with your team.
|
||||
|
||||
```json title="opencode.json"
|
||||
{
|
||||
"$schema": "http://opencode.ai/config.json"
|
||||
"theme": "opencode",
|
||||
"model": "anthropic/claude-sonnet-4-20250514" // format is provider/model
|
||||
"autoshare": false,
|
||||
"autoupdate": true,
|
||||
}
|
||||
```
|
||||
|
||||
#### Keybinds
|
||||
|
||||
You can configure custom keybinds, the values listed below are the defaults.
|
||||
|
||||
```json title="opencode.json"
|
||||
{
|
||||
"$schema": "http://opencode.ai/config.json",
|
||||
"keybinds": {
|
||||
"leader": "ctrl+x",
|
||||
"help": "<leader>h",
|
||||
"editor_open": "<leader>e",
|
||||
"session_new": "<leader>n",
|
||||
"session_list": "<leader>l",
|
||||
"session_share": "<leader>s",
|
||||
"session_interrupt": "esc",
|
||||
"session_compact": "<leader>c",
|
||||
"tool_details": "<leader>d",
|
||||
"model_list": "<leader>m",
|
||||
"theme_list": "<leader>t",
|
||||
"project_init": "<leader>i",
|
||||
"input_clear": "ctrl+c",
|
||||
"input_paste": "ctrl+v",
|
||||
"input_submit": "enter",
|
||||
"input_newline": "shift+enter,ctrl+j",
|
||||
"history_previous": "up",
|
||||
"history_next": "down",
|
||||
"messages_page_up": "pgup",
|
||||
"messages_page_down": "pgdown",
|
||||
"messages_half_page_up": "ctrl+alt+u",
|
||||
"messages_half_page_down": "ctrl+alt+d",
|
||||
"messages_previous": "ctrl+alt+k",
|
||||
"messages_next": "ctrl+alt+j",
|
||||
"messages_first": "ctrl+g",
|
||||
"messages_last": "ctrl+alt+g",
|
||||
"app_exit": "ctrl+c,<leader>q"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### MCP
|
||||
|
||||
```json title="opencode.json"
|
||||
{
|
||||
"$schema": "http://opencode.ai/config.json",
|
||||
"mcp": {
|
||||
"localmcp": {
|
||||
"type": "local",
|
||||
"command": ["bun", "x", "my-mcp-command"],
|
||||
"environment": {
|
||||
"MY_ENV_VAR": "my_env_var_value"
|
||||
}
|
||||
},
|
||||
"remotemcp": {
|
||||
"type": "remote",
|
||||
"url": "https://my-mcp-server.com"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Providers
|
||||
|
||||
You can use opencode with any provider listed at [here](https://ai-sdk.dev/providers/ai-sdk-providers). Be sure to specify the npm package to use to load the provider. Remember most popular providers are preloaded from [models.dev](https://models.dev)
|
||||
|
||||
```json title="opencode.json"
|
||||
{
|
||||
"$schema": "https://opencode.ai/config.json",
|
||||
"provider": {
|
||||
"ollama": {
|
||||
"npm": "@ai-sdk/openai-compatible",
|
||||
"options": {
|
||||
"baseURL": "http://localhost:11434/v1"
|
||||
},
|
||||
"models": {
|
||||
"llama2": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
For more info on how to configure opencode [**head over to our docs**](https://opencode.ai/docs).
|
||||
|
||||
### Contributing
|
||||
|
||||
@@ -163,7 +44,7 @@ To run opencode locally you need.
|
||||
- Bun
|
||||
- Golang 1.24.x
|
||||
|
||||
To run.
|
||||
And run.
|
||||
|
||||
```bash
|
||||
$ bun install
|
||||
|
||||
+3
-3
@@ -23,9 +23,9 @@ export const api = new sst.cloudflare.Worker("Api", {
|
||||
},
|
||||
])
|
||||
args.migrations = {
|
||||
oldTag: "v1",
|
||||
newTag: "v1",
|
||||
//newSqliteClasses: ["SyncServer"],
|
||||
// oldTag: "v1",
|
||||
// newTag: "v1",
|
||||
newSqliteClasses: ["SyncServer"],
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
@@ -9,14 +9,14 @@ import { map, pipe, sortBy, values } from "remeda"
|
||||
|
||||
export const AuthCommand = cmd({
|
||||
command: "auth",
|
||||
describe: "manage credentials",
|
||||
describe: "Manage credentials",
|
||||
builder: (yargs) =>
|
||||
yargs
|
||||
.command(AuthLoginCommand)
|
||||
.command(AuthLogoutCommand)
|
||||
.command(AuthListCommand)
|
||||
.demandCommand(),
|
||||
async handler() {},
|
||||
async handler() { },
|
||||
})
|
||||
|
||||
export const AuthListCommand = cmd({
|
||||
|
||||
@@ -20,7 +20,7 @@ const TOOL: Record<string, [string, string]> = {
|
||||
list: ["List", UI.Style.TEXT_INFO_BOLD],
|
||||
read: ["Read", UI.Style.TEXT_HIGHLIGHT_BOLD],
|
||||
write: ["Write", UI.Style.TEXT_SUCCESS_BOLD],
|
||||
websearch: ["Search", UI.Style.TEXT_MUTED_BOLD],
|
||||
websearch: ["Search", UI.Style.TEXT_DIM_BOLD],
|
||||
}
|
||||
|
||||
export const RunCommand = cmd({
|
||||
@@ -29,7 +29,7 @@ export const RunCommand = cmd({
|
||||
builder: (yargs: Argv) => {
|
||||
return yargs
|
||||
.positional("message", {
|
||||
describe: "Message to send",
|
||||
describe: "message to send",
|
||||
type: "string",
|
||||
array: true,
|
||||
default: [],
|
||||
@@ -46,7 +46,7 @@ export const RunCommand = cmd({
|
||||
})
|
||||
.option("share", {
|
||||
type: "boolean",
|
||||
describe: "Share the session",
|
||||
describe: "share the session",
|
||||
})
|
||||
.option("model", {
|
||||
type: "string",
|
||||
@@ -90,8 +90,8 @@ export const RunCommand = cmd({
|
||||
await Session.share(session.id)
|
||||
UI.println(
|
||||
UI.Style.TEXT_INFO_BOLD +
|
||||
"~ https://opencode.ai/s/" +
|
||||
session.id.slice(-8),
|
||||
"~ https://opencode.ai/s/" +
|
||||
session.id.slice(-8),
|
||||
)
|
||||
}
|
||||
UI.empty()
|
||||
@@ -109,8 +109,8 @@ export const RunCommand = cmd({
|
||||
UI.println(
|
||||
color + `|`,
|
||||
UI.Style.TEXT_NORMAL +
|
||||
UI.Style.TEXT_DIM +
|
||||
` ${type.padEnd(7, " ")}`,
|
||||
UI.Style.TEXT_DIM +
|
||||
` ${type.padEnd(7, " ")}`,
|
||||
"",
|
||||
UI.Style.TEXT_NORMAL + title,
|
||||
)
|
||||
|
||||
@@ -5,10 +5,10 @@ import { Installation } from "../../installation"
|
||||
|
||||
export const UpgradeCommand = {
|
||||
command: "upgrade [target]",
|
||||
describe: "Upgrade opencode to the latest version or a specific version",
|
||||
describe: "upgrade opencode to the latest version or a specific version",
|
||||
builder: (yargs: Argv) => {
|
||||
return yargs.positional("target", {
|
||||
describe: "Specific version to upgrade to (e.g., '0.1.48' or 'v0.1.48')",
|
||||
describe: "specific version to upgrade to (e.g., '0.1.48' or 'v0.1.48')",
|
||||
type: "string",
|
||||
})
|
||||
},
|
||||
|
||||
@@ -36,7 +36,7 @@ const cli = yargs(hideBin(process.argv))
|
||||
.usage("\n" + UI.logo())
|
||||
.command({
|
||||
command: "$0 [project]",
|
||||
describe: "Start opencode TUI",
|
||||
describe: "start opencode TUI",
|
||||
builder: (yargs) =>
|
||||
yargs.positional("project", {
|
||||
type: "string",
|
||||
@@ -84,21 +84,21 @@ const cli = yargs(hideBin(process.argv))
|
||||
},
|
||||
})
|
||||
|
||||
;(async () => {
|
||||
if (Installation.VERSION === "dev") return
|
||||
if (Installation.isSnapshot()) return
|
||||
const config = await Config.global()
|
||||
if (config.autoupdate === false) return
|
||||
const latest = await Installation.latest()
|
||||
if (Installation.VERSION === latest) return
|
||||
const method = await Installation.method()
|
||||
if (method === "unknown") return
|
||||
await Installation.upgrade(method, latest)
|
||||
.then(() => {
|
||||
Bus.publish(Installation.Event.Updated, { version: latest })
|
||||
})
|
||||
.catch(() => {})
|
||||
})()
|
||||
; (async () => {
|
||||
if (Installation.VERSION === "dev") return
|
||||
if (Installation.isSnapshot()) return
|
||||
const config = await Config.global()
|
||||
if (config.autoupdate === false) return
|
||||
const latest = await Installation.latest()
|
||||
if (Installation.VERSION === latest) return
|
||||
const method = await Installation.method()
|
||||
if (method === "unknown") return
|
||||
await Installation.upgrade(method, latest)
|
||||
.then(() => {
|
||||
Bus.publish(Installation.Event.Updated, { version: latest })
|
||||
})
|
||||
.catch(() => { })
|
||||
})()
|
||||
|
||||
await proc.exited
|
||||
server.stop()
|
||||
|
||||
@@ -23,7 +23,7 @@ type App struct {
|
||||
Info client.AppInfo
|
||||
Version string
|
||||
StatePath string
|
||||
Configg *client.ConfigInfo
|
||||
Config *client.ConfigInfo
|
||||
Client *client.ClientWithResponses
|
||||
State *config.State
|
||||
Provider *client.ProviderInfo
|
||||
@@ -95,7 +95,7 @@ func New(
|
||||
Info: appInfo,
|
||||
Version: version,
|
||||
StatePath: appStatePath,
|
||||
Configg: configInfo,
|
||||
Config: configInfo,
|
||||
State: appState,
|
||||
Client: httpClient,
|
||||
Session: &client.SessionInfo{},
|
||||
|
||||
@@ -186,7 +186,7 @@ func LoadFromConfig(config *client.ConfigInfo) CommandRegistry {
|
||||
},
|
||||
{
|
||||
Name: ProjectInitCommand,
|
||||
Description: "create or update AGENTS.md",
|
||||
Description: "create/update AGENTS.md",
|
||||
Keybindings: parseBindings("<leader>i"),
|
||||
Trigger: "init",
|
||||
},
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package chat
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
@@ -252,6 +253,7 @@ func renderToolInvocation(
|
||||
metadata client.MessageInfo_Metadata_Tool_AdditionalProperties,
|
||||
showDetails bool,
|
||||
isLast bool,
|
||||
contentOnly bool,
|
||||
) string {
|
||||
ignoredTools := []string{"todoread"}
|
||||
if slices.Contains(ignoredTools, toolCall.ToolName) {
|
||||
@@ -305,14 +307,19 @@ func renderToolInvocation(
|
||||
toolArgsMap := make(map[string]any)
|
||||
if toolCall.Args != nil {
|
||||
value := *toolCall.Args
|
||||
m, ok := value.(map[string]any)
|
||||
if ok {
|
||||
if m, ok := value.(map[string]any); ok {
|
||||
toolArgsMap = m
|
||||
firstKey := ""
|
||||
|
||||
keys := make([]string, 0, len(toolArgsMap))
|
||||
for key := range toolArgsMap {
|
||||
firstKey = key
|
||||
break
|
||||
keys = append(keys, key)
|
||||
}
|
||||
slices.Sort(keys)
|
||||
firstKey := ""
|
||||
if len(keys) > 0 {
|
||||
firstKey = keys[0]
|
||||
}
|
||||
|
||||
toolArgs = renderArgs(&toolArgsMap, firstKey)
|
||||
}
|
||||
}
|
||||
@@ -337,22 +344,11 @@ func renderToolInvocation(
|
||||
}
|
||||
}
|
||||
|
||||
elapsed := ""
|
||||
start := metadata.Time.Start
|
||||
end := metadata.Time.End
|
||||
durationMs := end - start
|
||||
duration := time.Duration(durationMs * float32(time.Millisecond))
|
||||
roundedDuration := time.Duration(duration.Round(time.Millisecond))
|
||||
if durationMs > 1000 {
|
||||
roundedDuration = time.Duration(duration.Round(time.Second))
|
||||
}
|
||||
elapsed = styles.Muted().Render(roundedDuration.String())
|
||||
|
||||
title := ""
|
||||
switch toolCall.ToolName {
|
||||
case "read":
|
||||
toolArgs = renderArgs(&toolArgsMap, "filePath")
|
||||
title = fmt.Sprintf("READ %s %s", toolArgs, elapsed)
|
||||
title = fmt.Sprintf("READ %s", toolArgs)
|
||||
if preview, ok := metadata.Get("preview"); ok && toolArgsMap["filePath"] != nil {
|
||||
filename := toolArgsMap["filePath"].(string)
|
||||
body = preview.(string)
|
||||
@@ -360,7 +356,7 @@ func renderToolInvocation(
|
||||
}
|
||||
case "edit":
|
||||
if filename, ok := toolArgsMap["filePath"].(string); ok {
|
||||
title = fmt.Sprintf("EDIT %s %s", relative(filename), elapsed)
|
||||
title = fmt.Sprintf("EDIT %s", relative(filename))
|
||||
if d, ok := metadata.Get("diff"); ok {
|
||||
patch := d.(string)
|
||||
var formattedDiff string
|
||||
@@ -401,14 +397,14 @@ func renderToolInvocation(
|
||||
}
|
||||
case "write":
|
||||
if filename, ok := toolArgsMap["filePath"].(string); ok {
|
||||
title = fmt.Sprintf("WRITE %s %s", relative(filename), elapsed)
|
||||
title = fmt.Sprintf("WRITE %s", relative(filename))
|
||||
if content, ok := toolArgsMap["content"].(string); ok {
|
||||
body = renderFile(filename, content)
|
||||
}
|
||||
}
|
||||
case "bash":
|
||||
if description, ok := toolArgsMap["description"].(string); ok {
|
||||
title = fmt.Sprintf("SHELL %s %s", description, elapsed)
|
||||
title = fmt.Sprintf("SHELL %s", description)
|
||||
}
|
||||
if stdout, ok := metadata.Get("stdout"); ok {
|
||||
command := toolArgsMap["command"].(string)
|
||||
@@ -419,7 +415,7 @@ func renderToolInvocation(
|
||||
}
|
||||
case "webfetch":
|
||||
toolArgs = renderArgs(&toolArgsMap, "url")
|
||||
title = fmt.Sprintf("FETCH %s %s", toolArgs, elapsed)
|
||||
title = fmt.Sprintf("FETCH %s", toolArgs)
|
||||
if format, ok := toolArgsMap["format"].(string); ok {
|
||||
body = *result
|
||||
body = truncateHeight(body, 10)
|
||||
@@ -429,7 +425,7 @@ func renderToolInvocation(
|
||||
body = renderContentBlock(body, WithFullWidth(), WithMarginBottom(1))
|
||||
}
|
||||
case "todowrite":
|
||||
title = fmt.Sprintf("PLAN %s", elapsed)
|
||||
title = fmt.Sprintf("PLAN")
|
||||
|
||||
if to, ok := metadata.Get("todos"); ok && finished {
|
||||
todos := to.([]any)
|
||||
@@ -448,14 +444,60 @@ func renderToolInvocation(
|
||||
body = toMarkdown(body, innerWidth, t.BackgroundSubtle())
|
||||
body = renderContentBlock(body, WithFullWidth(), WithMarginBottom(1))
|
||||
}
|
||||
case "task":
|
||||
if description, ok := toolArgsMap["description"].(string); ok {
|
||||
title = fmt.Sprintf("TASK %s", description)
|
||||
if summary, ok := metadata.Get("summary"); ok {
|
||||
toolcalls := summary.([]any)
|
||||
// toolcalls :=
|
||||
|
||||
steps := []string{}
|
||||
for _, toolcall := range toolcalls {
|
||||
call := toolcall.(map[string]any)
|
||||
if toolInvocation, ok := call["toolInvocation"].(map[string]any); ok {
|
||||
data, _ := json.Marshal(toolInvocation)
|
||||
var toolCall client.MessageToolInvocationToolCall
|
||||
_ = json.Unmarshal(data, &toolCall)
|
||||
|
||||
if metadata, ok := call["metadata"].(map[string]any); ok {
|
||||
data, _ = json.Marshal(metadata)
|
||||
var toolMetadata client.MessageInfo_Metadata_Tool_AdditionalProperties
|
||||
_ = json.Unmarshal(data, &toolMetadata)
|
||||
|
||||
step := renderToolInvocation(
|
||||
toolCall,
|
||||
nil,
|
||||
toolMetadata,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
)
|
||||
steps = append(steps, step)
|
||||
}
|
||||
}
|
||||
}
|
||||
body = strings.Join(steps, "\n")
|
||||
body = renderContentBlock(body, WithFullWidth(), WithMarginBottom(1))
|
||||
}
|
||||
}
|
||||
|
||||
default:
|
||||
toolName := renderToolName(toolCall.ToolName)
|
||||
title = fmt.Sprintf("%s %s %s", toolName, toolArgs, elapsed)
|
||||
title = fmt.Sprintf("%s %s", toolName, toolArgs)
|
||||
if result == nil {
|
||||
empty := ""
|
||||
result = &empty
|
||||
}
|
||||
body = *result
|
||||
body = truncateHeight(body, 10)
|
||||
body = renderContentBlock(body, WithFullWidth(), WithMarginBottom(1))
|
||||
}
|
||||
|
||||
if contentOnly {
|
||||
title = "∟ " + title
|
||||
return title
|
||||
}
|
||||
|
||||
if !showDetails {
|
||||
title = "∟ " + title
|
||||
padding := calculatePadding()
|
||||
@@ -496,8 +538,6 @@ func renderToolInvocation(
|
||||
|
||||
func renderToolName(name string) string {
|
||||
switch name {
|
||||
// case agent.AgentToolName:
|
||||
// return "Task"
|
||||
case "list":
|
||||
return "LIST"
|
||||
case "webfetch":
|
||||
@@ -557,8 +597,8 @@ func renderFile(filename string, content string, options ...fileRenderingOption)
|
||||
|
||||
func renderToolAction(name string) string {
|
||||
switch name {
|
||||
// case agent.AgentToolName:
|
||||
// return "Preparing prompt..."
|
||||
case "task":
|
||||
return "Searching..."
|
||||
case "bash":
|
||||
return "Building command..."
|
||||
case "edit":
|
||||
@@ -589,9 +629,17 @@ func renderArgs(args *map[string]any, titleKey string) string {
|
||||
if args == nil || len(*args) == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
keys := make([]string, 0, len(*args))
|
||||
for key := range *args {
|
||||
keys = append(keys, key)
|
||||
}
|
||||
slices.Sort(keys)
|
||||
|
||||
title := ""
|
||||
parts := []string{}
|
||||
for key, value := range *args {
|
||||
for _, key := range keys {
|
||||
value := (*args)[key]
|
||||
if value == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
tea "github.com/charmbracelet/bubbletea/v2"
|
||||
"github.com/charmbracelet/lipgloss/v2"
|
||||
"github.com/sst/opencode/internal/app"
|
||||
"github.com/sst/opencode/internal/components/commands"
|
||||
"github.com/sst/opencode/internal/components/dialog"
|
||||
"github.com/sst/opencode/internal/layout"
|
||||
"github.com/sst/opencode/internal/styles"
|
||||
@@ -37,6 +38,7 @@ type messagesComponent struct {
|
||||
viewport viewport.Model
|
||||
spinner spinner.Model
|
||||
attachments viewport.Model
|
||||
commands commands.CommandsComponent
|
||||
cache *MessageCache
|
||||
rendering bool
|
||||
showToolDetails bool
|
||||
@@ -46,7 +48,7 @@ type renderFinishedMsg struct{}
|
||||
type ToggleToolDetailsMsg struct{}
|
||||
|
||||
func (m *messagesComponent) Init() tea.Cmd {
|
||||
return tea.Batch(m.viewport.Init(), m.spinner.Tick)
|
||||
return tea.Batch(m.viewport.Init(), m.spinner.Tick, m.commands.Init())
|
||||
}
|
||||
|
||||
func (m *messagesComponent) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
@@ -93,6 +95,11 @@ func (m *messagesComponent) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
spinner, cmd := m.spinner.Update(msg)
|
||||
m.spinner = spinner
|
||||
cmds = append(cmds, cmd)
|
||||
|
||||
updated, cmd := m.commands.Update(msg)
|
||||
m.commands = updated.(commands.CommandsComponent)
|
||||
cmds = append(cmds, cmd)
|
||||
|
||||
return m, tea.Batch(cmds...)
|
||||
}
|
||||
|
||||
@@ -191,6 +198,7 @@ func (m *messagesComponent) renderView() {
|
||||
metadata,
|
||||
m.showToolDetails,
|
||||
isLastToolInvocation,
|
||||
false,
|
||||
)
|
||||
m.cache.Set(key, content)
|
||||
}
|
||||
@@ -202,6 +210,7 @@ func (m *messagesComponent) renderView() {
|
||||
metadata,
|
||||
m.showToolDetails,
|
||||
isLastToolInvocation,
|
||||
false,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -279,7 +288,13 @@ func (m *messagesComponent) View() string {
|
||||
return m.home()
|
||||
}
|
||||
if m.rendering {
|
||||
return m.viewport.View()
|
||||
return lipgloss.Place(
|
||||
m.width,
|
||||
m.height,
|
||||
lipgloss.Center,
|
||||
lipgloss.Center,
|
||||
"Loading session...",
|
||||
)
|
||||
}
|
||||
t := theme.CurrentTheme()
|
||||
return lipgloss.JoinVertical(
|
||||
@@ -317,50 +332,42 @@ func (m *messagesComponent) home() string {
|
||||
// cwd := app.Info.Path.Cwd
|
||||
// config := app.Info.Path.Config
|
||||
|
||||
commands := [][]string{
|
||||
{"/help", "show help"},
|
||||
{"/sessions", "list sessions"},
|
||||
{"/new", "start a new session"},
|
||||
{"/model", "switch model"},
|
||||
{"/theme", "switch theme"},
|
||||
{"/exit", "exit the app"},
|
||||
}
|
||||
versionStyle := lipgloss.NewStyle().
|
||||
Background(t.Background()).
|
||||
Foreground(t.TextMuted()).
|
||||
Width(lipgloss.Width(logo)).
|
||||
Align(lipgloss.Right)
|
||||
version := versionStyle.Render(m.app.Version)
|
||||
|
||||
commandLines := []string{}
|
||||
for _, command := range commands {
|
||||
commandLines = append(commandLines, (base(command[0]+" ") + muted(command[1])))
|
||||
}
|
||||
|
||||
logoAndVersion := lipgloss.JoinVertical(
|
||||
lipgloss.Right,
|
||||
logo,
|
||||
muted(m.app.Version),
|
||||
logoAndVersion := strings.Join([]string{logo, version}, "\n")
|
||||
logoAndVersion = lipgloss.PlaceHorizontal(
|
||||
m.width,
|
||||
lipgloss.Center,
|
||||
logoAndVersion,
|
||||
lipgloss.WithWhitespaceStyle(lipgloss.NewStyle().Background(t.Background())),
|
||||
)
|
||||
commands := lipgloss.PlaceHorizontal(
|
||||
m.width,
|
||||
lipgloss.Center,
|
||||
m.commands.View(),
|
||||
lipgloss.WithWhitespaceStyle(lipgloss.NewStyle().Background(t.Background())),
|
||||
)
|
||||
|
||||
lines := []string{}
|
||||
lines = append(lines, "")
|
||||
lines = append(lines, "")
|
||||
lines = append(lines, logoAndVersion)
|
||||
lines = append(lines, "")
|
||||
lines = append(lines, "")
|
||||
// lines = append(lines, base("cwd ")+muted(cwd))
|
||||
// lines = append(lines, base("config ")+muted(config))
|
||||
// lines = append(lines, "")
|
||||
lines = append(lines, commandLines...)
|
||||
lines = append(lines, "")
|
||||
if m.rendering {
|
||||
lines = append(lines, base("Loading session..."))
|
||||
} else {
|
||||
lines = append(lines, "")
|
||||
}
|
||||
lines = append(lines, commands)
|
||||
|
||||
return lipgloss.Place(
|
||||
m.width,
|
||||
m.height,
|
||||
lipgloss.Center,
|
||||
lipgloss.Center,
|
||||
baseStyle.Width(lipgloss.Width(logoAndVersion)).Render(
|
||||
strings.Join(lines, "\n"),
|
||||
),
|
||||
baseStyle.Render(strings.Join(lines, "\n")),
|
||||
lipgloss.WithWhitespaceStyle(lipgloss.NewStyle().Background(t.Background())),
|
||||
)
|
||||
}
|
||||
@@ -379,6 +386,7 @@ func (m *messagesComponent) SetSize(width, height int) tea.Cmd {
|
||||
m.viewport.SetHeight(height - lipgloss.Height(m.header()))
|
||||
m.attachments.SetWidth(width + 40)
|
||||
m.attachments.SetHeight(3)
|
||||
m.commands.SetSize(width, height)
|
||||
m.renderView()
|
||||
return nil
|
||||
}
|
||||
@@ -442,11 +450,19 @@ func NewMessagesComponent(app *app.App) MessagesComponent {
|
||||
attachments := viewport.New()
|
||||
vp.KeyMap = viewport.KeyMap{}
|
||||
|
||||
t := theme.CurrentTheme()
|
||||
commandsView := commands.New(
|
||||
app,
|
||||
commands.WithBackground(t.Background()),
|
||||
commands.WithLimit(6),
|
||||
)
|
||||
|
||||
return &messagesComponent{
|
||||
app: app,
|
||||
viewport: vp,
|
||||
spinner: s,
|
||||
attachments: attachments,
|
||||
commands: commandsView,
|
||||
showToolDetails: true,
|
||||
cache: NewMessageCache(),
|
||||
tail: true,
|
||||
|
||||
@@ -0,0 +1,196 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
tea "github.com/charmbracelet/bubbletea/v2"
|
||||
"github.com/charmbracelet/lipgloss/v2"
|
||||
"github.com/charmbracelet/lipgloss/v2/compat"
|
||||
"github.com/sst/opencode/internal/app"
|
||||
"github.com/sst/opencode/internal/commands"
|
||||
"github.com/sst/opencode/internal/layout"
|
||||
"github.com/sst/opencode/internal/styles"
|
||||
"github.com/sst/opencode/internal/theme"
|
||||
)
|
||||
|
||||
type CommandsComponent interface {
|
||||
tea.Model
|
||||
tea.ViewModel
|
||||
layout.Sizeable
|
||||
}
|
||||
|
||||
type commandsComponent struct {
|
||||
app *app.App
|
||||
width, height int
|
||||
showKeybinds bool
|
||||
background *compat.AdaptiveColor
|
||||
limit *int
|
||||
}
|
||||
|
||||
func (c *commandsComponent) SetSize(width, height int) tea.Cmd {
|
||||
c.width = width
|
||||
c.height = height
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *commandsComponent) GetSize() (int, int) {
|
||||
return c.width, c.height
|
||||
}
|
||||
|
||||
func (c *commandsComponent) Init() tea.Cmd {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *commandsComponent) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
switch msg := msg.(type) {
|
||||
case tea.WindowSizeMsg:
|
||||
c.width = msg.Width
|
||||
c.height = msg.Height
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func (c *commandsComponent) View() string {
|
||||
t := theme.CurrentTheme()
|
||||
|
||||
triggerStyle := lipgloss.NewStyle().
|
||||
Foreground(t.Primary()).
|
||||
Bold(true)
|
||||
|
||||
descriptionStyle := lipgloss.NewStyle().
|
||||
Foreground(t.Text())
|
||||
|
||||
keybindStyle := lipgloss.NewStyle().
|
||||
Foreground(t.TextMuted())
|
||||
|
||||
if c.background != nil {
|
||||
triggerStyle = triggerStyle.Background(*c.background)
|
||||
descriptionStyle = descriptionStyle.Background(*c.background)
|
||||
keybindStyle = keybindStyle.Background(*c.background)
|
||||
}
|
||||
|
||||
var commandsWithTriggers []commands.Command
|
||||
for _, cmd := range c.app.Commands.Sorted() {
|
||||
if cmd.Trigger != "" {
|
||||
commandsWithTriggers = append(commandsWithTriggers, cmd)
|
||||
}
|
||||
}
|
||||
if c.limit != nil && len(commandsWithTriggers) > *c.limit {
|
||||
commandsWithTriggers = commandsWithTriggers[:*c.limit]
|
||||
}
|
||||
|
||||
if len(commandsWithTriggers) == 0 {
|
||||
return styles.Muted().Render("No commands with triggers available")
|
||||
}
|
||||
|
||||
// Calculate column widths
|
||||
maxTriggerWidth := 0
|
||||
maxDescriptionWidth := 0
|
||||
maxKeybindWidth := 0
|
||||
|
||||
// Prepare command data
|
||||
type commandRow struct {
|
||||
trigger string
|
||||
description string
|
||||
keybinds string
|
||||
}
|
||||
|
||||
rows := make([]commandRow, 0, len(commandsWithTriggers))
|
||||
|
||||
for _, cmd := range commandsWithTriggers {
|
||||
trigger := "/" + cmd.Trigger
|
||||
description := cmd.Description
|
||||
|
||||
// Format keybindings
|
||||
var keybindStrs []string
|
||||
if c.showKeybinds {
|
||||
for _, kb := range cmd.Keybindings {
|
||||
if kb.RequiresLeader {
|
||||
keybindStrs = append(keybindStrs, *c.app.Config.Keybinds.Leader+" "+kb.Key)
|
||||
} else {
|
||||
keybindStrs = append(keybindStrs, kb.Key)
|
||||
}
|
||||
}
|
||||
}
|
||||
keybinds := strings.Join(keybindStrs, ", ")
|
||||
|
||||
rows = append(rows, commandRow{
|
||||
trigger: trigger,
|
||||
description: description,
|
||||
keybinds: keybinds,
|
||||
})
|
||||
|
||||
// Update max widths
|
||||
if len(trigger) > maxTriggerWidth {
|
||||
maxTriggerWidth = len(trigger)
|
||||
}
|
||||
if len(description) > maxDescriptionWidth {
|
||||
maxDescriptionWidth = len(description)
|
||||
}
|
||||
if len(keybinds) > maxKeybindWidth {
|
||||
maxKeybindWidth = len(keybinds)
|
||||
}
|
||||
}
|
||||
|
||||
// Add padding between columns
|
||||
columnPadding := 3
|
||||
|
||||
// Build the output
|
||||
var output strings.Builder
|
||||
|
||||
for _, row := range rows {
|
||||
// Pad each column to align properly
|
||||
trigger := fmt.Sprintf("%-*s", maxTriggerWidth, row.trigger)
|
||||
description := fmt.Sprintf("%-*s", maxDescriptionWidth, row.description)
|
||||
|
||||
// Apply styles and combine
|
||||
line := triggerStyle.Render(trigger) +
|
||||
triggerStyle.Render(strings.Repeat(" ", columnPadding)) +
|
||||
descriptionStyle.Render(description)
|
||||
|
||||
if c.showKeybinds && row.keybinds != "" {
|
||||
line += keybindStyle.Render(strings.Repeat(" ", columnPadding)) +
|
||||
keybindStyle.Render(row.keybinds)
|
||||
}
|
||||
|
||||
output.WriteString(line + "\n")
|
||||
}
|
||||
|
||||
// Remove trailing newline
|
||||
result := strings.TrimSuffix(output.String(), "\n")
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
type Option func(*commandsComponent)
|
||||
|
||||
func WithKeybinds(show bool) Option {
|
||||
return func(c *commandsComponent) {
|
||||
c.showKeybinds = show
|
||||
}
|
||||
}
|
||||
|
||||
func WithBackground(background compat.AdaptiveColor) Option {
|
||||
return func(c *commandsComponent) {
|
||||
c.background = &background
|
||||
}
|
||||
}
|
||||
|
||||
func WithLimit(limit int) Option {
|
||||
return func(c *commandsComponent) {
|
||||
c.limit = &limit
|
||||
}
|
||||
}
|
||||
|
||||
func New(app *app.App, opts ...Option) CommandsComponent {
|
||||
c := &commandsComponent{
|
||||
app: app,
|
||||
background: nil,
|
||||
showKeybinds: true,
|
||||
}
|
||||
for _, opt := range opts {
|
||||
opt(c)
|
||||
}
|
||||
return c
|
||||
}
|
||||
@@ -212,6 +212,11 @@ func (a appModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
case dialog.CompletionDialogCloseMsg:
|
||||
a.showCompletionDialog = false
|
||||
a.completions.SetProvider(a.completionManager.DefaultProvider())
|
||||
case client.EventInstallationUpdated:
|
||||
return a, toast.NewSuccessToast(
|
||||
"New version installed",
|
||||
toast.WithTitle("opencode updated to "+msg.Properties.Version+", restart to apply."),
|
||||
)
|
||||
case client.EventSessionUpdated:
|
||||
if msg.Properties.Info.Id == a.app.Session.Id {
|
||||
a.app.Session = &msg.Properties.Info
|
||||
@@ -229,6 +234,12 @@ func (a appModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
a.app.Messages = append(a.app.Messages, msg.Properties.Info)
|
||||
}
|
||||
}
|
||||
case client.EventSessionError:
|
||||
unknownError, err := msg.Properties.Error.AsUnknownError()
|
||||
if err == nil {
|
||||
slog.Error("Server error", "name", unknownError.Name, "message", unknownError.Data.Message)
|
||||
return a, toast.NewErrorToast(unknownError.Data.Message, toast.WithTitle(unknownError.Name))
|
||||
}
|
||||
case tea.WindowSizeMsg:
|
||||
msg.Height -= 2 // Make space for the status bar
|
||||
a.width, a.height = msg.Width, msg.Height
|
||||
@@ -243,12 +254,13 @@ func (a appModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
}
|
||||
a.layout.SetSize(a.width, a.height)
|
||||
case app.SessionSelectedMsg:
|
||||
var err error
|
||||
a.app.Session = msg
|
||||
a.app.Messages, err = a.app.ListMessages(context.Background(), msg.Id)
|
||||
messages, err := a.app.ListMessages(context.Background(), msg.Id)
|
||||
if err != nil {
|
||||
slog.Error("Failed to list messages", "error", err)
|
||||
return a, toast.NewErrorToast("Failed to open session")
|
||||
}
|
||||
a.app.Session = msg
|
||||
a.app.Messages = messages
|
||||
case app.ModelSelectedMsg:
|
||||
a.app.Provider = &msg.Provider
|
||||
a.app.Model = &msg.Model
|
||||
@@ -544,8 +556,8 @@ func NewModel(app *app.App) tea.Model {
|
||||
messagesContainer := layout.NewContainer(messages)
|
||||
|
||||
var leaderBinding *key.Binding
|
||||
if (*app.Configg.Keybinds).Leader != nil {
|
||||
binding := key.NewBinding(key.WithKeys(*app.Configg.Keybinds.Leader))
|
||||
if (*app.Config.Keybinds).Leader != nil {
|
||||
binding := key.NewBinding(key.WithKeys(*app.Config.Keybinds.Leader))
|
||||
leaderBinding = &binding
|
||||
}
|
||||
|
||||
|
||||
@@ -525,6 +525,9 @@
|
||||
{
|
||||
"$ref": "#/components/schemas/Event.storage.write"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/Event.installation.updated"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/Event.lsp.client.diagnostics"
|
||||
},
|
||||
@@ -537,9 +540,6 @@
|
||||
{
|
||||
"$ref": "#/components/schemas/Event.message.part.updated"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/Event.installation.updated"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/Event.session.updated"
|
||||
},
|
||||
@@ -551,11 +551,11 @@
|
||||
"propertyName": "type",
|
||||
"mapping": {
|
||||
"storage.write": "#/components/schemas/Event.storage.write",
|
||||
"installation.updated": "#/components/schemas/Event.installation.updated",
|
||||
"lsp.client.diagnostics": "#/components/schemas/Event.lsp.client.diagnostics",
|
||||
"permission.updated": "#/components/schemas/Event.permission.updated",
|
||||
"message.updated": "#/components/schemas/Event.message.updated",
|
||||
"message.part.updated": "#/components/schemas/Event.message.part.updated",
|
||||
"installation.updated": "#/components/schemas/Event.installation.updated",
|
||||
"session.updated": "#/components/schemas/Event.session.updated",
|
||||
"session.error": "#/components/schemas/Event.session.error"
|
||||
}
|
||||
@@ -586,6 +586,30 @@
|
||||
"properties"
|
||||
]
|
||||
},
|
||||
"Event.installation.updated": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"const": "installation.updated"
|
||||
},
|
||||
"properties": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"version": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"version"
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"type",
|
||||
"properties"
|
||||
]
|
||||
},
|
||||
"Event.lsp.client.diagnostics": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -1201,30 +1225,6 @@
|
||||
"properties"
|
||||
]
|
||||
},
|
||||
"Event.installation.updated": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"const": "installation.updated"
|
||||
},
|
||||
"properties": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"version": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"version"
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"type",
|
||||
"properties"
|
||||
]
|
||||
},
|
||||
"Event.session.updated": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -1391,13 +1391,16 @@
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"$schema": {
|
||||
"type": "string"
|
||||
"type": "string",
|
||||
"description": "JSON schema reference for configuration validation"
|
||||
},
|
||||
"theme": {
|
||||
"type": "string"
|
||||
"type": "string",
|
||||
"description": "Theme name to use for the interface"
|
||||
},
|
||||
"keybinds": {
|
||||
"$ref": "#/components/schemas/Config.Keybinds"
|
||||
"$ref": "#/components/schemas/Config.Keybinds",
|
||||
"description": "Custom keybind configurations"
|
||||
},
|
||||
"autoshare": {
|
||||
"type": "boolean",
|
||||
@@ -1508,7 +1511,8 @@
|
||||
"required": [
|
||||
"models"
|
||||
]
|
||||
}
|
||||
},
|
||||
"description": "Custom provider configurations and model overrides"
|
||||
},
|
||||
"mcp": {
|
||||
"type": "object",
|
||||
@@ -1528,7 +1532,8 @@
|
||||
"remote": "#/components/schemas/Config.McpRemote"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "MCP (Model Context Protocol) server configurations"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -1536,85 +1541,112 @@
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"leader": {
|
||||
"type": "string"
|
||||
"type": "string",
|
||||
"description": "Leader key for keybind combinations"
|
||||
},
|
||||
"help": {
|
||||
"type": "string"
|
||||
"type": "string",
|
||||
"description": "Show help dialog"
|
||||
},
|
||||
"editor_open": {
|
||||
"type": "string"
|
||||
"type": "string",
|
||||
"description": "Open external editor"
|
||||
},
|
||||
"session_new": {
|
||||
"type": "string"
|
||||
"type": "string",
|
||||
"description": "Create a new session"
|
||||
},
|
||||
"session_list": {
|
||||
"type": "string"
|
||||
"type": "string",
|
||||
"description": "List all sessions"
|
||||
},
|
||||
"session_share": {
|
||||
"type": "string"
|
||||
"type": "string",
|
||||
"description": "Share current session"
|
||||
},
|
||||
"session_interrupt": {
|
||||
"type": "string"
|
||||
"type": "string",
|
||||
"description": "Interrupt current session"
|
||||
},
|
||||
"session_compact": {
|
||||
"type": "string"
|
||||
"type": "string",
|
||||
"description": "Toggle compact mode for session"
|
||||
},
|
||||
"tool_details": {
|
||||
"type": "string"
|
||||
"type": "string",
|
||||
"description": "Show tool details"
|
||||
},
|
||||
"model_list": {
|
||||
"type": "string"
|
||||
"type": "string",
|
||||
"description": "List available models"
|
||||
},
|
||||
"theme_list": {
|
||||
"type": "string"
|
||||
"type": "string",
|
||||
"description": "List available themes"
|
||||
},
|
||||
"project_init": {
|
||||
"type": "string"
|
||||
"type": "string",
|
||||
"description": "Initialize project configuration"
|
||||
},
|
||||
"input_clear": {
|
||||
"type": "string"
|
||||
"type": "string",
|
||||
"description": "Clear input field"
|
||||
},
|
||||
"input_paste": {
|
||||
"type": "string"
|
||||
"type": "string",
|
||||
"description": "Paste from clipboard"
|
||||
},
|
||||
"input_submit": {
|
||||
"type": "string"
|
||||
"type": "string",
|
||||
"description": "Submit input"
|
||||
},
|
||||
"input_newline": {
|
||||
"type": "string"
|
||||
"type": "string",
|
||||
"description": "Insert newline in input"
|
||||
},
|
||||
"history_previous": {
|
||||
"type": "string"
|
||||
"type": "string",
|
||||
"description": "Navigate to previous history item"
|
||||
},
|
||||
"history_next": {
|
||||
"type": "string"
|
||||
"type": "string",
|
||||
"description": "Navigate to next history item"
|
||||
},
|
||||
"messages_page_up": {
|
||||
"type": "string"
|
||||
"type": "string",
|
||||
"description": "Scroll messages up by one page"
|
||||
},
|
||||
"messages_page_down": {
|
||||
"type": "string"
|
||||
"type": "string",
|
||||
"description": "Scroll messages down by one page"
|
||||
},
|
||||
"messages_half_page_up": {
|
||||
"type": "string"
|
||||
"type": "string",
|
||||
"description": "Scroll messages up by half page"
|
||||
},
|
||||
"messages_half_page_down": {
|
||||
"type": "string"
|
||||
"type": "string",
|
||||
"description": "Scroll messages down by half page"
|
||||
},
|
||||
"messages_previous": {
|
||||
"type": "string"
|
||||
"type": "string",
|
||||
"description": "Navigate to previous message"
|
||||
},
|
||||
"messages_next": {
|
||||
"type": "string"
|
||||
"type": "string",
|
||||
"description": "Navigate to next message"
|
||||
},
|
||||
"messages_first": {
|
||||
"type": "string"
|
||||
"type": "string",
|
||||
"description": "Navigate to first message"
|
||||
},
|
||||
"messages_last": {
|
||||
"type": "string"
|
||||
"type": "string",
|
||||
"description": "Navigate to last message"
|
||||
},
|
||||
"app_exit": {
|
||||
"type": "string"
|
||||
"type": "string",
|
||||
"description": "Exit the application"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -1723,19 +1755,22 @@
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"const": "local"
|
||||
"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"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
@@ -1748,10 +1783,12 @@
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"const": "remote"
|
||||
"const": "remote",
|
||||
"description": "Type of MCP server connection"
|
||||
},
|
||||
"url": {
|
||||
"type": "string"
|
||||
"type": "string",
|
||||
"description": "URL of the remote MCP server"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
|
||||
@@ -41,6 +41,7 @@ type AppInfo struct {
|
||||
|
||||
// ConfigInfo defines model for Config.Info.
|
||||
type ConfigInfo struct {
|
||||
// Schema JSON schema reference for configuration validation
|
||||
Schema *string `json:"$schema,omitempty"`
|
||||
|
||||
// Autoshare Share newly created sessions automatically
|
||||
@@ -50,12 +51,16 @@ type ConfigInfo struct {
|
||||
Autoupdate *bool `json:"autoupdate,omitempty"`
|
||||
|
||||
// DisabledProviders Disable providers that are loaded automatically
|
||||
DisabledProviders *[]string `json:"disabled_providers,omitempty"`
|
||||
Keybinds *ConfigKeybinds `json:"keybinds,omitempty"`
|
||||
Mcp *map[string]ConfigInfo_Mcp_AdditionalProperties `json:"mcp,omitempty"`
|
||||
DisabledProviders *[]string `json:"disabled_providers,omitempty"`
|
||||
Keybinds *ConfigKeybinds `json:"keybinds,omitempty"`
|
||||
|
||||
// Mcp MCP (Model Context Protocol) server configurations
|
||||
Mcp *map[string]ConfigInfo_Mcp_AdditionalProperties `json:"mcp,omitempty"`
|
||||
|
||||
// Model Model to use in the format of provider/model, eg anthropic/claude-2
|
||||
Model *string `json:"model,omitempty"`
|
||||
Model *string `json:"model,omitempty"`
|
||||
|
||||
// Provider Custom provider configurations and model overrides
|
||||
Provider *map[string]struct {
|
||||
Api *string `json:"api,omitempty"`
|
||||
Env *[]string `json:"env,omitempty"`
|
||||
@@ -81,6 +86,8 @@ type ConfigInfo struct {
|
||||
Npm *string `json:"npm,omitempty"`
|
||||
Options *map[string]interface{} `json:"options,omitempty"`
|
||||
} `json:"provider,omitempty"`
|
||||
|
||||
// Theme Theme name to use for the interface
|
||||
Theme *string `json:"theme,omitempty"`
|
||||
}
|
||||
|
||||
@@ -91,46 +98,107 @@ type ConfigInfo_Mcp_AdditionalProperties struct {
|
||||
|
||||
// ConfigKeybinds defines model for Config.Keybinds.
|
||||
type ConfigKeybinds struct {
|
||||
AppExit *string `json:"app_exit,omitempty"`
|
||||
EditorOpen *string `json:"editor_open,omitempty"`
|
||||
Help *string `json:"help,omitempty"`
|
||||
HistoryNext *string `json:"history_next,omitempty"`
|
||||
HistoryPrevious *string `json:"history_previous,omitempty"`
|
||||
InputClear *string `json:"input_clear,omitempty"`
|
||||
InputNewline *string `json:"input_newline,omitempty"`
|
||||
InputPaste *string `json:"input_paste,omitempty"`
|
||||
InputSubmit *string `json:"input_submit,omitempty"`
|
||||
Leader *string `json:"leader,omitempty"`
|
||||
MessagesFirst *string `json:"messages_first,omitempty"`
|
||||
// AppExit Exit the application
|
||||
AppExit *string `json:"app_exit,omitempty"`
|
||||
|
||||
// EditorOpen Open external editor
|
||||
EditorOpen *string `json:"editor_open,omitempty"`
|
||||
|
||||
// Help Show help dialog
|
||||
Help *string `json:"help,omitempty"`
|
||||
|
||||
// HistoryNext Navigate to next history item
|
||||
HistoryNext *string `json:"history_next,omitempty"`
|
||||
|
||||
// HistoryPrevious Navigate to previous history item
|
||||
HistoryPrevious *string `json:"history_previous,omitempty"`
|
||||
|
||||
// InputClear Clear input field
|
||||
InputClear *string `json:"input_clear,omitempty"`
|
||||
|
||||
// InputNewline Insert newline in input
|
||||
InputNewline *string `json:"input_newline,omitempty"`
|
||||
|
||||
// InputPaste Paste from clipboard
|
||||
InputPaste *string `json:"input_paste,omitempty"`
|
||||
|
||||
// InputSubmit Submit input
|
||||
InputSubmit *string `json:"input_submit,omitempty"`
|
||||
|
||||
// Leader Leader key for keybind combinations
|
||||
Leader *string `json:"leader,omitempty"`
|
||||
|
||||
// MessagesFirst Navigate to first message
|
||||
MessagesFirst *string `json:"messages_first,omitempty"`
|
||||
|
||||
// MessagesHalfPageDown Scroll messages down by half page
|
||||
MessagesHalfPageDown *string `json:"messages_half_page_down,omitempty"`
|
||||
MessagesHalfPageUp *string `json:"messages_half_page_up,omitempty"`
|
||||
MessagesLast *string `json:"messages_last,omitempty"`
|
||||
MessagesNext *string `json:"messages_next,omitempty"`
|
||||
MessagesPageDown *string `json:"messages_page_down,omitempty"`
|
||||
MessagesPageUp *string `json:"messages_page_up,omitempty"`
|
||||
MessagesPrevious *string `json:"messages_previous,omitempty"`
|
||||
ModelList *string `json:"model_list,omitempty"`
|
||||
ProjectInit *string `json:"project_init,omitempty"`
|
||||
SessionCompact *string `json:"session_compact,omitempty"`
|
||||
SessionInterrupt *string `json:"session_interrupt,omitempty"`
|
||||
SessionList *string `json:"session_list,omitempty"`
|
||||
SessionNew *string `json:"session_new,omitempty"`
|
||||
SessionShare *string `json:"session_share,omitempty"`
|
||||
ThemeList *string `json:"theme_list,omitempty"`
|
||||
ToolDetails *string `json:"tool_details,omitempty"`
|
||||
|
||||
// MessagesHalfPageUp Scroll messages up by half page
|
||||
MessagesHalfPageUp *string `json:"messages_half_page_up,omitempty"`
|
||||
|
||||
// MessagesLast Navigate to last message
|
||||
MessagesLast *string `json:"messages_last,omitempty"`
|
||||
|
||||
// MessagesNext Navigate to next message
|
||||
MessagesNext *string `json:"messages_next,omitempty"`
|
||||
|
||||
// MessagesPageDown Scroll messages down by one page
|
||||
MessagesPageDown *string `json:"messages_page_down,omitempty"`
|
||||
|
||||
// MessagesPageUp Scroll messages up by one page
|
||||
MessagesPageUp *string `json:"messages_page_up,omitempty"`
|
||||
|
||||
// MessagesPrevious Navigate to previous message
|
||||
MessagesPrevious *string `json:"messages_previous,omitempty"`
|
||||
|
||||
// ModelList List available models
|
||||
ModelList *string `json:"model_list,omitempty"`
|
||||
|
||||
// ProjectInit Initialize project configuration
|
||||
ProjectInit *string `json:"project_init,omitempty"`
|
||||
|
||||
// SessionCompact Toggle compact mode for session
|
||||
SessionCompact *string `json:"session_compact,omitempty"`
|
||||
|
||||
// SessionInterrupt Interrupt current session
|
||||
SessionInterrupt *string `json:"session_interrupt,omitempty"`
|
||||
|
||||
// SessionList List all sessions
|
||||
SessionList *string `json:"session_list,omitempty"`
|
||||
|
||||
// SessionNew Create a new session
|
||||
SessionNew *string `json:"session_new,omitempty"`
|
||||
|
||||
// SessionShare Share current session
|
||||
SessionShare *string `json:"session_share,omitempty"`
|
||||
|
||||
// ThemeList List available themes
|
||||
ThemeList *string `json:"theme_list,omitempty"`
|
||||
|
||||
// ToolDetails Show tool details
|
||||
ToolDetails *string `json:"tool_details,omitempty"`
|
||||
}
|
||||
|
||||
// ConfigMcpLocal defines model for Config.McpLocal.
|
||||
type ConfigMcpLocal struct {
|
||||
Command []string `json:"command"`
|
||||
// Command Command and arguments to run the MCP server
|
||||
Command []string `json:"command"`
|
||||
|
||||
// Environment Environment variables to set when running the MCP server
|
||||
Environment *map[string]string `json:"environment,omitempty"`
|
||||
Type string `json:"type"`
|
||||
|
||||
// Type Type of MCP server connection
|
||||
Type string `json:"type"`
|
||||
}
|
||||
|
||||
// ConfigMcpRemote defines model for Config.McpRemote.
|
||||
type ConfigMcpRemote struct {
|
||||
// Type Type of MCP server connection
|
||||
Type string `json:"type"`
|
||||
Url string `json:"url"`
|
||||
|
||||
// Url URL of the remote MCP server
|
||||
Url string `json:"url"`
|
||||
}
|
||||
|
||||
// Error defines model for Error.
|
||||
@@ -684,6 +752,34 @@ func (t *Event) MergeEventStorageWrite(v EventStorageWrite) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// AsEventInstallationUpdated returns the union data inside the Event as a EventInstallationUpdated
|
||||
func (t Event) AsEventInstallationUpdated() (EventInstallationUpdated, error) {
|
||||
var body EventInstallationUpdated
|
||||
err := json.Unmarshal(t.union, &body)
|
||||
return body, err
|
||||
}
|
||||
|
||||
// FromEventInstallationUpdated overwrites any union data inside the Event as the provided EventInstallationUpdated
|
||||
func (t *Event) FromEventInstallationUpdated(v EventInstallationUpdated) error {
|
||||
v.Type = "installation.updated"
|
||||
b, err := json.Marshal(v)
|
||||
t.union = b
|
||||
return err
|
||||
}
|
||||
|
||||
// MergeEventInstallationUpdated performs a merge with any union data inside the Event, using the provided EventInstallationUpdated
|
||||
func (t *Event) MergeEventInstallationUpdated(v EventInstallationUpdated) error {
|
||||
v.Type = "installation.updated"
|
||||
b, err := json.Marshal(v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
merged, err := runtime.JSONMerge(t.union, b)
|
||||
t.union = merged
|
||||
return err
|
||||
}
|
||||
|
||||
// AsEventLspClientDiagnostics returns the union data inside the Event as a EventLspClientDiagnostics
|
||||
func (t Event) AsEventLspClientDiagnostics() (EventLspClientDiagnostics, error) {
|
||||
var body EventLspClientDiagnostics
|
||||
@@ -796,34 +892,6 @@ func (t *Event) MergeEventMessagePartUpdated(v EventMessagePartUpdated) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// AsEventInstallationUpdated returns the union data inside the Event as a EventInstallationUpdated
|
||||
func (t Event) AsEventInstallationUpdated() (EventInstallationUpdated, error) {
|
||||
var body EventInstallationUpdated
|
||||
err := json.Unmarshal(t.union, &body)
|
||||
return body, err
|
||||
}
|
||||
|
||||
// FromEventInstallationUpdated overwrites any union data inside the Event as the provided EventInstallationUpdated
|
||||
func (t *Event) FromEventInstallationUpdated(v EventInstallationUpdated) error {
|
||||
v.Type = "installation.updated"
|
||||
b, err := json.Marshal(v)
|
||||
t.union = b
|
||||
return err
|
||||
}
|
||||
|
||||
// MergeEventInstallationUpdated performs a merge with any union data inside the Event, using the provided EventInstallationUpdated
|
||||
func (t *Event) MergeEventInstallationUpdated(v EventInstallationUpdated) error {
|
||||
v.Type = "installation.updated"
|
||||
b, err := json.Marshal(v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
merged, err := runtime.JSONMerge(t.union, b)
|
||||
t.union = merged
|
||||
return err
|
||||
}
|
||||
|
||||
// AsEventSessionUpdated returns the union data inside the Event as a EventSessionUpdated
|
||||
func (t Event) AsEventSessionUpdated() (EventSessionUpdated, error) {
|
||||
var body EventSessionUpdated
|
||||
|
||||
@@ -7,7 +7,6 @@ import theme from "toolbeam-docs-theme"
|
||||
import { rehypeHeadingIds } from "@astrojs/markdown-remark"
|
||||
import rehypeAutolinkHeadings from "rehype-autolink-headings"
|
||||
|
||||
const discord = "https://discord.gg/sst"
|
||||
const github = "https://github.com/sst/opencode"
|
||||
|
||||
// https://astro.build/config
|
||||
@@ -28,10 +27,9 @@ export default defineConfig({
|
||||
integrations: [
|
||||
solidJs(),
|
||||
starlight({
|
||||
title: "OpenCode",
|
||||
title: "opencode",
|
||||
expressiveCode: { themes: ["github-light", "github-dark"] },
|
||||
social: [
|
||||
{ icon: "discord", label: "Discord", href: discord },
|
||||
{ icon: "github", label: "GitHub", href: github },
|
||||
],
|
||||
editLink: {
|
||||
@@ -52,8 +50,7 @@ export default defineConfig({
|
||||
"docs/config",
|
||||
"docs/models",
|
||||
"docs/themes",
|
||||
"docs/shortcuts",
|
||||
"docs/lsp-servers",
|
||||
"docs/keybinds",
|
||||
"docs/mcp-servers",
|
||||
],
|
||||
components: {
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 439 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 438 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 432 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 442 KiB |
@@ -3,6 +3,7 @@ import config from 'virtual:starlight/user-config';
|
||||
import { Icon } from '@astrojs/starlight/components';
|
||||
import { HeaderLinks } from 'toolbeam-docs-theme/components';
|
||||
import Default from 'toolbeam-docs-theme/overrides/Header.astro';
|
||||
import SocialIcons from 'virtual:starlight/components/SocialIcons';
|
||||
import SiteTitle from '@astrojs/starlight/components/SiteTitle.astro';
|
||||
|
||||
const path = Astro.url.pathname;
|
||||
@@ -11,19 +12,32 @@ const links = config.social || [];
|
||||
---
|
||||
|
||||
{ path.startsWith("/s")
|
||||
? <div class="header sl-flex">
|
||||
<div class="title-wrapper sl-flex">
|
||||
<SiteTitle {...Astro.props} />
|
||||
</div>
|
||||
<div class="middle-group sl-flex">
|
||||
<HeaderLinks {...Astro.props} />
|
||||
</div>
|
||||
</div>
|
||||
? <div class="header sl-flex">
|
||||
<div class="title-wrapper sl-flex">
|
||||
<SiteTitle {...Astro.props} />
|
||||
</div>
|
||||
<div class="middle-group sl-flex">
|
||||
<HeaderLinks {...Astro.props} />
|
||||
</div>
|
||||
<div class="sl-hidden md:sl-flex right-group">
|
||||
{
|
||||
links.length > 0 && (
|
||||
<div class="sl-flex social-icons">
|
||||
{links.map(({ href, icon }) => (
|
||||
<a {href} rel="me" target="_blank">
|
||||
<Icon name={icon} size="1rem" />
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
: <Default {...Astro.props}><slot /></Default>
|
||||
}
|
||||
|
||||
<style>
|
||||
.header {
|
||||
gap: var(--sl-nav-gap);
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
@@ -53,4 +67,50 @@ const links = config.social || [];
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
.right-group,
|
||||
.social-icons {
|
||||
gap: 1rem;
|
||||
align-items: center;
|
||||
|
||||
a {
|
||||
line-height: 1;
|
||||
|
||||
svg {
|
||||
color: var(--sl-color-text-dimmed);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 50rem) {
|
||||
:global(:root[data-has-sidebar]) {
|
||||
--__sidebar-pad: calc(2 * var(--sl-nav-pad-x));
|
||||
}
|
||||
:global(:root:not([data-has-toc])) {
|
||||
--__toc-width: 0rem;
|
||||
}
|
||||
.header {
|
||||
--__sidebar-width: max(0rem, var(--sl-content-inline-start, 0rem) - var(--sl-nav-pad-x));
|
||||
--__main-column-fr: calc(
|
||||
(
|
||||
100% + var(--__sidebar-pad, 0rem) - var(--__toc-width, var(--sl-sidebar-width)) -
|
||||
(2 * var(--__toc-width, var(--sl-nav-pad-x))) - var(--sl-content-inline-start, 0rem) -
|
||||
var(--sl-content-width)
|
||||
) / 2
|
||||
);
|
||||
display: grid;
|
||||
grid-template-columns:
|
||||
/* 1 (site title): runs up until the main content column’s left edge or the width of the title, whichever is the largest */
|
||||
minmax(
|
||||
calc(var(--__sidebar-width) + max(0rem, var(--__main-column-fr) - var(--sl-nav-gap))),
|
||||
auto
|
||||
)
|
||||
/* 2 (search box): all free space that is available. */
|
||||
1fr
|
||||
/* 3 (right items): use the space that these need. */
|
||||
auto;
|
||||
align-content: center;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ const imageAttrs = {
|
||||
const github = config.social.filter(s => s.icon === 'github')[0];
|
||||
|
||||
const command = "npm i -g";
|
||||
const pkg = "opencode";
|
||||
const pkg = "opencode-ai";
|
||||
|
||||
let darkImage: ImageMetadata | undefined;
|
||||
let lightImage: ImageMetadata | undefined;
|
||||
@@ -68,12 +68,12 @@ if (image) {
|
||||
|
||||
<section class="content">
|
||||
<ul>
|
||||
<li><b>Native TUI</b>: A native terminal UI for a smoother, snappier experience.</li>
|
||||
<li><b>LSP enabled</b>: Loads the right LSPs for your codebase. Helps the LLM make fewer mistakes.</li>
|
||||
<li><b>Multi-session</b>: Start multiple conversations in a project to have agents working in parallel.</li>
|
||||
<li><b>Use any model</b>: Supports all the models from OpenAI, Anthropic, Google, OpenRouter, and more.</li>
|
||||
<li><b>Change tracking</b>: View the file changes from the current conversation in the sidebar.</li>
|
||||
<li><b>Edit with Vim</b>: Use Vim as an external editor to compose longer messages.</li>
|
||||
<li><b>Native TUI</b>: A responsive, native, themeable terminal UI.</li>
|
||||
<li><b>LSP enabled</b>: Automatically loads the right LSPs for the LLM.</li>
|
||||
<li><b>Multi-session</b>: Start multiple agents in parallel on the same project.</li>
|
||||
<li><b>Shareable links</b>: Share a link to any sessions for reference or to debug.</li>
|
||||
<li><b>Claude Pro</b>: Log in with Anthropic to use your Claude Pro or Max account.</li>
|
||||
<li><b>Use any model</b>: Supports 75+ LLM providers through <a href="https://models.dev">Models.dev</a>, including local models.</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
@@ -94,7 +94,7 @@ if (image) {
|
||||
--heading-font-size: var(--sl-text-3xl);
|
||||
|
||||
margin: 1rem;
|
||||
border: 2px solid var(--sl-color-white);
|
||||
border: 2px solid var(--sl-color-border);
|
||||
}
|
||||
@media (max-width: 30rem) {
|
||||
.hero {
|
||||
@@ -126,7 +126,7 @@ section.cta {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
border-top: 2px solid var(--sl-color-white);
|
||||
border-top: 2px solid var(--sl-color-border);
|
||||
|
||||
& > div {
|
||||
flex: 1;
|
||||
@@ -145,7 +145,7 @@ section.cta {
|
||||
}
|
||||
|
||||
& > div + div {
|
||||
border-left: 2px solid var(--sl-color-white);
|
||||
border-left: 2px solid var(--sl-color-border);
|
||||
}
|
||||
|
||||
.command {
|
||||
@@ -195,7 +195,7 @@ section.cta {
|
||||
}
|
||||
|
||||
section.content {
|
||||
border-top: 2px solid var(--sl-color-white);
|
||||
border-top: 2px solid var(--sl-color-border);
|
||||
padding: var(--padding);
|
||||
|
||||
ul {
|
||||
@@ -212,7 +212,7 @@ section.content {
|
||||
}
|
||||
|
||||
section.approach {
|
||||
border-top: 2px solid var(--sl-color-white);
|
||||
border-top: 2px solid var(--sl-color-border);
|
||||
padding: var(--padding);
|
||||
|
||||
p + p {
|
||||
@@ -221,7 +221,7 @@ section.approach {
|
||||
}
|
||||
|
||||
section.footer {
|
||||
border-top: 2px solid var(--sl-color-white);
|
||||
border-top: 2px solid var(--sl-color-border);
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
||||
@@ -233,7 +233,7 @@ section.footer {
|
||||
}
|
||||
|
||||
& > div + div {
|
||||
border-left: 2px solid var(--sl-color-white);
|
||||
border-left: 2px solid var(--sl-color-border);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -2,88 +2,119 @@
|
||||
title: CLI
|
||||
---
|
||||
|
||||
Once installed you can run the OpenCode CLI.
|
||||
Running the opencode CLI starts it for the current directory.
|
||||
|
||||
```bash
|
||||
opencode
|
||||
```
|
||||
|
||||
Or pass in flags. For example, to start with debug logging:
|
||||
Or you can start it for a specific working directory.
|
||||
|
||||
```bash
|
||||
opencode -d
|
||||
opencode /path/to/project
|
||||
```
|
||||
|
||||
Or start with a specific working directory.
|
||||
---
|
||||
|
||||
## Commands
|
||||
|
||||
The opencode CLI also has the following commands.
|
||||
|
||||
### run
|
||||
|
||||
Run opencode in non-interactive mode by passing a prompt directly.
|
||||
|
||||
```bash
|
||||
opencode -c /path/to/project
|
||||
opencode run [message..]
|
||||
```
|
||||
|
||||
This is useful for scripting, automation, or when you want a quick answer without launching the full TUI. For example.
|
||||
|
||||
```bash "opencode run"
|
||||
opencode run Explain the use of context in Go
|
||||
```
|
||||
|
||||
#### Flags
|
||||
|
||||
| Flag | Short | Description |
|
||||
| ----------------- | ----- | --------------------- |
|
||||
| `--continue` | `-c` | Continue the last session |
|
||||
| `--session` | `-s` | Session ID to continue |
|
||||
| `--share` | | Share the session |
|
||||
| `--model` | `-m` | Mode to use in the form of provider/model |
|
||||
|
||||
---
|
||||
|
||||
### auth
|
||||
|
||||
Command to manage credentials and login for providers.
|
||||
|
||||
```bash
|
||||
opencode auth [command]
|
||||
```
|
||||
|
||||
#### login
|
||||
|
||||
Logs you into a provider and saves them in the credentials file in `~/.local/share/opencode/auth.json`.
|
||||
|
||||
```bash
|
||||
opencode auth login
|
||||
```
|
||||
|
||||
When opencode starts up it will loads the providers from the credentials file. And if there are any keys defined in your environments or a `.env` file in your project.
|
||||
|
||||
#### list
|
||||
|
||||
Lists all the authenticated providers as stored in the credentials file.
|
||||
|
||||
```bash
|
||||
opencode auth list
|
||||
```
|
||||
|
||||
Or the short version.
|
||||
|
||||
```bash
|
||||
opencode auth ls
|
||||
```
|
||||
|
||||
#### logout
|
||||
|
||||
Logs you out of a provider by clearing it from the credentials file.
|
||||
|
||||
```bash
|
||||
opencode auth logout
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### upgrade
|
||||
|
||||
Updates opencode to the latest version or a specific version.
|
||||
|
||||
```bash
|
||||
opencode upgrade [target]
|
||||
```
|
||||
|
||||
To upgrade to the latest version.
|
||||
|
||||
```bash
|
||||
opencode upgrade
|
||||
```
|
||||
|
||||
To upgrade to a specific version.
|
||||
|
||||
```bash
|
||||
opencode upgrade v0.1.48
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Flags
|
||||
|
||||
The OpenCode CLI takes the following flags.
|
||||
The opencode CLI takes the following flags.
|
||||
|
||||
| Flag | Short | Description |
|
||||
| ----------------- | ----- | -------------------------------------------------------- |
|
||||
| `--help` | `-h` | Display help |
|
||||
| `--debug` | `-d` | Enable debug mode |
|
||||
| `--cwd` | `-c` | Set current working directory |
|
||||
| `--prompt` | `-p` | Run a single prompt in non-interactive mode |
|
||||
| `--output-format` | `-f` | Output format for non-interactive mode, `text` or `json` |
|
||||
| `--quiet` | `-q` | Hide spinner in non-interactive mode |
|
||||
| `--verbose` | | Display logs to stderr in non-interactive mode |
|
||||
| `--allowedTools` | | Restrict the agent to only use specified tools |
|
||||
| `--excludedTools` | | Prevent the agent from using specified tools |
|
||||
|
||||
## Non-interactive
|
||||
|
||||
By default, OpenCode runs in interactive mode.
|
||||
|
||||
But you can also run OpenCode in non-interactive mode by passing a prompt directly as a command-line argument. This is useful for scripting, automation, or when you want a quick answer without launching the full TUI.
|
||||
|
||||
For example, to run a single prompt use the `-p` flag.
|
||||
|
||||
```bash "-p"
|
||||
opencode -p "Explain the use of context in Go"
|
||||
```
|
||||
|
||||
If you want to run without showing the spinner, use `-q`.
|
||||
|
||||
```bash "-q"
|
||||
opencode -p "Explain the use of context in Go" -q
|
||||
```
|
||||
|
||||
In this mode, OpenCode will process your prompt, print the result to standard output, and then exit. All **permissions are auto-approved** for the session.
|
||||
|
||||
#### Tool restrictions
|
||||
|
||||
You can control which tools the AI assistant has access to in non-interactive mode.
|
||||
|
||||
- `--allowedTools`
|
||||
|
||||
A comma-separated list of tools that the agent is allowed to use. Only these tools will be available.
|
||||
|
||||
```bash "--allowedTools"
|
||||
opencode -p "Explain the use of context in Go" --allowedTools=view,ls,glob
|
||||
```
|
||||
|
||||
- `--excludedTools`
|
||||
|
||||
Comma-separated list of tools that the agent is not allowed to use. All other tools will be available.
|
||||
|
||||
```bash "--excludedTools"
|
||||
opencode -p "Explain the use of context in Go" --excludedTools=bash,edit
|
||||
```
|
||||
|
||||
These flags are mutually exclusive. So you can either use `--allowedTools` or `--excludedTools`, but not both.
|
||||
|
||||
#### Output formats
|
||||
|
||||
In non-interactive mode, you can also set the CLI to return as JSON using `-f`.
|
||||
|
||||
```bash "-f json"
|
||||
opencode -p "Explain the use of context in Go" -f json
|
||||
```
|
||||
|
||||
By default, this is set to `text`, to return plain text.
|
||||
| Flag | Short | Description |
|
||||
| ----------------- | ----- | --------------------- |
|
||||
| `--help` | `-h` | Display help |
|
||||
| `--version` | | Print version number |
|
||||
| `--print-logs` | | Print logs to stderr |
|
||||
|
||||
@@ -2,86 +2,90 @@
|
||||
title: Config
|
||||
---
|
||||
|
||||
You can configure OpenCode using the OpenCode config. It can be places in:
|
||||
You can configure opencode using a JSON config file that can be placed in:
|
||||
|
||||
- `$HOME/.opencode.json`
|
||||
- `$XDG_CONFIG_HOME/opencode/.opencode.json`
|
||||
- Globally under `~/.config/opencode/config.json`.
|
||||
- Your project root under `opencode.json`. This is safe to checked into Git and uses the same schema as the global one.
|
||||
|
||||
Or in the current directory, `./.opencode.json`.
|
||||
|
||||
## OpenCode config
|
||||
|
||||
The config file has the following structure.
|
||||
|
||||
```json title=".opencode.json"
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"directory": ".opencode"
|
||||
},
|
||||
"providers": {
|
||||
"openai": {
|
||||
"apiKey": "your-api-key",
|
||||
"disabled": false
|
||||
},
|
||||
"anthropic": {
|
||||
"apiKey": "your-api-key",
|
||||
"disabled": false
|
||||
},
|
||||
"groq": {
|
||||
"apiKey": "your-api-key",
|
||||
"disabled": false
|
||||
},
|
||||
"openrouter": {
|
||||
"apiKey": "your-api-key",
|
||||
"disabled": false
|
||||
}
|
||||
},
|
||||
"agents": {
|
||||
"primary": {
|
||||
"model": "claude-3.7-sonnet",
|
||||
"maxTokens": 5000
|
||||
},
|
||||
"task": {
|
||||
"model": "claude-3.7-sonnet",
|
||||
"maxTokens": 5000
|
||||
},
|
||||
"title": {
|
||||
"model": "claude-3.7-sonnet",
|
||||
"maxTokens": 80
|
||||
}
|
||||
},
|
||||
"mcpServers": {
|
||||
"example": {
|
||||
"type": "stdio",
|
||||
"command": "path/to/mcp-server",
|
||||
"env": [],
|
||||
"args": []
|
||||
}
|
||||
},
|
||||
"lsp": {
|
||||
"go": {
|
||||
"disabled": false,
|
||||
"command": "gopls"
|
||||
}
|
||||
},
|
||||
"debug": false,
|
||||
"debugLSP": false
|
||||
"$schema": "https://opencode.ai/config.json",
|
||||
"theme": "opencode",
|
||||
"model": "anthropic/claude-sonnet-4-20250514",
|
||||
"autoshare": false,
|
||||
"autoupdate": true
|
||||
}
|
||||
```
|
||||
|
||||
## Environment variables
|
||||
In most cases, you'll want to use the global config for things like themes, providers, or keybinds. Having a config per project is useful if you are using different providers for your company.
|
||||
|
||||
For the providers, you can also specify the keys using environment variables.
|
||||
When opencode starts up, it looks for a config file in the current directory or traverse up to the nearest Git directory.
|
||||
|
||||
| Environment Variable | Models |
|
||||
| -------------------------- | ------------------------------------------ |
|
||||
| `ANTHROPIC_API_KEY` | Claude |
|
||||
| `OPENAI_API_KEY` | OpenAI |
|
||||
| `GEMINI_API_KEY` | Google Gemini |
|
||||
| `GROQ_API_KEY` | Groq |
|
||||
| `AWS_ACCESS_KEY_ID` | Amazon Bedrock |
|
||||
| `AWS_SECRET_ACCESS_KEY` | Amazon Bedrock |
|
||||
| `AWS_REGION` | Amazon Bedrock |
|
||||
| `AZURE_OPENAI_ENDPOINT` | Azure OpenAI |
|
||||
| `AZURE_OPENAI_API_KEY` | Azure OpenAI, optional when using Entra ID |
|
||||
| `AZURE_OPENAI_API_VERSION` | Azure OpenAI |
|
||||
---
|
||||
|
||||
## Schema
|
||||
|
||||
The config file has a schema that's defined in [**`opencode.ai/config.json`**](https://opencode.ai/config.json).
|
||||
|
||||
Your editor should be able to validate and autocomplete based on the schema.
|
||||
|
||||
---
|
||||
|
||||
### Models
|
||||
|
||||
You can configure the providers and models you want to use in your opencode config through the `provider` and `model` options.
|
||||
|
||||
```json title="opencode.json"
|
||||
{
|
||||
"$schema": "https://opencode.ai/config.json",
|
||||
"provider": { },
|
||||
"model": ""
|
||||
}
|
||||
```
|
||||
|
||||
[Learn more here](/docs/models).
|
||||
|
||||
---
|
||||
|
||||
### Themes
|
||||
|
||||
You can configure the theme you want to use in your opencode config through the `theme` option.
|
||||
|
||||
```json title="opencode.json"
|
||||
{
|
||||
"$schema": "https://opencode.ai/config.json",
|
||||
"theme": ""
|
||||
}
|
||||
```
|
||||
|
||||
[Learn more here](/docs/themes).
|
||||
|
||||
---
|
||||
|
||||
### Keybinds
|
||||
|
||||
You can customize your keybinds through the `keybinds` option.
|
||||
|
||||
```json title="opencode.json"
|
||||
{
|
||||
"$schema": "https://opencode.ai/config.json",
|
||||
"keybinds": { }
|
||||
}
|
||||
```
|
||||
|
||||
[Learn more here](/docs/keybinds).
|
||||
|
||||
---
|
||||
|
||||
### MCP servers
|
||||
|
||||
You can configure MCP servers you want to use through the `mcp` option.
|
||||
|
||||
```json title="opencode.json"
|
||||
{
|
||||
"$schema": "https://opencode.ai/config.json",
|
||||
"mcp": { }
|
||||
}
|
||||
```
|
||||
|
||||
[Learn more here](/docs/mcp-servers).
|
||||
|
||||
@@ -2,57 +2,89 @@
|
||||
title: Intro
|
||||
---
|
||||
|
||||
OpenCode is an AI coding agent built natively for the terminal. It features:
|
||||
import { Tabs, TabItem } from '@astrojs/starlight/components';
|
||||
|
||||
- Native TUI for a smoother, snappier experience
|
||||
- Uses LSPs to help the LLM make fewer mistakes
|
||||
- Opening multiple conversations with the same project
|
||||
- Use of any model through the AI SDK
|
||||
- Tracks and visualizes all the file changes
|
||||
- Editing longer messages with Vim
|
||||
[**opencode**](/) is an AI coding agent built for the terminal. It features:
|
||||
|
||||
## Installation
|
||||
- A responsive, native, themeable terminal UI.
|
||||
- Automatically loads the right LSPs, so the LLMs make fewer mistakes.
|
||||
- Have multiple agents working in parallel on the same project.
|
||||
- Create shareable links to any session for reference or to debug.
|
||||
- Log in with Anthropic to use your Claude Pro or Claude Max account.
|
||||
- Supports 75+ LLM providers through [Models.dev](https://models.dev), including local models.
|
||||
|
||||
```bash
|
||||
npm i -g opencode
|
||||
```
|
||||
---
|
||||
|
||||
If you don't have NPM installed, you can also install the OpenCode binary through the following.
|
||||
## Install
|
||||
|
||||
#### Using the install script
|
||||
<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>
|
||||
</Tabs>
|
||||
|
||||
You can also install the opencode binary through the following.
|
||||
|
||||
##### Using the install script
|
||||
|
||||
```bash
|
||||
curl -fsSL https://opencode.ai/install | bash
|
||||
```
|
||||
|
||||
Or install a specific version.
|
||||
|
||||
```bash
|
||||
curl -fsSL https://opencode.ai/install | VERSION=0.1.0 bash
|
||||
```
|
||||
|
||||
#### Using Homebrew on macOS and Linux
|
||||
##### Using Homebrew on macOS
|
||||
|
||||
```bash
|
||||
brew install sst/tap/opencode
|
||||
```
|
||||
|
||||
#### Using AUR in Arch Linux
|
||||
|
||||
With yay.
|
||||
|
||||
```bash
|
||||
yay -S opencode-bin
|
||||
```
|
||||
|
||||
Or with paru.
|
||||
##### Using Paru on Arch Linux
|
||||
|
||||
```bash
|
||||
paru -S opencode-bin
|
||||
```
|
||||
|
||||
#### Using Go
|
||||
---
|
||||
|
||||
## Providers
|
||||
|
||||
We recommend signing up for Claude Pro or Max, running `opencode auth login` and selecting Anthropic. It's the most cost-effective way to use opencode.
|
||||
|
||||
```bash
|
||||
go install github.com/sst/opencode@latest
|
||||
$ opencode auth login
|
||||
|
||||
┌ Add credential
|
||||
│
|
||||
◆ Select provider
|
||||
│ ● Anthropic (recommended)
|
||||
│ ○ OpenAI
|
||||
│ ○ Google
|
||||
│ ○ Amazon Bedrock
|
||||
│ ○ Azure
|
||||
│ ○ DeepSeek
|
||||
│ ○ Groq
|
||||
│ ...
|
||||
└
|
||||
```
|
||||
|
||||
opencode is powered by the provider list at [Models.dev](https://models.dev), so you can use `opencode auth login` to configure API keys for any provider you'd like to use. This is stored in `~/.local/share/opencode/auth.json`.
|
||||
|
||||
The Models.dev dataset is also used to detect common environment variables like `OPENAI_API_KEY` to autoload that provider.
|
||||
|
||||
If there are additional providers you want to use you can submit a PR to the [Models.dev repo](https://github.com/sst/models.dev). You can also [add them to your config](/docs/config) for yourself.
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
---
|
||||
title: Keybinds
|
||||
---
|
||||
|
||||
opencode has a list of keybinds that you can customize through the opencode config.
|
||||
|
||||
```json title="opencode.json"
|
||||
{
|
||||
"$schema": "https://opencode.ai/config.json",
|
||||
"keybinds": {
|
||||
"leader": "ctrl+x",
|
||||
"help": "<leader>h",
|
||||
"editor_open": "<leader>e",
|
||||
"session_new": "<leader>n",
|
||||
"session_list": "<leader>l",
|
||||
"session_share": "<leader>s",
|
||||
"session_interrupt": "esc",
|
||||
"session_compact": "<leader>c",
|
||||
"tool_details": "<leader>d",
|
||||
"model_list": "<leader>m",
|
||||
"theme_list": "<leader>t",
|
||||
"project_init": "<leader>i",
|
||||
"input_clear": "ctrl+c",
|
||||
"input_paste": "ctrl+v",
|
||||
"input_submit": "enter",
|
||||
"input_newline": "shift+enter,ctrl+j",
|
||||
"history_previous": "up",
|
||||
"history_next": "down",
|
||||
"messages_page_up": "pgup",
|
||||
"messages_page_down": "pgdown",
|
||||
"messages_half_page_up": "ctrl+alt+u",
|
||||
"messages_half_page_down": "ctrl+alt+d",
|
||||
"messages_previous": "ctrl+alt+k",
|
||||
"messages_next": "ctrl+alt+j",
|
||||
"messages_first": "ctrl+g",
|
||||
"messages_last": "ctrl+alt+g",
|
||||
"app_exit": "ctrl+c,<leader>q"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Leader key
|
||||
|
||||
opencode uses a `leader` key for most keybinds. This avoids conflicts in your terminal.
|
||||
|
||||
By default, `ctrl+x` is the leader key and most actions require you to first press the leader key and then the shortcut. For example, to start a new session you first press `ctrl+x` and then press `n`.
|
||||
|
||||
You don't need to use a leader key for your keybinds but we recommend doing so.
|
||||
@@ -2,7 +2,7 @@
|
||||
title: LSP servers
|
||||
---
|
||||
|
||||
OpenCode integrates with _Language Server Protocol_, or LSP to improve how the LLM interacts with your codebase.
|
||||
opencode integrates with _Language Server Protocol_, or LSP to improve how the LLM interacts with your codebase.
|
||||
|
||||
LSP servers for different languages give the LLM:
|
||||
|
||||
@@ -11,13 +11,13 @@ LSP servers for different languages give the LLM:
|
||||
|
||||
## Auto-detection
|
||||
|
||||
By default, OpenCode will **automatically detect** the languages used in your project and add the right LSP servers.
|
||||
By default, opencode will **automatically detect** the languages used in your project and add the right LSP servers.
|
||||
|
||||
## Manual configuration
|
||||
|
||||
You can also manually configure LSP servers by adding them under the `lsp` section in your OpenCode config.
|
||||
You can also manually configure LSP servers by adding them under the `lsp` section in your opencode config.
|
||||
|
||||
```json title=".opencode.json"
|
||||
```json title="opencode.json"
|
||||
{
|
||||
"lsp": {
|
||||
"go": {
|
||||
|
||||
@@ -2,27 +2,33 @@
|
||||
title: MCP servers
|
||||
---
|
||||
|
||||
You can add external tools to OpenCode using the _Model Context Protocol_, or MCP. OpenCode supports both:
|
||||
You can add external tools to opencode using the _Model Context Protocol_, or MCP. opencode supports both:
|
||||
|
||||
- Local servers that use standard input/output, `stdio`
|
||||
- Remote servers that use server-sent events `sse`
|
||||
- Local servers
|
||||
- And remote servers
|
||||
|
||||
## Add MCP servers
|
||||
Once added, MCP tools are automatically available to the LLM alongside built-in tools.
|
||||
|
||||
You can define MCP servers in your OpenCode config under the `mcpServers` section:
|
||||
---
|
||||
|
||||
## Configure
|
||||
|
||||
You can define MCP servers in your opencode config under `mcp`.
|
||||
|
||||
### Local
|
||||
|
||||
To add a local or `stdio` MCP server.
|
||||
Add a local MCP servers under `mcp.localmcp`.
|
||||
|
||||
```json title=".opencode.json" {4}
|
||||
```json title="opencode.json"
|
||||
{
|
||||
"mcpServers": {
|
||||
"local-example": {
|
||||
"type": "stdio",
|
||||
"command": "path/to/mcp-server",
|
||||
"env": [],
|
||||
"args": []
|
||||
"$schema": "https://opencode.ai/config.json",
|
||||
"mcp": {
|
||||
"localmcp": {
|
||||
"type": "local",
|
||||
"command": ["bun", "x", "my-mcp-command"],
|
||||
"environment": {
|
||||
"MY_ENV_VAR": "my_env_var_value"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -30,22 +36,16 @@ To add a local or `stdio` MCP server.
|
||||
|
||||
### Remote
|
||||
|
||||
To add a remote or `sse` MCP server.
|
||||
Add a remote MCP servers under `mcp.remotemcp`.
|
||||
|
||||
```json title=".opencode.json" {4}
|
||||
```json title="opencode.json"
|
||||
{
|
||||
"mcpServers": {
|
||||
"remote-example": {
|
||||
"type": "sse",
|
||||
"url": "https://example.com/mcp",
|
||||
"headers": {
|
||||
"Authorization": "Bearer token"
|
||||
}
|
||||
"$schema": "https://opencode.ai/config.json",
|
||||
"mcp": {
|
||||
"remotemcp": {
|
||||
"type": "remote",
|
||||
"url": "https://my-mcp-server.com"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
Once added, MCP tools are automatically available to the LLM alongside built-in tools. They follow the same permission model; requiring user approval before execution.
|
||||
|
||||
@@ -2,33 +2,88 @@
|
||||
title: Models
|
||||
---
|
||||
|
||||
OpenCode uses the [AI SDK](https://ai-sdk.dev/) to have the support for **all the AI models**.
|
||||
opencode uses the [AI SDK](https://ai-sdk.dev/) and [Models.dev](https://models.dev) to support for **75+ LLM providers** and it supports running local models.
|
||||
|
||||
Start by setting the [keys for the providers](/docs/config) you want to use in your OpenCode config.
|
||||
---
|
||||
|
||||
## Model select
|
||||
## Providers
|
||||
|
||||
You can now select the model you want from the menu by hitting `Ctrl+O`.
|
||||
You can configure providers in your opencode config under the `provider` section.
|
||||
|
||||
## Multiple models
|
||||
### Defaults
|
||||
|
||||
You can also use specific models for specific tasks. For example, you can use a smaller model to generate the title of the conversation or to run a sub task.
|
||||
Most popular providers are preloaded by default. If you've added the credentials for a provider through `opencode auth login`, they'll be available when you start opencode.
|
||||
|
||||
```json title=".opencode.json"
|
||||
### Custom
|
||||
|
||||
You can add custom providers by specifying the npm package for the provider and the models you want to use.
|
||||
|
||||
```json title="opencode.json" {5,9-11}
|
||||
{
|
||||
"agents": {
|
||||
"primary": {
|
||||
"model": "gpt-4",
|
||||
"maxTokens": 5000
|
||||
},
|
||||
"task": {
|
||||
"model": "gpt-3.5-turbo",
|
||||
"maxTokens": 5000
|
||||
},
|
||||
"title": {
|
||||
"model": "gpt-3.5-turbo",
|
||||
"maxTokens": 80
|
||||
"$schema": "https://opencode.ai/config.json",
|
||||
"provider": {
|
||||
"openrouter": {
|
||||
"npm": "@openrouter/ai-sdk-provider",
|
||||
"name": "OpenRouter",
|
||||
"options": {},
|
||||
"models": {
|
||||
"anthropic/claude-3.5-sonnet": {
|
||||
"name": "Claude 3.5 Sonnet"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Local
|
||||
|
||||
To configure a local model, specify the npm package to use and the `baseURL`.
|
||||
|
||||
```json title="opencode.json" {5,7}
|
||||
{
|
||||
"$schema": "https://opencode.ai/config.json",
|
||||
"provider": {
|
||||
"ollama": {
|
||||
"npm": "@ai-sdk/openai-compatible",
|
||||
"options": {
|
||||
"baseURL": "http://localhost:11434/v1"
|
||||
},
|
||||
"models": {
|
||||
"llama2": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Select a model
|
||||
|
||||
If you have multiple models, you can select the model you want by typing in:
|
||||
|
||||
```bash frame="none"
|
||||
/models
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Loading models
|
||||
|
||||
When opencode starts up, it checks for the following:
|
||||
|
||||
1. The model list in the opencode config.
|
||||
|
||||
```json title="opencode.json"
|
||||
{
|
||||
"$schema": "https://opencode.ai/config.json",
|
||||
"model": "anthropic/claude-sonnet-4-20250514"
|
||||
}
|
||||
```
|
||||
|
||||
The format here is `provider/model`.
|
||||
|
||||
2. The last used model.
|
||||
|
||||
3. The first model using an internal priority.
|
||||
|
||||
@@ -1,68 +0,0 @@
|
||||
---
|
||||
title: Keyboard shortcuts
|
||||
sidebar:
|
||||
label: Shortcuts
|
||||
---
|
||||
|
||||
Below are a list of keyboard shortcuts that OpenCode supports.
|
||||
|
||||
## Global
|
||||
|
||||
| Shortcut | Action |
|
||||
| -------- | ------------------------------------------------------- |
|
||||
| `Ctrl+C` | Quit application |
|
||||
| `Ctrl+?` | Toggle help dialog |
|
||||
| `?` | Toggle help dialog (when not in editing mode) |
|
||||
| `Ctrl+L` | View logs |
|
||||
| `Ctrl+A` | Switch session |
|
||||
| `Ctrl+K` | Command dialog |
|
||||
| `Ctrl+O` | Toggle model selection dialog |
|
||||
| `Esc` | Close current overlay/dialog or return to previous mode |
|
||||
|
||||
## Chat pane
|
||||
|
||||
| Shortcut | Action |
|
||||
| -------- | --------------------------------------- |
|
||||
| `Ctrl+N` | Create new session |
|
||||
| `Ctrl+X` | Cancel current operation/generation |
|
||||
| `i` | Focus editor (when not in writing mode) |
|
||||
| `Esc` | Exit writing mode and focus messages |
|
||||
|
||||
## Editor view
|
||||
|
||||
| Shortcut | Action |
|
||||
| ------------------- | ----------------------------------------- |
|
||||
| `Ctrl+S` | Send message (when editor is focused) |
|
||||
| `Enter` or `Ctrl+S` | Send message (when editor is not focused) |
|
||||
| `Ctrl+E` | Open external editor |
|
||||
| `Esc` | Blur editor and focus messages |
|
||||
|
||||
## Session dialog
|
||||
|
||||
| Shortcut | Action |
|
||||
| ---------- | ---------------- |
|
||||
| `↑` or `k` | Previous session |
|
||||
| `↓` or `j` | Next session |
|
||||
| `Enter` | Select session |
|
||||
| `Esc` | Close dialog |
|
||||
|
||||
## Model dialog
|
||||
|
||||
| Shortcut | Action |
|
||||
| ---------- | ----------------- |
|
||||
| `↑` or `k` | Move up |
|
||||
| `↓` or `j` | Move down |
|
||||
| `←` or `h` | Previous provider |
|
||||
| `→` or `l` | Next provider |
|
||||
| `Esc` | Close dialog |
|
||||
|
||||
## Permission dialog
|
||||
|
||||
| Shortcut | Action |
|
||||
| ----------------------- | ---------------------------- |
|
||||
| `←` or `left` | Switch options left |
|
||||
| `→` or `right` or `tab` | Switch options right |
|
||||
| `Enter` or `space` | Confirm selection |
|
||||
| `a` | Allow permission |
|
||||
| `A` | Allow permission for session |
|
||||
| `d` | Deny permission |
|
||||
@@ -2,74 +2,47 @@
|
||||
title: Themes
|
||||
---
|
||||
|
||||
OpenCode supports most common terminal themes and you can create your own custom theme.
|
||||
opencode will support most common terminal themes and you'll soon be able to create your own custom theme.
|
||||
|
||||
## Built-in themes
|
||||
---
|
||||
|
||||
The following predefined themes are available:
|
||||
## Built-in
|
||||
|
||||
The following built-in themes are available:
|
||||
|
||||
- `opencode`
|
||||
- `catppuccin`
|
||||
- `dracula`
|
||||
- `flexoki`
|
||||
- `gruvbox`
|
||||
- `monokai`
|
||||
- `onedark`
|
||||
|
||||

|
||||
|
||||
- `ayu`
|
||||
|
||||

|
||||
|
||||
- `everforest`
|
||||
|
||||

|
||||
|
||||
- `tokyonight`
|
||||
- `tron`
|
||||
- `custom`
|
||||
|
||||
Where `opencode` is the default theme and `custom` let's you define your own theme.
|
||||

|
||||
|
||||
## Setting a theme
|
||||
---
|
||||
|
||||
You can set your theme in your OpenCode config.
|
||||
## Configure
|
||||
|
||||
```json title=".opencode.json"
|
||||
{
|
||||
"tui": {
|
||||
"theme": "monokai"
|
||||
}
|
||||
}
|
||||
To select a theme, type in:
|
||||
|
||||
```bash frame="none"
|
||||
/themes
|
||||
```
|
||||
|
||||
## Create a theme
|
||||
Your selected theme will be used the next time you start opencode.
|
||||
|
||||
You can create your own custom theme by setting the `theme: custom` and providing color definitions through the `customTheme`.
|
||||
You can also configure it in your opencode config.
|
||||
|
||||
```json title=".opencode.json"
|
||||
```json title="opencode.json" {3}
|
||||
{
|
||||
"tui": {
|
||||
"theme": "custom",
|
||||
"customTheme": {
|
||||
"primary": "#ffcc00",
|
||||
"secondary": "#00ccff",
|
||||
"accent": { "dark": "#aa00ff", "light": "#ddccff" },
|
||||
"error": "#ff0000"
|
||||
}
|
||||
}
|
||||
"$schema": "https://opencode.ai/config.json",
|
||||
"theme": "tokyonight"
|
||||
}
|
||||
```
|
||||
|
||||
#### Color keys
|
||||
|
||||
You can define any of the following color keys in your `customTheme`.
|
||||
|
||||
| Type | Color keys |
|
||||
| ----------------- | ------------------------------------------------------- |
|
||||
| Base colors | `primary`, `secondary`, `accent` |
|
||||
| Status colors | `error`, `warning`, `success`, `info` |
|
||||
| Text colors | `text`, `textMuted` |
|
||||
| Background colors | `background`, `backgroundSubtle`, `backgroundElement` |
|
||||
| Border colors | `border`, `borderActive`, `borderSubtle` |
|
||||
| Diff view colors | `diffAdded`, `diffRemoved`, `diffContext`, etc. |
|
||||
|
||||
You don't need to define all the color keys. Any undefined colors will fall back to the default `opencode` theme colors.
|
||||
|
||||
#### Color definitions
|
||||
|
||||
Color keys can take:
|
||||
|
||||
1. **Hex string**: A single hex color string, like `"#aabbcc"`, that'll be used for both light and dark terminal backgrounds.
|
||||
|
||||
2. **Light and dark colors**: An object with `dark` and `light` hex colors that'll be set based on the terminal's background.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
title: OpenCode
|
||||
title: opencode
|
||||
description: The AI coding agent built for the terminal.
|
||||
template: splash
|
||||
hero:
|
||||
@@ -8,5 +8,5 @@ hero:
|
||||
image:
|
||||
dark: ../../assets/logo-dark.svg
|
||||
light: ../../assets/logo-light.svg
|
||||
alt: OpenCode logo
|
||||
alt: opencode logo
|
||||
---
|
||||
|
||||
@@ -42,6 +42,13 @@ const ogImage = `https://social-cards.sst.dev/opencode-share/${encodedTitle}.png
|
||||
template: "splash",
|
||||
tableOfContents: false,
|
||||
head: [
|
||||
{
|
||||
tag: "meta",
|
||||
attrs: {
|
||||
name: "description",
|
||||
content: "opencode - The AI coding agent built for the terminal.",
|
||||
},
|
||||
},
|
||||
{
|
||||
tag: "meta",
|
||||
attrs: {
|
||||
|
||||
@@ -3,6 +3,10 @@
|
||||
--sl-color-divider: var(--sl-color-gray-5);
|
||||
}
|
||||
|
||||
.expressive-code .frame {
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.shiki,
|
||||
.shiki span {
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 75 KiB |
Reference in New Issue
Block a user