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 +15 -6
- package/dist/commands/link.js +1 -1
- package/dist/commands/logout.d.ts +5 -0
- package/dist/commands/logout.js +281 -0
- package/dist/commands/setup.js +26 -218
- package/dist/index.js +36 -3
- package/dist/lib/codex-history-migration.d.ts +23 -0
- package/dist/lib/codex-history-migration.js +334 -0
- package/package.json +4 -5
- package/dist/commands/proxy.d.ts +0 -13
- package/dist/commands/proxy.js +0 -306
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
|
-
##
|
|
63
|
+
## Link Only (Optional)
|
|
64
64
|
|
|
65
|
-
|
|
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
|
-
|
|
71
|
+
## Logout
|
|
73
72
|
|
|
74
|
-
-
|
|
73
|
+
To remove local The Claw Bay API-key auth state from this machine:
|
|
75
74
|
|
|
76
|
-
|
|
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
|
|
package/dist/commands/link.js
CHANGED
|
@@ -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
|
|
23
|
+
this.log(`Run "theclawbay setup" to configure Codex/OpenClaw/OpenCode on this machine.`);
|
|
24
24
|
});
|
|
25
25
|
}
|
|
26
26
|
}
|
|
@@ -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;
|
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_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.
|
|
20
|
-
const DEFAULT_OPENCLAW_MODEL = "gpt-5.
|
|
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
|
|
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
|
-
|
|
6
|
-
|
|
7
|
-
|
|
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);
|