theclawbay 0.3.65 → 0.3.66

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.
@@ -14,6 +14,7 @@ const core_1 = require("@oclif/core");
14
14
  const yaml_1 = require("yaml");
15
15
  const base_command_1 = require("../lib/base-command");
16
16
  const codex_auth_seeding_1 = require("../lib/codex-auth-seeding");
17
+ const codex_desktop_model_picker_1 = require("../lib/codex-desktop-model-picker");
17
18
  const codex_history_migration_1 = require("../lib/codex-history-migration");
18
19
  const codex_model_cache_migration_1 = require("../lib/codex-model-cache-migration");
19
20
  const paths_1 = require("../lib/config/paths");
@@ -2989,6 +2990,7 @@ class SetupCommand extends base_command_1.BaseCommand {
2989
2990
  let authSeedResult = null;
2990
2991
  let stateDbMigration = null;
2991
2992
  let modelCacheMigration = null;
2993
+ let desktopModelPickerMigration = null;
2992
2994
  let continueConfigPath = null;
2993
2995
  let clineConfigPaths = [];
2994
2996
  let gsdConfigPaths = [];
@@ -3088,7 +3090,7 @@ class SetupCommand extends base_command_1.BaseCommand {
3088
3090
  neutralizeSources: HISTORY_PROVIDER_NEUTRALIZE_SOURCES,
3089
3091
  });
3090
3092
  }
3091
- authSeedResult = await (0, codex_auth_seeding_1.ensureCodexApiKeyAuth)({
3093
+ authSeedResult = await (0, codex_auth_seeding_1.ensureCodexUsageLimitAuth)({
3092
3094
  codexHome: paths_1.codexDir,
3093
3095
  apiKey: authCredential,
3094
3096
  replaceExistingAuth: true,
@@ -3103,6 +3105,7 @@ class SetupCommand extends base_command_1.BaseCommand {
3103
3105
  modelCacheMigration = await (0, codex_model_cache_migration_1.ensureCodexModelCacheHasGpt54)({
3104
3106
  codexHome: paths_1.codexDir,
3105
3107
  });
3108
+ desktopModelPickerMigration = await (0, codex_desktop_model_picker_1.ensureCodexDesktopModelPickerSupport)();
3106
3109
  }
3107
3110
  if (selectedSetupClients.has("continue")) {
3108
3111
  progress.update("Configuring Continue");
@@ -3231,11 +3234,16 @@ class SetupCommand extends base_command_1.BaseCommand {
3231
3234
  }
3232
3235
  if (modelCacheMigration?.warning)
3233
3236
  summaryNotes.add(modelCacheMigration.warning);
3237
+ if (desktopModelPickerMigration?.warning)
3238
+ summaryNotes.add(desktopModelPickerMigration.warning);
3234
3239
  if (authSeedResult?.warning)
3235
3240
  summaryNotes.add(authSeedResult.warning);
3236
3241
  if (authSeedResult &&
3237
3242
  ["seeded", "updated", "already_seeded"].includes(authSeedResult.action)) {
3238
- summaryNotes.add("Codex desktop clients were pinned to local API-key auth so current VS Code and Codex app builds keep the The Claw Bay model picker instead of falling back to OpenAI's default model catalog.");
3243
+ summaryNotes.add("Codex desktop clients were pinned to a local ChatGPT-compatible auth shim so current VS Code and Codex app builds keep the The Claw Bay model picker instead of falling back to OpenAI's default model catalog.");
3244
+ }
3245
+ if (desktopModelPickerMigration?.action === "patched") {
3246
+ 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.");
3239
3247
  }
3240
3248
  if (selectedSetupClients.has("codex") && !migrateCodexConversations) {
3241
3249
  summaryNotes.add("Left existing Codex conversations untouched.");
@@ -3338,16 +3346,25 @@ class SetupCommand extends base_command_1.BaseCommand {
3338
3346
  else if (modelCacheMigration) {
3339
3347
  this.log("- Codex model cache: no change.");
3340
3348
  }
3349
+ if (desktopModelPickerMigration?.action === "patched") {
3350
+ this.log("- Codex desktop picker: refreshed VS Code / Codex app picker compatibility so supported The Claw Bay models keep their real labels.");
3351
+ }
3352
+ else if (desktopModelPickerMigration?.warning) {
3353
+ this.log(`- Codex desktop picker: ${desktopModelPickerMigration.warning}`);
3354
+ }
3355
+ else if (desktopModelPickerMigration) {
3356
+ this.log("- Codex desktop picker: no change.");
3357
+ }
3341
3358
  if (authSeedResult?.action === "seeded") {
3342
3359
  this.log(authSeedResult.replacedExistingAuth
3343
- ? "- Codex login state: backed up the existing Codex auth and switched local desktop auth to The Claw Bay API-key mode."
3344
- : "- Codex login state: seeded local desktop auth in The Claw Bay API-key mode.");
3360
+ ? "- Codex login state: backed up the existing Codex auth and switched local desktop auth to the Claw Bay desktop compatibility mode."
3361
+ : "- Codex login state: seeded local desktop auth in the Claw Bay desktop compatibility mode.");
3345
3362
  }
3346
3363
  else if (authSeedResult?.action === "updated") {
3347
- this.log("- Codex login state: refreshed the local The Claw Bay API-key auth seed.");
3364
+ this.log("- Codex login state: refreshed the local The Claw Bay desktop compatibility auth seed.");
3348
3365
  }
3349
3366
  else if (authSeedResult?.action === "already_seeded") {
3350
- this.log("- Codex login state: local desktop auth was already pinned to The Claw Bay API-key mode.");
3367
+ this.log("- Codex login state: local desktop auth was already pinned to the Claw Bay desktop compatibility mode.");
3351
3368
  }
3352
3369
  else if (authSeedResult?.warning) {
3353
3370
  this.log(`- Codex login state: ${authSeedResult.warning}`);
@@ -74,15 +74,19 @@ function buildFakeJwt(params) {
74
74
  }
75
75
  function buildSeededAuthDocument(params) {
76
76
  if (params.mode === "chatgpt-auth-tokens") {
77
+ const fakeJwt = buildFakeJwt({
78
+ email: THECLAWBAY_CHATGPT_EMAIL,
79
+ accountId: THECLAWBAY_CHATGPT_ACCOUNT_ID,
80
+ planType: THECLAWBAY_CHATGPT_PLAN_TYPE,
81
+ });
77
82
  return {
78
83
  auth_mode: "chatgptAuthTokens",
79
84
  tokens: {
80
- id_token: buildFakeJwt({
81
- email: THECLAWBAY_CHATGPT_EMAIL,
82
- accountId: THECLAWBAY_CHATGPT_ACCOUNT_ID,
83
- planType: THECLAWBAY_CHATGPT_PLAN_TYPE,
84
- }),
85
- access_token: params.apiKey,
85
+ // Current desktop Codex builds derive the local ChatGPT-style account
86
+ // identity from these token fields. Keep them obviously fake so the real
87
+ // The Claw Bay bearer stays scoped to experimental_bearer_token only.
88
+ id_token: fakeJwt,
89
+ access_token: fakeJwt,
86
90
  refresh_token: "",
87
91
  account_id: THECLAWBAY_CHATGPT_ACCOUNT_ID,
88
92
  },
@@ -240,11 +244,12 @@ async function ensureCodexApiKeyAuth(params) {
240
244
  });
241
245
  }
242
246
  async function ensureCodexUsageLimitAuth(params) {
243
- // Current Codex releases treat chatgptAuthTokens.access_token as a real
244
- // ChatGPT JWT and use it against chatgpt.com. The Claw Bay credentials are
245
- // provider-scoped bearer tokens, so the legacy fake-ChatGPT seed path is no
246
- // longer safe to apply here.
247
- return ensureCodexApiKeyAuth(params);
247
+ return ensureCodexAuth({
248
+ codexHome: params.codexHome,
249
+ apiKey: params.apiKey,
250
+ mode: "chatgpt-auth-tokens",
251
+ replaceExistingAuth: params.replaceExistingAuth ?? false,
252
+ });
248
253
  }
249
254
  async function cleanupSeededCodexAuth(params) {
250
255
  const authPath = node_path_1.default.join(params.codexHome, AUTH_FILE);
@@ -0,0 +1,7 @@
1
+ export type EnsureCodexDesktopModelPickerResult = {
2
+ action: "patched" | "already_patched" | "skipped";
3
+ warning?: string;
4
+ patchedBundles: number;
5
+ patchedCaches: number;
6
+ };
7
+ export declare function ensureCodexDesktopModelPickerSupport(): Promise<EnsureCodexDesktopModelPickerResult>;
@@ -0,0 +1,417 @@
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.ensureCodexDesktopModelPickerSupport = ensureCodexDesktopModelPickerSupport;
7
+ const node_child_process_1 = require("node:child_process");
8
+ const promises_1 = __importDefault(require("node:fs/promises"));
9
+ const node_os_1 = __importDefault(require("node:os"));
10
+ const node_path_1 = __importDefault(require("node:path"));
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)()));
14
+ const STATSIG_MODEL_PICKER_CONFIG_ID = "107580212";
15
+ function uniqueStrings(values) {
16
+ return Array.from(new Set(values.filter((value) => Boolean(value))));
17
+ }
18
+ async function pathExists(filePath) {
19
+ try {
20
+ await promises_1.default.access(filePath);
21
+ return true;
22
+ }
23
+ catch {
24
+ return false;
25
+ }
26
+ }
27
+ function isWslInteropRuntime() {
28
+ return node_os_1.default.platform() === "linux" && Boolean(process.env.WSL_DISTRO_NAME || process.env.WSL_INTEROP);
29
+ }
30
+ function readWindowsCommandStdout(command) {
31
+ const shell = node_os_1.default.platform() === "win32" ? "powershell.exe" : isWslInteropRuntime() ? "powershell.exe" : null;
32
+ if (!shell)
33
+ return null;
34
+ const result = (0, node_child_process_1.spawnSync)(shell, ["-NoProfile", "-Command", command], {
35
+ encoding: "utf8",
36
+ stdio: ["ignore", "pipe", "ignore"],
37
+ });
38
+ if (result.status !== 0)
39
+ return null;
40
+ const output = result.stdout.replace(/\r/g, "").trim();
41
+ return output || null;
42
+ }
43
+ function resolveWindowsPathForHost(input) {
44
+ if (!input)
45
+ return null;
46
+ const trimmed = input.trim();
47
+ if (!trimmed)
48
+ return null;
49
+ if (node_os_1.default.platform() === "win32")
50
+ return trimmed;
51
+ const match = /^([A-Za-z]):\\(.*)$/.exec(trimmed);
52
+ if (!match)
53
+ return null;
54
+ return node_path_1.default.posix.join("/mnt", match[1].toLowerCase(), match[2].replace(/\\/g, "/"));
55
+ }
56
+ function resolveWindowsHomeDirForHost() {
57
+ const reported = readWindowsCommandStdout("$env:USERPROFILE");
58
+ if (reported)
59
+ return resolveWindowsPathForHost(reported);
60
+ return node_os_1.default.platform() === "win32" ? node_os_1.default.homedir() : null;
61
+ }
62
+ function editorExtensionDirCandidates() {
63
+ const home = node_os_1.default.homedir();
64
+ const dirs = [];
65
+ if (node_os_1.default.platform() === "darwin") {
66
+ return uniqueStrings([
67
+ node_path_1.default.join(home, ".vscode", "extensions"),
68
+ node_path_1.default.join(home, ".vscode-insiders", "extensions"),
69
+ node_path_1.default.join(home, ".vscode-oss", "extensions"),
70
+ ]);
71
+ }
72
+ if (node_os_1.default.platform() === "win32") {
73
+ const homeDir = process.env.USERPROFILE?.trim() || home;
74
+ return uniqueStrings([
75
+ node_path_1.default.join(homeDir, ".vscode", "extensions"),
76
+ node_path_1.default.join(homeDir, ".vscode-insiders", "extensions"),
77
+ node_path_1.default.join(homeDir, ".vscode-oss", "extensions"),
78
+ ]);
79
+ }
80
+ dirs.push(node_path_1.default.join(home, ".vscode", "extensions"), node_path_1.default.join(home, ".vscode-insiders", "extensions"), node_path_1.default.join(home, ".vscode-oss", "extensions"));
81
+ if (isWslInteropRuntime()) {
82
+ const windowsHome = resolveWindowsHomeDirForHost();
83
+ if (windowsHome) {
84
+ dirs.push(node_path_1.default.join(windowsHome, ".vscode", "extensions"), node_path_1.default.join(windowsHome, ".vscode-insiders", "extensions"), node_path_1.default.join(windowsHome, ".vscode-oss", "extensions"));
85
+ }
86
+ }
87
+ return uniqueStrings(dirs);
88
+ }
89
+ async function findWritableCodexWebviewBundleFiles() {
90
+ const results = [];
91
+ for (const extensionDir of editorExtensionDirCandidates()) {
92
+ let entries = [];
93
+ try {
94
+ entries = await promises_1.default.readdir(extensionDir, { withFileTypes: true });
95
+ }
96
+ catch (error) {
97
+ const err = error;
98
+ if (err.code === "ENOENT")
99
+ continue;
100
+ throw error;
101
+ }
102
+ for (const entry of entries) {
103
+ if (!entry.isDirectory())
104
+ continue;
105
+ if (!entry.name.toLowerCase().startsWith("openai.chatgpt-"))
106
+ continue;
107
+ const assetsDir = node_path_1.default.join(extensionDir, entry.name, "webview", "assets");
108
+ let assetEntries = [];
109
+ try {
110
+ assetEntries = await promises_1.default.readdir(assetsDir, { withFileTypes: true });
111
+ }
112
+ catch (error) {
113
+ const err = error;
114
+ if (err.code === "ENOENT")
115
+ continue;
116
+ throw error;
117
+ }
118
+ for (const assetEntry of assetEntries) {
119
+ if (!assetEntry.isFile())
120
+ continue;
121
+ if (!assetEntry.name.endsWith(".js"))
122
+ continue;
123
+ if (!assetEntry.name.startsWith("font-settings-") && !assetEntry.name.startsWith("index-"))
124
+ continue;
125
+ const filePath = node_path_1.default.join(assetsDir, assetEntry.name);
126
+ try {
127
+ await promises_1.default.access(filePath, 2);
128
+ results.push(filePath);
129
+ }
130
+ catch {
131
+ continue;
132
+ }
133
+ }
134
+ }
135
+ }
136
+ return uniqueStrings(results);
137
+ }
138
+ function patchDesktopIndexBundle(source) {
139
+ return source.replace(/let (\w+)=([\w$.]+)\.email\?\?void 0,(\w+)=\2\.userId\?\?void 0,(\w+)=(\w+)\?\?void 0,(\w+)=(\w+)\?\?void 0,(\w+);/g, "let $1=$2.email??void 0,$3=$2.userId??$7??$5??void 0,$4=$5??void 0,$6=$7??void 0,$8;");
140
+ }
141
+ function patchDesktopFontSettingsBundle(source) {
142
+ return source.replace(/if\((\w+)\.useHiddenModels\?\1\.availableModels\.has\((\w+)\.model\):!\2\.hidden\)\{/g, "if(!$2.hidden||$1.availableModels.has($2.model)){");
143
+ }
144
+ async function patchCodexWebviewBundles() {
145
+ const bundleFiles = await findWritableCodexWebviewBundleFiles();
146
+ let patched = 0;
147
+ for (const filePath of bundleFiles) {
148
+ const original = await promises_1.default.readFile(filePath, "utf8");
149
+ const next = filePath.includes(`${node_path_1.default.sep}font-settings-`)
150
+ ? patchDesktopFontSettingsBundle(original)
151
+ : patchDesktopIndexBundle(original);
152
+ if (next === original)
153
+ continue;
154
+ await promises_1.default.writeFile(filePath, next, "utf8");
155
+ patched += 1;
156
+ }
157
+ return { patched, scanned: bundleFiles.length };
158
+ }
159
+ function resolvePythonCommand() {
160
+ const candidates = [
161
+ { command: "python3", args: [] },
162
+ { command: "python", args: [] },
163
+ { command: "py", args: ["-3"] },
164
+ ];
165
+ for (const candidate of candidates) {
166
+ const result = (0, node_child_process_1.spawnSync)(candidate.command, [...candidate.args, "-c", "import sys; print(sys.version)"], {
167
+ encoding: "utf8",
168
+ stdio: ["ignore", "pipe", "ignore"],
169
+ });
170
+ if (result.status === 0)
171
+ return candidate;
172
+ }
173
+ return null;
174
+ }
175
+ function venvPythonPath(venvDir) {
176
+ if (node_os_1.default.platform() === "win32") {
177
+ return node_path_1.default.join(venvDir, "Scripts", "python.exe");
178
+ }
179
+ return node_path_1.default.join(venvDir, "bin", "python");
180
+ }
181
+ function runPython(command, script, extraArgs = []) {
182
+ return (0, node_child_process_1.spawnSync)(command.command, [...command.args, "-c", script, ...extraArgs], {
183
+ encoding: "utf8",
184
+ stdio: ["ignore", "pipe", "pipe"],
185
+ });
186
+ }
187
+ function pythonImportsLevelDb(command) {
188
+ const result = runPython(command, "import plyvel");
189
+ return result.status === 0;
190
+ }
191
+ async function ensureLocalPythonWithLevelDbSupport() {
192
+ const systemPython = resolvePythonCommand();
193
+ if (!systemPython)
194
+ return null;
195
+ if (pythonImportsLevelDb(systemPython))
196
+ return systemPython;
197
+ const venvDir = node_path_1.default.join(paths_1.theclawbayStateDir, "python", "leveldb");
198
+ const venvPython = venvPythonPath(venvDir);
199
+ const venvCommand = { command: venvPython, args: [] };
200
+ const existingVenv = (0, node_child_process_1.spawnSync)(venvPython, ["-c", "import sys; print(sys.executable)"], {
201
+ stdio: "ignore",
202
+ });
203
+ if (existingVenv.status !== 0) {
204
+ await promises_1.default.mkdir(node_path_1.default.dirname(venvDir), { recursive: true });
205
+ const createVenv = (0, node_child_process_1.spawnSync)(systemPython.command, [...systemPython.args, "-m", "venv", venvDir], {
206
+ encoding: "utf8",
207
+ stdio: ["ignore", "pipe", "pipe"],
208
+ });
209
+ if (createVenv.status !== 0)
210
+ return null;
211
+ }
212
+ if (!pythonImportsLevelDb(venvCommand)) {
213
+ const pipArgs = ["-m", "pip", "install", "--disable-pip-version-check", "plyvel-ci"];
214
+ let install = (0, node_child_process_1.spawnSync)(venvPython, pipArgs, {
215
+ encoding: "utf8",
216
+ stdio: ["ignore", "pipe", "pipe"],
217
+ });
218
+ if (install.status !== 0) {
219
+ install = (0, node_child_process_1.spawnSync)(venvPython, ["-m", "pip", "install", "--disable-pip-version-check", "plyvel"], {
220
+ encoding: "utf8",
221
+ stdio: ["ignore", "pipe", "pipe"],
222
+ });
223
+ if (install.status !== 0)
224
+ return null;
225
+ }
226
+ }
227
+ return pythonImportsLevelDb(venvCommand) ? venvCommand : null;
228
+ }
229
+ async function findWindowsCodexPackageLevelDbDirs(windowsHome) {
230
+ const packagesRoot = node_path_1.default.join(windowsHome, "AppData", "Local", "Packages");
231
+ let entries = [];
232
+ try {
233
+ entries = await promises_1.default.readdir(packagesRoot, { withFileTypes: true });
234
+ }
235
+ catch (error) {
236
+ const err = error;
237
+ if (err.code === "ENOENT")
238
+ return [];
239
+ throw error;
240
+ }
241
+ return uniqueStrings(entries
242
+ .filter((entry) => entry.isDirectory() && entry.name.startsWith("OpenAI.Codex_"))
243
+ .map((entry) => node_path_1.default.join(packagesRoot, entry.name, "LocalCache", "Roaming", "Codex", "Local Storage", "leveldb")));
244
+ }
245
+ async function desktopStatsigLevelDbCandidates() {
246
+ const home = node_os_1.default.homedir();
247
+ const candidates = [];
248
+ if (node_os_1.default.platform() === "darwin") {
249
+ candidates.push(node_path_1.default.join(home, "Library", "Application Support", "Code", "Local Storage", "leveldb"), node_path_1.default.join(home, "Library", "Application Support", "Code - Insiders", "Local Storage", "leveldb"), node_path_1.default.join(home, "Library", "Application Support", "Codex", "Local Storage", "leveldb"), node_path_1.default.join(home, "Library", "Containers", "OpenAI.Codex", "Data", "Library", "Application Support", "Codex", "Local Storage", "leveldb"));
250
+ }
251
+ else if (node_os_1.default.platform() === "win32") {
252
+ const homeDir = process.env.USERPROFILE?.trim() || home;
253
+ candidates.push(node_path_1.default.join(homeDir, "AppData", "Roaming", "Code", "Local Storage", "leveldb"), node_path_1.default.join(homeDir, "AppData", "Roaming", "Code - Insiders", "Local Storage", "leveldb"));
254
+ candidates.push(...(await findWindowsCodexPackageLevelDbDirs(homeDir)));
255
+ }
256
+ else {
257
+ candidates.push(node_path_1.default.join(home, ".config", "Code", "Local Storage", "leveldb"), node_path_1.default.join(home, ".config", "Code - Insiders", "Local Storage", "leveldb"), node_path_1.default.join(home, ".config", "Codex", "Local Storage", "leveldb"));
258
+ if (isWslInteropRuntime()) {
259
+ const windowsHome = resolveWindowsHomeDirForHost();
260
+ if (windowsHome) {
261
+ candidates.push(node_path_1.default.join(windowsHome, "AppData", "Roaming", "Code", "Local Storage", "leveldb"), node_path_1.default.join(windowsHome, "AppData", "Roaming", "Code - Insiders", "Local Storage", "leveldb"));
262
+ candidates.push(...(await findWindowsCodexPackageLevelDbDirs(windowsHome)));
263
+ }
264
+ }
265
+ }
266
+ const existing = [];
267
+ for (const candidate of uniqueStrings(candidates)) {
268
+ if (await pathExists(candidate))
269
+ existing.push(candidate);
270
+ }
271
+ return existing;
272
+ }
273
+ function buildStatsigPatchScript() {
274
+ return [
275
+ "import json, sys",
276
+ "import plyvel",
277
+ "target_models = json.loads(sys.argv[1])",
278
+ `config_id = ${JSON.stringify(STATSIG_MODEL_PICKER_CONFIG_ID)}`,
279
+ "for db_path in sys.argv[2:]:",
280
+ " result = {'db': db_path, 'patched': 0, 'error': None}",
281
+ " try:",
282
+ " db = plyvel.DB(db_path, create_if_missing=False)",
283
+ " except Exception as exc:",
284
+ " result['error'] = str(exc)",
285
+ " print(json.dumps(result))",
286
+ " continue",
287
+ " try:",
288
+ " for key, raw in db:",
289
+ " if b'statsig.cached.evaluations.' not in key:",
290
+ " continue",
291
+ " try:",
292
+ " text = raw.decode('utf-8')",
293
+ " prefix = '\\x01' if text.startswith('\\x01') else ''",
294
+ " if prefix:",
295
+ " text = text[1:]",
296
+ " outer = json.loads(text)",
297
+ " inner = json.loads(outer.get('data', '{}'))",
298
+ " dynamic_configs = inner.get('dynamic_configs')",
299
+ " if not isinstance(dynamic_configs, dict):",
300
+ " continue",
301
+ " config = dynamic_configs.get(config_id)",
302
+ " if not isinstance(config, dict):",
303
+ " continue",
304
+ " value = config.get('value')",
305
+ " if not isinstance(value, dict):",
306
+ " continue",
307
+ " current_models = value.get('available_models')",
308
+ " merged = []",
309
+ " seen = set()",
310
+ " for model_id in list(target_models) + (current_models if isinstance(current_models, list) else []):",
311
+ " if not isinstance(model_id, str):",
312
+ " continue",
313
+ " normalized = model_id.strip()",
314
+ " if not normalized or normalized in seen:",
315
+ " continue",
316
+ " seen.add(normalized)",
317
+ " merged.append(normalized)",
318
+ " changed = False",
319
+ " if value.get('use_hidden_models') is not False:",
320
+ " value['use_hidden_models'] = False",
321
+ " changed = True",
322
+ " if value.get('available_models') != merged:",
323
+ " value['available_models'] = merged",
324
+ " changed = True",
325
+ " if not changed:",
326
+ " continue",
327
+ " outer['data'] = json.dumps(inner, separators=(',', ':'))",
328
+ " encoded = (prefix + json.dumps(outer, separators=(',', ':'))).encode('utf-8')",
329
+ " db.put(key, encoded)",
330
+ " result['patched'] += 1",
331
+ " except Exception:",
332
+ " continue",
333
+ " finally:",
334
+ " db.close()",
335
+ " print(json.dumps(result))",
336
+ ].join("\\n");
337
+ }
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], {
352
+ encoding: "utf8",
353
+ stdio: ["ignore", "pipe", "pipe"],
354
+ });
355
+ if (run.status !== 0) {
356
+ return {
357
+ 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
+ ],
361
+ };
362
+ }
363
+ let patched = 0;
364
+ const warnings = [];
365
+ const lines = (run.stdout ?? "").split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
366
+ for (const line of lines) {
367
+ try {
368
+ const parsed = JSON.parse(line);
369
+ patched += typeof parsed.patched === "number" ? parsed.patched : 0;
370
+ if (typeof parsed.error === "string" && parsed.error.trim()) {
371
+ const dbLabel = typeof parsed.db === "string" ? parsed.db : "desktop cache";
372
+ warnings.push(`${dbLabel}: ${parsed.error.trim()}`);
373
+ }
374
+ }
375
+ catch {
376
+ continue;
377
+ }
378
+ }
379
+ return { patched, warnings };
380
+ }
381
+ async function ensureCodexDesktopModelPickerSupport() {
382
+ try {
383
+ const bundleResult = await patchCodexWebviewBundles();
384
+ const cacheResult = await patchDesktopStatsigCaches();
385
+ const warning = cacheResult.warnings.length > 0 ? cacheResult.warnings.join(" ") : undefined;
386
+ if (bundleResult.patched > 0 || cacheResult.patched > 0) {
387
+ return {
388
+ action: "patched",
389
+ warning,
390
+ patchedBundles: bundleResult.patched,
391
+ patchedCaches: cacheResult.patched,
392
+ };
393
+ }
394
+ if (warning) {
395
+ return {
396
+ action: "skipped",
397
+ warning,
398
+ patchedBundles: 0,
399
+ patchedCaches: 0,
400
+ };
401
+ }
402
+ return {
403
+ action: "already_patched",
404
+ patchedBundles: 0,
405
+ patchedCaches: 0,
406
+ };
407
+ }
408
+ catch (error) {
409
+ const message = error instanceof Error ? error.message : String(error);
410
+ return {
411
+ action: "skipped",
412
+ warning: `Could not refresh the desktop model picker: ${message}`,
413
+ patchedBundles: 0,
414
+ patchedCaches: 0,
415
+ };
416
+ }
417
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "theclawbay",
3
- "version": "0.3.65",
3
+ "version": "0.3.66",
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": {