Skip to content
Open
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
119 changes: 119 additions & 0 deletions packages/opencode/src/cli/cmd/cache.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
import type { Argv } from "yargs"
import { cmd } from "./cmd"
import * as prompts from "@clack/prompts"
import { Global } from "../../global"
import { getDirectorySize, formatSize, shortenPath } from "../util"
import fs from "fs/promises"
import path from "path"

const CacheCleanCommand = cmd({
command: "clean",
describe: "remove cached plugins and packages",
builder: (yargs: Argv) =>
yargs
.option("force", {
alias: "f",
type: "boolean",
describe: "skip confirmation prompt",
default: false,
})
.option("dry-run", {
type: "boolean",
describe: "show what would be removed without removing",
default: false,
}),
async handler(args) {
const exists = await fs
.access(Global.Path.cache)
.then(() => true)
.catch(() => false)

if (!exists) {
prompts.log.info("Cache directory does not exist")
return
}

const size = await getDirectorySize(Global.Path.cache)

prompts.log.info(`Cache: ${shortenPath(Global.Path.cache)} (${formatSize(size)})`)

if (args.dryRun) {
prompts.log.warn("Dry run - no changes made")
return
}

if (!args.force) {
const confirm = await prompts.confirm({
message: "Remove cache directory?",
initialValue: true,
})
if (!confirm || prompts.isCancel(confirm)) {
prompts.log.warn("Cancelled")
return
}
}

const spinner = prompts.spinner()
spinner.start("Removing cache...")

const err = await fs.rm(Global.Path.cache, { recursive: true, force: true }).catch((e) => e)
if (err) {
spinner.stop("Failed to remove cache", 1)
prompts.log.error(err.message)
return
}

spinner.stop("Cache removed")
},
})

const CacheInfoCommand = cmd({
command: "info",
describe: "show cache directory information",
async handler() {
const exists = await fs
.access(Global.Path.cache)
.then(() => true)
.catch(() => false)

prompts.log.info(`Path: ${shortenPath(Global.Path.cache)}`)

if (!exists) {
prompts.log.info("Status: not created")
return
}

const size = await getDirectorySize(Global.Path.cache)
prompts.log.info(`Size: ${formatSize(size)}`)

const pkgjson = Bun.file(path.join(Global.Path.cache, "package.json"))
const parsed = await pkgjson.json().catch(() => null)

if (parsed?.dependencies) {
const deps = Object.entries(parsed.dependencies)
if (deps.length > 0) {
prompts.log.info(`Packages:`)
for (const [pkg, version] of deps) {
prompts.log.info(` ${pkg}@${version}`)
}
}
}

const versionFile = Bun.file(path.join(Global.Path.cache, "version"))
const version = await versionFile.text().catch(() => null)
if (version) {
prompts.log.info(`Cache version: ${version.trim()}`)
}
},
})

export const CacheCommand = cmd({
command: "cache",
describe: "manage plugin and package cache",
builder: (yargs) =>
yargs
.command(CacheCleanCommand)
.command(CacheInfoCommand)
.demandCommand(1, "Please specify a subcommand: clean or info"),
async handler() {},
})
39 changes: 1 addition & 38 deletions packages/opencode/src/cli/cmd/uninstall.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { UI } from "../ui"
import * as prompts from "@clack/prompts"
import { Installation } from "../../installation"
import { Global } from "../../global"
import { getDirectorySize, formatSize, shortenPath } from "../util"
import { $ } from "bun"
import fs from "fs/promises"
import path from "path"
Expand Down Expand Up @@ -304,41 +305,3 @@ async function cleanShellConfig(file: string) {
const output = filtered.join("\n") + "\n"
await Bun.write(file, output)
}

async function getDirectorySize(dir: string): Promise<number> {
let total = 0

const walk = async (current: string) => {
const entries = await fs.readdir(current, { withFileTypes: true }).catch(() => [])

for (const entry of entries) {
const full = path.join(current, entry.name)
if (entry.isDirectory()) {
await walk(full)
continue
}
if (entry.isFile()) {
const stat = await fs.stat(full).catch(() => null)
if (stat) total += stat.size
}
}
}

await walk(dir)
return total
}

function formatSize(bytes: number): string {
if (bytes < 1024) return `${bytes} B`
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`
if (bytes < 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MB`
return `${(bytes / (1024 * 1024 * 1024)).toFixed(1)} GB`
}

function shortenPath(p: string): string {
const home = os.homedir()
if (p.startsWith(home)) {
return p.replace(home, "~")
}
return p
}
41 changes: 41 additions & 0 deletions packages/opencode/src/cli/util.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import fs from "fs/promises"
import path from "path"
import os from "os"

export async function getDirectorySize(dir: string): Promise<number> {
let total = 0

const walk = async (current: string) => {
const entries = await fs.readdir(current, { withFileTypes: true }).catch(() => [])

for (const entry of entries) {
const full = path.join(current, entry.name)
if (entry.isDirectory()) {
await walk(full)
continue
}
if (entry.isFile()) {
const stat = await fs.stat(full).catch(() => null)
if (stat) total += stat.size
}
}
}

await walk(dir)
return total
}

export function formatSize(bytes: number): string {
if (bytes < 1024) return `${bytes} B`
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`
if (bytes < 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MB`
return `${(bytes / (1024 * 1024 * 1024)).toFixed(1)} GB`
}

export function shortenPath(p: string): string {
const home = os.homedir()
if (p.startsWith(home)) {
return p.replace(home, "~")
}
return p
}
2 changes: 2 additions & 0 deletions packages/opencode/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import { EOL } from "os"
import { WebCommand } from "./cli/cmd/web"
import { PrCommand } from "./cli/cmd/pr"
import { SessionCommand } from "./cli/cmd/session"
import { CacheCommand } from "./cli/cmd/cache"

process.on("unhandledRejection", (e) => {
Log.Default.error("rejection", {
Expand Down Expand Up @@ -98,6 +99,7 @@ const cli = yargs(hideBin(process.argv))
.command(GithubCommand)
.command(PrCommand)
.command(SessionCommand)
.command(CacheCommand)
.fail((msg) => {
if (
msg.startsWith("Unknown argument") ||
Expand Down