droid-patch 0.5.0 → 0.6.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/dist/cli.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
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";
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-DKVU8DM_.mjs";
3
3
  import bin from "tiny-bin";
4
4
  import { styleText } from "node:util";
5
5
  import { existsSync, readFileSync } from "node:fs";
@@ -415,7 +415,48 @@ const server = http.createServer(async (req, res) => {
415
415
  return;
416
416
  }
417
417
 
418
- // Proxy all other requests to Factory API
418
+ // === Standalone mode (controlled by STANDALONE_MODE env) ===
419
+ // Whitelist approach: only allow core LLM APIs, mock everything else
420
+ if (process.env.STANDALONE_MODE === '1') {
421
+ const pathname = url.pathname;
422
+
423
+ // Whitelist: Core APIs that should be forwarded to upstream
424
+ const isCoreLLMApi = pathname.startsWith('/api/llm/a/') || pathname.startsWith('/api/llm/o/');
425
+ // /api/tools/exa/search is already handled above
426
+
427
+ if (!isCoreLLMApi) {
428
+ // Special handling for specific routes
429
+ if (pathname === '/api/sessions/create') {
430
+ log('Mock (dynamic):', pathname);
431
+ const sessionId = \`local-\${Date.now()}-\${Math.random().toString(36).slice(2, 10)}\`;
432
+ res.writeHead(200, { 'Content-Type': 'application/json' });
433
+ res.end(JSON.stringify({ id: sessionId }));
434
+ return;
435
+ }
436
+
437
+ if (pathname === '/api/cli/whoami') {
438
+ log('Mock (401):', pathname);
439
+ res.writeHead(401, { 'Content-Type': 'application/json' });
440
+ res.end(JSON.stringify({ error: 'Unauthorized', message: 'Local mode - use token fallback' }));
441
+ return;
442
+ }
443
+
444
+ if (pathname === '/api/tools/get-url-contents') {
445
+ log('Mock (404):', pathname);
446
+ res.writeHead(404, { 'Content-Type': 'application/json' });
447
+ res.end(JSON.stringify({ error: 'Not available', message: 'Use local URL fetch fallback' }));
448
+ return;
449
+ }
450
+
451
+ // All other non-core APIs: return empty success
452
+ log('Mock (default):', pathname);
453
+ res.writeHead(200, { 'Content-Type': 'application/json' });
454
+ res.end(JSON.stringify({}));
455
+ return;
456
+ }
457
+ }
458
+
459
+ // Proxy core LLM requests to upstream API
419
460
  log('Proxy:', req.method, url.pathname);
420
461
 
421
462
  const proxyUrl = new URL(FACTORY_API + url.pathname + url.search);
@@ -477,7 +518,8 @@ process.on('SIGINT', () => { server.close(); process.exit(0); });
477
518
  * - Proxy is killed when droid exits
478
519
  * - Supports multiple droid instances running simultaneously
479
520
  */
480
- function generateUnifiedWrapper(droidPath, proxyScriptPath) {
521
+ function generateUnifiedWrapper(droidPath, proxyScriptPath, standalone = false) {
522
+ const standaloneEnv = standalone ? "STANDALONE_MODE=1 " : "";
481
523
  return `#!/bin/bash
482
524
  # Droid with WebSearch
483
525
  # Auto-generated by droid-patch --websearch
@@ -487,6 +529,7 @@ PROXY_SCRIPT="${proxyScriptPath}"
487
529
  DROID_BIN="${droidPath}"
488
530
  PROXY_PID=""
489
531
  PORT_FILE="/tmp/droid-websearch-\$\$.port"
532
+ STANDALONE="${standalone ? "1" : "0"}"
490
533
 
491
534
  # Cleanup function - kill proxy when droid exits
492
535
  cleanup() {
@@ -502,13 +545,14 @@ cleanup() {
502
545
  trap cleanup EXIT INT TERM
503
546
 
504
547
  [ -n "\$DROID_SEARCH_DEBUG" ] && echo "[websearch] Starting proxy..." >&2
548
+ [ "\$STANDALONE" = "1" ] && [ -n "\$DROID_SEARCH_DEBUG" ] && echo "[websearch] Standalone mode enabled" >&2
505
549
 
506
550
  # Start proxy with port 0 (system will assign available port)
507
551
  # Proxy writes actual port to PORT_FILE
508
552
  if [ -n "\$DROID_SEARCH_DEBUG" ]; then
509
- SEARCH_PROXY_PORT=0 SEARCH_PROXY_PORT_FILE="\$PORT_FILE" node "\$PROXY_SCRIPT" 2>&1 &
553
+ ${standaloneEnv}SEARCH_PROXY_PORT=0 SEARCH_PROXY_PORT_FILE="\$PORT_FILE" node "\$PROXY_SCRIPT" 2>&1 &
510
554
  else
511
- SEARCH_PROXY_PORT=0 SEARCH_PROXY_PORT_FILE="\$PORT_FILE" node "\$PROXY_SCRIPT" >/dev/null 2>&1 &
555
+ ${standaloneEnv}SEARCH_PROXY_PORT=0 SEARCH_PROXY_PORT_FILE="\$PORT_FILE" node "\$PROXY_SCRIPT" >/dev/null 2>&1 &
512
556
  fi
513
557
  PROXY_PID=\$!
514
558
 
@@ -561,16 +605,18 @@ exit \$DROID_EXIT_CODE
561
605
  * @param droidPath - Path to droid binary
562
606
  * @param aliasName - Alias name for the wrapper
563
607
  * @param apiBase - Custom API base URL for proxy to forward requests to
608
+ * @param standalone - Standalone mode: mock non-LLM Factory APIs
564
609
  */
565
- async function createWebSearchUnifiedFiles(outputDir, droidPath, aliasName, apiBase) {
610
+ async function createWebSearchUnifiedFiles(outputDir, droidPath, aliasName, apiBase, standalone = false) {
566
611
  if (!existsSync(outputDir)) await mkdir(outputDir, { recursive: true });
567
612
  const proxyScriptPath = join(outputDir, `${aliasName}-proxy.js`);
568
613
  const wrapperScriptPath = join(outputDir, aliasName);
569
614
  await writeFile(proxyScriptPath, generateSearchProxyServer(apiBase || "https://api.factory.ai"));
570
615
  console.log(`[*] Created proxy script: ${proxyScriptPath}`);
571
- await writeFile(wrapperScriptPath, generateUnifiedWrapper(droidPath, proxyScriptPath));
616
+ await writeFile(wrapperScriptPath, generateUnifiedWrapper(droidPath, proxyScriptPath, standalone));
572
617
  await chmod(wrapperScriptPath, 493);
573
618
  console.log(`[*] Created wrapper: ${wrapperScriptPath}`);
619
+ if (standalone) console.log(`[*] Standalone mode enabled`);
574
620
  return {
575
621
  wrapperScript: wrapperScriptPath,
576
622
  preloadScript: proxyScriptPath
@@ -629,21 +675,23 @@ function findDefaultDroidPath() {
629
675
  for (const p of paths) if (existsSync(p)) return p;
630
676
  return join(home, ".droid", "bin", "droid");
631
677
  }
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) => {
678
+ 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("--standalone", "Standalone mode: mock non-LLM Factory APIs (use with --websearch)").option("--reasoning-effort", "Enable reasoning effort for custom models (set to high, enable UI selector)").option("--disable-telemetry", "Disable telemetry and Sentry error reporting (block data uploads)").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) => {
633
679
  const alias = args?.[0];
634
680
  const isCustom = options["is-custom"];
635
681
  const skipLogin = options["skip-login"];
636
682
  const apiBase = options["api-base"];
637
683
  const websearch = options["websearch"];
684
+ const standalone = options["standalone"];
638
685
  const websearchTarget = websearch ? apiBase || "https://api.factory.ai" : void 0;
639
686
  const reasoningEffort = options["reasoning-effort"];
687
+ const noTelemetry = options["disable-telemetry"];
640
688
  const dryRun = options["dry-run"];
641
689
  const path = options.path || findDefaultDroidPath();
642
690
  const outputDir = options.output;
643
691
  const backup = options.backup !== false;
644
692
  const verbose = options.verbose;
645
693
  const outputPath = outputDir && alias ? join(outputDir, alias) : void 0;
646
- if (websearch && !isCustom && !skipLogin && !reasoningEffort) {
694
+ if (websearch && !isCustom && !skipLogin && !reasoningEffort && !noTelemetry) {
647
695
  if (!alias) {
648
696
  console.log(styleText("red", "Error: Alias name required for --websearch"));
649
697
  console.log(styleText("gray", "Usage: npx droid-patch --websearch <alias>"));
@@ -654,8 +702,9 @@ bin("droid-patch", "CLI tool to patch droid binary with various modifications").
654
702
  console.log(styleText("cyan", "═".repeat(60)));
655
703
  console.log();
656
704
  console.log(styleText("white", `Forward target: ${websearchTarget}`));
705
+ if (standalone) console.log(styleText("white", `Standalone mode: enabled`));
657
706
  console.log();
658
- const { wrapperScript } = await createWebSearchUnifiedFiles(join(homedir(), ".droid-patch", "proxy"), path, alias, websearchTarget);
707
+ const { wrapperScript } = await createWebSearchUnifiedFiles(join(homedir(), ".droid-patch", "proxy"), path, alias, websearchTarget, standalone);
659
708
  await createAliasForWrapper(wrapperScript, alias, verbose);
660
709
  const droidVersion = getDroidVersion(path);
661
710
  await saveAliasMetadata(createMetadata(alias, path, {
@@ -663,7 +712,9 @@ bin("droid-patch", "CLI tool to patch droid binary with various modifications").
663
712
  skipLogin: false,
664
713
  apiBase: apiBase || null,
665
714
  websearch: true,
666
- reasoningEffort: false
715
+ reasoningEffort: false,
716
+ noTelemetry: false,
717
+ standalone
667
718
  }, {
668
719
  droidPatchVersion: version,
669
720
  droidVersion
@@ -693,19 +744,23 @@ bin("droid-patch", "CLI tool to patch droid binary with various modifications").
693
744
  console.log(styleText("gray", " export DROID_SEARCH_DEBUG=1"));
694
745
  return;
695
746
  }
696
- if (!isCustom && !skipLogin && !apiBase && !websearch && !reasoningEffort) {
747
+ if (!isCustom && !skipLogin && !apiBase && !websearch && !reasoningEffort && !noTelemetry) {
697
748
  console.log(styleText("yellow", "No patch flags specified. Available patches:"));
698
749
  console.log(styleText("gray", " --is-custom Patch isCustom for custom models"));
699
750
  console.log(styleText("gray", " --skip-login Bypass login by injecting a fake API key"));
700
751
  console.log(styleText("gray", " --api-base Replace Factory API URL (binary patch)"));
701
752
  console.log(styleText("gray", " --websearch Enable local WebSearch proxy"));
702
753
  console.log(styleText("gray", " --reasoning-effort Set reasoning effort level for custom models"));
754
+ console.log(styleText("gray", " --disable-telemetry Disable telemetry and Sentry error reporting"));
755
+ console.log(styleText("gray", " --standalone Standalone mode: mock non-LLM Factory APIs"));
703
756
  console.log();
704
757
  console.log("Usage examples:");
705
758
  console.log(styleText("cyan", " npx droid-patch --is-custom droid-custom"));
706
759
  console.log(styleText("cyan", " npx droid-patch --skip-login droid-nologin"));
707
760
  console.log(styleText("cyan", " npx droid-patch --is-custom --skip-login droid-patched"));
708
761
  console.log(styleText("cyan", " npx droid-patch --websearch droid-search"));
762
+ console.log(styleText("cyan", " npx droid-patch --websearch --standalone droid-local"));
763
+ console.log(styleText("cyan", " npx droid-patch --disable-telemetry droid-private"));
709
764
  console.log(styleText("cyan", " npx droid-patch --websearch --api-base=http://127.0.0.1:20002 my-droid"));
710
765
  process.exit(1);
711
766
  }
@@ -786,6 +841,26 @@ bin("droid-patch", "CLI tool to patch droid binary with various modifications").
786
841
  replacement: Buffer.from("if(0&&!B.supportedReasoningEfforts.includes(R))")
787
842
  });
788
843
  }
844
+ if (noTelemetry) {
845
+ patches.push({
846
+ name: "noTelemetrySentryEnv1",
847
+ description: "Break ENABLE_SENTRY env var check (E->X)",
848
+ pattern: Buffer.from("ENABLE_SENTRY"),
849
+ replacement: Buffer.from("XNABLE_SENTRY")
850
+ });
851
+ patches.push({
852
+ name: "noTelemetrySentryEnv2",
853
+ description: "Break VITE_VERCEL_ENV env var check (V->X)",
854
+ pattern: Buffer.from("VITE_VERCEL_ENV"),
855
+ replacement: Buffer.from("XITE_VERCEL_ENV")
856
+ });
857
+ patches.push({
858
+ name: "noTelemetryFlushBlock",
859
+ description: "Make flushToWeb always return (!0|| = always true)",
860
+ pattern: Buffer.from("this.webEvents.length===0"),
861
+ replacement: Buffer.from("!0||this.webEvents.length")
862
+ });
863
+ }
789
864
  try {
790
865
  const result = await patchDroid({
791
866
  inputPath: path,
@@ -817,11 +892,12 @@ bin("droid-patch", "CLI tool to patch droid binary with various modifications").
817
892
  if (result.success && result.outputPath && alias) {
818
893
  console.log();
819
894
  if (websearch) {
820
- const { wrapperScript } = await createWebSearchUnifiedFiles(join(homedir(), ".droid-patch", "proxy"), result.outputPath, alias, websearchTarget);
895
+ const { wrapperScript } = await createWebSearchUnifiedFiles(join(homedir(), ".droid-patch", "proxy"), result.outputPath, alias, websearchTarget, standalone);
821
896
  await createAliasForWrapper(wrapperScript, alias, verbose);
822
897
  console.log();
823
898
  console.log(styleText("cyan", "WebSearch enabled"));
824
899
  console.log(styleText("white", ` Forward target: ${websearchTarget}`));
900
+ if (standalone) console.log(styleText("white", ` Standalone mode: enabled`));
825
901
  } else await createAlias(result.outputPath, alias, verbose);
826
902
  const droidVersion = getDroidVersion(path);
827
903
  await saveAliasMetadata(createMetadata(alias, path, {
@@ -829,7 +905,9 @@ bin("droid-patch", "CLI tool to patch droid binary with various modifications").
829
905
  skipLogin: !!skipLogin,
830
906
  apiBase: apiBase || null,
831
907
  websearch: !!websearch,
832
- reasoningEffort: !!reasoningEffort
908
+ reasoningEffort: !!reasoningEffort,
909
+ noTelemetry: !!noTelemetry,
910
+ standalone: !!standalone
833
911
  }, {
834
912
  droidPatchVersion: version,
835
913
  droidVersion
@@ -849,7 +927,7 @@ bin("droid-patch", "CLI tool to patch droid binary with various modifications").
849
927
  }
850
928
  }).command("list", "List all droid-patch aliases").action(async () => {
851
929
  await listAliases();
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) => {
930
+ }).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, disable-telemetry, standalone)").action(async (options, args) => {
853
931
  const target = args?.[0];
854
932
  const patchVersion = options["patch-version"];
855
933
  const droidVersion = options["droid-version"];
@@ -984,6 +1062,26 @@ bin("droid-patch", "CLI tool to patch droid binary with various modifications").
984
1062
  replacement: Buffer.from("if(0&&!B.supportedReasoningEfforts.includes(R))")
985
1063
  });
986
1064
  }
1065
+ if (meta.patches.noTelemetry) {
1066
+ patches.push({
1067
+ name: "noTelemetrySentryEnv1",
1068
+ description: "Break ENABLE_SENTRY env var check (E->X)",
1069
+ pattern: Buffer.from("ENABLE_SENTRY"),
1070
+ replacement: Buffer.from("XNABLE_SENTRY")
1071
+ });
1072
+ patches.push({
1073
+ name: "noTelemetrySentryEnv2",
1074
+ description: "Break VITE_VERCEL_ENV env var check (V->X)",
1075
+ pattern: Buffer.from("VITE_VERCEL_ENV"),
1076
+ replacement: Buffer.from("XITE_VERCEL_ENV")
1077
+ });
1078
+ patches.push({
1079
+ name: "noTelemetryFlushBlock",
1080
+ description: "Make flushToWeb always return (!0|| = always true)",
1081
+ pattern: Buffer.from("this.webEvents.length===0"),
1082
+ replacement: Buffer.from("!0||this.webEvents.length")
1083
+ });
1084
+ }
987
1085
  const outputPath = join(join(homedir(), ".droid-patch", "bins"), `${meta.name}-patched`);
988
1086
  if (patches.length > 0) {
989
1087
  if (!(await patchDroid({
@@ -1008,8 +1106,11 @@ bin("droid-patch", "CLI tool to patch droid binary with various modifications").
1008
1106
  }
1009
1107
  if (meta.patches.websearch || !!meta.patches.proxy) {
1010
1108
  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);
1012
- if (verbose) console.log(styleText("gray", ` Regenerated websearch wrapper`));
1109
+ await createWebSearchUnifiedFiles(join(homedir(), ".droid-patch", "proxy"), patches.length > 0 ? outputPath : newBinaryPath, meta.name, forwardTarget, meta.patches.standalone || false);
1110
+ if (verbose) {
1111
+ console.log(styleText("gray", ` Regenerated websearch wrapper`));
1112
+ if (meta.patches.standalone) console.log(styleText("gray", ` Standalone mode: enabled`));
1113
+ }
1013
1114
  if (meta.patches.proxy && !meta.patches.websearch) {
1014
1115
  meta.patches.websearch = true;
1015
1116
  meta.patches.apiBase = meta.patches.proxy;