skill.test.ts 3.5 KB

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