droid-patch 0.1.2 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.js","names":["patches: Patch[]","err: Error"],"sources":["../src/cli.ts"],"sourcesContent":["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 { patchDroid, type Patch } from \"./patcher.ts\";\nimport { createAlias, removeAlias, listAliases } from \"./alias.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 findDefaultDroidPath(): string {\n const home = homedir();\n const paths = [\n join(home, \".droid/bin/droid\"),\n \"/usr/local/bin/droid\",\n \"./droid\",\n ];\n for (const p of paths) {\n if (existsSync(p)) return p;\n }\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(\"--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 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 if (!isCustom && !skipLogin) {\n console.log(\n styleText(\"yellow\", \"No patch flags specified. Available patches:\"),\n );\n console.log(\n styleText(\"gray\", \" --is-custom Patch isCustom for custom models\"),\n );\n console.log(\n styleText(\n \"gray\",\n \" --skip-login Bypass login by injecting a fake API key\",\n ),\n );\n console.log();\n console.log(\"Usage examples:\");\n console.log(\n styleText(\"cyan\", \" npx droid-patch --is-custom droid-custom\"),\n );\n console.log(\n styleText(\"cyan\", \" npx droid-patch --skip-login droid-nologin\"),\n );\n console.log(\n styleText(\n \"cyan\",\n \" npx droid-patch --is-custom --skip-login droid-patched\",\n ),\n );\n console.log(\n styleText(\"cyan\", \" npx droid-patch --skip-login -o . my-droid\"),\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:\n '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 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(\n styleText(\"gray\", \"To apply the patches, run without --dry-run:\"),\n );\n console.log(\n styleText(\n \"cyan\",\n ` npx droid-patch --is-custom ${alias || \"<alias-name>\"}`,\n ),\n );\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(\n styleText(\"white\", `Patched binary saved to: ${result.outputPath}`),\n );\n process.exit(0);\n }\n\n if (result.success && result.outputPath && alias) {\n console.log();\n await createAlias(result.outputPath, alias, verbose);\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 a droid-patch alias or patched binary file\")\n .argument(\"<alias-or-path>\", \"Alias name or file path to remove\")\n .action(async (_options, args) => {\n const target = args[0] as string;\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 .run()\n .catch((err: Error) => {\n console.error(err);\n process.exit(1);\n });\n"],"mappings":"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"}
1
+ {"version":3,"file":"cli.js","names":["droidPath: string","proxyScriptPath: string","outputDir: string","aliasName: string","preloadScriptPath: string","bunfigDir: string","droidDir: string","patches: Patch[]","err: Error"],"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(\n droidPath: string,\n proxyScriptPath: string,\n): 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(\n `[websearch] Local URL too long: ${localUrl.length} > ${originalUrl.length}`,\n );\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(\n wrapperScriptPath,\n generateWrapperScript(droidPath, proxyScriptPath),\n );\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(\n droidPath: string,\n bunfigDir: string,\n): 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(\n wrapperScriptPath,\n generatePreloadWrapperScript(droidPath, droidDir),\n );\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 * Features:\n * - Auto-shutdown on idle (default 5 minutes without requests)\n * - Timeout configurable via DROID_PROXY_IDLE_TIMEOUT env var (seconds)\n * - Set to 0 to disable timeout\n */\nfunction generateSearchProxyServer(): string {\n return `#!/usr/bin/env node\n// Droid WebSearch Proxy Server\n// Auto-generated by droid-patch --websearch\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 || '23119');\n\n// Idle timeout in seconds, default 5 minutes, set to 0 to disable\nconst IDLE_TIMEOUT = parseInt(process.env.DROID_PROXY_IDLE_TIMEOUT || '300');\nlet lastActivityTime = Date.now();\nlet idleCheckTimer = null;\n\nfunction log(...args) {\n if (DEBUG) console.error('[websearch]', ...args);\n}\n\n// Update activity time\nfunction updateActivity() {\n lastActivityTime = Date.now();\n}\n\n// Check if any droid process is running (droid instances using the proxy)\nfunction isDroidRunning() {\n try {\n const { execSync } = require('child_process');\n // Use ps to check if droid.patched binary is running\n // Exclude scripts and grep itself, only match actual droid binary processes\n const result = execSync(\n 'ps aux | grep -E \"[d]roid\\\\\\\\.patched\" | grep -v grep | wc -l',\n { encoding: 'utf-8', timeout: 1000 }\n ).trim();\n return parseInt(result) > 0;\n } catch {\n return false;\n }\n}\n\n// Check idle time and possibly exit\nfunction checkIdleAndExit() {\n if (IDLE_TIMEOUT <= 0) return; // Timeout disabled\n\n // If droid process is running, refresh activity time (like heartbeat)\n if (isDroidRunning()) {\n log('Droid process detected, keeping proxy alive');\n updateActivity();\n return;\n }\n\n const idleMs = Date.now() - lastActivityTime;\n const timeoutMs = IDLE_TIMEOUT * 1000;\n\n if (idleMs >= timeoutMs) {\n log(\\`Idle for \\${Math.round(idleMs / 1000)}s and no droid running, shutting down...\\`);\n cleanup();\n process.exit(0);\n }\n}\n\n// Cleanup resources\nfunction cleanup() {\n if (idleCheckTimer) {\n clearInterval(idleCheckTimer);\n idleCheckTimer = null;\n }\n // Delete PID file\n try {\n fs.unlinkSync('/tmp/droid-search-proxy.pid');\n } catch {}\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 FACTORY_API = 'https://api.factory.ai';\n\nconst server = http.createServer(async (req, res) => {\n const url = new URL(req.url, \\`http://\\${req.headers.host}\\`);\n\n // Health check - don't update activity time to avoid self-ping preventing timeout\n if (url.pathname === '/health') {\n const idleSeconds = Math.round((Date.now() - lastActivityTime) / 1000);\n const droidRunning = isDroidRunning();\n res.writeHead(200, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({\n status: 'ok',\n port: PORT,\n idleTimeout: IDLE_TIMEOUT,\n idleSeconds: idleSeconds,\n droidRunning: droidRunning,\n // If droid is running, won't shutdown; otherwise calculate based on idle time\n willShutdownIn: IDLE_TIMEOUT > 0 && !droidRunning ? Math.max(0, IDLE_TIMEOUT - idleSeconds) : null\n }));\n return;\n }\n\n // Update activity time (only non-health-check requests refresh it)\n updateActivity();\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 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 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 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 // Start idle check timer\n // Check interval = min(timeout/2, 30s) to ensure timely timeout detection\n if (IDLE_TIMEOUT > 0) {\n const checkInterval = Math.min(IDLE_TIMEOUT * 500, 30000); // milliseconds\n log(\\`Idle timeout: \\${IDLE_TIMEOUT}s (will auto-shutdown when idle)\\`);\n idleCheckTimer = setInterval(checkIdleAndExit, checkInterval);\n } else {\n log('Idle timeout: disabled (will run forever)');\n }\n});\n\nprocess.on('SIGTERM', () => { cleanup(); server.close(); process.exit(0); });\nprocess.on('SIGINT', () => { cleanup(); server.close(); process.exit(0); });\n`;\n}\n\n/**\n * Generate unified Wrapper script\n * Uses shared proxy server mode:\n * - All droid instances share the same proxy process\n * - Proxy starts automatically if not running\n * - Proxy runs as background daemon, doesn't exit with droid\n */\nfunction generateUnifiedWrapper(\n droidPath: string,\n proxyScriptPath: string,\n): string {\n return `#!/bin/bash\n# Droid with WebSearch\n# Auto-generated by droid-patch --websearch\n\nPROXY_SCRIPT=\"${proxyScriptPath}\"\nDROID_BIN=\"${droidPath}\"\nPORT=23119\nPID_FILE=\"/tmp/droid-search-proxy.pid\"\nLOG_FILE=\"/tmp/droid-search-proxy.log\"\n\n# Check if proxy is running\nis_proxy_running() {\n if [ -f \"$PID_FILE\" ]; then\n local pid\n pid=$(cat \"$PID_FILE\")\n if kill -0 \"$pid\" 2>/dev/null; then\n # Process exists, check if port responds\n if curl -s \"http://127.0.0.1:$PORT/health\" > /dev/null 2>&1; then\n return 0\n fi\n fi\n # PID file exists but process doesn't exist or port not responding, cleanup\n rm -f \"$PID_FILE\"\n fi\n return 1\n}\n\n# Start shared proxy\nstart_shared_proxy() {\n # First check if port is occupied by another program\n if lsof -i:\"$PORT\" > /dev/null 2>&1; then\n # Port is occupied, check if it's our proxy\n if curl -s \"http://127.0.0.1:$PORT/health\" > /dev/null 2>&1; then\n [ -n \"$DROID_SEARCH_DEBUG\" ] && echo \"[websearch] Proxy already running on port $PORT\" >&2\n return 0\n else\n echo \"[websearch] Port $PORT is occupied by another process\" >&2\n return 1\n fi\n fi\n\n [ -n \"$DROID_SEARCH_DEBUG\" ] && echo \"[websearch] Starting shared proxy on port $PORT...\" >&2\n\n # Start proxy as background daemon\n SEARCH_PROXY_PORT=\"$PORT\" nohup node \"$PROXY_SCRIPT\" >> \"$LOG_FILE\" 2>&1 &\n echo $! > \"$PID_FILE\"\n\n # Wait for proxy to start\n for i in {1..30}; do\n if curl -s \"http://127.0.0.1:$PORT/health\" > /dev/null 2>&1; then\n [ -n \"$DROID_SEARCH_DEBUG\" ] && echo \"[websearch] Proxy ready on port $PORT (PID: $(cat $PID_FILE))\" >&2\n return 0\n fi\n sleep 0.1\n done\n\n echo \"[websearch] Failed to start proxy\" >&2\n rm -f \"$PID_FILE\"\n return 1\n}\n\n# Ensure proxy is running\nensure_proxy() {\n if is_proxy_running; then\n [ -n \"$DROID_SEARCH_DEBUG\" ] && echo \"[websearch] Using existing proxy on port $PORT\" >&2\n return 0\n fi\n start_shared_proxy\n}\n\n# Start/reuse proxy\nif ! ensure_proxy; then\n echo \"[websearch] Running without search proxy\" >&2\n exec \"$DROID_BIN\" \"$@\"\nfi\n\n# Run droid, set API to point to local proxy\nexport FACTORY_API_BASE_URL_OVERRIDE=\"http://127.0.0.1:$PORT\"\nexec \"$DROID_BIN\" \"$@\"\n`;\n}\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 */\nexport async function createWebSearchUnifiedFiles(\n outputDir: string,\n droidPath: string,\n aliasName: 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\n await writeFile(proxyScriptPath, generateSearchProxyServer());\n console.log(`[*] Created proxy script: ${proxyScriptPath}`);\n\n // Write unified wrapper\n await writeFile(\n wrapperScriptPath,\n generateUnifiedWrapper(droidPath, proxyScriptPath),\n );\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 { patchDroid, type Patch } from \"./patcher.ts\";\nimport {\n createAlias,\n removeAlias,\n listAliases,\n createAliasForWrapper,\n} from \"./alias.ts\";\nimport { createWebSearchUnifiedFiles } from \"./websearch-patch.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 findDefaultDroidPath(): string {\n const home = homedir();\n const paths = [\n join(home, \".droid/bin/droid\"),\n \"/usr/local/bin/droid\",\n \"./droid\",\n ];\n for (const p of paths) {\n if (existsSync(p)) return p;\n }\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 base URL (https://api.factory.ai) with custom URL\",\n )\n .option(\n \"--websearch\",\n \"Enable local WebSearch via fetch hook (Google PSE + DuckDuckGo fallback)\",\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 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 if (webSearch && !isCustom && !skipLogin && !apiBase) {\n if (!alias) {\n console.log(\n styleText(\"red\", \"Error: Alias name required for --websearch\"),\n );\n console.log(\n styleText(\"gray\", \"Usage: npx droid-patch --websearch <alias>\"),\n );\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\n // Create unified websearch files (preload script + wrapper)\n const websearchDir = join(homedir(), \".droid-patch\", \"websearch\");\n const { wrapperScript } = await createWebSearchUnifiedFiles(\n websearchDir,\n path,\n alias,\n );\n\n // Create alias pointing to wrapper\n await createAliasForWrapper(wrapperScript, alias, verbose);\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(\n \"gray\",\n \" Proxy auto-shuts down after 5 min idle (no manual cleanup needed)\",\n ),\n );\n console.log(\n styleText(\"gray\", \" To disable: export DROID_PROXY_IDLE_TIMEOUT=0\"),\n );\n console.log();\n console.log(\"Search providers (in priority order):\");\n console.log(styleText(\"yellow\", \" 1. Smithery Exa (best quality):\"));\n console.log(\n styleText(\"gray\", \" export SMITHERY_API_KEY=your_api_key\"),\n );\n console.log(\n styleText(\"gray\", \" export SMITHERY_PROFILE=your_profile\"),\n );\n console.log(styleText(\"gray\", \" 2. Google PSE:\"));\n console.log(\n styleText(\"gray\", \" export GOOGLE_PSE_API_KEY=your_api_key\"),\n );\n console.log(\n styleText(\"gray\", \" export GOOGLE_PSE_CX=your_search_engine_id\"),\n );\n console.log(\n styleText(\n \"gray\",\n \" 3-6. Serper, Brave, SearXNG, DuckDuckGo (fallbacks)\",\n ),\n );\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) {\n console.log(\n styleText(\"yellow\", \"No patch flags specified. Available patches:\"),\n );\n console.log(\n styleText(\"gray\", \" --is-custom Patch isCustom for custom models\"),\n );\n console.log(\n styleText(\n \"gray\",\n \" --skip-login Bypass login by injecting a fake API key\",\n ),\n );\n console.log(\n styleText(\n \"gray\",\n \" --api-base Replace Factory API URL with custom server\",\n ),\n );\n console.log(\n styleText(\n \"gray\",\n \" --websearch Enable local WebSearch (Google PSE + DuckDuckGo)\",\n ),\n );\n console.log();\n console.log(\"Usage examples:\");\n console.log(\n styleText(\"cyan\", \" npx droid-patch --is-custom droid-custom\"),\n );\n console.log(\n styleText(\"cyan\", \" npx droid-patch --skip-login droid-nologin\"),\n );\n console.log(\n styleText(\n \"cyan\",\n \" npx droid-patch --is-custom --skip-login droid-patched\",\n ),\n );\n console.log(\n styleText(\"cyan\", \" npx droid-patch --skip-login -o . my-droid\"),\n );\n console.log(\n styleText(\n \"cyan\",\n \" npx droid-patch --api-base http://localhost:3000 droid-local\",\n ),\n );\n console.log(\n styleText(\"cyan\", \" npx droid-patch --websearch droid-search\"),\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:\n '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 if (apiBase) {\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(\n \"red\",\n `Error: API base URL must be ${originalLength} characters or less`,\n ),\n );\n console.log(\n styleText(\n \"gray\",\n ` Your URL: \"${normalizedUrl}\" (${normalizedUrl.length} chars)`,\n ),\n );\n console.log(\n styleText(\"gray\", ` Maximum: ${originalLength} characters`),\n );\n console.log();\n console.log(\n styleText(\n \"yellow\",\n \"Tip: Use a shorter URL or set up a local redirect.\",\n ),\n );\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 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(\n styleText(\"gray\", \"To apply the patches, run without --dry-run:\"),\n );\n console.log(\n styleText(\n \"cyan\",\n ` npx droid-patch --is-custom ${alias || \"<alias-name>\"}`,\n ),\n );\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(\n styleText(\"white\", `Patched binary saved to: ${result.outputPath}`),\n );\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 websearchDir = join(homedir(), \".droid-patch\", \"websearch\");\n const { wrapperScript } = await createWebSearchUnifiedFiles(\n websearchDir,\n result.outputPath,\n alias,\n );\n await createAliasForWrapper(wrapperScript, alias, verbose);\n\n console.log();\n console.log(styleText(\"cyan\", \"WebSearch providers (optional):\"));\n console.log(\n styleText(\n \"gray\",\n \" Works out of the box with DuckDuckGo fallback\",\n ),\n );\n console.log(\n styleText(\"gray\", \" For better results, configure a provider:\"),\n );\n console.log();\n console.log(\n styleText(\"yellow\", \" Smithery Exa\"),\n styleText(\"gray\", \" - Best quality, free via smithery.ai\"),\n );\n console.log(\n styleText(\n \"gray\",\n \" export SMITHERY_API_KEY=... SMITHERY_PROFILE=...\",\n ),\n );\n console.log(\n styleText(\"yellow\", \" Google PSE\"),\n styleText(\"gray\", \" - 10,000/day free\"),\n );\n console.log(\n styleText(\n \"gray\",\n \" export GOOGLE_PSE_API_KEY=... GOOGLE_PSE_CX=...\",\n ),\n );\n console.log();\n console.log(\n styleText(\n \"gray\",\n \" See README for all providers and setup guides\",\n ),\n );\n } else {\n await createAlias(result.outputPath, alias, verbose);\n }\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 a droid-patch alias or patched binary file\")\n .argument(\"<alias-or-path>\", \"Alias name or file path to remove\")\n .action(async (_options, args) => {\n const target = args[0] as string;\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(\"proxy-status\", \"Check websearch proxy status\")\n .action(async () => {\n const pidFile = \"/tmp/droid-search-proxy.pid\";\n const logFile = \"/tmp/droid-search-proxy.log\";\n const port = 23119;\n\n console.log(styleText(\"cyan\", \"═\".repeat(60)));\n console.log(styleText([\"cyan\", \"bold\"], \" WebSearch Proxy Status\"));\n console.log(styleText(\"cyan\", \"═\".repeat(60)));\n console.log();\n\n // Check if proxy is running\n try {\n const response = await fetch(`http://127.0.0.1:${port}/health`);\n if (response.ok) {\n const data = (await response.json()) as {\n status: string;\n port: number;\n idleTimeout?: number;\n idleSeconds?: number;\n droidRunning?: boolean;\n willShutdownIn?: number | null;\n };\n console.log(styleText(\"green\", ` Status: Running ✓`));\n console.log(styleText(\"white\", ` Port: ${port}`));\n\n if (existsSync(pidFile)) {\n const { readFileSync } = await import(\"node:fs\");\n const pid = readFileSync(pidFile, \"utf-8\").trim();\n console.log(styleText(\"white\", ` PID: ${pid}`));\n }\n\n // Show droid running status\n if (data.droidRunning !== undefined) {\n console.log(\n styleText(\n \"white\",\n ` Droid running: ${data.droidRunning ? \"yes (proxy will stay alive)\" : \"no\"}`,\n ),\n );\n }\n\n // Show idle timeout info\n if (data.idleTimeout !== undefined) {\n if (data.idleTimeout > 0) {\n const idleMins = Math.floor((data.idleSeconds || 0) / 60);\n const idleSecs = (data.idleSeconds || 0) % 60;\n if (data.droidRunning) {\n console.log(\n styleText(\n \"white\",\n ` Idle: ${idleMins}m ${idleSecs}s (won't shutdown while droid runs)`,\n ),\n );\n } else if (data.willShutdownIn !== null) {\n const shutdownMins = Math.floor((data.willShutdownIn || 0) / 60);\n const shutdownSecs = (data.willShutdownIn || 0) % 60;\n console.log(\n styleText(\"white\", ` Idle: ${idleMins}m ${idleSecs}s`),\n );\n console.log(\n styleText(\n \"white\",\n ` Auto-shutdown in: ${shutdownMins}m ${shutdownSecs}s`,\n ),\n );\n }\n } else {\n console.log(styleText(\"white\", ` Auto-shutdown: disabled`));\n }\n }\n\n console.log(styleText(\"white\", ` Log: ${logFile}`));\n console.log();\n console.log(styleText(\"gray\", \"To stop the proxy manually:\"));\n console.log(styleText(\"cyan\", \" npx droid-patch proxy-stop\"));\n console.log();\n console.log(styleText(\"gray\", \"To disable auto-shutdown:\"));\n console.log(styleText(\"cyan\", \" export DROID_PROXY_IDLE_TIMEOUT=0\"));\n }\n } catch {\n console.log(styleText(\"yellow\", ` Status: Not running`));\n console.log();\n console.log(\n styleText(\n \"gray\",\n \"The proxy will start automatically when you run droid-full.\",\n ),\n );\n console.log(\n styleText(\n \"gray\",\n \"It will auto-shutdown after 5 minutes of idle (configurable).\",\n ),\n );\n }\n console.log();\n })\n .command(\"proxy-stop\", \"Stop the websearch proxy\")\n .action(async () => {\n const pidFile = \"/tmp/droid-search-proxy.pid\";\n\n if (!existsSync(pidFile)) {\n console.log(styleText(\"yellow\", \"Proxy is not running (no PID file)\"));\n return;\n }\n\n try {\n const { readFileSync, unlinkSync } = await import(\"node:fs\");\n const pid = readFileSync(pidFile, \"utf-8\").trim();\n\n process.kill(parseInt(pid), \"SIGTERM\");\n unlinkSync(pidFile);\n\n console.log(styleText(\"green\", `[*] Proxy stopped (PID: ${pid})`));\n } catch (error) {\n console.log(\n styleText(\n \"yellow\",\n `[!] Could not stop proxy: ${(error as Error).message}`,\n ),\n );\n\n // Clean up stale PID file\n try {\n const { unlinkSync } = await import(\"node:fs\");\n unlinkSync(pidFile);\n console.log(styleText(\"gray\", \"Cleaned up stale PID file\"));\n } catch {}\n }\n })\n .command(\"proxy-log\", \"Show websearch proxy logs\")\n .action(async () => {\n const logFile = \"/tmp/droid-search-proxy.log\";\n\n if (!existsSync(logFile)) {\n console.log(styleText(\"yellow\", \"No log file found\"));\n return;\n }\n\n const { readFileSync } = await import(\"node:fs\");\n const log = readFileSync(logFile, \"utf-8\");\n const lines = log.split(\"\\n\").slice(-50); // Last 50 lines\n\n console.log(styleText(\"cyan\", \"═\".repeat(60)));\n console.log(\n styleText([\"cyan\", \"bold\"], \" WebSearch Proxy Logs (last 50 lines)\"),\n );\n console.log(styleText(\"cyan\", \"═\".repeat(60)));\n console.log();\n console.log(lines.join(\"\\n\"));\n })\n .run()\n .catch((err: Error) => {\n console.error(err);\n process.exit(1);\n });\n"],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -43,6 +43,12 @@ interface ReplaceOriginalResult {
43
43
  backupPath: string;
44
44
  }
45
45
  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
+
46
52
  declare function restoreOriginal(originalPath: string): Promise<void>;
47
53
 
48
54
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","names":[],"sources":["../src/patcher.ts","../src/alias.ts"],"sourcesContent":null,"mappings":";UAKiB,KAAA;EAAA,IAAA,EAAA,MAAK;EAAA,WAAA,EAAA,MAAA;EAAA,OAGX,EAAA,MAAA;EAAM,WACF,EAAA,MAAA;AAAM;AAGJ,UAAA,YAAA,CAGN;EAMD,SAAA,EAAA,MAAW;EAQJ,UAAA,CAAA,EAAA,MAAgB;EASX,OAAA,EAvBX,KAuBqB,EAAA;EAAA,MAAA,CAAA,EAAA,OAAA;EAAA,MACrB,CAAA,EAAA,OAAA;EAAY,OACZ,CAAA,EAAA,OAAA;;AAAD,UAnBA,WAAA,CAmBA;;;;EC8GO,OAAA,EAAA,OAAA;EAMK,cAAW,CAAA,EAAA,OAAA;;AAItB,UDnIM,gBAAA,CCmIN;EAAiB,OAAzB,EAAA,OAAA;EAAO,MAAA,CAAA,EAAA,OAAA;EAwMY,OAAA,EDxUX,WCwUsB,EAAA;EAiDX,UAAA,CAAA,EAAA,MAAW;EA6GhB,aAAA,CAAA,EAAA,OAAA;EAKK,YAAA,CAAA,EAAA,MAAe;;AAI1B,iBDzeW,UAAA,CCyeX,OAAA,EDxeA,YCweA,CAAA,EDveR,OCueQ,CDveA,gBCueA,CAAA,CAAA;;UAzXM,iBAAA;EDjJA,SAAK,EAAA,MAAA;EAAA,UAAA,EAAA,MAAA;EAAA,SAGX,CAAA,EAAA,OAAA;;AACU,iBCmJC,WAAA,CDnJD,iBAAA,EAAA,MAAA,EAAA,SAAA,EAAA,MAAA,EAAA,OAAA,CAAA,EAAA,OAAA,CAAA,ECuJlB,ODvJkB,CCuJV,iBDvJU,CAAA;AAGJ,iBC4VK,WAAA,CDzVN,SAAA,EAAA,MAAA,CAAA,ECyVsC,ODzVtC,CAAA,IAAA,CAAA;AAMN,iBCoYY,WAAA,CAAA,CDpYD,ECoYgB,ODpYhB,CAAA,IAAA,CAAA;AAQJ,UCyeA,qBAAA,CDteN;EAMW,YAAA,EAAU,MAAA;EAAA,UAAA,EAAA,MAAA;;AAErB,iBCmeW,eAAA,CDneX,iBAAA,EAAA,MAAA,EAAA,YAAA,EAAA,MAAA,EAAA,OAAA,CAAA,EAAA,OAAA,CAAA,ECueR,ODveQ,CCueA,qBDveA,CAAA;AAAR,iBCkjBmB,eAAA,CDljBnB,YAAA,EAAA,MAAA,CAAA,ECkjB0D,ODljB1D,CAAA,IAAA,CAAA;;;AAAO"}
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../src/patcher.ts","../src/alias.ts"],"sourcesContent":null,"mappings":";UAKiB,KAAA;EAAA,IAAA,EAAA,MAAK;EAAA,WAAA,EAAA,MAAA;EAAA,OAGX,EAAA,MAAA;EAAM,WACF,EAAA,MAAA;AAAM;AAGJ,UAAA,YAAA,CAGN;EAMD,SAAA,EAAA,MAAW;EAQJ,UAAA,CAAA,EAAA,MAAgB;EASX,OAAA,EAvBX,KAuBqB,EAAA;EAAA,MAAA,CAAA,EAAA,OAAA;EAAA,MACrB,CAAA,EAAA,OAAA;EAAY,OACZ,CAAA,EAAA,OAAA;;AAAD,UAnBA,WAAA,CAmBA;;;;EC8GO,OAAA,EAAA,OAAA;EAMK,cAAW,CAAA,EAAA,OAAA;;AAItB,UDnIM,gBAAA,CCmIN;EAAiB,OAAzB,EAAA,OAAA;EAAO,MAAA,CAAA,EAAA,OAAA;EAwMY,OAAA,EDxUX,WCwUsB,EAAA;EAgFX,UAAA,CAAA,EAAA,MAAW;EAiHhB,aAAA,CAAA,EAAA,OAAA;EAKK,YAAA,CAAA,EAAA,MAAe;;AAI1B,iBD5gBW,UAAA,CC4gBX,OAAA,ED3gBA,YC2gBA,CAAA,ED1gBR,OC0gBQ,CD1gBA,gBC0gBA,CAAA,CAAA;;AD7iBM,UCiJA,iBAAA,CDjJK;EAAA,SAAA,EAAA,MAAA;EAAA,UAGX,EAAA,MAAA;EAAM,SACF,CAAA,EAAA,OAAA;AAAM;AAGJ,iBCgJK,WAAA,CD7IN,iBAAA,EAAA,MAAA,EAAA,SAAA,EAAA,MAAA,EAAA,OAAA,CAAA,EAAA,OAAA,CAAA,ECiJb,ODjJa,CCiJL,iBDjJK,CAAA;AAMN,iBCmVY,WAAA,CDnVD,SAAA,EAAA,MAAA,CAAA,ECmViC,ODnVjC,CAAA,IAAA,CAAA;AAQJ,iBC2ZK,WAAA,CAAA,CDxZX,ECwZ0B,ODxZf,CAAA,IAAA,CAAA;AAMA,UCmgBL,qBAAA,CDngBe;EAAA,YAAA,EAAA,MAAA;EAAA,UACrB,EAAA,MAAA;;AACR,iBCsgBmB,eAAA,CDtgBnB,iBAAA,EAAA,MAAA,EAAA,YAAA,EAAA,MAAA,EAAA,OAAA,CAAA,EAAA,OAAA,CAAA,EC0gBA,OD1gBA,CC0gBQ,qBD1gBR,CAAA;AAAO;;;;AC8GV;;AAMiC,iBAwnBX,eAAA,CAxnBW,YAAA,EAAA,MAAA,CAAA,EAwnB4B,OAxnB5B,CAAA,IAAA,CAAA"}
package/dist/index.js CHANGED
@@ -1,3 +1,3 @@
1
- import { createAlias, listAliases, patchDroid, removeAlias, replaceOriginal, restoreOriginal } from "./alias-DcCF7R2B.js";
1
+ import { createAlias, listAliases, patchDroid, removeAlias, replaceOriginal, restoreOriginal } from "./alias-C9LRaTwF.js";
2
2
 
3
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.1.2",
3
+ "version": "0.2.1",
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": {
@@ -1 +0,0 @@
1
- {"version":3,"file":"alias-DcCF7R2B.js","names":["options: PatchOptions","results: PatchResult[]","buffer: Buffer","pattern: Buffer","positions: number[]","position: number","patternLength: number","contextSize: number","shellConfigPath: string","exportLine: string","patchedBinaryPath: string","aliasName: string","aliases: AliasInfo[]","originalPath: string"],"sources":["../src/patcher.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","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\";\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\nfunction 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 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 if (target.includes(\".droid-patch/bins\")) {\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 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 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 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 if (target.includes(\".droid-patch/bins\")) {\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\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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"}