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.
package/dist/commands/setup.js
CHANGED
|
@@ -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.
|
|
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
|
|
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
|
|
3344
|
-
: "- Codex login state: seeded local desktop auth in
|
|
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
|
|
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
|
|
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
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
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
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
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.
|
|
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": {
|