Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
- To regenerate the legacy JavaScript SDK, run `./packages/sdk/js/script/build.ts`.
- After changing the public Protocol or Server `HttpApi`, run `bun run generate` from `packages/client`. Do not edit `src/generated` or `src/generated-effect` directly.
- Keep runtime dependencies directed from Schema to Core and Protocol, then from Core and Protocol to Server. Client runtime code may depend on Schema and Protocol but never Core or Server; `sdk-next` composes Client, Core, and Server.
- The default branch in this repo is `dev`.
- Local `main` ref may not exist; use `dev` or `origin/dev` for diffs.
- The default branch in this repo is `main`; use `main` or `origin/main` for diffs. (Upstream opencode's default is `dev`; this fork does not have that branch.)

## Branch Names

Expand Down
15 changes: 9 additions & 6 deletions DIVERGENCES.md

Large diffs are not rendered by default.

8 changes: 4 additions & 4 deletions packages/core/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -178,28 +178,28 @@ const layer = Layer.effect(
? []
: yield* fs
.up({
targets: [".opencode", ...names.toReversed()],
targets: [".codev", ...names.toReversed()],
start: location.directory,
stop: location.project.directory,
})
.pipe(Effect.orDie)
const directories = [
globalDirectory,
...discovered
.filter((item) => path.basename(item) === ".opencode")
.filter((item) => path.basename(item) === ".codev")
.toReversed()
.map((directory) => AbsolutePath.make(directory)),
]
// A config closer to the opened directory should win over one higher up.
// Search starts nearby, so reverse the results before applying them.
const directPaths = discovered.filter((item) => path.basename(item) !== ".opencode").toReversed()
const directPaths = discovered.filter((item) => path.basename(item) !== ".codev").toReversed()
const direct = yield* Effect.forEach(directPaths, loadFile).pipe(
Effect.orDie,
Effect.map((configs) => configs.filter((config): config is Document => config !== undefined)),
)
const supplementary = yield* Effect.forEach(directories, loadDirectory).pipe(Effect.orDie)
// Apply general settings first and more specific settings last:
// global config, project files, then `.opencode` files.
// global config, project files, then `.codev` files.
const configs = [...(supplementary[0] ?? []), ...direct, ...supplementary.slice(1).flat()]
// Rules use the opposite order so a user-global rule can override a
// repository rule. Statement order inside each file stays unchanged.
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/global.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { Flock } from "./util/flock"
import { Flag } from "./flag/flag"
import { makeGlobalNode } from "./effect/app-node"

const app = "codev-code"
const app = "codev"
const data = path.join(xdgData!, app)
const cache = path.join(xdgCache!, app)
const config = path.join(xdgConfig!, app)
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/plugin/agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ export const Plugin = define({
{ action: "plan_exit", resource: "*", effect: "allow" },
{ action: "external_directory", resource: path.join(Global.Path.data, "plans", "*"), effect: "allow" },
{ action: "edit", resource: "*", effect: "deny" },
{ action: "edit", resource: path.join(".opencode", "plans", "*.md"), effect: "allow" },
{ action: "edit", resource: path.join(".codev", "plans", "*.md"), effect: "allow" },
{
action: "edit",
resource: path.relative(worktree, path.join(Global.Path.data, "plans", "*.md")),
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/plugin/skill.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export const Plugin = define({
skill: SkillV2.Info.make({
name: "customize-opencode",
description:
"Use ONLY when the user is editing or creating opencode's own configuration: codev.json, codev.jsonc, files under .opencode/, or files under ~/.config/opencode/. Also use when creating or fixing opencode agents, subagents, commands, skills, plugins, MCP servers, or permission rules. Do not use for the user's own application code, or for any project that is not configuring opencode itself.",
"Use ONLY when the user is editing or creating opencode's own configuration: codev.json, codev.jsonc, files under .codev/, or files under ~/.config/codev/. Also use when creating or fixing opencode agents, subagents, commands, skills, plugins, MCP servers, or permission rules. Do not use for the user's own application code, or for any project that is not configuring opencode itself.",
location: AbsolutePath.make("/builtin/customize-opencode.md"),
content: CustomizeOpencodeContent,
}),
Expand Down
26 changes: 13 additions & 13 deletions packages/core/src/plugin/skill/customize-opencode.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,14 +39,14 @@ already-loaded config until then.

| Scope | Path |
| ----------------------------- | ------------------------------------------------------------------------------------------------------------------------- |
| Project config | `./codev.json`, `./codev.jsonc`, or `.opencode/codev.json` (opencode walks up from the cwd to the worktree root) |
| Global config | `~/.config/opencode/codev.json` (NOT `~/.opencode/`) |
| Project agents | `.opencode/agent/<name>.md` or `.opencode/agents/<name>.md` |
| Global agents | `~/.config/opencode/agent(s)/<name>.md` |
| Project commands | `.opencode/command/<name>.md` or `.opencode/commands/<name>.md` |
| Global commands | `~/.config/opencode/command(s)/<name>.md` |
| Project skills | `.opencode/skill(s)/<name>/SKILL.md` |
| Global skills | `~/.config/opencode/skill(s)/<name>/SKILL.md` |
| Project config | `./codev.json`, `./codev.jsonc`, or `.codev/codev.json` (opencode walks up from the cwd to the worktree root) |
| Global config | `~/.config/codev/codev.json` (`~/.codev/` is a config directory for agents/commands/plugins, not where the global config file lives) |
| Project agents | `.codev/agent/<name>.md` or `.codev/agents/<name>.md` |
| Global agents | `~/.config/codev/agent(s)/<name>.md` |
| Project commands | `.codev/command/<name>.md` or `.codev/commands/<name>.md` |
| Global commands | `~/.config/codev/command(s)/<name>.md` |
| Project skills | `.codev/skill(s)/<name>/SKILL.md` |
| Global skills | `~/.config/codev/skill(s)/<name>/SKILL.md` |
| External skills (auto-loaded) | `~/.claude/skills/<name>/SKILL.md`, `~/.agents/skills/<name>/SKILL.md` |

Configs from each scope are deep-merged. Project overrides global. Unknown
Expand All @@ -71,7 +71,7 @@ Every field is optional.
"instructions": ["AGENTS.md", "docs/style.md"],

"skills": {
"paths": [".opencode/skills", "/abs/path/to/skills"],
"paths": [".codev/skills", "/abs/path/to/skills"],
"urls": ["https://example.com/.well-known/skills/"]
},

Expand Down Expand Up @@ -165,7 +165,7 @@ file is named `SKILL.md` exactly, and lives in its own folder named after the
skill:

```
.opencode/skills/my-skill/SKILL.md
.codev/skills/my-skill/SKILL.md
```

Frontmatter:
Expand Down Expand Up @@ -244,7 +244,7 @@ Two ways to define an agent. Use the file form for anything non-trivial.
### File

```
.opencode/agent/my-reviewer.md OR .opencode/agents/my-reviewer.md
.codev/agent/my-reviewer.md OR .codev/agents/my-reviewer.md
```

```markdown
Expand Down Expand Up @@ -286,7 +286,7 @@ opencode's command loader scans for `**/*.md` inside command directories. The
file is named after the command, and lives directly inside the `command` folder:

```
.opencode/command/deploy.md
.codev/command/deploy.md
```

Frontmatter:
Expand Down Expand Up @@ -320,7 +320,7 @@ model: anthropic/claude-sonnet-4-6
```

Auto-discovered plugins (no config entry needed): any `*.ts` or `*.js` file in
`.opencode/plugin/` or `.opencode/plugins/`.
`.codev/plugin/` or `.codev/plugins/`.

A plugin module exports `default` (or any named export) of type
`Plugin = (input: PluginInput, options?) => Promise<Hooks>`. The export is a
Expand Down
18 changes: 9 additions & 9 deletions packages/core/test/config/config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -749,7 +749,7 @@ describe("Config", () => {
),
)

it.live("loads global, ancestor, and .opencode configuration up to the project boundary", () =>
it.live("loads global, ancestor, and .codev configuration up to the project boundary", () =>
Effect.acquireRelease(
Effect.promise(() => tmpdir()),
(tmp) => Effect.promise(() => tmp[Symbol.asyncDispose]()),
Expand All @@ -763,17 +763,17 @@ describe("Config", () => {
yield* Effect.promise(async () => {
await fs.mkdir(global, { recursive: true })
await fs.mkdir(directory, { recursive: true })
await fs.mkdir(path.join(root, ".opencode"), { recursive: true })
await fs.mkdir(path.join(directory, ".opencode"), { recursive: true })
await fs.mkdir(path.join(root, ".codev"), { recursive: true })
await fs.mkdir(path.join(directory, ".codev"), { recursive: true })
await Promise.all([
fs.writeFile(path.join(tmp.path, "codev.json"), JSON.stringify({ $schema: "outside" })),
fs.writeFile(path.join(global, "codev.json"), JSON.stringify({ $schema: "global" })),
fs.writeFile(path.join(root, "codev.json"), JSON.stringify({ $schema: "root" })),
fs.writeFile(path.join(parent, "codev.jsonc"), JSON.stringify({ $schema: "parent" })),
fs.writeFile(path.join(directory, "codev.json"), JSON.stringify({ $schema: "directory" })),
fs.writeFile(path.join(root, ".opencode", "codev.json"), JSON.stringify({ $schema: "root-dot" })),
fs.writeFile(path.join(root, ".codev", "codev.json"), JSON.stringify({ $schema: "root-dot" })),
fs.writeFile(
path.join(directory, ".opencode", "codev.jsonc"),
path.join(directory, ".codev", "codev.jsonc"),
JSON.stringify({ $schema: "directory-dot" }),
),
])
Expand All @@ -786,8 +786,8 @@ describe("Config", () => {

expect(entries.filter((entry) => entry.type === "directory").map((entry) => entry.path)).toEqual([
AbsolutePath.make(global),
AbsolutePath.make(path.join(root, ".opencode")),
AbsolutePath.make(path.join(directory, ".opencode")),
AbsolutePath.make(path.join(root, ".codev")),
AbsolutePath.make(path.join(directory, ".codev")),
])
expect(documents.map((document) => document.info.$schema)).toEqual([
"global",
Expand All @@ -804,9 +804,9 @@ describe("Config", () => {
"parent",
"directory",
"root-dot",
AbsolutePath.make(path.join(root, ".opencode")),
AbsolutePath.make(path.join(root, ".codev")),
"directory-dot",
AbsolutePath.make(path.join(directory, ".opencode")),
AbsolutePath.make(path.join(directory, ".codev")),
])
}).pipe(
Effect.provide(
Expand Down
2 changes: 1 addition & 1 deletion packages/core/test/global.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { Global } from "@opencode-ai/core/global"

describe("global paths", () => {
test("tmp path is under the system temp directory", () => {
expect(Global.Path.tmp).toBe(path.join(os.tmpdir(), "codev-code"))
expect(Global.Path.tmp).toBe(path.join(os.tmpdir(), "codev"))
expect(Global.make().tmp).toBe(Global.Path.tmp)
})

Expand Down
8 changes: 4 additions & 4 deletions packages/core/test/ripgrep.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,14 +39,14 @@ describe("Ripgrep", () => {
Effect.promise(() => tmpdir()),
(tmp) =>
Effect.gen(function* () {
yield* Effect.promise(() => fs.mkdir(path.join(tmp.path, ".opencode")))
yield* Effect.promise(() => fs.writeFile(path.join(tmp.path, ".opencode", "config"), "needle\n"))
yield* Effect.promise(() => fs.mkdir(path.join(tmp.path, ".codev")))
yield* Effect.promise(() => fs.writeFile(path.join(tmp.path, ".codev", "config"), "needle\n"))
yield* Effect.promise(() => fs.mkdir(path.join(tmp.path, ".git")))
yield* Effect.promise(() => fs.writeFile(path.join(tmp.path, ".git", "config"), "needle\n"))
const ripgrep = yield* Ripgrep.Service

const files = yield* ripgrep.find({ cwd: tmp.path, pattern: "**/*", limit: 10 })
expect(files.map((item) => item.path)).toContain(RelativePath.make(".opencode/config"))
expect(files.map((item) => item.path)).toContain(RelativePath.make(".codev/config"))
expect(files.map((item) => item.path)).not.toContain(RelativePath.make(".git/config"))

const observed: string[] = []
Expand All @@ -59,7 +59,7 @@ describe("Ripgrep", () => {
expect(observed).toEqual(limited.map((item) => item.path))

const matches = yield* ripgrep.grep({ cwd: tmp.path, pattern: "needle", include: "config", limit: 10 })
expect(matches.map((item) => item.entry.path)).toContain(RelativePath.make(".opencode/config"))
expect(matches.map((item) => item.entry.path)).toContain(RelativePath.make(".codev/config"))
expect(matches.map((item) => item.entry.path)).not.toContain(RelativePath.make(".git/config"))
}),
(tmp) => Effect.promise(() => tmp[Symbol.asyncDispose]()),
Expand Down
18 changes: 9 additions & 9 deletions packages/opencode/specs/tui-plugins.md
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ npm plugins can declare a version compatibility range in `package.json` using th

- The value is a semver range checked against the running OpenCode version.
- If the range is not satisfied, the plugin is skipped with a warning and a session error.
- If `engines.opencode` is absent, no check is performed (backward compatible).
- If `engines.codev` is absent, no check is performed (backward compatible).
- File plugins are never checked; only npm package plugins are validated.

- Install flow is shared by CLI and TUI in `src/plugin/install.ts`.
Expand All @@ -195,8 +195,8 @@ npm plugins can declare a version compatibility range in `package.json` using th
- Alias: `opencode plug <module>`.
- `-g` / `--global` writes into the global config dir.
- Local installs resolve target dir inside `patchPluginConfig`.
- For local scope, path is `<worktree>/.opencode` only when VCS is git and `worktree !== "/"`; otherwise `<directory>/.opencode`.
- Root-worktree fallback (`worktree === "/"` uses `<directory>/.opencode`) is covered by regression tests.
- For local scope, path is `<worktree>/.codev` only when VCS is git and `worktree !== "/"`; otherwise `<directory>/.codev`.
- Root-worktree fallback (`worktree === "/"` uses `<directory>/.codev`) is covered by regression tests.
- `patchPluginConfig` applies all detected targets (`server` and/or `tui`) in one call.
- `patchPluginConfig` returns structured result unions (`ok`, `code`, fields by error kind) instead of custom thrown errors.
- `patchPluginConfig` serializes per-target config writes with `Flock.acquire(...)`.
Expand All @@ -213,7 +213,7 @@ npm plugins can declare a version compatibility range in `package.json` using th
- There is no uninstall, list, or update CLI command for external plugins.
- Local file plugins are configured directly in `tui.json`.

When `plugin` entries exist in a writable `.opencode` dir or `OPENCODE_CONFIG_DIR`, OpenCode installs `@opencode-ai/plugin` into that dir and writes:
When `plugin` entries exist in a writable `.codev` dir or `OPENCODE_CONFIG_DIR`, OpenCode installs `@opencode-ai/plugin` into that dir and writes:

- `package.json`
- `bun.lock`
Expand Down Expand Up @@ -413,7 +413,7 @@ Theme install behavior:
- If the theme name already exists, install is skipped unless plugin metadata state is `updated`.
- On `updated`, host skips rewrite when tracked `mtime`/`size` is unchanged.
- When a theme already exists and state is not `updated`, host can still persist theme metadata when destination already exists.
- Local plugins persist installed themes under the local `.opencode/themes` area near the plugin config source.
- Local plugins persist installed themes under the local `.codev/themes` area near the plugin config source.
- Global plugins persist installed themes under the global `themes` dir.
- Invalid or unreadable theme files are ignored.

Expand Down Expand Up @@ -538,7 +538,7 @@ The plugin manager is exposed as a command with title `Plugins` and value `plugi

## Current in-repo examples

- Local smoke plugin: `.opencode/plugins/tui-smoke.tsx`
- Local vim plugin: `.opencode/plugins/tui-vim.tsx`
- Local smoke config: `.opencode/tui.json`
- Local smoke theme: `.opencode/plugins/smoke-theme.json`
- Local smoke plugin: `.codev/plugins/tui-smoke.tsx`
- Local vim plugin: `.codev/plugins/tui-vim.tsx`
- Local smoke config: `.codev/tui.json`
- Local smoke theme: `.codev/plugins/smoke-theme.json`
2 changes: 1 addition & 1 deletion packages/opencode/src/agent/agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ const layer = Layer.effect(
},
edit: {
"*": "deny",
[path.join(".opencode", "plans", "*.md")]: "allow",
[path.join(".codev", "plans", "*.md")]: "allow",
[path.relative(ctx.worktree, path.join(Global.Path.data, path.join("plans", "*.md")))]: "allow",
},
}),
Expand Down
2 changes: 1 addition & 1 deletion packages/opencode/src/cli/cmd/agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ const AgentCreateCommand = effectCmd({
if (prompts.isCancel(scopeResult)) throw new UI.CancelledError()
scope = scopeResult
}
targetPath = path.join(scope === "global" ? Global.Path.config : path.join(ctx.worktree, ".opencode"), "agents")
targetPath = path.join(scope === "global" ? Global.Path.config : path.join(ctx.worktree, ".codev"), "agents")
}

// Get description
Expand Down
4 changes: 2 additions & 2 deletions packages/opencode/src/cli/cmd/mcp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -392,11 +392,11 @@ export const McpLogoutCommand = effectCmd({
})

async function resolveConfigPath(baseDir: string, global = false) {
// Check for existing config files (prefer .jsonc over .json, check .opencode/ subdirectory too)
// Check for existing config files (prefer .jsonc over .json, check .codev/ subdirectory too)
const candidates = [path.join(baseDir, "codev.json"), path.join(baseDir, "codev.jsonc")]

if (!global) {
candidates.push(path.join(baseDir, ".opencode", "codev.json"), path.join(baseDir, ".opencode", "codev.jsonc"))
candidates.push(path.join(baseDir, ".codev", "codev.json"), path.join(baseDir, ".codev", "codev.jsonc"))
}

for (const candidate of candidates) {
Expand Down
Loading
Loading