[Sync] opencode 2.x
This commit is contained in:
@@ -0,0 +1,73 @@
|
||||
# opencode config
|
||||
|
||||
Config tree for [opencode](https://opencode.ai) (2.x), tuned for **small local models** behind a
|
||||
llama.cpp router. Stowed into `~/.config/opencode/` as per-file symlinks; editing here edits the live
|
||||
config (restart opencode, or `opencode service restart`, to pick changes up — the 2.x background
|
||||
service caches config).
|
||||
|
||||
## Layout
|
||||
|
||||
| Path | What |
|
||||
|---|---|
|
||||
| `opencode.json` | Providers, permissions, primary agents, defaults. Written in the v1 schema, which 2.x still accepts. |
|
||||
| `openai-gpt.jsonc` | Optional GPT/Codex OAuth overlay (OpenAI provider + `opencode-openai-codex-auth` plugin). Not loaded by itself — see *GPT overlay*. |
|
||||
| `AGENTS.md` | Global rules prepended to every session. Same text as the pi config's `AGENTS.md`: terse, verify-before-asserting, small-model friendly. |
|
||||
| `agents/*.md` | Four subagents with real prompts and scoped permissions: `code-reviewer`, `test-runner` (hidden), `doc-writer` (edits docs only), `security-auditor` (hidden). |
|
||||
| `commands/*.md` | `/commit` (Conventional Commits message, never commits) and `/review` (read-only diff review), both on the `plan` agent. |
|
||||
| `plugins/verbose/` | 2.x TUI plugin: `<leader>v`, `/verbose`, or the palette entry toggles the *verbose view* — thinking blocks expanded and tool calls ungrouped — reading the live state from the built-in toggles so a mixed state converges. `server.ts` is a no-op half required by the loader. |
|
||||
| `themes/catppuccin-mocha.json` | Catppuccin Mocha (dark) / Latte (light) theme following the pi theme's semantic mapping. Built-in `catppuccin` exists too; this one matches pi. |
|
||||
| `tui.json` | TUI settings in the v1 schema (2.x compat): theme, leader timeout, stacked diffs, block cursor, attention notifications, a few extra keybinds. |
|
||||
|
||||
The sandbox wrapper lives in `../fish/functions/opencodes.fish`: `opencodes` runs opencode under
|
||||
bwrap with the stowed config dirs, `~/.cache/opencode`, state and data bound, and only the needed
|
||||
environment passed through. Extra arguments go to opencode.
|
||||
|
||||
## Providers and models
|
||||
|
||||
Two OpenAI-compatible providers point at the same llama.cpp router: `llama.cpp` on the LAN and
|
||||
`duskadiy` through the public hostname (`apiKey` from `$DUSKADIY_API_KEY`). Model ids are the preset
|
||||
section names in `fl/.config/llamacpp/config.ini`; keep the lists in sync with it and with the pi and
|
||||
aichat configs. Both providers use a one-hour request timeout — the default five minutes cut off
|
||||
long local replies.
|
||||
|
||||
Decisions that look odd but are deliberate:
|
||||
|
||||
- **No `small_model`.** The router runs `--models-max 1`; any second model id evicts the resident one.
|
||||
With `small_model` unset, title generation falls back to the session's model. Subagents carry no
|
||||
pinned model for the same reason — the task tool uses the caller's model.
|
||||
- **No `temperature` / `top_p` from opencode.** Verified by capturing request bodies: only
|
||||
`max_tokens` is sent, so the router presets own sampling.
|
||||
- **`reasoning: true`** only on the presets that actually think (no `reasoning-budget = 0`);
|
||||
GLM also replays `reasoning_content` in history because its preset sets `reasoning-preserve`.
|
||||
- **`snapshot: false`** — file-change undo is not used. **`tool_output`** capped at 200 lines /
|
||||
16 KiB for 24k-context models. **`compaction.reserved`/`prune`** are inert at this context size.
|
||||
- **`question: allow`** everywhere so agents ask instead of guessing.
|
||||
|
||||
## Agents
|
||||
|
||||
`auto` is the default agent and cycles with `build` and `plan` on Tab. It runs without prompts except
|
||||
for the serious set — git history rewriting and pushing, `rm`, killing processes, `systemctl`,
|
||||
network transfers, package installs — which ask, and `sudo`/`dd`/`mkfs`/secret-file reads, which are
|
||||
denied. Colors: build primary, plan info, auto yellow. All three cap agentic iterations at 30.
|
||||
|
||||
Permission rules resolve **global first, agent rules appended, last match wins**, so an agent that
|
||||
wants a blanket rule must restate its own denies after it (the `auto` block does).
|
||||
|
||||
## GPT overlay
|
||||
|
||||
`openai-gpt.jsonc` is a template. opencode 2.x ignores `OPENCODE_CONFIG`, but it merges a second
|
||||
global file named `opencode.jsonc`. `fish/config.fish` links `opencode.jsonc → openai-gpt.jsonc`
|
||||
only on a host whose `~/.local/share/opencode/auth.json` holds an OpenAI credential (from
|
||||
`opencode auth login`), and removes the link again when the login is gone.
|
||||
|
||||
## Host overlays
|
||||
|
||||
`lw/.config/opencode/opencode.json` is a fork of the common file that adds the laptop's local CPU
|
||||
llama.cpp provider (`lwcpp`). Mirror every edit between the two.
|
||||
|
||||
## Lifting pieces into a stock opencode
|
||||
|
||||
Everything is plain files: copy `agents/`, `commands/`, `plugins/verbose/` or the theme into your own
|
||||
`~/.config/opencode/`. The plugin needs 2.x (it uses the `keymap.layer` / `dispatch` API and is
|
||||
discovered as a directory under `plugins/`). The permission blocks in `opencode.json` are independent
|
||||
of the providers and can be pasted as-is.
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"$schema": "https://opencode.ai/config.json",
|
||||
"model": "llama.cpp/gemma-4-26B-A4B-it-UD-IQ4_XS",
|
||||
"default_agent": "auto",
|
||||
"instructions": ["CLAUDE.md", "AGENTS.md"],
|
||||
"share": "disabled",
|
||||
"autoupdate": "notify",
|
||||
@@ -303,47 +304,6 @@
|
||||
"grep * .env*": "deny"
|
||||
}
|
||||
}
|
||||
},
|
||||
"code-reviewer": {
|
||||
"description": "Reviews code for best practices and potential issues",
|
||||
"prompt": "You are a code reviewer. Focus on security, performance, and maintainability.",
|
||||
"mode": "subagent",
|
||||
"permission": {
|
||||
"edit": "deny"
|
||||
}
|
||||
},
|
||||
"test-runner": {
|
||||
"description": "Runs and interprets test suites",
|
||||
"prompt": "You are a test runner. Focus on identifying failing tests and providing clear reproduction steps.",
|
||||
"mode": "subagent",
|
||||
"hidden": true,
|
||||
"permission": {
|
||||
"edit": "deny"
|
||||
}
|
||||
},
|
||||
"doc-writer": {
|
||||
"description": "Maintains project documentation and docstrings",
|
||||
"prompt": "You are a technical writer. Focus on clarity, accuracy, and keeping documentation in sync with code changes.",
|
||||
"mode": "subagent",
|
||||
"permission": {
|
||||
"edit": {
|
||||
"*": "deny",
|
||||
"**/*.md": "allow",
|
||||
"**/*.mdx": "allow",
|
||||
"**/*.markdown": "allow",
|
||||
"**/*.txt": "allow",
|
||||
"**/*.rst": "allow"
|
||||
}
|
||||
}
|
||||
},
|
||||
"security-auditor": {
|
||||
"description": "Scans for security vulnerabilities and hardcoded secrets",
|
||||
"prompt": "You are a security auditor. Focus on OWASP principles, secret detection, and dependency vulnerabilities.",
|
||||
"mode": "subagent",
|
||||
"hidden": true,
|
||||
"permission": {
|
||||
"edit": "deny"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
// opencode v2 plugin "coja.verbose" — server half. Intentionally empty: all behaviour is in ./tui.ts.
|
||||
// A local plugin is a DIRECTORY under ~/.config/opencode/plugins/ whose `server`/`index` and `tui`
|
||||
// modules are resolved by name (packages/plugin/src/host.ts resolve()). No imports on purpose:
|
||||
// `define()` is an identity helper, and a dependency-free module needs no node_modules.
|
||||
export default {
|
||||
id: "coja.verbose",
|
||||
setup() {},
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
// opencode v2 TUI plugin: one key toggles the "verbose view".
|
||||
//
|
||||
// v2 dropped the v1 tool-details / timestamps / generic-output toggles. The session view now has two
|
||||
// display toggles that matter: thinking blocks (session.thinking show|hide) and exploration grouping
|
||||
// (session.grouping auto|none — "auto" folds read/glob/grep tool calls into one collapsed group).
|
||||
// Verbose = thinking expanded AND tool calls shown individually. Both settings persist in the TUI config.
|
||||
//
|
||||
// State is read from the built-in commands' live titles (the only plugin-visible signal):
|
||||
// session.toggle.thinking → "Collapse thinking" while expanded, else "Expand thinking"
|
||||
// session.toggle.exploration_grouping → "Show tool calls individually" while grouped, else "Group related tool calls"
|
||||
// then only the toggles that differ from the target are dispatched, so a mixed state converges.
|
||||
//
|
||||
// Auto-discovered from ~/.config/opencode/plugins/verbose/ (server.ts + this file). Key configurable
|
||||
// via plugin options; default <leader>v. Also /verbose and a command-palette entry.
|
||||
const THINKING = "session.toggle.thinking"
|
||||
const GROUPING = "session.toggle.exploration_grouping"
|
||||
|
||||
type Command = { readonly id?: string; readonly title?: string }
|
||||
type Context = {
|
||||
readonly options?: Readonly<Record<string, unknown>>
|
||||
readonly keymap: {
|
||||
layer(input: () => { readonly commands?: readonly unknown[] }): void
|
||||
dispatch(id: string, input?: string): void
|
||||
commands(): readonly Command[]
|
||||
}
|
||||
readonly ui: { readonly toast: { show(o: { message: string; variant?: string; duration?: number }): void } }
|
||||
}
|
||||
|
||||
export default {
|
||||
id: "coja.verbose",
|
||||
setup(context: Context) {
|
||||
const key = typeof context.options?.key === "string" && context.options.key ? (context.options.key as string) : "<leader>v"
|
||||
const title = (id: string) => context.keymap.commands().find((c) => c.id === id)?.title ?? ""
|
||||
|
||||
context.keymap.layer(() => ({
|
||||
commands: [
|
||||
{
|
||||
id: "verbose.toggle",
|
||||
title: "Toggle verbose view (expand thinking, ungroup tool calls)",
|
||||
group: "Session",
|
||||
bind: key,
|
||||
palette: true,
|
||||
slash: { name: "verbose" },
|
||||
run() {
|
||||
const thinkingTitle = title(THINKING)
|
||||
const groupingTitle = title(GROUPING)
|
||||
if (!thinkingTitle && !groupingTitle) {
|
||||
context.ui.toast.show({ message: "Verbose view: open a session first", variant: "warning", duration: 2000 })
|
||||
return
|
||||
}
|
||||
const thinkingVerbose = thinkingTitle.startsWith("Collapse")
|
||||
const groupingVerbose = groupingTitle.startsWith("Group")
|
||||
const on = !(thinkingVerbose && groupingVerbose)
|
||||
if (thinkingTitle && thinkingVerbose !== on) context.keymap.dispatch(THINKING)
|
||||
if (groupingTitle && groupingVerbose !== on) context.keymap.dispatch(GROUPING)
|
||||
context.ui.toast.show({ message: on ? "Verbose view on" : "Verbose view off", variant: "info", duration: 1500 })
|
||||
},
|
||||
},
|
||||
],
|
||||
}))
|
||||
},
|
||||
}
|
||||
@@ -1,71 +0,0 @@
|
||||
// opencode TUI plugin: one key toggles the whole "verbose view".
|
||||
//
|
||||
// The session view keeps four independent display toggles in the TUI's persisted KV store
|
||||
// (~/.local/state/opencode/kv.json). Built-in commands only flip one each. This plugin reads all
|
||||
// four and drives them to the same state: if every one is on, turn all off; otherwise turn all on.
|
||||
// Writing the KV keys directly is what the built-in toggles do too (kv.signal is a reactive
|
||||
// store), so the open session re-renders immediately and the choice survives restarts.
|
||||
//
|
||||
// Declared in tui.json: "plugin": [["./tui-plugins/verbose.ts", { "key": "<leader>v" }]]
|
||||
// (TUI plugins are never auto-discovered, and this dir is deliberately NOT ~/.config/opencode/plugins/,
|
||||
// which the server-side plugin loader scans and would reject a TUI-only module.)
|
||||
// Also available as /verbose and from the command palette.
|
||||
import type { TuiPlugin, TuiPluginApi, TuiPluginModule } from "@opencode-ai/plugin/tui"
|
||||
|
||||
// KV keys + values as used by packages/tui/src/routes/session/index.tsx and context/thinking.ts (v1.18.29)
|
||||
const KEYS = {
|
||||
details: "tool_details_visibility", // boolean, default true
|
||||
generic: "generic_tool_output_visibility", // boolean, default false
|
||||
timestamps: "timestamps", // "show" | "hide", default "hide"
|
||||
thinking: "thinking_mode", // "show" | "hide", default "hide"
|
||||
} as const
|
||||
|
||||
const COMMAND = "verbose.toggle"
|
||||
|
||||
function current(api: TuiPluginApi) {
|
||||
return {
|
||||
details: api.kv.get<boolean>(KEYS.details, true) === true,
|
||||
generic: api.kv.get<boolean>(KEYS.generic, false) === true,
|
||||
timestamps: api.kv.get<string>(KEYS.timestamps, "hide") === "show",
|
||||
thinking: api.kv.get<string>(KEYS.thinking, "hide") === "show",
|
||||
}
|
||||
}
|
||||
|
||||
function apply(api: TuiPluginApi, on: boolean) {
|
||||
api.kv.set(KEYS.details, on)
|
||||
api.kv.set(KEYS.generic, on)
|
||||
api.kv.set(KEYS.timestamps, on ? "show" : "hide")
|
||||
api.kv.set(KEYS.thinking, on ? "show" : "hide")
|
||||
}
|
||||
|
||||
const tui: TuiPlugin = async (api, options) => {
|
||||
const opts = (options ?? {}) as Record<string, unknown>
|
||||
const key = typeof opts.key === "string" && opts.key.length > 0 ? opts.key : "<leader>v"
|
||||
|
||||
api.keymap.registerLayer({
|
||||
mode: "base",
|
||||
commands: [
|
||||
{
|
||||
name: COMMAND,
|
||||
title: "Toggle verbose view (tool details, generic output, thinking, timestamps)",
|
||||
category: "Session",
|
||||
namespace: "palette",
|
||||
slashName: "verbose",
|
||||
run() {
|
||||
const state = current(api)
|
||||
const on = !Object.values(state).every(Boolean)
|
||||
apply(api, on)
|
||||
api.ui.toast({
|
||||
variant: "info",
|
||||
message: on ? "Verbose view on" : "Verbose view off",
|
||||
duration: 1500,
|
||||
})
|
||||
},
|
||||
},
|
||||
],
|
||||
bindings: [{ key, cmd: COMMAND, desc: "Toggle verbose view" }],
|
||||
})
|
||||
}
|
||||
|
||||
const plugin: TuiPluginModule & { id: string } = { id: "coja.verbose", tui }
|
||||
export default plugin
|
||||
@@ -1,7 +1,6 @@
|
||||
{
|
||||
"$schema": "https://opencode.ai/tui.json",
|
||||
"theme": "lucent-orng",
|
||||
"plugin": [["./tui-plugins/verbose.ts", { "key": "<leader>v" }]],
|
||||
"leader_timeout": 1500,
|
||||
"diff_style": "stacked",
|
||||
"cursor": { "style": "block", "blinking": false },
|
||||
@@ -11,8 +10,6 @@
|
||||
"prompt_stash_pop": "<leader>o",
|
||||
"session_fork": "<leader>f",
|
||||
"messages_last_user": "<leader>k",
|
||||
"display_thinking": "<leader>i",
|
||||
"tool_details": "<leader>d",
|
||||
"session_toggle_timestamps": "<leader>w"
|
||||
"display_thinking": "<leader>i"
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user