@borgresearch/odin-mcp 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +91 -16
- package/dist/chunk-4LCKW236.js +205 -0
- package/dist/chunk-4LCKW236.js.map +1 -0
- package/dist/chunk-D5FYYTYV.js +1211 -0
- package/dist/chunk-D5FYYTYV.js.map +1 -0
- package/dist/chunk-ODZB3YP2.js +113 -0
- package/dist/chunk-ODZB3YP2.js.map +1 -0
- package/dist/chunk-XHJYVUHG.js +195 -0
- package/dist/chunk-XHJYVUHG.js.map +1 -0
- package/dist/client.d.ts +39 -0
- package/dist/client.js +12 -0
- package/dist/client.js.map +1 -0
- package/dist/guidance.d.ts +53 -0
- package/dist/guidance.js +18 -0
- package/dist/guidance.js.map +1 -0
- package/dist/http.d.ts +8 -0
- package/dist/http.js +34 -0
- package/dist/http.js.map +1 -0
- package/dist/index.js +15 -782
- package/dist/index.js.map +1 -1
- package/dist/server.d.ts +11 -0
- package/dist/server.js +13 -0
- package/dist/server.js.map +1 -0
- package/dist/tools.d.ts +29 -0
- package/dist/tools.js +9 -0
- package/dist/tools.js.map +1 -0
- package/package.json +26 -4
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/client.ts","../src/tools/get-asset.ts","../src/formatters/markdown.ts","../src/tools/finding-reference.ts","../src/tools/get-finding.ts","../src/tools/get-finding-retests.ts","../src/tools/get-pentest.ts","../src/tools/link-finding-pull-request.ts","../src/tools/list-assets.ts","../src/tools/list-findings.ts","../src/tools/list-pentests.ts","../src/tools/update-finding-status.ts"],"sourcesContent":["import { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport { StdioServerTransport } from \"@modelcontextprotocol/sdk/server/stdio.js\";\nimport { DEFAULT_ODIN_BASE_URL, OdinApiError, OdinClient } from \"./client.js\";\nimport { getAssetTool } from \"./tools/get-asset.js\";\nimport { getFindingTool } from \"./tools/get-finding.js\";\nimport { getFindingRetestsTool } from \"./tools/get-finding-retests.js\";\nimport { getPentestTool } from \"./tools/get-pentest.js\";\nimport { linkFindingPullRequestTool } from \"./tools/link-finding-pull-request.js\";\nimport { listAssetsTool } from \"./tools/list-assets.js\";\nimport { listFindingsTool } from \"./tools/list-findings.js\";\nimport { listPentestsTool } from \"./tools/list-pentests.js\";\nimport { updateFindingStatusTool } from \"./tools/update-finding-status.js\";\n\nconst PACKAGE_VERSION = \"0.2.0\";\n\ninterface ToolResult {\n markdown: string;\n structured: unknown;\n}\n\nasync function main() {\n const apiKey = process.env.ODIN_API_KEY;\n if (!apiKey) {\n console.error(\n \"ODIN_API_KEY is not set or invalid. Create one at Management > API Keys in the Odin dashboard.\",\n );\n process.exit(1);\n }\n\n const baseUrl = process.env.ODIN_BASE_URL ?? DEFAULT_ODIN_BASE_URL;\n const client = new OdinClient({\n apiKey,\n baseUrl,\n vercelProtectionBypassSecret:\n process.env.ODIN_VERCEL_PROTECTION_BYPASS_SECRET,\n });\n\n const server = new McpServer({\n name: \"@borgresearch/odin-mcp\",\n version: PACKAGE_VERSION,\n });\n\n const tools = [\n listFindingsTool,\n getFindingTool,\n updateFindingStatusTool,\n linkFindingPullRequestTool,\n getFindingRetestsTool,\n listPentestsTool,\n getPentestTool,\n listAssetsTool,\n getAssetTool,\n ];\n\n for (const tool of tools) {\n const run = tool.handler(client) as (\n params: unknown,\n ) => Promise<ToolResult>;\n server.registerTool(\n tool.name,\n {\n title: tool.title,\n description: tool.description,\n inputSchema: tool.inputSchema.shape,\n },\n async (params: unknown) => {\n try {\n const { markdown, structured } = await run(params);\n return {\n content: [{ type: \"text\" as const, text: markdown }],\n structuredContent: { markdown, ...toObject(structured) },\n };\n } catch (error) {\n return {\n isError: true,\n content: [{ type: \"text\" as const, text: toErrorMessage(error) }],\n };\n }\n },\n );\n }\n\n const transport = new StdioServerTransport();\n await server.connect(transport);\n console.error(\n `odin-mcp ${PACKAGE_VERSION} connected (baseUrl=${baseUrl}, tools=${tools.length}).`,\n );\n}\n\nfunction toObject(value: unknown): Record<string, unknown> {\n if (value && typeof value === \"object\" && !Array.isArray(value)) {\n return value as Record<string, unknown>;\n }\n return { data: value };\n}\n\nfunction toErrorMessage(error: unknown): string {\n if (error instanceof OdinApiError) return error.message;\n if (error instanceof Error) return `Unexpected error: ${error.message}`;\n return `Unexpected error: ${String(error)}`;\n}\n\nmain().catch((error) => {\n console.error(\"odin-mcp failed to start:\", error);\n process.exit(1);\n});\n","export class OdinApiError extends Error {\n readonly status: number;\n readonly path: string;\n readonly method: string;\n readonly retryAfterSeconds: number | null;\n readonly body: unknown;\n\n constructor(params: {\n status: number;\n path: string;\n method: string;\n retryAfterSeconds: number | null;\n body: unknown;\n message: string;\n }) {\n super(params.message);\n this.name = \"OdinApiError\";\n this.status = params.status;\n this.path = params.path;\n this.method = params.method;\n this.retryAfterSeconds = params.retryAfterSeconds;\n this.body = params.body;\n }\n}\n\nexport interface OdinClientOptions {\n apiKey: string;\n baseUrl?: string;\n fetchImpl?: typeof fetch;\n vercelProtectionBypassSecret?: string;\n}\n\nexport const DEFAULT_ODIN_BASE_URL = \"https://odin.borghq.io\";\n\nconst WRITE_METHODS = new Set([\"PATCH\", \"POST\", \"PUT\", \"DELETE\"]);\n\nexport class OdinClient {\n private readonly baseUrl: string;\n private readonly apiKey: string;\n private readonly fetchImpl: typeof fetch;\n private readonly vercelProtectionBypassSecret: string | undefined;\n\n constructor(options: OdinClientOptions) {\n if (!options.apiKey) {\n throw new Error(\"OdinClient requires an apiKey\");\n }\n this.apiKey = options.apiKey;\n this.baseUrl = (options.baseUrl ?? DEFAULT_ODIN_BASE_URL).replace(\n /\\/+$/,\n \"\",\n );\n this.fetchImpl = options.fetchImpl ?? fetch;\n this.vercelProtectionBypassSecret =\n options.vercelProtectionBypassSecret?.trim() || undefined;\n }\n\n async get<T>(\n path: string,\n searchParams?: Record<string, string | number | undefined>,\n ): Promise<T> {\n const url = this.buildUrl(path, searchParams);\n return this.request<T>(\"GET\", url, path);\n }\n\n async patch<T>(path: string, body: unknown): Promise<T> {\n const url = this.buildUrl(path);\n return this.request<T>(\"PATCH\", url, path, body);\n }\n\n // Public HTTP method API — kept alongside get/patch even when unused internally\n // fallow-ignore-next-line unused-class-member\n async post<T>(path: string, body: unknown): Promise<T> {\n const url = this.buildUrl(path);\n return this.request<T>(\"POST\", url, path, body);\n }\n\n private buildUrl(\n path: string,\n searchParams?: Record<string, string | number | undefined>,\n ): string {\n const url = new URL(path.startsWith(\"/\") ? path : `/${path}`, this.baseUrl);\n if (searchParams) {\n for (const [key, value] of Object.entries(searchParams)) {\n if (value === undefined || value === \"\") continue;\n url.searchParams.set(key, String(value));\n }\n }\n return url.toString();\n }\n\n private async request<T>(\n method: string,\n url: string,\n path: string,\n body?: unknown,\n ): Promise<T> {\n const headers: Record<string, string> = {\n Authorization: `Bearer ${this.apiKey}`,\n \"Content-Type\": \"application/json\",\n Accept: \"application/json\",\n };\n if (this.vercelProtectionBypassSecret) {\n headers[\"x-vercel-protection-bypass\"] = this.vercelProtectionBypassSecret;\n }\n\n const response = await this.fetchImpl(url, {\n method,\n headers,\n body: body === undefined ? undefined : JSON.stringify(body),\n });\n\n const rawText = await response.text();\n const parsed = rawText ? safeJsonParse(rawText) : null;\n\n if (!response.ok) {\n const retryAfterSeconds = parseRetryAfter(\n response.headers.get(\"Retry-After\"),\n );\n throw new OdinApiError({\n status: response.status,\n path,\n method,\n retryAfterSeconds,\n body: parsed ?? rawText,\n message: formatErrorMessage({\n status: response.status,\n method,\n path,\n retryAfterSeconds,\n parsed,\n rawText,\n }),\n });\n }\n\n return parsed as T;\n }\n}\n\nfunction safeJsonParse(text: string): unknown {\n try {\n return JSON.parse(text);\n } catch {\n return text;\n }\n}\n\nfunction parseRetryAfter(header: string | null): number | null {\n if (!header) return null;\n const trimmed = header.trim();\n if (/^\\d+$/.test(trimmed)) return Number.parseInt(trimmed, 10);\n const dateMs = Date.parse(trimmed);\n if (Number.isNaN(dateMs)) return null;\n const seconds = Math.ceil((dateMs - Date.now()) / 1000);\n return seconds > 0 ? seconds : null;\n}\n\ninterface FormatErrorInput {\n status: number;\n method: string;\n path: string;\n retryAfterSeconds: number | null;\n parsed: unknown;\n rawText: string;\n}\n\nfunction formatErrorMessage(input: FormatErrorInput): string {\n const serverMessage = extractServerMessage(input.parsed);\n const actionable = actionableMessage(input);\n if (actionable) {\n return serverMessage\n ? `${actionable} (server: ${serverMessage})`\n : actionable;\n }\n const suffix = serverMessage\n ? ` — ${serverMessage}`\n : input.rawText\n ? ` — ${input.rawText.slice(0, 200)}`\n : \"\";\n return `Odin API ${input.status} on ${input.path}${suffix}.`;\n}\n\nfunction actionableMessage(input: FormatErrorInput): string | null {\n switch (input.status) {\n case 401:\n return \"ODIN_API_KEY is not set or invalid. Create one at Management > API Keys in the Odin dashboard.\";\n case 403:\n if (WRITE_METHODS.has(input.method)) {\n return \"API key does not have write permission. Create a new key with 'Read & Write' permissions.\";\n }\n return null;\n case 404:\n if (isFindingDetailPath(input.path)) {\n return \"Finding not found. Check the finding ID is correct.\";\n }\n return null;\n case 429: {\n const n = input.retryAfterSeconds;\n return n\n ? `Rate limit exceeded. Try again in ${n} seconds.`\n : \"Rate limit exceeded. Try again shortly.\";\n }\n default:\n return null;\n }\n}\n\nfunction isFindingDetailPath(path: string): boolean {\n // /api/findings/{id} or /api/findings/{id}/status\n return /^\\/api\\/findings\\/[^/]+(\\/.*)?$/.test(path);\n}\n\nfunction extractServerMessage(parsed: unknown): string | null {\n if (parsed && typeof parsed === \"object\" && \"error\" in parsed) {\n const err = (parsed as { error: unknown }).error;\n if (typeof err === \"string\") return err;\n }\n return null;\n}\n","import { z } from \"zod\";\nimport type { OdinClient } from \"../client.js\";\nimport { formatAssetDetail } from \"../formatters/markdown.js\";\nimport type { AssetDetailResponse } from \"../types.js\";\n\nexport const getAssetInput = z.object({\n assetId: z.string().min(1),\n});\n\nexport const getAssetTool = {\n name: \"get_asset\",\n title: \"Get asset\",\n description:\n \"Fetch a single attack-surface asset by ID, including the findings that reference it in their latest revision.\",\n inputSchema: getAssetInput,\n handler:\n (client: OdinClient) => async (params: z.infer<typeof getAssetInput>) => {\n const data = await client.get<AssetDetailResponse>(\n `/api/assets/${encodeURIComponent(params.assetId)}`,\n );\n return {\n markdown: formatAssetDetail(data),\n structured: { asset: data.asset, findings: data.findings },\n };\n },\n};\n","import type {\n Asset,\n AssetDetailResponse,\n AssetsListResponse,\n FindingDetailResponse,\n FindingStatus,\n FindingSummary,\n FindingsListResponse,\n LinkFindingPullRequestResponse,\n Pentest,\n PentestDetailResponse,\n PentestListResponse,\n Retest,\n RetestHistoryResponse,\n} from \"../types.js\";\n\nconst STATUS_LABELS: Record<FindingStatus, string> = {\n REPORTED: \"Reported\",\n ACKNOWLEDGED: \"Acknowledged\",\n MITIGATING: \"Mitigating\",\n OPEN_FOR_RETEST: \"Open for Retest\",\n NEEDS_REVISION: \"Needs Revision\",\n FIXED_AND_RETESTED: \"Fixed & Retested\",\n};\n\nconst RETEST_STATUS_LABELS: Record<Retest[\"status\"], string> = {\n QUEUED: \"Queued\",\n RUNNING: \"Running\",\n COMPLETED: \"Completed\",\n FAILED: \"Failed\",\n SKIPPED: \"Skipped\",\n};\n\nexport function formatFindingsList(data: FindingsListResponse): string {\n const { findings, stats, pagination } = data;\n if (findings.length === 0) {\n return \"No findings match the supplied filters.\";\n }\n\n const rows = findings.map((f) => {\n const severity = `${f.severity.label} (CVSS ${f.severity.cvss.toFixed(1)})`;\n return `| ${f.identifier ?? \"—\"} | ${f.findingId} | ${escapePipes(f.title)} | ${severity} | ${formatStatus(f.status)} | ${formatDate(f.createdAt)} |`;\n });\n\n return [\n `Showing ${findings.length} of ${stats.total} finding(s). Offset ${pagination.offset}, limit ${pagination.limit}. hasMore=${pagination.hasMore}.`,\n \"\",\n \"| Identifier | Internal ID | Title | Severity | Status | Created |\",\n \"| --- | --- | --- | --- | --- | --- |\",\n ...rows,\n ].join(\"\\n\");\n}\n\nexport function formatFindingDetail(data: FindingDetailResponse): string {\n const { finding, revision } = data;\n const context = data.remediationContext;\n const identifier = context?.identifier ?? finding.identifier ?? null;\n const displayIdentifier = identifier ?? finding.findingId;\n const severity = `${finding.severity.label} (CVSS ${finding.severity.cvss.toFixed(1)})`;\n const header = [\n `# ${escapePipes(displayIdentifier)} — ${escapePipes(finding.title)}`,\n \"\",\n `- **Finding:** ${displayIdentifier}`,\n `- **Internal ID:** ${finding.findingId}`,\n `- **Severity:** ${severity}`,\n `- **Status:** ${formatStatus(finding.status)}`,\n `- **Repository:** ${context?.repository ?? finding.repository ?? \"Not recorded\"}`,\n `- **Pentest:** ${finding.context?.pentestId ?? \"standalone\"}`,\n `- **Created:** ${formatDate(finding.createdAt)}`,\n `- **Updated:** ${formatDate(finding.updatedAt)}`,\n ];\n\n const content = context\n ? {\n revisionNumber: context.revision.revisionNumber,\n summary: context.revision.summary,\n description: context.revision.description,\n impact: context.revision.impact,\n details: context.revision.details,\n remediation: context.revision.remediation,\n affectedAssets: context.revision.affectedAssets,\n codeReference: context.revision.codeReference,\n }\n : revision\n ? {\n revisionNumber: revision.revisionNumber,\n summary: revision.content.summaryMd,\n description: revision.content.descriptionMd,\n impact: revision.content.businessImpactMd,\n details: revision.content.detailsMd,\n remediation: revision.content.remediationMd,\n affectedAssets: revision.content.affectedAssets ?? [],\n codeReference: null,\n }\n : null;\n\n const sections: string[] = [];\n if (!content) {\n sections.push(\"_No revision content available._\");\n } else {\n sections.push(`_Revision ${content.revisionNumber ?? \"unknown\"}_`);\n if (content.summary) sections.push(\"## Summary\", content.summary);\n if (content.description)\n sections.push(\"## Description\", content.description);\n if (content.impact) sections.push(\"## Business Impact\", content.impact);\n if (content.details) sections.push(\"## Details\", content.details);\n if (content.remediation)\n sections.push(\"## Remediation\", content.remediation);\n if (content.codeReference) {\n sections.push(\n \"## Code reference\",\n `- ${content.codeReference.file}:${content.codeReference.line}`,\n );\n if (content.codeReference.snippet) {\n sections.push(\"```\", content.codeReference.snippet, \"```\");\n }\n }\n if (content.affectedAssets?.length) {\n sections.push(\n \"## Affected Assets\",\n content.affectedAssets\n .map((asset) => `- ${asset.display} (${asset.assetId})`)\n .join(\"\\n\"),\n );\n }\n }\n\n if (context) {\n sections.push(\n \"## Remediation workflow\",\n `- **Suggested branch:** ${context.suggestedBranch ?? \"Not available\"}`,\n `- **Ready for a user-created PR:** ${yesNo(context.workflow.readyForUserCreatedPullRequest)}`,\n `- **GitHub connected:** ${yesNo(context.workflow.githubConnected)}`,\n `- **Automatic retests:** ${context.workflow.automaticRetestsReady ? \"Ready after merge\" : \"Not ready\"}`,\n );\n if (context.workflow.blockers.length) {\n sections.push(\n \"### Blockers\",\n context.workflow.blockers.map((blocker) => `- ${blocker}`).join(\"\\n\"),\n );\n }\n if (context.workflow.nextSteps.length) {\n sections.push(\n \"### Next steps\",\n context.workflow.nextSteps.map((step) => `- ${step}`).join(\"\\n\"),\n );\n }\n\n if (context.linkedPullRequests.length) {\n sections.push(\n \"## Linked GitHub pull requests\",\n context.linkedPullRequests\n .map(\n (pullRequest) =>\n `- [#${pullRequest.number}](${pullRequest.url}) ${escapePipes(pullRequest.title)} — ${pullRequest.state}, linked via ${pullRequest.linkedVia}`,\n )\n .join(\"\\n\"),\n );\n } else {\n sections.push(\"## Linked GitHub pull requests\", \"_None linked._\");\n }\n\n sections.push(\"## Latest retest\");\n if (context.latestRetest.state === \"unavailable\") {\n sections.push(`_${context.latestRetest.reason}_`);\n } else if (context.latestRetest.state === \"none\") {\n sections.push(\"_No retest history is available yet._\");\n } else {\n sections.push(formatRetestSummary(context.latestRetest.retest));\n }\n }\n\n return [...header, \"\", ...sections].join(\"\\n\");\n}\n\nexport function formatFindingStatusUpdate(finding: FindingSummary): string {\n const identifier = finding.identifier ?? finding.findingId;\n return [\n `Updated finding **${identifier}** (internal ID ${finding.findingId}) status to **${formatStatus(finding.status)}**.`,\n `Title: ${finding.title}`,\n ].join(\"\\n\");\n}\n\nexport function formatFindingPullRequestLink(\n data: LinkFindingPullRequestResponse,\n): string {\n const identifier = data.finding.identifier ?? data.finding.findingId;\n const pullRequest = data.pullRequest;\n const readiness = data.workflow.automaticRetestsReady\n ? \"Automatic retesting is ready after this PR merges.\"\n : \"Automatic retesting is not currently ready; review the blockers below.\";\n const blockers = data.workflow.blockers.length\n ? `\\n${data.workflow.blockers.map((blocker) => `- ${blocker}`).join(\"\\n\")}`\n : \"\";\n\n return [\n `Linked existing GitHub pull request [#${pullRequest.number}](${pullRequest.url}) to **${identifier}**.`,\n `Repository: ${pullRequest.repo}; state: ${pullRequest.state}; finding status: ${formatStatus(data.finding.status)}.`,\n \"Odin did not create, modify, merge, or push this pull request.\",\n readiness,\n blockers ? `Workflow blockers:${blockers}` : \"\",\n ]\n .filter(Boolean)\n .join(\"\\n\");\n}\n\nexport function formatFindingRetests(data: RetestHistoryResponse): string {\n if (data.state === \"none\" || data.retests.length === 0) {\n return \"No retest history is available for this finding.\";\n }\n\n return [\n `Retest history (${data.retests.length}):`,\n \"\",\n \"| Retest | Status | Trigger | Verdict | Created | Completed |\",\n \"| --- | --- | --- | --- | --- | --- |\",\n ...data.retests.map(\n (retest) =>\n `| ${retest.retestId} | ${RETEST_STATUS_LABELS[retest.status]} | ${retest.trigger} | ${retest.combinedVerdict ?? \"—\"} | ${formatDate(retest.createdAt)} | ${formatDate(retest.completedAt ?? 0)} |`,\n ),\n \"\",\n \"Queued and running entries are still in progress; completed, skipped, and failed entries are final for that run.\",\n ].join(\"\\n\");\n}\n\nexport function formatPentestsList(data: PentestListResponse): string {\n if (data.pentests.length === 0) return \"No pentests found.\";\n const rows = data.pentests.map(formatPentestRow);\n return [\n \"| Pentest ID | Name | Status | Start | End | Scope |\",\n \"| --- | --- | --- | --- | --- | --- |\",\n ...rows,\n ].join(\"\\n\");\n}\n\nexport function formatPentestDetail(data: PentestDetailResponse): string {\n const p = data.pentest;\n const end = p.endAt ? formatDate(p.endAt) : \"open\";\n const scope = p.scope.length\n ? p.scope.map((s) => `- ${s}`).join(\"\\n\")\n : \"_No scope defined._\";\n return [\n `# ${p.name}`,\n \"\",\n `- **ID:** ${p.pentestId}`,\n `- **Status:** ${p.status}`,\n `- **Start:** ${formatDate(p.startAt)}`,\n `- **End:** ${end}`,\n `- **Created:** ${formatDate(p.createdAt)}`,\n \"\",\n \"## Scope\",\n \"\",\n scope,\n ].join(\"\\n\");\n}\n\nexport function formatFindingsForAsset(findings: FindingSummary[]): string {\n if (findings.length === 0) return \"_No findings reference this asset._\";\n return [\n `## Findings (${findings.length})`,\n \"\",\n \"| Identifier | Internal ID | Title | Severity | Status | Created |\",\n \"| --- | --- | --- | --- | --- | --- |\",\n ...findings.map(formatFindingRow),\n ].join(\"\\n\");\n}\n\nexport function formatAssetsList(data: AssetsListResponse): string {\n const { assets, stats, pagination } = data;\n if (assets.length === 0) return \"No assets match the supplied filters.\";\n const rows = assets.map(formatAssetRow);\n return [\n `Showing ${assets.length} of ${stats.total} asset(s). Offset ${pagination.offset}, limit ${pagination.limit}. hasMore=${pagination.hasMore}.`,\n \"\",\n \"| Asset ID | Type | Value | Source | Status | Last seen |\",\n \"| --- | --- | --- | --- | --- | --- |\",\n ...rows,\n ].join(\"\\n\");\n}\n\nexport function formatAssetDetail(data: AssetDetailResponse): string {\n const { asset, findings } = data;\n const header = [\n `# ${asset.value}`,\n \"\",\n `- **ID:** ${asset.assetId}`,\n `- **Type:** ${asset.type}`,\n `- **Source:** ${asset.source}`,\n `- **Status:** ${asset.status}`,\n `- **Last seen:** ${formatDate(asset.lastSeenAt)}`,\n `- **Created:** ${formatDate(asset.createdAt)}`,\n ];\n\n return [...header, \"\", formatFindingsForAsset(findings)].join(\"\\n\");\n}\n\nfunction formatFindingRow(finding: FindingSummary): string {\n const severity = `${finding.severity.label} (CVSS ${finding.severity.cvss.toFixed(1)})`;\n return `| ${finding.identifier ?? \"—\"} | ${finding.findingId} | ${escapePipes(finding.title)} | ${severity} | ${formatStatus(finding.status)} | ${formatDate(finding.createdAt)} |`;\n}\n\nfunction formatPentestRow(p: Pentest): string {\n const end = p.endAt ? formatDate(p.endAt) : \"open\";\n const scope =\n p.scope.slice(0, 3).join(\", \") + (p.scope.length > 3 ? \", …\" : \"\");\n return `| ${p.pentestId} | ${escapePipes(p.name)} | ${p.status} | ${formatDate(p.startAt)} | ${end} | ${escapePipes(scope)} |`;\n}\n\nfunction formatAssetRow(a: Asset): string {\n return `| ${a.assetId} | ${a.type} | ${escapePipes(a.value)} | ${a.source} | ${a.status} | ${formatDate(a.lastSeenAt)} |`;\n}\n\nfunction formatRetestSummary(retest: {\n status: Retest[\"status\"];\n combinedVerdict: Retest[\"combinedVerdict\"];\n statusAction: Retest[\"statusAction\"];\n trigger: Retest[\"trigger\"];\n createdAt: number;\n completedAt: number | null;\n}): string {\n return [\n `- **Status:** ${RETEST_STATUS_LABELS[retest.status]}`,\n `- **Trigger:** ${retest.trigger}`,\n `- **Verdict:** ${retest.combinedVerdict ?? \"Not available yet\"}`,\n `- **Finding action:** ${retest.statusAction ?? \"None\"}`,\n `- **Created:** ${formatDate(retest.createdAt)}`,\n `- **Completed:** ${formatDate(retest.completedAt ?? 0)}`,\n ].join(\"\\n\");\n}\n\nfunction formatStatus(status: FindingStatus): string {\n return STATUS_LABELS[status] ?? status;\n}\n\nfunction yesNo(value: boolean): string {\n return value ? \"Yes\" : \"No\";\n}\n\nfunction escapePipes(value: string): string {\n return value.replace(/\\|/g, \"\\\\|\");\n}\n\nfunction formatDate(epoch: number): string {\n if (!epoch) return \"—\";\n // Odin stores timestamps as milliseconds for documents and seconds for some fields\n // (pentest startAt/endAt are seconds). Heuristic: values < 10^12 are seconds.\n const ms = epoch < 1e12 ? epoch * 1000 : epoch;\n return new Date(ms).toISOString().slice(0, 10);\n}\n","import { z } from \"zod\";\nimport type { OdinClient } from \"../client.js\";\nimport type { FindingDetailResponse } from \"../types.js\";\n\nexport const findingReferenceFields = {\n identifier: z\n .string()\n .trim()\n .min(1)\n .optional()\n .describe(\n 'Human-readable finding identifier such as \"ODI-12\". Preferred input.',\n ),\n findingId: z\n .string()\n .trim()\n .min(1)\n .optional()\n .describe(\n \"Raw internal finding ID, useful when chaining from another tool.\",\n ),\n};\n\nexport const findingReferenceInput = z\n .object(findingReferenceFields)\n .refine((value) => Boolean(value.identifier || value.findingId), {\n message: \"Provide either identifier or findingId.\",\n });\n\nexport type FindingReferenceInput = z.infer<typeof findingReferenceInput>;\n\n/** Prefer the human reference when both values are supplied. */\nexport function findingReferenceValue(\n reference: FindingReferenceInput,\n): string {\n const value = reference.identifier ?? reference.findingId;\n if (!value) {\n throw new Error(\"A finding identifier or findingId is required.\");\n }\n return value;\n}\n\nexport function findingReferencePath(\n reference: FindingReferenceInput,\n suffix = \"\",\n): string {\n return `/api/findings/${encodeURIComponent(findingReferenceValue(reference))}${suffix}`;\n}\n\n/**\n * Resolves only when a caller supplied an identifier. A supplied internal ID\n * is already suitable for mutation chaining and avoids an unnecessary read.\n */\nexport async function resolveFindingId(\n client: OdinClient,\n reference: FindingReferenceInput,\n): Promise<string> {\n if (reference.findingId) return reference.findingId;\n\n const data = await client.get<FindingDetailResponse>(\n findingReferencePath(reference),\n );\n return data.remediationContext?.findingId ?? data.finding.findingId;\n}\n","import type { z } from \"zod\";\nimport type { OdinClient } from \"../client.js\";\nimport { formatFindingDetail } from \"../formatters/markdown.js\";\nimport type { FindingDetailResponse } from \"../types.js\";\nimport {\n findingReferenceInput,\n findingReferencePath,\n} from \"./finding-reference.js\";\n\nexport const getFindingInput = findingReferenceInput;\n\nexport const getFindingTool = {\n name: \"get_finding\",\n title: \"Get finding remediation context\",\n description:\n \"Fetch a finding by human identifier such as ODI-12 (preferred) or by internal findingId. Returns bounded remediation context, the suggested branch name, linked GitHub pull requests, workflow readiness, and retest state. Odin only provides context and can link an existing PR; it never creates commits, pushes branches, or creates pull requests.\",\n inputSchema: getFindingInput,\n handler:\n (client: OdinClient) => async (params: z.infer<typeof getFindingInput>) => {\n const data = await client.get<FindingDetailResponse>(\n findingReferencePath(params),\n );\n return {\n markdown: formatFindingDetail(data),\n structured: {\n finding: data.remediationContext ?? data.finding,\n revision: data.remediationContext?.revision ?? data.revision,\n remediationContext: data.remediationContext ?? null,\n },\n };\n },\n};\n","import { z } from \"zod\";\nimport type { OdinClient } from \"../client.js\";\nimport { OdinApiError } from \"../client.js\";\nimport { formatFindingRetests } from \"../formatters/markdown.js\";\nimport type { RetestHistoryResponse } from \"../types.js\";\nimport {\n findingReferenceFields,\n findingReferencePath,\n} from \"./finding-reference.js\";\n\nexport const getFindingRetestsInput = z\n .object(findingReferenceFields)\n .refine((value) => Boolean(value.identifier || value.findingId), {\n message: \"Provide either identifier or findingId.\",\n });\n\nexport const getFindingRetestsTool = {\n name: \"get_finding_retests\",\n title: \"Get finding retest history\",\n description:\n \"Read the retest history for a finding by human identifier such as ODI-12 or internal findingId. Distinguishes no history, queued or running, completed, skipped, failed, and gated/unavailable results. This tool never triggers a retest or spends credits.\",\n inputSchema: getFindingRetestsInput,\n handler:\n (client: OdinClient) =>\n async (params: z.infer<typeof getFindingRetestsInput>) => {\n try {\n const data = await client.get<RetestHistoryResponse>(\n findingReferencePath(params, \"/retests\"),\n );\n return {\n markdown: formatFindingRetests(data),\n structured: {\n state: data.state,\n retests: data.retests,\n },\n };\n } catch (error) {\n if (\n error instanceof OdinApiError &&\n error.status === 404 &&\n isRetestHistoryUnavailable(error.body)\n ) {\n const reason =\n \"Retest history is gated or unavailable for this organisation.\";\n return {\n markdown: reason,\n structured: { state: \"unavailable\", reason, retests: [] },\n };\n }\n throw error;\n }\n },\n};\n\nfunction isRetestHistoryUnavailable(body: unknown): boolean {\n return (\n typeof body === \"object\" &&\n body !== null &&\n \"code\" in body &&\n (body as { code?: unknown }).code === \"RETEST_HISTORY_UNAVAILABLE\"\n );\n}\n","import { z } from \"zod\";\nimport type { OdinClient } from \"../client.js\";\nimport { formatPentestDetail } from \"../formatters/markdown.js\";\nimport type { PentestDetailResponse } from \"../types.js\";\n\nexport const getPentestInput = z.object({\n pentestId: z.string().min(1),\n});\n\nexport const getPentestTool = {\n name: \"get_pentest\",\n title: \"Get pentest\",\n description:\n \"Fetch a single pentest by ID, including status, scheduled window, and in-scope targets.\",\n inputSchema: getPentestInput,\n handler:\n (client: OdinClient) => async (params: z.infer<typeof getPentestInput>) => {\n const data = await client.get<PentestDetailResponse>(\n `/api/pentests/${encodeURIComponent(params.pentestId)}`,\n );\n return {\n markdown: formatPentestDetail(data),\n structured: { pentest: data.pentest },\n };\n },\n};\n","import { z } from \"zod\";\nimport type { OdinClient } from \"../client.js\";\nimport { formatFindingPullRequestLink } from \"../formatters/markdown.js\";\nimport type { LinkFindingPullRequestResponse } from \"../types.js\";\nimport {\n findingReferenceFields,\n findingReferencePath,\n} from \"./finding-reference.js\";\n\nexport const linkFindingPullRequestInput = z\n .object({\n ...findingReferenceFields,\n url: z\n .string()\n .url()\n .describe(\"Existing GitHub pull request URL to link to the finding.\"),\n })\n .refine((value) => Boolean(value.identifier || value.findingId), {\n message: \"Provide either identifier or findingId.\",\n });\n\nexport const linkFindingPullRequestTool = {\n name: \"link_finding_pull_request\",\n title: \"Link an existing GitHub pull request\",\n description:\n \"Link an existing accessible GitHub pull request to a finding identified by ODI-12 or findingId. This operation is idempotent. It does not create, modify, merge, or push a pull request, commit, or branch, and it requires an API key with Read & Write permission; cookie-authenticated callers also need member role. Ask the user before linking.\",\n inputSchema: linkFindingPullRequestInput,\n handler:\n (client: OdinClient) =>\n async (params: z.infer<typeof linkFindingPullRequestInput>) => {\n const data = await client.post<LinkFindingPullRequestResponse>(\n findingReferencePath(params, \"/link-pr\"),\n { url: params.url },\n );\n return {\n markdown: formatFindingPullRequestLink(data),\n structured: {\n finding: data.finding,\n pullRequest: data.pullRequest,\n linkSource: data.linkSource,\n workflow: data.workflow,\n },\n };\n },\n};\n","import { z } from \"zod\";\nimport type { OdinClient } from \"../client.js\";\nimport { formatAssetsList } from \"../formatters/markdown.js\";\nimport type { AssetsListResponse } from \"../types.js\";\n\nexport const listAssetsInput = z.object({\n type: z.enum([\"DOMAIN\", \"SUBDOMAIN\", \"IP\"]).optional(),\n source: z.enum([\"MANUAL\", \"RECON\", \"IMPORT\", \"AGENT\"]).optional(),\n status: z\n .string()\n .optional()\n .describe(\n \"Dynamic asset status set by recon (e.g. ACTIVE, INACTIVE, TLS_ERROR).\",\n ),\n search: z.string().optional(),\n sortBy: z.enum([\"lastSeenAt\", \"createdAt\", \"value\"]).optional(),\n sortOrder: z.enum([\"asc\", \"desc\"]).optional(),\n limit: z.number().int().min(1).max(100).optional().default(50),\n offset: z.number().int().min(0).optional().default(0),\n});\n\nexport const listAssetsTool = {\n name: \"list_assets\",\n title: \"List assets\",\n description:\n \"List attack-surface assets for the organisation. Supports filtering by type, source, status, and free-text search.\",\n inputSchema: listAssetsInput,\n handler:\n (client: OdinClient) => async (params: z.infer<typeof listAssetsInput>) => {\n const data = await client.get<AssetsListResponse>(\"/api/assets\", params);\n return {\n markdown: formatAssetsList(data),\n structured: {\n assets: data.assets,\n stats: data.stats,\n pagination: data.pagination,\n },\n };\n },\n};\n","import { z } from \"zod\";\nimport type { OdinClient } from \"../client.js\";\nimport { formatFindingsList } from \"../formatters/markdown.js\";\nimport type { FindingsListResponse } from \"../types.js\";\n\nexport const listFindingsInput = z.object({\n pentestId: z.string().optional().describe(\"Filter to a single pentest.\"),\n assetId: z\n .string()\n .optional()\n .describe(\n \"Filter to findings whose latest revision references this asset.\",\n ),\n repository: z\n .string()\n .optional()\n .describe(\"Filter to a source repository such as owner/name.\"),\n severity: z\n .enum([\"CRITICAL\", \"HIGH\", \"MEDIUM\", \"LOW\", \"INFORMATIONAL\"])\n .optional(),\n status: z\n .enum([\n \"REPORTED\",\n \"ACKNOWLEDGED\",\n \"MITIGATING\",\n \"OPEN_FOR_RETEST\",\n \"NEEDS_REVISION\",\n \"FIXED_AND_RETESTED\",\n ])\n .optional(),\n search: z\n .string()\n .optional()\n .describe(\"Free-text search on finding titles and human identifiers.\"),\n sortBy: z.enum([\"severity\", \"createdAt\"]).optional(),\n sortOrder: z.enum([\"asc\", \"desc\"]).optional(),\n limit: z.number().int().min(1).max(200).optional().default(50),\n offset: z.number().int().min(0).optional().default(0),\n});\n\nexport const listFindingsTool = {\n name: \"list_findings\",\n title: \"List findings\",\n description:\n \"List security findings for the organisation. Supports filtering by pentest, asset, repository, severity, current client-visible status, and searching finding titles or human identifiers such as ODI-12.\",\n inputSchema: listFindingsInput,\n handler:\n (client: OdinClient) =>\n async (params: z.infer<typeof listFindingsInput>) => {\n const data = await client.get<FindingsListResponse>(\n \"/api/findings\",\n params,\n );\n return {\n markdown: formatFindingsList(data),\n structured: {\n findings: data.findings,\n stats: data.stats,\n pagination: data.pagination,\n },\n };\n },\n};\n","import { z } from \"zod\";\nimport type { OdinClient } from \"../client.js\";\nimport { formatPentestsList } from \"../formatters/markdown.js\";\nimport type { PentestListResponse } from \"../types.js\";\n\nexport const listPentestsInput = z.object({});\n\nexport const listPentestsTool = {\n name: \"list_pentests\",\n title: \"List pentests\",\n description:\n \"List all pentests for the organisation, including status, scheduled window, and in-scope targets.\",\n inputSchema: listPentestsInput,\n handler: (client: OdinClient) => async () => {\n const data = await client.get<PentestListResponse>(\"/api/pentests\");\n return {\n markdown: formatPentestsList(data),\n structured: { pentests: data.pentests },\n };\n },\n};\n","import { z } from \"zod\";\nimport type { OdinClient } from \"../client.js\";\nimport { formatFindingStatusUpdate } from \"../formatters/markdown.js\";\nimport type { FindingStatusUpdateResponse } from \"../types.js\";\nimport {\n findingReferenceFields,\n resolveFindingId,\n} from \"./finding-reference.js\";\n\nexport const updateFindingStatusInput = z\n .object({\n ...findingReferenceFields,\n status: z\n .enum([\"MITIGATING\", \"OPEN_FOR_RETEST\", \"ACKNOWLEDGED\"])\n .describe(\n \"Target status. MITIGATING means work has started, OPEN_FOR_RETEST means the fix is ready for verification, and ACKNOWLEDGED means the organisation accepts the risk for now.\",\n ),\n })\n .refine((value) => Boolean(value.identifier || value.findingId), {\n message: \"Provide either identifier or findingId.\",\n });\n\nexport const updateFindingStatusTool = {\n name: \"update_finding_status\",\n title: \"Update finding status\",\n description:\n \"Change a finding's status by human identifier such as ODI-12 or internal findingId. Requires an API key with Read & Write permission; cookie-authenticated callers also need member role. Only use this after the user explicitly asks for the status change; a matching pull request is not permission to mutate a finding.\",\n inputSchema: updateFindingStatusInput,\n handler:\n (client: OdinClient) =>\n async (params: z.infer<typeof updateFindingStatusInput>) => {\n const findingId = await resolveFindingId(client, params);\n const data = await client.patch<FindingStatusUpdateResponse>(\n `/api/findings/${encodeURIComponent(findingId)}/status`,\n { status: params.status },\n );\n return {\n markdown: formatFindingStatusUpdate(data.finding),\n structured: { finding: data.finding },\n };\n },\n};\n"],"mappings":";;;AAAA,SAAS,iBAAiB;AAC1B,SAAS,4BAA4B;;;ACD9B,IAAM,eAAN,cAA2B,MAAM;AAAA,EAC7B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAET,YAAY,QAOT;AACD,UAAM,OAAO,OAAO;AACpB,SAAK,OAAO;AACZ,SAAK,SAAS,OAAO;AACrB,SAAK,OAAO,OAAO;AACnB,SAAK,SAAS,OAAO;AACrB,SAAK,oBAAoB,OAAO;AAChC,SAAK,OAAO,OAAO;AAAA,EACrB;AACF;AASO,IAAM,wBAAwB;AAErC,IAAM,gBAAgB,oBAAI,IAAI,CAAC,SAAS,QAAQ,OAAO,QAAQ,CAAC;AAEzD,IAAM,aAAN,MAAiB;AAAA,EACL;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAEjB,YAAY,SAA4B;AACtC,QAAI,CAAC,QAAQ,QAAQ;AACnB,YAAM,IAAI,MAAM,+BAA+B;AAAA,IACjD;AACA,SAAK,SAAS,QAAQ;AACtB,SAAK,WAAW,QAAQ,WAAW,uBAAuB;AAAA,MACxD;AAAA,MACA;AAAA,IACF;AACA,SAAK,YAAY,QAAQ,aAAa;AACtC,SAAK,+BACH,QAAQ,8BAA8B,KAAK,KAAK;AAAA,EACpD;AAAA,EAEA,MAAM,IACJ,MACA,cACY;AACZ,UAAM,MAAM,KAAK,SAAS,MAAM,YAAY;AAC5C,WAAO,KAAK,QAAW,OAAO,KAAK,IAAI;AAAA,EACzC;AAAA,EAEA,MAAM,MAAS,MAAc,MAA2B;AACtD,UAAM,MAAM,KAAK,SAAS,IAAI;AAC9B,WAAO,KAAK,QAAW,SAAS,KAAK,MAAM,IAAI;AAAA,EACjD;AAAA;AAAA;AAAA,EAIA,MAAM,KAAQ,MAAc,MAA2B;AACrD,UAAM,MAAM,KAAK,SAAS,IAAI;AAC9B,WAAO,KAAK,QAAW,QAAQ,KAAK,MAAM,IAAI;AAAA,EAChD;AAAA,EAEQ,SACN,MACA,cACQ;AACR,UAAM,MAAM,IAAI,IAAI,KAAK,WAAW,GAAG,IAAI,OAAO,IAAI,IAAI,IAAI,KAAK,OAAO;AAC1E,QAAI,cAAc;AAChB,iBAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,YAAY,GAAG;AACvD,YAAI,UAAU,UAAa,UAAU,GAAI;AACzC,YAAI,aAAa,IAAI,KAAK,OAAO,KAAK,CAAC;AAAA,MACzC;AAAA,IACF;AACA,WAAO,IAAI,SAAS;AAAA,EACtB;AAAA,EAEA,MAAc,QACZ,QACA,KACA,MACA,MACY;AACZ,UAAM,UAAkC;AAAA,MACtC,eAAe,UAAU,KAAK,MAAM;AAAA,MACpC,gBAAgB;AAAA,MAChB,QAAQ;AAAA,IACV;AACA,QAAI,KAAK,8BAA8B;AACrC,cAAQ,4BAA4B,IAAI,KAAK;AAAA,IAC/C;AAEA,UAAM,WAAW,MAAM,KAAK,UAAU,KAAK;AAAA,MACzC;AAAA,MACA;AAAA,MACA,MAAM,SAAS,SAAY,SAAY,KAAK,UAAU,IAAI;AAAA,IAC5D,CAAC;AAED,UAAM,UAAU,MAAM,SAAS,KAAK;AACpC,UAAM,SAAS,UAAU,cAAc,OAAO,IAAI;AAElD,QAAI,CAAC,SAAS,IAAI;AAChB,YAAM,oBAAoB;AAAA,QACxB,SAAS,QAAQ,IAAI,aAAa;AAAA,MACpC;AACA,YAAM,IAAI,aAAa;AAAA,QACrB,QAAQ,SAAS;AAAA,QACjB;AAAA,QACA;AAAA,QACA;AAAA,QACA,MAAM,UAAU;AAAA,QAChB,SAAS,mBAAmB;AAAA,UAC1B,QAAQ,SAAS;AAAA,UACjB;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,QACF,CAAC;AAAA,MACH,CAAC;AAAA,IACH;AAEA,WAAO;AAAA,EACT;AACF;AAEA,SAAS,cAAc,MAAuB;AAC5C,MAAI;AACF,WAAO,KAAK,MAAM,IAAI;AAAA,EACxB,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,SAAS,gBAAgB,QAAsC;AAC7D,MAAI,CAAC,OAAQ,QAAO;AACpB,QAAM,UAAU,OAAO,KAAK;AAC5B,MAAI,QAAQ,KAAK,OAAO,EAAG,QAAO,OAAO,SAAS,SAAS,EAAE;AAC7D,QAAM,SAAS,KAAK,MAAM,OAAO;AACjC,MAAI,OAAO,MAAM,MAAM,EAAG,QAAO;AACjC,QAAM,UAAU,KAAK,MAAM,SAAS,KAAK,IAAI,KAAK,GAAI;AACtD,SAAO,UAAU,IAAI,UAAU;AACjC;AAWA,SAAS,mBAAmB,OAAiC;AAC3D,QAAM,gBAAgB,qBAAqB,MAAM,MAAM;AACvD,QAAM,aAAa,kBAAkB,KAAK;AAC1C,MAAI,YAAY;AACd,WAAO,gBACH,GAAG,UAAU,aAAa,aAAa,MACvC;AAAA,EACN;AACA,QAAM,SAAS,gBACX,WAAM,aAAa,KACnB,MAAM,UACJ,WAAM,MAAM,QAAQ,MAAM,GAAG,GAAG,CAAC,KACjC;AACN,SAAO,YAAY,MAAM,MAAM,OAAO,MAAM,IAAI,GAAG,MAAM;AAC3D;AAEA,SAAS,kBAAkB,OAAwC;AACjE,UAAQ,MAAM,QAAQ;AAAA,IACpB,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,UAAI,cAAc,IAAI,MAAM,MAAM,GAAG;AACnC,eAAO;AAAA,MACT;AACA,aAAO;AAAA,IACT,KAAK;AACH,UAAI,oBAAoB,MAAM,IAAI,GAAG;AACnC,eAAO;AAAA,MACT;AACA,aAAO;AAAA,IACT,KAAK,KAAK;AACR,YAAM,IAAI,MAAM;AAChB,aAAO,IACH,qCAAqC,CAAC,cACtC;AAAA,IACN;AAAA,IACA;AACE,aAAO;AAAA,EACX;AACF;AAEA,SAAS,oBAAoB,MAAuB;AAElD,SAAO,kCAAkC,KAAK,IAAI;AACpD;AAEA,SAAS,qBAAqB,QAAgC;AAC5D,MAAI,UAAU,OAAO,WAAW,YAAY,WAAW,QAAQ;AAC7D,UAAM,MAAO,OAA8B;AAC3C,QAAI,OAAO,QAAQ,SAAU,QAAO;AAAA,EACtC;AACA,SAAO;AACT;;;AC1NA,SAAS,SAAS;;;ACgBlB,IAAM,gBAA+C;AAAA,EACnD,UAAU;AAAA,EACV,cAAc;AAAA,EACd,YAAY;AAAA,EACZ,iBAAiB;AAAA,EACjB,gBAAgB;AAAA,EAChB,oBAAoB;AACtB;AAEA,IAAM,uBAAyD;AAAA,EAC7D,QAAQ;AAAA,EACR,SAAS;AAAA,EACT,WAAW;AAAA,EACX,QAAQ;AAAA,EACR,SAAS;AACX;AAEO,SAAS,mBAAmB,MAAoC;AACrE,QAAM,EAAE,UAAU,OAAO,WAAW,IAAI;AACxC,MAAI,SAAS,WAAW,GAAG;AACzB,WAAO;AAAA,EACT;AAEA,QAAM,OAAO,SAAS,IAAI,CAAC,MAAM;AAC/B,UAAM,WAAW,GAAG,EAAE,SAAS,KAAK,UAAU,EAAE,SAAS,KAAK,QAAQ,CAAC,CAAC;AACxE,WAAO,KAAK,EAAE,cAAc,QAAG,MAAM,EAAE,SAAS,MAAM,YAAY,EAAE,KAAK,CAAC,MAAM,QAAQ,MAAM,aAAa,EAAE,MAAM,CAAC,MAAM,WAAW,EAAE,SAAS,CAAC;AAAA,EACnJ,CAAC;AAED,SAAO;AAAA,IACL,WAAW,SAAS,MAAM,OAAO,MAAM,KAAK,uBAAuB,WAAW,MAAM,WAAW,WAAW,KAAK,aAAa,WAAW,OAAO;AAAA,IAC9I;AAAA,IACA;AAAA,IACA;AAAA,IACA,GAAG;AAAA,EACL,EAAE,KAAK,IAAI;AACb;AAEO,SAAS,oBAAoB,MAAqC;AACvE,QAAM,EAAE,SAAS,SAAS,IAAI;AAC9B,QAAM,UAAU,KAAK;AACrB,QAAM,aAAa,SAAS,cAAc,QAAQ,cAAc;AAChE,QAAM,oBAAoB,cAAc,QAAQ;AAChD,QAAM,WAAW,GAAG,QAAQ,SAAS,KAAK,UAAU,QAAQ,SAAS,KAAK,QAAQ,CAAC,CAAC;AACpF,QAAM,SAAS;AAAA,IACb,KAAK,YAAY,iBAAiB,CAAC,WAAM,YAAY,QAAQ,KAAK,CAAC;AAAA,IACnE;AAAA,IACA,kBAAkB,iBAAiB;AAAA,IACnC,sBAAsB,QAAQ,SAAS;AAAA,IACvC,mBAAmB,QAAQ;AAAA,IAC3B,iBAAiB,aAAa,QAAQ,MAAM,CAAC;AAAA,IAC7C,qBAAqB,SAAS,cAAc,QAAQ,cAAc,cAAc;AAAA,IAChF,kBAAkB,QAAQ,SAAS,aAAa,YAAY;AAAA,IAC5D,kBAAkB,WAAW,QAAQ,SAAS,CAAC;AAAA,IAC/C,kBAAkB,WAAW,QAAQ,SAAS,CAAC;AAAA,EACjD;AAEA,QAAM,UAAU,UACZ;AAAA,IACE,gBAAgB,QAAQ,SAAS;AAAA,IACjC,SAAS,QAAQ,SAAS;AAAA,IAC1B,aAAa,QAAQ,SAAS;AAAA,IAC9B,QAAQ,QAAQ,SAAS;AAAA,IACzB,SAAS,QAAQ,SAAS;AAAA,IAC1B,aAAa,QAAQ,SAAS;AAAA,IAC9B,gBAAgB,QAAQ,SAAS;AAAA,IACjC,eAAe,QAAQ,SAAS;AAAA,EAClC,IACA,WACE;AAAA,IACE,gBAAgB,SAAS;AAAA,IACzB,SAAS,SAAS,QAAQ;AAAA,IAC1B,aAAa,SAAS,QAAQ;AAAA,IAC9B,QAAQ,SAAS,QAAQ;AAAA,IACzB,SAAS,SAAS,QAAQ;AAAA,IAC1B,aAAa,SAAS,QAAQ;AAAA,IAC9B,gBAAgB,SAAS,QAAQ,kBAAkB,CAAC;AAAA,IACpD,eAAe;AAAA,EACjB,IACA;AAEN,QAAM,WAAqB,CAAC;AAC5B,MAAI,CAAC,SAAS;AACZ,aAAS,KAAK,kCAAkC;AAAA,EAClD,OAAO;AACL,aAAS,KAAK,aAAa,QAAQ,kBAAkB,SAAS,GAAG;AACjE,QAAI,QAAQ,QAAS,UAAS,KAAK,cAAc,QAAQ,OAAO;AAChE,QAAI,QAAQ;AACV,eAAS,KAAK,kBAAkB,QAAQ,WAAW;AACrD,QAAI,QAAQ,OAAQ,UAAS,KAAK,sBAAsB,QAAQ,MAAM;AACtE,QAAI,QAAQ,QAAS,UAAS,KAAK,cAAc,QAAQ,OAAO;AAChE,QAAI,QAAQ;AACV,eAAS,KAAK,kBAAkB,QAAQ,WAAW;AACrD,QAAI,QAAQ,eAAe;AACzB,eAAS;AAAA,QACP;AAAA,QACA,KAAK,QAAQ,cAAc,IAAI,IAAI,QAAQ,cAAc,IAAI;AAAA,MAC/D;AACA,UAAI,QAAQ,cAAc,SAAS;AACjC,iBAAS,KAAK,OAAO,QAAQ,cAAc,SAAS,KAAK;AAAA,MAC3D;AAAA,IACF;AACA,QAAI,QAAQ,gBAAgB,QAAQ;AAClC,eAAS;AAAA,QACP;AAAA,QACA,QAAQ,eACL,IAAI,CAAC,UAAU,KAAK,MAAM,OAAO,KAAK,MAAM,OAAO,GAAG,EACtD,KAAK,IAAI;AAAA,MACd;AAAA,IACF;AAAA,EACF;AAEA,MAAI,SAAS;AACX,aAAS;AAAA,MACP;AAAA,MACA,2BAA2B,QAAQ,mBAAmB,eAAe;AAAA,MACrE,sCAAsC,MAAM,QAAQ,SAAS,8BAA8B,CAAC;AAAA,MAC5F,2BAA2B,MAAM,QAAQ,SAAS,eAAe,CAAC;AAAA,MAClE,4BAA4B,QAAQ,SAAS,wBAAwB,sBAAsB,WAAW;AAAA,IACxG;AACA,QAAI,QAAQ,SAAS,SAAS,QAAQ;AACpC,eAAS;AAAA,QACP;AAAA,QACA,QAAQ,SAAS,SAAS,IAAI,CAAC,YAAY,KAAK,OAAO,EAAE,EAAE,KAAK,IAAI;AAAA,MACtE;AAAA,IACF;AACA,QAAI,QAAQ,SAAS,UAAU,QAAQ;AACrC,eAAS;AAAA,QACP;AAAA,QACA,QAAQ,SAAS,UAAU,IAAI,CAAC,SAAS,KAAK,IAAI,EAAE,EAAE,KAAK,IAAI;AAAA,MACjE;AAAA,IACF;AAEA,QAAI,QAAQ,mBAAmB,QAAQ;AACrC,eAAS;AAAA,QACP;AAAA,QACA,QAAQ,mBACL;AAAA,UACC,CAAC,gBACC,OAAO,YAAY,MAAM,KAAK,YAAY,GAAG,KAAK,YAAY,YAAY,KAAK,CAAC,WAAM,YAAY,KAAK,gBAAgB,YAAY,SAAS;AAAA,QAChJ,EACC,KAAK,IAAI;AAAA,MACd;AAAA,IACF,OAAO;AACL,eAAS,KAAK,kCAAkC,gBAAgB;AAAA,IAClE;AAEA,aAAS,KAAK,kBAAkB;AAChC,QAAI,QAAQ,aAAa,UAAU,eAAe;AAChD,eAAS,KAAK,IAAI,QAAQ,aAAa,MAAM,GAAG;AAAA,IAClD,WAAW,QAAQ,aAAa,UAAU,QAAQ;AAChD,eAAS,KAAK,uCAAuC;AAAA,IACvD,OAAO;AACL,eAAS,KAAK,oBAAoB,QAAQ,aAAa,MAAM,CAAC;AAAA,IAChE;AAAA,EACF;AAEA,SAAO,CAAC,GAAG,QAAQ,IAAI,GAAG,QAAQ,EAAE,KAAK,IAAI;AAC/C;AAEO,SAAS,0BAA0B,SAAiC;AACzE,QAAM,aAAa,QAAQ,cAAc,QAAQ;AACjD,SAAO;AAAA,IACL,qBAAqB,UAAU,mBAAmB,QAAQ,SAAS,iBAAiB,aAAa,QAAQ,MAAM,CAAC;AAAA,IAChH,UAAU,QAAQ,KAAK;AAAA,EACzB,EAAE,KAAK,IAAI;AACb;AAEO,SAAS,6BACd,MACQ;AACR,QAAM,aAAa,KAAK,QAAQ,cAAc,KAAK,QAAQ;AAC3D,QAAM,cAAc,KAAK;AACzB,QAAM,YAAY,KAAK,SAAS,wBAC5B,uDACA;AACJ,QAAM,WAAW,KAAK,SAAS,SAAS,SACpC;AAAA,EAAK,KAAK,SAAS,SAAS,IAAI,CAAC,YAAY,KAAK,OAAO,EAAE,EAAE,KAAK,IAAI,CAAC,KACvE;AAEJ,SAAO;AAAA,IACL,yCAAyC,YAAY,MAAM,KAAK,YAAY,GAAG,UAAU,UAAU;AAAA,IACnG,eAAe,YAAY,IAAI,YAAY,YAAY,KAAK,qBAAqB,aAAa,KAAK,QAAQ,MAAM,CAAC;AAAA,IAClH;AAAA,IACA;AAAA,IACA,WAAW,qBAAqB,QAAQ,KAAK;AAAA,EAC/C,EACG,OAAO,OAAO,EACd,KAAK,IAAI;AACd;AAEO,SAAS,qBAAqB,MAAqC;AACxE,MAAI,KAAK,UAAU,UAAU,KAAK,QAAQ,WAAW,GAAG;AACtD,WAAO;AAAA,EACT;AAEA,SAAO;AAAA,IACL,mBAAmB,KAAK,QAAQ,MAAM;AAAA,IACtC;AAAA,IACA;AAAA,IACA;AAAA,IACA,GAAG,KAAK,QAAQ;AAAA,MACd,CAAC,WACC,KAAK,OAAO,QAAQ,MAAM,qBAAqB,OAAO,MAAM,CAAC,MAAM,OAAO,OAAO,MAAM,OAAO,mBAAmB,QAAG,MAAM,WAAW,OAAO,SAAS,CAAC,MAAM,WAAW,OAAO,eAAe,CAAC,CAAC;AAAA,IACnM;AAAA,IACA;AAAA,IACA;AAAA,EACF,EAAE,KAAK,IAAI;AACb;AAEO,SAAS,mBAAmB,MAAmC;AACpE,MAAI,KAAK,SAAS,WAAW,EAAG,QAAO;AACvC,QAAM,OAAO,KAAK,SAAS,IAAI,gBAAgB;AAC/C,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,GAAG;AAAA,EACL,EAAE,KAAK,IAAI;AACb;AAEO,SAAS,oBAAoB,MAAqC;AACvE,QAAM,IAAI,KAAK;AACf,QAAM,MAAM,EAAE,QAAQ,WAAW,EAAE,KAAK,IAAI;AAC5C,QAAM,QAAQ,EAAE,MAAM,SAClB,EAAE,MAAM,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,EAAE,KAAK,IAAI,IACtC;AACJ,SAAO;AAAA,IACL,KAAK,EAAE,IAAI;AAAA,IACX;AAAA,IACA,aAAa,EAAE,SAAS;AAAA,IACxB,iBAAiB,EAAE,MAAM;AAAA,IACzB,gBAAgB,WAAW,EAAE,OAAO,CAAC;AAAA,IACrC,cAAc,GAAG;AAAA,IACjB,kBAAkB,WAAW,EAAE,SAAS,CAAC;AAAA,IACzC;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,EAAE,KAAK,IAAI;AACb;AAEO,SAAS,uBAAuB,UAAoC;AACzE,MAAI,SAAS,WAAW,EAAG,QAAO;AAClC,SAAO;AAAA,IACL,gBAAgB,SAAS,MAAM;AAAA,IAC/B;AAAA,IACA;AAAA,IACA;AAAA,IACA,GAAG,SAAS,IAAI,gBAAgB;AAAA,EAClC,EAAE,KAAK,IAAI;AACb;AAEO,SAAS,iBAAiB,MAAkC;AACjE,QAAM,EAAE,QAAQ,OAAO,WAAW,IAAI;AACtC,MAAI,OAAO,WAAW,EAAG,QAAO;AAChC,QAAM,OAAO,OAAO,IAAI,cAAc;AACtC,SAAO;AAAA,IACL,WAAW,OAAO,MAAM,OAAO,MAAM,KAAK,qBAAqB,WAAW,MAAM,WAAW,WAAW,KAAK,aAAa,WAAW,OAAO;AAAA,IAC1I;AAAA,IACA;AAAA,IACA;AAAA,IACA,GAAG;AAAA,EACL,EAAE,KAAK,IAAI;AACb;AAEO,SAAS,kBAAkB,MAAmC;AACnE,QAAM,EAAE,OAAO,SAAS,IAAI;AAC5B,QAAM,SAAS;AAAA,IACb,KAAK,MAAM,KAAK;AAAA,IAChB;AAAA,IACA,aAAa,MAAM,OAAO;AAAA,IAC1B,eAAe,MAAM,IAAI;AAAA,IACzB,iBAAiB,MAAM,MAAM;AAAA,IAC7B,iBAAiB,MAAM,MAAM;AAAA,IAC7B,oBAAoB,WAAW,MAAM,UAAU,CAAC;AAAA,IAChD,kBAAkB,WAAW,MAAM,SAAS,CAAC;AAAA,EAC/C;AAEA,SAAO,CAAC,GAAG,QAAQ,IAAI,uBAAuB,QAAQ,CAAC,EAAE,KAAK,IAAI;AACpE;AAEA,SAAS,iBAAiB,SAAiC;AACzD,QAAM,WAAW,GAAG,QAAQ,SAAS,KAAK,UAAU,QAAQ,SAAS,KAAK,QAAQ,CAAC,CAAC;AACpF,SAAO,KAAK,QAAQ,cAAc,QAAG,MAAM,QAAQ,SAAS,MAAM,YAAY,QAAQ,KAAK,CAAC,MAAM,QAAQ,MAAM,aAAa,QAAQ,MAAM,CAAC,MAAM,WAAW,QAAQ,SAAS,CAAC;AACjL;AAEA,SAAS,iBAAiB,GAAoB;AAC5C,QAAM,MAAM,EAAE,QAAQ,WAAW,EAAE,KAAK,IAAI;AAC5C,QAAM,QACJ,EAAE,MAAM,MAAM,GAAG,CAAC,EAAE,KAAK,IAAI,KAAK,EAAE,MAAM,SAAS,IAAI,aAAQ;AACjE,SAAO,KAAK,EAAE,SAAS,MAAM,YAAY,EAAE,IAAI,CAAC,MAAM,EAAE,MAAM,MAAM,WAAW,EAAE,OAAO,CAAC,MAAM,GAAG,MAAM,YAAY,KAAK,CAAC;AAC5H;AAEA,SAAS,eAAe,GAAkB;AACxC,SAAO,KAAK,EAAE,OAAO,MAAM,EAAE,IAAI,MAAM,YAAY,EAAE,KAAK,CAAC,MAAM,EAAE,MAAM,MAAM,EAAE,MAAM,MAAM,WAAW,EAAE,UAAU,CAAC;AACvH;AAEA,SAAS,oBAAoB,QAOlB;AACT,SAAO;AAAA,IACL,iBAAiB,qBAAqB,OAAO,MAAM,CAAC;AAAA,IACpD,kBAAkB,OAAO,OAAO;AAAA,IAChC,kBAAkB,OAAO,mBAAmB,mBAAmB;AAAA,IAC/D,yBAAyB,OAAO,gBAAgB,MAAM;AAAA,IACtD,kBAAkB,WAAW,OAAO,SAAS,CAAC;AAAA,IAC9C,oBAAoB,WAAW,OAAO,eAAe,CAAC,CAAC;AAAA,EACzD,EAAE,KAAK,IAAI;AACb;AAEA,SAAS,aAAa,QAA+B;AACnD,SAAO,cAAc,MAAM,KAAK;AAClC;AAEA,SAAS,MAAM,OAAwB;AACrC,SAAO,QAAQ,QAAQ;AACzB;AAEA,SAAS,YAAY,OAAuB;AAC1C,SAAO,MAAM,QAAQ,OAAO,KAAK;AACnC;AAEA,SAAS,WAAW,OAAuB;AACzC,MAAI,CAAC,MAAO,QAAO;AAGnB,QAAM,KAAK,QAAQ,OAAO,QAAQ,MAAO;AACzC,SAAO,IAAI,KAAK,EAAE,EAAE,YAAY,EAAE,MAAM,GAAG,EAAE;AAC/C;;;ADvVO,IAAM,gBAAgB,EAAE,OAAO;AAAA,EACpC,SAAS,EAAE,OAAO,EAAE,IAAI,CAAC;AAC3B,CAAC;AAEM,IAAM,eAAe;AAAA,EAC1B,MAAM;AAAA,EACN,OAAO;AAAA,EACP,aACE;AAAA,EACF,aAAa;AAAA,EACb,SACE,CAAC,WAAuB,OAAO,WAA0C;AACvE,UAAM,OAAO,MAAM,OAAO;AAAA,MACxB,eAAe,mBAAmB,OAAO,OAAO,CAAC;AAAA,IACnD;AACA,WAAO;AAAA,MACL,UAAU,kBAAkB,IAAI;AAAA,MAChC,YAAY,EAAE,OAAO,KAAK,OAAO,UAAU,KAAK,SAAS;AAAA,IAC3D;AAAA,EACF;AACJ;;;AEzBA,SAAS,KAAAA,UAAS;AAIX,IAAM,yBAAyB;AAAA,EACpC,YAAYA,GACT,OAAO,EACP,KAAK,EACL,IAAI,CAAC,EACL,SAAS,EACT;AAAA,IACC;AAAA,EACF;AAAA,EACF,WAAWA,GACR,OAAO,EACP,KAAK,EACL,IAAI,CAAC,EACL,SAAS,EACT;AAAA,IACC;AAAA,EACF;AACJ;AAEO,IAAM,wBAAwBA,GAClC,OAAO,sBAAsB,EAC7B,OAAO,CAAC,UAAU,QAAQ,MAAM,cAAc,MAAM,SAAS,GAAG;AAAA,EAC/D,SAAS;AACX,CAAC;AAKI,SAAS,sBACd,WACQ;AACR,QAAM,QAAQ,UAAU,cAAc,UAAU;AAChD,MAAI,CAAC,OAAO;AACV,UAAM,IAAI,MAAM,gDAAgD;AAAA,EAClE;AACA,SAAO;AACT;AAEO,SAAS,qBACd,WACA,SAAS,IACD;AACR,SAAO,iBAAiB,mBAAmB,sBAAsB,SAAS,CAAC,CAAC,GAAG,MAAM;AACvF;AAMA,eAAsB,iBACpB,QACA,WACiB;AACjB,MAAI,UAAU,UAAW,QAAO,UAAU;AAE1C,QAAM,OAAO,MAAM,OAAO;AAAA,IACxB,qBAAqB,SAAS;AAAA,EAChC;AACA,SAAO,KAAK,oBAAoB,aAAa,KAAK,QAAQ;AAC5D;;;ACtDO,IAAM,kBAAkB;AAExB,IAAM,iBAAiB;AAAA,EAC5B,MAAM;AAAA,EACN,OAAO;AAAA,EACP,aACE;AAAA,EACF,aAAa;AAAA,EACb,SACE,CAAC,WAAuB,OAAO,WAA4C;AACzE,UAAM,OAAO,MAAM,OAAO;AAAA,MACxB,qBAAqB,MAAM;AAAA,IAC7B;AACA,WAAO;AAAA,MACL,UAAU,oBAAoB,IAAI;AAAA,MAClC,YAAY;AAAA,QACV,SAAS,KAAK,sBAAsB,KAAK;AAAA,QACzC,UAAU,KAAK,oBAAoB,YAAY,KAAK;AAAA,QACpD,oBAAoB,KAAK,sBAAsB;AAAA,MACjD;AAAA,IACF;AAAA,EACF;AACJ;;;AC/BA,SAAS,KAAAC,UAAS;AAUX,IAAM,yBAAyBC,GACnC,OAAO,sBAAsB,EAC7B,OAAO,CAAC,UAAU,QAAQ,MAAM,cAAc,MAAM,SAAS,GAAG;AAAA,EAC/D,SAAS;AACX,CAAC;AAEI,IAAM,wBAAwB;AAAA,EACnC,MAAM;AAAA,EACN,OAAO;AAAA,EACP,aACE;AAAA,EACF,aAAa;AAAA,EACb,SACE,CAAC,WACD,OAAO,WAAmD;AACxD,QAAI;AACF,YAAM,OAAO,MAAM,OAAO;AAAA,QACxB,qBAAqB,QAAQ,UAAU;AAAA,MACzC;AACA,aAAO;AAAA,QACL,UAAU,qBAAqB,IAAI;AAAA,QACnC,YAAY;AAAA,UACV,OAAO,KAAK;AAAA,UACZ,SAAS,KAAK;AAAA,QAChB;AAAA,MACF;AAAA,IACF,SAAS,OAAO;AACd,UACE,iBAAiB,gBACjB,MAAM,WAAW,OACjB,2BAA2B,MAAM,IAAI,GACrC;AACA,cAAM,SACJ;AACF,eAAO;AAAA,UACL,UAAU;AAAA,UACV,YAAY,EAAE,OAAO,eAAe,QAAQ,SAAS,CAAC,EAAE;AAAA,QAC1D;AAAA,MACF;AACA,YAAM;AAAA,IACR;AAAA,EACF;AACJ;AAEA,SAAS,2BAA2B,MAAwB;AAC1D,SACE,OAAO,SAAS,YAChB,SAAS,QACT,UAAU,QACT,KAA4B,SAAS;AAE1C;;;AC7DA,SAAS,KAAAC,UAAS;AAKX,IAAM,kBAAkBC,GAAE,OAAO;AAAA,EACtC,WAAWA,GAAE,OAAO,EAAE,IAAI,CAAC;AAC7B,CAAC;AAEM,IAAM,iBAAiB;AAAA,EAC5B,MAAM;AAAA,EACN,OAAO;AAAA,EACP,aACE;AAAA,EACF,aAAa;AAAA,EACb,SACE,CAAC,WAAuB,OAAO,WAA4C;AACzE,UAAM,OAAO,MAAM,OAAO;AAAA,MACxB,iBAAiB,mBAAmB,OAAO,SAAS,CAAC;AAAA,IACvD;AACA,WAAO;AAAA,MACL,UAAU,oBAAoB,IAAI;AAAA,MAClC,YAAY,EAAE,SAAS,KAAK,QAAQ;AAAA,IACtC;AAAA,EACF;AACJ;;;ACzBA,SAAS,KAAAC,UAAS;AASX,IAAM,8BAA8BC,GACxC,OAAO;AAAA,EACN,GAAG;AAAA,EACH,KAAKA,GACF,OAAO,EACP,IAAI,EACJ,SAAS,0DAA0D;AACxE,CAAC,EACA,OAAO,CAAC,UAAU,QAAQ,MAAM,cAAc,MAAM,SAAS,GAAG;AAAA,EAC/D,SAAS;AACX,CAAC;AAEI,IAAM,6BAA6B;AAAA,EACxC,MAAM;AAAA,EACN,OAAO;AAAA,EACP,aACE;AAAA,EACF,aAAa;AAAA,EACb,SACE,CAAC,WACD,OAAO,WAAwD;AAC7D,UAAM,OAAO,MAAM,OAAO;AAAA,MACxB,qBAAqB,QAAQ,UAAU;AAAA,MACvC,EAAE,KAAK,OAAO,IAAI;AAAA,IACpB;AACA,WAAO;AAAA,MACL,UAAU,6BAA6B,IAAI;AAAA,MAC3C,YAAY;AAAA,QACV,SAAS,KAAK;AAAA,QACd,aAAa,KAAK;AAAA,QAClB,YAAY,KAAK;AAAA,QACjB,UAAU,KAAK;AAAA,MACjB;AAAA,IACF;AAAA,EACF;AACJ;;;AC5CA,SAAS,KAAAC,UAAS;AAKX,IAAM,kBAAkBC,GAAE,OAAO;AAAA,EACtC,MAAMA,GAAE,KAAK,CAAC,UAAU,aAAa,IAAI,CAAC,EAAE,SAAS;AAAA,EACrD,QAAQA,GAAE,KAAK,CAAC,UAAU,SAAS,UAAU,OAAO,CAAC,EAAE,SAAS;AAAA,EAChE,QAAQA,GACL,OAAO,EACP,SAAS,EACT;AAAA,IACC;AAAA,EACF;AAAA,EACF,QAAQA,GAAE,OAAO,EAAE,SAAS;AAAA,EAC5B,QAAQA,GAAE,KAAK,CAAC,cAAc,aAAa,OAAO,CAAC,EAAE,SAAS;AAAA,EAC9D,WAAWA,GAAE,KAAK,CAAC,OAAO,MAAM,CAAC,EAAE,SAAS;AAAA,EAC5C,OAAOA,GAAE,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,IAAI,GAAG,EAAE,SAAS,EAAE,QAAQ,EAAE;AAAA,EAC7D,QAAQA,GAAE,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,SAAS,EAAE,QAAQ,CAAC;AACtD,CAAC;AAEM,IAAM,iBAAiB;AAAA,EAC5B,MAAM;AAAA,EACN,OAAO;AAAA,EACP,aACE;AAAA,EACF,aAAa;AAAA,EACb,SACE,CAAC,WAAuB,OAAO,WAA4C;AACzE,UAAM,OAAO,MAAM,OAAO,IAAwB,eAAe,MAAM;AACvE,WAAO;AAAA,MACL,UAAU,iBAAiB,IAAI;AAAA,MAC/B,YAAY;AAAA,QACV,QAAQ,KAAK;AAAA,QACb,OAAO,KAAK;AAAA,QACZ,YAAY,KAAK;AAAA,MACnB;AAAA,IACF;AAAA,EACF;AACJ;;;ACvCA,SAAS,KAAAC,UAAS;AAKX,IAAM,oBAAoBC,GAAE,OAAO;AAAA,EACxC,WAAWA,GAAE,OAAO,EAAE,SAAS,EAAE,SAAS,6BAA6B;AAAA,EACvE,SAASA,GACN,OAAO,EACP,SAAS,EACT;AAAA,IACC;AAAA,EACF;AAAA,EACF,YAAYA,GACT,OAAO,EACP,SAAS,EACT,SAAS,mDAAmD;AAAA,EAC/D,UAAUA,GACP,KAAK,CAAC,YAAY,QAAQ,UAAU,OAAO,eAAe,CAAC,EAC3D,SAAS;AAAA,EACZ,QAAQA,GACL,KAAK;AAAA,IACJ;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,CAAC,EACA,SAAS;AAAA,EACZ,QAAQA,GACL,OAAO,EACP,SAAS,EACT,SAAS,2DAA2D;AAAA,EACvE,QAAQA,GAAE,KAAK,CAAC,YAAY,WAAW,CAAC,EAAE,SAAS;AAAA,EACnD,WAAWA,GAAE,KAAK,CAAC,OAAO,MAAM,CAAC,EAAE,SAAS;AAAA,EAC5C,OAAOA,GAAE,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,IAAI,GAAG,EAAE,SAAS,EAAE,QAAQ,EAAE;AAAA,EAC7D,QAAQA,GAAE,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,SAAS,EAAE,QAAQ,CAAC;AACtD,CAAC;AAEM,IAAM,mBAAmB;AAAA,EAC9B,MAAM;AAAA,EACN,OAAO;AAAA,EACP,aACE;AAAA,EACF,aAAa;AAAA,EACb,SACE,CAAC,WACD,OAAO,WAA8C;AACnD,UAAM,OAAO,MAAM,OAAO;AAAA,MACxB;AAAA,MACA;AAAA,IACF;AACA,WAAO;AAAA,MACL,UAAU,mBAAmB,IAAI;AAAA,MACjC,YAAY;AAAA,QACV,UAAU,KAAK;AAAA,QACf,OAAO,KAAK;AAAA,QACZ,YAAY,KAAK;AAAA,MACnB;AAAA,IACF;AAAA,EACF;AACJ;;;AC9DA,SAAS,KAAAC,UAAS;AAKX,IAAM,oBAAoBC,GAAE,OAAO,CAAC,CAAC;AAErC,IAAM,mBAAmB;AAAA,EAC9B,MAAM;AAAA,EACN,OAAO;AAAA,EACP,aACE;AAAA,EACF,aAAa;AAAA,EACb,SAAS,CAAC,WAAuB,YAAY;AAC3C,UAAM,OAAO,MAAM,OAAO,IAAyB,eAAe;AAClE,WAAO;AAAA,MACL,UAAU,mBAAmB,IAAI;AAAA,MACjC,YAAY,EAAE,UAAU,KAAK,SAAS;AAAA,IACxC;AAAA,EACF;AACF;;;ACpBA,SAAS,KAAAC,UAAS;AASX,IAAM,2BAA2BC,GACrC,OAAO;AAAA,EACN,GAAG;AAAA,EACH,QAAQA,GACL,KAAK,CAAC,cAAc,mBAAmB,cAAc,CAAC,EACtD;AAAA,IACC;AAAA,EACF;AACJ,CAAC,EACA,OAAO,CAAC,UAAU,QAAQ,MAAM,cAAc,MAAM,SAAS,GAAG;AAAA,EAC/D,SAAS;AACX,CAAC;AAEI,IAAM,0BAA0B;AAAA,EACrC,MAAM;AAAA,EACN,OAAO;AAAA,EACP,aACE;AAAA,EACF,aAAa;AAAA,EACb,SACE,CAAC,WACD,OAAO,WAAqD;AAC1D,UAAM,YAAY,MAAM,iBAAiB,QAAQ,MAAM;AACvD,UAAM,OAAO,MAAM,OAAO;AAAA,MACxB,iBAAiB,mBAAmB,SAAS,CAAC;AAAA,MAC9C,EAAE,QAAQ,OAAO,OAAO;AAAA,IAC1B;AACA,WAAO;AAAA,MACL,UAAU,0BAA0B,KAAK,OAAO;AAAA,MAChD,YAAY,EAAE,SAAS,KAAK,QAAQ;AAAA,IACtC;AAAA,EACF;AACJ;;;AZ5BA,IAAM,kBAAkB;AAOxB,eAAe,OAAO;AACpB,QAAM,SAAS,QAAQ,IAAI;AAC3B,MAAI,CAAC,QAAQ;AACX,YAAQ;AAAA,MACN;AAAA,IACF;AACA,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,QAAM,UAAU,QAAQ,IAAI,iBAAiB;AAC7C,QAAM,SAAS,IAAI,WAAW;AAAA,IAC5B;AAAA,IACA;AAAA,IACA,8BACE,QAAQ,IAAI;AAAA,EAChB,CAAC;AAED,QAAM,SAAS,IAAI,UAAU;AAAA,IAC3B,MAAM;AAAA,IACN,SAAS;AAAA,EACX,CAAC;AAED,QAAM,QAAQ;AAAA,IACZ;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAEA,aAAW,QAAQ,OAAO;AACxB,UAAM,MAAM,KAAK,QAAQ,MAAM;AAG/B,WAAO;AAAA,MACL,KAAK;AAAA,MACL;AAAA,QACE,OAAO,KAAK;AAAA,QACZ,aAAa,KAAK;AAAA,QAClB,aAAa,KAAK,YAAY;AAAA,MAChC;AAAA,MACA,OAAO,WAAoB;AACzB,YAAI;AACF,gBAAM,EAAE,UAAU,WAAW,IAAI,MAAM,IAAI,MAAM;AACjD,iBAAO;AAAA,YACL,SAAS,CAAC,EAAE,MAAM,QAAiB,MAAM,SAAS,CAAC;AAAA,YACnD,mBAAmB,EAAE,UAAU,GAAG,SAAS,UAAU,EAAE;AAAA,UACzD;AAAA,QACF,SAAS,OAAO;AACd,iBAAO;AAAA,YACL,SAAS;AAAA,YACT,SAAS,CAAC,EAAE,MAAM,QAAiB,MAAM,eAAe,KAAK,EAAE,CAAC;AAAA,UAClE;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,QAAM,YAAY,IAAI,qBAAqB;AAC3C,QAAM,OAAO,QAAQ,SAAS;AAC9B,UAAQ;AAAA,IACN,YAAY,eAAe,uBAAuB,OAAO,WAAW,MAAM,MAAM;AAAA,EAClF;AACF;AAEA,SAAS,SAAS,OAAyC;AACzD,MAAI,SAAS,OAAO,UAAU,YAAY,CAAC,MAAM,QAAQ,KAAK,GAAG;AAC/D,WAAO;AAAA,EACT;AACA,SAAO,EAAE,MAAM,MAAM;AACvB;AAEA,SAAS,eAAe,OAAwB;AAC9C,MAAI,iBAAiB,aAAc,QAAO,MAAM;AAChD,MAAI,iBAAiB,MAAO,QAAO,qBAAqB,MAAM,OAAO;AACrE,SAAO,qBAAqB,OAAO,KAAK,CAAC;AAC3C;AAEA,KAAK,EAAE,MAAM,CAAC,UAAU;AACtB,UAAQ,MAAM,6BAA6B,KAAK;AAChD,UAAQ,KAAK,CAAC;AAChB,CAAC;","names":["z","z","z","z","z","z","z","z","z","z","z","z","z","z","z"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["import { StdioServerTransport } from \"@modelcontextprotocol/sdk/server/stdio.js\";\nimport { DEFAULT_ODIN_BASE_URL, OdinClient } from \"./client.js\";\nimport { createOdinMcpServer, PACKAGE_VERSION } from \"./server.js\";\n\nasync function main() {\n const apiKey = process.env.ODIN_API_KEY;\n if (!apiKey) {\n throw new Error(\n \"ODIN_API_KEY is required. Create a key under Management > API Keys.\",\n );\n }\n const baseUrl = process.env.ODIN_BASE_URL ?? DEFAULT_ODIN_BASE_URL;\n const client = new OdinClient({\n apiKey,\n baseUrl,\n vercelProtectionBypassSecret:\n process.env.ODIN_VERCEL_PROTECTION_BYPASS_SECRET,\n });\n const server = createOdinMcpServer(client, { appUrl: baseUrl });\n await server.connect(new StdioServerTransport());\n console.error(`odin-mcp ${PACKAGE_VERSION} connected over stdio.`);\n}\n\nmain().catch((error) => {\n console.error(\"odin-mcp failed to start:\", error);\n process.exit(1);\n});\n"],"mappings":";;;;;;;;;;;;;AAAA,SAAS,4BAA4B;AAIrC,eAAe,OAAO;AACpB,QAAM,SAAS,QAAQ,IAAI;AAC3B,MAAI,CAAC,QAAQ;AACX,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AACA,QAAM,UAAU,QAAQ,IAAI,iBAAiB;AAC7C,QAAM,SAAS,IAAI,WAAW;AAAA,IAC5B;AAAA,IACA;AAAA,IACA,8BACE,QAAQ,IAAI;AAAA,EAChB,CAAC;AACD,QAAM,SAAS,oBAAoB,QAAQ,EAAE,QAAQ,QAAQ,CAAC;AAC9D,QAAM,OAAO,QAAQ,IAAI,qBAAqB,CAAC;AAC/C,UAAQ,MAAM,YAAY,eAAe,wBAAwB;AACnE;AAEA,KAAK,EAAE,MAAM,CAAC,UAAU;AACtB,UAAQ,MAAM,6BAA6B,KAAK;AAChD,UAAQ,KAAK,CAAC;AAChB,CAAC;","names":[]}
|
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
import { OdinClient } from './client.js';
|
|
3
|
+
|
|
4
|
+
declare const PACKAGE_VERSION: string;
|
|
5
|
+
interface OdinMcpServerOptions {
|
|
6
|
+
/** Public Odin origin for links in guidance. Defaults to production. */
|
|
7
|
+
appUrl?: string;
|
|
8
|
+
}
|
|
9
|
+
declare function createOdinMcpServer(client: OdinClient, options?: OdinMcpServerOptions): McpServer;
|
|
10
|
+
|
|
11
|
+
export { type OdinMcpServerOptions, PACKAGE_VERSION, createOdinMcpServer };
|
package/dist/server.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
PACKAGE_VERSION,
|
|
4
|
+
createOdinMcpServer
|
|
5
|
+
} from "./chunk-ODZB3YP2.js";
|
|
6
|
+
import "./chunk-4LCKW236.js";
|
|
7
|
+
import "./chunk-D5FYYTYV.js";
|
|
8
|
+
import "./chunk-XHJYVUHG.js";
|
|
9
|
+
export {
|
|
10
|
+
PACKAGE_VERSION,
|
|
11
|
+
createOdinMcpServer
|
|
12
|
+
};
|
|
13
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
package/dist/tools.d.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { OdinClient } from './client.js';
|
|
3
|
+
|
|
4
|
+
interface OdinToolAnnotations {
|
|
5
|
+
readOnlyHint: boolean;
|
|
6
|
+
destructiveHint: boolean;
|
|
7
|
+
idempotentHint: boolean;
|
|
8
|
+
openWorldHint: boolean;
|
|
9
|
+
}
|
|
10
|
+
interface OdinToolResult {
|
|
11
|
+
markdown: string;
|
|
12
|
+
structured: unknown;
|
|
13
|
+
}
|
|
14
|
+
/** One Odin tool, independent of the MCP transport that serves it. */
|
|
15
|
+
interface OdinTool {
|
|
16
|
+
name: string;
|
|
17
|
+
title: string;
|
|
18
|
+
description: string;
|
|
19
|
+
inputSchema: z.ZodType;
|
|
20
|
+
annotations: OdinToolAnnotations;
|
|
21
|
+
run: (client: OdinClient, params: unknown) => Promise<OdinToolResult>;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Every tool odin-mcp serves, in registration order. The MCP server and
|
|
25
|
+
* web-odin's Slack agent both run these, each with its own `OdinClient`.
|
|
26
|
+
*/
|
|
27
|
+
declare const ODIN_TOOLS: readonly OdinTool[];
|
|
28
|
+
|
|
29
|
+
export { ODIN_TOOLS, type OdinTool, type OdinToolAnnotations, type OdinToolResult };
|
package/dist/tools.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@borgresearch/odin-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"mcpName": "io.github.BorgDevelopment/odin-mcp",
|
|
4
5
|
"description": "MCP server for the Odin security API",
|
|
5
6
|
"keywords": [
|
|
6
7
|
"mcp",
|
|
@@ -30,6 +31,26 @@
|
|
|
30
31
|
".": {
|
|
31
32
|
"types": "./dist/index.d.ts",
|
|
32
33
|
"import": "./dist/index.js"
|
|
34
|
+
},
|
|
35
|
+
"./server": {
|
|
36
|
+
"types": "./dist/server.d.ts",
|
|
37
|
+
"import": "./dist/server.js"
|
|
38
|
+
},
|
|
39
|
+
"./http": {
|
|
40
|
+
"types": "./dist/http.d.ts",
|
|
41
|
+
"import": "./dist/http.js"
|
|
42
|
+
},
|
|
43
|
+
"./client": {
|
|
44
|
+
"types": "./dist/client.d.ts",
|
|
45
|
+
"import": "./dist/client.js"
|
|
46
|
+
},
|
|
47
|
+
"./guidance": {
|
|
48
|
+
"types": "./dist/guidance.d.ts",
|
|
49
|
+
"import": "./dist/guidance.js"
|
|
50
|
+
},
|
|
51
|
+
"./tools": {
|
|
52
|
+
"types": "./dist/tools.d.ts",
|
|
53
|
+
"import": "./dist/tools.js"
|
|
33
54
|
}
|
|
34
55
|
},
|
|
35
56
|
"files": [
|
|
@@ -56,7 +77,7 @@
|
|
|
56
77
|
"inspect:dev": "pnpm build && pnpm inspect"
|
|
57
78
|
},
|
|
58
79
|
"dependencies": {
|
|
59
|
-
"@modelcontextprotocol/sdk": "^1.
|
|
80
|
+
"@modelcontextprotocol/sdk": "^1.31.0",
|
|
60
81
|
"zod": "^4.3.6"
|
|
61
82
|
},
|
|
62
83
|
"devDependencies": {
|
|
@@ -65,6 +86,7 @@
|
|
|
65
86
|
"@types/node": "^24.10.1",
|
|
66
87
|
"tsup": "^8.3.5",
|
|
67
88
|
"typescript": "^5.9.3",
|
|
68
|
-
"vitest": "^4.1.
|
|
69
|
-
}
|
|
89
|
+
"vitest": "^4.1.11"
|
|
90
|
+
},
|
|
91
|
+
"packageManager": "pnpm@11.2.2"
|
|
70
92
|
}
|