Skip to content
Open
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
65 changes: 60 additions & 5 deletions packages/opencode/src/cli/cmd/uninstall.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ interface RemovalTargets {
directories: Array<{ path: string; label: string; keep: boolean }>
shellConfig: string | null
binary: string | null
bins: string[] | null
modules: string[] | null
}

export const UninstallCommand = {
Expand Down Expand Up @@ -96,8 +98,11 @@ async function collectRemovalTargets(args: UninstallArgs, method: Installation.M

const shellConfig = method === "curl" ? await getShellConfigFile() : null
const binary = method === "curl" ? process.execPath : null
const bun = method === "bun" && process.platform === "win32" ? await getBunPaths() : null
const bins = bun ? bun.bins : null
const modules = bun ? bun.modules : null

return { directories, shellConfig, binary }
return { directories, shellConfig, binary, bins, modules }
}

async function showRemovalSummary(targets: RemovalTargets, method: Installation.Method) {
Expand Down Expand Up @@ -133,6 +138,8 @@ async function showRemovalSummary(targets: RemovalTargets, method: Installation.
bun: "bun remove -g opencode-ai",
yarn: "yarn global remove opencode-ai",
brew: "brew uninstall opencode",
choco: "choco uninstall opencode",
scoop: "scoop uninstall opencode",
}
prompts.log.info(` ✓ Package: ${cmds[method] || method}`)
}
Expand Down Expand Up @@ -182,16 +189,46 @@ async function executeUninstall(method: Installation.Method, targets: RemovalTar
bun: ["bun", "remove", "-g", "opencode-ai"],
yarn: ["yarn", "global", "remove", "opencode-ai"],
brew: ["brew", "uninstall", "opencode"],
choco: ["choco", "uninstall", "opencode"],
scoop: ["scoop", "uninstall", "opencode"],
}

const cmd = cmds[method]
if (cmd) {
spinner.start(`Running ${cmd.join(" ")}...`)
const result = await $`${cmd}`.quiet().nothrow()
const result =
method === "choco"
? await $`echo Y | choco uninstall opencode -y -r`.quiet().nothrow()
: await $`${cmd}`.quiet().nothrow()
if (method === "bun" && process.platform === "win32") {
const items = [...(targets.bins || []), ...(targets.modules || [])]
const existing: string[] = []

for (const item of items) {
const exists = await fs
.access(item)
.then(() => true)
.catch(() => false)
if (!exists) continue
existing.push(item)
}

for (const item of existing) {
fs.rm(item, { recursive: true, force: true }).catch((err) => {
errors.push(`Bun: ${shortenPath(item)}: ${err.message}`)
})
}
}
if (result.exitCode !== 0) {
spinner.stop(`Package manager uninstall failed`, 1)
prompts.log.warn(`You may need to run manually: ${cmd.join(" ")}`)
errors.push(`Package manager: exit code ${result.exitCode}`)
spinner.stop(`Package manager uninstall failed: exit code ${result.exitCode}`, 1)
if (
method === "choco" &&
result.stdout.toString("utf8").includes("not running from an elevated command shell")
) {
prompts.log.warn(`You may need to run '${cmd.join(" ")}' from an elevated command shell`)
} else {
prompts.log.warn(`You may need to run manually: ${cmd.join(" ")}`)
}
} else {
spinner.stop("Package removed")
}
Expand Down Expand Up @@ -221,6 +258,24 @@ async function executeUninstall(method: Installation.Method, targets: RemovalTar
prompts.log.success("Thank you for using OpenCode!")
}

async function getBunPaths() {
const root = Bun.env.BUN_INSTALL || path.join(os.homedir(), ".bun")
const bin = path.join(root, "bin")
const modroot = path.join(root, "install", "global", "node_modules")
const defaults = [path.join(modroot, "opencode-ai")]
const entries = await fs.readdir(modroot, { withFileTypes: true }).catch(() => [])
const modules = [
...new Set([
...defaults,
...entries
.filter((entry) => entry.isDirectory() && entry.name.startsWith("opencode"))
.map((entry) => path.join(modroot, entry.name)),
]),
]
const bins = ["opencode", "opencode.bunx", "opencode.exe"].map((name) => path.join(bin, name))
return { bins, modules }
}

async function getShellConfigFile(): Promise<string | null> {
const shell = path.basename(process.env.SHELL || "bash")
const home = os.homedir()
Expand Down