skill.test.ts 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990
  1. import path from "path"
  2. import { describe, expect } from "bun:test"
  3. import { Effect, Layer, Schema } from "effect"
  4. import { Config } from "@opencode-ai/core/config"
  5. import { ConfigSkillPlugin } from "@opencode-ai/core/config/plugin/skill"
  6. import { Global } from "@opencode-ai/core/global"
  7. import { Location } from "@opencode-ai/core/location"
  8. import { AbsolutePath } from "@opencode-ai/core/schema"
  9. import { SkillV2 } from "@opencode-ai/core/skill"
  10. import { location } from "../fixture/location"
  11. import { testEffect } from "../lib/effect"
  12. import { host } from "../plugin/host"
  13. const it = testEffect(Layer.empty)
  14. const decode = Schema.decodeUnknownSync(Config.Info)
  15. describe("ConfigSkillPlugin.Plugin", () => {
  16. it.effect("registers configured skill directories and URLs", () =>
  17. Effect.gen(function* () {
  18. const directory = AbsolutePath.make("/repo/packages/app")
  19. const sources: SkillV2.Source[] = []
  20. const transform = Effect.fnUntraced(function* (update: (draft: SkillV2.Draft) => void | Effect.Effect<void>) {
  21. const result = update({
  22. source: (source) => {
  23. sources.push(source)
  24. },
  25. list: () => sources,
  26. })
  27. if (Effect.isEffect(result)) yield* result
  28. const dispose = Effect.sync(() => {
  29. sources.length = 0
  30. })
  31. yield* Effect.addFinalizer(() => dispose)
  32. return { dispose }
  33. })
  34. yield* ConfigSkillPlugin.Plugin.effect(
  35. host({
  36. skill: { list: () => Effect.die("unused skill.list"), transform, reload: () => Effect.void },
  37. }),
  38. ).pipe(
  39. Effect.provideService(Global.Service, Global.Service.of({ ...Global.make(), home: "/home/test" })),
  40. Effect.provideService(Location.Service, Location.Service.of(location({ directory }))),
  41. Effect.provideService(
  42. Config.Service,
  43. Config.Service.of({
  44. entries: () =>
  45. Effect.succeed([
  46. new Config.ClaudeDirectory({ type: "claude", path: AbsolutePath.make("/repo/.claude") }),
  47. new Config.AgentsDirectory({ type: "agents", path: AbsolutePath.make("/repo/.agents") }),
  48. new Config.Directory({ type: "directory", path: AbsolutePath.make("/repo/.opencode") }),
  49. new Config.Document({
  50. type: "document",
  51. info: decode({
  52. skills: ["./skills", "~/shared-skills", "/opt/skills", "https://example.test/skills/"],
  53. }),
  54. }),
  55. ]),
  56. }),
  57. ),
  58. )
  59. expect(sources).toEqual([
  60. SkillV2.DirectorySource.make({
  61. type: "directory",
  62. path: AbsolutePath.make(path.join("/repo/.claude", "skills")),
  63. }),
  64. SkillV2.DirectorySource.make({
  65. type: "directory",
  66. path: AbsolutePath.make(path.join("/repo/.agents", "skills")),
  67. }),
  68. SkillV2.DirectorySource.make({
  69. type: "directory",
  70. path: AbsolutePath.make(path.join("/repo/.opencode", "skill")),
  71. }),
  72. SkillV2.DirectorySource.make({
  73. type: "directory",
  74. path: AbsolutePath.make(path.join("/repo/.opencode", "skills")),
  75. }),
  76. SkillV2.DirectorySource.make({ type: "directory", path: AbsolutePath.make(path.join(directory, "skills")) }),
  77. SkillV2.DirectorySource.make({
  78. type: "directory",
  79. path: AbsolutePath.make(path.join("/home/test", "shared-skills")),
  80. }),
  81. SkillV2.DirectorySource.make({ type: "directory", path: AbsolutePath.make("/opt/skills") }),
  82. SkillV2.UrlSource.make({ type: "url", url: "https://example.test/skills/" }),
  83. ])
  84. }),
  85. )
  86. })