summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorKit Langton <[email protected]>2026-04-16 16:30:52 -0400
committerGitHub <[email protected]>2026-04-16 20:30:52 +0000
commit3fe906f517eb60aa20fd47c95ec3c131452e8d91 (patch)
tree2f6624e17b4aff4aac3cf14991c822efa7b39841
parenta8d8a35cd3033602befc6648d00ed6be37aed826 (diff)
downloadopencode-3fe906f517eb60aa20fd47c95ec3c131452e8d91.tar.gz
opencode-3fe906f517eb60aa20fd47c95ec3c131452e8d91.zip
refactor: collapse command barrel into command/index.ts (#22903)
-rw-r--r--packages/opencode/src/command/command.ts186
-rw-r--r--packages/opencode/src/command/index.ts189
2 files changed, 188 insertions, 187 deletions
diff --git a/packages/opencode/src/command/command.ts b/packages/opencode/src/command/command.ts
deleted file mode 100644
index 4ea132524..000000000
--- a/packages/opencode/src/command/command.ts
+++ /dev/null
@@ -1,186 +0,0 @@
-import { BusEvent } from "@/bus/bus-event"
-import { InstanceState } from "@/effect"
-import { EffectBridge } from "@/effect"
-import type { InstanceContext } from "@/project/instance"
-import { SessionID, MessageID } from "@/session/schema"
-import { Effect, Layer, Context } from "effect"
-import z from "zod"
-import { Config } from "../config"
-import { MCP } from "../mcp"
-import { Skill } from "../skill"
-import PROMPT_INITIALIZE from "./template/initialize.txt"
-import PROMPT_REVIEW from "./template/review.txt"
-
-type State = {
- commands: Record<string, Info>
-}
-
-export const Event = {
- Executed: BusEvent.define(
- "command.executed",
- z.object({
- name: z.string(),
- sessionID: SessionID.zod,
- arguments: z.string(),
- messageID: MessageID.zod,
- }),
- ),
-}
-
-export const Info = z
- .object({
- name: z.string(),
- description: z.string().optional(),
- agent: z.string().optional(),
- model: z.string().optional(),
- source: z.enum(["command", "mcp", "skill"]).optional(),
- // workaround for zod not supporting async functions natively so we use getters
- // https://zod.dev/v4/changelog?id=zfunction
- template: z.promise(z.string()).or(z.string()),
- subtask: z.boolean().optional(),
- hints: z.array(z.string()),
- })
- .meta({
- ref: "Command",
- })
-
-// for some reason zod is inferring `string` for z.promise(z.string()).or(z.string()) so we have to manually override it
-export type Info = Omit<z.infer<typeof Info>, "template"> & { template: Promise<string> | string }
-
-export function hints(template: string) {
- const result: string[] = []
- const numbered = template.match(/\$\d+/g)
- if (numbered) {
- for (const match of [...new Set(numbered)].sort()) result.push(match)
- }
- if (template.includes("$ARGUMENTS")) result.push("$ARGUMENTS")
- return result
-}
-
-export const Default = {
- INIT: "init",
- REVIEW: "review",
-} as const
-
-export interface Interface {
- readonly get: (name: string) => Effect.Effect<Info | undefined>
- readonly list: () => Effect.Effect<Info[]>
-}
-
-export class Service extends Context.Service<Service, Interface>()("@opencode/Command") {}
-
-export const layer = Layer.effect(
- Service,
- Effect.gen(function* () {
- const config = yield* Config.Service
- const mcp = yield* MCP.Service
- const skill = yield* Skill.Service
-
- const init = Effect.fn("Command.state")(function* (ctx: InstanceContext) {
- const cfg = yield* config.get()
- const bridge = yield* EffectBridge.make()
- const commands: Record<string, Info> = {}
-
- commands[Default.INIT] = {
- name: Default.INIT,
- description: "guided AGENTS.md setup",
- source: "command",
- get template() {
- return PROMPT_INITIALIZE.replace("${path}", ctx.worktree)
- },
- hints: hints(PROMPT_INITIALIZE),
- }
- commands[Default.REVIEW] = {
- name: Default.REVIEW,
- description: "review changes [commit|branch|pr], defaults to uncommitted",
- source: "command",
- get template() {
- return PROMPT_REVIEW.replace("${path}", ctx.worktree)
- },
- subtask: true,
- hints: hints(PROMPT_REVIEW),
- }
-
- for (const [name, command] of Object.entries(cfg.command ?? {})) {
- commands[name] = {
- name,
- agent: command.agent,
- model: command.model,
- description: command.description,
- source: "command",
- get template() {
- return command.template
- },
- subtask: command.subtask,
- hints: hints(command.template),
- }
- }
-
- for (const [name, prompt] of Object.entries(yield* mcp.prompts())) {
- commands[name] = {
- name,
- source: "mcp",
- description: prompt.description,
- get template() {
- return bridge.promise(
- mcp
- .getPrompt(
- prompt.client,
- prompt.name,
- prompt.arguments
- ? Object.fromEntries(prompt.arguments.map((argument, i) => [argument.name, `$${i + 1}`]))
- : {},
- )
- .pipe(
- Effect.map(
- (template) =>
- template?.messages
- .map((message) => (message.content.type === "text" ? message.content.text : ""))
- .join("\n") || "",
- ),
- ),
- )
- },
- hints: prompt.arguments?.map((_, i) => `$${i + 1}`) ?? [],
- }
- }
-
- for (const item of yield* skill.all()) {
- if (commands[item.name]) continue
- commands[item.name] = {
- name: item.name,
- description: item.description,
- source: "skill",
- get template() {
- return item.content
- },
- hints: [],
- }
- }
-
- return {
- commands,
- }
- })
-
- const state = yield* InstanceState.make<State>((ctx) => init(ctx))
-
- const get = Effect.fn("Command.get")(function* (name: string) {
- const s = yield* InstanceState.get(state)
- return s.commands[name]
- })
-
- const list = Effect.fn("Command.list")(function* () {
- const s = yield* InstanceState.get(state)
- return Object.values(s.commands)
- })
-
- return Service.of({ get, list })
- }),
-)
-
-export const defaultLayer = layer.pipe(
- Layer.provide(Config.defaultLayer),
- Layer.provide(MCP.defaultLayer),
- Layer.provide(Skill.defaultLayer),
-)
diff --git a/packages/opencode/src/command/index.ts b/packages/opencode/src/command/index.ts
index 2e530360c..27ba357ec 100644
--- a/packages/opencode/src/command/index.ts
+++ b/packages/opencode/src/command/index.ts
@@ -1 +1,188 @@
-export * as Command from "./command"
+import { BusEvent } from "@/bus/bus-event"
+import { InstanceState } from "@/effect"
+import { EffectBridge } from "@/effect"
+import type { InstanceContext } from "@/project/instance"
+import { SessionID, MessageID } from "@/session/schema"
+import { Effect, Layer, Context } from "effect"
+import z from "zod"
+import { Config } from "../config"
+import { MCP } from "../mcp"
+import { Skill } from "../skill"
+import PROMPT_INITIALIZE from "./template/initialize.txt"
+import PROMPT_REVIEW from "./template/review.txt"
+
+type State = {
+ commands: Record<string, Info>
+}
+
+export const Event = {
+ Executed: BusEvent.define(
+ "command.executed",
+ z.object({
+ name: z.string(),
+ sessionID: SessionID.zod,
+ arguments: z.string(),
+ messageID: MessageID.zod,
+ }),
+ ),
+}
+
+export const Info = z
+ .object({
+ name: z.string(),
+ description: z.string().optional(),
+ agent: z.string().optional(),
+ model: z.string().optional(),
+ source: z.enum(["command", "mcp", "skill"]).optional(),
+ // workaround for zod not supporting async functions natively so we use getters
+ // https://zod.dev/v4/changelog?id=zfunction
+ template: z.promise(z.string()).or(z.string()),
+ subtask: z.boolean().optional(),
+ hints: z.array(z.string()),
+ })
+ .meta({
+ ref: "Command",
+ })
+
+// for some reason zod is inferring `string` for z.promise(z.string()).or(z.string()) so we have to manually override it
+export type Info = Omit<z.infer<typeof Info>, "template"> & { template: Promise<string> | string }
+
+export function hints(template: string) {
+ const result: string[] = []
+ const numbered = template.match(/\$\d+/g)
+ if (numbered) {
+ for (const match of [...new Set(numbered)].sort()) result.push(match)
+ }
+ if (template.includes("$ARGUMENTS")) result.push("$ARGUMENTS")
+ return result
+}
+
+export const Default = {
+ INIT: "init",
+ REVIEW: "review",
+} as const
+
+export interface Interface {
+ readonly get: (name: string) => Effect.Effect<Info | undefined>
+ readonly list: () => Effect.Effect<Info[]>
+}
+
+export class Service extends Context.Service<Service, Interface>()("@opencode/Command") {}
+
+export const layer = Layer.effect(
+ Service,
+ Effect.gen(function* () {
+ const config = yield* Config.Service
+ const mcp = yield* MCP.Service
+ const skill = yield* Skill.Service
+
+ const init = Effect.fn("Command.state")(function* (ctx: InstanceContext) {
+ const cfg = yield* config.get()
+ const bridge = yield* EffectBridge.make()
+ const commands: Record<string, Info> = {}
+
+ commands[Default.INIT] = {
+ name: Default.INIT,
+ description: "guided AGENTS.md setup",
+ source: "command",
+ get template() {
+ return PROMPT_INITIALIZE.replace("${path}", ctx.worktree)
+ },
+ hints: hints(PROMPT_INITIALIZE),
+ }
+ commands[Default.REVIEW] = {
+ name: Default.REVIEW,
+ description: "review changes [commit|branch|pr], defaults to uncommitted",
+ source: "command",
+ get template() {
+ return PROMPT_REVIEW.replace("${path}", ctx.worktree)
+ },
+ subtask: true,
+ hints: hints(PROMPT_REVIEW),
+ }
+
+ for (const [name, command] of Object.entries(cfg.command ?? {})) {
+ commands[name] = {
+ name,
+ agent: command.agent,
+ model: command.model,
+ description: command.description,
+ source: "command",
+ get template() {
+ return command.template
+ },
+ subtask: command.subtask,
+ hints: hints(command.template),
+ }
+ }
+
+ for (const [name, prompt] of Object.entries(yield* mcp.prompts())) {
+ commands[name] = {
+ name,
+ source: "mcp",
+ description: prompt.description,
+ get template() {
+ return bridge.promise(
+ mcp
+ .getPrompt(
+ prompt.client,
+ prompt.name,
+ prompt.arguments
+ ? Object.fromEntries(prompt.arguments.map((argument, i) => [argument.name, `$${i + 1}`]))
+ : {},
+ )
+ .pipe(
+ Effect.map(
+ (template) =>
+ template?.messages
+ .map((message) => (message.content.type === "text" ? message.content.text : ""))
+ .join("\n") || "",
+ ),
+ ),
+ )
+ },
+ hints: prompt.arguments?.map((_, i) => `$${i + 1}`) ?? [],
+ }
+ }
+
+ for (const item of yield* skill.all()) {
+ if (commands[item.name]) continue
+ commands[item.name] = {
+ name: item.name,
+ description: item.description,
+ source: "skill",
+ get template() {
+ return item.content
+ },
+ hints: [],
+ }
+ }
+
+ return {
+ commands,
+ }
+ })
+
+ const state = yield* InstanceState.make<State>((ctx) => init(ctx))
+
+ const get = Effect.fn("Command.get")(function* (name: string) {
+ const s = yield* InstanceState.get(state)
+ return s.commands[name]
+ })
+
+ const list = Effect.fn("Command.list")(function* () {
+ const s = yield* InstanceState.get(state)
+ return Object.values(s.commands)
+ })
+
+ return Service.of({ get, list })
+ }),
+)
+
+export const defaultLayer = layer.pipe(
+ Layer.provide(Config.defaultLayer),
+ Layer.provide(MCP.defaultLayer),
+ Layer.provide(Skill.defaultLayer),
+)
+
+export * as Command from "."