droid-patch 0.4.1 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +55 -55
- package/README.zh-CN.md +55 -55
- package/dist/{alias-BGsm9wXL.js → alias-DVggcM0G.mjs} +240 -41
- package/dist/alias-DVggcM0G.mjs.map +1 -0
- package/dist/{cli.js → cli.mjs} +166 -296
- package/dist/cli.mjs.map +1 -0
- package/dist/{index.d.ts → index.d.mts} +4 -12
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +3 -0
- package/package.json +8 -8
- package/dist/alias-BGsm9wXL.js.map +0 -1
- package/dist/cli.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -3
- /package/dist/{cli.d.ts → cli.d.mts} +0 -0
package/dist/{cli.js → cli.mjs}
RENAMED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
2
|
+
import { a as removeAlias, d as listAllMetadata, f as loadAliasMetadata, i as listAliases, l as createMetadata, m as patchDroid, n as createAlias, o as removeAliasesByFilter, p as saveAliasMetadata, r as createAliasForWrapper, t as clearAllAliases, u as formatPatches } from "./alias-DVggcM0G.mjs";
|
|
3
3
|
import bin from "tiny-bin";
|
|
4
4
|
import { styleText } from "node:util";
|
|
5
5
|
import { existsSync, readFileSync } from "node:fs";
|
|
@@ -15,15 +15,15 @@ import { chmod, mkdir, writeFile } from "node:fs/promises";
|
|
|
15
15
|
* Since BUN_CONFIG_PRELOAD doesn't work with compiled binaries,
|
|
16
16
|
* use a local proxy server to intercept search requests instead
|
|
17
17
|
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
* - Set to 0 to disable timeout
|
|
18
|
+
* Each droid instance runs its own proxy server.
|
|
19
|
+
* The proxy is killed automatically when droid exits.
|
|
20
|
+
* @param factoryApiUrl - Custom Factory API URL (default: https://api.factory.ai)
|
|
22
21
|
*/
|
|
23
|
-
function generateSearchProxyServer() {
|
|
22
|
+
function generateSearchProxyServer(factoryApiUrl = "https://api.factory.ai") {
|
|
24
23
|
return `#!/usr/bin/env node
|
|
25
24
|
// Droid WebSearch Proxy Server
|
|
26
25
|
// Auto-generated by droid-patch --websearch
|
|
26
|
+
// This proxy runs as a child process of droid and is killed when droid exits
|
|
27
27
|
|
|
28
28
|
const http = require('http');
|
|
29
29
|
const https = require('https');
|
|
@@ -31,71 +31,13 @@ const { execSync } = require('child_process');
|
|
|
31
31
|
const fs = require('fs');
|
|
32
32
|
|
|
33
33
|
const DEBUG = process.env.DROID_SEARCH_DEBUG === '1';
|
|
34
|
-
const PORT = parseInt(process.env.SEARCH_PROXY_PORT || '
|
|
35
|
-
|
|
36
|
-
// Idle timeout in seconds, default 5 minutes, set to 0 to disable
|
|
37
|
-
const IDLE_TIMEOUT = parseInt(process.env.DROID_PROXY_IDLE_TIMEOUT || '300');
|
|
38
|
-
let lastActivityTime = Date.now();
|
|
39
|
-
let idleCheckTimer = null;
|
|
34
|
+
const PORT = parseInt(process.env.SEARCH_PROXY_PORT || '0'); // 0 = auto-assign
|
|
35
|
+
const FACTORY_API = '${factoryApiUrl}';
|
|
40
36
|
|
|
41
37
|
function log(...args) {
|
|
42
38
|
if (DEBUG) console.error('[websearch]', ...args);
|
|
43
39
|
}
|
|
44
40
|
|
|
45
|
-
// Update activity time
|
|
46
|
-
function updateActivity() {
|
|
47
|
-
lastActivityTime = Date.now();
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
// Check if any droid process is running (droid instances using the proxy)
|
|
51
|
-
function isDroidRunning() {
|
|
52
|
-
try {
|
|
53
|
-
const { execSync } = require('child_process');
|
|
54
|
-
// Use ps to check if droid.patched binary is running
|
|
55
|
-
// Exclude scripts and grep itself, only match actual droid binary processes
|
|
56
|
-
const result = execSync(
|
|
57
|
-
'ps aux | grep -E "[d]roid\\\\.patched" | grep -v grep | wc -l',
|
|
58
|
-
{ encoding: 'utf-8', timeout: 1000 }
|
|
59
|
-
).trim();
|
|
60
|
-
return parseInt(result) > 0;
|
|
61
|
-
} catch {
|
|
62
|
-
return false;
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
// Check idle time and possibly exit
|
|
67
|
-
function checkIdleAndExit() {
|
|
68
|
-
if (IDLE_TIMEOUT <= 0) return; // Timeout disabled
|
|
69
|
-
|
|
70
|
-
// If droid process is running, refresh activity time (like heartbeat)
|
|
71
|
-
if (isDroidRunning()) {
|
|
72
|
-
log('Droid process detected, keeping proxy alive');
|
|
73
|
-
updateActivity();
|
|
74
|
-
return;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
const idleMs = Date.now() - lastActivityTime;
|
|
78
|
-
const timeoutMs = IDLE_TIMEOUT * 1000;
|
|
79
|
-
|
|
80
|
-
if (idleMs >= timeoutMs) {
|
|
81
|
-
log(\`Idle for \${Math.round(idleMs / 1000)}s and no droid running, shutting down...\`);
|
|
82
|
-
cleanup();
|
|
83
|
-
process.exit(0);
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
// Cleanup resources
|
|
88
|
-
function cleanup() {
|
|
89
|
-
if (idleCheckTimer) {
|
|
90
|
-
clearInterval(idleCheckTimer);
|
|
91
|
-
idleCheckTimer = null;
|
|
92
|
-
}
|
|
93
|
-
// Delete PID file
|
|
94
|
-
try {
|
|
95
|
-
fs.unlinkSync('/tmp/droid-search-proxy.pid');
|
|
96
|
-
} catch {}
|
|
97
|
-
}
|
|
98
|
-
|
|
99
41
|
// === Search Implementation ===
|
|
100
42
|
|
|
101
43
|
// Smithery Exa MCP - highest priority, requires SMITHERY_API_KEY and SMITHERY_PROFILE
|
|
@@ -442,31 +384,16 @@ async function search(query, numResults = 10) {
|
|
|
442
384
|
|
|
443
385
|
// === HTTP Proxy Server ===
|
|
444
386
|
|
|
445
|
-
const FACTORY_API = 'https://api.factory.ai';
|
|
446
|
-
|
|
447
387
|
const server = http.createServer(async (req, res) => {
|
|
448
388
|
const url = new URL(req.url, \`http://\${req.headers.host}\`);
|
|
449
389
|
|
|
450
|
-
// Health check
|
|
390
|
+
// Health check
|
|
451
391
|
if (url.pathname === '/health') {
|
|
452
|
-
const idleSeconds = Math.round((Date.now() - lastActivityTime) / 1000);
|
|
453
|
-
const droidRunning = isDroidRunning();
|
|
454
392
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
455
|
-
res.end(JSON.stringify({
|
|
456
|
-
status: 'ok',
|
|
457
|
-
port: PORT,
|
|
458
|
-
idleTimeout: IDLE_TIMEOUT,
|
|
459
|
-
idleSeconds: idleSeconds,
|
|
460
|
-
droidRunning: droidRunning,
|
|
461
|
-
// If droid is running, won't shutdown; otherwise calculate based on idle time
|
|
462
|
-
willShutdownIn: IDLE_TIMEOUT > 0 && !droidRunning ? Math.max(0, IDLE_TIMEOUT - idleSeconds) : null
|
|
463
|
-
}));
|
|
393
|
+
res.end(JSON.stringify({ status: 'ok', port: server.address()?.port || PORT }));
|
|
464
394
|
return;
|
|
465
395
|
}
|
|
466
396
|
|
|
467
|
-
// Update activity time (only non-health-check requests refresh it)
|
|
468
|
-
updateActivity();
|
|
469
|
-
|
|
470
397
|
// Search endpoint - intercept
|
|
471
398
|
if (url.pathname === '/api/tools/exa/search' && req.method === 'POST') {
|
|
472
399
|
let body = '';
|
|
@@ -492,7 +419,9 @@ const server = http.createServer(async (req, res) => {
|
|
|
492
419
|
log('Proxy:', req.method, url.pathname);
|
|
493
420
|
|
|
494
421
|
const proxyUrl = new URL(FACTORY_API + url.pathname + url.search);
|
|
495
|
-
|
|
422
|
+
// Choose http or https based on target protocol
|
|
423
|
+
const proxyModule = proxyUrl.protocol === 'https:' ? https : http;
|
|
424
|
+
const proxyReq = proxyModule.request(proxyUrl, {
|
|
496
425
|
method: req.method,
|
|
497
426
|
headers: { ...req.headers, host: proxyUrl.host }
|
|
498
427
|
}, proxyRes => {
|
|
@@ -524,6 +453,9 @@ server.listen(PORT, '127.0.0.1', () => {
|
|
|
524
453
|
fs.writeFileSync(portFile, String(actualPort));
|
|
525
454
|
}
|
|
526
455
|
|
|
456
|
+
// Output PORT= line for wrapper script to parse
|
|
457
|
+
console.log('PORT=' + actualPort);
|
|
458
|
+
|
|
527
459
|
const hasSmithery = process.env.SMITHERY_API_KEY && process.env.SMITHERY_PROFILE;
|
|
528
460
|
log('Search proxy started on http://127.0.0.1:' + actualPort);
|
|
529
461
|
log('Smithery Exa:', hasSmithery ? 'configured (priority 1)' : 'not set');
|
|
@@ -531,109 +463,89 @@ server.listen(PORT, '127.0.0.1', () => {
|
|
|
531
463
|
log('Serper:', process.env.SERPER_API_KEY ? 'configured' : 'not set');
|
|
532
464
|
log('Brave:', process.env.BRAVE_API_KEY ? 'configured' : 'not set');
|
|
533
465
|
log('SearXNG:', process.env.SEARXNG_URL || 'not set');
|
|
534
|
-
|
|
535
|
-
// Start idle check timer
|
|
536
|
-
// Check interval = min(timeout/2, 30s) to ensure timely timeout detection
|
|
537
|
-
if (IDLE_TIMEOUT > 0) {
|
|
538
|
-
const checkInterval = Math.min(IDLE_TIMEOUT * 500, 30000); // milliseconds
|
|
539
|
-
log(\`Idle timeout: \${IDLE_TIMEOUT}s (will auto-shutdown when idle)\`);
|
|
540
|
-
idleCheckTimer = setInterval(checkIdleAndExit, checkInterval);
|
|
541
|
-
} else {
|
|
542
|
-
log('Idle timeout: disabled (will run forever)');
|
|
543
|
-
}
|
|
544
466
|
});
|
|
545
467
|
|
|
546
|
-
process.on('SIGTERM', () => {
|
|
547
|
-
process.on('SIGINT', () => {
|
|
468
|
+
process.on('SIGTERM', () => { server.close(); process.exit(0); });
|
|
469
|
+
process.on('SIGINT', () => { server.close(); process.exit(0); });
|
|
548
470
|
`;
|
|
549
471
|
}
|
|
550
472
|
/**
|
|
551
473
|
* Generate unified Wrapper script
|
|
552
|
-
*
|
|
553
|
-
* -
|
|
554
|
-
* - Proxy
|
|
555
|
-
* - Proxy
|
|
474
|
+
* Each droid instance runs its own proxy:
|
|
475
|
+
* - Uses port 0 to let system auto-assign available port
|
|
476
|
+
* - Proxy runs as child process
|
|
477
|
+
* - Proxy is killed when droid exits
|
|
478
|
+
* - Supports multiple droid instances running simultaneously
|
|
556
479
|
*/
|
|
557
480
|
function generateUnifiedWrapper(droidPath, proxyScriptPath) {
|
|
558
481
|
return `#!/bin/bash
|
|
559
482
|
# Droid with WebSearch
|
|
560
483
|
# Auto-generated by droid-patch --websearch
|
|
484
|
+
# Each instance runs its own proxy on a system-assigned port
|
|
561
485
|
|
|
562
486
|
PROXY_SCRIPT="${proxyScriptPath}"
|
|
563
487
|
DROID_BIN="${droidPath}"
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
if kill -0 "$pid" 2>/dev/null; then
|
|
574
|
-
# Process exists, check if port responds
|
|
575
|
-
if curl -s "http://127.0.0.1:$PORT/health" > /dev/null 2>&1; then
|
|
576
|
-
return 0
|
|
577
|
-
fi
|
|
578
|
-
fi
|
|
579
|
-
# PID file exists but process doesn't exist or port not responding, cleanup
|
|
580
|
-
rm -f "$PID_FILE"
|
|
488
|
+
PROXY_PID=""
|
|
489
|
+
PORT_FILE="/tmp/droid-websearch-\$\$.port"
|
|
490
|
+
|
|
491
|
+
# Cleanup function - kill proxy when droid exits
|
|
492
|
+
cleanup() {
|
|
493
|
+
if [ -n "\$PROXY_PID" ] && kill -0 "\$PROXY_PID" 2>/dev/null; then
|
|
494
|
+
[ -n "\$DROID_SEARCH_DEBUG" ] && echo "[websearch] Stopping proxy (PID: \$PROXY_PID)" >&2
|
|
495
|
+
kill "\$PROXY_PID" 2>/dev/null
|
|
496
|
+
wait "\$PROXY_PID" 2>/dev/null
|
|
581
497
|
fi
|
|
582
|
-
|
|
498
|
+
rm -f "\$PORT_FILE"
|
|
583
499
|
}
|
|
584
500
|
|
|
585
|
-
#
|
|
586
|
-
|
|
587
|
-
# First check if port is occupied by another program
|
|
588
|
-
if lsof -i:"$PORT" > /dev/null 2>&1; then
|
|
589
|
-
# Port is occupied, check if it's our proxy
|
|
590
|
-
if curl -s "http://127.0.0.1:$PORT/health" > /dev/null 2>&1; then
|
|
591
|
-
[ -n "$DROID_SEARCH_DEBUG" ] && echo "[websearch] Proxy already running on port $PORT" >&2
|
|
592
|
-
return 0
|
|
593
|
-
else
|
|
594
|
-
echo "[websearch] Port $PORT is occupied by another process" >&2
|
|
595
|
-
return 1
|
|
596
|
-
fi
|
|
597
|
-
fi
|
|
598
|
-
|
|
599
|
-
[ -n "$DROID_SEARCH_DEBUG" ] && echo "[websearch] Starting shared proxy on port $PORT..." >&2
|
|
501
|
+
# Set up trap to cleanup on exit
|
|
502
|
+
trap cleanup EXIT INT TERM
|
|
600
503
|
|
|
601
|
-
|
|
602
|
-
SEARCH_PROXY_PORT="$PORT" nohup node "$PROXY_SCRIPT" >> "$LOG_FILE" 2>&1 &
|
|
603
|
-
echo $! > "$PID_FILE"
|
|
504
|
+
[ -n "\$DROID_SEARCH_DEBUG" ] && echo "[websearch] Starting proxy..." >&2
|
|
604
505
|
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
506
|
+
# Start proxy with port 0 (system will assign available port)
|
|
507
|
+
# Proxy writes actual port to PORT_FILE
|
|
508
|
+
if [ -n "\$DROID_SEARCH_DEBUG" ]; then
|
|
509
|
+
SEARCH_PROXY_PORT=0 SEARCH_PROXY_PORT_FILE="\$PORT_FILE" node "\$PROXY_SCRIPT" 2>&1 &
|
|
510
|
+
else
|
|
511
|
+
SEARCH_PROXY_PORT=0 SEARCH_PROXY_PORT_FILE="\$PORT_FILE" node "\$PROXY_SCRIPT" >/dev/null 2>&1 &
|
|
512
|
+
fi
|
|
513
|
+
PROXY_PID=\$!
|
|
514
|
+
|
|
515
|
+
# Wait for proxy to start and get actual port (max 5 seconds)
|
|
516
|
+
for i in {1..50}; do
|
|
517
|
+
# Check if proxy process is still running
|
|
518
|
+
if ! kill -0 "\$PROXY_PID" 2>/dev/null; then
|
|
519
|
+
[ -n "\$DROID_SEARCH_DEBUG" ] && echo "[websearch] Proxy process died" >&2
|
|
520
|
+
break
|
|
521
|
+
fi
|
|
522
|
+
if [ -f "\$PORT_FILE" ]; then
|
|
523
|
+
ACTUAL_PORT=\$(cat "\$PORT_FILE" 2>/dev/null)
|
|
524
|
+
if [ -n "\$ACTUAL_PORT" ] && curl -s "http://127.0.0.1:\$ACTUAL_PORT/health" > /dev/null 2>&1; then
|
|
525
|
+
[ -n "\$DROID_SEARCH_DEBUG" ] && echo "[websearch] Proxy ready on port \$ACTUAL_PORT (PID: \$PROXY_PID)" >&2
|
|
526
|
+
break
|
|
610
527
|
fi
|
|
611
|
-
sleep 0.1
|
|
612
|
-
done
|
|
613
|
-
|
|
614
|
-
echo "[websearch] Failed to start proxy" >&2
|
|
615
|
-
rm -f "$PID_FILE"
|
|
616
|
-
return 1
|
|
617
|
-
}
|
|
618
|
-
|
|
619
|
-
# Ensure proxy is running
|
|
620
|
-
ensure_proxy() {
|
|
621
|
-
if is_proxy_running; then
|
|
622
|
-
[ -n "$DROID_SEARCH_DEBUG" ] && echo "[websearch] Using existing proxy on port $PORT" >&2
|
|
623
|
-
return 0
|
|
624
528
|
fi
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
#
|
|
629
|
-
if !
|
|
630
|
-
echo "[websearch]
|
|
631
|
-
|
|
529
|
+
sleep 0.1
|
|
530
|
+
done
|
|
531
|
+
|
|
532
|
+
# Check if proxy started successfully
|
|
533
|
+
if [ ! -f "\$PORT_FILE" ] || [ -z "\$(cat "\$PORT_FILE" 2>/dev/null)" ]; then
|
|
534
|
+
echo "[websearch] Failed to start proxy, running without websearch" >&2
|
|
535
|
+
cleanup
|
|
536
|
+
exec "\$DROID_BIN" "\$@"
|
|
632
537
|
fi
|
|
633
538
|
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
539
|
+
ACTUAL_PORT=\$(cat "\$PORT_FILE")
|
|
540
|
+
rm -f "\$PORT_FILE"
|
|
541
|
+
|
|
542
|
+
# Run droid with proxy
|
|
543
|
+
export FACTORY_API_BASE_URL_OVERRIDE="http://127.0.0.1:\$ACTUAL_PORT"
|
|
544
|
+
"\$DROID_BIN" "\$@"
|
|
545
|
+
DROID_EXIT_CODE=\$?
|
|
546
|
+
|
|
547
|
+
# Cleanup will be called by trap
|
|
548
|
+
exit \$DROID_EXIT_CODE
|
|
637
549
|
`;
|
|
638
550
|
}
|
|
639
551
|
/**
|
|
@@ -644,12 +556,17 @@ exec "$DROID_BIN" "$@"
|
|
|
644
556
|
* - proxy server intercepts search requests, passes through other requests
|
|
645
557
|
* - uses FACTORY_API_BASE_URL_OVERRIDE env var to point to proxy
|
|
646
558
|
* - alias works directly, no extra steps needed
|
|
559
|
+
*
|
|
560
|
+
* @param outputDir - Directory to write files to
|
|
561
|
+
* @param droidPath - Path to droid binary
|
|
562
|
+
* @param aliasName - Alias name for the wrapper
|
|
563
|
+
* @param apiBase - Custom API base URL for proxy to forward requests to
|
|
647
564
|
*/
|
|
648
|
-
async function createWebSearchUnifiedFiles(outputDir, droidPath, aliasName) {
|
|
565
|
+
async function createWebSearchUnifiedFiles(outputDir, droidPath, aliasName, apiBase) {
|
|
649
566
|
if (!existsSync(outputDir)) await mkdir(outputDir, { recursive: true });
|
|
650
567
|
const proxyScriptPath = join(outputDir, `${aliasName}-proxy.js`);
|
|
651
568
|
const wrapperScriptPath = join(outputDir, aliasName);
|
|
652
|
-
await writeFile(proxyScriptPath, generateSearchProxyServer());
|
|
569
|
+
await writeFile(proxyScriptPath, generateSearchProxyServer(apiBase || "https://api.factory.ai"));
|
|
653
570
|
console.log(`[*] Created proxy script: ${proxyScriptPath}`);
|
|
654
571
|
await writeFile(wrapperScriptPath, generateUnifiedWrapper(droidPath, proxyScriptPath));
|
|
655
572
|
await chmod(wrapperScriptPath, 493);
|
|
@@ -666,13 +583,29 @@ const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
|
666
583
|
function getVersion() {
|
|
667
584
|
try {
|
|
668
585
|
const pkgPath = join(__dirname, "..", "package.json");
|
|
669
|
-
|
|
670
|
-
return pkg.version || "0.0.0";
|
|
586
|
+
return JSON.parse(readFileSync(pkgPath, "utf-8")).version || "0.0.0";
|
|
671
587
|
} catch {
|
|
672
588
|
return "0.0.0";
|
|
673
589
|
}
|
|
674
590
|
}
|
|
675
591
|
const version = getVersion();
|
|
592
|
+
function getDroidVersion(droidPath) {
|
|
593
|
+
try {
|
|
594
|
+
const result = execSync(`"${droidPath}" --version`, {
|
|
595
|
+
encoding: "utf-8",
|
|
596
|
+
stdio: [
|
|
597
|
+
"pipe",
|
|
598
|
+
"pipe",
|
|
599
|
+
"pipe"
|
|
600
|
+
],
|
|
601
|
+
timeout: 5e3
|
|
602
|
+
}).trim();
|
|
603
|
+
const match = result.match(/(\d+\.\d+\.\d+)/);
|
|
604
|
+
return match ? match[1] : result || void 0;
|
|
605
|
+
} catch {
|
|
606
|
+
return;
|
|
607
|
+
}
|
|
608
|
+
}
|
|
676
609
|
function findDefaultDroidPath() {
|
|
677
610
|
const home = homedir();
|
|
678
611
|
try {
|
|
@@ -696,12 +629,13 @@ function findDefaultDroidPath() {
|
|
|
696
629
|
for (const p of paths) if (existsSync(p)) return p;
|
|
697
630
|
return join(home, ".droid", "bin", "droid");
|
|
698
631
|
}
|
|
699
|
-
bin("droid-patch", "CLI tool to patch droid binary with various modifications").package("droid-patch", version).option("--is-custom", "Patch isCustom:!0 to isCustom:!1 (enable context compression for custom models)").option("--skip-login", "Inject a fake FACTORY_API_KEY to bypass login requirement (no real key needed)").option("--api-base <url>", "Replace Factory API
|
|
632
|
+
bin("droid-patch", "CLI tool to patch droid binary with various modifications").package("droid-patch", version).option("--is-custom", "Patch isCustom:!0 to isCustom:!1 (enable context compression for custom models)").option("--skip-login", "Inject a fake FACTORY_API_KEY to bypass login requirement (no real key needed)").option("--api-base <url>", "Replace Factory API URL with custom URL (binary patch, or forward target with --websearch)").option("--websearch", "Enable local WebSearch proxy (each instance runs own proxy, auto-cleanup on exit)").option("--reasoning-effort", "Enable reasoning effort for custom models (set to high, enable UI selector)").option("--dry-run", "Verify patches without actually modifying the binary").option("-p, --path <path>", "Path to the droid binary").option("-o, --output <dir>", "Output directory for patched binary").option("--no-backup", "Do not create backup of original binary").option("-v, --verbose", "Enable verbose output").argument("[alias]", "Alias name for the patched binary").action(async (options, args) => {
|
|
700
633
|
const alias = args?.[0];
|
|
701
634
|
const isCustom = options["is-custom"];
|
|
702
635
|
const skipLogin = options["skip-login"];
|
|
703
636
|
const apiBase = options["api-base"];
|
|
704
|
-
const
|
|
637
|
+
const websearch = options["websearch"];
|
|
638
|
+
const websearchTarget = websearch ? apiBase || "https://api.factory.ai" : void 0;
|
|
705
639
|
const reasoningEffort = options["reasoning-effort"];
|
|
706
640
|
const dryRun = options["dry-run"];
|
|
707
641
|
const path = options.path || findDefaultDroidPath();
|
|
@@ -709,7 +643,7 @@ bin("droid-patch", "CLI tool to patch droid binary with various modifications").
|
|
|
709
643
|
const backup = options.backup !== false;
|
|
710
644
|
const verbose = options.verbose;
|
|
711
645
|
const outputPath = outputDir && alias ? join(outputDir, alias) : void 0;
|
|
712
|
-
if (
|
|
646
|
+
if (websearch && !isCustom && !skipLogin && !reasoningEffort) {
|
|
713
647
|
if (!alias) {
|
|
714
648
|
console.log(styleText("red", "Error: Alias name required for --websearch"));
|
|
715
649
|
console.log(styleText("gray", "Usage: npx droid-patch --websearch <alias>"));
|
|
@@ -719,17 +653,21 @@ bin("droid-patch", "CLI tool to patch droid binary with various modifications").
|
|
|
719
653
|
console.log(styleText(["cyan", "bold"], " Droid WebSearch Setup"));
|
|
720
654
|
console.log(styleText("cyan", "═".repeat(60)));
|
|
721
655
|
console.log();
|
|
722
|
-
|
|
723
|
-
|
|
656
|
+
console.log(styleText("white", `Forward target: ${websearchTarget}`));
|
|
657
|
+
console.log();
|
|
658
|
+
const { wrapperScript } = await createWebSearchUnifiedFiles(join(homedir(), ".droid-patch", "proxy"), path, alias, websearchTarget);
|
|
724
659
|
await createAliasForWrapper(wrapperScript, alias, verbose);
|
|
725
|
-
const
|
|
660
|
+
const droidVersion = getDroidVersion(path);
|
|
661
|
+
await saveAliasMetadata(createMetadata(alias, path, {
|
|
726
662
|
isCustom: false,
|
|
727
663
|
skipLogin: false,
|
|
728
|
-
apiBase: null,
|
|
664
|
+
apiBase: apiBase || null,
|
|
729
665
|
websearch: true,
|
|
730
666
|
reasoningEffort: false
|
|
731
|
-
}
|
|
732
|
-
|
|
667
|
+
}, {
|
|
668
|
+
droidPatchVersion: version,
|
|
669
|
+
droidVersion
|
|
670
|
+
}));
|
|
733
671
|
console.log();
|
|
734
672
|
console.log(styleText("green", "═".repeat(60)));
|
|
735
673
|
console.log(styleText(["green", "bold"], " WebSearch Ready!"));
|
|
@@ -755,22 +693,20 @@ bin("droid-patch", "CLI tool to patch droid binary with various modifications").
|
|
|
755
693
|
console.log(styleText("gray", " export DROID_SEARCH_DEBUG=1"));
|
|
756
694
|
return;
|
|
757
695
|
}
|
|
758
|
-
if (!isCustom && !skipLogin && !apiBase && !
|
|
696
|
+
if (!isCustom && !skipLogin && !apiBase && !websearch && !reasoningEffort) {
|
|
759
697
|
console.log(styleText("yellow", "No patch flags specified. Available patches:"));
|
|
760
698
|
console.log(styleText("gray", " --is-custom Patch isCustom for custom models"));
|
|
761
699
|
console.log(styleText("gray", " --skip-login Bypass login by injecting a fake API key"));
|
|
762
|
-
console.log(styleText("gray", " --api-base Replace Factory API URL
|
|
763
|
-
console.log(styleText("gray", " --websearch Enable local WebSearch
|
|
700
|
+
console.log(styleText("gray", " --api-base Replace Factory API URL (binary patch)"));
|
|
701
|
+
console.log(styleText("gray", " --websearch Enable local WebSearch proxy"));
|
|
764
702
|
console.log(styleText("gray", " --reasoning-effort Set reasoning effort level for custom models"));
|
|
765
703
|
console.log();
|
|
766
704
|
console.log("Usage examples:");
|
|
767
705
|
console.log(styleText("cyan", " npx droid-patch --is-custom droid-custom"));
|
|
768
706
|
console.log(styleText("cyan", " npx droid-patch --skip-login droid-nologin"));
|
|
769
707
|
console.log(styleText("cyan", " npx droid-patch --is-custom --skip-login droid-patched"));
|
|
770
|
-
console.log(styleText("cyan", " npx droid-patch --skip-login -o . my-droid"));
|
|
771
|
-
console.log(styleText("cyan", " npx droid-patch --api-base http://localhost:3000 droid-local"));
|
|
772
708
|
console.log(styleText("cyan", " npx droid-patch --websearch droid-search"));
|
|
773
|
-
console.log(styleText("cyan", " npx droid-patch --
|
|
709
|
+
console.log(styleText("cyan", " npx droid-patch --websearch --api-base=http://127.0.0.1:20002 my-droid"));
|
|
774
710
|
process.exit(1);
|
|
775
711
|
}
|
|
776
712
|
if (!alias && !dryRun) {
|
|
@@ -795,9 +731,9 @@ bin("droid-patch", "CLI tool to patch droid binary with various modifications").
|
|
|
795
731
|
pattern: Buffer.from("process.env.FACTORY_API_KEY"),
|
|
796
732
|
replacement: Buffer.from("\"fk-droid-patch-skip-00000\"")
|
|
797
733
|
});
|
|
798
|
-
if (apiBase) {
|
|
734
|
+
if (apiBase && !websearch) {
|
|
799
735
|
const originalUrl = "https://api.factory.ai";
|
|
800
|
-
const originalLength =
|
|
736
|
+
const originalLength = 22;
|
|
801
737
|
let normalizedUrl = apiBase.replace(/\/+$/, "");
|
|
802
738
|
if (normalizedUrl.length > originalLength) {
|
|
803
739
|
console.log(styleText("red", `Error: API base URL must be ${originalLength} characters or less`));
|
|
@@ -880,30 +816,24 @@ bin("droid-patch", "CLI tool to patch droid binary with various modifications").
|
|
|
880
816
|
}
|
|
881
817
|
if (result.success && result.outputPath && alias) {
|
|
882
818
|
console.log();
|
|
883
|
-
if (
|
|
884
|
-
const
|
|
885
|
-
const { wrapperScript } = await createWebSearchUnifiedFiles(websearchDir, result.outputPath, alias);
|
|
819
|
+
if (websearch) {
|
|
820
|
+
const { wrapperScript } = await createWebSearchUnifiedFiles(join(homedir(), ".droid-patch", "proxy"), result.outputPath, alias, websearchTarget);
|
|
886
821
|
await createAliasForWrapper(wrapperScript, alias, verbose);
|
|
887
822
|
console.log();
|
|
888
|
-
console.log(styleText("cyan", "WebSearch
|
|
889
|
-
console.log(styleText("
|
|
890
|
-
console.log(styleText("gray", " For better results, configure a provider:"));
|
|
891
|
-
console.log();
|
|
892
|
-
console.log(styleText("yellow", " Smithery Exa"), styleText("gray", " - Best quality, free via smithery.ai"));
|
|
893
|
-
console.log(styleText("gray", " export SMITHERY_API_KEY=... SMITHERY_PROFILE=..."));
|
|
894
|
-
console.log(styleText("yellow", " Google PSE"), styleText("gray", " - 10,000/day free"));
|
|
895
|
-
console.log(styleText("gray", " export GOOGLE_PSE_API_KEY=... GOOGLE_PSE_CX=..."));
|
|
896
|
-
console.log();
|
|
897
|
-
console.log(styleText("gray", " See README for all providers and setup guides"));
|
|
823
|
+
console.log(styleText("cyan", "WebSearch enabled"));
|
|
824
|
+
console.log(styleText("white", ` Forward target: ${websearchTarget}`));
|
|
898
825
|
} else await createAlias(result.outputPath, alias, verbose);
|
|
899
|
-
const
|
|
826
|
+
const droidVersion = getDroidVersion(path);
|
|
827
|
+
await saveAliasMetadata(createMetadata(alias, path, {
|
|
900
828
|
isCustom: !!isCustom,
|
|
901
829
|
skipLogin: !!skipLogin,
|
|
902
830
|
apiBase: apiBase || null,
|
|
903
|
-
websearch: !!
|
|
831
|
+
websearch: !!websearch,
|
|
904
832
|
reasoningEffort: !!reasoningEffort
|
|
905
|
-
}
|
|
906
|
-
|
|
833
|
+
}, {
|
|
834
|
+
droidPatchVersion: version,
|
|
835
|
+
droidVersion
|
|
836
|
+
}));
|
|
907
837
|
}
|
|
908
838
|
if (result.success) {
|
|
909
839
|
console.log();
|
|
@@ -919,8 +849,23 @@ bin("droid-patch", "CLI tool to patch droid binary with various modifications").
|
|
|
919
849
|
}
|
|
920
850
|
}).command("list", "List all droid-patch aliases").action(async () => {
|
|
921
851
|
await listAliases();
|
|
922
|
-
}).command("remove", "Remove
|
|
923
|
-
const target = args[0];
|
|
852
|
+
}).command("remove", "Remove alias(es) by name or filter").argument("[alias-or-path]", "Alias name or file path to remove").option("--patch-version <version>", "Remove aliases created by this droid-patch version").option("--droid-version <version>", "Remove aliases for this droid version").option("--flag <flag>", "Remove aliases with this flag (is-custom, skip-login, websearch, api-base, reasoning-effort)").action(async (options, args) => {
|
|
853
|
+
const target = args?.[0];
|
|
854
|
+
const patchVersion = options["patch-version"];
|
|
855
|
+
const droidVersion = options["droid-version"];
|
|
856
|
+
const flag = options.flag;
|
|
857
|
+
if (patchVersion || droidVersion || flag) {
|
|
858
|
+
await removeAliasesByFilter({
|
|
859
|
+
patchVersion,
|
|
860
|
+
droidVersion,
|
|
861
|
+
flags: flag ? [flag] : void 0
|
|
862
|
+
});
|
|
863
|
+
return;
|
|
864
|
+
}
|
|
865
|
+
if (!target) {
|
|
866
|
+
console.error(styleText("red", "Error: Provide an alias name or use filter options (--patch-version, --droid-version, --flag)"));
|
|
867
|
+
process.exit(1);
|
|
868
|
+
}
|
|
924
869
|
if (target.includes("/") || existsSync(target)) {
|
|
925
870
|
const { unlink: unlink$1 } = await import("node:fs/promises");
|
|
926
871
|
try {
|
|
@@ -933,86 +878,8 @@ bin("droid-patch", "CLI tool to patch droid binary with various modifications").
|
|
|
933
878
|
} else await removeAlias(target);
|
|
934
879
|
}).command("version", "Print droid-patch version").action(() => {
|
|
935
880
|
console.log(`droid-patch v${version}`);
|
|
936
|
-
}).command("
|
|
937
|
-
|
|
938
|
-
const logFile = "/tmp/droid-search-proxy.log";
|
|
939
|
-
const port = 23119;
|
|
940
|
-
console.log(styleText("cyan", "═".repeat(60)));
|
|
941
|
-
console.log(styleText(["cyan", "bold"], " WebSearch Proxy Status"));
|
|
942
|
-
console.log(styleText("cyan", "═".repeat(60)));
|
|
943
|
-
console.log();
|
|
944
|
-
try {
|
|
945
|
-
const response = await fetch(`http://127.0.0.1:${port}/health`);
|
|
946
|
-
if (response.ok) {
|
|
947
|
-
const data = await response.json();
|
|
948
|
-
console.log(styleText("green", ` Status: Running ✓`));
|
|
949
|
-
console.log(styleText("white", ` Port: ${port}`));
|
|
950
|
-
if (existsSync(pidFile)) {
|
|
951
|
-
const { readFileSync: readFileSync$1 } = await import("node:fs");
|
|
952
|
-
const pid = readFileSync$1(pidFile, "utf-8").trim();
|
|
953
|
-
console.log(styleText("white", ` PID: ${pid}`));
|
|
954
|
-
}
|
|
955
|
-
if (data.droidRunning !== void 0) console.log(styleText("white", ` Droid running: ${data.droidRunning ? "yes (proxy will stay alive)" : "no"}`));
|
|
956
|
-
if (data.idleTimeout !== void 0) if (data.idleTimeout > 0) {
|
|
957
|
-
const idleMins = Math.floor((data.idleSeconds || 0) / 60);
|
|
958
|
-
const idleSecs = (data.idleSeconds || 0) % 60;
|
|
959
|
-
if (data.droidRunning) console.log(styleText("white", ` Idle: ${idleMins}m ${idleSecs}s (won't shutdown while droid runs)`));
|
|
960
|
-
else if (data.willShutdownIn !== null) {
|
|
961
|
-
const shutdownMins = Math.floor((data.willShutdownIn || 0) / 60);
|
|
962
|
-
const shutdownSecs = (data.willShutdownIn || 0) % 60;
|
|
963
|
-
console.log(styleText("white", ` Idle: ${idleMins}m ${idleSecs}s`));
|
|
964
|
-
console.log(styleText("white", ` Auto-shutdown in: ${shutdownMins}m ${shutdownSecs}s`));
|
|
965
|
-
}
|
|
966
|
-
} else console.log(styleText("white", ` Auto-shutdown: disabled`));
|
|
967
|
-
console.log(styleText("white", ` Log: ${logFile}`));
|
|
968
|
-
console.log();
|
|
969
|
-
console.log(styleText("gray", "To stop the proxy manually:"));
|
|
970
|
-
console.log(styleText("cyan", " npx droid-patch proxy-stop"));
|
|
971
|
-
console.log();
|
|
972
|
-
console.log(styleText("gray", "To disable auto-shutdown:"));
|
|
973
|
-
console.log(styleText("cyan", " export DROID_PROXY_IDLE_TIMEOUT=0"));
|
|
974
|
-
}
|
|
975
|
-
} catch {
|
|
976
|
-
console.log(styleText("yellow", ` Status: Not running`));
|
|
977
|
-
console.log();
|
|
978
|
-
console.log(styleText("gray", "The proxy will start automatically when you run droid-full."));
|
|
979
|
-
console.log(styleText("gray", "It will auto-shutdown after 5 minutes of idle (configurable)."));
|
|
980
|
-
}
|
|
981
|
-
console.log();
|
|
982
|
-
}).command("proxy-stop", "Stop the websearch proxy").action(async () => {
|
|
983
|
-
const pidFile = "/tmp/droid-search-proxy.pid";
|
|
984
|
-
if (!existsSync(pidFile)) {
|
|
985
|
-
console.log(styleText("yellow", "Proxy is not running (no PID file)"));
|
|
986
|
-
return;
|
|
987
|
-
}
|
|
988
|
-
try {
|
|
989
|
-
const { readFileSync: readFileSync$1, unlinkSync: unlinkSync$1 } = await import("node:fs");
|
|
990
|
-
const pid = readFileSync$1(pidFile, "utf-8").trim();
|
|
991
|
-
process.kill(parseInt(pid), "SIGTERM");
|
|
992
|
-
unlinkSync$1(pidFile);
|
|
993
|
-
console.log(styleText("green", `[*] Proxy stopped (PID: ${pid})`));
|
|
994
|
-
} catch (error) {
|
|
995
|
-
console.log(styleText("yellow", `[!] Could not stop proxy: ${error.message}`));
|
|
996
|
-
try {
|
|
997
|
-
const { unlinkSync: unlinkSync$1 } = await import("node:fs");
|
|
998
|
-
unlinkSync$1(pidFile);
|
|
999
|
-
console.log(styleText("gray", "Cleaned up stale PID file"));
|
|
1000
|
-
} catch {}
|
|
1001
|
-
}
|
|
1002
|
-
}).command("proxy-log", "Show websearch proxy logs").action(async () => {
|
|
1003
|
-
const logFile = "/tmp/droid-search-proxy.log";
|
|
1004
|
-
if (!existsSync(logFile)) {
|
|
1005
|
-
console.log(styleText("yellow", "No log file found"));
|
|
1006
|
-
return;
|
|
1007
|
-
}
|
|
1008
|
-
const { readFileSync: readFileSync$1 } = await import("node:fs");
|
|
1009
|
-
const log = readFileSync$1(logFile, "utf-8");
|
|
1010
|
-
const lines = log.split("\n").slice(-50);
|
|
1011
|
-
console.log(styleText("cyan", "═".repeat(60)));
|
|
1012
|
-
console.log(styleText(["cyan", "bold"], " WebSearch Proxy Logs (last 50 lines)"));
|
|
1013
|
-
console.log(styleText("cyan", "═".repeat(60)));
|
|
1014
|
-
console.log();
|
|
1015
|
-
console.log(lines.join("\n"));
|
|
881
|
+
}).command("clear", "Remove all droid-patch aliases and related files").action(async () => {
|
|
882
|
+
await clearAllAliases();
|
|
1016
883
|
}).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) => {
|
|
1017
884
|
const aliasName = args?.[0];
|
|
1018
885
|
const dryRun = options["dry-run"];
|
|
@@ -1077,7 +944,7 @@ bin("droid-patch", "CLI tool to patch droid binary with various modifications").
|
|
|
1077
944
|
});
|
|
1078
945
|
if (meta.patches.apiBase) {
|
|
1079
946
|
const originalUrl = "https://api.factory.ai";
|
|
1080
|
-
const paddedUrl = meta.patches.apiBase.padEnd(
|
|
947
|
+
const paddedUrl = meta.patches.apiBase.padEnd(22, " ");
|
|
1081
948
|
patches.push({
|
|
1082
949
|
name: "apiBase",
|
|
1083
950
|
description: `Replace Factory API URL with "${meta.patches.apiBase}"`,
|
|
@@ -1117,18 +984,16 @@ bin("droid-patch", "CLI tool to patch droid binary with various modifications").
|
|
|
1117
984
|
replacement: Buffer.from("if(0&&!B.supportedReasoningEfforts.includes(R))")
|
|
1118
985
|
});
|
|
1119
986
|
}
|
|
1120
|
-
const
|
|
1121
|
-
const outputPath = join(binsDir, `${meta.name}-patched`);
|
|
987
|
+
const outputPath = join(join(homedir(), ".droid-patch", "bins"), `${meta.name}-patched`);
|
|
1122
988
|
if (patches.length > 0) {
|
|
1123
|
-
|
|
989
|
+
if (!(await patchDroid({
|
|
1124
990
|
inputPath: newBinaryPath,
|
|
1125
991
|
outputPath,
|
|
1126
992
|
patches,
|
|
1127
993
|
dryRun: false,
|
|
1128
994
|
backup: false,
|
|
1129
995
|
verbose
|
|
1130
|
-
})
|
|
1131
|
-
if (!result.success) {
|
|
996
|
+
})).success) {
|
|
1132
997
|
console.log(styleText("red", ` ✗ Failed to apply patches`));
|
|
1133
998
|
failCount++;
|
|
1134
999
|
continue;
|
|
@@ -1141,13 +1006,17 @@ bin("droid-patch", "CLI tool to patch droid binary with various modifications").
|
|
|
1141
1006
|
console.log(styleText("yellow", ` [!] Could not re-sign binary`));
|
|
1142
1007
|
}
|
|
1143
1008
|
}
|
|
1144
|
-
if (meta.patches.websearch) {
|
|
1145
|
-
const
|
|
1146
|
-
|
|
1147
|
-
await createWebSearchUnifiedFiles(websearchDir, targetBinaryPath, meta.name);
|
|
1009
|
+
if (meta.patches.websearch || !!meta.patches.proxy) {
|
|
1010
|
+
const forwardTarget = meta.patches.apiBase || meta.patches.proxy || "https://api.factory.ai";
|
|
1011
|
+
await createWebSearchUnifiedFiles(join(homedir(), ".droid-patch", "proxy"), patches.length > 0 ? outputPath : newBinaryPath, meta.name, forwardTarget);
|
|
1148
1012
|
if (verbose) console.log(styleText("gray", ` Regenerated websearch wrapper`));
|
|
1013
|
+
if (meta.patches.proxy && !meta.patches.websearch) {
|
|
1014
|
+
meta.patches.websearch = true;
|
|
1015
|
+
meta.patches.apiBase = meta.patches.proxy;
|
|
1016
|
+
delete meta.patches.proxy;
|
|
1017
|
+
}
|
|
1149
1018
|
}
|
|
1150
|
-
meta.updatedAt = new Date().toISOString();
|
|
1019
|
+
meta.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1151
1020
|
meta.originalBinaryPath = newBinaryPath;
|
|
1152
1021
|
await saveAliasMetadata(meta);
|
|
1153
1022
|
console.log(styleText("green", ` ✓ Updated successfully`));
|
|
@@ -1177,4 +1046,5 @@ bin("droid-patch", "CLI tool to patch droid binary with various modifications").
|
|
|
1177
1046
|
});
|
|
1178
1047
|
|
|
1179
1048
|
//#endregion
|
|
1180
|
-
|
|
1049
|
+
export { };
|
|
1050
|
+
//# sourceMappingURL=cli.mjs.map
|