pilothub 0.0.1

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 (272) hide show
  1. package/.env.local.example +19 -0
  2. package/.github/workflows/ci.yml +40 -0
  3. package/.oxlintrc.json +3 -0
  4. package/AGENTS.md +45 -0
  5. package/CHANGELOG.md +138 -0
  6. package/DEPRECATIONS.md +7 -0
  7. package/LICENSE +21 -0
  8. package/README.md +150 -0
  9. package/biome.json +41 -0
  10. package/convex/_generated/api.d.ts +153 -0
  11. package/convex/_generated/api.js +23 -0
  12. package/convex/_generated/dataModel.d.ts +60 -0
  13. package/convex/_generated/server.d.ts +143 -0
  14. package/convex/_generated/server.js +93 -0
  15. package/convex/auth.config.ts +8 -0
  16. package/convex/auth.ts +19 -0
  17. package/convex/comments.ts +88 -0
  18. package/convex/crons.ts +34 -0
  19. package/convex/devSeed.ts +459 -0
  20. package/convex/devSeedExtra.ts +541 -0
  21. package/convex/downloads.ts +78 -0
  22. package/convex/githubBackups.ts +170 -0
  23. package/convex/githubBackupsNode.ts +183 -0
  24. package/convex/githubImport.ts +317 -0
  25. package/convex/githubSoulBackups.ts +170 -0
  26. package/convex/githubSoulBackupsNode.ts +186 -0
  27. package/convex/http.ts +194 -0
  28. package/convex/httpApi.handlers.test.ts +488 -0
  29. package/convex/httpApi.test.ts +70 -0
  30. package/convex/httpApi.ts +305 -0
  31. package/convex/httpApiV1.handlers.test.ts +584 -0
  32. package/convex/httpApiV1.ts +1172 -0
  33. package/convex/leaderboards.ts +39 -0
  34. package/convex/lib/access.ts +36 -0
  35. package/convex/lib/apiTokenAuth.ts +36 -0
  36. package/convex/lib/badges.ts +50 -0
  37. package/convex/lib/changelog.test.ts +34 -0
  38. package/convex/lib/changelog.ts +278 -0
  39. package/convex/lib/embeddings.ts +38 -0
  40. package/convex/lib/githubBackup.ts +443 -0
  41. package/convex/lib/githubImport.test.ts +247 -0
  42. package/convex/lib/githubImport.ts +425 -0
  43. package/convex/lib/githubSoulBackup.ts +443 -0
  44. package/convex/lib/leaderboards.ts +103 -0
  45. package/convex/lib/moderation.ts +42 -0
  46. package/convex/lib/public.ts +89 -0
  47. package/convex/lib/searchText.test.ts +46 -0
  48. package/convex/lib/searchText.ts +27 -0
  49. package/convex/lib/skillBackfill.test.ts +34 -0
  50. package/convex/lib/skillBackfill.ts +67 -0
  51. package/convex/lib/skillPublish.test.ts +28 -0
  52. package/convex/lib/skillPublish.ts +284 -0
  53. package/convex/lib/skillStats.ts +80 -0
  54. package/convex/lib/skills.test.ts +197 -0
  55. package/convex/lib/skills.ts +273 -0
  56. package/convex/lib/soulChangelog.ts +273 -0
  57. package/convex/lib/soulPublish.ts +236 -0
  58. package/convex/lib/tokens.test.ts +33 -0
  59. package/convex/lib/tokens.ts +51 -0
  60. package/convex/lib/webhooks.test.ts +91 -0
  61. package/convex/lib/webhooks.ts +112 -0
  62. package/convex/maintenance.test.ts +270 -0
  63. package/convex/maintenance.ts +840 -0
  64. package/convex/rateLimits.ts +50 -0
  65. package/convex/schema.ts +472 -0
  66. package/convex/search.test.ts +12 -0
  67. package/convex/search.ts +254 -0
  68. package/convex/seed.test.ts +37 -0
  69. package/convex/seed.ts +254 -0
  70. package/convex/seedSouls.ts +111 -0
  71. package/convex/skillStatEvents.ts +568 -0
  72. package/convex/skills.ts +1606 -0
  73. package/convex/soulComments.ts +88 -0
  74. package/convex/soulDownloads.ts +14 -0
  75. package/convex/soulStars.ts +71 -0
  76. package/convex/souls.ts +570 -0
  77. package/convex/stars.ts +108 -0
  78. package/convex/statsMaintenance.ts +205 -0
  79. package/convex/telemetry.ts +434 -0
  80. package/convex/tokens.ts +88 -0
  81. package/convex/tsconfig.json +7 -0
  82. package/convex/uploads.ts +20 -0
  83. package/convex/users.ts +122 -0
  84. package/convex/webhooks.ts +50 -0
  85. package/convex.json +3 -0
  86. package/docs/README.md +32 -0
  87. package/docs/api.md +51 -0
  88. package/docs/architecture.md +61 -0
  89. package/docs/auth.md +54 -0
  90. package/docs/cli.md +117 -0
  91. package/docs/deploy.md +78 -0
  92. package/docs/diffing.md +84 -0
  93. package/docs/github-import.md +171 -0
  94. package/docs/http-api.md +187 -0
  95. package/docs/manual-testing.md +64 -0
  96. package/docs/mintlify.md +43 -0
  97. package/docs/quickstart.md +120 -0
  98. package/docs/skill-format.md +58 -0
  99. package/docs/soul-format.md +37 -0
  100. package/docs/spec.md +177 -0
  101. package/docs/telemetry.md +91 -0
  102. package/docs/troubleshooting.md +49 -0
  103. package/docs/webhook.md +51 -0
  104. package/e2e/menu-smoke.pw.test.ts +49 -0
  105. package/e2e/pilothub.e2e.test.ts +494 -0
  106. package/e2e/search-exact.pw.test.ts +97 -0
  107. package/package.json +84 -0
  108. package/packages/pilothub/LICENSE +22 -0
  109. package/packages/pilothub/README.md +57 -0
  110. package/packages/pilothub/bin/pilothub.js +2 -0
  111. package/packages/pilothub/package.json +41 -0
  112. package/packages/pilothub/src/browserAuth.test.ts +96 -0
  113. package/packages/pilothub/src/browserAuth.ts +174 -0
  114. package/packages/pilothub/src/cli/buildInfo.ts +94 -0
  115. package/packages/pilothub/src/cli/commands/auth.ts +97 -0
  116. package/packages/pilothub/src/cli/commands/delete.test.ts +73 -0
  117. package/packages/pilothub/src/cli/commands/delete.ts +83 -0
  118. package/packages/pilothub/src/cli/commands/publish.test.ts +122 -0
  119. package/packages/pilothub/src/cli/commands/publish.ts +108 -0
  120. package/packages/pilothub/src/cli/commands/skills.test.ts +191 -0
  121. package/packages/pilothub/src/cli/commands/skills.ts +380 -0
  122. package/packages/pilothub/src/cli/commands/star.ts +46 -0
  123. package/packages/pilothub/src/cli/commands/sync.test.ts +310 -0
  124. package/packages/pilothub/src/cli/commands/sync.ts +200 -0
  125. package/packages/pilothub/src/cli/commands/syncHelpers.test.ts +26 -0
  126. package/packages/pilothub/src/cli/commands/syncHelpers.ts +427 -0
  127. package/packages/pilothub/src/cli/commands/syncTypes.ts +27 -0
  128. package/packages/pilothub/src/cli/commands/unstar.ts +48 -0
  129. package/packages/pilothub/src/cli/helpStyle.ts +45 -0
  130. package/packages/pilothub/src/cli/pilotbotConfig.test.ts +159 -0
  131. package/packages/pilothub/src/cli/pilotbotConfig.ts +147 -0
  132. package/packages/pilothub/src/cli/registry.test.ts +63 -0
  133. package/packages/pilothub/src/cli/registry.ts +43 -0
  134. package/packages/pilothub/src/cli/scanSkills.test.ts +64 -0
  135. package/packages/pilothub/src/cli/scanSkills.ts +84 -0
  136. package/packages/pilothub/src/cli/slug.ts +16 -0
  137. package/packages/pilothub/src/cli/types.ts +12 -0
  138. package/packages/pilothub/src/cli/ui.ts +75 -0
  139. package/packages/pilothub/src/cli.ts +311 -0
  140. package/packages/pilothub/src/config.ts +36 -0
  141. package/packages/pilothub/src/discovery.test.ts +75 -0
  142. package/packages/pilothub/src/discovery.ts +19 -0
  143. package/packages/pilothub/src/http.test.ts +156 -0
  144. package/packages/pilothub/src/http.ts +301 -0
  145. package/packages/pilothub/src/schema/ark.ts +29 -0
  146. package/packages/pilothub/src/schema/index.ts +5 -0
  147. package/packages/pilothub/src/schema/routes.ts +22 -0
  148. package/packages/pilothub/src/schema/schemas.ts +260 -0
  149. package/packages/pilothub/src/schema/textFiles.test.ts +23 -0
  150. package/packages/pilothub/src/schema/textFiles.ts +66 -0
  151. package/packages/pilothub/src/skills.test.ts +191 -0
  152. package/packages/pilothub/src/skills.ts +172 -0
  153. package/packages/pilothub/src/types.ts +10 -0
  154. package/packages/pilothub/tsconfig.json +14 -0
  155. package/packages/schema/README.md +3 -0
  156. package/packages/schema/dist/ark.d.ts +4 -0
  157. package/packages/schema/dist/ark.js +26 -0
  158. package/packages/schema/dist/ark.js.map +1 -0
  159. package/packages/schema/dist/index.d.ts +5 -0
  160. package/packages/schema/dist/index.js +5 -0
  161. package/packages/schema/dist/index.js.map +1 -0
  162. package/packages/schema/dist/routes.d.ts +21 -0
  163. package/packages/schema/dist/routes.js +22 -0
  164. package/packages/schema/dist/routes.js.map +1 -0
  165. package/packages/schema/dist/schemas.d.ts +297 -0
  166. package/packages/schema/dist/schemas.js +243 -0
  167. package/packages/schema/dist/schemas.js.map +1 -0
  168. package/packages/schema/dist/textFiles.d.ts +5 -0
  169. package/packages/schema/dist/textFiles.js +66 -0
  170. package/packages/schema/dist/textFiles.js.map +1 -0
  171. package/packages/schema/package.json +26 -0
  172. package/packages/schema/src/ark.ts +29 -0
  173. package/packages/schema/src/index.ts +5 -0
  174. package/packages/schema/src/routes.ts +22 -0
  175. package/packages/schema/src/schemas.test.ts +123 -0
  176. package/packages/schema/src/schemas.ts +287 -0
  177. package/packages/schema/src/textFiles.test.ts +23 -0
  178. package/packages/schema/src/textFiles.ts +66 -0
  179. package/packages/schema/tsconfig.json +15 -0
  180. package/pilothub +46 -0
  181. package/playwright.config.ts +33 -0
  182. package/public/.well-known/pilothub.json +6 -0
  183. package/public/api/v1/openapi.json +379 -0
  184. package/public/favicon.ico +0 -0
  185. package/public/logo192.png +0 -0
  186. package/public/logo512.png +0 -0
  187. package/public/manifest.json +25 -0
  188. package/public/og.png +0 -0
  189. package/public/og.svg +98 -0
  190. package/public/pilot-logo.png +0 -0
  191. package/public/pilot-mark.png +0 -0
  192. package/public/robots.txt +3 -0
  193. package/public/tanstack-circle-logo.png +0 -0
  194. package/public/tanstack-word-logo-white.svg +1 -0
  195. package/scripts/check-peer-deps.ts +56 -0
  196. package/scripts/docs-list.ts +148 -0
  197. package/scripts/run-playwright-local.sh +14 -0
  198. package/server/og/fetchSkillOgMeta.ts +27 -0
  199. package/server/og/fetchSoulOgMeta.ts +27 -0
  200. package/server/og/ogAssets.ts +80 -0
  201. package/server/og/skillOgSvg.test.ts +59 -0
  202. package/server/og/skillOgSvg.ts +258 -0
  203. package/server/og/soulOgSvg.ts +209 -0
  204. package/server/routes/og/skill.png.ts +103 -0
  205. package/server/routes/og/soul.png.ts +111 -0
  206. package/src/__tests__/skill-detail-page.test.tsx +86 -0
  207. package/src/__tests__/skills-index.test.tsx +145 -0
  208. package/src/__tests__/upload.route.test.tsx +228 -0
  209. package/src/components/AppProviders.tsx +19 -0
  210. package/src/components/ClientOnly.tsx +18 -0
  211. package/src/components/Footer.tsx +29 -0
  212. package/src/components/Header.tsx +295 -0
  213. package/src/components/InstallSwitcher.tsx +53 -0
  214. package/src/components/SkillCard.tsx +36 -0
  215. package/src/components/SkillDetailPage.tsx +817 -0
  216. package/src/components/SkillDiffCard.tsx +485 -0
  217. package/src/components/SoulCard.tsx +19 -0
  218. package/src/components/SoulDetailPage.tsx +263 -0
  219. package/src/components/UserBootstrap.tsx +18 -0
  220. package/src/components/ui/dropdown-menu.tsx +67 -0
  221. package/src/components/ui/toggle-group.tsx +35 -0
  222. package/src/convex/client.ts +3 -0
  223. package/src/lib/badges.ts +29 -0
  224. package/src/lib/diffing.test.ts +163 -0
  225. package/src/lib/diffing.ts +106 -0
  226. package/src/lib/gravatar.test.ts +9 -0
  227. package/src/lib/gravatar.ts +158 -0
  228. package/src/lib/og.test.ts +142 -0
  229. package/src/lib/og.ts +156 -0
  230. package/src/lib/publicUser.ts +39 -0
  231. package/src/lib/roles.ts +19 -0
  232. package/src/lib/site.test.ts +130 -0
  233. package/src/lib/site.ts +84 -0
  234. package/src/lib/theme-transition.test.ts +134 -0
  235. package/src/lib/theme-transition.ts +134 -0
  236. package/src/lib/theme.test.tsx +88 -0
  237. package/src/lib/theme.ts +43 -0
  238. package/src/lib/uploadFiles.jsdom.test.ts +33 -0
  239. package/src/lib/uploadFiles.test.ts +123 -0
  240. package/src/lib/uploadFiles.ts +245 -0
  241. package/src/lib/uploadUtils.test.ts +78 -0
  242. package/src/lib/uploadUtils.ts +93 -0
  243. package/src/lib/useAuthStatus.ts +12 -0
  244. package/src/lib/utils.test.ts +9 -0
  245. package/src/lib/utils.ts +6 -0
  246. package/src/logo.svg +12 -0
  247. package/src/routeTree.gen.ts +345 -0
  248. package/src/router.tsx +17 -0
  249. package/src/routes/$owner/$slug.tsx +55 -0
  250. package/src/routes/__root.tsx +136 -0
  251. package/src/routes/admin.tsx +11 -0
  252. package/src/routes/cli/auth.tsx +168 -0
  253. package/src/routes/dashboard.tsx +97 -0
  254. package/src/routes/import.tsx +415 -0
  255. package/src/routes/index.tsx +252 -0
  256. package/src/routes/management.tsx +529 -0
  257. package/src/routes/settings.tsx +203 -0
  258. package/src/routes/skills/index.tsx +422 -0
  259. package/src/routes/souls/$slug.tsx +55 -0
  260. package/src/routes/souls/index.tsx +243 -0
  261. package/src/routes/stars.tsx +68 -0
  262. package/src/routes/u/$handle.tsx +307 -0
  263. package/src/routes/upload/utils.ts +81 -0
  264. package/src/routes/upload.tsx +499 -0
  265. package/src/styles.css +2718 -0
  266. package/tsconfig.json +24 -0
  267. package/tsconfig.oxlint.json +16 -0
  268. package/vercel.json +8 -0
  269. package/vite.config.ts +48 -0
  270. package/vitest.config.ts +47 -0
  271. package/vitest.e2e.config.ts +11 -0
  272. package/vitest.setup.ts +1 -0
@@ -0,0 +1,88 @@
1
+ import { v } from 'convex/values'
2
+ import type { Doc } from './_generated/dataModel'
3
+ import { internalMutation, internalQuery, mutation, query } from './_generated/server'
4
+ import { requireUser } from './lib/access'
5
+ import { generateToken, hashToken } from './lib/tokens'
6
+
7
+ export const listMine = query({
8
+ args: {},
9
+ handler: async (ctx) => {
10
+ const { userId } = await requireUser(ctx)
11
+ const tokens = await ctx.db
12
+ .query('apiTokens')
13
+ .withIndex('by_user', (q) => q.eq('userId', userId))
14
+ .order('desc')
15
+ .take(100)
16
+ return tokens.map((token) => ({
17
+ _id: token._id,
18
+ label: token.label,
19
+ prefix: token.prefix,
20
+ createdAt: token.createdAt,
21
+ lastUsedAt: token.lastUsedAt,
22
+ revokedAt: token.revokedAt,
23
+ }))
24
+ },
25
+ })
26
+
27
+ export const create = mutation({
28
+ args: { label: v.string() },
29
+ handler: async (ctx, args) => {
30
+ const { userId } = await requireUser(ctx)
31
+ const label = args.label.trim() || 'CLI token'
32
+ const { token, prefix } = generateToken()
33
+ const tokenHash = await hashToken(token)
34
+
35
+ const now = Date.now()
36
+ const tokenId = await ctx.db.insert('apiTokens', {
37
+ userId,
38
+ label,
39
+ prefix,
40
+ tokenHash,
41
+ createdAt: now,
42
+ lastUsedAt: undefined,
43
+ revokedAt: undefined,
44
+ })
45
+
46
+ return { token, tokenId, label, prefix, createdAt: now }
47
+ },
48
+ })
49
+
50
+ export const revoke = mutation({
51
+ args: { tokenId: v.id('apiTokens') },
52
+ handler: async (ctx, args) => {
53
+ const { userId } = await requireUser(ctx)
54
+ const token = await ctx.db.get(args.tokenId)
55
+ if (!token) throw new Error('Token not found')
56
+ if (token.userId !== userId) throw new Error('Forbidden')
57
+ if (token.revokedAt) return
58
+ await ctx.db.patch(token._id, { revokedAt: Date.now() })
59
+ },
60
+ })
61
+
62
+ export const getByHashInternal = internalQuery({
63
+ args: { tokenHash: v.string() },
64
+ handler: async (ctx, args) => {
65
+ return ctx.db
66
+ .query('apiTokens')
67
+ .withIndex('by_hash', (q) => q.eq('tokenHash', args.tokenHash))
68
+ .unique()
69
+ },
70
+ })
71
+
72
+ export const touchInternal = internalMutation({
73
+ args: { tokenId: v.id('apiTokens') },
74
+ handler: async (ctx, args) => {
75
+ const token = await ctx.db.get(args.tokenId)
76
+ if (!token || token.revokedAt) return
77
+ await ctx.db.patch(token._id, { lastUsedAt: Date.now() })
78
+ },
79
+ })
80
+
81
+ export const getUserForTokenInternal = internalQuery({
82
+ args: { tokenId: v.id('apiTokens') },
83
+ handler: async (ctx, args): Promise<Doc<'users'> | null> => {
84
+ const token = await ctx.db.get(args.tokenId)
85
+ if (!token || token.revokedAt) return null
86
+ return ctx.db.get(token.userId)
87
+ },
88
+ })
@@ -0,0 +1,7 @@
1
+ {
2
+ "extends": "../tsconfig.json",
3
+ "compilerOptions": {
4
+ "moduleResolution": "Bundler",
5
+ "skipLibCheck": true
6
+ }
7
+ }
@@ -0,0 +1,20 @@
1
+ import { v } from 'convex/values'
2
+ import { internalMutation, mutation } from './_generated/server'
3
+ import { requireUser } from './lib/access'
4
+
5
+ export const generateUploadUrl = mutation({
6
+ args: {},
7
+ handler: async (ctx) => {
8
+ await requireUser(ctx)
9
+ return ctx.storage.generateUploadUrl()
10
+ },
11
+ })
12
+
13
+ export const generateUploadUrlForUserInternal = internalMutation({
14
+ args: { userId: v.id('users') },
15
+ handler: async (ctx, args) => {
16
+ const user = await ctx.db.get(args.userId)
17
+ if (!user || user.deletedAt) throw new Error('User not found')
18
+ return ctx.storage.generateUploadUrl()
19
+ },
20
+ })
@@ -0,0 +1,122 @@
1
+ import { getAuthUserId } from '@convex-dev/auth/server'
2
+ import { v } from 'convex/values'
3
+ import { internal } from './_generated/api'
4
+ import { internalQuery, mutation, query } from './_generated/server'
5
+ import { assertAdmin, requireUser } from './lib/access'
6
+ import { toPublicUser } from './lib/public'
7
+
8
+ const DEFAULT_ROLE = 'user'
9
+ const ADMIN_HANDLE = 'steipete'
10
+
11
+ export const getById = query({
12
+ args: { userId: v.id('users') },
13
+ handler: async (ctx, args) => toPublicUser(await ctx.db.get(args.userId)),
14
+ })
15
+
16
+ export const getByIdInternal = internalQuery({
17
+ args: { userId: v.id('users') },
18
+ handler: async (ctx, args) => ctx.db.get(args.userId),
19
+ })
20
+
21
+ export const me = query({
22
+ args: {},
23
+ handler: async (ctx) => {
24
+ const userId = await getAuthUserId(ctx)
25
+ if (!userId) return null
26
+ const user = await ctx.db.get(userId)
27
+ if (!user || user.deletedAt) return null
28
+ return user
29
+ },
30
+ })
31
+
32
+ export const ensure = mutation({
33
+ args: {},
34
+ handler: async (ctx) => {
35
+ const { userId, user } = await requireUser(ctx)
36
+ const now = Date.now()
37
+ const updates: Record<string, unknown> = {}
38
+
39
+ const handle = user.handle ?? user.name ?? user.email?.split('@')[0]
40
+ if (!user.handle && handle) updates.handle = handle
41
+ if (!user.displayName) updates.displayName = handle
42
+ if (!user.role) {
43
+ updates.role = handle === ADMIN_HANDLE ? 'admin' : DEFAULT_ROLE
44
+ }
45
+ if (!user.createdAt) updates.createdAt = user._creationTime
46
+ updates.updatedAt = now
47
+
48
+ if (Object.keys(updates).length > 0) {
49
+ await ctx.db.patch(userId, updates)
50
+ }
51
+
52
+ return ctx.db.get(userId)
53
+ },
54
+ })
55
+
56
+ export const updateProfile = mutation({
57
+ args: {
58
+ displayName: v.string(),
59
+ bio: v.optional(v.string()),
60
+ },
61
+ handler: async (ctx, args) => {
62
+ const { userId } = await requireUser(ctx)
63
+ await ctx.db.patch(userId, {
64
+ displayName: args.displayName.trim(),
65
+ bio: args.bio?.trim(),
66
+ updatedAt: Date.now(),
67
+ })
68
+ },
69
+ })
70
+
71
+ export const deleteAccount = mutation({
72
+ args: {},
73
+ handler: async (ctx) => {
74
+ const { userId } = await requireUser(ctx)
75
+ await ctx.db.patch(userId, {
76
+ deletedAt: Date.now(),
77
+ updatedAt: Date.now(),
78
+ })
79
+ await ctx.runMutation(internal.telemetry.clearUserTelemetryInternal, { userId })
80
+ },
81
+ })
82
+
83
+ export const list = query({
84
+ args: { limit: v.optional(v.number()) },
85
+ handler: async (ctx, args) => {
86
+ const { user } = await requireUser(ctx)
87
+ assertAdmin(user)
88
+ const limit = args.limit ?? 50
89
+ return ctx.db.query('users').order('desc').take(limit)
90
+ },
91
+ })
92
+
93
+ export const getByHandle = query({
94
+ args: { handle: v.string() },
95
+ handler: async (ctx, args) => {
96
+ const user = await ctx.db
97
+ .query('users')
98
+ .withIndex('handle', (q) => q.eq('handle', args.handle))
99
+ .unique()
100
+ return toPublicUser(user)
101
+ },
102
+ })
103
+
104
+ export const setRole = mutation({
105
+ args: {
106
+ userId: v.id('users'),
107
+ role: v.union(v.literal('admin'), v.literal('moderator'), v.literal('user')),
108
+ },
109
+ handler: async (ctx, args) => {
110
+ const { user } = await requireUser(ctx)
111
+ assertAdmin(user)
112
+ await ctx.db.patch(args.userId, { role: args.role, updatedAt: Date.now() })
113
+ await ctx.db.insert('auditLogs', {
114
+ actorUserId: user._id,
115
+ action: 'role.change',
116
+ targetType: 'user',
117
+ targetId: args.userId,
118
+ metadata: { role: args.role },
119
+ createdAt: Date.now(),
120
+ })
121
+ },
122
+ })
@@ -0,0 +1,50 @@
1
+ import { v } from 'convex/values'
2
+ import { internalAction } from './_generated/server'
3
+ import { buildDiscordPayload, getWebhookConfig, shouldSendWebhook } from './lib/webhooks'
4
+
5
+ export const sendDiscordWebhook = internalAction({
6
+ args: {
7
+ event: v.union(v.literal('skill.publish'), v.literal('skill.highlighted')),
8
+ skill: v.object({
9
+ slug: v.string(),
10
+ displayName: v.string(),
11
+ summary: v.optional(v.string()),
12
+ version: v.optional(v.string()),
13
+ ownerHandle: v.optional(v.string()),
14
+ highlighted: v.optional(v.boolean()),
15
+ tags: v.optional(v.array(v.string())),
16
+ }),
17
+ },
18
+ handler: async (_ctx, args) => {
19
+ const config = getWebhookConfig()
20
+ const logMeta = {
21
+ event: args.event,
22
+ slug: args.skill.slug,
23
+ version: args.skill.version ?? null,
24
+ highlighted: args.skill.highlighted ?? false,
25
+ highlightedOnly: config.highlightedOnly,
26
+ }
27
+ if (!shouldSendWebhook(args.event, args.skill, config)) {
28
+ console.info('[webhook] skipped', logMeta)
29
+ return { ok: false, skipped: true }
30
+ }
31
+
32
+ const payload = buildDiscordPayload(args.event, args.skill, config)
33
+ const response = await fetch(config.url as string, {
34
+ method: 'POST',
35
+ headers: { 'Content-Type': 'application/json' },
36
+ body: JSON.stringify(payload),
37
+ })
38
+ if (!response.ok) {
39
+ const message = await response.text()
40
+ console.error('[webhook] failed', {
41
+ ...logMeta,
42
+ status: response.status,
43
+ body: message.slice(0, 300),
44
+ })
45
+ throw new Error(`Discord webhook failed: ${response.status} ${message}`)
46
+ }
47
+ console.info('[webhook] sent', { ...logMeta, status: response.status })
48
+ return { ok: true }
49
+ },
50
+ })
package/convex.json ADDED
@@ -0,0 +1,3 @@
1
+ {
2
+ "functions": "convex"
3
+ }
package/docs/README.md ADDED
@@ -0,0 +1,32 @@
1
+ ---
2
+ summary: 'Documentation index + reading order.'
3
+ read_when:
4
+ - New contributor onboarding
5
+ - Looking for the right doc
6
+ ---
7
+
8
+ # Docs
9
+
10
+ Reading order (new contributor):
11
+
12
+ 1. `README.md` (repo root): run locally.
13
+ 2. `docs/quickstart.md`: end-to-end: search → install → publish → sync.
14
+ 3. `docs/architecture.md`: how the pieces fit (TanStack Start + Convex + CLI).
15
+ 4. `docs/skill-format.md`: what a “skill” is on disk + on the registry.
16
+ 5. `docs/cli.md`: CLI reference (flags, config, lockfiles, sync rules).
17
+ 6. `docs/http-api.md`: HTTP endpoints used by the CLI + public API.
18
+ 7. `docs/auth.md`: GitHub OAuth + API tokens + CLI loopback login.
19
+ 8. `docs/deploy.md`: Convex + Vercel deployment + rewrites.
20
+ 9. `docs/troubleshooting.md`: common failure modes.
21
+
22
+ Feature/ops docs (already present):
23
+
24
+ - `docs/spec.md`: product + implementation spec (data model + flows).
25
+ - `docs/telemetry.md`: what `pilothub sync` reports; opt-out.
26
+ - `docs/webhook.md`: Discord webhook events/payload.
27
+ - `docs/diffing.md`: version-to-version diff UI spec.
28
+ - `docs/manual-testing.md`: CLI smoke scripts.
29
+
30
+ Docs tooling:
31
+
32
+ - `docs/mintlify.md`: publish these docs with Mintlify.
package/docs/api.md ADDED
@@ -0,0 +1,51 @@
1
+ ---
2
+ summary: 'Public REST API (v1) overview and conventions.'
3
+ read_when:
4
+ - Building API clients
5
+ - Adding endpoints or schemas
6
+ ---
7
+
8
+ # API v1
9
+
10
+ Base: `https://pilothub.com`
11
+
12
+ OpenAPI: `/api/v1/openapi.json`
13
+
14
+ ## Auth
15
+
16
+ - Public read: no token required.
17
+ - Write + account: `Authorization: Bearer clh_...`.
18
+
19
+ ## Rate limits
20
+
21
+ Per IP + per API key:
22
+
23
+ - Read: 120/min per IP, 600/min per key
24
+ - Write: 30/min per IP, 120/min per key
25
+
26
+ Headers: `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset`, `Retry-After` (on 429).
27
+
28
+ ## Endpoints
29
+
30
+ Public read:
31
+
32
+ - `GET /api/v1/search?q=...`
33
+ - `GET /api/v1/skills?limit=&cursor=&sort=`
34
+ - `sort`: `updated` (default), `downloads`, `stars` (`rating`), `installsCurrent` (`installs`), `installsAllTime`, `trending`
35
+ - `GET /api/v1/skills/{slug}`
36
+ - `GET /api/v1/skills/{slug}/versions?limit=&cursor=`
37
+ - `GET /api/v1/skills/{slug}/versions/{version}`
38
+ - `GET /api/v1/skills/{slug}/file?path=&version=&tag=`
39
+ - `GET /api/v1/resolve?slug=&hash=`
40
+ - `GET /api/v1/download?slug=&version=&tag=`
41
+
42
+ Auth required:
43
+
44
+ - `POST /api/v1/skills` (publish, multipart preferred)
45
+ - `DELETE /api/v1/skills/{slug}`
46
+ - `POST /api/v1/skills/{slug}/undelete`
47
+ - `GET /api/v1/whoami`
48
+
49
+ ## Legacy
50
+
51
+ Legacy `/api/*` and `/api/cli/*` still available. See `DEPRECATIONS.md`.
@@ -0,0 +1,61 @@
1
+ ---
2
+ summary: 'System overview: web app + Convex backend + CLI + shared schema.'
3
+ read_when:
4
+ - Orienting in codebase
5
+ - Tracing a user flow across layers
6
+ ---
7
+
8
+ # Architecture
9
+
10
+ ## Pieces
11
+
12
+ - Web app: TanStack Start (React) under `src/`.
13
+ - Backend: Convex under `convex/` (DB, storage, actions, HTTP routes).
14
+ - CLI: `packages/pilothub/` (published as `pilothub`).
15
+ - Shared schemas/routes: `packages/schema/` (`pilothub-schema`).
16
+
17
+ ## Data + storage
18
+
19
+ - Skill “bundle” = versioned set of text files stored in Convex `_storage`.
20
+ - Metadata extracted from `SKILL.md` frontmatter.
21
+ - Stats stored on `skills` (downloads, installs, stars, comments, …).
22
+
23
+ ## Main flows
24
+
25
+ ### Browse (web)
26
+
27
+ - UI reads skill metadata + latest version from Convex queries/actions.
28
+ - `SKILL.md` rendered as Markdown.
29
+
30
+ ### Search (HTTP)
31
+
32
+ - `/api/v1/search?q=...` routes to Convex action for vector search.
33
+ - Embeddings currently generated during publish.
34
+
35
+ ### Install (CLI)
36
+
37
+ - Resolve latest version via `/api/v1/skills/<slug>`.
38
+ - Download zip via `/api/v1/download?slug=...&version=...`.
39
+ - Extract into `./skills/<slug>` (default).
40
+ - Persist install state:
41
+ - `./.pilothub/lock.json` (per workdir)
42
+ - `./skills/<slug>/.pilothub/origin.json` (per skill folder)
43
+
44
+ ### Update (CLI)
45
+
46
+ - Hash local files, call `/api/v1/resolve?slug=...&hash=<sha256>`.
47
+ - If local matches a known version → use that for “current”.
48
+ - If local doesn’t match:
49
+ - refuse by default
50
+ - or overwrite with `--force`
51
+
52
+ ### Publish (CLI)
53
+
54
+ - Publish via `POST /api/v1/skills` (multipart; requires Bearer token).
55
+
56
+ ### Sync (CLI)
57
+
58
+ - Scan roots for skill folders (contain `SKILL.md`).
59
+ - Compute fingerprint; compare to registry state.
60
+ - Optionally reports telemetry (see `docs/telemetry.md`).
61
+ - Publishes new/changed skills (skips modified installed skills inside install root).
package/docs/auth.md ADDED
@@ -0,0 +1,54 @@
1
+ ---
2
+ summary: 'Auth overview: GitHub OAuth (web) + API tokens (CLI).'
3
+ read_when:
4
+ - Working on login/token flows
5
+ - Debugging 401s
6
+ ---
7
+
8
+ # Auth
9
+
10
+ ## Web auth (GitHub OAuth)
11
+
12
+ - Convex Auth + GitHub OAuth App.
13
+ - Env vars:
14
+ - `AUTH_GITHUB_ID`
15
+ - `AUTH_GITHUB_SECRET`
16
+ - `CONVEX_SITE_URL` (used by auth config)
17
+
18
+ Local setup steps are in the repo root `README.md`.
19
+
20
+ ## API tokens (CLI)
21
+
22
+ The CLI uses a long-lived API token (Bearer token) for publish/sync/delete.
23
+
24
+ ### Browser flow (default)
25
+
26
+ `pilothub login` does:
27
+
28
+ 1. Starts a loopback HTTP server on `127.0.0.1` (random port).
29
+ 2. Opens `<site>/cli/auth?redirect_uri=http://127.0.0.1:<port>/callback&state=...`.
30
+ 3. Web UI requires GitHub login, then creates a token and redirects back to the loopback server.
31
+ 4. CLI stores the token in the global config file.
32
+
33
+ ### Headless flow
34
+
35
+ Create a token in the web UI (Settings → API tokens) and paste it:
36
+
37
+ ```bash
38
+ pilothub login --token clh_...
39
+ ```
40
+
41
+ ### Token storage
42
+
43
+ Default global config path:
44
+
45
+ - macOS: `~/Library/Application Support/pilothub/config.json`
46
+
47
+ Override:
48
+
49
+ - `PILOTHUB_CONFIG_PATH=/path/to/config.json`
50
+
51
+ ### Revocation
52
+
53
+ - Tokens can be revoked in the web UI.
54
+ - Revoked tokens return `401 Unauthorized` on CLI endpoints.
package/docs/cli.md ADDED
@@ -0,0 +1,117 @@
1
+ ---
2
+ summary: 'CLI reference: commands, flags, config, lockfile, sync behavior.'
3
+ read_when:
4
+ - Working on CLI behavior
5
+ - Debugging install/update/sync
6
+ ---
7
+
8
+ # CLI
9
+
10
+ CLI package: `packages/pilothub/` (bin: `pilothub`).
11
+
12
+ From this repo you can run it via the wrapper script:
13
+
14
+ ```bash
15
+ bun pilothub --help
16
+ ```
17
+
18
+ ## Global flags
19
+
20
+ - `--workdir <dir>`: working directory (default: cwd; falls back to Pilotbot workspace if configured)
21
+ - `--dir <dir>`: install dir under workdir (default: `skills`)
22
+ - `--site <url>`: base URL for browser login (default: `https://pilothub.com`)
23
+ - `--registry <url>`: API base URL (default: discovered, else `https://pilothub.com`)
24
+ - `--no-input`: disable prompts
25
+
26
+ Env equivalents:
27
+
28
+ - `PILOTHUB_SITE`
29
+ - `PILOTHUB_REGISTRY`
30
+ - `PILOTHUB_WORKDIR`
31
+
32
+ ## Config file
33
+
34
+ Stores your API token + cached registry URL.
35
+
36
+ - macOS: `~/Library/Application Support/pilothub/config.json`
37
+ - override: `PILOTHUB_CONFIG_PATH`
38
+
39
+ ## Commands
40
+
41
+ ### `login` / `auth login`
42
+
43
+ - Default: opens browser to `<site>/cli/auth` and completes via loopback callback.
44
+ - Headless: `pilothub login --token clh_...`
45
+
46
+ ### `whoami`
47
+
48
+ - Verifies the stored token via `/api/v1/whoami`.
49
+
50
+ ### `star <slug>` / `unstar <slug>`
51
+
52
+ - Adds/removes a skill from your highlights.
53
+ - Calls `POST /api/v1/stars/<slug>` and `DELETE /api/v1/stars/<slug>`.
54
+ - `--yes` skips confirmation.
55
+
56
+ ### `search <query...>`
57
+
58
+ - Calls `/api/v1/search?q=...`.
59
+
60
+ ### `explore`
61
+
62
+ - Lists latest updated skills via `/api/v1/skills?limit=...` (sorted by `updatedAt` desc).
63
+ - Flags:
64
+ - `--limit <n>` (1–200, default: 25)
65
+ - `--sort newest|downloads|rating|installs|installsAllTime|trending` (default: newest)
66
+ - `--json` (machine-readable output)
67
+ - Output: `<slug> v<version> <age> <summary>` (summary truncated to 50 chars).
68
+
69
+ ### `install <slug>`
70
+
71
+ - Resolves latest version via `/api/v1/skills/<slug>`.
72
+ - Downloads zip via `/api/v1/download`.
73
+ - Extracts into `<workdir>/<dir>/<slug>`.
74
+ - Writes:
75
+ - `<workdir>/.pilothub/lock.json`
76
+ - `<skill>/.pilothub/origin.json`
77
+
78
+ ### `list`
79
+
80
+ - Reads `<workdir>/.pilothub/lock.json`.
81
+
82
+ ### `update [slug]` / `update --all`
83
+
84
+ - Computes fingerprint from local files.
85
+ - If fingerprint matches a known version: no prompt.
86
+ - If fingerprint does not match:
87
+ - refuses by default
88
+ - overwrites with `--force` (or prompt, if interactive)
89
+
90
+ ### `publish <path>`
91
+
92
+ - Publishes via `POST /api/v1/skills` (multipart).
93
+ - Requires semver: `--version 1.2.3`.
94
+
95
+ ### `sync`
96
+
97
+ - Scans for local skill folders and publishes new/changed ones.
98
+ - Roots can be any folder: a skills directory or a single skill folder with `SKILL.md`.
99
+ - Auto-adds Pilotbot skill roots when `~/.pilotbot/pilotbot.json` is present:
100
+ - `agent.workspace/skills` (main agent)
101
+ - `routing.agents.*.workspace/skills` (per-agent)
102
+ - `~/.pilotbot/skills` (shared)
103
+ - `skills.load.extraDirs` (shared packs)
104
+ - Respects `PILOTBOT_CONFIG_PATH` and `PILOTBOT_STATE_DIR`.
105
+ - Flags:
106
+ - `--root <dir...>` extra scan roots
107
+ - `--all` upload without prompting
108
+ - `--dry-run` show plan only
109
+ - `--bump patch|minor|major` (default: patch)
110
+ - `--changelog <text>` (non-interactive)
111
+ - `--tags a,b,c` (default: latest)
112
+ - `--concurrency <n>` (default: 4)
113
+
114
+ Telemetry:
115
+
116
+ - Sent during `sync` when logged in, unless `PILOTHUB_DISABLE_TELEMETRY=1`.
117
+ - Details: `docs/telemetry.md`.
package/docs/deploy.md ADDED
@@ -0,0 +1,78 @@
1
+ ---
2
+ summary: 'Deploy checklist: Convex backend + Vercel web app + /api rewrites.'
3
+ read_when:
4
+ - Shipping to production
5
+ - Debugging /api routing
6
+ ---
7
+
8
+ # Deploy
9
+
10
+ PilotHub is two deployables:
11
+
12
+ - Web app (TanStack Start) → typically Vercel.
13
+ - Convex backend → Convex deployment (serves `/api/...` routes).
14
+
15
+ ## 1) Deploy Convex
16
+
17
+ From your local machine:
18
+
19
+ ```bash
20
+ bunx convex deploy
21
+ ```
22
+
23
+ Ensure Convex env is set (auth + embeddings):
24
+
25
+ - `AUTH_GITHUB_ID`
26
+ - `AUTH_GITHUB_SECRET`
27
+ - `CONVEX_SITE_URL`
28
+ - `JWT_PRIVATE_KEY`
29
+ - `JWKS`
30
+ - `OPENAI_API_KEY`
31
+ - `SITE_URL` (your web app URL)
32
+ - Optional webhook env (see `docs/webhook.md`)
33
+
34
+ ## 2) Deploy web app (Vercel)
35
+
36
+ Set env vars:
37
+
38
+ - `VITE_CONVEX_URL`
39
+ - `VITE_CONVEX_SITE_URL` (Convex “site” URL)
40
+ - `CONVEX_SITE_URL` (same value; used by auth provider config)
41
+ - `SITE_URL` (web app URL)
42
+
43
+ ## 3) Route `/api/*` to Convex
44
+
45
+ This repo currently uses `vercel.json` rewrites:
46
+
47
+ - `source: /api/:path*`
48
+ - `destination: https://<deployment>.convex.site/api/:path*`
49
+
50
+ For self-host:
51
+
52
+ - update `vercel.json` to your deployment’s Convex site URL.
53
+
54
+ ## 4) Registry discovery
55
+
56
+ The CLI can discover the API base from:
57
+
58
+ - `/.well-known/pilothub.json`
59
+
60
+ If you don’t serve that file, users must set:
61
+
62
+ ```bash
63
+ export PILOTHUB_REGISTRY=https://your-site.example
64
+ ```
65
+
66
+ ## 5) Post-deploy checks
67
+
68
+ ```bash
69
+ curl -i "https://<site>/api/v1/search?q=test"
70
+ curl -i "https://<site>/api/v1/skills/gifgrep"
71
+ ```
72
+
73
+ Then:
74
+
75
+ ```bash
76
+ pilothub login --site https://<site>
77
+ pilothub whoami
78
+ ```