Skip to content
Merged
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
13 changes: 6 additions & 7 deletions docs/integrations.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,15 +52,14 @@ Create `~/.config/opencode/plugins/docket.ts`, or
`.opencode/plugins/docket.ts` for one project. These locations follow the
[OpenCode plugin guide](https://opencode.ai/docs/plugins/).

The current Docket installer writes `plugins/docket/index.ts`. OpenCode's
The installer writes `plugins/docket.ts` for you. Its
[plugin loader](https://github.com/anomalyco/opencode/blob/dev/packages/opencode/src/config/plugin.ts)
scans files directly inside `plugins/`, so that nested file is not discovered
automatically. If you used the installer, move its `index.ts` to
`plugins/docket.ts` after checking that the destination does not already exist.
Otherwise, create the file below. Keep one active copy to avoid duplicate context.
scans files directly inside `plugins/`, so a file one level down is never
discovered.

The installer does not manage the manually placed `docket.ts` file. Update or
remove it yourself when changing this integration.
Installers before 0.11.0 wrote `plugins/docket/index.ts`, which OpenCode never
loaded. A new install or an update removes that file. Keep one active copy to
avoid duplicate context.

```js
import { execFileSync } from "node:child_process"
Expand Down
28 changes: 22 additions & 6 deletions installer/planner.go
Original file line number Diff line number Diff line change
Expand Up @@ -339,13 +339,25 @@ func planCopilot(env Environment) ([]Action, error) {
return jsonWrite(p, data, "copilot")
}

// OpenCode discovers plugins with the glob {plugin,plugins}/*.{ts,js} and
// include:"file", so a file one level down at plugins/docket/index.ts never
// loads. Installs before 0.11.0 wrote that path; remove it here.
func planOpenCode(env Environment) []Action {
p := join(env, env.Home, ".config", "opencode", "plugins", "docket", "index.ts")
var actions []Action
nested := join(env, env.Home, ".config", "opencode", "plugins", "docket", "index.ts")
if text, ok := readText(env, nested); ok && ownedOpenCode(env, text) {
actions = append(actions, Action{Kind: "remove", Path: nested, Label: "opencode"})
}
p := openCodePluginPath(env)
text := openCodeSource(env)
if sameFile(env, p, text) {
return nil
return actions
}
return []Action{{Kind: "write", Path: p, Text: text, Label: "opencode"}}
return append(actions, Action{Kind: "write", Path: p, Text: text, Label: "opencode"})
}

func openCodePluginPath(env Environment) string {
return join(env, env.Home, ".config", "opencode", "plugins", "docket.ts")
}

func buildUpdate(env Environment, prefix string, checkout string) (Plan, error) {
Expand Down Expand Up @@ -544,9 +556,13 @@ func buildUninstall(env Environment, prefix string, project bool) (Plan, error)
if err != nil {
return Plan{}, err
}
oc := join(env, env.Home, ".config", "opencode", "plugins", "docket", "index.ts")
if text, ok := readText(env, oc); ok && ownedOpenCode(env, text) {
plan.Actions = append(plan.Actions, Action{Kind: "remove", Path: oc, Label: "opencode"})
for _, oc := range []string{
openCodePluginPath(env),
join(env, env.Home, ".config", "opencode", "plugins", "docket", "index.ts"),
} {
if text, ok := readText(env, oc); ok && ownedOpenCode(env, text) {
plan.Actions = append(plan.Actions, Action{Kind: "remove", Path: oc, Label: "opencode"})
}
}
if project {
rule := join(env, env.Cwd, ".cursor", "rules", "docket.mdc")
Expand Down
30 changes: 30 additions & 0 deletions installer/planner_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -584,6 +584,36 @@ func TestUpdateReinstallsTheCodexPluginUnderItsOwnMarketplaceName(t *testing.T)
}
}

func TestOpenCodePluginLandsWhereTheLoaderGlobLooks(t *testing.T) {
env := testEnv(nil)
plan, err := BuildPlan(env, Options{Harness: []string{"opencode"}, Prefix: "/home/a/.local/bin"})
if err != nil {
t.Fatalf("BuildPlan: %v", err)
}
// The loader globs {plugin,plugins}/*.{ts,js} with include:"file", so a
// nested docket/index.ts is never discovered.
if _, ok := findAction(plan.Actions, "write", "/home/a/.config/opencode/plugins/docket.ts"); !ok {
t.Fatalf("no write to plugins/docket.ts: %#v", plan.Actions)
}
for _, action := range plan.Actions {
if strings.HasSuffix(action.Path, "/docket/index.ts") && action.Kind == "write" {
t.Fatalf("still writing the nested path: %#v", action)
}
}
}

func TestOpenCodeInstallRemovesTheUndiscoverableNestedPlugin(t *testing.T) {
nested := "/home/a/.config/opencode/plugins/docket/index.ts"
env := testEnv(map[string]string{nested: openCodeSource(testEnv(nil))}, nested)
plan, err := BuildPlan(env, Options{Harness: []string{"opencode"}, Prefix: "/home/a/.local/bin"})
if err != nil {
t.Fatalf("BuildPlan: %v", err)
}
if _, ok := findAction(plan.Actions, "remove", nested); !ok {
t.Fatalf("pre-0.11.0 plugin not removed: %#v", plan.Actions)
}
}

func TestUpdatePrefersTheCodexPluginActuallyInstalled(t *testing.T) {
env := testEnv(map[string]string{
"/home/a/.local/bin/.docket-codex.json": codexReceipt(testEnv(nil)),
Expand Down
Loading