← Public packages
@kentcdodds/raycast
Raycast launcher: stored execute commands via webhooks (canonical; do not use raycast-shortcuts).
src/app.ts
92 lines · 3.1 KB · TypeScriptimport listCommands from './list-commands.ts'
import run from './run.ts'
import { bearerFromRequest, verifyLauncherToken } from './launcher-auth.ts'
function json(data: unknown, status = 200) {
return new Response(JSON.stringify(data), {
status,
headers: {
'content-type': 'application/json; charset=utf-8',
'cache-control': 'no-store',
},
})
}
async function readBody(request: Request): Promise<Record<string, unknown>> {
if (request.method === 'GET' || request.method === 'HEAD') return {}
const text = await request.text()
if (!text.trim()) return {}
const parsed = JSON.parse(text) as unknown
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
throw new Error('JSON body must be an object')
}
const record = parsed as Record<string, unknown>
if (record.params && typeof record.params === 'object' && !Array.isArray(record.params)) {
return record.params as Record<string, unknown>
}
return record
}
function apiPathOf(pathname: string) {
const idx = pathname.indexOf('/api/')
if (idx >= 0) return pathname.slice(idx).replace(/\/+$/, '') || '/api'
if (pathname.startsWith('/api/')) return pathname.replace(/\/+$/, '')
return null
}
/**
* Hosted Raycast API: bearer-token list/run without minted webhook URLs.
* The package app runtime calls this default export as `fetch`.
*
* @example
* import handleApp from 'kody:@kentcdodds/raycast/app'
* await handleApp(new Request('https://example.com/api/list-commands', { method: 'POST' }))
*/
export default async function handleApp(request: Request): Promise<Response> {
const url = new URL(request.url)
const apiPath = apiPathOf(url.pathname)
if (request.method === 'GET' && !apiPath) {
return json({
ok: true,
name: '@kentcdodds/raycast',
routes: ['POST /api/list-commands', 'POST /api/run'],
auth: 'Authorization: Bearer <launcher-token>',
})
}
if (!apiPath) return json({ error: 'not_found' }, 404)
const token = bearerFromRequest(request)
if (!(await verifyLauncherToken(token))) {
return json({ error: 'unauthorized' }, 401)
}
try {
if (apiPath === '/api/list-commands' && (request.method === 'POST' || request.method === 'GET')) {
const params = await readBody(request)
return json(await listCommands({ includeCode: Boolean(params.includeCode) }))
}
if (apiPath === '/api/run' && request.method === 'POST') {
const params = await readBody(request)
const nested =
params.params && typeof params.params === 'object' && !Array.isArray(params.params)
? (params.params as Record<string, unknown>)
: {}
return json(
await run({
commandId: typeof params.commandId === 'string' ? params.commandId : undefined,
code: typeof params.code === 'string' ? params.code : undefined,
params: nested,
idempotencyKey:
request.headers.get('idempotency-key') ||
(typeof params.idempotencyKey === 'string' ? params.idempotencyKey : undefined),
}),
)
}
return json({ error: 'not_found' }, 404)
} catch (error) {
const message = error instanceof Error ? error.message : String(error)
return json({ error: message }, 400)
}
}