Skip to content
Open
Show file tree
Hide file tree
Changes from all 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,
)
},
},
},
})
57 changes: 57 additions & 0 deletions src/routes/api/v1/intent/packages.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { createFileRoute } from '@tanstack/react-router'
import { safeParse } from 'valibot'

export const Route = createFileRoute('/api/v1/intent/packages')({
server: {
handlers: {
GET: async ({ request }: { request: Request }) => {
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 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 parsed = safeParse(fns.intentDirectorySchema, {
search,
framework,
sort,
page,
pageSize,
})
if (!parsed.success) {
return intentErrorResponse(
parsed.issues[0].message,
'INVALID_REQUEST',
400,
decision.rl,
)
}
const result = await fns.getIntentDirectory({ data: parsed.output })

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