{"version":3,"file":"browser.mjs","names":[],"sources":["../../../src/tools/browser.ts"],"sourcesContent":["/**\n * Browser Automation Tool — navigate, click, type, extract via PinchTab.\n *\n * Actions:\n *   navigate    — Navigate to a URL and extract page content\n *   click       — Click an element by CSS selector\n *   type        — Type text into an input field\n *   extract     — Extract structured data from current page\n *   screenshot  — Take a page screenshot (use sparingly)\n *   status      — Check if PinchTab is running\n *\n * Uses PinchTab HTTP API (Go binary, 12MB, stealth mode).\n * Token-efficient: ~800 tokens/page vs 10K+ for screenshots.\n *\n * Use cases: claim airdrops, interact with dApp UIs, scrape dashboards,\n * browse protocol docs, fill forms.\n */\n\nimport { Type } from '@sinclair/typebox';\nimport { stringEnum, jsonResult, errorResult, readStringParam } from '../lib/tool-helpers.js';\nimport { getBrowserService } from '../services/browser-service.js';\n\nconst ACTIONS = ['navigate', 'click', 'type', 'extract', 'screenshot', 'status'] as const;\n\nconst BrowserSchema = Type.Object({\n  action: stringEnum(ACTIONS, {\n    description:\n      'navigate: go to URL and extract content. click: click element. ' +\n      'type: type into input. extract: get structured data. ' +\n      'screenshot: take screenshot. status: check PinchTab.',\n  }),\n  url: Type.Optional(Type.String({\n    description: 'URL to navigate to. Required for navigate action.',\n  })),\n  selector: Type.Optional(Type.String({\n    description: 'CSS selector for click/type/extract/screenshot actions.',\n  })),\n  text: Type.Optional(Type.String({\n    description: 'Text to type into input field. Required for type action.',\n  })),\n  wait_for: Type.Optional(Type.String({\n    description: 'CSS selector to wait for after navigation (ensures page is loaded).',\n  })),\n  stealth: Type.Optional(Type.Boolean({\n    description: 'Enable stealth mode to avoid bot detection. Default: true.',\n  })),\n  format: Type.Optional(Type.String({\n    description: 'Extraction format: \"text\" (default), \"json\", \"table\".',\n  })),\n  press_enter: Type.Optional(Type.Boolean({\n    description: 'Press Enter after typing. Default: false.',\n  })),\n  full_page: Type.Optional(Type.Boolean({\n    description: 'Take full-page screenshot instead of viewport. Default: false.',\n  })),\n});\n\nexport function createBrowserTool() {\n  return {\n    name: 'browser',\n    label: 'Browser',\n    ownerOnly: true,\n    description:\n      'Browser automation via PinchTab. Navigate to URLs, click elements, type text, ' +\n      'and extract structured content from web pages. Token-efficient (~800 tokens/page). ' +\n      'Use for: claiming airdrops, interacting with dApp UIs, scraping dashboards. ' +\n      'Requires PinchTab binary running locally.',\n    parameters: BrowserSchema,\n    execute: async (_toolCallId: string, args: unknown) => {\n      const params = args as Record<string, unknown>;\n      const action = readStringParam(params, 'action', { required: true })!;\n\n      switch (action) {\n        case 'navigate':\n          return handleNavigate(params);\n        case 'click':\n          return handleClick(params);\n        case 'type':\n          return handleType(params);\n        case 'extract':\n          return handleExtract(params);\n        case 'screenshot':\n          return handleScreenshot(params);\n        case 'status':\n          return handleStatus();\n        default:\n          return errorResult(`Unknown action: ${action}. Use: navigate, click, type, extract, screenshot, status`);\n      }\n    },\n  };\n}\n\n// ── Action Handlers ─────────────────────────────────────────────────────\n\nasync function handleNavigate(params: Record<string, unknown>) {\n  const url = readStringParam(params, 'url');\n  if (!url) {\n    return errorResult('url is required for navigate action.');\n  }\n\n  // Basic URL validation\n  try {\n    new URL(url);\n  } catch {\n    return errorResult(`Invalid URL: \"${url}\". Must be a fully-formed URL (e.g. https://app.aave.com).`);\n  }\n\n  try {\n    const service = getBrowserService();\n    const status = await service.getStatus();\n    if (!status.running) {\n      return errorResult(\n        'PinchTab is not running. Start it with: pinchtab serve\\n' +\n        'Install from: https://pinchtab.com',\n      );\n    }\n\n    const result = await service.navigate(url, {\n      waitFor: readStringParam(params, 'wait_for') ?? undefined,\n      stealth: params.stealth !== false,\n    });\n\n    return jsonResult({\n      url: result.url,\n      title: result.title,\n      status: result.status,\n      loadTimeMs: result.loadTimeMs,\n      content: truncateContent(result.content, 4000),\n      links: result.links.slice(0, 20).map(l => ({\n        text: l.text.slice(0, 80),\n        href: l.href,\n      })),\n      forms: result.forms.length > 0 ? result.forms : undefined,\n      tip: result.links.length > 20\n        ? `Showing 20 of ${result.links.length} links. Use extract with a selector for specific content.`\n        : undefined,\n    });\n  } catch (err) {\n    return errorResult(`Navigate failed: ${err instanceof Error ? err.message : String(err)}`);\n  }\n}\n\nasync function handleClick(params: Record<string, unknown>) {\n  const selector = readStringParam(params, 'selector');\n  if (!selector) {\n    return errorResult('selector is required for click action (CSS selector, e.g. \"button.submit\", \"#claim-btn\").');\n  }\n\n  try {\n    const service = getBrowserService();\n    const result = await service.click(selector);\n\n    return jsonResult({\n      clicked: result.clicked,\n      selector: result.selector,\n      newUrl: result.newUrl,\n      content: result.content ? truncateContent(result.content, 2000) : undefined,\n    });\n  } catch (err) {\n    return errorResult(`Click failed: ${err instanceof Error ? err.message : String(err)}`);\n  }\n}\n\nasync function handleType(params: Record<string, unknown>) {\n  const selector = readStringParam(params, 'selector');\n  const text = readStringParam(params, 'text');\n  if (!selector || !text) {\n    return errorResult('Both selector and text are required for type action.');\n  }\n\n  try {\n    const service = getBrowserService();\n    const result = await service.type(selector, text, {\n      pressEnter: params.press_enter === true,\n    });\n\n    return jsonResult({\n      typed: result.typed,\n      selector: result.selector,\n      value: result.value,\n    });\n  } catch (err) {\n    return errorResult(`Type failed: ${err instanceof Error ? err.message : String(err)}`);\n  }\n}\n\nasync function handleExtract(params: Record<string, unknown>) {\n  try {\n    const service = getBrowserService();\n    const format = readStringParam(params, 'format');\n    const result = await service.extract({\n      selector: readStringParam(params, 'selector') ?? undefined,\n      format: (format as 'text' | 'json' | 'table') ?? 'text',\n    });\n\n    return jsonResult({\n      data: result.data,\n      tokens: result.tokens,\n    });\n  } catch (err) {\n    return errorResult(`Extract failed: ${err instanceof Error ? err.message : String(err)}`);\n  }\n}\n\nasync function handleScreenshot(params: Record<string, unknown>) {\n  try {\n    const service = getBrowserService();\n    const result = await service.screenshot({\n      fullPage: params.full_page === true,\n      selector: readStringParam(params, 'selector') ?? undefined,\n    });\n\n    return jsonResult({\n      width: result.width,\n      height: result.height,\n      sizeKb: Math.round((result.base64.length * 3) / 4 / 1024),\n      note: 'Screenshot captured. Prefer extract action for token-efficient content retrieval.',\n      // Don't include base64 in result — too large for LLM context.\n      // In a real implementation, save to file and return path.\n      saved: 'screenshot available via PinchTab UI',\n    });\n  } catch (err) {\n    return errorResult(`Screenshot failed: ${err instanceof Error ? err.message : String(err)}`);\n  }\n}\n\nasync function handleStatus() {\n  try {\n    const service = getBrowserService();\n    const status = await service.getStatus();\n\n    return jsonResult({\n      running: status.running,\n      url: status.url,\n      version: status.version,\n      currentPage: status.currentPage,\n      note: status.running\n        ? 'PinchTab is running and ready.'\n        : 'PinchTab is not running. Start with: pinchtab serve\\nInstall: https://pinchtab.com',\n    });\n  } catch (err) {\n    return errorResult(`Status check failed: ${err instanceof Error ? err.message : String(err)}`);\n  }\n}\n\n// ── Helpers ──────────────────────────────────────────────────────────────\n\nfunction truncateContent(content: string, maxLength: number): string {\n  if (content.length <= maxLength) return content;\n  return content.slice(0, maxLength) + `\\n... [truncated, ${content.length - maxLength} more chars]`;\n}\n"],"mappings":"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"}