Browse Source

feat: unwrap ucommand namespace to flat exports + barrel (#22700)

Kit Langton 2 days ago
parent
commit
426815a829
2 changed files with 187 additions and 188 deletions
  1. 186 0
      packages/opencode/src/command/command.ts
  2. 1 188
      packages/opencode/src/command/index.ts

+ 186 - 0
packages/opencode/src/command/command.ts

@@ -0,0 +1,186 @@
+import { BusEvent } from "@/bus/bus-event"
+import { InstanceState } from "@/effect/instance-state"
+import { EffectBridge } from "@/effect/bridge"
+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),
+)

+ 1 - 188
packages/opencode/src/command/index.ts

@@ -1,188 +1 @@
-import { BusEvent } from "@/bus/bus-event"
-import { InstanceState } from "@/effect/instance-state"
-import { EffectBridge } from "@/effect/bridge"
-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"
-
-export namespace Command {
-  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 "./command"