@sailresearch/code 0.1.1 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -16,6 +16,21 @@ persist nothing.
16
16
 
17
17
  Claude Code itself must be installed (`npm install -g @anthropic-ai/claude-code`).
18
18
 
19
+ ## Trusted workspaces (no permission prompts)
20
+
21
+ For trusted repos, containers, or VMs where you want long-running agent tasks
22
+ without clicking through Claude Code permission prompts:
23
+
24
+ ```sh
25
+ npx @sailresearch/code --trusted
26
+ ```
27
+
28
+ `--trusted` launches Claude Code in bypass-permissions mode. This avoids Claude
29
+ Code's auto-mode safety classifier path, so it also avoids failures like
30
+ `safety classifier temporarily unavailable` when Sail model requests are
31
+ otherwise healthy. Use it only where you are comfortable letting Claude Code run
32
+ tools without prompting.
33
+
19
34
  ## Persistent setup / VS Code
20
35
 
21
36
  To make `claude` route to Sail without the launcher (for VS Code, or a bare
@@ -49,18 +64,25 @@ Notes:
49
64
  ## Choosing models
50
65
 
51
66
  ```sh
52
- npx @sailresearch/code --model zai-org/GLM-5.2-FP8 --background-model openai/gpt-oss-120b
67
+ npx @sailresearch/code --model moonshotai/Kimi-K2.6
53
68
  ```
54
69
 
55
- Both tiers default to GLM-5.2. `--background-model` moves Claude Code's
56
- haiku/background tier (titles, summaries) to a cheaper model from the
57
- [Sail catalog](https://docs.sailresearch.com/models).
70
+ GLM-5.2 is the default. The `/model` picker also exposes Kimi-K2.6 via Sail.
71
+ Use `--model` with any model from the [Sail catalog](https://docs.sailresearch.com/models)
72
+ to launch directly into that model.
73
+
74
+ The launcher and `setup` write Claude Code's `availableModels` setting so fresh
75
+ local sessions show only Sail-backed picker rows. Claude Code concatenates
76
+ non-managed `availableModels` from user, project, local, and temporary settings,
77
+ though, so rows from another settings scope may still appear. Rows that name
78
+ non-Sail models (for example `claude-*` IDs) still route to Sail and will not be
79
+ served there; remove them from the other settings file or enforce the picker
80
+ with managed settings. `sail-code` prints a warning when it can see those extra
81
+ rows.
58
82
 
59
83
  The launcher does **not** force a completion window: Claude Code has no flag
60
84
  for adding request metadata, so requests go out without `completion_window` and
61
- Sail's API default applies. For GLM-5.2 that is the **standard** tier
62
- (`$0.50` input / `$2.50` output per 1M tokens) because standard pricing is
63
- available. See [Completion windows](https://docs.sailresearch.com/completion-windows)
85
+ Sail's API default applies. See [Completion windows](https://docs.sailresearch.com/completion-windows)
64
86
  for the latency/price tradeoff and how to opt into another tier.
65
87
 
66
88
  ## Commit attribution
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sailresearch/code",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "Run Claude Code on GLM-5.2 via Sail — one command, no Anthropic account.",
5
5
  "license": "MIT",
6
6
  "author": "Sail Research",
package/src/cli.js CHANGED
@@ -15,6 +15,12 @@ import { resolveCredentials, run } from "./run.js";
15
15
  import { revert, setup } from "./setup.js";
16
16
 
17
17
  const require = createRequire(import.meta.url);
18
+ const TRUSTED_CLAUDE_ARGS = ["--permission-mode", "bypassPermissions"];
19
+ const CLAUDE_PERMISSION_FLAGS = [
20
+ "--permission-mode",
21
+ "--dangerously-skip-permissions",
22
+ "--allow-dangerously-skip-permissions",
23
+ ];
18
24
 
19
25
  const HELP = `sail-code — run Claude Code on GLM-5.2 via Sail (no Anthropic account)
20
26
 
@@ -29,6 +35,7 @@ Flags:
29
35
  --model <id> Main model (default ${DEFAULT_MODEL})
30
36
  --background-model <id> Background/haiku-tier model (default: same as --model)
31
37
  --mode <name> Sail environment: ${MODE_VALUES.join(" | ")}
38
+ --trusted Skip Claude Code permission prompts in trusted workspaces
32
39
  -h, --help Show this help
33
40
  -v, --version Show the version
34
41
 
@@ -44,6 +51,7 @@ export function parseArgs(argv) {
44
51
  modeFlag: undefined,
45
52
  scope: undefined,
46
53
  revert: false,
54
+ trusted: false,
47
55
  help: false,
48
56
  version: false,
49
57
  claudeArgs: [],
@@ -85,6 +93,7 @@ export function parseArgs(argv) {
85
93
  } else if (arg === "--user") parsed.scope = "user";
86
94
  else if (arg === "--project") parsed.scope = "project";
87
95
  else if (arg === "--revert") parsed.revert = true;
96
+ else if (arg === "--trusted") parsed.trusted = true;
88
97
  else if (arg === "-h" || arg === "--help") parsed.help = true;
89
98
  else if (arg === "-v" || arg === "--version") parsed.version = true;
90
99
  else
@@ -99,6 +108,22 @@ export function parseArgs(argv) {
99
108
  if (parsed.scope && parsed.command !== "setup") {
100
109
  throw new Error(`--${parsed.scope} only applies to setup`);
101
110
  }
111
+ if (parsed.trusted && parsed.command !== "run") {
112
+ throw new Error("--trusted only applies when launching claude");
113
+ }
114
+ if (parsed.trusted) {
115
+ const conflict = parsed.claudeArgs.find((arg) =>
116
+ CLAUDE_PERMISSION_FLAGS.some(
117
+ (flag) => arg === flag || arg.startsWith(`${flag}=`),
118
+ ),
119
+ );
120
+ if (conflict) {
121
+ throw new Error(
122
+ `--trusted conflicts with Claude permission flag "${conflict}" after --`,
123
+ );
124
+ }
125
+ parsed.claudeArgs = [...TRUSTED_CLAUDE_ARGS, ...parsed.claudeArgs];
126
+ }
102
127
  return parsed;
103
128
  }
104
129
 
@@ -159,6 +184,7 @@ export async function main() {
159
184
  modeFlag: args.modeFlag,
160
185
  model: args.model,
161
186
  backgroundModel: args.backgroundModel,
187
+ trusted: args.trusted,
162
188
  claudeArgs: args.claudeArgs,
163
189
  });
164
190
  }
package/src/constants.js CHANGED
@@ -8,6 +8,7 @@
8
8
  */
9
9
 
10
10
  export const DEFAULT_MODEL = "zai-org/GLM-5.2-FP8";
11
+ export const KIMI_MODEL = "moonshotai/Kimi-K2.6";
11
12
 
12
13
  /** Public API targets by mode name, mirroring the Sail CLI's mode map. */
13
14
  export const MODE_API_URLS = {
@@ -58,6 +59,53 @@ export const DEFAULT_MODEL_CAPABILITIES = "effort,thinking";
58
59
  export const DEFAULT_MODEL_NAME = "GLM-5.2 via Sail";
59
60
  export const DEFAULT_MODEL_DESCRIPTION =
60
61
  "Sail standard pricing: $0.50 input / $2.50 output per 1M tokens";
62
+ export const KIMI_MODEL_NAME = "Kimi-K2.6 via Sail";
63
+ export const KIMI_MODEL_DESCRIPTION =
64
+ "Sail pricing: priority $0.45 input / $3.00 output, ASAP $1.00 input / $4.00 output per 1M tokens";
65
+ export const KIMI_MODEL_CAPABILITIES = "effort,thinking,xhigh_effort";
66
+
67
+ export function buildModelPickerSettings(
68
+ mainModel = DEFAULT_MODEL,
69
+ backgroundModel = mainModel,
70
+ ) {
71
+ return {
72
+ availableModels: [
73
+ ...new Set([
74
+ DEFAULT_MODEL,
75
+ mainModel || DEFAULT_MODEL,
76
+ backgroundModel || mainModel || DEFAULT_MODEL,
77
+ KIMI_MODEL,
78
+ ]),
79
+ ],
80
+ enforceAvailableModels: true,
81
+ };
82
+ }
83
+
84
+ export function extraAvailableModels(settings, mainModel, backgroundModel) {
85
+ if (!Array.isArray(settings?.availableModels)) return [];
86
+ const allowed = new Set(
87
+ buildModelPickerSettings(mainModel, backgroundModel).availableModels,
88
+ );
89
+ return [
90
+ ...new Set(
91
+ settings.availableModels.filter(
92
+ (model) => typeof model === "string" && !allowed.has(model),
93
+ ),
94
+ ),
95
+ ];
96
+ }
97
+
98
+ export function formatExtraAvailableModelsWarning(source, extras) {
99
+ if (extras.length === 0) return null;
100
+ return (
101
+ `Warning: ${source} already allows model(s) outside Sail's picker list: ` +
102
+ `${extras.join(", ")}. Claude Code concatenates non-managed ` +
103
+ "`availableModels` across user/project/local settings, so those rows " +
104
+ "may remain selectable. Rows that name non-Sail models (for example " +
105
+ "`claude-*` IDs) still route to Sail and will not be served there. Remove " +
106
+ "them from that settings file or enforce the picker with managed settings."
107
+ );
108
+ }
61
109
 
62
110
  export function buildClaudeEnv({ apiUrl, apiKey, model, backgroundModel }) {
63
111
  const base = String(apiUrl ?? "").replace(/\/+$/, "");
@@ -97,6 +145,14 @@ export function buildClaudeEnv({ apiUrl, apiKey, model, backgroundModel }) {
97
145
  ANTHROPIC_DEFAULT_SONNET_MODEL: main,
98
146
  ANTHROPIC_DEFAULT_HAIKU_MODEL: background,
99
147
  ANTHROPIC_DEFAULT_FABLE_MODEL: main,
148
+ // Add a single explicit Kimi row to the /model picker. Claude Code supports
149
+ // only one custom option this way; keep aliases pinned separately above so
150
+ // built-in Claude IDs cannot leak through to Sail.
151
+ ANTHROPIC_CUSTOM_MODEL_OPTION: KIMI_MODEL,
152
+ ANTHROPIC_CUSTOM_MODEL_OPTION_NAME: KIMI_MODEL_NAME,
153
+ ANTHROPIC_CUSTOM_MODEL_OPTION_DESCRIPTION: KIMI_MODEL_DESCRIPTION,
154
+ ANTHROPIC_CUSTOM_MODEL_OPTION_SUPPORTED_CAPABILITIES:
155
+ KIMI_MODEL_CAPABILITIES,
100
156
  // Declare effort/thinking and friendly display metadata for aliases pinned
101
157
  // to the default GLM model (see DEFAULT_MODEL_CAPABILITIES / _NAME /
102
158
  // _DESCRIPTION). Custom overrides get no declaration.
package/src/run.js CHANGED
@@ -14,6 +14,9 @@ import {
14
14
  HEADER_INJECTION_VARS,
15
15
  MODEL_ALIAS_COMPANION_VARS,
16
16
  buildClaudeEnv,
17
+ buildModelPickerSettings,
18
+ extraAvailableModels,
19
+ formatExtraAvailableModelsWarning,
17
20
  } from "./constants.js";
18
21
  import {
19
22
  loadAuthKey,
@@ -203,6 +206,72 @@ export function buildSettingsEnv(sailEnv) {
203
206
  return settingsEnv;
204
207
  }
205
208
 
209
+ export function buildRuntimeSettings(sailEnv) {
210
+ return {
211
+ env: buildSettingsEnv(sailEnv),
212
+ ...buildModelPickerSettings(
213
+ sailEnv.ANTHROPIC_MODEL,
214
+ sailEnv.ANTHROPIC_DEFAULT_HAIKU_MODEL,
215
+ ),
216
+ };
217
+ }
218
+
219
+ function userClaudeSettingsPath() {
220
+ const configDir = process.env.CLAUDE_CONFIG_DIR?.trim();
221
+ return path.join(
222
+ configDir || path.join(os.homedir(), ".claude"),
223
+ "settings.json",
224
+ );
225
+ }
226
+
227
+ function projectClaudeSettingsPaths() {
228
+ const claudeDir = path.join(process.cwd(), ".claude");
229
+ return [
230
+ path.join(claudeDir, "settings.json"),
231
+ path.join(claudeDir, "settings.local.json"),
232
+ ];
233
+ }
234
+
235
+ function readJSONFileBestEffort(filePath) {
236
+ try {
237
+ const text = fs.readFileSync(filePath, "utf8").replace(/^\uFEFF/, "");
238
+ const parsed = JSON.parse(text);
239
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
240
+ return parsed;
241
+ }
242
+ } catch {
243
+ // Runtime picker warnings are advisory; malformed or missing settings files
244
+ // should not block launching Claude Code.
245
+ }
246
+ return null;
247
+ }
248
+
249
+ export function lowerScopeAvailableModelWarnings(sailEnv, paths) {
250
+ const mainModel = sailEnv.ANTHROPIC_MODEL;
251
+ const backgroundModel = sailEnv.ANTHROPIC_DEFAULT_HAIKU_MODEL;
252
+ return paths
253
+ .map((filePath) => {
254
+ const settings = readJSONFileBestEffort(filePath);
255
+ if (!settings) return null;
256
+ const extras = extraAvailableModels(settings, mainModel, backgroundModel);
257
+ return formatExtraAvailableModelsWarning(filePath, extras);
258
+ })
259
+ .filter(Boolean);
260
+ }
261
+
262
+ export function runtimeAvailableModelWarnings(sailEnv) {
263
+ return lowerScopeAvailableModelWarnings(sailEnv, [
264
+ userClaudeSettingsPath(),
265
+ ...projectClaudeSettingsPaths(),
266
+ ]);
267
+ }
268
+
269
+ function warnRuntimeAvailableModels(sailEnv) {
270
+ for (const warning of runtimeAvailableModelWarnings(sailEnv)) {
271
+ console.error(warning);
272
+ }
273
+ }
274
+
206
275
  /**
207
276
  * Spawn `claude` with the Sail env overlaid; resolves to its exit code.
208
277
  *
@@ -226,6 +295,7 @@ export async function runClaude({ env, claudeArgs, spawn = spawnClaude }) {
226
295
  "override it.",
227
296
  );
228
297
  } else {
298
+ warnRuntimeAvailableModels(env);
229
299
  settingsFile = path.join(
230
300
  os.tmpdir(),
231
301
  `sail-code-${crypto.randomBytes(8).toString("hex")}.json`,
@@ -233,7 +303,7 @@ export async function runClaude({ env, claudeArgs, spawn = spawnClaude }) {
233
303
  try {
234
304
  fs.writeFileSync(
235
305
  settingsFile,
236
- JSON.stringify({ env: buildSettingsEnv(env) }, null, 2),
306
+ JSON.stringify(buildRuntimeSettings(env), null, 2),
237
307
  { mode: 0o600 },
238
308
  );
239
309
  } catch (err) {
@@ -317,9 +387,20 @@ export async function runClaude({ env, claudeArgs, spawn = spawnClaude }) {
317
387
  }
318
388
 
319
389
  /** The `run` command. */
320
- export async function run({ modeFlag, model, backgroundModel, claudeArgs }) {
390
+ export async function run({
391
+ modeFlag,
392
+ model,
393
+ backgroundModel,
394
+ trusted,
395
+ claudeArgs,
396
+ }) {
321
397
  const { apiKey, apiUrl } = await resolveCredentials({ modeFlag });
322
398
  const env = buildClaudeEnv({ apiUrl, apiKey, model, backgroundModel });
399
+ if (trusted) {
400
+ console.error(
401
+ "Trusted mode: skipping Claude Code permission prompts. Use only in trusted workspaces.",
402
+ );
403
+ }
323
404
  console.error(
324
405
  `Running Claude Code on ${env.ANTHROPIC_MODEL} via Sail (${apiUrl}) — ` +
325
406
  "no Anthropic account used.",
package/src/setup.js CHANGED
@@ -23,6 +23,9 @@ import {
23
23
  MODEL_ALIAS_COMPANION_VARS,
24
24
  OWNED_ENV_KEYS,
25
25
  buildClaudeEnv,
26
+ buildModelPickerSettings,
27
+ extraAvailableModels,
28
+ formatExtraAvailableModelsWarning,
26
29
  } from "./constants.js";
27
30
  import { atomicWrite } from "./credentials.js";
28
31
  import { resolveCredentials } from "./run.js";
@@ -60,6 +63,7 @@ export const SAIL_PR_ATTRIBUTION = "";
60
63
  * to disable PR attribution) and indistinguishable from what we write.
61
64
  */
62
65
  export const OWNED_ATTRIBUTION_KEYS = ["commit", "pr"];
66
+ export const MANAGED_MODEL_PICKER_ENV_KEY = "SAIL_CODE_MANAGED_MODEL_PICKER";
63
67
 
64
68
  /**
65
69
  * Merge the Sail commit attribution into a settings object's top-level
@@ -77,6 +81,129 @@ export function mergeAttribution(settings, mainModel) {
77
81
  };
78
82
  }
79
83
 
84
+ export function mergeModelPickerSettings(settings, mainModel, backgroundModel) {
85
+ const pickerSettings = buildModelPickerSettings(mainModel, backgroundModel);
86
+ Object.assign(settings, pickerSettings);
87
+ // Record the exact non-secret picker list setup wrote. If the byte backup is
88
+ // lost, fallback revert can then strip custom-model picker settings without
89
+ // guessing whether an arbitrary row was Sail-generated or user-managed.
90
+ settings.env = {
91
+ ...(settings.env ?? {}),
92
+ [MANAGED_MODEL_PICKER_ENV_KEY]: JSON.stringify(pickerSettings),
93
+ };
94
+ }
95
+
96
+ function sameStringArray(a, b) {
97
+ return (
98
+ Array.isArray(a) &&
99
+ Array.isArray(b) &&
100
+ a.length === b.length &&
101
+ a.every((value, i) => value === b[i])
102
+ );
103
+ }
104
+
105
+ function sameModelPickerSettings(settings, pickerSettings) {
106
+ return (
107
+ pickerSettings?.enforceAvailableModels === true &&
108
+ settings?.enforceAvailableModels === true &&
109
+ sameStringArray(settings.availableModels, pickerSettings.availableModels)
110
+ );
111
+ }
112
+
113
+ function managedModelPickerSettings(settings) {
114
+ const encoded = settings?.env?.[MANAGED_MODEL_PICKER_ENV_KEY];
115
+ if (typeof encoded !== "string") return null;
116
+ try {
117
+ const parsed = JSON.parse(encoded);
118
+ if (
119
+ parsed &&
120
+ typeof parsed === "object" &&
121
+ !Array.isArray(parsed) &&
122
+ parsed.enforceAvailableModels === true &&
123
+ Array.isArray(parsed.availableModels) &&
124
+ parsed.availableModels.every((model) => typeof model === "string")
125
+ ) {
126
+ return parsed;
127
+ }
128
+ } catch {
129
+ // Malformed marker: ignore it and fall back to the exact default matcher.
130
+ }
131
+ return null;
132
+ }
133
+
134
+ export function isSailGeneratedModelPickerSettings(settings) {
135
+ const managedPicker = managedModelPickerSettings(settings);
136
+ if (managedPicker) {
137
+ return sameModelPickerSettings(settings, managedPicker);
138
+ }
139
+ return sameModelPickerSettings(
140
+ settings,
141
+ buildModelPickerSettings(DEFAULT_MODEL),
142
+ );
143
+ }
144
+
145
+ function readSettingsIfPresent(filePath) {
146
+ try {
147
+ return readSettings(filePath);
148
+ } catch {
149
+ // Best-effort sibling-scope inspection for warnings only. The target
150
+ // settings file is parsed strictly by setup(); a sibling project
151
+ // settings.json should not block writing the local Sail file.
152
+ return { settings: {}, existed: false };
153
+ }
154
+ }
155
+
156
+ function projectSharedSettingsPath(localSettingsPath) {
157
+ return path.join(path.dirname(localSettingsPath), "settings.json");
158
+ }
159
+
160
+ function currentProjectSettingsPaths() {
161
+ const claudeDir = path.join(process.cwd(), ".claude");
162
+ return [
163
+ path.join(claudeDir, "settings.json"),
164
+ path.join(claudeDir, "settings.local.json"),
165
+ ];
166
+ }
167
+
168
+ export function extraAvailableModelWarningSources({
169
+ scope,
170
+ filePath,
171
+ settings,
172
+ mainModel,
173
+ backgroundModel,
174
+ }) {
175
+ const background = backgroundModel || mainModel;
176
+ const seenPaths = new Set();
177
+ const sources = [];
178
+
179
+ const addSource = (source, sourceSettings) => {
180
+ const resolved = path.resolve(source);
181
+ if (seenPaths.has(resolved)) return;
182
+ seenPaths.add(resolved);
183
+ const extras = extraAvailableModels(sourceSettings, mainModel, background);
184
+ if (extras.length > 0) sources.push({ source, extras });
185
+ };
186
+
187
+ addSource(filePath, settings);
188
+
189
+ const extraPaths =
190
+ scope === "project"
191
+ ? [projectSharedSettingsPath(filePath)]
192
+ : currentProjectSettingsPaths();
193
+
194
+ for (const source of extraPaths) {
195
+ const { settings: sourceSettings, existed } = readSettingsIfPresent(source);
196
+ if (existed) addSource(source, sourceSettings);
197
+ }
198
+
199
+ return sources;
200
+ }
201
+
202
+ function warnExtraAvailableModels(source, extras) {
203
+ const warning = formatExtraAvailableModelsWarning(source, extras);
204
+ if (warning) console.error(warning);
205
+ }
206
+
80
207
  /**
81
208
  * Backup sentinel recorded when the settings file did NOT exist before the
82
209
  * first `setup`. A re-run then finds a backup already present (so it won't
@@ -393,6 +520,13 @@ export async function setup({
393
520
  const mainModel = model || DEFAULT_MODEL;
394
521
 
395
522
  const { settings, existed } = readSettings(filePath);
523
+ const extraModelWarningSources = extraAvailableModelWarningSources({
524
+ scope,
525
+ filePath,
526
+ settings,
527
+ mainModel,
528
+ backgroundModel: backgroundModel || mainModel,
529
+ });
396
530
  // Create the settings dir before writing the backup/sentinel. On a fresh
397
531
  // machine (no ~/.claude, or a brand-new CLAUDE_CONFIG_DIR) the sentinel write
398
532
  // below would otherwise ENOENT — after the key was already minted — since
@@ -425,6 +559,7 @@ export async function setup({
425
559
 
426
560
  const removedKeys = mergeSailEnv(settings, env, { scope });
427
561
  mergeAttribution(settings, mainModel);
562
+ mergeModelPickerSettings(settings, mainModel, backgroundModel || mainModel);
428
563
  writeSettings(filePath, settings, { containsSecret: true });
429
564
 
430
565
  if (removedKeys.length > 0) {
@@ -452,6 +587,9 @@ export async function setup({
452
587
  "them in your shell profile.",
453
588
  );
454
589
  }
590
+ for (const { source, extras } of extraModelWarningSources) {
591
+ warnExtraAvailableModels(source, extras);
592
+ }
455
593
 
456
594
  const revertCmd =
457
595
  "sail-code setup --revert" + (scope === "project" ? " --project" : "");
@@ -522,6 +660,7 @@ export function revert({ scope = "user" }) {
522
660
  console.error(`Nothing to revert: ${filePath} does not exist.`);
523
661
  return 0;
524
662
  }
663
+ const shouldStripModelPicker = isSailGeneratedModelPickerSettings(settings);
525
664
  if (settings.env && typeof settings.env === "object") {
526
665
  for (const key of OWNED_ENV_KEYS) delete settings.env[key];
527
666
  // Clear the falsy overrides a project-scope setup wrote, but only when
@@ -532,6 +671,7 @@ export function revert({ scope = "user" }) {
532
671
  ]) {
533
672
  if (settings.env[key] === "") delete settings.env[key];
534
673
  }
674
+ delete settings.env[MANAGED_MODEL_PICKER_ENV_KEY];
535
675
  if (Object.keys(settings.env).length === 0) delete settings.env;
536
676
  }
537
677
  // Remove the Sail commit attribution, but only where the value still matches
@@ -561,6 +701,10 @@ export function revert({ scope = "user" }) {
561
701
  delete settings.attribution;
562
702
  }
563
703
  }
704
+ if (shouldStripModelPicker) {
705
+ delete settings.availableModels;
706
+ delete settings.enforceAvailableModels;
707
+ }
564
708
  writeSettings(filePath, settings);
565
709
  console.error(
566
710
  `Removed Sail routing keys from ${filePath} (no backup was present).`,