(
target: CleanupTarget,
plugin: Awaited<ReturnType<typeof loadClaudePlugin>>,
roots: {
codexHome: string
piHome: string
opencodeHome: string
kiroHome: string
copilotHome: string
droidHome: string
qwenHome: string
windsurfHome: string
agentsHome: string
workspaceRoot: string
hasExplicitOutput: boolean
hasExplicitOpenCodeHome: boolean
},
)
| 143 | }) |
| 144 | |
| 145 | async function cleanupTarget( |
| 146 | target: CleanupTarget, |
| 147 | plugin: Awaited<ReturnType<typeof loadClaudePlugin>>, |
| 148 | roots: { |
| 149 | codexHome: string |
| 150 | piHome: string |
| 151 | opencodeHome: string |
| 152 | kiroHome: string |
| 153 | copilotHome: string |
| 154 | droidHome: string |
| 155 | qwenHome: string |
| 156 | windsurfHome: string |
| 157 | agentsHome: string |
| 158 | workspaceRoot: string |
| 159 | hasExplicitOutput: boolean |
| 160 | hasExplicitOpenCodeHome: boolean |
| 161 | }, |
| 162 | ): Promise<CleanupResult[]> { |
| 163 | switch (target) { |
| 164 | case "codex": |
| 165 | return [ |
| 166 | await cleanupCodex(plugin, roots.codexHome), |
| 167 | await cleanupCodexSharedAgents(plugin, roots.agentsHome, roots.codexHome), |
| 168 | ] |
| 169 | case "opencode": { |
| 170 | // Mirror install: when `--output <workspace>` is passed (without an |
| 171 | // explicit `--opencode-home`), install writes managed artifacts under |
| 172 | // `<workspace>/.opencode/{agents,skills,commands,plugins}`. Cleanup must |
| 173 | // scan the same directory or stale workspace artifacts get left behind. |
| 174 | // An explicit `--opencode-home` remains authoritative so users can still |
| 175 | // target a specific global-style root. When neither is set, fall back to |
| 176 | // the OpenCode global root (OPENCODE_CONFIG_DIR / XDG default). |
| 177 | if (roots.hasExplicitOpenCodeHome) { |
| 178 | return [await cleanupOpenCode(plugin, roots.opencodeHome)] |
| 179 | } |
| 180 | if (roots.hasExplicitOutput) { |
| 181 | return [await cleanupOpenCode(plugin, resolveOpenCodeWorkspaceRoot(roots.workspaceRoot))] |
| 182 | } |
| 183 | return [await cleanupOpenCode(plugin, roots.opencodeHome)] |
| 184 | } |
| 185 | case "pi": |
| 186 | return [await cleanupPi(plugin, roots.piHome)] |
| 187 | case "kiro": |
| 188 | return [await cleanupKiro(plugin, roots.kiroHome)] |
| 189 | case "copilot": { |
| 190 | // Same race-prevention as Copilot below: if a user points `--copilot-home`, |
| 191 | // `--output`, or `--agents-home` at the same directory these parallel |
| 192 | // passes collide on renames. Default values are distinct so the dedup |
| 193 | // is mostly defensive, but keep the shape consistent across targets |
| 194 | // that fan out with `Promise.all`. |
| 195 | const rootsToClean = roots.hasExplicitOutput |
| 196 | ? [resolveCopilotWorkspaceRoot(roots.workspaceRoot)] |
| 197 | : await dedupeRoots([roots.copilotHome, resolveCopilotWorkspaceRoot(roots.workspaceRoot), roots.agentsHome]) |
| 198 | return await Promise.all(rootsToClean.map((root) => cleanupCopilot(plugin, root))) |
| 199 | } |
| 200 | case "droid": |
| 201 | return [await cleanupDroid(plugin, roots.hasExplicitOutput ? resolveDroidWorkspaceRoot(roots.workspaceRoot) : roots.droidHome)] |
| 202 | case "qwen": |
no test coverage detected