theclawbay 0.2.13 → 0.3.2

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/README.md CHANGED
@@ -60,20 +60,29 @@ After setup, restart terminal/VS Code once only if your own shell workflows depe
60
60
  - Full ChatGPT account model-picker behavior is tied to ChatGPT auth mode.
61
61
  - `theclawbay setup` keeps your existing Codex login state unchanged on purpose (so local history context is preserved and setup stays non-destructive).
62
62
 
63
- ## Run Relay (Optional)
63
+ ## Link Only (Optional)
64
64
 
65
- Only needed as a fallback compatibility mode:
65
+ If you only want to save backend/API-key state without applying local client config yet:
66
66
 
67
67
  ```sh
68
68
  theclawbay link --api-key <apiKey>
69
- theclawbay proxy
70
69
  ```
71
70
 
72
- By default this starts a local relay on `http://127.0.0.1:2455` and forwards to:
71
+ ## Logout
73
72
 
74
- - `https://theclawbay.com/api/codex-auth/v1/proxy/...`
73
+ To remove local The Claw Bay API-key auth state from this machine:
75
74
 
76
- The command auto-detects whether `openclaw` and/or `opencode` are installed and configures them automatically.
75
+ ```sh
76
+ theclawbay logout
77
+ ```
78
+
79
+ ## Release Notes (Maintainers)
80
+
81
+ If a previous publish failed after version bump, publish the current local version without bumping again:
82
+
83
+ ```sh
84
+ npm run release:publish
85
+ ```
77
86
 
78
87
  ## Notes
79
88
 
@@ -20,7 +20,7 @@ class LinkCommand extends base_command_1.BaseCommand {
20
20
  });
21
21
  this.log(`Linked. Managed config written to ${paths_1.managedConfigPath}`);
22
22
  this.log(`Backend: ${backendUrl}`);
23
- this.log(`Run "theclawbay proxy" to start the local relay.`);
23
+ this.log(`Run "theclawbay setup" to configure Codex/OpenClaw/OpenCode on this machine.`);
24
24
  });
25
25
  }
26
26
  }
@@ -0,0 +1,5 @@
1
+ import { BaseCommand } from "../lib/base-command";
2
+ export default class LogoutCommand extends BaseCommand {
3
+ static description: string;
4
+ run(): Promise<void>;
5
+ }
@@ -0,0 +1,281 @@
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
+ const promises_1 = __importDefault(require("node:fs/promises"));
7
+ const node_os_1 = __importDefault(require("node:os"));
8
+ const node_path_1 = __importDefault(require("node:path"));
9
+ const base_command_1 = require("../lib/base-command");
10
+ const codex_history_migration_1 = require("../lib/codex-history-migration");
11
+ const paths_1 = require("../lib/config/paths");
12
+ const OPENAI_PROVIDER_ID = "openai";
13
+ const DEFAULT_PROVIDER_ID = "theclawbay";
14
+ const LEGACY_PROVIDER_ID = "codex-lb";
15
+ const WAN_PROVIDER_ID = "theclawbay-wan";
16
+ const MANAGED_START = "# theclawbay-managed:start";
17
+ const MANAGED_END = "# theclawbay-managed:end";
18
+ const SHELL_START = "# theclawbay-shell-managed:start";
19
+ const SHELL_END = "# theclawbay-shell-managed:end";
20
+ const ENV_FILE = node_path_1.default.join(node_os_1.default.homedir(), ".config", "theclawbay", "env");
21
+ const ENV_KEY_NAME = "CODEX_LB_API_KEY";
22
+ const MIGRATION_STATE_FILE = node_path_1.default.join(paths_1.codexDir, "theclawbay.migration.json");
23
+ const HISTORY_PROVIDER_NEUTRALIZE_SOURCES = new Set([
24
+ OPENAI_PROVIDER_ID,
25
+ WAN_PROVIDER_ID,
26
+ DEFAULT_PROVIDER_ID,
27
+ LEGACY_PROVIDER_ID,
28
+ ]);
29
+ const HISTORY_PROVIDER_DB_MIGRATE_SOURCES = [DEFAULT_PROVIDER_ID, WAN_PROVIDER_ID, LEGACY_PROVIDER_ID];
30
+ function removeManagedBlock(source, start, end) {
31
+ const markerStart = source.indexOf(start);
32
+ if (markerStart < 0)
33
+ return source;
34
+ const markerEnd = source.indexOf(end, markerStart);
35
+ if (markerEnd < 0)
36
+ return `${source.slice(0, markerStart).trimEnd()}\n`;
37
+ return `${(source.slice(0, markerStart) + source.slice(markerEnd + end.length)).trimEnd()}\n`;
38
+ }
39
+ function removeProviderTable(source, providerId) {
40
+ const header = `[model_providers.${providerId}]`;
41
+ const lines = source.split(/\r?\n/);
42
+ const output = [];
43
+ for (let i = 0; i < lines.length; i++) {
44
+ if ((lines[i] ?? "").trim() !== header) {
45
+ output.push(lines[i] ?? "");
46
+ continue;
47
+ }
48
+ i++;
49
+ while (i < lines.length && !/^\s*\[[^\]]+\]\s*$/.test(lines[i] ?? ""))
50
+ i++;
51
+ i--;
52
+ }
53
+ return `${output.join("\n").trimEnd()}\n`;
54
+ }
55
+ function removeTopLevelProviderSelection(source) {
56
+ const lines = source.split(/\r?\n/);
57
+ const filtered = [];
58
+ for (let i = 0; i < lines.length; i++) {
59
+ const line = lines[i] ?? "";
60
+ if (/^\s*\[/.test(line)) {
61
+ filtered.push(...lines.slice(i));
62
+ break;
63
+ }
64
+ if (/^\s*#/.test(line) || !line.trim()) {
65
+ filtered.push(line);
66
+ continue;
67
+ }
68
+ const match = line.match(/^\s*model_provider\s*=\s*"([^"]+)"\s*$/);
69
+ if (!match) {
70
+ filtered.push(line);
71
+ continue;
72
+ }
73
+ if (match[1] === DEFAULT_PROVIDER_ID || match[1] === LEGACY_PROVIDER_ID)
74
+ continue;
75
+ filtered.push(line);
76
+ }
77
+ return `${filtered.join("\n").trimEnd()}\n`;
78
+ }
79
+ async function readFileIfExists(filePath) {
80
+ try {
81
+ return await promises_1.default.readFile(filePath, "utf8");
82
+ }
83
+ catch (error) {
84
+ const err = error;
85
+ if (err.code === "ENOENT")
86
+ return null;
87
+ throw error;
88
+ }
89
+ }
90
+ async function removeFileIfExists(filePath) {
91
+ try {
92
+ await promises_1.default.unlink(filePath);
93
+ return true;
94
+ }
95
+ catch (error) {
96
+ const err = error;
97
+ if (err.code === "ENOENT")
98
+ return false;
99
+ throw error;
100
+ }
101
+ }
102
+ async function writeIfChanged(filePath, next, previous) {
103
+ if (next === previous)
104
+ return false;
105
+ await promises_1.default.writeFile(filePath, next, "utf8");
106
+ return true;
107
+ }
108
+ function objectRecordOr(value, fallback) {
109
+ if (typeof value === "object" && value !== null && !Array.isArray(value))
110
+ return { ...value };
111
+ return fallback;
112
+ }
113
+ async function cleanupCodexConfig() {
114
+ const configPath = node_path_1.default.join(paths_1.codexDir, "config.toml");
115
+ const existing = await readFileIfExists(configPath);
116
+ if (existing === null)
117
+ return false;
118
+ let next = existing;
119
+ next = removeManagedBlock(next, MANAGED_START, MANAGED_END);
120
+ next = removeProviderTable(next, DEFAULT_PROVIDER_ID);
121
+ next = removeProviderTable(next, LEGACY_PROVIDER_ID);
122
+ next = removeTopLevelProviderSelection(next);
123
+ return writeIfChanged(configPath, next, existing);
124
+ }
125
+ async function cleanupShellFiles() {
126
+ const updated = [];
127
+ const shellRcPaths = [".bashrc", ".zshrc", ".profile"].map((name) => node_path_1.default.join(node_os_1.default.homedir(), name));
128
+ for (const rcPath of shellRcPaths) {
129
+ const existing = await readFileIfExists(rcPath);
130
+ if (existing === null)
131
+ continue;
132
+ const next = removeManagedBlock(existing, SHELL_START, SHELL_END);
133
+ if (await writeIfChanged(rcPath, next, existing))
134
+ updated.push(rcPath);
135
+ }
136
+ return updated;
137
+ }
138
+ async function cleanupVsCodeHooks() {
139
+ const updated = [];
140
+ const homes = [".vscode-server", ".vscode-server-insiders"];
141
+ for (const home of homes) {
142
+ const setupPath = node_path_1.default.join(node_os_1.default.homedir(), home, "server-env-setup");
143
+ const existing = await readFileIfExists(setupPath);
144
+ if (existing === null)
145
+ continue;
146
+ const next = removeManagedBlock(existing, SHELL_START, SHELL_END);
147
+ if (await writeIfChanged(setupPath, next, existing))
148
+ updated.push(setupPath);
149
+ }
150
+ return updated;
151
+ }
152
+ async function cleanupOpenClawConfig() {
153
+ const configPath = node_path_1.default.join(node_os_1.default.homedir(), ".openclaw", "openclaw.json");
154
+ const existingRaw = await readFileIfExists(configPath);
155
+ if (existingRaw === null || !existingRaw.trim())
156
+ return false;
157
+ let doc;
158
+ try {
159
+ doc = objectRecordOr(JSON.parse(existingRaw), {});
160
+ }
161
+ catch {
162
+ return false;
163
+ }
164
+ let changed = false;
165
+ const modelsRoot = objectRecordOr(doc.models, {});
166
+ const providersRoot = objectRecordOr(modelsRoot.providers, {});
167
+ for (const id of [DEFAULT_PROVIDER_ID, LEGACY_PROVIDER_ID]) {
168
+ if (id in providersRoot) {
169
+ delete providersRoot[id];
170
+ changed = true;
171
+ }
172
+ }
173
+ modelsRoot.providers = providersRoot;
174
+ doc.models = modelsRoot;
175
+ const agentsRoot = objectRecordOr(doc.agents, {});
176
+ const defaultsRoot = objectRecordOr(agentsRoot.defaults, {});
177
+ const modelRoot = objectRecordOr(defaultsRoot.model, {});
178
+ const primary = modelRoot.primary;
179
+ if (typeof primary === "string" &&
180
+ (primary.startsWith(`${DEFAULT_PROVIDER_ID}/`) || primary.startsWith(`${LEGACY_PROVIDER_ID}/`))) {
181
+ delete modelRoot.primary;
182
+ changed = true;
183
+ }
184
+ defaultsRoot.model = modelRoot;
185
+ agentsRoot.defaults = defaultsRoot;
186
+ doc.agents = agentsRoot;
187
+ if (!changed)
188
+ return false;
189
+ await promises_1.default.writeFile(configPath, `${JSON.stringify(doc, null, 2)}\n`, "utf8");
190
+ return true;
191
+ }
192
+ async function cleanupOpenCodeConfig() {
193
+ const configPath = node_path_1.default.join(node_os_1.default.homedir(), ".config", "opencode", "opencode.json");
194
+ const existingRaw = await readFileIfExists(configPath);
195
+ if (existingRaw === null || !existingRaw.trim())
196
+ return false;
197
+ let doc;
198
+ try {
199
+ doc = objectRecordOr(JSON.parse(existingRaw), {});
200
+ }
201
+ catch {
202
+ return false;
203
+ }
204
+ let changed = false;
205
+ const providerRoot = objectRecordOr(doc.provider, {});
206
+ for (const id of [DEFAULT_PROVIDER_ID, LEGACY_PROVIDER_ID]) {
207
+ if (id in providerRoot) {
208
+ delete providerRoot[id];
209
+ changed = true;
210
+ }
211
+ }
212
+ doc.provider = providerRoot;
213
+ const model = doc.model;
214
+ if (typeof model === "string" &&
215
+ (model.startsWith(`${DEFAULT_PROVIDER_ID}/`) || model.startsWith(`${LEGACY_PROVIDER_ID}/`))) {
216
+ delete doc.model;
217
+ changed = true;
218
+ }
219
+ if (!changed)
220
+ return false;
221
+ await promises_1.default.writeFile(configPath, `${JSON.stringify(doc, null, 2)}\n`, "utf8");
222
+ return true;
223
+ }
224
+ class LogoutCommand extends base_command_1.BaseCommand {
225
+ async run() {
226
+ await this.runSafe(async () => {
227
+ const deletedManagedPaths = (await Promise.all([
228
+ removeFileIfExists(paths_1.managedConfigPath),
229
+ removeFileIfExists(paths_1.legacyManagedConfigPathClayBay),
230
+ removeFileIfExists(paths_1.legacyManagedConfigPathCodexAuth),
231
+ ])).filter(Boolean).length;
232
+ const removedEnvFile = await removeFileIfExists(ENV_FILE);
233
+ const updatedShellFiles = await cleanupShellFiles();
234
+ const updatedVsCodeHooks = await cleanupVsCodeHooks();
235
+ const updatedCodexConfig = await cleanupCodexConfig();
236
+ const updatedOpenClawConfig = await cleanupOpenClawConfig();
237
+ const updatedOpenCodeConfig = await cleanupOpenCodeConfig();
238
+ const sessionMigration = await (0, codex_history_migration_1.migrateSessionProviders)({
239
+ codexHome: paths_1.codexDir,
240
+ migrationStateFile: MIGRATION_STATE_FILE,
241
+ neutralizeSources: HISTORY_PROVIDER_NEUTRALIZE_SOURCES,
242
+ });
243
+ const stateDbMigration = await (0, codex_history_migration_1.migrateStateDbProviders)({
244
+ codexHome: paths_1.codexDir,
245
+ targetProvider: OPENAI_PROVIDER_ID,
246
+ sourceProviders: HISTORY_PROVIDER_DB_MIGRATE_SOURCES,
247
+ });
248
+ delete process.env[ENV_KEY_NAME];
249
+ this.log("Logout complete");
250
+ this.log(`- Managed configs removed: ${deletedManagedPaths}`);
251
+ this.log(`- API key env file removed: ${removedEnvFile ? "yes" : "no"}`);
252
+ this.log(`- Shell profiles updated: ${updatedShellFiles.length ? updatedShellFiles.join(", ") : "none"}`);
253
+ this.log(`- VS Code env hooks updated: ${updatedVsCodeHooks.length ? updatedVsCodeHooks.join(", ") : "none"}`);
254
+ this.log(`- Codex config cleaned: ${updatedCodexConfig ? "yes" : "no"}`);
255
+ if (sessionMigration.rewritten > 0) {
256
+ this.log(`- Conversations: updated ${sessionMigration.rewritten}/${sessionMigration.scanned} local sessions for cross-provider visibility.`);
257
+ }
258
+ else if (sessionMigration.fastSkipped) {
259
+ this.log("- Conversations: already migrated (fast check).");
260
+ }
261
+ else {
262
+ this.log("- Conversations: no local sessions required migration.");
263
+ }
264
+ if (sessionMigration.retimed > 0) {
265
+ this.log(`- Conversation ordering: repaired timestamps on ${sessionMigration.retimed} local sessions.`);
266
+ }
267
+ if (stateDbMigration.updated > 0) {
268
+ this.log(`- Conversation cache: relabeled ${stateDbMigration.updated} local Codex history rows back to OpenAI.`);
269
+ }
270
+ else if (stateDbMigration.failed.length > 0) {
271
+ const detail = stateDbMigration.warning ? ` ${stateDbMigration.warning}` : "";
272
+ this.log(`- Conversation cache: could not update local Codex history DB.${detail}`);
273
+ }
274
+ this.log(`- OpenClaw config cleaned: ${updatedOpenClawConfig ? "yes" : "no"}`);
275
+ this.log(`- OpenCode config cleaned: ${updatedOpenCodeConfig ? "yes" : "no"}`);
276
+ this.log("Note: restart terminals/VS Code windows to clear already-loaded shell environment.");
277
+ });
278
+ }
279
+ }
280
+ LogoutCommand.description = "Log out from local The Claw Bay API-key auth on this machine";
281
+ exports.default = LogoutCommand;
@@ -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_history_migration_1 = require("../lib/codex-history-migration");
12
13
  const paths_1 = require("../lib/config/paths");
13
14
  const api_key_1 = require("../lib/managed/api-key");
14
15
  const config_1 = require("../lib/managed/config");
@@ -16,10 +17,12 @@ const errors_1 = require("../lib/managed/errors");
16
17
  const DEFAULT_BACKEND_URL = "https://theclawbay.com";
17
18
  const DEFAULT_PROVIDER_ID = "theclawbay";
18
19
  const LEGACY_PROVIDER_ID = "codex-lb";
19
- const DEFAULT_CODEX_MODEL = "gpt-5.2-codex";
20
- const DEFAULT_OPENCLAW_MODEL = "gpt-5.3-codex";
21
- const DEFAULT_MODELS = ["gpt-5.3-codex", "gpt-5.3-codex-spark", "gpt-5.2-codex", "gpt-5.1-codex"];
20
+ const DEFAULT_CODEX_MODEL = "gpt-5.4";
21
+ const DEFAULT_OPENCLAW_MODEL = "gpt-5.4";
22
+ const DEFAULT_MODELS = ["gpt-5.4", "gpt-5.3-codex", "gpt-5.3-codex-spark", "gpt-5.2-codex", "gpt-5.1-codex"];
22
23
  const PREFERRED_MODELS = [
24
+ "gpt-5.4",
25
+ "gpt-5.4-codex",
23
26
  "gpt-5.3-codex",
24
27
  "gpt-5.3-codex-spark",
25
28
  "gpt-5.2-codex",
@@ -28,6 +31,8 @@ const PREFERRED_MODELS = [
28
31
  "gpt-5.1-codex",
29
32
  ];
30
33
  const MODEL_DISPLAY_NAMES = {
34
+ "gpt-5.4": "GPT-5.4",
35
+ "gpt-5.4-codex": "GPT-5.4",
31
36
  "gpt-5.3-codex": "GPT-5.3 Codex",
32
37
  "gpt-5.3-codex-spark": "GPT-5.3 Codex Spark",
33
38
  "gpt-5.2-codex": "GPT-5.2 Codex",
@@ -44,6 +49,7 @@ const SHELL_START = "# theclawbay-shell-managed:start";
44
49
  const SHELL_END = "# theclawbay-shell-managed:end";
45
50
  const OPENCLAW_PROVIDER_ID = DEFAULT_PROVIDER_ID;
46
51
  const HISTORY_PROVIDER_NEUTRALIZE_SOURCES = new Set(["openai", "theclawbay-wan", DEFAULT_PROVIDER_ID, LEGACY_PROVIDER_ID]);
52
+ const HISTORY_PROVIDER_DB_MIGRATE_SOURCES = ["openai", "theclawbay-wan", LEGACY_PROVIDER_ID];
47
53
  function trimTrailingSlash(value) {
48
54
  return value.replace(/\/+$/g, "");
49
55
  }
@@ -113,220 +119,6 @@ function shellQuote(value) {
113
119
  function modelDisplayName(modelId) {
114
120
  return MODEL_DISPLAY_NAMES[modelId] ?? modelId;
115
121
  }
116
- function renderProgressBar(current, total) {
117
- if (total <= 0)
118
- return "";
119
- const width = 24;
120
- const ratio = Math.max(0, Math.min(1, current / total));
121
- const filled = Math.round(width * ratio);
122
- return `[${"#".repeat(filled)}${"-".repeat(width - filled)}] ${Math.round(ratio * 100)
123
- .toString()
124
- .padStart(3, " ")}%`;
125
- }
126
- function inferRolloutUpdatedAt(source) {
127
- const lines = source.split(/\r?\n/);
128
- for (let i = lines.length - 1; i >= 0; i--) {
129
- const trimmed = (lines[i] ?? "").trim();
130
- if (!trimmed)
131
- continue;
132
- try {
133
- const parsed = JSON.parse(trimmed);
134
- if (typeof parsed.timestamp !== "string")
135
- continue;
136
- const ms = Date.parse(parsed.timestamp);
137
- if (!Number.isFinite(ms))
138
- continue;
139
- return new Date(ms);
140
- }
141
- catch {
142
- continue;
143
- }
144
- }
145
- return null;
146
- }
147
- async function listSessionRollouts(root) {
148
- const results = [];
149
- const walk = async (dir) => {
150
- let entries = [];
151
- try {
152
- entries = await promises_1.default.readdir(dir, { withFileTypes: true });
153
- }
154
- catch (error) {
155
- const err = error;
156
- if (err.code === "ENOENT")
157
- return;
158
- throw error;
159
- }
160
- for (const entry of entries) {
161
- const nextPath = node_path_1.default.join(dir, entry.name);
162
- if (entry.isDirectory()) {
163
- await walk(nextPath);
164
- continue;
165
- }
166
- if (entry.isFile() && nextPath.endsWith(".jsonl")) {
167
- results.push(nextPath);
168
- }
169
- }
170
- };
171
- await walk(root);
172
- return results;
173
- }
174
- async function readMigrationState() {
175
- try {
176
- const raw = await promises_1.default.readFile(MIGRATION_STATE_FILE, "utf8");
177
- const parsed = JSON.parse(raw);
178
- if (parsed.version === 1 &&
179
- typeof parsed.fileCount === "number" &&
180
- Number.isFinite(parsed.fileCount) &&
181
- typeof parsed.maxMtimeMs === "number" &&
182
- Number.isFinite(parsed.maxMtimeMs)) {
183
- return {
184
- version: 1,
185
- fileCount: Math.max(0, Math.floor(parsed.fileCount)),
186
- maxMtimeMs: Math.max(0, Math.floor(parsed.maxMtimeMs)),
187
- };
188
- }
189
- return null;
190
- }
191
- catch {
192
- return null;
193
- }
194
- }
195
- async function writeMigrationState(state) {
196
- await promises_1.default.mkdir(node_path_1.default.dirname(MIGRATION_STATE_FILE), { recursive: true });
197
- await promises_1.default.writeFile(MIGRATION_STATE_FILE, `${JSON.stringify(state, null, 2)}\n`, "utf8");
198
- }
199
- async function computeSessionSnapshot(files) {
200
- let maxMtimeMs = 0;
201
- for (const file of files) {
202
- try {
203
- const stats = await promises_1.default.stat(file);
204
- const mtimeMs = Math.floor(stats.mtimeMs);
205
- if (mtimeMs > maxMtimeMs)
206
- maxMtimeMs = mtimeMs;
207
- }
208
- catch {
209
- // ignore transient missing file/race
210
- }
211
- }
212
- return { fileCount: files.length, maxMtimeMs };
213
- }
214
- async function migrateSessionProviders(params) {
215
- const sessionsRoot = node_path_1.default.join(params.codexHome, "sessions");
216
- const files = await listSessionRollouts(sessionsRoot);
217
- const snapshot = await computeSessionSnapshot(files);
218
- const previousState = await readMigrationState();
219
- if (previousState &&
220
- previousState.fileCount === snapshot.fileCount &&
221
- previousState.maxMtimeMs === snapshot.maxMtimeMs) {
222
- return { scanned: 0, rewritten: 0, skipped: 0, retimed: 0, fastSkipped: true };
223
- }
224
- let rewritten = 0;
225
- let skipped = 0;
226
- let retimed = 0;
227
- const useProgress = process.stdout.isTTY && files.length > 0;
228
- const drawProgress = (current) => {
229
- if (!useProgress)
230
- return;
231
- const bar = renderProgressBar(current, files.length);
232
- process.stdout.write(`\rMigrating conversations ${current}/${files.length} ${bar}`);
233
- if (current >= files.length)
234
- process.stdout.write("\n");
235
- };
236
- drawProgress(0);
237
- for (let i = 0; i < files.length; i++) {
238
- const file = files[i];
239
- let source = "";
240
- let originalStats = null;
241
- try {
242
- source = await promises_1.default.readFile(file, "utf8");
243
- originalStats = await promises_1.default.stat(file);
244
- }
245
- catch {
246
- skipped++;
247
- drawProgress(i + 1);
248
- continue;
249
- }
250
- const desiredMtime = inferRolloutUpdatedAt(source) ?? originalStats.mtime;
251
- const desiredAtime = originalStats.atime;
252
- const maybeRetainMtime = async () => {
253
- const diffMs = Math.abs(desiredMtime.getTime() - originalStats.mtime.getTime());
254
- if (diffMs <= 1500)
255
- return;
256
- try {
257
- await promises_1.default.utimes(file, desiredAtime, desiredMtime);
258
- retimed++;
259
- }
260
- catch {
261
- // best-effort only
262
- }
263
- };
264
- if (!source.trim()) {
265
- skipped++;
266
- await maybeRetainMtime();
267
- drawProgress(i + 1);
268
- continue;
269
- }
270
- const lines = source.split(/\r?\n/);
271
- const firstNonEmptyIndex = lines.findIndex((line) => line.trim().length > 0);
272
- if (firstNonEmptyIndex < 0) {
273
- skipped++;
274
- drawProgress(i + 1);
275
- continue;
276
- }
277
- let parsed;
278
- try {
279
- parsed = JSON.parse(lines[firstNonEmptyIndex] ?? "");
280
- }
281
- catch {
282
- skipped++;
283
- drawProgress(i + 1);
284
- continue;
285
- }
286
- if ((parsed.type ?? "") !== "session_meta") {
287
- skipped++;
288
- drawProgress(i + 1);
289
- continue;
290
- }
291
- const payload = parsed.payload;
292
- if (!payload || typeof payload !== "object") {
293
- skipped++;
294
- drawProgress(i + 1);
295
- continue;
296
- }
297
- const currentProvider = payload.model_provider;
298
- if (typeof currentProvider !== "string") {
299
- skipped++;
300
- drawProgress(i + 1);
301
- continue;
302
- }
303
- if (!HISTORY_PROVIDER_NEUTRALIZE_SOURCES.has(currentProvider)) {
304
- skipped++;
305
- drawProgress(i + 1);
306
- continue;
307
- }
308
- // Remove explicit provider tag so the same thread stays visible across provider contexts.
309
- delete payload.model_provider;
310
- lines[firstNonEmptyIndex] = JSON.stringify(parsed);
311
- const next = lines.join("\n");
312
- if (next === source) {
313
- skipped++;
314
- await maybeRetainMtime();
315
- drawProgress(i + 1);
316
- continue;
317
- }
318
- await promises_1.default.writeFile(file, next, "utf8");
319
- await maybeRetainMtime();
320
- rewritten++;
321
- drawProgress(i + 1);
322
- }
323
- await writeMigrationState({
324
- version: 1,
325
- fileCount: snapshot.fileCount,
326
- maxMtimeMs: snapshot.maxMtimeMs,
327
- });
328
- return { scanned: files.length, rewritten, skipped, retimed, fastSkipped: false };
329
- }
330
122
  async function fetchBackendModelIds(backendUrl, apiKey) {
331
123
  const url = `${trimTrailingSlash(backendUrl)}/api/codex-auth/v1/proxy/v1/models`;
332
124
  try {
@@ -658,7 +450,16 @@ class SetupCommand extends base_command_1.BaseCommand {
658
450
  const codexConfigPath = await writeCodexConfig({ backendUrl, model: resolved.model, apiKey });
659
451
  const updatedShellFiles = await persistApiKeyEnv(apiKey);
660
452
  const updatedVsCodeEnvFiles = await persistVsCodeServerEnvSource();
661
- const sessionMigration = await migrateSessionProviders({ codexHome: paths_1.codexDir });
453
+ const sessionMigration = await (0, codex_history_migration_1.migrateSessionProviders)({
454
+ codexHome: paths_1.codexDir,
455
+ migrationStateFile: MIGRATION_STATE_FILE,
456
+ neutralizeSources: HISTORY_PROVIDER_NEUTRALIZE_SOURCES,
457
+ });
458
+ const stateDbMigration = await (0, codex_history_migration_1.migrateStateDbProviders)({
459
+ codexHome: paths_1.codexDir,
460
+ targetProvider: DEFAULT_PROVIDER_ID,
461
+ sourceProviders: HISTORY_PROVIDER_DB_MIGRATE_SOURCES,
462
+ });
662
463
  const hasOpenClaw = hasCommand("openclaw");
663
464
  const hasOpenCode = hasCommand("opencode");
664
465
  let openClawConfigPath = null;
@@ -701,6 +502,13 @@ class SetupCommand extends base_command_1.BaseCommand {
701
502
  if (sessionMigration.retimed > 0) {
702
503
  this.log(`- Conversation ordering: repaired timestamps on ${sessionMigration.retimed} local sessions.`);
703
504
  }
505
+ if (stateDbMigration.updated > 0) {
506
+ this.log(`- Conversation cache: relabeled ${stateDbMigration.updated} local Codex history rows for The Claw Bay.`);
507
+ }
508
+ else if (stateDbMigration.failed.length > 0) {
509
+ const detail = stateDbMigration.warning ? ` ${stateDbMigration.warning}` : "";
510
+ this.log(`- Conversation cache: could not update local Codex history DB.${detail}`);
511
+ }
704
512
  this.log(`- API key env: ${ENV_FILE}`);
705
513
  this.log(`- Shell profiles updated: ${updatedShellFiles.join(", ")}`);
706
514
  this.log(`- VS Code env hooks updated: ${updatedVsCodeEnvFiles.join(", ")}`);
package/dist/index.js CHANGED
@@ -1,7 +1,40 @@
1
1
  #!/usr/bin/env node
2
2
  "use strict";
3
+ var __importDefault = (this && this.__importDefault) || function (mod) {
4
+ return (mod && mod.__esModule) ? mod : { "default": mod };
5
+ };
3
6
  Object.defineProperty(exports, "__esModule", { value: true });
7
+ const node_fs_1 = require("node:fs");
8
+ const node_path_1 = __importDefault(require("node:path"));
4
9
  const core_1 = require("@oclif/core");
5
- void (0, core_1.run)()
6
- .then(() => (0, core_1.flush)())
7
- .catch(core_1.Errors.handle);
10
+ const update_check_1 = require("./lib/update-check");
11
+ function detectPackageVersion() {
12
+ try {
13
+ const packageJsonPath = node_path_1.default.join(__dirname, "..", "package.json");
14
+ const raw = (0, node_fs_1.readFileSync)(packageJsonPath, "utf8");
15
+ const parsed = JSON.parse(raw);
16
+ return typeof parsed.version === "string" ? parsed.version : "0.0.0";
17
+ }
18
+ catch {
19
+ return "0.0.0";
20
+ }
21
+ }
22
+ async function maybePrintBareInvocationHint() {
23
+ if (process.argv.slice(2).length > 0)
24
+ return;
25
+ const packageName = "theclawbay";
26
+ const currentVersion = detectPackageVersion();
27
+ await (0, update_check_1.maybeNotifyUpdate)({
28
+ packageName,
29
+ currentVersion,
30
+ log: (message) => console.log(message),
31
+ }).catch(() => { });
32
+ console.log("Get your API key at: https://theclawbay.com/keys");
33
+ console.log("Then run: theclawbay setup --api-key <apiKey>");
34
+ console.log("");
35
+ }
36
+ async function main() {
37
+ await maybePrintBareInvocationHint();
38
+ await (0, core_1.run)().then(() => (0, core_1.flush)());
39
+ }
40
+ void main().catch(core_1.Errors.handle);