theclawbay 0.3.67 → 0.3.69

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.
@@ -1548,10 +1548,10 @@ class LogoutCommand extends base_command_1.BaseCommand {
1548
1548
  this.log("- Codex login state: no cleanup needed.");
1549
1549
  }
1550
1550
  if (modelCacheCleanup.action === "removed") {
1551
- this.log("- Codex model cache: removed the Claw Bay GPT-5.4 / GPT-5.4 Mini seed.");
1551
+ this.log("- Codex model cache: removed the Claw Bay managed model catalog.");
1552
1552
  }
1553
1553
  else if (modelCacheCleanup.action === "preserved") {
1554
- this.log("- Codex model cache: preserved an existing GPT-5.4 / GPT-5.4 Mini entry.");
1554
+ this.log("- Codex model cache: preserved an existing model catalog entry.");
1555
1555
  }
1556
1556
  else if (modelCacheCleanup.warning) {
1557
1557
  this.log(`- Codex model cache: ${modelCacheCleanup.warning}`);
@@ -28,7 +28,9 @@ const DEFAULT_PROVIDER_ID = "theclawbay";
28
28
  const CLI_HTTP_USER_AGENT = "theclawbay-cli";
29
29
  const SUPPORTED_MODEL_IDS = (0, supported_models_1.getSupportedModelIds)();
30
30
  const MODEL_DISPLAY_NAMES = (0, supported_models_1.getSupportedModelDisplayNames)();
31
- const DEFAULT_CODEX_MODEL = SUPPORTED_MODEL_IDS.includes("gpt-5.4") ? "gpt-5.4" : (SUPPORTED_MODEL_IDS[0] ?? "gpt-5.4");
31
+ const DEFAULT_CODEX_MODEL = (["gpt-5.5", "gpt-5.4"].find((modelId) => SUPPORTED_MODEL_IDS.includes(modelId)) ??
32
+ SUPPORTED_MODEL_IDS[0] ??
33
+ "gpt-5.4");
32
34
  const DEFAULT_CONTINUE_MODEL = DEFAULT_CODEX_MODEL;
33
35
  const DEFAULT_CLINE_MODEL = DEFAULT_CODEX_MODEL;
34
36
  const DEFAULT_OPENCLAW_MODEL = DEFAULT_CODEX_MODEL;
@@ -3245,6 +3247,9 @@ class SetupCommand extends base_command_1.BaseCommand {
3245
3247
  if (desktopModelPickerMigration?.action === "patched") {
3246
3248
  summaryNotes.add("Desktop model pickers were refreshed so current VS Code and Codex app builds stop labeling supported The Claw Bay models like GPT-5.5 as Custom.");
3247
3249
  }
3250
+ if (selectedSetupClients.has("codex")) {
3251
+ summaryNotes.add("If Codex updates later, rerun `theclawbay setup` once to refresh its local model catalog.");
3252
+ }
3248
3253
  if (selectedSetupClients.has("codex") && !migrateCodexConversations) {
3249
3254
  summaryNotes.add("Left existing Codex conversations untouched.");
3250
3255
  }
@@ -9,8 +9,8 @@ const promises_1 = __importDefault(require("node:fs/promises"));
9
9
  const node_os_1 = __importDefault(require("node:os"));
10
10
  const node_path_1 = __importDefault(require("node:path"));
11
11
  const paths_1 = require("./config/paths");
12
- const supported_models_1 = require("./supported-models");
13
- const DESKTOP_MODEL_IDS = Array.from(new Set((0, supported_models_1.getSupportedModelIds)()));
12
+ const codex_model_catalog_1 = require("./codex-model-catalog");
13
+ const DESKTOP_MODEL_IDS = Array.from(new Set((0, codex_model_catalog_1.getVisibleHardcodedCodexModelIds)()));
14
14
  const STATSIG_MODEL_PICKER_CONFIG_ID = "107580212";
15
15
  function uniqueStrings(values) {
16
16
  return Array.from(new Set(values.filter((value) => Boolean(value))));
@@ -40,6 +40,16 @@ function readWindowsCommandStdout(command) {
40
40
  const output = result.stdout.replace(/\r/g, "").trim();
41
41
  return output || null;
42
42
  }
43
+ function desktopClientsLikelyRunning() {
44
+ if (node_os_1.default.platform() === "win32" || isWslInteropRuntime()) {
45
+ const running = readWindowsCommandStdout("Get-Process Code,Codex -ErrorAction SilentlyContinue | Select-Object -ExpandProperty ProcessName");
46
+ return Boolean(running);
47
+ }
48
+ const result = (0, node_child_process_1.spawnSync)("sh", ["-lc", "pgrep -if '(^|/)(code|codex)$' >/dev/null"], {
49
+ stdio: "ignore",
50
+ });
51
+ return result.status === 0;
52
+ }
43
53
  function resolveWindowsPathForHost(input) {
44
54
  if (!input)
45
55
  return null;
@@ -333,31 +343,18 @@ function buildStatsigPatchScript() {
333
343
  " finally:",
334
344
  " db.close()",
335
345
  " print(json.dumps(result))",
336
- ].join("\\n");
346
+ ].join("\n");
337
347
  }
338
- async function patchDesktopStatsigCaches() {
339
- const dbDirs = await desktopStatsigLevelDbCandidates();
340
- if (dbDirs.length === 0)
341
- return { patched: 0, warnings: [] };
342
- const python = await ensureLocalPythonWithLevelDbSupport();
343
- if (!python) {
344
- return {
345
- patched: 0,
346
- warnings: [
347
- "Could not install the local desktop model-picker helper, so Codex app model labels may stay stale until the next supported cache refresh.",
348
- ],
349
- };
350
- }
351
- const run = (0, node_child_process_1.spawnSync)(python.command, [...python.args, "-c", buildStatsigPatchScript(), JSON.stringify(DESKTOP_MODEL_IDS), ...dbDirs], {
348
+ function patchStatsigCache(python, dbDir) {
349
+ const run = (0, node_child_process_1.spawnSync)(python.command, [...python.args, "-c", buildStatsigPatchScript(), JSON.stringify(DESKTOP_MODEL_IDS), dbDir], {
352
350
  encoding: "utf8",
353
351
  stdio: ["ignore", "pipe", "pipe"],
354
352
  });
355
353
  if (run.status !== 0) {
354
+ const detail = (run.stderr || run.stdout || "").trim();
356
355
  return {
357
356
  patched: 0,
358
- warnings: [
359
- "Could not refresh the desktop model-picker cache. Close Codex app / VS Code and rerun setup if the picker still shows Custom.",
360
- ],
357
+ warning: detail ? `${dbDir}: ${detail}` : `${dbDir}: desktop cache helper exited with status ${run.status}`,
361
358
  };
362
359
  }
363
360
  let patched = 0;
@@ -368,7 +365,7 @@ async function patchDesktopStatsigCaches() {
368
365
  const parsed = JSON.parse(line);
369
366
  patched += typeof parsed.patched === "number" ? parsed.patched : 0;
370
367
  if (typeof parsed.error === "string" && parsed.error.trim()) {
371
- const dbLabel = typeof parsed.db === "string" ? parsed.db : "desktop cache";
368
+ const dbLabel = typeof parsed.db === "string" ? parsed.db : dbDir;
372
369
  warnings.push(`${dbLabel}: ${parsed.error.trim()}`);
373
370
  }
374
371
  }
@@ -376,7 +373,45 @@ async function patchDesktopStatsigCaches() {
376
373
  continue;
377
374
  }
378
375
  }
379
- return { patched, warnings };
376
+ return { patched, warning: warnings.length > 0 ? warnings.join(" ") : undefined };
377
+ }
378
+ async function patchDesktopStatsigCaches() {
379
+ const dbDirs = await desktopStatsigLevelDbCandidates();
380
+ if (dbDirs.length === 0)
381
+ return { patched: 0, warnings: [] };
382
+ const python = await ensureLocalPythonWithLevelDbSupport();
383
+ if (!python) {
384
+ return {
385
+ patched: 0,
386
+ warnings: [
387
+ "Could not install the local desktop model-picker helper, so Codex app model labels may stay stale until the next supported cache refresh.",
388
+ ],
389
+ };
390
+ }
391
+ let patched = 0;
392
+ const warnings = [];
393
+ for (const dbDir of dbDirs) {
394
+ const result = patchStatsigCache(python, dbDir);
395
+ patched += result.patched;
396
+ if (result.warning)
397
+ warnings.push(result.warning);
398
+ }
399
+ if (patched === 0 && warnings.length > 0) {
400
+ const prefix = desktopClientsLikelyRunning()
401
+ ? "Could not refresh the desktop model-picker cache because VS Code or Codex app is still running."
402
+ : "Could not refresh the desktop model-picker cache.";
403
+ return {
404
+ patched,
405
+ warnings: [`${prefix} ${warnings.join(" ")}`],
406
+ };
407
+ }
408
+ if (patched > 0 && warnings.length > 0) {
409
+ const filteredWarnings = warnings.filter((warning) => !/does not exist|No such file/i.test(warning));
410
+ if (filteredWarnings.length > 0) {
411
+ return { patched, warnings: filteredWarnings };
412
+ }
413
+ }
414
+ return { patched, warnings: [] };
380
415
  }
381
416
  async function ensureCodexDesktopModelPickerSupport() {
382
417
  try {
@@ -10,36 +10,22 @@ const promises_1 = __importDefault(require("node:fs/promises"));
10
10
  const node_path_1 = __importDefault(require("node:path"));
11
11
  const node_child_process_1 = require("node:child_process");
12
12
  const node_os_1 = __importDefault(require("node:os"));
13
+ const codex_model_catalog_1 = require("./codex-model-catalog");
13
14
  const supported_models_1 = require("./supported-models");
14
15
  const MODELS_CACHE_FILE = "models_cache.json";
15
16
  const MODELS_CACHE_STATE_FILE = "theclawbay.models-cache.json";
16
17
  const STATE_DB_FILE_PATTERN = /^state_\d+\.sqlite$/;
17
- const TARGET_MODEL_IDS = (0, supported_models_1.getSupportedModelIds)();
18
+ const CATALOG_MODELS = (0, codex_model_catalog_1.getHardcodedCodexModelCatalog)();
19
+ const CATALOG_MODEL_IDS = (0, codex_model_catalog_1.getHardcodedCodexModelIds)();
18
20
  const LEGACY_REMOVED_MODEL_IDS = (0, supported_models_1.getLegacyRemovedModelIds)();
19
- const TARGET_MODEL_ID_SET = new Set(TARGET_MODEL_IDS);
20
- const TRACKED_MODEL_ID_SET = new Set([...TARGET_MODEL_IDS, ...LEGACY_REMOVED_MODEL_IDS]);
21
+ const CATALOG_MODEL_ID_SET = new Set(CATALOG_MODEL_IDS);
22
+ const TRACKED_MODEL_ID_SET = new Set([...CATALOG_MODEL_IDS, ...LEGACY_REMOVED_MODEL_IDS]);
21
23
  const LEGACY_REMOVED_MODEL_ID_SET = new Set(LEGACY_REMOVED_MODEL_IDS);
22
24
  const SEED_MARKER_KEY = "_theclawbay_seeded";
23
25
  // Older releases stored the model id as the marker value; accept those so cleanup still works.
24
26
  const SEED_MARKER_VALUE = "theclawbay";
25
27
  const LEGACY_SEED_MARKER_VALUES = new Set([...TRACKED_MODEL_ID_SET, SEED_MARKER_VALUE, true]);
26
- const SUPPORTED_MODELS = (0, supported_models_1.getSupportedModels)();
27
- const SUPPORTED_MODEL_CONFIG = new Map(SUPPORTED_MODELS.map((model) => [model.id, model]));
28
- const TEMPLATE_MODEL_IDS = TARGET_MODEL_IDS;
29
- const DEFAULT_REASONING_LEVELS = [
30
- { effort: "low", description: "Fast responses with lighter reasoning" },
31
- { effort: "medium", description: "Balances speed and reasoning depth for everyday tasks" },
32
- { effort: "high", description: "Greater reasoning depth for complex problems" },
33
- { effort: "xhigh", description: "Extra high reasoning depth for complex problems" },
34
- ];
35
- const FALLBACK_BASE_INSTRUCTIONS = `You are Codex, a coding agent based on GPT-5. You and the user share the same workspace and collaborate to achieve the user's goals.
36
-
37
- ## General
38
-
39
- - Prefer rg or rg --files for search when available.
40
- - Make safe, minimal code changes that preserve user work.
41
- - Explain what changed and any remaining risk clearly.`;
42
- const SEEDED_CACHE_FRESHNESS_YEARS = 10;
28
+ const SEEDED_CACHE_FRESHNESS_ISO = "2099-12-31T23:59:59.000Z";
43
29
  function objectRecordOr(value) {
44
30
  if (typeof value === "object" && value !== null && !Array.isArray(value)) {
45
31
  return value;
@@ -148,7 +134,7 @@ async function readPatchState(statePath) {
148
134
  }
149
135
  async function writePatchState(statePath, fingerprints) {
150
136
  await promises_1.default.mkdir(node_path_1.default.dirname(statePath), { recursive: true });
151
- const models = TARGET_MODEL_IDS.flatMap((modelId) => {
137
+ const models = CATALOG_MODEL_IDS.flatMap((modelId) => {
152
138
  const fingerprint = fingerprints[modelId];
153
139
  if (!fingerprint)
154
140
  return [];
@@ -166,93 +152,43 @@ function findModel(models, slug) {
166
152
  function hasSeedMarker(model) {
167
153
  return LEGACY_SEED_MARKER_VALUES.has(model[SEED_MARKER_KEY]);
168
154
  }
169
- function applySeedMarker(model) {
155
+ function stripSeedMarker(model) {
156
+ if (!(SEED_MARKER_KEY in model))
157
+ return cloneJson(model);
170
158
  const next = cloneJson(model);
171
- next[SEED_MARKER_KEY] = SEED_MARKER_VALUE;
159
+ delete next[SEED_MARKER_KEY];
172
160
  return next;
173
161
  }
174
- function seedDescription(modelId) {
175
- return SUPPORTED_MODEL_CONFIG.get(modelId)?.note ?? "The Claw Bay model.";
162
+ function buildCatalogModels() {
163
+ return cloneJson(CATALOG_MODELS).map((model) => stripSeedMarker(model));
176
164
  }
177
- function normalizeSeedModel(template, modelId) {
178
- const modelConfig = SUPPORTED_MODEL_CONFIG.get(modelId);
179
- const seed = applySeedMarker(template ?? {});
180
- seed.slug = modelId;
181
- seed.display_name = modelConfig?.label ?? modelId;
182
- seed.description = seedDescription(modelId);
183
- if (typeof seed.default_reasoning_level !== "string") {
184
- seed.default_reasoning_level = "medium";
185
- }
186
- if (!Array.isArray(seed.supported_reasoning_levels) || seed.supported_reasoning_levels.length === 0) {
187
- seed.supported_reasoning_levels = cloneJson(DEFAULT_REASONING_LEVELS);
188
- }
189
- if (typeof seed.shell_type !== "string") {
190
- seed.shell_type = "shell_command";
191
- }
192
- seed.visibility = "list";
193
- seed.supported_in_api = true;
194
- seed.priority = 0;
195
- seed.upgrade = null;
196
- if (typeof seed.base_instructions !== "string" || !seed.base_instructions.trim()) {
197
- seed.base_instructions = FALLBACK_BASE_INSTRUCTIONS;
198
- }
199
- if (typeof seed.supports_reasoning_summaries !== "boolean") {
200
- seed.supports_reasoning_summaries = true;
201
- }
202
- if (typeof seed.support_verbosity !== "boolean") {
203
- seed.support_verbosity = true;
204
- }
205
- if (seed.default_verbosity === undefined) {
206
- seed.default_verbosity = "low";
207
- }
208
- if (seed.apply_patch_tool_type === undefined) {
209
- seed.apply_patch_tool_type = "freeform";
210
- }
211
- const truncationPolicy = objectRecordOr(seed.truncation_policy);
212
- if (!truncationPolicy || typeof truncationPolicy.mode !== "string" || typeof truncationPolicy.limit !== "number") {
213
- seed.truncation_policy = { mode: "tokens", limit: 10000 };
214
- }
215
- if (typeof seed.supports_parallel_tool_calls !== "boolean") {
216
- seed.supports_parallel_tool_calls = true;
217
- }
218
- if (typeof seed.context_window !== "number") {
219
- seed.context_window = 272000;
220
- }
221
- if (typeof seed.effective_context_window_percent !== "number") {
222
- seed.effective_context_window_percent = 95;
223
- }
224
- if (!Array.isArray(seed.experimental_supported_tools)) {
225
- seed.experimental_supported_tools = [];
226
- }
227
- if (!Array.isArray(seed.input_modalities) || seed.input_modalities.length === 0) {
228
- seed.input_modalities = ["text", "image"];
229
- }
230
- if (typeof seed.prefer_websockets !== "boolean") {
231
- seed.prefer_websockets = true;
232
- }
233
- return seed;
165
+ function buildCatalogModelMap() {
166
+ return new Map(buildCatalogModels().flatMap((model) => {
167
+ const slug = typeof model.slug === "string" ? model.slug : "";
168
+ return slug ? [[slug, model]] : [];
169
+ }));
234
170
  }
235
- function buildSeedModel(models, modelId) {
236
- for (const candidate of TEMPLATE_MODEL_IDS) {
237
- const template = findModel(models, candidate);
238
- if (template)
239
- return normalizeSeedModel(template, modelId);
240
- }
241
- return normalizeSeedModel(null, modelId);
171
+ function sameFingerprintMaps(left, right) {
172
+ const leftKeys = Object.keys(left).sort();
173
+ const rightKeys = Object.keys(right).sort();
174
+ if (leftKeys.length !== rightKeys.length)
175
+ return false;
176
+ return leftKeys.every((key, index) => key === rightKeys[index] && left[key] === right[key]);
242
177
  }
243
- function hasSeededTheClawBayModel(models) {
244
- return models.some((model) => hasSeedMarker(model));
178
+ function hasCatalogModels(models) {
179
+ return models.some((model) => {
180
+ const slug = typeof model.slug === "string" ? model.slug : "";
181
+ return Boolean(slug) && CATALOG_MODEL_ID_SET.has(slug);
182
+ });
245
183
  }
246
184
  function nextFetchedAtIsoForDocument(doc) {
247
- if (!hasSeededTheClawBayModel(doc.models)) {
185
+ if (!hasCatalogModels(doc.models)) {
248
186
  return new Date().toISOString();
249
187
  }
250
- // Current desktop Codex builds only consult models_cache.json for custom-provider
251
- // catalogs in API-key mode, then silently fall back to compiled OpenAI models once
252
- // the five-minute TTL expires. Keep the seeded cache durable until setup refreshes it.
253
- const next = new Date();
254
- next.setUTCFullYear(next.getUTCFullYear() + SEEDED_CACHE_FRESHNESS_YEARS);
255
- return next.toISOString();
188
+ // Current Codex builds only consult models_cache.json for custom-provider
189
+ // catalogs in API-key mode. Pin the cache freshness far into the future so
190
+ // setup can refresh the catalog on demand without the five-minute TTL.
191
+ return SEEDED_CACHE_FRESHNESS_ISO;
256
192
  }
257
193
  function setCacheFreshnessMetadata(doc, clientVersion) {
258
194
  let changed = false;
@@ -562,22 +498,22 @@ async function ensureCodexModelCacheHasGpt54(params) {
562
498
  try {
563
499
  const existingState = await readPatchState(statePath);
564
500
  const parsed = await readJsonIfExists(cachePath);
501
+ const catalogModels = buildCatalogModels();
502
+ const catalogModelMap = buildCatalogModelMap();
565
503
  if (parsed === null) {
566
504
  const clientVersion = await inferCodexClientVersion(params.codexHome);
567
505
  if (!clientVersion) {
568
506
  await removeFileIfExists(statePath);
569
507
  return {
570
508
  action: "skipped",
571
- warning: "Codex models cache was not found, and Codex version could not be inferred for a safe The Claw Bay model seed.",
509
+ warning: "Codex models cache was not found, and Codex version could not be inferred for a safe The Claw Bay model catalog refresh.",
572
510
  };
573
511
  }
574
- const docModels = [];
575
- const nextState = {};
576
- for (const modelId of TARGET_MODEL_IDS) {
577
- const seed = buildSeedModel(docModels, modelId);
578
- docModels.push(seed);
579
- nextState[modelId] = fingerprintModel(seed);
580
- }
512
+ const docModels = cloneJson(catalogModels);
513
+ const nextState = Object.fromEntries(docModels.flatMap((model) => {
514
+ const slug = typeof model.slug === "string" ? model.slug : "";
515
+ return slug ? [[slug, fingerprintModel(model)]] : [];
516
+ }));
581
517
  const doc = {
582
518
  fetched_at: nextFetchedAtIsoForDocument({ models: docModels }),
583
519
  client_version: clientVersion,
@@ -592,105 +528,92 @@ async function ensureCodexModelCacheHasGpt54(params) {
592
528
  if (!doc) {
593
529
  return {
594
530
  action: "skipped",
595
- warning: "Codex models cache exists but is not valid JSON in the expected format; skipped The Claw Bay model seed.",
531
+ warning: "Codex models cache exists but is not valid JSON in the expected format; skipped The Claw Bay model catalog refresh.",
596
532
  };
597
533
  }
598
534
  const clientVersion = (await inferCodexClientVersion(params.codexHome)) ??
599
535
  (typeof doc.client_version === "string" && doc.client_version ? doc.client_version : null);
600
536
  let changed = false;
601
537
  let seeded = false;
602
- let stateChanged = false;
603
- const nextState = { ...existingState };
604
- const legacyRemovedBefore = doc.models.length;
605
- doc.models = doc.models.filter((entry) => {
538
+ const currentCatalogEntries = new Map();
539
+ const rest = [];
540
+ for (const entry of doc.models) {
606
541
  const slug = typeof entry.slug === "string" ? entry.slug : "";
607
- if (!slug || !LEGACY_REMOVED_MODEL_ID_SET.has(slug))
608
- return true;
609
- if (!hasSeedMarker(entry))
610
- return true;
611
- delete nextState[slug];
612
- stateChanged = true;
613
- return false;
614
- });
615
- if (doc.models.length !== legacyRemovedBefore) {
616
- changed = true;
617
- }
618
- for (const modelId of TARGET_MODEL_IDS) {
619
- const existingModel = findModel(doc.models, modelId);
620
- const trackedFingerprint = existingState[modelId];
621
- if (existingModel) {
622
- const currentFingerprint = fingerprintModel(existingModel);
623
- if (hasSeedMarker(existingModel)) {
624
- if (!trackedFingerprint || trackedFingerprint !== currentFingerprint) {
625
- nextState[modelId] = currentFingerprint;
626
- stateChanged = true;
627
- }
628
- continue;
542
+ if (!slug) {
543
+ rest.push(entry);
544
+ continue;
545
+ }
546
+ if (LEGACY_REMOVED_MODEL_ID_SET.has(slug) && hasSeedMarker(entry)) {
547
+ changed = true;
548
+ continue;
549
+ }
550
+ if (CATALOG_MODEL_ID_SET.has(slug)) {
551
+ if (!currentCatalogEntries.has(slug)) {
552
+ currentCatalogEntries.set(slug, entry);
629
553
  }
630
- if (trackedFingerprint && trackedFingerprint === currentFingerprint) {
631
- const seededModel = applySeedMarker(existingModel);
632
- doc.models = doc.models.map((entry) => (entry.slug === modelId ? seededModel : entry));
633
- nextState[modelId] = fingerprintModel(seededModel);
634
- seeded = true;
554
+ else {
635
555
  changed = true;
636
- stateChanged = true;
637
- continue;
638
- }
639
- if (trackedFingerprint && trackedFingerprint !== currentFingerprint) {
640
- delete nextState[modelId];
641
- stateChanged = true;
642
556
  }
643
557
  continue;
644
558
  }
645
- const seed = buildSeedModel(doc.models, modelId);
646
- doc.models = [seed, ...doc.models];
647
- nextState[modelId] = fingerprintModel(seed);
648
- seeded = true;
649
- changed = true;
650
- stateChanged = true;
559
+ const normalized = stripSeedMarker(entry);
560
+ if (fingerprintModel(normalized) !== fingerprintModel(entry)) {
561
+ changed = true;
562
+ }
563
+ rest.push(normalized);
651
564
  }
652
- // Keep The Claw Bay seeds grouped at the top for easy discovery in the picker.
653
- const bySlug = new Map();
654
- const rest = [];
655
- for (const entry of doc.models) {
656
- const slug = typeof entry.slug === "string" ? entry.slug : "";
657
- if (slug && TARGET_MODEL_ID_SET.has(slug) && !bySlug.has(slug)) {
658
- bySlug.set(slug, entry);
565
+ const orderedCatalog = [];
566
+ const nextState = {};
567
+ for (const modelId of CATALOG_MODEL_IDS) {
568
+ const desiredModel = catalogModelMap.get(modelId);
569
+ if (!desiredModel)
570
+ continue;
571
+ const desiredFingerprint = fingerprintModel(desiredModel);
572
+ const trackedFingerprint = existingState[modelId];
573
+ const existingModel = currentCatalogEntries.get(modelId);
574
+ if (!existingModel) {
575
+ orderedCatalog.push(cloneJson(desiredModel));
576
+ nextState[modelId] = desiredFingerprint;
577
+ changed = true;
578
+ seeded = true;
659
579
  continue;
660
580
  }
661
- rest.push(entry);
662
- }
663
- const orderedTargets = [];
664
- for (const id of TARGET_MODEL_IDS) {
665
- const entry = bySlug.get(id);
666
- if (entry)
667
- orderedTargets.push(entry);
581
+ const normalizedExisting = stripSeedMarker(existingModel);
582
+ const existingFingerprint = fingerprintModel(normalizedExisting);
583
+ if (existingFingerprint === desiredFingerprint && !hasSeedMarker(existingModel)) {
584
+ orderedCatalog.push(normalizedExisting);
585
+ if (trackedFingerprint === desiredFingerprint) {
586
+ nextState[modelId] = trackedFingerprint;
587
+ }
588
+ continue;
589
+ }
590
+ orderedCatalog.push(cloneJson(desiredModel));
591
+ nextState[modelId] = desiredFingerprint;
592
+ changed = true;
593
+ seeded = true;
668
594
  }
669
- doc.models = [...orderedTargets, ...rest];
595
+ doc.models = [...orderedCatalog, ...rest];
670
596
  if (setCacheFreshnessMetadata(doc, clientVersion)) {
671
597
  changed = true;
672
598
  }
599
+ const stateChanged = !sameFingerprintMaps(existingState, nextState);
673
600
  if (changed) {
674
601
  await promises_1.default.writeFile(cachePath, `${JSON.stringify(doc, null, 2)}\n`, "utf8");
675
602
  }
676
- const hasAnyTracked = TARGET_MODEL_IDS.some((id) => Boolean(nextState[id]));
603
+ const hasAnyTracked = CATALOG_MODEL_IDS.some((id) => Boolean(nextState[id]));
677
604
  if (!hasAnyTracked) {
678
605
  if (Object.keys(existingState).length > 0) {
679
606
  await removeFileIfExists(statePath);
680
607
  }
681
608
  }
682
- else if (stateChanged || seeded) {
609
+ else if (stateChanged) {
683
610
  await writePatchState(statePath, nextState);
684
611
  }
685
612
  if (seeded)
686
613
  return { action: "seeded" };
687
614
  if (changed || stateChanged)
688
615
  return { action: "refreshed" };
689
- const allSeeded = TARGET_MODEL_IDS.every((id) => {
690
- const model = findModel(doc.models, id);
691
- return Boolean(model && hasSeedMarker(model));
692
- });
693
- return { action: allSeeded ? "already_seeded" : "already_present" };
616
+ return { action: hasAnyTracked ? "already_seeded" : "already_present" };
694
617
  }
695
618
  catch (error) {
696
619
  const message = error instanceof Error ? error.message : String(error);
@@ -724,9 +647,17 @@ async function cleanupSeededCodexModelCache(params) {
724
647
  let preserved = 0;
725
648
  doc.models = doc.models.filter((entry) => {
726
649
  const slug = typeof entry.slug === "string" ? entry.slug : "";
727
- if (!slug || !TRACKED_MODEL_ID_SET.has(slug))
650
+ if (!slug)
651
+ return true;
652
+ if (LEGACY_REMOVED_MODEL_ID_SET.has(slug) && hasSeedMarker(entry)) {
653
+ removed += 1;
654
+ return false;
655
+ }
656
+ const trackedFingerprint = state[slug];
657
+ if (!trackedFingerprint)
728
658
  return true;
729
- if (!hasSeedMarker(entry)) {
659
+ const normalized = stripSeedMarker(entry);
660
+ if (fingerprintModel(normalized) !== trackedFingerprint) {
730
661
  preserved += 1;
731
662
  return true;
732
663
  }
@@ -0,0 +1,5 @@
1
+ type JsonObject = Record<string, unknown>;
2
+ export declare function getHardcodedCodexModelCatalog(): JsonObject[];
3
+ export declare function getHardcodedCodexModelIds(): string[];
4
+ export declare function getVisibleHardcodedCodexModelIds(): string[];
5
+ export {};
@@ -0,0 +1,46 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.getHardcodedCodexModelCatalog = getHardcodedCodexModelCatalog;
7
+ exports.getHardcodedCodexModelIds = getHardcodedCodexModelIds;
8
+ exports.getVisibleHardcodedCodexModelIds = getVisibleHardcodedCodexModelIds;
9
+ const node_path_1 = __importDefault(require("node:path"));
10
+ const node_fs_1 = require("node:fs");
11
+ function configPath() {
12
+ return node_path_1.default.resolve(__dirname, "..", "..", "theclawbay-codex-model-catalog.json");
13
+ }
14
+ function parseCodexModelCatalog(raw) {
15
+ const parsed = JSON.parse(raw);
16
+ if (!Array.isArray(parsed)) {
17
+ throw new Error("codex model catalog must be an array");
18
+ }
19
+ return parsed.map((entry) => {
20
+ if (typeof entry !== "object" || entry === null || Array.isArray(entry)) {
21
+ throw new Error("codex model catalog contains an invalid entry");
22
+ }
23
+ return entry;
24
+ });
25
+ }
26
+ function cloneJson(value) {
27
+ return JSON.parse(JSON.stringify(value));
28
+ }
29
+ let cachedCatalog = null;
30
+ function getHardcodedCodexModelCatalog() {
31
+ if (!cachedCatalog) {
32
+ cachedCatalog = parseCodexModelCatalog((0, node_fs_1.readFileSync)(configPath(), "utf8"));
33
+ }
34
+ return cloneJson(cachedCatalog);
35
+ }
36
+ function getHardcodedCodexModelIds() {
37
+ return getHardcodedCodexModelCatalog()
38
+ .map((model) => (typeof model.slug === "string" ? model.slug.trim() : ""))
39
+ .filter(Boolean);
40
+ }
41
+ function getVisibleHardcodedCodexModelIds() {
42
+ return getHardcodedCodexModelCatalog()
43
+ .filter((model) => model.visibility === "list")
44
+ .map((model) => (typeof model.slug === "string" ? model.slug.trim() : ""))
45
+ .filter(Boolean);
46
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "theclawbay",
3
- "version": "0.3.67",
3
+ "version": "0.3.69",
4
4
  "description": "CLI for connecting Codex, Continue, Cline, GSD, OpenClaw, OpenCode, Kilo, Roo Code, Aider, experimental Trae, and experimental Zo to The Claw Bay.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -27,6 +27,7 @@
27
27
  },
28
28
  "files": [
29
29
  "dist",
30
+ "theclawbay-codex-model-catalog.json",
30
31
  "theclawbay-supported-models.json",
31
32
  "README.md",
32
33
  "LICENSE"