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,209 +0,0 @@
1
- import { FONT_MONO, FONT_SANS } from './ogAssets'
2
-
3
- export type SoulOgSvgParams = {
4
- markDataUrl: string
5
- title: string
6
- description: string
7
- ownerLabel: string
8
- versionLabel: string
9
- footer: string
10
- }
11
-
12
- function escapeXml(value: string) {
13
- return value
14
- .replace(/&/g, '&')
15
- .replace(/</g, '&lt;')
16
- .replace(/>/g, '&gt;')
17
- .replace(/"/g, '&quot;')
18
- .replace(/'/g, '&#39;')
19
- }
20
-
21
- function wrapText(value: string, maxChars: number, maxLines: number) {
22
- const words = value.trim().split(/\s+/).filter(Boolean)
23
- const lines: string[] = []
24
- let current = ''
25
-
26
- function pushLine(line: string) {
27
- if (!line) return
28
- lines.push(line)
29
- }
30
-
31
- function splitLongWord(word: string) {
32
- if (word.length <= maxChars) return [word]
33
- const parts: string[] = []
34
- let remaining = word
35
- while (remaining.length > maxChars) {
36
- parts.push(`${remaining.slice(0, maxChars - 1)}…`)
37
- remaining = remaining.slice(maxChars - 1)
38
- }
39
- if (remaining) parts.push(remaining)
40
- return parts
41
- }
42
-
43
- for (const word of words) {
44
- if (word.length > maxChars) {
45
- if (current) {
46
- pushLine(current)
47
- current = ''
48
- if (lines.length >= maxLines - 1) break
49
- }
50
- const parts = splitLongWord(word)
51
- for (const part of parts) {
52
- pushLine(part)
53
- if (lines.length >= maxLines) break
54
- }
55
- current = ''
56
- if (lines.length >= maxLines - 1) break
57
- continue
58
- }
59
-
60
- const next = current ? `${current} ${word}` : word
61
- if (next.length <= maxChars) {
62
- current = next
63
- continue
64
- }
65
- pushLine(current)
66
- current = word
67
- if (lines.length >= maxLines - 1) break
68
- }
69
- if (lines.length < maxLines && current) pushLine(current)
70
- if (lines.length > maxLines) lines.length = maxLines
71
-
72
- const usedWords = lines.join(' ').split(/\s+/).filter(Boolean).length
73
- if (usedWords < words.length) {
74
- const last = lines.at(-1) ?? ''
75
- const trimmed = last.length > maxChars ? last.slice(0, maxChars) : last
76
- lines[lines.length - 1] = `${trimmed.replace(/\s+$/g, '').replace(/[.。,;:!?]+$/g, '')}…`
77
- }
78
- return lines
79
- }
80
-
81
- export function buildSoulOgSvg(params: SoulOgSvgParams) {
82
- const rawTitle = params.title.trim() || 'SoulHub'
83
- const rawDescription = params.description.trim() || 'SOUL.md bundle on SoulHub.'
84
-
85
- const cardX = 72
86
- const cardY = 96
87
- const cardW = 640
88
- const cardH = 456
89
- const cardR = 34
90
-
91
- const titleLines = wrapText(rawTitle, 22, 2)
92
- const descLines = wrapText(rawDescription, 42, 3)
93
-
94
- const titleFontSize = titleLines.length > 1 || rawTitle.length > 24 ? 72 : 80
95
- const titleY = titleLines.length > 1 ? 258 : 280
96
- const titleLineHeight = 84
97
-
98
- const descY = titleLines.length > 1 ? 395 : 380
99
- const descLineHeight = 34
100
-
101
- const pillText = `${params.ownerLabel} • ${params.versionLabel}`
102
- const footerY = cardY + cardH - 18
103
-
104
- const titleTspans = titleLines
105
- .map((line, index) => {
106
- const dy = index === 0 ? 0 : titleLineHeight
107
- return `<tspan x="114" dy="${dy}">${escapeXml(line)}</tspan>`
108
- })
109
- .join('')
110
-
111
- const descTspans = descLines
112
- .map((line, index) => {
113
- const dy = index === 0 ? 0 : descLineHeight
114
- return `<tspan x="114" dy="${dy}">${escapeXml(line)}</tspan>`
115
- })
116
- .join('')
117
-
118
- return `<?xml version="1.0" encoding="UTF-8"?>
119
- <svg width="1200" height="630" viewBox="0 0 1200 630" fill="none" xmlns="http://www.w3.org/2000/svg">
120
- <defs>
121
- <linearGradient id="bg" x1="0" y1="0" x2="1200" y2="630" gradientUnits="userSpaceOnUse">
122
- <stop stop-color="#0E1314"/>
123
- <stop offset="0.55" stop-color="#142021"/>
124
- <stop offset="1" stop-color="#0E1314"/>
125
- </linearGradient>
126
-
127
- <radialGradient id="glowGold" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(300 80) rotate(120) scale(520 420)">
128
- <stop stop-color="#E7B96B" stop-opacity="0.45"/>
129
- <stop offset="1" stop-color="#E7B96B" stop-opacity="0"/>
130
- </radialGradient>
131
-
132
- <radialGradient id="glowTeal" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(1040 140) rotate(140) scale(520 420)">
133
- <stop stop-color="#6AD6C4" stop-opacity="0.35"/>
134
- <stop offset="1" stop-color="#6AD6C4" stop-opacity="0"/>
135
- </radialGradient>
136
-
137
- <filter id="softBlur" x="-40%" y="-40%" width="180%" height="180%">
138
- <feGaussianBlur stdDeviation="24"/>
139
- </filter>
140
-
141
- <filter id="cardShadow" x="-20%" y="-20%" width="140%" height="140%">
142
- <feDropShadow dx="0" dy="18" stdDeviation="26" flood-color="#000000" flood-opacity="0.6"/>
143
- </filter>
144
-
145
- <linearGradient id="pill" x1="0" y1="0" x2="520" y2="0" gradientUnits="userSpaceOnUse">
146
- <stop stop-color="#E7B96B" stop-opacity="0.26"/>
147
- <stop offset="1" stop-color="#E7B96B" stop-opacity="0.12"/>
148
- </linearGradient>
149
-
150
- <linearGradient id="stroke" x1="0" y1="0" x2="0" y2="1">
151
- <stop stop-color="#FFFFFF" stop-opacity="0.18"/>
152
- <stop offset="1" stop-color="#FFFFFF" stop-opacity="0.08"/>
153
- </linearGradient>
154
-
155
- <clipPath id="cardClip">
156
- <rect x="${cardX}" y="${cardY}" width="${cardW}" height="${cardH}" rx="${cardR}"/>
157
- </clipPath>
158
- </defs>
159
-
160
- <rect width="1200" height="630" fill="url(#bg)"/>
161
- <circle cx="300" cy="80" r="520" fill="url(#glowGold)" filter="url(#softBlur)"/>
162
- <circle cx="1040" cy="140" r="520" fill="url(#glowTeal)" filter="url(#softBlur)"/>
163
-
164
- <g opacity="0.12">
165
- <path d="M0 90 C180 130 360 50 540 96 C720 142 840 220 1200 170" stroke="#FFFFFF" stroke-opacity="0.12" stroke-width="2"/>
166
- <path d="M0 190 C240 250 400 170 600 214 C800 258 960 330 1200 300" stroke="#FFFFFF" stroke-opacity="0.1" stroke-width="2"/>
167
- <path d="M0 450 C240 390 460 520 660 470 C860 420 1000 500 1200 460" stroke="#FFFFFF" stroke-opacity="0.08" stroke-width="2"/>
168
- </g>
169
-
170
- <g opacity="0.24" filter="url(#softBlur)">
171
- <image href="${params.markDataUrl}" x="740" y="70" width="560" height="560" preserveAspectRatio="xMidYMid meet"/>
172
- </g>
173
-
174
- <g filter="url(#cardShadow)">
175
- <rect x="${cardX}" y="${cardY}" width="${cardW}" height="${cardH}" rx="${cardR}" fill="#1B201F" fill-opacity="0.92" stroke="url(#stroke)"/>
176
- </g>
177
-
178
- <g clip-path="url(#cardClip)">
179
- <image href="${params.markDataUrl}" x="108" y="134" width="46" height="46" preserveAspectRatio="xMidYMid meet"/>
180
-
181
- <g>
182
- <rect x="166" y="136" width="520" height="42" rx="21" fill="url(#pill)" stroke="#E7B96B" stroke-opacity="0.3"/>
183
- <text x="186" y="163"
184
- fill="#F7F1E8"
185
- font-size="18"
186
- font-weight="600"
187
- font-family="${FONT_SANS}, sans-serif"
188
- opacity="0.92">${escapeXml(pillText)}</text>
189
- </g>
190
-
191
- <text x="114" y="${titleY}"
192
- fill="#F7F1E8"
193
- font-size="${titleFontSize}"
194
- font-weight="800"
195
- font-family="${FONT_SANS}, sans-serif">${titleTspans}</text>
196
-
197
- <text x="114" y="${descY}"
198
- fill="#C7BFB5"
199
- font-size="26"
200
- font-weight="500"
201
- font-family="${FONT_SANS}, sans-serif">${descTspans}</text>
202
-
203
- <text x="114" y="${footerY}"
204
- fill="#B7B0A6"
205
- font-size="18"
206
- font-family="${FONT_MONO}, monospace">${escapeXml(params.footer)}</text>
207
- </g>
208
- </svg>`
209
- }
@@ -1,103 +0,0 @@
1
- import { initWasm, Resvg } from '@resvg/resvg-wasm'
2
- import { defineEventHandler, getQuery, getRequestHost, setHeader } from 'h3'
3
-
4
- import { fetchSkillOgMeta } from '../../og/fetchSkillOgMeta'
5
- import {
6
- FONT_MONO,
7
- FONT_SANS,
8
- getFontBuffers,
9
- getMarkDataUrl,
10
- getResvgWasm,
11
- } from '../../og/ogAssets'
12
- import { buildSkillOgSvg } from '../../og/skillOgSvg'
13
-
14
- type OgQuery = {
15
- slug?: string
16
- owner?: string
17
- version?: string
18
- title?: string
19
- description?: string
20
- v?: string
21
- }
22
-
23
- let wasmInitPromise: Promise<void> | null = null
24
-
25
- function cleanString(value: unknown) {
26
- if (typeof value !== 'string') return ''
27
- return value.trim()
28
- }
29
-
30
- function getApiBase(eventHost: string | null) {
31
- const direct = process.env.VITE_CONVEX_SITE_URL?.trim()
32
- if (direct) return direct
33
-
34
- const site = process.env.SITE_URL?.trim() || process.env.VITE_SITE_URL?.trim()
35
- if (site) return site
36
-
37
- if (eventHost) return `https://${eventHost}`
38
- return 'https://pilothub.com'
39
- }
40
-
41
- async function ensureWasm() {
42
- if (!wasmInitPromise) {
43
- wasmInitPromise = getResvgWasm().then((wasm) => initWasm(wasm))
44
- }
45
- await wasmInitPromise
46
- }
47
-
48
- export default defineEventHandler(async (event) => {
49
- const query = getQuery(event) as OgQuery
50
- const slug = cleanString(query.slug)
51
- if (!slug) {
52
- setHeader(event, 'Content-Type', 'text/plain; charset=utf-8')
53
- return 'Missing `slug` query param.'
54
- }
55
-
56
- const ownerFromQuery = cleanString(query.owner)
57
- const versionFromQuery = cleanString(query.version)
58
- const titleFromQuery = cleanString(query.title)
59
- const descriptionFromQuery = cleanString(query.description)
60
-
61
- const needFetch = !titleFromQuery || !descriptionFromQuery || !ownerFromQuery || !versionFromQuery
62
- const meta = needFetch ? await fetchSkillOgMeta(slug, getApiBase(getRequestHost(event))) : null
63
-
64
- const owner = ownerFromQuery || meta?.owner || ''
65
- const version = versionFromQuery || meta?.version || ''
66
- const title = titleFromQuery || meta?.displayName || slug
67
- const description = descriptionFromQuery || meta?.summary || ''
68
-
69
- const ownerLabel = owner ? `@${owner}` : 'pilothub'
70
- const versionLabel = version ? `v${version}` : 'latest'
71
- const footer = owner ? `pilothub.com/${owner}/${slug}` : `pilothub.com/skills/${slug}`
72
-
73
- const cacheKey = version ? 'public, max-age=31536000, immutable' : 'public, max-age=3600'
74
- setHeader(event, 'Cache-Control', cacheKey)
75
- setHeader(event, 'Content-Type', 'image/png')
76
-
77
- const [markDataUrl, fontBuffers] = await Promise.all([
78
- getMarkDataUrl(),
79
- ensureWasm().then(() => getFontBuffers()),
80
- ])
81
-
82
- const svg = buildSkillOgSvg({
83
- markDataUrl,
84
- title,
85
- description,
86
- ownerLabel,
87
- versionLabel,
88
- footer,
89
- })
90
-
91
- const resvg = new Resvg(svg, {
92
- fitTo: { mode: 'width', value: 1200 },
93
- font: {
94
- fontBuffers,
95
- defaultFontFamily: FONT_SANS,
96
- sansSerifFamily: FONT_SANS,
97
- monospaceFamily: FONT_MONO,
98
- },
99
- })
100
- const png = resvg.render().asPng()
101
- resvg.free()
102
- return png
103
- })
@@ -1,111 +0,0 @@
1
- import { initWasm, Resvg } from '@resvg/resvg-wasm'
2
- import { defineEventHandler, getQuery, getRequestHost, setHeader } from 'h3'
3
-
4
- import type { SoulOgMeta } from '../../og/fetchSoulOgMeta'
5
- import { fetchSoulOgMeta } from '../../og/fetchSoulOgMeta'
6
- import {
7
- FONT_MONO,
8
- FONT_SANS,
9
- getFontBuffers,
10
- getMarkDataUrl,
11
- getResvgWasm,
12
- } from '../../og/ogAssets'
13
- import { buildSoulOgSvg } from '../../og/soulOgSvg'
14
-
15
- type OgQuery = {
16
- slug?: string
17
- owner?: string
18
- version?: string
19
- title?: string
20
- description?: string
21
- v?: string
22
- }
23
-
24
- let wasmInitPromise: Promise<void> | null = null
25
-
26
- function cleanString(value: unknown) {
27
- if (typeof value !== 'string') return ''
28
- return value.trim()
29
- }
30
-
31
- function getApiBase(eventHost: string | null) {
32
- const direct = process.env.VITE_CONVEX_SITE_URL?.trim()
33
- if (direct) return direct
34
-
35
- const site = process.env.SITE_URL?.trim() || process.env.VITE_SITE_URL?.trim()
36
- if (site) return site
37
-
38
- if (eventHost) return `https://${eventHost}`
39
- return 'https://onlycrabs.ai'
40
- }
41
-
42
- async function ensureWasm() {
43
- if (!wasmInitPromise) {
44
- wasmInitPromise = getResvgWasm().then((wasm) => initWasm(wasm))
45
- }
46
- await wasmInitPromise
47
- }
48
-
49
- function buildFooter(slug: string, owner: string | null) {
50
- if (owner) return `@${owner}/${slug}`
51
- return `souls/${slug}`
52
- }
53
-
54
- export default defineEventHandler(async (event) => {
55
- const query = getQuery(event) as OgQuery
56
- const slug = cleanString(query.slug)
57
- if (!slug) {
58
- setHeader(event, 'Content-Type', 'text/plain; charset=utf-8')
59
- return 'Missing `slug` query param.'
60
- }
61
-
62
- const ownerFromQuery = cleanString(query.owner)
63
- const versionFromQuery = cleanString(query.version)
64
- const titleFromQuery = cleanString(query.title)
65
- const descriptionFromQuery = cleanString(query.description)
66
-
67
- const needFetch = !titleFromQuery || !descriptionFromQuery || !ownerFromQuery || !versionFromQuery
68
- const meta: SoulOgMeta | null = needFetch
69
- ? await fetchSoulOgMeta(slug, getApiBase(getRequestHost(event)))
70
- : null
71
-
72
- const owner = ownerFromQuery || meta?.owner || ''
73
- const version = versionFromQuery || meta?.version || ''
74
- const title = titleFromQuery || meta?.displayName || slug
75
- const description = descriptionFromQuery || meta?.summary || ''
76
-
77
- const ownerLabel = owner ? `@${owner}` : 'SoulHub'
78
- const versionLabel = version ? `v${version}` : 'latest'
79
- const footer = buildFooter(slug, owner || null)
80
-
81
- const cacheKey = version ? 'public, max-age=31536000, immutable' : 'public, max-age=3600'
82
- setHeader(event, 'Cache-Control', cacheKey)
83
- setHeader(event, 'Content-Type', 'image/png')
84
-
85
- const [markDataUrl, fontBuffers] = await Promise.all([
86
- getMarkDataUrl(),
87
- ensureWasm().then(() => getFontBuffers()),
88
- ])
89
-
90
- const svg = buildSoulOgSvg({
91
- markDataUrl,
92
- title,
93
- description,
94
- ownerLabel,
95
- versionLabel,
96
- footer,
97
- })
98
-
99
- const resvg = new Resvg(svg, {
100
- fitTo: { mode: 'width', value: 1200 },
101
- font: {
102
- fontBuffers,
103
- defaultFontFamily: FONT_SANS,
104
- sansSerifFamily: FONT_SANS,
105
- monospaceFamily: FONT_MONO,
106
- },
107
- })
108
- const png = resvg.render().asPng()
109
- resvg.free()
110
- return png
111
- })
@@ -1,86 +0,0 @@
1
- import { render, screen, waitFor } from '@testing-library/react'
2
- import { vi } from 'vitest'
3
-
4
- import { SkillDetailPage } from '../components/SkillDetailPage'
5
-
6
- const navigateMock = vi.fn()
7
- const useAuthStatusMock = vi.fn()
8
-
9
- vi.mock('@tanstack/react-router', () => ({
10
- useNavigate: () => navigateMock,
11
- }))
12
-
13
- const useQueryMock = vi.fn()
14
- const getReadmeMock = vi.fn()
15
-
16
- vi.mock('convex/react', () => ({
17
- useQuery: (...args: unknown[]) => useQueryMock(...args),
18
- useMutation: () => vi.fn(),
19
- useAction: () => getReadmeMock,
20
- }))
21
-
22
- vi.mock('../lib/useAuthStatus', () => ({
23
- useAuthStatus: () => useAuthStatusMock(),
24
- }))
25
-
26
- describe('SkillDetailPage', () => {
27
- beforeEach(() => {
28
- useQueryMock.mockReset()
29
- getReadmeMock.mockReset()
30
- navigateMock.mockReset()
31
- useAuthStatusMock.mockReset()
32
- getReadmeMock.mockResolvedValue({ text: '' })
33
- useAuthStatusMock.mockReturnValue({
34
- isAuthenticated: false,
35
- isLoading: false,
36
- me: null,
37
- })
38
- useQueryMock.mockImplementation((_fn: unknown, args: unknown) => {
39
- if (args === 'skip') return undefined
40
- return undefined
41
- })
42
- })
43
-
44
- it('shows a loading indicator while loading', () => {
45
- useQueryMock.mockImplementationOnce(() => undefined) // getBySlug
46
-
47
- render(<SkillDetailPage slug="weather" />)
48
- expect(screen.getByText(/Loading skill/i)).toBeTruthy()
49
- expect(screen.queryByText(/Skill not found/i)).toBeNull()
50
- })
51
-
52
- it('shows not found when skill query resolves to null', async () => {
53
- useQueryMock.mockImplementationOnce(() => null) // getBySlug
54
-
55
- render(<SkillDetailPage slug="missing-skill" />)
56
- expect(await screen.findByText(/Skill not found/i)).toBeTruthy()
57
- })
58
-
59
- it('redirects legacy routes to canonical owner/slug', async () => {
60
- useQueryMock.mockImplementationOnce(() => ({
61
- skill: {
62
- _id: 'skills:1',
63
- slug: 'weather',
64
- displayName: 'Weather',
65
- summary: 'Get current weather.',
66
- ownerUserId: 'users:1',
67
- tags: {},
68
- stats: { stars: 0, downloads: 0 },
69
- },
70
- owner: { handle: 'steipete', name: 'Peter' },
71
- latestVersion: { _id: 'skillVersions:1', version: '1.0.0', parsed: {} },
72
- }))
73
-
74
- render(<SkillDetailPage slug="weather" redirectToCanonical />)
75
- expect(screen.getByText(/Loading skill/i)).toBeTruthy()
76
-
77
- await waitFor(() => {
78
- expect(navigateMock).toHaveBeenCalled()
79
- })
80
- expect(navigateMock).toHaveBeenCalledWith({
81
- to: '/$owner/$slug',
82
- params: { owner: 'steipete', slug: 'weather' },
83
- replace: true,
84
- })
85
- })
86
- })
@@ -1,145 +0,0 @@
1
- /* @vitest-environment jsdom */
2
- import { act, fireEvent, render, screen } from '@testing-library/react'
3
- import type { ReactNode } from 'react'
4
- import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
5
-
6
- import { SkillsIndex } from '../routes/skills/index'
7
-
8
- const navigateMock = vi.fn()
9
- const useActionMock = vi.fn()
10
- const usePaginatedQueryMock = vi.fn()
11
- let searchMock: Record<string, unknown> = {}
12
-
13
- vi.mock('@tanstack/react-router', () => ({
14
- createFileRoute: () => (_config: { component: unknown; validateSearch: unknown }) => ({
15
- useNavigate: () => navigateMock,
16
- useSearch: () => searchMock,
17
- }),
18
- Link: (props: { children: ReactNode }) => <a href="/">{props.children}</a>,
19
- }))
20
-
21
- vi.mock('convex/react', () => ({
22
- useAction: (...args: unknown[]) => useActionMock(...args),
23
- }))
24
-
25
- vi.mock('convex-helpers/react', () => ({
26
- usePaginatedQuery: (...args: unknown[]) => usePaginatedQueryMock(...args),
27
- }))
28
-
29
- describe('SkillsIndex', () => {
30
- beforeEach(() => {
31
- usePaginatedQueryMock.mockReset()
32
- useActionMock.mockReset()
33
- navigateMock.mockReset()
34
- searchMock = {}
35
- useActionMock.mockReturnValue(() => Promise.resolve([]))
36
- // Default: return empty results with Exhausted status
37
- usePaginatedQueryMock.mockReturnValue({
38
- results: [],
39
- status: 'Exhausted',
40
- loadMore: vi.fn(),
41
- })
42
- })
43
-
44
- afterEach(() => {
45
- vi.useRealTimers()
46
- vi.unstubAllGlobals()
47
- })
48
-
49
- it('requests the first skills page', () => {
50
- render(<SkillsIndex />)
51
- // usePaginatedQuery should be called with the API endpoint and empty args
52
- expect(usePaginatedQueryMock).toHaveBeenCalledWith(
53
- expect.anything(),
54
- {},
55
- { initialNumItems: 25 },
56
- )
57
- })
58
-
59
- it('renders an empty state when no skills are returned', () => {
60
- render(<SkillsIndex />)
61
- expect(screen.getByText('No skills match that filter.')).toBeTruthy()
62
- })
63
-
64
- it('skips list query and calls search when query is set', async () => {
65
- searchMock = { q: 'remind' }
66
- const actionFn = vi.fn().mockResolvedValue([])
67
- useActionMock.mockReturnValue(actionFn)
68
- vi.useFakeTimers()
69
-
70
- render(<SkillsIndex />)
71
-
72
- // usePaginatedQuery should be called with 'skip' when there's a search query
73
- expect(usePaginatedQueryMock).toHaveBeenCalledWith(expect.anything(), 'skip', {
74
- initialNumItems: 25,
75
- })
76
- await act(async () => {
77
- await vi.runAllTimersAsync()
78
- })
79
- expect(actionFn).toHaveBeenCalledWith({
80
- query: 'remind',
81
- highlightedOnly: false,
82
- limit: 25,
83
- })
84
- await act(async () => {
85
- await vi.runAllTimersAsync()
86
- })
87
- expect(actionFn).toHaveBeenCalledWith({
88
- query: 'remind',
89
- highlightedOnly: false,
90
- limit: 25,
91
- })
92
- })
93
-
94
- it('loads more results when search pagination is requested', async () => {
95
- searchMock = { q: 'remind' }
96
- vi.stubGlobal('IntersectionObserver', undefined)
97
- const actionFn = vi
98
- .fn()
99
- .mockResolvedValueOnce(makeSearchResults(25))
100
- .mockResolvedValueOnce(makeSearchResults(50))
101
- useActionMock.mockReturnValue(actionFn)
102
- vi.useFakeTimers()
103
-
104
- render(<SkillsIndex />)
105
- await act(async () => {
106
- await vi.runAllTimersAsync()
107
- })
108
-
109
- const loadMoreButton = screen.getByRole('button', { name: 'Load more' })
110
- await act(async () => {
111
- fireEvent.click(loadMoreButton)
112
- await vi.runAllTimersAsync()
113
- })
114
-
115
- expect(actionFn).toHaveBeenLastCalledWith({
116
- query: 'remind',
117
- highlightedOnly: false,
118
- limit: 50,
119
- })
120
- })
121
- })
122
-
123
- function makeSearchResults(count: number) {
124
- return Array.from({ length: count }, (_, index) => ({
125
- score: 0.9,
126
- skill: {
127
- _id: `skill_${index}`,
128
- slug: `skill-${index}`,
129
- displayName: `Skill ${index}`,
130
- summary: `Summary ${index}`,
131
- tags: {},
132
- stats: {
133
- downloads: 0,
134
- installsCurrent: 0,
135
- installsAllTime: 0,
136
- stars: 0,
137
- versions: 1,
138
- comments: 0,
139
- },
140
- createdAt: 0,
141
- updatedAt: 0,
142
- },
143
- version: null,
144
- }))
145
- }