theclawbay 0.3.7 → 0.3.9
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/logout.js +18 -0
- package/dist/commands/setup.js +27 -2
- package/dist/lib/codex-auth-seeding.d.ts +15 -0
- package/dist/lib/codex-auth-seeding.js +190 -0
- package/dist/lib/codex-model-cache-migration.d.ts +1 -1
- package/dist/lib/codex-model-cache-migration.js +28 -0
- package/package.json +1 -1
package/dist/commands/logout.js
CHANGED
|
@@ -7,6 +7,7 @@ const promises_1 = __importDefault(require("node:fs/promises"));
|
|
|
7
7
|
const node_os_1 = __importDefault(require("node:os"));
|
|
8
8
|
const node_path_1 = __importDefault(require("node:path"));
|
|
9
9
|
const base_command_1 = require("../lib/base-command");
|
|
10
|
+
const codex_auth_seeding_1 = require("../lib/codex-auth-seeding");
|
|
10
11
|
const codex_history_migration_1 = require("../lib/codex-history-migration");
|
|
11
12
|
const codex_model_cache_migration_1 = require("../lib/codex-model-cache-migration");
|
|
12
13
|
const paths_1 = require("../lib/config/paths");
|
|
@@ -246,6 +247,9 @@ class LogoutCommand extends base_command_1.BaseCommand {
|
|
|
246
247
|
targetProvider: OPENAI_PROVIDER_ID,
|
|
247
248
|
sourceProviders: HISTORY_PROVIDER_DB_MIGRATE_SOURCES,
|
|
248
249
|
});
|
|
250
|
+
const authSeedCleanup = await (0, codex_auth_seeding_1.cleanupSeededCodexApiKeyAuth)({
|
|
251
|
+
codexHome: paths_1.codexDir,
|
|
252
|
+
});
|
|
249
253
|
const modelCacheCleanup = await (0, codex_model_cache_migration_1.cleanupSeededCodexModelCache)({
|
|
250
254
|
codexHome: paths_1.codexDir,
|
|
251
255
|
});
|
|
@@ -275,6 +279,20 @@ class LogoutCommand extends base_command_1.BaseCommand {
|
|
|
275
279
|
const detail = stateDbMigration.warning ? ` ${stateDbMigration.warning}` : "";
|
|
276
280
|
this.log(`- Conversation cache: could not update local Codex history DB.${detail}`);
|
|
277
281
|
}
|
|
282
|
+
if (authSeedCleanup.action === "removed") {
|
|
283
|
+
this.log("- Codex login state: removed the Claw Bay auth seed.");
|
|
284
|
+
}
|
|
285
|
+
else if (authSeedCleanup.action === "preserved") {
|
|
286
|
+
this.log("- Codex login state: preserved existing Codex auth.");
|
|
287
|
+
if (authSeedCleanup.warning)
|
|
288
|
+
this.log(`- Codex login state: ${authSeedCleanup.warning}`);
|
|
289
|
+
}
|
|
290
|
+
else if (authSeedCleanup.warning) {
|
|
291
|
+
this.log(`- Codex login state: ${authSeedCleanup.warning}`);
|
|
292
|
+
}
|
|
293
|
+
else {
|
|
294
|
+
this.log("- Codex login state: no cleanup needed.");
|
|
295
|
+
}
|
|
278
296
|
if (modelCacheCleanup.action === "removed") {
|
|
279
297
|
this.log("- Codex model cache: removed the Claw Bay GPT-5.4 seed.");
|
|
280
298
|
}
|
package/dist/commands/setup.js
CHANGED
|
@@ -9,6 +9,7 @@ const node_path_1 = __importDefault(require("node:path"));
|
|
|
9
9
|
const node_child_process_1 = require("node:child_process");
|
|
10
10
|
const core_1 = require("@oclif/core");
|
|
11
11
|
const base_command_1 = require("../lib/base-command");
|
|
12
|
+
const codex_auth_seeding_1 = require("../lib/codex-auth-seeding");
|
|
12
13
|
const codex_history_migration_1 = require("../lib/codex-history-migration");
|
|
13
14
|
const codex_model_cache_migration_1 = require("../lib/codex-model-cache-migration");
|
|
14
15
|
const paths_1 = require("../lib/config/paths");
|
|
@@ -208,7 +209,7 @@ async function writeCodexConfig(params) {
|
|
|
208
209
|
'name = "OpenAI"',
|
|
209
210
|
`base_url = "${proxyRoot}/backend-api/codex"`,
|
|
210
211
|
'wire_api = "responses"',
|
|
211
|
-
"requires_openai_auth =
|
|
212
|
+
"requires_openai_auth = true",
|
|
212
213
|
`experimental_bearer_token = "${params.apiKey}"`,
|
|
213
214
|
MANAGED_END,
|
|
214
215
|
]);
|
|
@@ -462,6 +463,10 @@ class SetupCommand extends base_command_1.BaseCommand {
|
|
|
462
463
|
migrationStateFile: MIGRATION_STATE_FILE,
|
|
463
464
|
neutralizeSources: HISTORY_PROVIDER_NEUTRALIZE_SOURCES,
|
|
464
465
|
});
|
|
466
|
+
const authSeed = await (0, codex_auth_seeding_1.ensureCodexApiKeyAuth)({
|
|
467
|
+
codexHome: paths_1.codexDir,
|
|
468
|
+
apiKey,
|
|
469
|
+
});
|
|
465
470
|
const stateDbMigration = await (0, codex_history_migration_1.migrateStateDbProviders)({
|
|
466
471
|
codexHome: paths_1.codexDir,
|
|
467
472
|
targetProvider: DEFAULT_PROVIDER_ID,
|
|
@@ -525,6 +530,9 @@ class SetupCommand extends base_command_1.BaseCommand {
|
|
|
525
530
|
else if (modelCacheMigration.action === "created") {
|
|
526
531
|
this.log("- Codex model cache: created a local picker cache with GPT-5.4.");
|
|
527
532
|
}
|
|
533
|
+
else if (modelCacheMigration.action === "refreshed") {
|
|
534
|
+
this.log("- Codex model cache: refreshed the local picker cache for GPT-5.4.");
|
|
535
|
+
}
|
|
528
536
|
else if (modelCacheMigration.action === "already_seeded") {
|
|
529
537
|
this.log("- Codex model cache: GPT-5.4 was already seeded locally.");
|
|
530
538
|
}
|
|
@@ -554,7 +562,24 @@ class SetupCommand extends base_command_1.BaseCommand {
|
|
|
554
562
|
else {
|
|
555
563
|
this.log("- OpenCode: not detected (skipped)");
|
|
556
564
|
}
|
|
557
|
-
|
|
565
|
+
if (authSeed.action === "seeded") {
|
|
566
|
+
this.log("- Codex login state: seeded local API-key auth for Codex UI.");
|
|
567
|
+
}
|
|
568
|
+
else if (authSeed.action === "updated") {
|
|
569
|
+
this.log("- Codex login state: refreshed the Claw Bay API-key auth seed.");
|
|
570
|
+
}
|
|
571
|
+
else if (authSeed.action === "already_seeded") {
|
|
572
|
+
this.log("- Codex login state: the Claw Bay API-key auth was already seeded.");
|
|
573
|
+
}
|
|
574
|
+
else if (authSeed.action === "already_present") {
|
|
575
|
+
this.log("- Codex login state: preserved existing local Codex auth.");
|
|
576
|
+
}
|
|
577
|
+
else if (authSeed.warning) {
|
|
578
|
+
this.log(`- Codex login state: ${authSeed.warning}`);
|
|
579
|
+
}
|
|
580
|
+
else {
|
|
581
|
+
this.log("- Codex login state: no change.");
|
|
582
|
+
}
|
|
558
583
|
this.log("Next: restart terminal/VS Code window only if you rely on CODEX_LB_API_KEY in your own scripts.");
|
|
559
584
|
});
|
|
560
585
|
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export type EnsureCodexApiKeyAuthResult = {
|
|
2
|
+
action: "seeded" | "updated" | "already_seeded" | "already_present" | "skipped";
|
|
3
|
+
warning?: string;
|
|
4
|
+
};
|
|
5
|
+
export type CleanupCodexApiKeyAuthResult = {
|
|
6
|
+
action: "removed" | "preserved" | "none";
|
|
7
|
+
warning?: string;
|
|
8
|
+
};
|
|
9
|
+
export declare function ensureCodexApiKeyAuth(params: {
|
|
10
|
+
codexHome: string;
|
|
11
|
+
apiKey: string;
|
|
12
|
+
}): Promise<EnsureCodexApiKeyAuthResult>;
|
|
13
|
+
export declare function cleanupSeededCodexApiKeyAuth(params: {
|
|
14
|
+
codexHome: string;
|
|
15
|
+
}): Promise<CleanupCodexApiKeyAuthResult>;
|
|
@@ -0,0 +1,190 @@
|
|
|
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.ensureCodexApiKeyAuth = ensureCodexApiKeyAuth;
|
|
7
|
+
exports.cleanupSeededCodexApiKeyAuth = cleanupSeededCodexApiKeyAuth;
|
|
8
|
+
const node_crypto_1 = require("node:crypto");
|
|
9
|
+
const promises_1 = __importDefault(require("node:fs/promises"));
|
|
10
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
11
|
+
const AUTH_FILE = "auth.json";
|
|
12
|
+
const AUTH_STATE_FILE = "theclawbay.auth-state.json";
|
|
13
|
+
const OPENAI_API_KEY_FIELD = "OPENAI_API_KEY";
|
|
14
|
+
function objectRecordOr(value) {
|
|
15
|
+
if (typeof value === "object" && value !== null && !Array.isArray(value)) {
|
|
16
|
+
return value;
|
|
17
|
+
}
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
function stableStringify(value) {
|
|
21
|
+
if (Array.isArray(value)) {
|
|
22
|
+
return `[${value.map((entry) => stableStringify(entry)).join(",")}]`;
|
|
23
|
+
}
|
|
24
|
+
if (value && typeof value === "object") {
|
|
25
|
+
const obj = value;
|
|
26
|
+
const keys = Object.keys(obj).sort();
|
|
27
|
+
return `{${keys.map((key) => `${JSON.stringify(key)}:${stableStringify(obj[key])}`).join(",")}}`;
|
|
28
|
+
}
|
|
29
|
+
return JSON.stringify(value);
|
|
30
|
+
}
|
|
31
|
+
function fingerprintAuthDocument(document) {
|
|
32
|
+
return (0, node_crypto_1.createHash)("sha256").update(stableStringify(document)).digest("hex");
|
|
33
|
+
}
|
|
34
|
+
function extractApiKey(document) {
|
|
35
|
+
const value = document[OPENAI_API_KEY_FIELD];
|
|
36
|
+
if (typeof value !== "string")
|
|
37
|
+
return null;
|
|
38
|
+
const trimmed = value.trim();
|
|
39
|
+
return trimmed ? trimmed : null;
|
|
40
|
+
}
|
|
41
|
+
function hasTokenData(document) {
|
|
42
|
+
const value = document.tokens;
|
|
43
|
+
return value !== undefined && value !== null;
|
|
44
|
+
}
|
|
45
|
+
function hasUsableAuth(document) {
|
|
46
|
+
return Boolean(extractApiKey(document)) || hasTokenData(document);
|
|
47
|
+
}
|
|
48
|
+
function buildSeededAuthDocument(apiKey) {
|
|
49
|
+
return {
|
|
50
|
+
[OPENAI_API_KEY_FIELD]: apiKey,
|
|
51
|
+
tokens: null,
|
|
52
|
+
last_refresh: null,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
async function readJsonTextIfExists(filePath) {
|
|
56
|
+
try {
|
|
57
|
+
const raw = await promises_1.default.readFile(filePath, "utf8");
|
|
58
|
+
return { parsed: JSON.parse(raw), raw };
|
|
59
|
+
}
|
|
60
|
+
catch (error) {
|
|
61
|
+
const err = error;
|
|
62
|
+
if (err.code === "ENOENT")
|
|
63
|
+
return null;
|
|
64
|
+
throw error;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
async function removeFileIfExists(filePath) {
|
|
68
|
+
try {
|
|
69
|
+
await promises_1.default.unlink(filePath);
|
|
70
|
+
}
|
|
71
|
+
catch (error) {
|
|
72
|
+
const err = error;
|
|
73
|
+
if (err.code !== "ENOENT")
|
|
74
|
+
throw error;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
async function readSeedState(filePath) {
|
|
78
|
+
const doc = await readJsonTextIfExists(filePath);
|
|
79
|
+
if (!doc)
|
|
80
|
+
return null;
|
|
81
|
+
const obj = objectRecordOr(doc.parsed);
|
|
82
|
+
if (!obj)
|
|
83
|
+
return null;
|
|
84
|
+
if (obj.version !== 1)
|
|
85
|
+
return null;
|
|
86
|
+
if (typeof obj.fingerprint !== "string" || !obj.fingerprint)
|
|
87
|
+
return null;
|
|
88
|
+
return {
|
|
89
|
+
version: 1,
|
|
90
|
+
fingerprint: obj.fingerprint,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
async function writeSeedState(filePath, fingerprint) {
|
|
94
|
+
await promises_1.default.mkdir(node_path_1.default.dirname(filePath), { recursive: true });
|
|
95
|
+
const contents = JSON.stringify({
|
|
96
|
+
version: 1,
|
|
97
|
+
fingerprint,
|
|
98
|
+
}, null, 2);
|
|
99
|
+
await promises_1.default.writeFile(filePath, `${contents}\n`, "utf8");
|
|
100
|
+
}
|
|
101
|
+
async function writeSeededAuth(authPath, statePath, apiKey) {
|
|
102
|
+
const next = buildSeededAuthDocument(apiKey);
|
|
103
|
+
const contents = JSON.stringify(next, null, 2);
|
|
104
|
+
await promises_1.default.mkdir(node_path_1.default.dirname(authPath), { recursive: true });
|
|
105
|
+
await promises_1.default.writeFile(authPath, `${contents}\n`, "utf8");
|
|
106
|
+
await promises_1.default.chmod(authPath, 0o600).catch(() => { });
|
|
107
|
+
await writeSeedState(statePath, fingerprintAuthDocument(next));
|
|
108
|
+
}
|
|
109
|
+
async function ensureCodexApiKeyAuth(params) {
|
|
110
|
+
const authPath = node_path_1.default.join(params.codexHome, AUTH_FILE);
|
|
111
|
+
const statePath = node_path_1.default.join(params.codexHome, AUTH_STATE_FILE);
|
|
112
|
+
const seedState = await readSeedState(statePath);
|
|
113
|
+
let existing;
|
|
114
|
+
try {
|
|
115
|
+
existing = await readJsonTextIfExists(authPath);
|
|
116
|
+
}
|
|
117
|
+
catch (error) {
|
|
118
|
+
return {
|
|
119
|
+
action: "skipped",
|
|
120
|
+
warning: `could not read local Codex auth file: ${error.message}`,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
if (!existing) {
|
|
124
|
+
await writeSeededAuth(authPath, statePath, params.apiKey);
|
|
125
|
+
return { action: "seeded" };
|
|
126
|
+
}
|
|
127
|
+
const existingDocument = objectRecordOr(existing.parsed);
|
|
128
|
+
if (!existingDocument) {
|
|
129
|
+
await removeFileIfExists(statePath);
|
|
130
|
+
return {
|
|
131
|
+
action: "skipped",
|
|
132
|
+
warning: "existing Codex auth.json is not a JSON object, so it was left unchanged.",
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
const existingFingerprint = fingerprintAuthDocument(existingDocument);
|
|
136
|
+
const seededByUs = seedState?.fingerprint === existingFingerprint;
|
|
137
|
+
const existingApiKey = extractApiKey(existingDocument);
|
|
138
|
+
if (seededByUs) {
|
|
139
|
+
if (existingApiKey === params.apiKey && !hasTokenData(existingDocument)) {
|
|
140
|
+
return { action: "already_seeded" };
|
|
141
|
+
}
|
|
142
|
+
await writeSeededAuth(authPath, statePath, params.apiKey);
|
|
143
|
+
return { action: "updated" };
|
|
144
|
+
}
|
|
145
|
+
await removeFileIfExists(statePath);
|
|
146
|
+
if (hasUsableAuth(existingDocument)) {
|
|
147
|
+
return { action: "already_present" };
|
|
148
|
+
}
|
|
149
|
+
await writeSeededAuth(authPath, statePath, params.apiKey);
|
|
150
|
+
return { action: "seeded" };
|
|
151
|
+
}
|
|
152
|
+
async function cleanupSeededCodexApiKeyAuth(params) {
|
|
153
|
+
const authPath = node_path_1.default.join(params.codexHome, AUTH_FILE);
|
|
154
|
+
const statePath = node_path_1.default.join(params.codexHome, AUTH_STATE_FILE);
|
|
155
|
+
const seedState = await readSeedState(statePath);
|
|
156
|
+
if (!seedState) {
|
|
157
|
+
return { action: "none" };
|
|
158
|
+
}
|
|
159
|
+
let existing;
|
|
160
|
+
try {
|
|
161
|
+
existing = await readJsonTextIfExists(authPath);
|
|
162
|
+
}
|
|
163
|
+
catch (error) {
|
|
164
|
+
await removeFileIfExists(statePath);
|
|
165
|
+
return {
|
|
166
|
+
action: "preserved",
|
|
167
|
+
warning: `could not read local Codex auth file, so it was preserved: ${error.message}`,
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
if (!existing) {
|
|
171
|
+
await removeFileIfExists(statePath);
|
|
172
|
+
return { action: "none" };
|
|
173
|
+
}
|
|
174
|
+
const existingDocument = objectRecordOr(existing.parsed);
|
|
175
|
+
if (!existingDocument) {
|
|
176
|
+
await removeFileIfExists(statePath);
|
|
177
|
+
return {
|
|
178
|
+
action: "preserved",
|
|
179
|
+
warning: "existing Codex auth.json is not a JSON object, so it was preserved.",
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
const existingFingerprint = fingerprintAuthDocument(existingDocument);
|
|
183
|
+
if (existingFingerprint !== seedState.fingerprint) {
|
|
184
|
+
await removeFileIfExists(statePath);
|
|
185
|
+
return { action: "preserved" };
|
|
186
|
+
}
|
|
187
|
+
await removeFileIfExists(authPath);
|
|
188
|
+
await removeFileIfExists(statePath);
|
|
189
|
+
return { action: "removed" };
|
|
190
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export type EnsureCodexModelCacheResult = {
|
|
2
|
-
action: "seeded" | "created" | "already_present" | "already_seeded" | "skipped";
|
|
2
|
+
action: "seeded" | "created" | "refreshed" | "already_present" | "already_seeded" | "skipped";
|
|
3
3
|
warning?: string;
|
|
4
4
|
};
|
|
5
5
|
export type CleanupCodexModelCacheResult = {
|
|
@@ -196,6 +196,19 @@ function buildSeedModel(models) {
|
|
|
196
196
|
}
|
|
197
197
|
return normalizeSeedModel(null);
|
|
198
198
|
}
|
|
199
|
+
function setCacheFreshnessMetadata(doc, clientVersion) {
|
|
200
|
+
let changed = false;
|
|
201
|
+
const now = new Date().toISOString();
|
|
202
|
+
if (doc.fetched_at !== now) {
|
|
203
|
+
doc.fetched_at = now;
|
|
204
|
+
changed = true;
|
|
205
|
+
}
|
|
206
|
+
if (clientVersion && doc.client_version !== clientVersion) {
|
|
207
|
+
doc.client_version = clientVersion;
|
|
208
|
+
changed = true;
|
|
209
|
+
}
|
|
210
|
+
return changed;
|
|
211
|
+
}
|
|
199
212
|
function parseCodexVersion(stdout) {
|
|
200
213
|
const match = stdout.match(/\b(\d+\.\d+\.\d+)\b/);
|
|
201
214
|
return match ? match[1] : null;
|
|
@@ -356,31 +369,46 @@ async function ensureCodexModelCacheHasGpt54(params) {
|
|
|
356
369
|
warning: "Codex models cache exists but is not valid JSON in the expected format; skipped GPT-5.4 seed.",
|
|
357
370
|
};
|
|
358
371
|
}
|
|
372
|
+
const clientVersion = (await inferCodexClientVersion(params.codexHome)) ??
|
|
373
|
+
(typeof doc.client_version === "string" && doc.client_version ? doc.client_version : null);
|
|
359
374
|
const existingModel = findModel(doc.models, TARGET_MODEL_ID);
|
|
360
375
|
if (existingModel) {
|
|
361
376
|
const currentFingerprint = fingerprintModel(existingModel);
|
|
362
377
|
if (existingState && existingState.fingerprint === currentFingerprint && !hasSeedMarker(existingModel)) {
|
|
363
378
|
const seededModel = applySeedMarker(existingModel);
|
|
364
379
|
doc.models = doc.models.map((entry) => (entry.slug === TARGET_MODEL_ID ? seededModel : entry));
|
|
380
|
+
setCacheFreshnessMetadata(doc, clientVersion);
|
|
365
381
|
await promises_1.default.writeFile(cachePath, `${JSON.stringify(doc, null, 2)}\n`, "utf8");
|
|
366
382
|
await writePatchState(statePath, fingerprintModel(seededModel));
|
|
367
383
|
return { action: "seeded" };
|
|
368
384
|
}
|
|
369
385
|
if (hasSeedMarker(existingModel)) {
|
|
386
|
+
const docChanged = setCacheFreshnessMetadata(doc, clientVersion);
|
|
370
387
|
if (!existingState || existingState.fingerprint !== currentFingerprint) {
|
|
371
388
|
await writePatchState(statePath, currentFingerprint);
|
|
389
|
+
await promises_1.default.writeFile(cachePath, `${JSON.stringify(doc, null, 2)}\n`, "utf8");
|
|
390
|
+
return { action: "refreshed" };
|
|
391
|
+
}
|
|
392
|
+
if (docChanged) {
|
|
393
|
+
await promises_1.default.writeFile(cachePath, `${JSON.stringify(doc, null, 2)}\n`, "utf8");
|
|
394
|
+
return { action: "refreshed" };
|
|
372
395
|
}
|
|
373
396
|
return { action: "already_seeded" };
|
|
374
397
|
}
|
|
375
398
|
if (existingState && existingState.fingerprint !== currentFingerprint) {
|
|
376
399
|
await removeFileIfExists(statePath);
|
|
377
400
|
}
|
|
401
|
+
if (setCacheFreshnessMetadata(doc, clientVersion)) {
|
|
402
|
+
await promises_1.default.writeFile(cachePath, `${JSON.stringify(doc, null, 2)}\n`, "utf8");
|
|
403
|
+
return { action: "refreshed" };
|
|
404
|
+
}
|
|
378
405
|
return {
|
|
379
406
|
action: existingState && existingState.fingerprint === currentFingerprint ? "already_seeded" : "already_present",
|
|
380
407
|
};
|
|
381
408
|
}
|
|
382
409
|
const seed = buildSeedModel(doc.models);
|
|
383
410
|
doc.models = [seed, ...doc.models];
|
|
411
|
+
setCacheFreshnessMetadata(doc, clientVersion);
|
|
384
412
|
await promises_1.default.writeFile(cachePath, `${JSON.stringify(doc, null, 2)}\n`, "utf8");
|
|
385
413
|
await writePatchState(statePath, fingerprintModel(seed));
|
|
386
414
|
return { action: "seeded" };
|