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,170 +0,0 @@
1
- import { v } from 'convex/values'
2
- import { internal } from './_generated/api'
3
- import type { Doc, Id } from './_generated/dataModel'
4
- import { action, internalMutation, internalQuery } from './_generated/server'
5
- import { assertRole, requireUserFromAction } from './lib/access'
6
-
7
- const DEFAULT_BATCH_SIZE = 50
8
- const MAX_BATCH_SIZE = 200
9
- const SYNC_STATE_KEY = 'default'
10
-
11
- type BackupPageItem =
12
- | {
13
- kind: 'ok'
14
- skillId: Id<'skills'>
15
- versionId: Id<'skillVersions'>
16
- slug: string
17
- displayName: string
18
- version: string
19
- ownerHandle: string
20
- files: Doc<'skillVersions'>['files']
21
- publishedAt: number
22
- }
23
- | { kind: 'missingLatestVersion'; skillId: Id<'skills'> }
24
- | { kind: 'missingVersionDoc'; skillId: Id<'skills'>; versionId: Id<'skillVersions'> }
25
- | { kind: 'missingOwner'; skillId: Id<'skills'>; ownerUserId: Id<'users'> }
26
-
27
- type BackupPageResult = {
28
- items: BackupPageItem[]
29
- cursor: string | null
30
- isDone: boolean
31
- }
32
-
33
- type BackupSyncState = {
34
- cursor: string | null
35
- }
36
-
37
- export type SyncGitHubBackupsResult = {
38
- stats: {
39
- skillsScanned: number
40
- skillsSkipped: number
41
- skillsBackedUp: number
42
- skillsMissingVersion: number
43
- skillsMissingOwner: number
44
- errors: number
45
- }
46
- cursor: string | null
47
- isDone: boolean
48
- }
49
-
50
- export const getGitHubBackupPageInternal = internalQuery({
51
- args: {
52
- cursor: v.optional(v.string()),
53
- batchSize: v.optional(v.number()),
54
- },
55
- handler: async (ctx, args): Promise<BackupPageResult> => {
56
- const batchSize = clampInt(args.batchSize ?? DEFAULT_BATCH_SIZE, 1, MAX_BATCH_SIZE)
57
- const { page, isDone, continueCursor } = await ctx.db
58
- .query('skills')
59
- .order('asc')
60
- .paginate({ cursor: args.cursor ?? null, numItems: batchSize })
61
-
62
- const items: BackupPageItem[] = []
63
- for (const skill of page) {
64
- if (skill.softDeletedAt) continue
65
- if (!skill.latestVersionId) {
66
- items.push({ kind: 'missingLatestVersion', skillId: skill._id })
67
- continue
68
- }
69
-
70
- const version = await ctx.db.get(skill.latestVersionId)
71
- if (!version) {
72
- items.push({
73
- kind: 'missingVersionDoc',
74
- skillId: skill._id,
75
- versionId: skill.latestVersionId,
76
- })
77
- continue
78
- }
79
-
80
- const owner = await ctx.db.get(skill.ownerUserId)
81
- if (!owner || owner.deletedAt) {
82
- items.push({ kind: 'missingOwner', skillId: skill._id, ownerUserId: skill.ownerUserId })
83
- continue
84
- }
85
-
86
- items.push({
87
- kind: 'ok',
88
- skillId: skill._id,
89
- versionId: version._id,
90
- slug: skill.slug,
91
- displayName: skill.displayName,
92
- version: version.version,
93
- ownerHandle: owner.handle ?? owner._id,
94
- files: version.files,
95
- publishedAt: version.createdAt,
96
- })
97
- }
98
-
99
- return { items, cursor: continueCursor, isDone }
100
- },
101
- })
102
-
103
- export const getGitHubBackupSyncStateInternal = internalQuery({
104
- args: {},
105
- handler: async (ctx): Promise<BackupSyncState> => {
106
- const state = await ctx.db
107
- .query('githubBackupSyncState')
108
- .withIndex('by_key', (q) => q.eq('key', SYNC_STATE_KEY))
109
- .unique()
110
- return { cursor: state?.cursor ?? null }
111
- },
112
- })
113
-
114
- export const setGitHubBackupSyncStateInternal = internalMutation({
115
- args: {
116
- cursor: v.optional(v.string()),
117
- },
118
- handler: async (ctx, args) => {
119
- const now = Date.now()
120
- const state = await ctx.db
121
- .query('githubBackupSyncState')
122
- .withIndex('by_key', (q) => q.eq('key', SYNC_STATE_KEY))
123
- .unique()
124
-
125
- if (!state) {
126
- await ctx.db.insert('githubBackupSyncState', {
127
- key: SYNC_STATE_KEY,
128
- cursor: args.cursor,
129
- updatedAt: now,
130
- })
131
- return { ok: true as const }
132
- }
133
-
134
- await ctx.db.patch(state._id, {
135
- cursor: args.cursor,
136
- updatedAt: now,
137
- })
138
-
139
- return { ok: true as const }
140
- },
141
- })
142
-
143
- export const syncGitHubBackups: ReturnType<typeof action> = action({
144
- args: {
145
- dryRun: v.optional(v.boolean()),
146
- batchSize: v.optional(v.number()),
147
- maxBatches: v.optional(v.number()),
148
- resetCursor: v.optional(v.boolean()),
149
- },
150
- handler: async (ctx, args): Promise<SyncGitHubBackupsResult> => {
151
- const { user } = await requireUserFromAction(ctx)
152
- assertRole(user, ['admin'])
153
-
154
- if (args.resetCursor && !args.dryRun) {
155
- await ctx.runMutation(internal.githubBackups.setGitHubBackupSyncStateInternal, {
156
- cursor: undefined,
157
- })
158
- }
159
-
160
- return ctx.runAction(internal.githubBackupsNode.syncGitHubBackupsInternal, {
161
- dryRun: args.dryRun,
162
- batchSize: args.batchSize,
163
- maxBatches: args.maxBatches,
164
- }) as Promise<SyncGitHubBackupsResult>
165
- },
166
- })
167
-
168
- function clampInt(value: number, min: number, max: number) {
169
- return Math.max(min, Math.min(max, Math.floor(value)))
170
- }
@@ -1,183 +0,0 @@
1
- 'use node'
2
-
3
- import { v } from 'convex/values'
4
- import { internal } from './_generated/api'
5
- import type { Doc } from './_generated/dataModel'
6
- import type { ActionCtx } from './_generated/server'
7
- import { internalAction } from './_generated/server'
8
- import {
9
- backupSkillToGitHub,
10
- fetchGitHubSkillMeta,
11
- getGitHubBackupContext,
12
- isGitHubBackupConfigured,
13
- } from './lib/githubBackup'
14
-
15
- const DEFAULT_BATCH_SIZE = 50
16
- const MAX_BATCH_SIZE = 200
17
- const DEFAULT_MAX_BATCHES = 5
18
- const MAX_MAX_BATCHES = 200
19
-
20
- type BackupPageItem =
21
- | {
22
- kind: 'ok'
23
- slug: string
24
- version: string
25
- displayName: string
26
- ownerHandle: string
27
- files: Doc<'skillVersions'>['files']
28
- publishedAt: number
29
- }
30
- | { kind: 'missingLatestVersion' }
31
- | { kind: 'missingVersionDoc' }
32
- | { kind: 'missingOwner' }
33
-
34
- export type GitHubBackupSyncStats = {
35
- skillsScanned: number
36
- skillsSkipped: number
37
- skillsBackedUp: number
38
- skillsMissingVersion: number
39
- skillsMissingOwner: number
40
- errors: number
41
- }
42
-
43
- export type SyncGitHubBackupsInternalArgs = {
44
- dryRun?: boolean
45
- batchSize?: number
46
- maxBatches?: number
47
- }
48
-
49
- export type SyncGitHubBackupsInternalResult = {
50
- stats: GitHubBackupSyncStats
51
- cursor: string | null
52
- isDone: boolean
53
- }
54
-
55
- export const backupSkillForPublishInternal = internalAction({
56
- args: {
57
- slug: v.string(),
58
- version: v.string(),
59
- displayName: v.string(),
60
- ownerHandle: v.string(),
61
- files: v.array(
62
- v.object({
63
- path: v.string(),
64
- size: v.number(),
65
- storageId: v.id('_storage'),
66
- sha256: v.string(),
67
- contentType: v.optional(v.string()),
68
- }),
69
- ),
70
- publishedAt: v.number(),
71
- },
72
- handler: async (ctx, args) => {
73
- if (!isGitHubBackupConfigured()) {
74
- return { skipped: true as const }
75
- }
76
- await backupSkillToGitHub(ctx, args)
77
- return { skipped: false as const }
78
- },
79
- })
80
-
81
- export async function syncGitHubBackupsInternalHandler(
82
- ctx: ActionCtx,
83
- args: SyncGitHubBackupsInternalArgs,
84
- ): Promise<SyncGitHubBackupsInternalResult> {
85
- const dryRun = Boolean(args.dryRun)
86
- const stats: GitHubBackupSyncStats = {
87
- skillsScanned: 0,
88
- skillsSkipped: 0,
89
- skillsBackedUp: 0,
90
- skillsMissingVersion: 0,
91
- skillsMissingOwner: 0,
92
- errors: 0,
93
- }
94
-
95
- if (!isGitHubBackupConfigured()) {
96
- return { stats, cursor: null, isDone: true }
97
- }
98
-
99
- const batchSize = clampInt(args.batchSize ?? DEFAULT_BATCH_SIZE, 1, MAX_BATCH_SIZE)
100
- const maxBatches = clampInt(args.maxBatches ?? DEFAULT_MAX_BATCHES, 1, MAX_MAX_BATCHES)
101
- const context = await getGitHubBackupContext()
102
-
103
- const state = dryRun
104
- ? { cursor: null as string | null }
105
- : ((await ctx.runQuery(internal.githubBackups.getGitHubBackupSyncStateInternal, {})) as {
106
- cursor: string | null
107
- })
108
-
109
- let cursor: string | null = state.cursor
110
- let isDone = false
111
-
112
- for (let batch = 0; batch < maxBatches; batch++) {
113
- const page = (await ctx.runQuery(internal.githubBackups.getGitHubBackupPageInternal, {
114
- cursor: cursor ?? undefined,
115
- batchSize,
116
- })) as { items: BackupPageItem[]; cursor: string | null; isDone: boolean }
117
-
118
- cursor = page.cursor
119
- isDone = page.isDone
120
-
121
- for (const item of page.items) {
122
- if (item.kind !== 'ok') {
123
- if (item.kind === 'missingLatestVersion' || item.kind === 'missingVersionDoc') {
124
- stats.skillsMissingVersion += 1
125
- } else if (item.kind === 'missingOwner') {
126
- stats.skillsMissingOwner += 1
127
- }
128
- continue
129
- }
130
-
131
- stats.skillsScanned += 1
132
- try {
133
- const meta = await fetchGitHubSkillMeta(context, item.ownerHandle, item.slug)
134
- if (meta?.latest?.version === item.version) {
135
- stats.skillsSkipped += 1
136
- continue
137
- }
138
-
139
- if (!dryRun) {
140
- await backupSkillToGitHub(
141
- ctx,
142
- {
143
- slug: item.slug,
144
- version: item.version,
145
- displayName: item.displayName,
146
- ownerHandle: item.ownerHandle,
147
- files: item.files,
148
- publishedAt: item.publishedAt,
149
- },
150
- context,
151
- )
152
- stats.skillsBackedUp += 1
153
- }
154
- } catch (error) {
155
- console.error('GitHub backup sync failed', error)
156
- stats.errors += 1
157
- }
158
- }
159
-
160
- if (!dryRun) {
161
- await ctx.runMutation(internal.githubBackups.setGitHubBackupSyncStateInternal, {
162
- cursor: isDone ? undefined : (cursor ?? undefined),
163
- })
164
- }
165
-
166
- if (isDone) break
167
- }
168
-
169
- return { stats, cursor, isDone }
170
- }
171
-
172
- export const syncGitHubBackupsInternal = internalAction({
173
- args: {
174
- dryRun: v.optional(v.boolean()),
175
- batchSize: v.optional(v.number()),
176
- maxBatches: v.optional(v.number()),
177
- },
178
- handler: syncGitHubBackupsInternalHandler,
179
- })
180
-
181
- function clampInt(value: number, min: number, max: number) {
182
- return Math.max(min, Math.min(max, Math.floor(value)))
183
- }
@@ -1,317 +0,0 @@
1
- import { ConvexError, v } from 'convex/values'
2
- import { unzipSync } from 'fflate'
3
- import semver from 'semver'
4
- import { api, internal } from './_generated/api'
5
- import type { Id } from './_generated/dataModel'
6
- import type { ActionCtx } from './_generated/server'
7
- import { action } from './_generated/server'
8
- import { requireUserFromAction } from './lib/access'
9
- import {
10
- buildGitHubImportFileList,
11
- computeDefaultSelectedPaths,
12
- detectGitHubImportCandidates,
13
- fetchGitHubZipBytes,
14
- listTextFilesUnderCandidate,
15
- normalizeRepoPath,
16
- parseGitHubImportUrl,
17
- resolveGitHubCommit,
18
- stripGitHubZipRoot,
19
- suggestDisplayName,
20
- suggestVersion,
21
- } from './lib/githubImport'
22
- import { publishVersionForUser } from './lib/skillPublish'
23
- import { sanitizePath } from './lib/skills'
24
-
25
- const MAX_SELECTED_BYTES = 50 * 1024 * 1024
26
- const MAX_UNZIPPED_BYTES = 80 * 1024 * 1024
27
- const MAX_FILE_COUNT = 7_500
28
- const MAX_SINGLE_FILE_BYTES = 10 * 1024 * 1024
29
-
30
- export const previewGitHubImport = action({
31
- args: { url: v.string() },
32
- handler: async (ctx, args) => {
33
- await requireUserFromAction(ctx)
34
-
35
- const parsed = parseGitHubImportUrl(args.url)
36
- const resolved = await resolveGitHubCommit(parsed, fetch)
37
- const zipBytes = await fetchGitHubZipBytes(resolved, fetch)
38
- const entries = unzipToEntries(zipBytes)
39
- const stripped = stripGitHubZipRoot(entries)
40
- const candidates = detectGitHubImportCandidates(stripped).filter((candidate) =>
41
- isCandidateUnderResolvedPath(candidate.path, resolved.path),
42
- )
43
- if (candidates.length === 0) throw new ConvexError('No SKILL.md found in this repo')
44
-
45
- return {
46
- resolved,
47
- candidates: candidates.map((candidate) => ({
48
- path: candidate.path,
49
- readmePath: candidate.readmePath,
50
- name: candidate.name ?? null,
51
- description: candidate.description ?? null,
52
- })),
53
- }
54
- },
55
- })
56
-
57
- export const previewGitHubImportCandidate = action({
58
- args: { url: v.string(), candidatePath: v.string() },
59
- handler: async (ctx, args) => {
60
- const { userId } = await requireUserFromAction(ctx)
61
-
62
- const parsed = parseGitHubImportUrl(args.url)
63
- const resolved = await resolveGitHubCommit(parsed, fetch)
64
- const zipBytes = await fetchGitHubZipBytes(resolved, fetch)
65
- const entries = unzipToEntries(zipBytes)
66
- const stripped = stripGitHubZipRoot(entries)
67
-
68
- const normalizedCandidatePath = normalizeRepoPath(args.candidatePath)
69
- if (!isCandidateUnderResolvedPath(normalizedCandidatePath, resolved.path)) {
70
- throw new ConvexError('Candidate path is outside the requested import scope')
71
- }
72
-
73
- const candidates = detectGitHubImportCandidates(stripped).filter((candidate) =>
74
- isCandidateUnderResolvedPath(candidate.path, resolved.path),
75
- )
76
-
77
- const candidate = candidates.find((item) => item.path === normalizedCandidatePath)
78
- if (!candidate) throw new ConvexError('Candidate not found')
79
-
80
- const files = listTextFilesUnderCandidate(stripped, candidate.path)
81
- const defaultSelectedPaths = computeDefaultSelectedPaths({ candidate, files })
82
- const fileList = buildGitHubImportFileList({
83
- candidate,
84
- files,
85
- defaultSelectedPaths,
86
- })
87
-
88
- const baseForNaming = candidate.path ? (candidate.path.split('/').at(-1) ?? '') : resolved.repo
89
- const suggestedDisplayName = suggestDisplayName(candidate, baseForNaming)
90
-
91
- const rawSlugBase = sanitizeSlug(candidate.path ? baseForNaming : resolved.repo)
92
- const suggestedSlug = await suggestAvailableSlug(ctx, userId, rawSlugBase)
93
-
94
- const existing = await ctx.runQuery(api.skills.getBySlug, { slug: suggestedSlug })
95
- const existingLatest =
96
- existing?.skill && existing.skill.ownerUserId === userId
97
- ? (existing.latestVersion?.version ?? null)
98
- : null
99
- const suggestedVersion = suggestVersion(existingLatest)
100
-
101
- return {
102
- resolved,
103
- candidate: {
104
- path: candidate.path,
105
- readmePath: candidate.readmePath,
106
- name: candidate.name ?? null,
107
- description: candidate.description ?? null,
108
- },
109
- defaults: {
110
- selectedPaths: defaultSelectedPaths,
111
- slug: suggestedSlug,
112
- displayName: suggestedDisplayName,
113
- version: suggestedVersion,
114
- tags: ['latest'],
115
- },
116
- files: fileList,
117
- }
118
- },
119
- })
120
-
121
- export const importGitHubSkill = action({
122
- args: {
123
- url: v.string(),
124
- commit: v.string(),
125
- candidatePath: v.string(),
126
- selectedPaths: v.array(v.string()),
127
- slug: v.optional(v.string()),
128
- displayName: v.optional(v.string()),
129
- version: v.optional(v.string()),
130
- tags: v.optional(v.array(v.string())),
131
- },
132
- handler: async (ctx, args) => {
133
- const { userId } = await requireUserFromAction(ctx)
134
-
135
- const parsed = parseGitHubImportUrl(args.url)
136
- const resolved = await resolveGitHubCommit(parsed, fetch)
137
- if (!/^[a-f0-9]{40}$/i.test(args.commit)) throw new ConvexError('Invalid commit')
138
- if (args.commit.toLowerCase() !== resolved.commit.toLowerCase()) {
139
- throw new ConvexError('Import is out of date. Re-run preview.')
140
- }
141
-
142
- const normalizedCandidatePath = normalizeRepoPath(args.candidatePath)
143
- if (!isCandidateUnderResolvedPath(normalizedCandidatePath, resolved.path)) {
144
- throw new ConvexError('Candidate path is outside the requested import scope')
145
- }
146
-
147
- const zipBytes = await fetchGitHubZipBytes(resolved, fetch)
148
- const entries = stripGitHubZipRoot(unzipToEntries(zipBytes))
149
-
150
- const candidates = detectGitHubImportCandidates(entries).filter((candidate) =>
151
- isCandidateUnderResolvedPath(candidate.path, resolved.path),
152
- )
153
- const candidate = candidates.find((item) => item.path === normalizedCandidatePath)
154
- if (!candidate) throw new ConvexError('Candidate not found')
155
-
156
- const filesUnderCandidate = listTextFilesUnderCandidate(entries, candidate.path)
157
- const byPath = new Map(filesUnderCandidate.map((file) => [file.path, file.bytes]))
158
-
159
- const selected = Array.from(
160
- new Set(args.selectedPaths.map((path) => normalizeRepoPath(path)).filter(Boolean)),
161
- )
162
- if (selected.length === 0) throw new ConvexError('No files selected')
163
-
164
- const candidateRoot = candidate.path ? `${candidate.path}/` : ''
165
- const normalizedReadmePath = normalizeRepoPath(candidate.readmePath)
166
- if (!selected.includes(normalizedReadmePath)) {
167
- throw new ConvexError('SKILL.md must be selected')
168
- }
169
-
170
- let totalBytes = 0
171
- const storedFiles: Array<{
172
- path: string
173
- size: number
174
- storageId: Id<'_storage'>
175
- sha256: string
176
- contentType?: string
177
- }> = []
178
-
179
- for (const path of selected.sort()) {
180
- if (candidateRoot && !path.startsWith(candidateRoot)) {
181
- throw new ConvexError('Selected file is outside the chosen skill folder')
182
- }
183
-
184
- const bytes = byPath.get(path)
185
- if (!bytes) continue
186
- totalBytes += bytes.byteLength
187
- if (totalBytes > MAX_SELECTED_BYTES) throw new ConvexError('Selected files exceed 50MB limit')
188
-
189
- const relPath = candidateRoot ? path.slice(candidateRoot.length) : path
190
- const sanitized = sanitizePath(relPath)
191
- if (!sanitized) throw new ConvexError('Invalid file paths')
192
-
193
- const sha256 = await sha256Hex(bytes)
194
- const safeBytes = new Uint8Array(bytes)
195
- const storageId = await ctx.storage.store(new Blob([safeBytes], { type: 'text/plain' }))
196
- storedFiles.push({
197
- path: sanitized,
198
- size: bytes.byteLength,
199
- storageId,
200
- sha256,
201
- contentType: 'text/plain',
202
- })
203
- }
204
-
205
- if (storedFiles.length === 0) throw new ConvexError('No files selected')
206
-
207
- const slugBase = (args.slug ?? '').trim().toLowerCase()
208
- const displayName = (args.displayName ?? '').trim()
209
- const tags = (args.tags ?? ['latest']).map((tag) => tag.trim()).filter(Boolean)
210
- const version = (args.version ?? '').trim()
211
-
212
- if (!slugBase) throw new ConvexError('Slug required')
213
- if (!displayName) throw new ConvexError('Display name required')
214
- if (!version || !semver.valid(version)) throw new ConvexError('Version must be valid semver')
215
-
216
- const result = await publishVersionForUser(ctx, userId, {
217
- slug: slugBase,
218
- displayName,
219
- version,
220
- changelog: '',
221
- tags,
222
- files: storedFiles,
223
- source: {
224
- kind: 'github',
225
- url: resolved.originalUrl,
226
- repo: `${resolved.owner}/${resolved.repo}`,
227
- ref: resolved.ref,
228
- commit: resolved.commit,
229
- path: candidate.path,
230
- importedAt: Date.now(),
231
- },
232
- })
233
-
234
- return { ok: true, slug: slugBase, version, ...result }
235
- },
236
- })
237
-
238
- function unzipToEntries(zipBytes: Uint8Array) {
239
- const entries = unzipSync(zipBytes)
240
- const out: Record<string, Uint8Array> = {}
241
- const rawPaths = Object.keys(entries)
242
- if (rawPaths.length > MAX_FILE_COUNT) throw new ConvexError('Repo archive has too many files')
243
- let totalBytes = 0
244
- for (const [rawPath, bytes] of Object.entries(entries)) {
245
- const normalizedPath = normalizeZipPath(rawPath)
246
- if (!normalizedPath) continue
247
- if (isJunkPath(normalizedPath)) continue
248
- if (!bytes) continue
249
- if (bytes.byteLength > MAX_SINGLE_FILE_BYTES) continue
250
- totalBytes += bytes.byteLength
251
- if (totalBytes > MAX_UNZIPPED_BYTES) throw new ConvexError('Repo archive is too large')
252
- out[normalizedPath] = bytes
253
- }
254
- return out
255
- }
256
-
257
- function isCandidateUnderResolvedPath(candidatePath: string, resolvedPath: string) {
258
- const root = normalizeRepoPath(resolvedPath)
259
- if (!root) return true
260
- if (!candidatePath) return false
261
- if (candidatePath === root) return true
262
- return candidatePath.startsWith(`${root}/`)
263
- }
264
-
265
- function sanitizeSlug(value: string) {
266
- return value
267
- .trim()
268
- .toLowerCase()
269
- .replace(/[^a-z0-9-]+/g, '-')
270
- .replace(/^-+/, '')
271
- .replace(/-+$/, '')
272
- .replace(/--+/g, '-')
273
- }
274
-
275
- async function suggestAvailableSlug(ctx: ActionCtx, userId: Id<'users'>, base: string) {
276
- const cleaned = sanitizeSlug(base)
277
- if (!cleaned) throw new ConvexError('Could not derive slug')
278
- for (let i = 0; i < 50; i += 1) {
279
- const candidate = i === 0 ? cleaned : `${cleaned}-${i + 1}`
280
- const existing = await ctx.runQuery(internal.skills.getSkillBySlugInternal, { slug: candidate })
281
- if (!existing) return candidate
282
- if (existing.ownerUserId === userId) return candidate
283
- }
284
- throw new ConvexError('Could not find an available slug')
285
- }
286
-
287
- async function sha256Hex(bytes: Uint8Array) {
288
- const normalized = new Uint8Array(bytes)
289
- const digest = await crypto.subtle.digest('SHA-256', normalized.buffer)
290
- return toHex(new Uint8Array(digest))
291
- }
292
-
293
- function toHex(bytes: Uint8Array) {
294
- let out = ''
295
- for (const byte of bytes) out += byte.toString(16).padStart(2, '0')
296
- return out
297
- }
298
-
299
- function normalizeZipPath(path: string) {
300
- const normalized = path
301
- .replaceAll('\u0000', '')
302
- .replaceAll('\\', '/')
303
- .trim()
304
- .replace(/^\.\/+/, '')
305
- .replace(/^\/+/, '')
306
- if (!normalized) return ''
307
- if (normalized.includes('..')) return ''
308
- return normalized
309
- }
310
-
311
- function isJunkPath(path: string) {
312
- const normalized = path.toLowerCase()
313
- if (normalized.startsWith('__macosx/')) return true
314
- if (normalized.endsWith('/.ds_store')) return true
315
- if (normalized === '.ds_store') return true
316
- return false
317
- }