import path from "path"
import fs from "fs/promises"
import { Effect } from "effect"
import type { AppFileSystem } from "@opencode-ai/core/filesystem"
import { Global } from "@opencode-ai/core/global"
import * as Log from "@opencode-ai/core/util/log"
interface EmbeddedSpecFileMap {
[key: string]: EmbeddedSpecFile
}
type EmbeddedSpecFile = string | { encoding: "base64"; content: string } | EmbeddedSpecFileMap
declare const DEVECO_DEFAULT_SPEC_RESOURCES: Record<string, EmbeddedSpecFile> | undefined
const binaryExtensions = new Set([".png", ".jpg", ".jpeg", ".gif", ".webp", ".ico", ".bin"])
async function walkDir(directory: string): Promise<string[]> {
const result: string[] = []
async function recurse(dir: string) {
for (const entry of await fs.readdir(dir, { withFileTypes: true })) {
const full = path.join(dir, entry.name)
if (entry.isDirectory()) await recurse(full)
else if (entry.name !== ".DS_Store") result.push(full)
}
}
await recurse(directory)
return result
}
async function loadSpecResourcesFromDisk(): Promise<Record<string, EmbeddedSpecFile>> {
const resourceDir = path.join(import.meta.dirname, "../../resources/spec")
try {
await fs.access(resourceDir)
} catch {
return {}
}
const data: Record<string, EmbeddedSpecFile> = {}
for (const entry of await fs.readdir(resourceDir, { withFileTypes: true })) {
if (entry.isDirectory()) {
const files: Record<string, EmbeddedSpecFile> = {}
const specPath = path.join(resourceDir, entry.name)
for (const file of await walkDir(specPath)) {
const rel = path.relative(specPath, file).replaceAll("\\", "/")
if (binaryExtensions.has(path.extname(file).toLowerCase())) {
const buf = await fs.readFile(file)
files[rel] = { encoding: "base64", content: buf.toString("base64") }
} else {
files[rel] = await fs.readFile(file, "utf-8")
}
}
data[entry.name] = files
} else if (entry.isFile()) {
data[entry.name] = await fs.readFile(path.join(resourceDir, entry.name), "utf-8")
}
}
return data
}
export namespace Defaults {
const log = Log.create({ service: "spec-defaults" })
export const ensure = Effect.fn("Spec.Defaults.ensure")(function* (version: string, fsys: AppFileSystem.Interface) {
const configDir = Global.Path.config
const specDir = path.join(configDir, "specs")
const versionFile = path.join(specDir, ".version")
const current = yield* Effect.gen(function* () {
const exists = yield* fsys.existsSafe(versionFile)
if (!exists) return ""
const buf = yield* fsys.readFile(versionFile)
return new TextDecoder().decode(buf)
}).pipe(Effect.catch(() => Effect.succeed("")))
if (current === version && current !== "local") {
return { configDir, specDir }
}
log.info("extracting default spec resources", { version })
yield* Effect.tryPromise(() =>
import("fs/promises").then((fs) => fs.rm(path.join(specDir, "commands"), { recursive: true, force: true })),
).pipe(Effect.catch(() => Effect.void))
yield* Effect.tryPromise(() =>
import("fs/promises").then((fs) => fs.rm(path.join(specDir, "templates"), { recursive: true, force: true })),
).pipe(Effect.catch(() => Effect.void))
const data =
typeof DEVECO_DEFAULT_SPEC_RESOURCES !== "undefined"
? DEVECO_DEFAULT_SPEC_RESOURCES
: yield* Effect.promise(loadSpecResourcesFromDisk)
for (const [name, content] of Object.entries(data)) {
if (typeof content === "string") {
continue
} else if (typeof content === "object" && !("encoding" in content)) {
const useConfigDir = name === "agents"
const baseDir = useConfigDir ? configDir : specDir
const targetDir = path.join(baseDir, name)
for (const [fileName, fileContent] of Object.entries(content as Record<string, EmbeddedSpecFile>)) {
if (typeof fileContent === "string") {
yield* fsys.writeWithDirs(path.join(targetDir, fileName), fileContent)
} else if (typeof fileContent === "object" && "encoding" in fileContent) {
const encoded = fileContent as { encoding: "base64"; content: string }
const decoded = Buffer.from(encoded.content, "base64")
yield* fsys.writeWithDirs(path.join(targetDir, fileName), Uint8Array.from(decoded))
}
}
}
}
yield* fsys.writeWithDirs(versionFile, version)
return { configDir, specDir }
})
}