command.test.ts 3.1 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283
  1. import fs from "fs/promises"
  2. import path from "path"
  3. import { describe, expect } from "bun:test"
  4. import { Effect, Schema } from "effect"
  5. import { CommandV2 } from "@opencode-ai/core/command"
  6. import { Config } from "@opencode-ai/core/config"
  7. import { ConfigCommandPlugin } from "@opencode-ai/core/config/plugin/command"
  8. import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder"
  9. import { LayerNode } from "@opencode-ai/core/effect/layer-node"
  10. import { FSUtil } from "@opencode-ai/core/fs-util"
  11. import { ModelV2 } from "@opencode-ai/core/model"
  12. import { ProviderV2 } from "@opencode-ai/core/provider"
  13. import { AbsolutePath } from "@opencode-ai/core/schema"
  14. import { tmpdir } from "../fixture/tmpdir"
  15. import { testEffect } from "../lib/effect"
  16. import { host } from "../plugin/host"
  17. const it = testEffect(AppNodeBuilder.build(LayerNode.group([CommandV2.node, FSUtil.node])))
  18. const decode = Schema.decodeUnknownSync(Config.Info)
  19. describe("ConfigCommandPlugin.Plugin", () => {
  20. it.live("loads inline and file-based commands in config order", () =>
  21. Effect.acquireRelease(
  22. Effect.promise(() => tmpdir()),
  23. (tmp) => Effect.promise(() => tmp[Symbol.asyncDispose]()),
  24. ).pipe(
  25. Effect.flatMap((tmp) =>
  26. Effect.gen(function* () {
  27. yield* Effect.promise(async () => {
  28. await fs.mkdir(path.join(tmp.path, "commands", "nested"), { recursive: true })
  29. await fs.writeFile(
  30. path.join(tmp.path, "commands", "review.md"),
  31. `---
  32. description: File review
  33. agent: reviewer
  34. model: anthropic/claude
  35. variant: high
  36. subtask: true
  37. ---
  38. Review files`,
  39. )
  40. await fs.writeFile(path.join(tmp.path, "commands", "nested", "docs.md"), "Write docs")
  41. await fs.writeFile(path.join(tmp.path, "commands", "empty.md"), "")
  42. })
  43. const command = yield* CommandV2.Service
  44. yield* ConfigCommandPlugin.Plugin.effect(host({ command: { ...command, reload: command.reload } })).pipe(
  45. Effect.provideService(
  46. Config.Service,
  47. Config.Service.of({
  48. entries: () =>
  49. Effect.succeed([
  50. new Config.Document({
  51. type: "document",
  52. info: decode({ commands: { review: { template: "Inline review" } } }),
  53. }),
  54. new Config.Directory({ type: "directory", path: AbsolutePath.make(tmp.path) }),
  55. ]),
  56. }),
  57. ),
  58. )
  59. expect(yield* command.list()).toEqual([
  60. CommandV2.Info.make({
  61. name: "review",
  62. template: "Review files",
  63. description: "File review",
  64. agent: "reviewer",
  65. model: {
  66. providerID: ProviderV2.ID.make("anthropic"),
  67. id: ModelV2.ID.make("claude"),
  68. variant: ModelV2.VariantID.make("high"),
  69. },
  70. subtask: true,
  71. }),
  72. CommandV2.Info.make({ name: "empty", template: "" }),
  73. CommandV2.Info.make({ name: "nested/docs", template: "Write docs" }),
  74. ])
  75. }),
  76. ),
  77. ),
  78. )
  79. })