← Public packages
@kody/integrations-sh
Client for the public integrations.sh registry: search, detect, cached surface, and live discover.
src/search.ts
83 lines · 2.3 KB · TypeScriptimport { fetchRegistryJson } from './client.ts'
import { isRecord } from './is-record.ts'
import { mapSearchResult, type SearchResult } from './types.ts'
import {
buildSearchUrl,
isSurfaceKind,
type SurfaceKind,
} from './urls.ts'
const SEARCH_FETCH_TIMEOUT_MS = 10_000
export type SearchInput = {
/** Provider name, product, or domain to search for. */
query: string
/** Limit results to one catalog kind. */
kind?: SurfaceKind
/** Maximum number of results (1–100, default 20). */
limit?: number
/** Catalog offset for paging. */
offset?: number
}
export type SearchOutput = {
results: Array<SearchResult>
}
/**
* Search the public integrations.sh catalog.
* Use this first when you need a canonical hostname or connect-target slugs.
*
* @param input - Query plus optional kind, limit, and offset
* @returns Untrusted catalog matches (domain, kinds, listing URL, surfaces)
*
* @example
* import search from 'kody:@<username>/integrations-sh/search'
* const { results } = await search({ query: 'github', kind: 'openapi' })
*/
export default async function search(input: SearchInput): Promise<SearchOutput> {
const query = input.query.trim()
if (query.length === 0) {
throw new Error('query is required')
}
if (input.kind != null && !isSurfaceKind(input.kind)) {
throw new Error('kind must be mcp, openapi, graphql, or cli')
}
const limit = input.limit ?? 20
if (!Number.isInteger(limit) || limit < 1 || limit > 100) {
throw new Error('limit must be an integer between 1 and 100')
}
if (
input.offset != null &&
(!Number.isInteger(input.offset) || input.offset < 0)
) {
throw new Error('offset must be a non-negative integer')
}
const url = buildSearchUrl({
query,
kind: input.kind,
limit,
offset: input.offset,
})
const fetched = await fetchRegistryJson(url, SEARCH_FETCH_TIMEOUT_MS)
if (fetched.outcome !== 'success') {
const detail =
fetched.outcome === 'not-found'
? `HTTP 404 for ${url}`
: fetched.message
throw new Error(`integrations.sh search failed: ${detail}`)
}
if (!isRecord(fetched.json) || !Array.isArray(fetched.json.results)) {
throw new Error(
'integrations.sh search failed: unexpected response shape',
)
}
const results = fetched.json.results
.map(mapSearchResult)
.filter((result): result is SearchResult => result != null)
.slice(0, limit)
return { results }
}