{"version":3,"sources":["../src/server/handlers/skills-sh-shared.ts"],"names":["HTTPException"],"mappings":";;;;;AAmBO,IAAM,iBAAA,GAAoB;AAO1B,IAAM,aAAA,GAAgB;AAE7B,IAAM,iBAAA,GAAoB,GAAA;AAC1B,IAAM,kBAAA,GAAqB,GAAA;AAC3B,IAAM,qBAAA,GAAwB,GAAA;AAwD9B,IAAM,gBAAA,GAAmB,wBAAA;AAElB,SAAS,oBAAoB,IAAA,EAAsB;AACxD,EAAA,IAAI,CAAC,gBAAA,CAAiB,IAAA,CAAK,IAAI,CAAA,EAAG;AAChC,IAAA,MAAM,IAAIA,gCAAc,GAAA,EAAK;AAAA,MAC3B,OAAA,EAAS,uBAAuB,IAAI,CAAA,kGAAA;AAAA,KACrC,CAAA;AAAA,EACH;AACA,EAAA,OAAO,IAAA;AACT;AAOO,SAAS,mBAAmB,QAAA,EAA0B;AAC3D,EAAA,IAAI,QAAA,CAAS,UAAA,CAAW,GAAG,CAAA,IAAK,QAAA,CAAS,UAAA,CAAW,IAAI,CAAA,IAAK,iBAAA,CAAkB,IAAA,CAAK,QAAQ,CAAA,EAAG;AAC7F,IAAA,MAAM,IAAIA,gCAAc,GAAA,EAAK;AAAA,MAC3B,OAAA,EAAS,sBAAsB,QAAQ,CAAA,kCAAA;AAAA,KACxC,CAAA;AAAA,EACH;AAGA,EAAA,MAAM,QAAA,GAAW,QAAA,CAAS,KAAA,CAAM,OAAO,CAAA;AACvC,EAAA,KAAA,MAAW,WAAW,QAAA,EAAU;AAC9B,IAAA,IAAI,OAAA,KAAY,IAAA,IAAQ,OAAA,KAAY,GAAA,EAAK;AACvC,MAAA,MAAM,IAAIA,gCAAc,GAAA,EAAK;AAAA,QAC3B,OAAA,EAAS,sBAAsB,QAAQ,CAAA,iCAAA;AAAA,OACxC,CAAA;AAAA,IACH;AAAA,EACF;AACA,EAAA,OAAO,QAAA;AACT;AAwBA,eAAsB,cAAA,CAAe,EAAE,CAAA,EAAG,KAAA,EAAM,EAAgE;AAC9G,EAAA,MAAM,UAAA,GAAa,IAAI,eAAA,EAAgB;AACvC,EAAA,MAAM,YAAY,UAAA,CAAW,MAAM,UAAA,CAAW,KAAA,IAAS,iBAAiB,CAAA;AAExE,EAAA,IAAI;AACF,IAAA,MAAM,GAAA,GAAM,GAAG,iBAAiB,CAAA,kBAAA,EAAqB,mBAAmB,CAAC,CAAC,aAAa,KAAK,CAAA,CAAA;AAC5F,IAAA,MAAM,QAAA,GAAW,MAAM,KAAA,CAAM,GAAA,EAAK,EAAE,MAAA,EAAQ,UAAA,CAAW,QAAQ,CAAA;AAE/D,IAAA,IAAI,CAAC,SAAS,EAAA,EAAI;AAChB,MAAA,MAAM,IAAIA,gCAAc,GAAA,EAAK;AAAA,QAC3B,SAAS,CAAA,kBAAA,EAAqB,QAAA,CAAS,MAAM,CAAA,CAAA,EAAI,SAAS,UAAU,CAAA;AAAA,OACrE,CAAA;AAAA,IACH;AAEA,IAAA,MAAM,IAAA,GAAQ,MAAM,QAAA,CAAS,IAAA,EAAK;AAClC,IAAA,OAAO;AAAA,MACL,KAAA,EAAO,CAAA;AAAA,MACP,UAAA,EAAY,OAAA;AAAA,MACZ,QAAQ,IAAA,CAAK,MAAA,CAAO,IAAI,CAAA,CAAA,MAAM,EAAE,IAAI,CAAA,CAAE,OAAA,EAAS,IAAA,EAAM,CAAA,CAAE,MAAM,QAAA,EAAU,CAAA,CAAE,UAAU,SAAA,EAAW,CAAA,CAAE,QAAO,CAAE,CAAA;AAAA,MACzG,OAAO,IAAA,CAAK;AAAA,KACd;AAAA,EACF,CAAA,SAAE;AACA,IAAA,YAAA,CAAa,SAAS,CAAA;AAAA,EACxB;AACF;AAGA,eAAsB,kBAAA,CAAmB;AAAA,EACvC,KAAA;AAAA,EACA;AACF,CAAA,EAGmC;AACjC,EAAA,MAAM,UAAA,GAAa,IAAI,eAAA,EAAgB;AACvC,EAAA,MAAM,YAAY,UAAA,CAAW,MAAM,UAAA,CAAW,KAAA,IAAS,iBAAiB,CAAA;AAExE,EAAA,IAAI;AACF,IAAA,MAAM,IAAA,GAAO,SAAS,CAAA,GAAI,IAAA,CAAK,MAAM,MAAA,GAAS,KAAK,IAAI,CAAA,GAAI,CAAA;AAC3D,IAAA,MAAM,MAAM,CAAA,EAAG,iBAAiB,CAAA,yBAAA,EAA4B,KAAK,SAAS,IAAI,CAAA,CAAA;AAC9E,IAAA,MAAM,QAAA,GAAW,MAAM,KAAA,CAAM,GAAA,EAAK,EAAE,MAAA,EAAQ,UAAA,CAAW,QAAQ,CAAA;AAE/D,IAAA,IAAI,CAAC,SAAS,EAAA,EAAI;AAChB,MAAA,MAAM,IAAIA,gCAAc,GAAA,EAAK;AAAA,QAC3B,SAAS,CAAA,kBAAA,EAAqB,QAAA,CAAS,MAAM,CAAA,CAAA,EAAI,SAAS,UAAU,CAAA;AAAA,OACrE,CAAA;AAAA,IACH;AAEA,IAAA,MAAM,IAAA,GAAQ,MAAM,QAAA,CAAS,IAAA,EAAK;AAClC,IAAA,OAAO;AAAA,MACL,QAAQ,IAAA,CAAK,MAAA,CAAO,IAAI,CAAA,CAAA,MAAM,EAAE,IAAI,CAAA,CAAE,OAAA,EAAS,IAAA,EAAM,CAAA,CAAE,MAAM,QAAA,EAAU,CAAA,CAAE,UAAU,SAAA,EAAW,CAAA,CAAE,QAAO,CAAE,CAAA;AAAA,MACzG,OAAO,IAAA,CAAK,KAAA;AAAA,MACZ,KAAA;AAAA,MACA;AAAA,KACF;AAAA,EACF,CAAA,SAAE;AACA,IAAA,YAAA,CAAa,SAAS,CAAA;AAAA,EACxB;AACF;AAOA,eAAsB,eAAA,CAAgB;AAAA,EACpC,KAAA;AAAA,EACA,IAAA;AAAA,EACA;AACF,CAAA,EAIiC;AAC/B,EAAA,MAAM,UAAA,GAAa,IAAI,eAAA,EAAgB;AACvC,EAAA,MAAM,YAAY,UAAA,CAAW,MAAM,UAAA,CAAW,KAAA,IAAS,kBAAkB,CAAA;AAEzE,EAAA,IAAI;AACF,IAAA,MAAM,GAAA,GAAM,CAAA,EAAG,iBAAiB,CAAA,YAAA,EAAe,mBAAmB,KAAK,CAAC,CAAA,CAAA,EAAI,kBAAA,CAAmB,IAAI,CAAC,CAAA,CAAA,EAAI,kBAAA,CAAmB,SAAS,CAAC,CAAA,QAAA,CAAA;AACrI,IAAA,MAAM,QAAA,GAAW,MAAM,KAAA,CAAM,GAAA,EAAK,EAAE,MAAA,EAAQ,UAAA,CAAW,QAAQ,CAAA;AAE/D,IAAA,IAAI,CAAC,SAAS,EAAA,EAAI;AAChB,MAAA,IAAI,QAAA,CAAS,WAAW,GAAA,EAAK;AAC3B,QAAA,MAAM,IAAIA,gCAAc,GAAA,EAAK;AAAA,UAC3B,SAAS,CAAA,sBAAA,EAAyB,SAAS,CAAA,MAAA,EAAS,KAAK,IAAI,IAAI,CAAA;AAAA,SAClE,CAAA;AAAA,MACH;AACA,MAAA,MAAM,IAAIA,gCAAc,GAAA,EAAK;AAAA,QAC3B,SAAS,CAAA,kBAAA,EAAqB,QAAA,CAAS,MAAM,CAAA,CAAA,EAAI,SAAS,UAAU,CAAA;AAAA,OACrE,CAAA;AAAA,IACH;AAEA,IAAA,MAAM,IAAA,GAAQ,MAAM,QAAA,CAAS,IAAA,EAAK;AAClC,IAAA,MAAM,OAAA,GAAU,IAAA,CAAK,YAAA,IAAgB,IAAA,CAAK,GAAA,IAAO,EAAA;AAEjD,IAAA,IAAI,CAAC,OAAA,EAAS;AACZ,MAAA,MAAM,IAAIA,gCAAc,GAAA,EAAK;AAAA,QAC3B,OAAA,EAAS,mCAAmC,SAAS,CAAA,CAAA;AAAA,OACtD,CAAA;AAAA,IACH;AAEA,IAAA,OAAO,EAAE,OAAA,EAAQ;AAAA,EACnB,CAAA,SAAE;AACA,IAAA,YAAA,CAAa,SAAS,CAAA;AAAA,EACxB;AACF;AAQA,eAAsB,eAAA,CACpB,KAAA,EACA,IAAA,EACA,SAAA,EACoC;AACpC,EAAA,MAAM,UAAA,GAAa,IAAI,eAAA,EAAgB;AACvC,EAAA,MAAM,YAAY,UAAA,CAAW,MAAM,UAAA,CAAW,KAAA,IAAS,qBAAqB,CAAA;AAE5E,EAAA,IAAI;AACF,IAAA,MAAM,GAAA,GAAM,CAAA,EAAG,iBAAiB,CAAA,YAAA,EAAe,mBAAmB,KAAK,CAAC,CAAA,CAAA,EAAI,kBAAA,CAAmB,IAAI,CAAC,CAAA,CAAA,EAAI,kBAAA,CAAmB,SAAS,CAAC,CAAA,MAAA,CAAA;AACrI,IAAA,MAAM,QAAA,GAAW,MAAM,KAAA,CAAM,GAAA,EAAK,EAAE,MAAA,EAAQ,UAAA,CAAW,QAAQ,CAAA;AAE/D,IAAA,IAAI,CAAC,SAAS,EAAA,EAAI;AAChB,MAAA,IAAI,QAAA,CAAS,WAAW,GAAA,EAAK;AAC3B,QAAA,OAAO,IAAA;AAAA,MACT;AACA,MAAA,MAAM,IAAI,MAAM,CAAA,kBAAA,EAAqB,QAAA,CAAS,MAAM,CAAA,CAAA,EAAI,QAAA,CAAS,UAAU,CAAA,CAAE,CAAA;AAAA,IAC/E;AAEA,IAAA,OAAQ,MAAM,SAAS,IAAA,EAAK;AAAA,EAC9B,CAAA,SAAE;AACA,IAAA,YAAA,CAAa,SAAS,CAAA;AAAA,EACxB;AACF","file":"chunk-AR7VSXHH.cjs","sourcesContent":["/**\n * Shared skills.sh helpers\n *\n * Pure proxy + safety logic for talking to the skills.sh API. Used by:\n * - Workspace skills.sh routes (filesystem-only install).\n * - Builder registry routes (stored-skill install with optional workspace\n * materialization).\n *\n * No workspace or stored-skill concepts leak into this module. Path resolution,\n * filesystem writes, and DB writes happen in the callers.\n */\n\nimport { HTTPException } from '../http-exception';\n\n// =============================================================================\n// Constants\n// =============================================================================\n\n/** Upstream skills.sh API base URL. */\nexport const SKILLS_SH_API_URL = 'https://skills-api-production.up.railway.app';\n\n/**\n * Directory path for skills installed via skills.sh, relative to the workspace\n * filesystem root (or to a CompositeFilesystem mount). Matches the convention\n * shared with Claude Code and other agent runtimes.\n */\nexport const SKILLS_SH_DIR = '.agents/skills';\n\nconst SEARCH_TIMEOUT_MS = 10_000;\nconst PREVIEW_TIMEOUT_MS = 10_000;\nconst FILE_FETCH_TIMEOUT_MS = 30_000;\n\n// =============================================================================\n// Types\n// =============================================================================\n\n/** Single skill summary returned by search/popular endpoints. */\nexport interface SkillsShSkillSummary {\n id: string;\n name: string;\n installs: number;\n topSource: string;\n}\n\nexport interface SkillsShSearchResult {\n query: string;\n searchType: 'query';\n skills: SkillsShSkillSummary[];\n count: number;\n}\n\nexport interface SkillsShPopularResult {\n skills: SkillsShSkillSummary[];\n count: number;\n limit: number;\n offset: number;\n}\n\n/** A single file from the skills.sh files endpoint. */\nexport interface SkillFileEntry {\n path: string;\n content: string;\n encoding: 'utf-8' | 'base64';\n}\n\n/** Response from the skills.sh files endpoint. */\nexport interface SkillFilesResponse {\n skillId: string;\n owner: string;\n repo: string;\n branch: string;\n files: SkillFileEntry[];\n}\n\n// =============================================================================\n// Safety validators\n// =============================================================================\n\n/**\n * Validate skill name to prevent path traversal attacks. Only allows\n * alphanumeric characters, hyphens, and underscores; must start with an\n * alphanumeric character.\n *\n * Throws an HTTP 400 on invalid input. Returns the validated name on success\n * so it can be used inline.\n */\nconst SKILL_NAME_REGEX = /^[a-z0-9][a-z0-9-_]*$/i;\n\nexport function assertSafeSkillName(name: string): string {\n if (!SKILL_NAME_REGEX.test(name)) {\n throw new HTTPException(400, {\n message: `Invalid skill name \"${name}\". Names must start with alphanumeric and contain only letters, numbers, hyphens, and underscores.`,\n });\n }\n return name;\n}\n\n/**\n * Validate that a file path is safe (no traversal, no absolute paths).\n * Prevents malicious API responses from writing files outside the skill\n * directory.\n */\nexport function assertSafeFilePath(filePath: string): string {\n if (filePath.startsWith('/') || filePath.startsWith('\\\\') || /^[a-zA-Z]:[\\\\/]/.test(filePath)) {\n throw new HTTPException(400, {\n message: `Invalid file path \"${filePath}\". Absolute paths are not allowed.`,\n });\n }\n // Normalize backslashes to forward slashes so Windows-style traversal\n // (e.g. \"..\\\\..\\\\etc\\\\passwd\") cannot bypass the segment check below.\n const segments = filePath.split(/[\\\\/]/);\n for (const segment of segments) {\n if (segment === '..' || segment === '.') {\n throw new HTTPException(400, {\n message: `Invalid file path \"${filePath}\". Path traversal is not allowed.`,\n });\n }\n }\n return filePath;\n}\n\n// =============================================================================\n// API calls\n// =============================================================================\n\ninterface UpstreamSkillsList {\n skills: Array<{\n skillId: string;\n name: string;\n installs: number;\n source: string;\n owner: string;\n repo: string;\n githubUrl: string;\n displayName: string;\n }>;\n total: number;\n page?: number;\n pageSize?: number;\n totalPages?: number;\n}\n\n/** Search skills.sh by query string. Throws HTTPException on upstream failure. */\nexport async function searchSkillsSh({ q, limit }: { q: string; limit: number }): Promise {\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), SEARCH_TIMEOUT_MS);\n\n try {\n const url = `${SKILLS_SH_API_URL}/api/skills?query=${encodeURIComponent(q)}&pageSize=${limit}`;\n const response = await fetch(url, { signal: controller.signal });\n\n if (!response.ok) {\n throw new HTTPException(502, {\n message: `Skills API error: ${response.status} ${response.statusText}`,\n });\n }\n\n const data = (await response.json()) as UpstreamSkillsList;\n return {\n query: q,\n searchType: 'query',\n skills: data.skills.map(s => ({ id: s.skillId, name: s.name, installs: s.installs, topSource: s.source })),\n count: data.total,\n };\n } finally {\n clearTimeout(timeoutId);\n }\n}\n\n/** Fetch the popular skills list from skills.sh. */\nexport async function getPopularSkillsSh({\n limit,\n offset,\n}: {\n limit: number;\n offset: number;\n}): Promise {\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), SEARCH_TIMEOUT_MS);\n\n try {\n const page = offset > 0 ? Math.floor(offset / limit) + 1 : 1;\n const url = `${SKILLS_SH_API_URL}/api/skills/top?pageSize=${limit}&page=${page}`;\n const response = await fetch(url, { signal: controller.signal });\n\n if (!response.ok) {\n throw new HTTPException(502, {\n message: `Skills API error: ${response.status} ${response.statusText}`,\n });\n }\n\n const data = (await response.json()) as UpstreamSkillsList;\n return {\n skills: data.skills.map(s => ({ id: s.skillId, name: s.name, installs: s.installs, topSource: s.source })),\n count: data.total,\n limit,\n offset,\n };\n } finally {\n clearTimeout(timeoutId);\n }\n}\n\n/**\n * Fetch the rendered SKILL.md / instructions content for a single skill.\n * Returns the content string, or throws HTTPException(404) when the skill\n * doesn't exist or has no content.\n */\nexport async function previewSkillsSh({\n owner,\n repo,\n skillName,\n}: {\n owner: string;\n repo: string;\n skillName: string;\n}): Promise<{ content: string }> {\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), PREVIEW_TIMEOUT_MS);\n\n try {\n const url = `${SKILLS_SH_API_URL}/api/skills/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/${encodeURIComponent(skillName)}/content`;\n const response = await fetch(url, { signal: controller.signal });\n\n if (!response.ok) {\n if (response.status === 404) {\n throw new HTTPException(404, {\n message: `Could not find skill \"${skillName}\" for ${owner}/${repo}`,\n });\n }\n throw new HTTPException(502, {\n message: `Skills API error: ${response.status} ${response.statusText}`,\n });\n }\n\n const data = (await response.json()) as { instructions?: string; raw?: string };\n const content = data.instructions || data.raw || '';\n\n if (!content) {\n throw new HTTPException(404, {\n message: `No content available for skill \"${skillName}\"`,\n });\n }\n\n return { content };\n } finally {\n clearTimeout(timeoutId);\n }\n}\n\n/**\n * Fetch the full file tree for a skill from the skills.sh files endpoint.\n * Returns null when the skill doesn't exist (404). Throws on other upstream\n * errors. Caller is responsible for validating each returned file path with\n * `assertSafeFilePath` before writing to disk.\n */\nexport async function fetchSkillFiles(\n owner: string,\n repo: string,\n skillName: string,\n): Promise {\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), FILE_FETCH_TIMEOUT_MS);\n\n try {\n const url = `${SKILLS_SH_API_URL}/api/skills/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/${encodeURIComponent(skillName)}/files`;\n const response = await fetch(url, { signal: controller.signal });\n\n if (!response.ok) {\n if (response.status === 404) {\n return null;\n }\n throw new Error(`Skills API error: ${response.status} ${response.statusText}`);\n }\n\n return (await response.json()) as SkillFilesResponse;\n } finally {\n clearTimeout(timeoutId);\n }\n}\n"]}