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,263 +0,0 @@
1
- import { useAction, useMutation, useQuery } from 'convex/react'
2
- import { useEffect, useMemo, useRef, useState } from 'react'
3
- import ReactMarkdown from 'react-markdown'
4
- import remarkGfm from 'remark-gfm'
5
- import { api } from '../../convex/_generated/api'
6
- import type { Doc } from '../../convex/_generated/dataModel'
7
- import type { PublicSoul, PublicUser } from '../lib/publicUser'
8
- import { isModerator } from '../lib/roles'
9
- import { useAuthStatus } from '../lib/useAuthStatus'
10
-
11
- type SoulDetailPageProps = {
12
- slug: string
13
- }
14
-
15
- type SoulBySlugResult = {
16
- soul: PublicSoul
17
- latestVersion: Doc<'soulVersions'> | null
18
- owner: PublicUser | null
19
- } | null
20
-
21
- export function SoulDetailPage({ slug }: SoulDetailPageProps) {
22
- const { isAuthenticated, me } = useAuthStatus()
23
- const result = useQuery(api.souls.getBySlug, { slug }) as SoulBySlugResult | undefined
24
- const toggleStar = useMutation(api.soulStars.toggle)
25
- const addComment = useMutation(api.soulComments.add)
26
- const removeComment = useMutation(api.soulComments.remove)
27
- const getReadme = useAction(api.souls.getReadme)
28
- const ensureSoulSeeds = useAction(api.seed.ensureSoulSeeds)
29
- const seedEnsuredRef = useRef(false)
30
- const [readme, setReadme] = useState<string | null>(null)
31
- const [readmeError, setReadmeError] = useState<string | null>(null)
32
- const [comment, setComment] = useState('')
33
-
34
- const isLoadingSoul = result === undefined
35
- const soul = result?.soul
36
- const owner = result?.owner
37
- const latestVersion = result?.latestVersion
38
- const versions = useQuery(
39
- api.souls.listVersions,
40
- soul ? { soulId: soul._id, limit: 50 } : 'skip',
41
- ) as Doc<'soulVersions'>[] | undefined
42
-
43
- const isStarred = useQuery(
44
- api.soulStars.isStarred,
45
- isAuthenticated && soul ? { soulId: soul._id } : 'skip',
46
- )
47
-
48
- const comments = useQuery(
49
- api.soulComments.listBySoul,
50
- soul ? { soulId: soul._id, limit: 50 } : 'skip',
51
- ) as Array<{ comment: Doc<'soulComments'>; user: PublicUser | null }> | undefined
52
-
53
- const readmeContent = useMemo(() => {
54
- if (!readme) return null
55
- return stripFrontmatter(readme)
56
- }, [readme])
57
-
58
- useEffect(() => {
59
- if (seedEnsuredRef.current) return
60
- seedEnsuredRef.current = true
61
- void ensureSoulSeeds({})
62
- }, [ensureSoulSeeds])
63
-
64
- useEffect(() => {
65
- if (!latestVersion) return
66
- setReadme(null)
67
- setReadmeError(null)
68
- let cancelled = false
69
- void getReadme({ versionId: latestVersion._id })
70
- .then((data) => {
71
- if (cancelled) return
72
- setReadme(data.text)
73
- })
74
- .catch((error) => {
75
- if (cancelled) return
76
- setReadmeError(error instanceof Error ? error.message : 'Failed to load SOUL.md')
77
- setReadme(null)
78
- })
79
- return () => {
80
- cancelled = true
81
- }
82
- }, [latestVersion, getReadme])
83
-
84
- if (isLoadingSoul) {
85
- return (
86
- <main className="section">
87
- <div className="card">
88
- <div className="loading-indicator">Loading soul…</div>
89
- </div>
90
- </main>
91
- )
92
- }
93
-
94
- if (result === null || !soul) {
95
- return (
96
- <main className="section">
97
- <div className="card">Soul not found.</div>
98
- </main>
99
- )
100
- }
101
-
102
- const ownerHandle = owner?.handle ?? owner?.name ?? null
103
- const downloadBase = `${import.meta.env.VITE_CONVEX_SITE_URL}/api/v1/souls/${soul.slug}/file`
104
-
105
- return (
106
- <main className="section">
107
- <div className="skill-detail-stack">
108
- <div className="card skill-hero">
109
- <div className="skill-hero-header">
110
- <div className="skill-hero-title">
111
- <h1 className="section-title" style={{ margin: 0 }}>
112
- {soul.displayName}
113
- </h1>
114
- <p className="section-subtitle">{soul.summary ?? 'No summary provided.'}</p>
115
- <div className="stat">
116
- ⭐ {soul.stats.stars} · ⤓ {soul.stats.downloads} · {soul.stats.versions} versions
117
- </div>
118
- {ownerHandle ? (
119
- <div className="stat">
120
- by <a href={`/u/${ownerHandle}`}>@{ownerHandle}</a>
121
- </div>
122
- ) : null}
123
- <div className="skill-actions">
124
- {isAuthenticated ? (
125
- <button
126
- className={`star-toggle${isStarred ? ' is-active' : ''}`}
127
- type="button"
128
- onClick={() => void toggleStar({ soulId: soul._id })}
129
- aria-label={isStarred ? 'Unstar soul' : 'Star soul'}
130
- >
131
- <span aria-hidden="true">★</span>
132
- </button>
133
- ) : null}
134
- </div>
135
- </div>
136
- <div className="skill-hero-cta">
137
- <div className="skill-version-pill">
138
- <span className="skill-version-label">Current version</span>
139
- <strong>v{latestVersion?.version ?? '—'}</strong>
140
- </div>
141
- <a
142
- className="btn btn-primary"
143
- href={`${downloadBase}?path=SOUL.md`}
144
- aria-label="Download SOUL.md"
145
- >
146
- Download SOUL.md
147
- </a>
148
- </div>
149
- </div>
150
- </div>
151
-
152
- <div className="card">
153
- <div className="skill-readme markdown">
154
- {readmeContent ? (
155
- <ReactMarkdown remarkPlugins={[remarkGfm]}>{readmeContent}</ReactMarkdown>
156
- ) : readmeError ? (
157
- <div className="stat">Failed to load SOUL.md: {readmeError}</div>
158
- ) : (
159
- <div className="loading-indicator">Loading SOUL.md…</div>
160
- )}
161
- </div>
162
- </div>
163
-
164
- <div className="card">
165
- <h2 className="section-title" style={{ fontSize: '1.2rem', marginBottom: 8 }}>
166
- Versions
167
- </h2>
168
- <div className="version-scroll">
169
- <div className="version-list">
170
- {(versions ?? []).map((version) => (
171
- <div key={version._id} className="version-row">
172
- <div className="version-info">
173
- <div>
174
- v{version.version} · {new Date(version.createdAt).toLocaleDateString()}
175
- {version.changelogSource === 'auto' ? (
176
- <span style={{ color: 'var(--ink-soft)' }}> · auto</span>
177
- ) : null}
178
- </div>
179
- <div style={{ color: '#5c554e', whiteSpace: 'pre-wrap' }}>
180
- {version.changelog}
181
- </div>
182
- </div>
183
- <div className="version-actions">
184
- <a
185
- className="btn version-zip"
186
- href={`${downloadBase}?path=SOUL.md&version=${encodeURIComponent(
187
- version.version,
188
- )}`}
189
- >
190
- SOUL.md
191
- </a>
192
- </div>
193
- </div>
194
- ))}
195
- </div>
196
- </div>
197
- </div>
198
-
199
- <div className="card">
200
- <h2 className="section-title" style={{ fontSize: '1.2rem', margin: 0 }}>
201
- Comments
202
- </h2>
203
- {isAuthenticated ? (
204
- <form
205
- onSubmit={(event) => {
206
- event.preventDefault()
207
- if (!comment.trim()) return
208
- void addComment({ soulId: soul._id, body: comment.trim() }).then(() =>
209
- setComment(''),
210
- )
211
- }}
212
- className="comment-form"
213
- >
214
- <textarea
215
- className="comment-input"
216
- rows={4}
217
- value={comment}
218
- onChange={(event) => setComment(event.target.value)}
219
- placeholder="Leave a note…"
220
- />
221
- <button className="btn comment-submit" type="submit">
222
- Post comment
223
- </button>
224
- </form>
225
- ) : (
226
- <p className="section-subtitle">Sign in to comment.</p>
227
- )}
228
- <div style={{ display: 'grid', gap: 12, marginTop: 16 }}>
229
- {(comments ?? []).length === 0 ? (
230
- <div className="stat">No comments yet.</div>
231
- ) : (
232
- (comments ?? []).map((entry) => (
233
- <div key={entry.comment._id} className="stat" style={{ alignItems: 'flex-start' }}>
234
- <div>
235
- <strong>@{entry.user?.handle ?? entry.user?.name ?? 'user'}</strong>
236
- <div style={{ color: '#5c554e' }}>{entry.comment.body}</div>
237
- </div>
238
- {isAuthenticated && me && (me._id === entry.comment.userId || isModerator(me)) ? (
239
- <button
240
- className="btn"
241
- type="button"
242
- onClick={() => void removeComment({ commentId: entry.comment._id })}
243
- >
244
- Delete
245
- </button>
246
- ) : null}
247
- </div>
248
- ))
249
- )}
250
- </div>
251
- </div>
252
- </div>
253
- </main>
254
- )
255
- }
256
-
257
- function stripFrontmatter(content: string) {
258
- const normalized = content.replace(/\r\n/g, '\n').replace(/\r/g, '\n')
259
- if (!normalized.startsWith('---')) return content
260
- const endIndex = normalized.indexOf('\n---', 3)
261
- if (endIndex === -1) return content
262
- return normalized.slice(endIndex + 4).replace(/^\n+/, '')
263
- }
@@ -1,18 +0,0 @@
1
- import { useMutation } from 'convex/react'
2
- import { useEffect, useRef } from 'react'
3
- import { api } from '../../convex/_generated/api'
4
- import { useAuthStatus } from '../lib/useAuthStatus'
5
-
6
- export function UserBootstrap() {
7
- const { isAuthenticated, isLoading } = useAuthStatus()
8
- const ensureUser = useMutation(api.users.ensure)
9
- const didRun = useRef(false)
10
-
11
- useEffect(() => {
12
- if (isLoading || !isAuthenticated || didRun.current) return
13
- didRun.current = true
14
- void ensureUser()
15
- }, [isAuthenticated, isLoading, ensureUser])
16
-
17
- return null
18
- }
@@ -1,67 +0,0 @@
1
- import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu'
2
- import * as React from 'react'
3
- import { cn } from '../../lib/utils'
4
-
5
- const DropdownMenu = DropdownMenuPrimitive.Root
6
- const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger
7
- const DropdownMenuGroup = DropdownMenuPrimitive.Group
8
- const DropdownMenuPortal = DropdownMenuPrimitive.Portal
9
- const DropdownMenuSub = DropdownMenuPrimitive.Sub
10
- const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup
11
-
12
- const DropdownMenuContent = React.forwardRef<
13
- React.ElementRef<typeof DropdownMenuPrimitive.Content>,
14
- React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content>
15
- >(({ className, sideOffset = 8, ...props }, ref) => (
16
- <DropdownMenuPrimitive.Portal>
17
- <DropdownMenuPrimitive.Content
18
- ref={ref}
19
- sideOffset={sideOffset}
20
- className={cn(
21
- 'z-50 min-w-[180px] rounded-xl border border-[color:var(--line)] bg-[color:var(--surface)] p-2 text-[color:var(--ink)] shadow-[var(--shadow)]',
22
- className,
23
- )}
24
- {...props}
25
- />
26
- </DropdownMenuPrimitive.Portal>
27
- ))
28
- DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName
29
-
30
- const DropdownMenuItem = React.forwardRef<
31
- React.ElementRef<typeof DropdownMenuPrimitive.Item>,
32
- React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item>
33
- >(({ className, ...props }, ref) => (
34
- <DropdownMenuPrimitive.Item
35
- ref={ref}
36
- className={cn(
37
- 'flex cursor-pointer select-none items-center gap-2 rounded-lg px-3 py-2 text-sm font-semibold text-[color:var(--ink)] outline-none transition-colors focus:bg-[color:rgba(255,107,74,0.12)] data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
38
- className,
39
- )}
40
- {...props}
41
- />
42
- ))
43
- DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName
44
-
45
- const DropdownMenuSeparator = React.forwardRef<
46
- React.ElementRef<typeof DropdownMenuPrimitive.Separator>,
47
- React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Separator>
48
- >(({ className, ...props }, ref) => (
49
- <DropdownMenuPrimitive.Separator
50
- ref={ref}
51
- className={cn('my-1 h-px bg-[color:var(--line)]', className)}
52
- {...props}
53
- />
54
- ))
55
- DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName
56
-
57
- export {
58
- DropdownMenu,
59
- DropdownMenuTrigger,
60
- DropdownMenuContent,
61
- DropdownMenuItem,
62
- DropdownMenuSeparator,
63
- DropdownMenuGroup,
64
- DropdownMenuPortal,
65
- DropdownMenuSub,
66
- DropdownMenuRadioGroup,
67
- }
@@ -1,35 +0,0 @@
1
- import * as ToggleGroupPrimitive from '@radix-ui/react-toggle-group'
2
- import * as React from 'react'
3
- import { cn } from '../../lib/utils'
4
-
5
- const ToggleGroup = React.forwardRef<
6
- React.ElementRef<typeof ToggleGroupPrimitive.Root>,
7
- React.ComponentPropsWithoutRef<typeof ToggleGroupPrimitive.Root>
8
- >(({ className, ...props }, ref) => (
9
- <ToggleGroupPrimitive.Root
10
- ref={ref}
11
- className={cn(
12
- 'inline-flex items-center gap-1 rounded-full border border-[color:var(--line)] bg-[color:var(--surface)] p-1',
13
- className,
14
- )}
15
- {...props}
16
- />
17
- ))
18
- ToggleGroup.displayName = ToggleGroupPrimitive.Root.displayName
19
-
20
- const ToggleGroupItem = React.forwardRef<
21
- React.ElementRef<typeof ToggleGroupPrimitive.Item>,
22
- React.ComponentPropsWithoutRef<typeof ToggleGroupPrimitive.Item>
23
- >(({ className, ...props }, ref) => (
24
- <ToggleGroupPrimitive.Item
25
- ref={ref}
26
- className={cn(
27
- 'inline-flex h-9 w-9 items-center justify-center rounded-full text-[color:var(--ink-soft)] transition-colors hover:text-[color:var(--ink)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[color:rgba(255,107,74,0.4)] data-[state=on]:bg-[color:var(--accent)] data-[state=on]:text-white',
28
- className,
29
- )}
30
- {...props}
31
- />
32
- ))
33
- ToggleGroupItem.displayName = ToggleGroupPrimitive.Item.displayName
34
-
35
- export { ToggleGroup, ToggleGroupItem }
@@ -1,3 +0,0 @@
1
- import { ConvexReactClient } from 'convex/react'
2
-
3
- export const convex = new ConvexReactClient(import.meta.env.VITE_CONVEX_URL as string)
package/src/lib/badges.ts DELETED
@@ -1,29 +0,0 @@
1
- import type { Doc, Id } from '../../convex/_generated/dataModel'
2
-
3
- type BadgeKind = Doc<'skillBadges'>['kind']
4
-
5
- type SkillBadgeMap = Partial<Record<BadgeKind, { byUserId: Id<'users'>; at: number }>>
6
-
7
- type SkillLike = { badges?: SkillBadgeMap | null }
8
-
9
- type BadgeLabel = 'Deprecated' | 'Official' | 'Highlighted'
10
-
11
- export function isSkillHighlighted(skill: SkillLike) {
12
- return Boolean(skill.badges?.highlighted)
13
- }
14
-
15
- export function isSkillOfficial(skill: SkillLike) {
16
- return Boolean(skill.badges?.official)
17
- }
18
-
19
- export function isSkillDeprecated(skill: SkillLike) {
20
- return Boolean(skill.badges?.deprecated)
21
- }
22
-
23
- export function getSkillBadges(skill: SkillLike): BadgeLabel[] {
24
- const badges: BadgeLabel[] = []
25
- if (isSkillDeprecated(skill)) badges.push('Deprecated')
26
- if (isSkillOfficial(skill)) badges.push('Official')
27
- if (isSkillHighlighted(skill)) badges.push('Highlighted')
28
- return badges
29
- }
@@ -1,163 +0,0 @@
1
- import { describe, expect, it } from 'vitest'
2
- import {
3
- buildFileDiffList,
4
- getDefaultDiffSelection,
5
- resolveLatestVersionId,
6
- resolvePreviousVersionId,
7
- selectDefaultFilePath,
8
- sortVersionsBySemver,
9
- } from './diffing'
10
-
11
- describe('diffing', () => {
12
- it('sorts versions by semver descending', () => {
13
- const ordered = sortVersionsBySemver([
14
- { id: 'a', version: '1.0.0' },
15
- { id: 'b', version: '2.0.0' },
16
- { id: 'c', version: '1.5.0' },
17
- ])
18
- expect(ordered.map((entry) => entry.version)).toEqual(['2.0.0', '1.5.0', '1.0.0'])
19
- })
20
-
21
- it('sorts valid semver ahead of invalid entries', () => {
22
- const ordered = sortVersionsBySemver([
23
- { id: 'a', version: 'not-a-version' },
24
- { id: 'b', version: '1.0.0' },
25
- { id: 'c', version: '2.0.0' },
26
- ])
27
- expect(ordered.map((entry) => entry.version)).toEqual(['2.0.0', '1.0.0', 'not-a-version'])
28
- })
29
-
30
- it('sorts when only one entry is valid', () => {
31
- const ordered = sortVersionsBySemver([
32
- { id: 'a', version: 'nope' },
33
- { id: 'b', version: '1.0.0' },
34
- ])
35
- expect(ordered.map((entry) => entry.version)).toEqual(['1.0.0', 'nope'])
36
- })
37
-
38
- it('sorts invalid entries lexicographically', () => {
39
- const ordered = sortVersionsBySemver([
40
- { id: 'a', version: 'beta' },
41
- { id: 'b', version: 'alpha' },
42
- ])
43
- expect(ordered.map((entry) => entry.version)).toEqual(['alpha', 'beta'])
44
- })
45
-
46
- it('resolves latest from tag when present', () => {
47
- const latestId = resolveLatestVersionId(
48
- [
49
- { id: 'a', version: '1.0.0' },
50
- { id: 'b', version: '2.0.0' },
51
- ],
52
- { latest: 'a' },
53
- )
54
- expect(latestId).toBe('a')
55
- })
56
-
57
- it('returns null when no versions exist', () => {
58
- const latestId = resolveLatestVersionId([], undefined)
59
- expect(latestId).toBeNull()
60
- })
61
-
62
- it('resolves previous via semver predecessor', () => {
63
- const latestId = 'b'
64
- const previousId = resolvePreviousVersionId(
65
- [
66
- { id: 'a', version: '1.0.0' },
67
- { id: 'b', version: '2.0.0' },
68
- { id: 'c', version: '1.5.0' },
69
- ],
70
- latestId,
71
- )
72
- expect(previousId).toBe('c')
73
- })
74
-
75
- it('falls back to second entry when latest missing', () => {
76
- const previousId = resolvePreviousVersionId(
77
- [
78
- { id: 'a', version: '2.0.0' },
79
- { id: 'b', version: '1.0.0' },
80
- { id: 'c', version: '0.5.0' },
81
- ],
82
- 'missing',
83
- )
84
- expect(previousId).toBe('b')
85
- })
86
-
87
- it('returns default selection previous vs latest', () => {
88
- const selection = getDefaultDiffSelection(
89
- [
90
- { id: 'a', version: '1.0.0' },
91
- { id: 'b', version: '2.0.0' },
92
- ],
93
- { latest: 'b' },
94
- )
95
- expect(selection).toEqual({ leftId: 'a', rightId: 'b' })
96
- })
97
-
98
- it('builds file diff list with statuses', () => {
99
- const diff = buildFileDiffList(
100
- [
101
- { path: 'SKILL.md', sha256: 'aaa', size: 10 },
102
- { path: 'a.ts', sha256: 'bbb', size: 10 },
103
- ],
104
- [
105
- { path: 'SKILL.md', sha256: 'aaa', size: 10 },
106
- { path: 'b.ts', sha256: 'ccc', size: 10 },
107
- { path: 'a.ts', sha256: 'ddd', size: 10 },
108
- ],
109
- )
110
- const statusByPath = Object.fromEntries(diff.map((item) => [item.path, item.status]))
111
- expect(statusByPath['SKILL.md']).toBe('same')
112
- expect(statusByPath['a.ts']).toBe('changed')
113
- expect(statusByPath['b.ts']).toBe('added')
114
- })
115
-
116
- it('orders file diff list by change status then path', () => {
117
- const diff = buildFileDiffList(
118
- [
119
- { path: 'c.txt', sha256: 'aaa', size: 1 },
120
- { path: 'a.txt', sha256: 'bbb', size: 1 },
121
- ],
122
- [
123
- { path: 'a.txt', sha256: 'ccc', size: 1 },
124
- { path: 'b.txt', sha256: 'ddd', size: 1 },
125
- ],
126
- )
127
- expect(diff.map((item) => item.path)).toEqual(['a.txt', 'b.txt', 'c.txt'])
128
- })
129
-
130
- it('orders file diff list alphabetically for same status', () => {
131
- const diff = buildFileDiffList(
132
- [
133
- { path: 'b.txt', sha256: 'aaa', size: 1 },
134
- { path: 'a.txt', sha256: 'bbb', size: 1 },
135
- ],
136
- [
137
- { path: 'b.txt', sha256: 'aaa', size: 1 },
138
- { path: 'a.txt', sha256: 'bbb', size: 1 },
139
- ],
140
- )
141
- expect(diff.map((item) => item.path)).toEqual(['a.txt', 'b.txt'])
142
- })
143
-
144
- it('selects SKILL.md as default file when present', () => {
145
- const path = selectDefaultFilePath([
146
- { path: 'notes.md', status: 'changed' },
147
- { path: 'SKILL.md', status: 'same' },
148
- ])
149
- expect(path).toBe('SKILL.md')
150
- })
151
-
152
- it('falls back to first changed file when SKILL.md missing', () => {
153
- const path = selectDefaultFilePath([
154
- { path: 'alpha.txt', status: 'same' },
155
- { path: 'beta.txt', status: 'changed' },
156
- ])
157
- expect(path).toBe('beta.txt')
158
- })
159
-
160
- it('returns null when no file entries exist', () => {
161
- expect(selectDefaultFilePath([])).toBeNull()
162
- })
163
- })
@@ -1,106 +0,0 @@
1
- import semver from 'semver'
2
-
3
- export const MAX_DIFF_FILE_BYTES = 200 * 1024
4
-
5
- type TagMap<IdType extends string> = Record<string, IdType>
6
-
7
- export type VersionEntry<IdType extends string = string> = {
8
- id: IdType
9
- version: string
10
- }
11
-
12
- export type FileMeta = {
13
- path: string
14
- sha256: string
15
- size: number
16
- }
17
-
18
- export type FileDiffStatus = 'added' | 'removed' | 'changed' | 'same'
19
-
20
- export type FileDiffItem = {
21
- path: string
22
- status: FileDiffStatus
23
- left?: FileMeta
24
- right?: FileMeta
25
- }
26
-
27
- export function sortVersionsBySemver<IdType extends string>(versions: VersionEntry<IdType>[]) {
28
- return [...versions].sort((a, b) => {
29
- const aValid = Boolean(semver.valid(a.version))
30
- const bValid = Boolean(semver.valid(b.version))
31
- if (aValid && bValid) return semver.rcompare(a.version, b.version)
32
- if (aValid) return -1
33
- if (bValid) return 1
34
- return a.version.localeCompare(b.version)
35
- })
36
- }
37
-
38
- export function resolveLatestVersionId<IdType extends string>(
39
- versions: VersionEntry<IdType>[],
40
- tags?: TagMap<IdType>,
41
- ) {
42
- if (tags?.latest) return tags.latest
43
- return sortVersionsBySemver(versions)[0]?.id ?? null
44
- }
45
-
46
- export function resolvePreviousVersionId<IdType extends string>(
47
- versions: VersionEntry<IdType>[],
48
- latestId?: IdType | null,
49
- ) {
50
- const ordered = sortVersionsBySemver(versions)
51
- if (!latestId) return ordered[1]?.id ?? null
52
- const latestIndex = ordered.findIndex((entry) => entry.id === latestId)
53
- if (latestIndex === -1) return ordered[1]?.id ?? null
54
- return ordered[latestIndex + 1]?.id ?? null
55
- }
56
-
57
- export function getDefaultDiffSelection<IdType extends string>(
58
- versions: VersionEntry<IdType>[],
59
- tags?: TagMap<IdType>,
60
- ) {
61
- const latestId = resolveLatestVersionId(versions, tags)
62
- const previousId = resolvePreviousVersionId(versions, latestId)
63
- return {
64
- leftId: previousId ?? latestId ?? null,
65
- rightId: latestId ?? previousId ?? null,
66
- }
67
- }
68
-
69
- export function buildFileDiffList(leftFiles: FileMeta[], rightFiles: FileMeta[]) {
70
- const entries = new Map<string, { left?: FileMeta; right?: FileMeta }>()
71
- for (const file of leftFiles) {
72
- entries.set(file.path, { left: file })
73
- }
74
- for (const file of rightFiles) {
75
- const existing = entries.get(file.path) ?? {}
76
- entries.set(file.path, { ...existing, right: file })
77
- }
78
-
79
- const statusRank: Record<FileDiffStatus, number> = {
80
- changed: 0,
81
- added: 1,
82
- removed: 2,
83
- same: 3,
84
- }
85
-
86
- return Array.from(entries.entries())
87
- .map(([path, info]) => {
88
- let status: FileDiffStatus = 'same'
89
- if (info.left && !info.right) status = 'removed'
90
- else if (!info.left && info.right) status = 'added'
91
- else if (info.left?.sha256 !== info.right?.sha256) status = 'changed'
92
- return { path, status, left: info.left, right: info.right }
93
- })
94
- .sort((a, b) => {
95
- const statusDiff = statusRank[a.status] - statusRank[b.status]
96
- if (statusDiff !== 0) return statusDiff
97
- return a.path.localeCompare(b.path)
98
- })
99
- }
100
-
101
- export function selectDefaultFilePath(items: FileDiffItem[]) {
102
- const readme = items.find((item) => item.path.toLowerCase() === 'skill.md')
103
- if (readme) return readme.path
104
- const changed = items.find((item) => item.status !== 'same')
105
- return changed?.path ?? items[0]?.path ?? null
106
- }