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.
- package/LICENSE +1 -0
- package/README.md +36 -129
- package/dist/browserAuth.d.ts +20 -0
- package/dist/browserAuth.js +156 -0
- package/dist/browserAuth.js.map +1 -0
- package/dist/browserAuth.test.d.ts +1 -0
- package/dist/browserAuth.test.js +83 -0
- package/dist/browserAuth.test.js.map +1 -0
- package/dist/cli/buildInfo.d.ts +3 -0
- package/dist/cli/buildInfo.js +103 -0
- package/dist/cli/buildInfo.js.map +1 -0
- package/dist/cli/commands/auth.d.ts +9 -0
- package/dist/cli/commands/auth.js +75 -0
- package/dist/cli/commands/auth.js.map +1 -0
- package/dist/cli/commands/delete.d.ts +11 -0
- package/dist/cli/commands/delete.js +67 -0
- package/dist/cli/commands/delete.js.map +1 -0
- package/dist/cli/commands/delete.test.d.ts +1 -0
- package/dist/cli/commands/delete.test.js +52 -0
- package/dist/cli/commands/delete.test.js.map +1 -0
- package/dist/cli/commands/publish.d.ts +9 -0
- package/dist/cli/commands/publish.js +87 -0
- package/dist/cli/commands/publish.js.map +1 -0
- package/dist/cli/commands/publish.test.d.ts +1 -0
- package/dist/cli/commands/publish.test.js +104 -0
- package/dist/cli/commands/publish.test.js.map +1 -0
- package/dist/cli/commands/skills.d.ts +23 -0
- package/dist/cli/commands/skills.js +298 -0
- package/dist/cli/commands/skills.js.map +1 -0
- package/dist/cli/commands/skills.test.d.ts +1 -0
- package/dist/cli/commands/skills.test.js +156 -0
- package/dist/cli/commands/skills.test.js.map +1 -0
- package/dist/cli/commands/star.d.ts +8 -0
- package/dist/cli/commands/star.js +38 -0
- package/dist/cli/commands/star.js.map +1 -0
- package/dist/cli/commands/sync.d.ts +3 -0
- package/dist/cli/commands/sync.js +160 -0
- package/dist/cli/commands/sync.js.map +1 -0
- package/dist/cli/commands/sync.test.d.ts +1 -0
- package/dist/cli/commands/sync.test.js +277 -0
- package/dist/cli/commands/sync.test.js.map +1 -0
- package/dist/cli/commands/syncHelpers.d.ts +76 -0
- package/dist/cli/commands/syncHelpers.js +349 -0
- package/dist/cli/commands/syncHelpers.js.map +1 -0
- package/dist/cli/commands/syncHelpers.test.d.ts +1 -0
- package/dist/cli/commands/syncHelpers.test.js +22 -0
- package/dist/cli/commands/syncHelpers.test.js.map +1 -0
- package/dist/cli/commands/syncTypes.d.ts +24 -0
- package/dist/cli/commands/syncTypes.js +2 -0
- package/dist/cli/commands/syncTypes.js.map +1 -0
- package/dist/cli/commands/unstar.d.ts +8 -0
- package/dist/cli/commands/unstar.js +38 -0
- package/dist/cli/commands/unstar.js.map +1 -0
- package/dist/cli/helpStyle.d.ts +13 -0
- package/dist/cli/helpStyle.js +38 -0
- package/dist/cli/helpStyle.js.map +1 -0
- package/dist/cli/pilotbotConfig.d.ts +6 -0
- package/dist/cli/pilotbotConfig.js +110 -0
- package/dist/cli/pilotbotConfig.js.map +1 -0
- package/dist/cli/pilotbotConfig.test.d.ts +1 -0
- package/dist/cli/pilotbotConfig.test.js +133 -0
- package/dist/cli/pilotbotConfig.test.js.map +1 -0
- package/dist/cli/registry.d.ts +7 -0
- package/dist/cli/registry.js +42 -0
- package/dist/cli/registry.js.map +1 -0
- package/dist/cli/registry.test.d.ts +1 -0
- package/dist/cli/registry.test.js +48 -0
- package/dist/cli/registry.test.js.map +1 -0
- package/dist/cli/scanSkills.d.ts +7 -0
- package/dist/cli/scanSkills.js +75 -0
- package/dist/cli/scanSkills.js.map +1 -0
- package/dist/cli/scanSkills.test.d.ts +1 -0
- package/dist/cli/scanSkills.test.js +60 -0
- package/dist/cli/scanSkills.test.js.map +1 -0
- package/dist/cli/slug.d.ts +2 -0
- package/dist/cli/slug.js +16 -0
- package/dist/cli/slug.js.map +1 -0
- package/dist/cli/types.d.ts +15 -0
- package/dist/cli/types.js +2 -0
- package/dist/cli/types.js.map +1 -0
- package/dist/cli/ui.d.ts +7 -0
- package/dist/cli/ui.js +72 -0
- package/dist/cli/ui.js.map +1 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +268 -0
- package/dist/cli.js.map +1 -0
- package/dist/config.d.ts +4 -0
- package/dist/config.js +38 -0
- package/dist/config.js.map +1 -0
- package/dist/discovery.d.ts +5 -0
- package/dist/discovery.js +21 -0
- package/dist/discovery.js.map +1 -0
- package/dist/discovery.test.d.ts +1 -0
- package/dist/discovery.test.js +46 -0
- package/dist/discovery.test.js.map +1 -0
- package/dist/http.d.ts +32 -0
- package/dist/http.js +261 -0
- package/dist/http.js.map +1 -0
- package/dist/http.test.d.ts +1 -0
- package/dist/http.test.js +135 -0
- package/dist/http.test.js.map +1 -0
- package/dist/schema/ark.js.map +1 -0
- package/dist/schema/index.js.map +1 -0
- package/dist/schema/routes.js.map +1 -0
- package/{packages/schema/dist → dist/schema}/schemas.d.ts +0 -39
- package/{packages/schema/dist → dist/schema}/schemas.js +0 -22
- package/dist/schema/schemas.js.map +1 -0
- package/dist/schema/textFiles.js.map +1 -0
- package/dist/schema/textFiles.test.d.ts +1 -0
- package/dist/schema/textFiles.test.js +20 -0
- package/dist/schema/textFiles.test.js.map +1 -0
- package/dist/skills.d.ts +43 -0
- package/dist/skills.js +163 -0
- package/dist/skills.js.map +1 -0
- package/dist/skills.test.d.ts +1 -0
- package/dist/skills.test.js +144 -0
- package/dist/skills.test.js.map +1 -0
- package/dist/types.d.ts +7 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +27 -70
- package/.env.local.example +0 -19
- package/.github/workflows/ci.yml +0 -40
- package/.oxlintrc.json +0 -3
- package/AGENTS.md +0 -45
- package/CHANGELOG.md +0 -138
- package/DEPRECATIONS.md +0 -7
- package/biome.json +0 -41
- package/convex/_generated/api.d.ts +0 -153
- package/convex/_generated/api.js +0 -23
- package/convex/_generated/dataModel.d.ts +0 -60
- package/convex/_generated/server.d.ts +0 -143
- package/convex/_generated/server.js +0 -93
- package/convex/auth.config.ts +0 -8
- package/convex/auth.ts +0 -19
- package/convex/comments.ts +0 -88
- package/convex/crons.ts +0 -34
- package/convex/devSeed.ts +0 -459
- package/convex/devSeedExtra.ts +0 -541
- package/convex/downloads.ts +0 -78
- package/convex/githubBackups.ts +0 -170
- package/convex/githubBackupsNode.ts +0 -183
- package/convex/githubImport.ts +0 -317
- package/convex/githubSoulBackups.ts +0 -170
- package/convex/githubSoulBackupsNode.ts +0 -186
- package/convex/http.ts +0 -194
- package/convex/httpApi.handlers.test.ts +0 -488
- package/convex/httpApi.test.ts +0 -70
- package/convex/httpApi.ts +0 -305
- package/convex/httpApiV1.handlers.test.ts +0 -584
- package/convex/httpApiV1.ts +0 -1172
- package/convex/leaderboards.ts +0 -39
- package/convex/lib/access.ts +0 -36
- package/convex/lib/apiTokenAuth.ts +0 -36
- package/convex/lib/badges.ts +0 -50
- package/convex/lib/changelog.test.ts +0 -34
- package/convex/lib/changelog.ts +0 -278
- package/convex/lib/embeddings.ts +0 -38
- package/convex/lib/githubBackup.ts +0 -443
- package/convex/lib/githubImport.test.ts +0 -247
- package/convex/lib/githubImport.ts +0 -425
- package/convex/lib/githubSoulBackup.ts +0 -443
- package/convex/lib/leaderboards.ts +0 -103
- package/convex/lib/moderation.ts +0 -42
- package/convex/lib/public.ts +0 -89
- package/convex/lib/searchText.test.ts +0 -46
- package/convex/lib/searchText.ts +0 -27
- package/convex/lib/skillBackfill.test.ts +0 -34
- package/convex/lib/skillBackfill.ts +0 -67
- package/convex/lib/skillPublish.test.ts +0 -28
- package/convex/lib/skillPublish.ts +0 -284
- package/convex/lib/skillStats.ts +0 -80
- package/convex/lib/skills.test.ts +0 -197
- package/convex/lib/skills.ts +0 -273
- package/convex/lib/soulChangelog.ts +0 -273
- package/convex/lib/soulPublish.ts +0 -236
- package/convex/lib/tokens.test.ts +0 -33
- package/convex/lib/tokens.ts +0 -51
- package/convex/lib/webhooks.test.ts +0 -91
- package/convex/lib/webhooks.ts +0 -112
- package/convex/maintenance.test.ts +0 -270
- package/convex/maintenance.ts +0 -840
- package/convex/rateLimits.ts +0 -50
- package/convex/schema.ts +0 -472
- package/convex/search.test.ts +0 -12
- package/convex/search.ts +0 -254
- package/convex/seed.test.ts +0 -37
- package/convex/seed.ts +0 -254
- package/convex/seedSouls.ts +0 -111
- package/convex/skillStatEvents.ts +0 -568
- package/convex/skills.ts +0 -1606
- package/convex/soulComments.ts +0 -88
- package/convex/soulDownloads.ts +0 -14
- package/convex/soulStars.ts +0 -71
- package/convex/souls.ts +0 -570
- package/convex/stars.ts +0 -108
- package/convex/statsMaintenance.ts +0 -205
- package/convex/telemetry.ts +0 -434
- package/convex/tokens.ts +0 -88
- package/convex/tsconfig.json +0 -7
- package/convex/uploads.ts +0 -20
- package/convex/users.ts +0 -122
- package/convex/webhooks.ts +0 -50
- package/convex.json +0 -3
- package/docs/README.md +0 -32
- package/docs/api.md +0 -51
- package/docs/architecture.md +0 -61
- package/docs/auth.md +0 -54
- package/docs/cli.md +0 -117
- package/docs/deploy.md +0 -78
- package/docs/diffing.md +0 -84
- package/docs/github-import.md +0 -171
- package/docs/http-api.md +0 -187
- package/docs/manual-testing.md +0 -64
- package/docs/mintlify.md +0 -43
- package/docs/quickstart.md +0 -120
- package/docs/skill-format.md +0 -58
- package/docs/soul-format.md +0 -37
- package/docs/spec.md +0 -177
- package/docs/telemetry.md +0 -91
- package/docs/troubleshooting.md +0 -49
- package/docs/webhook.md +0 -51
- package/e2e/menu-smoke.pw.test.ts +0 -49
- package/e2e/pilothub.e2e.test.ts +0 -494
- package/e2e/search-exact.pw.test.ts +0 -97
- package/packages/pilothub/LICENSE +0 -22
- package/packages/pilothub/README.md +0 -57
- package/packages/pilothub/package.json +0 -41
- package/packages/pilothub/src/browserAuth.test.ts +0 -96
- package/packages/pilothub/src/browserAuth.ts +0 -174
- package/packages/pilothub/src/cli/buildInfo.ts +0 -94
- package/packages/pilothub/src/cli/commands/auth.ts +0 -97
- package/packages/pilothub/src/cli/commands/delete.test.ts +0 -73
- package/packages/pilothub/src/cli/commands/delete.ts +0 -83
- package/packages/pilothub/src/cli/commands/publish.test.ts +0 -122
- package/packages/pilothub/src/cli/commands/publish.ts +0 -108
- package/packages/pilothub/src/cli/commands/skills.test.ts +0 -191
- package/packages/pilothub/src/cli/commands/skills.ts +0 -380
- package/packages/pilothub/src/cli/commands/star.ts +0 -46
- package/packages/pilothub/src/cli/commands/sync.test.ts +0 -310
- package/packages/pilothub/src/cli/commands/sync.ts +0 -200
- package/packages/pilothub/src/cli/commands/syncHelpers.test.ts +0 -26
- package/packages/pilothub/src/cli/commands/syncHelpers.ts +0 -427
- package/packages/pilothub/src/cli/commands/syncTypes.ts +0 -27
- package/packages/pilothub/src/cli/commands/unstar.ts +0 -48
- package/packages/pilothub/src/cli/helpStyle.ts +0 -45
- package/packages/pilothub/src/cli/pilotbotConfig.test.ts +0 -159
- package/packages/pilothub/src/cli/pilotbotConfig.ts +0 -147
- package/packages/pilothub/src/cli/registry.test.ts +0 -63
- package/packages/pilothub/src/cli/registry.ts +0 -43
- package/packages/pilothub/src/cli/scanSkills.test.ts +0 -64
- package/packages/pilothub/src/cli/scanSkills.ts +0 -84
- package/packages/pilothub/src/cli/slug.ts +0 -16
- package/packages/pilothub/src/cli/types.ts +0 -12
- package/packages/pilothub/src/cli/ui.ts +0 -75
- package/packages/pilothub/src/cli.ts +0 -311
- package/packages/pilothub/src/config.ts +0 -36
- package/packages/pilothub/src/discovery.test.ts +0 -75
- package/packages/pilothub/src/discovery.ts +0 -19
- package/packages/pilothub/src/http.test.ts +0 -156
- package/packages/pilothub/src/http.ts +0 -301
- package/packages/pilothub/src/schema/ark.ts +0 -29
- package/packages/pilothub/src/schema/index.ts +0 -5
- package/packages/pilothub/src/schema/routes.ts +0 -22
- package/packages/pilothub/src/schema/schemas.ts +0 -260
- package/packages/pilothub/src/schema/textFiles.test.ts +0 -23
- package/packages/pilothub/src/schema/textFiles.ts +0 -66
- package/packages/pilothub/src/skills.test.ts +0 -191
- package/packages/pilothub/src/skills.ts +0 -172
- package/packages/pilothub/src/types.ts +0 -10
- package/packages/pilothub/tsconfig.json +0 -14
- package/packages/schema/README.md +0 -3
- package/packages/schema/dist/ark.js.map +0 -1
- package/packages/schema/dist/index.js.map +0 -1
- package/packages/schema/dist/routes.js.map +0 -1
- package/packages/schema/dist/schemas.js.map +0 -1
- package/packages/schema/dist/textFiles.js.map +0 -1
- package/packages/schema/package.json +0 -26
- package/packages/schema/src/ark.ts +0 -29
- package/packages/schema/src/index.ts +0 -5
- package/packages/schema/src/routes.ts +0 -22
- package/packages/schema/src/schemas.test.ts +0 -123
- package/packages/schema/src/schemas.ts +0 -287
- package/packages/schema/src/textFiles.test.ts +0 -23
- package/packages/schema/src/textFiles.ts +0 -66
- package/packages/schema/tsconfig.json +0 -15
- package/pilothub +0 -46
- package/playwright.config.ts +0 -33
- package/public/.well-known/pilothub.json +0 -6
- package/public/api/v1/openapi.json +0 -379
- package/public/favicon.ico +0 -0
- package/public/logo192.png +0 -0
- package/public/logo512.png +0 -0
- package/public/manifest.json +0 -25
- package/public/og.png +0 -0
- package/public/og.svg +0 -98
- package/public/pilot-logo.png +0 -0
- package/public/pilot-mark.png +0 -0
- package/public/robots.txt +0 -3
- package/public/tanstack-circle-logo.png +0 -0
- package/public/tanstack-word-logo-white.svg +0 -1
- package/scripts/check-peer-deps.ts +0 -56
- package/scripts/docs-list.ts +0 -148
- package/scripts/run-playwright-local.sh +0 -14
- package/server/og/fetchSkillOgMeta.ts +0 -27
- package/server/og/fetchSoulOgMeta.ts +0 -27
- package/server/og/ogAssets.ts +0 -80
- package/server/og/skillOgSvg.test.ts +0 -59
- package/server/og/skillOgSvg.ts +0 -258
- package/server/og/soulOgSvg.ts +0 -209
- package/server/routes/og/skill.png.ts +0 -103
- package/server/routes/og/soul.png.ts +0 -111
- package/src/__tests__/skill-detail-page.test.tsx +0 -86
- package/src/__tests__/skills-index.test.tsx +0 -145
- package/src/__tests__/upload.route.test.tsx +0 -228
- package/src/components/AppProviders.tsx +0 -19
- package/src/components/ClientOnly.tsx +0 -18
- package/src/components/Footer.tsx +0 -29
- package/src/components/Header.tsx +0 -295
- package/src/components/InstallSwitcher.tsx +0 -53
- package/src/components/SkillCard.tsx +0 -36
- package/src/components/SkillDetailPage.tsx +0 -817
- package/src/components/SkillDiffCard.tsx +0 -485
- package/src/components/SoulCard.tsx +0 -19
- package/src/components/SoulDetailPage.tsx +0 -263
- package/src/components/UserBootstrap.tsx +0 -18
- package/src/components/ui/dropdown-menu.tsx +0 -67
- package/src/components/ui/toggle-group.tsx +0 -35
- package/src/convex/client.ts +0 -3
- package/src/lib/badges.ts +0 -29
- package/src/lib/diffing.test.ts +0 -163
- package/src/lib/diffing.ts +0 -106
- package/src/lib/gravatar.test.ts +0 -9
- package/src/lib/gravatar.ts +0 -158
- package/src/lib/og.test.ts +0 -142
- package/src/lib/og.ts +0 -156
- package/src/lib/publicUser.ts +0 -39
- package/src/lib/roles.ts +0 -19
- package/src/lib/site.test.ts +0 -130
- package/src/lib/site.ts +0 -84
- package/src/lib/theme-transition.test.ts +0 -134
- package/src/lib/theme-transition.ts +0 -134
- package/src/lib/theme.test.tsx +0 -88
- package/src/lib/theme.ts +0 -43
- package/src/lib/uploadFiles.jsdom.test.ts +0 -33
- package/src/lib/uploadFiles.test.ts +0 -123
- package/src/lib/uploadFiles.ts +0 -245
- package/src/lib/uploadUtils.test.ts +0 -78
- package/src/lib/uploadUtils.ts +0 -93
- package/src/lib/useAuthStatus.ts +0 -12
- package/src/lib/utils.test.ts +0 -9
- package/src/lib/utils.ts +0 -6
- package/src/logo.svg +0 -12
- package/src/routeTree.gen.ts +0 -345
- package/src/router.tsx +0 -17
- package/src/routes/$owner/$slug.tsx +0 -55
- package/src/routes/__root.tsx +0 -136
- package/src/routes/admin.tsx +0 -11
- package/src/routes/cli/auth.tsx +0 -168
- package/src/routes/dashboard.tsx +0 -97
- package/src/routes/import.tsx +0 -415
- package/src/routes/index.tsx +0 -252
- package/src/routes/management.tsx +0 -529
- package/src/routes/settings.tsx +0 -203
- package/src/routes/skills/index.tsx +0 -422
- package/src/routes/souls/$slug.tsx +0 -55
- package/src/routes/souls/index.tsx +0 -243
- package/src/routes/stars.tsx +0 -68
- package/src/routes/u/$handle.tsx +0 -307
- package/src/routes/upload/utils.ts +0 -81
- package/src/routes/upload.tsx +0 -499
- package/src/styles.css +0 -2718
- package/tsconfig.json +0 -24
- package/tsconfig.oxlint.json +0 -16
- package/vercel.json +0 -8
- package/vite.config.ts +0 -48
- package/vitest.config.ts +0 -47
- package/vitest.e2e.config.ts +0 -11
- package/vitest.setup.ts +0 -1
- /package/{packages/pilothub/bin → bin}/pilothub.js +0 -0
- /package/{packages/schema/dist → dist/schema}/ark.d.ts +0 -0
- /package/{packages/schema/dist → dist/schema}/ark.js +0 -0
- /package/{packages/schema/dist → dist/schema}/index.d.ts +0 -0
- /package/{packages/schema/dist → dist/schema}/index.js +0 -0
- /package/{packages/schema/dist → dist/schema}/routes.d.ts +0 -0
- /package/{packages/schema/dist → dist/schema}/routes.js +0 -0
- /package/{packages/schema/dist → dist/schema}/textFiles.d.ts +0 -0
- /package/{packages/schema/dist → dist/schema}/textFiles.js +0 -0
|
@@ -1,243 +0,0 @@
|
|
|
1
|
-
import { createFileRoute, Link } from '@tanstack/react-router'
|
|
2
|
-
import { useAction, useQuery } from 'convex/react'
|
|
3
|
-
import { useEffect, useMemo, useRef, useState } from 'react'
|
|
4
|
-
import { api } from '../../../convex/_generated/api'
|
|
5
|
-
import { SoulCard } from '../../components/SoulCard'
|
|
6
|
-
import type { PublicSoul } from '../../lib/publicUser'
|
|
7
|
-
|
|
8
|
-
const sortKeys = ['newest', 'downloads', 'stars', 'name', 'updated'] as const
|
|
9
|
-
type SortKey = (typeof sortKeys)[number]
|
|
10
|
-
type SortDir = 'asc' | 'desc'
|
|
11
|
-
|
|
12
|
-
function parseSort(value: unknown): SortKey {
|
|
13
|
-
if (typeof value !== 'string') return 'newest'
|
|
14
|
-
if ((sortKeys as readonly string[]).includes(value)) return value as SortKey
|
|
15
|
-
return 'newest'
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
function parseDir(value: unknown, sort: SortKey): SortDir {
|
|
19
|
-
if (value === 'asc' || value === 'desc') return value
|
|
20
|
-
return sort === 'name' ? 'asc' : 'desc'
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export const Route = createFileRoute('/souls/')({
|
|
24
|
-
validateSearch: (search) => {
|
|
25
|
-
return {
|
|
26
|
-
q: typeof search.q === 'string' && search.q.trim() ? search.q : undefined,
|
|
27
|
-
sort: typeof search.sort === 'string' ? parseSort(search.sort) : undefined,
|
|
28
|
-
dir: search.dir === 'asc' || search.dir === 'desc' ? search.dir : undefined,
|
|
29
|
-
view: search.view === 'cards' || search.view === 'list' ? search.view : undefined,
|
|
30
|
-
focus: search.focus === 'search' ? 'search' : undefined,
|
|
31
|
-
}
|
|
32
|
-
},
|
|
33
|
-
component: SoulsIndex,
|
|
34
|
-
})
|
|
35
|
-
|
|
36
|
-
function SoulsIndex() {
|
|
37
|
-
const navigate = Route.useNavigate()
|
|
38
|
-
const search = Route.useSearch()
|
|
39
|
-
const sort = search.sort ?? 'newest'
|
|
40
|
-
const dir = parseDir(search.dir, sort)
|
|
41
|
-
const view = search.view ?? 'list'
|
|
42
|
-
const [query, setQuery] = useState(search.q ?? '')
|
|
43
|
-
|
|
44
|
-
const souls = useQuery(api.souls.list, { limit: 500 }) as PublicSoul[] | undefined
|
|
45
|
-
const ensureSoulSeeds = useAction(api.seed.ensureSoulSeeds)
|
|
46
|
-
const seedEnsuredRef = useRef(false)
|
|
47
|
-
const searchInputRef = useRef<HTMLInputElement>(null)
|
|
48
|
-
const isLoadingSouls = souls === undefined
|
|
49
|
-
|
|
50
|
-
useEffect(() => {
|
|
51
|
-
setQuery(search.q ?? '')
|
|
52
|
-
}, [search.q])
|
|
53
|
-
|
|
54
|
-
// Auto-focus search input when focus=search param is present
|
|
55
|
-
useEffect(() => {
|
|
56
|
-
if (search.focus === 'search' && searchInputRef.current) {
|
|
57
|
-
searchInputRef.current.focus()
|
|
58
|
-
// Clear the focus param from URL to avoid re-focusing on navigation
|
|
59
|
-
void navigate({ search: (prev) => ({ ...prev, focus: undefined }), replace: true })
|
|
60
|
-
}
|
|
61
|
-
}, [search.focus, navigate])
|
|
62
|
-
|
|
63
|
-
useEffect(() => {
|
|
64
|
-
if (seedEnsuredRef.current) return
|
|
65
|
-
seedEnsuredRef.current = true
|
|
66
|
-
void ensureSoulSeeds({})
|
|
67
|
-
}, [ensureSoulSeeds])
|
|
68
|
-
|
|
69
|
-
const filtered = useMemo(() => {
|
|
70
|
-
const value = query.trim().toLowerCase()
|
|
71
|
-
const all = souls ?? []
|
|
72
|
-
if (!value) return all
|
|
73
|
-
return all.filter((soul) => {
|
|
74
|
-
if (soul.slug.toLowerCase().includes(value)) return true
|
|
75
|
-
if (soul.displayName.toLowerCase().includes(value)) return true
|
|
76
|
-
return (soul.summary ?? '').toLowerCase().includes(value)
|
|
77
|
-
})
|
|
78
|
-
}, [query, souls])
|
|
79
|
-
|
|
80
|
-
const sorted = useMemo(() => {
|
|
81
|
-
const multiplier = dir === 'asc' ? 1 : -1
|
|
82
|
-
const results = [...filtered]
|
|
83
|
-
results.sort((a, b) => {
|
|
84
|
-
switch (sort) {
|
|
85
|
-
case 'downloads':
|
|
86
|
-
return (a.stats.downloads - b.stats.downloads) * multiplier
|
|
87
|
-
case 'stars':
|
|
88
|
-
return (a.stats.stars - b.stats.stars) * multiplier
|
|
89
|
-
case 'updated':
|
|
90
|
-
return (a.updatedAt - b.updatedAt) * multiplier
|
|
91
|
-
case 'name':
|
|
92
|
-
return (
|
|
93
|
-
(a.displayName.localeCompare(b.displayName) || a.slug.localeCompare(b.slug)) *
|
|
94
|
-
multiplier
|
|
95
|
-
)
|
|
96
|
-
default:
|
|
97
|
-
return (a.createdAt - b.createdAt) * multiplier
|
|
98
|
-
}
|
|
99
|
-
})
|
|
100
|
-
return results
|
|
101
|
-
}, [dir, filtered, sort])
|
|
102
|
-
|
|
103
|
-
const showing = sorted.length
|
|
104
|
-
const total = souls?.length
|
|
105
|
-
|
|
106
|
-
return (
|
|
107
|
-
<main className="section">
|
|
108
|
-
<header className="skills-header">
|
|
109
|
-
<div>
|
|
110
|
-
<h1 className="section-title" style={{ marginBottom: 8 }}>
|
|
111
|
-
Souls
|
|
112
|
-
</h1>
|
|
113
|
-
<p className="section-subtitle" style={{ marginBottom: 0 }}>
|
|
114
|
-
{isLoadingSouls
|
|
115
|
-
? 'Loading souls…'
|
|
116
|
-
: `${showing}${typeof total === 'number' ? ` of ${total}` : ''} souls.`}
|
|
117
|
-
</p>
|
|
118
|
-
</div>
|
|
119
|
-
<div className="skills-toolbar">
|
|
120
|
-
<div className="skills-search">
|
|
121
|
-
<input
|
|
122
|
-
ref={searchInputRef}
|
|
123
|
-
className="skills-search-input"
|
|
124
|
-
value={query}
|
|
125
|
-
onChange={(event) => {
|
|
126
|
-
const next = event.target.value
|
|
127
|
-
const trimmed = next.trim()
|
|
128
|
-
setQuery(next)
|
|
129
|
-
void navigate({
|
|
130
|
-
search: (prev) => ({ ...prev, q: trimmed ? next : undefined }),
|
|
131
|
-
replace: true,
|
|
132
|
-
})
|
|
133
|
-
}}
|
|
134
|
-
placeholder="Filter by name, slug, or summary…"
|
|
135
|
-
/>
|
|
136
|
-
</div>
|
|
137
|
-
<div className="skills-toolbar-row">
|
|
138
|
-
<select
|
|
139
|
-
className="skills-sort"
|
|
140
|
-
value={sort}
|
|
141
|
-
onChange={(event) => {
|
|
142
|
-
const sort = parseSort(event.target.value)
|
|
143
|
-
void navigate({
|
|
144
|
-
search: (prev) => ({
|
|
145
|
-
...prev,
|
|
146
|
-
sort,
|
|
147
|
-
dir: parseDir(prev.dir, sort),
|
|
148
|
-
}),
|
|
149
|
-
replace: true,
|
|
150
|
-
})
|
|
151
|
-
}}
|
|
152
|
-
aria-label="Sort souls"
|
|
153
|
-
>
|
|
154
|
-
<option value="newest">Newest</option>
|
|
155
|
-
<option value="updated">Recently updated</option>
|
|
156
|
-
<option value="downloads">Downloads</option>
|
|
157
|
-
<option value="stars">Stars</option>
|
|
158
|
-
<option value="name">Name</option>
|
|
159
|
-
</select>
|
|
160
|
-
<button
|
|
161
|
-
className="skills-dir"
|
|
162
|
-
type="button"
|
|
163
|
-
aria-label={`Sort direction ${dir}`}
|
|
164
|
-
onClick={() => {
|
|
165
|
-
void navigate({
|
|
166
|
-
search: (prev) => ({
|
|
167
|
-
...prev,
|
|
168
|
-
dir: parseDir(prev.dir, sort) === 'asc' ? 'desc' : 'asc',
|
|
169
|
-
}),
|
|
170
|
-
replace: true,
|
|
171
|
-
})
|
|
172
|
-
}}
|
|
173
|
-
>
|
|
174
|
-
{dir === 'asc' ? '↑' : '↓'}
|
|
175
|
-
</button>
|
|
176
|
-
<button
|
|
177
|
-
className={`skills-view${view === 'cards' ? ' is-active' : ''}`}
|
|
178
|
-
type="button"
|
|
179
|
-
onClick={() => {
|
|
180
|
-
void navigate({
|
|
181
|
-
search: (prev) => ({
|
|
182
|
-
...prev,
|
|
183
|
-
view: prev.view === 'cards' ? undefined : 'cards',
|
|
184
|
-
}),
|
|
185
|
-
replace: true,
|
|
186
|
-
})
|
|
187
|
-
}}
|
|
188
|
-
>
|
|
189
|
-
{view === 'cards' ? 'List' : 'Cards'}
|
|
190
|
-
</button>
|
|
191
|
-
</div>
|
|
192
|
-
</div>
|
|
193
|
-
</header>
|
|
194
|
-
|
|
195
|
-
{isLoadingSouls ? (
|
|
196
|
-
<div className="card">
|
|
197
|
-
<div className="loading-indicator">Loading souls…</div>
|
|
198
|
-
</div>
|
|
199
|
-
) : showing === 0 ? (
|
|
200
|
-
<div className="card">No souls match that filter.</div>
|
|
201
|
-
) : view === 'cards' ? (
|
|
202
|
-
<div className="grid">
|
|
203
|
-
{sorted.map((soul) => (
|
|
204
|
-
<SoulCard
|
|
205
|
-
key={soul._id}
|
|
206
|
-
soul={soul}
|
|
207
|
-
summaryFallback="A SOUL.md bundle."
|
|
208
|
-
meta={
|
|
209
|
-
<div className="stat">
|
|
210
|
-
⭐ {soul.stats.stars} · ⤓ {soul.stats.downloads} · {soul.stats.versions} v
|
|
211
|
-
</div>
|
|
212
|
-
}
|
|
213
|
-
/>
|
|
214
|
-
))}
|
|
215
|
-
</div>
|
|
216
|
-
) : (
|
|
217
|
-
<div className="skills-list">
|
|
218
|
-
{sorted.map((soul) => (
|
|
219
|
-
<Link
|
|
220
|
-
key={soul._id}
|
|
221
|
-
className="skills-row"
|
|
222
|
-
to="/souls/$slug"
|
|
223
|
-
params={{ slug: soul.slug }}
|
|
224
|
-
>
|
|
225
|
-
<div className="skills-row-main">
|
|
226
|
-
<div className="skills-row-title">
|
|
227
|
-
<span>{soul.displayName}</span>
|
|
228
|
-
<span className="skills-row-slug">/{soul.slug}</span>
|
|
229
|
-
</div>
|
|
230
|
-
<div className="skills-row-summary">{soul.summary ?? 'SOUL.md bundle.'}</div>
|
|
231
|
-
</div>
|
|
232
|
-
<div className="skills-row-metrics">
|
|
233
|
-
<span>⤓ {soul.stats.downloads}</span>
|
|
234
|
-
<span>★ {soul.stats.stars}</span>
|
|
235
|
-
<span>{soul.stats.versions} v</span>
|
|
236
|
-
</div>
|
|
237
|
-
</Link>
|
|
238
|
-
))}
|
|
239
|
-
</div>
|
|
240
|
-
)}
|
|
241
|
-
</main>
|
|
242
|
-
)
|
|
243
|
-
}
|
package/src/routes/stars.tsx
DELETED
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
import { createFileRoute, Link } from '@tanstack/react-router'
|
|
2
|
-
import { useMutation, useQuery } from 'convex/react'
|
|
3
|
-
import { api } from '../../convex/_generated/api'
|
|
4
|
-
import type { Doc } from '../../convex/_generated/dataModel'
|
|
5
|
-
import type { PublicSkill } from '../lib/publicUser'
|
|
6
|
-
|
|
7
|
-
export const Route = createFileRoute('/stars')({
|
|
8
|
-
component: Stars,
|
|
9
|
-
})
|
|
10
|
-
|
|
11
|
-
function Stars() {
|
|
12
|
-
const me = useQuery(api.users.me) as Doc<'users'> | null | undefined
|
|
13
|
-
const skills =
|
|
14
|
-
(useQuery(api.stars.listByUser, me ? { userId: me._id, limit: 50 } : 'skip') as
|
|
15
|
-
| PublicSkill[]
|
|
16
|
-
| undefined) ?? []
|
|
17
|
-
|
|
18
|
-
const toggleStar = useMutation(api.stars.toggle)
|
|
19
|
-
|
|
20
|
-
if (!me) {
|
|
21
|
-
return (
|
|
22
|
-
<main className="section">
|
|
23
|
-
<div className="card">Sign in to see your highlights.</div>
|
|
24
|
-
</main>
|
|
25
|
-
)
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
return (
|
|
29
|
-
<main className="section">
|
|
30
|
-
<h1 className="section-title">Your highlights</h1>
|
|
31
|
-
<p className="section-subtitle">Skills you’ve starred for quick access.</p>
|
|
32
|
-
<div className="grid">
|
|
33
|
-
{skills.length === 0 ? (
|
|
34
|
-
<div className="card">No stars yet.</div>
|
|
35
|
-
) : (
|
|
36
|
-
skills.map((skill) => {
|
|
37
|
-
const owner = encodeURIComponent(String(skill.ownerUserId))
|
|
38
|
-
return (
|
|
39
|
-
<div key={skill._id} className="card skill-card">
|
|
40
|
-
<Link to="/$owner/$slug" params={{ owner, slug: skill.slug }}>
|
|
41
|
-
<h3 className="skill-card-title">{skill.displayName}</h3>
|
|
42
|
-
</Link>
|
|
43
|
-
<div className="skill-card-footer skill-card-footer-inline">
|
|
44
|
-
<span className="stat">⭐ {skill.stats.stars}</span>
|
|
45
|
-
<button
|
|
46
|
-
className="star-toggle is-active"
|
|
47
|
-
type="button"
|
|
48
|
-
onClick={async () => {
|
|
49
|
-
try {
|
|
50
|
-
await toggleStar({ skillId: skill._id })
|
|
51
|
-
} catch (error) {
|
|
52
|
-
console.error('Failed to unstar skill:', error)
|
|
53
|
-
window.alert('Unable to unstar this skill. Please try again.')
|
|
54
|
-
}
|
|
55
|
-
}}
|
|
56
|
-
aria-label={`Unstar ${skill.displayName}`}
|
|
57
|
-
>
|
|
58
|
-
<span aria-hidden="true">★</span>
|
|
59
|
-
</button>
|
|
60
|
-
</div>
|
|
61
|
-
</div>
|
|
62
|
-
)
|
|
63
|
-
})
|
|
64
|
-
)}
|
|
65
|
-
</div>
|
|
66
|
-
</main>
|
|
67
|
-
)
|
|
68
|
-
}
|
package/src/routes/u/$handle.tsx
DELETED
|
@@ -1,307 +0,0 @@
|
|
|
1
|
-
import { createFileRoute } from '@tanstack/react-router'
|
|
2
|
-
import { useMutation, useQuery } from 'convex/react'
|
|
3
|
-
import { useEffect, useState } from 'react'
|
|
4
|
-
import { api } from '../../../convex/_generated/api'
|
|
5
|
-
import type { Doc } from '../../../convex/_generated/dataModel'
|
|
6
|
-
import { SkillCard } from '../../components/SkillCard'
|
|
7
|
-
import { getSkillBadges } from '../../lib/badges'
|
|
8
|
-
import type { PublicSkill, PublicUser } from '../../lib/publicUser'
|
|
9
|
-
|
|
10
|
-
export const Route = createFileRoute('/u/$handle')({
|
|
11
|
-
component: UserProfile,
|
|
12
|
-
})
|
|
13
|
-
|
|
14
|
-
function UserProfile() {
|
|
15
|
-
const { handle } = Route.useParams()
|
|
16
|
-
const me = useQuery(api.users.me) as Doc<'users'> | null | undefined
|
|
17
|
-
const user = useQuery(api.users.getByHandle, { handle }) as PublicUser | null | undefined
|
|
18
|
-
const publishedSkills = useQuery(
|
|
19
|
-
api.skills.list,
|
|
20
|
-
user ? { ownerUserId: user._id, limit: 50 } : 'skip',
|
|
21
|
-
) as PublicSkill[] | undefined
|
|
22
|
-
const starredSkills = useQuery(
|
|
23
|
-
api.stars.listByUser,
|
|
24
|
-
user ? { userId: user._id, limit: 50 } : 'skip',
|
|
25
|
-
) as PublicSkill[] | undefined
|
|
26
|
-
|
|
27
|
-
const isSelf = Boolean(me && user && me._id === user._id)
|
|
28
|
-
const [tab, setTab] = useState<'stars' | 'installed'>('stars')
|
|
29
|
-
const [includeRemoved, setIncludeRemoved] = useState(false)
|
|
30
|
-
const installed = useQuery(
|
|
31
|
-
api.telemetry.getMyInstalled,
|
|
32
|
-
isSelf && tab === 'installed' ? { includeRemoved } : 'skip',
|
|
33
|
-
) as TelemetryResponse | null | undefined
|
|
34
|
-
|
|
35
|
-
useEffect(() => {
|
|
36
|
-
if (!isSelf && tab === 'installed') setTab('stars')
|
|
37
|
-
}, [isSelf, tab])
|
|
38
|
-
|
|
39
|
-
if (user === undefined) {
|
|
40
|
-
return (
|
|
41
|
-
<main className="section">
|
|
42
|
-
<div className="card">
|
|
43
|
-
<div className="loading-indicator">Loading user…</div>
|
|
44
|
-
</div>
|
|
45
|
-
</main>
|
|
46
|
-
)
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
if (user === null) {
|
|
50
|
-
return (
|
|
51
|
-
<main className="section">
|
|
52
|
-
<div className="card">User not found.</div>
|
|
53
|
-
</main>
|
|
54
|
-
)
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
const avatar = user.image
|
|
58
|
-
const displayName = user.displayName ?? user.name ?? user.handle ?? 'User'
|
|
59
|
-
const displayHandle = user.handle ?? user.name ?? handle
|
|
60
|
-
const initial = displayName.charAt(0).toUpperCase()
|
|
61
|
-
const isLoadingSkills = starredSkills === undefined
|
|
62
|
-
const skills = starredSkills ?? []
|
|
63
|
-
const isLoadingPublished = publishedSkills === undefined
|
|
64
|
-
const published = publishedSkills ?? []
|
|
65
|
-
|
|
66
|
-
return (
|
|
67
|
-
<main className="section">
|
|
68
|
-
<div className="card settings-profile" style={{ marginBottom: 22 }}>
|
|
69
|
-
<div className="settings-avatar" aria-hidden="true">
|
|
70
|
-
{avatar ? <img src={avatar} alt="" /> : <span>{initial}</span>}
|
|
71
|
-
</div>
|
|
72
|
-
<div className="settings-profile-body">
|
|
73
|
-
<div className="settings-name">{displayName}</div>
|
|
74
|
-
<div className="settings-handle">@{displayHandle}</div>
|
|
75
|
-
</div>
|
|
76
|
-
</div>
|
|
77
|
-
|
|
78
|
-
{isSelf ? (
|
|
79
|
-
<div className="profile-tabs" role="tablist" aria-label="Profile tabs">
|
|
80
|
-
<button
|
|
81
|
-
className={tab === 'stars' ? 'profile-tab is-active' : 'profile-tab'}
|
|
82
|
-
type="button"
|
|
83
|
-
role="tab"
|
|
84
|
-
aria-selected={tab === 'stars'}
|
|
85
|
-
onClick={() => setTab('stars')}
|
|
86
|
-
>
|
|
87
|
-
Stars
|
|
88
|
-
</button>
|
|
89
|
-
<button
|
|
90
|
-
className={tab === 'installed' ? 'profile-tab is-active' : 'profile-tab'}
|
|
91
|
-
type="button"
|
|
92
|
-
role="tab"
|
|
93
|
-
aria-selected={tab === 'installed'}
|
|
94
|
-
onClick={() => setTab('installed')}
|
|
95
|
-
>
|
|
96
|
-
Installed
|
|
97
|
-
</button>
|
|
98
|
-
</div>
|
|
99
|
-
) : null}
|
|
100
|
-
|
|
101
|
-
{tab === 'installed' && isSelf ? (
|
|
102
|
-
<InstalledSection
|
|
103
|
-
includeRemoved={includeRemoved}
|
|
104
|
-
onToggleRemoved={() => setIncludeRemoved((value) => !value)}
|
|
105
|
-
data={installed}
|
|
106
|
-
/>
|
|
107
|
-
) : (
|
|
108
|
-
<>
|
|
109
|
-
<h2 className="section-title" style={{ fontSize: '1.3rem' }}>
|
|
110
|
-
Published
|
|
111
|
-
</h2>
|
|
112
|
-
<p className="section-subtitle">Skills published by this user.</p>
|
|
113
|
-
|
|
114
|
-
{isLoadingPublished ? (
|
|
115
|
-
<div className="card">
|
|
116
|
-
<div className="loading-indicator">Loading published skills…</div>
|
|
117
|
-
</div>
|
|
118
|
-
) : published.length > 0 ? (
|
|
119
|
-
<div className="grid" style={{ marginBottom: 18 }}>
|
|
120
|
-
{published.map((skill) => (
|
|
121
|
-
<SkillCard
|
|
122
|
-
key={skill._id}
|
|
123
|
-
skill={skill}
|
|
124
|
-
badge={getSkillBadges(skill)}
|
|
125
|
-
summaryFallback="Agent-ready skill pack."
|
|
126
|
-
meta={
|
|
127
|
-
<div className="stat">
|
|
128
|
-
⭐ {skill.stats.stars} · ⤓ {skill.stats.downloads} · ⤒{' '}
|
|
129
|
-
{skill.stats.installsAllTime ?? 0}
|
|
130
|
-
</div>
|
|
131
|
-
}
|
|
132
|
-
/>
|
|
133
|
-
))}
|
|
134
|
-
</div>
|
|
135
|
-
) : null}
|
|
136
|
-
|
|
137
|
-
<h2 className="section-title" style={{ fontSize: '1.3rem' }}>
|
|
138
|
-
Stars
|
|
139
|
-
</h2>
|
|
140
|
-
<p className="section-subtitle">Skills this user has starred.</p>
|
|
141
|
-
|
|
142
|
-
{isLoadingSkills ? (
|
|
143
|
-
<div className="card">
|
|
144
|
-
<div className="loading-indicator">Loading stars…</div>
|
|
145
|
-
</div>
|
|
146
|
-
) : skills.length === 0 ? (
|
|
147
|
-
<div className="card">No stars yet.</div>
|
|
148
|
-
) : (
|
|
149
|
-
<div className="grid">
|
|
150
|
-
{skills.map((skill) => (
|
|
151
|
-
<SkillCard
|
|
152
|
-
key={skill._id}
|
|
153
|
-
skill={skill}
|
|
154
|
-
badge={getSkillBadges(skill)}
|
|
155
|
-
summaryFallback="Agent-ready skill pack."
|
|
156
|
-
meta={
|
|
157
|
-
<div className="stat">
|
|
158
|
-
⭐ {skill.stats.stars} · ⤓ {skill.stats.downloads} · ⤒{' '}
|
|
159
|
-
{skill.stats.installsAllTime ?? 0}
|
|
160
|
-
</div>
|
|
161
|
-
}
|
|
162
|
-
/>
|
|
163
|
-
))}
|
|
164
|
-
</div>
|
|
165
|
-
)}
|
|
166
|
-
</>
|
|
167
|
-
)}
|
|
168
|
-
</main>
|
|
169
|
-
)
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
function InstalledSection(props: {
|
|
173
|
-
includeRemoved: boolean
|
|
174
|
-
onToggleRemoved: () => void
|
|
175
|
-
data: TelemetryResponse | null | undefined
|
|
176
|
-
}) {
|
|
177
|
-
const clearTelemetry = useMutation(api.telemetry.clearMyTelemetry)
|
|
178
|
-
const [showRaw, setShowRaw] = useState(false)
|
|
179
|
-
const data = props.data
|
|
180
|
-
if (data === undefined) {
|
|
181
|
-
return (
|
|
182
|
-
<>
|
|
183
|
-
<h2 className="section-title" style={{ fontSize: '1.3rem' }}>
|
|
184
|
-
Installed
|
|
185
|
-
</h2>
|
|
186
|
-
<div className="card">
|
|
187
|
-
<div className="loading-indicator">Loading telemetry…</div>
|
|
188
|
-
</div>
|
|
189
|
-
</>
|
|
190
|
-
)
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
if (data === null) {
|
|
194
|
-
return (
|
|
195
|
-
<>
|
|
196
|
-
<h2 className="section-title" style={{ fontSize: '1.3rem' }}>
|
|
197
|
-
Installed
|
|
198
|
-
</h2>
|
|
199
|
-
<div className="card">Sign in to view your installed skills.</div>
|
|
200
|
-
</>
|
|
201
|
-
)
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
return (
|
|
205
|
-
<>
|
|
206
|
-
<h2 className="section-title" style={{ fontSize: '1.3rem' }}>
|
|
207
|
-
Installed
|
|
208
|
-
</h2>
|
|
209
|
-
<p className="section-subtitle" style={{ maxWidth: 760 }}>
|
|
210
|
-
Private view. Only you can see your folders/roots. Everyone else only sees aggregated
|
|
211
|
-
install counts per skill.
|
|
212
|
-
</p>
|
|
213
|
-
<div className="profile-actions">
|
|
214
|
-
<button className="btn" type="button" onClick={props.onToggleRemoved}>
|
|
215
|
-
{props.includeRemoved ? 'Hide removed' : 'Show removed'}
|
|
216
|
-
</button>
|
|
217
|
-
<button className="btn" type="button" onClick={() => setShowRaw((value) => !value)}>
|
|
218
|
-
{showRaw ? 'Hide JSON' : 'Show JSON'}
|
|
219
|
-
</button>
|
|
220
|
-
<button
|
|
221
|
-
className="btn"
|
|
222
|
-
type="button"
|
|
223
|
-
onClick={() => {
|
|
224
|
-
if (!window.confirm('Delete all telemetry data?')) return
|
|
225
|
-
void clearTelemetry()
|
|
226
|
-
}}
|
|
227
|
-
>
|
|
228
|
-
Delete telemetry
|
|
229
|
-
</button>
|
|
230
|
-
</div>
|
|
231
|
-
|
|
232
|
-
{showRaw ? (
|
|
233
|
-
<div className="card telemetry-json" style={{ marginBottom: 18 }}>
|
|
234
|
-
<pre className="mono" style={{ margin: 0, whiteSpace: 'pre-wrap' }}>
|
|
235
|
-
{JSON.stringify(data, null, 2)}
|
|
236
|
-
</pre>
|
|
237
|
-
</div>
|
|
238
|
-
) : null}
|
|
239
|
-
|
|
240
|
-
{data.roots.length === 0 ? (
|
|
241
|
-
<div className="card">No telemetry yet. Run `pilothub sync` from the CLI.</div>
|
|
242
|
-
) : (
|
|
243
|
-
<div style={{ display: 'grid', gap: 16 }}>
|
|
244
|
-
{data.roots.map((root) => (
|
|
245
|
-
<div key={root.rootId} className="card telemetry-root">
|
|
246
|
-
<div className="telemetry-root-header">
|
|
247
|
-
<div>
|
|
248
|
-
<div className="telemetry-root-title">{root.label}</div>
|
|
249
|
-
<div className="telemetry-root-meta">
|
|
250
|
-
Last sync {new Date(root.lastSeenAt).toLocaleString()}
|
|
251
|
-
{root.expiredAt ? ' · stale' : ''}
|
|
252
|
-
</div>
|
|
253
|
-
</div>
|
|
254
|
-
<div className="tag">{root.skills.length} skills</div>
|
|
255
|
-
</div>
|
|
256
|
-
{root.skills.length === 0 ? (
|
|
257
|
-
<div className="stat">No skills found in this root.</div>
|
|
258
|
-
) : (
|
|
259
|
-
<div className="telemetry-skill-list">
|
|
260
|
-
{root.skills.map((entry) => (
|
|
261
|
-
<div key={`${root.rootId}:${entry.skill.slug}`} className="telemetry-skill-row">
|
|
262
|
-
<a
|
|
263
|
-
className="telemetry-skill-link"
|
|
264
|
-
href={`/${encodeURIComponent(String(entry.skill.ownerUserId))}/${entry.skill.slug}`}
|
|
265
|
-
>
|
|
266
|
-
<span>{entry.skill.displayName}</span>
|
|
267
|
-
<span className="telemetry-skill-slug">/{entry.skill.slug}</span>
|
|
268
|
-
</a>
|
|
269
|
-
<div className="telemetry-skill-meta mono">
|
|
270
|
-
{entry.lastVersion ? `v${entry.lastVersion}` : 'v?'}{' '}
|
|
271
|
-
{entry.removedAt ? '· removed' : ''}
|
|
272
|
-
</div>
|
|
273
|
-
</div>
|
|
274
|
-
))}
|
|
275
|
-
</div>
|
|
276
|
-
)}
|
|
277
|
-
</div>
|
|
278
|
-
))}
|
|
279
|
-
</div>
|
|
280
|
-
)}
|
|
281
|
-
</>
|
|
282
|
-
)
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
type TelemetryResponse = {
|
|
286
|
-
roots: Array<{
|
|
287
|
-
rootId: string
|
|
288
|
-
label: string
|
|
289
|
-
firstSeenAt: number
|
|
290
|
-
lastSeenAt: number
|
|
291
|
-
expiredAt?: number
|
|
292
|
-
skills: Array<{
|
|
293
|
-
skill: {
|
|
294
|
-
slug: string
|
|
295
|
-
displayName: string
|
|
296
|
-
summary?: string
|
|
297
|
-
stats: unknown
|
|
298
|
-
ownerUserId: string
|
|
299
|
-
}
|
|
300
|
-
firstSeenAt: number
|
|
301
|
-
lastSeenAt: number
|
|
302
|
-
lastVersion?: string
|
|
303
|
-
removedAt?: number
|
|
304
|
-
}>
|
|
305
|
-
}>
|
|
306
|
-
cutoffDays: number
|
|
307
|
-
}
|
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
import { isTextContentType, TEXT_FILE_EXTENSION_SET } from 'pilothub-schema'
|
|
2
|
-
|
|
3
|
-
export async function uploadFile(uploadUrl: string, file: File) {
|
|
4
|
-
const response = await fetch(uploadUrl, {
|
|
5
|
-
method: 'POST',
|
|
6
|
-
headers: { 'Content-Type': file.type || 'application/octet-stream' },
|
|
7
|
-
body: file,
|
|
8
|
-
})
|
|
9
|
-
if (!response.ok) {
|
|
10
|
-
throw new Error(`Upload failed: ${await response.text()}`)
|
|
11
|
-
}
|
|
12
|
-
const payload = (await response.json()) as { storageId: string }
|
|
13
|
-
return payload.storageId
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export async function hashFile(file: File) {
|
|
17
|
-
const buffer =
|
|
18
|
-
typeof file.arrayBuffer === 'function'
|
|
19
|
-
? await file.arrayBuffer()
|
|
20
|
-
: await new Response(file).arrayBuffer()
|
|
21
|
-
const hash = await crypto.subtle.digest('SHA-256', new Uint8Array(buffer))
|
|
22
|
-
const bytes = new Uint8Array(hash)
|
|
23
|
-
return Array.from(bytes)
|
|
24
|
-
.map((byte) => byte.toString(16).padStart(2, '0'))
|
|
25
|
-
.join('')
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export function formatBytes(bytes: number) {
|
|
29
|
-
if (!Number.isFinite(bytes)) return '0 B'
|
|
30
|
-
const units = ['B', 'KB', 'MB', 'GB']
|
|
31
|
-
let size = bytes
|
|
32
|
-
let unit = 0
|
|
33
|
-
while (size >= 1024 && unit < units.length - 1) {
|
|
34
|
-
size /= 1024
|
|
35
|
-
unit += 1
|
|
36
|
-
}
|
|
37
|
-
return `${size.toFixed(size < 10 && unit > 0 ? 1 : 0)} ${units[unit]}`
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
export function formatPublishError(error: unknown) {
|
|
41
|
-
if (error && typeof error === 'object' && 'data' in error) {
|
|
42
|
-
const data = (error as { data?: unknown }).data
|
|
43
|
-
if (typeof data === 'string' && data.trim()) return data.trim()
|
|
44
|
-
if (
|
|
45
|
-
data &&
|
|
46
|
-
typeof data === 'object' &&
|
|
47
|
-
'message' in data &&
|
|
48
|
-
typeof (data as { message?: unknown }).message === 'string'
|
|
49
|
-
) {
|
|
50
|
-
const message = (data as { message?: string }).message?.trim()
|
|
51
|
-
if (message) return message
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
if (error instanceof Error) {
|
|
55
|
-
const cleaned = error.message
|
|
56
|
-
.replace(/\[CONVEX[^\]]*\]\s*/g, '')
|
|
57
|
-
.replace(/\[Request ID:[^\]]*\]\s*/g, '')
|
|
58
|
-
.replace(/^Server Error Called by client\s*/i, '')
|
|
59
|
-
.replace(/^ConvexError:\s*/i, '')
|
|
60
|
-
.trim()
|
|
61
|
-
if (cleaned && cleaned !== 'Server Error') return cleaned
|
|
62
|
-
}
|
|
63
|
-
return 'Publish failed. Please try again.'
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
export function isTextFile(file: File) {
|
|
67
|
-
const path = (file.webkitRelativePath || file.name).trim().toLowerCase()
|
|
68
|
-
if (!path) return false
|
|
69
|
-
const parts = path.split('.')
|
|
70
|
-
const extension = parts.length > 1 ? (parts.at(-1) ?? '') : ''
|
|
71
|
-
if (file.type && isTextContentType(file.type)) return true
|
|
72
|
-
if (extension && TEXT_FILE_EXTENSION_SET.has(extension)) return true
|
|
73
|
-
return false
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
export async function readText(blob: Blob) {
|
|
77
|
-
if (typeof (blob as Blob & { text?: unknown }).text === 'function') {
|
|
78
|
-
return (blob as Blob & { text: () => Promise<string> }).text()
|
|
79
|
-
}
|
|
80
|
-
return new Response(blob as BodyInit).text()
|
|
81
|
-
}
|