85ff86fe9e
Extract error handling, parsing logic, and variable substitution into dedicated modules. This reduces duplication between tui.json and opencode.json parsing and makes the config system easier to extend for future config formats.
61 lines
1.9 KiB
TypeScript
61 lines
1.9 KiB
TypeScript
export * as ConfigCommand from "./command"
|
|
|
|
import { Log } from "../util"
|
|
import z from "zod"
|
|
import { NamedError } from "@opencode-ai/shared/util/error"
|
|
import { Glob } from "@opencode-ai/shared/util/glob"
|
|
import { Bus } from "@/bus"
|
|
import { configEntryNameFromPath } from "./entry-name"
|
|
import { InvalidError } from "./error"
|
|
import * as ConfigMarkdown from "./markdown"
|
|
import { ConfigModelID } from "./model-id"
|
|
|
|
const log = Log.create({ service: "config" })
|
|
|
|
export const Info = z.object({
|
|
template: z.string(),
|
|
description: z.string().optional(),
|
|
agent: z.string().optional(),
|
|
model: ConfigModelID.optional(),
|
|
subtask: z.boolean().optional(),
|
|
})
|
|
|
|
export type Info = z.infer<typeof Info>
|
|
|
|
export async function load(dir: string) {
|
|
const result: Record<string, Info> = {}
|
|
for (const item of await Glob.scan("{command,commands}/**/*.md", {
|
|
cwd: dir,
|
|
absolute: true,
|
|
dot: true,
|
|
symlink: true,
|
|
})) {
|
|
const md = await ConfigMarkdown.parse(item).catch(async (err) => {
|
|
const message = ConfigMarkdown.FrontmatterError.isInstance(err)
|
|
? err.data.message
|
|
: `Failed to parse command ${item}`
|
|
const { Session } = await import("@/session")
|
|
void Bus.publish(Session.Event.Error, { error: new NamedError.Unknown({ message }).toObject() })
|
|
log.error("failed to load command", { command: item, err })
|
|
return undefined
|
|
})
|
|
if (!md) continue
|
|
|
|
const patterns = ["/.opencode/command/", "/.opencode/commands/", "/command/", "/commands/"]
|
|
const name = configEntryNameFromPath(item, patterns)
|
|
|
|
const config = {
|
|
name,
|
|
...md.data,
|
|
template: md.content.trim(),
|
|
}
|
|
const parsed = Info.safeParse(config)
|
|
if (parsed.success) {
|
|
result[config.name] = parsed.data
|
|
continue
|
|
}
|
|
throw new InvalidError({ path: item, issues: parsed.error.issues }, { cause: parsed.error })
|
|
}
|
|
return result
|
|
}
|