droid-patch 0.4.0 → 0.5.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.
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.mjs","names":["patches: Patch[]","unlink","metaList: Awaited<ReturnType<typeof loadAliasMetadata>>[]"],"sources":["../src/websearch-patch.ts","../src/cli.ts"],"sourcesContent":["import type { Patch } from \"./patcher.ts\";\nimport { writeFile, chmod, mkdir } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { existsSync } from \"node:fs\";\n\n/**\n * WebSearch Patch Generator\n *\n * Since injecting code directly into binary is complex (requires exact byte length matching),\n * we use a more practical approach:\n *\n * 1. --websearch option will:\n * a) Generate a standalone search proxy server script\n * b) Modify droid's API URL to point to local proxy (using --api-base)\n * c) Create a wrapper script to start both proxy and droid\n *\n * Environment variables:\n * - GOOGLE_PSE_API_KEY: Google Programmable Search Engine API Key\n * - GOOGLE_PSE_CX: Google Custom Search Engine ID\n * - If not set, will fallback to DuckDuckGo\n */\n\n/**\n * Generate search proxy server code\n */\nfunction generateSearchProxyServerCode(): string {\n return `#!/usr/bin/env node\n/**\n * Droid WebSearch Proxy Server\n * Auto-generated by droid-patch --websearch\n * \n * Supports:\n * - Google PSE (requires GOOGLE_PSE_API_KEY and GOOGLE_PSE_CX)\n * - DuckDuckGo (free fallback)\n */\n\nconst http = require('http');\nconst https = require('https');\n\nconst FACTORY_API = 'https://api.factory.ai';\n\n// Auto-find available port\nfunction findAvailablePort(startPort = 23119) {\n return new Promise((resolve, reject) => {\n const net = require('net');\n const server = net.createServer();\n \n server.listen(startPort, '127.0.0.1', () => {\n const port = server.address().port;\n server.close(() => resolve(port));\n });\n \n server.on('error', (err) => {\n if (err.code === 'EADDRINUSE') {\n // Port is in use, try next one\n resolve(findAvailablePort(startPort + 1));\n } else {\n reject(err);\n }\n });\n });\n}\n\nlet PORT = process.env.SEARCH_PROXY_PORT || 23119;\n\n// === Search Implementation ===\n\nasync function searchGooglePSE(query, numResults, apiKey, cx) {\n // Use curl command\n const { execSync } = require('child_process');\n \n const url = 'https://www.googleapis.com/customsearch/v1?key=' + apiKey + '&cx=' + cx + '&q=' + encodeURIComponent(query) + '&num=' + Math.min(numResults, 10);\n const curlCmd = \\`curl -s \"\\${url}\"\\`;\n \n try {\n const jsonStr = execSync(curlCmd, { encoding: 'utf-8', timeout: 15000 });\n const data = JSON.parse(jsonStr);\n \n return (data.items || []).map(item => ({\n title: item.title,\n url: item.link,\n snippet: item.snippet,\n publishedDate: null,\n author: null,\n score: null\n }));\n } catch (e) {\n throw new Error('Google PSE error: ' + e.message);\n }\n}\n\nasync function searchDuckDuckGo(query, numResults) {\n // Use curl command, because Node.js fetch may have issues in some environments\n const { execSync } = require('child_process');\n\n // Method 1: Try using DuckDuckGo HTML lite version (via curl)\n try {\n const curlCmd = \\`curl -s -X POST \"https://lite.duckduckgo.com/lite/\" -H \"Content-Type: application/x-www-form-urlencoded\" -H \"User-Agent: Mozilla/5.0\" -d \"q=\\${encodeURIComponent(query)}\"\\`;\n const html = execSync(curlCmd, { encoding: 'utf-8', timeout: 15000 });\n\n if (html && html.length > 1000) {\n const results = parseDDGLiteHTML(html, numResults);\n if (results.length > 0) {\n console.error('[search] DDG lite returned ' + results.length + ' results');\n return results;\n }\n }\n } catch (e) {\n console.error('[search] DDG lite (curl) failed:', e.message);\n }\n\n // Method 2: Fallback to Instant Answer API (via curl)\n try {\n const apiUrl = 'https://api.duckduckgo.com/?q=' + encodeURIComponent(query) + '&format=json&no_html=1&skip_disambig=1';\n const curlCmd = \\`curl -s \"\\${apiUrl}\" -H \"User-Agent: Mozilla/5.0\"\\`;\n const jsonStr = execSync(curlCmd, { encoding: 'utf-8', timeout: 15000 });\n const data = JSON.parse(jsonStr);\n \n const results = [];\n\n if (data.Abstract && data.AbstractURL) {\n results.push({\n title: data.Heading || query,\n url: data.AbstractURL,\n snippet: data.Abstract,\n publishedDate: null,\n author: null,\n score: null\n });\n }\n\n for (const topic of (data.RelatedTopics || [])) {\n if (results.length >= numResults) break;\n if (topic.Text && topic.FirstURL) {\n results.push({\n title: topic.Text.substring(0, 100),\n url: topic.FirstURL,\n snippet: topic.Text,\n publishedDate: null,\n author: null,\n score: null\n });\n }\n if (topic.Topics) {\n for (const st of topic.Topics) {\n if (results.length >= numResults) break;\n if (st.Text && st.FirstURL) {\n results.push({\n title: st.Text.substring(0, 100),\n url: st.FirstURL,\n snippet: st.Text,\n publishedDate: null,\n author: null,\n score: null\n });\n }\n }\n }\n }\n\n if (results.length > 0) {\n console.error('[search] DDG API returned ' + results.length + ' results');\n return results;\n }\n } catch (e) {\n console.error('[search] DDG API (curl) failed:', e.message);\n }\n\n return [];\n}\n\n// Parse DuckDuckGo Lite HTML\nfunction parseDDGLiteHTML(html, maxResults) {\n const results = [];\n\n // Match result links - DuckDuckGo Lite format\n // <a rel=\"nofollow\" href=\"URL\">TITLE</a>\n const linkRegex = /<a[^>]+rel=\"nofollow\"[^>]+href=\"([^\"]+)\"[^>]*>([^<]+)<\\\\/a>/gi;\n const snippetRegex = /<td[^>]*class=\"result-snippet\"[^>]*>([^<]*)<\\\\/td>/gi;\n\n const links = [];\n let match;\n\n // Extract all links\n while ((match = linkRegex.exec(html)) !== null && links.length < maxResults) {\n let url = match[1];\n // Skip DuckDuckGo internal links\n if (url.includes('duckduckgo.com') && !url.includes('uddg=')) continue;\n // Decode redirect URL\n if (url.includes('uddg=')) {\n const uddgMatch = url.match(/uddg=([^&]+)/);\n if (uddgMatch) url = decodeURIComponent(uddgMatch[1]);\n }\n links.push({\n url: url,\n title: decodeHTMLEntities(match[2].trim())\n });\n }\n\n // Extract snippets\n const snippets = [];\n while ((match = snippetRegex.exec(html)) !== null && snippets.length < maxResults) {\n snippets.push(decodeHTMLEntities(match[1].trim()));\n }\n\n // Combine results\n for (let i = 0; i < links.length && results.length < maxResults; i++) {\n results.push({\n title: links[i].title,\n url: links[i].url,\n snippet: snippets[i] || '',\n publishedDate: null,\n author: null,\n score: null\n });\n }\n \n return results;\n}\n\nfunction decodeHTMLEntities(str) {\n return str\n .replace(/&amp;/g, '&')\n .replace(/&lt;/g, '<')\n .replace(/&gt;/g, '>')\n .replace(/&quot;/g, '\"')\n .replace(/&#39;/g, \"'\")\n .replace(/&nbsp;/g, ' ');\n}\n\nasync function search(query, numResults = 10) {\n const googleApiKey = process.env.GOOGLE_PSE_API_KEY;\n const googleCx = process.env.GOOGLE_PSE_CX;\n\n // Try Google PSE first\n if (googleApiKey && googleCx) {\n try {\n console.error('[search] Trying Google PSE...');\n const results = await searchGooglePSE(query, numResults, googleApiKey, googleCx);\n if (results.length > 0) {\n console.error('[search] Google PSE returned ' + results.length + ' results');\n return { results, source: 'google-pse' };\n }\n } catch (e) {\n console.error('[search] Google PSE failed:', e.message);\n }\n }\n\n // Fallback to DuckDuckGo\n try {\n console.error('[search] Using DuckDuckGo...');\n const results = await searchDuckDuckGo(query, numResults);\n console.error('[search] DuckDuckGo returned ' + results.length + ' results');\n return { results, source: 'duckduckgo' };\n } catch (e) {\n console.error('[search] DuckDuckGo failed:', e.message);\n }\n\n return { results: [], source: 'none' };\n}\n\n// === HTTP Server ===\n\nconst server = http.createServer(async (req, res) => {\n const url = new URL(req.url, 'http://' + req.headers.host);\n\n // Health check\n if (url.pathname === '/health') {\n res.writeHead(200, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ \n status: 'ok',\n google: !!(process.env.GOOGLE_PSE_API_KEY && process.env.GOOGLE_PSE_CX),\n duckduckgo: true\n }));\n return;\n }\n\n // Search endpoint\n if (url.pathname === '/api/tools/exa/search' && req.method === 'POST') {\n let body = '';\n req.on('data', chunk => body += chunk);\n req.on('end', async () => {\n try {\n const { query, numResults } = JSON.parse(body);\n console.error('[search] Query: \"' + query + '\"');\n \n const { results, source } = await search(query, numResults || 10);\n console.error('[search] ' + results.length + ' results from ' + source);\n \n res.writeHead(200, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ results }));\n } catch (e) {\n console.error('[search] Error:', e);\n res.writeHead(500, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ error: String(e), results: [] }));\n }\n });\n return;\n }\n\n // Proxy other requests to Factory API\n console.error('[proxy] ' + req.method + ' ' + url.pathname);\n \n const proxyUrl = new URL(FACTORY_API + url.pathname + url.search);\n \n const proxyReq = https.request(proxyUrl, {\n method: req.method,\n headers: { ...req.headers, host: proxyUrl.host }\n }, proxyRes => {\n res.writeHead(proxyRes.statusCode, proxyRes.headers);\n proxyRes.pipe(res);\n });\n\n proxyReq.on('error', e => {\n console.error('[proxy] Error:', e.message);\n res.writeHead(502, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ error: 'Proxy failed' }));\n });\n\n if (req.method !== 'GET' && req.method !== 'HEAD') {\n req.pipe(proxyReq);\n } else {\n proxyReq.end();\n }\n});\n\n// Start server (async, auto-find available port)\n(async () => {\n const fs = require('fs');\n const path = require('path');\n\n // If port not specified, auto-find available port\n if (!process.env.SEARCH_PROXY_PORT) {\n PORT = await findAvailablePort(23119);\n }\n\n server.listen(PORT, '127.0.0.1', () => {\n const hasGoogle = process.env.GOOGLE_PSE_API_KEY && process.env.GOOGLE_PSE_CX;\n\n // Write port number to temp file for wrapper script to read\n const portFile = process.env.SEARCH_PROXY_PORT_FILE || path.join(require('os').tmpdir(), 'droid-search-proxy-' + process.pid + '.port');\n fs.writeFileSync(portFile, PORT.toString());\n\n // Output port number to stdout (for parent process to capture)\n console.log('PORT=' + PORT);\n \n console.error('');\n console.error('╔═══════════════════════════════════════════════════════════════╗');\n console.error('║ Droid WebSearch Proxy ║');\n console.error('╠═══════════════════════════════════════════════════════════════╣');\n console.error('║ 🔍 Google PSE: ' + (hasGoogle ? 'Configured ✓' : 'Not set (set GOOGLE_PSE_API_KEY & CX)').padEnd(45) + '║');\n console.error('║ 🦆 DuckDuckGo: Always available ║');\n console.error('║ 🚀 Server: http://127.0.0.1:' + PORT + ' ║'.slice(0, 65) + '║');\n console.error('╚═══════════════════════════════════════════════════════════════╝');\n console.error('');\n });\n})();\n\n// Handle graceful shutdown\nprocess.on('SIGTERM', () => server.close());\nprocess.on('SIGINT', () => server.close());\n`;\n}\n\n/**\n * Generate wrapper script, auto-start proxy and droid\n */\nfunction generateWrapperScript(droidPath: string, proxyScriptPath: string): string {\n return `#!/bin/bash\n# Droid with WebSearch Proxy\n# Auto-generated by droid-patch --websearch\n\nPROXY_SCRIPT=\"${proxyScriptPath}\"\nDROID_BIN=\"${droidPath}\"\nPORT_FILE=\"/tmp/droid-search-proxy-$$.port\"\n\n# Start proxy and get dynamic port\nstart_proxy() {\n # Start proxy, capture output to get port\n SEARCH_PROXY_PORT_FILE=\"$PORT_FILE\" node \"$PROXY_SCRIPT\" &\n PROXY_PID=$!\n\n # Wait for proxy to start and get port\n for i in {1..20}; do\n if [ -f \"$PORT_FILE\" ]; then\n PORT=$(cat \"$PORT_FILE\")\n if curl -s \"http://127.0.0.1:$PORT/health\" > /dev/null 2>&1; then\n echo \"[websearch] Proxy started on port $PORT\"\n return 0\n fi\n fi\n sleep 0.2\n done\n\n echo \"[websearch] Failed to start proxy\"\n kill $PROXY_PID 2>/dev/null\n return 1\n}\n\n# Cleanup function\ncleanup() {\n [ -n \"$PROXY_PID\" ] && kill $PROXY_PID 2>/dev/null\n [ -f \"$PORT_FILE\" ] && rm -f \"$PORT_FILE\"\n}\ntrap cleanup EXIT\n\n# Start proxy\nif ! start_proxy; then\n exit 1\nfi\n\n# Run droid\nexport FACTORY_API_BASE_URL_OVERRIDE=\"http://127.0.0.1:$PORT\"\nexec \"$DROID_BIN\" \"$@\"\n`;\n}\n\n/**\n * Generate WebSearch Patch\n *\n * Since injecting code directly into binary is complex, we use the following strategy:\n * 1. Create proxy server script\n * 2. Modify API URL to point to local\n * 3. Return a combined patch\n */\nexport function generateWebSearchPatch(): Patch | null {\n // Return a URL replacement patch\n // Use local proxy port 23119 (idle port)\n const originalUrl = \"https://api.factory.ai\";\n const localUrl = \"http://127.0.0.1:23119\";\n\n // Need to pad to same length\n if (localUrl.length > originalUrl.length) {\n console.error(`[websearch] Local URL too long: ${localUrl.length} > ${originalUrl.length}`);\n return null;\n }\n\n const paddedUrl = localUrl.padEnd(originalUrl.length, \" \");\n\n return {\n name: \"webSearch\",\n description: `Replace API URL with local proxy (${localUrl})`,\n pattern: Buffer.from(originalUrl),\n replacement: Buffer.from(paddedUrl),\n };\n}\n\n/**\n * Create WebSearch proxy files\n */\nexport async function createWebSearchProxyFiles(\n outputDir: string,\n droidPath: string,\n aliasName: string,\n): Promise<{ proxyScript: string; wrapperScript: string }> {\n // Ensure directory exists\n if (!existsSync(outputDir)) {\n await mkdir(outputDir, { recursive: true });\n }\n\n const proxyScriptPath = join(outputDir, `${aliasName}-search-proxy.js`);\n const wrapperScriptPath = join(outputDir, `${aliasName}-with-search`);\n\n // Write proxy server script\n await writeFile(proxyScriptPath, generateSearchProxyServerCode());\n console.log(`[*] Created search proxy: ${proxyScriptPath}`);\n\n // Write wrapper script\n await writeFile(wrapperScriptPath, generateWrapperScript(droidPath, proxyScriptPath));\n await chmod(wrapperScriptPath, 0o755);\n console.log(`[*] Created wrapper script: ${wrapperScriptPath}`);\n\n return {\n proxyScript: proxyScriptPath,\n wrapperScript: wrapperScriptPath,\n };\n}\n\n/**\n * Get proxy server code (for export)\n */\nexport function getSearchProxyCode(): string {\n return generateSearchProxyServerCode();\n}\n\n/**\n * Generate Bun preload script\n * This script executes before droid main program, starts search proxy\n */\nfunction generatePreloadScript(): string {\n return `// Droid WebSearch Preload Script\n// Auto-generated by droid-patch --websearch-preload\n// Start search proxy before droid main program\n\nconst http = require('http');\nconst https = require('https');\nconst { execSync } = require('child_process');\n\nconst PORT = process.env.DROID_SEARCH_PORT || 23119;\nconst FACTORY_API = 'https://api.factory.ai';\n\n// Google PSE search\nasync function searchGooglePSE(query, num) {\n const apiKey = process.env.GOOGLE_PSE_API_KEY;\n const cx = process.env.GOOGLE_PSE_CX;\n if (!apiKey || !cx) return null;\n \n try {\n const url = \\`https://www.googleapis.com/customsearch/v1?key=\\${apiKey}&cx=\\${cx}&q=\\${encodeURIComponent(query)}&num=\\${Math.min(num, 10)}\\`;\n const res = await fetch(url);\n const data = await res.json();\n if (data.error) return null;\n return (data.items || []).map(item => ({\n title: item.title,\n url: item.link,\n content: item.snippet || ''\n }));\n } catch (e) {\n return null;\n }\n}\n\n// DuckDuckGo search (use curl for reliability)\nfunction searchDuckDuckGo(query, num) {\n try {\n const url = \\`https://api.duckduckgo.com/?q=\\${encodeURIComponent(query)}&format=json&no_html=1&skip_disambig=1\\`;\n const output = execSync(\\`curl -s \"\\${url}\"\\`, { encoding: 'utf8', timeout: 10000 });\n const data = JSON.parse(output);\n const results = [];\n\n if (data.AbstractText && data.AbstractURL) {\n results.push({ title: data.Heading || query, url: data.AbstractURL, content: data.AbstractText });\n }\n\n for (const t of (data.RelatedTopics || [])) {\n if (results.length >= num) break;\n if (t.Text && t.FirstURL) {\n results.push({ title: t.Text.split(' - ')[0], url: t.FirstURL, content: t.Text });\n }\n // Handle subcategories\n if (t.Topics) {\n for (const sub of t.Topics) {\n if (results.length >= num) break;\n if (sub.Text && sub.FirstURL) {\n results.push({ title: sub.Text.split(' - ')[0], url: sub.FirstURL, content: sub.Text });\n }\n }\n }\n }\n return results;\n } catch (e) {\n return [];\n }\n}\n\n// Search function\nasync function search(query, num) {\n // Try Google PSE first\n const googleResults = await searchGooglePSE(query, num);\n if (googleResults && googleResults.length > 0) {\n console.error('[preload-search] Using Google PSE');\n return googleResults;\n }\n\n // Fallback to DuckDuckGo\n console.error('[preload-search] Using DuckDuckGo');\n return searchDuckDuckGo(query, num);\n}\n\n// Check if port is already in use\nfunction isPortInUse(port) {\n try {\n execSync(\\`curl -s http://127.0.0.1:\\${port}/health\\`, { timeout: 1000 });\n return true;\n } catch {\n return false;\n }\n}\n\n// Skip if proxy already running\nif (isPortInUse(PORT)) {\n console.error(\\`[preload] Search proxy already running on port \\${PORT}\\`);\n} else {\n // Start proxy server\n const server = http.createServer(async (req, res) => {\n const url = new URL(req.url, \\`http://\\${req.headers.host}\\`);\n\n // Health check\n if (url.pathname === '/health') {\n res.writeHead(200, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ status: 'ok' }));\n return;\n }\n\n // Search endpoint\n if (url.pathname === '/api/tools/exa/search' && req.method === 'POST') {\n let body = '';\n req.on('data', c => body += c);\n req.on('end', async () => {\n try {\n const { query, numResults } = JSON.parse(body);\n console.error(\\`[preload-search] Query: \"\\${query}\"\\`);\n const results = await search(query, numResults || 10);\n console.error(\\`[preload-search] Found \\${results.length} results\\`);\n res.writeHead(200, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ results }));\n } catch (e) {\n console.error('[preload-search] Error:', e.message);\n res.writeHead(500, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ error: String(e), results: [] }));\n }\n });\n return;\n }\n\n // Proxy other requests to Factory API\n const proxyUrl = new URL(FACTORY_API + url.pathname + url.search);\n const proxyReq = https.request(proxyUrl, {\n method: req.method,\n headers: { ...req.headers, host: proxyUrl.host }\n }, proxyRes => {\n res.writeHead(proxyRes.statusCode, proxyRes.headers);\n proxyRes.pipe(res);\n });\n proxyReq.on('error', (e) => {\n console.error('[preload-proxy] Error:', e.message);\n res.writeHead(502, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ error: 'Proxy failed' }));\n });\n if (req.method !== 'GET' && req.method !== 'HEAD') {\n req.pipe(proxyReq);\n } else {\n proxyReq.end();\n }\n });\n\n server.listen(PORT, '127.0.0.1', () => {\n console.error(\\`[preload] Search proxy started on http://127.0.0.1:\\${PORT}\\`);\n });\n}\n`;\n}\n\n/**\n * Generate bunfig.toml content\n */\nfunction generateBunfigToml(preloadScriptPath: string): string {\n return `# Droid WebSearch Configuration\n# Auto-generated by droid-patch --websearch-preload\n\npreload = [\"${preloadScriptPath}\"]\n`;\n}\n\n/**\n * Generate preload wrapper script\n * This script cd's to the bunfig.toml directory, then executes droid\n */\nfunction generatePreloadWrapperScript(droidPath: string, bunfigDir: string): string {\n return `#!/bin/bash\n# Droid with WebSearch (Preload)\n# Auto-generated by droid-patch --preload\n\nBUNFIG_DIR=\"${bunfigDir}\"\nDROID_BIN=\"${droidPath}\"\nORIGINAL_DIR=\"$(pwd)\"\n\n# cd to bunfig.toml directory (Bun reads bunfig.toml from cwd)\ncd \"$BUNFIG_DIR\"\n\n# Execute droid, pass all arguments, set working directory to original\nexec \"$DROID_BIN\" --cwd \"$ORIGINAL_DIR\" \"$@\"\n`;\n}\n\n/**\n * Create WebSearch files using Preload method\n *\n * Advantages:\n * - No need to modify binary\n * - Uses Bun's native preload mechanism\n *\n * Files created:\n * - preload script (search proxy)\n * - bunfig.toml (Bun configuration)\n * - wrapper script (directly executable command)\n */\nexport async function createWebSearchPreloadFiles(\n droidDir: string,\n droidPath: string,\n aliasName: string,\n): Promise<{\n preloadScript: string;\n bunfigPath: string;\n wrapperScript: string;\n}> {\n // Ensure directory exists\n if (!existsSync(droidDir)) {\n await mkdir(droidDir, { recursive: true });\n }\n\n const preloadScriptPath = join(droidDir, `${aliasName}-search-preload.js`);\n const bunfigPath = join(droidDir, \"bunfig.toml\");\n const wrapperScriptPath = join(droidDir, aliasName);\n\n // Write preload script\n await writeFile(preloadScriptPath, generatePreloadScript());\n console.log(`[*] Created preload script: ${preloadScriptPath}`);\n\n // Write bunfig.toml\n await writeFile(bunfigPath, generateBunfigToml(preloadScriptPath));\n console.log(`[*] Created bunfig.toml: ${bunfigPath}`);\n\n // Write wrapper script\n await writeFile(wrapperScriptPath, generatePreloadWrapperScript(droidPath, droidDir));\n await chmod(wrapperScriptPath, 0o755);\n console.log(`[*] Created wrapper: ${wrapperScriptPath}`);\n\n return {\n preloadScript: preloadScriptPath,\n bunfigPath: bunfigPath,\n wrapperScript: wrapperScriptPath,\n };\n}\n\n/**\n * Get preload script code (for export)\n */\nexport function getPreloadScriptCode(): string {\n return generatePreloadScript();\n}\n\n/**\n * Generate unified Fetch Hook Preload script\n * Directly hooks globalThis.fetch, no proxy server needed\n * @internal Reserved for future use - alternative to proxy server approach\n */\nfunction _generateFetchHookPreload(): string {\n return `// Droid WebSearch Fetch Hook\n// Auto-generated by droid-patch --websearch\n// Hook globalThis.fetch to intercept search requests\n\nconst DEBUG = process.env.DROID_SEARCH_DEBUG === '1';\n\nfunction log(...args) {\n if (DEBUG) console.error('[websearch]', ...args);\n}\n\n// === Search Implementation ===\n\nasync function searchGooglePSE(query, numResults) {\n const apiKey = process.env.GOOGLE_PSE_API_KEY;\n const cx = process.env.GOOGLE_PSE_CX;\n if (!apiKey || !cx) return null;\n\n try {\n const url = \\`https://www.googleapis.com/customsearch/v1?key=\\${apiKey}&cx=\\${cx}&q=\\${encodeURIComponent(query)}&num=\\${Math.min(numResults, 10)}\\`;\n const res = await fetch(url);\n const data = await res.json();\n if (data.error) {\n log('Google PSE error:', data.error.message);\n return null;\n }\n return (data.items || []).map(item => ({\n title: item.title,\n url: item.link,\n content: item.snippet || '',\n publishedDate: null,\n author: null,\n score: null\n }));\n } catch (e) {\n log('Google PSE failed:', e.message);\n return null;\n }\n}\n\nasync function searchDuckDuckGo(query, numResults) {\n const { execSync } = require('child_process');\n\n // Method 1: Try DuckDuckGo HTML lite\n try {\n const curlCmd = \\`curl -s -X POST \"https://lite.duckduckgo.com/lite/\" -H \"Content-Type: application/x-www-form-urlencoded\" -H \"User-Agent: Mozilla/5.0\" -d \"q=\\${encodeURIComponent(query)}\"\\`;\n const html = execSync(curlCmd, { encoding: 'utf-8', timeout: 15000 });\n\n if (html && html.length > 1000) {\n const results = parseDDGLiteHTML(html, numResults);\n if (results.length > 0) {\n log('DDG lite:', results.length, 'results');\n return results;\n }\n }\n } catch (e) {\n log('DDG lite failed:', e.message);\n }\n\n // Method 2: Fallback to Instant Answer API\n try {\n const apiUrl = \\`https://api.duckduckgo.com/?q=\\${encodeURIComponent(query)}&format=json&no_html=1&skip_disambig=1\\`;\n const curlCmd = \\`curl -s \"\\${apiUrl}\" -H \"User-Agent: Mozilla/5.0\"\\`;\n const jsonStr = execSync(curlCmd, { encoding: 'utf-8', timeout: 15000 });\n const data = JSON.parse(jsonStr);\n\n const results = [];\n\n if (data.Abstract && data.AbstractURL) {\n results.push({\n title: data.Heading || query,\n url: data.AbstractURL,\n content: data.Abstract,\n publishedDate: null,\n author: null,\n score: null\n });\n }\n\n for (const topic of (data.RelatedTopics || [])) {\n if (results.length >= numResults) break;\n if (topic.Text && topic.FirstURL) {\n results.push({\n title: topic.Text.substring(0, 100),\n url: topic.FirstURL,\n content: topic.Text,\n publishedDate: null,\n author: null,\n score: null\n });\n }\n if (topic.Topics) {\n for (const st of topic.Topics) {\n if (results.length >= numResults) break;\n if (st.Text && st.FirstURL) {\n results.push({\n title: st.Text.substring(0, 100),\n url: st.FirstURL,\n content: st.Text,\n publishedDate: null,\n author: null,\n score: null\n });\n }\n }\n }\n }\n\n if (results.length > 0) {\n log('DDG API:', results.length, 'results');\n return results;\n }\n } catch (e) {\n log('DDG API failed:', e.message);\n }\n\n return [];\n}\n\nfunction parseDDGLiteHTML(html, maxResults) {\n const results = [];\n const linkRegex = /<a[^>]+rel=\"nofollow\"[^>]+href=\"([^\"]+)\"[^>]*>([^<]+)<\\\\/a>/gi;\n const snippetRegex = /<td[^>]*class=\"result-snippet\"[^>]*>([^<]*)<\\\\/td>/gi;\n\n const links = [];\n let match;\n\n while ((match = linkRegex.exec(html)) !== null && links.length < maxResults) {\n let url = match[1];\n if (url.includes('duckduckgo.com') && !url.includes('uddg=')) continue;\n if (url.includes('uddg=')) {\n const uddgMatch = url.match(/uddg=([^&]+)/);\n if (uddgMatch) url = decodeURIComponent(uddgMatch[1]);\n }\n links.push({\n url: url,\n title: decodeHTMLEntities(match[2].trim())\n });\n }\n\n const snippets = [];\n while ((match = snippetRegex.exec(html)) !== null && snippets.length < maxResults) {\n snippets.push(decodeHTMLEntities(match[1].trim()));\n }\n\n for (let i = 0; i < links.length && results.length < maxResults; i++) {\n results.push({\n title: links[i].title,\n url: links[i].url,\n content: snippets[i] || '',\n publishedDate: null,\n author: null,\n score: null\n });\n }\n\n return results;\n}\n\nfunction decodeHTMLEntities(str) {\n return str\n .replace(/&amp;/g, '&')\n .replace(/&lt;/g, '<')\n .replace(/&gt;/g, '>')\n .replace(/&quot;/g, '\"')\n .replace(/&#39;/g, \"'\")\n .replace(/&nbsp;/g, ' ');\n}\n\nasync function search(query, numResults = 10) {\n // Try Google PSE first\n const googleResults = await searchGooglePSE(query, numResults);\n if (googleResults && googleResults.length > 0) {\n log('Using Google PSE');\n return { results: googleResults, source: 'google-pse' };\n }\n\n // Fallback to DuckDuckGo\n log('Using DuckDuckGo');\n const ddgResults = await searchDuckDuckGo(query, numResults);\n return { results: ddgResults, source: 'duckduckgo' };\n}\n\n// === Fetch Hook ===\n\nconst originalFetch = globalThis.fetch;\n\nglobalThis.fetch = async function(input, init) {\n const url = typeof input === 'string' ? input : (input instanceof URL ? input.href : input.url);\n\n // Intercept search requests\n if (url && url.includes('/api/tools/exa/search')) {\n log('Intercepted search request');\n\n try {\n let body = init?.body;\n if (body && typeof body !== 'string') {\n body = await new Response(body).text();\n }\n\n const { query, numResults } = JSON.parse(body || '{}');\n log('Query:', query);\n\n const { results, source } = await search(query, numResults || 10);\n log('Results:', results.length, 'from', source);\n\n return new Response(JSON.stringify({ results }), {\n status: 200,\n headers: { 'Content-Type': 'application/json' }\n });\n } catch (e) {\n log('Search error:', e.message);\n return new Response(JSON.stringify({ error: String(e), results: [] }), {\n status: 500,\n headers: { 'Content-Type': 'application/json' }\n });\n }\n }\n\n // Pass through all other requests\n return originalFetch.apply(this, arguments);\n};\n\n// Also hook Bun.fetch if available\nif (typeof Bun !== 'undefined' && Bun.fetch) {\n const originalBunFetch = Bun.fetch;\n Bun.fetch = globalThis.fetch;\n}\n\nlog('Fetch hook installed');\n`;\n}\n\n/**\n * Generate search proxy server code (runs in background)\n * Since BUN_CONFIG_PRELOAD doesn't work with compiled binaries,\n * use a local proxy server to intercept search requests instead\n *\n * Each droid instance runs its own proxy server.\n * The proxy is killed automatically when droid exits.\n * @param factoryApiUrl - Custom Factory API URL (default: https://api.factory.ai)\n */\nfunction generateSearchProxyServer(factoryApiUrl: string = \"https://api.factory.ai\"): string {\n return `#!/usr/bin/env node\n// Droid WebSearch Proxy Server\n// Auto-generated by droid-patch --websearch\n// This proxy runs as a child process of droid and is killed when droid exits\n\nconst http = require('http');\nconst https = require('https');\nconst { execSync } = require('child_process');\nconst fs = require('fs');\n\nconst DEBUG = process.env.DROID_SEARCH_DEBUG === '1';\nconst PORT = parseInt(process.env.SEARCH_PROXY_PORT || '0'); // 0 = auto-assign\nconst FACTORY_API = '${factoryApiUrl}';\n\nfunction log(...args) {\n if (DEBUG) console.error('[websearch]', ...args);\n}\n\n// === Search Implementation ===\n\n// Smithery Exa MCP - highest priority, requires SMITHERY_API_KEY and SMITHERY_PROFILE\nasync function searchSmitheryExa(query, numResults) {\n const apiKey = process.env.SMITHERY_API_KEY;\n const profile = process.env.SMITHERY_PROFILE;\n if (!apiKey || !profile) return null;\n\n try {\n // Construct URL with authentication\n const serverUrl = \\`https://server.smithery.ai/exa/mcp?api_key=\\${encodeURIComponent(apiKey)}&profile=\\${encodeURIComponent(profile)}\\`;\n log('Smithery Exa request');\n\n // Use MCP protocol to call the search tool via HTTP POST\n const requestBody = JSON.stringify({\n jsonrpc: '2.0',\n id: 1,\n method: 'tools/call',\n params: {\n name: 'web_search_exa',\n arguments: {\n query: query,\n numResults: numResults\n }\n }\n });\n\n const curlCmd = \\`curl -s -X POST \"\\${serverUrl}\" -H \"Content-Type: application/json\" -d '\\${requestBody.replace(/'/g, \"'\\\\\\\\\\\\\\\\''\")}'\\`;\n const jsonStr = execSync(curlCmd, { encoding: 'utf-8', timeout: 30000 });\n const response = JSON.parse(jsonStr);\n\n // Parse MCP response\n if (response.result && response.result.content) {\n // MCP returns content as array of text blocks\n const textContent = response.result.content.find(c => c.type === 'text');\n if (textContent && textContent.text) {\n try {\n const searchResults = JSON.parse(textContent.text);\n if (Array.isArray(searchResults) && searchResults.length > 0) {\n return searchResults.slice(0, numResults).map(item => ({\n title: item.title || '',\n url: item.url || '',\n content: item.text || item.snippet || item.highlights?.join(' ') || '',\n publishedDate: item.publishedDate || null,\n author: item.author || null,\n score: item.score || null\n }));\n }\n } catch (parseErr) {\n log('Smithery response parsing failed');\n }\n }\n }\n\n if (response.error) {\n log('Smithery Exa error:', response.error.message || response.error);\n return null;\n }\n } catch (e) {\n log('Smithery Exa failed:', e.message);\n return null;\n }\n return null;\n}\n\nasync function searchGooglePSE(query, numResults) {\n const apiKey = process.env.GOOGLE_PSE_API_KEY;\n const cx = process.env.GOOGLE_PSE_CX;\n if (!apiKey || !cx) return null;\n\n try {\n const url = \\`https://www.googleapis.com/customsearch/v1?key=\\${apiKey}&cx=\\${cx}&q=\\${encodeURIComponent(query)}&num=\\${Math.min(numResults, 10)}\\`;\n log('Google PSE request:', url.replace(apiKey, '***'));\n\n const curlCmd = \\`curl -s \"\\${url}\"\\`;\n const jsonStr = execSync(curlCmd, { encoding: 'utf-8', timeout: 15000 });\n const data = JSON.parse(jsonStr);\n\n if (data.error) {\n log('Google PSE error:', data.error.message);\n return null;\n }\n return (data.items || []).map(item => ({\n title: item.title,\n url: item.link,\n content: item.snippet || '',\n publishedDate: null,\n author: null,\n score: null\n }));\n } catch (e) {\n log('Google PSE failed:', e.message);\n return null;\n }\n}\n\n// SearXNG - self-hosted meta search engine\nasync function searchSearXNG(query, numResults) {\n const searxngUrl = process.env.SEARXNG_URL;\n if (!searxngUrl) return null;\n\n try {\n const url = \\`\\${searxngUrl}/search?q=\\${encodeURIComponent(query)}&format=json&engines=google,bing,duckduckgo\\`;\n log('SearXNG request:', url);\n\n const curlCmd = \\`curl -s \"\\${url}\" -H \"Accept: application/json\"\\`;\n const jsonStr = execSync(curlCmd, { encoding: 'utf-8', timeout: 15000 });\n const data = JSON.parse(jsonStr);\n\n if (data.results && data.results.length > 0) {\n return data.results.slice(0, numResults).map(item => ({\n title: item.title,\n url: item.url,\n content: item.content || '',\n publishedDate: null,\n author: null,\n score: null\n }));\n }\n } catch (e) {\n log('SearXNG failed:', e.message);\n }\n return null;\n}\n\n// Serper API - free tier available (2500 queries/month)\nasync function searchSerper(query, numResults) {\n const apiKey = process.env.SERPER_API_KEY;\n if (!apiKey) return null;\n\n try {\n const curlCmd = \\`curl -s \"https://google.serper.dev/search\" -H \"X-API-KEY: \\${apiKey}\" -H \"Content-Type: application/json\" -d '{\"q\":\"\\${query.replace(/\"/g, '\\\\\\\\\"')}\",\"num\":\\${numResults}}'\\`;\n log('Serper request');\n\n const jsonStr = execSync(curlCmd, { encoding: 'utf-8', timeout: 15000 });\n const data = JSON.parse(jsonStr);\n\n if (data.organic && data.organic.length > 0) {\n return data.organic.slice(0, numResults).map(item => ({\n title: item.title,\n url: item.link,\n content: item.snippet || '',\n publishedDate: null,\n author: null,\n score: null\n }));\n }\n } catch (e) {\n log('Serper failed:', e.message);\n }\n return null;\n}\n\n// Brave Search API - free tier available\nasync function searchBrave(query, numResults) {\n const apiKey = process.env.BRAVE_API_KEY;\n if (!apiKey) return null;\n\n try {\n const url = \\`https://api.search.brave.com/res/v1/web/search?q=\\${encodeURIComponent(query)}&count=\\${numResults}\\`;\n const curlCmd = \\`curl -s \"\\${url}\" -H \"Accept: application/json\" -H \"X-Subscription-Token: \\${apiKey}\"\\`;\n log('Brave request');\n\n const jsonStr = execSync(curlCmd, { encoding: 'utf-8', timeout: 15000 });\n const data = JSON.parse(jsonStr);\n\n if (data.web && data.web.results && data.web.results.length > 0) {\n return data.web.results.slice(0, numResults).map(item => ({\n title: item.title,\n url: item.url,\n content: item.description || '',\n publishedDate: null,\n author: null,\n score: null\n }));\n }\n } catch (e) {\n log('Brave failed:', e.message);\n }\n return null;\n}\n\n// DuckDuckGo - limited reliability due to bot detection\nasync function searchDuckDuckGo(query, numResults) {\n // DuckDuckGo Instant Answer API (limited results but more reliable)\n try {\n const apiUrl = \\`https://api.duckduckgo.com/?q=\\${encodeURIComponent(query)}&format=json&no_html=1&skip_disambig=1\\`;\n const curlCmd = \\`curl -s \"\\${apiUrl}\" -H \"User-Agent: Mozilla/5.0\"\\`;\n const jsonStr = execSync(curlCmd, { encoding: 'utf-8', timeout: 15000 });\n const data = JSON.parse(jsonStr);\n\n const results = [];\n\n if (data.Abstract && data.AbstractURL) {\n results.push({\n title: data.Heading || query,\n url: data.AbstractURL,\n content: data.Abstract,\n publishedDate: null,\n author: null,\n score: null\n });\n }\n\n for (const topic of (data.RelatedTopics || [])) {\n if (results.length >= numResults) break;\n if (topic.Text && topic.FirstURL) {\n results.push({\n title: topic.Text.substring(0, 100),\n url: topic.FirstURL,\n content: topic.Text,\n publishedDate: null,\n author: null,\n score: null\n });\n }\n if (topic.Topics) {\n for (const st of topic.Topics) {\n if (results.length >= numResults) break;\n if (st.Text && st.FirstURL) {\n results.push({\n title: st.Text.substring(0, 100),\n url: st.FirstURL,\n content: st.Text,\n publishedDate: null,\n author: null,\n score: null\n });\n }\n }\n }\n }\n\n if (results.length > 0) {\n log('DDG API:', results.length, 'results');\n return results;\n }\n } catch (e) {\n log('DDG API failed:', e.message);\n }\n\n return [];\n}\n\nfunction parseDDGLiteHTML(html, maxResults) {\n const results = [];\n const linkRegex = /<a[^>]+rel=\"nofollow\"[^>]+href=\"([^\"]+)\"[^>]*>([^<]+)<\\\\/a>/gi;\n const snippetRegex = /<td[^>]*class=\"result-snippet\"[^>]*>([^<]*)<\\\\/td>/gi;\n\n const links = [];\n let match;\n\n while ((match = linkRegex.exec(html)) !== null && links.length < maxResults) {\n let url = match[1];\n if (url.includes('duckduckgo.com') && !url.includes('uddg=')) continue;\n if (url.includes('uddg=')) {\n const uddgMatch = url.match(/uddg=([^&]+)/);\n if (uddgMatch) url = decodeURIComponent(uddgMatch[1]);\n }\n links.push({\n url: url,\n title: decodeHTMLEntities(match[2].trim())\n });\n }\n\n const snippets = [];\n while ((match = snippetRegex.exec(html)) !== null && snippets.length < maxResults) {\n snippets.push(decodeHTMLEntities(match[1].trim()));\n }\n\n for (let i = 0; i < links.length && results.length < maxResults; i++) {\n results.push({\n title: links[i].title,\n url: links[i].url,\n content: snippets[i] || '',\n publishedDate: null,\n author: null,\n score: null\n });\n }\n\n return results;\n}\n\nfunction decodeHTMLEntities(str) {\n return str\n .replace(/&amp;/g, '&')\n .replace(/&lt;/g, '<')\n .replace(/&gt;/g, '>')\n .replace(/&quot;/g, '\"')\n .replace(/&#39;/g, \"'\")\n .replace(/&nbsp;/g, ' ');\n}\n\nasync function search(query, numResults = 10) {\n // Priority order:\n // 1. Smithery Exa MCP (best quality if configured)\n // 2. Google PSE (most reliable if configured)\n // 3. Serper (free tier: 2500/month)\n // 4. Brave Search (free tier available)\n // 5. SearXNG (self-hosted)\n // 6. DuckDuckGo (limited due to bot detection)\n\n // 1. Smithery Exa MCP (highest priority)\n const smitheryResults = await searchSmitheryExa(query, numResults);\n if (smitheryResults && smitheryResults.length > 0) {\n log('Using Smithery Exa');\n return { results: smitheryResults, source: 'smithery-exa' };\n }\n\n // 2. Google PSE\n const googleResults = await searchGooglePSE(query, numResults);\n if (googleResults && googleResults.length > 0) {\n log('Using Google PSE');\n return { results: googleResults, source: 'google-pse' };\n }\n\n // 3. Serper\n const serperResults = await searchSerper(query, numResults);\n if (serperResults && serperResults.length > 0) {\n log('Using Serper');\n return { results: serperResults, source: 'serper' };\n }\n\n // 4. Brave Search\n const braveResults = await searchBrave(query, numResults);\n if (braveResults && braveResults.length > 0) {\n log('Using Brave Search');\n return { results: braveResults, source: 'brave' };\n }\n\n // 5. SearXNG\n const searxngResults = await searchSearXNG(query, numResults);\n if (searxngResults && searxngResults.length > 0) {\n log('Using SearXNG');\n return { results: searxngResults, source: 'searxng' };\n }\n\n // 6. DuckDuckGo (last resort, limited results)\n log('Using DuckDuckGo (fallback)');\n const ddgResults = await searchDuckDuckGo(query, numResults);\n return { results: ddgResults, source: 'duckduckgo' };\n}\n\n// === HTTP Proxy Server ===\n\nconst server = http.createServer(async (req, res) => {\n const url = new URL(req.url, \\`http://\\${req.headers.host}\\`);\n\n // Health check\n if (url.pathname === '/health') {\n res.writeHead(200, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ status: 'ok', port: server.address()?.port || PORT }));\n return;\n }\n\n // Search endpoint - intercept\n if (url.pathname === '/api/tools/exa/search' && req.method === 'POST') {\n let body = '';\n req.on('data', c => body += c);\n req.on('end', async () => {\n try {\n const { query, numResults } = JSON.parse(body);\n log('Search query:', query);\n const { results, source } = await search(query, numResults || 10);\n log('Results:', results.length, 'from', source);\n res.writeHead(200, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ results }));\n } catch (e) {\n log('Search error:', e.message);\n res.writeHead(500, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ error: String(e), results: [] }));\n }\n });\n return;\n }\n\n // Proxy all other requests to Factory API\n log('Proxy:', req.method, url.pathname);\n\n const proxyUrl = new URL(FACTORY_API + url.pathname + url.search);\n // Choose http or https based on target protocol\n const proxyModule = proxyUrl.protocol === 'https:' ? https : http;\n const proxyReq = proxyModule.request(proxyUrl, {\n method: req.method,\n headers: { ...req.headers, host: proxyUrl.host }\n }, proxyRes => {\n res.writeHead(proxyRes.statusCode, proxyRes.headers);\n proxyRes.pipe(res);\n });\n\n proxyReq.on('error', e => {\n log('Proxy error:', e.message);\n res.writeHead(502, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ error: 'Proxy failed: ' + e.message }));\n });\n\n if (req.method !== 'GET' && req.method !== 'HEAD') {\n req.pipe(proxyReq);\n } else {\n proxyReq.end();\n }\n});\n\n// If port is 0, system will automatically assign an available port\nserver.listen(PORT, '127.0.0.1', () => {\n const actualPort = server.address().port;\n const hasGoogle = process.env.GOOGLE_PSE_API_KEY && process.env.GOOGLE_PSE_CX;\n\n // Write port file for parent process to read\n const portFile = process.env.SEARCH_PROXY_PORT_FILE;\n if (portFile) {\n fs.writeFileSync(portFile, String(actualPort));\n }\n\n // Output PORT= line for wrapper script to parse\n console.log('PORT=' + actualPort);\n\n const hasSmithery = process.env.SMITHERY_API_KEY && process.env.SMITHERY_PROFILE;\n log('Search proxy started on http://127.0.0.1:' + actualPort);\n log('Smithery Exa:', hasSmithery ? 'configured (priority 1)' : 'not set');\n log('Google PSE:', hasGoogle ? 'configured' : 'not set');\n log('Serper:', process.env.SERPER_API_KEY ? 'configured' : 'not set');\n log('Brave:', process.env.BRAVE_API_KEY ? 'configured' : 'not set');\n log('SearXNG:', process.env.SEARXNG_URL || 'not set');\n});\n\nprocess.on('SIGTERM', () => { server.close(); process.exit(0); });\nprocess.on('SIGINT', () => { server.close(); process.exit(0); });\n`;\n}\n\n/**\n * Generate unified Wrapper script\n * Each droid instance runs its own proxy:\n * - Uses port 0 to let system auto-assign available port\n * - Proxy runs as child process\n * - Proxy is killed when droid exits\n * - Supports multiple droid instances running simultaneously\n */\n/* eslint-disable no-useless-escape */\nfunction generateUnifiedWrapper(droidPath: string, proxyScriptPath: string): string {\n return `#!/bin/bash\n# Droid with WebSearch\n# Auto-generated by droid-patch --websearch\n# Each instance runs its own proxy on a system-assigned port\n\nPROXY_SCRIPT=\"${proxyScriptPath}\"\nDROID_BIN=\"${droidPath}\"\nPROXY_PID=\"\"\nPORT_FILE=\"/tmp/droid-websearch-\\$\\$.port\"\n\n# Cleanup function - kill proxy when droid exits\ncleanup() {\n if [ -n \"\\$PROXY_PID\" ] && kill -0 \"\\$PROXY_PID\" 2>/dev/null; then\n [ -n \"\\$DROID_SEARCH_DEBUG\" ] && echo \"[websearch] Stopping proxy (PID: \\$PROXY_PID)\" >&2\n kill \"\\$PROXY_PID\" 2>/dev/null\n wait \"\\$PROXY_PID\" 2>/dev/null\n fi\n rm -f \"\\$PORT_FILE\"\n}\n\n# Set up trap to cleanup on exit\ntrap cleanup EXIT INT TERM\n\n[ -n \"\\$DROID_SEARCH_DEBUG\" ] && echo \"[websearch] Starting proxy...\" >&2\n\n# Start proxy with port 0 (system will assign available port)\n# Proxy writes actual port to PORT_FILE\nif [ -n \"\\$DROID_SEARCH_DEBUG\" ]; then\n SEARCH_PROXY_PORT=0 SEARCH_PROXY_PORT_FILE=\"\\$PORT_FILE\" node \"\\$PROXY_SCRIPT\" 2>&1 &\nelse\n SEARCH_PROXY_PORT=0 SEARCH_PROXY_PORT_FILE=\"\\$PORT_FILE\" node \"\\$PROXY_SCRIPT\" >/dev/null 2>&1 &\nfi\nPROXY_PID=\\$!\n\n# Wait for proxy to start and get actual port (max 5 seconds)\nfor i in {1..50}; do\n # Check if proxy process is still running\n if ! kill -0 \"\\$PROXY_PID\" 2>/dev/null; then\n [ -n \"\\$DROID_SEARCH_DEBUG\" ] && echo \"[websearch] Proxy process died\" >&2\n break\n fi\n if [ -f \"\\$PORT_FILE\" ]; then\n ACTUAL_PORT=\\$(cat \"\\$PORT_FILE\" 2>/dev/null)\n if [ -n \"\\$ACTUAL_PORT\" ] && curl -s \"http://127.0.0.1:\\$ACTUAL_PORT/health\" > /dev/null 2>&1; then\n [ -n \"\\$DROID_SEARCH_DEBUG\" ] && echo \"[websearch] Proxy ready on port \\$ACTUAL_PORT (PID: \\$PROXY_PID)\" >&2\n break\n fi\n fi\n sleep 0.1\ndone\n\n# Check if proxy started successfully\nif [ ! -f \"\\$PORT_FILE\" ] || [ -z \"\\$(cat \"\\$PORT_FILE\" 2>/dev/null)\" ]; then\n echo \"[websearch] Failed to start proxy, running without websearch\" >&2\n cleanup\n exec \"\\$DROID_BIN\" \"\\$@\"\nfi\n\nACTUAL_PORT=\\$(cat \"\\$PORT_FILE\")\nrm -f \"\\$PORT_FILE\"\n\n# Run droid with proxy\nexport FACTORY_API_BASE_URL_OVERRIDE=\"http://127.0.0.1:\\$ACTUAL_PORT\"\n\"\\$DROID_BIN\" \"\\$@\"\nDROID_EXIT_CODE=\\$?\n\n# Cleanup will be called by trap\nexit \\$DROID_EXIT_CODE\n`;\n}\n/* eslint-enable no-useless-escape */\n\n/**\n * Create unified WebSearch files\n *\n * Approach: Proxy server mode\n * - wrapper script starts local proxy server\n * - proxy server intercepts search requests, passes through other requests\n * - uses FACTORY_API_BASE_URL_OVERRIDE env var to point to proxy\n * - alias works directly, no extra steps needed\n *\n * @param outputDir - Directory to write files to\n * @param droidPath - Path to droid binary\n * @param aliasName - Alias name for the wrapper\n * @param apiBase - Custom API base URL for proxy to forward requests to\n */\nexport async function createWebSearchUnifiedFiles(\n outputDir: string,\n droidPath: string,\n aliasName: string,\n apiBase?: string,\n): Promise<{ wrapperScript: string; preloadScript: string }> {\n if (!existsSync(outputDir)) {\n await mkdir(outputDir, { recursive: true });\n }\n\n const proxyScriptPath = join(outputDir, `${aliasName}-proxy.js`);\n const wrapperScriptPath = join(outputDir, aliasName);\n\n // Write proxy server script with custom API base if provided\n const factoryApiUrl = apiBase || \"https://api.factory.ai\";\n await writeFile(proxyScriptPath, generateSearchProxyServer(factoryApiUrl));\n console.log(`[*] Created proxy script: ${proxyScriptPath}`);\n\n // Write unified wrapper\n await writeFile(wrapperScriptPath, generateUnifiedWrapper(droidPath, proxyScriptPath));\n await chmod(wrapperScriptPath, 0o755);\n console.log(`[*] Created wrapper: ${wrapperScriptPath}`);\n\n return {\n wrapperScript: wrapperScriptPath,\n preloadScript: proxyScriptPath, // Keep interface compatible\n };\n}\n","import bin from \"tiny-bin\";\nimport { styleText } from \"node:util\";\nimport { existsSync, readFileSync } from \"node:fs\";\nimport { join, dirname } from \"node:path\";\nimport { homedir } from \"node:os\";\nimport { fileURLToPath } from \"node:url\";\nimport { execSync } from \"node:child_process\";\nimport { patchDroid, type Patch } from \"./patcher.ts\";\nimport {\n createAlias,\n removeAlias,\n listAliases,\n createAliasForWrapper,\n clearAllAliases,\n removeAliasesByFilter,\n type FilterFlag,\n} from \"./alias.ts\";\nimport { createWebSearchUnifiedFiles } from \"./websearch-patch.ts\";\nimport {\n saveAliasMetadata,\n createMetadata,\n loadAliasMetadata,\n listAllMetadata,\n formatPatches,\n} from \"./metadata.ts\";\n\nconst __dirname = dirname(fileURLToPath(import.meta.url));\n\nfunction getVersion(): string {\n try {\n const pkgPath = join(__dirname, \"..\", \"package.json\");\n const pkg = JSON.parse(readFileSync(pkgPath, \"utf-8\"));\n return pkg.version || \"0.0.0\";\n } catch {\n return \"0.0.0\";\n }\n}\n\nconst version = getVersion();\n\nfunction getDroidVersion(droidPath: string): string | undefined {\n try {\n const result = execSync(`\"${droidPath}\" --version`, {\n encoding: \"utf-8\",\n stdio: [\"pipe\", \"pipe\", \"pipe\"],\n timeout: 5000,\n }).trim();\n // Parse version from output like \"droid 1.2.3\" or just \"1.2.3\"\n const match = result.match(/(\\d+\\.\\d+\\.\\d+)/);\n return match ? match[1] : result || undefined;\n } catch {\n return undefined;\n }\n}\n\nfunction findDefaultDroidPath(): string {\n const home = homedir();\n\n // Try `which droid` first to find droid in PATH\n try {\n const result = execSync(\"which droid\", {\n encoding: \"utf-8\",\n stdio: [\"pipe\", \"pipe\", \"pipe\"],\n }).trim();\n if (result && existsSync(result)) {\n return result;\n }\n } catch {\n // which command failed, continue with fallback paths\n }\n\n // Common installation paths\n const paths = [\n // Default sh install location\n join(home, \".droid\", \"bin\", \"droid\"),\n // Homebrew on Apple Silicon\n \"/opt/homebrew/bin/droid\",\n // Homebrew on Intel Mac / Linux\n \"/usr/local/bin/droid\",\n // Linux system-wide\n \"/usr/bin/droid\",\n // Current directory\n \"./droid\",\n ];\n\n for (const p of paths) {\n if (existsSync(p)) return p;\n }\n\n // Return default path even if not found (will error later with helpful message)\n return join(home, \".droid\", \"bin\", \"droid\");\n}\n\nbin(\"droid-patch\", \"CLI tool to patch droid binary with various modifications\")\n .package(\"droid-patch\", version)\n .option(\n \"--is-custom\",\n \"Patch isCustom:!0 to isCustom:!1 (enable context compression for custom models)\",\n )\n .option(\n \"--skip-login\",\n \"Inject a fake FACTORY_API_KEY to bypass login requirement (no real key needed)\",\n )\n .option(\n \"--api-base <url>\",\n \"Replace Factory API URL with custom URL (binary patch, or forward target with --websearch)\",\n )\n .option(\n \"--websearch\",\n \"Enable local WebSearch proxy (each instance runs own proxy, auto-cleanup on exit)\",\n )\n .option(\n \"--reasoning-effort\",\n \"Enable reasoning effort for custom models (set to high, enable UI selector)\",\n )\n .option(\"--dry-run\", \"Verify patches without actually modifying the binary\")\n .option(\"-p, --path <path>\", \"Path to the droid binary\")\n .option(\"-o, --output <dir>\", \"Output directory for patched binary\")\n .option(\"--no-backup\", \"Do not create backup of original binary\")\n .option(\"-v, --verbose\", \"Enable verbose output\")\n .argument(\"[alias]\", \"Alias name for the patched binary\")\n .action(async (options, args) => {\n const alias = args?.[0] as string | undefined;\n const isCustom = options[\"is-custom\"] as boolean;\n const skipLogin = options[\"skip-login\"] as boolean;\n const apiBase = options[\"api-base\"] as string | undefined;\n const websearch = options[\"websearch\"] as boolean;\n // When --websearch is used with --api-base, forward to custom URL\n // Otherwise forward to official Factory API\n const websearchTarget = websearch ? apiBase || \"https://api.factory.ai\" : undefined;\n const reasoningEffort = options[\"reasoning-effort\"] as boolean;\n const dryRun = options[\"dry-run\"] as boolean;\n const path = (options.path as string) || findDefaultDroidPath();\n const outputDir = options.output as string | undefined;\n const backup = options.backup !== false;\n const verbose = options.verbose as boolean;\n\n // If -o is specified with alias, output to that directory with alias name\n const outputPath = outputDir && alias ? join(outputDir, alias) : undefined;\n\n // Handle --websearch only (no binary patching needed)\n // When --websearch is used alone, create proxy wrapper without modifying binary\n if (websearch && !isCustom && !skipLogin && !reasoningEffort) {\n if (!alias) {\n console.log(styleText(\"red\", \"Error: Alias name required for --websearch\"));\n console.log(styleText(\"gray\", \"Usage: npx droid-patch --websearch <alias>\"));\n process.exit(1);\n }\n\n console.log(styleText(\"cyan\", \"═\".repeat(60)));\n console.log(styleText([\"cyan\", \"bold\"], \" Droid WebSearch Setup\"));\n console.log(styleText(\"cyan\", \"═\".repeat(60)));\n console.log();\n console.log(styleText(\"white\", `Forward target: ${websearchTarget}`));\n console.log();\n\n // Create websearch proxy files (proxy script + wrapper)\n const proxyDir = join(homedir(), \".droid-patch\", \"proxy\");\n const { wrapperScript } = await createWebSearchUnifiedFiles(\n proxyDir,\n path,\n alias,\n websearchTarget,\n );\n\n // Create alias pointing to wrapper\n await createAliasForWrapper(wrapperScript, alias, verbose);\n\n // Save metadata for update command\n const droidVersion = getDroidVersion(path);\n const metadata = createMetadata(\n alias,\n path,\n {\n isCustom: false,\n skipLogin: false,\n apiBase: apiBase || null,\n websearch: true,\n reasoningEffort: false,\n },\n {\n droidPatchVersion: version,\n droidVersion,\n },\n );\n await saveAliasMetadata(metadata);\n\n console.log();\n console.log(styleText(\"green\", \"═\".repeat(60)));\n console.log(styleText([\"green\", \"bold\"], \" WebSearch Ready!\"));\n console.log(styleText(\"green\", \"═\".repeat(60)));\n console.log();\n console.log(\"Run directly:\");\n console.log(styleText(\"yellow\", ` ${alias}`));\n console.log();\n console.log(styleText(\"cyan\", \"Auto-shutdown:\"));\n console.log(\n styleText(\"gray\", \" Proxy auto-shuts down after 5 min idle (no manual cleanup needed)\"),\n );\n console.log(styleText(\"gray\", \" To disable: export DROID_PROXY_IDLE_TIMEOUT=0\"));\n console.log();\n console.log(\"Search providers (in priority order):\");\n console.log(styleText(\"yellow\", \" 1. Smithery Exa (best quality):\"));\n console.log(styleText(\"gray\", \" export SMITHERY_API_KEY=your_api_key\"));\n console.log(styleText(\"gray\", \" export SMITHERY_PROFILE=your_profile\"));\n console.log(styleText(\"gray\", \" 2. Google PSE:\"));\n console.log(styleText(\"gray\", \" export GOOGLE_PSE_API_KEY=your_api_key\"));\n console.log(styleText(\"gray\", \" export GOOGLE_PSE_CX=your_search_engine_id\"));\n console.log(styleText(\"gray\", \" 3-6. Serper, Brave, SearXNG, DuckDuckGo (fallbacks)\"));\n console.log();\n console.log(\"Debug mode:\");\n console.log(styleText(\"gray\", \" export DROID_SEARCH_DEBUG=1\"));\n return;\n }\n\n if (!isCustom && !skipLogin && !apiBase && !websearch && !reasoningEffort) {\n console.log(styleText(\"yellow\", \"No patch flags specified. Available patches:\"));\n console.log(styleText(\"gray\", \" --is-custom Patch isCustom for custom models\"));\n console.log(\n styleText(\"gray\", \" --skip-login Bypass login by injecting a fake API key\"),\n );\n console.log(\n styleText(\"gray\", \" --api-base Replace Factory API URL (binary patch)\"),\n );\n console.log(styleText(\"gray\", \" --websearch Enable local WebSearch proxy\"));\n console.log(\n styleText(\"gray\", \" --reasoning-effort Set reasoning effort level for custom models\"),\n );\n console.log();\n console.log(\"Usage examples:\");\n console.log(styleText(\"cyan\", \" npx droid-patch --is-custom droid-custom\"));\n console.log(styleText(\"cyan\", \" npx droid-patch --skip-login droid-nologin\"));\n console.log(styleText(\"cyan\", \" npx droid-patch --is-custom --skip-login droid-patched\"));\n console.log(styleText(\"cyan\", \" npx droid-patch --websearch droid-search\"));\n console.log(\n styleText(\n \"cyan\",\n \" npx droid-patch --websearch --api-base=http://127.0.0.1:20002 my-droid\",\n ),\n );\n process.exit(1);\n }\n\n if (!alias && !dryRun) {\n console.log(styleText(\"red\", \"Error: alias name is required\"));\n console.log(\n styleText(\n \"gray\",\n \"Usage: droid-patch [--is-custom] [--skip-login] [-o <dir>] <alias-name>\",\n ),\n );\n process.exit(1);\n }\n\n console.log(styleText(\"cyan\", \"═\".repeat(60)));\n console.log(styleText([\"cyan\", \"bold\"], \" Droid Binary Patcher\"));\n console.log(styleText(\"cyan\", \"═\".repeat(60)));\n console.log();\n\n const patches: Patch[] = [];\n if (isCustom) {\n patches.push({\n name: \"isCustom\",\n description: \"Change isCustom:!0 to isCustom:!1\",\n pattern: Buffer.from(\"isCustom:!0\"),\n replacement: Buffer.from(\"isCustom:!1\"),\n });\n }\n\n // Add skip-login patch: replace process.env.FACTORY_API_KEY with a fixed fake key\n // \"process.env.FACTORY_API_KEY\" is 27 chars, we replace with \"fk-droid-patch-skip-00000\" (25 chars + quotes = 27)\n if (skipLogin) {\n patches.push({\n name: \"skipLogin\",\n description: 'Replace process.env.FACTORY_API_KEY with \"fk-droid-patch-skip-00000\"',\n pattern: Buffer.from(\"process.env.FACTORY_API_KEY\"),\n replacement: Buffer.from('\"fk-droid-patch-skip-00000\"'),\n });\n }\n\n // Add api-base patch: replace the Factory API base URL\n // Original: \"https://api.factory.ai\" (22 chars)\n // We need to pad the replacement URL to be exactly 22 chars\n // Note: When --websearch is used, --api-base sets the forward target instead of binary patching\n if (apiBase && !websearch) {\n const originalUrl = \"https://api.factory.ai\";\n const originalLength = originalUrl.length; // 22 chars\n\n // Validate and normalize the URL\n let normalizedUrl = apiBase.replace(/\\/+$/, \"\"); // Remove trailing slashes\n\n if (normalizedUrl.length > originalLength) {\n console.log(\n styleText(\"red\", `Error: API base URL must be ${originalLength} characters or less`),\n );\n console.log(\n styleText(\"gray\", ` Your URL: \"${normalizedUrl}\" (${normalizedUrl.length} chars)`),\n );\n console.log(styleText(\"gray\", ` Maximum: ${originalLength} characters`));\n console.log();\n console.log(styleText(\"yellow\", \"Tip: Use a shorter URL or set up a local redirect.\"));\n console.log(styleText(\"gray\", \" Examples:\"));\n console.log(styleText(\"gray\", \" http://127.0.0.1:3000 (19 chars)\"));\n console.log(styleText(\"gray\", \" http://localhost:80 (19 chars)\"));\n process.exit(1);\n }\n\n // Pad the URL with spaces at the end to match original length\n // Note: trailing spaces in URL are generally ignored\n const paddedUrl = normalizedUrl.padEnd(originalLength, \" \");\n\n patches.push({\n name: \"apiBase\",\n description: `Replace Factory API URL with \"${normalizedUrl}\"`,\n pattern: Buffer.from(originalUrl),\n replacement: Buffer.from(paddedUrl),\n });\n }\n\n // Add reasoning-effort patch: set custom models to use \"high\" reasoning\n // Also modify UI conditions to show reasoning selector for custom models\n if (reasoningEffort) {\n // [\"none\"] is 8 chars, [\"high\"] is 8 chars - perfect match!\n patches.push({\n name: \"reasoningEffortSupported\",\n description: 'Change supportedReasoningEfforts:[\"none\"] to [\"high\"]',\n pattern: Buffer.from('supportedReasoningEfforts:[\"none\"]'),\n replacement: Buffer.from('supportedReasoningEfforts:[\"high\"]'),\n });\n\n // \"none\" is 4 chars, \"high\" is 4 chars - perfect match!\n patches.push({\n name: \"reasoningEffortDefault\",\n description: 'Change defaultReasoningEffort:\"none\" to \"high\"',\n pattern: Buffer.from('defaultReasoningEffort:\"none\"'),\n replacement: Buffer.from('defaultReasoningEffort:\"high\"'),\n });\n\n // Change UI condition from length>1 to length>0\n // This allows custom models with single reasoning option to show the selector\n patches.push({\n name: \"reasoningEffortUIShow\",\n description: \"Change supportedReasoningEfforts.length>1 to length>0\",\n pattern: Buffer.from(\"supportedReasoningEfforts.length>1\"),\n replacement: Buffer.from(\"supportedReasoningEfforts.length>0\"),\n });\n\n // Change UI condition from length<=1 to length<=0\n // This enables the reasoning setting in /settings menu for custom models\n patches.push({\n name: \"reasoningEffortUIEnable\",\n description: \"Change supportedReasoningEfforts.length<=1 to length<=0\",\n pattern: Buffer.from(\"supportedReasoningEfforts.length<=1\"),\n replacement: Buffer.from(\"supportedReasoningEfforts.length<=0\"),\n });\n\n // Bypass reasoning effort validation to allow settings.json override\n // This allows \"xhigh\" in settings.json to work even though default is \"high\"\n // Original: if(R&&!B.supportedReasoningEfforts.includes(R)) throw error\n // Changed: if(0&&...) - never throws, any value is accepted\n patches.push({\n name: \"reasoningEffortValidationBypass\",\n description: \"Bypass reasoning effort validation (allows xhigh in settings.json)\",\n pattern: Buffer.from(\"if(R&&!B.supportedReasoningEfforts.includes(R))\"),\n replacement: Buffer.from(\"if(0&&!B.supportedReasoningEfforts.includes(R))\"),\n });\n }\n\n try {\n const result = await patchDroid({\n inputPath: path,\n outputPath: outputPath,\n patches,\n dryRun,\n backup,\n verbose,\n });\n\n if (dryRun) {\n console.log();\n console.log(styleText(\"blue\", \"═\".repeat(60)));\n console.log(styleText([\"blue\", \"bold\"], \" DRY RUN COMPLETE\"));\n console.log(styleText(\"blue\", \"═\".repeat(60)));\n console.log();\n console.log(styleText(\"gray\", \"To apply the patches, run without --dry-run:\"));\n console.log(styleText(\"cyan\", ` npx droid-patch --is-custom ${alias || \"<alias-name>\"}`));\n process.exit(0);\n }\n\n // If -o is specified, just output the file without creating alias\n if (outputDir && result.success && result.outputPath) {\n console.log();\n console.log(styleText(\"green\", \"═\".repeat(60)));\n console.log(styleText([\"green\", \"bold\"], \" PATCH SUCCESSFUL\"));\n console.log(styleText(\"green\", \"═\".repeat(60)));\n console.log();\n console.log(styleText(\"white\", `Patched binary saved to: ${result.outputPath}`));\n process.exit(0);\n }\n\n if (result.success && result.outputPath && alias) {\n console.log();\n\n // If --websearch is also used, create wrapper and point to it\n if (websearch) {\n const proxyDir = join(homedir(), \".droid-patch\", \"proxy\");\n const { wrapperScript } = await createWebSearchUnifiedFiles(\n proxyDir,\n result.outputPath,\n alias,\n websearchTarget,\n );\n await createAliasForWrapper(wrapperScript, alias, verbose);\n\n console.log();\n console.log(styleText(\"cyan\", \"WebSearch enabled\"));\n console.log(styleText(\"white\", ` Forward target: ${websearchTarget}`));\n } else {\n await createAlias(result.outputPath, alias, verbose);\n }\n\n // Save metadata for update command\n const droidVersion = getDroidVersion(path);\n const metadata = createMetadata(\n alias,\n path,\n {\n isCustom: !!isCustom,\n skipLogin: !!skipLogin,\n apiBase: apiBase || null,\n websearch: !!websearch,\n reasoningEffort: !!reasoningEffort,\n },\n {\n droidPatchVersion: version,\n droidVersion,\n },\n );\n await saveAliasMetadata(metadata);\n }\n\n if (result.success) {\n console.log();\n console.log(styleText(\"green\", \"═\".repeat(60)));\n console.log(styleText([\"green\", \"bold\"], \" PATCH SUCCESSFUL\"));\n console.log(styleText(\"green\", \"═\".repeat(60)));\n }\n\n process.exit(result.success ? 0 : 1);\n } catch (error) {\n console.error(styleText(\"red\", `Error: ${(error as Error).message}`));\n if (verbose) console.error((error as Error).stack);\n process.exit(1);\n }\n })\n .command(\"list\", \"List all droid-patch aliases\")\n .action(async () => {\n await listAliases();\n })\n .command(\"remove\", \"Remove alias(es) by name or filter\")\n .argument(\"[alias-or-path]\", \"Alias name or file path to remove\")\n .option(\"--patch-version <version>\", \"Remove aliases created by this droid-patch version\")\n .option(\"--droid-version <version>\", \"Remove aliases for this droid version\")\n .option(\n \"--flag <flag>\",\n \"Remove aliases with this flag (is-custom, skip-login, websearch, api-base, reasoning-effort)\",\n )\n .action(async (options, args) => {\n const target = args?.[0] as string | undefined;\n const patchVersion = options[\"patch-version\"] as string | undefined;\n const droidVersion = options[\"droid-version\"] as string | undefined;\n const flag = options.flag as FilterFlag | undefined;\n\n // If filter options are provided, use filter mode\n if (patchVersion || droidVersion || flag) {\n await removeAliasesByFilter({\n patchVersion,\n droidVersion,\n flags: flag ? [flag] : undefined,\n });\n return;\n }\n\n // If no target and no filter, show error\n if (!target) {\n console.error(\n styleText(\n \"red\",\n \"Error: Provide an alias name or use filter options (--patch-version, --droid-version, --flag)\",\n ),\n );\n process.exit(1);\n }\n\n // Check if it's a file path (contains / or .)\n if (target.includes(\"/\") || existsSync(target)) {\n // It's a file path, delete directly\n const { unlink } = await import(\"node:fs/promises\");\n try {\n await unlink(target);\n console.log(styleText(\"green\", `[*] Removed: ${target}`));\n } catch (error) {\n console.error(styleText(\"red\", `Error: ${(error as Error).message}`));\n process.exit(1);\n }\n } else {\n // It's an alias name\n await removeAlias(target);\n }\n })\n .command(\"version\", \"Print droid-patch version\")\n .action(() => {\n console.log(`droid-patch v${version}`);\n })\n .command(\"clear\", \"Remove all droid-patch aliases and related files\")\n .action(async () => {\n await clearAllAliases();\n })\n .command(\"update\", \"Update aliases with latest droid binary\")\n .argument(\"[alias]\", \"Specific alias to update (optional, updates all if not specified)\")\n .option(\"--dry-run\", \"Preview without making changes\")\n .option(\"-p, --path <path>\", \"Path to new droid binary\")\n .option(\"-v, --verbose\", \"Enable verbose output\")\n .action(async (options, args) => {\n const aliasName = args?.[0] as string | undefined;\n const dryRun = options[\"dry-run\"] as boolean;\n const newBinaryPath = (options.path as string) || findDefaultDroidPath();\n const verbose = options.verbose as boolean;\n\n console.log(styleText(\"cyan\", \"═\".repeat(60)));\n console.log(styleText([\"cyan\", \"bold\"], \" Droid-Patch Update\"));\n console.log(styleText(\"cyan\", \"═\".repeat(60)));\n console.log();\n\n // Verify the new binary exists\n if (!existsSync(newBinaryPath)) {\n console.log(styleText(\"red\", `Error: Droid binary not found at ${newBinaryPath}`));\n console.log(styleText(\"gray\", \"Use -p to specify a different path\"));\n process.exit(1);\n }\n\n // Get aliases to update\n let metaList: Awaited<ReturnType<typeof loadAliasMetadata>>[];\n if (aliasName) {\n const meta = await loadAliasMetadata(aliasName);\n if (!meta) {\n console.log(styleText(\"red\", `Error: No metadata found for alias \"${aliasName}\"`));\n console.log(\n styleText(\"gray\", \"This alias may have been created before update tracking was added.\"),\n );\n console.log(styleText(\"gray\", \"Remove and recreate the alias to enable update support.\"));\n process.exit(1);\n }\n metaList = [meta];\n } else {\n metaList = await listAllMetadata();\n if (metaList.length === 0) {\n console.log(styleText(\"yellow\", \"No aliases with metadata found.\"));\n console.log(styleText(\"gray\", \"Create aliases with droid-patch to enable update support.\"));\n process.exit(0);\n }\n }\n\n console.log(styleText(\"white\", `Using droid binary: ${newBinaryPath}`));\n console.log(styleText(\"white\", `Found ${metaList.length} alias(es) to update`));\n if (dryRun) {\n console.log(styleText(\"blue\", \"(DRY RUN - no changes will be made)\"));\n }\n console.log();\n\n let successCount = 0;\n let failCount = 0;\n\n for (const meta of metaList) {\n if (!meta) continue;\n\n console.log(styleText(\"cyan\", `─`.repeat(40)));\n console.log(styleText(\"white\", `Updating: ${styleText([\"cyan\", \"bold\"], meta.name)}`));\n console.log(styleText(\"gray\", ` Patches: ${formatPatches(meta.patches)}`));\n\n if (dryRun) {\n console.log(styleText(\"blue\", ` [DRY RUN] Would re-apply patches`));\n successCount++;\n continue;\n }\n\n try {\n // Build patch list based on metadata\n const patches: Patch[] = [];\n\n if (meta.patches.isCustom) {\n patches.push({\n name: \"isCustom\",\n description: \"Change isCustom:!0 to isCustom:!1\",\n pattern: Buffer.from(\"isCustom:!0\"),\n replacement: Buffer.from(\"isCustom:!1\"),\n });\n }\n\n if (meta.patches.skipLogin) {\n patches.push({\n name: \"skipLogin\",\n description: \"Replace process.env.FACTORY_API_KEY with fake key\",\n pattern: Buffer.from(\"process.env.FACTORY_API_KEY\"),\n replacement: Buffer.from('\"fk-droid-patch-skip-00000\"'),\n });\n }\n\n if (meta.patches.apiBase) {\n const originalUrl = \"https://api.factory.ai\";\n const paddedUrl = meta.patches.apiBase.padEnd(originalUrl.length, \" \");\n patches.push({\n name: \"apiBase\",\n description: `Replace Factory API URL with \"${meta.patches.apiBase}\"`,\n pattern: Buffer.from(originalUrl),\n replacement: Buffer.from(paddedUrl),\n });\n }\n\n if (meta.patches.reasoningEffort) {\n patches.push({\n name: \"reasoningEffortSupported\",\n description: 'Change supportedReasoningEfforts:[\"none\"] to [\"high\"]',\n pattern: Buffer.from('supportedReasoningEfforts:[\"none\"]'),\n replacement: Buffer.from('supportedReasoningEfforts:[\"high\"]'),\n });\n patches.push({\n name: \"reasoningEffortDefault\",\n description: 'Change defaultReasoningEffort:\"none\" to \"high\"',\n pattern: Buffer.from('defaultReasoningEffort:\"none\"'),\n replacement: Buffer.from('defaultReasoningEffort:\"high\"'),\n });\n patches.push({\n name: \"reasoningEffortUIShow\",\n description: \"Change supportedReasoningEfforts.length>1 to length>0\",\n pattern: Buffer.from(\"supportedReasoningEfforts.length>1\"),\n replacement: Buffer.from(\"supportedReasoningEfforts.length>0\"),\n });\n patches.push({\n name: \"reasoningEffortUIEnable\",\n description: \"Change supportedReasoningEfforts.length<=1 to length<=0\",\n pattern: Buffer.from(\"supportedReasoningEfforts.length<=1\"),\n replacement: Buffer.from(\"supportedReasoningEfforts.length<=0\"),\n });\n patches.push({\n name: \"reasoningEffortValidationBypass\",\n description: \"Bypass reasoning effort validation (allows xhigh in settings.json)\",\n pattern: Buffer.from(\"if(R&&!B.supportedReasoningEfforts.includes(R))\"),\n replacement: Buffer.from(\"if(0&&!B.supportedReasoningEfforts.includes(R))\"),\n });\n }\n\n // Determine output path based on whether this is a websearch alias\n const binsDir = join(homedir(), \".droid-patch\", \"bins\");\n const outputPath = join(binsDir, `${meta.name}-patched`);\n\n // Apply patches (only if there are binary patches to apply)\n if (patches.length > 0) {\n const result = await patchDroid({\n inputPath: newBinaryPath,\n outputPath,\n patches,\n dryRun: false,\n backup: false,\n verbose,\n });\n\n if (!result.success) {\n console.log(styleText(\"red\", ` ✗ Failed to apply patches`));\n failCount++;\n continue;\n }\n\n // Re-sign on macOS\n if (process.platform === \"darwin\") {\n try {\n const { execSync } = await import(\"node:child_process\");\n execSync(`codesign --force --deep --sign - \"${outputPath}\"`, {\n stdio: \"pipe\",\n });\n if (verbose) {\n console.log(styleText(\"gray\", ` Re-signed binary`));\n }\n } catch {\n console.log(styleText(\"yellow\", ` [!] Could not re-sign binary`));\n }\n }\n }\n\n // If websearch is enabled, regenerate wrapper files\n // Support both new 'websearch' field and old 'proxy' field for backward compatibility\n const hasWebsearch = meta.patches.websearch || !!meta.patches.proxy;\n if (hasWebsearch) {\n // Determine forward target: apiBase > proxy (legacy) > default\n const forwardTarget =\n meta.patches.apiBase || meta.patches.proxy || \"https://api.factory.ai\";\n const proxyDir = join(homedir(), \".droid-patch\", \"proxy\");\n const targetBinaryPath = patches.length > 0 ? outputPath : newBinaryPath;\n await createWebSearchUnifiedFiles(proxyDir, targetBinaryPath, meta.name, forwardTarget);\n if (verbose) {\n console.log(styleText(\"gray\", ` Regenerated websearch wrapper`));\n }\n // Migrate old proxy field to new websearch field\n if (meta.patches.proxy && !meta.patches.websearch) {\n meta.patches.websearch = true;\n meta.patches.apiBase = meta.patches.proxy;\n delete meta.patches.proxy;\n }\n }\n\n // Update metadata\n meta.updatedAt = new Date().toISOString();\n meta.originalBinaryPath = newBinaryPath;\n await saveAliasMetadata(meta);\n\n console.log(styleText(\"green\", ` ✓ Updated successfully`));\n successCount++;\n } catch (error) {\n console.log(styleText(\"red\", ` ✗ Error: ${(error as Error).message}`));\n if (verbose) {\n console.error((error as Error).stack);\n }\n failCount++;\n }\n }\n\n console.log();\n console.log(styleText(\"cyan\", \"═\".repeat(60)));\n if (dryRun) {\n console.log(styleText([\"blue\", \"bold\"], \" DRY RUN COMPLETE\"));\n console.log(styleText(\"gray\", ` Would update ${successCount} alias(es)`));\n } else if (failCount === 0) {\n console.log(styleText([\"green\", \"bold\"], \" UPDATE COMPLETE\"));\n console.log(styleText(\"gray\", ` Updated ${successCount} alias(es)`));\n } else {\n console.log(styleText([\"yellow\", \"bold\"], \" UPDATE FINISHED WITH ERRORS\"));\n console.log(styleText(\"gray\", ` Success: ${successCount}, Failed: ${failCount}`));\n }\n console.log(styleText(\"cyan\", \"═\".repeat(60)));\n })\n .run()\n .catch((err: Error) => {\n console.error(err);\n process.exit(1);\n });\n"],"mappings":"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"}
@@ -28,7 +28,8 @@ interface PatchDroidResult {
28
28
  noPatchNeeded?: boolean;
29
29
  patchedCount?: number;
30
30
  }
31
- declare function patchDroid(options: PatchOptions): Promise<PatchDroidResult>; //#endregion
31
+ declare function patchDroid(options: PatchOptions): Promise<PatchDroidResult>;
32
+ //#endregion
32
33
  //#region src/alias.d.ts
33
34
  interface CreateAliasResult {
34
35
  aliasPath: string;
@@ -43,16 +44,7 @@ interface ReplaceOriginalResult {
43
44
  backupPath: string;
44
45
  }
45
46
  declare function replaceOriginal(patchedBinaryPath: string, originalPath: string, verbose?: boolean): Promise<ReplaceOriginalResult>;
46
- /**
47
- * Create alias for wrapper script
48
- * Unlike createAlias, this function creates symlink pointing to wrapper script
49
- * Used for features like websearch that require preprocessing
50
- */
51
-
52
47
  declare function restoreOriginal(originalPath: string): Promise<void>;
53
-
54
48
  //#endregion
55
- //# sourceMappingURL=alias.d.ts.map
56
-
57
- export { CreateAliasResult, Patch, PatchDroidResult, PatchOptions, ReplaceOriginalResult, createAlias, listAliases, patchDroid, removeAlias, replaceOriginal, restoreOriginal };
58
- //# sourceMappingURL=index.d.ts.map
49
+ export { type CreateAliasResult, type Patch, type PatchDroidResult, type PatchOptions, type ReplaceOriginalResult, createAlias, listAliases, patchDroid, removeAlias, replaceOriginal, restoreOriginal };
50
+ //# sourceMappingURL=index.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/patcher.ts","../src/alias.ts"],"sourcesContent":[],"mappings":";UAKiB,KAAA;EAAA,IAAA,EAAA,MAAK;EAOL,WAAA,EAAA,MAAY;EASnB,OAAA,EAbC,MAaU;EAQJ,WAAA,EApBF,MAoBkB;AASjC;AAA0C,UA1BzB,YAAA,CA0ByB;EAAuB,SAAA,EAAA,MAAA;EAAR,UAAA,CAAA,EAAA,MAAA;EAAO,OAAA,EAvBrD,KAuBqD,EAAA;;;;ACsGhE;AAMA,UD7HU,WAAA,CC6HuB;EA6KX,IAAA,EAAA,MAAA;EAqGA,KAAA,EAAA,MAAA;EAgIL,SAAA,CAAA,EAAA,MAAA,EAAA;EAKK,OAAA,EAAA,OAAA;EA8LA,cAAA,CAAA,EAAA,OAAe;;UD1sBpB,gBAAA;;;WAGN;;;;;iBAMW,UAAA,UAAoB,eAAe,QAAQ;;;AAjChD,UCuIA,iBAAA,CDnIF;EAGE,SAAA,EAAA,MAAY;EASnB,UAAA,EAAA,MAAW;EAQJ,SAAA,CAAA,EAAA,OAAgB;AASjC;AAA0C,iBC4GpB,WAAA,CD5GoB,iBAAA,EAAA,MAAA,EAAA,SAAA,EAAA,MAAA,EAAA,OAAA,CAAA,EAAA,OAAA,CAAA,ECgHvC,ODhHuC,CCgH/B,iBDhH+B,CAAA;AAAuB,iBCyR3C,WAAA,CDzR2C,SAAA,EAAA,MAAA,CAAA,ECyRX,ODzRW,CAAA,IAAA,CAAA;AAAR,iBC8XnC,WAAA,CAAA,CD9XmC,EC8XpB,OD9XoB,CAAA,IAAA,CAAA;AAAO,UC8f/C,qBAAA,CD9f+C;;;;ACsG/C,iBA6ZK,eAAA,CA7ZY,iBAAA,EAAA,MAAA,EAAA,YAAA,EAAA,MAAA,EAAA,OAAA,CAAA,EAAA,OAAA,CAAA,EAia/B,OAja+B,CAiavB,qBAjauB,CAAA;iBA2lBZ,eAAA,wBAAuC"}
package/dist/index.mjs ADDED
@@ -0,0 +1,3 @@
1
+ import { a as removeAlias, c as restoreOriginal, i as listAliases, m as patchDroid, n as createAlias, s as replaceOriginal } from "./alias-DVggcM0G.mjs";
2
+
3
+ export { createAlias, listAliases, patchDroid, removeAlias, replaceOriginal, restoreOriginal };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "droid-patch",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "CLI tool to patch droid binary with various modifications",
5
5
  "homepage": "https://github.com/kingsword09/droid-patch#readme",
6
6
  "bugs": {
@@ -11,10 +11,10 @@
11
11
  "url": "git+https://github.com/kingsword09/droid-patch.git"
12
12
  },
13
13
  "type": "module",
14
- "main": "dist/index.js",
15
- "types": "dist/index.d.ts",
14
+ "main": "dist/index.mjs",
15
+ "types": "dist/index.d.mts",
16
16
  "bin": {
17
- "droid-patch": "./dist/cli.js"
17
+ "droid-patch": "./dist/cli.mjs"
18
18
  },
19
19
  "files": [
20
20
  "dist"
@@ -42,11 +42,11 @@
42
42
  "tiny-bin": "^1.11.3"
43
43
  },
44
44
  "devDependencies": {
45
- "@types/node": "^22.10.2",
46
- "oxfmt": "^0.13.0",
45
+ "@types/node": "^22.19.3",
46
+ "oxfmt": "^0.17.0",
47
47
  "oxlint": "^1.32.0",
48
- "oxlint-tsgolint": "^0.8.5",
49
- "tsdown": "^0.9.3",
48
+ "oxlint-tsgolint": "^0.9.0",
49
+ "tsdown": "^0.17.4",
50
50
  "typescript": "^5.7.2"
51
51
  },
52
52
  "engines": {
@@ -1 +0,0 @@
1
- {"version":3,"file":"alias-UwlvAO5o.js","names":["options: PatchOptions","results: PatchResult[]","buffer: Buffer","pattern: Buffer","positions: number[]","position: number","patternLength: number","contextSize: number","aliasName: string","meta: AliasMetadata","metaList: AliasMetadata[]","name: string","originalBinaryPath: string","patches: AliasMetadata[\"patches\"]","applied: string[]","shellConfigPath: string","exportLine: string","patchedBinaryPath: string","aliasName: string","aliases: AliasInfo[]","originalPath: string","wrapperPath: string"],"sources":["../src/patcher.ts","../src/metadata.ts","../src/alias.ts"],"sourcesContent":["import { readFile, writeFile, copyFile, chmod, stat } from \"node:fs/promises\";\nimport { existsSync } from \"node:fs\";\nimport { execSync } from \"node:child_process\";\nimport { styleText } from \"node:util\";\n\nexport interface Patch {\n name: string;\n description: string;\n pattern: Buffer;\n replacement: Buffer;\n}\n\nexport interface PatchOptions {\n inputPath: string;\n outputPath?: string;\n patches: Patch[];\n dryRun?: boolean;\n backup?: boolean;\n verbose?: boolean;\n}\n\ninterface PatchResult {\n name: string;\n found: number;\n positions?: number[];\n success: boolean;\n alreadyPatched?: boolean;\n}\n\nexport interface PatchDroidResult {\n success: boolean;\n dryRun?: boolean;\n results: PatchResult[];\n outputPath?: string;\n noPatchNeeded?: boolean;\n patchedCount?: number;\n}\n\nexport async function patchDroid(\n options: PatchOptions,\n): Promise<PatchDroidResult> {\n const {\n inputPath,\n outputPath,\n patches,\n dryRun = false,\n backup = true,\n verbose = false,\n } = options;\n\n const finalOutputPath = outputPath || `${inputPath}.patched`;\n\n if (!existsSync(inputPath)) {\n throw new Error(`Binary not found: ${inputPath}`);\n }\n\n const stats = await stat(inputPath);\n const fileSizeMB = (stats.size / (1024 * 1024)).toFixed(2);\n\n console.log(\n styleText(\"white\", `[*] Reading binary: ${styleText(\"cyan\", inputPath)}`),\n );\n console.log(\n styleText(\"white\", `[*] File size: ${styleText(\"cyan\", fileSizeMB)} MB`),\n );\n console.log();\n\n const data = await readFile(inputPath);\n const buffer = Buffer.from(data);\n\n const results: PatchResult[] = [];\n\n for (const patch of patches) {\n console.log(\n styleText(\n \"white\",\n `[*] Checking patch: ${styleText(\"yellow\", patch.name)}`,\n ),\n );\n console.log(styleText(\"gray\", ` ${patch.description}`));\n\n const positions = findAllPositions(buffer, patch.pattern);\n\n if (positions.length === 0) {\n console.log(\n styleText(\"yellow\", ` ! Pattern not found - may already be patched`),\n );\n results.push({\n name: patch.name,\n found: 0,\n success: false,\n alreadyPatched: buffer.includes(patch.replacement),\n });\n\n const replacementPositions = findAllPositions(buffer, patch.replacement);\n if (replacementPositions.length > 0) {\n console.log(\n styleText(\n \"blue\",\n ` ✓ Found ${replacementPositions.length} occurrences of patched pattern`,\n ),\n );\n console.log(\n styleText(\"blue\", ` ✓ Binary appears to be already patched`),\n );\n results[results.length - 1].alreadyPatched = true;\n results[results.length - 1].success = true;\n }\n continue;\n }\n\n console.log(\n styleText(\"green\", ` ✓ Found ${positions.length} occurrences`),\n );\n\n if (verbose) {\n for (const pos of positions.slice(0, 5)) {\n const context = getContext(buffer, pos, patch.pattern.length, 25);\n console.log(\n styleText(\n \"gray\",\n ` @ 0x${pos.toString(16).padStart(8, \"0\")}: ...${context}...`,\n ),\n );\n }\n if (positions.length > 5) {\n console.log(\n styleText(\"gray\", ` ... and ${positions.length - 5} more`),\n );\n }\n }\n\n results.push({\n name: patch.name,\n found: positions.length,\n positions,\n success: true,\n });\n }\n\n console.log();\n\n if (dryRun) {\n console.log(styleText(\"blue\", \"─\".repeat(60)));\n console.log(styleText([\"blue\", \"bold\"], \" DRY RUN RESULTS\"));\n console.log(styleText(\"blue\", \"─\".repeat(60)));\n console.log();\n\n for (const result of results) {\n if (result.alreadyPatched) {\n console.log(styleText(\"blue\", ` [✓] ${result.name}: Already patched`));\n } else if (result.found > 0) {\n console.log(\n styleText(\n \"green\",\n ` [✓] ${result.name}: ${result.found} occurrences will be patched`,\n ),\n );\n } else {\n console.log(\n styleText(\"yellow\", ` [!] ${result.name}: Pattern not found`),\n );\n }\n }\n\n return {\n success: results.every((r) => r.success || r.alreadyPatched),\n dryRun: true,\n results,\n };\n }\n\n const patchesNeeded = results.filter((r) => r.found > 0 && !r.alreadyPatched);\n\n if (patchesNeeded.length === 0) {\n const allPatched = results.every((r) => r.alreadyPatched);\n if (allPatched) {\n console.log(\n styleText(\n \"blue\",\n \"[*] All patches already applied. Binary is up to date.\",\n ),\n );\n return {\n success: true,\n outputPath: inputPath,\n results,\n noPatchNeeded: true,\n };\n }\n console.log(styleText(\"yellow\", \"[!] No patches could be applied.\"));\n return { success: false, results };\n }\n\n if (backup) {\n const backupPath = `${inputPath}.backup`;\n if (!existsSync(backupPath)) {\n await copyFile(inputPath, backupPath);\n console.log(\n styleText(\n \"white\",\n `[*] Created backup: ${styleText(\"cyan\", backupPath)}`,\n ),\n );\n } else {\n console.log(\n styleText(\"gray\", `[*] Backup already exists: ${backupPath}`),\n );\n }\n }\n\n console.log(styleText(\"white\", \"[*] Applying patches...\"));\n const patchedBuffer = Buffer.from(buffer);\n\n let totalPatched = 0;\n for (const patch of patches) {\n const result = results.find((r) => r.name === patch.name);\n if (!result || !result.positions) continue;\n\n for (const pos of result.positions) {\n patch.replacement.copy(patchedBuffer, pos);\n totalPatched++;\n }\n }\n\n console.log(styleText(\"green\", `[*] Applied ${totalPatched} patches`));\n\n await writeFile(finalOutputPath, patchedBuffer);\n console.log(\n styleText(\n \"white\",\n `[*] Patched binary saved: ${styleText(\"cyan\", finalOutputPath)}`,\n ),\n );\n\n await chmod(finalOutputPath, 0o755);\n console.log(styleText(\"gray\", \"[*] Set executable permission\"));\n\n console.log();\n console.log(styleText(\"white\", \"[*] Verifying patches...\"));\n const verifyBuffer = await readFile(finalOutputPath);\n\n let allVerified = true;\n for (const patch of patches) {\n const oldCount = findAllPositions(verifyBuffer, patch.pattern).length;\n const newCount = findAllPositions(verifyBuffer, patch.replacement).length;\n\n if (oldCount === 0) {\n console.log(\n styleText(\n \"green\",\n ` ✓ ${patch.name}: Verified (${newCount} patched)`,\n ),\n );\n } else {\n console.log(\n styleText(\n \"red\",\n ` ✗ ${patch.name}: ${oldCount} occurrences not patched`,\n ),\n );\n allVerified = false;\n }\n }\n\n if (allVerified) {\n console.log();\n console.log(styleText(\"green\", \"[+] All patches verified successfully!\"));\n }\n\n if (process.platform === \"darwin\") {\n console.log();\n try {\n console.log(styleText(\"gray\", \"[*] Re-signing binary for macOS...\"));\n execSync(`codesign --force --deep --sign - \"${finalOutputPath}\"`, {\n stdio: \"pipe\",\n });\n console.log(styleText(\"green\", \"[*] Binary re-signed successfully\"));\n } catch {\n console.log(styleText(\"yellow\", \"[!] Could not re-sign binary\"));\n console.log(\n styleText(\n \"gray\",\n ` You may need to run: codesign --force --deep --sign - ${finalOutputPath}`,\n ),\n );\n }\n\n try {\n execSync(`xattr -cr \"${finalOutputPath}\"`, { stdio: \"pipe\" });\n } catch {\n // Ignore\n }\n }\n\n return {\n success: allVerified,\n outputPath: finalOutputPath,\n results,\n patchedCount: totalPatched,\n };\n}\n\nfunction findAllPositions(buffer: Buffer, pattern: Buffer): number[] {\n const positions: number[] = [];\n let pos = 0;\n\n while (true) {\n pos = buffer.indexOf(pattern, pos);\n if (pos === -1) break;\n positions.push(pos);\n pos += pattern.length;\n }\n\n return positions;\n}\n\nfunction getContext(\n buffer: Buffer,\n position: number,\n patternLength: number,\n contextSize: number,\n): string {\n const start = Math.max(0, position - contextSize);\n const end = Math.min(buffer.length, position + patternLength + contextSize);\n const slice = buffer.slice(start, end);\n\n let str = \"\";\n for (let i = 0; i < slice.length; i++) {\n const c = slice[i];\n if (c >= 32 && c < 127) {\n str += String.fromCharCode(c);\n } else {\n str += \".\";\n }\n }\n return str;\n}\n","/**\n * Alias Metadata Management\n *\n * Stores and retrieves metadata about created aliases, including\n * which patches were applied. This enables the `update` command\n * to re-apply the same patches when the original droid binary is updated.\n */\n\nimport { existsSync } from \"node:fs\";\nimport { mkdir, readdir, readFile, unlink, writeFile } from \"node:fs/promises\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\n\n/**\n * Metadata structure for an alias\n */\nexport interface AliasMetadata {\n /** Alias name */\n name: string;\n /** ISO timestamp when created */\n createdAt: string;\n /** ISO timestamp when last updated */\n updatedAt: string;\n /** Path to the original droid binary used for patching */\n originalBinaryPath: string;\n /** Patches that were applied */\n patches: {\n isCustom: boolean;\n skipLogin: boolean;\n apiBase: string | null;\n websearch: boolean;\n reasoningEffort: boolean;\n };\n}\n\n// Directory for storing metadata files\nconst META_DIR = join(homedir(), \".droid-patch\", \"meta\");\n\n/**\n * Ensure metadata directory exists\n */\nasync function ensureMetaDir(): Promise<void> {\n if (!existsSync(META_DIR)) {\n await mkdir(META_DIR, { recursive: true });\n }\n}\n\n/**\n * Get the path to a metadata file for an alias\n */\nfunction getMetaPath(aliasName: string): string {\n return join(META_DIR, `${aliasName}.json`);\n}\n\n/**\n * Save alias metadata to disk\n */\nexport async function saveAliasMetadata(meta: AliasMetadata): Promise<void> {\n await ensureMetaDir();\n const metaPath = getMetaPath(meta.name);\n await writeFile(metaPath, JSON.stringify(meta, null, 2));\n}\n\n/**\n * Load alias metadata from disk\n * Returns null if metadata doesn't exist\n */\nexport async function loadAliasMetadata(\n aliasName: string,\n): Promise<AliasMetadata | null> {\n const metaPath = getMetaPath(aliasName);\n if (!existsSync(metaPath)) {\n return null;\n }\n try {\n const content = await readFile(metaPath, \"utf-8\");\n return JSON.parse(content) as AliasMetadata;\n } catch {\n return null;\n }\n}\n\n/**\n * List all alias metadata\n */\nexport async function listAllMetadata(): Promise<AliasMetadata[]> {\n await ensureMetaDir();\n\n const files = await readdir(META_DIR);\n const metaList: AliasMetadata[] = [];\n\n for (const file of files) {\n if (!file.endsWith(\".json\")) continue;\n\n const aliasName = file.replace(/\\.json$/, \"\");\n const meta = await loadAliasMetadata(aliasName);\n if (meta) {\n metaList.push(meta);\n }\n }\n\n return metaList;\n}\n\n/**\n * Remove alias metadata\n */\nexport async function removeAliasMetadata(aliasName: string): Promise<boolean> {\n const metaPath = getMetaPath(aliasName);\n if (!existsSync(metaPath)) {\n return false;\n }\n try {\n await unlink(metaPath);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Create a new metadata object with current timestamp\n */\nexport function createMetadata(\n name: string,\n originalBinaryPath: string,\n patches: AliasMetadata[\"patches\"],\n): AliasMetadata {\n const now = new Date().toISOString();\n return {\n name,\n createdAt: now,\n updatedAt: now,\n originalBinaryPath,\n patches,\n };\n}\n\n/**\n * Format patches for display\n */\nexport function formatPatches(patches: AliasMetadata[\"patches\"]): string {\n const applied: string[] = [];\n if (patches.isCustom) applied.push(\"isCustom\");\n if (patches.skipLogin) applied.push(\"skipLogin\");\n if (patches.apiBase) applied.push(`apiBase(${patches.apiBase})`);\n if (patches.websearch) applied.push(\"websearch\");\n if (patches.reasoningEffort) applied.push(\"reasoningEffort\");\n return applied.length > 0 ? applied.join(\", \") : \"(none)\";\n}\n","import {\n existsSync,\n mkdirSync,\n readdirSync,\n unlinkSync,\n lstatSync,\n readFileSync,\n appendFileSync,\n writeFileSync,\n} from \"node:fs\";\nimport { symlink, readlink, unlink, copyFile, chmod } from \"node:fs/promises\";\nimport { join, basename, dirname } from \"node:path\";\nimport { homedir } from \"node:os\";\nimport { execSync } from \"node:child_process\";\nimport { styleText } from \"node:util\";\nimport { removeAliasMetadata } from \"./metadata.ts\";\n\nconst DROID_PATCH_DIR = join(homedir(), \".droid-patch\");\nconst ALIASES_DIR = join(DROID_PATCH_DIR, \"aliases\");\nconst BINS_DIR = join(DROID_PATCH_DIR, \"bins\");\n\nconst COMMON_PATH_DIRS = [\n join(homedir(), \".local/bin\"),\n join(homedir(), \"bin\"),\n join(homedir(), \".bin\"),\n \"/opt/homebrew/bin\",\n \"/usr/local/bin\",\n join(homedir(), \".npm-global/bin\"),\n join(homedir(), \".npm/bin\"),\n join(homedir(), \".pnpm-global/bin\"),\n join(homedir(), \".yarn/bin\"),\n join(homedir(), \".config/yarn/global/node_modules/.bin\"),\n join(homedir(), \".cargo/bin\"),\n join(homedir(), \"go/bin\"),\n join(homedir(), \".deno/bin\"),\n join(homedir(), \".bun/bin\"),\n join(homedir(), \".local/share/mise/shims\"),\n join(homedir(), \".asdf/shims\"),\n join(homedir(), \".nvm/current/bin\"),\n join(homedir(), \".volta/bin\"),\n join(homedir(), \".fnm/current/bin\"),\n];\n\nfunction ensureDirectories(): void {\n if (!existsSync(DROID_PATCH_DIR)) {\n mkdirSync(DROID_PATCH_DIR, { recursive: true });\n }\n if (!existsSync(ALIASES_DIR)) {\n mkdirSync(ALIASES_DIR, { recursive: true });\n }\n if (!existsSync(BINS_DIR)) {\n mkdirSync(BINS_DIR, { recursive: true });\n }\n}\n\nfunction checkPathInclusion(): boolean {\n const pathEnv = process.env.PATH || \"\";\n return pathEnv.split(\":\").includes(ALIASES_DIR);\n}\n\nexport function findWritablePathDir(): string | null {\n const pathEnv = process.env.PATH || \"\";\n const pathDirs = pathEnv.split(\":\");\n\n for (const dir of COMMON_PATH_DIRS) {\n if (pathDirs.includes(dir)) {\n try {\n if (!existsSync(dir)) {\n mkdirSync(dir, { recursive: true });\n }\n const testFile = join(dir, `.droid-patch-test-${Date.now()}`);\n writeFileSync(testFile, \"\");\n unlinkSync(testFile);\n return dir;\n } catch {\n continue;\n }\n }\n }\n\n return null;\n}\n\nfunction getShellConfigPath(): string {\n const shell = process.env.SHELL || \"/bin/bash\";\n const shellName = basename(shell);\n\n switch (shellName) {\n case \"zsh\":\n return join(homedir(), \".zshrc\");\n case \"bash\": {\n const bashProfile = join(homedir(), \".bash_profile\");\n if (existsSync(bashProfile)) return bashProfile;\n return join(homedir(), \".bashrc\");\n }\n case \"fish\":\n return join(homedir(), \".config/fish/config.fish\");\n default:\n return join(homedir(), \".profile\");\n }\n}\n\nfunction isPathConfigured(shellConfigPath: string): boolean {\n if (!existsSync(shellConfigPath)) {\n return false;\n }\n\n try {\n const content = readFileSync(shellConfigPath, \"utf-8\");\n return (\n content.includes(\".droid-patch/aliases\") ||\n content.includes(\"droid-patch/aliases\")\n );\n } catch {\n return false;\n }\n}\n\nfunction addPathToShellConfig(\n shellConfigPath: string,\n verbose = false,\n): boolean {\n const shell = process.env.SHELL || \"/bin/bash\";\n const shellName = basename(shell);\n\n let exportLine: string;\n if (shellName === \"fish\") {\n exportLine = `\\n# Added by droid-patch\\nfish_add_path \"${ALIASES_DIR}\"\\n`;\n } else {\n exportLine = `\\n# Added by droid-patch\\nexport PATH=\"${ALIASES_DIR}:$PATH\"\\n`;\n }\n\n try {\n appendFileSync(shellConfigPath, exportLine);\n if (verbose) {\n console.log(\n styleText(\"gray\", ` Added PATH export to: ${shellConfigPath}`),\n );\n }\n return true;\n } catch (error) {\n console.log(\n styleText(\n \"yellow\",\n `[!] Could not write to ${shellConfigPath}: ${(error as Error).message}`,\n ),\n );\n return false;\n }\n}\n\nexport interface CreateAliasResult {\n aliasPath: string;\n binaryPath: string;\n immediate?: boolean;\n}\n\nexport async function createAlias(\n patchedBinaryPath: string,\n aliasName: string,\n verbose = false,\n): Promise<CreateAliasResult> {\n ensureDirectories();\n\n console.log(\n styleText(\"white\", `[*] Creating alias: ${styleText(\"cyan\", aliasName)}`),\n );\n\n const writablePathDir = findWritablePathDir();\n\n if (writablePathDir) {\n const targetPath = join(writablePathDir, aliasName);\n const binaryDest = join(BINS_DIR, `${aliasName}-patched`);\n await copyFile(patchedBinaryPath, binaryDest);\n await chmod(binaryDest, 0o755);\n\n if (verbose) {\n console.log(styleText(\"gray\", ` Stored binary: ${binaryDest}`));\n }\n\n if (existsSync(targetPath)) {\n await unlink(targetPath);\n if (verbose) {\n console.log(styleText(\"gray\", ` Removed existing: ${targetPath}`));\n }\n }\n\n await symlink(binaryDest, targetPath);\n\n if (process.platform === \"darwin\") {\n try {\n console.log(styleText(\"gray\", \"[*] Re-signing binary for macOS...\"));\n execSync(`codesign --force --deep --sign - \"${binaryDest}\"`, {\n stdio: \"pipe\",\n });\n console.log(styleText(\"green\", \"[*] Binary re-signed successfully\"));\n } catch {\n console.log(styleText(\"yellow\", \"[!] Could not re-sign binary\"));\n }\n\n try {\n execSync(`xattr -cr \"${binaryDest}\"`, { stdio: \"pipe\" });\n } catch {\n // Ignore\n }\n }\n\n console.log(\n styleText(\"green\", `[*] Created: ${targetPath} -> ${binaryDest}`),\n );\n console.log();\n console.log(styleText(\"green\", \"─\".repeat(60)));\n console.log(\n styleText([\"green\", \"bold\"], \" ALIAS READY - NO ACTION REQUIRED!\"),\n );\n console.log(styleText(\"green\", \"─\".repeat(60)));\n console.log();\n console.log(\n styleText(\n \"white\",\n `The alias \"${styleText([\"cyan\", \"bold\"], aliasName)}\" is now available in ALL terminals.`,\n ),\n );\n console.log(styleText(\"gray\", `(Installed to: ${writablePathDir})`));\n\n return {\n aliasPath: targetPath,\n binaryPath: binaryDest,\n immediate: true,\n };\n }\n\n console.log(\n styleText(\n \"yellow\",\n \"[*] No writable PATH directory found, using fallback...\",\n ),\n );\n\n const binaryDest = join(BINS_DIR, `${aliasName}-patched`);\n await copyFile(patchedBinaryPath, binaryDest);\n await chmod(binaryDest, 0o755);\n\n if (verbose) {\n console.log(styleText(\"gray\", ` Copied binary to: ${binaryDest}`));\n }\n\n if (process.platform === \"darwin\") {\n try {\n console.log(styleText(\"gray\", \"[*] Re-signing binary for macOS...\"));\n execSync(`codesign --force --deep --sign - \"${binaryDest}\"`, {\n stdio: \"pipe\",\n });\n console.log(styleText(\"green\", \"[*] Binary re-signed successfully\"));\n } catch {\n console.log(\n styleText(\n \"yellow\",\n \"[!] Could not re-sign binary. You may need to do this manually:\",\n ),\n );\n console.log(\n styleText(\n \"gray\",\n ` codesign --force --deep --sign - \"${binaryDest}\"`,\n ),\n );\n }\n\n try {\n execSync(`xattr -cr \"${binaryDest}\"`, { stdio: \"pipe\" });\n } catch {\n // Ignore\n }\n }\n\n const symlinkPath = join(ALIASES_DIR, aliasName);\n\n if (existsSync(symlinkPath)) {\n await unlink(symlinkPath);\n if (verbose) {\n console.log(styleText(\"gray\", ` Removed existing symlink`));\n }\n }\n\n await symlink(binaryDest, symlinkPath);\n await chmod(symlinkPath, 0o755);\n\n console.log(\n styleText(\"green\", `[*] Created symlink: ${symlinkPath} -> ${binaryDest}`),\n );\n\n const shellConfig = getShellConfigPath();\n\n if (!checkPathInclusion()) {\n if (!isPathConfigured(shellConfig)) {\n console.log(\n styleText(\"white\", `[*] Configuring PATH in ${shellConfig}...`),\n );\n\n if (addPathToShellConfig(shellConfig, verbose)) {\n console.log(styleText(\"green\", `[*] PATH configured successfully!`));\n console.log();\n console.log(styleText(\"yellow\", \"─\".repeat(60)));\n console.log(styleText([\"yellow\", \"bold\"], \" ACTION REQUIRED\"));\n console.log(styleText(\"yellow\", \"─\".repeat(60)));\n console.log();\n console.log(\n styleText(\"white\", \"To use the alias in this terminal, run:\"),\n );\n console.log();\n console.log(styleText(\"cyan\", ` source ${shellConfig}`));\n console.log();\n console.log(styleText(\"gray\", \"Or simply open a new terminal window.\"));\n console.log(styleText(\"yellow\", \"─\".repeat(60)));\n } else {\n const exportLine = `export PATH=\"${ALIASES_DIR}:$PATH\"`;\n console.log();\n console.log(styleText(\"yellow\", \"─\".repeat(60)));\n console.log(\n styleText([\"yellow\", \"bold\"], \" Manual PATH Configuration Required\"),\n );\n console.log(styleText(\"yellow\", \"─\".repeat(60)));\n console.log();\n console.log(styleText(\"white\", \"Add this line to your shell config:\"));\n console.log(styleText(\"cyan\", ` ${exportLine}`));\n console.log();\n console.log(styleText(\"gray\", `Shell config file: ${shellConfig}`));\n console.log(styleText(\"yellow\", \"─\".repeat(60)));\n }\n } else {\n console.log(\n styleText(\"green\", `[*] PATH already configured in ${shellConfig}`),\n );\n console.log();\n console.log(\n styleText(\n \"yellow\",\n `Note: Run \\`source ${shellConfig}\\` or open a new terminal to use the alias.`,\n ),\n );\n }\n } else {\n console.log(\n styleText(\"green\", `[*] PATH already includes aliases directory`),\n );\n console.log();\n console.log(\n styleText(\n \"green\",\n `You can now use \"${styleText([\"cyan\", \"bold\"], aliasName)}\" command directly!`,\n ),\n );\n }\n\n return {\n aliasPath: symlinkPath,\n binaryPath: binaryDest,\n };\n}\n\nexport async function removeAlias(aliasName: string): Promise<void> {\n console.log(\n styleText(\"white\", `[*] Removing alias: ${styleText(\"cyan\", aliasName)}`),\n );\n\n let removed = false;\n\n // Check common PATH directories for symlinks\n for (const pathDir of COMMON_PATH_DIRS) {\n const pathSymlink = join(pathDir, aliasName);\n if (existsSync(pathSymlink)) {\n try {\n const stats = lstatSync(pathSymlink);\n if (stats.isSymbolicLink()) {\n const target = await readlink(pathSymlink);\n // Support both regular aliases (.droid-patch/bins) and websearch wrappers (.droid-patch/websearch)\n if (\n target.includes(\".droid-patch/bins\") ||\n target.includes(\".droid-patch/websearch\")\n ) {\n await unlink(pathSymlink);\n console.log(styleText(\"green\", ` Removed: ${pathSymlink}`));\n removed = true;\n }\n }\n } catch {\n // Ignore\n }\n }\n }\n\n // Check aliases directory\n const symlinkPath = join(ALIASES_DIR, aliasName);\n if (existsSync(symlinkPath)) {\n await unlink(symlinkPath);\n console.log(styleText(\"green\", ` Removed: ${symlinkPath}`));\n removed = true;\n }\n\n // Remove binary if exists\n const binaryPath = join(BINS_DIR, `${aliasName}-patched`);\n if (existsSync(binaryPath)) {\n await unlink(binaryPath);\n console.log(styleText(\"green\", ` Removed binary: ${binaryPath}`));\n removed = true;\n }\n\n // Remove websearch wrapper and related files if exist\n const websearchDir = join(DROID_PATCH_DIR, \"websearch\");\n const wrapperPath = join(websearchDir, aliasName);\n const proxyPath = join(websearchDir, `${aliasName}-proxy.js`);\n const preloadPath = join(websearchDir, `${aliasName}-preload.js`);\n\n if (existsSync(wrapperPath)) {\n await unlink(wrapperPath);\n console.log(styleText(\"green\", ` Removed wrapper: ${wrapperPath}`));\n removed = true;\n }\n\n if (existsSync(proxyPath)) {\n await unlink(proxyPath);\n console.log(styleText(\"green\", ` Removed proxy: ${proxyPath}`));\n removed = true;\n }\n\n if (existsSync(preloadPath)) {\n await unlink(preloadPath);\n console.log(styleText(\"green\", ` Removed preload: ${preloadPath}`));\n removed = true;\n }\n\n // Remove metadata\n const metaRemoved = await removeAliasMetadata(aliasName);\n if (metaRemoved) {\n console.log(styleText(\"green\", ` Removed metadata`));\n removed = true;\n }\n\n if (!removed) {\n console.log(styleText(\"yellow\", ` Alias \"${aliasName}\" not found`));\n } else {\n console.log(\n styleText(\"green\", `[*] Alias \"${aliasName}\" removed successfully`),\n );\n }\n}\n\nexport async function listAliases(): Promise<void> {\n ensureDirectories();\n\n console.log(styleText(\"cyan\", \"═\".repeat(60)));\n console.log(styleText([\"cyan\", \"bold\"], \" Droid-Patch Aliases\"));\n console.log(styleText(\"cyan\", \"═\".repeat(60)));\n console.log();\n\n interface AliasInfo {\n name: string;\n target: string;\n location: string;\n immediate: boolean;\n }\n\n const aliases: AliasInfo[] = [];\n\n for (const pathDir of COMMON_PATH_DIRS) {\n if (!existsSync(pathDir)) continue;\n\n try {\n const files = readdirSync(pathDir);\n for (const file of files) {\n const fullPath = join(pathDir, file);\n try {\n const stats = lstatSync(fullPath);\n if (stats.isSymbolicLink()) {\n const target = await readlink(fullPath);\n // Support both regular aliases and websearch wrappers\n if (\n target.includes(\".droid-patch/bins\") ||\n target.includes(\".droid-patch/websearch\")\n ) {\n aliases.push({\n name: file,\n target,\n location: pathDir,\n immediate: true,\n });\n }\n }\n } catch {\n // Ignore\n }\n }\n } catch {\n // Directory can't be read\n }\n }\n\n try {\n const files = readdirSync(ALIASES_DIR);\n\n for (const file of files) {\n const fullPath = join(ALIASES_DIR, file);\n try {\n const stats = lstatSync(fullPath);\n if (stats.isSymbolicLink()) {\n const target = await readlink(fullPath);\n if (!aliases.find((a) => a.name === file)) {\n aliases.push({\n name: file,\n target,\n location: ALIASES_DIR,\n immediate: false,\n });\n }\n }\n } catch {\n // Ignore\n }\n }\n } catch {\n // Directory doesn't exist or can't be read\n }\n\n if (aliases.length === 0) {\n console.log(styleText(\"gray\", \" No aliases configured.\"));\n console.log();\n console.log(\n styleText(\n \"gray\",\n \" Create one with: npx droid-patch --is-custom <alias-name>\",\n ),\n );\n } else {\n console.log(styleText(\"white\", ` Found ${aliases.length} alias(es):`));\n console.log();\n for (const alias of aliases) {\n const status = alias.immediate\n ? styleText(\"green\", \"✓ immediate\")\n : styleText(\"yellow\", \"requires source\");\n console.log(\n styleText(\n \"green\",\n ` • ${styleText([\"cyan\", \"bold\"], alias.name)} [${status}]`,\n ),\n );\n console.log(styleText(\"gray\", ` → ${alias.target}`));\n }\n }\n\n console.log();\n console.log(styleText(\"gray\", ` Aliases directory: ${ALIASES_DIR}`));\n console.log(\n styleText(\n \"gray\",\n ` PATH configured: ${checkPathInclusion() ? styleText(\"green\", \"Yes\") : styleText(\"yellow\", \"No\")}`,\n ),\n );\n console.log();\n}\n\nexport interface ReplaceOriginalResult {\n originalPath: string;\n backupPath: string;\n}\n\nexport async function replaceOriginal(\n patchedBinaryPath: string,\n originalPath: string,\n verbose = false,\n): Promise<ReplaceOriginalResult> {\n ensureDirectories();\n\n console.log(\n styleText(\n \"white\",\n `[*] Replacing original binary: ${styleText(\"cyan\", originalPath)}`,\n ),\n );\n\n const latestBackupPath = join(BINS_DIR, \"droid-original-latest\");\n\n if (!existsSync(latestBackupPath)) {\n await copyFile(originalPath, latestBackupPath);\n console.log(styleText(\"green\", `[*] Created backup: ${latestBackupPath}`));\n } else {\n if (verbose) {\n console.log(\n styleText(\"gray\", ` Backup already exists: ${latestBackupPath}`),\n );\n }\n }\n\n await copyFile(patchedBinaryPath, originalPath);\n await chmod(originalPath, 0o755);\n console.log(styleText(\"green\", `[*] Replaced: ${originalPath}`));\n\n if (process.platform === \"darwin\") {\n try {\n console.log(styleText(\"gray\", \"[*] Re-signing binary for macOS...\"));\n execSync(`codesign --force --deep --sign - \"${originalPath}\"`, {\n stdio: \"pipe\",\n });\n console.log(styleText(\"green\", \"[*] Binary re-signed successfully\"));\n } catch {\n console.log(\n styleText(\n \"yellow\",\n \"[!] Could not re-sign binary. You may need to run:\",\n ),\n );\n console.log(\n styleText(\n \"gray\",\n ` codesign --force --deep --sign - \"${originalPath}\"`,\n ),\n );\n }\n\n try {\n execSync(`xattr -cr \"${originalPath}\"`, { stdio: \"pipe\" });\n } catch {\n // Ignore\n }\n }\n\n console.log();\n console.log(styleText(\"green\", \"─\".repeat(60)));\n console.log(styleText([\"green\", \"bold\"], \" REPLACEMENT COMPLETE\"));\n console.log(styleText(\"green\", \"─\".repeat(60)));\n console.log();\n console.log(\n styleText(\"white\", \"The patched binary is now active in all terminals.\"),\n );\n console.log(styleText(\"white\", \"No need to restart or source anything!\"));\n console.log();\n console.log(styleText(\"gray\", `To restore the original, run:`));\n console.log(styleText(\"cyan\", ` npx droid-patch restore`));\n\n return {\n originalPath,\n backupPath: latestBackupPath,\n };\n}\n\n/**\n * Create alias for wrapper script\n * Unlike createAlias, this function creates symlink pointing to wrapper script\n * Used for features like websearch that require preprocessing\n */\nexport async function createAliasForWrapper(\n wrapperPath: string,\n aliasName: string,\n verbose = false,\n): Promise<CreateAliasResult> {\n ensureDirectories();\n\n console.log(\n styleText(\"white\", `[*] Creating alias: ${styleText(\"cyan\", aliasName)}`),\n );\n\n const writablePathDir = findWritablePathDir();\n\n if (writablePathDir) {\n const targetPath = join(writablePathDir, aliasName);\n\n if (verbose) {\n console.log(styleText(\"gray\", ` Wrapper: ${wrapperPath}`));\n }\n\n if (existsSync(targetPath)) {\n await unlink(targetPath);\n if (verbose) {\n console.log(styleText(\"gray\", ` Removed existing: ${targetPath}`));\n }\n }\n\n await symlink(wrapperPath, targetPath);\n\n console.log(\n styleText(\"green\", `[*] Created: ${targetPath} -> ${wrapperPath}`),\n );\n console.log();\n console.log(styleText(\"green\", \"─\".repeat(60)));\n console.log(\n styleText([\"green\", \"bold\"], \" ALIAS READY - NO ACTION REQUIRED!\"),\n );\n console.log(styleText(\"green\", \"─\".repeat(60)));\n console.log();\n console.log(\n styleText(\n \"white\",\n `The alias \"${styleText([\"cyan\", \"bold\"], aliasName)}\" is now available in ALL terminals.`,\n ),\n );\n console.log(styleText(\"gray\", `(Installed to: ${writablePathDir})`));\n\n return {\n aliasPath: targetPath,\n binaryPath: wrapperPath,\n immediate: true,\n };\n }\n\n // Fallback: use ~/.droid-patch/aliases\n console.log(\n styleText(\n \"yellow\",\n \"[*] No writable PATH directory found, using fallback...\",\n ),\n );\n\n const symlinkPath = join(ALIASES_DIR, aliasName);\n\n if (existsSync(symlinkPath)) {\n await unlink(symlinkPath);\n if (verbose) {\n console.log(styleText(\"gray\", ` Removed existing symlink`));\n }\n }\n\n await symlink(wrapperPath, symlinkPath);\n\n console.log(\n styleText(\"green\", `[*] Created symlink: ${symlinkPath} -> ${wrapperPath}`),\n );\n\n const shellConfig = getShellConfigPath();\n\n if (!checkPathInclusion()) {\n if (!isPathConfigured(shellConfig)) {\n console.log(\n styleText(\"white\", `[*] Configuring PATH in ${shellConfig}...`),\n );\n\n if (addPathToShellConfig(shellConfig, verbose)) {\n console.log(styleText(\"green\", `[*] PATH configured successfully!`));\n console.log();\n console.log(styleText(\"yellow\", \"─\".repeat(60)));\n console.log(styleText([\"yellow\", \"bold\"], \" ACTION REQUIRED\"));\n console.log(styleText(\"yellow\", \"─\".repeat(60)));\n console.log();\n console.log(\n styleText(\"white\", \"To use the alias in this terminal, run:\"),\n );\n console.log();\n console.log(styleText(\"cyan\", ` source ${shellConfig}`));\n console.log();\n console.log(styleText(\"gray\", \"Or simply open a new terminal window.\"));\n console.log(styleText(\"yellow\", \"─\".repeat(60)));\n } else {\n const exportLine = `export PATH=\"${ALIASES_DIR}:$PATH\"`;\n console.log();\n console.log(styleText(\"yellow\", \"─\".repeat(60)));\n console.log(\n styleText([\"yellow\", \"bold\"], \" Manual PATH Configuration Required\"),\n );\n console.log(styleText(\"yellow\", \"─\".repeat(60)));\n console.log();\n console.log(styleText(\"white\", \"Add this line to your shell config:\"));\n console.log(styleText(\"cyan\", ` ${exportLine}`));\n console.log();\n console.log(styleText(\"gray\", `Shell config file: ${shellConfig}`));\n console.log(styleText(\"yellow\", \"─\".repeat(60)));\n }\n } else {\n console.log(\n styleText(\"green\", `[*] PATH already configured in ${shellConfig}`),\n );\n console.log();\n console.log(\n styleText(\n \"yellow\",\n `Note: Run \\`source ${shellConfig}\\` or open a new terminal to use the alias.`,\n ),\n );\n }\n } else {\n console.log(\n styleText(\"green\", `[*] PATH already includes aliases directory`),\n );\n console.log();\n console.log(\n styleText(\n \"green\",\n `You can now use \"${styleText([\"cyan\", \"bold\"], aliasName)}\" command directly!`,\n ),\n );\n }\n\n return {\n aliasPath: symlinkPath,\n binaryPath: wrapperPath,\n };\n}\n\nexport async function restoreOriginal(originalPath: string): Promise<void> {\n ensureDirectories();\n\n const latestBackupPath = join(BINS_DIR, \"droid-original-latest\");\n\n console.log(styleText(\"cyan\", \"═\".repeat(60)));\n console.log(styleText([\"cyan\", \"bold\"], \" Restore Original Droid\"));\n console.log(styleText(\"cyan\", \"═\".repeat(60)));\n console.log();\n\n if (!existsSync(latestBackupPath)) {\n const localBackup = `${originalPath}.backup`;\n if (existsSync(localBackup)) {\n console.log(styleText(\"white\", `[*] Found local backup: ${localBackup}`));\n console.log(styleText(\"white\", `[*] Restoring to: ${originalPath}`));\n\n await copyFile(localBackup, originalPath);\n await chmod(originalPath, 0o755);\n\n if (process.platform === \"darwin\") {\n try {\n execSync(`codesign --force --deep --sign - \"${originalPath}\"`, {\n stdio: \"pipe\",\n });\n execSync(`xattr -cr \"${originalPath}\"`, { stdio: \"pipe\" });\n } catch {\n // Ignore\n }\n }\n\n console.log();\n console.log(styleText(\"green\", \"═\".repeat(60)));\n console.log(styleText([\"green\", \"bold\"], \" RESTORE COMPLETE\"));\n console.log(styleText(\"green\", \"═\".repeat(60)));\n console.log();\n console.log(\n styleText(\n \"green\",\n \"Original droid binary has been restored from local backup.\",\n ),\n );\n return;\n }\n\n console.log(styleText(\"red\", \"[!] No backup found.\"));\n console.log(styleText(\"gray\", ` Checked: ${latestBackupPath}`));\n console.log(styleText(\"gray\", ` Checked: ${localBackup}`));\n console.log();\n console.log(\n styleText(\"gray\", \"If you have a manual backup, restore it with:\"),\n );\n console.log(styleText(\"cyan\", ` cp /path/to/backup ${originalPath}`));\n return;\n }\n\n console.log(styleText(\"white\", `[*] Restoring from: ${latestBackupPath}`));\n console.log(styleText(\"white\", `[*] Restoring to: ${originalPath}`));\n\n const targetDir = dirname(originalPath);\n if (!existsSync(targetDir)) {\n mkdirSync(targetDir, { recursive: true });\n }\n\n await copyFile(latestBackupPath, originalPath);\n await chmod(originalPath, 0o755);\n\n if (process.platform === \"darwin\") {\n try {\n execSync(`codesign --force --deep --sign - \"${originalPath}\"`, {\n stdio: \"pipe\",\n });\n execSync(`xattr -cr \"${originalPath}\"`, { stdio: \"pipe\" });\n } catch {\n // Ignore\n }\n }\n\n console.log();\n console.log(styleText(\"green\", \"═\".repeat(60)));\n console.log(styleText([\"green\", \"bold\"], \" RESTORE COMPLETE\"));\n console.log(styleText(\"green\", \"═\".repeat(60)));\n console.log();\n console.log(styleText(\"green\", \"Original droid binary has been restored.\"));\n console.log(\n styleText(\"green\", \"All terminals will now use the original version.\"),\n );\n}\n"],"mappings":"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"}