theclawbay 0.3.6 → 0.3.8

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");
@@ -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,
@@ -554,7 +559,24 @@ class SetupCommand extends base_command_1.BaseCommand {
554
559
  else {
555
560
  this.log("- OpenCode: not detected (skipped)");
556
561
  }
557
- this.log("- Codex login state: unchanged");
562
+ if (authSeed.action === "seeded") {
563
+ this.log("- Codex login state: seeded local API-key auth for Codex UI.");
564
+ }
565
+ else if (authSeed.action === "updated") {
566
+ this.log("- Codex login state: refreshed the Claw Bay API-key auth seed.");
567
+ }
568
+ else if (authSeed.action === "already_seeded") {
569
+ this.log("- Codex login state: the Claw Bay API-key auth was already seeded.");
570
+ }
571
+ else if (authSeed.action === "already_present") {
572
+ this.log("- Codex login state: preserved existing local Codex auth.");
573
+ }
574
+ else if (authSeed.warning) {
575
+ this.log(`- Codex login state: ${authSeed.warning}`);
576
+ }
577
+ else {
578
+ this.log("- Codex login state: no change.");
579
+ }
558
580
  this.log("Next: restart terminal/VS Code window only if you rely on CODEX_LB_API_KEY in your own scripts.");
559
581
  });
560
582
  }
@@ -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
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "theclawbay",
3
- "version": "0.3.6",
3
+ "version": "0.3.8",
4
4
  "description": "The Claw Bay CLI: one-time API-key setup for direct Codex access.",
5
5
  "license": "MIT",
6
6
  "bin": {