mirror of
https://github.com/sst/opencode.git
synced 2025-07-07 16:14:59 +00:00
BREAKING CONFIG CHANGE
We have changed the config format yet again - but this should be the final time. You can see the readme for more details but the summary is - got rid of global providers config - got rid of global toml - global config is now in `~/.config/opencode/config.json` - it will be merged with any project level config
This commit is contained in:
parent
e5e9b3e3c0
commit
bd8c3cd0f1
8 changed files with 273 additions and 123 deletions
102
README.md
102
README.md
|
@ -61,66 +61,56 @@ The Models.dev dataset is also used to detect common environment variables like
|
|||
|
||||
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.
|
||||
|
||||
### Global Config
|
||||
### Config
|
||||
|
||||
Some basic configuration is available in the global config file.
|
||||
|
||||
```toml
|
||||
# ~/.config/opencode/config
|
||||
theme = "opencode"
|
||||
provider = "anthropic"
|
||||
model = "claude-sonnet-4-20250514"
|
||||
autoupdate = true
|
||||
|
||||
keybinds.leader = "ctrl+x"
|
||||
keybinds.session_new = "<leader>n"
|
||||
keybinds.editor_open = "<leader>e"
|
||||
```
|
||||
|
||||
#### Keybinds
|
||||
|
||||
You can configure the keybinds in the global config file. (Note: values listed below are the defaults.)
|
||||
|
||||
```toml
|
||||
# ~/.config/opencode/config
|
||||
|
||||
[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"
|
||||
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"
|
||||
```
|
||||
|
||||
### Project Config
|
||||
|
||||
Project configuration is optional. You can place an `opencode.json` file in the root of your repo and is meant to be checked in and shared with your team.
|
||||
Config is optional and can be placed in the root of your repo or globally in `~/.config/opencode/config`. 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",
|
||||
"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"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -147,7 +137,7 @@ Project configuration is optional. You can place an `opencode.json` file in the
|
|||
|
||||
#### 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.
|
||||
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"
|
||||
{
|
||||
|
|
|
@ -95,16 +95,23 @@
|
|||
"additionalProperties": false
|
||||
},
|
||||
"autoshare": {
|
||||
"type": "boolean"
|
||||
"type": "boolean",
|
||||
"description": "Share newly created sessions automatically"
|
||||
},
|
||||
"autoupdate": {
|
||||
"type": "boolean"
|
||||
"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"
|
||||
},
|
||||
"provider": {
|
||||
"type": "object",
|
||||
|
|
|
@ -64,44 +64,62 @@ export namespace Config {
|
|||
export const Mcp = z.discriminatedUnion("type", [McpLocal, McpRemote])
|
||||
export type Mcp = z.infer<typeof Mcp>
|
||||
|
||||
export const Keybinds = z
|
||||
.object({
|
||||
leader: z.string().optional(),
|
||||
help: z.string().optional(),
|
||||
editor_open: z.string().optional(),
|
||||
session_new: z.string().optional(),
|
||||
session_list: z.string().optional(),
|
||||
session_share: z.string().optional(),
|
||||
session_interrupt: z.string().optional(),
|
||||
session_compact: z.string().optional(),
|
||||
tool_details: z.string().optional(),
|
||||
model_list: z.string().optional(),
|
||||
theme_list: z.string().optional(),
|
||||
project_init: z.string().optional(),
|
||||
input_clear: z.string().optional(),
|
||||
input_paste: z.string().optional(),
|
||||
input_submit: z.string().optional(),
|
||||
input_newline: z.string().optional(),
|
||||
history_previous: z.string().optional(),
|
||||
history_next: z.string().optional(),
|
||||
messages_page_up: z.string().optional(),
|
||||
messages_page_down: z.string().optional(),
|
||||
messages_half_page_up: z.string().optional(),
|
||||
messages_half_page_down: z.string().optional(),
|
||||
messages_previous: z.string().optional(),
|
||||
messages_next: z.string().optional(),
|
||||
messages_first: z.string().optional(),
|
||||
messages_last: z.string().optional(),
|
||||
app_exit: z.string().optional(),
|
||||
})
|
||||
.openapi({
|
||||
ref: "Config.Keybinds",
|
||||
})
|
||||
export const Info = z
|
||||
.object({
|
||||
$schema: z.string().optional(),
|
||||
theme: z.string().optional(),
|
||||
keybinds: z
|
||||
.object({
|
||||
leader: z.string().optional(),
|
||||
help: z.string().optional(),
|
||||
editor_open: z.string().optional(),
|
||||
session_new: z.string().optional(),
|
||||
session_list: z.string().optional(),
|
||||
session_share: z.string().optional(),
|
||||
session_interrupt: z.string().optional(),
|
||||
session_compact: z.string().optional(),
|
||||
tool_details: z.string().optional(),
|
||||
model_list: z.string().optional(),
|
||||
theme_list: z.string().optional(),
|
||||
project_init: z.string().optional(),
|
||||
input_clear: z.string().optional(),
|
||||
input_paste: z.string().optional(),
|
||||
input_submit: z.string().optional(),
|
||||
input_newline: z.string().optional(),
|
||||
history_previous: z.string().optional(),
|
||||
history_next: z.string().optional(),
|
||||
messages_page_up: z.string().optional(),
|
||||
messages_page_down: z.string().optional(),
|
||||
messages_half_page_up: z.string().optional(),
|
||||
messages_half_page_down: z.string().optional(),
|
||||
messages_previous: z.string().optional(),
|
||||
messages_next: z.string().optional(),
|
||||
messages_first: z.string().optional(),
|
||||
messages_last: z.string().optional(),
|
||||
app_exit: z.string().optional(),
|
||||
})
|
||||
keybinds: Keybinds.optional(),
|
||||
autoshare: z
|
||||
.boolean()
|
||||
.optional()
|
||||
.describe("Share newly created sessions automatically"),
|
||||
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"),
|
||||
model: z
|
||||
.string()
|
||||
.describe(
|
||||
"Model to use in the format of provider/model, eg anthropic/claude-2",
|
||||
)
|
||||
.optional(),
|
||||
autoshare: z.boolean().optional(),
|
||||
autoupdate: z.boolean().optional(),
|
||||
disabled_providers: z.array(z.string()).optional(),
|
||||
provider: z
|
||||
.record(
|
||||
ModelsDev.Provider.partial().extend({
|
||||
|
@ -130,9 +148,9 @@ export namespace Config {
|
|||
},
|
||||
})
|
||||
.then(async (mod) => {
|
||||
delete mod.default.provider
|
||||
delete mod.default.model
|
||||
result = mergeDeep(result, mod.default)
|
||||
const { provider, model, ...rest } = mod.default
|
||||
if (provider && model) result.model = `${provider}/${model}`
|
||||
result = mergeDeep(result, rest)
|
||||
await Bun.write(
|
||||
path.join(Global.Path.config, "config.json"),
|
||||
JSON.stringify(result, null, 2),
|
||||
|
|
|
@ -5,6 +5,7 @@ import (
|
|||
"fmt"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"log/slog"
|
||||
|
||||
|
@ -61,8 +62,10 @@ func New(
|
|||
}
|
||||
configInfo := configResponse.JSON200
|
||||
if configInfo.Keybinds == nil {
|
||||
keybinds := make(map[string]string)
|
||||
keybinds["leader"] = "ctrl+x"
|
||||
leader := "ctrl+x"
|
||||
keybinds := client.ConfigKeybinds{
|
||||
Leader: &leader,
|
||||
}
|
||||
configInfo.Keybinds = &keybinds
|
||||
}
|
||||
|
||||
|
@ -76,6 +79,12 @@ func New(
|
|||
if configInfo.Theme != nil {
|
||||
appState.Theme = *configInfo.Theme
|
||||
}
|
||||
if configInfo.Model != nil {
|
||||
splits := strings.Split(*configInfo.Model, "/")
|
||||
appState.Provider = splits[0]
|
||||
appState.Model = strings.Join(splits[1:], "/")
|
||||
}
|
||||
|
||||
if appState.Theme != "" {
|
||||
theme.SetTheme(appState.Theme)
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package commands
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
|
@ -106,17 +107,6 @@ func (k Command) Matches(msg tea.KeyPressMsg, leader bool) bool {
|
|||
return false
|
||||
}
|
||||
|
||||
func (k Command) FromConfig(config *client.ConfigInfo) Command {
|
||||
if config.Keybinds == nil {
|
||||
return k
|
||||
}
|
||||
keybinds := *config.Keybinds
|
||||
if keybind, ok := keybinds[string(k.Name)]; ok {
|
||||
k.Keybindings = parseBindings(keybind)
|
||||
}
|
||||
return k
|
||||
}
|
||||
|
||||
func parseBindings(bindings ...string) []Keybinding {
|
||||
var parsedBindings []Keybinding
|
||||
for _, binding := range bindings {
|
||||
|
@ -278,8 +268,14 @@ func LoadFromConfig(config *client.ConfigInfo) CommandRegistry {
|
|||
},
|
||||
}
|
||||
registry := make(CommandRegistry)
|
||||
keybinds := map[string]string{}
|
||||
marshalled, _ := json.Marshal(*config.Keybinds)
|
||||
json.Unmarshal(marshalled, &keybinds)
|
||||
for _, command := range defaults {
|
||||
registry[command.Name] = command.FromConfig(config)
|
||||
if keybind, ok := keybinds[string(command.Name)]; ok {
|
||||
command.Keybindings = parseBindings(keybind)
|
||||
}
|
||||
registry[command.Name] = command
|
||||
}
|
||||
return registry
|
||||
}
|
||||
|
|
|
@ -509,8 +509,8 @@ func NewModel(app *app.App) tea.Model {
|
|||
messagesContainer := layout.NewContainer(messages)
|
||||
|
||||
var leaderBinding *key.Binding
|
||||
if leader, ok := (*app.Configg.Keybinds)["leader"]; ok {
|
||||
binding := key.NewBinding(key.WithKeys(leader))
|
||||
if (*app.Configg.Keybinds).Leader != nil {
|
||||
binding := key.NewBinding(key.WithKeys(*app.Configg.Keybinds.Leader))
|
||||
leaderBinding = &binding
|
||||
}
|
||||
|
||||
|
|
|
@ -1397,22 +1397,26 @@
|
|||
"type": "string"
|
||||
},
|
||||
"keybinds": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
"$ref": "#/components/schemas/Config.Keybinds"
|
||||
},
|
||||
"autoshare": {
|
||||
"type": "boolean"
|
||||
"type": "boolean",
|
||||
"description": "Share newly created sessions automatically"
|
||||
},
|
||||
"autoupdate": {
|
||||
"type": "boolean"
|
||||
"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"
|
||||
},
|
||||
"provider": {
|
||||
"type": "object",
|
||||
|
@ -1528,6 +1532,92 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"Config.Keybinds": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"leader": {
|
||||
"type": "string"
|
||||
},
|
||||
"help": {
|
||||
"type": "string"
|
||||
},
|
||||
"editor_open": {
|
||||
"type": "string"
|
||||
},
|
||||
"session_new": {
|
||||
"type": "string"
|
||||
},
|
||||
"session_list": {
|
||||
"type": "string"
|
||||
},
|
||||
"session_share": {
|
||||
"type": "string"
|
||||
},
|
||||
"session_interrupt": {
|
||||
"type": "string"
|
||||
},
|
||||
"session_compact": {
|
||||
"type": "string"
|
||||
},
|
||||
"tool_details": {
|
||||
"type": "string"
|
||||
},
|
||||
"model_list": {
|
||||
"type": "string"
|
||||
},
|
||||
"theme_list": {
|
||||
"type": "string"
|
||||
},
|
||||
"project_init": {
|
||||
"type": "string"
|
||||
},
|
||||
"input_clear": {
|
||||
"type": "string"
|
||||
},
|
||||
"input_paste": {
|
||||
"type": "string"
|
||||
},
|
||||
"input_submit": {
|
||||
"type": "string"
|
||||
},
|
||||
"input_newline": {
|
||||
"type": "string"
|
||||
},
|
||||
"history_previous": {
|
||||
"type": "string"
|
||||
},
|
||||
"history_next": {
|
||||
"type": "string"
|
||||
},
|
||||
"messages_page_up": {
|
||||
"type": "string"
|
||||
},
|
||||
"messages_page_down": {
|
||||
"type": "string"
|
||||
},
|
||||
"messages_half_page_up": {
|
||||
"type": "string"
|
||||
},
|
||||
"messages_half_page_down": {
|
||||
"type": "string"
|
||||
},
|
||||
"messages_previous": {
|
||||
"type": "string"
|
||||
},
|
||||
"messages_next": {
|
||||
"type": "string"
|
||||
},
|
||||
"messages_first": {
|
||||
"type": "string"
|
||||
},
|
||||
"messages_last": {
|
||||
"type": "string"
|
||||
},
|
||||
"app_exit": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"Provider.Info": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
|
@ -41,13 +41,22 @@ type AppInfo struct {
|
|||
|
||||
// ConfigInfo defines model for Config.Info.
|
||||
type ConfigInfo struct {
|
||||
Schema *string `json:"$schema,omitempty"`
|
||||
Autoshare *bool `json:"autoshare,omitempty"`
|
||||
Autoupdate *bool `json:"autoupdate,omitempty"`
|
||||
Schema *string `json:"$schema,omitempty"`
|
||||
|
||||
// Autoshare Share newly created sessions automatically
|
||||
Autoshare *bool `json:"autoshare,omitempty"`
|
||||
|
||||
// Autoupdate Automatically update to the latest version
|
||||
Autoupdate *bool `json:"autoupdate,omitempty"`
|
||||
|
||||
// DisabledProviders Disable providers that are loaded automatically
|
||||
DisabledProviders *[]string `json:"disabled_providers,omitempty"`
|
||||
Keybinds *map[string]string `json:"keybinds,omitempty"`
|
||||
Keybinds *ConfigKeybinds `json:"keybinds,omitempty"`
|
||||
Mcp *map[string]ConfigInfo_Mcp_AdditionalProperties `json:"mcp,omitempty"`
|
||||
Provider *map[string]struct {
|
||||
|
||||
// Model Model to use in the format of provider/model, eg anthropic/claude-2
|
||||
Model *string `json:"model,omitempty"`
|
||||
Provider *map[string]struct {
|
||||
Api *string `json:"api,omitempty"`
|
||||
Env *[]string `json:"env,omitempty"`
|
||||
Id *string `json:"id,omitempty"`
|
||||
|
@ -80,6 +89,37 @@ type ConfigInfo_Mcp_AdditionalProperties struct {
|
|||
union json.RawMessage
|
||||
}
|
||||
|
||||
// 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"`
|
||||
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"`
|
||||
}
|
||||
|
||||
// ConfigMcpLocal defines model for Config.McpLocal.
|
||||
type ConfigMcpLocal struct {
|
||||
Command []string `json:"command"`
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue