superacli 1.1.0 → 1.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (261) hide show
  1. package/.beads/.br_history/issues.20260308_235202_180577215.jsonl +57 -0
  2. package/.beads/.br_history/issues.20260308_235202_387414163.jsonl +57 -0
  3. package/.beads/.br_history/issues.20260308_235202_564422794.jsonl +57 -0
  4. package/.beads/.br_history/issues.20260308_235202_742600597.jsonl +57 -0
  5. package/.beads/.br_history/issues.20260308_235208_133360069.jsonl +57 -0
  6. package/.beads/.br_history/issues.20260308_235505_473406307.jsonl +57 -0
  7. package/.beads/.br_history/issues.20260308_235505_662360489.jsonl +57 -0
  8. package/.beads/.br_history/issues.20260308_235505_843935624.jsonl +57 -0
  9. package/.beads/.br_history/issues.20260308_235506_044530221.jsonl +57 -0
  10. package/.beads/.br_history/issues.20260309_002618_115728731.jsonl +57 -0
  11. package/.beads/.br_history/issues.20260309_003748_878174586.jsonl +57 -0
  12. package/.beads/.br_history/issues.20260309_004057_868755623.jsonl +57 -0
  13. package/.beads/.br_history/issues.20260309_004058_512842163.jsonl +57 -0
  14. package/.beads/.br_history/issues.20260309_004058_994445226.jsonl +57 -0
  15. package/.beads/.br_history/issues.20260309_004059_475988596.jsonl +57 -0
  16. package/.beads/.br_history/issues.20260309_161902_566857851.jsonl +57 -0
  17. package/.beads/.br_history/issues.20260309_170512_277017739.jsonl +57 -0
  18. package/.beads/.br_history/issues.20260309_170512_477876921.jsonl +57 -0
  19. package/.beads/.br_history/issues.20260309_170512_664382701.jsonl +57 -0
  20. package/.beads/.br_history/issues.20260309_170512_859400333.jsonl +57 -0
  21. package/.beads/.br_history/issues.20260309_212326_082771164.jsonl +57 -0
  22. package/.beads/.br_history/issues.20260309_212326_245619716.jsonl +58 -0
  23. package/.beads/.br_history/issues.20260309_212326_403198317.jsonl +59 -0
  24. package/.beads/.br_history/issues.20260309_212332_539197678.jsonl +60 -0
  25. package/.beads/.br_history/issues.20260309_212332_731373599.jsonl +60 -0
  26. package/.beads/.br_history/issues.20260309_212332_928710953.jsonl +60 -0
  27. package/.beads/.br_history/issues.20260309_213021_341505240.jsonl +60 -0
  28. package/.beads/.br_history/issues.20260309_213022_023136934.jsonl +60 -0
  29. package/.beads/.br_history/issues.20260309_213022_400050719.jsonl +60 -0
  30. package/.beads/issues.jsonl +20 -17
  31. package/README.md +1 -1
  32. package/__tests__/adapter-schema.test.js +3 -0
  33. package/__tests__/aws-plugin.test.js +84 -0
  34. package/__tests__/az-plugin.test.js +84 -0
  35. package/__tests__/builtin-adapter.test.js +29 -0
  36. package/__tests__/cline-plugin.test.js +109 -0
  37. package/__tests__/cline-skill.test.js +49 -0
  38. package/__tests__/docker-plugin.test.js +3 -1
  39. package/__tests__/eza-plugin.test.js +81 -0
  40. package/__tests__/gcloud-plugin.test.js +86 -0
  41. package/__tests__/gh-plugin.test.js +86 -0
  42. package/__tests__/helm-plugin.test.js +81 -0
  43. package/__tests__/http-adapter.test.js +118 -0
  44. package/__tests__/just-plugin.test.js +82 -0
  45. package/__tests__/kubectl-plugin.test.js +83 -0
  46. package/__tests__/linear-plugin.test.js +81 -0
  47. package/__tests__/mcp-adapter.test.js +187 -0
  48. package/__tests__/nextest-plugin.test.js +82 -0
  49. package/__tests__/npm-plugin.test.js +81 -0
  50. package/__tests__/nullclaw-plugin.test.js +157 -0
  51. package/__tests__/openapi-adapter.test.js +199 -0
  52. package/__tests__/plugin-agency-agents.test.js +6 -6
  53. package/__tests__/plugin-nullclaw.test.js +78 -0
  54. package/__tests__/plugin-visual-explainer.test.js +62 -0
  55. package/__tests__/plugins-manager.test.js +59 -10
  56. package/__tests__/pnpm-plugin.test.js +81 -0
  57. package/__tests__/poetry-plugin.test.js +83 -0
  58. package/__tests__/process-adapter.test.js +124 -90
  59. package/__tests__/pulumi-plugin.test.js +81 -0
  60. package/__tests__/railway-plugin.test.js +84 -0
  61. package/__tests__/server-app.test.js +67 -0
  62. package/__tests__/server-config-service.test.js +79 -0
  63. package/__tests__/server-routes-ask.test.js +89 -0
  64. package/__tests__/server-routes-commands.test.js +55 -0
  65. package/__tests__/server-routes-config.test.js +87 -0
  66. package/__tests__/server-routes-jobs.test.js +53 -0
  67. package/__tests__/server-routes-misc.test.js +112 -0
  68. package/__tests__/server-storage-adapter.test.js +40 -0
  69. package/__tests__/server-storage-file.test.js +73 -0
  70. package/__tests__/server-storage-mongo.test.js +74 -0
  71. package/__tests__/shell-adapter.test.js +81 -22
  72. package/__tests__/stripe-plugin.test.js +3 -1
  73. package/__tests__/supabase-plugin.test.js +86 -0
  74. package/__tests__/terraform-plugin.test.js +83 -0
  75. package/__tests__/uv-plugin.test.js +81 -0
  76. package/__tests__/vercel-plugin.test.js +81 -0
  77. package/__tests__/watchexec-plugin.test.js +80 -0
  78. package/cli/adapter-schema.js +5 -0
  79. package/cli/adapters/process.js +53 -2
  80. package/cli/plugin-install-guidance.js +320 -0
  81. package/cli/plugins-manager.js +272 -212
  82. package/cli/skills.js +16 -5
  83. package/cli/supercli.js +26 -2
  84. package/docs/plugins.md +2 -0
  85. package/docs/skills/cline-non-interactive/SKILL.md +59 -0
  86. package/docs/skills-catalog.md +35 -0
  87. package/docs/visual-overview.md +21 -0
  88. package/jest.config.js +4 -1
  89. package/package.json +4 -3
  90. package/plugins/agency-agents/plugin.json +5 -0
  91. package/{cli/plugin-agency-agents.js → plugins/agency-agents/scripts/post-install.js} +13 -3
  92. package/plugins/aws/README.md +46 -0
  93. package/plugins/aws/plugin.json +42 -0
  94. package/plugins/az/README.md +46 -0
  95. package/plugins/az/plugin.json +42 -0
  96. package/plugins/clickup/plugin.json +38 -0
  97. package/plugins/clickup/scripts/post-install.js +107 -0
  98. package/plugins/clickup/scripts/post-uninstall.js +30 -0
  99. package/plugins/cline/README.md +48 -0
  100. package/plugins/cline/plugin.json +92 -0
  101. package/plugins/eza/README.md +40 -0
  102. package/plugins/eza/plugin.json +42 -0
  103. package/plugins/gcloud/README.md +46 -0
  104. package/plugins/gcloud/plugin.json +42 -0
  105. package/plugins/gh/README.md +46 -0
  106. package/plugins/gh/plugin.json +43 -0
  107. package/plugins/helm/README.md +42 -0
  108. package/plugins/helm/plugin.json +42 -0
  109. package/plugins/just/README.md +42 -0
  110. package/plugins/just/plugin.json +42 -0
  111. package/plugins/kubectl/README.md +46 -0
  112. package/plugins/kubectl/plugin.json +42 -0
  113. package/plugins/linear/README.md +60 -0
  114. package/plugins/linear/plugin.json +42 -0
  115. package/plugins/nextest/README.md +42 -0
  116. package/plugins/nextest/plugin.json +42 -0
  117. package/plugins/npm/README.md +46 -0
  118. package/plugins/npm/plugin.json +42 -0
  119. package/plugins/nullclaw/README.md +45 -0
  120. package/plugins/nullclaw/plugin.json +64 -0
  121. package/plugins/nullclaw/scripts/post-install.js +189 -0
  122. package/plugins/nullclaw/scripts/post-uninstall.js +25 -0
  123. package/plugins/openfang/plugin.json +37 -0
  124. package/plugins/openfang/scripts/post-install.js +163 -0
  125. package/plugins/openfang/scripts/post-uninstall.js +30 -0
  126. package/plugins/plugins.json +234 -0
  127. package/plugins/pnpm/README.md +46 -0
  128. package/plugins/pnpm/plugin.json +42 -0
  129. package/plugins/poetry/README.md +46 -0
  130. package/plugins/poetry/plugin.json +42 -0
  131. package/plugins/pulumi/README.md +46 -0
  132. package/plugins/pulumi/plugin.json +42 -0
  133. package/plugins/railway/README.md +58 -0
  134. package/plugins/railway/plugin.json +43 -0
  135. package/plugins/supabase/README.md +55 -0
  136. package/plugins/supabase/plugin.json +42 -0
  137. package/plugins/superpowers/plugin.json +22 -0
  138. package/plugins/superpowers/scripts/post-install.js +124 -0
  139. package/plugins/superpowers/scripts/post-uninstall.js +30 -0
  140. package/plugins/terraform/README.md +46 -0
  141. package/plugins/terraform/plugin.json +42 -0
  142. package/plugins/uv/README.md +46 -0
  143. package/plugins/uv/plugin.json +42 -0
  144. package/plugins/vercel/README.md +47 -0
  145. package/plugins/vercel/plugin.json +42 -0
  146. package/plugins/visual-explainer/plugin.json +15 -0
  147. package/plugins/visual-explainer/scripts/post-install.js +111 -0
  148. package/plugins/watchexec/README.md +40 -0
  149. package/plugins/watchexec/plugin.json +42 -0
  150. package/tests/test-aws-smoke.sh +56 -0
  151. package/tests/test-az-smoke.sh +56 -0
  152. package/tests/test-cline-smoke.sh +37 -0
  153. package/tests/test-eza-smoke.sh +33 -0
  154. package/tests/test-gcloud-smoke.sh +56 -0
  155. package/tests/test-gh-smoke.sh +56 -0
  156. package/tests/test-helm-smoke.sh +33 -0
  157. package/tests/test-just-smoke.sh +40 -0
  158. package/tests/test-kubectl-smoke.sh +37 -0
  159. package/tests/test-linear-smoke.sh +97 -0
  160. package/tests/test-nextest-smoke.sh +33 -0
  161. package/tests/test-npm-smoke.sh +32 -0
  162. package/tests/test-nullclaw-smoke.sh +51 -0
  163. package/tests/test-plugins-registry.js +110 -0
  164. package/tests/test-pnpm-smoke.sh +33 -0
  165. package/tests/test-poetry-smoke.sh +33 -0
  166. package/tests/test-pulumi-smoke.sh +33 -0
  167. package/tests/test-railway-smoke.sh +95 -0
  168. package/tests/test-supabase-smoke.sh +95 -0
  169. package/tests/test-terraform-smoke.sh +33 -0
  170. package/tests/test-uv-smoke.sh +33 -0
  171. package/tests/test-vercel-smoke.sh +55 -0
  172. package/tests/test-watchexec-smoke.sh +33 -0
  173. package/.beads/.br_history/issues.20260308_180927_477542428.jsonl +0 -12
  174. package/.beads/.br_history/issues.20260308_181032_020230108.jsonl +0 -12
  175. package/.beads/.br_history/issues.20260308_181032_180539413.jsonl +0 -12
  176. package/.beads/.br_history/issues.20260308_181032_372621506.jsonl +0 -12
  177. package/.beads/.br_history/issues.20260308_181032_565142225.jsonl +0 -12
  178. package/.beads/.br_history/issues.20260308_181311_336346464.jsonl +0 -12
  179. package/.beads/.br_history/issues.20260308_181444_039234498.jsonl +0 -13
  180. package/.beads/.br_history/issues.20260308_181503_794764403.jsonl +0 -13
  181. package/.beads/.br_history/issues.20260308_181503_950163105.jsonl +0 -13
  182. package/.beads/.br_history/issues.20260308_192031_852553505.jsonl +0 -13
  183. package/.beads/.br_history/issues.20260308_193552_846920518.jsonl +0 -14
  184. package/.beads/.br_history/issues.20260308_194054_394884833.jsonl +0 -14
  185. package/.beads/.br_history/issues.20260308_194209_440472460.jsonl +0 -15
  186. package/.beads/.br_history/issues.20260308_195319_099391899.jsonl +0 -15
  187. package/.beads/.br_history/issues.20260308_195324_176987204.jsonl +0 -16
  188. package/.beads/.br_history/issues.20260308_195436_929114019.jsonl +0 -16
  189. package/.beads/.br_history/issues.20260308_195437_055808298.jsonl +0 -17
  190. package/.beads/.br_history/issues.20260308_195437_304297399.jsonl +0 -18
  191. package/.beads/.br_history/issues.20260308_195437_556007332.jsonl +0 -19
  192. package/.beads/.br_history/issues.20260308_195444_987209695.jsonl +0 -20
  193. package/.beads/.br_history/issues.20260308_195445_133350193.jsonl +0 -20
  194. package/.beads/.br_history/issues.20260308_195445_400185615.jsonl +0 -20
  195. package/.beads/.br_history/issues.20260308_195445_689886334.jsonl +0 -20
  196. package/.beads/.br_history/issues.20260308_195445_949947727.jsonl +0 -20
  197. package/.beads/.br_history/issues.20260308_195745_580473297.jsonl +0 -20
  198. package/.beads/.br_history/issues.20260308_195745_725920532.jsonl +0 -20
  199. package/.beads/.br_history/issues.20260308_195745_968227911.jsonl +0 -20
  200. package/.beads/.br_history/issues.20260308_195746_224276322.jsonl +0 -20
  201. package/.beads/.br_history/issues.20260308_200018_386890807.jsonl +0 -20
  202. package/ref-btcbot/.env.example +0 -19
  203. package/ref-btcbot/README.md +0 -3
  204. package/ref-btcbot/docs/bot.md +0 -72
  205. package/ref-btcbot/docs/skills/btcbot.backtest/SKILL.md +0 -70
  206. package/ref-btcbot/docs/skills/btcbot.config/SKILL.md +0 -79
  207. package/ref-btcbot/docs/skills/btcbot.orders/SKILL.md +0 -60
  208. package/ref-btcbot/docs/skills/btcbot.positions/SKILL.md +0 -54
  209. package/ref-btcbot/docs/skills/btcbot.risk/SKILL.md +0 -69
  210. package/ref-btcbot/docs/skills/btcbot.run-loop/SKILL.md +0 -63
  211. package/ref-btcbot/docs/skills/btcbot.run-once/SKILL.md +0 -63
  212. package/ref-btcbot/docs/skills/btcbot.status/SKILL.md +0 -59
  213. package/ref-btcbot/examples/sample-candles.json +0 -52
  214. package/ref-btcbot/package.json +0 -18
  215. package/ref-btcbot/plugin/plugin.json +0 -146
  216. package/ref-btcbot/src/cli.js +0 -104
  217. package/ref-btcbot/src/config.js +0 -80
  218. package/ref-btcbot/src/core/bot-runner.js +0 -78
  219. package/ref-btcbot/src/core/exchange-factory.js +0 -19
  220. package/ref-btcbot/src/core/live-exchange.js +0 -12
  221. package/ref-btcbot/src/core/paper-exchange.js +0 -82
  222. package/ref-btcbot/src/core/risk-engine.js +0 -42
  223. package/ref-btcbot/src/core/state-repository.js +0 -47
  224. package/ref-btcbot/src/services/backtest-service.js +0 -44
  225. package/ref-btcbot/src/services/market-data.js +0 -32
  226. package/ref-btcbot/src/storage/json-store.js +0 -45
  227. package/ref-btcbot/src/strategy/hedge-strategy.js +0 -65
  228. package/ref-btcbot/src/strategy/indicators.js +0 -56
  229. package/ref-btcbot/src/web/api.js +0 -50
  230. package/ref-btcbot/src/web/app.js +0 -51
  231. package/ref-btcbot/src/web/index.html +0 -70
  232. package/ref-btcbot/src/web/server.js +0 -33
  233. /package/.beads/.br_history/{issues.20260308_180927_477542428.jsonl.meta.json → issues.20260308_235202_180577215.jsonl.meta.json} +0 -0
  234. /package/.beads/.br_history/{issues.20260308_181032_020230108.jsonl.meta.json → issues.20260308_235202_387414163.jsonl.meta.json} +0 -0
  235. /package/.beads/.br_history/{issues.20260308_181032_180539413.jsonl.meta.json → issues.20260308_235202_564422794.jsonl.meta.json} +0 -0
  236. /package/.beads/.br_history/{issues.20260308_181032_372621506.jsonl.meta.json → issues.20260308_235202_742600597.jsonl.meta.json} +0 -0
  237. /package/.beads/.br_history/{issues.20260308_181032_565142225.jsonl.meta.json → issues.20260308_235208_133360069.jsonl.meta.json} +0 -0
  238. /package/.beads/.br_history/{issues.20260308_181311_336346464.jsonl.meta.json → issues.20260308_235505_473406307.jsonl.meta.json} +0 -0
  239. /package/.beads/.br_history/{issues.20260308_181444_039234498.jsonl.meta.json → issues.20260308_235505_662360489.jsonl.meta.json} +0 -0
  240. /package/.beads/.br_history/{issues.20260308_181503_794764403.jsonl.meta.json → issues.20260308_235505_843935624.jsonl.meta.json} +0 -0
  241. /package/.beads/.br_history/{issues.20260308_181503_950163105.jsonl.meta.json → issues.20260308_235506_044530221.jsonl.meta.json} +0 -0
  242. /package/.beads/.br_history/{issues.20260308_192031_852553505.jsonl.meta.json → issues.20260309_002618_115728731.jsonl.meta.json} +0 -0
  243. /package/.beads/.br_history/{issues.20260308_193552_846920518.jsonl.meta.json → issues.20260309_003748_878174586.jsonl.meta.json} +0 -0
  244. /package/.beads/.br_history/{issues.20260308_194054_394884833.jsonl.meta.json → issues.20260309_004057_868755623.jsonl.meta.json} +0 -0
  245. /package/.beads/.br_history/{issues.20260308_194209_440472460.jsonl.meta.json → issues.20260309_004058_512842163.jsonl.meta.json} +0 -0
  246. /package/.beads/.br_history/{issues.20260308_195319_099391899.jsonl.meta.json → issues.20260309_004058_994445226.jsonl.meta.json} +0 -0
  247. /package/.beads/.br_history/{issues.20260308_195324_176987204.jsonl.meta.json → issues.20260309_004059_475988596.jsonl.meta.json} +0 -0
  248. /package/.beads/.br_history/{issues.20260308_195436_929114019.jsonl.meta.json → issues.20260309_161902_566857851.jsonl.meta.json} +0 -0
  249. /package/.beads/.br_history/{issues.20260308_195437_055808298.jsonl.meta.json → issues.20260309_170512_277017739.jsonl.meta.json} +0 -0
  250. /package/.beads/.br_history/{issues.20260308_195437_304297399.jsonl.meta.json → issues.20260309_170512_477876921.jsonl.meta.json} +0 -0
  251. /package/.beads/.br_history/{issues.20260308_195437_556007332.jsonl.meta.json → issues.20260309_170512_664382701.jsonl.meta.json} +0 -0
  252. /package/.beads/.br_history/{issues.20260308_195444_987209695.jsonl.meta.json → issues.20260309_170512_859400333.jsonl.meta.json} +0 -0
  253. /package/.beads/.br_history/{issues.20260308_195445_133350193.jsonl.meta.json → issues.20260309_212326_082771164.jsonl.meta.json} +0 -0
  254. /package/.beads/.br_history/{issues.20260308_195445_400185615.jsonl.meta.json → issues.20260309_212326_245619716.jsonl.meta.json} +0 -0
  255. /package/.beads/.br_history/{issues.20260308_195445_689886334.jsonl.meta.json → issues.20260309_212326_403198317.jsonl.meta.json} +0 -0
  256. /package/.beads/.br_history/{issues.20260308_195445_949947727.jsonl.meta.json → issues.20260309_212332_539197678.jsonl.meta.json} +0 -0
  257. /package/.beads/.br_history/{issues.20260308_195745_580473297.jsonl.meta.json → issues.20260309_212332_731373599.jsonl.meta.json} +0 -0
  258. /package/.beads/.br_history/{issues.20260308_195745_725920532.jsonl.meta.json → issues.20260309_212332_928710953.jsonl.meta.json} +0 -0
  259. /package/.beads/.br_history/{issues.20260308_195745_968227911.jsonl.meta.json → issues.20260309_213021_341505240.jsonl.meta.json} +0 -0
  260. /package/.beads/.br_history/{issues.20260308_195746_224276322.jsonl.meta.json → issues.20260309_213022_023136934.jsonl.meta.json} +0 -0
  261. /package/.beads/.br_history/{issues.20260308_200018_386890807.jsonl.meta.json → issues.20260309_213022_400050719.jsonl.meta.json} +0 -0
@@ -0,0 +1,62 @@
1
+ const { spawnSync } = require("child_process")
2
+ const { addProvider, syncCatalog } = require("../cli/skills-catalog")
3
+ const {
4
+ run,
5
+ buildRemoteEntriesFromTree
6
+ } = require("../plugins/visual-explainer/scripts/post-install")
7
+
8
+ jest.mock("child_process")
9
+ jest.mock("../cli/skills-catalog")
10
+
11
+ describe("plugin-visual-explainer", () => {
12
+ beforeEach(() => {
13
+ jest.clearAllMocks()
14
+ })
15
+
16
+ test("buildRemoteEntriesFromTree filters only normalized markdown skills", () => {
17
+ const entries = buildRemoteEntriesFromTree({
18
+ tree: [
19
+ { type: "blob", path: "plugins/visual-explainer-normalized/SKILL.md" },
20
+ { type: "blob", path: "plugins/visual-explainer-normalized/commands/diff-review.md" },
21
+ { type: "blob", path: "plugins/visual-explainer/commands/diff-review.md" },
22
+ { type: "blob", path: "plugins/visual-explainer-normalized/templates/architecture.html" }
23
+ ]
24
+ })
25
+
26
+ expect(entries).toHaveLength(2)
27
+ expect(entries[0].id).toBe("visual-explainer.commands.diff-review")
28
+ expect(entries[1].id).toBe("visual-explainer.skill")
29
+ })
30
+
31
+ test("run stores provider and syncs catalog", () => {
32
+ spawnSync.mockReturnValue({
33
+ status: 0,
34
+ stdout: JSON.stringify({
35
+ tree: [
36
+ { type: "blob", path: "plugins/visual-explainer-normalized/SKILL.md" },
37
+ { type: "blob", path: "plugins/visual-explainer-normalized/commands/generate-web-diagram.md" }
38
+ ]
39
+ })
40
+ })
41
+ syncCatalog.mockReturnValue({ skills: [1, 2, 3] })
42
+
43
+ const result = run()
44
+
45
+ expect(addProvider).toHaveBeenCalledWith(expect.objectContaining({
46
+ name: "visual-explainer",
47
+ type: "remote_static",
48
+ entries: expect.any(Array)
49
+ }))
50
+ expect(syncCatalog).toHaveBeenCalled()
51
+ expect(result).toEqual({
52
+ provider: "visual-explainer",
53
+ entries: 2,
54
+ synced_skills: 3
55
+ })
56
+ })
57
+
58
+ test("run throws on curl failure", () => {
59
+ spawnSync.mockReturnValue({ status: 22, stderr: "404" })
60
+ expect(() => run()).toThrow(/Failed to fetch visual-explainer metadata/)
61
+ })
62
+ })
@@ -8,7 +8,6 @@ jest.mock("fs")
8
8
  jest.mock("child_process")
9
9
  jest.mock("../cli/plugins-store")
10
10
  jest.mock("../cli/plugins-registry")
11
- jest.mock("../cli/plugin-agency-agents")
12
11
 
13
12
  const {
14
13
  installPlugin,
@@ -27,7 +26,6 @@ const {
27
26
  } = require("../cli/plugins-store")
28
27
 
29
28
  const { getRegistryPlugin } = require("../cli/plugins-registry")
30
- const { installAgencyAgentsSkillProvider } = require("../cli/plugin-agency-agents")
31
29
 
32
30
  describe("plugins-manager", () => {
33
31
  beforeEach(() => {
@@ -230,20 +228,48 @@ describe("plugins-manager", () => {
230
228
  fs.existsSync.mockReturnValue(true)
231
229
  fs.statSync.mockReturnValue({ isDirectory: () => false })
232
230
  fs.readFileSync.mockReturnValue(JSON.stringify(manifest))
233
- installAgencyAgentsSkillProvider.mockReturnValue({ provider: "agency-agents", entries: 1, synced_skills: 1 })
234
- })
235
-
236
- test("runs agency-agents post install mapping", () => {
237
- fs.readFileSync.mockReturnValue(JSON.stringify({ name: "agency-agents", commands: [] }))
238
- const result = installPlugin("agency-agents")
239
- expect(installAgencyAgentsSkillProvider).toHaveBeenCalled()
240
- expect(result.post_install).toEqual({ provider: "agency-agents", entries: 1, synced_skills: 1 })
241
231
  })
242
232
 
243
233
  test("throws on invalid onConflict", () => {
244
234
  expect(() => installPlugin("p1", { onConflict: "invalid" })).toThrow(/Invalid --on-conflict/)
245
235
  })
246
236
 
237
+ test("runs manifest-defined post install hook", () => {
238
+ fs.readFileSync.mockReturnValue(JSON.stringify({
239
+ name: "visual-explainer",
240
+ commands: [],
241
+ post_install: { script: "scripts/post-install.js" }
242
+ }))
243
+ spawnSync.mockReturnValue({ status: 0, stdout: "{\"provider\":\"visual-explainer\"}" })
244
+ const result = installPlugin("visual-explainer")
245
+ expect(result.post_install).toEqual({ provider: "visual-explainer" })
246
+ })
247
+
248
+ test("rejects post install path traversal", () => {
249
+ fs.readFileSync.mockReturnValue(JSON.stringify({
250
+ name: "visual-explainer",
251
+ commands: [],
252
+ post_install: { script: "../evil.js" }
253
+ }))
254
+ expect(() => installPlugin("visual-explainer")).toThrow(/Invalid post-install script path/)
255
+ })
256
+
257
+ test("stores serialized uninstall hook for later removal", () => {
258
+ fs.readFileSync
259
+ .mockReturnValueOnce(JSON.stringify({
260
+ name: "nullclaw",
261
+ commands: [],
262
+ post_uninstall: { script: "scripts/post-uninstall.js" }
263
+ }))
264
+ .mockReturnValueOnce("module.exports = {}")
265
+ fs.existsSync.mockReturnValue(true)
266
+ fs.statSync.mockReturnValue({ isDirectory: () => false })
267
+
268
+ installPlugin("nullclaw")
269
+ const writtenLock = writePluginsLock.mock.calls[0][0]
270
+ expect(writtenLock.installed.nullclaw.lifecycle_hooks.post_uninstall.script_source).toBe("module.exports = {}")
271
+ })
272
+
247
273
  test("handles same plugin command (skip logic)", () => {
248
274
  readPluginsLock.mockReturnValue({
249
275
  installed: { p1: { name: "p1", commands: [{ namespace: "n", resource: "r", action: "a" }] } }
@@ -292,6 +318,29 @@ describe("plugins-manager", () => {
292
318
  readPluginsLock.mockReturnValue({ installed: { p1: { name: "p1" } } })
293
319
  expect(removePlugin("p1")).toBe(true)
294
320
  })
321
+ test("removePlugin runs stored uninstall hook", () => {
322
+ fs.mkdtempSync.mockReturnValue("/tmp/dcli-plugin-hook-123")
323
+ readPluginsLock.mockReturnValue({
324
+ installed: {
325
+ p1: {
326
+ name: "p1",
327
+ lifecycle_hooks: {
328
+ post_uninstall: {
329
+ runtime: "node",
330
+ timeout_ms: 1000,
331
+ script_name: "post-uninstall.js",
332
+ script_source: "process.stdout.write(JSON.stringify({ok:true}))"
333
+ }
334
+ }
335
+ }
336
+ }
337
+ })
338
+ spawnSync.mockReturnValue({ status: 0, stdout: "{\"ok\":true}" })
339
+ expect(removePlugin("p1")).toBe(true)
340
+ expect(spawnSync).toHaveBeenCalled()
341
+ const writtenLock = writePluginsLock.mock.calls[0][0]
342
+ expect(writtenLock.installed.p1).toBeUndefined()
343
+ })
295
344
  test("removePlugin fail", () => {
296
345
  readPluginsLock.mockReturnValue({ installed: {} })
297
346
  expect(removePlugin("p1")).toBe(false)
@@ -0,0 +1,81 @@
1
+ const fs = require("fs")
2
+ const os = require("os")
3
+ const path = require("path")
4
+ const { execSync } = require("child_process")
5
+
6
+ const CLI = path.join(__dirname, "..", "cli", "supercli.js")
7
+
8
+ function runNoServer(args, options = {}) {
9
+ try {
10
+ const env = { ...process.env }
11
+ delete env.SUPERCLI_SERVER
12
+ const out = execSync(`node ${CLI} ${args}`, {
13
+ encoding: "utf-8",
14
+ timeout: 15000,
15
+ env: { ...env, ...(options.env || {}) }
16
+ })
17
+ return { ok: true, output: out.trim(), code: 0 }
18
+ } catch (err) {
19
+ return {
20
+ ok: false,
21
+ output: (err.stdout || "").trim(),
22
+ stderr: (err.stderr || "").trim(),
23
+ code: err.status
24
+ }
25
+ }
26
+ }
27
+
28
+ function writeFakePnpmBinary(dir) {
29
+ const bin = path.join(dir, "pnpm")
30
+ fs.writeFileSync(bin, [
31
+ "#!/usr/bin/env node",
32
+ "const args = process.argv.slice(2);",
33
+ "if (args[0] === '--version') { console.log('9.0.0-test'); process.exit(0); }",
34
+ "console.log(JSON.stringify({ ok: true, args }));"
35
+ ].join("\n"), "utf-8")
36
+ fs.chmodSync(bin, 0o755)
37
+ return bin
38
+ }
39
+
40
+ describe("pnpm plugin", () => {
41
+ const fakeDir = fs.mkdtempSync(path.join(os.tmpdir(), "dcli-pnpm-"))
42
+ const tempHome = fs.mkdtempSync(path.join(os.tmpdir(), "dcli-home-pnpm-"))
43
+ writeFakePnpmBinary(fakeDir)
44
+ const env = { ...process.env, PATH: `${fakeDir}:${process.env.PATH || ""}`, SUPERCLI_HOME: tempHome }
45
+
46
+ beforeAll(() => {
47
+ runNoServer("plugins install ./plugins/pnpm --on-conflict replace --json", { env })
48
+ })
49
+
50
+ afterAll(() => {
51
+ runNoServer("plugins remove pnpm --json", { env })
52
+ fs.rmSync(fakeDir, { recursive: true, force: true })
53
+ fs.rmSync(tempHome, { recursive: true, force: true })
54
+ })
55
+
56
+ test("routes cli version wrapped command", () => {
57
+ const r = runNoServer("pnpm cli version --json", { env })
58
+ expect(r.ok).toBe(true)
59
+ const data = JSON.parse(r.output)
60
+ expect(data.command).toBe("pnpm.cli.version")
61
+ expect(data.data.raw).toBe("9.0.0-test")
62
+ })
63
+
64
+ test("supports namespace passthrough", () => {
65
+ const r = runNoServer("pnpm search react --json", { env })
66
+ expect(r.ok).toBe(true)
67
+ const data = JSON.parse(r.output)
68
+ expect(data.command).toBe("pnpm.passthrough")
69
+ expect(data.data.args[0]).toBe("search")
70
+ expect(data.data.args).toContain("react")
71
+ expect(data.data.args).toContain("--json")
72
+ })
73
+
74
+ test("doctor reports pnpm dependency as healthy", () => {
75
+ const r = runNoServer("plugins doctor pnpm --json", { env })
76
+ expect(r.ok).toBe(true)
77
+ const data = JSON.parse(r.output)
78
+ expect(data.ok).toBe(true)
79
+ expect(data.checks.some(c => c.type === "binary" && c.binary === "pnpm" && c.ok === true)).toBe(true)
80
+ })
81
+ })
@@ -0,0 +1,83 @@
1
+ const fs = require("fs")
2
+ const os = require("os")
3
+ const path = require("path")
4
+ const { execSync } = require("child_process")
5
+
6
+ const CLI = path.join(__dirname, "..", "cli", "supercli.js")
7
+
8
+ function runNoServer(args, options = {}) {
9
+ try {
10
+ const env = { ...process.env }
11
+ delete env.SUPERCLI_SERVER
12
+ const out = execSync(`node ${CLI} ${args}`, {
13
+ encoding: "utf-8",
14
+ timeout: 15000,
15
+ env: { ...env, ...(options.env || {}) }
16
+ })
17
+ return { ok: true, output: out.trim(), code: 0 }
18
+ } catch (err) {
19
+ return {
20
+ ok: false,
21
+ output: (err.stdout || "").trim(),
22
+ stderr: (err.stderr || "").trim(),
23
+ code: err.status
24
+ }
25
+ }
26
+ }
27
+
28
+ function writeFakePoetryBinary(dir) {
29
+ const bin = path.join(dir, "poetry")
30
+ fs.writeFileSync(bin, [
31
+ "#!/usr/bin/env node",
32
+ "const args = process.argv.slice(2);",
33
+ "if (args[0] === '--version') { console.log('Poetry (version 2.1.1-test)'); process.exit(0); }",
34
+ "console.log(JSON.stringify({ ok: true, args }));"
35
+ ].join("\n"), "utf-8")
36
+ fs.chmodSync(bin, 0o755)
37
+ return bin
38
+ }
39
+
40
+ describe("poetry plugin", () => {
41
+ const fakeDir = fs.mkdtempSync(path.join(os.tmpdir(), "dcli-poetry-"))
42
+ const tempHome = fs.mkdtempSync(path.join(os.tmpdir(), "dcli-home-poetry-"))
43
+ writeFakePoetryBinary(fakeDir)
44
+ const env = { ...process.env, PATH: `${fakeDir}:${process.env.PATH || ""}`, SUPERCLI_HOME: tempHome }
45
+
46
+ beforeAll(() => {
47
+ runNoServer("plugins install ./plugins/poetry --on-conflict replace --json", { env })
48
+ })
49
+
50
+ afterAll(() => {
51
+ runNoServer("plugins remove poetry --json", { env })
52
+ fs.rmSync(fakeDir, { recursive: true, force: true })
53
+ fs.rmSync(tempHome, { recursive: true, force: true })
54
+ })
55
+
56
+ test("routes cli version wrapped command", () => {
57
+ const r = runNoServer("poetry cli version --json", { env })
58
+ expect(r.ok).toBe(true)
59
+ const data = JSON.parse(r.output)
60
+ expect(data.command).toBe("poetry.cli.version")
61
+ expect(data.data.raw).toBe("Poetry (version 2.1.1-test)")
62
+ })
63
+
64
+ test("supports namespace passthrough", () => {
65
+ const r = runNoServer("poetry self show --format json --json", { env })
66
+ expect(r.ok).toBe(true)
67
+ const data = JSON.parse(r.output)
68
+ expect(data.command).toBe("poetry.passthrough")
69
+ expect(data.data.args[0]).toBe("self")
70
+ expect(data.data.args[1]).toBe("show")
71
+ expect(data.data.args).toContain("--format")
72
+ expect(data.data.args).toContain("json")
73
+ expect(data.data.args).toContain("--json")
74
+ })
75
+
76
+ test("doctor reports poetry dependency as healthy", () => {
77
+ const r = runNoServer("plugins doctor poetry --json", { env })
78
+ expect(r.ok).toBe(true)
79
+ const data = JSON.parse(r.output)
80
+ expect(data.ok).toBe(true)
81
+ expect(data.checks.some(c => c.type === "binary" && c.binary === "poetry" && c.ok === true)).toBe(true)
82
+ })
83
+ })
@@ -1,109 +1,143 @@
1
- const os = require("os")
2
1
  const { execute } = require("../cli/adapters/process")
2
+ const { spawn, spawnSync } = require("child_process")
3
+ const EventEmitter = require("events")
4
+ const os = require("os")
5
+
6
+ jest.mock("child_process")
3
7
 
4
8
  describe("process adapter", () => {
5
- test("coerces array flags and skips false values", async () => {
6
- const result = await execute({
7
- adapterConfig: {
8
- command: "node",
9
- baseArgs: ["-e", "process.stdout.write(JSON.stringify(process.argv.slice(1)))", "--"],
10
- parseJson: true,
11
- timeout_ms: 2000
12
- }
13
- }, {
14
- name: "demo",
15
- tags: ["a", "b"],
16
- enabled: false,
17
- count: 2
18
- })
9
+ let mockChild
19
10
 
20
- expect(result).toContain("--name")
21
- expect(result).toContain("demo")
22
- expect(result).toContain("--tags")
23
- expect(result).toContain("a")
24
- expect(result).toContain("b")
25
- expect(result).toContain("--count")
26
- expect(result).not.toContain("--enabled")
11
+ beforeEach(() => {
12
+ jest.clearAllMocks()
13
+ mockChild = new EventEmitter()
14
+ mockChild.stdout = new EventEmitter()
15
+ mockChild.stdout.setEncoding = jest.fn()
16
+ mockChild.stderr = new EventEmitter()
17
+ mockChild.stderr.setEncoding = jest.fn()
18
+ mockChild.stdin = { write: jest.fn(), end: jest.fn() }
19
+ mockChild.kill = jest.fn()
20
+ spawn.mockReturnValue(mockChild)
21
+ spawnSync.mockReturnValue({ status: 0, stdout: "/bin/node" })
27
22
  })
28
23
 
29
- test("returns invalid_argument with missing dependency help", async () => {
30
- await expect(execute({
31
- adapterConfig: {
32
- command: "definitely_missing_binary_xyz",
33
- parseJson: false,
34
- missingDependencyHelp: "Run: dcli beads install steps"
35
- }
36
- }, {})).rejects.toMatchObject({
37
- code: 85,
38
- type: "invalid_argument"
39
- })
24
+ test("detectInteractiveFlags coverage (short flags, long flag with equals)", async () => {
25
+ const oldTTY = process.stdout.isTTY
26
+ process.stdout.isTTY = false
27
+ try {
28
+ await expect(execute({
29
+ adapterConfig: {
30
+ command: "node",
31
+ passthrough: true,
32
+ interactiveFlags: ["--tty", "-i", "-t"]
33
+ },
34
+ }, { __rawArgs: ["--tty=true", "-it"] })).rejects.toMatchObject({ code: 91 })
35
+ } finally {
36
+ process.stdout.isTTY = oldTTY
37
+ }
40
38
  })
41
39
 
42
- test("supports cwd and env injection", async () => {
43
- const result = await execute({
44
- adapterConfig: {
45
- command: "node",
46
- baseArgs: ["-e", "process.stdout.write(JSON.stringify({cwd:process.cwd(),x:process.env.X_TEST}))", "--"],
47
- parseJson: true,
48
- cwd: os.tmpdir(),
49
- env: { X_TEST: "ok" },
50
- timeout_ms: 2000
51
- }
52
- }, {})
53
-
54
- expect(result.cwd).toBe(os.tmpdir())
55
- expect(result.x).toBe("ok")
40
+ test("throws if command missing", async () => {
41
+ await expect(execute({ adapterConfig: {} }, {})).rejects.toThrow(/requires adapterConfig.command/)
56
42
  })
57
43
 
58
- test("rejects interactive flags in non-tty context", async () => {
59
- const originalIsTTY = process.stdout.isTTY
60
- Object.defineProperty(process.stdout, "isTTY", {
61
- value: false,
62
- configurable: true
63
- })
44
+ test("successfully executes command and parses JSON", async () => {
45
+ const promise = execute({
46
+ adapterConfig: { command: "node", baseArgs: ["-e", "ok"], parseJson: true }
47
+ }, { foo: "bar" })
64
48
 
65
- await expect(execute({
66
- adapterConfig: {
67
- command: "node",
68
- baseArgs: ["-e", "process.stdout.write('ok')", "--"],
69
- parseJson: false,
70
- interactiveFlags: ["--interactive", "-i"]
71
- }
72
- }, {
73
- interactive: true
74
- })).rejects.toMatchObject({
75
- code: 91,
76
- type: "safety_violation"
77
- })
49
+ mockChild.stdout.emit("data", '{"result": true}')
50
+ mockChild.emit("close", 0)
78
51
 
79
- Object.defineProperty(process.stdout, "isTTY", {
80
- value: originalIsTTY,
81
- configurable: true
82
- })
52
+ const result = await promise
53
+ expect(result).toEqual({ result: true })
83
54
  })
84
55
 
85
- test("rejects requiresInteractive in non-tty context", async () => {
86
- const originalIsTTY = process.stdout.isTTY
87
- Object.defineProperty(process.stdout, "isTTY", {
88
- value: false,
89
- configurable: true
90
- })
56
+ test("handles spawn error ENOENT", async () => {
57
+ const promise = execute({ adapterConfig: { command: "node" } }, {})
58
+ mockChild.emit("error", { code: "ENOENT" })
59
+ await expect(promise).rejects.toMatchObject({ code: 85 })
60
+ })
91
61
 
92
- await expect(execute({
93
- adapterConfig: {
94
- command: "node",
95
- baseArgs: ["-e", "process.stdout.write('ok')", "--"],
96
- parseJson: false,
97
- requiresInteractive: true
98
- }
99
- }, {})).rejects.toMatchObject({
100
- code: 91,
101
- type: "safety_violation"
102
- })
62
+ test("handles timeout", async () => {
63
+ jest.useFakeTimers()
64
+ const promise = execute({ adapterConfig: { command: "node", timeout_ms: 100 } }, {})
65
+ jest.advanceTimersByTime(200)
66
+ await expect(promise).rejects.toThrow("timed out")
67
+ jest.useRealTimers()
68
+ })
103
69
 
104
- Object.defineProperty(process.stdout, "isTTY", {
105
- value: originalIsTTY,
106
- configurable: true
70
+ test("handles non-zero exit", async () => {
71
+ const promise = execute({ adapterConfig: { command: "node" } }, {})
72
+ mockChild.stderr.emit("data", "error")
73
+ mockChild.emit("close", 1)
74
+ await expect(promise).rejects.toThrow("Process adapter failed")
75
+ })
76
+
77
+ test("handles passthroughInteractive", async () => {
78
+ const promise = execute({
79
+ adapterConfig: { command: "node", passthrough: true }
80
+ }, { __passthroughInteractive: true })
81
+ mockChild.emit("close", 0)
82
+ const result = await promise
83
+ expect(result.passthrough).toBe(true)
84
+ })
85
+
86
+ test("includes nonTtyBaseArgs", async () => {
87
+ const oldTTY = process.stdout.isTTY
88
+ process.stdout.isTTY = false
89
+ try {
90
+ const promise = execute({
91
+ adapterConfig: { command: "node", nonTtyBaseArgs: ["--no-tty"] }
92
+ }, {})
93
+ mockChild.emit("close", 0)
94
+ await promise
95
+ expect(spawn).toHaveBeenCalledWith("node", ["--no-tty"], expect.anything())
96
+ } finally {
97
+ process.stdout.isTTY = oldTTY
98
+ }
99
+ })
100
+
101
+ test("handles JSON parse error", async () => {
102
+ const promise = execute({ adapterConfig: { command: "node", parseJson: true } }, {})
103
+ mockChild.stdout.emit("data", "invalid")
104
+ mockChild.emit("close", 0)
105
+ const result = await promise
106
+ expect(result.raw).toBe("invalid")
107
+ })
108
+
109
+ test("handles parseJson false", async () => {
110
+ const promise = execute({ adapterConfig: { command: "node", parseJson: false } }, {})
111
+ mockChild.stdout.emit("data", "text")
112
+ mockChild.emit("close", 0)
113
+ const result = await promise
114
+ expect(result.raw).toBe("text")
115
+ })
116
+
117
+ test("streams jsonl events incrementally", async () => {
118
+ const onStreamEvent = jest.fn()
119
+ const promise = execute({ adapterConfig: { command: "node", stream: "jsonl" } }, {}, { onStreamEvent })
120
+ mockChild.stdout.emit("data", '{"type":"say","text":"one"}\n{"type":"say"')
121
+ mockChild.stdout.emit("data", ',"text":"two"}\n')
122
+ mockChild.emit("close", 0)
123
+ const result = await promise
124
+ expect(onStreamEvent).toHaveBeenCalledTimes(2)
125
+ expect(onStreamEvent.mock.calls[0][0]).toEqual({ type: "say", text: "one" })
126
+ expect(result).toEqual({
127
+ streamed: true,
128
+ stream: "jsonl",
129
+ event_count: 2,
130
+ last_event: { type: "say", text: "two" }
107
131
  })
108
132
  })
133
+
134
+ test("flushes trailing jsonl line without newline", async () => {
135
+ const onStreamEvent = jest.fn()
136
+ const promise = execute({ adapterConfig: { command: "node", stream: "jsonl" } }, {}, { onStreamEvent })
137
+ mockChild.stdout.emit("data", '{"type":"task_started","taskId":"123"}')
138
+ mockChild.emit("close", 0)
139
+ const result = await promise
140
+ expect(onStreamEvent).toHaveBeenCalledWith({ type: "task_started", taskId: "123" })
141
+ expect(result.event_count).toBe(1)
142
+ })
109
143
  })
@@ -0,0 +1,81 @@
1
+ const fs = require("fs")
2
+ const os = require("os")
3
+ const path = require("path")
4
+ const { execSync } = require("child_process")
5
+
6
+ const CLI = path.join(__dirname, "..", "cli", "supercli.js")
7
+
8
+ function runNoServer(args, options = {}) {
9
+ try {
10
+ const env = { ...process.env }
11
+ delete env.SUPERCLI_SERVER
12
+ const out = execSync(`node ${CLI} ${args}`, {
13
+ encoding: "utf-8",
14
+ timeout: 15000,
15
+ env: { ...env, ...(options.env || {}) }
16
+ })
17
+ return { ok: true, output: out.trim(), code: 0 }
18
+ } catch (err) {
19
+ return {
20
+ ok: false,
21
+ output: (err.stdout || "").trim(),
22
+ stderr: (err.stderr || "").trim(),
23
+ code: err.status
24
+ }
25
+ }
26
+ }
27
+
28
+ function writeFakePulumiBinary(dir) {
29
+ const bin = path.join(dir, "pulumi")
30
+ fs.writeFileSync(bin, [
31
+ "#!/usr/bin/env node",
32
+ "const args = process.argv.slice(2);",
33
+ "if (args[0] === 'version') { console.log('v3.160.0-test'); process.exit(0); }",
34
+ "console.log(JSON.stringify({ ok: true, args }));"
35
+ ].join("\n"), "utf-8")
36
+ fs.chmodSync(bin, 0o755)
37
+ return bin
38
+ }
39
+
40
+ describe("pulumi plugin", () => {
41
+ const fakeDir = fs.mkdtempSync(path.join(os.tmpdir(), "dcli-pulumi-"))
42
+ const tempHome = fs.mkdtempSync(path.join(os.tmpdir(), "dcli-home-pulumi-"))
43
+ writeFakePulumiBinary(fakeDir)
44
+ const env = { ...process.env, PATH: `${fakeDir}:${process.env.PATH || ""}`, SUPERCLI_HOME: tempHome }
45
+
46
+ beforeAll(() => {
47
+ runNoServer("plugins install ./plugins/pulumi --on-conflict replace --json", { env })
48
+ })
49
+
50
+ afterAll(() => {
51
+ runNoServer("plugins remove pulumi --json", { env })
52
+ fs.rmSync(fakeDir, { recursive: true, force: true })
53
+ fs.rmSync(tempHome, { recursive: true, force: true })
54
+ })
55
+
56
+ test("routes cli version wrapped command", () => {
57
+ const r = runNoServer("pulumi cli version --json", { env })
58
+ expect(r.ok).toBe(true)
59
+ const data = JSON.parse(r.output)
60
+ expect(data.command).toBe("pulumi.cli.version")
61
+ expect(data.data.raw).toBe("v3.160.0-test")
62
+ })
63
+
64
+ test("supports namespace passthrough", () => {
65
+ const r = runNoServer("pulumi stack ls --json", { env })
66
+ expect(r.ok).toBe(true)
67
+ const data = JSON.parse(r.output)
68
+ expect(data.command).toBe("pulumi.passthrough")
69
+ expect(data.data.args[0]).toBe("stack")
70
+ expect(data.data.args[1]).toBe("ls")
71
+ expect(data.data.args).toContain("--json")
72
+ })
73
+
74
+ test("doctor reports pulumi dependency as healthy", () => {
75
+ const r = runNoServer("plugins doctor pulumi --json", { env })
76
+ expect(r.ok).toBe(true)
77
+ const data = JSON.parse(r.output)
78
+ expect(data.ok).toBe(true)
79
+ expect(data.checks.some(c => c.type === "binary" && c.binary === "pulumi" && c.ok === true)).toBe(true)
80
+ })
81
+ })