pilothub 0.0.1 → 0.0.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 (388) hide show
  1. package/LICENSE +1 -0
  2. package/README.md +36 -129
  3. package/dist/browserAuth.d.ts +20 -0
  4. package/dist/browserAuth.js +156 -0
  5. package/dist/browserAuth.js.map +1 -0
  6. package/dist/browserAuth.test.d.ts +1 -0
  7. package/dist/browserAuth.test.js +83 -0
  8. package/dist/browserAuth.test.js.map +1 -0
  9. package/dist/cli/buildInfo.d.ts +3 -0
  10. package/dist/cli/buildInfo.js +103 -0
  11. package/dist/cli/buildInfo.js.map +1 -0
  12. package/dist/cli/commands/auth.d.ts +9 -0
  13. package/dist/cli/commands/auth.js +75 -0
  14. package/dist/cli/commands/auth.js.map +1 -0
  15. package/dist/cli/commands/delete.d.ts +11 -0
  16. package/dist/cli/commands/delete.js +67 -0
  17. package/dist/cli/commands/delete.js.map +1 -0
  18. package/dist/cli/commands/delete.test.d.ts +1 -0
  19. package/dist/cli/commands/delete.test.js +52 -0
  20. package/dist/cli/commands/delete.test.js.map +1 -0
  21. package/dist/cli/commands/publish.d.ts +9 -0
  22. package/dist/cli/commands/publish.js +87 -0
  23. package/dist/cli/commands/publish.js.map +1 -0
  24. package/dist/cli/commands/publish.test.d.ts +1 -0
  25. package/dist/cli/commands/publish.test.js +104 -0
  26. package/dist/cli/commands/publish.test.js.map +1 -0
  27. package/dist/cli/commands/skills.d.ts +23 -0
  28. package/dist/cli/commands/skills.js +298 -0
  29. package/dist/cli/commands/skills.js.map +1 -0
  30. package/dist/cli/commands/skills.test.d.ts +1 -0
  31. package/dist/cli/commands/skills.test.js +156 -0
  32. package/dist/cli/commands/skills.test.js.map +1 -0
  33. package/dist/cli/commands/star.d.ts +8 -0
  34. package/dist/cli/commands/star.js +38 -0
  35. package/dist/cli/commands/star.js.map +1 -0
  36. package/dist/cli/commands/sync.d.ts +3 -0
  37. package/dist/cli/commands/sync.js +160 -0
  38. package/dist/cli/commands/sync.js.map +1 -0
  39. package/dist/cli/commands/sync.test.d.ts +1 -0
  40. package/dist/cli/commands/sync.test.js +277 -0
  41. package/dist/cli/commands/sync.test.js.map +1 -0
  42. package/dist/cli/commands/syncHelpers.d.ts +76 -0
  43. package/dist/cli/commands/syncHelpers.js +349 -0
  44. package/dist/cli/commands/syncHelpers.js.map +1 -0
  45. package/dist/cli/commands/syncHelpers.test.d.ts +1 -0
  46. package/dist/cli/commands/syncHelpers.test.js +22 -0
  47. package/dist/cli/commands/syncHelpers.test.js.map +1 -0
  48. package/dist/cli/commands/syncTypes.d.ts +24 -0
  49. package/dist/cli/commands/syncTypes.js +2 -0
  50. package/dist/cli/commands/syncTypes.js.map +1 -0
  51. package/dist/cli/commands/unstar.d.ts +8 -0
  52. package/dist/cli/commands/unstar.js +38 -0
  53. package/dist/cli/commands/unstar.js.map +1 -0
  54. package/dist/cli/helpStyle.d.ts +13 -0
  55. package/dist/cli/helpStyle.js +38 -0
  56. package/dist/cli/helpStyle.js.map +1 -0
  57. package/dist/cli/pilotbotConfig.d.ts +6 -0
  58. package/dist/cli/pilotbotConfig.js +110 -0
  59. package/dist/cli/pilotbotConfig.js.map +1 -0
  60. package/dist/cli/pilotbotConfig.test.d.ts +1 -0
  61. package/dist/cli/pilotbotConfig.test.js +133 -0
  62. package/dist/cli/pilotbotConfig.test.js.map +1 -0
  63. package/dist/cli/registry.d.ts +7 -0
  64. package/dist/cli/registry.js +42 -0
  65. package/dist/cli/registry.js.map +1 -0
  66. package/dist/cli/registry.test.d.ts +1 -0
  67. package/dist/cli/registry.test.js +48 -0
  68. package/dist/cli/registry.test.js.map +1 -0
  69. package/dist/cli/scanSkills.d.ts +7 -0
  70. package/dist/cli/scanSkills.js +75 -0
  71. package/dist/cli/scanSkills.js.map +1 -0
  72. package/dist/cli/scanSkills.test.d.ts +1 -0
  73. package/dist/cli/scanSkills.test.js +60 -0
  74. package/dist/cli/scanSkills.test.js.map +1 -0
  75. package/dist/cli/slug.d.ts +2 -0
  76. package/dist/cli/slug.js +16 -0
  77. package/dist/cli/slug.js.map +1 -0
  78. package/dist/cli/types.d.ts +15 -0
  79. package/dist/cli/types.js +2 -0
  80. package/dist/cli/types.js.map +1 -0
  81. package/dist/cli/ui.d.ts +7 -0
  82. package/dist/cli/ui.js +72 -0
  83. package/dist/cli/ui.js.map +1 -0
  84. package/dist/cli.d.ts +2 -0
  85. package/dist/cli.js +268 -0
  86. package/dist/cli.js.map +1 -0
  87. package/dist/config.d.ts +4 -0
  88. package/dist/config.js +38 -0
  89. package/dist/config.js.map +1 -0
  90. package/dist/discovery.d.ts +5 -0
  91. package/dist/discovery.js +21 -0
  92. package/dist/discovery.js.map +1 -0
  93. package/dist/discovery.test.d.ts +1 -0
  94. package/dist/discovery.test.js +46 -0
  95. package/dist/discovery.test.js.map +1 -0
  96. package/dist/http.d.ts +32 -0
  97. package/dist/http.js +261 -0
  98. package/dist/http.js.map +1 -0
  99. package/dist/http.test.d.ts +1 -0
  100. package/dist/http.test.js +135 -0
  101. package/dist/http.test.js.map +1 -0
  102. package/dist/schema/ark.js.map +1 -0
  103. package/dist/schema/index.js.map +1 -0
  104. package/dist/schema/routes.js.map +1 -0
  105. package/{packages/schema/dist → dist/schema}/schemas.d.ts +0 -39
  106. package/{packages/schema/dist → dist/schema}/schemas.js +0 -22
  107. package/dist/schema/schemas.js.map +1 -0
  108. package/dist/schema/textFiles.js.map +1 -0
  109. package/dist/schema/textFiles.test.d.ts +1 -0
  110. package/dist/schema/textFiles.test.js +20 -0
  111. package/dist/schema/textFiles.test.js.map +1 -0
  112. package/dist/skills.d.ts +43 -0
  113. package/dist/skills.js +163 -0
  114. package/dist/skills.js.map +1 -0
  115. package/dist/skills.test.d.ts +1 -0
  116. package/dist/skills.test.js +144 -0
  117. package/dist/skills.test.js.map +1 -0
  118. package/dist/types.d.ts +7 -0
  119. package/dist/types.js +2 -0
  120. package/dist/types.js.map +1 -0
  121. package/package.json +27 -70
  122. package/.env.local.example +0 -19
  123. package/.github/workflows/ci.yml +0 -40
  124. package/.oxlintrc.json +0 -3
  125. package/AGENTS.md +0 -45
  126. package/CHANGELOG.md +0 -138
  127. package/DEPRECATIONS.md +0 -7
  128. package/biome.json +0 -41
  129. package/convex/_generated/api.d.ts +0 -153
  130. package/convex/_generated/api.js +0 -23
  131. package/convex/_generated/dataModel.d.ts +0 -60
  132. package/convex/_generated/server.d.ts +0 -143
  133. package/convex/_generated/server.js +0 -93
  134. package/convex/auth.config.ts +0 -8
  135. package/convex/auth.ts +0 -19
  136. package/convex/comments.ts +0 -88
  137. package/convex/crons.ts +0 -34
  138. package/convex/devSeed.ts +0 -459
  139. package/convex/devSeedExtra.ts +0 -541
  140. package/convex/downloads.ts +0 -78
  141. package/convex/githubBackups.ts +0 -170
  142. package/convex/githubBackupsNode.ts +0 -183
  143. package/convex/githubImport.ts +0 -317
  144. package/convex/githubSoulBackups.ts +0 -170
  145. package/convex/githubSoulBackupsNode.ts +0 -186
  146. package/convex/http.ts +0 -194
  147. package/convex/httpApi.handlers.test.ts +0 -488
  148. package/convex/httpApi.test.ts +0 -70
  149. package/convex/httpApi.ts +0 -305
  150. package/convex/httpApiV1.handlers.test.ts +0 -584
  151. package/convex/httpApiV1.ts +0 -1172
  152. package/convex/leaderboards.ts +0 -39
  153. package/convex/lib/access.ts +0 -36
  154. package/convex/lib/apiTokenAuth.ts +0 -36
  155. package/convex/lib/badges.ts +0 -50
  156. package/convex/lib/changelog.test.ts +0 -34
  157. package/convex/lib/changelog.ts +0 -278
  158. package/convex/lib/embeddings.ts +0 -38
  159. package/convex/lib/githubBackup.ts +0 -443
  160. package/convex/lib/githubImport.test.ts +0 -247
  161. package/convex/lib/githubImport.ts +0 -425
  162. package/convex/lib/githubSoulBackup.ts +0 -443
  163. package/convex/lib/leaderboards.ts +0 -103
  164. package/convex/lib/moderation.ts +0 -42
  165. package/convex/lib/public.ts +0 -89
  166. package/convex/lib/searchText.test.ts +0 -46
  167. package/convex/lib/searchText.ts +0 -27
  168. package/convex/lib/skillBackfill.test.ts +0 -34
  169. package/convex/lib/skillBackfill.ts +0 -67
  170. package/convex/lib/skillPublish.test.ts +0 -28
  171. package/convex/lib/skillPublish.ts +0 -284
  172. package/convex/lib/skillStats.ts +0 -80
  173. package/convex/lib/skills.test.ts +0 -197
  174. package/convex/lib/skills.ts +0 -273
  175. package/convex/lib/soulChangelog.ts +0 -273
  176. package/convex/lib/soulPublish.ts +0 -236
  177. package/convex/lib/tokens.test.ts +0 -33
  178. package/convex/lib/tokens.ts +0 -51
  179. package/convex/lib/webhooks.test.ts +0 -91
  180. package/convex/lib/webhooks.ts +0 -112
  181. package/convex/maintenance.test.ts +0 -270
  182. package/convex/maintenance.ts +0 -840
  183. package/convex/rateLimits.ts +0 -50
  184. package/convex/schema.ts +0 -472
  185. package/convex/search.test.ts +0 -12
  186. package/convex/search.ts +0 -254
  187. package/convex/seed.test.ts +0 -37
  188. package/convex/seed.ts +0 -254
  189. package/convex/seedSouls.ts +0 -111
  190. package/convex/skillStatEvents.ts +0 -568
  191. package/convex/skills.ts +0 -1606
  192. package/convex/soulComments.ts +0 -88
  193. package/convex/soulDownloads.ts +0 -14
  194. package/convex/soulStars.ts +0 -71
  195. package/convex/souls.ts +0 -570
  196. package/convex/stars.ts +0 -108
  197. package/convex/statsMaintenance.ts +0 -205
  198. package/convex/telemetry.ts +0 -434
  199. package/convex/tokens.ts +0 -88
  200. package/convex/tsconfig.json +0 -7
  201. package/convex/uploads.ts +0 -20
  202. package/convex/users.ts +0 -122
  203. package/convex/webhooks.ts +0 -50
  204. package/convex.json +0 -3
  205. package/docs/README.md +0 -32
  206. package/docs/api.md +0 -51
  207. package/docs/architecture.md +0 -61
  208. package/docs/auth.md +0 -54
  209. package/docs/cli.md +0 -117
  210. package/docs/deploy.md +0 -78
  211. package/docs/diffing.md +0 -84
  212. package/docs/github-import.md +0 -171
  213. package/docs/http-api.md +0 -187
  214. package/docs/manual-testing.md +0 -64
  215. package/docs/mintlify.md +0 -43
  216. package/docs/quickstart.md +0 -120
  217. package/docs/skill-format.md +0 -58
  218. package/docs/soul-format.md +0 -37
  219. package/docs/spec.md +0 -177
  220. package/docs/telemetry.md +0 -91
  221. package/docs/troubleshooting.md +0 -49
  222. package/docs/webhook.md +0 -51
  223. package/e2e/menu-smoke.pw.test.ts +0 -49
  224. package/e2e/pilothub.e2e.test.ts +0 -494
  225. package/e2e/search-exact.pw.test.ts +0 -97
  226. package/packages/pilothub/LICENSE +0 -22
  227. package/packages/pilothub/README.md +0 -57
  228. package/packages/pilothub/package.json +0 -41
  229. package/packages/pilothub/src/browserAuth.test.ts +0 -96
  230. package/packages/pilothub/src/browserAuth.ts +0 -174
  231. package/packages/pilothub/src/cli/buildInfo.ts +0 -94
  232. package/packages/pilothub/src/cli/commands/auth.ts +0 -97
  233. package/packages/pilothub/src/cli/commands/delete.test.ts +0 -73
  234. package/packages/pilothub/src/cli/commands/delete.ts +0 -83
  235. package/packages/pilothub/src/cli/commands/publish.test.ts +0 -122
  236. package/packages/pilothub/src/cli/commands/publish.ts +0 -108
  237. package/packages/pilothub/src/cli/commands/skills.test.ts +0 -191
  238. package/packages/pilothub/src/cli/commands/skills.ts +0 -380
  239. package/packages/pilothub/src/cli/commands/star.ts +0 -46
  240. package/packages/pilothub/src/cli/commands/sync.test.ts +0 -310
  241. package/packages/pilothub/src/cli/commands/sync.ts +0 -200
  242. package/packages/pilothub/src/cli/commands/syncHelpers.test.ts +0 -26
  243. package/packages/pilothub/src/cli/commands/syncHelpers.ts +0 -427
  244. package/packages/pilothub/src/cli/commands/syncTypes.ts +0 -27
  245. package/packages/pilothub/src/cli/commands/unstar.ts +0 -48
  246. package/packages/pilothub/src/cli/helpStyle.ts +0 -45
  247. package/packages/pilothub/src/cli/pilotbotConfig.test.ts +0 -159
  248. package/packages/pilothub/src/cli/pilotbotConfig.ts +0 -147
  249. package/packages/pilothub/src/cli/registry.test.ts +0 -63
  250. package/packages/pilothub/src/cli/registry.ts +0 -43
  251. package/packages/pilothub/src/cli/scanSkills.test.ts +0 -64
  252. package/packages/pilothub/src/cli/scanSkills.ts +0 -84
  253. package/packages/pilothub/src/cli/slug.ts +0 -16
  254. package/packages/pilothub/src/cli/types.ts +0 -12
  255. package/packages/pilothub/src/cli/ui.ts +0 -75
  256. package/packages/pilothub/src/cli.ts +0 -311
  257. package/packages/pilothub/src/config.ts +0 -36
  258. package/packages/pilothub/src/discovery.test.ts +0 -75
  259. package/packages/pilothub/src/discovery.ts +0 -19
  260. package/packages/pilothub/src/http.test.ts +0 -156
  261. package/packages/pilothub/src/http.ts +0 -301
  262. package/packages/pilothub/src/schema/ark.ts +0 -29
  263. package/packages/pilothub/src/schema/index.ts +0 -5
  264. package/packages/pilothub/src/schema/routes.ts +0 -22
  265. package/packages/pilothub/src/schema/schemas.ts +0 -260
  266. package/packages/pilothub/src/schema/textFiles.test.ts +0 -23
  267. package/packages/pilothub/src/schema/textFiles.ts +0 -66
  268. package/packages/pilothub/src/skills.test.ts +0 -191
  269. package/packages/pilothub/src/skills.ts +0 -172
  270. package/packages/pilothub/src/types.ts +0 -10
  271. package/packages/pilothub/tsconfig.json +0 -14
  272. package/packages/schema/README.md +0 -3
  273. package/packages/schema/dist/ark.js.map +0 -1
  274. package/packages/schema/dist/index.js.map +0 -1
  275. package/packages/schema/dist/routes.js.map +0 -1
  276. package/packages/schema/dist/schemas.js.map +0 -1
  277. package/packages/schema/dist/textFiles.js.map +0 -1
  278. package/packages/schema/package.json +0 -26
  279. package/packages/schema/src/ark.ts +0 -29
  280. package/packages/schema/src/index.ts +0 -5
  281. package/packages/schema/src/routes.ts +0 -22
  282. package/packages/schema/src/schemas.test.ts +0 -123
  283. package/packages/schema/src/schemas.ts +0 -287
  284. package/packages/schema/src/textFiles.test.ts +0 -23
  285. package/packages/schema/src/textFiles.ts +0 -66
  286. package/packages/schema/tsconfig.json +0 -15
  287. package/pilothub +0 -46
  288. package/playwright.config.ts +0 -33
  289. package/public/.well-known/pilothub.json +0 -6
  290. package/public/api/v1/openapi.json +0 -379
  291. package/public/favicon.ico +0 -0
  292. package/public/logo192.png +0 -0
  293. package/public/logo512.png +0 -0
  294. package/public/manifest.json +0 -25
  295. package/public/og.png +0 -0
  296. package/public/og.svg +0 -98
  297. package/public/pilot-logo.png +0 -0
  298. package/public/pilot-mark.png +0 -0
  299. package/public/robots.txt +0 -3
  300. package/public/tanstack-circle-logo.png +0 -0
  301. package/public/tanstack-word-logo-white.svg +0 -1
  302. package/scripts/check-peer-deps.ts +0 -56
  303. package/scripts/docs-list.ts +0 -148
  304. package/scripts/run-playwright-local.sh +0 -14
  305. package/server/og/fetchSkillOgMeta.ts +0 -27
  306. package/server/og/fetchSoulOgMeta.ts +0 -27
  307. package/server/og/ogAssets.ts +0 -80
  308. package/server/og/skillOgSvg.test.ts +0 -59
  309. package/server/og/skillOgSvg.ts +0 -258
  310. package/server/og/soulOgSvg.ts +0 -209
  311. package/server/routes/og/skill.png.ts +0 -103
  312. package/server/routes/og/soul.png.ts +0 -111
  313. package/src/__tests__/skill-detail-page.test.tsx +0 -86
  314. package/src/__tests__/skills-index.test.tsx +0 -145
  315. package/src/__tests__/upload.route.test.tsx +0 -228
  316. package/src/components/AppProviders.tsx +0 -19
  317. package/src/components/ClientOnly.tsx +0 -18
  318. package/src/components/Footer.tsx +0 -29
  319. package/src/components/Header.tsx +0 -295
  320. package/src/components/InstallSwitcher.tsx +0 -53
  321. package/src/components/SkillCard.tsx +0 -36
  322. package/src/components/SkillDetailPage.tsx +0 -817
  323. package/src/components/SkillDiffCard.tsx +0 -485
  324. package/src/components/SoulCard.tsx +0 -19
  325. package/src/components/SoulDetailPage.tsx +0 -263
  326. package/src/components/UserBootstrap.tsx +0 -18
  327. package/src/components/ui/dropdown-menu.tsx +0 -67
  328. package/src/components/ui/toggle-group.tsx +0 -35
  329. package/src/convex/client.ts +0 -3
  330. package/src/lib/badges.ts +0 -29
  331. package/src/lib/diffing.test.ts +0 -163
  332. package/src/lib/diffing.ts +0 -106
  333. package/src/lib/gravatar.test.ts +0 -9
  334. package/src/lib/gravatar.ts +0 -158
  335. package/src/lib/og.test.ts +0 -142
  336. package/src/lib/og.ts +0 -156
  337. package/src/lib/publicUser.ts +0 -39
  338. package/src/lib/roles.ts +0 -19
  339. package/src/lib/site.test.ts +0 -130
  340. package/src/lib/site.ts +0 -84
  341. package/src/lib/theme-transition.test.ts +0 -134
  342. package/src/lib/theme-transition.ts +0 -134
  343. package/src/lib/theme.test.tsx +0 -88
  344. package/src/lib/theme.ts +0 -43
  345. package/src/lib/uploadFiles.jsdom.test.ts +0 -33
  346. package/src/lib/uploadFiles.test.ts +0 -123
  347. package/src/lib/uploadFiles.ts +0 -245
  348. package/src/lib/uploadUtils.test.ts +0 -78
  349. package/src/lib/uploadUtils.ts +0 -93
  350. package/src/lib/useAuthStatus.ts +0 -12
  351. package/src/lib/utils.test.ts +0 -9
  352. package/src/lib/utils.ts +0 -6
  353. package/src/logo.svg +0 -12
  354. package/src/routeTree.gen.ts +0 -345
  355. package/src/router.tsx +0 -17
  356. package/src/routes/$owner/$slug.tsx +0 -55
  357. package/src/routes/__root.tsx +0 -136
  358. package/src/routes/admin.tsx +0 -11
  359. package/src/routes/cli/auth.tsx +0 -168
  360. package/src/routes/dashboard.tsx +0 -97
  361. package/src/routes/import.tsx +0 -415
  362. package/src/routes/index.tsx +0 -252
  363. package/src/routes/management.tsx +0 -529
  364. package/src/routes/settings.tsx +0 -203
  365. package/src/routes/skills/index.tsx +0 -422
  366. package/src/routes/souls/$slug.tsx +0 -55
  367. package/src/routes/souls/index.tsx +0 -243
  368. package/src/routes/stars.tsx +0 -68
  369. package/src/routes/u/$handle.tsx +0 -307
  370. package/src/routes/upload/utils.ts +0 -81
  371. package/src/routes/upload.tsx +0 -499
  372. package/src/styles.css +0 -2718
  373. package/tsconfig.json +0 -24
  374. package/tsconfig.oxlint.json +0 -16
  375. package/vercel.json +0 -8
  376. package/vite.config.ts +0 -48
  377. package/vitest.config.ts +0 -47
  378. package/vitest.e2e.config.ts +0 -11
  379. package/vitest.setup.ts +0 -1
  380. /package/{packages/pilothub/bin → bin}/pilothub.js +0 -0
  381. /package/{packages/schema/dist → dist/schema}/ark.d.ts +0 -0
  382. /package/{packages/schema/dist → dist/schema}/ark.js +0 -0
  383. /package/{packages/schema/dist → dist/schema}/index.d.ts +0 -0
  384. /package/{packages/schema/dist → dist/schema}/index.js +0 -0
  385. /package/{packages/schema/dist → dist/schema}/routes.d.ts +0 -0
  386. /package/{packages/schema/dist → dist/schema}/routes.js +0 -0
  387. /package/{packages/schema/dist → dist/schema}/textFiles.d.ts +0 -0
  388. /package/{packages/schema/dist → dist/schema}/textFiles.js +0 -0
@@ -1,427 +0,0 @@
1
- import { createHash } from 'node:crypto'
2
- import { realpath } from 'node:fs/promises'
3
- import { homedir } from 'node:os'
4
- import { resolve } from 'node:path'
5
- import { isCancel, multiselect } from '@clack/prompts'
6
- import semver from 'semver'
7
- import { apiRequest, downloadZip } from '../../http.js'
8
- import {
9
- ApiCliTelemetrySyncResponseSchema,
10
- ApiRoutes,
11
- ApiV1SkillResolveResponseSchema,
12
- ApiV1SkillResponseSchema,
13
- ApiV1WhoamiResponseSchema,
14
- LegacyApiRoutes,
15
- } from '../../schema/index.js'
16
- import { hashSkillZip } from '../../skills.js'
17
- import { getRegistry } from '../registry.js'
18
- import { findSkillFolders, type SkillFolder } from '../scanSkills.js'
19
- import type { GlobalOpts } from '../types.js'
20
- import { fail, formatError } from '../ui.js'
21
- import type { Candidate, LocalSkill } from './syncTypes.js'
22
-
23
- export async function reportTelemetryIfEnabled(params: {
24
- token: string
25
- registry: string
26
- scan: { roots: string[]; skillsByRoot: Record<string, SkillFolder[]> }
27
- candidates: Candidate[]
28
- }) {
29
- if (isTelemetryDisabled()) return
30
- const versionBySlug = new Map<string, string | null>()
31
- for (const candidate of params.candidates) {
32
- versionBySlug.set(candidate.slug, candidate.matchVersion ?? null)
33
- }
34
-
35
- const roots = params.scan.roots.map((root) => ({
36
- rootId: rootTelemetryId(root),
37
- label: formatRootLabel(root),
38
- skills: (params.scan.skillsByRoot[root] ?? []).map((skill) => ({
39
- slug: skill.slug,
40
- version: versionBySlug.get(skill.slug) ?? null,
41
- })),
42
- }))
43
-
44
- try {
45
- await apiRequest(
46
- params.registry,
47
- {
48
- method: 'POST',
49
- path: LegacyApiRoutes.cliTelemetrySync,
50
- token: params.token,
51
- body: { roots },
52
- },
53
- ApiCliTelemetrySyncResponseSchema,
54
- )
55
- } catch {
56
- // ignore telemetry failures
57
- }
58
- }
59
-
60
- function isTelemetryDisabled() {
61
- const raw = process.env.PILOTHUB_DISABLE_TELEMETRY
62
- if (!raw) return false
63
- return ['1', 'true', 'yes', 'on'].includes(raw.trim().toLowerCase())
64
- }
65
-
66
- export function buildScanRoots(opts: GlobalOpts, extraRoots: string[] | undefined) {
67
- const roots = [opts.workdir, opts.dir, ...(extraRoots ?? [])]
68
- return Array.from(new Set(roots.map((root) => resolve(root))))
69
- }
70
-
71
- export function normalizeConcurrency(value: number | undefined) {
72
- const raw = typeof value === 'number' ? value : 4
73
- const rounded = Number.isFinite(raw) ? Math.round(raw) : 4
74
- return Math.min(32, Math.max(1, rounded))
75
- }
76
-
77
- export async function mapWithConcurrency<T, R>(
78
- items: T[],
79
- limit: number,
80
- fn: (item: T) => Promise<R>,
81
- ) {
82
- const results = Array.from({ length: items.length }) as R[]
83
- let nextIndex = 0
84
- const workerCount = Math.min(Math.max(1, limit), items.length || 1)
85
-
86
- async function worker() {
87
- while (true) {
88
- const index = nextIndex
89
- nextIndex += 1
90
- if (index >= items.length) return
91
- results[index] = await fn(items[index] as T)
92
- }
93
- }
94
-
95
- await Promise.all(Array.from({ length: workerCount }, () => worker()))
96
- return results
97
- }
98
-
99
- export async function checkRegistrySyncState(
100
- registry: string,
101
- skill: LocalSkill,
102
- resolveSupport: { value: boolean | null },
103
- ): Promise<Candidate> {
104
- if (resolveSupport.value !== false) {
105
- try {
106
- const resolved = await apiRequest(
107
- registry,
108
- {
109
- method: 'GET',
110
- path: `${ApiRoutes.resolve}?slug=${encodeURIComponent(skill.slug)}&hash=${encodeURIComponent(skill.fingerprint)}`,
111
- },
112
- ApiV1SkillResolveResponseSchema,
113
- )
114
- resolveSupport.value = true
115
- const latestVersion = resolved.latestVersion?.version ?? null
116
- const matchVersion = resolved.match?.version ?? null
117
- if (!latestVersion) {
118
- return {
119
- ...skill,
120
- status: 'new',
121
- matchVersion: null,
122
- latestVersion: null,
123
- }
124
- }
125
- return {
126
- ...skill,
127
- status: matchVersion ? 'synced' : 'update',
128
- matchVersion,
129
- latestVersion,
130
- }
131
- } catch (error) {
132
- const message = formatError(error)
133
- if (/skill not found/i.test(message) || /HTTP 404/i.test(message)) {
134
- resolveSupport.value = true
135
- return {
136
- ...skill,
137
- status: 'new',
138
- matchVersion: null,
139
- latestVersion: null,
140
- }
141
- }
142
- if (/no matching routes found/i.test(message)) {
143
- resolveSupport.value = false
144
- } else {
145
- throw error
146
- }
147
- }
148
- }
149
-
150
- const meta = await apiRequest(
151
- registry,
152
- { method: 'GET', path: `${ApiRoutes.skills}/${encodeURIComponent(skill.slug)}` },
153
- ApiV1SkillResponseSchema,
154
- ).catch(() => null)
155
-
156
- const latestVersion = meta?.latestVersion?.version ?? null
157
- if (!latestVersion) {
158
- return {
159
- ...skill,
160
- status: 'new',
161
- matchVersion: null,
162
- latestVersion: null,
163
- }
164
- }
165
-
166
- const zip = await downloadZip(registry, { slug: skill.slug, version: latestVersion })
167
- const remote = hashSkillZip(zip).fingerprint
168
- const matchVersion = remote === skill.fingerprint ? latestVersion : null
169
-
170
- return {
171
- ...skill,
172
- status: matchVersion ? 'synced' : 'update',
173
- matchVersion,
174
- latestVersion,
175
- }
176
- }
177
-
178
- export async function scanRoots(roots: string[]) {
179
- const result = await scanRootsWithLabels(roots)
180
- return {
181
- roots: result.roots,
182
- skillsByRoot: result.skillsByRoot,
183
- skills: result.skills,
184
- rootsWithSkills: result.rootsWithSkills,
185
- }
186
- }
187
-
188
- export async function scanRootsWithLabels(roots: string[], labels?: Record<string, string>) {
189
- const all: SkillFolder[] = []
190
- const rootsWithSkills: string[] = []
191
- const uniqueRoots = await dedupeRoots(roots)
192
- const skillsByRoot: Record<string, SkillFolder[]> = {}
193
- const rootLabels: Record<string, string> = {}
194
- for (const root of uniqueRoots) {
195
- const found = await findSkillFolders(root)
196
- skillsByRoot[root] = found
197
- if (found.length > 0) rootsWithSkills.push(root)
198
- all.push(...found)
199
- if (labels?.[root]) rootLabels[root] = labels[root] as string
200
- }
201
- const byFolder = new Map<string, SkillFolder>()
202
- for (const folder of all) {
203
- byFolder.set(folder.folder, folder)
204
- }
205
- return {
206
- roots: uniqueRoots,
207
- skillsByRoot,
208
- skills: Array.from(byFolder.values()),
209
- rootsWithSkills,
210
- rootLabels,
211
- }
212
- }
213
-
214
- export function mergeScan(
215
- left: {
216
- roots: string[]
217
- skillsByRoot: Record<string, SkillFolder[]>
218
- skills: SkillFolder[]
219
- rootsWithSkills: string[]
220
- rootLabels: Record<string, string>
221
- },
222
- right: {
223
- roots: string[]
224
- skillsByRoot: Record<string, SkillFolder[]>
225
- skills: SkillFolder[]
226
- rootsWithSkills: string[]
227
- rootLabels: Record<string, string>
228
- },
229
- ) {
230
- const mergedRoots = Array.from(new Set([...left.roots, ...right.roots]))
231
- const skillsByRoot: Record<string, SkillFolder[]> = {}
232
- for (const root of mergedRoots) {
233
- skillsByRoot[root] = right.skillsByRoot[root] ?? left.skillsByRoot[root] ?? []
234
- }
235
- const rootLabels: Record<string, string> = { ...left.rootLabels, ...right.rootLabels }
236
- const byFolder = new Map<string, SkillFolder>()
237
- for (const entry of [...left.skills, ...right.skills]) {
238
- byFolder.set(entry.folder, entry)
239
- }
240
- const skills = Array.from(byFolder.values())
241
- const rootsWithSkills = mergedRoots.filter((root) => (skillsByRoot[root]?.length ?? 0) > 0)
242
- return { roots: mergedRoots, skillsByRoot, skills, rootsWithSkills, rootLabels }
243
- }
244
-
245
- async function dedupeRoots(roots: string[]) {
246
- const seen = new Set<string>()
247
- const unique: string[] = []
248
- for (const root of roots) {
249
- const resolved = resolve(root)
250
- const canonical = await realpath(resolved).catch(() => null)
251
- const key = canonical ?? resolved
252
- if (seen.has(key)) continue
253
- seen.add(key)
254
- unique.push(key)
255
- }
256
- return unique
257
- }
258
-
259
- export async function selectToUpload(
260
- candidates: Candidate[],
261
- params: { allowPrompt: boolean; all: boolean; bump: 'patch' | 'minor' | 'major' },
262
- ): Promise<Candidate[]> {
263
- if (params.all || !params.allowPrompt) return candidates
264
-
265
- const valueByKey = new Map<string, Candidate>()
266
- const choices = candidates.map((candidate) => {
267
- const key = candidate.folder
268
- valueByKey.set(key, candidate)
269
- return {
270
- value: key,
271
- label: `${candidate.slug} ${formatActionableStatus(candidate, params.bump)}`,
272
- hint: `${abbreviatePath(candidate.folder)} | ${candidate.fileCount} files`,
273
- }
274
- })
275
-
276
- const picked = await multiselect({
277
- message: 'Select skills to upload',
278
- options: choices,
279
- initialValues: choices.map((choice) => choice.value),
280
- required: false,
281
- })
282
- if (isCancel(picked)) fail('Canceled')
283
- const selected = picked.map((key) => valueByKey.get(String(key))).filter(Boolean) as Candidate[]
284
- return selected
285
- }
286
-
287
- export async function resolvePublishMeta(
288
- skill: Candidate,
289
- params: { bump: 'patch' | 'minor' | 'major'; allowPrompt: boolean; changelogFlag?: string },
290
- ) {
291
- if (skill.status === 'new') {
292
- return { publishVersion: '1.0.0', changelog: '' }
293
- }
294
-
295
- const latest = skill.latestVersion
296
- if (!latest) fail(`Could not resolve latest version for ${skill.slug}`)
297
- const publishVersion = semver.inc(latest, params.bump)
298
- if (!publishVersion) fail(`Could not bump version for ${skill.slug}`)
299
-
300
- const fromFlag = params.changelogFlag?.trim()
301
- if (fromFlag) return { publishVersion, changelog: fromFlag }
302
-
303
- return { publishVersion, changelog: '' }
304
- }
305
-
306
- export async function getRegistryWithAuth(opts: GlobalOpts, token: string) {
307
- const registry = await getRegistry(opts, { cache: true })
308
- await apiRequest(
309
- registry,
310
- { method: 'GET', path: ApiRoutes.whoami, token },
311
- ApiV1WhoamiResponseSchema,
312
- )
313
- return registry
314
- }
315
-
316
- export function formatList(values: string[], max: number) {
317
- if (values.length === 0) return ''
318
- const shown = values.map(abbreviatePath)
319
- if (shown.length <= max) return shown.join('\n')
320
- const head = shown.slice(0, Math.max(1, max - 1))
321
- const rest = values.length - head.length
322
- return [...head, `… +${rest} more`].join('\n')
323
- }
324
-
325
- export function printSection(title: string, body?: string) {
326
- const trimmed = body?.trim()
327
- if (!trimmed) {
328
- console.log(title)
329
- return
330
- }
331
- if (trimmed.includes('\n')) {
332
- console.log(`\n${title}\n${trimmed}`)
333
- return
334
- }
335
- console.log(`${title}: ${trimmed}`)
336
- }
337
-
338
- function abbreviatePath(value: string) {
339
- const home = homedir()
340
- if (value.startsWith(home)) return `~${value.slice(home.length)}`
341
- return value
342
- }
343
-
344
- function rootTelemetryId(value: string) {
345
- return createHash('sha256').update(value).digest('hex')
346
- }
347
-
348
- function formatRootLabel(value: string) {
349
- const home = homedir()
350
- if (value === home) return '~'
351
-
352
- const normalized = value.replaceAll('\\', '/')
353
- const normalizedHome = home.replaceAll('\\', '/')
354
- const isHome = normalized === normalizedHome || normalized.startsWith(`${normalizedHome}/`)
355
-
356
- const stripped = isHome ? normalized.slice(normalizedHome.length).replace(/^\//, '') : normalized
357
- const parts = stripped.split('/').filter(Boolean)
358
- const tail = parts.slice(-2).join('/')
359
-
360
- if (!tail) return isHome ? '~' : '…'
361
- return isHome ? `~/${tail}` : `…/${tail}`
362
- }
363
-
364
- export function dedupeSkillsBySlug(skills: SkillFolder[]) {
365
- const bySlug = new Map<string, SkillFolder[]>()
366
- for (const skill of skills) {
367
- const existing = bySlug.get(skill.slug)
368
- if (existing) existing.push(skill)
369
- else bySlug.set(skill.slug, [skill])
370
- }
371
- const unique: SkillFolder[] = []
372
- const duplicates: string[] = []
373
- for (const [slug, entries] of bySlug.entries()) {
374
- unique.push(entries[0] as SkillFolder)
375
- if (entries.length > 1) duplicates.push(`${slug} (${entries.length})`)
376
- }
377
- return { skills: unique, duplicates }
378
- }
379
-
380
- export function formatActionableStatus(
381
- candidate: Candidate,
382
- bump: 'patch' | 'minor' | 'major',
383
- ): string {
384
- if (candidate.status === 'new') return 'NEW'
385
- const latest = candidate.latestVersion
386
- const next = latest ? semver.inc(latest, bump) : null
387
- if (latest && next) return `UPDATE ${latest} → ${next}`
388
- return 'UPDATE'
389
- }
390
-
391
- export function formatActionableLine(
392
- candidate: Candidate,
393
- bump: 'patch' | 'minor' | 'major',
394
- ): string {
395
- return `${candidate.slug} ${formatActionableStatus(candidate, bump)} (${candidate.fileCount} files)`
396
- }
397
-
398
- function formatSyncedLine(candidate: Candidate): string {
399
- const version = candidate.matchVersion ?? candidate.latestVersion ?? 'unknown'
400
- return `${candidate.slug} synced (${version})`
401
- }
402
-
403
- export function formatSyncedSummary(candidate: Candidate): string {
404
- const version = candidate.matchVersion ?? candidate.latestVersion
405
- return version ? `${candidate.slug}@${version}` : candidate.slug
406
- }
407
-
408
- export function formatBulletList(lines: string[], max: number): string {
409
- if (lines.length <= max) return lines.map((line) => `- ${line}`).join('\n')
410
- const head = lines.slice(0, max)
411
- const rest = lines.length - head.length
412
- return [...head, `... +${rest} more`].map((line) => `- ${line}`).join('\n')
413
- }
414
-
415
- export function formatSyncedDisplay(synced: Candidate[]) {
416
- const lines = synced.map(formatSyncedLine)
417
- if (lines.length <= 12) return formatBulletList(lines, 12)
418
- return formatCommaList(synced.map(formatSyncedSummary), 24)
419
- }
420
-
421
- export function formatCommaList(values: string[], max: number) {
422
- if (values.length === 0) return ''
423
- if (values.length <= max) return values.join(', ')
424
- const head = values.slice(0, Math.max(1, max - 1))
425
- const rest = values.length - head.length
426
- return `${head.join(', ')}, ... +${rest} more`
427
- }
@@ -1,27 +0,0 @@
1
- import type { SkillOrigin } from '../../skills.js'
2
- import type { SkillFolder } from '../scanSkills.js'
3
-
4
- export type SyncOptions = {
5
- root?: string[]
6
- all?: boolean
7
- dryRun?: boolean
8
- bump?: 'patch' | 'minor' | 'major'
9
- changelog?: string
10
- tags?: string
11
- concurrency?: number
12
- }
13
-
14
- export type Candidate = SkillFolder & {
15
- fingerprint: string
16
- fileCount: number
17
- origin: SkillOrigin | null
18
- status: 'synced' | 'new' | 'update'
19
- matchVersion: string | null
20
- latestVersion: string | null
21
- }
22
-
23
- export type LocalSkill = SkillFolder & {
24
- fingerprint: string
25
- fileCount: number
26
- origin: SkillOrigin | null
27
- }
@@ -1,48 +0,0 @@
1
- import { readGlobalConfig } from '../../config.js'
2
- import { apiRequest } from '../../http.js'
3
- import { ApiRoutes, ApiV1UnstarResponseSchema } from '../../schema/index.js'
4
- import { getRegistry } from '../registry.js'
5
- import type { GlobalOpts } from '../types.js'
6
- import { createSpinner, fail, formatError, isInteractive, promptConfirm } from '../ui.js'
7
-
8
- async function requireToken() {
9
- const cfg = await readGlobalConfig()
10
- const token = cfg?.token
11
- if (!token) fail('Not logged in. Run: pilothub login')
12
- return token
13
- }
14
-
15
- export async function cmdUnstarSkill(
16
- opts: GlobalOpts,
17
- slugArg: string,
18
- options: { yes?: boolean },
19
- inputAllowed: boolean,
20
- ) {
21
- const slug = slugArg.trim().toLowerCase()
22
- if (!slug) fail('Slug required')
23
- const allowPrompt = isInteractive() && inputAllowed !== false
24
-
25
- if (!options.yes) {
26
- if (!allowPrompt) fail('Pass --yes (no input)')
27
- const ok = await promptConfirm(`Unstar ${slug}?`)
28
- if (!ok) return
29
- }
30
-
31
- const token = await requireToken()
32
- const registry = await getRegistry(opts, { cache: true })
33
- const spinner = createSpinner(`Unstarring ${slug}`)
34
- try {
35
- const result = await apiRequest(
36
- registry,
37
- { method: 'DELETE', path: `${ApiRoutes.stars}/${encodeURIComponent(slug)}`, token },
38
- ApiV1UnstarResponseSchema,
39
- )
40
- spinner.succeed(
41
- result.alreadyUnstarred ? `OK. ${slug} already unstarred.` : `OK. Unstarred ${slug}`,
42
- )
43
- return result
44
- } catch (error) {
45
- spinner.fail(formatError(error))
46
- throw error
47
- }
48
- }
@@ -1,45 +0,0 @@
1
- type Color = (value: string) => string
2
-
3
- function wrap(start: string, end = '\x1b[0m'): Color {
4
- return (value) => `${start}${value}${end}`
5
- }
6
-
7
- const ansi = {
8
- reset: '\x1b[0m',
9
- bold: wrap('\x1b[1m'),
10
- dim: wrap('\x1b[2m'),
11
- cyan: wrap('\x1b[36m'),
12
- green: wrap('\x1b[32m'),
13
- yellow: wrap('\x1b[33m'),
14
- }
15
-
16
- function isColorEnabled() {
17
- if (!process.stdout.isTTY) return false
18
- if (process.env.NO_COLOR) return false
19
- return true
20
- }
21
-
22
- export function styleTitle(value: string) {
23
- if (!isColorEnabled()) return value
24
- return `${ansi.bold(ansi.cyan(value))}${ansi.reset}`
25
- }
26
-
27
- export function configureCommanderHelp(program: {
28
- configureHelp: (config: {
29
- sectionTitle?: (title: string) => string
30
- optionTerm?: (option: { flags: string }) => string
31
- commandTerm?: (cmd: { name: () => string }) => string
32
- }) => unknown
33
- }) {
34
- if (!isColorEnabled()) return
35
- program.configureHelp({
36
- sectionTitle: (title) => ansi.bold(ansi.cyan(title)),
37
- optionTerm: (option) => ansi.yellow(option.flags),
38
- commandTerm: (cmd) => ansi.green(cmd.name()),
39
- })
40
- }
41
-
42
- export function styleEnvBlock(value: string) {
43
- if (!isColorEnabled()) return value
44
- return `${ansi.dim(value)}${ansi.reset}`
45
- }
@@ -1,159 +0,0 @@
1
- /* @vitest-environment node */
2
- import { mkdir, mkdtemp, writeFile } from 'node:fs/promises'
3
- import { tmpdir } from 'node:os'
4
- import { join, resolve } from 'node:path'
5
- import { afterEach, describe, expect, it } from 'vitest'
6
- import { resolvePilotbotDefaultWorkspace, resolvePilotbotSkillRoots } from './pilotbotConfig.js'
7
-
8
- const originalEnv = { ...process.env }
9
-
10
- afterEach(() => {
11
- process.env = { ...originalEnv }
12
- })
13
-
14
- describe('resolvePilotbotSkillRoots', () => {
15
- it('reads JSON5 config and resolves per-agent + shared skill roots', async () => {
16
- const base = await mkdtemp(join(tmpdir(), 'pilothub-pilotbot-'))
17
- const home = join(base, 'home')
18
- const stateDir = join(base, 'state')
19
- const configPath = join(base, 'pilotbot.json')
20
-
21
- process.env.HOME = home
22
- process.env.PILOTBOT_STATE_DIR = stateDir
23
- process.env.PILOTBOT_CONFIG_PATH = configPath
24
-
25
- const config = `{
26
- // JSON5 comments + trailing commas supported
27
- agents: {
28
- defaults: { workspace: '~/pilot-main', },
29
- list: [
30
- { id: 'work', name: 'Work Bot', workspace: '~/pilot-work', },
31
- { id: 'family', workspace: '~/pilot-family', },
32
- ],
33
- },
34
- // legacy entries still supported
35
- agent: { workspace: '~/pilot-legacy', },
36
- routing: {
37
- agents: {
38
- work: { name: 'Work Bot', workspace: '~/pilot-work', },
39
- family: { workspace: '~/pilot-family' },
40
- },
41
- },
42
- skills: {
43
- load: { extraDirs: ['~/shared/skills', '/opt/skills',], },
44
- },
45
- }`
46
- await writeFile(configPath, config, 'utf8')
47
-
48
- const { roots, labels } = await resolvePilotbotSkillRoots()
49
-
50
- const expectedRoots = [
51
- resolve(stateDir, 'skills'),
52
- resolve(home, 'pilot-main', 'skills'),
53
- resolve(home, 'pilot-work', 'skills'),
54
- resolve(home, 'pilot-family', 'skills'),
55
- resolve(home, 'shared', 'skills'),
56
- resolve('/opt/skills'),
57
- ]
58
-
59
- expect(roots).toEqual(expect.arrayContaining(expectedRoots))
60
- expect(labels[resolve(stateDir, 'skills')]).toBe('Shared skills')
61
- expect(labels[resolve(home, 'pilot-main', 'skills')]).toBe('Agent: main')
62
- expect(labels[resolve(home, 'pilot-work', 'skills')]).toBe('Agent: Work Bot')
63
- expect(labels[resolve(home, 'pilot-family', 'skills')]).toBe('Agent: family')
64
- expect(labels[resolve(home, 'shared', 'skills')]).toBe('Extra: skills')
65
- expect(labels[resolve('/opt/skills')]).toBe('Extra: skills')
66
- })
67
-
68
- it('resolves default workspace from agents.defaults and agents.list', async () => {
69
- const base = await mkdtemp(join(tmpdir(), 'pilothub-pilotbot-default-'))
70
- const home = join(base, 'home')
71
- const stateDir = join(base, 'state')
72
- const configPath = join(base, 'pilotbot.json')
73
- const workspaceMain = join(base, 'workspace-main')
74
- const workspaceList = join(base, 'workspace-list')
75
-
76
- process.env.HOME = home
77
- process.env.PILOTBOT_STATE_DIR = stateDir
78
- process.env.PILOTBOT_CONFIG_PATH = configPath
79
-
80
- const config = `{
81
- agents: {
82
- defaults: { workspace: "${workspaceMain}", },
83
- list: [
84
- { id: 'main', workspace: "${workspaceList}", default: true },
85
- ],
86
- },
87
- }`
88
- await writeFile(configPath, config, 'utf8')
89
-
90
- const workspace = await resolvePilotbotDefaultWorkspace()
91
- expect(workspace).toBe(resolve(workspaceMain))
92
- })
93
-
94
- it('falls back to default agent in agents.list when defaults missing', async () => {
95
- const base = await mkdtemp(join(tmpdir(), 'pilothub-pilotbot-list-'))
96
- const home = join(base, 'home')
97
- const configPath = join(base, 'pilotbot.json')
98
- const workspaceMain = join(base, 'workspace-main')
99
- const workspaceWork = join(base, 'workspace-work')
100
-
101
- process.env.HOME = home
102
- process.env.PILOTBOT_CONFIG_PATH = configPath
103
-
104
- const config = `{
105
- agents: {
106
- list: [
107
- { id: 'main', workspace: "${workspaceMain}", default: true },
108
- { id: 'work', workspace: "${workspaceWork}" },
109
- ],
110
- },
111
- }`
112
- await writeFile(configPath, config, 'utf8')
113
-
114
- const workspace = await resolvePilotbotDefaultWorkspace()
115
- expect(workspace).toBe(resolve(workspaceMain))
116
- })
117
-
118
- it('respects PILOTBOT_STATE_DIR and PILOTBOT_CONFIG_PATH overrides', async () => {
119
- const base = await mkdtemp(join(tmpdir(), 'pilothub-pilotbot-override-'))
120
- const home = join(base, 'home')
121
- const stateDir = join(base, 'custom-state')
122
- const configPath = join(base, 'config', 'pilotbot.json')
123
-
124
- process.env.HOME = home
125
- process.env.PILOTBOT_STATE_DIR = stateDir
126
- process.env.PILOTBOT_CONFIG_PATH = configPath
127
-
128
- const config = `{
129
- agent: { workspace: "${join(base, 'workspace-main')}" },
130
- }`
131
- await mkdir(join(base, 'config'), { recursive: true })
132
- await writeFile(configPath, config, 'utf8')
133
-
134
- const { roots, labels } = await resolvePilotbotSkillRoots()
135
-
136
- expect(roots).toEqual(
137
- expect.arrayContaining([
138
- resolve(stateDir, 'skills'),
139
- resolve(join(base, 'workspace-main'), 'skills'),
140
- ]),
141
- )
142
- expect(labels[resolve(stateDir, 'skills')]).toBe('Shared skills')
143
- expect(labels[resolve(join(base, 'workspace-main'), 'skills')]).toBe('Agent: main')
144
- })
145
-
146
- it('returns shared skills root when config is missing', async () => {
147
- const base = await mkdtemp(join(tmpdir(), 'pilothub-pilotbot-missing-'))
148
- const stateDir = join(base, 'state')
149
- const configPath = join(base, 'missing', 'pilotbot.json')
150
-
151
- process.env.PILOTBOT_STATE_DIR = stateDir
152
- process.env.PILOTBOT_CONFIG_PATH = configPath
153
-
154
- const { roots, labels } = await resolvePilotbotSkillRoots()
155
-
156
- expect(roots).toEqual([resolve(stateDir, 'skills')])
157
- expect(labels[resolve(stateDir, 'skills')]).toBe('Shared skills')
158
- })
159
- })