diff --git a/docs/integrations.md b/docs/integrations.md index 3f8b91d..704648c 100644 --- a/docs/integrations.md +++ b/docs/integrations.md @@ -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" diff --git a/installer/planner.go b/installer/planner.go index d7518eb..29d6718 100644 --- a/installer/planner.go +++ b/installer/planner.go @@ -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) { @@ -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") diff --git a/installer/planner_test.go b/installer/planner_test.go index 0b1cc06..baa519d 100644 --- a/installer/planner_test.go +++ b/installer/planner_test.go @@ -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)),