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/dist/cli.mjs
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
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-
|
|
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-12JqnRQZ.mjs";
|
|
3
3
|
import bin from "tiny-bin";
|
|
4
4
|
import { styleText } from "node:util";
|
|
5
|
-
import { existsSync, readFileSync } from "node:fs";
|
|
5
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
6
6
|
import { dirname, join } from "node:path";
|
|
7
|
-
import { homedir } from "node:os";
|
|
7
|
+
import { homedir, platform } from "node:os";
|
|
8
8
|
import { fileURLToPath } from "node:url";
|
|
9
9
|
import { execSync } from "node:child_process";
|
|
10
10
|
import { chmod, mkdir, writeFile } from "node:fs/promises";
|
|
11
|
+
import { createInterface } from "node:readline/promises";
|
|
11
12
|
|
|
12
13
|
//#region src/websearch-external.ts
|
|
13
14
|
/**
|
|
@@ -518,8 +519,9 @@ process.on('SIGINT', function() { server.close(); process.exit(0); });
|
|
|
518
519
|
|
|
519
520
|
//#endregion
|
|
520
521
|
//#region src/websearch-patch.ts
|
|
522
|
+
const IS_WINDOWS$1 = platform() === "win32";
|
|
521
523
|
/**
|
|
522
|
-
* Generate unified wrapper script
|
|
524
|
+
* Generate unified wrapper script (Unix bash)
|
|
523
525
|
*/
|
|
524
526
|
function generateUnifiedWrapper(droidPath, proxyScriptPath, standalone = false) {
|
|
525
527
|
const standaloneEnv = standalone ? "STANDALONE_MODE=1 " : "";
|
|
@@ -601,6 +603,91 @@ exit $DROID_EXIT_CODE
|
|
|
601
603
|
`;
|
|
602
604
|
}
|
|
603
605
|
/**
|
|
606
|
+
* Generate Windows wrapper script (.cmd batch file)
|
|
607
|
+
*/
|
|
608
|
+
function generateWindowsWrapper(droidPath, proxyScriptPath, standalone = false) {
|
|
609
|
+
return `@echo off
|
|
610
|
+
setlocal enabledelayedexpansion
|
|
611
|
+
REM Droid with WebSearch
|
|
612
|
+
REM Auto-generated by droid-patch --websearch
|
|
613
|
+
|
|
614
|
+
set "PROXY_SCRIPT=${proxyScriptPath}"
|
|
615
|
+
set "DROID_BIN=${droidPath}"
|
|
616
|
+
set "PORT_FILE=%TEMP%\\droid-websearch-%RANDOM%%RANDOM%.port"
|
|
617
|
+
|
|
618
|
+
REM Check for passthrough commands
|
|
619
|
+
set "PASSTHROUGH=0"
|
|
620
|
+
for %%a in (%*) do (
|
|
621
|
+
if "%%a"=="--help" set "PASSTHROUGH=1"
|
|
622
|
+
if "%%a"=="-h" set "PASSTHROUGH=1"
|
|
623
|
+
if "%%a"=="--version" set "PASSTHROUGH=1"
|
|
624
|
+
if "%%a"=="-V" set "PASSTHROUGH=1"
|
|
625
|
+
if "%%a"=="help" set "PASSTHROUGH=1"
|
|
626
|
+
if "%%a"=="version" set "PASSTHROUGH=1"
|
|
627
|
+
if "%%a"=="completion" set "PASSTHROUGH=1"
|
|
628
|
+
if "%%a"=="completions" set "PASSTHROUGH=1"
|
|
629
|
+
if "%%a"=="exec" set "PASSTHROUGH=1"
|
|
630
|
+
if "%%a"=="plugin" set "PASSTHROUGH=1"
|
|
631
|
+
)
|
|
632
|
+
if "%PASSTHROUGH%"=="1" (
|
|
633
|
+
"%DROID_BIN%" %*
|
|
634
|
+
exit /b %ERRORLEVEL%
|
|
635
|
+
)
|
|
636
|
+
|
|
637
|
+
REM Start proxy server
|
|
638
|
+
${standalone ? "set STANDALONE_MODE=1\r\n" : ""}set "SEARCH_PROXY_PORT=0"
|
|
639
|
+
set "SEARCH_PROXY_PORT_FILE=%PORT_FILE%"
|
|
640
|
+
|
|
641
|
+
if defined DROID_SEARCH_DEBUG (
|
|
642
|
+
echo [websearch] Starting proxy... 1>&2
|
|
643
|
+
start /b node "%PROXY_SCRIPT%"
|
|
644
|
+
) else (
|
|
645
|
+
start /b node "%PROXY_SCRIPT%" >nul 2>&1
|
|
646
|
+
)
|
|
647
|
+
|
|
648
|
+
REM Wait for port file
|
|
649
|
+
set "RETRY=0"
|
|
650
|
+
:wait_loop
|
|
651
|
+
if %RETRY% GEQ 50 goto :proxy_failed
|
|
652
|
+
set /a RETRY+=1
|
|
653
|
+
timeout /t 1 /nobreak >nul 2>&1
|
|
654
|
+
if not exist "%PORT_FILE%" goto :wait_loop
|
|
655
|
+
|
|
656
|
+
REM Read port and check health
|
|
657
|
+
set /p ACTUAL_PORT=<"%PORT_FILE%"
|
|
658
|
+
if not defined ACTUAL_PORT goto :wait_loop
|
|
659
|
+
|
|
660
|
+
REM Simple health check using curl or PowerShell
|
|
661
|
+
where curl >nul 2>&1
|
|
662
|
+
if %ERRORLEVEL%==0 (
|
|
663
|
+
curl -s "http://127.0.0.1:%ACTUAL_PORT%/health" >nul 2>&1
|
|
664
|
+
if %ERRORLEVEL%==0 goto :proxy_ready
|
|
665
|
+
) else (
|
|
666
|
+
powershell -Command "try { Invoke-WebRequest -Uri 'http://127.0.0.1:%ACTUAL_PORT%/health' -UseBasicParsing -TimeoutSec 2 | Out-Null; exit 0 } catch { exit 1 }" >nul 2>&1
|
|
667
|
+
if %ERRORLEVEL%==0 goto :proxy_ready
|
|
668
|
+
)
|
|
669
|
+
goto :wait_loop
|
|
670
|
+
|
|
671
|
+
:proxy_failed
|
|
672
|
+
echo [websearch] Failed to start proxy, running without websearch 1>&2
|
|
673
|
+
del "%PORT_FILE%" 2>nul
|
|
674
|
+
"%DROID_BIN%" %*
|
|
675
|
+
exit /b %ERRORLEVEL%
|
|
676
|
+
|
|
677
|
+
:proxy_ready
|
|
678
|
+
if defined DROID_SEARCH_DEBUG echo [websearch] Proxy ready on port %ACTUAL_PORT% 1>&2
|
|
679
|
+
del "%PORT_FILE%" 2>nul
|
|
680
|
+
|
|
681
|
+
set "FACTORY_API_BASE_URL_OVERRIDE=http://127.0.0.1:%ACTUAL_PORT%"
|
|
682
|
+
"%DROID_BIN%" %*
|
|
683
|
+
set "DROID_EXIT_CODE=%ERRORLEVEL%"
|
|
684
|
+
|
|
685
|
+
REM Cleanup: kill node processes started by this script
|
|
686
|
+
REM Note: Windows doesn't have easy process tree tracking, proxy will exit when parent exits
|
|
687
|
+
exit /b %DROID_EXIT_CODE%
|
|
688
|
+
`;
|
|
689
|
+
}
|
|
690
|
+
/**
|
|
604
691
|
* Create unified WebSearch files
|
|
605
692
|
*
|
|
606
693
|
* @param outputDir - Directory to write files to
|
|
@@ -613,13 +700,13 @@ exit $DROID_EXIT_CODE
|
|
|
613
700
|
async function createWebSearchUnifiedFiles(outputDir, droidPath, aliasName, apiBase, standalone = false, useNativeProvider = false) {
|
|
614
701
|
if (!existsSync(outputDir)) await mkdir(outputDir, { recursive: true });
|
|
615
702
|
const proxyScriptPath = join(outputDir, `${aliasName}-proxy.js`);
|
|
616
|
-
const wrapperScriptPath = join(outputDir, aliasName);
|
|
703
|
+
const wrapperScriptPath = IS_WINDOWS$1 ? join(outputDir, `${aliasName}.cmd`) : join(outputDir, aliasName);
|
|
617
704
|
const factoryApiUrl = apiBase || "https://api.factory.ai";
|
|
618
705
|
await writeFile(proxyScriptPath, useNativeProvider ? generateNativeSearchProxyServer(factoryApiUrl) : generateExternalSearchProxyServer(factoryApiUrl));
|
|
619
706
|
console.log(`[*] Created proxy script: ${proxyScriptPath}`);
|
|
620
707
|
console.log(`[*] Mode: ${useNativeProvider ? "native provider (requires proxy plugin)" : "external providers"}`);
|
|
621
|
-
await writeFile(wrapperScriptPath, generateUnifiedWrapper(droidPath, proxyScriptPath, standalone));
|
|
622
|
-
await chmod(wrapperScriptPath, 493);
|
|
708
|
+
await writeFile(wrapperScriptPath, IS_WINDOWS$1 ? generateWindowsWrapper(droidPath, proxyScriptPath, standalone) : generateUnifiedWrapper(droidPath, proxyScriptPath, standalone));
|
|
709
|
+
if (!IS_WINDOWS$1) await chmod(wrapperScriptPath, 493);
|
|
623
710
|
console.log(`[*] Created wrapper: ${wrapperScriptPath}`);
|
|
624
711
|
if (standalone) console.log(`[*] Standalone mode enabled`);
|
|
625
712
|
return {
|
|
@@ -628,9 +715,278 @@ async function createWebSearchUnifiedFiles(outputDir, droidPath, aliasName, apiB
|
|
|
628
715
|
};
|
|
629
716
|
}
|
|
630
717
|
|
|
718
|
+
//#endregion
|
|
719
|
+
//#region src/model-manager.ts
|
|
720
|
+
/**
|
|
721
|
+
* Custom Model Manager
|
|
722
|
+
* Manages custom models in ~/.factory/settings.json
|
|
723
|
+
*/
|
|
724
|
+
const FACTORY_DIR = join(homedir(), ".factory");
|
|
725
|
+
const SETTINGS_PATH = join(FACTORY_DIR, "settings.json");
|
|
726
|
+
/**
|
|
727
|
+
* Generate model ID from displayName and index
|
|
728
|
+
* Format: custom:{DisplayName}-{index} where spaces are replaced with -
|
|
729
|
+
* This matches droid's buildCustomModelId function
|
|
730
|
+
*/
|
|
731
|
+
function generateModelId(displayName, index) {
|
|
732
|
+
return `custom:${displayName.trim().replace(/\s+/g, "-")}-${index}`;
|
|
733
|
+
}
|
|
734
|
+
/**
|
|
735
|
+
* Load settings.json
|
|
736
|
+
*/
|
|
737
|
+
function loadSettings() {
|
|
738
|
+
if (!existsSync(SETTINGS_PATH)) return {};
|
|
739
|
+
try {
|
|
740
|
+
const content = readFileSync(SETTINGS_PATH, "utf-8");
|
|
741
|
+
return JSON.parse(content);
|
|
742
|
+
} catch {
|
|
743
|
+
return {};
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
/**
|
|
747
|
+
* Save settings.json
|
|
748
|
+
*/
|
|
749
|
+
function saveSettings(settings) {
|
|
750
|
+
if (!existsSync(FACTORY_DIR)) mkdirSync(FACTORY_DIR, { recursive: true });
|
|
751
|
+
writeFileSync(SETTINGS_PATH, JSON.stringify(settings, null, 2));
|
|
752
|
+
}
|
|
753
|
+
/**
|
|
754
|
+
* Rebuild all model IDs and indexes based on their array position
|
|
755
|
+
* This is necessary because droid uses array index as part of the ID
|
|
756
|
+
*/
|
|
757
|
+
function rebuildModelIds(models) {
|
|
758
|
+
return models.map((model, index) => ({
|
|
759
|
+
...model,
|
|
760
|
+
id: generateModelId(model.displayName, index),
|
|
761
|
+
index,
|
|
762
|
+
noImageSupport: model.noImageSupport ?? false
|
|
763
|
+
}));
|
|
764
|
+
}
|
|
765
|
+
/**
|
|
766
|
+
* Add a custom model at specified index (or end if not specified)
|
|
767
|
+
*/
|
|
768
|
+
function addModel(modelName, displayName, baseUrl, apiKey, provider, insertIndex) {
|
|
769
|
+
const settings = loadSettings();
|
|
770
|
+
if (!settings.customModels) settings.customModels = [];
|
|
771
|
+
const actualIndex = insertIndex ?? settings.customModels.length;
|
|
772
|
+
if (actualIndex < 0 || actualIndex > settings.customModels.length) return {
|
|
773
|
+
success: false,
|
|
774
|
+
model: {},
|
|
775
|
+
message: `Invalid index ${actualIndex}. Valid range: 0-${settings.customModels.length}`
|
|
776
|
+
};
|
|
777
|
+
const newModel = {
|
|
778
|
+
model: modelName,
|
|
779
|
+
id: "",
|
|
780
|
+
baseUrl,
|
|
781
|
+
apiKey,
|
|
782
|
+
displayName,
|
|
783
|
+
provider,
|
|
784
|
+
index: 0,
|
|
785
|
+
noImageSupport: false
|
|
786
|
+
};
|
|
787
|
+
settings.customModels.splice(actualIndex, 0, newModel);
|
|
788
|
+
settings.customModels = rebuildModelIds(settings.customModels);
|
|
789
|
+
const insertedModel = settings.customModels[actualIndex];
|
|
790
|
+
saveSettings(settings);
|
|
791
|
+
return {
|
|
792
|
+
success: true,
|
|
793
|
+
model: insertedModel,
|
|
794
|
+
message: `Added model "${displayName}" at index ${actualIndex} with ID "${insertedModel.id}"`
|
|
795
|
+
};
|
|
796
|
+
}
|
|
797
|
+
/**
|
|
798
|
+
* Remove a custom model by index, ID, or displayName
|
|
799
|
+
*/
|
|
800
|
+
function removeModel(identifier) {
|
|
801
|
+
const settings = loadSettings();
|
|
802
|
+
if (!settings.customModels || settings.customModels.length === 0) return {
|
|
803
|
+
success: false,
|
|
804
|
+
message: "No custom models configured"
|
|
805
|
+
};
|
|
806
|
+
let index;
|
|
807
|
+
const numericIndex = parseInt(identifier, 10);
|
|
808
|
+
if (!isNaN(numericIndex) && numericIndex >= 0 && numericIndex < settings.customModels.length) index = numericIndex;
|
|
809
|
+
else index = settings.customModels.findIndex((m) => m.id === identifier || m.displayName === identifier);
|
|
810
|
+
if (index === -1) return {
|
|
811
|
+
success: false,
|
|
812
|
+
message: `Model "${identifier}" not found. Use index (0-${settings.customModels.length - 1}), ID, or display name.`
|
|
813
|
+
};
|
|
814
|
+
const oldIds = settings.customModels.map((m) => ({
|
|
815
|
+
id: m.id,
|
|
816
|
+
displayName: m.displayName
|
|
817
|
+
}));
|
|
818
|
+
const removed = settings.customModels.splice(index, 1)[0];
|
|
819
|
+
const oldDefaultId = settings.sessionDefaultSettings?.model;
|
|
820
|
+
settings.customModels = rebuildModelIds(settings.customModels);
|
|
821
|
+
const updatedModels = [];
|
|
822
|
+
for (let i = index; i < settings.customModels.length; i++) {
|
|
823
|
+
const oldInfo = oldIds[i + 1];
|
|
824
|
+
const newModel = settings.customModels[i];
|
|
825
|
+
if (oldInfo && oldInfo.id !== newModel.id) updatedModels.push({
|
|
826
|
+
oldId: oldInfo.id,
|
|
827
|
+
newId: newModel.id,
|
|
828
|
+
displayName: newModel.displayName
|
|
829
|
+
});
|
|
830
|
+
}
|
|
831
|
+
if (oldDefaultId) if (oldDefaultId === removed.id) delete settings.sessionDefaultSettings.model;
|
|
832
|
+
else {
|
|
833
|
+
const updatedDefault = updatedModels.find((u) => u.oldId === oldDefaultId);
|
|
834
|
+
if (updatedDefault) settings.sessionDefaultSettings.model = updatedDefault.newId;
|
|
835
|
+
}
|
|
836
|
+
saveSettings(settings);
|
|
837
|
+
return {
|
|
838
|
+
success: true,
|
|
839
|
+
removed,
|
|
840
|
+
message: `Removed model "${removed.displayName}" (was at index ${index})`,
|
|
841
|
+
updatedModels
|
|
842
|
+
};
|
|
843
|
+
}
|
|
844
|
+
/**
|
|
845
|
+
* List all custom models
|
|
846
|
+
*/
|
|
847
|
+
function listModels() {
|
|
848
|
+
return loadSettings().customModels || [];
|
|
849
|
+
}
|
|
850
|
+
/**
|
|
851
|
+
* Get current default model
|
|
852
|
+
*/
|
|
853
|
+
function getDefaultModel() {
|
|
854
|
+
return loadSettings().sessionDefaultSettings?.model;
|
|
855
|
+
}
|
|
856
|
+
/**
|
|
857
|
+
* Print models list with detailed info
|
|
858
|
+
*/
|
|
859
|
+
function printModelsList() {
|
|
860
|
+
const models = listModels();
|
|
861
|
+
const defaultModel = getDefaultModel();
|
|
862
|
+
console.log(styleText("cyan", "═".repeat(60)));
|
|
863
|
+
console.log(styleText(["cyan", "bold"], " Custom Models"));
|
|
864
|
+
console.log(styleText("cyan", "═".repeat(60)));
|
|
865
|
+
console.log();
|
|
866
|
+
if (models.length === 0) {
|
|
867
|
+
console.log(styleText("gray", " No custom models configured."));
|
|
868
|
+
console.log();
|
|
869
|
+
console.log(styleText("gray", " Add one with: npx droid-patch add-model"));
|
|
870
|
+
} else {
|
|
871
|
+
console.log(styleText("white", ` Found ${models.length} model(s):`));
|
|
872
|
+
console.log();
|
|
873
|
+
for (let i = 0; i < models.length; i++) {
|
|
874
|
+
const model = models[i];
|
|
875
|
+
const defaultMark = model.id === defaultModel ? styleText("green", " [DEFAULT]") : "";
|
|
876
|
+
const indexMark = styleText("gray", `[${i}]`);
|
|
877
|
+
console.log(` ${indexMark} ${styleText(["cyan", "bold"], model.displayName)}${defaultMark}`);
|
|
878
|
+
console.log(styleText("gray", ` ID: ${model.id}`));
|
|
879
|
+
console.log(styleText("gray", ` Model: ${model.model}`));
|
|
880
|
+
console.log(styleText("gray", ` Provider: ${model.provider}`));
|
|
881
|
+
console.log(styleText("gray", ` Base URL: ${model.baseUrl}`));
|
|
882
|
+
console.log(styleText("gray", ` API Key: ${model.apiKey.substring(0, 8)}...`));
|
|
883
|
+
console.log();
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
console.log(styleText("gray", ` Settings file: ${SETTINGS_PATH}`));
|
|
887
|
+
console.log();
|
|
888
|
+
}
|
|
889
|
+
/**
|
|
890
|
+
* Interactive prompt helper using readline/promises
|
|
891
|
+
*/
|
|
892
|
+
async function prompt(question, defaultValue) {
|
|
893
|
+
const defaultHint = defaultValue ? ` (${defaultValue})` : "";
|
|
894
|
+
const rl = createInterface({
|
|
895
|
+
input: process.stdin,
|
|
896
|
+
output: process.stdout
|
|
897
|
+
});
|
|
898
|
+
try {
|
|
899
|
+
return (await rl.question(`${question}${defaultHint}: `)).trim() || defaultValue || "";
|
|
900
|
+
} finally {
|
|
901
|
+
rl.close();
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
/**
|
|
905
|
+
* Interactive prompt for selecting from options
|
|
906
|
+
*/
|
|
907
|
+
async function promptSelect(question, options) {
|
|
908
|
+
console.log(question);
|
|
909
|
+
options.forEach((opt, i) => {
|
|
910
|
+
console.log(styleText("cyan", ` ${i + 1}. ${opt}`));
|
|
911
|
+
});
|
|
912
|
+
const rl = createInterface({
|
|
913
|
+
input: process.stdin,
|
|
914
|
+
output: process.stdout
|
|
915
|
+
});
|
|
916
|
+
try {
|
|
917
|
+
const answer = await rl.question("Select (number): ");
|
|
918
|
+
const idx = parseInt(answer.trim(), 10) - 1;
|
|
919
|
+
if (idx >= 0 && idx < options.length) return options[idx];
|
|
920
|
+
return options[0];
|
|
921
|
+
} finally {
|
|
922
|
+
rl.close();
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
/**
|
|
926
|
+
* Interactive mode for adding a model
|
|
927
|
+
*/
|
|
928
|
+
async function addModelInteractive(insertIndex) {
|
|
929
|
+
console.log(styleText("cyan", "═".repeat(60)));
|
|
930
|
+
console.log(styleText(["cyan", "bold"], " Add Custom Model (Interactive)"));
|
|
931
|
+
console.log(styleText("cyan", "═".repeat(60)));
|
|
932
|
+
console.log();
|
|
933
|
+
const models = listModels();
|
|
934
|
+
if (models.length > 0) {
|
|
935
|
+
console.log(styleText("gray", `Current models: ${models.length}`));
|
|
936
|
+
models.forEach((m, i) => {
|
|
937
|
+
console.log(styleText("gray", ` [${i}] ${m.displayName}`));
|
|
938
|
+
});
|
|
939
|
+
console.log();
|
|
940
|
+
}
|
|
941
|
+
const displayName = await prompt("Display name (e.g., 'Opus [proxy]')");
|
|
942
|
+
if (!displayName) {
|
|
943
|
+
console.log(styleText("red", "Display name is required"));
|
|
944
|
+
return;
|
|
945
|
+
}
|
|
946
|
+
const modelName = await prompt("Model name (e.g., 'claude-sonnet-4-20250514')");
|
|
947
|
+
if (!modelName) {
|
|
948
|
+
console.log(styleText("red", "Model name is required"));
|
|
949
|
+
return;
|
|
950
|
+
}
|
|
951
|
+
const baseUrl = await prompt("Base URL (e.g., 'http://127.0.0.1:20002/droid')");
|
|
952
|
+
if (!baseUrl) {
|
|
953
|
+
console.log(styleText("red", "Base URL is required"));
|
|
954
|
+
return;
|
|
955
|
+
}
|
|
956
|
+
const apiKey = await prompt("API Key");
|
|
957
|
+
if (!apiKey) {
|
|
958
|
+
console.log(styleText("red", "API Key is required"));
|
|
959
|
+
return;
|
|
960
|
+
}
|
|
961
|
+
const provider = await promptSelect("Provider:", [
|
|
962
|
+
"anthropic",
|
|
963
|
+
"openai",
|
|
964
|
+
"generic-chat-completion-api"
|
|
965
|
+
]);
|
|
966
|
+
let actualIndex = insertIndex;
|
|
967
|
+
if (actualIndex === void 0 && models.length > 0) {
|
|
968
|
+
const indexStr = await prompt(`Insert at index (0-${models.length})`, String(models.length));
|
|
969
|
+
actualIndex = parseInt(indexStr, 10);
|
|
970
|
+
if (isNaN(actualIndex) || actualIndex < 0 || actualIndex > models.length) actualIndex = models.length;
|
|
971
|
+
}
|
|
972
|
+
console.log();
|
|
973
|
+
const result = addModel(modelName, displayName, baseUrl, apiKey, provider, actualIndex);
|
|
974
|
+
if (result.success) {
|
|
975
|
+
console.log(styleText("green", `[+] ${result.message}`));
|
|
976
|
+
console.log();
|
|
977
|
+
console.log(styleText("white", "Model details:"));
|
|
978
|
+
console.log(styleText("gray", ` ID: ${result.model.id}`));
|
|
979
|
+
console.log(styleText("gray", ` Display Name: ${result.model.displayName}`));
|
|
980
|
+
console.log(styleText("gray", ` Model: ${result.model.model}`));
|
|
981
|
+
console.log(styleText("gray", ` Provider: ${result.model.provider}`));
|
|
982
|
+
console.log(styleText("gray", ` Base URL: ${result.model.baseUrl}`));
|
|
983
|
+
} else console.log(styleText("red", `Error: ${result.message}`));
|
|
984
|
+
}
|
|
985
|
+
|
|
631
986
|
//#endregion
|
|
632
987
|
//#region src/cli.ts
|
|
633
988
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
989
|
+
const IS_WINDOWS = platform() === "win32";
|
|
634
990
|
function getVersion() {
|
|
635
991
|
try {
|
|
636
992
|
const pkgPath = join(__dirname, "..", "package.json");
|
|
@@ -659,6 +1015,27 @@ function getDroidVersion(droidPath) {
|
|
|
659
1015
|
}
|
|
660
1016
|
function findDefaultDroidPath() {
|
|
661
1017
|
const home = homedir();
|
|
1018
|
+
if (IS_WINDOWS) {
|
|
1019
|
+
try {
|
|
1020
|
+
const firstResult = execSync("where droid", {
|
|
1021
|
+
encoding: "utf-8",
|
|
1022
|
+
stdio: [
|
|
1023
|
+
"pipe",
|
|
1024
|
+
"pipe",
|
|
1025
|
+
"pipe"
|
|
1026
|
+
]
|
|
1027
|
+
}).trim().split(/\r?\n/)[0];
|
|
1028
|
+
if (firstResult && existsSync(firstResult)) return firstResult;
|
|
1029
|
+
} catch {}
|
|
1030
|
+
const windowsPaths = [
|
|
1031
|
+
join(home, ".droid", "bin", "droid.exe"),
|
|
1032
|
+
join(home, "AppData", "Local", "Programs", "droid", "droid.exe"),
|
|
1033
|
+
join(home, "scoop", "apps", "droid", "current", "droid.exe"),
|
|
1034
|
+
"./droid.exe"
|
|
1035
|
+
];
|
|
1036
|
+
for (const p of windowsPaths) if (existsSync(p)) return p;
|
|
1037
|
+
return join(home, ".droid", "bin", "droid.exe");
|
|
1038
|
+
}
|
|
662
1039
|
try {
|
|
663
1040
|
const result = execSync("which droid", {
|
|
664
1041
|
encoding: "utf-8",
|
|
@@ -680,7 +1057,7 @@ function findDefaultDroidPath() {
|
|
|
680
1057
|
for (const p of paths) if (existsSync(p)) return p;
|
|
681
1058
|
return join(home, ".droid", "bin", "droid");
|
|
682
1059
|
}
|
|
683
|
-
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 API URL (standalone: binary patch, max 22 chars; with --websearch: proxy forward target, no limit)").option("--websearch", "Enable local WebSearch proxy with external providers (Smithery, Google PSE, etc.)").option("--websearch-proxy", "Enable native provider websearch (requires proxy plugin, reads ~/.factory/settings.json)").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("--
|
|
1060
|
+
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 API URL (standalone: binary patch, max 22 chars; with --websearch: proxy forward target, no limit)").option("--websearch", "Enable local WebSearch proxy with external providers (Smithery, Google PSE, etc.)").option("--websearch-proxy", "Enable native provider websearch (requires proxy plugin, reads ~/.factory/settings.json)").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) => {
|
|
684
1061
|
const alias = args?.[0];
|
|
685
1062
|
const isCustom = options["is-custom"];
|
|
686
1063
|
const skipLogin = options["skip-login"];
|
|
@@ -691,15 +1068,13 @@ bin("droid-patch", "CLI tool to patch droid binary with various modifications").
|
|
|
691
1068
|
const websearchTarget = websearch ? apiBase || "https://api.factory.ai" : void 0;
|
|
692
1069
|
const reasoningEffort = options["reasoning-effort"];
|
|
693
1070
|
const noTelemetry = options["disable-telemetry"];
|
|
694
|
-
const autoHigh = options["auto-high"];
|
|
695
|
-
const specModelCustom = options["spec-model-custom"];
|
|
696
1071
|
const dryRun = options["dry-run"];
|
|
697
1072
|
const path = options.path || findDefaultDroidPath();
|
|
698
1073
|
const outputDir = options.output;
|
|
699
1074
|
const backup = options.backup !== false;
|
|
700
1075
|
const verbose = options.verbose;
|
|
701
1076
|
const outputPath = outputDir && alias ? join(outputDir, alias) : void 0;
|
|
702
|
-
const needsBinaryPatch = !!isCustom || !!skipLogin || !!reasoningEffort || !!noTelemetry || !!
|
|
1077
|
+
const needsBinaryPatch = !!isCustom || !!skipLogin || !!reasoningEffort || !!noTelemetry || !!apiBase && !websearch && !websearchProxy;
|
|
703
1078
|
if (websearch && websearchProxy) {
|
|
704
1079
|
console.log(styleText("red", "Error: Cannot use --websearch and --websearch-proxy together"));
|
|
705
1080
|
console.log(styleText("gray", "Choose one:"));
|
|
@@ -788,7 +1163,7 @@ bin("droid-patch", "CLI tool to patch droid binary with various modifications").
|
|
|
788
1163
|
}
|
|
789
1164
|
return;
|
|
790
1165
|
}
|
|
791
|
-
if (!isCustom && !skipLogin && !apiBase && !websearch && !reasoningEffort && !noTelemetry
|
|
1166
|
+
if (!isCustom && !skipLogin && !apiBase && !websearch && !reasoningEffort && !noTelemetry) {
|
|
792
1167
|
console.log(styleText("yellow", "No patch flags specified. Available patches:"));
|
|
793
1168
|
console.log(styleText("gray", " --is-custom Patch isCustom for custom models"));
|
|
794
1169
|
console.log(styleText("gray", " --skip-login Bypass login by injecting a fake API key"));
|
|
@@ -796,8 +1171,6 @@ bin("droid-patch", "CLI tool to patch droid binary with various modifications").
|
|
|
796
1171
|
console.log(styleText("gray", " --websearch Enable local WebSearch proxy"));
|
|
797
1172
|
console.log(styleText("gray", " --reasoning-effort Set reasoning effort level for custom models"));
|
|
798
1173
|
console.log(styleText("gray", " --disable-telemetry Disable telemetry and Sentry error reporting"));
|
|
799
|
-
console.log(styleText("gray", " --auto-high Set default autonomy mode to auto-high"));
|
|
800
|
-
console.log(styleText("gray", " --spec-model-custom Enable custom models as spec model"));
|
|
801
1174
|
console.log(styleText("gray", " --standalone Standalone mode: mock non-LLM Factory APIs"));
|
|
802
1175
|
console.log();
|
|
803
1176
|
console.log("Usage examples:");
|
|
@@ -909,30 +1282,6 @@ bin("droid-patch", "CLI tool to patch droid binary with various modifications").
|
|
|
909
1282
|
replacement: Buffer.from("!0||this.webEvents.length")
|
|
910
1283
|
});
|
|
911
1284
|
}
|
|
912
|
-
if (autoHigh) patches.push({
|
|
913
|
-
name: "autoHighAutonomy",
|
|
914
|
-
description: "Hardcode getCurrentAutonomyMode() to return \"auto-high\"",
|
|
915
|
-
pattern: Buffer.from("getCurrentAutonomyMode(){return this.autonomyMode}"),
|
|
916
|
-
replacement: Buffer.from("getCurrentAutonomyMode(){return\"auto-high\" }")
|
|
917
|
-
});
|
|
918
|
-
if (specModelCustom) {
|
|
919
|
-
patches.push({
|
|
920
|
-
name: "specModelCustomUIShow",
|
|
921
|
-
description: "Show all custom models in spec model selector (bypass filter)",
|
|
922
|
-
pattern: Buffer.from(""),
|
|
923
|
-
replacement: Buffer.from(""),
|
|
924
|
-
regexPattern: /([A-Za-z_$])=([A-Za-z_$])\?([A-Za-z_$])\.filter\(\(([A-Za-z_$])\)=>\2\.includes\(\4\.id\)\):\3/g,
|
|
925
|
-
regexReplacement: "$1=0?$3.filter(($4)=>$2.includes($4.id)):$3"
|
|
926
|
-
});
|
|
927
|
-
patches.push({
|
|
928
|
-
name: "specModelCustomValidation",
|
|
929
|
-
description: "Bypass compatibilityGroup check for custom spec models",
|
|
930
|
-
pattern: Buffer.from(""),
|
|
931
|
-
replacement: Buffer.from(""),
|
|
932
|
-
regexPattern: /([A-Z])=([a-zA-Z_$][a-zA-Z0-9_$]*)\(A\),([A-Z])=\2\(R\);if\(\1&&\1!==\3\)\{/g,
|
|
933
|
-
regexReplacement: "$1=$2(A),$3=$2(R);if($3&&$1!==$3){"
|
|
934
|
-
});
|
|
935
|
-
}
|
|
936
1285
|
try {
|
|
937
1286
|
const result = await patchDroid({
|
|
938
1287
|
inputPath: path,
|
|
@@ -990,9 +1339,7 @@ bin("droid-patch", "CLI tool to patch droid binary with various modifications").
|
|
|
990
1339
|
websearchProxy: !!websearchProxy,
|
|
991
1340
|
reasoningEffort: !!reasoningEffort,
|
|
992
1341
|
noTelemetry: !!noTelemetry,
|
|
993
|
-
standalone: !!standalone
|
|
994
|
-
autoHigh: !!autoHigh,
|
|
995
|
-
specModelCustom: !!specModelCustom
|
|
1342
|
+
standalone: !!standalone
|
|
996
1343
|
}, {
|
|
997
1344
|
droidPatchVersion: version,
|
|
998
1345
|
droidVersion,
|
|
@@ -1013,7 +1360,7 @@ bin("droid-patch", "CLI tool to patch droid binary with various modifications").
|
|
|
1013
1360
|
}
|
|
1014
1361
|
}).command("list", "List all droid-patch aliases").action(async () => {
|
|
1015
1362
|
await listAliases();
|
|
1016
|
-
}).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
|
|
1363
|
+
}).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) => {
|
|
1017
1364
|
const target = args?.[0];
|
|
1018
1365
|
const patchVersion = options["patch-version"];
|
|
1019
1366
|
const droidVersion = options["droid-version"];
|
|
@@ -1027,8 +1374,7 @@ bin("droid-patch", "CLI tool to patch droid binary with various modifications").
|
|
|
1027
1374
|
"api-base",
|
|
1028
1375
|
"reasoning-effort",
|
|
1029
1376
|
"disable-telemetry",
|
|
1030
|
-
"standalone"
|
|
1031
|
-
"spec-model-custom"
|
|
1377
|
+
"standalone"
|
|
1032
1378
|
];
|
|
1033
1379
|
if (!allowedFlags.includes(flagRaw)) {
|
|
1034
1380
|
console.error(styleText("red", `Error: Invalid --flag value: ${flagRaw}`));
|
|
@@ -1189,30 +1535,6 @@ bin("droid-patch", "CLI tool to patch droid binary with various modifications").
|
|
|
1189
1535
|
replacement: Buffer.from("!0||this.webEvents.length")
|
|
1190
1536
|
});
|
|
1191
1537
|
}
|
|
1192
|
-
if (meta.patches.autoHigh) patches.push({
|
|
1193
|
-
name: "autoHighAutonomy",
|
|
1194
|
-
description: "Hardcode getCurrentAutonomyMode() to return \"auto-high\"",
|
|
1195
|
-
pattern: Buffer.from("getCurrentAutonomyMode(){return this.autonomyMode}"),
|
|
1196
|
-
replacement: Buffer.from("getCurrentAutonomyMode(){return\"auto-high\" }")
|
|
1197
|
-
});
|
|
1198
|
-
if (meta.patches.specModelCustom) {
|
|
1199
|
-
patches.push({
|
|
1200
|
-
name: "specModelCustomUIShow",
|
|
1201
|
-
description: "Show all custom models in spec model selector (bypass filter)",
|
|
1202
|
-
pattern: Buffer.from(""),
|
|
1203
|
-
replacement: Buffer.from(""),
|
|
1204
|
-
regexPattern: /([A-Za-z_$])=([A-Za-z_$])\?([A-Za-z_$])\.filter\(\(([A-Za-z_$])\)=>\2\.includes\(\4\.id\)\):\3/g,
|
|
1205
|
-
regexReplacement: "$1=0?$3.filter(($4)=>$2.includes($4.id)):$3"
|
|
1206
|
-
});
|
|
1207
|
-
patches.push({
|
|
1208
|
-
name: "specModelCustomValidation",
|
|
1209
|
-
description: "Bypass compatibilityGroup check for custom spec models",
|
|
1210
|
-
pattern: Buffer.from(""),
|
|
1211
|
-
replacement: Buffer.from(""),
|
|
1212
|
-
regexPattern: /([A-Z])=([a-zA-Z_$][a-zA-Z0-9_$]*)\(A\),([A-Z])=\2\(R\);if\(\1&&\1!==\3\)\{/g,
|
|
1213
|
-
regexReplacement: "$1=$2(A),$3=$2(R);if($3&&$1!==$3){"
|
|
1214
|
-
});
|
|
1215
|
-
}
|
|
1216
1538
|
const outputPath = join(join(homedir(), ".droid-patch", "bins"), `${meta.name}-patched`);
|
|
1217
1539
|
if (patches.length > 0) {
|
|
1218
1540
|
if (!(await patchDroid({
|
|
@@ -1318,6 +1640,105 @@ bin("droid-patch", "CLI tool to patch droid binary with various modifications").
|
|
|
1318
1640
|
console.log(styleText("gray", ` Success: ${successCount}, Failed: ${failCount}`));
|
|
1319
1641
|
}
|
|
1320
1642
|
console.log(styleText("cyan", "═".repeat(60)));
|
|
1643
|
+
}).command("add-model", "Add a custom model to settings.json (interactive if no options)").option("-m, --model <model>", "Model name (e.g., claude-sonnet-4-20250514)").option("-n, --name <name>", "Display name (e.g., 'Opus [proxy]')").option("-u, --url <url>", "Base URL (e.g., http://127.0.0.1:20002/droid)").option("-k, --key <key>", "API key").option("-p, --provider <provider>", "Provider: anthropic, openai, or generic-chat-completion-api").option("-i, --index <index>", "Insert at index (auto-assigned if not specified)").action(async (options) => {
|
|
1644
|
+
const model = options.model;
|
|
1645
|
+
const displayName = options.name;
|
|
1646
|
+
const baseUrl = options.url;
|
|
1647
|
+
const apiKey = options.key;
|
|
1648
|
+
const providerStr = options.provider;
|
|
1649
|
+
const indexStr = options.index;
|
|
1650
|
+
if (!model && !displayName && !baseUrl && !apiKey && !providerStr) {
|
|
1651
|
+
await addModelInteractive(indexStr ? parseInt(indexStr, 10) : void 0);
|
|
1652
|
+
return;
|
|
1653
|
+
}
|
|
1654
|
+
if (!model || !displayName || !baseUrl || !apiKey || !providerStr) {
|
|
1655
|
+
console.log(styleText("yellow", "Missing required options. Enter interactive mode or provide all options."));
|
|
1656
|
+
console.log();
|
|
1657
|
+
console.log(styleText("white", "Interactive mode:"));
|
|
1658
|
+
console.log(styleText("cyan", " npx droid-patch add-model"));
|
|
1659
|
+
console.log();
|
|
1660
|
+
console.log(styleText("white", "Full command mode:"));
|
|
1661
|
+
console.log(styleText("cyan", " npx droid-patch add-model \\"));
|
|
1662
|
+
console.log(styleText("cyan", " -m \"claude-sonnet-4-20250514\" \\"));
|
|
1663
|
+
console.log(styleText("cyan", " -n \"Opus [proxy]\" \\"));
|
|
1664
|
+
console.log(styleText("cyan", " -u \"http://127.0.0.1:20002/droid\" \\"));
|
|
1665
|
+
console.log(styleText("cyan", " -k \"your-api-key\" \\"));
|
|
1666
|
+
console.log(styleText("cyan", " -p \"anthropic\""));
|
|
1667
|
+
console.log();
|
|
1668
|
+
console.log(styleText("gray", "Providers: anthropic, openai, generic-chat-completion-api"));
|
|
1669
|
+
console.log(styleText("gray", "Optional: -i <index> to insert at specific position"));
|
|
1670
|
+
process.exit(1);
|
|
1671
|
+
}
|
|
1672
|
+
const validProviders = [
|
|
1673
|
+
"anthropic",
|
|
1674
|
+
"openai",
|
|
1675
|
+
"generic-chat-completion-api"
|
|
1676
|
+
];
|
|
1677
|
+
if (!validProviders.includes(providerStr)) {
|
|
1678
|
+
console.log(styleText("red", `Error: Invalid provider "${providerStr}"`));
|
|
1679
|
+
console.log(styleText("gray", `Valid providers: ${validProviders.join(", ")}`));
|
|
1680
|
+
process.exit(1);
|
|
1681
|
+
}
|
|
1682
|
+
const index = indexStr ? parseInt(indexStr, 10) : void 0;
|
|
1683
|
+
if (indexStr && (isNaN(index) || index < 0)) {
|
|
1684
|
+
console.log(styleText("red", "Error: Index must be a non-negative number"));
|
|
1685
|
+
process.exit(1);
|
|
1686
|
+
}
|
|
1687
|
+
const result = addModel(model, displayName, baseUrl, apiKey, providerStr, index);
|
|
1688
|
+
if (result.success) {
|
|
1689
|
+
console.log(styleText("green", `[+] ${result.message}`));
|
|
1690
|
+
console.log();
|
|
1691
|
+
console.log(styleText("white", "Model details:"));
|
|
1692
|
+
console.log(styleText("gray", ` ID: ${result.model.id}`));
|
|
1693
|
+
console.log(styleText("gray", ` Display Name: ${result.model.displayName}`));
|
|
1694
|
+
console.log(styleText("gray", ` Model: ${result.model.model}`));
|
|
1695
|
+
console.log(styleText("gray", ` Provider: ${result.model.provider}`));
|
|
1696
|
+
console.log(styleText("gray", ` Base URL: ${result.model.baseUrl}`));
|
|
1697
|
+
} else {
|
|
1698
|
+
console.log(styleText("red", `Error: ${result.message}`));
|
|
1699
|
+
process.exit(1);
|
|
1700
|
+
}
|
|
1701
|
+
}).command("remove-model", "Remove a custom model from settings.json").argument("<identifier>", "Model index, ID, or display name to remove").action((options, args) => {
|
|
1702
|
+
const identifier = args?.[0];
|
|
1703
|
+
if (!identifier) {
|
|
1704
|
+
console.log(styleText("red", "Error: Model identifier required"));
|
|
1705
|
+
console.log();
|
|
1706
|
+
console.log(styleText("white", "Usage:"));
|
|
1707
|
+
console.log(styleText("cyan", " npx droid-patch remove-model <identifier>"));
|
|
1708
|
+
console.log();
|
|
1709
|
+
console.log(styleText("gray", "Identifier can be:"));
|
|
1710
|
+
console.log(styleText("gray", " - Index number (e.g., 0, 1, 2)"));
|
|
1711
|
+
console.log(styleText("gray", " - Model ID (e.g., custom:Opus-[proxy]-0)"));
|
|
1712
|
+
console.log(styleText("gray", " - Display name (e.g., 'Opus [proxy]')"));
|
|
1713
|
+
console.log();
|
|
1714
|
+
console.log(styleText("gray", "Use 'npx droid-patch list-models' to see all models"));
|
|
1715
|
+
process.exit(1);
|
|
1716
|
+
}
|
|
1717
|
+
const result = removeModel(identifier);
|
|
1718
|
+
if (result.success && result.removed) {
|
|
1719
|
+
console.log(styleText("green", `[+] ${result.message}`));
|
|
1720
|
+
console.log();
|
|
1721
|
+
console.log(styleText("white", "Removed model details:"));
|
|
1722
|
+
console.log(styleText("gray", ` ID: ${result.removed.id}`));
|
|
1723
|
+
console.log(styleText("gray", ` Display Name: ${result.removed.displayName}`));
|
|
1724
|
+
console.log(styleText("gray", ` Model: ${result.removed.model}`));
|
|
1725
|
+
console.log(styleText("gray", ` Provider: ${result.removed.provider}`));
|
|
1726
|
+
if (result.updatedModels && result.updatedModels.length > 0) {
|
|
1727
|
+
console.log();
|
|
1728
|
+
console.log(styleText("yellow", "Updated model IDs (due to index shift):"));
|
|
1729
|
+
for (const updated of result.updatedModels) {
|
|
1730
|
+
console.log(styleText("gray", ` ${updated.displayName}:`));
|
|
1731
|
+
console.log(styleText("gray", ` ${updated.oldId} → ${updated.newId}`));
|
|
1732
|
+
}
|
|
1733
|
+
}
|
|
1734
|
+
console.log();
|
|
1735
|
+
console.log(styleText("gray", "Use 'npx droid-patch list-models' to see current state."));
|
|
1736
|
+
} else {
|
|
1737
|
+
console.log(styleText("red", `Error: ${result.message}`));
|
|
1738
|
+
process.exit(1);
|
|
1739
|
+
}
|
|
1740
|
+
}).command("list-models", "List all custom models in settings.json").action(() => {
|
|
1741
|
+
printModelsList();
|
|
1321
1742
|
}).run().catch((err) => {
|
|
1322
1743
|
console.error(err);
|
|
1323
1744
|
process.exit(1);
|