droid-patch 0.2.1 → 0.4.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.js CHANGED
@@ -1,11 +1,12 @@
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-UwlvAO5o.js";
3
3
  import bin from "tiny-bin";
4
4
  import { styleText } from "node:util";
5
5
  import { existsSync, readFileSync } from "node:fs";
6
6
  import { dirname, join } from "node:path";
7
7
  import { homedir } from "node:os";
8
8
  import { fileURLToPath } from "node:url";
9
+ import { execSync } from "node:child_process";
9
10
  import { chmod, mkdir, writeFile } from "node:fs/promises";
10
11
 
11
12
  //#region src/websearch-patch.ts
@@ -674,20 +675,34 @@ function getVersion() {
674
675
  const version = getVersion();
675
676
  function findDefaultDroidPath() {
676
677
  const home = homedir();
678
+ try {
679
+ const result = execSync("which droid", {
680
+ encoding: "utf-8",
681
+ stdio: [
682
+ "pipe",
683
+ "pipe",
684
+ "pipe"
685
+ ]
686
+ }).trim();
687
+ if (result && existsSync(result)) return result;
688
+ } catch {}
677
689
  const paths = [
678
- join(home, ".droid/bin/droid"),
690
+ join(home, ".droid", "bin", "droid"),
691
+ "/opt/homebrew/bin/droid",
679
692
  "/usr/local/bin/droid",
693
+ "/usr/bin/droid",
680
694
  "./droid"
681
695
  ];
682
696
  for (const p of paths) if (existsSync(p)) return p;
683
- return join(home, ".droid/bin/droid");
697
+ return join(home, ".droid", "bin", "droid");
684
698
  }
685
- 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 base URL (https://api.factory.ai) with custom URL").option("--websearch", "Enable local WebSearch via fetch hook (Google PSE + DuckDuckGo fallback)").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) => {
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 base URL (https://api.factory.ai) with custom URL").option("--websearch", "Enable local WebSearch via fetch hook (Google PSE + DuckDuckGo fallback)").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) => {
686
700
  const alias = args?.[0];
687
701
  const isCustom = options["is-custom"];
688
702
  const skipLogin = options["skip-login"];
689
703
  const apiBase = options["api-base"];
690
704
  const webSearch = options["websearch"];
705
+ const reasoningEffort = options["reasoning-effort"];
691
706
  const dryRun = options["dry-run"];
692
707
  const path = options.path || findDefaultDroidPath();
693
708
  const outputDir = options.output;
@@ -707,6 +722,14 @@ bin("droid-patch", "CLI tool to patch droid binary with various modifications").
707
722
  const websearchDir = join(homedir(), ".droid-patch", "websearch");
708
723
  const { wrapperScript } = await createWebSearchUnifiedFiles(websearchDir, path, alias);
709
724
  await createAliasForWrapper(wrapperScript, alias, verbose);
725
+ const metadata = createMetadata(alias, path, {
726
+ isCustom: false,
727
+ skipLogin: false,
728
+ apiBase: null,
729
+ websearch: true,
730
+ reasoningEffort: false
731
+ });
732
+ await saveAliasMetadata(metadata);
710
733
  console.log();
711
734
  console.log(styleText("green", "═".repeat(60)));
712
735
  console.log(styleText(["green", "bold"], " WebSearch Ready!"));
@@ -732,12 +755,13 @@ bin("droid-patch", "CLI tool to patch droid binary with various modifications").
732
755
  console.log(styleText("gray", " export DROID_SEARCH_DEBUG=1"));
733
756
  return;
734
757
  }
735
- if (!isCustom && !skipLogin && !apiBase && !webSearch) {
758
+ if (!isCustom && !skipLogin && !apiBase && !webSearch && !reasoningEffort) {
736
759
  console.log(styleText("yellow", "No patch flags specified. Available patches:"));
737
- console.log(styleText("gray", " --is-custom Patch isCustom for custom models"));
738
- console.log(styleText("gray", " --skip-login Bypass login by injecting a fake API key"));
739
- console.log(styleText("gray", " --api-base Replace Factory API URL with custom server"));
740
- console.log(styleText("gray", " --websearch Enable local WebSearch (Google PSE + DuckDuckGo)"));
760
+ console.log(styleText("gray", " --is-custom Patch isCustom for custom models"));
761
+ 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 with custom server"));
763
+ console.log(styleText("gray", " --websearch Enable local WebSearch (Google PSE + DuckDuckGo)"));
764
+ console.log(styleText("gray", " --reasoning-effort Set reasoning effort level for custom models"));
741
765
  console.log();
742
766
  console.log("Usage examples:");
743
767
  console.log(styleText("cyan", " npx droid-patch --is-custom droid-custom"));
@@ -746,6 +770,7 @@ bin("droid-patch", "CLI tool to patch droid binary with various modifications").
746
770
  console.log(styleText("cyan", " npx droid-patch --skip-login -o . my-droid"));
747
771
  console.log(styleText("cyan", " npx droid-patch --api-base http://localhost:3000 droid-local"));
748
772
  console.log(styleText("cyan", " npx droid-patch --websearch droid-search"));
773
+ console.log(styleText("cyan", " npx droid-patch --reasoning-effort high droid-reasoning"));
749
774
  process.exit(1);
750
775
  }
751
776
  if (!alias && !dryRun) {
@@ -793,6 +818,32 @@ bin("droid-patch", "CLI tool to patch droid binary with various modifications").
793
818
  replacement: Buffer.from(paddedUrl)
794
819
  });
795
820
  }
821
+ if (reasoningEffort) {
822
+ patches.push({
823
+ name: "reasoningEffortSupported",
824
+ description: "Change supportedReasoningEfforts:[\"none\"] to [\"high\"]",
825
+ pattern: Buffer.from("supportedReasoningEfforts:[\"none\"]"),
826
+ replacement: Buffer.from("supportedReasoningEfforts:[\"high\"]")
827
+ });
828
+ patches.push({
829
+ name: "reasoningEffortDefault",
830
+ description: "Change defaultReasoningEffort:\"none\" to \"high\"",
831
+ pattern: Buffer.from("defaultReasoningEffort:\"none\""),
832
+ replacement: Buffer.from("defaultReasoningEffort:\"high\"")
833
+ });
834
+ patches.push({
835
+ name: "reasoningEffortUIShow",
836
+ description: "Change supportedReasoningEfforts.length>1 to length>0",
837
+ pattern: Buffer.from("supportedReasoningEfforts.length>1"),
838
+ replacement: Buffer.from("supportedReasoningEfforts.length>0")
839
+ });
840
+ patches.push({
841
+ name: "reasoningEffortUIEnable",
842
+ description: "Change supportedReasoningEfforts.length<=1 to length<=0",
843
+ pattern: Buffer.from("supportedReasoningEfforts.length<=1"),
844
+ replacement: Buffer.from("supportedReasoningEfforts.length<=0")
845
+ });
846
+ }
796
847
  try {
797
848
  const result = await patchDroid({
798
849
  inputPath: path,
@@ -839,6 +890,14 @@ bin("droid-patch", "CLI tool to patch droid binary with various modifications").
839
890
  console.log();
840
891
  console.log(styleText("gray", " See README for all providers and setup guides"));
841
892
  } else await createAlias(result.outputPath, alias, verbose);
893
+ const metadata = createMetadata(alias, path, {
894
+ isCustom: !!isCustom,
895
+ skipLogin: !!skipLogin,
896
+ apiBase: apiBase || null,
897
+ websearch: !!webSearch,
898
+ reasoningEffort: !!reasoningEffort
899
+ });
900
+ await saveAliasMetadata(metadata);
842
901
  }
843
902
  if (result.success) {
844
903
  console.log();
@@ -948,6 +1007,158 @@ bin("droid-patch", "CLI tool to patch droid binary with various modifications").
948
1007
  console.log(styleText("cyan", "═".repeat(60)));
949
1008
  console.log();
950
1009
  console.log(lines.join("\n"));
1010
+ }).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) => {
1011
+ const aliasName = args?.[0];
1012
+ const dryRun = options["dry-run"];
1013
+ const newBinaryPath = options.path || findDefaultDroidPath();
1014
+ const verbose = options.verbose;
1015
+ console.log(styleText("cyan", "═".repeat(60)));
1016
+ console.log(styleText(["cyan", "bold"], " Droid-Patch Update"));
1017
+ console.log(styleText("cyan", "═".repeat(60)));
1018
+ console.log();
1019
+ if (!existsSync(newBinaryPath)) {
1020
+ console.log(styleText("red", `Error: Droid binary not found at ${newBinaryPath}`));
1021
+ console.log(styleText("gray", "Use -p to specify a different path"));
1022
+ process.exit(1);
1023
+ }
1024
+ let metaList;
1025
+ if (aliasName) {
1026
+ const meta = await loadAliasMetadata(aliasName);
1027
+ if (!meta) {
1028
+ console.log(styleText("red", `Error: No metadata found for alias "${aliasName}"`));
1029
+ console.log(styleText("gray", "This alias may have been created before update tracking was added."));
1030
+ console.log(styleText("gray", "Remove and recreate the alias to enable update support."));
1031
+ process.exit(1);
1032
+ }
1033
+ metaList = [meta];
1034
+ } else {
1035
+ metaList = await listAllMetadata();
1036
+ if (metaList.length === 0) {
1037
+ console.log(styleText("yellow", "No aliases with metadata found."));
1038
+ console.log(styleText("gray", "Create aliases with droid-patch to enable update support."));
1039
+ process.exit(0);
1040
+ }
1041
+ }
1042
+ console.log(styleText("white", `Using droid binary: ${newBinaryPath}`));
1043
+ console.log(styleText("white", `Found ${metaList.length} alias(es) to update`));
1044
+ if (dryRun) console.log(styleText("blue", "(DRY RUN - no changes will be made)"));
1045
+ console.log();
1046
+ let successCount = 0;
1047
+ let failCount = 0;
1048
+ for (const meta of metaList) {
1049
+ if (!meta) continue;
1050
+ console.log(styleText("cyan", `─`.repeat(40)));
1051
+ console.log(styleText("white", `Updating: ${styleText(["cyan", "bold"], meta.name)}`));
1052
+ console.log(styleText("gray", ` Patches: ${formatPatches(meta.patches)}`));
1053
+ if (dryRun) {
1054
+ console.log(styleText("blue", ` [DRY RUN] Would re-apply patches`));
1055
+ successCount++;
1056
+ continue;
1057
+ }
1058
+ try {
1059
+ const patches = [];
1060
+ if (meta.patches.isCustom) patches.push({
1061
+ name: "isCustom",
1062
+ description: "Change isCustom:!0 to isCustom:!1",
1063
+ pattern: Buffer.from("isCustom:!0"),
1064
+ replacement: Buffer.from("isCustom:!1")
1065
+ });
1066
+ if (meta.patches.skipLogin) patches.push({
1067
+ name: "skipLogin",
1068
+ description: "Replace process.env.FACTORY_API_KEY with fake key",
1069
+ pattern: Buffer.from("process.env.FACTORY_API_KEY"),
1070
+ replacement: Buffer.from("\"fk-droid-patch-skip-00000\"")
1071
+ });
1072
+ if (meta.patches.apiBase) {
1073
+ const originalUrl = "https://api.factory.ai";
1074
+ const paddedUrl = meta.patches.apiBase.padEnd(originalUrl.length, " ");
1075
+ patches.push({
1076
+ name: "apiBase",
1077
+ description: `Replace Factory API URL with "${meta.patches.apiBase}"`,
1078
+ pattern: Buffer.from(originalUrl),
1079
+ replacement: Buffer.from(paddedUrl)
1080
+ });
1081
+ }
1082
+ if (meta.patches.reasoningEffort) {
1083
+ patches.push({
1084
+ name: "reasoningEffortSupported",
1085
+ description: "Change supportedReasoningEfforts:[\"none\"] to [\"high\"]",
1086
+ pattern: Buffer.from("supportedReasoningEfforts:[\"none\"]"),
1087
+ replacement: Buffer.from("supportedReasoningEfforts:[\"high\"]")
1088
+ });
1089
+ patches.push({
1090
+ name: "reasoningEffortDefault",
1091
+ description: "Change defaultReasoningEffort:\"none\" to \"high\"",
1092
+ pattern: Buffer.from("defaultReasoningEffort:\"none\""),
1093
+ replacement: Buffer.from("defaultReasoningEffort:\"high\"")
1094
+ });
1095
+ patches.push({
1096
+ name: "reasoningEffortUIShow",
1097
+ description: "Change supportedReasoningEfforts.length>1 to length>0",
1098
+ pattern: Buffer.from("supportedReasoningEfforts.length>1"),
1099
+ replacement: Buffer.from("supportedReasoningEfforts.length>0")
1100
+ });
1101
+ patches.push({
1102
+ name: "reasoningEffortUIEnable",
1103
+ description: "Change supportedReasoningEfforts.length<=1 to length<=0",
1104
+ pattern: Buffer.from("supportedReasoningEfforts.length<=1"),
1105
+ replacement: Buffer.from("supportedReasoningEfforts.length<=0")
1106
+ });
1107
+ }
1108
+ const binsDir = join(homedir(), ".droid-patch", "bins");
1109
+ const outputPath = join(binsDir, `${meta.name}-patched`);
1110
+ if (patches.length > 0) {
1111
+ const result = await patchDroid({
1112
+ inputPath: newBinaryPath,
1113
+ outputPath,
1114
+ patches,
1115
+ dryRun: false,
1116
+ backup: false,
1117
+ verbose
1118
+ });
1119
+ if (!result.success) {
1120
+ console.log(styleText("red", ` ✗ Failed to apply patches`));
1121
+ failCount++;
1122
+ continue;
1123
+ }
1124
+ if (process.platform === "darwin") try {
1125
+ const { execSync: execSync$1 } = await import("node:child_process");
1126
+ execSync$1(`codesign --force --deep --sign - "${outputPath}"`, { stdio: "pipe" });
1127
+ if (verbose) console.log(styleText("gray", ` Re-signed binary`));
1128
+ } catch {
1129
+ console.log(styleText("yellow", ` [!] Could not re-sign binary`));
1130
+ }
1131
+ }
1132
+ if (meta.patches.websearch) {
1133
+ const websearchDir = join(homedir(), ".droid-patch", "websearch");
1134
+ const targetBinaryPath = patches.length > 0 ? outputPath : newBinaryPath;
1135
+ await createWebSearchUnifiedFiles(websearchDir, targetBinaryPath, meta.name);
1136
+ if (verbose) console.log(styleText("gray", ` Regenerated websearch wrapper`));
1137
+ }
1138
+ meta.updatedAt = new Date().toISOString();
1139
+ meta.originalBinaryPath = newBinaryPath;
1140
+ await saveAliasMetadata(meta);
1141
+ console.log(styleText("green", ` ✓ Updated successfully`));
1142
+ successCount++;
1143
+ } catch (error) {
1144
+ console.log(styleText("red", ` ✗ Error: ${error.message}`));
1145
+ if (verbose) console.error(error.stack);
1146
+ failCount++;
1147
+ }
1148
+ }
1149
+ console.log();
1150
+ console.log(styleText("cyan", "═".repeat(60)));
1151
+ if (dryRun) {
1152
+ console.log(styleText(["blue", "bold"], " DRY RUN COMPLETE"));
1153
+ console.log(styleText("gray", ` Would update ${successCount} alias(es)`));
1154
+ } else if (failCount === 0) {
1155
+ console.log(styleText(["green", "bold"], " UPDATE COMPLETE"));
1156
+ console.log(styleText("gray", ` Updated ${successCount} alias(es)`));
1157
+ } else {
1158
+ console.log(styleText(["yellow", "bold"], " UPDATE FINISHED WITH ERRORS"));
1159
+ console.log(styleText("gray", ` Success: ${successCount}, Failed: ${failCount}`));
1160
+ }
1161
+ console.log(styleText("cyan", "═".repeat(60)));
951
1162
  }).run().catch((err) => {
952
1163
  console.error(err);
953
1164
  process.exit(1);