import { cursorApi, trimString } from './lib/client.ts' export type ArtifactItem = { path: string sizeBytes?: number updatedAt?: string [key: string]: unknown } export type ListArtifactsResult = { items: ArtifactItem[] [key: string]: unknown } function requireAgentId(agentId: string | undefined): string { const id = trimString(agentId) if (!id) throw new Error('agentId is required') return id } /** * List artifacts produced by an agent. * * @param params.agentId - Agent id. * @returns Artifact paths relative to the workspace `artifacts/` directory. * @example * import { listArtifacts } from 'kody:@kentcdodds/cursor/artifacts' * const { items } = await listArtifacts({ agentId: 'bc-...' }) * // => { items: [{ path: 'artifacts/screenshot.png', sizeBytes: 12345 }] } */ export async function listArtifacts(params: { agentId: string }): Promise { const agentId = requireAgentId(params.agentId) return cursorApi({ path: `/v1/agents/${agentId}/artifacts`, }) } const TEXT_TYPES = new Set([ 'text/plain', 'text/html', 'text/css', 'text/csv', 'application/json', 'application/xml', 'text/markdown', 'application/javascript', ]) function isTextualContentType(contentType: string): boolean { const base = contentType.split(';')[0].trim().toLowerCase() return TEXT_TYPES.has(base) || base.startsWith('text/') } export type DownloadArtifactResult = | { contentType: string; text: string } | { contentType: string; base64: string } /** * Download an artifact by path (fetches presigned URL content). * * @param params.agentId - Agent id. * @param params.path - Relative artifact path from listArtifacts. * @returns Text for textual content, or base64 for binary. * @example * import { downloadArtifact } from 'kody:@kentcdodds/cursor/artifacts' * const file = await downloadArtifact({ * agentId: 'bc-...', * path: 'artifacts/screenshot.png', * }) * // => { contentType: 'image/png', base64: '...' } */ export async function downloadArtifact(params: { agentId: string path: string }): Promise { const agentId = requireAgentId(params.agentId) const path = trimString(params.path) if (!path) throw new Error('path is required') const presign = (await cursorApi<{ url?: string; expiresAt?: string }>({ path: `/v1/agents/${agentId}/artifacts/download`, query: { path }, })) as { url?: string } const url = trimString(presign.url) if (!url) throw new Error('Artifact download URL was not returned') const response = await fetch(url) if (!response.ok) { throw new Error(`Artifact fetch ${response.status}`) } const contentType = response.headers.get('content-type') || 'application/octet-stream' const buffer = await response.arrayBuffer() if (isTextualContentType(contentType)) { const text = new TextDecoder().decode(buffer) return { contentType, text } } const bytes = new Uint8Array(buffer) let binary = '' for (let i = 0; i < bytes.length; i++) { binary += String.fromCharCode(bytes[i]) } return { contentType, base64: btoa(binary) } } /** Default export: list artifacts for an agent. */ export default listArtifacts