droid-patch 0.12.1 → 0.13.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
@@ -74,6 +74,40 @@ npx droid-patch --skip-login -o /path/to/dir my-droid
74
74
  | `--no-backup` | Skip creating backup of original binary |
75
75
  | `-v, --verbose` | Enable verbose output |
76
76
 
77
+ ### Manage Custom Models
78
+
79
+ ```bash
80
+ # List all custom models
81
+ npx droid-patch list-models
82
+
83
+ # Add model interactively
84
+ npx droid-patch add-model
85
+
86
+ # Add model via command line
87
+ npx droid-patch add-model \
88
+ -m "claude-sonnet-4-20250514" \
89
+ -n "Sonnet [proxy]" \
90
+ -u "http://127.0.0.1:20002/droid" \
91
+ -k "your-api-key" \
92
+ -p "anthropic"
93
+
94
+ # Insert model at specific position
95
+ npx droid-patch add-model -i 0 # Interactive, insert at position 0
96
+
97
+ # Remove model (supports index, ID, or displayName)
98
+ npx droid-patch remove-model 0 # By index
99
+ npx droid-patch remove-model "custom:Sonnet-[proxy]-1" # By ID
100
+ npx droid-patch remove-model "Sonnet [proxy]" # By display name
101
+ ```
102
+
103
+ **Model ID Format**: `custom:{DisplayName}-{index}`
104
+
105
+ - Spaces in `DisplayName` are replaced with `-`
106
+ - `index` is the position in the array (starting from 0)
107
+ - Example: `displayName: "Opus [proxy]"` → `id: "custom:Opus-[proxy]-0"`
108
+
109
+ **Important**: When deleting or inserting models, subsequent model IDs are automatically updated (because index changes).
110
+
77
111
  ### Manage Aliases and Files
78
112
 
79
113
  ```bash
@@ -285,19 +319,25 @@ Configure your custom model in `~/.factory/settings.json`:
285
319
  "customModels": [
286
320
  {
287
321
  "model": "claude-sonnet-4-20250514",
288
- "id": "custom:Claude-Proxy-0",
322
+ "id": "custom:Opus-[proxy]-0",
289
323
  "baseUrl": "http://127.0.0.1:20002/droid",
290
324
  "apiKey": "your-api-key",
291
- "displayName": "Claude [proxy]",
325
+ "displayName": "Opus [proxy]",
292
326
  "provider": "anthropic"
293
327
  }
294
328
  ],
295
329
  "sessionDefaultSettings": {
296
- "model": "custom:Claude-Proxy-0"
330
+ "model": "custom:Opus-[proxy]-0"
297
331
  }
298
332
  }
299
333
  ```
300
334
 
335
+ **Important**: The `id` field must match the `displayName` pattern:
336
+
337
+ - Format: `custom:{DisplayName}-{index}` where spaces are replaced with `-`
338
+ - Example: `displayName: "Opus [proxy]"` → `id: "custom:Opus-[proxy]-0"`
339
+ - The trailing number (`-0`) is the index (starting from 0)
340
+
301
341
  ### `--reasoning-effort`
302
342
 
303
343
  Enables reasoning effort control for custom models by patching the binary to:
package/README.zh-CN.md CHANGED
@@ -74,6 +74,40 @@ npx droid-patch --skip-login -o /path/to/dir my-droid
74
74
  | `--no-backup` | 跳过创建原始二进制文件的备份 |
75
75
  | `-v, --verbose` | 启用详细输出 |
76
76
 
77
+ ### 管理自定义模型
78
+
79
+ ```bash
80
+ # 列出所有自定义模型
81
+ npx droid-patch list-models
82
+
83
+ # 交互式添加模型
84
+ npx droid-patch add-model
85
+
86
+ # 命令行添加模型
87
+ npx droid-patch add-model \
88
+ -m "claude-sonnet-4-20250514" \
89
+ -n "Sonnet [proxy]" \
90
+ -u "http://127.0.0.1:20002/droid" \
91
+ -k "your-api-key" \
92
+ -p "anthropic"
93
+
94
+ # 在指定位置插入模型
95
+ npx droid-patch add-model -i 0 # 交互式,插入到位置 0
96
+
97
+ # 删除模型(支持 index、ID 或 displayName)
98
+ npx droid-patch remove-model 0 # 按索引
99
+ npx droid-patch remove-model "custom:Sonnet-[proxy]-1" # 按 ID
100
+ npx droid-patch remove-model "Sonnet [proxy]" # 按显示名称
101
+ ```
102
+
103
+ **模型 ID 格式**:`custom:{DisplayName}-{index}`
104
+
105
+ - `DisplayName` 中的空格会被替换为 `-`
106
+ - `index` 是模型在数组中的位置(从 0 开始)
107
+ - 示例:`displayName: "Opus [proxy]"` → `id: "custom:Opus-[proxy]-0"`
108
+
109
+ **重要**:删除或插入模型时,后续模型的 ID 会自动更新(因为 index 会变化)。
110
+
77
111
  ### 管理别名和文件
78
112
 
79
113
  ```bash
@@ -1,11 +1,12 @@
1
1
  import { styleText } from "node:util";
2
2
  import { appendFileSync, existsSync, lstatSync, mkdirSync, readFileSync, readdirSync, unlinkSync, writeFileSync } from "node:fs";
3
- import { basename, dirname, join } from "node:path";
4
- import { homedir } from "node:os";
3
+ import { basename, delimiter, dirname, join } from "node:path";
4
+ import { homedir, platform } from "node:os";
5
5
  import { execSync } from "node:child_process";
6
6
  import { chmod, copyFile, mkdir, readFile, readdir, readlink, stat, symlink, unlink, writeFile } from "node:fs/promises";
7
7
 
8
8
  //#region src/patcher.ts
9
+ const IS_WINDOWS$1 = platform() === "win32";
9
10
  async function patchDroid(options) {
10
11
  const { inputPath, outputPath, patches, dryRun = false, backup = true, verbose = false } = options;
11
12
  const finalOutputPath = outputPath || `${inputPath}.patched`;
@@ -152,13 +153,24 @@ async function patchDroid(options) {
152
153
  console.log(styleText("white", "[*] Applying patches..."));
153
154
  const totalPatched = results.reduce((sum, r) => sum + (r.positions?.length || 0), 0);
154
155
  console.log(styleText("green", `[*] Applied ${totalPatched} patches`));
155
- await writeFile(finalOutputPath, workingBuffer);
156
- console.log(styleText("white", `[*] Patched binary saved: ${styleText("cyan", finalOutputPath)}`));
157
- await chmod(finalOutputPath, 493);
156
+ let actualOutputPath = finalOutputPath;
157
+ try {
158
+ await writeFile(finalOutputPath, workingBuffer);
159
+ } catch (error) {
160
+ if (error.code === "EBUSY" && IS_WINDOWS$1) {
161
+ const timestamp = Date.now();
162
+ const ext = finalOutputPath.endsWith(".exe") ? ".exe" : "";
163
+ actualOutputPath = `${finalOutputPath.replace(/\.exe$/, "").replace(/\.patched$/, "").replace(/-\d+$/, "")}-${timestamp}${ext ? ext : ".patched"}`;
164
+ console.log(styleText("yellow", `[!] Original file locked, saving to: ${actualOutputPath}`));
165
+ await writeFile(actualOutputPath, workingBuffer);
166
+ } else throw error;
167
+ }
168
+ console.log(styleText("white", `[*] Patched binary saved: ${styleText("cyan", actualOutputPath)}`));
169
+ await chmod(actualOutputPath, 493);
158
170
  console.log(styleText("gray", "[*] Set executable permission"));
159
171
  console.log();
160
172
  console.log(styleText("white", "[*] Verifying patches..."));
161
- const verifyBuffer = await readFile(finalOutputPath);
173
+ const verifyBuffer = await readFile(actualOutputPath);
162
174
  let allVerified = true;
163
175
  for (const patch of patches) {
164
176
  if (patch.regexPattern && patch.regexReplacement) {
@@ -206,7 +218,7 @@ async function patchDroid(options) {
206
218
  }
207
219
  return {
208
220
  success: allVerified,
209
- outputPath: finalOutputPath,
221
+ outputPath: actualOutputPath,
210
222
  results,
211
223
  patchedCount: totalPatched
212
224
  };
@@ -338,17 +350,17 @@ function formatPatches(patches) {
338
350
  if (patches.reasoningEffort) applied.push("reasoningEffort");
339
351
  if (patches.noTelemetry) applied.push("noTelemetry");
340
352
  if (patches.standalone) applied.push("standalone");
341
- if (patches.autoHigh) applied.push("autoHigh");
342
353
  if (patches.specModelCustom) applied.push("specModelCustom");
343
354
  return applied.length > 0 ? applied.join(", ") : "(none)";
344
355
  }
345
356
 
346
357
  //#endregion
347
358
  //#region src/alias.ts
359
+ const IS_WINDOWS = platform() === "win32";
348
360
  const DROID_PATCH_DIR = join(homedir(), ".droid-patch");
349
361
  const ALIASES_DIR = join(DROID_PATCH_DIR, "aliases");
350
362
  const BINS_DIR = join(DROID_PATCH_DIR, "bins");
351
- const COMMON_PATH_DIRS = [
363
+ const UNIX_PATH_DIRS = [
352
364
  join(homedir(), ".local/bin"),
353
365
  join(homedir(), "bin"),
354
366
  join(homedir(), ".bin"),
@@ -369,17 +381,23 @@ const COMMON_PATH_DIRS = [
369
381
  join(homedir(), ".volta/bin"),
370
382
  join(homedir(), ".fnm/current/bin")
371
383
  ];
384
+ const WINDOWS_PATH_DIRS = [
385
+ join(homedir(), ".droid-patch", "bin"),
386
+ join(homedir(), "scoop", "shims"),
387
+ join(homedir(), "AppData", "Local", "Programs", "bin")
388
+ ];
389
+ const COMMON_PATH_DIRS = IS_WINDOWS ? WINDOWS_PATH_DIRS : UNIX_PATH_DIRS;
372
390
  function ensureDirectories() {
373
391
  if (!existsSync(DROID_PATCH_DIR)) mkdirSync(DROID_PATCH_DIR, { recursive: true });
374
392
  if (!existsSync(ALIASES_DIR)) mkdirSync(ALIASES_DIR, { recursive: true });
375
393
  if (!existsSync(BINS_DIR)) mkdirSync(BINS_DIR, { recursive: true });
376
394
  }
377
395
  function checkPathInclusion() {
378
- return (process.env.PATH || "").split(":").includes(ALIASES_DIR);
396
+ return (process.env.PATH || "").split(delimiter).some((p) => p.toLowerCase() === ALIASES_DIR.toLowerCase());
379
397
  }
380
398
  function findWritablePathDir() {
381
- const pathDirs = (process.env.PATH || "").split(":");
382
- for (const dir of COMMON_PATH_DIRS) if (pathDirs.includes(dir)) try {
399
+ const pathDirs = (process.env.PATH || "").split(delimiter);
400
+ for (const dir of COMMON_PATH_DIRS) if (pathDirs.some((p) => p.toLowerCase() === dir.toLowerCase())) try {
383
401
  if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
384
402
  const testFile = join(dir, `.droid-patch-test-${Date.now()}`);
385
403
  writeFileSync(testFile, "");
@@ -391,6 +409,7 @@ function findWritablePathDir() {
391
409
  return null;
392
410
  }
393
411
  function getShellConfigPath() {
412
+ if (IS_WINDOWS) return "";
394
413
  switch (basename(process.env.SHELL || "/bin/bash")) {
395
414
  case "zsh": return join(homedir(), ".zshrc");
396
415
  case "bash": {
@@ -403,6 +422,7 @@ function getShellConfigPath() {
403
422
  }
404
423
  }
405
424
  function isPathConfigured(shellConfigPath) {
425
+ if (IS_WINDOWS || !shellConfigPath) return false;
406
426
  if (!existsSync(shellConfigPath)) return false;
407
427
  try {
408
428
  const content = readFileSync(shellConfigPath, "utf-8");
@@ -411,6 +431,37 @@ function isPathConfigured(shellConfigPath) {
411
431
  return false;
412
432
  }
413
433
  }
434
+ /**
435
+ * Add directory to Windows user PATH using setx command
436
+ * This modifies the user's PATH permanently (requires terminal restart)
437
+ */
438
+ function addToWindowsUserPath(dir) {
439
+ try {
440
+ let existingPath = "";
441
+ try {
442
+ const match = execSync("reg query \"HKCU\\Environment\" /v Path 2>nul", {
443
+ encoding: "utf-8",
444
+ stdio: [
445
+ "pipe",
446
+ "pipe",
447
+ "pipe"
448
+ ]
449
+ }).match(/Path\s+REG_(?:EXPAND_)?SZ\s+(.+)/);
450
+ existingPath = match ? match[1].trim() : "";
451
+ } catch {}
452
+ if (existingPath.split(";").map((p) => p.toLowerCase().trim()).includes(dir.toLowerCase())) return true;
453
+ execSync(`setx PATH "${existingPath ? `${existingPath};${dir}` : dir}"`, { stdio: "pipe" });
454
+ return true;
455
+ } catch {
456
+ return false;
457
+ }
458
+ }
459
+ /**
460
+ * Generate Windows .cmd launcher script
461
+ */
462
+ function generateWindowsLauncher(targetPath) {
463
+ return `@echo off\r\n"${targetPath}" %*\r\n`;
464
+ }
414
465
  function addPathToShellConfig(shellConfigPath, verbose = false) {
415
466
  const shellName = basename(process.env.SHELL || "/bin/bash");
416
467
  let exportLine;
@@ -428,6 +479,7 @@ function addPathToShellConfig(shellConfigPath, verbose = false) {
428
479
  async function createAlias(patchedBinaryPath, aliasName, verbose = false) {
429
480
  ensureDirectories();
430
481
  console.log(styleText("white", `[*] Creating alias: ${styleText("cyan", aliasName)}`));
482
+ if (IS_WINDOWS) return createWindowsAlias(patchedBinaryPath, aliasName, verbose);
431
483
  const writablePathDir = findWritablePathDir();
432
484
  if (writablePathDir) {
433
485
  const targetPath = join(writablePathDir, aliasName);
@@ -536,10 +588,103 @@ async function createAlias(patchedBinaryPath, aliasName, verbose = false) {
536
588
  binaryPath: binaryDest
537
589
  };
538
590
  }
591
+ /**
592
+ * Create alias on Windows using .cmd launcher and setx for PATH
593
+ */
594
+ /**
595
+ * Try to copy file, handling Windows file locking
596
+ * If target is locked, use a new filename with timestamp
597
+ */
598
+ async function copyFileWithLockHandling(src, dest, verbose = false) {
599
+ try {
600
+ await copyFile(src, dest);
601
+ return dest;
602
+ } catch (error) {
603
+ if (error.code === "EBUSY" && IS_WINDOWS) {
604
+ const timestamp = Date.now();
605
+ const ext = dest.endsWith(".exe") ? ".exe" : "";
606
+ const newDest = `${dest.replace(/\.exe$/, "").replace(/-\d+$/, "")}-${timestamp}${ext}`;
607
+ if (verbose) console.log(styleText("yellow", ` [!] File locked, using new path: ${newDest}`));
608
+ await copyFile(src, newDest);
609
+ return newDest;
610
+ }
611
+ throw error;
612
+ }
613
+ }
614
+ async function createWindowsAlias(patchedBinaryPath, aliasName, verbose = false) {
615
+ const binDir = join(DROID_PATCH_DIR, "bin");
616
+ if (!existsSync(binDir)) mkdirSync(binDir, { recursive: true });
617
+ const binaryDest = await copyFileWithLockHandling(patchedBinaryPath, join(BINS_DIR, `${aliasName}-patched.exe`), verbose);
618
+ if (verbose) console.log(styleText("gray", ` Stored binary: ${binaryDest}`));
619
+ const cmdPath = join(binDir, `${aliasName}.cmd`);
620
+ writeFileSync(cmdPath, generateWindowsLauncher(binaryDest));
621
+ if (verbose) console.log(styleText("gray", ` Created launcher: ${cmdPath}`));
622
+ const pathAdded = addToWindowsUserPath(binDir);
623
+ console.log(styleText("green", `[*] Created: ${cmdPath}`));
624
+ console.log();
625
+ if (pathAdded) {
626
+ if (checkPathInclusion()) {
627
+ console.log(styleText("green", "─".repeat(60)));
628
+ console.log(styleText(["green", "bold"], " ALIAS READY!"));
629
+ console.log(styleText("green", "─".repeat(60)));
630
+ console.log();
631
+ console.log(styleText("white", `The alias "${styleText(["cyan", "bold"], aliasName)}" is now available.`));
632
+ console.log(styleText("gray", `(Installed to: ${binDir})`));
633
+ return {
634
+ aliasPath: cmdPath,
635
+ binaryPath: binaryDest,
636
+ immediate: true
637
+ };
638
+ }
639
+ console.log(styleText("yellow", "─".repeat(60)));
640
+ console.log(styleText(["yellow", "bold"], " PATH Updated - Restart Terminal"));
641
+ console.log(styleText("yellow", "─".repeat(60)));
642
+ console.log();
643
+ console.log(styleText("white", "PATH has been updated. Please restart your terminal."));
644
+ console.log(styleText("white", `Then you can use "${styleText(["cyan", "bold"], aliasName)}" command directly.`));
645
+ console.log();
646
+ console.log(styleText("gray", `Installed to: ${binDir}`));
647
+ } else {
648
+ console.log(styleText("yellow", "─".repeat(60)));
649
+ console.log(styleText(["yellow", "bold"], " Manual PATH Configuration Required"));
650
+ console.log(styleText("yellow", "─".repeat(60)));
651
+ console.log();
652
+ console.log(styleText("white", "Add this directory to your PATH:"));
653
+ console.log(styleText("cyan", ` ${binDir}`));
654
+ console.log();
655
+ console.log(styleText("gray", "Or run directly:"));
656
+ console.log(styleText("cyan", ` "${cmdPath}"`));
657
+ }
658
+ return {
659
+ aliasPath: cmdPath,
660
+ binaryPath: binaryDest,
661
+ immediate: false
662
+ };
663
+ }
539
664
  async function removeAlias(aliasName) {
540
665
  console.log(styleText("white", `[*] Removing alias: ${styleText("cyan", aliasName)}`));
541
666
  let removed = false;
542
- for (const pathDir of COMMON_PATH_DIRS) {
667
+ if (IS_WINDOWS) {
668
+ const cmdPath = join(join(DROID_PATCH_DIR, "bin"), `${aliasName}.cmd`);
669
+ if (existsSync(cmdPath)) {
670
+ await unlink(cmdPath);
671
+ console.log(styleText("green", ` Removed: ${cmdPath}`));
672
+ removed = true;
673
+ }
674
+ const exePath = join(BINS_DIR, `${aliasName}-patched.exe`);
675
+ if (existsSync(exePath)) {
676
+ await unlink(exePath);
677
+ console.log(styleText("green", ` Removed binary: ${exePath}`));
678
+ removed = true;
679
+ }
680
+ const proxyWrapperCmd = join(join(DROID_PATCH_DIR, "proxy"), `${aliasName}.cmd`);
681
+ if (existsSync(proxyWrapperCmd)) {
682
+ await unlink(proxyWrapperCmd);
683
+ console.log(styleText("green", ` Removed wrapper: ${proxyWrapperCmd}`));
684
+ removed = true;
685
+ }
686
+ }
687
+ if (!IS_WINDOWS) for (const pathDir of COMMON_PATH_DIRS) {
543
688
  const pathSymlink = join(pathDir, aliasName);
544
689
  if (existsSync(pathSymlink)) try {
545
690
  if (lstatSync(pathSymlink).isSymbolicLink()) {
@@ -629,43 +774,87 @@ async function listAliases() {
629
774
  console.log(styleText("cyan", "═".repeat(60)));
630
775
  console.log();
631
776
  const aliases = [];
632
- for (const pathDir of COMMON_PATH_DIRS) {
633
- if (!existsSync(pathDir)) continue;
777
+ if (IS_WINDOWS) {
778
+ const binDir = join(DROID_PATCH_DIR, "bin");
779
+ if (existsSync(binDir)) try {
780
+ const files = readdirSync(binDir);
781
+ for (const file of files) if (file.endsWith(".cmd")) {
782
+ const aliasName = file.replace(/\.cmd$/, "");
783
+ const fullPath = join(binDir, file);
784
+ try {
785
+ const match = readFileSync(fullPath, "utf-8").match(/"([^"]+)"/);
786
+ const target = match ? match[1] : fullPath;
787
+ aliases.push({
788
+ name: aliasName,
789
+ target,
790
+ location: binDir,
791
+ immediate: checkPathInclusion()
792
+ });
793
+ } catch {
794
+ aliases.push({
795
+ name: aliasName,
796
+ target: fullPath,
797
+ location: binDir,
798
+ immediate: false
799
+ });
800
+ }
801
+ }
802
+ } catch {}
803
+ const proxyDir = join(DROID_PATCH_DIR, "proxy");
804
+ if (existsSync(proxyDir)) try {
805
+ const files = readdirSync(proxyDir);
806
+ for (const file of files) if (file.endsWith(".cmd")) {
807
+ const aliasName = file.replace(/\.cmd$/, "");
808
+ if (!aliases.find((a) => a.name === aliasName)) {
809
+ const fullPath = join(proxyDir, file);
810
+ aliases.push({
811
+ name: aliasName,
812
+ target: fullPath,
813
+ location: proxyDir,
814
+ immediate: false
815
+ });
816
+ }
817
+ }
818
+ } catch {}
819
+ } else {
820
+ for (const pathDir of COMMON_PATH_DIRS) {
821
+ if (!existsSync(pathDir)) continue;
822
+ try {
823
+ const files = readdirSync(pathDir);
824
+ for (const file of files) {
825
+ const fullPath = join(pathDir, file);
826
+ try {
827
+ if (lstatSync(fullPath).isSymbolicLink()) {
828
+ const target = await readlink(fullPath);
829
+ if (target.includes(".droid-patch/bins") || target.includes(".droid-patch/websearch") || target.includes(".droid-patch/proxy") || target.includes(".droid-patch/statusline")) aliases.push({
830
+ name: file,
831
+ target,
832
+ location: pathDir,
833
+ immediate: true
834
+ });
835
+ }
836
+ } catch {}
837
+ }
838
+ } catch {}
839
+ }
634
840
  try {
635
- const files = readdirSync(pathDir);
841
+ const files = readdirSync(ALIASES_DIR);
636
842
  for (const file of files) {
637
- const fullPath = join(pathDir, file);
843
+ const fullPath = join(ALIASES_DIR, file);
638
844
  try {
639
845
  if (lstatSync(fullPath).isSymbolicLink()) {
640
846
  const target = await readlink(fullPath);
641
- if (target.includes(".droid-patch/bins") || target.includes(".droid-patch/websearch") || target.includes(".droid-patch/proxy") || target.includes(".droid-patch/statusline")) aliases.push({
847
+ if (!aliases.find((a) => a.name === file)) aliases.push({
642
848
  name: file,
643
849
  target,
644
- location: pathDir,
645
- immediate: true
850
+ location: ALIASES_DIR,
851
+ immediate: false
646
852
  });
647
853
  }
648
854
  } catch {}
649
855
  }
650
856
  } catch {}
651
857
  }
652
- try {
653
- const files = readdirSync(ALIASES_DIR);
654
- for (const file of files) {
655
- const fullPath = join(ALIASES_DIR, file);
656
- try {
657
- if (lstatSync(fullPath).isSymbolicLink()) {
658
- const target = await readlink(fullPath);
659
- if (!aliases.find((a) => a.name === file)) aliases.push({
660
- name: file,
661
- target,
662
- location: ALIASES_DIR,
663
- immediate: false
664
- });
665
- }
666
- } catch {}
667
- }
668
- } catch {}
669
858
  if (aliases.length === 0) {
670
859
  console.log(styleText("gray", " No aliases configured."));
671
860
  console.log();
@@ -744,6 +933,7 @@ async function replaceOriginal(patchedBinaryPath, originalPath, verbose = false)
744
933
  async function createAliasForWrapper(wrapperPath, aliasName, verbose = false) {
745
934
  ensureDirectories();
746
935
  console.log(styleText("white", `[*] Creating alias: ${styleText("cyan", aliasName)}`));
936
+ if (IS_WINDOWS) return createWindowsWrapperAlias(wrapperPath, aliasName, verbose);
747
937
  const writablePathDir = findWritablePathDir();
748
938
  if (writablePathDir) {
749
939
  const targetPath = join(writablePathDir, aliasName);
@@ -819,6 +1009,58 @@ async function createAliasForWrapper(wrapperPath, aliasName, verbose = false) {
819
1009
  binaryPath: wrapperPath
820
1010
  };
821
1011
  }
1012
+ /**
1013
+ * Create Windows alias for wrapper script (.cmd pointing to wrapper .cmd)
1014
+ */
1015
+ async function createWindowsWrapperAlias(wrapperPath, aliasName, verbose = false) {
1016
+ const binDir = join(DROID_PATCH_DIR, "bin");
1017
+ if (!existsSync(binDir)) mkdirSync(binDir, { recursive: true });
1018
+ if (verbose) console.log(styleText("gray", ` Wrapper: ${wrapperPath}`));
1019
+ const cmdPath = join(binDir, `${aliasName}.cmd`);
1020
+ writeFileSync(cmdPath, generateWindowsLauncher(wrapperPath));
1021
+ if (verbose) console.log(styleText("gray", ` Created launcher: ${cmdPath}`));
1022
+ const pathAdded = addToWindowsUserPath(binDir);
1023
+ console.log(styleText("green", `[*] Created: ${cmdPath}`));
1024
+ console.log();
1025
+ if (pathAdded) {
1026
+ if (checkPathInclusion()) {
1027
+ console.log(styleText("green", "─".repeat(60)));
1028
+ console.log(styleText(["green", "bold"], " ALIAS READY!"));
1029
+ console.log(styleText("green", "─".repeat(60)));
1030
+ console.log();
1031
+ console.log(styleText("white", `The alias "${styleText(["cyan", "bold"], aliasName)}" is now available.`));
1032
+ console.log(styleText("gray", `(Installed to: ${binDir})`));
1033
+ return {
1034
+ aliasPath: cmdPath,
1035
+ binaryPath: wrapperPath,
1036
+ immediate: true
1037
+ };
1038
+ }
1039
+ console.log(styleText("yellow", "─".repeat(60)));
1040
+ console.log(styleText(["yellow", "bold"], " PATH Updated - Restart Terminal"));
1041
+ console.log(styleText("yellow", "─".repeat(60)));
1042
+ console.log();
1043
+ console.log(styleText("white", "PATH has been updated. Please restart your terminal."));
1044
+ console.log(styleText("white", `Then you can use "${styleText(["cyan", "bold"], aliasName)}" command directly.`));
1045
+ console.log();
1046
+ console.log(styleText("gray", `Installed to: ${binDir}`));
1047
+ } else {
1048
+ console.log(styleText("yellow", "─".repeat(60)));
1049
+ console.log(styleText(["yellow", "bold"], " Manual PATH Configuration Required"));
1050
+ console.log(styleText("yellow", "─".repeat(60)));
1051
+ console.log();
1052
+ console.log(styleText("white", "Add this directory to your PATH:"));
1053
+ console.log(styleText("cyan", ` ${binDir}`));
1054
+ console.log();
1055
+ console.log(styleText("gray", "Or run directly:"));
1056
+ console.log(styleText("cyan", ` "${cmdPath}"`));
1057
+ }
1058
+ return {
1059
+ aliasPath: cmdPath,
1060
+ binaryPath: wrapperPath,
1061
+ immediate: false
1062
+ };
1063
+ }
822
1064
  async function restoreOriginal(originalPath) {
823
1065
  ensureDirectories();
824
1066
  const latestBackupPath = join(BINS_DIR, "droid-original-latest");
@@ -1058,4 +1300,4 @@ async function clearAllAliases() {
1058
1300
 
1059
1301
  //#endregion
1060
1302
  export { removeAlias as a, restoreOriginal as c, listAllMetadata as d, loadAliasMetadata as f, listAliases as i, createMetadata as l, patchDroid as m, createAlias as n, removeAliasesByFilter as o, saveAliasMetadata as p, createAliasForWrapper as r, replaceOriginal as s, clearAllAliases as t, formatPatches as u };
1061
- //# sourceMappingURL=alias-DwnTg_u5.mjs.map
1303
+ //# sourceMappingURL=alias-hvk8y5gC.mjs.map