@canaryai/cli 0.2.8 → 0.2.12
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 +77 -92
- package/dist/chunk-CEW4BDXD.js +186 -0
- package/dist/chunk-CEW4BDXD.js.map +1 -0
- package/dist/chunk-ERSNYLMZ.js +229 -0
- package/dist/chunk-ERSNYLMZ.js.map +1 -0
- package/dist/{chunk-FK3EZADZ.js → chunk-MSMC6UXW.js} +2021 -873
- package/dist/chunk-MSMC6UXW.js.map +1 -0
- package/dist/{chunk-K2OB72B6.js → chunk-Q7WFBG5C.js} +2 -2
- package/dist/{debug-workflow-55G4Y6YT.js → debug-workflow-53ULOFJC.js} +57 -36
- package/dist/debug-workflow-53ULOFJC.js.map +1 -0
- package/dist/{docs-RPFT7ZJB.js → docs-BEE3LOCO.js} +2 -2
- package/dist/{feature-flag-2FDSKOVX.js → feature-flag-CYTDV4ZB.js} +3 -2
- package/dist/{feature-flag-2FDSKOVX.js.map → feature-flag-CYTDV4ZB.js.map} +1 -1
- package/dist/index.js +72 -137
- package/dist/index.js.map +1 -1
- package/dist/init-M6I3MG3D.js +146 -0
- package/dist/init-M6I3MG3D.js.map +1 -0
- package/dist/{issues-6ZDNDSD6.js → issues-NLM72HLU.js} +3 -2
- package/dist/{issues-6ZDNDSD6.js.map → issues-NLM72HLU.js.map} +1 -1
- package/dist/{knobs-MZRTYS3P.js → knobs-O35GAU5M.js} +3 -2
- package/dist/{knobs-MZRTYS3P.js.map → knobs-O35GAU5M.js.map} +1 -1
- package/dist/list-4K4EIGAT.js +57 -0
- package/dist/list-4K4EIGAT.js.map +1 -0
- package/dist/local-NHXXPHZ3.js +63 -0
- package/dist/local-NHXXPHZ3.js.map +1 -0
- package/dist/{local-browser-X7J27IGS.js → local-browser-VAZORCO3.js} +3 -3
- package/dist/login-ZLP64YQP.js +130 -0
- package/dist/login-ZLP64YQP.js.map +1 -0
- package/dist/mcp-ZF5G5DCB.js +377 -0
- package/dist/mcp-ZF5G5DCB.js.map +1 -0
- package/dist/{record-4OX7HXWQ.js → record-V6QKFFH3.js} +133 -72
- package/dist/record-V6QKFFH3.js.map +1 -0
- package/dist/{release-L4IXOHDF.js → release-7TI7EIGD.js} +8 -4
- package/dist/release-7TI7EIGD.js.map +1 -0
- package/dist/session-UGNJXRUW.js +819 -0
- package/dist/session-UGNJXRUW.js.map +1 -0
- package/dist/skill-ORWAPBDW.js +424 -0
- package/dist/skill-ORWAPBDW.js.map +1 -0
- package/dist/{src-I4EXB5OD.js → src-4VIDSK4A.js} +18 -2
- package/dist/start-E532F3BU.js +112 -0
- package/dist/start-E532F3BU.js.map +1 -0
- package/dist/workflow-HXIUXRFI.js +613 -0
- package/dist/workflow-HXIUXRFI.js.map +1 -0
- package/package.json +1 -1
- package/dist/chunk-6WWHXWCS.js +0 -65
- package/dist/chunk-6WWHXWCS.js.map +0 -1
- package/dist/chunk-DXIAHB72.js +0 -340
- package/dist/chunk-DXIAHB72.js.map +0 -1
- package/dist/chunk-FK3EZADZ.js.map +0 -1
- package/dist/debug-workflow-55G4Y6YT.js.map +0 -1
- package/dist/mcp-4JVLADZL.js +0 -688
- package/dist/mcp-4JVLADZL.js.map +0 -1
- package/dist/record-4OX7HXWQ.js.map +0 -1
- package/dist/release-L4IXOHDF.js.map +0 -1
- /package/dist/{chunk-K2OB72B6.js.map → chunk-Q7WFBG5C.js.map} +0 -0
- /package/dist/{docs-RPFT7ZJB.js.map → docs-BEE3LOCO.js.map} +0 -0
- /package/dist/{local-browser-X7J27IGS.js.map → local-browser-VAZORCO3.js.map} +0 -0
- /package/dist/{src-I4EXB5OD.js.map → src-4VIDSK4A.js.map} +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/workflow.ts","../src/workflow-create.ts"],"sourcesContent":["/**\n * CLI Workflow Management\n *\n * Inspect workflow definitions and steps from the terminal.\n */\n\nimport fs from \"node:fs/promises\";\nimport process from \"node:process\";\nimport { resolveConfig, getArgValue, hasFlag } from \"./auth.js\";\nimport { apiRequest } from \"./cli-helpers.js\";\nimport { handleWorkflowCreate } from \"./workflow-create.js\";\n\n/* ── Types ────────────────────────────────────────────────────────────── */\n\ntype WorkflowNode = {\n id: string;\n nodeType: string;\n orderIndex: number;\n configJson: Record<string, unknown>;\n};\n\ntype WorkflowSummary = {\n id: string;\n name: string;\n description: string | null;\n status: string;\n flowType: string;\n nodes: WorkflowNode[];\n edges: unknown[];\n};\n\ntype Pagination = {\n page: number;\n pageSize: number;\n totalItems: number;\n totalPages: number;\n};\n\ntype WorkflowListItem = {\n id: string;\n name: string;\n status: string;\n flowType: string;\n nodeCount?: number;\n};\n\ntype WorkflowListResponse = {\n ok: boolean;\n error?: string;\n data: WorkflowListItem[];\n pagination: Pagination;\n};\n\ntype WorkflowDetailResponse = {\n ok?: boolean;\n error?: string;\n workflow?: WorkflowSummary;\n targetOrgId?: string;\n targetOrgName?: string;\n};\n\n/* ── Node Helpers ─────────────────────────────────────────────────────── */\n\nfunction truncate(text: string, max: number): string {\n if (text.length <= max) return text;\n return text.slice(0, max - 1) + \"…\";\n}\n\nfunction getNodeTitle(node: WorkflowNode): string {\n const c = node.configJson;\n switch (node.nodeType) {\n case \"login\":\n return (c.credentialName as string) || \"Login\";\n case \"navigate\":\n return (c.pageTitle as string) || truncate((c.pageUrl as string) || \"Navigate\", 40);\n case \"action\":\n return (c.customActionTitle as string) || truncate((c.actionDescription as string) || \"Action\", 50);\n case \"assertion\":\n return truncate((c.condition as string) || \"Assertion\", 50);\n case \"setup\":\n return (c.setupFlowName as string) || \"Setup\";\n case \"seed\":\n return (c.seedWorkflowName as string) || \"Seed\";\n case \"wait\":\n return \"Wait\";\n case \"condition\":\n return truncate((c.conditionDescription as string) || \"Condition\", 50);\n case \"end\":\n return \"End\";\n case \"api_sequence\":\n return \"API Sequence\";\n default:\n return node.nodeType;\n }\n}\n\nfunction getCleanConfig(node: WorkflowNode): Record<string, unknown> {\n const c = node.configJson;\n switch (node.nodeType) {\n case \"login\":\n return pick(c, [\"credentialName\", \"performLogin\"]);\n case \"navigate\":\n return {\n ...pick(c, [\"pageUrl\", \"pageTitle\"]),\n ...(c.openInNewTab ? { openInNewTab: true } : {}),\n };\n case \"action\":\n return pick(c, [\n \"actionDescription\",\n \"customActionInstructions\",\n \"playbookSteps\",\n \"inputValues\",\n \"declaredOutputs\",\n ]);\n case \"assertion\":\n return pick(c, [\"condition\", \"expectedOutcome\", \"strict\", \"alwaysUseAgent\"]);\n case \"setup\":\n return pick(c, [\"setupFlowId\", \"setupFlowName\"]);\n case \"seed\":\n return pick(c, [\"seedWorkflowId\", \"seedWorkflowName\", \"snapshotVersion\"]);\n case \"end\":\n return pick(c, [\"outcome\"]);\n case \"wait\":\n case \"condition\":\n case \"api_sequence\":\n return c;\n default:\n return c;\n }\n}\n\nfunction pick(\n obj: Record<string, unknown>,\n keys: string[]\n): Record<string, unknown> {\n const result: Record<string, unknown> = {};\n for (const key of keys) {\n if (obj[key] !== undefined && obj[key] !== null) {\n result[key] = obj[key];\n }\n }\n return result;\n}\n\n/* ── Markdown Formatter ───────────────────────────────────────────────── */\n\nfunction formatWorkflowMarkdown(workflow: WorkflowSummary): string {\n const sections: string[] = [];\n\n sections.push(`# ${workflow.name}`);\n const meta = [`**ID:** ${workflow.id}`, `**Status:** ${workflow.status}`, `**Type:** ${workflow.flowType}`];\n sections.push(meta.join(\" | \"));\n\n if (workflow.description) {\n sections.push(`**Description:** ${workflow.description}`);\n }\n\n sections.push(\"---\");\n\n const sorted = [...workflow.nodes].sort((a, b) => a.orderIndex - b.orderIndex);\n\n for (let i = 0; i < sorted.length; i++) {\n const node = sorted[i];\n const title = getNodeTitle(node);\n const stepLines: string[] = [];\n\n stepLines.push(`## Step ${i + 1}: ${title}`);\n stepLines.push(`**Type:** ${node.nodeType}`);\n\n const c = node.configJson;\n switch (node.nodeType) {\n case \"login\": {\n if (c.credentialName) stepLines.push(`**Credential:** ${c.credentialName}`);\n if (c.performLogin !== undefined) stepLines.push(`**Perform Login:** ${c.performLogin ? \"Yes\" : \"No\"}`);\n break;\n }\n case \"navigate\": {\n if (c.pageUrl) stepLines.push(`**URL:** ${c.pageUrl}`);\n if (c.openInNewTab) stepLines.push(`**Open in New Tab:** Yes`);\n break;\n }\n case \"action\": {\n const instructions = (c.customActionInstructions as string) || (c.actionDescription as string);\n if (instructions) stepLines.push(`**Instructions:** ${instructions}`);\n const playbook = c.playbookSteps as Array<{ order: number; action: string; target: string }> | undefined;\n if (playbook && playbook.length > 0) {\n stepLines.push(\"\");\n stepLines.push(\"**Playbook:**\");\n for (const step of playbook) {\n stepLines.push(`${step.order}. [${step.action}] ${step.target}`);\n }\n }\n break;\n }\n case \"assertion\": {\n if (c.condition) stepLines.push(`**Condition:** ${c.condition}`);\n if (c.strict) stepLines.push(`**Strict:** Yes`);\n break;\n }\n case \"setup\": {\n if (c.setupFlowName) stepLines.push(`**Setup Flow:** ${c.setupFlowName}`);\n break;\n }\n case \"seed\": {\n if (c.seedWorkflowName) stepLines.push(`**Seed Workflow:** ${c.seedWorkflowName}`);\n break;\n }\n case \"wait\": {\n if (c.waitDuration) stepLines.push(`**Duration:** ${c.waitDuration}`);\n break;\n }\n case \"condition\": {\n if (c.conditionDescription) stepLines.push(`**Condition:** ${c.conditionDescription}`);\n break;\n }\n case \"end\": {\n if (c.outcome) stepLines.push(`**Outcome:** ${c.outcome}`);\n break;\n }\n }\n\n sections.push(stepLines.join(\"\\n\"));\n }\n\n return sections.join(\"\\n\\n\");\n}\n\n/* ── JSON Formatter ───────────────────────────────────────────────────── */\n\nfunction formatWorkflowJson(workflow: WorkflowSummary): object {\n const sorted = [...workflow.nodes].sort((a, b) => a.orderIndex - b.orderIndex);\n\n return {\n workflow: {\n id: workflow.id,\n name: workflow.name,\n description: workflow.description,\n status: workflow.status,\n flowType: workflow.flowType,\n },\n steps: sorted.map((node, i) => ({\n index: i + 1,\n nodeId: node.id,\n nodeType: node.nodeType,\n title: getNodeTitle(node),\n config: getCleanConfig(node),\n })),\n };\n}\n\n/* ── Sub-command Handlers ─────────────────────────────────────────────── */\n\nasync function handleList(argv: string[], apiUrl: string, token: string): Promise<void> {\n const jsonOutput = hasFlag(argv, \"--json\");\n\n const params = new URLSearchParams();\n const search = getArgValue(argv, \"--search\");\n const flowType = getArgValue(argv, \"--flow-type\");\n const status = getArgValue(argv, \"--status\");\n const page = getArgValue(argv, \"--page\");\n const pageSize = getArgValue(argv, \"--page-size\");\n\n if (search) params.set(\"search\", search);\n if (flowType) params.set(\"flowType\", flowType);\n if (status) params.set(\"status\", status);\n if (page) params.set(\"page\", page);\n if (pageSize) params.set(\"pageSize\", pageSize);\n\n const qs = params.toString();\n const path = `/workflows${qs ? `?${qs}` : \"\"}`;\n\n const result = await apiRequest<WorkflowListResponse>(apiUrl, token, \"GET\", path);\n\n if (!result.ok) {\n console.error(`Error: ${result.error}`);\n process.exit(1);\n }\n\n if (jsonOutput) {\n console.log(JSON.stringify({ data: result.data, pagination: result.pagination }, null, 2));\n return;\n }\n\n // Default table output\n const { data, pagination } = result;\n console.log(`Workflows: ${pagination.totalItems} total (page ${pagination.page}/${pagination.totalPages})\\n`);\n\n if (data.length === 0) {\n console.log(\"No workflows found.\");\n return;\n }\n\n // Column widths\n const idW = 36;\n const nameW = 30;\n const statusW = 12;\n const typeW = 10;\n\n const header = [\n \"ID\".padEnd(idW),\n \"Name\".padEnd(nameW),\n \"Status\".padEnd(statusW),\n \"Type\".padEnd(typeW),\n ].join(\" \");\n\n console.log(header);\n\n for (const w of data) {\n const row = [\n w.id.padEnd(idW),\n truncate(w.name, nameW).padEnd(nameW),\n w.status.padEnd(statusW),\n w.flowType.padEnd(typeW),\n ].join(\" \");\n console.log(row);\n }\n}\n\nasync function handleGet(argv: string[], apiUrl: string, token: string): Promise<void> {\n const workflowId = argv[0];\n if (!workflowId || workflowId.startsWith(\"--\")) {\n console.error(\"Error: Missing workflow ID.\");\n console.error(\"Usage: canary workflow get <workflowId>\");\n process.exit(1);\n }\n\n const jsonOutput = hasFlag(argv, \"--json\") || getArgValue(argv, \"--format\") === \"json\";\n const markdownOutput = hasFlag(argv, \"--markdown\") || getArgValue(argv, \"--format\") === \"markdown\";\n const outputFile = getArgValue(argv, \"--output\");\n\n const res = await fetch(`${apiUrl}/workflows/${workflowId}`, {\n headers: { Authorization: `Bearer ${token}`, \"Content-Type\": \"application/json\" },\n });\n if (res.status === 401) {\n console.error(\"Error: Unauthorized. Your session may have expired.\");\n console.error(\"Run: canary login\");\n process.exit(1);\n }\n const result = (await res.json()) as WorkflowDetailResponse;\n\n if (result.error === \"WORKFLOW_NOT_FOUND\") {\n console.error(\"Error: Workflow not found.\");\n process.exit(1);\n }\n if (result.error === \"WORKFLOW_WRONG_ORG\") {\n console.error(`Error: Workflow belongs to another org: ${result.targetOrgName ?? result.targetOrgId}`);\n process.exit(1);\n }\n if (!result.workflow) {\n console.error(`Error: ${result.error ?? \"Unexpected response\"}`);\n process.exit(1);\n }\n\n const workflow = result.workflow;\n let output: string;\n\n if (markdownOutput) {\n output = formatWorkflowMarkdown(workflow);\n } else {\n // Default to JSON for agent-friendly output\n output = JSON.stringify(formatWorkflowJson(workflow), null, 2);\n }\n\n if (outputFile) {\n try {\n await fs.writeFile(outputFile, output, \"utf-8\");\n console.log(`Written to ${outputFile}`);\n } catch (err) {\n console.error(`Error writing to ${outputFile}: ${err instanceof Error ? err.message : String(err)}`);\n process.exit(1);\n }\n return;\n }\n\n console.log(output);\n}\n\n/* ── Help & Entry Point ───────────────────────────────────────────────── */\n\nfunction printWorkflowHelp(): void {\n console.log(\n [\n \"Usage: canary workflow <sub-command> [options]\",\n \"\",\n \"Sub-commands:\",\n \" list [options] List workflows\",\n \" get <workflowId> [options] Get workflow definition with steps\",\n \" create [options] Create workflow from JSON (stdin or file)\",\n \"\",\n \"List options:\",\n \" --search <query> Search by name\",\n \" --flow-type <type> Filter: standard, setup, seed, teardown\",\n \" --status <status> Filter: draft, published, archived\",\n \" --page <n> Page number (default: 1)\",\n \" --page-size <n> Page size (default: 25)\",\n \" --json Output raw JSON\",\n \"\",\n \"Get options:\",\n \" --format json|markdown Output format (default: json)\",\n \" --json Shorthand for --format json\",\n \" --markdown Shorthand for --format markdown\",\n \" --output <file> Write output to file\",\n \"\",\n \"Create options:\",\n \" --from-stdin Read workflow JSON from stdin (default)\",\n \" --from-file <path> Read workflow JSON from file\",\n \" --quarantine Create with quarantined status (auto-promotes after 3 passes)\",\n \" --run-and-publish Create as draft, run test, publish on success\",\n \" --property <name|id> Property for run-and-publish\",\n \" --environment <name|id> Environment for run-and-publish\",\n \"\",\n \"Common options:\",\n \" --env <env> Target environment (prod, dev, local)\",\n \" --api-url <url> API URL override\",\n \" --token <key> API token override\",\n ].join(\"\\n\")\n );\n}\n\nexport async function runWorkflow(argv: string[]): Promise<void> {\n const [subCommand, ...rest] = argv;\n\n if (!subCommand || subCommand === \"help\" || hasFlag(argv, \"--help\", \"-h\")) {\n printWorkflowHelp();\n return;\n }\n\n const { apiUrl, token } = await resolveConfig(argv);\n\n switch (subCommand) {\n case \"list\":\n await handleList(rest, apiUrl, token);\n break;\n case \"get\":\n await handleGet(rest, apiUrl, token);\n break;\n case \"create\":\n await handleWorkflowCreate(rest, apiUrl, token);\n break;\n default:\n console.error(`Unknown sub-command: ${subCommand}`);\n printWorkflowHelp();\n process.exit(1);\n }\n}\n","/**\n * CLI Workflow Creation\n *\n * Creates a workflow with nodes and edges atomically via the API.\n * Supports reading workflow JSON from stdin or file.\n *\n * @module workflow-create\n */\n\nimport fs from \"node:fs/promises\";\nimport process from \"node:process\";\nimport { createParser, type EventSourceMessage } from \"eventsource-parser\";\nimport { getArgValue, hasFlag } from \"./auth.js\";\n\n/* ── Types ────────────────────────────────────────────────────────────── */\n\ntype WorkflowNodeInput = {\n nodeType: string;\n configJson?: Record<string, unknown>;\n};\n\ntype WorkflowCreateInput = {\n name: string;\n description?: string;\n status?: \"draft\" | \"quarantined\";\n flowType?: \"standard\" | \"setup\" | \"seed\" | \"teardown\";\n createdVia?: string;\n propertyId?: string;\n credentialId?: string;\n nodes: WorkflowNodeInput[];\n};\n\ntype CreateResponse = {\n ok: boolean;\n error?: string;\n workflow?: {\n id: string;\n name: string;\n status: string;\n flowType: string;\n createdVia: string | null;\n };\n nodes?: Array<{ id: string; nodeType: string; orderIndex: number }>;\n edges?: Array<{ id: string; sourceNodeId: string; targetNodeId: string }>;\n};\n\ntype SessionStatusResponse = {\n sessions?: Array<{\n id: string;\n propertyId?: string;\n credentialId?: string;\n propertyName?: string;\n credentialName?: string;\n }>;\n};\n\ntype TriggerRunResponse = {\n ok: boolean;\n error?: string;\n jobId?: string;\n suiteId?: string;\n appUrl?: string;\n};\n\ntype WorkflowTestEvent = {\n type: \"workflow-test\";\n workflowId: string;\n testRunId: string;\n status: string;\n errorMessage?: string | null;\n message?: string;\n};\n\ntype WorkflowTestSuiteEvent = {\n type: \"workflow-test-suite\";\n status: string;\n totalWorkflows: number;\n completedWorkflows: number;\n failedWorkflows: number;\n successfulWorkflows: number;\n};\n\n/* ── Input Reading ───────────────────────────────────────────────────── */\n\nasync function readFromStdin(): Promise<string> {\n const chunks: Buffer[] = [];\n const stdin = process.stdin;\n\n if (stdin.isTTY) {\n throw new Error(\"No input on stdin. Pipe workflow JSON or use --from-file.\");\n }\n\n return new Promise((resolve, reject) => {\n stdin.on(\"data\", (chunk) => chunks.push(chunk));\n stdin.on(\"end\", () => resolve(Buffer.concat(chunks).toString(\"utf-8\")));\n stdin.on(\"error\", reject);\n });\n}\n\nasync function readWorkflowInput(argv: string[]): Promise<WorkflowCreateInput> {\n let raw: string;\n\n const fromFile = getArgValue(argv, \"--from-file\");\n if (fromFile) {\n try {\n raw = await fs.readFile(fromFile, \"utf-8\");\n } catch (err) {\n throw new Error(`Failed to read file ${fromFile}: ${err instanceof Error ? err.message : String(err)}`);\n }\n } else {\n raw = await readFromStdin();\n }\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch {\n throw new Error(\"Invalid JSON input. Please provide valid workflow JSON.\");\n }\n\n const input = parsed as WorkflowCreateInput;\n\n if (!input.name || typeof input.name !== \"string\") {\n throw new Error('Workflow JSON must include a \"name\" string field.');\n }\n\n if (!Array.isArray(input.nodes) || input.nodes.length === 0) {\n throw new Error('Workflow JSON must include a non-empty \"nodes\" array.');\n }\n\n const validNodeTypes = [\"login\", \"navigate\", \"action\", \"assertion\", \"end\", \"setup\", \"seed\", \"wait\"];\n for (const node of input.nodes) {\n if (!node.nodeType || !validNodeTypes.includes(node.nodeType)) {\n throw new Error(\n `Invalid nodeType \"${node.nodeType}\". Valid types: ${validNodeTypes.join(\", \")}`\n );\n }\n }\n\n return input;\n}\n\n/* ── Session Auto-linking ────────────────────────────────────────────── */\n\nasync function detectSessionContext(\n apiUrl: string,\n token: string\n): Promise<{ propertyId?: string; credentialId?: string }> {\n try {\n const res = await fetch(`${apiUrl}/sessions/status`, {\n headers: { Authorization: `Bearer ${token}` },\n });\n if (!res.ok) return {};\n\n const data = (await res.json()) as SessionStatusResponse;\n const sessions = data.sessions;\n\n if (!sessions || sessions.length === 0) return {};\n\n // Use the first active session's property/credential\n const session = sessions[0];\n return {\n propertyId: session.propertyId,\n credentialId: session.credentialId,\n };\n } catch {\n // Session detection is best-effort\n return {};\n }\n}\n\n/* ── Run-and-Publish ─────────────────────────────────────────────────── */\n\nasync function runAndPublish(opts: {\n apiUrl: string;\n token: string;\n workflowId: string;\n workflowName: string;\n propertyId?: string;\n environmentId?: string;\n}): Promise<boolean> {\n const { apiUrl, token, workflowId, workflowName } = opts;\n\n // 1. Trigger a test run for this single workflow\n const body: Record<string, string> = {};\n if (opts.propertyId) body.propertyId = opts.propertyId;\n if (opts.environmentId) body.environmentId = opts.environmentId;\n\n const triggerUrl = `${apiUrl}/workflows/test-runs?namePattern=${encodeURIComponent(workflowName)}`;\n\n let triggerRes: Response;\n try {\n triggerRes = await fetch(triggerUrl, {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${token}`,\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify(body),\n });\n } catch (err) {\n console.error(`Failed to trigger test run: ${err}`);\n return false;\n }\n\n if (!triggerRes.ok) {\n const errorText = await triggerRes.text();\n console.error(`Failed to trigger test run: ${triggerRes.status} ${errorText}`);\n return false;\n }\n\n const triggerData = (await triggerRes.json()) as TriggerRunResponse;\n if (!triggerData.ok || !triggerData.suiteId) {\n console.error(`Failed to trigger test run: ${triggerData.error ?? \"Unknown error\"}`);\n return false;\n }\n\n const { suiteId } = triggerData;\n console.log(`Test run started (suite: ${suiteId})`);\n console.log(\"Streaming results...\\n\");\n\n // 2. Stream SSE events\n const streamUrl = `${apiUrl}/workflows/test-runs/stream?suiteId=${suiteId}`;\n\n let streamRes: Response;\n try {\n streamRes = await fetch(streamUrl, {\n headers: {\n Authorization: `Bearer ${token}`,\n Accept: \"text/event-stream\",\n },\n });\n } catch (err) {\n console.error(`Failed to connect to event stream: ${err}`);\n return false;\n }\n\n if (!streamRes.ok || !streamRes.body) {\n console.error(`Failed to connect to event stream: ${streamRes.status}`);\n return false;\n }\n\n let success = false;\n let hasCompleted = false;\n\n const parser = createParser({\n onEvent: (event: EventSourceMessage) => {\n if (!event.data) return;\n\n try {\n const data = JSON.parse(event.data) as WorkflowTestEvent | WorkflowTestSuiteEvent;\n\n if (event.event === \"workflow-test\") {\n const testEvent = data as WorkflowTestEvent;\n if (testEvent.status === \"success\") {\n console.log(` \\u2713 ${workflowName}`);\n success = true;\n } else if (testEvent.status === \"failed\") {\n console.log(` \\u2717 ${workflowName}`);\n if (testEvent.errorMessage) {\n console.log(` Error: ${testEvent.errorMessage.slice(0, 200)}`);\n }\n success = false;\n } else if (testEvent.status === \"running\") {\n console.log(` \\u25B6 Running ${workflowName}...`);\n }\n }\n\n if (event.event === \"workflow-test-suite\") {\n const suiteEvent = data as WorkflowTestSuiteEvent;\n if (suiteEvent.status === \"completed\") {\n hasCompleted = true;\n }\n }\n } catch {\n // Ignore parse errors for keepalive\n }\n },\n });\n\n const reader = streamRes.body.getReader();\n const decoder = new TextDecoder();\n\n try {\n while (!hasCompleted) {\n const { done, value } = await reader.read();\n if (done) break;\n parser.feed(decoder.decode(value, { stream: true }));\n }\n } finally {\n reader.releaseLock();\n }\n\n // 3. If success, publish the workflow\n if (success) {\n try {\n const publishRes = await fetch(`${apiUrl}/workflows/${workflowId}/publish`, {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${token}`,\n \"Content-Type\": \"application/json\",\n },\n });\n\n if (publishRes.ok) {\n console.log(`\\n\\u2713 Workflow published successfully`);\n return true;\n } else {\n console.error(`\\nFailed to publish workflow: ${publishRes.status}`);\n return false;\n }\n } catch (err) {\n console.error(`\\nFailed to publish workflow: ${err}`);\n return false;\n }\n }\n\n console.log(`\\n\\u2717 Test failed — workflow left in draft for manual review`);\n return false;\n}\n\n/* ── Main Handler ────────────────────────────────────────────────────── */\n\nexport async function handleWorkflowCreate(\n argv: string[],\n apiUrl: string,\n token: string\n): Promise<void> {\n const quarantine = hasFlag(argv, \"--quarantine\");\n const runAndPublishFlag = hasFlag(argv, \"--run-and-publish\");\n const propertyArg = getArgValue(argv, \"--property\");\n const environmentArg = getArgValue(argv, \"--environment\");\n\n if (quarantine && runAndPublishFlag) {\n console.error(\"Error: --quarantine and --run-and-publish are mutually exclusive.\");\n process.exit(1);\n }\n\n // 1. Read and validate input\n let input: WorkflowCreateInput;\n try {\n input = await readWorkflowInput(argv);\n } catch (err) {\n console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);\n process.exit(1);\n }\n\n // 2. Auto-detect session context for property/credential linking\n if (!input.propertyId && !input.credentialId) {\n const sessionCtx = await detectSessionContext(apiUrl, token);\n if (sessionCtx.propertyId) {\n input.propertyId = sessionCtx.propertyId;\n console.log(`Auto-linked property from active session`);\n }\n if (sessionCtx.credentialId) {\n input.credentialId = sessionCtx.credentialId;\n console.log(`Auto-linked credential from active session`);\n }\n }\n\n // 3. Set status based on flags\n if (quarantine) {\n input.status = \"quarantined\";\n }\n\n input.createdVia = \"cli\";\n\n // 4. Create workflow via API\n const res = await fetch(`${apiUrl}/workflows/create-with-nodes`, {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${token}`,\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify(input),\n });\n\n if (res.status === 401) {\n console.error(\"Error: Unauthorized. Your session may have expired.\");\n console.error(\"Run: canary login\");\n process.exit(1);\n }\n\n const result = (await res.json()) as CreateResponse;\n\n if (!result.ok || !result.workflow) {\n console.error(`Error: ${result.error ?? \"Failed to create workflow\"}`);\n process.exit(1);\n }\n\n const { workflow, nodes, edges } = result;\n\n console.log(`\\n\\u2713 Workflow created: ${workflow.name}`);\n console.log(` ID: ${workflow.id}`);\n console.log(` Status: ${workflow.status}`);\n console.log(` Nodes: ${nodes?.length ?? 0}`);\n console.log(` Edges: ${edges?.length ?? 0}`);\n\n // Derive web UI URL from API URL\n const webUrl = apiUrl.replace(/\\/api\\b|api\\./, \"app.\").replace(/:\\d+$/, \":5173\");\n console.log(` URL: ${webUrl}/workflows/${workflow.id}`);\n\n // 5. Run-and-publish if requested\n if (runAndPublishFlag) {\n console.log(\"\\nTriggering test run...\");\n\n const passed = await runAndPublish({\n apiUrl,\n token,\n workflowId: workflow.id,\n workflowName: workflow.name,\n propertyId: propertyArg ?? input.propertyId,\n environmentId: environmentArg,\n });\n\n if (!passed) {\n process.exit(1);\n }\n }\n}\n"],"mappings":"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","names":["fs","process","process","fs"]}
|
package/package.json
CHANGED
package/dist/chunk-6WWHXWCS.js
DELETED
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
import { createRequire as __cr } from "module"; const require = __cr(import.meta.url);
|
|
2
|
-
import {
|
|
3
|
-
getArgValue
|
|
4
|
-
} from "./chunk-PWWQGYFG.js";
|
|
5
|
-
|
|
6
|
-
// src/cli-helpers.ts
|
|
7
|
-
import process from "process";
|
|
8
|
-
function toLifecycleLabel(stage) {
|
|
9
|
-
switch (stage) {
|
|
10
|
-
case "deprecated":
|
|
11
|
-
return "deprecated";
|
|
12
|
-
case "ready_for_cleanup":
|
|
13
|
-
return "ready_for_cleanup";
|
|
14
|
-
default:
|
|
15
|
-
return "active";
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
function parseLifecycleStage(argv) {
|
|
19
|
-
const stage = getArgValue(argv, "--stage");
|
|
20
|
-
if (!stage || !["active", "deprecated", "ready_for_cleanup"].includes(stage)) {
|
|
21
|
-
console.error("Error: --stage is required and must be one of: active, deprecated, ready_for_cleanup");
|
|
22
|
-
process.exit(1);
|
|
23
|
-
}
|
|
24
|
-
return stage;
|
|
25
|
-
}
|
|
26
|
-
async function apiRequest(apiUrl, token, method, path, body) {
|
|
27
|
-
const res = await fetch(`${apiUrl}${path}`, {
|
|
28
|
-
method,
|
|
29
|
-
headers: {
|
|
30
|
-
Authorization: `Bearer ${token}`,
|
|
31
|
-
"Content-Type": "application/json"
|
|
32
|
-
},
|
|
33
|
-
...body ? { body: JSON.stringify(body) } : {}
|
|
34
|
-
});
|
|
35
|
-
if (res.status === 401) {
|
|
36
|
-
console.error("Error: Unauthorized. Your session may have expired.");
|
|
37
|
-
console.error("Run: canary login");
|
|
38
|
-
process.exit(1);
|
|
39
|
-
}
|
|
40
|
-
return await res.json();
|
|
41
|
-
}
|
|
42
|
-
async function fetchList(apiUrl, token, path, listKey) {
|
|
43
|
-
const res = await fetch(`${apiUrl}${path}`, {
|
|
44
|
-
headers: { Authorization: `Bearer ${token}` }
|
|
45
|
-
});
|
|
46
|
-
if (res.status === 401) {
|
|
47
|
-
console.error("Error: Unauthorized. Your session may have expired.");
|
|
48
|
-
console.error("Run: canary login");
|
|
49
|
-
process.exit(1);
|
|
50
|
-
}
|
|
51
|
-
const json = await res.json();
|
|
52
|
-
if (!json.ok) {
|
|
53
|
-
console.error(`Error: ${json.error}`);
|
|
54
|
-
process.exit(1);
|
|
55
|
-
}
|
|
56
|
-
return json[listKey] ?? [];
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
export {
|
|
60
|
-
toLifecycleLabel,
|
|
61
|
-
parseLifecycleStage,
|
|
62
|
-
apiRequest,
|
|
63
|
-
fetchList
|
|
64
|
-
};
|
|
65
|
-
//# sourceMappingURL=chunk-6WWHXWCS.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/cli-helpers.ts"],"sourcesContent":["/**\n * Shared CLI helpers for superadmin management commands (knobs, feature-flags).\n */\n\nimport process from \"node:process\";\nimport { getArgValue } from \"./auth.js\";\n\nexport type LifecycleStage = \"active\" | \"deprecated\" | \"ready_for_cleanup\";\n\nexport function toLifecycleLabel(stage: LifecycleStage): string {\n switch (stage) {\n case \"deprecated\":\n return \"deprecated\";\n case \"ready_for_cleanup\":\n return \"ready_for_cleanup\";\n default:\n return \"active\";\n }\n}\n\nexport function parseLifecycleStage(argv: string[]): LifecycleStage {\n const stage = getArgValue(argv, \"--stage\");\n if (!stage || ![\"active\", \"deprecated\", \"ready_for_cleanup\"].includes(stage)) {\n console.error(\"Error: --stage is required and must be one of: active, deprecated, ready_for_cleanup\");\n process.exit(1);\n }\n return stage as LifecycleStage;\n}\n\nexport async function apiRequest<T extends { ok: boolean; error?: string }>(\n apiUrl: string,\n token: string,\n method: string,\n path: string,\n body?: Record<string, unknown>\n): Promise<T> {\n const res = await fetch(`${apiUrl}${path}`, {\n method,\n headers: {\n Authorization: `Bearer ${token}`,\n \"Content-Type\": \"application/json\",\n },\n ...(body ? { body: JSON.stringify(body) } : {}),\n });\n\n if (res.status === 401) {\n console.error(\"Error: Unauthorized. Your session may have expired.\");\n console.error(\"Run: canary login\");\n process.exit(1);\n }\n\n return (await res.json()) as T;\n}\n\nexport async function fetchList<T>(\n apiUrl: string,\n token: string,\n path: string,\n listKey: string\n): Promise<T[]> {\n const res = await fetch(`${apiUrl}${path}`, {\n headers: { Authorization: `Bearer ${token}` },\n });\n\n if (res.status === 401) {\n console.error(\"Error: Unauthorized. Your session may have expired.\");\n console.error(\"Run: canary login\");\n process.exit(1);\n }\n\n const json = (await res.json()) as Record<string, unknown>;\n\n if (!json.ok) {\n console.error(`Error: ${json.error}`);\n process.exit(1);\n }\n\n return (json[listKey] as T[]) ?? [];\n}\n"],"mappings":";;;;;;AAIA,OAAO,aAAa;AAKb,SAAS,iBAAiB,OAA+B;AAC9D,UAAQ,OAAO;AAAA,IACb,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT;AACE,aAAO;AAAA,EACX;AACF;AAEO,SAAS,oBAAoB,MAAgC;AAClE,QAAM,QAAQ,YAAY,MAAM,SAAS;AACzC,MAAI,CAAC,SAAS,CAAC,CAAC,UAAU,cAAc,mBAAmB,EAAE,SAAS,KAAK,GAAG;AAC5E,YAAQ,MAAM,sFAAsF;AACpG,YAAQ,KAAK,CAAC;AAAA,EAChB;AACA,SAAO;AACT;AAEA,eAAsB,WACpB,QACA,OACA,QACA,MACA,MACY;AACZ,QAAM,MAAM,MAAM,MAAM,GAAG,MAAM,GAAG,IAAI,IAAI;AAAA,IAC1C;AAAA,IACA,SAAS;AAAA,MACP,eAAe,UAAU,KAAK;AAAA,MAC9B,gBAAgB;AAAA,IAClB;AAAA,IACA,GAAI,OAAO,EAAE,MAAM,KAAK,UAAU,IAAI,EAAE,IAAI,CAAC;AAAA,EAC/C,CAAC;AAED,MAAI,IAAI,WAAW,KAAK;AACtB,YAAQ,MAAM,qDAAqD;AACnE,YAAQ,MAAM,mBAAmB;AACjC,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,SAAQ,MAAM,IAAI,KAAK;AACzB;AAEA,eAAsB,UACpB,QACA,OACA,MACA,SACc;AACd,QAAM,MAAM,MAAM,MAAM,GAAG,MAAM,GAAG,IAAI,IAAI;AAAA,IAC1C,SAAS,EAAE,eAAe,UAAU,KAAK,GAAG;AAAA,EAC9C,CAAC;AAED,MAAI,IAAI,WAAW,KAAK;AACtB,YAAQ,MAAM,qDAAqD;AACnE,YAAQ,MAAM,mBAAmB;AACjC,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,QAAM,OAAQ,MAAM,IAAI,KAAK;AAE7B,MAAI,CAAC,KAAK,IAAI;AACZ,YAAQ,MAAM,UAAU,KAAK,KAAK,EAAE;AACpC,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,SAAQ,KAAK,OAAO,KAAa,CAAC;AACpC;","names":[]}
|
package/dist/chunk-DXIAHB72.js
DELETED
|
@@ -1,340 +0,0 @@
|
|
|
1
|
-
import { createRequire as __cr } from "module"; const require = __cr(import.meta.url);
|
|
2
|
-
import {
|
|
3
|
-
readStoredToken
|
|
4
|
-
} from "./chunk-PWWQGYFG.js";
|
|
5
|
-
|
|
6
|
-
// src/local-run.ts
|
|
7
|
-
import process from "process";
|
|
8
|
-
function getArgValue(argv, key) {
|
|
9
|
-
const index = argv.indexOf(key);
|
|
10
|
-
if (index === -1) return void 0;
|
|
11
|
-
return argv[index + 1];
|
|
12
|
-
}
|
|
13
|
-
async function runLocalTest(argv) {
|
|
14
|
-
const apiUrl = getArgValue(argv, "--api-url") ?? process.env.CANARY_API_URL ?? "https://api.trycanary.ai";
|
|
15
|
-
const token = getArgValue(argv, "--token") ?? process.env.CANARY_API_TOKEN ?? await readStoredToken();
|
|
16
|
-
const title = getArgValue(argv, "--title");
|
|
17
|
-
const featureSpec = getArgValue(argv, "--feature");
|
|
18
|
-
const startUrl = getArgValue(argv, "--start-url");
|
|
19
|
-
const tunnelUrl = getArgValue(argv, "--tunnel-url");
|
|
20
|
-
if (!tunnelUrl && !startUrl) {
|
|
21
|
-
console.error("Missing --tunnel-url or --start-url");
|
|
22
|
-
process.exit(1);
|
|
23
|
-
}
|
|
24
|
-
if (!token) {
|
|
25
|
-
console.error("Missing token. Run `canary login` first or set CANARY_API_TOKEN.");
|
|
26
|
-
process.exit(1);
|
|
27
|
-
}
|
|
28
|
-
const result = await createLocalRun({
|
|
29
|
-
apiUrl,
|
|
30
|
-
token,
|
|
31
|
-
title,
|
|
32
|
-
featureSpec,
|
|
33
|
-
startUrl,
|
|
34
|
-
tunnelUrl
|
|
35
|
-
});
|
|
36
|
-
console.log(`Local test queued: ${result.runId}`);
|
|
37
|
-
if (result.watchUrl) {
|
|
38
|
-
console.log(`Watch: ${result.watchUrl}`);
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
async function createLocalRun(input) {
|
|
42
|
-
const body = {
|
|
43
|
-
title: input.title ?? null,
|
|
44
|
-
featureSpec: input.featureSpec ?? null,
|
|
45
|
-
startUrl: input.startUrl ?? null,
|
|
46
|
-
tunnelPublicUrl: input.tunnelUrl ?? null
|
|
47
|
-
};
|
|
48
|
-
const response = await fetch(`${input.apiUrl}/local-tests/runs`, {
|
|
49
|
-
method: "POST",
|
|
50
|
-
headers: {
|
|
51
|
-
"content-type": "application/json",
|
|
52
|
-
authorization: `Bearer ${input.token}`
|
|
53
|
-
},
|
|
54
|
-
body: JSON.stringify(body)
|
|
55
|
-
});
|
|
56
|
-
const json = await response.json();
|
|
57
|
-
if (!response.ok || !json.ok || !json.runId) {
|
|
58
|
-
throw new Error(json.error ?? response.statusText);
|
|
59
|
-
}
|
|
60
|
-
return { runId: json.runId, watchUrl: json.watchUrl };
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
// src/tunnel.ts
|
|
64
|
-
import { createHash } from "crypto";
|
|
65
|
-
import os from "os";
|
|
66
|
-
import process2 from "process";
|
|
67
|
-
function getArgValue2(argv, key) {
|
|
68
|
-
const index = argv.indexOf(key);
|
|
69
|
-
if (index === -1) return void 0;
|
|
70
|
-
return argv[index + 1];
|
|
71
|
-
}
|
|
72
|
-
function toWebSocketUrl(apiUrl) {
|
|
73
|
-
const url = new URL(apiUrl);
|
|
74
|
-
url.protocol = url.protocol === "https:" ? "wss:" : "ws:";
|
|
75
|
-
return url.toString();
|
|
76
|
-
}
|
|
77
|
-
function createFingerprint() {
|
|
78
|
-
const raw = `${os.hostname()}-${os.userInfo().username}-${process2.version}`;
|
|
79
|
-
return createHash("sha256").update(raw).digest("hex").slice(0, 16);
|
|
80
|
-
}
|
|
81
|
-
async function runTunnel(argv) {
|
|
82
|
-
const apiUrl = getArgValue2(argv, "--api-url") ?? process2.env.CANARY_API_URL ?? "https://api.trycanary.ai";
|
|
83
|
-
const token = getArgValue2(argv, "--token") ?? process2.env.CANARY_API_TOKEN ?? await readStoredToken();
|
|
84
|
-
const portRaw = getArgValue2(argv, "--port") ?? process2.env.CANARY_LOCAL_PORT;
|
|
85
|
-
if (!portRaw) {
|
|
86
|
-
console.error("Missing --port");
|
|
87
|
-
process2.exit(1);
|
|
88
|
-
}
|
|
89
|
-
const port = Number(portRaw);
|
|
90
|
-
if (Number.isNaN(port) || port <= 0) {
|
|
91
|
-
console.error("Invalid --port value");
|
|
92
|
-
process2.exit(1);
|
|
93
|
-
}
|
|
94
|
-
if (!token) {
|
|
95
|
-
console.error("Missing token. Run `canary login` first or set CANARY_API_TOKEN.");
|
|
96
|
-
process2.exit(1);
|
|
97
|
-
}
|
|
98
|
-
const maxReconnectAttempts = 10;
|
|
99
|
-
const baseReconnectDelayMs = 1e3;
|
|
100
|
-
let reconnectAttempts = 0;
|
|
101
|
-
const connect = async () => {
|
|
102
|
-
try {
|
|
103
|
-
const data = await createTunnel({ apiUrl, token, port });
|
|
104
|
-
console.log(`Tunnel connected: ${data.publicUrl ?? data.tunnelId}`);
|
|
105
|
-
if (data.publicUrl) {
|
|
106
|
-
console.log(`Public URL: ${data.publicUrl}`);
|
|
107
|
-
console.log("");
|
|
108
|
-
console.log("To use this tunnel for sandbox agent callbacks, add to apps/api/.env:");
|
|
109
|
-
console.log(` SANDBOX_AGENT_API_URL=${data.publicUrl}`);
|
|
110
|
-
console.log("");
|
|
111
|
-
}
|
|
112
|
-
const ws = connectTunnel({
|
|
113
|
-
apiUrl,
|
|
114
|
-
tunnelId: data.tunnelId,
|
|
115
|
-
token: data.token,
|
|
116
|
-
port,
|
|
117
|
-
onReady: () => {
|
|
118
|
-
reconnectAttempts = 0;
|
|
119
|
-
}
|
|
120
|
-
});
|
|
121
|
-
return new Promise((resolve, reject) => {
|
|
122
|
-
ws.onclose = (event) => {
|
|
123
|
-
console.log(`Tunnel closed (code: ${event.code})`);
|
|
124
|
-
if (reconnectAttempts < maxReconnectAttempts) {
|
|
125
|
-
const delay = Math.min(baseReconnectDelayMs * Math.pow(2, reconnectAttempts), 3e4);
|
|
126
|
-
reconnectAttempts++;
|
|
127
|
-
console.log(`Reconnecting in ${delay}ms (attempt ${reconnectAttempts}/${maxReconnectAttempts})...`);
|
|
128
|
-
setTimeout(() => {
|
|
129
|
-
connect().then(resolve).catch(reject);
|
|
130
|
-
}, delay);
|
|
131
|
-
} else {
|
|
132
|
-
console.error("Max reconnection attempts reached. Exiting.");
|
|
133
|
-
process2.exit(1);
|
|
134
|
-
}
|
|
135
|
-
};
|
|
136
|
-
ws.onerror = (event) => {
|
|
137
|
-
console.error("Tunnel error:", event);
|
|
138
|
-
};
|
|
139
|
-
});
|
|
140
|
-
} catch (error) {
|
|
141
|
-
if (reconnectAttempts < maxReconnectAttempts) {
|
|
142
|
-
const delay = Math.min(baseReconnectDelayMs * Math.pow(2, reconnectAttempts), 3e4);
|
|
143
|
-
reconnectAttempts++;
|
|
144
|
-
console.error(`Failed to create tunnel: ${error}`);
|
|
145
|
-
console.log(`Retrying in ${delay}ms (attempt ${reconnectAttempts}/${maxReconnectAttempts})...`);
|
|
146
|
-
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
147
|
-
return connect();
|
|
148
|
-
} else {
|
|
149
|
-
console.error("Max reconnection attempts reached. Exiting.");
|
|
150
|
-
process2.exit(1);
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
};
|
|
154
|
-
await connect();
|
|
155
|
-
}
|
|
156
|
-
async function createTunnel(input) {
|
|
157
|
-
const response = await fetch(`${input.apiUrl}/local-tests/tunnels`, {
|
|
158
|
-
method: "POST",
|
|
159
|
-
headers: {
|
|
160
|
-
"content-type": "application/json",
|
|
161
|
-
authorization: `Bearer ${input.token}`
|
|
162
|
-
},
|
|
163
|
-
body: JSON.stringify({
|
|
164
|
-
requestedPort: input.port,
|
|
165
|
-
clientFingerprint: createFingerprint()
|
|
166
|
-
})
|
|
167
|
-
});
|
|
168
|
-
const data = await response.json();
|
|
169
|
-
if (!response.ok || !data.ok || !data.tunnelId || !data.token) {
|
|
170
|
-
throw new Error(data.error ?? response.statusText);
|
|
171
|
-
}
|
|
172
|
-
return { tunnelId: data.tunnelId, publicUrl: data.publicUrl, token: data.token };
|
|
173
|
-
}
|
|
174
|
-
function handleHttpRequest(ctx, request) {
|
|
175
|
-
const targetUrl = `http://localhost:${ctx.port}${request.path.startsWith("/") ? request.path : `/${request.path}`}`;
|
|
176
|
-
const body = request.bodyBase64 ? Buffer.from(request.bodyBase64, "base64") : void 0;
|
|
177
|
-
const headers = { ...request.headers };
|
|
178
|
-
delete headers.host;
|
|
179
|
-
delete headers["content-length"];
|
|
180
|
-
fetch(targetUrl, { method: request.method, headers, body: body ?? void 0 }).then(async (res) => {
|
|
181
|
-
const resBody = await res.arrayBuffer();
|
|
182
|
-
const resHeaders = Object.fromEntries(res.headers.entries());
|
|
183
|
-
delete resHeaders["set-cookie"];
|
|
184
|
-
const getSetCookie = res.headers.getSetCookie;
|
|
185
|
-
const setCookieValues = typeof getSetCookie === "function" ? getSetCookie.call(res.headers) : [];
|
|
186
|
-
const fallbackSetCookie = res.headers.get("set-cookie");
|
|
187
|
-
if (setCookieValues.length === 0 && fallbackSetCookie) {
|
|
188
|
-
setCookieValues.push(fallbackSetCookie);
|
|
189
|
-
}
|
|
190
|
-
if (setCookieValues.length > 0) {
|
|
191
|
-
resHeaders["set-cookie"] = setCookieValues;
|
|
192
|
-
}
|
|
193
|
-
const responsePayload = {
|
|
194
|
-
type: "http_response",
|
|
195
|
-
id: request.id,
|
|
196
|
-
status: res.status,
|
|
197
|
-
headers: resHeaders,
|
|
198
|
-
bodyBase64: resBody.byteLength ? Buffer.from(resBody).toString("base64") : null
|
|
199
|
-
};
|
|
200
|
-
ctx.ws.send(JSON.stringify(responsePayload));
|
|
201
|
-
}).catch((error) => {
|
|
202
|
-
const responsePayload = {
|
|
203
|
-
type: "http_response",
|
|
204
|
-
id: request.id,
|
|
205
|
-
status: 502,
|
|
206
|
-
headers: { "content-type": "text/plain" },
|
|
207
|
-
bodyBase64: Buffer.from(`Tunnel error: ${String(error)}`).toString("base64")
|
|
208
|
-
};
|
|
209
|
-
ctx.ws.send(JSON.stringify(responsePayload));
|
|
210
|
-
});
|
|
211
|
-
}
|
|
212
|
-
function handleWsOpen(ctx, request) {
|
|
213
|
-
const targetUrl = `ws://localhost:${ctx.port}${request.path.startsWith("/") ? request.path : `/${request.path}`}`;
|
|
214
|
-
const protocolsHeader = request.headers["sec-websocket-protocol"] ?? request.headers["Sec-WebSocket-Protocol"];
|
|
215
|
-
const protocols = protocolsHeader ? protocolsHeader.split(",").map((v) => v.trim()).filter(Boolean) : void 0;
|
|
216
|
-
const localWs = new WebSocket(targetUrl, protocols);
|
|
217
|
-
ctx.wsConnections.set(request.id, localWs);
|
|
218
|
-
localWs.onopen = () => {
|
|
219
|
-
ctx.ws.send(JSON.stringify({ type: "ws_ready", id: request.id }));
|
|
220
|
-
const queued = ctx.wsQueues.get(request.id);
|
|
221
|
-
if (queued) {
|
|
222
|
-
for (const message of queued) {
|
|
223
|
-
ctx.ws.send(JSON.stringify(message));
|
|
224
|
-
}
|
|
225
|
-
ctx.wsQueues.delete(request.id);
|
|
226
|
-
}
|
|
227
|
-
};
|
|
228
|
-
localWs.onmessage = (event) => {
|
|
229
|
-
const data = typeof event.data === "string" ? Buffer.from(event.data) : Buffer.from(event.data);
|
|
230
|
-
const response = {
|
|
231
|
-
type: "ws_message",
|
|
232
|
-
id: request.id,
|
|
233
|
-
dataBase64: data.toString("base64"),
|
|
234
|
-
isBinary: typeof event.data !== "string"
|
|
235
|
-
};
|
|
236
|
-
ctx.ws.send(JSON.stringify(response));
|
|
237
|
-
};
|
|
238
|
-
localWs.onclose = (event) => {
|
|
239
|
-
ctx.wsConnections.delete(request.id);
|
|
240
|
-
const response = {
|
|
241
|
-
type: "ws_close",
|
|
242
|
-
id: request.id,
|
|
243
|
-
code: event.code,
|
|
244
|
-
reason: event.reason
|
|
245
|
-
};
|
|
246
|
-
ctx.ws.send(JSON.stringify(response));
|
|
247
|
-
};
|
|
248
|
-
localWs.onerror = () => {
|
|
249
|
-
ctx.wsConnections.delete(request.id);
|
|
250
|
-
const response = {
|
|
251
|
-
type: "ws_close",
|
|
252
|
-
id: request.id,
|
|
253
|
-
code: 1011,
|
|
254
|
-
reason: "local_ws_error"
|
|
255
|
-
};
|
|
256
|
-
ctx.ws.send(JSON.stringify(response));
|
|
257
|
-
};
|
|
258
|
-
}
|
|
259
|
-
function handleWsMessage(ctx, message) {
|
|
260
|
-
const localWs = ctx.wsConnections.get(message.id);
|
|
261
|
-
const data = Buffer.from(message.dataBase64, "base64");
|
|
262
|
-
if (!localWs || localWs.readyState !== WebSocket.OPEN) {
|
|
263
|
-
const queued = ctx.wsQueues.get(message.id) ?? [];
|
|
264
|
-
queued.push(message);
|
|
265
|
-
ctx.wsQueues.set(message.id, queued);
|
|
266
|
-
return;
|
|
267
|
-
}
|
|
268
|
-
if (message.isBinary) {
|
|
269
|
-
localWs.send(data);
|
|
270
|
-
} else {
|
|
271
|
-
localWs.send(data.toString());
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
function handleWsClose(ctx, message) {
|
|
275
|
-
const localWs = ctx.wsConnections.get(message.id);
|
|
276
|
-
if (!localWs) {
|
|
277
|
-
const queued = ctx.wsQueues.get(message.id) ?? [];
|
|
278
|
-
queued.push(message);
|
|
279
|
-
ctx.wsQueues.set(message.id, queued);
|
|
280
|
-
return;
|
|
281
|
-
}
|
|
282
|
-
localWs.close(message.code ?? 1e3, message.reason ?? "");
|
|
283
|
-
ctx.wsConnections.delete(message.id);
|
|
284
|
-
}
|
|
285
|
-
function connectTunnel(input) {
|
|
286
|
-
const wsUrl = toWebSocketUrl(
|
|
287
|
-
`${input.apiUrl}/local-tests/tunnels/${input.tunnelId}/connect?token=${input.token}`
|
|
288
|
-
);
|
|
289
|
-
const ws = new WebSocket(wsUrl);
|
|
290
|
-
const ctx = {
|
|
291
|
-
ws,
|
|
292
|
-
port: input.port,
|
|
293
|
-
wsConnections: /* @__PURE__ */ new Map(),
|
|
294
|
-
wsQueues: /* @__PURE__ */ new Map()
|
|
295
|
-
};
|
|
296
|
-
ws.onopen = () => {
|
|
297
|
-
input.onReady?.();
|
|
298
|
-
};
|
|
299
|
-
ws.onerror = (event) => {
|
|
300
|
-
console.error("Tunnel error", event);
|
|
301
|
-
};
|
|
302
|
-
ws.onclose = () => {
|
|
303
|
-
console.log("Tunnel closed");
|
|
304
|
-
};
|
|
305
|
-
ws.onmessage = async (event) => {
|
|
306
|
-
try {
|
|
307
|
-
const raw = typeof event.data === "string" ? event.data : Buffer.from(event.data).toString();
|
|
308
|
-
const payload = JSON.parse(raw);
|
|
309
|
-
switch (payload.type) {
|
|
310
|
-
case "http_request":
|
|
311
|
-
handleHttpRequest(ctx, payload);
|
|
312
|
-
break;
|
|
313
|
-
case "ws_open":
|
|
314
|
-
handleWsOpen(ctx, payload);
|
|
315
|
-
break;
|
|
316
|
-
case "ws_message":
|
|
317
|
-
handleWsMessage(ctx, payload);
|
|
318
|
-
break;
|
|
319
|
-
case "ws_close":
|
|
320
|
-
handleWsClose(ctx, payload);
|
|
321
|
-
break;
|
|
322
|
-
case "health_ping":
|
|
323
|
-
ws.send(JSON.stringify({ type: "health_pong" }));
|
|
324
|
-
break;
|
|
325
|
-
}
|
|
326
|
-
} catch (error) {
|
|
327
|
-
console.error("Tunnel message error", error);
|
|
328
|
-
}
|
|
329
|
-
};
|
|
330
|
-
return ws;
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
export {
|
|
334
|
-
runLocalTest,
|
|
335
|
-
createLocalRun,
|
|
336
|
-
runTunnel,
|
|
337
|
-
createTunnel,
|
|
338
|
-
connectTunnel
|
|
339
|
-
};
|
|
340
|
-
//# sourceMappingURL=chunk-DXIAHB72.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/local-run.ts","../src/tunnel.ts"],"sourcesContent":["import process from \"node:process\";\nimport { readStoredToken } from \"./auth\";\n\ntype LocalRunOptions = {\n apiUrl: string;\n token?: string;\n title?: string;\n featureSpec?: string;\n startUrl?: string;\n tunnelUrl?: string;\n};\n\ntype LocalRunResponse = {\n ok: boolean;\n runId?: string;\n watchUrl?: string;\n error?: string;\n};\n\nfunction getArgValue(argv: string[], key: string): string | undefined {\n const index = argv.indexOf(key);\n if (index === -1) return undefined;\n return argv[index + 1];\n}\n\nexport async function runLocalTest(argv: string[]) {\n const apiUrl = getArgValue(argv, \"--api-url\") ?? process.env.CANARY_API_URL ?? \"https://api.trycanary.ai\";\n const token =\n getArgValue(argv, \"--token\") ??\n process.env.CANARY_API_TOKEN ??\n (await readStoredToken());\n const title = getArgValue(argv, \"--title\");\n const featureSpec = getArgValue(argv, \"--feature\");\n const startUrl = getArgValue(argv, \"--start-url\");\n const tunnelUrl = getArgValue(argv, \"--tunnel-url\");\n\n if (!tunnelUrl && !startUrl) {\n console.error(\"Missing --tunnel-url or --start-url\");\n process.exit(1);\n }\n\n if (!token) {\n console.error(\"Missing token. Run `canary login` first or set CANARY_API_TOKEN.\");\n process.exit(1);\n }\n\n const result = await createLocalRun({\n apiUrl,\n token,\n title,\n featureSpec,\n startUrl,\n tunnelUrl,\n });\n\n console.log(`Local test queued: ${result.runId}`);\n if (result.watchUrl) {\n console.log(`Watch: ${result.watchUrl}`);\n }\n}\n\nexport async function createLocalRun(input: {\n apiUrl: string;\n token: string;\n title?: string;\n featureSpec?: string;\n startUrl?: string;\n tunnelUrl?: string;\n}): Promise<{ runId: string; watchUrl?: string }> {\n const body = {\n title: input.title ?? null,\n featureSpec: input.featureSpec ?? null,\n startUrl: input.startUrl ?? null,\n tunnelPublicUrl: input.tunnelUrl ?? null,\n };\n\n const response = await fetch(`${input.apiUrl}/local-tests/runs`, {\n method: \"POST\",\n headers: {\n \"content-type\": \"application/json\",\n authorization: `Bearer ${input.token}`,\n },\n body: JSON.stringify(body),\n });\n\n const json = (await response.json()) as LocalRunResponse;\n if (!response.ok || !json.ok || !json.runId) {\n throw new Error(json.error ?? response.statusText);\n }\n\n return { runId: json.runId, watchUrl: json.watchUrl };\n}\n","import { createHash } from \"node:crypto\";\nimport os from \"node:os\";\nimport process from \"node:process\";\nimport { readStoredToken } from \"./auth\";\n\nexport type CreateTunnelResponse = {\n ok: boolean;\n tunnelId?: string;\n publicUrl?: string;\n token?: string;\n error?: string;\n};\n\ntype ProxyRequest = {\n type: \"http_request\";\n id: string;\n method: string;\n path: string;\n headers: Record<string, string>;\n bodyBase64?: string | null;\n};\n\ntype ProxyResponse = {\n type: \"http_response\";\n id: string;\n status: number;\n headers?: Record<string, string | string[]>;\n bodyBase64?: string | null;\n};\n\ntype WsOpen = {\n type: \"ws_open\";\n id: string;\n path: string;\n headers: Record<string, string>;\n};\n\ntype WsReady = {\n type: \"ws_ready\";\n id: string;\n};\n\ntype WsMessage = {\n type: \"ws_message\";\n id: string;\n dataBase64: string;\n isBinary: boolean;\n};\n\ntype WsClose = {\n type: \"ws_close\";\n id: string;\n code?: number;\n reason?: string;\n};\n\nfunction getArgValue(argv: string[], key: string): string | undefined {\n const index = argv.indexOf(key);\n if (index === -1) return undefined;\n return argv[index + 1];\n}\n\nfunction toWebSocketUrl(apiUrl: string): string {\n const url = new URL(apiUrl);\n url.protocol = url.protocol === \"https:\" ? \"wss:\" : \"ws:\";\n return url.toString();\n}\n\nfunction createFingerprint(): string {\n const raw = `${os.hostname()}-${os.userInfo().username}-${process.version}`;\n return createHash(\"sha256\").update(raw).digest(\"hex\").slice(0, 16);\n}\n\nexport async function runTunnel(argv: string[]) {\n const apiUrl = getArgValue(argv, \"--api-url\") ?? process.env.CANARY_API_URL ?? \"https://api.trycanary.ai\";\n const token =\n getArgValue(argv, \"--token\") ??\n process.env.CANARY_API_TOKEN ??\n (await readStoredToken());\n const portRaw = getArgValue(argv, \"--port\") ?? process.env.CANARY_LOCAL_PORT;\n\n if (!portRaw) {\n console.error(\"Missing --port\");\n process.exit(1);\n }\n\n const port = Number(portRaw);\n if (Number.isNaN(port) || port <= 0) {\n console.error(\"Invalid --port value\");\n process.exit(1);\n }\n\n if (!token) {\n console.error(\"Missing token. Run `canary login` first or set CANARY_API_TOKEN.\");\n process.exit(1);\n }\n\n const maxReconnectAttempts = 10;\n const baseReconnectDelayMs = 1000;\n let reconnectAttempts = 0;\n\n const connect = async (): Promise<void> => {\n try {\n const data = await createTunnel({ apiUrl, token, port });\n\n console.log(`Tunnel connected: ${data.publicUrl ?? data.tunnelId}`);\n if (data.publicUrl) {\n console.log(`Public URL: ${data.publicUrl}`);\n console.log(\"\");\n console.log(\"To use this tunnel for sandbox agent callbacks, add to apps/api/.env:\");\n console.log(` SANDBOX_AGENT_API_URL=${data.publicUrl}`);\n console.log(\"\");\n }\n\n const ws = connectTunnel({\n apiUrl,\n tunnelId: data.tunnelId,\n token: data.token,\n port,\n onReady: () => {\n reconnectAttempts = 0;\n },\n });\n\n return new Promise<void>((resolve, reject) => {\n ws.onclose = (event) => {\n console.log(`Tunnel closed (code: ${event.code})`);\n\n if (reconnectAttempts < maxReconnectAttempts) {\n const delay = Math.min(baseReconnectDelayMs * Math.pow(2, reconnectAttempts), 30000);\n reconnectAttempts++;\n console.log(`Reconnecting in ${delay}ms (attempt ${reconnectAttempts}/${maxReconnectAttempts})...`);\n setTimeout(() => {\n connect().then(resolve).catch(reject);\n }, delay);\n } else {\n console.error(\"Max reconnection attempts reached. Exiting.\");\n process.exit(1);\n }\n };\n\n ws.onerror = (event) => {\n console.error(\"Tunnel error:\", event);\n };\n });\n } catch (error) {\n if (reconnectAttempts < maxReconnectAttempts) {\n const delay = Math.min(baseReconnectDelayMs * Math.pow(2, reconnectAttempts), 30000);\n reconnectAttempts++;\n console.error(`Failed to create tunnel: ${error}`);\n console.log(`Retrying in ${delay}ms (attempt ${reconnectAttempts}/${maxReconnectAttempts})...`);\n await new Promise((resolve) => setTimeout(resolve, delay));\n return connect();\n } else {\n console.error(\"Max reconnection attempts reached. Exiting.\");\n process.exit(1);\n }\n }\n };\n\n await connect();\n}\n\nexport async function createTunnel(input: {\n apiUrl: string;\n token: string;\n port: number;\n}): Promise<{ tunnelId: string; publicUrl?: string; token: string }> {\n const response = await fetch(`${input.apiUrl}/local-tests/tunnels`, {\n method: \"POST\",\n headers: {\n \"content-type\": \"application/json\",\n authorization: `Bearer ${input.token}`,\n },\n body: JSON.stringify({\n requestedPort: input.port,\n clientFingerprint: createFingerprint(),\n }),\n });\n\n const data = (await response.json()) as CreateTunnelResponse;\n if (!response.ok || !data.ok || !data.tunnelId || !data.token) {\n throw new Error(data.error ?? response.statusText);\n }\n\n return { tunnelId: data.tunnelId, publicUrl: data.publicUrl, token: data.token };\n}\n\n// === Tunnel Message Handlers ===\n\ntype TunnelContext = {\n ws: WebSocket;\n port: number;\n wsConnections: Map<string, WebSocket>;\n wsQueues: Map<string, Array<WsMessage | WsClose>>;\n};\n\nfunction handleHttpRequest(ctx: TunnelContext, request: ProxyRequest): void {\n const targetUrl = `http://localhost:${ctx.port}${\n request.path.startsWith(\"/\") ? request.path : `/${request.path}`\n }`;\n const body = request.bodyBase64 ? Buffer.from(request.bodyBase64, \"base64\") : undefined;\n const headers = { ...request.headers };\n delete headers.host;\n delete headers[\"content-length\"];\n\n fetch(targetUrl, { method: request.method, headers, body: body ?? undefined })\n .then(async (res) => {\n const resBody = await res.arrayBuffer();\n const resHeaders: Record<string, string | string[]> = Object.fromEntries(res.headers.entries());\n delete resHeaders[\"set-cookie\"];\n\n const getSetCookie = (res.headers as Headers & { getSetCookie?: () => string[] }).getSetCookie;\n const setCookieValues = typeof getSetCookie === \"function\" ? getSetCookie.call(res.headers) : [];\n const fallbackSetCookie = res.headers.get(\"set-cookie\");\n if (setCookieValues.length === 0 && fallbackSetCookie) {\n setCookieValues.push(fallbackSetCookie);\n }\n if (setCookieValues.length > 0) {\n resHeaders[\"set-cookie\"] = setCookieValues;\n }\n\n const responsePayload: ProxyResponse = {\n type: \"http_response\",\n id: request.id,\n status: res.status,\n headers: resHeaders,\n bodyBase64: resBody.byteLength ? Buffer.from(resBody).toString(\"base64\") : null,\n };\n ctx.ws.send(JSON.stringify(responsePayload));\n })\n .catch((error) => {\n const responsePayload: ProxyResponse = {\n type: \"http_response\",\n id: request.id,\n status: 502,\n headers: { \"content-type\": \"text/plain\" },\n bodyBase64: Buffer.from(`Tunnel error: ${String(error)}`).toString(\"base64\"),\n };\n ctx.ws.send(JSON.stringify(responsePayload));\n });\n}\n\nfunction handleWsOpen(ctx: TunnelContext, request: WsOpen): void {\n const targetUrl = `ws://localhost:${ctx.port}${\n request.path.startsWith(\"/\") ? request.path : `/${request.path}`\n }`;\n const protocolsHeader =\n request.headers[\"sec-websocket-protocol\"] ?? request.headers[\"Sec-WebSocket-Protocol\"];\n const protocols = protocolsHeader\n ? protocolsHeader.split(\",\").map((v) => v.trim()).filter(Boolean)\n : undefined;\n const localWs = new WebSocket(targetUrl, protocols);\n ctx.wsConnections.set(request.id, localWs);\n\n localWs.onopen = () => {\n ctx.ws.send(JSON.stringify({ type: \"ws_ready\", id: request.id } satisfies WsReady));\n const queued = ctx.wsQueues.get(request.id);\n if (queued) {\n for (const message of queued) {\n ctx.ws.send(JSON.stringify(message));\n }\n ctx.wsQueues.delete(request.id);\n }\n };\n\n localWs.onmessage = (event) => {\n const data =\n typeof event.data === \"string\"\n ? Buffer.from(event.data)\n : Buffer.from(event.data as ArrayBuffer);\n const response: WsMessage = {\n type: \"ws_message\",\n id: request.id,\n dataBase64: data.toString(\"base64\"),\n isBinary: typeof event.data !== \"string\",\n };\n ctx.ws.send(JSON.stringify(response));\n };\n\n localWs.onclose = (event) => {\n ctx.wsConnections.delete(request.id);\n const response: WsClose = {\n type: \"ws_close\",\n id: request.id,\n code: event.code,\n reason: event.reason,\n };\n ctx.ws.send(JSON.stringify(response));\n };\n\n localWs.onerror = () => {\n ctx.wsConnections.delete(request.id);\n const response: WsClose = {\n type: \"ws_close\",\n id: request.id,\n code: 1011,\n reason: \"local_ws_error\",\n };\n ctx.ws.send(JSON.stringify(response));\n };\n}\n\nfunction handleWsMessage(ctx: TunnelContext, message: WsMessage): void {\n const localWs = ctx.wsConnections.get(message.id);\n const data = Buffer.from(message.dataBase64, \"base64\");\n if (!localWs || localWs.readyState !== WebSocket.OPEN) {\n const queued = ctx.wsQueues.get(message.id) ?? [];\n queued.push(message);\n ctx.wsQueues.set(message.id, queued);\n return;\n }\n if (message.isBinary) {\n localWs.send(data);\n } else {\n localWs.send(data.toString());\n }\n}\n\nfunction handleWsClose(ctx: TunnelContext, message: WsClose): void {\n const localWs = ctx.wsConnections.get(message.id);\n if (!localWs) {\n const queued = ctx.wsQueues.get(message.id) ?? [];\n queued.push(message);\n ctx.wsQueues.set(message.id, queued);\n return;\n }\n localWs.close(message.code ?? 1000, message.reason ?? \"\");\n ctx.wsConnections.delete(message.id);\n}\n\n// === Connect ===\n\nexport function connectTunnel(input: {\n apiUrl: string;\n tunnelId: string;\n token: string;\n port: number;\n onReady?: () => void;\n}): WebSocket {\n const wsUrl = toWebSocketUrl(\n `${input.apiUrl}/local-tests/tunnels/${input.tunnelId}/connect?token=${input.token}`\n );\n const ws = new WebSocket(wsUrl);\n const ctx: TunnelContext = {\n ws,\n port: input.port,\n wsConnections: new Map(),\n wsQueues: new Map(),\n };\n\n ws.onopen = () => {\n input.onReady?.();\n };\n\n ws.onerror = (event) => {\n console.error(\"Tunnel error\", event);\n };\n\n ws.onclose = () => {\n console.log(\"Tunnel closed\");\n };\n\n ws.onmessage = async (event) => {\n try {\n const raw =\n typeof event.data === \"string\"\n ? event.data\n : Buffer.from(event.data as ArrayBuffer).toString();\n const payload = JSON.parse(raw) as { type: string };\n\n switch (payload.type) {\n case \"http_request\":\n handleHttpRequest(ctx, payload as ProxyRequest);\n break;\n case \"ws_open\":\n handleWsOpen(ctx, payload as WsOpen);\n break;\n case \"ws_message\":\n handleWsMessage(ctx, payload as WsMessage);\n break;\n case \"ws_close\":\n handleWsClose(ctx, payload as WsClose);\n break;\n case \"health_ping\":\n ws.send(JSON.stringify({ type: \"health_pong\" }));\n break;\n }\n } catch (error) {\n console.error(\"Tunnel message error\", error);\n }\n };\n\n return ws;\n}\n"],"mappings":"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","names":["process","getArgValue","process"]}
|