Skip to content
Open
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/components/intent/SkillDependencyGraph.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -268,8 +268,8 @@ export function SkillDependencyGraph({
const datum = point?.datum
if (!datum || datum.kind !== 'node') return
void navigate({
to: '/intent/registry/$packageName/$skillName',
params: { packageName, skillName: datum.id },
to: '/intent/registry/$packageName/{$}',
params: { packageName, _splat: datum.id },
})
}}
/>
Expand Down
180 changes: 161 additions & 19 deletions src/routeTree.gen.ts

Large diffs are not rendered by default.

39 changes: 39 additions & 0 deletions src/routes/api/v1/intent/packages.$name.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { createFileRoute } from '@tanstack/react-router'

export const Route = createFileRoute('/api/v1/intent/packages/$name')({
server: {
handlers: {
GET: async ({
request,
params,
}: {
request: Request
params: { name: string }
}) => {
const [{ applyIntentRateLimit, intentJsonResponse, intentErrorResponse }, fns] =
await Promise.all([
import('~/utils/intent-api.server'),
import('~/utils/intent.functions'),
])

const decision = await applyIntentRateLimit(request)
if (decision.limited) return decision.response

const detail = await fns.getIntentPackageDetail({
data: { name: params.name },
})

if (!detail) {
return intentErrorResponse(
`Package not found: ${params.name}`,
'NOT_FOUND',
404,
decision.rl,
)
}

return intentJsonResponse(detail, decision.rl)
},
},
},
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import { createFileRoute } from '@tanstack/react-router'

export const Route = createFileRoute(
'/api/v1/intent/packages/$name/versions/$version/skills/$skill',
)({
server: {
handlers: {
GET: async ({
request,
params,
}: {
request: Request
params: { name: string; version: string; skill: string }
}) => {
const [
{
applyIntentRateLimit,
intentJsonResponse,
intentErrorResponse,
buildSkillContentUrls,
},
fns,
dbModule,
] = await Promise.all([
import('~/utils/intent-api.server'),
import('~/utils/intent.functions'),
import('~/utils/intent-db.server'),
])

const decision = await applyIntentRateLimit(request)
if (decision.limited) return decision.response

const versions = await dbModule.getPackageVersions(params.name)
const versionRecord = versions.find((v) => v.version === params.version)
if (!versionRecord) {
return intentErrorResponse(
`Version not found: ${params.name}@${params.version}`,
'NOT_FOUND',
404,
decision.rl,
)
}

const skills = await dbModule.getSkillsForVersion(versionRecord.id)
const skill = skills.find((s) => s.name === params.skill)

if (!skill) {
return intentErrorResponse(
`Skill not found: ${params.skill} in ${params.name}@${params.version}`,
'NOT_FOUND',
404,
decision.rl,
)
}

// Markdown body is kept as an undocumented escape hatch only fetched
// when the caller explicitly opts in (?include=markdown). Default
// responses point at the CDN to keep our egress near zero.
const url = new URL(request.url)
const includeMarkdown = url.searchParams
.get('include')
?.split(',')
.includes('markdown')

const markdown = includeMarkdown
? await fns.getIntentSkillMarkdown({
data: {
packageName: params.name,
version: params.version,
skillName: params.skill,
},
})
: undefined

return intentJsonResponse(
{
packageName: params.name,
version: params.version,
skill: {
id: skill.id,
name: skill.name,
description: skill.description,
type: skill.type,
framework: skill.framework,
requires: skill.requires,
skillPath: skill.skillPath,
contentHash: skill.contentHash,
lineCount: skill.lineCount,
},
content: buildSkillContentUrls(
params.name,
params.version,
skill.skillPath,
),
...(includeMarkdown ? { markdown } : {}),
},
decision.rl,
)
},
},
},
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { createFileRoute } from '@tanstack/react-router'

export const Route = createFileRoute(
'/api/v1/intent/packages/$name/versions/$version/skills',
)({
server: {
handlers: {
GET: async ({
request,
params,
}: {
request: Request
params: { name: string; version: string }
}) => {
const [
{
applyIntentRateLimit,
intentJsonResponse,
intentErrorResponse,
buildSkillContentUrls,
},
fns,
] = await Promise.all([
import('~/utils/intent-api.server'),
import('~/utils/intent.functions'),
])

const decision = await applyIntentRateLimit(request)
if (decision.limited) return decision.response

const result = await fns.getIntentVersionSkills({
data: { packageName: params.name, version: params.version },
})

if (!result) {
return intentErrorResponse(
`Version not found: ${params.name}@${params.version}`,
'NOT_FOUND',
404,
decision.rl,
)
}

return intentJsonResponse(
{
...result,
skills: result.skills.map((skill) => ({
...skill,
content: buildSkillContentUrls(
params.name,
params.version,
skill.skillPath,
),
})),
},
decision.rl,
)
},
},
},
})
41 changes: 41 additions & 0 deletions src/routes/api/v1/intent/packages.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { createFileRoute } from '@tanstack/react-router'

export const Route = createFileRoute('/api/v1/intent/packages')({
server: {
handlers: {
GET: async ({ request }: { request: Request }) => {
const [{ applyIntentRateLimit, intentJsonResponse }, fns] =
await Promise.all([
import('~/utils/intent-api.server'),
import('~/utils/intent.functions'),
])

const decision = await applyIntentRateLimit(request)
if (decision.limited) return decision.response

const url = new URL(request.url)
const search = url.searchParams.get('q') ?? undefined
const framework = url.searchParams.get('framework') ?? undefined
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved
const sortParam = url.searchParams.get('sort')
const sort: 'downloads' | 'name' | 'skills' | 'newest' | undefined =
sortParam === 'downloads' ||
sortParam === 'name' ||
sortParam === 'skills' ||
sortParam === 'newest'
? sortParam
: undefined
const page = Math.max(0, parseInt(url.searchParams.get('page') ?? '0', 10) || 0)
const pageSize = Math.min(
Math.max(parseInt(url.searchParams.get('pageSize') ?? '24', 10) || 24, 1),
48,
)

const result = await fns.getIntentDirectory({
data: { search, framework, sort, page, pageSize },
})

return intentJsonResponse(result, decision.rl)
},
},
},
})
53 changes: 53 additions & 0 deletions src/routes/api/v1/intent/search.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { createFileRoute } from '@tanstack/react-router'

export const Route = createFileRoute('/api/v1/intent/search')({
server: {
handlers: {
GET: async ({ request }: { request: Request }) => {
const [
{
applyIntentRateLimit,
intentJsonResponse,
intentErrorResponse,
buildSkillContentUrls,
},
dbModule,
] = await Promise.all([
import('~/utils/intent-api.server'),
import('~/utils/intent-db.server'),
])

const decision = await applyIntentRateLimit(request)
if (decision.limited) return decision.response

const url = new URL(request.url)
const q = url.searchParams.get('q')?.trim() ?? ''
const limitParam = url.searchParams.get('limit')
const limit = Math.min(
Math.max(parseInt(limitParam ?? '20', 10) || 20, 1),
100,
)

if (!q) {
return intentErrorResponse(
'Missing required query parameter: q',
'INVALID_REQUEST',
400,
decision.rl,
)
}

const rows = await dbModule.searchSkills(q, limit)
const results = rows.map((row) => ({
...row,
content: buildSkillContentUrls(
row.packageName,
row.version,
row.skillPath,
),
}))
return intentJsonResponse({ query: q, limit, results }, decision.rl)
},
},
},
})
8 changes: 4 additions & 4 deletions src/routes/intent/registry/$packageName.index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -244,8 +244,8 @@ function SkillsList({
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2 flex-wrap">
<Link
to="/intent/registry/$packageName/$skillName"
params={{ packageName, skillName: skill.name }}
to="/intent/registry/$packageName/{$}"
params={{ packageName, _splat: skill.name }}
className="font-mono text-sm font-semibold text-gray-900 dark:text-gray-100 hover:text-sky-600 dark:hover:text-sky-400 transition-colors"
>
{skill.name}
Expand Down Expand Up @@ -607,8 +607,8 @@ function ChangelogSkillRow({
{statusConfig.prefix}
</span>
<Link
to="/intent/registry/$packageName/$skillName"
params={{ packageName, skillName: name }}
to="/intent/registry/$packageName/{$}"
params={{ packageName, _splat: name }}
className="font-mono text-sm text-gray-900 dark:text-gray-100 hover:text-sky-600 dark:hover:text-sky-400 transition-colors truncate"
>
{name}
Expand Down
10 changes: 4 additions & 6 deletions src/routes/intent/registry/$packageName.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -149,10 +149,8 @@ function PackageLayoutInner({
readonly activeVersion: string
readonly setVersion: (v: string) => void
}) {
const { packageName, skillName } = useParams({ strict: false }) as {
packageName: string
skillName?: string
}
const { packageName } = Route.useParams()
const { _splat: skillName } = useParams({ strict: false })

const skillsQuery = useSuspenseQuery(
intentVersionSkillsQueryOptions({
Expand Down Expand Up @@ -431,8 +429,8 @@ function SkillsNav({
return (
<Link
key={s.name}
to="/intent/registry/$packageName/$skillName"
params={{ packageName, skillName: s.name }}
to="/intent/registry/$packageName/{$}"
params={{ packageName, _splat: s.name }}
onClick={onNavigate}
className={`flex flex-col items-start px-2 py-1.5 rounded-md text-sm font-mono transition-colors ${
isActive
Expand Down
Loading
Loading