skill.test.ts 3.5 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586
  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 { ConfigSkillPlugin } from "@opencode-ai/core/config/plugin/skill"
  6. import { Global } from "@opencode-ai/util/global"
  7. import { Location } from "@opencode-ai/core/location"
  8. import { AbsolutePath } from "@opencode-ai/core/schema"
  9. import { Skill } 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: Skill.Source[] = []
  20. const transform = Effect.fnUntraced(function* (update: (draft: Skill.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.provide(
  42. Config.testLayer([
  43. new Config.ClaudeDirectory({ type: "claude", path: AbsolutePath.make("/repo/.claude") }),
  44. new Config.AgentsDirectory({ type: "agents", path: AbsolutePath.make("/repo/.agents") }),
  45. new Config.Directory({ type: "directory", path: AbsolutePath.make("/repo/.opencode") }),
  46. new Config.Document({
  47. type: "document",
  48. info: decode({
  49. skills: ["./skills", "~/shared-skills", "/opt/skills", "https://example.test/skills/"],
  50. }),
  51. }),
  52. ]),
  53. ),
  54. )
  55. expect(sources).toEqual([
  56. Skill.DirectorySource.make({
  57. type: "directory",
  58. path: AbsolutePath.make(path.join("/repo/.claude", "skills")),
  59. }),
  60. Skill.DirectorySource.make({
  61. type: "directory",
  62. path: AbsolutePath.make(path.join("/repo/.agents", "skills")),
  63. }),
  64. Skill.DirectorySource.make({
  65. type: "directory",
  66. path: AbsolutePath.make(path.join("/repo/.opencode", "skill")),
  67. }),
  68. Skill.DirectorySource.make({
  69. type: "directory",
  70. path: AbsolutePath.make(path.join("/repo/.opencode", "skills")),
  71. }),
  72. Skill.DirectorySource.make({ type: "directory", path: AbsolutePath.make(path.join(directory, "skills")) }),
  73. Skill.DirectorySource.make({
  74. type: "directory",
  75. path: AbsolutePath.make(path.join("/home/test", "shared-skills")),
  76. }),
  77. Skill.DirectorySource.make({ type: "directory", path: AbsolutePath.make("/opt/skills") }),
  78. Skill.UrlSource.make({ type: "url", url: "https://example.test/skills/" }),
  79. ])
  80. }),
  81. )
  82. })