unrestricted 1.0.0 → 1.1.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.
Files changed (2) hide show
  1. package/dist/unrestricted.mjs +2040 -492
  2. package/package.json +1 -1
@@ -12,6 +12,10 @@ var __commonJS = (cb, mod) => function __require() {
12
12
  throw mod = 0, e2;
13
13
  }
14
14
  };
15
+ var __export = (target, all) => {
16
+ for (var name in all)
17
+ __defProp(target, name, { get: all[name], enumerable: true });
18
+ };
15
19
  var __copyProps = (to, from, except, desc) => {
16
20
  if (from && typeof from === "object" || typeof from === "function") {
17
21
  for (let key of __getOwnPropNames(from))
@@ -489,6 +493,23 @@ var x = class {
489
493
  }
490
494
  }
491
495
  };
496
+ var dD = class extends x {
497
+ get cursor() {
498
+ return this.value ? 0 : 1;
499
+ }
500
+ get _value() {
501
+ return this.cursor === 0;
502
+ }
503
+ constructor(u2) {
504
+ super(u2, false), this.value = !!u2.initialValue, this.on("value", () => {
505
+ this.value = this._value;
506
+ }), this.on("confirm", (t) => {
507
+ this.output.write(import_sisteransi.cursor.move(0, -1)), this.value = t, this.state = "submit", this.close();
508
+ }), this.on("cursor", () => {
509
+ this.value = !this.value;
510
+ });
511
+ }
512
+ };
492
513
  var A;
493
514
  A = /* @__PURE__ */ new WeakMap();
494
515
  var kD = Object.defineProperty;
@@ -580,6 +601,25 @@ var G2 = (t) => {
580
601
  return j2 || E ? import_picocolors.default.dim("...") : i(p2, v + l2 === n);
581
602
  });
582
603
  };
604
+ var ye = (t) => {
605
+ const n = t.active ?? "Yes", r2 = t.inactive ?? "No";
606
+ return new dD({ active: n, inactive: r2, initialValue: t.initialValue ?? true, render() {
607
+ const i = `${import_picocolors.default.gray(o)}
608
+ ${b2(this.state)} ${t.message}
609
+ `, s = this.value ? n : r2;
610
+ switch (this.state) {
611
+ case "submit":
612
+ return `${i}${import_picocolors.default.gray(o)} ${import_picocolors.default.dim(s)}`;
613
+ case "cancel":
614
+ return `${i}${import_picocolors.default.gray(o)} ${import_picocolors.default.strikethrough(import_picocolors.default.dim(s))}
615
+ ${import_picocolors.default.gray(o)}`;
616
+ default:
617
+ return `${i}${import_picocolors.default.cyan(o)} ${this.value ? `${import_picocolors.default.green(k2)} ${n}` : `${import_picocolors.default.dim(P2)} ${import_picocolors.default.dim(n)}`} ${import_picocolors.default.dim("/")} ${this.value ? `${import_picocolors.default.dim(P2)} ${import_picocolors.default.dim(r2)}` : `${import_picocolors.default.green(k2)} ${r2}`}
618
+ ${import_picocolors.default.cyan(d2)}
619
+ `;
620
+ }
621
+ } }).prompt();
622
+ };
583
623
  var fe = (t) => {
584
624
  const n = (r2, i) => {
585
625
  const s = r2.label ?? String(r2.value);
@@ -724,13 +764,11 @@ var Y2 = ({ indicator: t = "dots" } = {}) => {
724
764
 
725
765
  // cli/src/command.ts
726
766
  var FAILED = { code: 1 };
767
+ var NOTHING_CHANGED = "Nothing changed.";
727
768
  function reason(error) {
728
769
  return error instanceof Error ? error.message : String(error);
729
770
  }
730
771
 
731
- // cli/src/writers/json-file.ts
732
- import { existsSync, readFileSync, writeFileSync } from "node:fs";
733
-
734
772
  // cli/src/text-file.ts
735
773
  var BOM = "\uFEFF";
736
774
  var NEW_FILE = { eol: "\n", bom: false };
@@ -752,19 +790,375 @@ function fromLf(text, layout) {
752
790
  return withBom(layout.eol === "\n" ? text : text.replace(/\r?\n/g, "\r\n"), layout);
753
791
  }
754
792
 
793
+ // cli/src/writers/tool-file.ts
794
+ import { readFileSync as readFileSync2, rmSync as rmSync2, statSync, writeFileSync as writeFileSync2 } from "node:fs";
795
+
796
+ // cli/src/backup.ts
797
+ import { mkdirSync, readFileSync, readdirSync, rmSync, writeFileSync } from "node:fs";
798
+
799
+ // cli/src/config.ts
800
+ import { homedir } from "node:os";
801
+ import { posix, win32 } from "node:path";
802
+ var THIS_MACHINE = { platform: process.platform, homedir };
803
+ var AUTO_UPDATE_TOOLS = ["opencode", "pi"];
804
+ var ALL_TOOLS = ["opencode", "codex", "qwen-code", "goose", "openhands", "aider", "continue", "crush", "pi", "claude"];
805
+ var DISABLED_TOOLS = /* @__PURE__ */ new Set(["aider", "claude"]);
806
+ var OFFERED_TOOLS = ALL_TOOLS.filter((tool) => !DISABLED_TOOLS.has(tool));
807
+ var TOOL_LABELS = {
808
+ opencode: "OpenCode",
809
+ codex: "Codex CLI",
810
+ "qwen-code": "Qwen Code",
811
+ goose: "Goose",
812
+ openhands: "OpenHands",
813
+ aider: "Aider",
814
+ continue: "Continue",
815
+ crush: "Crush",
816
+ pi: "Pi",
817
+ claude: "Claude Code"
818
+ };
819
+ var SETUP_INTRO = "unrestricted.sh setup";
820
+ var SETUP_OUTRO = "Done.";
821
+ var WINDOWS_PLATFORM = "win32";
822
+ var CONFIG_HOME_DIR = ".config";
823
+ var OPENCODE_CONFIG_DIR = "opencode";
824
+ var OPENCODE_CONFIG_FILE = "opencode.json";
825
+ var OPENCODE_JSONC_FILE = "opencode.jsonc";
826
+ var AIDER_CONFIG_FILE = ".aider.conf.yml";
827
+ var CONTINUE_CONFIG_DIR = ".continue";
828
+ var CONTINUE_CONFIG_FILE = "config.yaml";
829
+ var CRUSH_CONFIG_DIR = "crush";
830
+ var CRUSH_CONFIG_FILE = "crushrc";
831
+ var PI_AGENT_DIR = ".pi/agent";
832
+ var PI_AGENT_DIR_ENV = "PI_CODING_AGENT_DIR";
833
+ var PI_MODELS_FILE = "models.json";
834
+ var PI_SETTINGS_FILE = "settings.json";
835
+ var PI_API = "openai-completions";
836
+ var PI_THINKING_LEVEL_MAP = { off: "none", minimal: null, low: "low", medium: "medium", high: "xhigh" };
837
+ var CODEX_CONFIG_DIR = ".codex";
838
+ var CODEX_CONFIG_FILE = "config.toml";
839
+ var QWEN_CONFIG_DIR = ".qwen";
840
+ var QWEN_SETTINGS_FILE = "settings.json";
841
+ var GOOSE_CONFIG_DIR = "goose";
842
+ var GOOSE_WINDOWS_CONFIG_DIR = "Block/goose/config";
843
+ var GOOSE_CONFIG_FILE = "config.yaml";
844
+ var GOOSE_CUSTOM_PROVIDERS_DIR = "custom_providers";
845
+ var OPENHANDS_CONFIG_DIR = ".openhands";
846
+ var OPENHANDS_SETTINGS_FILE = "agent_settings.json";
847
+ var WINDOWS_APP_DATA_ENV = "APPDATA";
848
+ var WINDOWS_APP_DATA_DIR = "AppData/Roaming";
849
+ var DEFAULT_BASE_URL = "https://api.unrestricted.sh/v1";
850
+ var DEFAULT_ORIGIN = "https://unrestricted.sh";
851
+ var DEFAULT_MODEL = "qwen3.8-27b-uncensored";
852
+ var DEFAULT_MODEL_NAME = "Qwen3.8 27B Uncensored";
853
+ var PREVIOUS_DEFAULT_MODELS = ["abliterated-model"];
854
+ var CONTEXT = 262144;
855
+ var MAX_OUTPUT = 32768;
856
+ var PROVIDER = "unrestricted";
857
+ var PROVIDER_NAME = "unrestricted.sh";
858
+ var API_KEY_ENV = "UNRESTRICTED_API_KEY";
859
+ var KEY_PREFIX = "unr_sk_";
860
+ var KEY_LENGTH = 39;
861
+ var KEY_ERROR = `UNRESTRICTED_API_KEY is not an ${KEY_PREFIX} key. Leave it unset to approve the connection in the browser instead.`;
862
+ function keyAcceptedMessage(base) {
863
+ return `Key accepted by ${base}`;
864
+ }
865
+ function piModel(id, name, context, maxOutput) {
866
+ return {
867
+ id,
868
+ name,
869
+ reasoning: true,
870
+ thinkingLevelMap: PI_THINKING_LEVEL_MAP,
871
+ input: ["text"],
872
+ contextWindow: context,
873
+ maxTokens: maxOutput
874
+ };
875
+ }
876
+ function isOwnModel(model, owner) {
877
+ return model === owner.model || model === DEFAULT_MODEL || PREVIOUS_DEFAULT_MODELS.includes(model) || owner.models.some((listed) => listed.id === model);
878
+ }
879
+ function isOwnDefault(value, owner) {
880
+ const prefix = `${owner.provider}/`;
881
+ return value.startsWith(prefix) && isOwnModel(value.slice(prefix.length), owner);
882
+ }
883
+ function disabledToolMessage(tool, origin) {
884
+ if (tool === "aider") return `${TOOL_LABELS.aider} is no longer set up by unrestricted.sh. See ${origin}/guides/aider to set it up by hand`;
885
+ return `${TOOL_LABELS[tool]} is not available yet`;
886
+ }
887
+ function isToolId(value) {
888
+ return ALL_TOOLS.includes(value);
889
+ }
890
+ function isAutoUpdateTool(tool) {
891
+ return AUTO_UPDATE_TOOLS.includes(tool);
892
+ }
893
+ function parseTools(value) {
894
+ const tools = [];
895
+ const unknown = [];
896
+ const disabled = [];
897
+ for (const entry of value.split(",")) {
898
+ const name = entry.trim();
899
+ if (!name) continue;
900
+ if (!isToolId(name)) unknown.push(name);
901
+ else if (DISABLED_TOOLS.has(name)) disabled.push(name);
902
+ else tools.push(name);
903
+ }
904
+ return { tools, unknown, disabled };
905
+ }
906
+ function stripTrailingSlashes(value) {
907
+ return value.replace(/\/+$/, "");
908
+ }
909
+ function pathsFor(platform) {
910
+ return platform === WINDOWS_PLATFORM ? win32 : posix;
911
+ }
912
+ function windowsShellPath(dir) {
913
+ if (!dir.startsWith("/") || dir.startsWith("//") || dir.includes("\\")) return dir;
914
+ const drive = /^\/(?:mnt\/|cygdrive\/)?([a-z])(?:\/(.*))?$/i.exec(dir);
915
+ if (drive === null) return dir;
916
+ return `${drive[1].toUpperCase()}:\\${(drive[2] ?? "").replaceAll("/", "\\")}`;
917
+ }
918
+ function resolvePiAgentDir(env, home, platform) {
919
+ const { join: join4 } = pathsFor(platform);
920
+ const set = env[PI_AGENT_DIR_ENV];
921
+ if (!set) return join4(home, ...PI_AGENT_DIR.split("/"));
922
+ const windows = platform === WINDOWS_PLATFORM;
923
+ const dir = windows ? windowsShellPath(set) : set;
924
+ if (dir === "~") return home;
925
+ if (dir.startsWith("~/") || windows && dir.startsWith("~\\")) return join4(home, dir.slice(2));
926
+ return dir;
927
+ }
928
+ function windowsVariable(env, name) {
929
+ const key = Object.keys(env).find((candidate) => candidate.toUpperCase() === name);
930
+ return key === void 0 ? void 0 : env[key];
931
+ }
932
+ function windowsAppData(env, home) {
933
+ return windowsVariable(env, WINDOWS_APP_DATA_ENV) || win32.join(home, ...WINDOWS_APP_DATA_DIR.split("/"));
934
+ }
935
+ function resolveGooseConfigDir(env, home, configHome, platform) {
936
+ if (platform !== WINDOWS_PLATFORM) return posix.join(configHome, GOOSE_CONFIG_DIR);
937
+ return win32.join(windowsAppData(env, home), ...GOOSE_WINDOWS_CONFIG_DIR.split("/"));
938
+ }
939
+ function homeOf(env, machine) {
940
+ if (env.HOME) return env.HOME;
941
+ try {
942
+ return machine.homedir();
943
+ } catch {
944
+ return "";
945
+ }
946
+ }
947
+ function readConfig(env, machine = THIS_MACHINE) {
948
+ const key = env.UNRESTRICTED_API_KEY ?? "";
949
+ if (key && !key.startsWith(KEY_PREFIX)) throw new Error(KEY_ERROR);
950
+ if (key && key.length !== KEY_LENGTH) throw new Error("UNRESTRICTED_API_KEY has the wrong length");
951
+ const home = homeOf(env, machine);
952
+ if (!home) throw new Error("HOME is not set and the home directory could not be found");
953
+ const { join: join4 } = pathsFor(machine.platform);
954
+ const model = env.UNRESTRICTED_MODEL || DEFAULT_MODEL;
955
+ const modelName = env.UNRESTRICTED_MODEL_NAME || DEFAULT_MODEL_NAME;
956
+ const configHome = env.XDG_CONFIG_HOME || join4(home, CONFIG_HOME_DIR);
957
+ return {
958
+ key: key || null,
959
+ base: env.UNRESTRICTED_BASE_URL || DEFAULT_BASE_URL,
960
+ origin: stripTrailingSlashes(env.UNRESTRICTED_ORIGIN || env.UNRESTRICTED_SETUP_ORIGIN || DEFAULT_ORIGIN),
961
+ model,
962
+ modelName,
963
+ context: CONTEXT,
964
+ maxOutput: MAX_OUTPUT,
965
+ models: [{ id: model, name: modelName, context: CONTEXT, maxOutput: MAX_OUTPUT }],
966
+ provider: PROVIDER,
967
+ platform: machine.platform,
968
+ home,
969
+ configHome,
970
+ piAgentDir: resolvePiAgentDir(env, home, machine.platform),
971
+ gooseConfigDir: resolveGooseConfigDir(env, home, configHome, machine.platform)
972
+ };
973
+ }
974
+
975
+ // cli/src/file-mode.ts
976
+ import { chmodSync } from "node:fs";
977
+ var WINDOWS = "win32";
978
+ function setMode(path, mode, platform = process.platform) {
979
+ if (platform === WINDOWS) return;
980
+ chmodSync(path, mode);
981
+ }
982
+
983
+ // cli/src/paths.ts
984
+ var GOOSE_PROVIDER_FILE = `${PROVIDER}.json`;
985
+ var CLAUDE_CONFIG_DIR = ".claude";
986
+ var CLAUDE_SETTINGS_FILE = "settings.json";
987
+ var CLAUDE_SESSION_FILE = "claude-settings.json";
988
+ var CLAUDE_LAUNCHER = "claude-unrestricted";
989
+ var STORE_DIR_NAME = "unrestricted";
990
+ var OPENCODE_PLUGINS_DIR = "plugins";
991
+ var PI_EXTENSIONS_DIR = "extensions";
992
+ var AUTO_UPDATE_FILE = "unrestricted.ts";
993
+ function tildePath(path, home, platform) {
994
+ const { sep } = pathsFor(platform);
995
+ if (home === "" || home.endsWith(sep)) return path;
996
+ if (path === home) return "~";
997
+ return path.startsWith(`${home}${sep}`) ? `~${sep}${path.slice(home.length + sep.length)}` : path;
998
+ }
999
+ function toolPaths(locations) {
1000
+ const { join: join4 } = pathsFor(locations.platform);
1001
+ const { home, configHome, piAgentDir, gooseConfigDir: gooseDir } = locations;
1002
+ const opencodeDir = join4(configHome, OPENCODE_CONFIG_DIR);
1003
+ const crushDir = join4(configHome, CRUSH_CONFIG_DIR);
1004
+ const continueDir = join4(home, CONTINUE_CONFIG_DIR);
1005
+ const codexDir = join4(home, CODEX_CONFIG_DIR);
1006
+ const qwenDir = join4(home, QWEN_CONFIG_DIR);
1007
+ const openhandsDir = join4(home, OPENHANDS_CONFIG_DIR);
1008
+ const claudeDir = join4(home, CLAUDE_CONFIG_DIR);
1009
+ const claudeLauncherDir = join4(home, ".local", "bin");
1010
+ return {
1011
+ opencodeDir,
1012
+ opencodeJson: join4(opencodeDir, OPENCODE_CONFIG_FILE),
1013
+ opencodeJsonc: join4(opencodeDir, OPENCODE_JSONC_FILE),
1014
+ opencodePlugin: join4(opencodeDir, OPENCODE_PLUGINS_DIR, AUTO_UPDATE_FILE),
1015
+ crushDir,
1016
+ crushrc: join4(crushDir, CRUSH_CONFIG_FILE),
1017
+ piAgentDir,
1018
+ piModels: join4(piAgentDir, PI_MODELS_FILE),
1019
+ piSettings: join4(piAgentDir, PI_SETTINGS_FILE),
1020
+ piExtension: join4(piAgentDir, PI_EXTENSIONS_DIR, AUTO_UPDATE_FILE),
1021
+ continueDir,
1022
+ continueConfig: join4(continueDir, CONTINUE_CONFIG_FILE),
1023
+ aiderConfig: join4(home, AIDER_CONFIG_FILE),
1024
+ codexDir,
1025
+ codexConfig: join4(codexDir, CODEX_CONFIG_FILE),
1026
+ qwenDir,
1027
+ qwenSettings: join4(qwenDir, QWEN_SETTINGS_FILE),
1028
+ gooseDir,
1029
+ gooseConfig: join4(gooseDir, GOOSE_CONFIG_FILE),
1030
+ gooseProvider: join4(gooseDir, GOOSE_CUSTOM_PROVIDERS_DIR, GOOSE_PROVIDER_FILE),
1031
+ openhandsDir,
1032
+ openhandsSettings: join4(openhandsDir, OPENHANDS_SETTINGS_FILE),
1033
+ claudeDir,
1034
+ claudeGlobalSettings: join4(claudeDir, CLAUDE_SETTINGS_FILE),
1035
+ claudeSessionSettings: join4(configHome, STORE_DIR_NAME, CLAUDE_SESSION_FILE),
1036
+ claudeLauncherDir,
1037
+ claudeLauncher: join4(claudeLauncherDir, CLAUDE_LAUNCHER)
1038
+ };
1039
+ }
1040
+
1041
+ // cli/src/backup.ts
1042
+ var BACKUPS_DIR_NAME = "backups";
1043
+ var BACKUPS_KEPT = 5;
1044
+ var DIR_MODE = 448;
1045
+ var FILE_MODE = 384;
1046
+ var STAMP = /^\d{8}T\d{6}Z$/;
1047
+ var backupsTaken = 0;
1048
+ function backupsDir(place) {
1049
+ return pathsFor(place.platform).join(place.configHome, STORE_DIR_NAME, BACKUPS_DIR_NAME);
1050
+ }
1051
+ function backupStamp(date) {
1052
+ return date.toISOString().replace(/\.\d+Z$/, "Z").replace(/[-:]/g, "");
1053
+ }
1054
+ function toolBackupsDir(place, tool) {
1055
+ return pathsFor(place.platform).join(backupsDir(place), tool);
1056
+ }
1057
+ function isExisting(error) {
1058
+ return typeof error === "object" && error !== null && "code" in error && error.code === "EEXIST";
1059
+ }
1060
+ function makePrivateDir(dir, platform) {
1061
+ mkdirSync(dir, { recursive: true, mode: DIR_MODE });
1062
+ setMode(dir, DIR_MODE, platform);
1063
+ }
1064
+ function backupsOf(place, tool, file) {
1065
+ const { basename, join: join4 } = pathsFor(place.platform);
1066
+ const dir = toolBackupsDir(place, tool);
1067
+ const prefix = `${basename(file)}.`;
1068
+ let names;
1069
+ try {
1070
+ names = readdirSync(dir);
1071
+ } catch {
1072
+ return [];
1073
+ }
1074
+ return names.filter((name) => name.startsWith(prefix) && STAMP.test(name.slice(prefix.length))).sort().map((name) => join4(dir, name));
1075
+ }
1076
+ function newestBackup(place, tool, file) {
1077
+ return backupsOf(place, tool, file).at(-1) ?? null;
1078
+ }
1079
+ function backUp(place, tool, file, now = /* @__PURE__ */ new Date()) {
1080
+ const { basename, join: join4 } = pathsFor(place.platform);
1081
+ const dir = toolBackupsDir(place, tool);
1082
+ for (const parent of [join4(place.configHome, STORE_DIR_NAME), backupsDir(place), dir]) makePrivateDir(parent, place.platform);
1083
+ const target = join4(dir, `${basename(file)}.${backupStamp(now)}`);
1084
+ try {
1085
+ writeFileSync(target, readFileSync(file), { mode: FILE_MODE, flag: "wx" });
1086
+ } catch (error) {
1087
+ if (!isExisting(error)) throw error;
1088
+ }
1089
+ setMode(target, FILE_MODE, place.platform);
1090
+ for (const older of backupsOf(place, tool, file).slice(0, -BACKUPS_KEPT)) rmSync(older, { force: true });
1091
+ backupsTaken += 1;
1092
+ return target;
1093
+ }
1094
+ function backupsMade() {
1095
+ return backupsTaken;
1096
+ }
1097
+ function backupsLine(place) {
1098
+ return `Backups of the changed files: ${tildePath(backupsDir(place), place.home, place.platform)}`;
1099
+ }
1100
+
1101
+ // cli/src/writers/tool-file.ts
1102
+ function isMissing(error) {
1103
+ return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
1104
+ }
1105
+ function isFile(path) {
1106
+ try {
1107
+ return statSync(path).isFile();
1108
+ } catch {
1109
+ return false;
1110
+ }
1111
+ }
1112
+ function readText(file) {
1113
+ try {
1114
+ return readFileSync2(file, "utf8");
1115
+ } catch (error) {
1116
+ if (isMissing(error)) return null;
1117
+ throw error;
1118
+ }
1119
+ }
1120
+ function saveText(place, tool, file, text, current) {
1121
+ if (current === text) return false;
1122
+ if (current !== null) backUp(place, tool, file);
1123
+ writeFileSync2(file, text);
1124
+ return true;
1125
+ }
1126
+ function deleteText(place, tool, file) {
1127
+ if (readText(file) === null) return false;
1128
+ backUp(place, tool, file);
1129
+ rmSync2(file, { force: true });
1130
+ return true;
1131
+ }
1132
+ function applyChanges(place, tool, changes) {
1133
+ for (const change of changes) {
1134
+ if (change.next === null) deleteText(place, tool, change.file);
1135
+ else saveText(place, tool, change.file, change.next, change.raw);
1136
+ }
1137
+ }
1138
+ function splitLines(content) {
1139
+ if (content === "") return [];
1140
+ return (content.endsWith("\n") ? content.slice(0, -1) : content).split("\n");
1141
+ }
1142
+ function isBlankText(text) {
1143
+ return text.trim() === "";
1144
+ }
1145
+
755
1146
  // cli/src/writers/json-file.ts
756
1147
  function asObject(value) {
757
1148
  return typeof value === "object" && value !== null && !Array.isArray(value) ? value : {};
758
1149
  }
759
1150
  function readJsonDocument(file) {
760
- if (!existsSync(file)) writeFileSync(file, "{}\n");
761
- const raw = readFileSync(file, "utf8");
1151
+ const raw = readText(file);
1152
+ if (raw === null) return { value: {}, layout: NEW_FILE, raw };
762
1153
  const text = toLf(raw).trim();
763
- return { value: text ? asObject(JSON.parse(text)) : {}, layout: layoutOf(raw) };
1154
+ return { value: text ? asObject(JSON.parse(text)) : {}, layout: layoutOf(raw), raw };
1155
+ }
1156
+ function jsonText(value, layout = NEW_FILE) {
1157
+ return fromLf(`${JSON.stringify(value, null, 2)}
1158
+ `, layout);
764
1159
  }
765
- function writeJsonDocument(file, value, layout = NEW_FILE) {
766
- writeFileSync(file, fromLf(`${JSON.stringify(value, null, 2)}
767
- `, layout));
1160
+ function isBareDocument(value) {
1161
+ return Object.keys(value).every((key) => key === "$schema");
768
1162
  }
769
1163
 
770
1164
  // cli/src/models.ts
@@ -868,180 +1262,42 @@ async function verifyKey(base, key) {
868
1262
  if (!response.ok) throw new Error(`the key was rejected by ${url}`);
869
1263
  return parseModelList(await readJson(response));
870
1264
  } finally {
871
- clearTimeout(timer);
872
- }
873
- }
874
- async function fetchModelCatalog(base, timeoutMs = TIMEOUT_MS) {
875
- const url = `${base}/models`;
876
- const answer = await getJson(url, { accept: "application/json" }, timeoutMs);
877
- if (!answer.ok) throw new Error(`${url} answered ${answer.status}.`);
878
- return parseModelCatalog(answer.body);
879
- }
880
- function parsePlan(value) {
881
- if (value === null) return null;
882
- if (typeof value !== "object" || Array.isArray(value)) return void 0;
883
- const { name, allowance_usd: allowanceUsd, used_usd: usedUsd, renews_at: renews } = asObject(value);
884
- const planName = printable(name, PLAN_NAME_MAX_LENGTH);
885
- const renewsAt = typeof renews === "string" ? new Date(renews) : null;
886
- if (planName === null || !isAmount(allowanceUsd) || !isAmount(usedUsd) || renewsAt === null || Number.isNaN(renewsAt.getTime())) {
887
- return void 0;
888
- }
889
- return { name: planName, allowanceUsd, usedUsd, renewsAt };
890
- }
891
- function parseAccount(body) {
892
- if (typeof body !== "object" || body === null || Array.isArray(body)) return null;
893
- const { balance_usd: balanceUsd, plan, month_spend_usd: monthSpendUsd, api_locked: apiLocked } = asObject(body);
894
- const parsedPlan = parsePlan(plan);
895
- if (!isAmount(balanceUsd) || !isAmount(monthSpendUsd) || typeof apiLocked !== "boolean" || parsedPlan === void 0) return null;
896
- return { balanceUsd, plan: parsedPlan, monthSpendUsd, apiLocked };
897
- }
898
- async function fetchAccount(base, key) {
899
- const url = `${base}/account`;
900
- const answer = await getJson(url, { Authorization: `Bearer ${key}`, accept: "application/json" }, TIMEOUT_MS);
901
- if (answer.status === 401) throw new Error('The key was rejected. Run "unrestricted login" to sign in again.');
902
- if (answer.status === 403) throw new Error(errorMessage(answer.body) ?? `${url} refused the key.`);
903
- if (!answer.ok) throw new Error(`${url} answered ${answer.status}.`);
904
- const account = parseAccount(answer.body);
905
- if (account === null) throw new Error(`Unexpected answer from ${url}.`);
906
- return account;
907
- }
908
-
909
- // cli/src/config.ts
910
- import { homedir } from "node:os";
911
- import { posix, win32 } from "node:path";
912
- var THIS_MACHINE = { platform: process.platform, homedir };
913
- var AUTO_UPDATE_TOOLS = ["opencode", "pi"];
914
- var ALL_TOOLS = ["opencode", "codex", "aider", "continue", "crush", "pi", "claude"];
915
- var DISABLED_TOOLS = /* @__PURE__ */ new Set(["codex", "claude"]);
916
- var OFFERED_TOOLS = ALL_TOOLS.filter((tool) => !DISABLED_TOOLS.has(tool));
917
- var TOOL_LABELS = {
918
- opencode: "OpenCode",
919
- codex: "Codex CLI",
920
- aider: "Aider",
921
- continue: "Continue",
922
- crush: "Crush",
923
- pi: "Pi",
924
- claude: "Claude Code"
925
- };
926
- var SETUP_INTRO = "unrestricted.sh setup";
927
- var SETUP_OUTRO = "Done.";
928
- var WINDOWS_PLATFORM = "win32";
929
- var CONFIG_HOME_DIR = ".config";
930
- var OPENCODE_CONFIG_DIR = "opencode";
931
- var OPENCODE_CONFIG_FILE = "opencode.json";
932
- var OPENCODE_JSONC_FILE = "opencode.jsonc";
933
- var AIDER_CONFIG_FILE = ".aider.conf.yml";
934
- var CONTINUE_CONFIG_DIR = ".continue";
935
- var CONTINUE_CONFIG_FILE = "config.yaml";
936
- var CRUSH_CONFIG_DIR = "crush";
937
- var CRUSH_CONFIG_FILE = "crushrc";
938
- var PI_AGENT_DIR = ".pi/agent";
939
- var PI_AGENT_DIR_ENV = "PI_CODING_AGENT_DIR";
940
- var PI_MODELS_FILE = "models.json";
941
- var PI_SETTINGS_FILE = "settings.json";
942
- var PI_API = "openai-completions";
943
- var PI_THINKING_LEVEL_MAP = { off: "none", minimal: null, low: "low", medium: "medium", high: "xhigh" };
944
- var DEFAULT_BASE_URL = "https://api.unrestricted.sh/v1";
945
- var DEFAULT_ORIGIN = "https://unrestricted.sh";
946
- var DEFAULT_MODEL = "qwen3.8-27b-uncensored";
947
- var DEFAULT_MODEL_NAME = "Qwen3.8 27B Uncensored";
948
- var PREVIOUS_DEFAULT_MODELS = ["abliterated-model"];
949
- var CONTEXT = 262144;
950
- var MAX_OUTPUT = 32768;
951
- var PROVIDER = "unrestricted";
952
- var KEY_PREFIX = "unr_sk_";
953
- var KEY_LENGTH = 39;
954
- var KEY_ERROR = `UNRESTRICTED_API_KEY is not an ${KEY_PREFIX} key. Leave it unset to approve the connection in the browser instead.`;
955
- function keyAcceptedMessage(base) {
956
- return `Key accepted by ${base}`;
957
- }
958
- function piModel(id, name, context, maxOutput) {
959
- return {
960
- id,
961
- name,
962
- reasoning: true,
963
- thinkingLevelMap: PI_THINKING_LEVEL_MAP,
964
- input: ["text"],
965
- contextWindow: context,
966
- maxTokens: maxOutput
967
- };
968
- }
969
- function isOwnModel(model, config) {
970
- return model === config.model || model === DEFAULT_MODEL || PREVIOUS_DEFAULT_MODELS.includes(model) || config.models.some((listed) => listed.id === model);
971
- }
972
- function isToolId(value) {
973
- return ALL_TOOLS.includes(value);
974
- }
975
- function isAutoUpdateTool(tool) {
976
- return AUTO_UPDATE_TOOLS.includes(tool);
977
- }
978
- function parseTools(value) {
979
- const tools = [];
980
- const unknown = [];
981
- const disabled = [];
982
- for (const entry of value.split(",")) {
983
- const name = entry.trim();
984
- if (!name) continue;
985
- if (!isToolId(name)) unknown.push(name);
986
- else if (DISABLED_TOOLS.has(name)) disabled.push(name);
987
- else tools.push(name);
1265
+ clearTimeout(timer);
988
1266
  }
989
- return { tools, unknown, disabled };
990
- }
991
- function stripTrailingSlashes(value) {
992
- return value.replace(/\/+$/, "");
993
- }
994
- function pathsFor(platform) {
995
- return platform === WINDOWS_PLATFORM ? win32 : posix;
996
1267
  }
997
- function windowsShellPath(dir) {
998
- if (!dir.startsWith("/") || dir.startsWith("//") || dir.includes("\\")) return dir;
999
- const drive = /^\/(?:mnt\/|cygdrive\/)?([a-z])(?:\/(.*))?$/i.exec(dir);
1000
- if (drive === null) return dir;
1001
- return `${drive[1].toUpperCase()}:\\${(drive[2] ?? "").replaceAll("/", "\\")}`;
1002
- }
1003
- function resolvePiAgentDir(env, home, platform) {
1004
- const { join: join4 } = pathsFor(platform);
1005
- const set = env[PI_AGENT_DIR_ENV];
1006
- if (!set) return join4(home, ...PI_AGENT_DIR.split("/"));
1007
- const windows = platform === WINDOWS_PLATFORM;
1008
- const dir = windows ? windowsShellPath(set) : set;
1009
- if (dir === "~") return home;
1010
- if (dir.startsWith("~/") || windows && dir.startsWith("~\\")) return join4(home, dir.slice(2));
1011
- return dir;
1268
+ async function fetchModelCatalog(base, timeoutMs = TIMEOUT_MS) {
1269
+ const url = `${base}/models`;
1270
+ const answer = await getJson(url, { accept: "application/json" }, timeoutMs);
1271
+ if (!answer.ok) throw new Error(`${url} answered ${answer.status}.`);
1272
+ return parseModelCatalog(answer.body);
1012
1273
  }
1013
- function homeOf(env, machine) {
1014
- if (env.HOME) return env.HOME;
1015
- try {
1016
- return machine.homedir();
1017
- } catch {
1018
- return "";
1274
+ function parsePlan(value) {
1275
+ if (value === null) return null;
1276
+ if (typeof value !== "object" || Array.isArray(value)) return void 0;
1277
+ const { name, allowance_usd: allowanceUsd, used_usd: usedUsd, renews_at: renews } = asObject(value);
1278
+ const planName = printable(name, PLAN_NAME_MAX_LENGTH);
1279
+ const renewsAt = typeof renews === "string" ? new Date(renews) : null;
1280
+ if (planName === null || !isAmount(allowanceUsd) || !isAmount(usedUsd) || renewsAt === null || Number.isNaN(renewsAt.getTime())) {
1281
+ return void 0;
1019
1282
  }
1283
+ return { name: planName, allowanceUsd, usedUsd, renewsAt };
1020
1284
  }
1021
- function readConfig(env, machine = THIS_MACHINE) {
1022
- const key = env.UNRESTRICTED_API_KEY ?? "";
1023
- if (key && !key.startsWith(KEY_PREFIX)) throw new Error(KEY_ERROR);
1024
- if (key && key.length !== KEY_LENGTH) throw new Error("UNRESTRICTED_API_KEY has the wrong length");
1025
- const home = homeOf(env, machine);
1026
- if (!home) throw new Error("HOME is not set and the home directory could not be found");
1027
- const { join: join4 } = pathsFor(machine.platform);
1028
- const model = env.UNRESTRICTED_MODEL || DEFAULT_MODEL;
1029
- const modelName = env.UNRESTRICTED_MODEL_NAME || DEFAULT_MODEL_NAME;
1030
- return {
1031
- key: key || null,
1032
- base: env.UNRESTRICTED_BASE_URL || DEFAULT_BASE_URL,
1033
- origin: stripTrailingSlashes(env.UNRESTRICTED_ORIGIN || env.UNRESTRICTED_SETUP_ORIGIN || DEFAULT_ORIGIN),
1034
- model,
1035
- modelName,
1036
- context: CONTEXT,
1037
- maxOutput: MAX_OUTPUT,
1038
- models: [{ id: model, name: modelName, context: CONTEXT, maxOutput: MAX_OUTPUT }],
1039
- provider: PROVIDER,
1040
- platform: machine.platform,
1041
- home,
1042
- configHome: env.XDG_CONFIG_HOME || join4(home, CONFIG_HOME_DIR),
1043
- piAgentDir: resolvePiAgentDir(env, home, machine.platform)
1044
- };
1285
+ function parseAccount(body) {
1286
+ if (typeof body !== "object" || body === null || Array.isArray(body)) return null;
1287
+ const { balance_usd: balanceUsd, plan, month_spend_usd: monthSpendUsd, api_locked: apiLocked } = asObject(body);
1288
+ const parsedPlan = parsePlan(plan);
1289
+ if (!isAmount(balanceUsd) || !isAmount(monthSpendUsd) || typeof apiLocked !== "boolean" || parsedPlan === void 0) return null;
1290
+ return { balanceUsd, plan: parsedPlan, monthSpendUsd, apiLocked };
1291
+ }
1292
+ async function fetchAccount(base, key) {
1293
+ const url = `${base}/account`;
1294
+ const answer = await getJson(url, { Authorization: `Bearer ${key}`, accept: "application/json" }, TIMEOUT_MS);
1295
+ if (answer.status === 401) throw new Error('The key was rejected. Run "unrestricted login" to sign in again.');
1296
+ if (answer.status === 403) throw new Error(errorMessage(answer.body) ?? `${url} refused the key.`);
1297
+ if (!answer.ok) throw new Error(`${url} answered ${answer.status}.`);
1298
+ const account = parseAccount(answer.body);
1299
+ if (account === null) throw new Error(`Unexpected answer from ${url}.`);
1300
+ return account;
1045
1301
  }
1046
1302
 
1047
1303
  // cli/src/format.ts
@@ -1081,68 +1337,17 @@ function table(rows, rightAligned = /* @__PURE__ */ new Set()) {
1081
1337
  }
1082
1338
 
1083
1339
  // cli/src/store.ts
1084
- import { existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2, renameSync, rmSync, writeFileSync as writeFileSync2 } from "node:fs";
1340
+ import { existsSync, mkdirSync as mkdirSync2, readFileSync as readFileSync3, renameSync, rmSync as rmSync3, writeFileSync as writeFileSync3 } from "node:fs";
1085
1341
  import { join } from "node:path";
1086
-
1087
- // cli/src/file-mode.ts
1088
- import { chmodSync } from "node:fs";
1089
- var WINDOWS = "win32";
1090
- function setMode(path, mode, platform = process.platform) {
1091
- if (platform === WINDOWS) return;
1092
- chmodSync(path, mode);
1093
- }
1094
-
1095
- // cli/src/paths.ts
1096
- var CODEX_CONFIG_DIR = ".codex";
1097
- var CODEX_CONFIG_FILE = "config.toml";
1098
- var CLAUDE_CONFIG_DIR = ".claude";
1099
- var CLAUDE_SETTINGS_FILE = "settings.json";
1100
- var CLAUDE_SESSION_FILE = "claude-settings.json";
1101
- var CLAUDE_LAUNCHER = "claude-unrestricted";
1102
- var STORE_DIR_NAME = "unrestricted";
1103
- var OPENCODE_PLUGINS_DIR = "plugins";
1104
- var PI_EXTENSIONS_DIR = "extensions";
1105
- var AUTO_UPDATE_FILE = "unrestricted.ts";
1106
- function toolPaths(locations) {
1107
- const { join: join4 } = pathsFor(locations.platform);
1108
- const { home, configHome, piAgentDir } = locations;
1109
- const opencodeDir = join4(configHome, OPENCODE_CONFIG_DIR);
1110
- const crushDir = join4(configHome, CRUSH_CONFIG_DIR);
1111
- const continueDir = join4(home, CONTINUE_CONFIG_DIR);
1112
- const codexDir = join4(home, CODEX_CONFIG_DIR);
1113
- const claudeDir = join4(home, CLAUDE_CONFIG_DIR);
1114
- const claudeLauncherDir = join4(home, ".local", "bin");
1115
- return {
1116
- opencodeDir,
1117
- opencodeJson: join4(opencodeDir, OPENCODE_CONFIG_FILE),
1118
- opencodeJsonc: join4(opencodeDir, OPENCODE_JSONC_FILE),
1119
- opencodePlugin: join4(opencodeDir, OPENCODE_PLUGINS_DIR, AUTO_UPDATE_FILE),
1120
- crushDir,
1121
- crushrc: join4(crushDir, CRUSH_CONFIG_FILE),
1122
- piAgentDir,
1123
- piModels: join4(piAgentDir, PI_MODELS_FILE),
1124
- piSettings: join4(piAgentDir, PI_SETTINGS_FILE),
1125
- piExtension: join4(piAgentDir, PI_EXTENSIONS_DIR, AUTO_UPDATE_FILE),
1126
- continueDir,
1127
- continueConfig: join4(continueDir, CONTINUE_CONFIG_FILE),
1128
- aiderConfig: join4(home, AIDER_CONFIG_FILE),
1129
- codexDir,
1130
- codexConfig: join4(codexDir, CODEX_CONFIG_FILE),
1131
- claudeDir,
1132
- claudeGlobalSettings: join4(claudeDir, CLAUDE_SETTINGS_FILE),
1133
- claudeSessionSettings: join4(configHome, STORE_DIR_NAME, CLAUDE_SESSION_FILE),
1134
- claudeLauncherDir,
1135
- claudeLauncher: join4(claudeLauncherDir, CLAUDE_LAUNCHER)
1136
- };
1137
- }
1138
-
1139
- // cli/src/store.ts
1140
1342
  var CREDENTIALS_FILE = "credentials.json";
1141
1343
  var STATE_FILE = "state.json";
1142
- var DIR_MODE = 448;
1143
- var FILE_MODE = 384;
1344
+ var DIR_MODE2 = 448;
1345
+ var FILE_MODE2 = 384;
1144
1346
  var WEB_ORIGIN = /^https?:\/\/\S+$/;
1145
1347
  var SHA256 = /^[0-9a-f]{64}$/;
1348
+ var MAX_DEFAULT_LENGTH = 512;
1349
+ var FIRST_PRINTABLE = " ";
1350
+ var DELETE = "\x7F";
1146
1351
  function storeDir(configHome) {
1147
1352
  return join(configHome, STORE_DIR_NAME);
1148
1353
  }
@@ -1158,7 +1363,7 @@ function isKey(value) {
1158
1363
  function readStoreFile(file) {
1159
1364
  let parsed;
1160
1365
  try {
1161
- parsed = JSON.parse(readFileSync2(file, "utf8"));
1366
+ parsed = JSON.parse(readFileSync3(file, "utf8"));
1162
1367
  } catch {
1163
1368
  return null;
1164
1369
  }
@@ -1166,12 +1371,12 @@ function readStoreFile(file) {
1166
1371
  }
1167
1372
  function writeStoreFile(configHome, file, value) {
1168
1373
  const dir = storeDir(configHome);
1169
- mkdirSync(dir, { recursive: true, mode: DIR_MODE });
1170
- setMode(dir, DIR_MODE);
1374
+ mkdirSync2(dir, { recursive: true, mode: DIR_MODE2 });
1375
+ setMode(dir, DIR_MODE2);
1171
1376
  const pending = `${file}.${process.pid}.tmp`;
1172
- writeFileSync2(pending, `${JSON.stringify(value, null, 2)}
1173
- `, { mode: FILE_MODE });
1174
- setMode(pending, FILE_MODE);
1377
+ writeFileSync3(pending, `${JSON.stringify(value, null, 2)}
1378
+ `, { mode: FILE_MODE2 });
1379
+ setMode(pending, FILE_MODE2);
1175
1380
  renameSync(pending, file);
1176
1381
  return file;
1177
1382
  }
@@ -1190,8 +1395,8 @@ function saveCredentials(configHome, credentials) {
1190
1395
  }
1191
1396
  function deleteCredentials(configHome) {
1192
1397
  const file = credentialsPath(configHome);
1193
- if (!existsSync2(file)) return false;
1194
- rmSync(file, { force: true });
1398
+ if (!existsSync(file)) return false;
1399
+ rmSync3(file, { force: true });
1195
1400
  return true;
1196
1401
  }
1197
1402
  function readAutoUpdate(value) {
@@ -1206,16 +1411,54 @@ function readAutoUpdate(value) {
1206
1411
  function withAutoUpdate(value, records) {
1207
1412
  return records === void 0 || Object.keys(records).length === 0 ? value : { ...value, autoUpdate: records };
1208
1413
  }
1414
+ function isDefaultValue(value) {
1415
+ return typeof value === "string" && value.length > 0 && value.length <= MAX_DEFAULT_LENGTH && [...value].every((character) => character >= FIRST_PRINTABLE && character !== DELETE);
1416
+ }
1417
+ function readDefaults(value) {
1418
+ const stored = asObject(value);
1419
+ const defaults = {};
1420
+ for (const tool of ALL_TOOLS) {
1421
+ const recorded = stored[tool];
1422
+ if (isDefaultValue(recorded)) defaults[tool] = recorded;
1423
+ }
1424
+ return defaults;
1425
+ }
1426
+ function withDefaults(value, defaults) {
1427
+ return defaults === void 0 || Object.keys(defaults).length === 0 ? value : { ...value, defaults };
1428
+ }
1209
1429
  function readState(configHome) {
1210
1430
  const stored = readStoreFile(statePath(configHome));
1211
1431
  if (stored === null || !Array.isArray(stored.tools)) return null;
1212
1432
  const tools = unique(stored.tools.filter((tool) => typeof tool === "string" && isToolId(tool)));
1213
1433
  const models2 = Array.isArray(stored.models) ? unique(stored.models.filter(isModelId)) : [];
1214
- return withAutoUpdate({ tools, models: models2, model: isModelId(stored.model) ? stored.model : null }, readAutoUpdate(stored.autoUpdate));
1434
+ const state = withAutoUpdate({ tools, models: models2, model: isModelId(stored.model) ? stored.model : null }, readAutoUpdate(stored.autoUpdate));
1435
+ return withDefaults(state, readDefaults(stored.defaults));
1215
1436
  }
1216
1437
  function saveState(configHome, state) {
1217
1438
  const { tools, models: models2, model } = state;
1218
- return writeStoreFile(configHome, statePath(configHome), withAutoUpdate({ tools, models: models2, model }, state.autoUpdate));
1439
+ const value = withDefaults(withAutoUpdate({ tools, models: models2, model }, state.autoUpdate), state.defaults);
1440
+ return writeStoreFile(configHome, statePath(configHome), value);
1441
+ }
1442
+ function deleteState(configHome) {
1443
+ const file = statePath(configHome);
1444
+ if (!existsSync(file)) return false;
1445
+ rmSync3(file, { force: true });
1446
+ return true;
1447
+ }
1448
+ function forgetTools(configHome, state, forgotten) {
1449
+ if (!state.tools.some((tool) => forgotten.includes(tool))) return;
1450
+ const tools = state.tools.filter((tool) => !forgotten.includes(tool));
1451
+ if (tools.length === 0) {
1452
+ deleteState(configHome);
1453
+ return;
1454
+ }
1455
+ const autoUpdate = { ...state.autoUpdate };
1456
+ const defaults = { ...state.defaults };
1457
+ for (const tool of forgotten) {
1458
+ if (isAutoUpdateTool(tool)) delete autoUpdate[tool];
1459
+ delete defaults[tool];
1460
+ }
1461
+ saveState(configHome, { ...state, tools, autoUpdate, defaults });
1219
1462
  }
1220
1463
  function mergeTools(previous, configured) {
1221
1464
  return unique([...previous, ...configured]);
@@ -1634,7 +1877,7 @@ async function models(context) {
1634
1877
 
1635
1878
  // cli/src/auto-update.ts
1636
1879
  import { createHash } from "node:crypto";
1637
- import { lstatSync, mkdirSync as mkdirSync2, readFileSync as readFileSync3, renameSync as renameSync2, rmSync as rmSync2, writeFileSync as writeFileSync3 } from "node:fs";
1880
+ import { lstatSync, mkdirSync as mkdirSync3, readFileSync as readFileSync4, renameSync as renameSync2, rmSync as rmSync4, writeFileSync as writeFileSync4 } from "node:fs";
1638
1881
  import { dirname } from "node:path";
1639
1882
 
1640
1883
  // cli/src/templates/opencode-plugin.ts?raw
@@ -1647,29 +1890,25 @@ var pi_extension_default = 'type ListedModel = {\n id: string;\n name: string;
1647
1890
  import { spawnSync } from "node:child_process";
1648
1891
 
1649
1892
  // cli/src/detect.ts
1650
- import { accessSync, constants, readdirSync, statSync } from "node:fs";
1893
+ import { accessSync, constants, readdirSync as readdirSync2, statSync as statSync2 } from "node:fs";
1651
1894
  import { join as join2 } from "node:path";
1652
1895
  var DEFAULT_PATHEXT = ".COM;.EXE;.BAT;.CMD";
1653
- function isFile(path) {
1896
+ function isFile2(path) {
1654
1897
  try {
1655
- return statSync(path).isFile();
1898
+ return statSync2(path).isFile();
1656
1899
  } catch {
1657
1900
  return false;
1658
1901
  }
1659
1902
  }
1660
1903
  function isDirectory(path) {
1661
1904
  try {
1662
- return statSync(path).isDirectory();
1905
+ return statSync2(path).isDirectory();
1663
1906
  } catch {
1664
1907
  return false;
1665
1908
  }
1666
1909
  }
1667
- function windowsVariable(env, name) {
1668
- const key = Object.keys(env).find((candidate) => candidate.toUpperCase() === name);
1669
- return key === void 0 ? void 0 : env[key];
1670
- }
1671
1910
  function isExecutable(path) {
1672
- if (!isFile(path)) return false;
1911
+ if (!isFile2(path)) return false;
1673
1912
  try {
1674
1913
  accessSync(path, constants.X_OK);
1675
1914
  return true;
@@ -1680,11 +1919,11 @@ function isExecutable(path) {
1680
1919
  function hasWindowsCommand(dir, names) {
1681
1920
  let entries;
1682
1921
  try {
1683
- entries = readdirSync(dir);
1922
+ entries = readdirSync2(dir);
1684
1923
  } catch {
1685
1924
  return false;
1686
1925
  }
1687
- return entries.some((entry) => names.has(entry.toLowerCase()) && isFile(join2(dir, entry)));
1926
+ return entries.some((entry) => names.has(entry.toLowerCase()) && isFile2(join2(dir, entry)));
1688
1927
  }
1689
1928
  function searchPath(value, platform) {
1690
1929
  const { delimiter } = pathsFor(platform);
@@ -1702,14 +1941,14 @@ function onPath(name, env = process.env, platform = process.platform) {
1702
1941
  function windowsCommandIn(dir, name, extensions) {
1703
1942
  let entries;
1704
1943
  try {
1705
- entries = readdirSync(dir);
1944
+ entries = readdirSync2(dir);
1706
1945
  } catch {
1707
1946
  return null;
1708
1947
  }
1709
1948
  for (const extension of extensions) {
1710
1949
  const wanted = `${name}${extension}`.toLowerCase();
1711
1950
  const entry = entries.find((candidate) => candidate.toLowerCase() === wanted);
1712
- if (entry !== void 0 && isFile(join2(dir, entry))) return join2(dir, entry);
1951
+ if (entry !== void 0 && isFile2(join2(dir, entry))) return join2(dir, entry);
1713
1952
  }
1714
1953
  return null;
1715
1954
  }
@@ -1733,8 +1972,14 @@ function detect(tool, config, env = process.env) {
1733
1972
  return found("opencode") || isDirectory(paths.opencodeDir);
1734
1973
  case "codex":
1735
1974
  return found("codex") || isDirectory(paths.codexDir);
1975
+ case "qwen-code":
1976
+ return found("qwen") || isDirectory(paths.qwenDir);
1977
+ case "goose":
1978
+ return found("goose") || isDirectory(paths.gooseDir);
1979
+ case "openhands":
1980
+ return found("openhands") || isDirectory(paths.openhandsDir);
1736
1981
  case "aider":
1737
- return found("aider") || isFile(paths.aiderConfig);
1982
+ return found("aider") || isFile2(paths.aiderConfig);
1738
1983
  case "continue":
1739
1984
  return isDirectory(paths.continueDir);
1740
1985
  case "crush":
@@ -1826,7 +2071,7 @@ function sha256(text) {
1826
2071
  function isOwnFile(text, marker) {
1827
2072
  return text.split(/\r?\n/).some((line) => line.trim() === marker);
1828
2073
  }
1829
- function isMissing(error) {
2074
+ function isMissing2(error) {
1830
2075
  return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
1831
2076
  }
1832
2077
  function readExisting(path, marker) {
@@ -1834,21 +2079,21 @@ function readExisting(path, marker) {
1834
2079
  try {
1835
2080
  stats = lstatSync(path);
1836
2081
  } catch (error) {
1837
- if (isMissing(error)) return { kind: "missing" };
2082
+ if (isMissing2(error)) return { kind: "missing" };
1838
2083
  throw error;
1839
2084
  }
1840
2085
  if (!stats.isFile()) return { kind: "foreign" };
1841
- const text = readFileSync3(path, "utf8");
2086
+ const text = readFileSync4(path, "utf8");
1842
2087
  return isOwnFile(text, marker) ? { kind: "ours", text } : { kind: "foreign" };
1843
2088
  }
1844
2089
  function writeAtomically(path, text) {
1845
- mkdirSync2(dirname(path), { recursive: true });
2090
+ mkdirSync3(dirname(path), { recursive: true });
1846
2091
  const pending = `${path}.${process.pid}.tmp`;
1847
2092
  try {
1848
- writeFileSync3(pending, text);
2093
+ writeFileSync4(pending, text);
1849
2094
  renameSync2(pending, path);
1850
2095
  } finally {
1851
- rmSync2(pending, { force: true });
2096
+ rmSync4(pending, { force: true });
1852
2097
  }
1853
2098
  }
1854
2099
  function writeAutoUpdateFile(tool, config, version2) {
@@ -1858,7 +2103,7 @@ function writeAutoUpdateFile(tool, config, version2) {
1858
2103
  const existing = readExisting(path, file.marker);
1859
2104
  if (!isAtLeast(version2, file.minimum)) {
1860
2105
  if (existing.kind !== "ours") return { status: "unsupported" };
1861
- rmSync2(path, { force: true });
2106
+ rmSync4(path, { force: true });
1862
2107
  return { status: "removed", path };
1863
2108
  }
1864
2109
  if (existing.kind === "foreign") return { status: "foreign", path };
@@ -1867,6 +2112,17 @@ function writeAutoUpdateFile(tool, config, version2) {
1867
2112
  writeAtomically(path, file.source);
1868
2113
  return { status: "written", path, sha256: hash };
1869
2114
  }
2115
+ function hasOwnAutoUpdateFile(tool, locations) {
2116
+ const file = AUTO_UPDATE_FILES[tool];
2117
+ return readExisting(file.path(toolPaths(locations)), file.marker).kind === "ours";
2118
+ }
2119
+ function removeAutoUpdateFile(tool, locations) {
2120
+ const file = AUTO_UPDATE_FILES[tool];
2121
+ const path = file.path(toolPaths(locations));
2122
+ if (readExisting(path, file.marker).kind !== "ours") return null;
2123
+ rmSync4(path, { force: true });
2124
+ return path;
2125
+ }
1870
2126
  function installAutoUpdate(tool, config, versionOf) {
1871
2127
  const file = AUTO_UPDATE_FILES[tool];
1872
2128
  try {
@@ -1900,27 +2156,116 @@ function autoUpdateMessage(tool, result) {
1900
2156
  }
1901
2157
 
1902
2158
  // cli/src/writers/aider.ts
1903
- import { existsSync as existsSync3, readFileSync as readFileSync4, writeFileSync as writeFileSync4 } from "node:fs";
1904
- function write(config) {
2159
+ var aider_exports = {};
2160
+ __export(aider_exports, {
2161
+ currentDefault: () => currentDefault,
2162
+ hasOwnEntries: () => hasOwnEntries,
2163
+ remove: () => remove,
2164
+ write: () => write
2165
+ });
2166
+ var TOOL = "aider";
2167
+ var SETTING = /^(openai-api-base|openai-api-key|model):\s*(.*?)\s*$/;
2168
+ var MODEL_PREFIX = "openai/";
2169
+ function setting(line) {
2170
+ const match = SETTING.exec(line);
2171
+ return match === null ? null : { key: match[1], value: match[2] };
2172
+ }
2173
+ function isModelLine(line) {
2174
+ return setting(line)?.key === "model";
2175
+ }
2176
+ function isOwnLine(line, owner) {
2177
+ const found = setting(line);
2178
+ if (found === null) return false;
2179
+ switch (found.key) {
2180
+ case "openai-api-base":
2181
+ return found.value === owner.base;
2182
+ case "openai-api-key":
2183
+ return found.value.startsWith(KEY_PREFIX);
2184
+ default:
2185
+ return found.value.startsWith(MODEL_PREFIX) && isOwnModel(found.value.slice(MODEL_PREFIX.length), owner);
2186
+ }
2187
+ }
2188
+ function write(config, options) {
1905
2189
  const file = toolPaths(config).aiderConfig;
1906
- if (!existsSync3(file)) writeFileSync4(file, "");
1907
- const raw = readFileSync4(file, "utf8");
1908
- const content = toLf(raw);
1909
- const existing = content === "" ? [] : (content.endsWith("\n") ? content.slice(0, -1) : content).split("\n");
1910
- const kept = existing.filter((line) => !/^(openai-api-base|openai-api-key|model):/.test(line));
1911
- kept.push(`openai-api-base: ${config.base}`, `openai-api-key: ${config.key}`, `model: openai/${config.model}`);
1912
- writeFileSync4(file, fromLf(`${kept.join("\n")}
1913
- `, layoutOf(raw)));
2190
+ const raw = readText(file);
2191
+ const replaced = (line) => {
2192
+ const key = setting(line)?.key;
2193
+ return key !== void 0 && (key !== "model" || options.setDefault);
2194
+ };
2195
+ const kept = splitLines(toLf(raw ?? "")).filter((line) => !replaced(line));
2196
+ kept.push(`openai-api-base: ${config.base}`, `openai-api-key: ${config.key}`);
2197
+ if (options.setDefault) kept.push(`model: ${MODEL_PREFIX}${config.model}`);
2198
+ saveText(config, TOOL, file, fromLf(`${kept.join("\n")}
2199
+ `, layoutOf(raw ?? "")), raw);
1914
2200
  setMode(file, 384, config.platform);
1915
2201
  return { path: file, next: "aider" };
1916
2202
  }
2203
+ function currentDefault(config) {
2204
+ let raw;
2205
+ try {
2206
+ raw = readText(toolPaths(config).aiderConfig);
2207
+ } catch {
2208
+ return null;
2209
+ }
2210
+ const models2 = splitLines(toLf(raw ?? "")).map(setting).filter((found) => found !== null && found.key === "model" && found.value !== "");
2211
+ return models2.at(-1)?.value ?? null;
2212
+ }
2213
+ function removal(config, previousDefault) {
2214
+ const file = toolPaths(config).aiderConfig;
2215
+ const raw = readText(file);
2216
+ if (raw === null) return [];
2217
+ const lines = splitLines(toLf(raw));
2218
+ const own = (line) => isOwnLine(line, config);
2219
+ if (!lines.some(own)) return [];
2220
+ const kept = lines.filter((line) => !own(line));
2221
+ const lastModel = lines.findLastIndex(isModelLine);
2222
+ if (previousDefault !== null && lastModel !== -1 && own(lines[lastModel]) && !kept.some(isModelLine)) {
2223
+ kept.splice(lines.slice(0, lastModel).filter((line) => !own(line)).length, 0, `model: ${previousDefault}`);
2224
+ }
2225
+ return [{ file, raw, next: kept.every(isBlankText) ? null : fromLf(`${kept.join("\n")}
2226
+ `, layoutOf(raw)) }];
2227
+ }
2228
+ function remove(config, previousDefault) {
2229
+ const changes = removal(config, previousDefault);
2230
+ applyChanges(config, TOOL, changes);
2231
+ return { path: toolPaths(config).aiderConfig, changed: changes.length > 0 };
2232
+ }
2233
+ function hasOwnEntries(config) {
2234
+ return removal(config, null).length > 0;
2235
+ }
1917
2236
 
1918
2237
  // cli/src/writers/claude.ts
1919
- import { existsSync as existsSync4, mkdirSync as mkdirSync3, writeFileSync as writeFileSync5 } from "node:fs";
2238
+ var claude_exports = {};
2239
+ __export(claude_exports, {
2240
+ apiOrigin: () => apiOrigin,
2241
+ currentDefault: () => currentDefault2,
2242
+ hasOwnEntries: () => hasOwnEntries2,
2243
+ launcherPath: () => launcherPath,
2244
+ remove: () => remove2,
2245
+ settingsPath: () => settingsPath,
2246
+ write: () => write2
2247
+ });
2248
+ import { mkdirSync as mkdirSync4, rmSync as rmSync5, writeFileSync as writeFileSync5 } from "node:fs";
1920
2249
  import { dirname as dirname2, resolve } from "node:path";
2250
+ var TOOL2 = "claude";
2251
+ var SESSION_ENV_KEYS = [
2252
+ "ANTHROPIC_BASE_URL",
2253
+ "ANTHROPIC_AUTH_TOKEN",
2254
+ "ANTHROPIC_MODEL",
2255
+ "ANTHROPIC_SMALL_FAST_MODEL",
2256
+ "CLAUDE_CODE_MAX_CONTEXT_TOKENS",
2257
+ "CLAUDE_CODE_DISABLE_UNKNOWN_MODEL_WINDOW_ENFORCEMENT"
2258
+ ];
2259
+ var MODEL_PICKER = "modelPicker";
1921
2260
  function apiOrigin(base) {
1922
2261
  return base.replace(/\/v1$/, "");
1923
2262
  }
2263
+ function settingsPath(config) {
2264
+ return toolPaths(config).claudeSessionSettings;
2265
+ }
2266
+ function launcherPath(config) {
2267
+ return toolPaths(config).claudeLauncher;
2268
+ }
1924
2269
  function underHome(path, home) {
1925
2270
  return path.startsWith(`${home}/`) ? path.slice(home.length + 1) : "";
1926
2271
  }
@@ -1932,50 +2277,61 @@ function shellPath(path, home) {
1932
2277
  const rest = underHome(path, home);
1933
2278
  return rest ? `"$HOME/${rest}"` : `"${path}"`;
1934
2279
  }
2280
+ function launcherText(settings, home) {
2281
+ return `#!/bin/sh
2282
+ exec claude --settings ${shellPath(settings, home)} "$@"
2283
+ `;
2284
+ }
1935
2285
  function pathContains(dir, env, platform) {
1936
2286
  return (env.PATH ?? "").split(pathsFor(platform).delimiter).filter(Boolean).some((entry) => resolve(entry) === resolve(dir));
1937
2287
  }
1938
- function isOurs(name, value, config) {
2288
+ function isOurs(name, value, owner) {
1939
2289
  if (typeof value !== "string") return false;
1940
2290
  switch (name) {
1941
2291
  case "ANTHROPIC_BASE_URL":
1942
- return value === apiOrigin(config.base);
2292
+ return value === apiOrigin(owner.base);
1943
2293
  case "ANTHROPIC_AUTH_TOKEN":
1944
2294
  return value.startsWith(KEY_PREFIX);
1945
2295
  case "ANTHROPIC_MODEL":
1946
2296
  case "ANTHROPIC_SMALL_FAST_MODEL":
1947
- return isOwnModel(value, config);
2297
+ return isOwnModel(value, owner);
1948
2298
  default:
1949
2299
  return false;
1950
2300
  }
1951
2301
  }
1952
- function removeGlobalEnv(config) {
1953
- const file = toolPaths(config).claudeGlobalSettings;
1954
- if (!existsSync4(file)) return void 0;
1955
- let document;
1956
- try {
1957
- document = readJsonDocument(file);
1958
- } catch {
1959
- return void 0;
1960
- }
1961
- const settings = document.value;
2302
+ function withoutOwnGlobalEnv(settings, owner) {
1962
2303
  const env = asObject(settings.env);
1963
2304
  const kept = {};
1964
2305
  let removed = 0;
1965
2306
  for (const [name, value] of Object.entries(env)) {
1966
- if (isOurs(name, value, config)) removed += 1;
2307
+ if (isOurs(name, value, owner)) removed += 1;
1967
2308
  else kept[name] = value;
1968
2309
  }
1969
- if (removed === 0) return void 0;
1970
- if (Object.keys(kept).length > 0) settings.env = kept;
1971
- else delete settings.env;
1972
- writeJsonDocument(file, settings, document.layout);
2310
+ if (removed === 0) return null;
2311
+ const next = { ...settings, env: kept };
2312
+ if (Object.keys(kept).length === 0) delete next.env;
2313
+ return next;
2314
+ }
2315
+ function readGlobalSettings(file) {
2316
+ try {
2317
+ const document = readJsonDocument(file);
2318
+ return document.raw === null ? null : document;
2319
+ } catch {
2320
+ return null;
2321
+ }
2322
+ }
2323
+ function removeGlobalEnv(config) {
2324
+ const file = toolPaths(config).claudeGlobalSettings;
2325
+ const document = readGlobalSettings(file);
2326
+ const next = document === null ? null : withoutOwnGlobalEnv(document.value, config);
2327
+ if (document === null || next === null) return void 0;
2328
+ saveText(config, TOOL2, file, jsonText(next, document.layout), document.raw);
1973
2329
  return `Removed the old global unrestricted.sh env from ${displayPath(file, config.home)}`;
1974
2330
  }
1975
- function write2(config, env = process.env) {
2331
+ function write2(config, _options, env = process.env) {
1976
2332
  const paths = toolPaths(config);
1977
2333
  const file = paths.claudeSessionSettings;
1978
- mkdirSync3(dirname2(file), { recursive: true });
2334
+ mkdirSync4(dirname2(file), { recursive: true });
1979
2335
  const document = readJsonDocument(file);
1980
2336
  const settings = document.value;
1981
2337
  settings.env = {
@@ -1987,63 +2343,129 @@ function write2(config, env = process.env) {
1987
2343
  CLAUDE_CODE_MAX_CONTEXT_TOKENS: String(config.context),
1988
2344
  CLAUDE_CODE_DISABLE_UNKNOWN_MODEL_WINDOW_ENFORCEMENT: "1"
1989
2345
  };
1990
- settings.modelPicker = {
2346
+ settings[MODEL_PICKER] = {
1991
2347
  options: [{ model: config.model, label: config.modelName, description: "unrestricted.sh" }],
1992
2348
  replaceBuiltInOptions: true
1993
2349
  };
1994
- writeJsonDocument(file, settings, document.layout);
2350
+ saveText(config, TOOL2, file, jsonText(settings, document.layout), document.raw);
1995
2351
  setMode(file, 384, config.platform);
1996
2352
  const launcher = paths.claudeLauncher;
1997
2353
  const bin = paths.claudeLauncherDir;
1998
- mkdirSync3(bin, { recursive: true });
1999
- writeFileSync5(launcher, `#!/bin/sh
2000
- exec claude --settings ${shellPath(file, config.home)} "$@"
2001
- `);
2354
+ mkdirSync4(bin, { recursive: true });
2355
+ writeFileSync5(launcher, launcherText(file, config.home));
2002
2356
  setMode(launcher, 493, config.platform);
2003
2357
  const hint = removeGlobalEnv(config);
2004
2358
  const next = pathContains(bin, env, config.platform) ? CLAUDE_LAUNCHER : `claude --settings ${displayPath(file, config.home)} (or add ~/.local/bin to PATH for ${CLAUDE_LAUNCHER})`;
2005
2359
  return hint ? { path: file, next, hint } : { path: file, next };
2006
2360
  }
2361
+ function currentDefault2(_config) {
2362
+ return null;
2363
+ }
2364
+ function withoutOwnSessionSettings(settings) {
2365
+ const env = asObject(settings.env);
2366
+ const keptEnv = Object.fromEntries(Object.entries(env).filter(([name]) => !SESSION_ENV_KEYS.includes(name)));
2367
+ const ownEnv = Object.keys(keptEnv).length < Object.keys(env).length;
2368
+ if (!ownEnv && !(MODEL_PICKER in settings)) return null;
2369
+ const next = { ...settings, env: keptEnv };
2370
+ if (Object.keys(keptEnv).length === 0) delete next.env;
2371
+ delete next[MODEL_PICKER];
2372
+ return next;
2373
+ }
2374
+ function changeTo(file, document, value) {
2375
+ return { file, raw: document.raw, next: isBareDocument(value) ? null : jsonText(value, document.layout) };
2376
+ }
2377
+ function removal2(config) {
2378
+ const paths = toolPaths(config);
2379
+ const changes = [];
2380
+ const session = readJsonDocument(paths.claudeSessionSettings);
2381
+ const cleaned = session.raw === null ? null : withoutOwnSessionSettings(session.value);
2382
+ if (cleaned !== null) changes.push(changeTo(paths.claudeSessionSettings, session, cleaned));
2383
+ const global = readGlobalSettings(paths.claudeGlobalSettings);
2384
+ const globalCleaned = global === null ? null : withoutOwnGlobalEnv(global.value, config);
2385
+ if (global !== null && globalCleaned !== null) changes.push(changeTo(paths.claudeGlobalSettings, global, globalCleaned));
2386
+ const launcher = readText(paths.claudeLauncher);
2387
+ const ownLauncher = launcher !== null && launcher === launcherText(paths.claudeSessionSettings, config.home);
2388
+ return { changes, launcher: ownLauncher ? paths.claudeLauncher : null };
2389
+ }
2390
+ function remove2(config, _previousDefault) {
2391
+ const plan = removal2(config);
2392
+ applyChanges(config, TOOL2, plan.changes);
2393
+ if (plan.launcher !== null) rmSync5(plan.launcher, { force: true });
2394
+ return {
2395
+ path: plan.changes[0]?.file ?? plan.launcher ?? toolPaths(config).claudeSessionSettings,
2396
+ changed: plan.changes.length > 0 || plan.launcher !== null
2397
+ };
2398
+ }
2399
+ function hasOwnEntries2(config) {
2400
+ const plan = removal2(config);
2401
+ return plan.changes.length > 0 || plan.launcher !== null;
2402
+ }
2007
2403
 
2008
2404
  // cli/src/writers/codex.ts
2009
- import { existsSync as existsSync5, mkdirSync as mkdirSync5, readFileSync as readFileSync6, writeFileSync as writeFileSync7 } from "node:fs";
2405
+ var codex_exports = {};
2406
+ __export(codex_exports, {
2407
+ applyTopLevelKeys: () => applyTopLevelKeys,
2408
+ currentDefault: () => currentDefault3,
2409
+ hasOwnEntries: () => hasOwnEntries3,
2410
+ remove: () => remove3,
2411
+ startCommand: () => startCommand,
2412
+ write: () => write3
2413
+ });
2414
+ import { mkdirSync as mkdirSync5, rmSync as rmSync6 } from "node:fs";
2010
2415
 
2011
2416
  // cli/src/env-file.ts
2012
- import { appendFileSync, mkdirSync as mkdirSync4, readFileSync as readFileSync5, statSync as statSync2, writeFileSync as writeFileSync6 } from "node:fs";
2013
2417
  import { join as join3 } from "node:path";
2014
- function isFile2(path) {
2015
- try {
2016
- return statSync2(path).isFile();
2017
- } catch {
2018
- return false;
2019
- }
2418
+ var ENV_FILE = "env";
2419
+ var KEY_EXPORT = new RegExp(`^export ${API_KEY_ENV}=unr_sk_\\S*$`);
2420
+ function envFilePath(config) {
2421
+ return join3(config.configHome, STORE_DIR_NAME, ENV_FILE);
2020
2422
  }
2021
- function writeEnvFile(config) {
2022
- const dir = join3(config.configHome, "unrestricted");
2023
- const file = join3(dir, "env");
2024
- mkdirSync4(dir, { recursive: true });
2025
- writeFileSync6(file, `export UNRESTRICTED_API_KEY=${config.key}
2026
- `);
2027
- setMode(file, 384, config.platform);
2028
- for (const rc of [join3(config.home, ".zshrc"), join3(config.home, ".bashrc")]) {
2029
- if (!isFile2(rc)) continue;
2030
- const content = readFileSync5(rc, "utf8");
2031
- if (content.includes(file)) continue;
2032
- appendFileSync(rc, fromLf(`
2033
- . "${file}"
2034
- `, { eol: layoutOf(content).eol, bom: false }));
2423
+ function shellProfiles(config) {
2424
+ return [join3(config.home, ".zshrc"), join3(config.home, ".bashrc")];
2425
+ }
2426
+ function sourceLine(file) {
2427
+ return `. "${file}"`;
2428
+ }
2429
+ function isOwnEnvFile(text) {
2430
+ const lines = splitLines(toLf(text)).filter((line) => !isBlankText(line));
2431
+ return lines.length > 0 && lines.every((line) => KEY_EXPORT.test(line));
2432
+ }
2433
+ function withoutSourceLine(lines, source) {
2434
+ const kept = [];
2435
+ for (const line of lines) {
2436
+ if (line !== source) kept.push(line);
2437
+ else if (kept.length > 0 && kept[kept.length - 1] === "") kept.pop();
2035
2438
  }
2036
- return file;
2439
+ return kept;
2440
+ }
2441
+ function envFileRemoval(config) {
2442
+ const file = envFilePath(config);
2443
+ const text = readText(file);
2444
+ if (text === null || !isOwnEnvFile(text)) return { envFile: null, changes: [] };
2445
+ const changes = [];
2446
+ for (const rc of shellProfiles(config)) {
2447
+ const raw = isFile(rc) ? readText(rc) : null;
2448
+ if (raw === null) continue;
2449
+ const lines = splitLines(toLf(raw));
2450
+ const kept = withoutSourceLine(lines, sourceLine(file));
2451
+ if (kept.length === lines.length) continue;
2452
+ changes.push({ file: rc, raw, next: fromLf(kept.length === 0 ? "" : `${kept.join("\n")}
2453
+ `, layoutOf(raw)) });
2454
+ }
2455
+ return { envFile: file, changes };
2037
2456
  }
2038
2457
 
2039
2458
  // cli/src/writers/codex.ts
2040
- function splitLines(content) {
2041
- if (content === "") return [];
2042
- return (content.endsWith("\n") ? content.slice(0, -1) : content).split("\n");
2043
- }
2459
+ var TOOL3 = "codex";
2460
+ var WIRE_API = "responses";
2461
+ var PROVIDER_KEY = /^model_provider[ \t]*=/;
2462
+ var MODEL_KEY = /^model[ \t]*=/;
2463
+ var STRING_VALUE = /^[A-Za-z_]+[ \t]*=[ \t]*"([^"]*)"/;
2464
+ var PROVIDER_SEPARATOR = "/";
2465
+ var LEGACY_ENV_KEY_LINE = `env_key = "${API_KEY_ENV}"`;
2044
2466
  function applyTopLevelKeys(content, provider, model) {
2045
2467
  const providerLine = `model_provider = "${provider}"`;
2046
- const modelLine2 = `model = "${model}"`;
2468
+ const modelLine3 = `model = "${model}"`;
2047
2469
  const lines = [];
2048
2470
  let inTable = false;
2049
2471
  let seenProvider = false;
@@ -2052,7 +2474,7 @@ function applyTopLevelKeys(content, provider, model) {
2052
2474
  if (line.startsWith("[")) {
2053
2475
  if (!inTable) {
2054
2476
  if (!seenProvider) lines.push(providerLine);
2055
- if (!seenModel) lines.push(modelLine2);
2477
+ if (!seenModel) lines.push(modelLine3);
2056
2478
  seenProvider = true;
2057
2479
  seenModel = true;
2058
2480
  }
@@ -2060,49 +2482,167 @@ function applyTopLevelKeys(content, provider, model) {
2060
2482
  lines.push(line);
2061
2483
  continue;
2062
2484
  }
2063
- if (!inTable && /^model_provider[ \t]*=/.test(line)) {
2485
+ if (!inTable && PROVIDER_KEY.test(line)) {
2064
2486
  lines.push(providerLine);
2065
2487
  seenProvider = true;
2066
2488
  continue;
2067
2489
  }
2068
- if (!inTable && /^model[ \t]*=/.test(line)) {
2069
- lines.push(modelLine2);
2490
+ if (!inTable && MODEL_KEY.test(line)) {
2491
+ lines.push(modelLine3);
2070
2492
  seenModel = true;
2071
2493
  continue;
2072
2494
  }
2073
2495
  lines.push(line);
2074
2496
  }
2075
2497
  if (!seenProvider) lines.push(providerLine);
2076
- if (!seenModel) lines.push(modelLine2);
2498
+ if (!seenModel) lines.push(modelLine3);
2077
2499
  return `${lines.join("\n")}
2078
2500
  `;
2079
2501
  }
2080
- function write3(config) {
2081
- const { codexDir: dir, codexConfig: file } = toolPaths(config);
2082
- mkdirSync5(dir, { recursive: true });
2083
- if (!existsSync5(file)) writeFileSync7(file, "");
2084
- const raw = readFileSync6(file, "utf8");
2085
- let content = toLf(raw);
2086
- const header = new RegExp(`^\\[model_providers\\.${config.provider}\\]`, "m");
2087
- if (!header.test(content)) {
2088
- content += `
2089
- [model_providers.${config.provider}]
2090
- name = "unrestricted.sh"
2091
- base_url = "${config.base}"
2092
- env_key = "UNRESTRICTED_API_KEY"
2093
- wire_api = "responses"
2502
+ function tableHeader(provider) {
2503
+ return `[model_providers.${provider}]`;
2504
+ }
2505
+ function tableKeys(config) {
2506
+ return [
2507
+ { name: "name", value: PROVIDER_NAME },
2508
+ { name: "base_url", value: config.base },
2509
+ { name: "experimental_bearer_token", value: config.key },
2510
+ { name: "wire_api", value: WIRE_API }
2511
+ ];
2512
+ }
2513
+ function keyLine(key) {
2514
+ return `${key.name} = "${key.value}"`;
2515
+ }
2516
+ function keyPattern(name) {
2517
+ return new RegExp(`^${name}[ \\t]*=`);
2518
+ }
2519
+ function tableEnd(lines, start2) {
2520
+ const next = lines.findIndex((line, index) => index > start2 && line.startsWith("["));
2521
+ return next === -1 ? lines.length : next;
2522
+ }
2523
+ function withOwnTable(content, config) {
2524
+ const lines = splitLines(content);
2525
+ const header = tableHeader(config.provider);
2526
+ const start2 = lines.findIndex((line) => line.trim() === header);
2527
+ if (start2 === -1) return `${content}
2528
+ ${[header, ...tableKeys(config).map(keyLine)].join("\n")}
2094
2529
  `;
2530
+ const end = tableEnd(lines, start2);
2531
+ const body = lines.slice(start2 + 1, end).filter((line) => line.trim() !== LEGACY_ENV_KEY_LINE);
2532
+ for (const key of tableKeys(config)) {
2533
+ const at = body.findIndex((line) => keyPattern(key.name).test(line));
2534
+ if (at !== -1) {
2535
+ body[at] = keyLine(key);
2536
+ continue;
2537
+ }
2538
+ const lastContent = body.findLastIndex((line) => !isBlankText(line));
2539
+ body.splice(lastContent + 1, 0, keyLine(key));
2095
2540
  }
2096
- writeFileSync7(file, fromLf(applyTopLevelKeys(content, config.provider, config.model), layoutOf(raw)));
2541
+ return `${[...lines.slice(0, start2 + 1), ...body, ...lines.slice(end)].join("\n")}
2542
+ `;
2543
+ }
2544
+ function topLevelEnd(lines) {
2545
+ const firstTable = lines.findIndex((line) => line.startsWith("["));
2546
+ return firstTable === -1 ? lines.length : firstTable;
2547
+ }
2548
+ function stringValue(line) {
2549
+ const match = line === void 0 ? null : STRING_VALUE.exec(line);
2550
+ return match === null ? null : match[1];
2551
+ }
2552
+ function defaultIn(content) {
2553
+ const lines = splitLines(content);
2554
+ const top = lines.slice(0, topLevelEnd(lines));
2555
+ const model = stringValue(top.find((line) => MODEL_KEY.test(line)));
2556
+ if (model === null || model === "") return null;
2557
+ const provider = stringValue(top.find((line) => PROVIDER_KEY.test(line)));
2558
+ return provider === null || provider === "" ? model : `${provider}${PROVIDER_SEPARATOR}${model}`;
2559
+ }
2560
+ function startCommand(config) {
2561
+ return `codex -c model_provider=${config.provider} -m ${config.model}`;
2562
+ }
2563
+ function write3(config, options) {
2564
+ const { codexDir: dir, codexConfig: file } = toolPaths(config);
2565
+ mkdirSync5(dir, { recursive: true });
2566
+ const raw = readText(file);
2567
+ const content = withOwnTable(toLf(raw ?? ""), config);
2568
+ const updated = options.setDefault ? applyTopLevelKeys(content, config.provider, config.model) : content;
2569
+ saveText(config, TOOL3, file, fromLf(updated, layoutOf(raw ?? "")), raw);
2097
2570
  setMode(file, 384, config.platform);
2098
- const envFile = writeEnvFile(config);
2099
- return { path: file, next: `open a new shell (or: . ${envFile}) then codex` };
2571
+ const startsOnOwnModel = defaultIn(updated)?.startsWith(`${config.provider}${PROVIDER_SEPARATOR}`) ?? false;
2572
+ return { path: file, next: startsOnOwnModel ? "codex" : startCommand(config) };
2573
+ }
2574
+ function currentDefault3(config) {
2575
+ let raw;
2576
+ try {
2577
+ raw = readText(toolPaths(config).codexConfig);
2578
+ } catch {
2579
+ return null;
2580
+ }
2581
+ return defaultIn(toLf(raw ?? ""));
2582
+ }
2583
+ function restoredLines(previousDefault) {
2584
+ const slash = previousDefault.indexOf(PROVIDER_SEPARATOR);
2585
+ if (slash <= 0) return [`model = "${previousDefault}"`];
2586
+ return [`model_provider = "${previousDefault.slice(0, slash)}"`, `model = "${previousDefault.slice(slash + 1)}"`];
2587
+ }
2588
+ function withoutOwnSettings(raw, owner, previousDefault) {
2589
+ const lines = splitLines(toLf(raw));
2590
+ const end = topLevelEnd(lines);
2591
+ const providerAt = lines.findIndex((line, index) => index < end && PROVIDER_KEY.test(line));
2592
+ const modelAt = lines.findIndex((line, index) => index < end && MODEL_KEY.test(line));
2593
+ const model = stringValue(lines[modelAt]);
2594
+ const ownProvider2 = providerAt !== -1 && stringValue(lines[providerAt]) === owner.provider;
2595
+ const ownDefault = ownProvider2 && model !== null && isOwnModel(model, owner);
2596
+ const dropped = /* @__PURE__ */ new Set();
2597
+ if (ownProvider2) dropped.add(providerAt);
2598
+ if (ownDefault) dropped.add(modelAt);
2599
+ const tableAt = lines.findIndex((line) => line.trim() === tableHeader(owner.provider));
2600
+ if (tableAt !== -1) {
2601
+ if (tableAt > 0 && isBlankText(lines[tableAt - 1])) dropped.add(tableAt - 1);
2602
+ for (let index = tableAt; index < tableEnd(lines, tableAt); index += 1) dropped.add(index);
2603
+ }
2604
+ if (dropped.size === 0) return void 0;
2605
+ const kept = lines.filter((_line, index) => !dropped.has(index));
2606
+ if (ownDefault && previousDefault !== null) {
2607
+ const firstOwn = Math.min(...[providerAt, modelAt].filter((index) => index !== -1));
2608
+ kept.splice(lines.slice(0, firstOwn).filter((_line, index) => !dropped.has(index)).length, 0, ...restoredLines(previousDefault));
2609
+ }
2610
+ return kept.every(isBlankText) ? null : fromLf(`${kept.join("\n")}
2611
+ `, layoutOf(raw));
2612
+ }
2613
+ function removal3(config, previousDefault) {
2614
+ const file = toolPaths(config).codexConfig;
2615
+ const raw = readText(file);
2616
+ const next = raw === null ? void 0 : withoutOwnSettings(raw, config, previousDefault);
2617
+ const env = envFileRemoval(config);
2618
+ const changes = next === void 0 ? [] : [{ file, raw, next }];
2619
+ return { changes: [...changes, ...env.changes], envFile: env.envFile };
2620
+ }
2621
+ function remove3(config, previousDefault) {
2622
+ const plan = removal3(config, previousDefault);
2623
+ applyChanges(config, TOOL3, plan.changes);
2624
+ if (plan.envFile !== null) rmSync6(plan.envFile, { force: true });
2625
+ const file = toolPaths(config).codexConfig;
2626
+ return { path: plan.changes[0]?.file ?? plan.envFile ?? file, changed: plan.changes.length > 0 || plan.envFile !== null };
2627
+ }
2628
+ function hasOwnEntries3(config) {
2629
+ const plan = removal3(config, null);
2630
+ return plan.changes.length > 0 || plan.envFile !== null;
2100
2631
  }
2101
2632
 
2102
2633
  // cli/src/writers/continue.ts
2103
- import { existsSync as existsSync6, mkdirSync as mkdirSync6, readFileSync as readFileSync7, writeFileSync as writeFileSync8 } from "node:fs";
2634
+ var continue_exports = {};
2635
+ __export(continue_exports, {
2636
+ currentDefault: () => currentDefault4,
2637
+ hasOwnEntries: () => hasOwnEntries4,
2638
+ remove: () => remove4,
2639
+ write: () => write4
2640
+ });
2641
+ import { mkdirSync as mkdirSync6 } from "node:fs";
2642
+ var TOOL4 = "continue";
2104
2643
  var ITEM_START = " - ";
2105
2644
  var ITEM_FIELD = /^ {4}([A-Za-z]+):\s*(.*?)\s*$/;
2645
+ var CREATED_HEADER = ["name: unrestricted.sh", "version: 0.0.1", "schema: v1", "models:"];
2106
2646
  function modelEntry(config, model) {
2107
2647
  return [
2108
2648
  ` - name: unrestricted.sh ${model.name}`,
@@ -2123,10 +2663,6 @@ function lastTopLevelLine(content) {
2123
2663
  const lines = content.split("\n").filter((line) => /^[A-Za-z]/.test(line));
2124
2664
  return lines.length ? lines[lines.length - 1] : "";
2125
2665
  }
2126
- function splitLines2(content) {
2127
- if (content === "") return [];
2128
- return (content.endsWith("\n") ? content.slice(0, -1) : content).split("\n");
2129
- }
2130
2666
  function modelItems(lines) {
2131
2667
  const header = lines.findIndex((line) => /^models:\s*$/.test(line));
2132
2668
  if (header === -1) return [];
@@ -2147,12 +2683,12 @@ function itemField(lines, item, field) {
2147
2683
  }
2148
2684
  return void 0;
2149
2685
  }
2150
- function isOwnItem(lines, item, config) {
2686
+ function isOwnItem(lines, item, owner) {
2151
2687
  const model = itemField(lines, item, "model");
2152
- return model !== void 0 && isOwnModel(model, config) && itemField(lines, item, "apiBase") === config.base;
2688
+ return model !== void 0 && isOwnModel(model, owner) && itemField(lines, item, "apiBase") === owner.base;
2153
2689
  }
2154
2690
  function replaceOwnItems(content, entries, config) {
2155
- const lines = splitLines2(content);
2691
+ const lines = splitLines(content);
2156
2692
  const own = modelItems(lines).filter((item) => isOwnItem(lines, item, config));
2157
2693
  if (own.length === 0) return null;
2158
2694
  const updated = [];
@@ -2166,81 +2702,460 @@ function replaceOwnItems(content, entries, config) {
2166
2702
  return `${updated.join("\n")}
2167
2703
  `;
2168
2704
  }
2169
- function write4(config) {
2705
+ function write4(config, _options) {
2170
2706
  const { continueDir: dir, continueConfig: file } = toolPaths(config);
2171
2707
  mkdirSync6(dir, { recursive: true });
2172
2708
  const entries = modelEntries(config);
2173
2709
  const next = `reload Continue and pick "unrestricted.sh ${config.modelName}"`;
2174
- if (!existsSync6(file)) {
2175
- writeFileSync8(file, `name: unrestricted.sh
2176
- version: 0.0.1
2177
- schema: v1
2178
- models:
2710
+ const raw = readText(file);
2711
+ if (raw === null) {
2712
+ saveText(config, TOOL4, file, `${CREATED_HEADER.join("\n")}
2179
2713
  ${entries}
2180
- `);
2714
+ `, null);
2181
2715
  setMode(file, 384, config.platform);
2182
2716
  return { path: file, next };
2183
2717
  }
2184
- const raw = readFileSync7(file, "utf8");
2185
2718
  const layout = layoutOf(raw);
2186
2719
  const content = toLf(raw);
2187
2720
  const replaced = replaceOwnItems(content, entries, config);
2188
2721
  if (replaced !== null) {
2189
- if (replaced !== content) writeFileSync8(file, fromLf(replaced, layout));
2722
+ if (replaced !== content) saveText(config, TOOL4, file, fromLf(replaced, layout), raw);
2190
2723
  return { path: file, next };
2191
2724
  }
2192
2725
  if (content.includes(`apiBase: ${config.base}`)) return { path: file, next };
2193
2726
  if (lastTopLevelLine(content) === "models:") {
2194
- writeFileSync8(file, fromLf(`${content}${entries}
2195
- `, layout));
2727
+ saveText(config, TOOL4, file, fromLf(`${content}${entries}
2728
+ `, layout), raw);
2196
2729
  return { path: file, next };
2197
2730
  }
2198
2731
  return { path: file, next, hint: `could not merge into ${file} automatically; add this under models:
2199
2732
  ${entries}` };
2200
2733
  }
2734
+ function currentDefault4(_config) {
2735
+ return null;
2736
+ }
2737
+ function isCreatedHeader(lines) {
2738
+ return lines.length === CREATED_HEADER.length && lines.every((line, index) => line === CREATED_HEADER[index]);
2739
+ }
2740
+ function removal4(config) {
2741
+ const file = toolPaths(config).continueConfig;
2742
+ const raw = readText(file);
2743
+ if (raw === null) return [];
2744
+ const lines = splitLines(toLf(raw));
2745
+ const own = modelItems(lines).filter((item) => isOwnItem(lines, item, config));
2746
+ if (own.length === 0) return [];
2747
+ const kept = lines.filter((_line, index) => !own.some((item) => index >= item.start && index < item.end));
2748
+ const next = isCreatedHeader(kept) || kept.every(isBlankText) ? null : fromLf(`${kept.join("\n")}
2749
+ `, layoutOf(raw));
2750
+ return [{ file, raw, next }];
2751
+ }
2752
+ function remove4(config, _previousDefault) {
2753
+ const changes = removal4(config);
2754
+ applyChanges(config, TOOL4, changes);
2755
+ return { path: toolPaths(config).continueConfig, changed: changes.length > 0 };
2756
+ }
2757
+ function hasOwnEntries4(config) {
2758
+ return removal4(config).length > 0;
2759
+ }
2201
2760
 
2202
2761
  // cli/src/writers/crush.ts
2203
- import { existsSync as existsSync7, mkdirSync as mkdirSync7, readFileSync as readFileSync8, writeFileSync as writeFileSync9 } from "node:fs";
2204
- function splitLines3(content) {
2205
- if (content === "") return [];
2206
- return (content.endsWith("\n") ? content.slice(0, -1) : content).split("\n");
2207
- }
2208
- function isOwnLine(line, config) {
2209
- const [command, action, target = ""] = line.trim().split(/\s+/);
2210
- if (command === "provider" && action === "add") return target === config.provider;
2762
+ var crush_exports = {};
2763
+ __export(crush_exports, {
2764
+ currentDefault: () => currentDefault5,
2765
+ hasOwnEntries: () => hasOwnEntries5,
2766
+ remove: () => remove5,
2767
+ write: () => write5
2768
+ });
2769
+ import { mkdirSync as mkdirSync7 } from "node:fs";
2770
+ var TOOL5 = "crush";
2771
+ function words(line) {
2772
+ return line.trim().split(/\s+/);
2773
+ }
2774
+ function isUseLine(line) {
2775
+ const [command, action] = words(line);
2776
+ return command === "model" && action === "use";
2777
+ }
2778
+ function useTarget(line) {
2779
+ const [command, action, target = ""] = words(line);
2780
+ return command === "model" && action === "use" && target !== "" ? target : null;
2781
+ }
2782
+ function isOwnLine2(line, owner) {
2783
+ const [command, action, target = ""] = words(line);
2784
+ if (command === "provider" && action === "add") return target === owner.provider;
2211
2785
  if (command !== "model" || action !== "add" && action !== "use") return false;
2212
- const prefix = `${config.provider}/`;
2213
- return target.startsWith(prefix) && isOwnModel(target.slice(prefix.length), config);
2786
+ const prefix = `${owner.provider}/`;
2787
+ return target.startsWith(prefix) && isOwnModel(target.slice(prefix.length), owner);
2214
2788
  }
2215
2789
  function modelLine(config, model) {
2216
2790
  return `model add ${config.provider}/${model.id} --name "${model.name}" --context-window ${model.context} --default-max-tokens ${model.maxOutput} --can-reason true`;
2217
2791
  }
2218
- function ownLines(config) {
2792
+ function ownBlock(config) {
2219
2793
  return [
2220
2794
  `provider add ${config.provider} --type openai-compat --base-url "${config.base}" --api-key "${config.key}"`,
2221
- ...config.models.map((model) => modelLine(config, model)),
2222
- `model use ${config.provider}/${config.model}`
2795
+ ...config.models.map((model) => modelLine(config, model))
2223
2796
  ];
2224
2797
  }
2225
- function write5(config) {
2798
+ function write5(config, options) {
2226
2799
  const { crushDir: dir, crushrc: file } = toolPaths(config);
2227
2800
  mkdirSync7(dir, { recursive: true });
2228
- const raw = existsSync7(file) ? readFileSync8(file, "utf8") : "";
2229
- const lines = splitLines3(toLf(raw));
2230
- const firstOwn = lines.findIndex((line) => isOwnLine(line, config));
2231
- const kept = lines.filter((line) => !isOwnLine(line, config));
2232
- kept.splice(firstOwn === -1 ? kept.length : firstOwn, 0, ...ownLines(config));
2233
- const updated = fromLf(`${kept.join("\n")}
2234
- `, layoutOf(raw));
2235
- if (updated !== raw) {
2236
- writeFileSync9(file, updated);
2237
- setMode(file, 384, config.platform);
2801
+ const raw = readText(file);
2802
+ const lines = splitLines(toLf(raw ?? ""));
2803
+ const replaced = (line) => isOwnLine2(line, config) && (options.setDefault || !isUseLine(line));
2804
+ const firstOwn = lines.findIndex((line) => isOwnLine2(line, config));
2805
+ const kept = lines.filter((line) => !replaced(line));
2806
+ const at = firstOwn === -1 ? kept.length : firstOwn;
2807
+ const block = ownBlock(config);
2808
+ kept.splice(at, 0, ...block);
2809
+ if (options.setDefault) {
2810
+ const end = at + block.length;
2811
+ kept.splice(kept.slice(end).some(isUseLine) ? kept.length : end, 0, `model use ${config.provider}/${config.model}`);
2238
2812
  }
2813
+ const updated = fromLf(`${kept.join("\n")}
2814
+ `, layoutOf(raw ?? ""));
2815
+ if (saveText(config, TOOL5, file, updated, raw)) setMode(file, 384, config.platform);
2239
2816
  return { path: file, next: "crush" };
2240
2817
  }
2818
+ function currentDefault5(config) {
2819
+ let raw;
2820
+ try {
2821
+ raw = readText(toolPaths(config).crushrc);
2822
+ } catch {
2823
+ return null;
2824
+ }
2825
+ if (raw === null) return null;
2826
+ const targets = splitLines(toLf(raw)).map(useTarget).filter((target) => target !== null);
2827
+ return targets.at(-1) ?? null;
2828
+ }
2829
+ function removal5(config, previousDefault) {
2830
+ const file = toolPaths(config).crushrc;
2831
+ const raw = readText(file);
2832
+ if (raw === null) return [];
2833
+ const lines = splitLines(toLf(raw));
2834
+ const own = (line) => isOwnLine2(line, config);
2835
+ if (!lines.some(own)) return [];
2836
+ const kept = lines.filter((line) => !own(line));
2837
+ const lastUse = lines.findLastIndex(isUseLine);
2838
+ if (previousDefault !== null && lastUse !== -1 && own(lines[lastUse]) && !kept.some(isUseLine)) {
2839
+ kept.splice(lines.slice(0, lastUse).filter((line) => !own(line)).length, 0, `model use ${previousDefault}`);
2840
+ }
2841
+ return [{ file, raw, next: kept.every(isBlankText) ? null : fromLf(`${kept.join("\n")}
2842
+ `, layoutOf(raw)) }];
2843
+ }
2844
+ function remove5(config, previousDefault) {
2845
+ const changes = removal5(config, previousDefault);
2846
+ applyChanges(config, TOOL5, changes);
2847
+ return { path: toolPaths(config).crushrc, changed: changes.length > 0 };
2848
+ }
2849
+ function hasOwnEntries5(config) {
2850
+ return removal5(config, null).length > 0;
2851
+ }
2852
+
2853
+ // cli/src/writers/goose.ts
2854
+ var goose_exports = {};
2855
+ __export(goose_exports, {
2856
+ currentDefault: () => currentDefault6,
2857
+ hasOwnEntries: () => hasOwnEntries6,
2858
+ remove: () => remove6,
2859
+ scalarValue: () => scalarValue,
2860
+ startCommand: () => startCommand2,
2861
+ withOwnDefault: () => withOwnDefault,
2862
+ write: () => write6,
2863
+ yamlScalar: () => yamlScalar
2864
+ });
2865
+ import { mkdirSync as mkdirSync8 } from "node:fs";
2866
+
2867
+ // cli/src/tool-entries.ts
2868
+ var QWEN_PROTOCOL = "openai";
2869
+ var QWEN_SETTINGS_VERSION = 4;
2870
+ var GOOSE_ENGINE = "openai";
2871
+ var OPENHANDS_MODEL_PREFIX = "openai/";
2872
+ var OPENHANDS_AGENT_USAGE = "agent";
2873
+ var OPENHANDS_CONDENSER_USAGE = "condenser";
2874
+ function qwenModel(source, model) {
2875
+ return {
2876
+ id: model.id,
2877
+ name: model.name,
2878
+ description: PROVIDER_NAME,
2879
+ envKey: API_KEY_ENV,
2880
+ baseUrl: source.base,
2881
+ generationConfig: { contextWindowSize: model.context, samplingParams: { max_tokens: model.maxOutput } }
2882
+ };
2883
+ }
2884
+ function gooseProvider(source, models2) {
2885
+ return {
2886
+ name: source.provider,
2887
+ engine: GOOSE_ENGINE,
2888
+ display_name: PROVIDER_NAME,
2889
+ description: `${PROVIDER_NAME} models`,
2890
+ base_url: source.base,
2891
+ models: models2.map((model) => ({ name: model.id, context_limit: model.context, reasoning: true })),
2892
+ headers: { Authorization: `Bearer ${source.key}` },
2893
+ supports_streaming: true,
2894
+ requires_auth: false
2895
+ };
2896
+ }
2897
+ function openhandsLlm(source, model, usage) {
2898
+ return {
2899
+ model: `${OPENHANDS_MODEL_PREFIX}${model.id}`,
2900
+ api_key: source.key,
2901
+ base_url: source.base,
2902
+ usage_id: usage,
2903
+ max_input_tokens: model.context,
2904
+ max_output_tokens: model.maxOutput
2905
+ };
2906
+ }
2907
+ function openhandsAgent(source, model) {
2908
+ return {
2909
+ llm: openhandsLlm(source, model, OPENHANDS_AGENT_USAGE),
2910
+ system_prompt_kwargs: { cli_mode: true },
2911
+ condenser: { kind: "LLMSummarizingCondenser", llm: openhandsLlm(source, model, OPENHANDS_CONDENSER_USAGE), max_size: 80, keep_first: 4 },
2912
+ kind: "Agent"
2913
+ };
2914
+ }
2915
+
2916
+ // cli/src/writers/goose.ts
2917
+ var TOOL6 = "goose";
2918
+ var ACTIVE_PROVIDER = "active_provider";
2919
+ var PROVIDERS = "providers";
2920
+ var LEGACY_PROVIDER = "GOOSE_PROVIDER";
2921
+ var LEGACY_MODEL = "GOOSE_MODEL";
2922
+ var MODEL_FIELD = "model";
2923
+ var EMPTY_FLOW_MAP = "{}";
2924
+ var DEFAULT_INDENT = " ";
2925
+ var PROVIDER_SEPARATOR2 = "/";
2926
+ var TOP_LEVEL_KEY = /^([A-Za-z0-9_][^:#\s]*)[ \t]*:(.*)$/;
2927
+ var NESTED_KEY = /^([ \t]+)([^\s:#][^:#]*?)[ \t]*:(.*)$/;
2928
+ var PLAIN_SCALAR = /^[A-Za-z][A-Za-z0-9._/-]*$/;
2929
+ var RESERVED_SCALARS = ["true", "false", "yes", "no", "on", "off", "null", "y", "n"];
2930
+ function yamlScalar(value) {
2931
+ if (PLAIN_SCALAR.test(value) && !RESERVED_SCALARS.includes(value.toLowerCase())) return value;
2932
+ return `'${value.replaceAll("'", "''")}'`;
2933
+ }
2934
+ function withoutComment(value) {
2935
+ const hash = value.search(/[ \t]#/);
2936
+ return (hash === -1 ? value : value.slice(0, hash)).trim();
2937
+ }
2938
+ function scalarValue(raw) {
2939
+ const value = raw.trim();
2940
+ if (value.startsWith("'")) {
2941
+ const close = value.lastIndexOf("'");
2942
+ return close > 0 ? value.slice(1, close).replaceAll("''", "'") : null;
2943
+ }
2944
+ if (value.startsWith('"')) {
2945
+ const close = value.lastIndexOf('"');
2946
+ return close > 0 ? value.slice(1, close).replace(/\\(["\\])/g, "$1") : null;
2947
+ }
2948
+ const plain = withoutComment(value);
2949
+ return plain === "" || /^[[{|>&*!]/.test(plain) ? null : plain;
2950
+ }
2951
+ function isContentLine(line) {
2952
+ return !isBlankText(line) && !line.trimStart().startsWith("#");
2953
+ }
2954
+ function topLevelAt(lines, key) {
2955
+ return lines.findIndex((line) => TOP_LEVEL_KEY.exec(line)?.[1] === key);
2956
+ }
2957
+ function topLevelValue(lines, key) {
2958
+ const at = topLevelAt(lines, key);
2959
+ const match = at === -1 ? null : TOP_LEVEL_KEY.exec(lines[at]);
2960
+ return match === null ? null : scalarValue(match[2]);
2961
+ }
2962
+ function blockEnd(lines, header) {
2963
+ let end = header + 1;
2964
+ for (let index = header + 1; index < lines.length; index += 1) {
2965
+ const line = lines[index];
2966
+ if (!isContentLine(line)) continue;
2967
+ if (!/^[ \t]/.test(line)) break;
2968
+ end = index + 1;
2969
+ }
2970
+ return end;
2971
+ }
2972
+ function providersBlock(lines) {
2973
+ const header = topLevelAt(lines, PROVIDERS);
2974
+ if (header === -1) return null;
2975
+ const inline = withoutComment(TOP_LEVEL_KEY.exec(lines[header])?.[2] ?? "");
2976
+ return { header, end: inline === "" ? blockEnd(lines, header) : header + 1, inline };
2977
+ }
2978
+ function indentOf(line) {
2979
+ return /^[ \t]*/.exec(line)?.[0] ?? "";
2980
+ }
2981
+ function childIndent(lines, block) {
2982
+ const first = block === null ? void 0 : lines.slice(block.header + 1, block.end).find(isContentLine);
2983
+ return first === void 0 ? DEFAULT_INDENT : indentOf(first);
2984
+ }
2985
+ function children(lines, block) {
2986
+ if (block.inline !== "") return [];
2987
+ const indent = childIndent(lines, block);
2988
+ const found = [];
2989
+ for (let index = block.header + 1; index < block.end; index += 1) {
2990
+ const match = NESTED_KEY.exec(lines[index]);
2991
+ if (match !== null && match[1] === indent) found.push({ name: match[2], start: index, end: index + 1 });
2992
+ else if (found.length > 0 && isContentLine(lines[index])) found[found.length - 1].end = index + 1;
2993
+ }
2994
+ return found;
2995
+ }
2996
+ function findChild(lines, name) {
2997
+ const block = providersBlock(lines);
2998
+ return block === null ? null : children(lines, block).find((child) => child.name === name) ?? null;
2999
+ }
3000
+ function modelLineOf(lines, child) {
3001
+ for (let index = child.start + 1; index < child.end; index += 1) {
3002
+ if (NESTED_KEY.exec(lines[index])?.[2] === MODEL_FIELD) return index;
3003
+ }
3004
+ return -1;
3005
+ }
3006
+ function modelOf(lines, child) {
3007
+ const at = child === null ? -1 : modelLineOf(lines, child);
3008
+ const match = at === -1 ? null : NESTED_KEY.exec(lines[at]);
3009
+ return match === null ? null : scalarValue(match[3]);
3010
+ }
3011
+ function defaultIn2(lines) {
3012
+ const provider = topLevelValue(lines, ACTIVE_PROVIDER) || topLevelValue(lines, LEGACY_PROVIDER);
3013
+ if (!provider) return null;
3014
+ return { provider, model: modelOf(lines, findChild(lines, provider)) || topLevelValue(lines, LEGACY_MODEL) || null };
3015
+ }
3016
+ function defaultLabel(value) {
3017
+ if (value === null) return null;
3018
+ return value.model === null ? value.provider : `${value.provider}${PROVIDER_SEPARATOR2}${value.model}`;
3019
+ }
3020
+ function modelLine2(indent, model) {
3021
+ return `${indent}${indent}${MODEL_FIELD}: ${yamlScalar(model)}`;
3022
+ }
3023
+ function childLines(indent, name, model) {
3024
+ return [`${indent}${name}:`, `${indent}${indent}enabled: true`, modelLine2(indent, model), `${indent}${indent}configured: true`];
3025
+ }
3026
+ function withChild(lines, name, model) {
3027
+ const block = providersBlock(lines);
3028
+ if (block === null) return [...lines, `${PROVIDERS}:`, ...childLines(DEFAULT_INDENT, name, model)];
3029
+ if (block.inline !== "") {
3030
+ if (block.inline !== EMPTY_FLOW_MAP) throw new Error(`its ${PROVIDERS} entry is not a block the setup can edit`);
3031
+ return [...lines.slice(0, block.header), `${PROVIDERS}:`, ...childLines(DEFAULT_INDENT, name, model), ...lines.slice(block.header + 1)];
3032
+ }
3033
+ const own = childLines(childIndent(lines, block), name, model);
3034
+ const existing = children(lines, block).find((child) => child.name === name);
3035
+ if (existing === void 0) return [...lines.slice(0, block.end), ...own, ...lines.slice(block.end)];
3036
+ return [...lines.slice(0, existing.start), ...own, ...lines.slice(existing.end)];
3037
+ }
3038
+ function withActiveProvider(lines, provider, position) {
3039
+ const line = `${ACTIVE_PROVIDER}: ${yamlScalar(provider)}`;
3040
+ const at = topLevelAt(lines, ACTIVE_PROVIDER);
3041
+ if (at !== -1) return [...lines.slice(0, at), line, ...lines.slice(at + 1)];
3042
+ const block = providersBlock(lines);
3043
+ const insertAt = position ?? (block === null ? lines.length : block.header);
3044
+ return [...lines.slice(0, insertAt), line, ...lines.slice(insertAt)];
3045
+ }
3046
+ function withOwnDefault(content, config) {
3047
+ const lines = withChild(withActiveProvider(splitLines(content), config.provider), config.provider, config.model);
3048
+ return `${lines.join("\n")}
3049
+ `;
3050
+ }
3051
+ function startCommand2(config) {
3052
+ return `goose session --provider ${config.provider} --model ${config.model}`;
3053
+ }
3054
+ function writeDefault(config) {
3055
+ const file = toolPaths(config).gooseConfig;
3056
+ const raw = readText(file);
3057
+ let updated;
3058
+ try {
3059
+ updated = withOwnDefault(toLf(raw ?? ""), config);
3060
+ } catch (error) {
3061
+ throw new Error(`${file}: ${error instanceof Error ? error.message : String(error)}; pick the model with "goose configure"`);
3062
+ }
3063
+ saveText(config, TOOL6, file, fromLf(updated, layoutOf(raw ?? "")), raw);
3064
+ }
3065
+ function write6(config, options) {
3066
+ const paths = toolPaths(config);
3067
+ mkdirSync8(pathsFor(config.platform).dirname(paths.gooseProvider), { recursive: true });
3068
+ const existing = readText(paths.gooseProvider);
3069
+ saveText(config, TOOL6, paths.gooseProvider, jsonText(gooseProvider(config, config.models), layoutOf(existing ?? "")), existing);
3070
+ setMode(paths.gooseProvider, 384, config.platform);
3071
+ if (options.setDefault) writeDefault(config);
3072
+ const startsOnOwnModel = currentDefault6(config)?.startsWith(`${config.provider}${PROVIDER_SEPARATOR2}`) ?? false;
3073
+ return { path: paths.gooseProvider, next: startsOnOwnModel ? "goose session" : startCommand2(config) };
3074
+ }
3075
+ function currentDefault6(config) {
3076
+ let raw;
3077
+ try {
3078
+ raw = readText(toolPaths(config).gooseConfig);
3079
+ } catch {
3080
+ return null;
3081
+ }
3082
+ return raw === null ? null : defaultLabel(defaultIn2(splitLines(toLf(raw))));
3083
+ }
3084
+ function parsedDefault(value) {
3085
+ const slash = value.indexOf(PROVIDER_SEPARATOR2);
3086
+ return slash <= 0 ? { provider: value, model: null } : { provider: value.slice(0, slash), model: value.slice(slash + 1) };
3087
+ }
3088
+ function withModelOf(lines, provider, model) {
3089
+ const child = findChild(lines, provider);
3090
+ if (child === null) return withChild(lines, provider, model);
3091
+ const line = modelLine2(childIndent(lines, providersBlock(lines)), model);
3092
+ const at = modelLineOf(lines, child);
3093
+ if (at === -1) return [...lines.slice(0, child.end), line, ...lines.slice(child.end)];
3094
+ return [...lines.slice(0, at), line, ...lines.slice(at + 1)];
3095
+ }
3096
+ function withPreviousDefault(lines, position, previousDefault) {
3097
+ if (defaultLabel(defaultIn2(lines)) === previousDefault) return [...lines];
3098
+ const previous = parsedDefault(previousDefault);
3099
+ const restored = withActiveProvider(lines, previous.provider, Math.min(position, lines.length));
3100
+ if (previous.model === null || defaultLabel(defaultIn2(restored)) === previousDefault) return restored;
3101
+ return withModelOf(restored, previous.provider, previous.model);
3102
+ }
3103
+ function withoutChild(lines, name) {
3104
+ const child = findChild(lines, name);
3105
+ if (child === null) return [...lines];
3106
+ const kept = [...lines.slice(0, child.start), ...lines.slice(child.end)];
3107
+ const block = providersBlock(kept);
3108
+ if (block === null || children(kept, block).length > 0) return kept;
3109
+ return [...kept.slice(0, block.header), ...kept.slice(block.end)];
3110
+ }
3111
+ function withoutOwnDefault(lines, provider, previousDefault) {
3112
+ const at = topLevelAt(lines, ACTIVE_PROVIDER);
3113
+ if (at === -1 || topLevelValue(lines, ACTIVE_PROVIDER) !== provider) return [...lines];
3114
+ const kept = [...lines.slice(0, at), ...lines.slice(at + 1)];
3115
+ return previousDefault === null ? kept : withPreviousDefault(kept, at, previousDefault);
3116
+ }
3117
+ function configRemoval(config, previousDefault) {
3118
+ const file = toolPaths(config).gooseConfig;
3119
+ const raw = readText(file);
3120
+ if (raw === null) return [];
3121
+ const lines = splitLines(toLf(raw));
3122
+ const kept = withoutChild(withoutOwnDefault(lines, config.provider, previousDefault), config.provider);
3123
+ if (kept.join("\n") === lines.join("\n")) return [];
3124
+ return [{ file, raw, next: kept.every(isBlankText) ? null : fromLf(`${kept.join("\n")}
3125
+ `, layoutOf(raw)) }];
3126
+ }
3127
+ function providerRemoval(config) {
3128
+ const file = toolPaths(config).gooseProvider;
3129
+ if (!isFile(file)) return [];
3130
+ try {
3131
+ readJsonDocument(file);
3132
+ } catch (error) {
3133
+ if (error instanceof SyntaxError) throw new Error(`${file} is not JSON Goose can read; fix it, then run "unrestricted remove" again`);
3134
+ throw error;
3135
+ }
3136
+ return [{ file, raw: readText(file), next: null }];
3137
+ }
3138
+ function removal6(config, previousDefault) {
3139
+ return [...providerRemoval(config), ...configRemoval(config, previousDefault)];
3140
+ }
3141
+ function remove6(config, previousDefault) {
3142
+ const changes = removal6(config, previousDefault);
3143
+ applyChanges(config, TOOL6, changes);
3144
+ return { path: changes[0]?.file ?? toolPaths(config).gooseProvider, changed: changes.length > 0 };
3145
+ }
3146
+ function hasOwnEntries6(config) {
3147
+ return removal6(config, null).length > 0;
3148
+ }
2241
3149
 
2242
3150
  // cli/src/writers/opencode.ts
2243
- import { mkdirSync as mkdirSync8, readFileSync as readFileSync9, statSync as statSync3, writeFileSync as writeFileSync10 } from "node:fs";
3151
+ var opencode_exports = {};
3152
+ __export(opencode_exports, {
3153
+ currentDefault: () => currentDefault7,
3154
+ hasOwnEntries: () => hasOwnEntries7,
3155
+ remove: () => remove7,
3156
+ write: () => write7
3157
+ });
3158
+ import { mkdirSync as mkdirSync9 } from "node:fs";
2244
3159
 
2245
3160
  // node_modules/.pnpm/jsonc-parser@3.3.1/node_modules/jsonc-parser/lib/esm/impl/scanner.js
2246
3161
  function createScanner(text, ignoreTrivia = false) {
@@ -3574,6 +4489,7 @@ var SyntaxKind;
3574
4489
  var parse2 = parse;
3575
4490
  var parseTree2 = parseTree;
3576
4491
  var findNodeAtLocation2 = findNodeAtLocation;
4492
+ var visit2 = visit;
3577
4493
  var ParseErrorCode;
3578
4494
  (function(ParseErrorCode2) {
3579
4495
  ParseErrorCode2[ParseErrorCode2["InvalidSymbol"] = 1] = "InvalidSymbol";
@@ -3626,6 +4542,19 @@ function parseJsoncObject(text) {
3626
4542
  if (errors.length > 0 || typeof value !== "object" || value === null || Array.isArray(value)) return null;
3627
4543
  return value;
3628
4544
  }
4545
+ function hasComments(text) {
4546
+ let found = false;
4547
+ visit2(
4548
+ text,
4549
+ {
4550
+ onComment: () => {
4551
+ found = true;
4552
+ }
4553
+ },
4554
+ PARSE_OPTIONS
4555
+ );
4556
+ return found;
4557
+ }
3629
4558
  function isJsonObject(value) {
3630
4559
  return typeof value === "object" && value !== null && !Array.isArray(value);
3631
4560
  }
@@ -3659,7 +4588,7 @@ function lineBreakAt(text, offset) {
3659
4588
  if (text.startsWith("\r\n", offset)) return 2;
3660
4589
  return text[offset] === "\n" ? 1 : 0;
3661
4590
  }
3662
- function removal(text, start2, end) {
4591
+ function removal7(text, start2, end) {
3663
4592
  let from = start2;
3664
4593
  while (isBlank(text[from - 1])) from -= 1;
3665
4594
  let to = end;
@@ -3677,20 +4606,22 @@ function removeJsonc(text, path) {
3677
4606
  if (property === void 0 || property.type !== "property" || siblings === void 0) return text;
3678
4607
  const end = property.offset + property.length;
3679
4608
  const following = nextToken(text, end);
3680
- if (text[following.offset] === ",") return applyEdits(text, [removal(text, property.offset, following.end)]);
4609
+ if (text[following.offset] === ",") return applyEdits(text, [removal7(text, property.offset, following.end)]);
3681
4610
  const index = siblings.indexOf(property);
3682
- if (index <= 0) return applyEdits(text, [removal(text, property.offset, end)]);
4611
+ if (index <= 0) return applyEdits(text, [removal7(text, property.offset, end)]);
3683
4612
  const previous = siblings[index - 1];
3684
4613
  const comma = nextToken(text, previous.offset + previous.length);
3685
- return applyEdits(text, [{ offset: comma.offset, length: comma.end - comma.offset, content: "" }, removal(text, property.offset, end)]);
4614
+ return applyEdits(text, [{ offset: comma.offset, length: comma.end - comma.offset, content: "" }, removal7(text, property.offset, end)]);
3686
4615
  }
3687
4616
 
3688
4617
  // cli/src/writers/opencode.ts
4618
+ var TOOL7 = "opencode";
3689
4619
  var SCHEMA = "https://opencode.ai/config.json";
3690
4620
  var PROVIDER_PACKAGE = "@ai-sdk/openai-compatible";
3691
- var PROVIDER_NAME = "unrestricted.sh";
4621
+ var PROVIDER_NAME2 = "unrestricted.sh";
3692
4622
  var OWN_PROVIDER_KEYS = ["npm", "name"];
3693
4623
  var OWN_OPTION_KEYS = ["baseURL", "apiKey"];
4624
+ var MODEL_PATH = ["model"];
3694
4625
  var FIRST = () => 0;
3695
4626
  var AFTER_SCHEMA = (keys) => keys.indexOf("$schema") + 1;
3696
4627
  function withoutOwnModels(models2, config) {
@@ -3706,34 +4637,38 @@ function ownModels(config) {
3706
4637
  function ownProvider(config) {
3707
4638
  return {
3708
4639
  npm: PROVIDER_PACKAGE,
3709
- name: PROVIDER_NAME,
4640
+ name: PROVIDER_NAME2,
3710
4641
  options: { baseURL: config.base, apiKey: config.key },
3711
4642
  models: ownModels(config)
3712
4643
  };
3713
4644
  }
3714
- function isFile3(path) {
3715
- try {
3716
- return statSync3(path).isFile();
3717
- } catch {
3718
- return false;
3719
- }
4645
+ function defaultModel(settings) {
4646
+ const model = settings.model;
4647
+ return typeof model === "string" && model !== "" ? model : null;
3720
4648
  }
3721
- function writeJson(config, file) {
3722
- const { value: settings, layout } = readJsonDocument(file);
3723
- const schema = settings.$schema;
3724
- settings.$schema = typeof schema === "string" && schema ? schema : SCHEMA;
4649
+ function hasOwnDefault(settings, owner) {
4650
+ const model = defaultModel(settings);
4651
+ return model !== null && isOwnDefault(model, owner);
4652
+ }
4653
+ function hasForeignDefault(settings, owner) {
4654
+ const model = defaultModel(settings);
4655
+ return model !== null && !isOwnDefault(model, owner);
4656
+ }
4657
+ function writeJson(config, file, options) {
4658
+ const document = readJsonDocument(file);
4659
+ const settings = document.raw === null || isBlankText(document.raw) ? { $schema: SCHEMA } : document.value;
3725
4660
  const providers = asObject(settings.provider);
3726
4661
  settings.provider = providers;
3727
4662
  const existing = asObject(providers[config.provider]);
3728
4663
  providers[config.provider] = {
3729
4664
  ...existing,
3730
4665
  npm: PROVIDER_PACKAGE,
3731
- name: PROVIDER_NAME,
4666
+ name: PROVIDER_NAME2,
3732
4667
  options: { ...asObject(existing.options), baseURL: config.base, apiKey: config.key },
3733
4668
  models: { ...withoutOwnModels(asObject(existing.models), config), ...ownModels(config) }
3734
4669
  };
3735
- settings.model = `${config.provider}/${config.model}`;
3736
- writeJsonDocument(file, settings, layout);
4670
+ if (options.setDefault) settings.model = `${config.provider}/${config.model}`;
4671
+ saveText(config, TOOL7, file, jsonText(settings, document.layout), document.raw);
3737
4672
  setMode(file, 384, config.platform);
3738
4673
  return { path: file, next: "opencode" };
3739
4674
  }
@@ -3760,7 +4695,7 @@ function withOwnProvider(text, config, settings) {
3760
4695
  const existing = providers[config.provider];
3761
4696
  if (!isJsonObject(existing)) return setJsonc(text, path, ownProvider(config), FIRST);
3762
4697
  let updated = setJsonc(text, [...path, "npm"], PROVIDER_PACKAGE, FIRST);
3763
- updated = setJsonc(updated, [...path, "name"], PROVIDER_NAME, (keys) => keys.indexOf("npm") + 1);
4698
+ updated = setJsonc(updated, [...path, "name"], PROVIDER_NAME2, (keys) => keys.indexOf("npm") + 1);
3764
4699
  if (isJsonObject(existing.options)) {
3765
4700
  updated = setJsonc(updated, [...path, "options", "baseURL"], config.base, FIRST);
3766
4701
  updated = setJsonc(updated, [...path, "options", "apiKey"], config.key, (keys) => keys.indexOf("baseURL") + 1);
@@ -3770,29 +4705,23 @@ function withOwnProvider(text, config, settings) {
3770
4705
  if (isJsonObject(existing.models)) return withOwnModels(updated, config, existing.models);
3771
4706
  return setJsonc(updated, [...path, "models"], ownModels(config), FIRST);
3772
4707
  }
3773
- function ownJsonPaths(settings, config) {
3774
- const paths = [];
3775
- const model = settings.model;
3776
- const prefix = `${config.provider}/`;
3777
- if (typeof model === "string" && model.startsWith(prefix) && isOwnModel(model.slice(prefix.length), config)) paths.push(["model"]);
4708
+ function ownProviderPaths(settings, owner) {
3778
4709
  const providers = asObject(settings.provider);
3779
- const entry = providers[config.provider];
3780
- if (!isJsonObject(entry)) return paths;
3781
- const path = ["provider", config.provider];
4710
+ const entry = providers[owner.provider];
4711
+ if (!isJsonObject(entry)) return [];
4712
+ const path = ["provider", owner.provider];
3782
4713
  const options = entry.options;
3783
4714
  const models2 = entry.models;
3784
4715
  const keptOptions = isJsonObject(options) ? Object.keys(options).filter((key) => !OWN_OPTION_KEYS.includes(key)) : [];
3785
- const keptModels = isJsonObject(models2) ? Object.keys(models2).filter((id) => !isOwnModel(id, config)) : [];
4716
+ const keptModels = isJsonObject(models2) ? Object.keys(models2).filter((id) => !isOwnModel(id, owner)) : [];
3786
4717
  const keptKeys = Object.keys(entry).filter((key) => {
3787
4718
  if (OWN_PROVIDER_KEYS.includes(key)) return false;
3788
4719
  if (key === "options" && isJsonObject(options)) return keptOptions.length > 0;
3789
4720
  if (key === "models" && isJsonObject(models2)) return keptModels.length > 0;
3790
4721
  return true;
3791
4722
  });
3792
- if (keptKeys.length === 0) {
3793
- paths.push(Object.keys(providers).length === 1 ? ["provider"] : path);
3794
- return paths;
3795
- }
4723
+ if (keptKeys.length === 0) return [Object.keys(providers).length === 1 ? ["provider"] : path];
4724
+ const paths = [];
3796
4725
  for (const key of OWN_PROVIDER_KEYS) if (key in entry) paths.push([...path, key]);
3797
4726
  if (isJsonObject(options)) {
3798
4727
  if (keptOptions.length === 0) paths.push([...path, "options"]);
@@ -3800,51 +4729,227 @@ function ownJsonPaths(settings, config) {
3800
4729
  }
3801
4730
  if (isJsonObject(models2)) {
3802
4731
  if (keptModels.length === 0) paths.push([...path, "models"]);
3803
- else for (const id of Object.keys(models2)) if (isOwnModel(id, config)) paths.push([...path, "models", id]);
4732
+ else for (const id of Object.keys(models2)) if (isOwnModel(id, owner)) paths.push([...path, "models", id]);
3804
4733
  }
3805
4734
  return paths;
3806
4735
  }
3807
- function withoutOwnJsonEntries(file, config) {
3808
- if (!isFile3(file)) return void 0;
3809
- const raw = readFileSync9(file, "utf8");
4736
+ function withoutOwnJsonEntries(config, file, options) {
4737
+ if (!isFile(file)) return void 0;
4738
+ const raw = readText(file) ?? "";
3810
4739
  const original = withoutBom(raw);
3811
- const settings = original.trim() === "" ? {} : parseJsoncObject(original);
4740
+ const settings = isBlankText(original) ? {} : parseJsoncObject(original);
3812
4741
  if (settings === null) return `${file} could not be read, so the setup left it as it is; OpenCode reads ${OPENCODE_JSONC_FILE} after it`;
4742
+ const paths = ownProviderPaths(settings, config);
4743
+ if (options.setDefault && hasOwnDefault(settings, config)) paths.push(MODEL_PATH);
3813
4744
  let updated = original;
3814
- for (const path of ownJsonPaths(settings, config)) updated = removeJsonc(updated, path);
3815
- if (updated !== original) writeFileSync10(file, withBom(updated, layoutOf(raw)));
4745
+ for (const path of paths) updated = removeJsonc(updated, path);
4746
+ saveText(config, TOOL7, file, withBom(updated, layoutOf(raw)), raw);
3816
4747
  return void 0;
3817
4748
  }
3818
- function writeJsonc(config, file, jsonFile) {
3819
- const raw = readFileSync9(file, "utf8");
4749
+ function writeJsonc(config, file, jsonFile, options) {
4750
+ const raw = readText(file) ?? "";
3820
4751
  const layout = layoutOf(raw);
3821
4752
  const original = withoutBom(raw);
3822
- const text = original.trim() === "" ? `{}${layout.eol}` : original;
4753
+ const blank = isBlankText(original);
4754
+ const text = blank ? `{}${layout.eol}` : original;
3823
4755
  const settings = parseJsoncObject(text);
3824
4756
  if (settings === null) throw new Error(`${file} is not a JSON object OpenCode can read; fix it, then run the setup again`);
3825
- const schema = settings.$schema;
3826
- let updated = typeof schema === "string" && schema ? text : setJsonc(text, ["$schema"], SCHEMA, FIRST);
4757
+ let updated = blank ? setJsonc(text, ["$schema"], SCHEMA, FIRST) : text;
3827
4758
  updated = withOwnProvider(updated, config, settings);
3828
- updated = setJsonc(updated, ["model"], `${config.provider}/${config.model}`, AFTER_SCHEMA);
3829
- const output = withBom(updated, layout);
3830
- if (output !== raw) writeFileSync10(file, output);
4759
+ if (options.setDefault) updated = setJsonc(updated, MODEL_PATH, `${config.provider}/${config.model}`, AFTER_SCHEMA);
4760
+ saveText(config, TOOL7, file, withBom(updated, layout), raw);
3831
4761
  setMode(file, 384, config.platform);
3832
- const hint = withoutOwnJsonEntries(jsonFile, config);
4762
+ const hint = withoutOwnJsonEntries(config, jsonFile, options);
3833
4763
  return hint === void 0 ? { path: file, next: "opencode" } : { path: file, next: "opencode", hint };
3834
4764
  }
3835
- function write6(config) {
4765
+ function write7(config, options) {
4766
+ const paths = toolPaths(config);
4767
+ mkdirSync9(paths.opencodeDir, { recursive: true });
4768
+ if (isFile(paths.opencodeJsonc)) return writeJsonc(config, paths.opencodeJsonc, paths.opencodeJson, options);
4769
+ return writeJson(config, paths.opencodeJson, options);
4770
+ }
4771
+ function settingsIn(file) {
4772
+ try {
4773
+ if (!isFile(file)) return null;
4774
+ const text = withoutBom(readText(file) ?? "");
4775
+ return isBlankText(text) ? {} : parseJsoncObject(text);
4776
+ } catch {
4777
+ return null;
4778
+ }
4779
+ }
4780
+ function currentDefault7(config) {
4781
+ const paths = toolPaths(config);
4782
+ for (const file of [paths.opencodeJsonc, paths.opencodeJson]) {
4783
+ const settings = settingsIn(file);
4784
+ const model = settings === null ? null : defaultModel(settings);
4785
+ if (model !== null) return model;
4786
+ }
4787
+ return null;
4788
+ }
4789
+ function readSettingsFile(file) {
4790
+ if (!isFile(file)) return null;
4791
+ const raw = readText(file) ?? "";
4792
+ const text = withoutBom(raw);
4793
+ const settings = isBlankText(text) ? {} : parseJsoncObject(text);
4794
+ if (settings === null) throw new Error(`${file} is not a JSON object OpenCode can read; fix it, then run "unrestricted remove" again`);
4795
+ return { file, raw, text, settings };
4796
+ }
4797
+ function isBareText(text) {
4798
+ const settings = parseJsoncObject(text);
4799
+ return settings !== null && isBareDocument(settings) && !hasComments(text);
4800
+ }
4801
+ function removal8(config, previousDefault) {
4802
+ const paths = toolPaths(config);
4803
+ const files = [paths.opencodeJsonc, paths.opencodeJson].map(readSettingsFile).filter((entry) => entry !== null);
4804
+ const holder = files.find((entry) => defaultModel(entry.settings) !== null);
4805
+ const restore = previousDefault !== null && holder !== void 0 && hasOwnDefault(holder.settings, config) && !files.some((entry) => hasForeignDefault(entry.settings, config));
4806
+ const changes = [];
4807
+ for (const entry of files) {
4808
+ let updated = entry.text;
4809
+ for (const path of ownProviderPaths(entry.settings, config)) updated = removeJsonc(updated, path);
4810
+ if (restore && entry === holder && previousDefault !== null) updated = setJsonc(updated, MODEL_PATH, previousDefault);
4811
+ else if (hasOwnDefault(entry.settings, config)) updated = removeJsonc(updated, MODEL_PATH);
4812
+ if (updated === entry.text) continue;
4813
+ changes.push({ file: entry.file, raw: entry.raw, next: isBareText(updated) ? null : withBom(updated, layoutOf(entry.raw)) });
4814
+ }
4815
+ return changes;
4816
+ }
4817
+ function remove7(config, previousDefault) {
4818
+ const changes = removal8(config, previousDefault);
4819
+ applyChanges(config, TOOL7, changes);
4820
+ const paths = toolPaths(config);
4821
+ const main = isFile(paths.opencodeJsonc) ? paths.opencodeJsonc : paths.opencodeJson;
4822
+ return { path: changes[0]?.file ?? main, changed: changes.length > 0 };
4823
+ }
4824
+ function hasOwnEntries7(config) {
4825
+ return removal8(config, null).length > 0;
4826
+ }
4827
+
4828
+ // cli/src/writers/openhands.ts
4829
+ var openhands_exports = {};
4830
+ __export(openhands_exports, {
4831
+ currentDefault: () => currentDefault8,
4832
+ hasOwnEntries: () => hasOwnEntries8,
4833
+ remove: () => remove8,
4834
+ write: () => write8
4835
+ });
4836
+ import { mkdirSync as mkdirSync10 } from "node:fs";
4837
+ var TOOL8 = "openhands";
4838
+ function withoutTrailingSlashes(value) {
4839
+ return value.replace(/\/+$/, "");
4840
+ }
4841
+ function ownModelId(value, owner) {
4842
+ const llm = asObject(value);
4843
+ const { model, base_url: base } = llm;
4844
+ if (typeof model !== "string" || !model.startsWith(OPENHANDS_MODEL_PREFIX) || typeof base !== "string") return null;
4845
+ const id = model.slice(OPENHANDS_MODEL_PREFIX.length);
4846
+ return isOwnModel(id, owner) && withoutTrailingSlashes(base) === withoutTrailingSlashes(owner.base) ? id : null;
4847
+ }
4848
+ function modelFor(config, id) {
4849
+ return config.models.find((model) => model.id === id) ?? { id, name: config.modelName, context: config.context, maxOutput: config.maxOutput };
4850
+ }
4851
+ function readDocument(file, command) {
4852
+ try {
4853
+ return readJsonDocument(file);
4854
+ } catch (error) {
4855
+ if (error instanceof SyntaxError) throw new Error(`${file} is not JSON OpenHands can read; fix it, then run ${command} again`);
4856
+ throw error;
4857
+ }
4858
+ }
4859
+ function withOwnLlm(settings, config, model) {
4860
+ const next = { ...settings, llm: openhandsLlm(config, model, OPENHANDS_AGENT_USAGE) };
4861
+ const condenser = settings.condenser;
4862
+ if (isJsonObject(condenser) && isJsonObject(condenser.llm)) next.condenser = { ...condenser, llm: openhandsLlm(config, model, OPENHANDS_CONDENSER_USAGE) };
4863
+ return next;
4864
+ }
4865
+ function write8(config, options) {
3836
4866
  const paths = toolPaths(config);
3837
- mkdirSync8(paths.opencodeDir, { recursive: true });
3838
- if (isFile3(paths.opencodeJsonc)) return writeJsonc(config, paths.opencodeJsonc, paths.opencodeJson);
3839
- return writeJson(config, paths.opencodeJson);
4867
+ const file = paths.openhandsSettings;
4868
+ mkdirSync10(paths.openhandsDir, { recursive: true });
4869
+ const document = readDocument(file, "the setup");
4870
+ const settings = document.value;
4871
+ const current = ownModelId(settings.llm, config);
4872
+ if (!options.setDefault && current === null) return { path: file, next: "openhands" };
4873
+ const model = modelFor(config, options.setDefault ? config.model : current ?? config.model);
4874
+ const fresh = Object.keys(settings).length === 0;
4875
+ const next = fresh ? openhandsAgent(config, model) : withOwnLlm(settings, config, model);
4876
+ saveText(config, TOOL8, file, jsonText(next, document.layout), document.raw);
4877
+ setMode(file, 384, config.platform);
4878
+ return { path: file, next: "openhands" };
4879
+ }
4880
+ function currentDefault8(config) {
4881
+ let settings;
4882
+ try {
4883
+ settings = readJsonDocument(toolPaths(config).openhandsSettings).value;
4884
+ } catch {
4885
+ return null;
4886
+ }
4887
+ const model = asObject(settings.llm).model;
4888
+ if (typeof model !== "string" || model === "") return null;
4889
+ const own = ownModelId(settings.llm, config);
4890
+ return own === null ? model : `${config.provider}/${own}`;
4891
+ }
4892
+ function earlierSettings(config, file, previousDefault) {
4893
+ for (const backup of backupsOf(config, TOOL8, file).reverse()) {
4894
+ let settings;
4895
+ try {
4896
+ settings = readJsonDocument(backup).value;
4897
+ } catch {
4898
+ continue;
4899
+ }
4900
+ if (asObject(settings.llm).model === previousDefault && ownModelId(settings.llm, config) === null) return settings;
4901
+ }
4902
+ return null;
4903
+ }
4904
+ function withEarlierLlm(settings, earlier, owner) {
4905
+ const next = { ...settings, llm: earlier.llm };
4906
+ const condenser = asObject(settings.condenser);
4907
+ if (ownModelId(condenser.llm, owner) === null) return next;
4908
+ const earlierCondenser = asObject(earlier.condenser).llm;
4909
+ next.condenser = { ...condenser, llm: isJsonObject(earlierCondenser) ? earlierCondenser : { ...asObject(earlier.llm), usage_id: OPENHANDS_CONDENSER_USAGE } };
4910
+ return next;
4911
+ }
4912
+ function withAgentCondenser(settings) {
4913
+ return { ...settings, condenser: { ...asObject(settings.condenser), llm: { ...asObject(settings.llm), usage_id: OPENHANDS_CONDENSER_USAGE } } };
4914
+ }
4915
+ function removal9(config, previousDefault) {
4916
+ const file = toolPaths(config).openhandsSettings;
4917
+ const document = readDocument(file, '"unrestricted remove"');
4918
+ if (document.raw === null) return [];
4919
+ const settings = document.value;
4920
+ const ownAgent = ownModelId(settings.llm, config) !== null;
4921
+ const ownCondenser = ownModelId(asObject(settings.condenser).llm, config) !== null;
4922
+ if (!ownAgent && !ownCondenser) return [];
4923
+ if (!ownAgent) return [{ file, raw: document.raw, next: jsonText(withAgentCondenser(settings), document.layout) }];
4924
+ const earlier = previousDefault === null ? null : earlierSettings(config, file, previousDefault);
4925
+ if (earlier === null) return [{ file, raw: document.raw, next: null }];
4926
+ return [{ file, raw: document.raw, next: jsonText(withEarlierLlm(settings, earlier, config), document.layout) }];
4927
+ }
4928
+ function remove8(config, previousDefault) {
4929
+ const changes = removal9(config, previousDefault);
4930
+ applyChanges(config, TOOL8, changes);
4931
+ return { path: toolPaths(config).openhandsSettings, changed: changes.length > 0 };
4932
+ }
4933
+ function hasOwnEntries8(config) {
4934
+ return removal9(config, null).length > 0;
3840
4935
  }
3841
4936
 
3842
4937
  // cli/src/writers/pi.ts
3843
- import { mkdirSync as mkdirSync9 } from "node:fs";
3844
- var PROVIDER_NAME2 = "unrestricted.sh";
3845
- function isOwnEntry(entry, config) {
4938
+ var pi_exports = {};
4939
+ __export(pi_exports, {
4940
+ currentDefault: () => currentDefault9,
4941
+ hasOwnEntries: () => hasOwnEntries9,
4942
+ remove: () => remove9,
4943
+ write: () => write9
4944
+ });
4945
+ import { mkdirSync as mkdirSync11 } from "node:fs";
4946
+ var TOOL9 = "pi";
4947
+ var PROVIDER_NAME3 = "unrestricted.sh";
4948
+ var OWN_PROVIDER_KEYS2 = ["name", "baseUrl", "api", "apiKey"];
4949
+ var PROVIDER_SEPARATOR3 = "/";
4950
+ function isOwnEntry(entry, owner) {
3846
4951
  const id = asObject(entry).id;
3847
- return typeof id === "string" && isOwnModel(id, config);
4952
+ return typeof id === "string" && isOwnModel(id, owner);
3848
4953
  }
3849
4954
  function withOwnModels2(existing, config) {
3850
4955
  const models2 = Array.isArray(existing) ? existing : [];
@@ -3859,7 +4964,7 @@ function withOwnProvider2(document, config) {
3859
4964
  const existing = asObject(providers[config.provider]);
3860
4965
  const provider = {
3861
4966
  ...existing,
3862
- name: PROVIDER_NAME2,
4967
+ name: PROVIDER_NAME3,
3863
4968
  baseUrl: config.base,
3864
4969
  api: PI_API,
3865
4970
  apiKey: config.key,
@@ -3867,33 +4972,423 @@ function withOwnProvider2(document, config) {
3867
4972
  };
3868
4973
  return { ...document, providers: { ...providers, [config.provider]: provider } };
3869
4974
  }
3870
- function write7(config) {
4975
+ function changeTo2(file, document, value) {
4976
+ return { file, raw: document.raw, next: isBareDocument(value) && document.raw !== null ? null : jsonText(value, document.layout) };
4977
+ }
4978
+ function write9(config, options) {
3871
4979
  const paths = toolPaths(config);
3872
- mkdirSync9(paths.piAgentDir, { recursive: true });
4980
+ mkdirSync11(paths.piAgentDir, { recursive: true });
3873
4981
  const models2 = readJsonDocument(paths.piModels);
3874
- const settings = readJsonDocument(paths.piSettings);
4982
+ const settings = options.setDefault ? readJsonDocument(paths.piSettings) : null;
4983
+ const changes = [{ file: paths.piModels, raw: models2.raw, next: jsonText(withOwnProvider2(models2.value, config), models2.layout) }];
4984
+ if (settings !== null) {
4985
+ const value = { ...settings.value, defaultProvider: config.provider, defaultModel: config.model };
4986
+ changes.push({ file: paths.piSettings, raw: settings.raw, next: jsonText(value, settings.layout) });
4987
+ }
4988
+ applyChanges(config, TOOL9, changes);
3875
4989
  setMode(paths.piModels, 384, config.platform);
3876
- writeJsonDocument(paths.piModels, withOwnProvider2(models2.value, config), models2.layout);
3877
- writeJsonDocument(
3878
- paths.piSettings,
3879
- { ...settings.value, defaultProvider: config.provider, defaultModel: config.model },
3880
- settings.layout
3881
- );
3882
4990
  return { path: paths.piModels, next: "pi" };
3883
4991
  }
4992
+ function readSettings(file) {
4993
+ try {
4994
+ return readJsonDocument(file).value;
4995
+ } catch {
4996
+ return null;
4997
+ }
4998
+ }
4999
+ function currentDefault9(config) {
5000
+ const settings = readSettings(toolPaths(config).piSettings);
5001
+ if (settings === null) return null;
5002
+ const { defaultProvider: provider, defaultModel: model } = settings;
5003
+ if (typeof model !== "string" || model === "") return null;
5004
+ return typeof provider === "string" && provider !== "" ? `${provider}${PROVIDER_SEPARATOR3}${model}` : model;
5005
+ }
5006
+ function withoutOwnProvider(document, owner) {
5007
+ const providers = document.providers;
5008
+ if (!isJsonObject(providers)) return null;
5009
+ const entry = providers[owner.provider];
5010
+ if (!isJsonObject(entry)) return null;
5011
+ const kept = {};
5012
+ for (const [key, value] of Object.entries(entry)) {
5013
+ if (OWN_PROVIDER_KEYS2.includes(key)) continue;
5014
+ if (key === "models" && Array.isArray(value)) {
5015
+ const models2 = value.filter((model) => !isOwnEntry(model, owner));
5016
+ if (models2.length > 0) kept.models = models2;
5017
+ continue;
5018
+ }
5019
+ kept[key] = value;
5020
+ }
5021
+ if (JSON.stringify(kept) === JSON.stringify(entry)) return null;
5022
+ const others = { ...providers };
5023
+ if (Object.keys(kept).length > 0) others[owner.provider] = kept;
5024
+ else delete others[owner.provider];
5025
+ const next = { ...document, providers: others };
5026
+ if (Object.keys(others).length === 0) delete next.providers;
5027
+ return next;
5028
+ }
5029
+ function withPreviousDefault2(settings, previousDefault) {
5030
+ const next = { ...settings };
5031
+ const slash = previousDefault === null ? -1 : previousDefault.indexOf(PROVIDER_SEPARATOR3);
5032
+ if (previousDefault === null) {
5033
+ delete next.defaultProvider;
5034
+ delete next.defaultModel;
5035
+ } else if (slash > 0) {
5036
+ next.defaultProvider = previousDefault.slice(0, slash);
5037
+ next.defaultModel = previousDefault.slice(slash + 1);
5038
+ } else {
5039
+ delete next.defaultProvider;
5040
+ next.defaultModel = previousDefault;
5041
+ }
5042
+ return next;
5043
+ }
5044
+ function hasOwnDefault2(settings, owner) {
5045
+ const { defaultProvider: provider, defaultModel: model } = settings;
5046
+ return provider === owner.provider && typeof model === "string" && isOwnModel(model, owner);
5047
+ }
5048
+ function readPiDocument(file) {
5049
+ try {
5050
+ return readJsonDocument(file);
5051
+ } catch (error) {
5052
+ if (error instanceof SyntaxError) throw new Error(`${file} is not JSON Pi can read; fix it, then run "unrestricted remove" again`);
5053
+ throw error;
5054
+ }
5055
+ }
5056
+ function removal10(config, previousDefault) {
5057
+ const paths = toolPaths(config);
5058
+ const changes = [];
5059
+ const models2 = readPiDocument(paths.piModels);
5060
+ const cleaned = models2.raw === null ? null : withoutOwnProvider(models2.value, config);
5061
+ if (cleaned !== null) changes.push(changeTo2(paths.piModels, models2, cleaned));
5062
+ const settings = readPiDocument(paths.piSettings);
5063
+ if (settings.raw !== null && hasOwnDefault2(settings.value, config)) {
5064
+ changes.push(changeTo2(paths.piSettings, settings, withPreviousDefault2(settings.value, previousDefault)));
5065
+ }
5066
+ return changes;
5067
+ }
5068
+ function remove9(config, previousDefault) {
5069
+ const changes = removal10(config, previousDefault);
5070
+ applyChanges(config, TOOL9, changes);
5071
+ return { path: changes[0]?.file ?? toolPaths(config).piModels, changed: changes.length > 0 };
5072
+ }
5073
+ function hasOwnEntries9(config) {
5074
+ return removal10(config, null).length > 0;
5075
+ }
5076
+
5077
+ // cli/src/writers/qwen-code.ts
5078
+ var qwen_code_exports = {};
5079
+ __export(qwen_code_exports, {
5080
+ currentDefault: () => currentDefault10,
5081
+ hasOwnEntries: () => hasOwnEntries10,
5082
+ parseSelection: () => parseSelection,
5083
+ remove: () => remove10,
5084
+ selectionLabel: () => selectionLabel,
5085
+ write: () => write10
5086
+ });
5087
+ import { mkdirSync as mkdirSync12 } from "node:fs";
5088
+ var TOOL10 = "qwen-code";
5089
+ var SETTINGS_VERSION_KEY = "$version";
5090
+ var BARE_KEYS = [SETTINGS_VERSION_KEY, "$schema"];
5091
+ var AUTH_TYPES = ["openai", "openai-responses", "qwen-oauth", "gemini", "vertex-ai", "anthropic"];
5092
+ var MODEL_PATH2 = ["model", "name"];
5093
+ var AUTH_TYPE_PATH = ["security", "auth", "selectedType"];
5094
+ var AFTER_VERSION = (keys) => keys.indexOf(SETTINGS_VERSION_KEY) + 1;
5095
+ function bucketPath(owner) {
5096
+ return ["modelProviders", owner.provider];
5097
+ }
5098
+ function protocolPath(owner) {
5099
+ return ["providerProtocol", owner.provider];
5100
+ }
5101
+ var KEY_PATH = ["env", API_KEY_ENV];
5102
+ function isOwnEntry2(entry, owner) {
5103
+ const id = asObject(entry).id;
5104
+ return typeof id === "string" && isOwnModel(id, owner);
5105
+ }
5106
+ function withOwnModels3(existing, config) {
5107
+ const models2 = Array.isArray(existing) ? existing : [];
5108
+ const firstOwn = models2.findIndex((entry) => isOwnEntry2(entry, config));
5109
+ const kept = models2.filter((entry) => !isOwnEntry2(entry, config));
5110
+ kept.splice(firstOwn === -1 ? kept.length : firstOwn, 0, ...config.models.map((model) => qwenModel(config, model)));
5111
+ return kept;
5112
+ }
5113
+ function stringAt(settings, path) {
5114
+ const value = valueAt(settings, path);
5115
+ return typeof value === "string" && value !== "" ? value : null;
5116
+ }
5117
+ function selectionIn(settings) {
5118
+ const model = stringAt(settings, MODEL_PATH2);
5119
+ return model === null ? null : { model, authType: stringAt(settings, AUTH_TYPE_PATH) };
5120
+ }
5121
+ function isOwnSelection(selection, owner) {
5122
+ return selection !== null && selection.authType === QWEN_PROTOCOL && isOwnModel(selection.model, owner);
5123
+ }
5124
+ function selectionLabel(selection) {
5125
+ return selection.authType === null ? selection.model : `${selection.model}(${selection.authType})`;
5126
+ }
5127
+ function parseSelection(value) {
5128
+ const open = value.lastIndexOf("(");
5129
+ if (open > 0 && value.endsWith(")")) {
5130
+ const authType = value.slice(open + 1, -1);
5131
+ if (AUTH_TYPES.includes(authType)) return { model: value.slice(0, open), authType };
5132
+ }
5133
+ return { model: value, authType: null };
5134
+ }
5135
+ function unreadable(file, command) {
5136
+ return new Error(`${file} is not a JSON object Qwen Code can read; fix it, then run ${command} again`);
5137
+ }
5138
+ function newSettings(config, options) {
5139
+ const settings = {
5140
+ [SETTINGS_VERSION_KEY]: QWEN_SETTINGS_VERSION,
5141
+ modelProviders: { [config.provider]: withOwnModels3([], config) },
5142
+ providerProtocol: { [config.provider]: QWEN_PROTOCOL },
5143
+ env: { [API_KEY_ENV]: config.key }
5144
+ };
5145
+ return options.setDefault ? { ...settings, security: { auth: { selectedType: QWEN_PROTOCOL } }, model: { name: config.model } } : settings;
5146
+ }
5147
+ function withOwnSettings(text, settings, config, options) {
5148
+ let updated = text;
5149
+ if (options.setDefault) {
5150
+ updated = setJsonc(updated, MODEL_PATH2, config.model, AFTER_VERSION);
5151
+ updated = setJsonc(updated, AUTH_TYPE_PATH, QWEN_PROTOCOL, AFTER_VERSION);
5152
+ }
5153
+ updated = setJsonc(updated, KEY_PATH, config.key, AFTER_VERSION);
5154
+ updated = setJsonc(updated, protocolPath(config), QWEN_PROTOCOL, AFTER_VERSION);
5155
+ return setJsonc(updated, bucketPath(config), withOwnModels3(valueAt(settings, bucketPath(config)), config), AFTER_VERSION);
5156
+ }
5157
+ function write10(config, options) {
5158
+ const paths = toolPaths(config);
5159
+ const file = paths.qwenSettings;
5160
+ mkdirSync12(paths.qwenDir, { recursive: true });
5161
+ const existing = readText(file);
5162
+ const layout = layoutOf(existing ?? "");
5163
+ const original = withoutBom(existing ?? "");
5164
+ const settings = isBlankText(original) ? null : parseJsoncObject(original);
5165
+ if (!isBlankText(original) && settings === null) throw unreadable(file, "the setup");
5166
+ const updated = settings === null ? jsonText(newSettings(config, options), layout) : withBom(withOwnSettings(original, settings, config, options), layout);
5167
+ saveText(config, TOOL10, file, updated, existing);
5168
+ setMode(file, 384, config.platform);
5169
+ return { path: file, next: "qwen" };
5170
+ }
5171
+ function settingsIn2(file) {
5172
+ try {
5173
+ if (!isFile(file)) return null;
5174
+ const text = withoutBom(readText(file) ?? "");
5175
+ return isBlankText(text) ? {} : parseJsoncObject(text);
5176
+ } catch {
5177
+ return null;
5178
+ }
5179
+ }
5180
+ function currentDefault10(config) {
5181
+ const settings = settingsIn2(toolPaths(config).qwenSettings);
5182
+ const selection = settings === null ? null : selectionIn(settings);
5183
+ if (selection === null) return null;
5184
+ return isOwnSelection(selection, config) ? `${config.provider}/${selection.model}` : selectionLabel(selection);
5185
+ }
5186
+ function readSettingsFile2(file) {
5187
+ if (!isFile(file)) return null;
5188
+ const raw = readText(file) ?? "";
5189
+ const text = withoutBom(raw);
5190
+ const settings = isBlankText(text) ? {} : parseJsoncObject(text);
5191
+ if (settings === null) throw unreadable(file, '"unrestricted remove"');
5192
+ return { file, raw, text, settings };
5193
+ }
5194
+ function removeWithEmptyParents(text, path) {
5195
+ const settings = parseJsoncObject(text) ?? {};
5196
+ let target = [...path];
5197
+ while (target.length > 1) {
5198
+ const parent = valueAt(settings, target.slice(0, -1));
5199
+ if (!isJsonObject(parent) || Object.keys(parent).length !== 1) break;
5200
+ target = target.slice(0, -1);
5201
+ }
5202
+ return removeJsonc(text, target);
5203
+ }
5204
+ function withoutOwnModels2(text, settings, owner) {
5205
+ const bucket = valueAt(settings, bucketPath(owner));
5206
+ if (!Array.isArray(bucket)) return text;
5207
+ const kept = bucket.filter((entry) => !isOwnEntry2(entry, owner));
5208
+ if (kept.length === bucket.length) return text;
5209
+ if (kept.length > 0) return setJsonc(text, bucketPath(owner), kept);
5210
+ const updated = removeWithEmptyParents(text, bucketPath(owner));
5211
+ return valueAt(settings, protocolPath(owner)) === void 0 ? updated : removeWithEmptyParents(updated, protocolPath(owner));
5212
+ }
5213
+ function withoutOwnKey(text, settings) {
5214
+ const key = valueAt(settings, KEY_PATH);
5215
+ return typeof key === "string" && key.startsWith(KEY_PREFIX) ? removeWithEmptyParents(text, KEY_PATH) : text;
5216
+ }
5217
+ function withPreviousSelection(text, previousDefault) {
5218
+ if (previousDefault === null) return removeWithEmptyParents(removeWithEmptyParents(text, MODEL_PATH2), AUTH_TYPE_PATH);
5219
+ const previous = parseSelection(previousDefault);
5220
+ const updated = setJsonc(text, MODEL_PATH2, previous.model);
5221
+ return previous.authType === null ? removeWithEmptyParents(updated, AUTH_TYPE_PATH) : setJsonc(updated, AUTH_TYPE_PATH, previous.authType);
5222
+ }
5223
+ function isBareText2(text) {
5224
+ const settings = parseJsoncObject(text);
5225
+ return settings !== null && Object.keys(settings).every((key) => BARE_KEYS.includes(key)) && !hasComments(text);
5226
+ }
5227
+ function removal11(config, previousDefault) {
5228
+ const entry = readSettingsFile2(toolPaths(config).qwenSettings);
5229
+ if (entry === null) return [];
5230
+ let updated = withoutOwnModels2(entry.text, entry.settings, config);
5231
+ updated = withoutOwnKey(updated, entry.settings);
5232
+ if (isOwnSelection(selectionIn(entry.settings), config)) updated = withPreviousSelection(updated, previousDefault);
5233
+ if (updated === entry.text) return [];
5234
+ return [{ file: entry.file, raw: entry.raw, next: isBareText2(updated) ? null : withBom(updated, layoutOf(entry.raw)) }];
5235
+ }
5236
+ function remove10(config, previousDefault) {
5237
+ const changes = removal11(config, previousDefault);
5238
+ applyChanges(config, TOOL10, changes);
5239
+ return { path: toolPaths(config).qwenSettings, changed: changes.length > 0 };
5240
+ }
5241
+ function hasOwnEntries10(config) {
5242
+ return removal11(config, null).length > 0;
5243
+ }
3884
5244
 
3885
5245
  // cli/src/writers/index.ts
3886
5246
  var writers = {
3887
- opencode: write6,
3888
- codex: write3,
3889
- aider: write,
3890
- continue: write4,
3891
- crush: write5,
3892
- pi: write7,
3893
- claude: write2
5247
+ opencode: opencode_exports,
5248
+ codex: codex_exports,
5249
+ "qwen-code": qwen_code_exports,
5250
+ goose: goose_exports,
5251
+ openhands: openhands_exports,
5252
+ aider: aider_exports,
5253
+ continue: continue_exports,
5254
+ crush: crush_exports,
5255
+ pi: pi_exports,
5256
+ claude: claude_exports
3894
5257
  };
3895
5258
 
5259
+ // cli/src/commands/remove.ts
5260
+ var REMOVE_INTRO = "unrestricted.sh remove";
5261
+ var NOTHING_TO_REMOVE = "Nothing to remove.";
5262
+ function labelList(tools) {
5263
+ const labels = tools.map((tool) => TOOL_LABELS[tool]);
5264
+ if (labels.length === 1) return labels[0];
5265
+ return `${labels.slice(0, -1).join(", ")} and ${labels[labels.length - 1]}`;
5266
+ }
5267
+ function removeQuestion(tools) {
5268
+ return `Remove unrestricted.sh from ${labelList(tools)}?`;
5269
+ }
5270
+ function removedLine(tool, path) {
5271
+ return `Removed from ${TOOL_LABELS[tool]} \xB7 ${path}`;
5272
+ }
5273
+ function nothingToRemoveLine(tool) {
5274
+ return `${TOOL_LABELS[tool]}: nothing to remove`;
5275
+ }
5276
+ function earlierAiderLine(path) {
5277
+ return `Your earlier Aider settings are in ${path}`;
5278
+ }
5279
+ function removeConfig(pending, state) {
5280
+ const ids = [...state?.models ?? [], ...state?.model ? [state.model] : []];
5281
+ return {
5282
+ platform: pending.platform,
5283
+ home: pending.home,
5284
+ configHome: pending.configHome,
5285
+ piAgentDir: pending.piAgentDir,
5286
+ gooseConfigDir: pending.gooseConfigDir,
5287
+ provider: pending.provider,
5288
+ base: pending.base,
5289
+ model: pending.model,
5290
+ models: ids.map((id) => ({ id }))
5291
+ };
5292
+ }
5293
+ function isSetUp(tool, config) {
5294
+ try {
5295
+ return writers[tool].hasOwnEntries(config) || isAutoUpdateTool(tool) && hasOwnAutoUpdateFile(tool, config);
5296
+ } catch {
5297
+ return false;
5298
+ }
5299
+ }
5300
+ function namedTools(args) {
5301
+ const tools = [];
5302
+ for (const name of args) {
5303
+ if (!isToolId(name)) M2.warn(`unknown tool: ${name} (skipped)`);
5304
+ else if (!tools.includes(name)) tools.push(name);
5305
+ }
5306
+ return tools;
5307
+ }
5308
+ function foundTools(config, state) {
5309
+ return ALL_TOOLS.filter((tool) => state?.tools.includes(tool) || isSetUp(tool, config));
5310
+ }
5311
+ async function confirmed(tools) {
5312
+ if (tools.length === 0 || !process.stdin.isTTY) return true;
5313
+ const answer = await ye({ message: removeQuestion(tools), initialValue: true });
5314
+ return !pD(answer) && answer;
5315
+ }
5316
+ function removeFrom(tool, config, previousDefault) {
5317
+ const result = writers[tool].remove(config, previousDefault);
5318
+ const file = isAutoUpdateTool(tool) ? removeAutoUpdateFile(tool, config) : null;
5319
+ return result.changed || file === null ? result : { path: file, changed: true };
5320
+ }
5321
+ function reportEarlierAider(config) {
5322
+ const earlier = newestBackup(config, "aider", toolPaths(config).aiderConfig);
5323
+ if (earlier !== null) M2.info(earlierAiderLine(earlier));
5324
+ }
5325
+ function forgetState(configHome, state, done) {
5326
+ if (state === null || state.tools.every((tool) => done.includes(tool))) return deleteState(configHome);
5327
+ forgetTools(configHome, state, done);
5328
+ return true;
5329
+ }
5330
+ function keyLines(forgotKey, configHome, origin) {
5331
+ const [removedKey, revoke] = logoutLines(true, credentialsPath(configHome), origin);
5332
+ return forgotKey ? `${removedKey}
5333
+ ${revoke}` : revoke;
5334
+ }
5335
+ async function remove11(context, args) {
5336
+ let pending;
5337
+ try {
5338
+ pending = readConfig(context.env, context.machine);
5339
+ } catch (error) {
5340
+ xe(reason(error));
5341
+ return FAILED;
5342
+ }
5343
+ const state = readState(pending.configHome);
5344
+ const config = removeConfig(pending, state);
5345
+ const everything = args.length === 0;
5346
+ Ie(REMOVE_INTRO);
5347
+ const tools = everything ? foundTools(config, state) : namedTools(args);
5348
+ if (!await confirmed(tools)) {
5349
+ xe(NOTHING_CHANGED);
5350
+ return { code: 0 };
5351
+ }
5352
+ const backupsBefore = backupsMade();
5353
+ const done = [];
5354
+ const failed = [];
5355
+ for (const tool of tools) {
5356
+ try {
5357
+ const result = removeFrom(tool, config, state?.defaults?.[tool] ?? null);
5358
+ if (result.changed) M2.success(removedLine(tool, result.path));
5359
+ else M2.info(nothingToRemoveLine(tool));
5360
+ if (tool === "aider") reportEarlierAider(config);
5361
+ done.push(tool);
5362
+ } catch (error) {
5363
+ failed.push(tool);
5364
+ M2.error(`${TOOL_LABELS[tool]} failed: ${reason(error)}`);
5365
+ }
5366
+ }
5367
+ if (backupsMade() > backupsBefore) M2.info(backupsLine(config));
5368
+ let anything = tools.length > 0;
5369
+ if (everything) {
5370
+ const forgotState = forgetState(config.configHome, state, done);
5371
+ const forgotKey = deleteCredentials(config.configHome);
5372
+ anything = anything || forgotState || forgotKey;
5373
+ if (anything) M2.info(keyLines(forgotKey, config.configHome, pending.origin));
5374
+ } else if (state !== null) {
5375
+ forgetTools(config.configHome, state, done);
5376
+ }
5377
+ Se(anything ? SETUP_OUTRO : NOTHING_TO_REMOVE);
5378
+ return { code: failed.length > 0 ? 1 : 0 };
5379
+ }
5380
+
3896
5381
  // cli/src/configure.ts
5382
+ var SWITCH_DEFAULT = { setDefault: true, kept: null };
5383
+ var KEEP_DEFAULT = { setDefault: false, kept: null };
5384
+ var START_COMMAND_TOOLS = /* @__PURE__ */ new Set(["codex", "goose"]);
5385
+ var ONE_MODEL_TOOLS = /* @__PURE__ */ new Set(["openhands"]);
5386
+ function keptDefaultMessage(current, tool, modelName) {
5387
+ const label = TOOL_LABELS[tool];
5388
+ if (ONE_MODEL_TOOLS.has(tool)) return `Kept ${current} in ${label}, which runs one model. Run the setup again and switch to use ${modelName}`;
5389
+ if (START_COMMAND_TOOLS.has(tool)) return `Kept ${current} as the default in ${label}. Start it with the command under Next to use ${modelName}`;
5390
+ return `Kept ${current} as the default in ${label}. Pick ${modelName} in its model list`;
5391
+ }
3897
5392
  function recordOf(result) {
3898
5393
  switch (result.status) {
3899
5394
  case "written":
@@ -3912,19 +5407,25 @@ function reportAutoUpdate(tool, result) {
3912
5407
  const message = autoUpdateMessage(tool, result);
3913
5408
  if (message !== null) M2[message.level](message.text);
3914
5409
  }
3915
- function configureTools(config, tools, versionOf) {
5410
+ function reportBackups(config, before) {
5411
+ if (backupsMade() > before) M2.info(backupsLine(config));
5412
+ }
5413
+ function configureTools(config, tools, choiceFor, versionOf) {
3916
5414
  const steps = [];
3917
5415
  const autoUpdate = {};
5416
+ const backupsBefore = backupsMade();
3918
5417
  for (const tool of tools) {
3919
5418
  const label = TOOL_LABELS[tool];
5419
+ const choice = choiceFor(tool);
3920
5420
  const step = Y2();
3921
5421
  step.start(`Configuring ${label}`);
3922
5422
  try {
3923
- const result = writers[tool](config);
5423
+ const result = writers[tool].write(config, { setDefault: choice.setDefault });
3924
5424
  const live = isAutoUpdateTool(tool) ? { tool, result: installAutoUpdate(tool, config, versionOf) } : null;
3925
5425
  step.stop(`Configuring ${label}`);
3926
5426
  M2.success(`${label} \xB7 ${result.path}`);
3927
5427
  if (result.hint) M2.warn(result.hint);
5428
+ if (choice.kept !== null) M2.info(keptDefaultMessage(choice.kept, tool, config.modelName));
3928
5429
  if (live !== null) {
3929
5430
  reportAutoUpdate(live.tool, live.result);
3930
5431
  const record = recordOf(live.result);
@@ -3933,13 +5434,15 @@ function configureTools(config, tools, versionOf) {
3933
5434
  steps.push(result.next);
3934
5435
  } catch (error) {
3935
5436
  step.stop(`${label} failed`);
5437
+ reportBackups(config, backupsBefore);
3936
5438
  xe(reason(error));
3937
5439
  return null;
3938
5440
  }
3939
5441
  }
5442
+ reportBackups(config, backupsBefore);
3940
5443
  return { steps, autoUpdate };
3941
5444
  }
3942
- function rememberSetup(config, tools, autoUpdate) {
5445
+ function rememberSetup(config, tools, autoUpdate, replaced) {
3943
5446
  try {
3944
5447
  const previous = readState(config.configHome);
3945
5448
  saveCredentials(config.configHome, { key: config.key, origin: config.origin });
@@ -3947,7 +5450,8 @@ function rememberSetup(config, tools, autoUpdate) {
3947
5450
  tools: mergeTools(previous?.tools ?? [], tools),
3948
5451
  models: config.models.map((model) => model.id),
3949
5452
  model: config.model,
3950
- autoUpdate: mergeAutoUpdate(previous?.autoUpdate ?? {}, autoUpdate)
5453
+ autoUpdate: mergeAutoUpdate(previous?.autoUpdate ?? {}, autoUpdate),
5454
+ defaults: { ...previous?.defaults, ...replaced }
3951
5455
  });
3952
5456
  } catch (error) {
3953
5457
  M2.warn(`could not save the setup in ${storeDir(config.configHome)}: ${reason(error)}`);
@@ -3956,12 +5460,15 @@ function rememberSetup(config, tools, autoUpdate) {
3956
5460
 
3957
5461
  // cli/src/commands/setup.ts
3958
5462
  var SETUP_TIP = 'Tip: install the CLI with "npm i -g unrestricted", then run "unrestricted update" when new models come out.';
3959
- async function chooseTools(context, detected) {
5463
+ function switchQuestion(tool, current, modelName) {
5464
+ return `${TOOL_LABELS[tool]} uses ${current} by default. Switch to ${modelName}?`;
5465
+ }
5466
+ async function chooseTools(context, config, detected) {
3960
5467
  const requested = context.env.UNRESTRICTED_TOOLS ?? "";
3961
5468
  if (requested.trim()) {
3962
5469
  const { tools, unknown, disabled } = parseTools(requested);
3963
5470
  for (const name of unknown) M2.warn(`unknown tool: ${name} (skipped)`);
3964
- for (const tool of disabled) M2.warn(`${TOOL_LABELS[tool]} is not available yet`);
5471
+ for (const tool of disabled) M2.warn(disabledToolMessage(tool, config.origin));
3965
5472
  return tools;
3966
5473
  }
3967
5474
  if (context.env.UNRESTRICTED_YES || !process.stdin.isTTY) return detected;
@@ -3977,6 +5484,31 @@ async function chooseTools(context, detected) {
3977
5484
  });
3978
5485
  return pD(answer) ? null : answer;
3979
5486
  }
5487
+ async function chooseDefaults(context, config, tools) {
5488
+ const choices = {};
5489
+ const replaced = {};
5490
+ for (const tool of tools) {
5491
+ const current = writers[tool].currentDefault(config);
5492
+ if (current === null || isOwnDefault(current, config)) {
5493
+ choices[tool] = SWITCH_DEFAULT;
5494
+ continue;
5495
+ }
5496
+ if (context.env.UNRESTRICTED_YES) {
5497
+ choices[tool] = SWITCH_DEFAULT;
5498
+ replaced[tool] = current;
5499
+ continue;
5500
+ }
5501
+ if (!process.stdin.isTTY) {
5502
+ choices[tool] = { setDefault: false, kept: current };
5503
+ continue;
5504
+ }
5505
+ const answer = await ye({ message: switchQuestion(tool, current, config.modelName), initialValue: true });
5506
+ if (pD(answer)) return null;
5507
+ choices[tool] = answer ? SWITCH_DEFAULT : KEEP_DEFAULT;
5508
+ if (answer) replaced[tool] = current;
5509
+ }
5510
+ return { choices, replaced };
5511
+ }
3980
5512
  async function setup(context) {
3981
5513
  let pending;
3982
5514
  try {
@@ -4010,18 +5542,28 @@ async function setup(context) {
4010
5542
  check.stop(keyAcceptedMessage(pending.base));
4011
5543
  const config = withListedModels({ ...pending, key }, listed);
4012
5544
  const listedIds = config.models.map((model) => model.id);
4013
- const selected = await chooseTools(context, detectTools(config, context.env));
5545
+ const selected = await chooseTools(context, config, detectTools(config, context.env));
4014
5546
  if (selected === null) {
4015
- xe("Nothing changed.");
5547
+ xe(NOTHING_CHANGED);
4016
5548
  return { code: 0, listed: listedIds };
4017
5549
  }
4018
5550
  if (selected.length === 0) {
4019
5551
  Se("Nothing to configure.");
4020
5552
  return { code: 0, listed: listedIds };
4021
5553
  }
4022
- const configured = configureTools(config, selected, versionReader(context.env, context.machine.platform));
5554
+ const defaults = await chooseDefaults(context, config, selected);
5555
+ if (defaults === null) {
5556
+ xe(NOTHING_CHANGED);
5557
+ return { code: 0, listed: listedIds };
5558
+ }
5559
+ const configured = configureTools(
5560
+ config,
5561
+ selected,
5562
+ (tool) => defaults.choices[tool] ?? SWITCH_DEFAULT,
5563
+ versionReader(context.env, context.machine.platform)
5564
+ );
4023
5565
  if (configured === null) return FAILED;
4024
- rememberSetup(config, selected, configured.autoUpdate);
5566
+ rememberSetup(config, selected, configured.autoUpdate, defaults.replaced);
4025
5567
  if (balanceNote) M2.info(balanceNote);
4026
5568
  Me(configured.steps.join("\n"), "Next");
4027
5569
  if (!context.installed) M2.info(SETUP_TIP);
@@ -4033,6 +5575,10 @@ async function setup(context) {
4033
5575
  var UPDATE_INTRO = "unrestricted.sh update";
4034
5576
  var NO_TOOLS_MESSAGE = 'No tools are set up yet. Run "unrestricted setup" to configure them.';
4035
5577
  var NO_KEY_MESSAGE2 = 'No key is stored. Run "unrestricted setup" to sign in and configure your tools.';
5578
+ var AIDER_NOT_UPDATED = 'Aider is no longer updated. Run "unrestricted remove aider" to take it out';
5579
+ function skippedToolMessage(tool) {
5580
+ return tool === "aider" ? AIDER_NOT_UPDATED : `${TOOL_LABELS[tool]} is not available yet`;
5581
+ }
4036
5582
  function selectedModel(env, pending, state, listed) {
4037
5583
  if (env.UNRESTRICTED_MODEL || state.model === null) return pending.model;
4038
5584
  return listed.some((model) => model.id === state.model) ? state.model : pending.model;
@@ -4068,11 +5614,11 @@ async function update(context) {
4068
5614
  }
4069
5615
  check.stop(keyAcceptedMessage(pending.base));
4070
5616
  const config = withListedModels({ ...pending, key, model: selectedModel(context.env, pending, state, listed) }, listed);
4071
- for (const tool of state.tools.filter((tool2) => DISABLED_TOOLS.has(tool2))) M2.warn(`${TOOL_LABELS[tool]} is not available yet`);
5617
+ for (const tool of state.tools.filter((tool2) => DISABLED_TOOLS.has(tool2))) M2.warn(skippedToolMessage(tool));
4072
5618
  const tools = state.tools.filter((tool) => !DISABLED_TOOLS.has(tool));
4073
- const configured = configureTools(config, tools, versionReader(context.env, context.machine.platform));
5619
+ const configured = configureTools(config, tools, () => KEEP_DEFAULT, versionReader(context.env, context.machine.platform));
4074
5620
  if (configured === null) return FAILED;
4075
- rememberSetup(config, tools, configured.autoUpdate);
5621
+ rememberSetup(config, tools, configured.autoUpdate, {});
4076
5622
  const added = config.models.filter((model) => !state.models.includes(model.id));
4077
5623
  if (added.length > 0) M2.info(`New: ${added.map((model) => model.name).join(", ")}`);
4078
5624
  Se(SETUP_OUTRO);
@@ -4111,14 +5657,15 @@ async function announceNewModels(context, listed) {
4111
5657
  }
4112
5658
 
4113
5659
  // cli/package.json
4114
- var version = "1.0.0";
5660
+ var version = "1.1.0";
4115
5661
 
4116
5662
  // cli/src/version.ts
4117
5663
  var VERSION = version;
4118
5664
 
4119
5665
  // cli/src/cli.ts
4120
- var COMMAND_NAMES = ["setup", "update", "models", "balance", "login", "logout"];
4121
- var COMMANDS = { setup, update, models, balance, login, logout };
5666
+ var COMMAND_NAMES = ["setup", "update", "remove", "models", "balance", "login", "logout"];
5667
+ var COMMANDS = { setup, update, remove: remove11, models, balance, login, logout };
5668
+ var TAKES_ARGUMENTS = /* @__PURE__ */ new Set(["remove"]);
4122
5669
  var HELP_FLAGS = ["-h", "--help"];
4123
5670
  var VERSION_FLAGS = ["-v", "--version"];
4124
5671
  var HELP_HINT = 'Run "unrestricted --help" for the commands.';
@@ -4136,6 +5683,7 @@ function helpText() {
4136
5683
  "Commands:",
4137
5684
  " setup Configure your coding tools (the default)",
4138
5685
  " update Rewrite the tools you set up with the current models and your stored key",
5686
+ " remove Take unrestricted.sh out of your coding tools",
4139
5687
  " models List the models with their context and prices per million tokens",
4140
5688
  " balance Show your credit, your plan's usage and this month's spend",
4141
5689
  " login Approve this machine in the browser and store its key",
@@ -4172,10 +5720,10 @@ function parseArguments(argv) {
4172
5720
  if (argv.some((argument) => VERSION_FLAGS.includes(argument))) return { kind: "version" };
4173
5721
  const option = argv.find((argument) => argument.startsWith("-"));
4174
5722
  if (option !== void 0) return { kind: "error", message: `unknown option: ${option}. ${HELP_HINT}` };
4175
- const [name = "setup", extra] = argv;
5723
+ const [name = "setup", ...args] = argv;
4176
5724
  if (!isCommandName(name)) return { kind: "error", message: `unknown command: ${name}. ${HELP_HINT}` };
4177
- if (extra !== void 0) return { kind: "error", message: `unexpected argument: ${extra}. ${HELP_HINT}` };
4178
- return { kind: "command", name };
5725
+ if (args.length > 0 && !TAKES_ARGUMENTS.has(name)) return { kind: "error", message: `unexpected argument: ${args[0]}. ${HELP_HINT}` };
5726
+ return { kind: "command", name, args };
4179
5727
  }
4180
5728
  async function runCli(argv, context) {
4181
5729
  const parsed = parseArguments(argv);
@@ -4193,7 +5741,7 @@ async function runCli(argv, context) {
4193
5741
  `);
4194
5742
  return 1;
4195
5743
  }
4196
- const result = await COMMANDS[parsed.name](context);
5744
+ const result = await COMMANDS[parsed.name](context, parsed.args);
4197
5745
  if (result.code === 0 && parsed.name !== "logout") await announceNewModels(context, result.listed);
4198
5746
  return result.code;
4199
5747
  }