2026-04-16 16:40:16 +00:00
|
|
|
export * as ConfigCommand from "./command"
|
|
|
|
|
|
2026-05-19 20:48:32 +00:00
|
|
|
import path from "path"
|
2026-04-27 18:33:33 +00:00
|
|
|
import * as Log from "@opencode-ai/core/util/log"
|
2026-05-11 14:22:45 +00:00
|
|
|
import { Cause, Exit, Schema } from "effect"
|
2026-04-25 14:59:17 +00:00
|
|
|
import { Glob } from "@opencode-ai/core/util/glob"
|
2026-04-16 16:40:16 +00:00
|
|
|
import { configEntryNameFromPath } from "./entry-name"
|
2026-04-16 17:29:00 +00:00
|
|
|
import { InvalidError } from "./error"
|
2026-04-16 16:19:43 +00:00
|
|
|
import * as ConfigMarkdown from "./markdown"
|
2026-04-16 16:40:16 +00:00
|
|
|
import { ConfigModelID } from "./model-id"
|
2026-04-16 16:19:43 +00:00
|
|
|
|
|
|
|
|
const log = Log.create({ service: "config" })
|
|
|
|
|
|
2026-04-17 20:59:24 +00:00
|
|
|
export const Info = Schema.Struct({
|
|
|
|
|
template: Schema.String,
|
|
|
|
|
description: Schema.optional(Schema.String),
|
|
|
|
|
agent: Schema.optional(Schema.String),
|
|
|
|
|
model: Schema.optional(ConfigModelID),
|
|
|
|
|
subtask: Schema.optional(Schema.Boolean),
|
2026-05-11 14:22:45 +00:00
|
|
|
})
|
2026-04-16 16:40:16 +00:00
|
|
|
|
2026-04-17 20:59:24 +00:00
|
|
|
export type Info = Schema.Schema.Type<typeof Info>
|
2026-04-16 16:40:16 +00:00
|
|
|
|
2026-05-11 14:22:45 +00:00
|
|
|
const decodeInfo = Schema.decodeUnknownExit(Info)
|
|
|
|
|
|
2026-04-16 16:40:16 +00:00
|
|
|
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,
|
|
|
|
|
})) {
|
2026-05-17 16:56:42 +00:00
|
|
|
const md = await ConfigMarkdown.parse(item).catch((err) => {
|
2026-04-16 16:40:16 +00:00
|
|
|
log.error("failed to load command", { command: item, err })
|
|
|
|
|
return undefined
|
|
|
|
|
})
|
|
|
|
|
if (!md) continue
|
|
|
|
|
|
2026-05-19 20:48:32 +00:00
|
|
|
const name = configEntryNameFromPath(path.relative(dir, item), ["command/", "commands/"])
|
2026-04-16 16:40:16 +00:00
|
|
|
|
|
|
|
|
const config = {
|
|
|
|
|
name,
|
|
|
|
|
...md.data,
|
|
|
|
|
template: md.content.trim(),
|
|
|
|
|
}
|
2026-05-11 14:22:45 +00:00
|
|
|
const parsed = decodeInfo(config, { errors: "all", propertyOrder: "original" })
|
|
|
|
|
if (Exit.isSuccess(parsed)) {
|
|
|
|
|
result[config.name] = parsed.value
|
2026-04-16 16:40:16 +00:00
|
|
|
continue
|
2026-04-16 16:19:43 +00:00
|
|
|
}
|
2026-05-11 14:22:45 +00:00
|
|
|
throw new InvalidError({ path: item, message: Cause.pretty(parsed.cause) }, { cause: Cause.squash(parsed.cause) })
|
2026-04-16 16:19:43 +00:00
|
|
|
}
|
2026-04-16 16:40:16 +00:00
|
|
|
return result
|
2026-04-16 16:19:43 +00:00
|
|
|
}
|