droid-patch 0.12.2 → 0.13.1
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 +43 -27
- package/README.zh-CN.md +34 -24
- package/dist/{alias-DwnTg_u5.mjs → alias-12JqnRQZ.mjs} +280 -42
- package/dist/alias-12JqnRQZ.mjs.map +1 -0
- package/dist/cli.mjs +489 -68
- package/dist/cli.mjs.map +1 -1
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +1 -1
- package/package.json +1 -1
- package/dist/alias-DwnTg_u5.mjs.map +0 -1
package/README.md
CHANGED
|
@@ -67,13 +67,46 @@ npx droid-patch --skip-login -o /path/to/dir my-droid
|
|
|
67
67
|
| `--standalone` | Standalone mode: mock non-LLM Factory APIs (use with `--websearch` or `--websearch-proxy`) |
|
|
68
68
|
| `--reasoning-effort` | Enable reasoning effort UI selector for custom models (set to high) |
|
|
69
69
|
| `--disable-telemetry` | Disable telemetry and Sentry error reporting |
|
|
70
|
-
| `--spec-model-custom` | Enable custom models as spec model (show in UI selector + bypass validation) |
|
|
71
70
|
| `--dry-run` | Verify patches without actually modifying the binary |
|
|
72
71
|
| `-p, --path <path>` | Path to the droid binary (default: `~/.droid/bin/droid`) |
|
|
73
72
|
| `-o, --output <dir>` | Output directory for patched binary (creates file without alias) |
|
|
74
73
|
| `--no-backup` | Skip creating backup of original binary |
|
|
75
74
|
| `-v, --verbose` | Enable verbose output |
|
|
76
75
|
|
|
76
|
+
### Manage Custom Models
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
# List all custom models
|
|
80
|
+
npx droid-patch list-models
|
|
81
|
+
|
|
82
|
+
# Add model interactively
|
|
83
|
+
npx droid-patch add-model
|
|
84
|
+
|
|
85
|
+
# Add model via command line
|
|
86
|
+
npx droid-patch add-model \
|
|
87
|
+
-m "claude-sonnet-4-20250514" \
|
|
88
|
+
-n "Sonnet [proxy]" \
|
|
89
|
+
-u "http://127.0.0.1:20002/droid" \
|
|
90
|
+
-k "your-api-key" \
|
|
91
|
+
-p "anthropic"
|
|
92
|
+
|
|
93
|
+
# Insert model at specific position
|
|
94
|
+
npx droid-patch add-model -i 0 # Interactive, insert at position 0
|
|
95
|
+
|
|
96
|
+
# Remove model (supports index, ID, or displayName)
|
|
97
|
+
npx droid-patch remove-model 0 # By index
|
|
98
|
+
npx droid-patch remove-model "custom:Sonnet-[proxy]-1" # By ID
|
|
99
|
+
npx droid-patch remove-model "Sonnet [proxy]" # By display name
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
**Model ID Format**: `custom:{DisplayName}-{index}`
|
|
103
|
+
|
|
104
|
+
- Spaces in `DisplayName` are replaced with `-`
|
|
105
|
+
- `index` is the position in the array (starting from 0)
|
|
106
|
+
- Example: `displayName: "Opus [proxy]"` → `id: "custom:Opus-[proxy]-0"`
|
|
107
|
+
|
|
108
|
+
**Important**: When deleting or inserting models, subsequent model IDs are automatically updated (because index changes).
|
|
109
|
+
|
|
77
110
|
### Manage Aliases and Files
|
|
78
111
|
|
|
79
112
|
```bash
|
|
@@ -285,19 +318,25 @@ Configure your custom model in `~/.factory/settings.json`:
|
|
|
285
318
|
"customModels": [
|
|
286
319
|
{
|
|
287
320
|
"model": "claude-sonnet-4-20250514",
|
|
288
|
-
"id": "custom:
|
|
321
|
+
"id": "custom:Opus-[proxy]-0",
|
|
289
322
|
"baseUrl": "http://127.0.0.1:20002/droid",
|
|
290
323
|
"apiKey": "your-api-key",
|
|
291
|
-
"displayName": "
|
|
324
|
+
"displayName": "Opus [proxy]",
|
|
292
325
|
"provider": "anthropic"
|
|
293
326
|
}
|
|
294
327
|
],
|
|
295
328
|
"sessionDefaultSettings": {
|
|
296
|
-
"model": "custom:
|
|
329
|
+
"model": "custom:Opus-[proxy]-0"
|
|
297
330
|
}
|
|
298
331
|
}
|
|
299
332
|
```
|
|
300
333
|
|
|
334
|
+
**Important**: The `id` field must match the `displayName` pattern:
|
|
335
|
+
|
|
336
|
+
- Format: `custom:{DisplayName}-{index}` where spaces are replaced with `-`
|
|
337
|
+
- Example: `displayName: "Opus [proxy]"` → `id: "custom:Opus-[proxy]-0"`
|
|
338
|
+
- The trailing number (`-0`) is the index (starting from 0)
|
|
339
|
+
|
|
301
340
|
### `--reasoning-effort`
|
|
302
341
|
|
|
303
342
|
Enables reasoning effort control for custom models by patching the binary to:
|
|
@@ -395,29 +434,6 @@ npx droid-patch --disable-telemetry droid-private
|
|
|
395
434
|
npx droid-patch --is-custom --skip-login --disable-telemetry droid-private
|
|
396
435
|
```
|
|
397
436
|
|
|
398
|
-
### `--spec-model-custom`
|
|
399
|
-
|
|
400
|
-
Enables custom models to be used as the spec model in spec mode.
|
|
401
|
-
|
|
402
|
-
**Purpose**: By default, droid only allows official models (like Claude, GPT) as spec models. This patch allows custom models to appear in the spec model selector and bypasses the compatibility validation.
|
|
403
|
-
|
|
404
|
-
**How it works**:
|
|
405
|
-
|
|
406
|
-
- Shows all custom models in the spec model selector UI (bypasses the filter that hides them)
|
|
407
|
-
- Bypasses the `compatibilityGroup` validation check (uses regex matching to handle minified function name changes across versions)
|
|
408
|
-
|
|
409
|
-
**Compatibility**: Requires droid 0.45.0 or later (the compatibilityGroup feature was added in this version).
|
|
410
|
-
|
|
411
|
-
**Usage**:
|
|
412
|
-
|
|
413
|
-
```bash
|
|
414
|
-
# Enable custom models as spec model
|
|
415
|
-
npx droid-patch --spec-model-custom droid-spec
|
|
416
|
-
|
|
417
|
-
# Combine with other patches for full custom model support
|
|
418
|
-
npx droid-patch --is-custom --reasoning-effort --spec-model-custom droid-custom-full
|
|
419
|
-
```
|
|
420
|
-
|
|
421
437
|
---
|
|
422
438
|
|
|
423
439
|
## WebSearch Configuration Guide
|
package/README.zh-CN.md
CHANGED
|
@@ -67,13 +67,46 @@ npx droid-patch --skip-login -o /path/to/dir my-droid
|
|
|
67
67
|
| `--standalone` | 独立模式:mock 非 LLM 的 Factory API(与 `--websearch` 或 `--websearch-proxy` 配合使用) |
|
|
68
68
|
| `--reasoning-effort` | 为自定义模型启用推理强度 UI 选择器(设置为 high) |
|
|
69
69
|
| `--disable-telemetry` | 禁用遥测数据上传和 Sentry 错误报告 |
|
|
70
|
-
| `--spec-model-custom` | 允许自定义模型作为 spec 模型(在 UI 选择器中显示 + 绕过验证) |
|
|
71
70
|
| `--dry-run` | 验证修补但不实际修改二进制文件 |
|
|
72
71
|
| `-p, --path <path>` | droid 二进制文件路径(默认:`~/.droid/bin/droid`) |
|
|
73
72
|
| `-o, --output <dir>` | 修补后二进制文件的输出目录(直接创建文件,不创建别名) |
|
|
74
73
|
| `--no-backup` | 跳过创建原始二进制文件的备份 |
|
|
75
74
|
| `-v, --verbose` | 启用详细输出 |
|
|
76
75
|
|
|
76
|
+
### 管理自定义模型
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
# 列出所有自定义模型
|
|
80
|
+
npx droid-patch list-models
|
|
81
|
+
|
|
82
|
+
# 交互式添加模型
|
|
83
|
+
npx droid-patch add-model
|
|
84
|
+
|
|
85
|
+
# 命令行添加模型
|
|
86
|
+
npx droid-patch add-model \
|
|
87
|
+
-m "claude-sonnet-4-20250514" \
|
|
88
|
+
-n "Sonnet [proxy]" \
|
|
89
|
+
-u "http://127.0.0.1:20002/droid" \
|
|
90
|
+
-k "your-api-key" \
|
|
91
|
+
-p "anthropic"
|
|
92
|
+
|
|
93
|
+
# 在指定位置插入模型
|
|
94
|
+
npx droid-patch add-model -i 0 # 交互式,插入到位置 0
|
|
95
|
+
|
|
96
|
+
# 删除模型(支持 index、ID 或 displayName)
|
|
97
|
+
npx droid-patch remove-model 0 # 按索引
|
|
98
|
+
npx droid-patch remove-model "custom:Sonnet-[proxy]-1" # 按 ID
|
|
99
|
+
npx droid-patch remove-model "Sonnet [proxy]" # 按显示名称
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
**模型 ID 格式**:`custom:{DisplayName}-{index}`
|
|
103
|
+
|
|
104
|
+
- `DisplayName` 中的空格会被替换为 `-`
|
|
105
|
+
- `index` 是模型在数组中的位置(从 0 开始)
|
|
106
|
+
- 示例:`displayName: "Opus [proxy]"` → `id: "custom:Opus-[proxy]-0"`
|
|
107
|
+
|
|
108
|
+
**重要**:删除或插入模型时,后续模型的 ID 会自动更新(因为 index 会变化)。
|
|
109
|
+
|
|
77
110
|
### 管理别名和文件
|
|
78
111
|
|
|
79
112
|
```bash
|
|
@@ -395,29 +428,6 @@ npx droid-patch --disable-telemetry droid-private
|
|
|
395
428
|
npx droid-patch --is-custom --skip-login --disable-telemetry droid-private
|
|
396
429
|
```
|
|
397
430
|
|
|
398
|
-
### `--spec-model-custom`
|
|
399
|
-
|
|
400
|
-
允许自定义模型作为 spec 模式中的 spec 模型使用。
|
|
401
|
-
|
|
402
|
-
**用途**:默认情况下,droid 只允许官方模型(如 Claude、GPT)作为 spec 模型。此补丁使自定义模型出现在 spec 模型选择器中,并绕过兼容性验证。
|
|
403
|
-
|
|
404
|
-
**工作原理**:
|
|
405
|
-
|
|
406
|
-
- 在 spec 模型选择器 UI 中显示所有自定义模型(绕过隐藏它们的过滤器)
|
|
407
|
-
- 绕过 `compatibilityGroup` 验证检查(使用正则表达式匹配以处理不同版本间混淆后的函数名变化)
|
|
408
|
-
|
|
409
|
-
**兼容性**:需要 droid 0.45.0 或更高版本(compatibilityGroup 功能在此版本中添加)。
|
|
410
|
-
|
|
411
|
-
**使用方法**:
|
|
412
|
-
|
|
413
|
-
```bash
|
|
414
|
-
# 允许自定义模型作为 spec 模型
|
|
415
|
-
npx droid-patch --spec-model-custom droid-spec
|
|
416
|
-
|
|
417
|
-
# 与其他补丁组合以获得完整的自定义模型支持
|
|
418
|
-
npx droid-patch --is-custom --reasoning-effort --spec-model-custom droid-custom-full
|
|
419
|
-
```
|
|
420
|
-
|
|
421
431
|
---
|
|
422
432
|
|
|
423
433
|
## WebSearch 配置指南
|
|
@@ -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
|
-
|
|
156
|
-
|
|
157
|
-
|
|
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(
|
|
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:
|
|
221
|
+
outputPath: actualOutputPath,
|
|
210
222
|
results,
|
|
211
223
|
patchedCount: totalPatched
|
|
212
224
|
};
|
|
@@ -338,17 +350,16 @@ 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
|
-
if (patches.specModelCustom) applied.push("specModelCustom");
|
|
343
353
|
return applied.length > 0 ? applied.join(", ") : "(none)";
|
|
344
354
|
}
|
|
345
355
|
|
|
346
356
|
//#endregion
|
|
347
357
|
//#region src/alias.ts
|
|
358
|
+
const IS_WINDOWS = platform() === "win32";
|
|
348
359
|
const DROID_PATCH_DIR = join(homedir(), ".droid-patch");
|
|
349
360
|
const ALIASES_DIR = join(DROID_PATCH_DIR, "aliases");
|
|
350
361
|
const BINS_DIR = join(DROID_PATCH_DIR, "bins");
|
|
351
|
-
const
|
|
362
|
+
const UNIX_PATH_DIRS = [
|
|
352
363
|
join(homedir(), ".local/bin"),
|
|
353
364
|
join(homedir(), "bin"),
|
|
354
365
|
join(homedir(), ".bin"),
|
|
@@ -369,17 +380,23 @@ const COMMON_PATH_DIRS = [
|
|
|
369
380
|
join(homedir(), ".volta/bin"),
|
|
370
381
|
join(homedir(), ".fnm/current/bin")
|
|
371
382
|
];
|
|
383
|
+
const WINDOWS_PATH_DIRS = [
|
|
384
|
+
join(homedir(), ".droid-patch", "bin"),
|
|
385
|
+
join(homedir(), "scoop", "shims"),
|
|
386
|
+
join(homedir(), "AppData", "Local", "Programs", "bin")
|
|
387
|
+
];
|
|
388
|
+
const COMMON_PATH_DIRS = IS_WINDOWS ? WINDOWS_PATH_DIRS : UNIX_PATH_DIRS;
|
|
372
389
|
function ensureDirectories() {
|
|
373
390
|
if (!existsSync(DROID_PATCH_DIR)) mkdirSync(DROID_PATCH_DIR, { recursive: true });
|
|
374
391
|
if (!existsSync(ALIASES_DIR)) mkdirSync(ALIASES_DIR, { recursive: true });
|
|
375
392
|
if (!existsSync(BINS_DIR)) mkdirSync(BINS_DIR, { recursive: true });
|
|
376
393
|
}
|
|
377
394
|
function checkPathInclusion() {
|
|
378
|
-
return (process.env.PATH || "").split(
|
|
395
|
+
return (process.env.PATH || "").split(delimiter).some((p) => p.toLowerCase() === ALIASES_DIR.toLowerCase());
|
|
379
396
|
}
|
|
380
397
|
function findWritablePathDir() {
|
|
381
|
-
const pathDirs = (process.env.PATH || "").split(
|
|
382
|
-
for (const dir of COMMON_PATH_DIRS) if (pathDirs.
|
|
398
|
+
const pathDirs = (process.env.PATH || "").split(delimiter);
|
|
399
|
+
for (const dir of COMMON_PATH_DIRS) if (pathDirs.some((p) => p.toLowerCase() === dir.toLowerCase())) try {
|
|
383
400
|
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
384
401
|
const testFile = join(dir, `.droid-patch-test-${Date.now()}`);
|
|
385
402
|
writeFileSync(testFile, "");
|
|
@@ -391,6 +408,7 @@ function findWritablePathDir() {
|
|
|
391
408
|
return null;
|
|
392
409
|
}
|
|
393
410
|
function getShellConfigPath() {
|
|
411
|
+
if (IS_WINDOWS) return "";
|
|
394
412
|
switch (basename(process.env.SHELL || "/bin/bash")) {
|
|
395
413
|
case "zsh": return join(homedir(), ".zshrc");
|
|
396
414
|
case "bash": {
|
|
@@ -403,6 +421,7 @@ function getShellConfigPath() {
|
|
|
403
421
|
}
|
|
404
422
|
}
|
|
405
423
|
function isPathConfigured(shellConfigPath) {
|
|
424
|
+
if (IS_WINDOWS || !shellConfigPath) return false;
|
|
406
425
|
if (!existsSync(shellConfigPath)) return false;
|
|
407
426
|
try {
|
|
408
427
|
const content = readFileSync(shellConfigPath, "utf-8");
|
|
@@ -411,6 +430,37 @@ function isPathConfigured(shellConfigPath) {
|
|
|
411
430
|
return false;
|
|
412
431
|
}
|
|
413
432
|
}
|
|
433
|
+
/**
|
|
434
|
+
* Add directory to Windows user PATH using setx command
|
|
435
|
+
* This modifies the user's PATH permanently (requires terminal restart)
|
|
436
|
+
*/
|
|
437
|
+
function addToWindowsUserPath(dir) {
|
|
438
|
+
try {
|
|
439
|
+
let existingPath = "";
|
|
440
|
+
try {
|
|
441
|
+
const match = execSync("reg query \"HKCU\\Environment\" /v Path 2>nul", {
|
|
442
|
+
encoding: "utf-8",
|
|
443
|
+
stdio: [
|
|
444
|
+
"pipe",
|
|
445
|
+
"pipe",
|
|
446
|
+
"pipe"
|
|
447
|
+
]
|
|
448
|
+
}).match(/Path\s+REG_(?:EXPAND_)?SZ\s+(.+)/);
|
|
449
|
+
existingPath = match ? match[1].trim() : "";
|
|
450
|
+
} catch {}
|
|
451
|
+
if (existingPath.split(";").map((p) => p.toLowerCase().trim()).includes(dir.toLowerCase())) return true;
|
|
452
|
+
execSync(`setx PATH "${existingPath ? `${existingPath};${dir}` : dir}"`, { stdio: "pipe" });
|
|
453
|
+
return true;
|
|
454
|
+
} catch {
|
|
455
|
+
return false;
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
/**
|
|
459
|
+
* Generate Windows .cmd launcher script
|
|
460
|
+
*/
|
|
461
|
+
function generateWindowsLauncher(targetPath) {
|
|
462
|
+
return `@echo off\r\n"${targetPath}" %*\r\n`;
|
|
463
|
+
}
|
|
414
464
|
function addPathToShellConfig(shellConfigPath, verbose = false) {
|
|
415
465
|
const shellName = basename(process.env.SHELL || "/bin/bash");
|
|
416
466
|
let exportLine;
|
|
@@ -428,6 +478,7 @@ function addPathToShellConfig(shellConfigPath, verbose = false) {
|
|
|
428
478
|
async function createAlias(patchedBinaryPath, aliasName, verbose = false) {
|
|
429
479
|
ensureDirectories();
|
|
430
480
|
console.log(styleText("white", `[*] Creating alias: ${styleText("cyan", aliasName)}`));
|
|
481
|
+
if (IS_WINDOWS) return createWindowsAlias(patchedBinaryPath, aliasName, verbose);
|
|
431
482
|
const writablePathDir = findWritablePathDir();
|
|
432
483
|
if (writablePathDir) {
|
|
433
484
|
const targetPath = join(writablePathDir, aliasName);
|
|
@@ -536,10 +587,103 @@ async function createAlias(patchedBinaryPath, aliasName, verbose = false) {
|
|
|
536
587
|
binaryPath: binaryDest
|
|
537
588
|
};
|
|
538
589
|
}
|
|
590
|
+
/**
|
|
591
|
+
* Create alias on Windows using .cmd launcher and setx for PATH
|
|
592
|
+
*/
|
|
593
|
+
/**
|
|
594
|
+
* Try to copy file, handling Windows file locking
|
|
595
|
+
* If target is locked, use a new filename with timestamp
|
|
596
|
+
*/
|
|
597
|
+
async function copyFileWithLockHandling(src, dest, verbose = false) {
|
|
598
|
+
try {
|
|
599
|
+
await copyFile(src, dest);
|
|
600
|
+
return dest;
|
|
601
|
+
} catch (error) {
|
|
602
|
+
if (error.code === "EBUSY" && IS_WINDOWS) {
|
|
603
|
+
const timestamp = Date.now();
|
|
604
|
+
const ext = dest.endsWith(".exe") ? ".exe" : "";
|
|
605
|
+
const newDest = `${dest.replace(/\.exe$/, "").replace(/-\d+$/, "")}-${timestamp}${ext}`;
|
|
606
|
+
if (verbose) console.log(styleText("yellow", ` [!] File locked, using new path: ${newDest}`));
|
|
607
|
+
await copyFile(src, newDest);
|
|
608
|
+
return newDest;
|
|
609
|
+
}
|
|
610
|
+
throw error;
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
async function createWindowsAlias(patchedBinaryPath, aliasName, verbose = false) {
|
|
614
|
+
const binDir = join(DROID_PATCH_DIR, "bin");
|
|
615
|
+
if (!existsSync(binDir)) mkdirSync(binDir, { recursive: true });
|
|
616
|
+
const binaryDest = await copyFileWithLockHandling(patchedBinaryPath, join(BINS_DIR, `${aliasName}-patched.exe`), verbose);
|
|
617
|
+
if (verbose) console.log(styleText("gray", ` Stored binary: ${binaryDest}`));
|
|
618
|
+
const cmdPath = join(binDir, `${aliasName}.cmd`);
|
|
619
|
+
writeFileSync(cmdPath, generateWindowsLauncher(binaryDest));
|
|
620
|
+
if (verbose) console.log(styleText("gray", ` Created launcher: ${cmdPath}`));
|
|
621
|
+
const pathAdded = addToWindowsUserPath(binDir);
|
|
622
|
+
console.log(styleText("green", `[*] Created: ${cmdPath}`));
|
|
623
|
+
console.log();
|
|
624
|
+
if (pathAdded) {
|
|
625
|
+
if (checkPathInclusion()) {
|
|
626
|
+
console.log(styleText("green", "─".repeat(60)));
|
|
627
|
+
console.log(styleText(["green", "bold"], " ALIAS READY!"));
|
|
628
|
+
console.log(styleText("green", "─".repeat(60)));
|
|
629
|
+
console.log();
|
|
630
|
+
console.log(styleText("white", `The alias "${styleText(["cyan", "bold"], aliasName)}" is now available.`));
|
|
631
|
+
console.log(styleText("gray", `(Installed to: ${binDir})`));
|
|
632
|
+
return {
|
|
633
|
+
aliasPath: cmdPath,
|
|
634
|
+
binaryPath: binaryDest,
|
|
635
|
+
immediate: true
|
|
636
|
+
};
|
|
637
|
+
}
|
|
638
|
+
console.log(styleText("yellow", "─".repeat(60)));
|
|
639
|
+
console.log(styleText(["yellow", "bold"], " PATH Updated - Restart Terminal"));
|
|
640
|
+
console.log(styleText("yellow", "─".repeat(60)));
|
|
641
|
+
console.log();
|
|
642
|
+
console.log(styleText("white", "PATH has been updated. Please restart your terminal."));
|
|
643
|
+
console.log(styleText("white", `Then you can use "${styleText(["cyan", "bold"], aliasName)}" command directly.`));
|
|
644
|
+
console.log();
|
|
645
|
+
console.log(styleText("gray", `Installed to: ${binDir}`));
|
|
646
|
+
} else {
|
|
647
|
+
console.log(styleText("yellow", "─".repeat(60)));
|
|
648
|
+
console.log(styleText(["yellow", "bold"], " Manual PATH Configuration Required"));
|
|
649
|
+
console.log(styleText("yellow", "─".repeat(60)));
|
|
650
|
+
console.log();
|
|
651
|
+
console.log(styleText("white", "Add this directory to your PATH:"));
|
|
652
|
+
console.log(styleText("cyan", ` ${binDir}`));
|
|
653
|
+
console.log();
|
|
654
|
+
console.log(styleText("gray", "Or run directly:"));
|
|
655
|
+
console.log(styleText("cyan", ` "${cmdPath}"`));
|
|
656
|
+
}
|
|
657
|
+
return {
|
|
658
|
+
aliasPath: cmdPath,
|
|
659
|
+
binaryPath: binaryDest,
|
|
660
|
+
immediate: false
|
|
661
|
+
};
|
|
662
|
+
}
|
|
539
663
|
async function removeAlias(aliasName) {
|
|
540
664
|
console.log(styleText("white", `[*] Removing alias: ${styleText("cyan", aliasName)}`));
|
|
541
665
|
let removed = false;
|
|
542
|
-
|
|
666
|
+
if (IS_WINDOWS) {
|
|
667
|
+
const cmdPath = join(join(DROID_PATCH_DIR, "bin"), `${aliasName}.cmd`);
|
|
668
|
+
if (existsSync(cmdPath)) {
|
|
669
|
+
await unlink(cmdPath);
|
|
670
|
+
console.log(styleText("green", ` Removed: ${cmdPath}`));
|
|
671
|
+
removed = true;
|
|
672
|
+
}
|
|
673
|
+
const exePath = join(BINS_DIR, `${aliasName}-patched.exe`);
|
|
674
|
+
if (existsSync(exePath)) {
|
|
675
|
+
await unlink(exePath);
|
|
676
|
+
console.log(styleText("green", ` Removed binary: ${exePath}`));
|
|
677
|
+
removed = true;
|
|
678
|
+
}
|
|
679
|
+
const proxyWrapperCmd = join(join(DROID_PATCH_DIR, "proxy"), `${aliasName}.cmd`);
|
|
680
|
+
if (existsSync(proxyWrapperCmd)) {
|
|
681
|
+
await unlink(proxyWrapperCmd);
|
|
682
|
+
console.log(styleText("green", ` Removed wrapper: ${proxyWrapperCmd}`));
|
|
683
|
+
removed = true;
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
if (!IS_WINDOWS) for (const pathDir of COMMON_PATH_DIRS) {
|
|
543
687
|
const pathSymlink = join(pathDir, aliasName);
|
|
544
688
|
if (existsSync(pathSymlink)) try {
|
|
545
689
|
if (lstatSync(pathSymlink).isSymbolicLink()) {
|
|
@@ -629,43 +773,87 @@ async function listAliases() {
|
|
|
629
773
|
console.log(styleText("cyan", "═".repeat(60)));
|
|
630
774
|
console.log();
|
|
631
775
|
const aliases = [];
|
|
632
|
-
|
|
633
|
-
|
|
776
|
+
if (IS_WINDOWS) {
|
|
777
|
+
const binDir = join(DROID_PATCH_DIR, "bin");
|
|
778
|
+
if (existsSync(binDir)) try {
|
|
779
|
+
const files = readdirSync(binDir);
|
|
780
|
+
for (const file of files) if (file.endsWith(".cmd")) {
|
|
781
|
+
const aliasName = file.replace(/\.cmd$/, "");
|
|
782
|
+
const fullPath = join(binDir, file);
|
|
783
|
+
try {
|
|
784
|
+
const match = readFileSync(fullPath, "utf-8").match(/"([^"]+)"/);
|
|
785
|
+
const target = match ? match[1] : fullPath;
|
|
786
|
+
aliases.push({
|
|
787
|
+
name: aliasName,
|
|
788
|
+
target,
|
|
789
|
+
location: binDir,
|
|
790
|
+
immediate: checkPathInclusion()
|
|
791
|
+
});
|
|
792
|
+
} catch {
|
|
793
|
+
aliases.push({
|
|
794
|
+
name: aliasName,
|
|
795
|
+
target: fullPath,
|
|
796
|
+
location: binDir,
|
|
797
|
+
immediate: false
|
|
798
|
+
});
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
} catch {}
|
|
802
|
+
const proxyDir = join(DROID_PATCH_DIR, "proxy");
|
|
803
|
+
if (existsSync(proxyDir)) try {
|
|
804
|
+
const files = readdirSync(proxyDir);
|
|
805
|
+
for (const file of files) if (file.endsWith(".cmd")) {
|
|
806
|
+
const aliasName = file.replace(/\.cmd$/, "");
|
|
807
|
+
if (!aliases.find((a) => a.name === aliasName)) {
|
|
808
|
+
const fullPath = join(proxyDir, file);
|
|
809
|
+
aliases.push({
|
|
810
|
+
name: aliasName,
|
|
811
|
+
target: fullPath,
|
|
812
|
+
location: proxyDir,
|
|
813
|
+
immediate: false
|
|
814
|
+
});
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
} catch {}
|
|
818
|
+
} else {
|
|
819
|
+
for (const pathDir of COMMON_PATH_DIRS) {
|
|
820
|
+
if (!existsSync(pathDir)) continue;
|
|
821
|
+
try {
|
|
822
|
+
const files = readdirSync(pathDir);
|
|
823
|
+
for (const file of files) {
|
|
824
|
+
const fullPath = join(pathDir, file);
|
|
825
|
+
try {
|
|
826
|
+
if (lstatSync(fullPath).isSymbolicLink()) {
|
|
827
|
+
const target = await readlink(fullPath);
|
|
828
|
+
if (target.includes(".droid-patch/bins") || target.includes(".droid-patch/websearch") || target.includes(".droid-patch/proxy") || target.includes(".droid-patch/statusline")) aliases.push({
|
|
829
|
+
name: file,
|
|
830
|
+
target,
|
|
831
|
+
location: pathDir,
|
|
832
|
+
immediate: true
|
|
833
|
+
});
|
|
834
|
+
}
|
|
835
|
+
} catch {}
|
|
836
|
+
}
|
|
837
|
+
} catch {}
|
|
838
|
+
}
|
|
634
839
|
try {
|
|
635
|
-
const files = readdirSync(
|
|
840
|
+
const files = readdirSync(ALIASES_DIR);
|
|
636
841
|
for (const file of files) {
|
|
637
|
-
const fullPath = join(
|
|
842
|
+
const fullPath = join(ALIASES_DIR, file);
|
|
638
843
|
try {
|
|
639
844
|
if (lstatSync(fullPath).isSymbolicLink()) {
|
|
640
845
|
const target = await readlink(fullPath);
|
|
641
|
-
if (
|
|
846
|
+
if (!aliases.find((a) => a.name === file)) aliases.push({
|
|
642
847
|
name: file,
|
|
643
848
|
target,
|
|
644
|
-
location:
|
|
645
|
-
immediate:
|
|
849
|
+
location: ALIASES_DIR,
|
|
850
|
+
immediate: false
|
|
646
851
|
});
|
|
647
852
|
}
|
|
648
853
|
} catch {}
|
|
649
854
|
}
|
|
650
855
|
} catch {}
|
|
651
856
|
}
|
|
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
857
|
if (aliases.length === 0) {
|
|
670
858
|
console.log(styleText("gray", " No aliases configured."));
|
|
671
859
|
console.log();
|
|
@@ -744,6 +932,7 @@ async function replaceOriginal(patchedBinaryPath, originalPath, verbose = false)
|
|
|
744
932
|
async function createAliasForWrapper(wrapperPath, aliasName, verbose = false) {
|
|
745
933
|
ensureDirectories();
|
|
746
934
|
console.log(styleText("white", `[*] Creating alias: ${styleText("cyan", aliasName)}`));
|
|
935
|
+
if (IS_WINDOWS) return createWindowsWrapperAlias(wrapperPath, aliasName, verbose);
|
|
747
936
|
const writablePathDir = findWritablePathDir();
|
|
748
937
|
if (writablePathDir) {
|
|
749
938
|
const targetPath = join(writablePathDir, aliasName);
|
|
@@ -819,6 +1008,58 @@ async function createAliasForWrapper(wrapperPath, aliasName, verbose = false) {
|
|
|
819
1008
|
binaryPath: wrapperPath
|
|
820
1009
|
};
|
|
821
1010
|
}
|
|
1011
|
+
/**
|
|
1012
|
+
* Create Windows alias for wrapper script (.cmd pointing to wrapper .cmd)
|
|
1013
|
+
*/
|
|
1014
|
+
async function createWindowsWrapperAlias(wrapperPath, aliasName, verbose = false) {
|
|
1015
|
+
const binDir = join(DROID_PATCH_DIR, "bin");
|
|
1016
|
+
if (!existsSync(binDir)) mkdirSync(binDir, { recursive: true });
|
|
1017
|
+
if (verbose) console.log(styleText("gray", ` Wrapper: ${wrapperPath}`));
|
|
1018
|
+
const cmdPath = join(binDir, `${aliasName}.cmd`);
|
|
1019
|
+
writeFileSync(cmdPath, generateWindowsLauncher(wrapperPath));
|
|
1020
|
+
if (verbose) console.log(styleText("gray", ` Created launcher: ${cmdPath}`));
|
|
1021
|
+
const pathAdded = addToWindowsUserPath(binDir);
|
|
1022
|
+
console.log(styleText("green", `[*] Created: ${cmdPath}`));
|
|
1023
|
+
console.log();
|
|
1024
|
+
if (pathAdded) {
|
|
1025
|
+
if (checkPathInclusion()) {
|
|
1026
|
+
console.log(styleText("green", "─".repeat(60)));
|
|
1027
|
+
console.log(styleText(["green", "bold"], " ALIAS READY!"));
|
|
1028
|
+
console.log(styleText("green", "─".repeat(60)));
|
|
1029
|
+
console.log();
|
|
1030
|
+
console.log(styleText("white", `The alias "${styleText(["cyan", "bold"], aliasName)}" is now available.`));
|
|
1031
|
+
console.log(styleText("gray", `(Installed to: ${binDir})`));
|
|
1032
|
+
return {
|
|
1033
|
+
aliasPath: cmdPath,
|
|
1034
|
+
binaryPath: wrapperPath,
|
|
1035
|
+
immediate: true
|
|
1036
|
+
};
|
|
1037
|
+
}
|
|
1038
|
+
console.log(styleText("yellow", "─".repeat(60)));
|
|
1039
|
+
console.log(styleText(["yellow", "bold"], " PATH Updated - Restart Terminal"));
|
|
1040
|
+
console.log(styleText("yellow", "─".repeat(60)));
|
|
1041
|
+
console.log();
|
|
1042
|
+
console.log(styleText("white", "PATH has been updated. Please restart your terminal."));
|
|
1043
|
+
console.log(styleText("white", `Then you can use "${styleText(["cyan", "bold"], aliasName)}" command directly.`));
|
|
1044
|
+
console.log();
|
|
1045
|
+
console.log(styleText("gray", `Installed to: ${binDir}`));
|
|
1046
|
+
} else {
|
|
1047
|
+
console.log(styleText("yellow", "─".repeat(60)));
|
|
1048
|
+
console.log(styleText(["yellow", "bold"], " Manual PATH Configuration Required"));
|
|
1049
|
+
console.log(styleText("yellow", "─".repeat(60)));
|
|
1050
|
+
console.log();
|
|
1051
|
+
console.log(styleText("white", "Add this directory to your PATH:"));
|
|
1052
|
+
console.log(styleText("cyan", ` ${binDir}`));
|
|
1053
|
+
console.log();
|
|
1054
|
+
console.log(styleText("gray", "Or run directly:"));
|
|
1055
|
+
console.log(styleText("cyan", ` "${cmdPath}"`));
|
|
1056
|
+
}
|
|
1057
|
+
return {
|
|
1058
|
+
aliasPath: cmdPath,
|
|
1059
|
+
binaryPath: wrapperPath,
|
|
1060
|
+
immediate: false
|
|
1061
|
+
};
|
|
1062
|
+
}
|
|
822
1063
|
async function restoreOriginal(originalPath) {
|
|
823
1064
|
ensureDirectories();
|
|
824
1065
|
const latestBackupPath = join(BINS_DIR, "droid-original-latest");
|
|
@@ -940,9 +1181,6 @@ async function removeAliasesByFilter(filter) {
|
|
|
940
1181
|
case "standalone":
|
|
941
1182
|
if (!patches.standalone) matches = false;
|
|
942
1183
|
break;
|
|
943
|
-
case "spec-model-custom":
|
|
944
|
-
if (!patches.specModelCustom) matches = false;
|
|
945
|
-
break;
|
|
946
1184
|
}
|
|
947
1185
|
if (!matches) break;
|
|
948
1186
|
}
|
|
@@ -1058,4 +1296,4 @@ async function clearAllAliases() {
|
|
|
1058
1296
|
|
|
1059
1297
|
//#endregion
|
|
1060
1298
|
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-
|
|
1299
|
+
//# sourceMappingURL=alias-12JqnRQZ.mjs.map
|