theclawbay 0.3.30 → 0.3.32

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
@@ -19,13 +19,6 @@ theclawbay setup --api-key <apiKey>
19
19
  In an interactive terminal, setup shows one picker for Codex, Continue, Cline, OpenClaw, OpenCode, Kilo, Roo Code, Aider, and Windows Trae.
20
20
  Each row includes a terminal-friendly icon plus the tool's official site, and you can toggle items with arrow keys plus `Enter` or by pressing their number.
21
21
 
22
- History notes:
23
-
24
- - Continue setup only updates `~/.continue/config.yaml` and leaves `~/.continue/sessions` untouched.
25
- - Cline setup only updates `~/.cline/data/globalState.json` and `~/.cline/data/secrets.json`; task history stays in `~/.cline/state` and `~/.cline/tasks`.
26
- - Roo Code setup uses a managed auto-import file plus editor settings and does not touch task history. Imported Roo profiles can persist inside Roo after first launch.
27
- - Aider setup appends a managed block to `~/.aider.conf.yml` and enables `restore-chat-history: true` without touching `.aider.chat.history.md`.
28
-
29
22
  ## Optional
30
23
 
31
24
  `theclawbay link --api-key <apiKey>`
@@ -678,10 +678,10 @@ class LogoutCommand extends base_command_1.BaseCommand {
678
678
  this.log("- Codex login state: no cleanup needed.");
679
679
  }
680
680
  if (modelCacheCleanup.action === "removed") {
681
- this.log("- Codex model cache: removed the Claw Bay GPT-5.4 seed.");
681
+ this.log("- Codex model cache: removed the Claw Bay GPT-5.4 / GPT-5.4 Mini seed.");
682
682
  }
683
683
  else if (modelCacheCleanup.action === "preserved") {
684
- this.log("- Codex model cache: preserved an existing GPT-5.4 entry.");
684
+ this.log("- Codex model cache: preserved an existing GPT-5.4 / GPT-5.4 Mini entry.");
685
685
  }
686
686
  else if (modelCacheCleanup.warning) {
687
687
  this.log(`- Codex model cache: ${modelCacheCleanup.warning}`);
@@ -460,25 +460,51 @@ async function promptForSetupClients(clients) {
460
460
  const wasRaw = stdin.isRaw;
461
461
  const applyIndex = options.length;
462
462
  const hyperlinksEnabled = supportsTerminalHyperlinks();
463
+ const ansi = {
464
+ reset: "\x1b[0m",
465
+ bold: "\x1b[1m",
466
+ dim: "\x1b[2m",
467
+ inverse: "\x1b[7m",
468
+ red: "\x1b[31m",
469
+ green: "\x1b[32m",
470
+ yellow: "\x1b[33m",
471
+ gray: "\x1b[90m",
472
+ };
473
+ const paint = (text, ...codes) => `${codes.join("")}${text}${ansi.reset}`;
463
474
  const clearScreen = () => {
464
475
  stdout.write("\x1b[2J\x1b[H");
465
476
  };
466
477
  const selectedCount = () => options.filter((option) => option.checked).length;
467
478
  const render = () => {
468
479
  clearScreen();
469
- stdout.write("Choose local clients to configure\n");
470
- stdout.write(`Use ↑/↓ to move, Enter to toggle the highlighted integration, or press 1-${options.length} to toggle directly.\n`);
471
- stdout.write("Each tool name links to its official site when your terminal supports it.\n");
472
- stdout.write("Move to Apply setup and press Enter when you're ready.\n\n");
480
+ stdout.write(`${paint("Choose local clients to configure", ansi.bold)}\n`);
481
+ stdout.write(`${paint(`Use ↑/↓ to move, Enter to toggle the highlighted integration, or press 1-${options.length} to toggle directly.`, ansi.dim, ansi.gray)}\n`);
482
+ stdout.write(`${paint("Each tool name links to its official site when your terminal supports it.", ansi.dim, ansi.gray)}\n`);
483
+ stdout.write(`${paint("Move to Apply setup and press Enter when you're ready.", ansi.dim, ansi.gray)}\n\n`);
473
484
  for (const [index, option] of options.entries()) {
474
- const pointer = index === cursor ? ">" : " ";
475
- const mark = option.detected ? (option.checked ? "[x]" : "[ ]") : "[-]";
476
- const badge = option.detected ? (option.recommended ? "recommended" : "optional") : "not detected";
477
- stdout.write(`${pointer} ${index + 1}. ${mark} ${formatSetupClientLabel(option, hyperlinksEnabled)} ${badge}\n`);
485
+ const pointer = index === cursor ? paint(">", ansi.bold) : " ";
486
+ const mark = option.detected
487
+ ? option.checked
488
+ ? paint("[x]", ansi.green, ansi.bold)
489
+ : paint("[ ]", ansi.gray)
490
+ : paint("[-]", ansi.red, ansi.bold);
491
+ const badge = option.detected
492
+ ? option.recommended
493
+ ? paint("recommended", ansi.green)
494
+ : paint("optional", ansi.yellow)
495
+ : paint("not detected", ansi.red);
496
+ const label = option.detected
497
+ ? formatSetupClientLabel(option, hyperlinksEnabled)
498
+ : paint(formatSetupClientLabel(option, hyperlinksEnabled), ansi.dim, ansi.gray);
499
+ stdout.write(`${pointer} ${index + 1}. ${mark} ${label} ${badge}\n`);
478
500
  }
479
501
  const applyPointer = cursor === applyIndex ? ">" : " ";
480
- stdout.write(`${applyPointer} Apply setup with ${selectedCount()} selected integration${selectedCount() === 1 ? "" : "s"}\n`);
481
- stdout.write(`\n${hint}\n`);
502
+ const applyLabel = `Apply setup (${selectedCount()} selected)`;
503
+ const applyStyled = cursor === applyIndex
504
+ ? paint(` ${applyLabel} `, ansi.inverse, ansi.bold, ansi.green)
505
+ : paint(applyLabel, ansi.bold, ansi.green);
506
+ stdout.write(`\n${applyPointer} ${applyStyled}\n`);
507
+ stdout.write(`\n${paint(hint, ansi.dim, ansi.gray)}\n`);
482
508
  };
483
509
  const finish = () => {
484
510
  if (settled)
@@ -1467,19 +1493,19 @@ class SetupCommand extends base_command_1.BaseCommand {
1467
1493
  this.log(`- Conversation cache: could not update local Codex history DB.${detail}`);
1468
1494
  }
1469
1495
  if (modelCacheMigration?.action === "seeded") {
1470
- this.log("- Codex model cache: added GPT-5.4 to the local picker cache.");
1496
+ this.log("- Codex model cache: added GPT-5.4 / GPT-5.4 Mini to the local picker cache.");
1471
1497
  }
1472
1498
  else if (modelCacheMigration?.action === "created") {
1473
- this.log("- Codex model cache: created a local picker cache with GPT-5.4.");
1499
+ this.log("- Codex model cache: created a local picker cache with GPT-5.4 / GPT-5.4 Mini.");
1474
1500
  }
1475
1501
  else if (modelCacheMigration?.action === "refreshed") {
1476
- this.log("- Codex model cache: refreshed the local picker cache for GPT-5.4.");
1502
+ this.log("- Codex model cache: refreshed the local picker cache for GPT-5.4 / GPT-5.4 Mini.");
1477
1503
  }
1478
1504
  else if (modelCacheMigration?.action === "already_seeded") {
1479
- this.log("- Codex model cache: GPT-5.4 was already seeded locally.");
1505
+ this.log("- Codex model cache: GPT-5.4 / GPT-5.4 Mini were already seeded locally.");
1480
1506
  }
1481
1507
  else if (modelCacheMigration?.action === "already_present") {
1482
- this.log("- Codex model cache: GPT-5.4 already existed locally.");
1508
+ this.log("- Codex model cache: GPT-5.4 / GPT-5.4 Mini already existed locally.");
1483
1509
  }
1484
1510
  else if (modelCacheMigration?.warning) {
1485
1511
  this.log(`- Codex model cache: ${modelCacheMigration.warning}`);
@@ -14,10 +14,13 @@ const supported_models_1 = require("./supported-models");
14
14
  const MODELS_CACHE_FILE = "models_cache.json";
15
15
  const MODELS_CACHE_STATE_FILE = "theclawbay.models-cache.json";
16
16
  const STATE_DB_FILE_PATTERN = /^state_\d+\.sqlite$/;
17
- const TARGET_MODEL_ID = "gpt-5.4";
17
+ const TARGET_MODEL_IDS = ["gpt-5.4", "gpt-5.4-mini"];
18
+ const TARGET_MODEL_ID_SET = new Set(TARGET_MODEL_IDS);
18
19
  const SEED_MARKER_KEY = "_theclawbay_seeded";
19
- const SEED_MARKER_VALUE = "gpt-5.4";
20
- const TEMPLATE_MODEL_IDS = (0, supported_models_1.getSupportedModelIds)().filter((id) => id !== TARGET_MODEL_ID);
20
+ // Older releases stored the model id as the marker value; accept those so cleanup still works.
21
+ const SEED_MARKER_VALUE = "theclawbay";
22
+ const LEGACY_SEED_MARKER_VALUES = new Set(["gpt-5.4", SEED_MARKER_VALUE, true]);
23
+ const TEMPLATE_MODEL_IDS = (0, supported_models_1.getSupportedModelIds)().filter((id) => !TARGET_MODEL_ID_SET.has(id));
21
24
  const DEFAULT_REASONING_LEVELS = [
22
25
  { effort: "low", description: "Fast responses with lighter reasoning" },
23
26
  { effort: "medium", description: "Balances speed and reasoning depth for everyday tasks" },
@@ -88,29 +91,66 @@ async function removeFileIfExists(filePath) {
88
91
  throw error;
89
92
  }
90
93
  }
94
+ function normalizePatchFingerprintMap(state) {
95
+ if (!state)
96
+ return {};
97
+ if (state.version === 1) {
98
+ if (!TARGET_MODEL_ID_SET.has(state.modelId))
99
+ return {};
100
+ return state.fingerprint ? { [state.modelId]: state.fingerprint } : {};
101
+ }
102
+ const output = {};
103
+ for (const entry of state.models) {
104
+ if (!TARGET_MODEL_ID_SET.has(entry.modelId))
105
+ continue;
106
+ if (entry.fingerprint)
107
+ output[entry.modelId] = entry.fingerprint;
108
+ }
109
+ return output;
110
+ }
91
111
  async function readPatchState(statePath) {
92
112
  const parsed = await readJsonIfExists(statePath);
93
113
  const obj = objectRecordOr(parsed);
94
114
  if (!obj)
95
- return null;
96
- if (obj.version !== 1)
97
- return null;
98
- if (obj.modelId !== TARGET_MODEL_ID)
99
- return null;
100
- if (typeof obj.fingerprint !== "string" || !obj.fingerprint)
101
- return null;
102
- return {
103
- version: 1,
104
- modelId: TARGET_MODEL_ID,
105
- fingerprint: obj.fingerprint,
106
- };
115
+ return {};
116
+ const version = obj.version;
117
+ if (version === 1) {
118
+ const modelId = typeof obj.modelId === "string" ? obj.modelId : "";
119
+ const fingerprint = typeof obj.fingerprint === "string" ? obj.fingerprint : "";
120
+ if (!modelId || !fingerprint)
121
+ return {};
122
+ return normalizePatchFingerprintMap({ version: 1, modelId, fingerprint });
123
+ }
124
+ if (version === 2) {
125
+ const modelsRaw = obj.models;
126
+ if (!Array.isArray(modelsRaw))
127
+ return {};
128
+ const models = [];
129
+ for (const entry of modelsRaw) {
130
+ const record = objectRecordOr(entry);
131
+ if (!record)
132
+ continue;
133
+ const modelId = typeof record.modelId === "string" ? record.modelId : "";
134
+ const fingerprint = typeof record.fingerprint === "string" ? record.fingerprint : "";
135
+ if (!modelId || !fingerprint)
136
+ continue;
137
+ models.push({ modelId, fingerprint });
138
+ }
139
+ return normalizePatchFingerprintMap({ version: 2, models });
140
+ }
141
+ return {};
107
142
  }
108
- async function writePatchState(statePath, fingerprint) {
143
+ async function writePatchState(statePath, fingerprints) {
109
144
  await promises_1.default.mkdir(node_path_1.default.dirname(statePath), { recursive: true });
145
+ const models = TARGET_MODEL_IDS.flatMap((modelId) => {
146
+ const fingerprint = fingerprints[modelId];
147
+ if (!fingerprint)
148
+ return [];
149
+ return [{ modelId, fingerprint }];
150
+ });
110
151
  const contents = JSON.stringify({
111
- version: 1,
112
- modelId: TARGET_MODEL_ID,
113
- fingerprint,
152
+ version: 2,
153
+ models,
114
154
  }, null, 2);
115
155
  await promises_1.default.writeFile(statePath, `${contents}\n`, "utf8");
116
156
  }
@@ -118,18 +158,23 @@ function findModel(models, slug) {
118
158
  return models.find((entry) => entry.slug === slug) ?? null;
119
159
  }
120
160
  function hasSeedMarker(model) {
121
- return model[SEED_MARKER_KEY] === SEED_MARKER_VALUE;
161
+ return LEGACY_SEED_MARKER_VALUES.has(model[SEED_MARKER_KEY]);
122
162
  }
123
163
  function applySeedMarker(model) {
124
164
  const next = cloneJson(model);
125
165
  next[SEED_MARKER_KEY] = SEED_MARKER_VALUE;
126
166
  return next;
127
167
  }
128
- function normalizeSeedModel(template) {
168
+ function seedDescription(modelId) {
169
+ if (modelId === "gpt-5.4-mini")
170
+ return "Smaller GPT-5.4 variant for quick iterations.";
171
+ return "Latest frontier agentic coding model.";
172
+ }
173
+ function normalizeSeedModel(template, modelId) {
129
174
  const seed = applySeedMarker(template ?? {});
130
- seed.slug = TARGET_MODEL_ID;
131
- seed.display_name = TARGET_MODEL_ID;
132
- seed.description = "Latest frontier agentic coding model.";
175
+ seed.slug = modelId;
176
+ seed.display_name = modelId;
177
+ seed.description = seedDescription(modelId);
133
178
  if (typeof seed.default_reasoning_level !== "string") {
134
179
  seed.default_reasoning_level = "medium";
135
180
  }
@@ -182,13 +227,13 @@ function normalizeSeedModel(template) {
182
227
  }
183
228
  return seed;
184
229
  }
185
- function buildSeedModel(models) {
230
+ function buildSeedModel(models, modelId) {
186
231
  for (const candidate of TEMPLATE_MODEL_IDS) {
187
232
  const template = findModel(models, candidate);
188
233
  if (template)
189
- return normalizeSeedModel(template);
234
+ return normalizeSeedModel(template, modelId);
190
235
  }
191
- return normalizeSeedModel(null);
236
+ return normalizeSeedModel(null, modelId);
192
237
  }
193
238
  function setCacheFreshnessMetadata(doc, clientVersion) {
194
239
  let changed = false;
@@ -342,70 +387,115 @@ async function ensureCodexModelCacheHasGpt54(params) {
342
387
  await removeFileIfExists(statePath);
343
388
  return {
344
389
  action: "skipped",
345
- warning: "Codex models cache was not found, and Codex version could not be inferred for a safe GPT-5.4 seed.",
390
+ warning: "Codex models cache was not found, and Codex version could not be inferred for a safe GPT-5.4 / GPT-5.4 Mini seed.",
346
391
  };
347
392
  }
348
- const seed = buildSeedModel([]);
393
+ const docModels = [];
394
+ const nextState = {};
395
+ for (const modelId of TARGET_MODEL_IDS) {
396
+ const seed = buildSeedModel(docModels, modelId);
397
+ docModels.push(seed);
398
+ nextState[modelId] = fingerprintModel(seed);
399
+ }
349
400
  const doc = {
350
401
  fetched_at: new Date().toISOString(),
351
402
  client_version: clientVersion,
352
- models: [seed],
403
+ models: docModels,
353
404
  };
354
405
  await promises_1.default.mkdir(params.codexHome, { recursive: true });
355
406
  await promises_1.default.writeFile(cachePath, `${JSON.stringify(doc, null, 2)}\n`, "utf8");
356
- await writePatchState(statePath, fingerprintModel(seed));
407
+ await writePatchState(statePath, nextState);
357
408
  return { action: "created" };
358
409
  }
359
410
  const doc = normalizeCacheDocument(parsed);
360
411
  if (!doc) {
361
412
  return {
362
413
  action: "skipped",
363
- warning: "Codex models cache exists but is not valid JSON in the expected format; skipped GPT-5.4 seed.",
414
+ warning: "Codex models cache exists but is not valid JSON in the expected format; skipped GPT-5.4 / GPT-5.4 Mini seed.",
364
415
  };
365
416
  }
366
417
  const clientVersion = (await inferCodexClientVersion(params.codexHome)) ??
367
418
  (typeof doc.client_version === "string" && doc.client_version ? doc.client_version : null);
368
- const existingModel = findModel(doc.models, TARGET_MODEL_ID);
369
- if (existingModel) {
370
- const currentFingerprint = fingerprintModel(existingModel);
371
- if (existingState && existingState.fingerprint === currentFingerprint && !hasSeedMarker(existingModel)) {
372
- const seededModel = applySeedMarker(existingModel);
373
- doc.models = doc.models.map((entry) => (entry.slug === TARGET_MODEL_ID ? seededModel : entry));
374
- setCacheFreshnessMetadata(doc, clientVersion);
375
- await promises_1.default.writeFile(cachePath, `${JSON.stringify(doc, null, 2)}\n`, "utf8");
376
- await writePatchState(statePath, fingerprintModel(seededModel));
377
- return { action: "seeded" };
378
- }
379
- if (hasSeedMarker(existingModel)) {
380
- const docChanged = setCacheFreshnessMetadata(doc, clientVersion);
381
- if (!existingState || existingState.fingerprint !== currentFingerprint) {
382
- await writePatchState(statePath, currentFingerprint);
383
- await promises_1.default.writeFile(cachePath, `${JSON.stringify(doc, null, 2)}\n`, "utf8");
384
- return { action: "refreshed" };
419
+ let changed = false;
420
+ let seeded = false;
421
+ let stateChanged = false;
422
+ const nextState = { ...existingState };
423
+ for (const modelId of TARGET_MODEL_IDS) {
424
+ const existingModel = findModel(doc.models, modelId);
425
+ const trackedFingerprint = existingState[modelId];
426
+ if (existingModel) {
427
+ const currentFingerprint = fingerprintModel(existingModel);
428
+ if (hasSeedMarker(existingModel)) {
429
+ if (!trackedFingerprint || trackedFingerprint !== currentFingerprint) {
430
+ nextState[modelId] = currentFingerprint;
431
+ stateChanged = true;
432
+ }
433
+ continue;
385
434
  }
386
- if (docChanged) {
387
- await promises_1.default.writeFile(cachePath, `${JSON.stringify(doc, null, 2)}\n`, "utf8");
388
- return { action: "refreshed" };
435
+ if (trackedFingerprint && trackedFingerprint === currentFingerprint) {
436
+ const seededModel = applySeedMarker(existingModel);
437
+ doc.models = doc.models.map((entry) => (entry.slug === modelId ? seededModel : entry));
438
+ nextState[modelId] = fingerprintModel(seededModel);
439
+ seeded = true;
440
+ changed = true;
441
+ stateChanged = true;
442
+ continue;
389
443
  }
390
- return { action: "already_seeded" };
444
+ if (trackedFingerprint && trackedFingerprint !== currentFingerprint) {
445
+ delete nextState[modelId];
446
+ stateChanged = true;
447
+ }
448
+ continue;
391
449
  }
392
- if (existingState && existingState.fingerprint !== currentFingerprint) {
393
- await removeFileIfExists(statePath);
450
+ const seed = buildSeedModel(doc.models, modelId);
451
+ doc.models = [seed, ...doc.models];
452
+ nextState[modelId] = fingerprintModel(seed);
453
+ seeded = true;
454
+ changed = true;
455
+ stateChanged = true;
456
+ }
457
+ // Keep The Claw Bay seeds grouped at the top for easy discovery in the picker.
458
+ const bySlug = new Map();
459
+ const rest = [];
460
+ for (const entry of doc.models) {
461
+ const slug = typeof entry.slug === "string" ? entry.slug : "";
462
+ if (slug && TARGET_MODEL_ID_SET.has(slug) && !bySlug.has(slug)) {
463
+ bySlug.set(slug, entry);
464
+ continue;
394
465
  }
395
- if (setCacheFreshnessMetadata(doc, clientVersion)) {
396
- await promises_1.default.writeFile(cachePath, `${JSON.stringify(doc, null, 2)}\n`, "utf8");
397
- return { action: "refreshed" };
466
+ rest.push(entry);
467
+ }
468
+ const orderedTargets = [];
469
+ for (const id of TARGET_MODEL_IDS) {
470
+ const entry = bySlug.get(id);
471
+ if (entry)
472
+ orderedTargets.push(entry);
473
+ }
474
+ doc.models = [...orderedTargets, ...rest];
475
+ if (setCacheFreshnessMetadata(doc, clientVersion)) {
476
+ changed = true;
477
+ }
478
+ if (changed) {
479
+ await promises_1.default.writeFile(cachePath, `${JSON.stringify(doc, null, 2)}\n`, "utf8");
480
+ }
481
+ const hasAnyTracked = TARGET_MODEL_IDS.some((id) => Boolean(nextState[id]));
482
+ if (!hasAnyTracked) {
483
+ if (Object.keys(existingState).length > 0) {
484
+ await removeFileIfExists(statePath);
398
485
  }
399
- return {
400
- action: existingState && existingState.fingerprint === currentFingerprint ? "already_seeded" : "already_present",
401
- };
402
486
  }
403
- const seed = buildSeedModel(doc.models);
404
- doc.models = [seed, ...doc.models];
405
- setCacheFreshnessMetadata(doc, clientVersion);
406
- await promises_1.default.writeFile(cachePath, `${JSON.stringify(doc, null, 2)}\n`, "utf8");
407
- await writePatchState(statePath, fingerprintModel(seed));
408
- return { action: "seeded" };
487
+ else if (stateChanged || seeded) {
488
+ await writePatchState(statePath, nextState);
489
+ }
490
+ if (seeded)
491
+ return { action: "seeded" };
492
+ if (changed || stateChanged)
493
+ return { action: "refreshed" };
494
+ const allSeeded = TARGET_MODEL_IDS.every((id) => {
495
+ const model = findModel(doc.models, id);
496
+ return Boolean(model && hasSeedMarker(model));
497
+ });
498
+ return { action: allSeeded ? "already_seeded" : "already_present" };
409
499
  }
410
500
  catch (error) {
411
501
  const message = error instanceof Error ? error.message : String(error);
@@ -420,7 +510,7 @@ async function cleanupSeededCodexModelCache(params) {
420
510
  const statePath = node_path_1.default.join(params.codexHome, MODELS_CACHE_STATE_FILE);
421
511
  try {
422
512
  const state = await readPatchState(statePath);
423
- if (!state) {
513
+ if (Object.keys(state).length === 0) {
424
514
  return { action: "none" };
425
515
  }
426
516
  const parsed = await readJsonIfExists(cachePath);
@@ -432,27 +522,26 @@ async function cleanupSeededCodexModelCache(params) {
432
522
  if (!doc) {
433
523
  return {
434
524
  action: "none",
435
- warning: "Codex models cache exists but is not valid JSON in the expected format; left GPT-5.4 cache patch state untouched.",
525
+ warning: "Codex models cache exists but is not valid JSON in the expected format; left GPT-5.4 / GPT-5.4 Mini cache patch state untouched.",
436
526
  };
437
527
  }
438
- const existingModel = findModel(doc.models, TARGET_MODEL_ID);
439
- if (!existingModel) {
440
- await removeFileIfExists(statePath);
441
- return { action: "none" };
442
- }
443
- const currentFingerprint = fingerprintModel(existingModel);
444
- if (!hasSeedMarker(existingModel)) {
445
- await removeFileIfExists(statePath);
446
- return { action: "preserved" };
447
- }
448
- if (currentFingerprint !== state.fingerprint) {
449
- await writePatchState(statePath, currentFingerprint);
450
- doc.models = doc.models.filter((entry) => entry.slug !== TARGET_MODEL_ID);
451
- await promises_1.default.writeFile(cachePath, `${JSON.stringify(doc, null, 2)}\n`, "utf8");
528
+ let removed = 0;
529
+ let preserved = 0;
530
+ doc.models = doc.models.filter((entry) => {
531
+ const slug = typeof entry.slug === "string" ? entry.slug : "";
532
+ if (!slug || !TARGET_MODEL_ID_SET.has(slug))
533
+ return true;
534
+ if (!hasSeedMarker(entry)) {
535
+ preserved += 1;
536
+ return true;
537
+ }
538
+ removed += 1;
539
+ return false;
540
+ });
541
+ if (removed === 0) {
452
542
  await removeFileIfExists(statePath);
453
- return { action: "removed" };
543
+ return { action: preserved > 0 ? "preserved" : "none" };
454
544
  }
455
- doc.models = doc.models.filter((entry) => entry.slug !== TARGET_MODEL_ID);
456
545
  await promises_1.default.writeFile(cachePath, `${JSON.stringify(doc, null, 2)}\n`, "utf8");
457
546
  await removeFileIfExists(statePath);
458
547
  return { action: "removed" };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "theclawbay",
3
- "version": "0.3.30",
3
+ "version": "0.3.32",
4
4
  "description": "CLI for connecting Codex, Continue, Cline, OpenClaw, OpenCode, Kilo, Roo Code, Aider, and experimental Trae to The Claw Bay.",
5
5
  "license": "MIT",
6
6
  "bin": {
@@ -8,6 +8,15 @@
8
8
  "cachedInputPer1M": 0.25,
9
9
  "outputPer1M": 15.0
10
10
  },
11
+ {
12
+ "id": "gpt-5.4-mini",
13
+ "label": "GPT-5.4 Mini",
14
+ "note": "Smaller GPT-5.4 variant for quick iterations.",
15
+ "tone": "sea",
16
+ "inputPer1M": 1.25,
17
+ "cachedInputPer1M": 0.125,
18
+ "outputPer1M": 10.0
19
+ },
11
20
  {
12
21
  "id": "gpt-5.3-codex",
13
22
  "label": "GPT-5.3 Codex",