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.
@@ -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
  }
@@ -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 = false",
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
- this.log("- Codex login state: unchanged");
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" };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "theclawbay",
3
- "version": "0.3.7",
3
+ "version": "0.3.9",
4
4
  "description": "The Claw Bay CLI: one-time API-key setup for direct Codex access.",
5
5
  "license": "MIT",
6
6
  "bin": {