droid-patch 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -80,6 +80,26 @@ npx droid-patch remove /path/to/patched-binary
80
80
  npx droid-patch proxy-status
81
81
  ```
82
82
 
83
+ ### Update Aliases
84
+
85
+ When the original droid binary is updated, you can re-apply patches to all aliases:
86
+
87
+ ```bash
88
+ # Update all aliases with new droid binary
89
+ npx droid-patch update
90
+
91
+ # Update a specific alias
92
+ npx droid-patch update <alias-name>
93
+
94
+ # Preview without making changes
95
+ npx droid-patch update --dry-run
96
+
97
+ # Use a different droid binary
98
+ npx droid-patch update -p /path/to/new/droid
99
+ ```
100
+
101
+ The update command reads metadata stored when aliases were created and re-applies the same patches automatically.
102
+
83
103
  ### Check Version
84
104
 
85
105
  ```bash
package/README.zh-CN.md CHANGED
@@ -80,6 +80,26 @@ npx droid-patch remove /path/to/patched-binary
80
80
  npx droid-patch proxy-status
81
81
  ```
82
82
 
83
+ ### 更新别名
84
+
85
+ 当原始 droid 二进制文件更新后,可以重新为所有别名应用补丁:
86
+
87
+ ```bash
88
+ # 更新所有别名
89
+ npx droid-patch update
90
+
91
+ # 更新指定别名
92
+ npx droid-patch update <alias-name>
93
+
94
+ # 预览(不实际更新)
95
+ npx droid-patch update --dry-run
96
+
97
+ # 使用不同的 droid 二进制文件
98
+ npx droid-patch update -p /path/to/new/droid
99
+ ```
100
+
101
+ update 命令会读取创建别名时保存的元数据,自动重新应用相同的补丁。
102
+
83
103
  ### 检查版本
84
104
 
85
105
  ```bash
@@ -2,7 +2,7 @@ import { styleText } from "node:util";
2
2
  import { appendFileSync, existsSync, lstatSync, mkdirSync, readFileSync, readdirSync, unlinkSync, writeFileSync } from "node:fs";
3
3
  import { basename, dirname, join } from "node:path";
4
4
  import { homedir } from "node:os";
5
- import { chmod, copyFile, readFile, readlink, stat, symlink, unlink, writeFile } from "node:fs/promises";
5
+ import { chmod, copyFile, mkdir, readFile, readdir, readlink, stat, symlink, unlink, writeFile } from "node:fs/promises";
6
6
  import { execSync } from "node:child_process";
7
7
 
8
8
  //#region src/patcher.ts
@@ -172,6 +172,96 @@ function getContext(buffer, position, patternLength, contextSize) {
172
172
  return str;
173
173
  }
174
174
 
175
+ //#endregion
176
+ //#region src/metadata.ts
177
+ const META_DIR = join(homedir(), ".droid-patch", "meta");
178
+ /**
179
+ * Ensure metadata directory exists
180
+ */
181
+ async function ensureMetaDir() {
182
+ if (!existsSync(META_DIR)) await mkdir(META_DIR, { recursive: true });
183
+ }
184
+ /**
185
+ * Get the path to a metadata file for an alias
186
+ */
187
+ function getMetaPath(aliasName) {
188
+ return join(META_DIR, `${aliasName}.json`);
189
+ }
190
+ /**
191
+ * Save alias metadata to disk
192
+ */
193
+ async function saveAliasMetadata(meta) {
194
+ await ensureMetaDir();
195
+ const metaPath = getMetaPath(meta.name);
196
+ await writeFile(metaPath, JSON.stringify(meta, null, 2));
197
+ }
198
+ /**
199
+ * Load alias metadata from disk
200
+ * Returns null if metadata doesn't exist
201
+ */
202
+ async function loadAliasMetadata(aliasName) {
203
+ const metaPath = getMetaPath(aliasName);
204
+ if (!existsSync(metaPath)) return null;
205
+ try {
206
+ const content = await readFile(metaPath, "utf-8");
207
+ return JSON.parse(content);
208
+ } catch {
209
+ return null;
210
+ }
211
+ }
212
+ /**
213
+ * List all alias metadata
214
+ */
215
+ async function listAllMetadata() {
216
+ await ensureMetaDir();
217
+ const files = await readdir(META_DIR);
218
+ const metaList = [];
219
+ for (const file of files) {
220
+ if (!file.endsWith(".json")) continue;
221
+ const aliasName = file.replace(/\.json$/, "");
222
+ const meta = await loadAliasMetadata(aliasName);
223
+ if (meta) metaList.push(meta);
224
+ }
225
+ return metaList;
226
+ }
227
+ /**
228
+ * Remove alias metadata
229
+ */
230
+ async function removeAliasMetadata(aliasName) {
231
+ const metaPath = getMetaPath(aliasName);
232
+ if (!existsSync(metaPath)) return false;
233
+ try {
234
+ await unlink(metaPath);
235
+ return true;
236
+ } catch {
237
+ return false;
238
+ }
239
+ }
240
+ /**
241
+ * Create a new metadata object with current timestamp
242
+ */
243
+ function createMetadata(name, originalBinaryPath, patches) {
244
+ const now = new Date().toISOString();
245
+ return {
246
+ name,
247
+ createdAt: now,
248
+ updatedAt: now,
249
+ originalBinaryPath,
250
+ patches
251
+ };
252
+ }
253
+ /**
254
+ * Format patches for display
255
+ */
256
+ function formatPatches(patches) {
257
+ const applied = [];
258
+ if (patches.isCustom) applied.push("isCustom");
259
+ if (patches.skipLogin) applied.push("skipLogin");
260
+ if (patches.apiBase) applied.push(`apiBase(${patches.apiBase})`);
261
+ if (patches.websearch) applied.push("websearch");
262
+ return applied.length > 0 ? applied.join(", ") : "(none)";
263
+ }
264
+
175
265
  //#endregion
176
266
  //#region src/alias.ts
177
267
  const DROID_PATCH_DIR = join(homedir(), ".droid-patch");
@@ -418,6 +508,11 @@ async function removeAlias(aliasName) {
418
508
  console.log(styleText("green", ` Removed preload: ${preloadPath}`));
419
509
  removed = true;
420
510
  }
511
+ const metaRemoved = await removeAliasMetadata(aliasName);
512
+ if (metaRemoved) {
513
+ console.log(styleText("green", ` Removed metadata`));
514
+ removed = true;
515
+ }
421
516
  if (!removed) console.log(styleText("yellow", ` Alias "${aliasName}" not found`));
422
517
  else console.log(styleText("green", `[*] Alias "${aliasName}" removed successfully`));
423
518
  }
@@ -661,5 +756,5 @@ async function restoreOriginal(originalPath) {
661
756
  }
662
757
 
663
758
  //#endregion
664
- export { createAlias, createAliasForWrapper, listAliases, patchDroid, removeAlias, replaceOriginal, restoreOriginal };
665
- //# sourceMappingURL=alias-C9LRaTwF.js.map
759
+ export { createAlias, createAliasForWrapper, createMetadata, formatPatches, listAliases, listAllMetadata, loadAliasMetadata, patchDroid, removeAlias, replaceOriginal, restoreOriginal, saveAliasMetadata };
760
+ //# sourceMappingURL=alias-Ols294ja.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"alias-Ols294ja.js","names":["options: PatchOptions","results: PatchResult[]","buffer: Buffer","pattern: Buffer","positions: number[]","position: number","patternLength: number","contextSize: number","aliasName: string","meta: AliasMetadata","metaList: AliasMetadata[]","name: string","originalBinaryPath: string","patches: AliasMetadata[\"patches\"]","applied: string[]","shellConfigPath: string","exportLine: string","patchedBinaryPath: string","aliasName: string","aliases: AliasInfo[]","originalPath: string","wrapperPath: string"],"sources":["../src/patcher.ts","../src/metadata.ts","../src/alias.ts"],"sourcesContent":["import { readFile, writeFile, copyFile, chmod, stat } from \"node:fs/promises\";\nimport { existsSync } from \"node:fs\";\nimport { execSync } from \"node:child_process\";\nimport { styleText } from \"node:util\";\n\nexport interface Patch {\n name: string;\n description: string;\n pattern: Buffer;\n replacement: Buffer;\n}\n\nexport interface PatchOptions {\n inputPath: string;\n outputPath?: string;\n patches: Patch[];\n dryRun?: boolean;\n backup?: boolean;\n verbose?: boolean;\n}\n\ninterface PatchResult {\n name: string;\n found: number;\n positions?: number[];\n success: boolean;\n alreadyPatched?: boolean;\n}\n\nexport interface PatchDroidResult {\n success: boolean;\n dryRun?: boolean;\n results: PatchResult[];\n outputPath?: string;\n noPatchNeeded?: boolean;\n patchedCount?: number;\n}\n\nexport async function patchDroid(\n options: PatchOptions,\n): Promise<PatchDroidResult> {\n const {\n inputPath,\n outputPath,\n patches,\n dryRun = false,\n backup = true,\n verbose = false,\n } = options;\n\n const finalOutputPath = outputPath || `${inputPath}.patched`;\n\n if (!existsSync(inputPath)) {\n throw new Error(`Binary not found: ${inputPath}`);\n }\n\n const stats = await stat(inputPath);\n const fileSizeMB = (stats.size / (1024 * 1024)).toFixed(2);\n\n console.log(\n styleText(\"white\", `[*] Reading binary: ${styleText(\"cyan\", inputPath)}`),\n );\n console.log(\n styleText(\"white\", `[*] File size: ${styleText(\"cyan\", fileSizeMB)} MB`),\n );\n console.log();\n\n const data = await readFile(inputPath);\n const buffer = Buffer.from(data);\n\n const results: PatchResult[] = [];\n\n for (const patch of patches) {\n console.log(\n styleText(\n \"white\",\n `[*] Checking patch: ${styleText(\"yellow\", patch.name)}`,\n ),\n );\n console.log(styleText(\"gray\", ` ${patch.description}`));\n\n const positions = findAllPositions(buffer, patch.pattern);\n\n if (positions.length === 0) {\n console.log(\n styleText(\"yellow\", ` ! Pattern not found - may already be patched`),\n );\n results.push({\n name: patch.name,\n found: 0,\n success: false,\n alreadyPatched: buffer.includes(patch.replacement),\n });\n\n const replacementPositions = findAllPositions(buffer, patch.replacement);\n if (replacementPositions.length > 0) {\n console.log(\n styleText(\n \"blue\",\n ` ✓ Found ${replacementPositions.length} occurrences of patched pattern`,\n ),\n );\n console.log(\n styleText(\"blue\", ` ✓ Binary appears to be already patched`),\n );\n results[results.length - 1].alreadyPatched = true;\n results[results.length - 1].success = true;\n }\n continue;\n }\n\n console.log(\n styleText(\"green\", ` ✓ Found ${positions.length} occurrences`),\n );\n\n if (verbose) {\n for (const pos of positions.slice(0, 5)) {\n const context = getContext(buffer, pos, patch.pattern.length, 25);\n console.log(\n styleText(\n \"gray\",\n ` @ 0x${pos.toString(16).padStart(8, \"0\")}: ...${context}...`,\n ),\n );\n }\n if (positions.length > 5) {\n console.log(\n styleText(\"gray\", ` ... and ${positions.length - 5} more`),\n );\n }\n }\n\n results.push({\n name: patch.name,\n found: positions.length,\n positions,\n success: true,\n });\n }\n\n console.log();\n\n if (dryRun) {\n console.log(styleText(\"blue\", \"─\".repeat(60)));\n console.log(styleText([\"blue\", \"bold\"], \" DRY RUN RESULTS\"));\n console.log(styleText(\"blue\", \"─\".repeat(60)));\n console.log();\n\n for (const result of results) {\n if (result.alreadyPatched) {\n console.log(styleText(\"blue\", ` [✓] ${result.name}: Already patched`));\n } else if (result.found > 0) {\n console.log(\n styleText(\n \"green\",\n ` [✓] ${result.name}: ${result.found} occurrences will be patched`,\n ),\n );\n } else {\n console.log(\n styleText(\"yellow\", ` [!] ${result.name}: Pattern not found`),\n );\n }\n }\n\n return {\n success: results.every((r) => r.success || r.alreadyPatched),\n dryRun: true,\n results,\n };\n }\n\n const patchesNeeded = results.filter((r) => r.found > 0 && !r.alreadyPatched);\n\n if (patchesNeeded.length === 0) {\n const allPatched = results.every((r) => r.alreadyPatched);\n if (allPatched) {\n console.log(\n styleText(\n \"blue\",\n \"[*] All patches already applied. Binary is up to date.\",\n ),\n );\n return {\n success: true,\n outputPath: inputPath,\n results,\n noPatchNeeded: true,\n };\n }\n console.log(styleText(\"yellow\", \"[!] No patches could be applied.\"));\n return { success: false, results };\n }\n\n if (backup) {\n const backupPath = `${inputPath}.backup`;\n if (!existsSync(backupPath)) {\n await copyFile(inputPath, backupPath);\n console.log(\n styleText(\n \"white\",\n `[*] Created backup: ${styleText(\"cyan\", backupPath)}`,\n ),\n );\n } else {\n console.log(\n styleText(\"gray\", `[*] Backup already exists: ${backupPath}`),\n );\n }\n }\n\n console.log(styleText(\"white\", \"[*] Applying patches...\"));\n const patchedBuffer = Buffer.from(buffer);\n\n let totalPatched = 0;\n for (const patch of patches) {\n const result = results.find((r) => r.name === patch.name);\n if (!result || !result.positions) continue;\n\n for (const pos of result.positions) {\n patch.replacement.copy(patchedBuffer, pos);\n totalPatched++;\n }\n }\n\n console.log(styleText(\"green\", `[*] Applied ${totalPatched} patches`));\n\n await writeFile(finalOutputPath, patchedBuffer);\n console.log(\n styleText(\n \"white\",\n `[*] Patched binary saved: ${styleText(\"cyan\", finalOutputPath)}`,\n ),\n );\n\n await chmod(finalOutputPath, 0o755);\n console.log(styleText(\"gray\", \"[*] Set executable permission\"));\n\n console.log();\n console.log(styleText(\"white\", \"[*] Verifying patches...\"));\n const verifyBuffer = await readFile(finalOutputPath);\n\n let allVerified = true;\n for (const patch of patches) {\n const oldCount = findAllPositions(verifyBuffer, patch.pattern).length;\n const newCount = findAllPositions(verifyBuffer, patch.replacement).length;\n\n if (oldCount === 0) {\n console.log(\n styleText(\n \"green\",\n ` ✓ ${patch.name}: Verified (${newCount} patched)`,\n ),\n );\n } else {\n console.log(\n styleText(\n \"red\",\n ` ✗ ${patch.name}: ${oldCount} occurrences not patched`,\n ),\n );\n allVerified = false;\n }\n }\n\n if (allVerified) {\n console.log();\n console.log(styleText(\"green\", \"[+] All patches verified successfully!\"));\n }\n\n if (process.platform === \"darwin\") {\n console.log();\n try {\n console.log(styleText(\"gray\", \"[*] Re-signing binary for macOS...\"));\n execSync(`codesign --force --deep --sign - \"${finalOutputPath}\"`, {\n stdio: \"pipe\",\n });\n console.log(styleText(\"green\", \"[*] Binary re-signed successfully\"));\n } catch {\n console.log(styleText(\"yellow\", \"[!] Could not re-sign binary\"));\n console.log(\n styleText(\n \"gray\",\n ` You may need to run: codesign --force --deep --sign - ${finalOutputPath}`,\n ),\n );\n }\n\n try {\n execSync(`xattr -cr \"${finalOutputPath}\"`, { stdio: \"pipe\" });\n } catch {\n // Ignore\n }\n }\n\n return {\n success: allVerified,\n outputPath: finalOutputPath,\n results,\n patchedCount: totalPatched,\n };\n}\n\nfunction findAllPositions(buffer: Buffer, pattern: Buffer): number[] {\n const positions: number[] = [];\n let pos = 0;\n\n while (true) {\n pos = buffer.indexOf(pattern, pos);\n if (pos === -1) break;\n positions.push(pos);\n pos += pattern.length;\n }\n\n return positions;\n}\n\nfunction getContext(\n buffer: Buffer,\n position: number,\n patternLength: number,\n contextSize: number,\n): string {\n const start = Math.max(0, position - contextSize);\n const end = Math.min(buffer.length, position + patternLength + contextSize);\n const slice = buffer.slice(start, end);\n\n let str = \"\";\n for (let i = 0; i < slice.length; i++) {\n const c = slice[i];\n if (c >= 32 && c < 127) {\n str += String.fromCharCode(c);\n } else {\n str += \".\";\n }\n }\n return str;\n}\n","/**\n * Alias Metadata Management\n *\n * Stores and retrieves metadata about created aliases, including\n * which patches were applied. This enables the `update` command\n * to re-apply the same patches when the original droid binary is updated.\n */\n\nimport { existsSync } from \"node:fs\";\nimport { mkdir, readdir, readFile, unlink, writeFile } from \"node:fs/promises\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\n\n/**\n * Metadata structure for an alias\n */\nexport interface AliasMetadata {\n /** Alias name */\n name: string;\n /** ISO timestamp when created */\n createdAt: string;\n /** ISO timestamp when last updated */\n updatedAt: string;\n /** Path to the original droid binary used for patching */\n originalBinaryPath: string;\n /** Patches that were applied */\n patches: {\n isCustom: boolean;\n skipLogin: boolean;\n apiBase: string | null;\n websearch: boolean;\n };\n}\n\n// Directory for storing metadata files\nconst META_DIR = join(homedir(), \".droid-patch\", \"meta\");\n\n/**\n * Ensure metadata directory exists\n */\nasync function ensureMetaDir(): Promise<void> {\n if (!existsSync(META_DIR)) {\n await mkdir(META_DIR, { recursive: true });\n }\n}\n\n/**\n * Get the path to a metadata file for an alias\n */\nfunction getMetaPath(aliasName: string): string {\n return join(META_DIR, `${aliasName}.json`);\n}\n\n/**\n * Save alias metadata to disk\n */\nexport async function saveAliasMetadata(meta: AliasMetadata): Promise<void> {\n await ensureMetaDir();\n const metaPath = getMetaPath(meta.name);\n await writeFile(metaPath, JSON.stringify(meta, null, 2));\n}\n\n/**\n * Load alias metadata from disk\n * Returns null if metadata doesn't exist\n */\nexport async function loadAliasMetadata(\n aliasName: string,\n): Promise<AliasMetadata | null> {\n const metaPath = getMetaPath(aliasName);\n if (!existsSync(metaPath)) {\n return null;\n }\n try {\n const content = await readFile(metaPath, \"utf-8\");\n return JSON.parse(content) as AliasMetadata;\n } catch {\n return null;\n }\n}\n\n/**\n * List all alias metadata\n */\nexport async function listAllMetadata(): Promise<AliasMetadata[]> {\n await ensureMetaDir();\n\n const files = await readdir(META_DIR);\n const metaList: AliasMetadata[] = [];\n\n for (const file of files) {\n if (!file.endsWith(\".json\")) continue;\n\n const aliasName = file.replace(/\\.json$/, \"\");\n const meta = await loadAliasMetadata(aliasName);\n if (meta) {\n metaList.push(meta);\n }\n }\n\n return metaList;\n}\n\n/**\n * Remove alias metadata\n */\nexport async function removeAliasMetadata(aliasName: string): Promise<boolean> {\n const metaPath = getMetaPath(aliasName);\n if (!existsSync(metaPath)) {\n return false;\n }\n try {\n await unlink(metaPath);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Create a new metadata object with current timestamp\n */\nexport function createMetadata(\n name: string,\n originalBinaryPath: string,\n patches: AliasMetadata[\"patches\"],\n): AliasMetadata {\n const now = new Date().toISOString();\n return {\n name,\n createdAt: now,\n updatedAt: now,\n originalBinaryPath,\n patches,\n };\n}\n\n/**\n * Format patches for display\n */\nexport function formatPatches(patches: AliasMetadata[\"patches\"]): string {\n const applied: string[] = [];\n if (patches.isCustom) applied.push(\"isCustom\");\n if (patches.skipLogin) applied.push(\"skipLogin\");\n if (patches.apiBase) applied.push(`apiBase(${patches.apiBase})`);\n if (patches.websearch) applied.push(\"websearch\");\n return applied.length > 0 ? applied.join(\", \") : \"(none)\";\n}\n","import {\n existsSync,\n mkdirSync,\n readdirSync,\n unlinkSync,\n lstatSync,\n readFileSync,\n appendFileSync,\n writeFileSync,\n} from \"node:fs\";\nimport { symlink, readlink, unlink, copyFile, chmod } from \"node:fs/promises\";\nimport { join, basename, dirname } from \"node:path\";\nimport { homedir } from \"node:os\";\nimport { execSync } from \"node:child_process\";\nimport { styleText } from \"node:util\";\nimport { removeAliasMetadata } from \"./metadata.ts\";\n\nconst DROID_PATCH_DIR = join(homedir(), \".droid-patch\");\nconst ALIASES_DIR = join(DROID_PATCH_DIR, \"aliases\");\nconst BINS_DIR = join(DROID_PATCH_DIR, \"bins\");\n\nconst COMMON_PATH_DIRS = [\n join(homedir(), \".local/bin\"),\n join(homedir(), \"bin\"),\n join(homedir(), \".bin\"),\n \"/opt/homebrew/bin\",\n \"/usr/local/bin\",\n join(homedir(), \".npm-global/bin\"),\n join(homedir(), \".npm/bin\"),\n join(homedir(), \".pnpm-global/bin\"),\n join(homedir(), \".yarn/bin\"),\n join(homedir(), \".config/yarn/global/node_modules/.bin\"),\n join(homedir(), \".cargo/bin\"),\n join(homedir(), \"go/bin\"),\n join(homedir(), \".deno/bin\"),\n join(homedir(), \".bun/bin\"),\n join(homedir(), \".local/share/mise/shims\"),\n join(homedir(), \".asdf/shims\"),\n join(homedir(), \".nvm/current/bin\"),\n join(homedir(), \".volta/bin\"),\n join(homedir(), \".fnm/current/bin\"),\n];\n\nfunction ensureDirectories(): void {\n if (!existsSync(DROID_PATCH_DIR)) {\n mkdirSync(DROID_PATCH_DIR, { recursive: true });\n }\n if (!existsSync(ALIASES_DIR)) {\n mkdirSync(ALIASES_DIR, { recursive: true });\n }\n if (!existsSync(BINS_DIR)) {\n mkdirSync(BINS_DIR, { recursive: true });\n }\n}\n\nfunction checkPathInclusion(): boolean {\n const pathEnv = process.env.PATH || \"\";\n return pathEnv.split(\":\").includes(ALIASES_DIR);\n}\n\nexport function findWritablePathDir(): string | null {\n const pathEnv = process.env.PATH || \"\";\n const pathDirs = pathEnv.split(\":\");\n\n for (const dir of COMMON_PATH_DIRS) {\n if (pathDirs.includes(dir)) {\n try {\n if (!existsSync(dir)) {\n mkdirSync(dir, { recursive: true });\n }\n const testFile = join(dir, `.droid-patch-test-${Date.now()}`);\n writeFileSync(testFile, \"\");\n unlinkSync(testFile);\n return dir;\n } catch {\n continue;\n }\n }\n }\n\n return null;\n}\n\nfunction getShellConfigPath(): string {\n const shell = process.env.SHELL || \"/bin/bash\";\n const shellName = basename(shell);\n\n switch (shellName) {\n case \"zsh\":\n return join(homedir(), \".zshrc\");\n case \"bash\": {\n const bashProfile = join(homedir(), \".bash_profile\");\n if (existsSync(bashProfile)) return bashProfile;\n return join(homedir(), \".bashrc\");\n }\n case \"fish\":\n return join(homedir(), \".config/fish/config.fish\");\n default:\n return join(homedir(), \".profile\");\n }\n}\n\nfunction isPathConfigured(shellConfigPath: string): boolean {\n if (!existsSync(shellConfigPath)) {\n return false;\n }\n\n try {\n const content = readFileSync(shellConfigPath, \"utf-8\");\n return (\n content.includes(\".droid-patch/aliases\") ||\n content.includes(\"droid-patch/aliases\")\n );\n } catch {\n return false;\n }\n}\n\nfunction addPathToShellConfig(\n shellConfigPath: string,\n verbose = false,\n): boolean {\n const shell = process.env.SHELL || \"/bin/bash\";\n const shellName = basename(shell);\n\n let exportLine: string;\n if (shellName === \"fish\") {\n exportLine = `\\n# Added by droid-patch\\nfish_add_path \"${ALIASES_DIR}\"\\n`;\n } else {\n exportLine = `\\n# Added by droid-patch\\nexport PATH=\"${ALIASES_DIR}:$PATH\"\\n`;\n }\n\n try {\n appendFileSync(shellConfigPath, exportLine);\n if (verbose) {\n console.log(\n styleText(\"gray\", ` Added PATH export to: ${shellConfigPath}`),\n );\n }\n return true;\n } catch (error) {\n console.log(\n styleText(\n \"yellow\",\n `[!] Could not write to ${shellConfigPath}: ${(error as Error).message}`,\n ),\n );\n return false;\n }\n}\n\nexport interface CreateAliasResult {\n aliasPath: string;\n binaryPath: string;\n immediate?: boolean;\n}\n\nexport async function createAlias(\n patchedBinaryPath: string,\n aliasName: string,\n verbose = false,\n): Promise<CreateAliasResult> {\n ensureDirectories();\n\n console.log(\n styleText(\"white\", `[*] Creating alias: ${styleText(\"cyan\", aliasName)}`),\n );\n\n const writablePathDir = findWritablePathDir();\n\n if (writablePathDir) {\n const targetPath = join(writablePathDir, aliasName);\n const binaryDest = join(BINS_DIR, `${aliasName}-patched`);\n await copyFile(patchedBinaryPath, binaryDest);\n await chmod(binaryDest, 0o755);\n\n if (verbose) {\n console.log(styleText(\"gray\", ` Stored binary: ${binaryDest}`));\n }\n\n if (existsSync(targetPath)) {\n await unlink(targetPath);\n if (verbose) {\n console.log(styleText(\"gray\", ` Removed existing: ${targetPath}`));\n }\n }\n\n await symlink(binaryDest, targetPath);\n\n if (process.platform === \"darwin\") {\n try {\n console.log(styleText(\"gray\", \"[*] Re-signing binary for macOS...\"));\n execSync(`codesign --force --deep --sign - \"${binaryDest}\"`, {\n stdio: \"pipe\",\n });\n console.log(styleText(\"green\", \"[*] Binary re-signed successfully\"));\n } catch {\n console.log(styleText(\"yellow\", \"[!] Could not re-sign binary\"));\n }\n\n try {\n execSync(`xattr -cr \"${binaryDest}\"`, { stdio: \"pipe\" });\n } catch {\n // Ignore\n }\n }\n\n console.log(\n styleText(\"green\", `[*] Created: ${targetPath} -> ${binaryDest}`),\n );\n console.log();\n console.log(styleText(\"green\", \"─\".repeat(60)));\n console.log(\n styleText([\"green\", \"bold\"], \" ALIAS READY - NO ACTION REQUIRED!\"),\n );\n console.log(styleText(\"green\", \"─\".repeat(60)));\n console.log();\n console.log(\n styleText(\n \"white\",\n `The alias \"${styleText([\"cyan\", \"bold\"], aliasName)}\" is now available in ALL terminals.`,\n ),\n );\n console.log(styleText(\"gray\", `(Installed to: ${writablePathDir})`));\n\n return {\n aliasPath: targetPath,\n binaryPath: binaryDest,\n immediate: true,\n };\n }\n\n console.log(\n styleText(\n \"yellow\",\n \"[*] No writable PATH directory found, using fallback...\",\n ),\n );\n\n const binaryDest = join(BINS_DIR, `${aliasName}-patched`);\n await copyFile(patchedBinaryPath, binaryDest);\n await chmod(binaryDest, 0o755);\n\n if (verbose) {\n console.log(styleText(\"gray\", ` Copied binary to: ${binaryDest}`));\n }\n\n if (process.platform === \"darwin\") {\n try {\n console.log(styleText(\"gray\", \"[*] Re-signing binary for macOS...\"));\n execSync(`codesign --force --deep --sign - \"${binaryDest}\"`, {\n stdio: \"pipe\",\n });\n console.log(styleText(\"green\", \"[*] Binary re-signed successfully\"));\n } catch {\n console.log(\n styleText(\n \"yellow\",\n \"[!] Could not re-sign binary. You may need to do this manually:\",\n ),\n );\n console.log(\n styleText(\n \"gray\",\n ` codesign --force --deep --sign - \"${binaryDest}\"`,\n ),\n );\n }\n\n try {\n execSync(`xattr -cr \"${binaryDest}\"`, { stdio: \"pipe\" });\n } catch {\n // Ignore\n }\n }\n\n const symlinkPath = join(ALIASES_DIR, aliasName);\n\n if (existsSync(symlinkPath)) {\n await unlink(symlinkPath);\n if (verbose) {\n console.log(styleText(\"gray\", ` Removed existing symlink`));\n }\n }\n\n await symlink(binaryDest, symlinkPath);\n await chmod(symlinkPath, 0o755);\n\n console.log(\n styleText(\"green\", `[*] Created symlink: ${symlinkPath} -> ${binaryDest}`),\n );\n\n const shellConfig = getShellConfigPath();\n\n if (!checkPathInclusion()) {\n if (!isPathConfigured(shellConfig)) {\n console.log(\n styleText(\"white\", `[*] Configuring PATH in ${shellConfig}...`),\n );\n\n if (addPathToShellConfig(shellConfig, verbose)) {\n console.log(styleText(\"green\", `[*] PATH configured successfully!`));\n console.log();\n console.log(styleText(\"yellow\", \"─\".repeat(60)));\n console.log(styleText([\"yellow\", \"bold\"], \" ACTION REQUIRED\"));\n console.log(styleText(\"yellow\", \"─\".repeat(60)));\n console.log();\n console.log(\n styleText(\"white\", \"To use the alias in this terminal, run:\"),\n );\n console.log();\n console.log(styleText(\"cyan\", ` source ${shellConfig}`));\n console.log();\n console.log(styleText(\"gray\", \"Or simply open a new terminal window.\"));\n console.log(styleText(\"yellow\", \"─\".repeat(60)));\n } else {\n const exportLine = `export PATH=\"${ALIASES_DIR}:$PATH\"`;\n console.log();\n console.log(styleText(\"yellow\", \"─\".repeat(60)));\n console.log(\n styleText([\"yellow\", \"bold\"], \" Manual PATH Configuration Required\"),\n );\n console.log(styleText(\"yellow\", \"─\".repeat(60)));\n console.log();\n console.log(styleText(\"white\", \"Add this line to your shell config:\"));\n console.log(styleText(\"cyan\", ` ${exportLine}`));\n console.log();\n console.log(styleText(\"gray\", `Shell config file: ${shellConfig}`));\n console.log(styleText(\"yellow\", \"─\".repeat(60)));\n }\n } else {\n console.log(\n styleText(\"green\", `[*] PATH already configured in ${shellConfig}`),\n );\n console.log();\n console.log(\n styleText(\n \"yellow\",\n `Note: Run \\`source ${shellConfig}\\` or open a new terminal to use the alias.`,\n ),\n );\n }\n } else {\n console.log(\n styleText(\"green\", `[*] PATH already includes aliases directory`),\n );\n console.log();\n console.log(\n styleText(\n \"green\",\n `You can now use \"${styleText([\"cyan\", \"bold\"], aliasName)}\" command directly!`,\n ),\n );\n }\n\n return {\n aliasPath: symlinkPath,\n binaryPath: binaryDest,\n };\n}\n\nexport async function removeAlias(aliasName: string): Promise<void> {\n console.log(\n styleText(\"white\", `[*] Removing alias: ${styleText(\"cyan\", aliasName)}`),\n );\n\n let removed = false;\n\n // Check common PATH directories for symlinks\n for (const pathDir of COMMON_PATH_DIRS) {\n const pathSymlink = join(pathDir, aliasName);\n if (existsSync(pathSymlink)) {\n try {\n const stats = lstatSync(pathSymlink);\n if (stats.isSymbolicLink()) {\n const target = await readlink(pathSymlink);\n // Support both regular aliases (.droid-patch/bins) and websearch wrappers (.droid-patch/websearch)\n if (\n target.includes(\".droid-patch/bins\") ||\n target.includes(\".droid-patch/websearch\")\n ) {\n await unlink(pathSymlink);\n console.log(styleText(\"green\", ` Removed: ${pathSymlink}`));\n removed = true;\n }\n }\n } catch {\n // Ignore\n }\n }\n }\n\n // Check aliases directory\n const symlinkPath = join(ALIASES_DIR, aliasName);\n if (existsSync(symlinkPath)) {\n await unlink(symlinkPath);\n console.log(styleText(\"green\", ` Removed: ${symlinkPath}`));\n removed = true;\n }\n\n // Remove binary if exists\n const binaryPath = join(BINS_DIR, `${aliasName}-patched`);\n if (existsSync(binaryPath)) {\n await unlink(binaryPath);\n console.log(styleText(\"green\", ` Removed binary: ${binaryPath}`));\n removed = true;\n }\n\n // Remove websearch wrapper and related files if exist\n const websearchDir = join(DROID_PATCH_DIR, \"websearch\");\n const wrapperPath = join(websearchDir, aliasName);\n const proxyPath = join(websearchDir, `${aliasName}-proxy.js`);\n const preloadPath = join(websearchDir, `${aliasName}-preload.js`);\n\n if (existsSync(wrapperPath)) {\n await unlink(wrapperPath);\n console.log(styleText(\"green\", ` Removed wrapper: ${wrapperPath}`));\n removed = true;\n }\n\n if (existsSync(proxyPath)) {\n await unlink(proxyPath);\n console.log(styleText(\"green\", ` Removed proxy: ${proxyPath}`));\n removed = true;\n }\n\n if (existsSync(preloadPath)) {\n await unlink(preloadPath);\n console.log(styleText(\"green\", ` Removed preload: ${preloadPath}`));\n removed = true;\n }\n\n // Remove metadata\n const metaRemoved = await removeAliasMetadata(aliasName);\n if (metaRemoved) {\n console.log(styleText(\"green\", ` Removed metadata`));\n removed = true;\n }\n\n if (!removed) {\n console.log(styleText(\"yellow\", ` Alias \"${aliasName}\" not found`));\n } else {\n console.log(\n styleText(\"green\", `[*] Alias \"${aliasName}\" removed successfully`),\n );\n }\n}\n\nexport async function listAliases(): Promise<void> {\n ensureDirectories();\n\n console.log(styleText(\"cyan\", \"═\".repeat(60)));\n console.log(styleText([\"cyan\", \"bold\"], \" Droid-Patch Aliases\"));\n console.log(styleText(\"cyan\", \"═\".repeat(60)));\n console.log();\n\n interface AliasInfo {\n name: string;\n target: string;\n location: string;\n immediate: boolean;\n }\n\n const aliases: AliasInfo[] = [];\n\n for (const pathDir of COMMON_PATH_DIRS) {\n if (!existsSync(pathDir)) continue;\n\n try {\n const files = readdirSync(pathDir);\n for (const file of files) {\n const fullPath = join(pathDir, file);\n try {\n const stats = lstatSync(fullPath);\n if (stats.isSymbolicLink()) {\n const target = await readlink(fullPath);\n // Support both regular aliases and websearch wrappers\n if (\n target.includes(\".droid-patch/bins\") ||\n target.includes(\".droid-patch/websearch\")\n ) {\n aliases.push({\n name: file,\n target,\n location: pathDir,\n immediate: true,\n });\n }\n }\n } catch {\n // Ignore\n }\n }\n } catch {\n // Directory can't be read\n }\n }\n\n try {\n const files = readdirSync(ALIASES_DIR);\n\n for (const file of files) {\n const fullPath = join(ALIASES_DIR, file);\n try {\n const stats = lstatSync(fullPath);\n if (stats.isSymbolicLink()) {\n const target = await readlink(fullPath);\n if (!aliases.find((a) => a.name === file)) {\n aliases.push({\n name: file,\n target,\n location: ALIASES_DIR,\n immediate: false,\n });\n }\n }\n } catch {\n // Ignore\n }\n }\n } catch {\n // Directory doesn't exist or can't be read\n }\n\n if (aliases.length === 0) {\n console.log(styleText(\"gray\", \" No aliases configured.\"));\n console.log();\n console.log(\n styleText(\n \"gray\",\n \" Create one with: npx droid-patch --is-custom <alias-name>\",\n ),\n );\n } else {\n console.log(styleText(\"white\", ` Found ${aliases.length} alias(es):`));\n console.log();\n for (const alias of aliases) {\n const status = alias.immediate\n ? styleText(\"green\", \"✓ immediate\")\n : styleText(\"yellow\", \"requires source\");\n console.log(\n styleText(\n \"green\",\n ` • ${styleText([\"cyan\", \"bold\"], alias.name)} [${status}]`,\n ),\n );\n console.log(styleText(\"gray\", ` → ${alias.target}`));\n }\n }\n\n console.log();\n console.log(styleText(\"gray\", ` Aliases directory: ${ALIASES_DIR}`));\n console.log(\n styleText(\n \"gray\",\n ` PATH configured: ${checkPathInclusion() ? styleText(\"green\", \"Yes\") : styleText(\"yellow\", \"No\")}`,\n ),\n );\n console.log();\n}\n\nexport interface ReplaceOriginalResult {\n originalPath: string;\n backupPath: string;\n}\n\nexport async function replaceOriginal(\n patchedBinaryPath: string,\n originalPath: string,\n verbose = false,\n): Promise<ReplaceOriginalResult> {\n ensureDirectories();\n\n console.log(\n styleText(\n \"white\",\n `[*] Replacing original binary: ${styleText(\"cyan\", originalPath)}`,\n ),\n );\n\n const latestBackupPath = join(BINS_DIR, \"droid-original-latest\");\n\n if (!existsSync(latestBackupPath)) {\n await copyFile(originalPath, latestBackupPath);\n console.log(styleText(\"green\", `[*] Created backup: ${latestBackupPath}`));\n } else {\n if (verbose) {\n console.log(\n styleText(\"gray\", ` Backup already exists: ${latestBackupPath}`),\n );\n }\n }\n\n await copyFile(patchedBinaryPath, originalPath);\n await chmod(originalPath, 0o755);\n console.log(styleText(\"green\", `[*] Replaced: ${originalPath}`));\n\n if (process.platform === \"darwin\") {\n try {\n console.log(styleText(\"gray\", \"[*] Re-signing binary for macOS...\"));\n execSync(`codesign --force --deep --sign - \"${originalPath}\"`, {\n stdio: \"pipe\",\n });\n console.log(styleText(\"green\", \"[*] Binary re-signed successfully\"));\n } catch {\n console.log(\n styleText(\n \"yellow\",\n \"[!] Could not re-sign binary. You may need to run:\",\n ),\n );\n console.log(\n styleText(\n \"gray\",\n ` codesign --force --deep --sign - \"${originalPath}\"`,\n ),\n );\n }\n\n try {\n execSync(`xattr -cr \"${originalPath}\"`, { stdio: \"pipe\" });\n } catch {\n // Ignore\n }\n }\n\n console.log();\n console.log(styleText(\"green\", \"─\".repeat(60)));\n console.log(styleText([\"green\", \"bold\"], \" REPLACEMENT COMPLETE\"));\n console.log(styleText(\"green\", \"─\".repeat(60)));\n console.log();\n console.log(\n styleText(\"white\", \"The patched binary is now active in all terminals.\"),\n );\n console.log(styleText(\"white\", \"No need to restart or source anything!\"));\n console.log();\n console.log(styleText(\"gray\", `To restore the original, run:`));\n console.log(styleText(\"cyan\", ` npx droid-patch restore`));\n\n return {\n originalPath,\n backupPath: latestBackupPath,\n };\n}\n\n/**\n * Create alias for wrapper script\n * Unlike createAlias, this function creates symlink pointing to wrapper script\n * Used for features like websearch that require preprocessing\n */\nexport async function createAliasForWrapper(\n wrapperPath: string,\n aliasName: string,\n verbose = false,\n): Promise<CreateAliasResult> {\n ensureDirectories();\n\n console.log(\n styleText(\"white\", `[*] Creating alias: ${styleText(\"cyan\", aliasName)}`),\n );\n\n const writablePathDir = findWritablePathDir();\n\n if (writablePathDir) {\n const targetPath = join(writablePathDir, aliasName);\n\n if (verbose) {\n console.log(styleText(\"gray\", ` Wrapper: ${wrapperPath}`));\n }\n\n if (existsSync(targetPath)) {\n await unlink(targetPath);\n if (verbose) {\n console.log(styleText(\"gray\", ` Removed existing: ${targetPath}`));\n }\n }\n\n await symlink(wrapperPath, targetPath);\n\n console.log(\n styleText(\"green\", `[*] Created: ${targetPath} -> ${wrapperPath}`),\n );\n console.log();\n console.log(styleText(\"green\", \"─\".repeat(60)));\n console.log(\n styleText([\"green\", \"bold\"], \" ALIAS READY - NO ACTION REQUIRED!\"),\n );\n console.log(styleText(\"green\", \"─\".repeat(60)));\n console.log();\n console.log(\n styleText(\n \"white\",\n `The alias \"${styleText([\"cyan\", \"bold\"], aliasName)}\" is now available in ALL terminals.`,\n ),\n );\n console.log(styleText(\"gray\", `(Installed to: ${writablePathDir})`));\n\n return {\n aliasPath: targetPath,\n binaryPath: wrapperPath,\n immediate: true,\n };\n }\n\n // Fallback: use ~/.droid-patch/aliases\n console.log(\n styleText(\n \"yellow\",\n \"[*] No writable PATH directory found, using fallback...\",\n ),\n );\n\n const symlinkPath = join(ALIASES_DIR, aliasName);\n\n if (existsSync(symlinkPath)) {\n await unlink(symlinkPath);\n if (verbose) {\n console.log(styleText(\"gray\", ` Removed existing symlink`));\n }\n }\n\n await symlink(wrapperPath, symlinkPath);\n\n console.log(\n styleText(\"green\", `[*] Created symlink: ${symlinkPath} -> ${wrapperPath}`),\n );\n\n const shellConfig = getShellConfigPath();\n\n if (!checkPathInclusion()) {\n if (!isPathConfigured(shellConfig)) {\n console.log(\n styleText(\"white\", `[*] Configuring PATH in ${shellConfig}...`),\n );\n\n if (addPathToShellConfig(shellConfig, verbose)) {\n console.log(styleText(\"green\", `[*] PATH configured successfully!`));\n console.log();\n console.log(styleText(\"yellow\", \"─\".repeat(60)));\n console.log(styleText([\"yellow\", \"bold\"], \" ACTION REQUIRED\"));\n console.log(styleText(\"yellow\", \"─\".repeat(60)));\n console.log();\n console.log(\n styleText(\"white\", \"To use the alias in this terminal, run:\"),\n );\n console.log();\n console.log(styleText(\"cyan\", ` source ${shellConfig}`));\n console.log();\n console.log(styleText(\"gray\", \"Or simply open a new terminal window.\"));\n console.log(styleText(\"yellow\", \"─\".repeat(60)));\n } else {\n const exportLine = `export PATH=\"${ALIASES_DIR}:$PATH\"`;\n console.log();\n console.log(styleText(\"yellow\", \"─\".repeat(60)));\n console.log(\n styleText([\"yellow\", \"bold\"], \" Manual PATH Configuration Required\"),\n );\n console.log(styleText(\"yellow\", \"─\".repeat(60)));\n console.log();\n console.log(styleText(\"white\", \"Add this line to your shell config:\"));\n console.log(styleText(\"cyan\", ` ${exportLine}`));\n console.log();\n console.log(styleText(\"gray\", `Shell config file: ${shellConfig}`));\n console.log(styleText(\"yellow\", \"─\".repeat(60)));\n }\n } else {\n console.log(\n styleText(\"green\", `[*] PATH already configured in ${shellConfig}`),\n );\n console.log();\n console.log(\n styleText(\n \"yellow\",\n `Note: Run \\`source ${shellConfig}\\` or open a new terminal to use the alias.`,\n ),\n );\n }\n } else {\n console.log(\n styleText(\"green\", `[*] PATH already includes aliases directory`),\n );\n console.log();\n console.log(\n styleText(\n \"green\",\n `You can now use \"${styleText([\"cyan\", \"bold\"], aliasName)}\" command directly!`,\n ),\n );\n }\n\n return {\n aliasPath: symlinkPath,\n binaryPath: wrapperPath,\n };\n}\n\nexport async function restoreOriginal(originalPath: string): Promise<void> {\n ensureDirectories();\n\n const latestBackupPath = join(BINS_DIR, \"droid-original-latest\");\n\n console.log(styleText(\"cyan\", \"═\".repeat(60)));\n console.log(styleText([\"cyan\", \"bold\"], \" Restore Original Droid\"));\n console.log(styleText(\"cyan\", \"═\".repeat(60)));\n console.log();\n\n if (!existsSync(latestBackupPath)) {\n const localBackup = `${originalPath}.backup`;\n if (existsSync(localBackup)) {\n console.log(styleText(\"white\", `[*] Found local backup: ${localBackup}`));\n console.log(styleText(\"white\", `[*] Restoring to: ${originalPath}`));\n\n await copyFile(localBackup, originalPath);\n await chmod(originalPath, 0o755);\n\n if (process.platform === \"darwin\") {\n try {\n execSync(`codesign --force --deep --sign - \"${originalPath}\"`, {\n stdio: \"pipe\",\n });\n execSync(`xattr -cr \"${originalPath}\"`, { stdio: \"pipe\" });\n } catch {\n // Ignore\n }\n }\n\n console.log();\n console.log(styleText(\"green\", \"═\".repeat(60)));\n console.log(styleText([\"green\", \"bold\"], \" RESTORE COMPLETE\"));\n console.log(styleText(\"green\", \"═\".repeat(60)));\n console.log();\n console.log(\n styleText(\n \"green\",\n \"Original droid binary has been restored from local backup.\",\n ),\n );\n return;\n }\n\n console.log(styleText(\"red\", \"[!] No backup found.\"));\n console.log(styleText(\"gray\", ` Checked: ${latestBackupPath}`));\n console.log(styleText(\"gray\", ` Checked: ${localBackup}`));\n console.log();\n console.log(\n styleText(\"gray\", \"If you have a manual backup, restore it with:\"),\n );\n console.log(styleText(\"cyan\", ` cp /path/to/backup ${originalPath}`));\n return;\n }\n\n console.log(styleText(\"white\", `[*] Restoring from: ${latestBackupPath}`));\n console.log(styleText(\"white\", `[*] Restoring to: ${originalPath}`));\n\n const targetDir = dirname(originalPath);\n if (!existsSync(targetDir)) {\n mkdirSync(targetDir, { recursive: true });\n }\n\n await copyFile(latestBackupPath, originalPath);\n await chmod(originalPath, 0o755);\n\n if (process.platform === \"darwin\") {\n try {\n execSync(`codesign --force --deep --sign - \"${originalPath}\"`, {\n stdio: \"pipe\",\n });\n execSync(`xattr -cr \"${originalPath}\"`, { stdio: \"pipe\" });\n } catch {\n // Ignore\n }\n }\n\n console.log();\n console.log(styleText(\"green\", \"═\".repeat(60)));\n console.log(styleText([\"green\", \"bold\"], \" RESTORE COMPLETE\"));\n console.log(styleText(\"green\", \"═\".repeat(60)));\n console.log();\n console.log(styleText(\"green\", \"Original droid binary has been restored.\"));\n console.log(\n styleText(\"green\", \"All terminals will now use the original version.\"),\n );\n}\n"],"mappings":"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"}
package/dist/cli.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { createAlias, createAliasForWrapper, listAliases, patchDroid, removeAlias } from "./alias-C9LRaTwF.js";
2
+ import { createAlias, createAliasForWrapper, createMetadata, formatPatches, listAliases, listAllMetadata, loadAliasMetadata, patchDroid, removeAlias, saveAliasMetadata } from "./alias-Ols294ja.js";
3
3
  import bin from "tiny-bin";
4
4
  import { styleText } from "node:util";
5
5
  import { existsSync, readFileSync } from "node:fs";
@@ -707,6 +707,13 @@ bin("droid-patch", "CLI tool to patch droid binary with various modifications").
707
707
  const websearchDir = join(homedir(), ".droid-patch", "websearch");
708
708
  const { wrapperScript } = await createWebSearchUnifiedFiles(websearchDir, path, alias);
709
709
  await createAliasForWrapper(wrapperScript, alias, verbose);
710
+ const metadata = createMetadata(alias, path, {
711
+ isCustom: false,
712
+ skipLogin: false,
713
+ apiBase: null,
714
+ websearch: true
715
+ });
716
+ await saveAliasMetadata(metadata);
710
717
  console.log();
711
718
  console.log(styleText("green", "═".repeat(60)));
712
719
  console.log(styleText(["green", "bold"], " WebSearch Ready!"));
@@ -828,10 +835,24 @@ bin("droid-patch", "CLI tool to patch droid binary with various modifications").
828
835
  const { wrapperScript } = await createWebSearchUnifiedFiles(websearchDir, result.outputPath, alias);
829
836
  await createAliasForWrapper(wrapperScript, alias, verbose);
830
837
  console.log();
831
- console.log("Optional: Set Google PSE for better results:");
832
- console.log(styleText("gray", " export GOOGLE_PSE_API_KEY=your_api_key"));
833
- console.log(styleText("gray", " export GOOGLE_PSE_CX=your_search_engine_id"));
838
+ console.log(styleText("cyan", "WebSearch providers (optional):"));
839
+ console.log(styleText("gray", " Works out of the box with DuckDuckGo fallback"));
840
+ console.log(styleText("gray", " For better results, configure a provider:"));
841
+ console.log();
842
+ console.log(styleText("yellow", " Smithery Exa"), styleText("gray", " - Best quality, free via smithery.ai"));
843
+ console.log(styleText("gray", " export SMITHERY_API_KEY=... SMITHERY_PROFILE=..."));
844
+ console.log(styleText("yellow", " Google PSE"), styleText("gray", " - 10,000/day free"));
845
+ console.log(styleText("gray", " export GOOGLE_PSE_API_KEY=... GOOGLE_PSE_CX=..."));
846
+ console.log();
847
+ console.log(styleText("gray", " See README for all providers and setup guides"));
834
848
  } else await createAlias(result.outputPath, alias, verbose);
849
+ const metadata = createMetadata(alias, path, {
850
+ isCustom: !!isCustom,
851
+ skipLogin: !!skipLogin,
852
+ apiBase: apiBase || null,
853
+ websearch: !!webSearch
854
+ });
855
+ await saveAliasMetadata(metadata);
835
856
  }
836
857
  if (result.success) {
837
858
  console.log();
@@ -941,6 +962,132 @@ bin("droid-patch", "CLI tool to patch droid binary with various modifications").
941
962
  console.log(styleText("cyan", "═".repeat(60)));
942
963
  console.log();
943
964
  console.log(lines.join("\n"));
965
+ }).command("update", "Update aliases with latest droid binary").argument("[alias]", "Specific alias to update (optional, updates all if not specified)").option("--dry-run", "Preview without making changes").option("-p, --path <path>", "Path to new droid binary").option("-v, --verbose", "Enable verbose output").action(async (options, args) => {
966
+ const aliasName = args?.[0];
967
+ const dryRun = options["dry-run"];
968
+ const newBinaryPath = options.path || findDefaultDroidPath();
969
+ const verbose = options.verbose;
970
+ console.log(styleText("cyan", "═".repeat(60)));
971
+ console.log(styleText(["cyan", "bold"], " Droid-Patch Update"));
972
+ console.log(styleText("cyan", "═".repeat(60)));
973
+ console.log();
974
+ if (!existsSync(newBinaryPath)) {
975
+ console.log(styleText("red", `Error: Droid binary not found at ${newBinaryPath}`));
976
+ console.log(styleText("gray", "Use -p to specify a different path"));
977
+ process.exit(1);
978
+ }
979
+ let metaList;
980
+ if (aliasName) {
981
+ const meta = await loadAliasMetadata(aliasName);
982
+ if (!meta) {
983
+ console.log(styleText("red", `Error: No metadata found for alias "${aliasName}"`));
984
+ console.log(styleText("gray", "This alias may have been created before update tracking was added."));
985
+ console.log(styleText("gray", "Remove and recreate the alias to enable update support."));
986
+ process.exit(1);
987
+ }
988
+ metaList = [meta];
989
+ } else {
990
+ metaList = await listAllMetadata();
991
+ if (metaList.length === 0) {
992
+ console.log(styleText("yellow", "No aliases with metadata found."));
993
+ console.log(styleText("gray", "Create aliases with droid-patch to enable update support."));
994
+ process.exit(0);
995
+ }
996
+ }
997
+ console.log(styleText("white", `Using droid binary: ${newBinaryPath}`));
998
+ console.log(styleText("white", `Found ${metaList.length} alias(es) to update`));
999
+ if (dryRun) console.log(styleText("blue", "(DRY RUN - no changes will be made)"));
1000
+ console.log();
1001
+ let successCount = 0;
1002
+ let failCount = 0;
1003
+ for (const meta of metaList) {
1004
+ if (!meta) continue;
1005
+ console.log(styleText("cyan", `─`.repeat(40)));
1006
+ console.log(styleText("white", `Updating: ${styleText(["cyan", "bold"], meta.name)}`));
1007
+ console.log(styleText("gray", ` Patches: ${formatPatches(meta.patches)}`));
1008
+ if (dryRun) {
1009
+ console.log(styleText("blue", ` [DRY RUN] Would re-apply patches`));
1010
+ successCount++;
1011
+ continue;
1012
+ }
1013
+ try {
1014
+ const patches = [];
1015
+ if (meta.patches.isCustom) patches.push({
1016
+ name: "isCustom",
1017
+ description: "Change isCustom:!0 to isCustom:!1",
1018
+ pattern: Buffer.from("isCustom:!0"),
1019
+ replacement: Buffer.from("isCustom:!1")
1020
+ });
1021
+ if (meta.patches.skipLogin) patches.push({
1022
+ name: "skipLogin",
1023
+ description: "Replace process.env.FACTORY_API_KEY with fake key",
1024
+ pattern: Buffer.from("process.env.FACTORY_API_KEY"),
1025
+ replacement: Buffer.from("\"fk-droid-patch-skip-00000\"")
1026
+ });
1027
+ if (meta.patches.apiBase) {
1028
+ const originalUrl = "https://api.factory.ai";
1029
+ const paddedUrl = meta.patches.apiBase.padEnd(originalUrl.length, " ");
1030
+ patches.push({
1031
+ name: "apiBase",
1032
+ description: `Replace Factory API URL with "${meta.patches.apiBase}"`,
1033
+ pattern: Buffer.from(originalUrl),
1034
+ replacement: Buffer.from(paddedUrl)
1035
+ });
1036
+ }
1037
+ const binsDir = join(homedir(), ".droid-patch", "bins");
1038
+ const outputPath = join(binsDir, `${meta.name}-patched`);
1039
+ if (patches.length > 0) {
1040
+ const result = await patchDroid({
1041
+ inputPath: newBinaryPath,
1042
+ outputPath,
1043
+ patches,
1044
+ dryRun: false,
1045
+ backup: false,
1046
+ verbose
1047
+ });
1048
+ if (!result.success) {
1049
+ console.log(styleText("red", ` ✗ Failed to apply patches`));
1050
+ failCount++;
1051
+ continue;
1052
+ }
1053
+ if (process.platform === "darwin") try {
1054
+ const { execSync } = await import("node:child_process");
1055
+ execSync(`codesign --force --deep --sign - "${outputPath}"`, { stdio: "pipe" });
1056
+ if (verbose) console.log(styleText("gray", ` Re-signed binary`));
1057
+ } catch {
1058
+ console.log(styleText("yellow", ` [!] Could not re-sign binary`));
1059
+ }
1060
+ }
1061
+ if (meta.patches.websearch) {
1062
+ const websearchDir = join(homedir(), ".droid-patch", "websearch");
1063
+ const targetBinaryPath = patches.length > 0 ? outputPath : newBinaryPath;
1064
+ await createWebSearchUnifiedFiles(websearchDir, targetBinaryPath, meta.name);
1065
+ if (verbose) console.log(styleText("gray", ` Regenerated websearch wrapper`));
1066
+ }
1067
+ meta.updatedAt = new Date().toISOString();
1068
+ meta.originalBinaryPath = newBinaryPath;
1069
+ await saveAliasMetadata(meta);
1070
+ console.log(styleText("green", ` ✓ Updated successfully`));
1071
+ successCount++;
1072
+ } catch (error) {
1073
+ console.log(styleText("red", ` ✗ Error: ${error.message}`));
1074
+ if (verbose) console.error(error.stack);
1075
+ failCount++;
1076
+ }
1077
+ }
1078
+ console.log();
1079
+ console.log(styleText("cyan", "═".repeat(60)));
1080
+ if (dryRun) {
1081
+ console.log(styleText(["blue", "bold"], " DRY RUN COMPLETE"));
1082
+ console.log(styleText("gray", ` Would update ${successCount} alias(es)`));
1083
+ } else if (failCount === 0) {
1084
+ console.log(styleText(["green", "bold"], " UPDATE COMPLETE"));
1085
+ console.log(styleText("gray", ` Updated ${successCount} alias(es)`));
1086
+ } else {
1087
+ console.log(styleText(["yellow", "bold"], " UPDATE FINISHED WITH ERRORS"));
1088
+ console.log(styleText("gray", ` Success: ${successCount}, Failed: ${failCount}`));
1089
+ }
1090
+ console.log(styleText("cyan", "═".repeat(60)));
944
1091
  }).run().catch((err) => {
945
1092
  console.error(err);
946
1093
  process.exit(1);
package/dist/cli.js.map CHANGED
@@ -1 +1 @@
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(\"Optional: Set Google PSE for better results:\");\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 } 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":";;;;;;;;;;;;;;;;;;;;AAk+BA,SAAS,4BAAoC;AAC3C,SAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6gBT;;;;;;;;AASD,SAAS,uBACPA,WACAC,iBACQ;AACR,SAAQ;;;;gBAIM,gBAAgB;aACnB,UAAU;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA2EtB;;;;;;;;;;AAWD,eAAsB,4BACpBC,WACAF,WACAG,WAC2D;AAC3D,MAAK,WAAW,UAAU,CACxB,OAAM,MAAM,WAAW,EAAE,WAAW,KAAM,EAAC;CAG7C,MAAM,kBAAkB,KAAK,YAAY,EAAE,UAAU,WAAW;CAChE,MAAM,oBAAoB,KAAK,WAAW,UAAU;AAGpD,OAAM,UAAU,iBAAiB,2BAA2B,CAAC;AAC7D,SAAQ,KAAK,4BAA4B,gBAAgB,EAAE;AAG3D,OAAM,UACJ,mBACA,uBAAuB,WAAW,gBAAgB,CACnD;AACD,OAAM,MAAM,mBAAmB,IAAM;AACrC,SAAQ,KAAK,uBAAuB,kBAAkB,EAAE;AAExD,QAAO;EACL,eAAe;EACf,eAAe;CAChB;AACF;;;;ACrmDD,MAAM,YAAY,QAAQ,cAAc,OAAO,KAAK,IAAI,CAAC;AAEzD,SAAS,aAAqB;AAC5B,KAAI;EACF,MAAM,UAAU,KAAK,WAAW,MAAM,eAAe;EACrD,MAAM,MAAM,KAAK,MAAM,aAAa,SAAS,QAAQ,CAAC;AACtD,SAAO,IAAI,WAAW;CACvB,QAAO;AACN,SAAO;CACR;AACF;AAED,MAAM,UAAU,YAAY;AAE5B,SAAS,uBAA+B;CACtC,MAAM,OAAO,SAAS;CACtB,MAAM,QAAQ;EACZ,KAAK,MAAM,mBAAmB;EAC9B;EACA;CACD;AACD,MAAK,MAAM,KAAK,MACd,KAAI,WAAW,EAAE,CAAE,QAAO;AAE5B,QAAO,KAAK,MAAM,mBAAmB;AACtC;AAED,IAAI,eAAe,4DAA4D,CAC5E,QAAQ,eAAe,QAAQ,CAC/B,OACC,eACA,kFACD,CACA,OACC,gBACA,iFACD,CACA,OACC,oBACA,wEACD,CACA,OACC,eACA,2EACD,CACA,OAAO,aAAa,uDAAuD,CAC3E,OAAO,qBAAqB,2BAA2B,CACvD,OAAO,sBAAsB,sCAAsC,CACnE,OAAO,eAAe,0CAA0C,CAChE,OAAO,iBAAiB,wBAAwB,CAChD,SAAS,WAAW,oCAAoC,CACxD,OAAO,OAAO,SAAS,SAAS;CAC/B,MAAM,QAAQ,OAAO;CACrB,MAAM,WAAW,QAAQ;CACzB,MAAM,YAAY,QAAQ;CAC1B,MAAM,UAAU,QAAQ;CACxB,MAAM,YAAY,QAAQ;CAC1B,MAAM,SAAS,QAAQ;CACvB,MAAM,OAAQ,QAAQ,QAAmB,sBAAsB;CAC/D,MAAM,YAAY,QAAQ;CAC1B,MAAM,SAAS,QAAQ,WAAW;CAClC,MAAM,UAAU,QAAQ;CAGxB,MAAM,aAAa,aAAa,QAAQ,KAAK,WAAW,MAAM;AAG9D,KAAI,cAAc,aAAa,cAAc,SAAS;AACpD,OAAK,OAAO;AACV,WAAQ,IACN,UAAU,OAAO,6CAA6C,CAC/D;AACD,WAAQ,IACN,UAAU,QAAQ,6CAA6C,CAChE;AACD,WAAQ,KAAK,EAAE;EAChB;AAED,UAAQ,IAAI,UAAU,QAAQ,IAAI,OAAO,GAAG,CAAC,CAAC;AAC9C,UAAQ,IAAI,UAAU,CAAC,QAAQ,MAAO,GAAE,0BAA0B,CAAC;AACnE,UAAQ,IAAI,UAAU,QAAQ,IAAI,OAAO,GAAG,CAAC,CAAC;AAC9C,UAAQ,KAAK;EAGb,MAAM,eAAe,KAAK,SAAS,EAAE,gBAAgB,YAAY;EACjE,MAAM,EAAE,eAAe,GAAG,MAAM,4BAC9B,cACA,MACA,MACD;AAGD,QAAM,sBAAsB,eAAe,OAAO,QAAQ;AAE1D,UAAQ,KAAK;AACb,UAAQ,IAAI,UAAU,SAAS,IAAI,OAAO,GAAG,CAAC,CAAC;AAC/C,UAAQ,IAAI,UAAU,CAAC,SAAS,MAAO,GAAE,qBAAqB,CAAC;AAC/D,UAAQ,IAAI,UAAU,SAAS,IAAI,OAAO,GAAG,CAAC,CAAC;AAC/C,UAAQ,KAAK;AACb,UAAQ,IAAI,gBAAgB;AAC5B,UAAQ,IAAI,UAAU,WAAW,IAAI,MAAM,EAAE,CAAC;AAC9C,UAAQ,KAAK;AACb,UAAQ,IAAI,UAAU,QAAQ,iBAAiB,CAAC;AAChD,UAAQ,IACN,UACE,QACA,sEACD,CACF;AACD,UAAQ,IACN,UAAU,QAAQ,kDAAkD,CACrE;AACD,UAAQ,KAAK;AACb,UAAQ,IAAI,wCAAwC;AACpD,UAAQ,IAAI,UAAU,UAAU,oCAAoC,CAAC;AACrE,UAAQ,IACN,UAAU,QAAQ,4CAA4C,CAC/D;AACD,UAAQ,IACN,UAAU,QAAQ,4CAA4C,CAC/D;AACD,UAAQ,IAAI,UAAU,QAAQ,mBAAmB,CAAC;AAClD,UAAQ,IACN,UAAU,QAAQ,8CAA8C,CACjE;AACD,UAAQ,IACN,UAAU,QAAQ,kDAAkD,CACrE;AACD,UAAQ,IACN,UACE,QACA,wDACD,CACF;AACD,UAAQ,KAAK;AACb,UAAQ,IAAI,cAAc;AAC1B,UAAQ,IAAI,UAAU,QAAQ,gCAAgC,CAAC;AAC/D;CACD;AAED,MAAK,aAAa,cAAc,YAAY,WAAW;AACrD,UAAQ,IACN,UAAU,UAAU,+CAA+C,CACpE;AACD,UAAQ,IACN,UAAU,QAAQ,oDAAoD,CACvE;AACD,UAAQ,IACN,UACE,QACA,4DACD,CACF;AACD,UAAQ,IACN,UACE,QACA,8DACD,CACF;AACD,UAAQ,IACN,UACE,QACA,oEACD,CACF;AACD,UAAQ,KAAK;AACb,UAAQ,IAAI,kBAAkB;AAC9B,UAAQ,IACN,UAAU,QAAQ,6CAA6C,CAChE;AACD,UAAQ,IACN,UAAU,QAAQ,+CAA+C,CAClE;AACD,UAAQ,IACN,UACE,QACA,2DACD,CACF;AACD,UAAQ,IACN,UAAU,QAAQ,+CAA+C,CAClE;AACD,UAAQ,IACN,UACE,QACA,iEACD,CACF;AACD,UAAQ,IACN,UAAU,QAAQ,6CAA6C,CAChE;AACD,UAAQ,KAAK,EAAE;CAChB;AAED,MAAK,UAAU,QAAQ;AACrB,UAAQ,IAAI,UAAU,OAAO,gCAAgC,CAAC;AAC9D,UAAQ,IACN,UACE,QACA,0EACD,CACF;AACD,UAAQ,KAAK,EAAE;CAChB;AAED,SAAQ,IAAI,UAAU,QAAQ,IAAI,OAAO,GAAG,CAAC,CAAC;AAC9C,SAAQ,IAAI,UAAU,CAAC,QAAQ,MAAO,GAAE,yBAAyB,CAAC;AAClE,SAAQ,IAAI,UAAU,QAAQ,IAAI,OAAO,GAAG,CAAC,CAAC;AAC9C,SAAQ,KAAK;CAEb,MAAMI,UAAmB,CAAE;AAC3B,KAAI,SACF,SAAQ,KAAK;EACX,MAAM;EACN,aAAa;EACb,SAAS,OAAO,KAAK,cAAc;EACnC,aAAa,OAAO,KAAK,cAAc;CACxC,EAAC;AAKJ,KAAI,UACF,SAAQ,KAAK;EACX,MAAM;EACN,aACE;EACF,SAAS,OAAO,KAAK,8BAA8B;EACnD,aAAa,OAAO,KAAK,gCAA8B;CACxD,EAAC;AAMJ,KAAI,SAAS;EACX,MAAM,cAAc;EACpB,MAAM,iBAAiB,YAAY;EAGnC,IAAI,gBAAgB,QAAQ,QAAQ,QAAQ,GAAG;AAE/C,MAAI,cAAc,SAAS,gBAAgB;AACzC,WAAQ,IACN,UACE,QACC,8BAA8B,eAAe,qBAC/C,CACF;AACD,WAAQ,IACN,UACE,SACC,eAAe,cAAc,KAAK,cAAc,OAAO,SACzD,CACF;AACD,WAAQ,IACN,UAAU,SAAS,cAAc,eAAe,aAAa,CAC9D;AACD,WAAQ,KAAK;AACb,WAAQ,IACN,UACE,UACA,qDACD,CACF;AACD,WAAQ,IAAI,UAAU,QAAQ,cAAc,CAAC;AAC7C,WAAQ,IAAI,UAAU,QAAQ,uCAAuC,CAAC;AACtE,WAAQ,IAAI,UAAU,QAAQ,sCAAsC,CAAC;AACrE,WAAQ,KAAK,EAAE;EAChB;EAID,MAAM,YAAY,cAAc,OAAO,gBAAgB,IAAI;AAE3D,UAAQ,KAAK;GACX,MAAM;GACN,cAAc,gCAAgC,cAAc;GAC5D,SAAS,OAAO,KAAK,YAAY;GACjC,aAAa,OAAO,KAAK,UAAU;EACpC,EAAC;CACH;AAED,KAAI;EACF,MAAM,SAAS,MAAM,WAAW;GAC9B,WAAW;GACC;GACZ;GACA;GACA;GACA;EACD,EAAC;AAEF,MAAI,QAAQ;AACV,WAAQ,KAAK;AACb,WAAQ,IAAI,UAAU,QAAQ,IAAI,OAAO,GAAG,CAAC,CAAC;AAC9C,WAAQ,IAAI,UAAU,CAAC,QAAQ,MAAO,GAAE,qBAAqB,CAAC;AAC9D,WAAQ,IAAI,UAAU,QAAQ,IAAI,OAAO,GAAG,CAAC,CAAC;AAC9C,WAAQ,KAAK;AACb,WAAQ,IACN,UAAU,QAAQ,+CAA+C,CAClE;AACD,WAAQ,IACN,UACE,SACC,gCAAgC,SAAS,eAAe,EAC1D,CACF;AACD,WAAQ,KAAK,EAAE;EAChB;AAGD,MAAI,aAAa,OAAO,WAAW,OAAO,YAAY;AACpD,WAAQ,KAAK;AACb,WAAQ,IAAI,UAAU,SAAS,IAAI,OAAO,GAAG,CAAC,CAAC;AAC/C,WAAQ,IAAI,UAAU,CAAC,SAAS,MAAO,GAAE,qBAAqB,CAAC;AAC/D,WAAQ,IAAI,UAAU,SAAS,IAAI,OAAO,GAAG,CAAC,CAAC;AAC/C,WAAQ,KAAK;AACb,WAAQ,IACN,UAAU,UAAU,2BAA2B,OAAO,WAAW,EAAE,CACpE;AACD,WAAQ,KAAK,EAAE;EAChB;AAED,MAAI,OAAO,WAAW,OAAO,cAAc,OAAO;AAChD,WAAQ,KAAK;AAGb,OAAI,WAAW;IACb,MAAM,eAAe,KAAK,SAAS,EAAE,gBAAgB,YAAY;IACjE,MAAM,EAAE,eAAe,GAAG,MAAM,4BAC9B,cACA,OAAO,YACP,MACD;AACD,UAAM,sBAAsB,eAAe,OAAO,QAAQ;AAE1D,YAAQ,KAAK;AACb,YAAQ,IAAI,+CAA+C;AAC3D,YAAQ,IACN,UAAU,QAAQ,2CAA2C,CAC9D;AACD,YAAQ,IACN,UAAU,QAAQ,+CAA+C,CAClE;GACF,MACC,OAAM,YAAY,OAAO,YAAY,OAAO,QAAQ;EAEvD;AAED,MAAI,OAAO,SAAS;AAClB,WAAQ,KAAK;AACb,WAAQ,IAAI,UAAU,SAAS,IAAI,OAAO,GAAG,CAAC,CAAC;AAC/C,WAAQ,IAAI,UAAU,CAAC,SAAS,MAAO,GAAE,qBAAqB,CAAC;AAC/D,WAAQ,IAAI,UAAU,SAAS,IAAI,OAAO,GAAG,CAAC,CAAC;EAChD;AAED,UAAQ,KAAK,OAAO,UAAU,IAAI,EAAE;CACrC,SAAQ,OAAO;AACd,UAAQ,MAAM,UAAU,QAAQ,SAAU,MAAgB,QAAQ,EAAE,CAAC;AACrE,MAAI,QAAS,SAAQ,MAAO,MAAgB,MAAM;AAClD,UAAQ,KAAK,EAAE;CAChB;AACF,EAAC,CACD,QAAQ,QAAQ,+BAA+B,CAC/C,OAAO,YAAY;AAClB,OAAM,aAAa;AACpB,EAAC,CACD,QAAQ,UAAU,oDAAoD,CACtE,SAAS,mBAAmB,oCAAoC,CAChE,OAAO,OAAO,UAAU,SAAS;CAChC,MAAM,SAAS,KAAK;AAEpB,KAAI,OAAO,SAAS,IAAI,IAAI,WAAW,OAAO,EAAE;EAE9C,MAAM,EAAE,kBAAQ,GAAG,MAAM,OAAO;AAChC,MAAI;AACF,SAAM,SAAO,OAAO;AACpB,WAAQ,IAAI,UAAU,UAAU,eAAe,OAAO,EAAE,CAAC;EAC1D,SAAQ,OAAO;AACd,WAAQ,MAAM,UAAU,QAAQ,SAAU,MAAgB,QAAQ,EAAE,CAAC;AACrE,WAAQ,KAAK,EAAE;EAChB;CACF,MAEC,OAAM,YAAY,OAAO;AAE5B,EAAC,CACD,QAAQ,WAAW,4BAA4B,CAC/C,OAAO,MAAM;AACZ,SAAQ,KAAK,eAAe,QAAQ,EAAE;AACvC,EAAC,CACD,QAAQ,gBAAgB,+BAA+B,CACvD,OAAO,YAAY;CAClB,MAAM,UAAU;CAChB,MAAM,UAAU;CAChB,MAAM,OAAO;AAEb,SAAQ,IAAI,UAAU,QAAQ,IAAI,OAAO,GAAG,CAAC,CAAC;AAC9C,SAAQ,IAAI,UAAU,CAAC,QAAQ,MAAO,GAAE,2BAA2B,CAAC;AACpE,SAAQ,IAAI,UAAU,QAAQ,IAAI,OAAO,GAAG,CAAC,CAAC;AAC9C,SAAQ,KAAK;AAGb,KAAI;EACF,MAAM,WAAW,MAAM,OAAO,mBAAmB,KAAK,SAAS;AAC/D,MAAI,SAAS,IAAI;GACf,MAAM,OAAQ,MAAM,SAAS,MAAM;AAQnC,WAAQ,IAAI,UAAU,UAAU,qBAAqB,CAAC;AACtD,WAAQ,IAAI,UAAU,UAAU,UAAU,KAAK,EAAE,CAAC;AAElD,OAAI,WAAW,QAAQ,EAAE;IACvB,MAAM,EAAE,8BAAc,GAAG,MAAM,OAAO;IACtC,MAAM,MAAM,eAAa,SAAS,QAAQ,CAAC,MAAM;AACjD,YAAQ,IAAI,UAAU,UAAU,SAAS,IAAI,EAAE,CAAC;GACjD;AAGD,OAAI,KAAK,wBACP,SAAQ,IACN,UACE,UACC,mBAAmB,KAAK,eAAe,gCAAgC,KAAK,EAC9E,CACF;AAIH,OAAI,KAAK,uBACP,KAAI,KAAK,cAAc,GAAG;IACxB,MAAM,WAAW,KAAK,OAAO,KAAK,eAAe,KAAK,GAAG;IACzD,MAAM,YAAY,KAAK,eAAe,KAAK;AAC3C,QAAI,KAAK,aACP,SAAQ,IACN,UACE,UACC,UAAU,SAAS,IAAI,SAAS,qCAClC,CACF;aACQ,KAAK,mBAAmB,MAAM;KACvC,MAAM,eAAe,KAAK,OAAO,KAAK,kBAAkB,KAAK,GAAG;KAChE,MAAM,gBAAgB,KAAK,kBAAkB,KAAK;AAClD,aAAQ,IACN,UAAU,UAAU,UAAU,SAAS,IAAI,SAAS,GAAG,CACxD;AACD,aAAQ,IACN,UACE,UACC,sBAAsB,aAAa,IAAI,aAAa,GACtD,CACF;IACF;GACF,MACC,SAAQ,IAAI,UAAU,UAAU,2BAA2B,CAAC;AAIhE,WAAQ,IAAI,UAAU,UAAU,SAAS,QAAQ,EAAE,CAAC;AACpD,WAAQ,KAAK;AACb,WAAQ,IAAI,UAAU,QAAQ,8BAA8B,CAAC;AAC7D,WAAQ,IAAI,UAAU,QAAQ,+BAA+B,CAAC;AAC9D,WAAQ,KAAK;AACb,WAAQ,IAAI,UAAU,QAAQ,4BAA4B,CAAC;AAC3D,WAAQ,IAAI,UAAU,QAAQ,sCAAsC,CAAC;EACtE;CACF,QAAO;AACN,UAAQ,IAAI,UAAU,WAAW,uBAAuB,CAAC;AACzD,UAAQ,KAAK;AACb,UAAQ,IACN,UACE,QACA,8DACD,CACF;AACD,UAAQ,IACN,UACE,QACA,gEACD,CACF;CACF;AACD,SAAQ,KAAK;AACd,EAAC,CACD,QAAQ,cAAc,2BAA2B,CACjD,OAAO,YAAY;CAClB,MAAM,UAAU;AAEhB,MAAK,WAAW,QAAQ,EAAE;AACxB,UAAQ,IAAI,UAAU,UAAU,qCAAqC,CAAC;AACtE;CACD;AAED,KAAI;EACF,MAAM,EAAE,8BAAc,0BAAY,GAAG,MAAM,OAAO;EAClD,MAAM,MAAM,eAAa,SAAS,QAAQ,CAAC,MAAM;AAEjD,UAAQ,KAAK,SAAS,IAAI,EAAE,UAAU;AACtC,eAAW,QAAQ;AAEnB,UAAQ,IAAI,UAAU,UAAU,0BAA0B,IAAI,GAAG,CAAC;CACnE,SAAQ,OAAO;AACd,UAAQ,IACN,UACE,WACC,4BAA6B,MAAgB,QAAQ,EACvD,CACF;AAGD,MAAI;GACF,MAAM,EAAE,0BAAY,GAAG,MAAM,OAAO;AACpC,gBAAW,QAAQ;AACnB,WAAQ,IAAI,UAAU,QAAQ,4BAA4B,CAAC;EAC5D,QAAO,CAAE;CACX;AACF,EAAC,CACD,QAAQ,aAAa,4BAA4B,CACjD,OAAO,YAAY;CAClB,MAAM,UAAU;AAEhB,MAAK,WAAW,QAAQ,EAAE;AACxB,UAAQ,IAAI,UAAU,UAAU,oBAAoB,CAAC;AACrD;CACD;CAED,MAAM,EAAE,8BAAc,GAAG,MAAM,OAAO;CACtC,MAAM,MAAM,eAAa,SAAS,QAAQ;CAC1C,MAAM,QAAQ,IAAI,MAAM,KAAK,CAAC,MAAA,IAAU;AAExC,SAAQ,IAAI,UAAU,QAAQ,IAAI,OAAO,GAAG,CAAC,CAAC;AAC9C,SAAQ,IACN,UAAU,CAAC,QAAQ,MAAO,GAAE,yCAAyC,CACtE;AACD,SAAQ,IAAI,UAAU,QAAQ,IAAI,OAAO,GAAG,CAAC,CAAC;AAC9C,SAAQ,KAAK;AACb,SAAQ,IAAI,MAAM,KAAK,KAAK,CAAC;AAC9B,EAAC,CACD,KAAK,CACL,MAAM,CAACC,QAAe;AACrB,SAAQ,MAAM,IAAI;AAClB,SAAQ,KAAK,EAAE;AAChB,EAAC"}
1
+ {"version":3,"file":"cli.js","names":["droidPath: string","proxyScriptPath: string","outputDir: string","aliasName: string","preloadScriptPath: string","bunfigDir: string","droidDir: string","patches: Patch[]","metaList: Awaited<ReturnType<typeof loadAliasMetadata>>[]","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\";\nimport {\n saveAliasMetadata,\n createMetadata,\n loadAliasMetadata,\n listAllMetadata,\n formatPatches,\n} from \"./metadata.ts\";\n\nconst __dirname = dirname(fileURLToPath(import.meta.url));\n\nfunction getVersion(): string {\n try {\n const pkgPath = join(__dirname, \"..\", \"package.json\");\n const pkg = JSON.parse(readFileSync(pkgPath, \"utf-8\"));\n return pkg.version || \"0.0.0\";\n } catch {\n return \"0.0.0\";\n }\n}\n\nconst version = getVersion();\n\nfunction 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 // Save metadata for update command\n const metadata = createMetadata(alias, path, {\n isCustom: false,\n skipLogin: false,\n apiBase: null,\n websearch: true,\n });\n await saveAliasMetadata(metadata);\n\n console.log();\n console.log(styleText(\"green\", \"═\".repeat(60)));\n console.log(styleText([\"green\", \"bold\"], \" WebSearch Ready!\"));\n console.log(styleText(\"green\", \"═\".repeat(60)));\n console.log();\n console.log(\"Run directly:\");\n console.log(styleText(\"yellow\", ` ${alias}`));\n console.log();\n console.log(styleText(\"cyan\", \"Auto-shutdown:\"));\n console.log(\n styleText(\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 // Save metadata for update command\n const metadata = createMetadata(alias, path, {\n isCustom: !!isCustom,\n skipLogin: !!skipLogin,\n apiBase: apiBase || null,\n websearch: !!webSearch,\n });\n await saveAliasMetadata(metadata);\n }\n\n if (result.success) {\n console.log();\n console.log(styleText(\"green\", \"═\".repeat(60)));\n console.log(styleText([\"green\", \"bold\"], \" PATCH SUCCESSFUL\"));\n console.log(styleText(\"green\", \"═\".repeat(60)));\n }\n\n process.exit(result.success ? 0 : 1);\n } catch (error) {\n console.error(styleText(\"red\", `Error: ${(error as Error).message}`));\n if (verbose) console.error((error as Error).stack);\n process.exit(1);\n }\n })\n .command(\"list\", \"List all droid-patch aliases\")\n .action(async () => {\n await listAliases();\n })\n .command(\"remove\", \"Remove 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 .command(\"update\", \"Update aliases with latest droid binary\")\n .argument(\n \"[alias]\",\n \"Specific alias to update (optional, updates all if not specified)\",\n )\n .option(\"--dry-run\", \"Preview without making changes\")\n .option(\"-p, --path <path>\", \"Path to new droid binary\")\n .option(\"-v, --verbose\", \"Enable verbose output\")\n .action(async (options, args) => {\n const aliasName = args?.[0] as string | undefined;\n const dryRun = options[\"dry-run\"] as boolean;\n const newBinaryPath = (options.path as string) || findDefaultDroidPath();\n const verbose = options.verbose as boolean;\n\n console.log(styleText(\"cyan\", \"═\".repeat(60)));\n console.log(styleText([\"cyan\", \"bold\"], \" Droid-Patch Update\"));\n console.log(styleText(\"cyan\", \"═\".repeat(60)));\n console.log();\n\n // Verify the new binary exists\n if (!existsSync(newBinaryPath)) {\n console.log(\n styleText(\"red\", `Error: Droid binary not found at ${newBinaryPath}`),\n );\n console.log(styleText(\"gray\", \"Use -p to specify a different path\"));\n process.exit(1);\n }\n\n // Get aliases to update\n let metaList: Awaited<ReturnType<typeof loadAliasMetadata>>[];\n if (aliasName) {\n const meta = await loadAliasMetadata(aliasName);\n if (!meta) {\n console.log(\n styleText(\"red\", `Error: No metadata found for alias \"${aliasName}\"`),\n );\n console.log(\n styleText(\n \"gray\",\n \"This alias may have been created before update tracking was added.\",\n ),\n );\n console.log(\n styleText(\n \"gray\",\n \"Remove and recreate the alias to enable update support.\",\n ),\n );\n process.exit(1);\n }\n metaList = [meta];\n } else {\n metaList = await listAllMetadata();\n if (metaList.length === 0) {\n console.log(styleText(\"yellow\", \"No aliases with metadata found.\"));\n console.log(\n styleText(\n \"gray\",\n \"Create aliases with droid-patch to enable update support.\",\n ),\n );\n process.exit(0);\n }\n }\n\n console.log(styleText(\"white\", `Using droid binary: ${newBinaryPath}`));\n console.log(\n styleText(\"white\", `Found ${metaList.length} alias(es) to update`),\n );\n if (dryRun) {\n console.log(styleText(\"blue\", \"(DRY RUN - no changes will be made)\"));\n }\n console.log();\n\n let successCount = 0;\n let failCount = 0;\n\n for (const meta of metaList) {\n if (!meta) continue;\n\n console.log(styleText(\"cyan\", `─`.repeat(40)));\n console.log(\n styleText(\n \"white\",\n `Updating: ${styleText([\"cyan\", \"bold\"], meta.name)}`,\n ),\n );\n console.log(\n styleText(\"gray\", ` Patches: ${formatPatches(meta.patches)}`),\n );\n\n if (dryRun) {\n console.log(styleText(\"blue\", ` [DRY RUN] Would re-apply patches`));\n successCount++;\n continue;\n }\n\n try {\n // Build patch list based on metadata\n const patches: Patch[] = [];\n\n if (meta.patches.isCustom) {\n patches.push({\n name: \"isCustom\",\n description: \"Change isCustom:!0 to isCustom:!1\",\n pattern: Buffer.from(\"isCustom:!0\"),\n replacement: Buffer.from(\"isCustom:!1\"),\n });\n }\n\n if (meta.patches.skipLogin) {\n patches.push({\n name: \"skipLogin\",\n description: \"Replace process.env.FACTORY_API_KEY with fake key\",\n pattern: Buffer.from(\"process.env.FACTORY_API_KEY\"),\n replacement: Buffer.from('\"fk-droid-patch-skip-00000\"'),\n });\n }\n\n if (meta.patches.apiBase) {\n const originalUrl = \"https://api.factory.ai\";\n const paddedUrl = meta.patches.apiBase.padEnd(\n originalUrl.length,\n \" \",\n );\n patches.push({\n name: \"apiBase\",\n description: `Replace Factory API URL with \"${meta.patches.apiBase}\"`,\n pattern: Buffer.from(originalUrl),\n replacement: Buffer.from(paddedUrl),\n });\n }\n\n // Determine output path based on whether this is a websearch alias\n const binsDir = join(homedir(), \".droid-patch\", \"bins\");\n const outputPath = join(binsDir, `${meta.name}-patched`);\n\n // Apply patches (only if there are binary patches to apply)\n if (patches.length > 0) {\n const result = await patchDroid({\n inputPath: newBinaryPath,\n outputPath,\n patches,\n dryRun: false,\n backup: false,\n verbose,\n });\n\n if (!result.success) {\n console.log(styleText(\"red\", ` ✗ Failed to apply patches`));\n failCount++;\n continue;\n }\n\n // Re-sign on macOS\n if (process.platform === \"darwin\") {\n try {\n const { execSync } = await import(\"node:child_process\");\n execSync(`codesign --force --deep --sign - \"${outputPath}\"`, {\n stdio: \"pipe\",\n });\n if (verbose) {\n console.log(styleText(\"gray\", ` Re-signed binary`));\n }\n } catch {\n console.log(\n styleText(\"yellow\", ` [!] Could not re-sign binary`),\n );\n }\n }\n }\n\n // If websearch is enabled, regenerate wrapper files\n if (meta.patches.websearch) {\n const websearchDir = join(homedir(), \".droid-patch\", \"websearch\");\n const targetBinaryPath =\n patches.length > 0 ? outputPath : newBinaryPath;\n await createWebSearchUnifiedFiles(\n websearchDir,\n targetBinaryPath,\n meta.name,\n );\n if (verbose) {\n console.log(styleText(\"gray\", ` Regenerated websearch wrapper`));\n }\n }\n\n // Update metadata\n meta.updatedAt = new Date().toISOString();\n meta.originalBinaryPath = newBinaryPath;\n await saveAliasMetadata(meta);\n\n console.log(styleText(\"green\", ` ✓ Updated successfully`));\n successCount++;\n } catch (error) {\n console.log(styleText(\"red\", ` ✗ Error: ${(error as Error).message}`));\n if (verbose) {\n console.error((error as Error).stack);\n }\n failCount++;\n }\n }\n\n console.log();\n console.log(styleText(\"cyan\", \"═\".repeat(60)));\n if (dryRun) {\n console.log(styleText([\"blue\", \"bold\"], \" DRY RUN COMPLETE\"));\n console.log(\n styleText(\"gray\", ` Would update ${successCount} alias(es)`),\n );\n } else if (failCount === 0) {\n console.log(styleText([\"green\", \"bold\"], \" UPDATE COMPLETE\"));\n console.log(styleText(\"gray\", ` Updated ${successCount} alias(es)`));\n } else {\n console.log(\n styleText([\"yellow\", \"bold\"], \" UPDATE FINISHED WITH ERRORS\"),\n );\n console.log(\n styleText(\"gray\", ` Success: ${successCount}, Failed: ${failCount}`),\n );\n }\n console.log(styleText(\"cyan\", \"═\".repeat(60)));\n })\n .run()\n .catch((err: Error) => {\n console.error(err);\n process.exit(1);\n });\n"],"mappings":"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"}
@@ -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;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"}
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;;;;EC+GO,OAAA,EAAA,OAAA;EAMK,cAAW,CAAA,EAAA,OAAA;;AAItB,UDpIM,gBAAA,CCoIN;EAAiB,OAAzB,EAAA,OAAA;EAAO,MAAA,CAAA,EAAA,OAAA;EAwMY,OAAA,EDzUX,WCyUsB,EAAA;EAuFX,UAAA,CAAA,EAAA,MAAW;EAiHhB,aAAA,CAAA,EAAA,OAAA;EAKK,YAAA,CAAA,EAAA,MAAe;;AAI1B,iBDphBW,UAAA,CCohBX,OAAA,EDnhBA,YCmhBA,CAAA,EDlhBR,OCkhBQ,CDlhBA,gBCkhBA,CAAA,CAAA;;ADrjBM,UCkJA,iBAAA,CDlJK;EAAA,SAAA,EAAA,MAAA;EAAA,UAGX,EAAA,MAAA;EAAM,SACF,CAAA,EAAA,OAAA;AAAM;AAGJ,iBCiJK,WAAA,CD9IN,iBAAA,EAAA,MAAA,EAAA,SAAA,EAAA,MAAA,EAAA,OAAA,CAAA,EAAA,OAAA,CAAA,ECkJb,ODlJa,CCkJL,iBDlJK,CAAA;AAMN,iBCoVY,WAAA,CDpVD,SAAA,EAAA,MAAA,CAAA,ECoViC,ODpVjC,CAAA,IAAA,CAAA;AAQJ,iBCmaK,WAAA,CAAA,CDhaX,ECga0B,ODhaf,CAAA,IAAA,CAAA;AAMA,UC2gBL,qBAAA,CD3gBe;EAAA,YAAA,EAAA,MAAA;EAAA,UACrB,EAAA,MAAA;;AACR,iBC8gBmB,eAAA,CD9gBnB,iBAAA,EAAA,MAAA,EAAA,YAAA,EAAA,MAAA,EAAA,OAAA,CAAA,EAAA,OAAA,CAAA,ECkhBA,ODlhBA,CCkhBQ,qBDlhBR,CAAA;AAAO;;;;AC+GV;;AAMiC,iBA+nBX,eAAA,CA/nBW,YAAA,EAAA,MAAA,CAAA,EA+nB4B,OA/nB5B,CAAA,IAAA,CAAA"}
package/dist/index.js CHANGED
@@ -1,3 +1,3 @@
1
- import { createAlias, listAliases, patchDroid, removeAlias, replaceOriginal, restoreOriginal } from "./alias-C9LRaTwF.js";
1
+ import { createAlias, listAliases, patchDroid, removeAlias, replaceOriginal, restoreOriginal } from "./alias-Ols294ja.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.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "CLI tool to patch droid binary with various modifications",
5
5
  "homepage": "https://github.com/kingsword09/droid-patch#readme",
6
6
  "bugs": {
@@ -1 +0,0 @@
1
- {"version":3,"file":"alias-C9LRaTwF.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","wrapperPath: 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\nexport function findWritablePathDir(): string | null {\n const pathEnv = process.env.PATH || \"\";\n const pathDirs = pathEnv.split(\":\");\n\n for (const dir of COMMON_PATH_DIRS) {\n if (pathDirs.includes(dir)) {\n try {\n if (!existsSync(dir)) {\n mkdirSync(dir, { recursive: true });\n }\n const testFile = join(dir, `.droid-patch-test-${Date.now()}`);\n writeFileSync(testFile, \"\");\n unlinkSync(testFile);\n return dir;\n } catch {\n continue;\n }\n }\n }\n\n return null;\n}\n\nfunction getShellConfigPath(): string {\n const shell = process.env.SHELL || \"/bin/bash\";\n const shellName = basename(shell);\n\n switch (shellName) {\n case \"zsh\":\n return join(homedir(), \".zshrc\");\n case \"bash\": {\n const bashProfile = join(homedir(), \".bash_profile\");\n if (existsSync(bashProfile)) return bashProfile;\n return join(homedir(), \".bashrc\");\n }\n case \"fish\":\n return join(homedir(), \".config/fish/config.fish\");\n default:\n return join(homedir(), \".profile\");\n }\n}\n\nfunction isPathConfigured(shellConfigPath: string): boolean {\n if (!existsSync(shellConfigPath)) {\n return false;\n }\n\n try {\n const content = readFileSync(shellConfigPath, \"utf-8\");\n return (\n content.includes(\".droid-patch/aliases\") ||\n content.includes(\"droid-patch/aliases\")\n );\n } catch {\n return false;\n }\n}\n\nfunction addPathToShellConfig(\n shellConfigPath: string,\n verbose = false,\n): boolean {\n const shell = process.env.SHELL || \"/bin/bash\";\n const shellName = basename(shell);\n\n let exportLine: string;\n if (shellName === \"fish\") {\n exportLine = `\\n# Added by droid-patch\\nfish_add_path \"${ALIASES_DIR}\"\\n`;\n } else {\n exportLine = `\\n# Added by droid-patch\\nexport PATH=\"${ALIASES_DIR}:$PATH\"\\n`;\n }\n\n try {\n appendFileSync(shellConfigPath, exportLine);\n if (verbose) {\n console.log(\n styleText(\"gray\", ` Added PATH export to: ${shellConfigPath}`),\n );\n }\n return true;\n } catch (error) {\n console.log(\n styleText(\n \"yellow\",\n `[!] Could not write to ${shellConfigPath}: ${(error as Error).message}`,\n ),\n );\n return false;\n }\n}\n\nexport interface CreateAliasResult {\n aliasPath: string;\n binaryPath: string;\n immediate?: boolean;\n}\n\nexport async function createAlias(\n patchedBinaryPath: string,\n aliasName: string,\n verbose = false,\n): Promise<CreateAliasResult> {\n ensureDirectories();\n\n console.log(\n styleText(\"white\", `[*] Creating alias: ${styleText(\"cyan\", aliasName)}`),\n );\n\n const writablePathDir = findWritablePathDir();\n\n if (writablePathDir) {\n const targetPath = join(writablePathDir, aliasName);\n const binaryDest = join(BINS_DIR, `${aliasName}-patched`);\n await copyFile(patchedBinaryPath, binaryDest);\n await chmod(binaryDest, 0o755);\n\n if (verbose) {\n console.log(styleText(\"gray\", ` Stored binary: ${binaryDest}`));\n }\n\n if (existsSync(targetPath)) {\n await unlink(targetPath);\n if (verbose) {\n console.log(styleText(\"gray\", ` Removed existing: ${targetPath}`));\n }\n }\n\n await symlink(binaryDest, targetPath);\n\n if (process.platform === \"darwin\") {\n try {\n console.log(styleText(\"gray\", \"[*] Re-signing binary for macOS...\"));\n execSync(`codesign --force --deep --sign - \"${binaryDest}\"`, {\n stdio: \"pipe\",\n });\n console.log(styleText(\"green\", \"[*] Binary re-signed successfully\"));\n } catch {\n console.log(styleText(\"yellow\", \"[!] Could not re-sign binary\"));\n }\n\n try {\n execSync(`xattr -cr \"${binaryDest}\"`, { stdio: \"pipe\" });\n } catch {\n // Ignore\n }\n }\n\n console.log(\n styleText(\"green\", `[*] Created: ${targetPath} -> ${binaryDest}`),\n );\n console.log();\n console.log(styleText(\"green\", \"─\".repeat(60)));\n console.log(\n styleText([\"green\", \"bold\"], \" ALIAS READY - NO ACTION REQUIRED!\"),\n );\n console.log(styleText(\"green\", \"─\".repeat(60)));\n console.log();\n console.log(\n styleText(\n \"white\",\n `The alias \"${styleText([\"cyan\", \"bold\"], aliasName)}\" is now available in ALL terminals.`,\n ),\n );\n console.log(styleText(\"gray\", `(Installed to: ${writablePathDir})`));\n\n return {\n aliasPath: targetPath,\n binaryPath: binaryDest,\n immediate: true,\n };\n }\n\n console.log(\n styleText(\n \"yellow\",\n \"[*] No writable PATH directory found, using fallback...\",\n ),\n );\n\n const binaryDest = join(BINS_DIR, `${aliasName}-patched`);\n await copyFile(patchedBinaryPath, binaryDest);\n await chmod(binaryDest, 0o755);\n\n if (verbose) {\n console.log(styleText(\"gray\", ` Copied binary to: ${binaryDest}`));\n }\n\n if (process.platform === \"darwin\") {\n try {\n console.log(styleText(\"gray\", \"[*] Re-signing binary for macOS...\"));\n execSync(`codesign --force --deep --sign - \"${binaryDest}\"`, {\n stdio: \"pipe\",\n });\n console.log(styleText(\"green\", \"[*] Binary re-signed successfully\"));\n } catch {\n console.log(\n styleText(\n \"yellow\",\n \"[!] Could not re-sign binary. You may need to do this manually:\",\n ),\n );\n console.log(\n styleText(\n \"gray\",\n ` codesign --force --deep --sign - \"${binaryDest}\"`,\n ),\n );\n }\n\n try {\n execSync(`xattr -cr \"${binaryDest}\"`, { stdio: \"pipe\" });\n } catch {\n // Ignore\n }\n }\n\n const symlinkPath = join(ALIASES_DIR, aliasName);\n\n if (existsSync(symlinkPath)) {\n await unlink(symlinkPath);\n if (verbose) {\n console.log(styleText(\"gray\", ` Removed existing symlink`));\n }\n }\n\n await symlink(binaryDest, symlinkPath);\n await chmod(symlinkPath, 0o755);\n\n console.log(\n styleText(\"green\", `[*] Created symlink: ${symlinkPath} -> ${binaryDest}`),\n );\n\n const shellConfig = getShellConfigPath();\n\n if (!checkPathInclusion()) {\n if (!isPathConfigured(shellConfig)) {\n console.log(\n styleText(\"white\", `[*] Configuring PATH in ${shellConfig}...`),\n );\n\n if (addPathToShellConfig(shellConfig, verbose)) {\n console.log(styleText(\"green\", `[*] PATH configured successfully!`));\n console.log();\n console.log(styleText(\"yellow\", \"─\".repeat(60)));\n console.log(styleText([\"yellow\", \"bold\"], \" ACTION REQUIRED\"));\n console.log(styleText(\"yellow\", \"─\".repeat(60)));\n console.log();\n console.log(\n styleText(\"white\", \"To use the alias in this terminal, run:\"),\n );\n console.log();\n console.log(styleText(\"cyan\", ` source ${shellConfig}`));\n console.log();\n console.log(styleText(\"gray\", \"Or simply open a new terminal window.\"));\n console.log(styleText(\"yellow\", \"─\".repeat(60)));\n } else {\n const exportLine = `export PATH=\"${ALIASES_DIR}:$PATH\"`;\n console.log();\n console.log(styleText(\"yellow\", \"─\".repeat(60)));\n console.log(\n styleText([\"yellow\", \"bold\"], \" Manual PATH Configuration Required\"),\n );\n console.log(styleText(\"yellow\", \"─\".repeat(60)));\n console.log();\n console.log(styleText(\"white\", \"Add this line to your shell config:\"));\n console.log(styleText(\"cyan\", ` ${exportLine}`));\n console.log();\n console.log(styleText(\"gray\", `Shell config file: ${shellConfig}`));\n console.log(styleText(\"yellow\", \"─\".repeat(60)));\n }\n } else {\n console.log(\n styleText(\"green\", `[*] PATH already configured in ${shellConfig}`),\n );\n console.log();\n console.log(\n styleText(\n \"yellow\",\n `Note: Run \\`source ${shellConfig}\\` or open a new terminal to use the alias.`,\n ),\n );\n }\n } else {\n console.log(\n styleText(\"green\", `[*] PATH already includes aliases directory`),\n );\n console.log();\n console.log(\n styleText(\n \"green\",\n `You can now use \"${styleText([\"cyan\", \"bold\"], aliasName)}\" command directly!`,\n ),\n );\n }\n\n return {\n aliasPath: symlinkPath,\n binaryPath: binaryDest,\n };\n}\n\nexport async function removeAlias(aliasName: string): Promise<void> {\n console.log(\n styleText(\"white\", `[*] Removing alias: ${styleText(\"cyan\", aliasName)}`),\n );\n\n let removed = false;\n\n // Check common PATH directories for symlinks\n for (const pathDir of COMMON_PATH_DIRS) {\n const pathSymlink = join(pathDir, aliasName);\n if (existsSync(pathSymlink)) {\n try {\n const stats = lstatSync(pathSymlink);\n if (stats.isSymbolicLink()) {\n const target = await readlink(pathSymlink);\n // Support both regular aliases (.droid-patch/bins) and websearch wrappers (.droid-patch/websearch)\n if (\n target.includes(\".droid-patch/bins\") ||\n target.includes(\".droid-patch/websearch\")\n ) {\n await unlink(pathSymlink);\n console.log(styleText(\"green\", ` Removed: ${pathSymlink}`));\n removed = true;\n }\n }\n } catch {\n // Ignore\n }\n }\n }\n\n // Check aliases directory\n const symlinkPath = join(ALIASES_DIR, aliasName);\n if (existsSync(symlinkPath)) {\n await unlink(symlinkPath);\n console.log(styleText(\"green\", ` Removed: ${symlinkPath}`));\n removed = true;\n }\n\n // Remove binary if exists\n const binaryPath = join(BINS_DIR, `${aliasName}-patched`);\n if (existsSync(binaryPath)) {\n await unlink(binaryPath);\n console.log(styleText(\"green\", ` Removed binary: ${binaryPath}`));\n removed = true;\n }\n\n // Remove websearch wrapper and related files if exist\n const websearchDir = join(DROID_PATCH_DIR, \"websearch\");\n const wrapperPath = join(websearchDir, aliasName);\n const proxyPath = join(websearchDir, `${aliasName}-proxy.js`);\n const preloadPath = join(websearchDir, `${aliasName}-preload.js`);\n\n if (existsSync(wrapperPath)) {\n await unlink(wrapperPath);\n console.log(styleText(\"green\", ` Removed wrapper: ${wrapperPath}`));\n removed = true;\n }\n\n if (existsSync(proxyPath)) {\n await unlink(proxyPath);\n console.log(styleText(\"green\", ` Removed proxy: ${proxyPath}`));\n removed = true;\n }\n\n if (existsSync(preloadPath)) {\n await unlink(preloadPath);\n console.log(styleText(\"green\", ` Removed preload: ${preloadPath}`));\n removed = true;\n }\n\n if (!removed) {\n console.log(styleText(\"yellow\", ` Alias \"${aliasName}\" not found`));\n } else {\n console.log(\n styleText(\"green\", `[*] Alias \"${aliasName}\" removed successfully`),\n );\n }\n}\n\nexport async function listAliases(): Promise<void> {\n ensureDirectories();\n\n console.log(styleText(\"cyan\", \"═\".repeat(60)));\n console.log(styleText([\"cyan\", \"bold\"], \" Droid-Patch Aliases\"));\n console.log(styleText(\"cyan\", \"═\".repeat(60)));\n console.log();\n\n interface AliasInfo {\n name: string;\n target: string;\n location: string;\n immediate: boolean;\n }\n\n const aliases: AliasInfo[] = [];\n\n for (const pathDir of COMMON_PATH_DIRS) {\n if (!existsSync(pathDir)) continue;\n\n try {\n const files = readdirSync(pathDir);\n for (const file of files) {\n const fullPath = join(pathDir, file);\n try {\n const stats = lstatSync(fullPath);\n if (stats.isSymbolicLink()) {\n const target = await readlink(fullPath);\n // Support both regular aliases and websearch wrappers\n if (\n target.includes(\".droid-patch/bins\") ||\n target.includes(\".droid-patch/websearch\")\n ) {\n aliases.push({\n name: file,\n target,\n location: pathDir,\n immediate: true,\n });\n }\n }\n } catch {\n // Ignore\n }\n }\n } catch {\n // Directory can't be read\n }\n }\n\n try {\n const files = readdirSync(ALIASES_DIR);\n\n for (const file of files) {\n const fullPath = join(ALIASES_DIR, file);\n try {\n const stats = lstatSync(fullPath);\n if (stats.isSymbolicLink()) {\n const target = await readlink(fullPath);\n if (!aliases.find((a) => a.name === file)) {\n aliases.push({\n name: file,\n target,\n location: ALIASES_DIR,\n immediate: false,\n });\n }\n }\n } catch {\n // Ignore\n }\n }\n } catch {\n // Directory doesn't exist or can't be read\n }\n\n if (aliases.length === 0) {\n console.log(styleText(\"gray\", \" No aliases configured.\"));\n console.log();\n console.log(\n styleText(\n \"gray\",\n \" Create one with: npx droid-patch --is-custom <alias-name>\",\n ),\n );\n } else {\n console.log(styleText(\"white\", ` Found ${aliases.length} alias(es):`));\n console.log();\n for (const alias of aliases) {\n const status = alias.immediate\n ? styleText(\"green\", \"✓ immediate\")\n : styleText(\"yellow\", \"requires source\");\n console.log(\n styleText(\n \"green\",\n ` • ${styleText([\"cyan\", \"bold\"], alias.name)} [${status}]`,\n ),\n );\n console.log(styleText(\"gray\", ` → ${alias.target}`));\n }\n }\n\n console.log();\n console.log(styleText(\"gray\", ` Aliases directory: ${ALIASES_DIR}`));\n console.log(\n styleText(\n \"gray\",\n ` PATH configured: ${checkPathInclusion() ? styleText(\"green\", \"Yes\") : styleText(\"yellow\", \"No\")}`,\n ),\n );\n console.log();\n}\n\nexport interface ReplaceOriginalResult {\n originalPath: string;\n backupPath: string;\n}\n\nexport async function replaceOriginal(\n patchedBinaryPath: string,\n originalPath: string,\n verbose = false,\n): Promise<ReplaceOriginalResult> {\n ensureDirectories();\n\n console.log(\n styleText(\n \"white\",\n `[*] Replacing original binary: ${styleText(\"cyan\", originalPath)}`,\n ),\n );\n\n const latestBackupPath = join(BINS_DIR, \"droid-original-latest\");\n\n if (!existsSync(latestBackupPath)) {\n await copyFile(originalPath, latestBackupPath);\n console.log(styleText(\"green\", `[*] Created backup: ${latestBackupPath}`));\n } else {\n if (verbose) {\n console.log(\n styleText(\"gray\", ` Backup already exists: ${latestBackupPath}`),\n );\n }\n }\n\n await copyFile(patchedBinaryPath, originalPath);\n await chmod(originalPath, 0o755);\n console.log(styleText(\"green\", `[*] Replaced: ${originalPath}`));\n\n if (process.platform === \"darwin\") {\n try {\n console.log(styleText(\"gray\", \"[*] Re-signing binary for macOS...\"));\n execSync(`codesign --force --deep --sign - \"${originalPath}\"`, {\n stdio: \"pipe\",\n });\n console.log(styleText(\"green\", \"[*] Binary re-signed successfully\"));\n } catch {\n console.log(\n styleText(\n \"yellow\",\n \"[!] Could not re-sign binary. You may need to run:\",\n ),\n );\n console.log(\n styleText(\n \"gray\",\n ` codesign --force --deep --sign - \"${originalPath}\"`,\n ),\n );\n }\n\n try {\n execSync(`xattr -cr \"${originalPath}\"`, { stdio: \"pipe\" });\n } catch {\n // Ignore\n }\n }\n\n console.log();\n console.log(styleText(\"green\", \"─\".repeat(60)));\n console.log(styleText([\"green\", \"bold\"], \" REPLACEMENT COMPLETE\"));\n console.log(styleText(\"green\", \"─\".repeat(60)));\n console.log();\n console.log(\n styleText(\"white\", \"The patched binary is now active in all terminals.\"),\n );\n console.log(styleText(\"white\", \"No need to restart or source anything!\"));\n console.log();\n console.log(styleText(\"gray\", `To restore the original, run:`));\n console.log(styleText(\"cyan\", ` npx droid-patch restore`));\n\n return {\n originalPath,\n backupPath: latestBackupPath,\n };\n}\n\n/**\n * Create alias for wrapper script\n * Unlike createAlias, this function creates symlink pointing to wrapper script\n * Used for features like websearch that require preprocessing\n */\nexport async function createAliasForWrapper(\n wrapperPath: string,\n aliasName: string,\n verbose = false,\n): Promise<CreateAliasResult> {\n ensureDirectories();\n\n console.log(\n styleText(\"white\", `[*] Creating alias: ${styleText(\"cyan\", aliasName)}`),\n );\n\n const writablePathDir = findWritablePathDir();\n\n if (writablePathDir) {\n const targetPath = join(writablePathDir, aliasName);\n\n if (verbose) {\n console.log(styleText(\"gray\", ` Wrapper: ${wrapperPath}`));\n }\n\n if (existsSync(targetPath)) {\n await unlink(targetPath);\n if (verbose) {\n console.log(styleText(\"gray\", ` Removed existing: ${targetPath}`));\n }\n }\n\n await symlink(wrapperPath, targetPath);\n\n console.log(\n styleText(\"green\", `[*] Created: ${targetPath} -> ${wrapperPath}`),\n );\n console.log();\n console.log(styleText(\"green\", \"─\".repeat(60)));\n console.log(\n styleText([\"green\", \"bold\"], \" ALIAS READY - NO ACTION REQUIRED!\"),\n );\n console.log(styleText(\"green\", \"─\".repeat(60)));\n console.log();\n console.log(\n styleText(\n \"white\",\n `The alias \"${styleText([\"cyan\", \"bold\"], aliasName)}\" is now available in ALL terminals.`,\n ),\n );\n console.log(styleText(\"gray\", `(Installed to: ${writablePathDir})`));\n\n return {\n aliasPath: targetPath,\n binaryPath: wrapperPath,\n immediate: true,\n };\n }\n\n // Fallback: use ~/.droid-patch/aliases\n console.log(\n styleText(\n \"yellow\",\n \"[*] No writable PATH directory found, using fallback...\",\n ),\n );\n\n const symlinkPath = join(ALIASES_DIR, aliasName);\n\n if (existsSync(symlinkPath)) {\n await unlink(symlinkPath);\n if (verbose) {\n console.log(styleText(\"gray\", ` Removed existing symlink`));\n }\n }\n\n await symlink(wrapperPath, symlinkPath);\n\n console.log(\n styleText(\"green\", `[*] Created symlink: ${symlinkPath} -> ${wrapperPath}`),\n );\n\n const shellConfig = getShellConfigPath();\n\n if (!checkPathInclusion()) {\n if (!isPathConfigured(shellConfig)) {\n console.log(\n styleText(\"white\", `[*] Configuring PATH in ${shellConfig}...`),\n );\n\n if (addPathToShellConfig(shellConfig, verbose)) {\n console.log(styleText(\"green\", `[*] PATH configured successfully!`));\n console.log();\n console.log(styleText(\"yellow\", \"─\".repeat(60)));\n console.log(styleText([\"yellow\", \"bold\"], \" ACTION REQUIRED\"));\n console.log(styleText(\"yellow\", \"─\".repeat(60)));\n console.log();\n console.log(\n styleText(\"white\", \"To use the alias in this terminal, run:\"),\n );\n console.log();\n console.log(styleText(\"cyan\", ` source ${shellConfig}`));\n console.log();\n console.log(styleText(\"gray\", \"Or simply open a new terminal window.\"));\n console.log(styleText(\"yellow\", \"─\".repeat(60)));\n } else {\n const exportLine = `export PATH=\"${ALIASES_DIR}:$PATH\"`;\n console.log();\n console.log(styleText(\"yellow\", \"─\".repeat(60)));\n console.log(\n styleText([\"yellow\", \"bold\"], \" Manual PATH Configuration Required\"),\n );\n console.log(styleText(\"yellow\", \"─\".repeat(60)));\n console.log();\n console.log(styleText(\"white\", \"Add this line to your shell config:\"));\n console.log(styleText(\"cyan\", ` ${exportLine}`));\n console.log();\n console.log(styleText(\"gray\", `Shell config file: ${shellConfig}`));\n console.log(styleText(\"yellow\", \"─\".repeat(60)));\n }\n } else {\n console.log(\n styleText(\"green\", `[*] PATH already configured in ${shellConfig}`),\n );\n console.log();\n console.log(\n styleText(\n \"yellow\",\n `Note: Run \\`source ${shellConfig}\\` or open a new terminal to use the alias.`,\n ),\n );\n }\n } else {\n console.log(\n styleText(\"green\", `[*] PATH already includes aliases directory`),\n );\n console.log();\n console.log(\n styleText(\n \"green\",\n `You can now use \"${styleText([\"cyan\", \"bold\"], aliasName)}\" command directly!`,\n ),\n );\n }\n\n return {\n aliasPath: symlinkPath,\n binaryPath: wrapperPath,\n };\n}\n\nexport async function restoreOriginal(originalPath: string): Promise<void> {\n ensureDirectories();\n\n const latestBackupPath = join(BINS_DIR, \"droid-original-latest\");\n\n console.log(styleText(\"cyan\", \"═\".repeat(60)));\n console.log(styleText([\"cyan\", \"bold\"], \" Restore Original Droid\"));\n console.log(styleText(\"cyan\", \"═\".repeat(60)));\n console.log();\n\n if (!existsSync(latestBackupPath)) {\n const localBackup = `${originalPath}.backup`;\n if (existsSync(localBackup)) {\n console.log(styleText(\"white\", `[*] Found local backup: ${localBackup}`));\n console.log(styleText(\"white\", `[*] Restoring to: ${originalPath}`));\n\n await copyFile(localBackup, originalPath);\n await chmod(originalPath, 0o755);\n\n if (process.platform === \"darwin\") {\n try {\n execSync(`codesign --force --deep --sign - \"${originalPath}\"`, {\n stdio: \"pipe\",\n });\n execSync(`xattr -cr \"${originalPath}\"`, { stdio: \"pipe\" });\n } catch {\n // Ignore\n }\n }\n\n console.log();\n console.log(styleText(\"green\", \"═\".repeat(60)));\n console.log(styleText([\"green\", \"bold\"], \" RESTORE COMPLETE\"));\n console.log(styleText(\"green\", \"═\".repeat(60)));\n console.log();\n console.log(\n styleText(\n \"green\",\n \"Original droid binary has been restored from local backup.\",\n ),\n );\n return;\n }\n\n console.log(styleText(\"red\", \"[!] No backup found.\"));\n console.log(styleText(\"gray\", ` Checked: ${latestBackupPath}`));\n console.log(styleText(\"gray\", ` Checked: ${localBackup}`));\n console.log();\n console.log(\n styleText(\"gray\", \"If you have a manual backup, restore it with:\"),\n );\n console.log(styleText(\"cyan\", ` cp /path/to/backup ${originalPath}`));\n return;\n }\n\n console.log(styleText(\"white\", `[*] Restoring from: ${latestBackupPath}`));\n console.log(styleText(\"white\", `[*] Restoring to: ${originalPath}`));\n\n const targetDir = dirname(originalPath);\n if (!existsSync(targetDir)) {\n mkdirSync(targetDir, { recursive: true });\n }\n\n await copyFile(latestBackupPath, originalPath);\n await chmod(originalPath, 0o755);\n\n if (process.platform === \"darwin\") {\n try {\n execSync(`codesign --force --deep --sign - \"${originalPath}\"`, {\n stdio: \"pipe\",\n });\n execSync(`xattr -cr \"${originalPath}\"`, { stdio: \"pipe\" });\n } catch {\n // Ignore\n }\n }\n\n console.log();\n console.log(styleText(\"green\", \"═\".repeat(60)));\n console.log(styleText([\"green\", \"bold\"], \" RESTORE COMPLETE\"));\n console.log(styleText(\"green\", \"═\".repeat(60)));\n console.log();\n console.log(styleText(\"green\", \"Original droid binary has been restored.\"));\n console.log(\n styleText(\"green\", \"All terminals will now use the original version.\"),\n );\n}\n"],"mappings":"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"}