theclawbay 0.2.4 → 0.2.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.
- package/README.md +17 -20
- package/dist/commands/proxy.js +80 -25
- package/dist/commands/setup.d.ts +0 -5
- package/dist/commands/setup.js +497 -153
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -32,27 +32,17 @@ theclawbay setup --api-key <apiKey>
|
|
|
32
32
|
```
|
|
33
33
|
|
|
34
34
|
This auto-detects installed clients and writes direct WAN API-key config so users can run directly.
|
|
35
|
-
|
|
35
|
+
It configures Codex with `model_provider = "codex-lb"` and a managed bearer token, auto-selects the highest backend-advertised model, and preserves conversation visibility by neutralizing legacy local session provider tags so chats remain visible across provider modes.
|
|
36
36
|
|
|
37
|
-
|
|
37
|
+
It also configures OpenClaw/OpenCode automatically when those CLIs are installed.
|
|
38
38
|
|
|
39
|
-
|
|
40
|
-
theclawbay setup --api-key <apiKey> --client codex
|
|
41
|
-
theclawbay setup --api-key <apiKey> --client openclaw
|
|
42
|
-
theclawbay setup --api-key <apiKey> --client both
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
If needed, skip the automatic Codex login step:
|
|
46
|
-
|
|
47
|
-
```sh
|
|
48
|
-
theclawbay setup --api-key <apiKey> --skip-login
|
|
49
|
-
```
|
|
39
|
+
`CODEX_LB_API_KEY` is persisted for restarts in:
|
|
50
40
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
41
|
+
- `~/.config/theclawbay/env`
|
|
42
|
+
- `~/.bashrc`
|
|
43
|
+
- `~/.zshrc`
|
|
44
|
+
- `~/.profile`
|
|
45
|
+
- `~/.vscode-server/server-env-setup` (and `~/.vscode-server-insiders/server-env-setup` when present)
|
|
56
46
|
|
|
57
47
|
If you operate a custom backend, pass it explicitly:
|
|
58
48
|
|
|
@@ -60,6 +50,14 @@ If you operate a custom backend, pass it explicitly:
|
|
|
60
50
|
theclawbay setup --api-key <apiKey> --backend https://your-domain.com
|
|
61
51
|
```
|
|
62
52
|
|
|
53
|
+
After setup, restart terminal/VS Code once so env-backed clients (OpenClaw/OpenCode) pick up `CODEX_LB_API_KEY`.
|
|
54
|
+
|
|
55
|
+
## Codex Extension Behavior
|
|
56
|
+
|
|
57
|
+
- In API-key provider mode, Codex may label the model source as `Custom`.
|
|
58
|
+
- Full ChatGPT account model-picker behavior is tied to ChatGPT auth mode.
|
|
59
|
+
- `theclawbay setup` keeps your existing Codex login state unchanged on purpose (so local history context is preserved and setup stays non-destructive).
|
|
60
|
+
|
|
63
61
|
## Run Relay (Optional)
|
|
64
62
|
|
|
65
63
|
Only needed as a fallback compatibility mode:
|
|
@@ -73,8 +71,7 @@ By default this starts a local relay on `http://127.0.0.1:2455` and forwards to:
|
|
|
73
71
|
|
|
74
72
|
- `https://theclawbay.com/api/codex-auth/v1/proxy/...`
|
|
75
73
|
|
|
76
|
-
The command
|
|
77
|
-
If both are installed and you're in an interactive terminal, it asks which one you want to configure.
|
|
74
|
+
The command auto-detects whether `openclaw` and/or `opencode` are installed and configures them automatically.
|
|
78
75
|
|
|
79
76
|
## Notes
|
|
80
77
|
|
package/dist/commands/proxy.js
CHANGED
|
@@ -82,36 +82,70 @@ async function askClientChoice() {
|
|
|
82
82
|
output: process.stdout,
|
|
83
83
|
});
|
|
84
84
|
try {
|
|
85
|
-
const answer = (await rl.question("Which
|
|
85
|
+
const answer = (await rl.question("Which clients do you want to configure? [1] All detected [2] Codex [3] OpenClaw [4] OpenCode [5] Codex+OpenClaw: "))
|
|
86
86
|
.trim()
|
|
87
87
|
.toLowerCase();
|
|
88
|
-
if (answer === "2" || answer === "
|
|
88
|
+
if (answer === "2" || answer === "codex")
|
|
89
|
+
return "codex";
|
|
90
|
+
if (answer === "3" || answer === "openclaw")
|
|
89
91
|
return "openclaw";
|
|
90
|
-
if (answer === "
|
|
92
|
+
if (answer === "4" || answer === "opencode")
|
|
93
|
+
return "opencode";
|
|
94
|
+
if (answer === "5" || answer === "both")
|
|
91
95
|
return "both";
|
|
92
|
-
return "
|
|
96
|
+
return "all";
|
|
93
97
|
}
|
|
94
98
|
finally {
|
|
95
99
|
rl.close();
|
|
96
100
|
}
|
|
97
101
|
}
|
|
102
|
+
function clientTargetsForChoice(choice) {
|
|
103
|
+
if (choice === "codex")
|
|
104
|
+
return { codex: true, openclaw: false, opencode: false };
|
|
105
|
+
if (choice === "openclaw")
|
|
106
|
+
return { codex: false, openclaw: true, opencode: false };
|
|
107
|
+
if (choice === "opencode")
|
|
108
|
+
return { codex: false, openclaw: false, opencode: true };
|
|
109
|
+
if (choice === "both")
|
|
110
|
+
return { codex: true, openclaw: true, opencode: false };
|
|
111
|
+
return { codex: true, openclaw: true, opencode: true };
|
|
112
|
+
}
|
|
113
|
+
function detectedClientTargets() {
|
|
114
|
+
return {
|
|
115
|
+
codex: hasCommand("codex"),
|
|
116
|
+
openclaw: hasCommand("openclaw"),
|
|
117
|
+
opencode: hasCommand("opencode"),
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
function countEnabledTargets(targets) {
|
|
121
|
+
return Number(targets.codex) + Number(targets.openclaw) + Number(targets.opencode);
|
|
122
|
+
}
|
|
98
123
|
async function resolveClientChoice(mode) {
|
|
99
|
-
if (mode === "
|
|
100
|
-
|
|
124
|
+
if (mode === "all") {
|
|
125
|
+
const detected = detectedClientTargets();
|
|
126
|
+
return countEnabledTargets(detected) > 0 ? detected : { codex: true, openclaw: false, opencode: false };
|
|
101
127
|
}
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
if (codexInstalled && !openclawInstalled)
|
|
105
|
-
return "codex";
|
|
106
|
-
if (!codexInstalled && openclawInstalled)
|
|
107
|
-
return "openclaw";
|
|
108
|
-
if (codexInstalled && openclawInstalled) {
|
|
109
|
-
if (process.stdin.isTTY && process.stdout.isTTY) {
|
|
110
|
-
return askClientChoice();
|
|
111
|
-
}
|
|
112
|
-
return "both";
|
|
128
|
+
if (mode === "codex" || mode === "openclaw" || mode === "opencode" || mode === "both") {
|
|
129
|
+
return clientTargetsForChoice(mode);
|
|
113
130
|
}
|
|
114
|
-
|
|
131
|
+
const detected = detectedClientTargets();
|
|
132
|
+
const enabledCount = countEnabledTargets(detected);
|
|
133
|
+
if (enabledCount === 0)
|
|
134
|
+
return { codex: true, openclaw: false, opencode: false };
|
|
135
|
+
if (enabledCount === 1)
|
|
136
|
+
return detected;
|
|
137
|
+
if (process.stdin.isTTY && process.stdout.isTTY) {
|
|
138
|
+
const choice = await askClientChoice();
|
|
139
|
+
if (choice === "all")
|
|
140
|
+
return detected;
|
|
141
|
+
const requested = clientTargetsForChoice(choice);
|
|
142
|
+
return {
|
|
143
|
+
codex: requested.codex && detected.codex,
|
|
144
|
+
openclaw: requested.openclaw && detected.openclaw,
|
|
145
|
+
opencode: requested.opencode && detected.opencode,
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
return detected;
|
|
115
149
|
}
|
|
116
150
|
class ProxyCommand extends base_command_1.BaseCommand {
|
|
117
151
|
async run() {
|
|
@@ -128,7 +162,7 @@ class ProxyCommand extends base_command_1.BaseCommand {
|
|
|
128
162
|
}
|
|
129
163
|
const basePath = ensureLeadingSlash(flags["base-path"].trim()).replace(/\/+$/g, "");
|
|
130
164
|
const proxyBase = `${trimTrailingSlash(backendUrl)}${basePath}`;
|
|
131
|
-
const
|
|
165
|
+
const targets = await resolveClientChoice(flags.client);
|
|
132
166
|
const server = node_http_1.default.createServer(async (req, res) => {
|
|
133
167
|
const method = req.method?.toUpperCase() ?? "GET";
|
|
134
168
|
const incomingUrl = new URL(req.url || "/", "http://local-proxy.invalid");
|
|
@@ -177,7 +211,7 @@ class ProxyCommand extends base_command_1.BaseCommand {
|
|
|
177
211
|
this.log(`theclawbay WAN relay listening at ${localBase}`);
|
|
178
212
|
this.log(`Forwarding to ${proxyBase}`);
|
|
179
213
|
this.log("");
|
|
180
|
-
if (
|
|
214
|
+
if (targets.codex) {
|
|
181
215
|
this.log("Codex CLI config snippet:");
|
|
182
216
|
this.log('model_provider = "theclawbay-wan"');
|
|
183
217
|
this.log("");
|
|
@@ -189,11 +223,11 @@ class ProxyCommand extends base_command_1.BaseCommand {
|
|
|
189
223
|
this.log("requires_openai_auth = true");
|
|
190
224
|
this.log("");
|
|
191
225
|
}
|
|
192
|
-
if (
|
|
226
|
+
if (targets.openclaw) {
|
|
193
227
|
const openClawProviderJson = JSON.stringify({
|
|
194
228
|
baseUrl: `${localBase}/v1`,
|
|
195
229
|
apiKey: "theclawbay-local",
|
|
196
|
-
api: "openai-
|
|
230
|
+
api: "openai-completions",
|
|
197
231
|
models: [
|
|
198
232
|
{ id: "gpt-5.3-codex", name: "GPT-5.3 Codex" },
|
|
199
233
|
{ id: "gpt-5.3-codex-spark", name: "GPT-5.3 Codex Spark" },
|
|
@@ -203,9 +237,30 @@ class ProxyCommand extends base_command_1.BaseCommand {
|
|
|
203
237
|
});
|
|
204
238
|
this.log("OpenClaw setup:");
|
|
205
239
|
this.log("Run these once:");
|
|
206
|
-
this.log('openclaw config set agents.defaults.model.primary "
|
|
240
|
+
this.log('openclaw config set agents.defaults.model.primary "codex-lb/gpt-5.3-codex"');
|
|
207
241
|
this.log('openclaw config set models.mode "merge"');
|
|
208
|
-
this.log(`openclaw config set models.providers.
|
|
242
|
+
this.log(`openclaw config set models.providers.codex-lb '${openClawProviderJson}' --json`);
|
|
243
|
+
this.log("");
|
|
244
|
+
}
|
|
245
|
+
if (targets.opencode) {
|
|
246
|
+
this.log("OpenCode setup:");
|
|
247
|
+
this.log("Set in ~/.config/opencode/opencode.json:");
|
|
248
|
+
this.log(JSON.stringify({
|
|
249
|
+
provider: {
|
|
250
|
+
"codex-lb": {
|
|
251
|
+
npm: "@ai-sdk/openai-compatible",
|
|
252
|
+
name: "codex-lb",
|
|
253
|
+
options: {
|
|
254
|
+
baseURL: `${localBase}/v1`,
|
|
255
|
+
apiKey: "theclawbay-local",
|
|
256
|
+
},
|
|
257
|
+
models: {
|
|
258
|
+
"gpt-5.3-codex": { name: "GPT-5.3 Codex" },
|
|
259
|
+
},
|
|
260
|
+
},
|
|
261
|
+
},
|
|
262
|
+
model: "codex-lb/gpt-5.3-codex",
|
|
263
|
+
}, null, 2));
|
|
209
264
|
this.log("");
|
|
210
265
|
}
|
|
211
266
|
await new Promise((resolve) => {
|
|
@@ -244,7 +299,7 @@ ProxyCommand.flags = {
|
|
|
244
299
|
client: core_1.Flags.string({
|
|
245
300
|
required: false,
|
|
246
301
|
default: "auto",
|
|
247
|
-
options: ["auto", "codex", "openclaw", "both"],
|
|
302
|
+
options: ["auto", "codex", "openclaw", "opencode", "both", "all"],
|
|
248
303
|
description: "Client target to guide setup for",
|
|
249
304
|
}),
|
|
250
305
|
};
|
package/dist/commands/setup.d.ts
CHANGED
|
@@ -4,11 +4,6 @@ export default class SetupCommand extends BaseCommand {
|
|
|
4
4
|
static flags: {
|
|
5
5
|
backend: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
6
6
|
"api-key": import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
7
|
-
provider: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
8
|
-
client: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
9
|
-
"openclaw-model": import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
10
|
-
"skip-login": import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
11
|
-
"openai-sync": import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
12
7
|
};
|
|
13
8
|
run(): Promise<void>;
|
|
14
9
|
}
|
package/dist/commands/setup.js
CHANGED
|
@@ -4,9 +4,9 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
const promises_1 = __importDefault(require("node:fs/promises"));
|
|
7
|
+
const node_os_1 = __importDefault(require("node:os"));
|
|
7
8
|
const node_path_1 = __importDefault(require("node:path"));
|
|
8
9
|
const node_child_process_1 = require("node:child_process");
|
|
9
|
-
const promises_2 = require("node:readline/promises");
|
|
10
10
|
const core_1 = require("@oclif/core");
|
|
11
11
|
const base_command_1 = require("../lib/base-command");
|
|
12
12
|
const paths_1 = require("../lib/config/paths");
|
|
@@ -14,10 +14,34 @@ const api_key_1 = require("../lib/managed/api-key");
|
|
|
14
14
|
const config_1 = require("../lib/managed/config");
|
|
15
15
|
const errors_1 = require("../lib/managed/errors");
|
|
16
16
|
const DEFAULT_BACKEND_URL = "https://theclawbay.com";
|
|
17
|
-
const DEFAULT_PROVIDER_ID = "
|
|
17
|
+
const DEFAULT_PROVIDER_ID = "codex-lb";
|
|
18
|
+
const DEFAULT_CODEX_MODEL = "gpt-5.2-codex";
|
|
19
|
+
const DEFAULT_MODELS = ["gpt-5.3-codex", "gpt-5.3-codex-spark", "gpt-5.2-codex", "gpt-5.1-codex"];
|
|
20
|
+
const PREFERRED_MODELS = [
|
|
21
|
+
"gpt-5.3-codex",
|
|
22
|
+
"gpt-5.3-codex-spark",
|
|
23
|
+
"gpt-5.2-codex",
|
|
24
|
+
"gpt-5.1-codex-max",
|
|
25
|
+
"gpt-5.1-codex-mini",
|
|
26
|
+
"gpt-5.1-codex",
|
|
27
|
+
];
|
|
28
|
+
const MODEL_DISPLAY_NAMES = {
|
|
29
|
+
"gpt-5.3-codex": "GPT-5.3 Codex",
|
|
30
|
+
"gpt-5.3-codex-spark": "GPT-5.3 Codex Spark",
|
|
31
|
+
"gpt-5.2-codex": "GPT-5.2 Codex",
|
|
32
|
+
"gpt-5.1-codex-max": "GPT-5.1 Codex Max",
|
|
33
|
+
"gpt-5.1-codex-mini": "GPT-5.1 Codex Mini",
|
|
34
|
+
"gpt-5.1-codex": "GPT-5.1 Codex",
|
|
35
|
+
};
|
|
36
|
+
const ENV_KEY_NAME = "CODEX_LB_API_KEY";
|
|
37
|
+
const ENV_FILE = node_path_1.default.join(node_os_1.default.homedir(), ".config", "theclawbay", "env");
|
|
38
|
+
const MIGRATION_STATE_FILE = node_path_1.default.join(paths_1.codexDir, "theclawbay.migration.json");
|
|
18
39
|
const MANAGED_START = "# theclawbay-managed:start";
|
|
19
40
|
const MANAGED_END = "# theclawbay-managed:end";
|
|
20
|
-
const
|
|
41
|
+
const SHELL_START = "# theclawbay-shell-managed:start";
|
|
42
|
+
const SHELL_END = "# theclawbay-shell-managed:end";
|
|
43
|
+
const OPENCLAW_PROVIDER_ID = "codex-lb";
|
|
44
|
+
const HISTORY_PROVIDER_NEUTRALIZE_SOURCES = new Set(["openai", "theclawbay-wan", "codex-lb"]);
|
|
21
45
|
function trimTrailingSlash(value) {
|
|
22
46
|
return value.replace(/\/+$/g, "");
|
|
23
47
|
}
|
|
@@ -34,58 +58,27 @@ function hasCommand(name) {
|
|
|
34
58
|
const result = (0, node_child_process_1.spawnSync)("which", [name], { stdio: "ignore" });
|
|
35
59
|
return result.status === 0;
|
|
36
60
|
}
|
|
37
|
-
|
|
38
|
-
const
|
|
39
|
-
|
|
40
|
-
output: process.stdout,
|
|
41
|
-
});
|
|
42
|
-
try {
|
|
43
|
-
const answer = (await rl.question("Which client do you want to configure? [1] Codex [2] OpenClaw [3] Both: "))
|
|
44
|
-
.trim()
|
|
45
|
-
.toLowerCase();
|
|
46
|
-
if (answer === "2" || answer === "openclaw")
|
|
47
|
-
return "openclaw";
|
|
48
|
-
if (answer === "3" || answer === "both")
|
|
49
|
-
return "both";
|
|
50
|
-
return "codex";
|
|
51
|
-
}
|
|
52
|
-
finally {
|
|
53
|
-
rl.close();
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
async function resolveClientChoice(mode) {
|
|
57
|
-
if (mode === "codex" || mode === "openclaw" || mode === "both") {
|
|
58
|
-
return mode;
|
|
59
|
-
}
|
|
60
|
-
const codexInstalled = hasCommand("codex");
|
|
61
|
-
const openclawInstalled = hasCommand("openclaw");
|
|
62
|
-
if (codexInstalled && !openclawInstalled)
|
|
63
|
-
return "codex";
|
|
64
|
-
if (!codexInstalled && openclawInstalled)
|
|
65
|
-
return "openclaw";
|
|
66
|
-
if (codexInstalled && openclawInstalled) {
|
|
67
|
-
if (process.stdin.isTTY && process.stdout.isTTY) {
|
|
68
|
-
return askClientChoice();
|
|
69
|
-
}
|
|
70
|
-
return "both";
|
|
71
|
-
}
|
|
72
|
-
return "codex";
|
|
73
|
-
}
|
|
74
|
-
function removeManagedBlock(source) {
|
|
75
|
-
const start = source.indexOf(MANAGED_START);
|
|
76
|
-
if (start < 0)
|
|
61
|
+
function removeManagedBlock(source, start, end) {
|
|
62
|
+
const markerStart = source.indexOf(start);
|
|
63
|
+
if (markerStart < 0)
|
|
77
64
|
return source;
|
|
78
|
-
const
|
|
79
|
-
if (
|
|
80
|
-
return source.slice(0,
|
|
81
|
-
return (source.slice(0,
|
|
65
|
+
const markerEnd = source.indexOf(end, markerStart);
|
|
66
|
+
if (markerEnd < 0)
|
|
67
|
+
return source.slice(0, markerStart).trimEnd() + "\n";
|
|
68
|
+
return (source.slice(0, markerStart) + source.slice(markerEnd + end.length)).trimEnd() + "\n";
|
|
69
|
+
}
|
|
70
|
+
function appendManagedBlock(source, lines) {
|
|
71
|
+
const body = lines.join("\n").trimEnd() + "\n";
|
|
72
|
+
if (!source.trim())
|
|
73
|
+
return body;
|
|
74
|
+
return `${source.trimEnd()}\n\n${body}`;
|
|
82
75
|
}
|
|
83
76
|
function removeProviderTable(source, providerId) {
|
|
84
77
|
const header = `[model_providers.${providerId}]`;
|
|
85
78
|
const lines = source.split(/\r?\n/);
|
|
86
79
|
const output = [];
|
|
87
80
|
for (let i = 0; i < lines.length; i++) {
|
|
88
|
-
if (lines[i]
|
|
81
|
+
if ((lines[i] ?? "").trim() !== header) {
|
|
89
82
|
output.push(lines[i] ?? "");
|
|
90
83
|
continue;
|
|
91
84
|
}
|
|
@@ -112,6 +105,283 @@ function upsertFirstKeyLine(source, key, tomlValue) {
|
|
|
112
105
|
}
|
|
113
106
|
return `${`${key} = ${tomlValue}\n${source}`.trimEnd()}\n`;
|
|
114
107
|
}
|
|
108
|
+
function shellQuote(value) {
|
|
109
|
+
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
110
|
+
}
|
|
111
|
+
function modelDisplayName(modelId) {
|
|
112
|
+
return MODEL_DISPLAY_NAMES[modelId] ?? modelId;
|
|
113
|
+
}
|
|
114
|
+
function renderProgressBar(current, total) {
|
|
115
|
+
if (total <= 0)
|
|
116
|
+
return "";
|
|
117
|
+
const width = 24;
|
|
118
|
+
const ratio = Math.max(0, Math.min(1, current / total));
|
|
119
|
+
const filled = Math.round(width * ratio);
|
|
120
|
+
return `[${"#".repeat(filled)}${"-".repeat(width - filled)}] ${Math.round(ratio * 100)
|
|
121
|
+
.toString()
|
|
122
|
+
.padStart(3, " ")}%`;
|
|
123
|
+
}
|
|
124
|
+
function inferRolloutUpdatedAt(source) {
|
|
125
|
+
const lines = source.split(/\r?\n/);
|
|
126
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
127
|
+
const trimmed = (lines[i] ?? "").trim();
|
|
128
|
+
if (!trimmed)
|
|
129
|
+
continue;
|
|
130
|
+
try {
|
|
131
|
+
const parsed = JSON.parse(trimmed);
|
|
132
|
+
if (typeof parsed.timestamp !== "string")
|
|
133
|
+
continue;
|
|
134
|
+
const ms = Date.parse(parsed.timestamp);
|
|
135
|
+
if (!Number.isFinite(ms))
|
|
136
|
+
continue;
|
|
137
|
+
return new Date(ms);
|
|
138
|
+
}
|
|
139
|
+
catch {
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
async function listSessionRollouts(root) {
|
|
146
|
+
const results = [];
|
|
147
|
+
const walk = async (dir) => {
|
|
148
|
+
let entries = [];
|
|
149
|
+
try {
|
|
150
|
+
entries = await promises_1.default.readdir(dir, { withFileTypes: true });
|
|
151
|
+
}
|
|
152
|
+
catch (error) {
|
|
153
|
+
const err = error;
|
|
154
|
+
if (err.code === "ENOENT")
|
|
155
|
+
return;
|
|
156
|
+
throw error;
|
|
157
|
+
}
|
|
158
|
+
for (const entry of entries) {
|
|
159
|
+
const nextPath = node_path_1.default.join(dir, entry.name);
|
|
160
|
+
if (entry.isDirectory()) {
|
|
161
|
+
await walk(nextPath);
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
if (entry.isFile() && nextPath.endsWith(".jsonl")) {
|
|
165
|
+
results.push(nextPath);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
};
|
|
169
|
+
await walk(root);
|
|
170
|
+
return results;
|
|
171
|
+
}
|
|
172
|
+
async function readMigrationState() {
|
|
173
|
+
try {
|
|
174
|
+
const raw = await promises_1.default.readFile(MIGRATION_STATE_FILE, "utf8");
|
|
175
|
+
const parsed = JSON.parse(raw);
|
|
176
|
+
if (parsed.version === 1 &&
|
|
177
|
+
typeof parsed.fileCount === "number" &&
|
|
178
|
+
Number.isFinite(parsed.fileCount) &&
|
|
179
|
+
typeof parsed.maxMtimeMs === "number" &&
|
|
180
|
+
Number.isFinite(parsed.maxMtimeMs)) {
|
|
181
|
+
return {
|
|
182
|
+
version: 1,
|
|
183
|
+
fileCount: Math.max(0, Math.floor(parsed.fileCount)),
|
|
184
|
+
maxMtimeMs: Math.max(0, Math.floor(parsed.maxMtimeMs)),
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
return null;
|
|
188
|
+
}
|
|
189
|
+
catch {
|
|
190
|
+
return null;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
async function writeMigrationState(state) {
|
|
194
|
+
await promises_1.default.mkdir(node_path_1.default.dirname(MIGRATION_STATE_FILE), { recursive: true });
|
|
195
|
+
await promises_1.default.writeFile(MIGRATION_STATE_FILE, `${JSON.stringify(state, null, 2)}\n`, "utf8");
|
|
196
|
+
}
|
|
197
|
+
async function computeSessionSnapshot(files) {
|
|
198
|
+
let maxMtimeMs = 0;
|
|
199
|
+
for (const file of files) {
|
|
200
|
+
try {
|
|
201
|
+
const stats = await promises_1.default.stat(file);
|
|
202
|
+
const mtimeMs = Math.floor(stats.mtimeMs);
|
|
203
|
+
if (mtimeMs > maxMtimeMs)
|
|
204
|
+
maxMtimeMs = mtimeMs;
|
|
205
|
+
}
|
|
206
|
+
catch {
|
|
207
|
+
// ignore transient missing file/race
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
return { fileCount: files.length, maxMtimeMs };
|
|
211
|
+
}
|
|
212
|
+
async function migrateSessionProviders(params) {
|
|
213
|
+
const sessionsRoot = node_path_1.default.join(params.codexHome, "sessions");
|
|
214
|
+
const files = await listSessionRollouts(sessionsRoot);
|
|
215
|
+
const snapshot = await computeSessionSnapshot(files);
|
|
216
|
+
const previousState = await readMigrationState();
|
|
217
|
+
if (previousState &&
|
|
218
|
+
previousState.fileCount === snapshot.fileCount &&
|
|
219
|
+
previousState.maxMtimeMs === snapshot.maxMtimeMs) {
|
|
220
|
+
return { scanned: 0, rewritten: 0, skipped: 0, retimed: 0, fastSkipped: true };
|
|
221
|
+
}
|
|
222
|
+
let rewritten = 0;
|
|
223
|
+
let skipped = 0;
|
|
224
|
+
let retimed = 0;
|
|
225
|
+
const useProgress = process.stdout.isTTY && files.length > 0;
|
|
226
|
+
const drawProgress = (current) => {
|
|
227
|
+
if (!useProgress)
|
|
228
|
+
return;
|
|
229
|
+
const bar = renderProgressBar(current, files.length);
|
|
230
|
+
process.stdout.write(`\rMigrating conversations ${current}/${files.length} ${bar}`);
|
|
231
|
+
if (current >= files.length)
|
|
232
|
+
process.stdout.write("\n");
|
|
233
|
+
};
|
|
234
|
+
drawProgress(0);
|
|
235
|
+
for (let i = 0; i < files.length; i++) {
|
|
236
|
+
const file = files[i];
|
|
237
|
+
let source = "";
|
|
238
|
+
let originalStats = null;
|
|
239
|
+
try {
|
|
240
|
+
source = await promises_1.default.readFile(file, "utf8");
|
|
241
|
+
originalStats = await promises_1.default.stat(file);
|
|
242
|
+
}
|
|
243
|
+
catch {
|
|
244
|
+
skipped++;
|
|
245
|
+
drawProgress(i + 1);
|
|
246
|
+
continue;
|
|
247
|
+
}
|
|
248
|
+
const desiredMtime = inferRolloutUpdatedAt(source) ?? originalStats.mtime;
|
|
249
|
+
const desiredAtime = originalStats.atime;
|
|
250
|
+
const maybeRetainMtime = async () => {
|
|
251
|
+
const diffMs = Math.abs(desiredMtime.getTime() - originalStats.mtime.getTime());
|
|
252
|
+
if (diffMs <= 1500)
|
|
253
|
+
return;
|
|
254
|
+
try {
|
|
255
|
+
await promises_1.default.utimes(file, desiredAtime, desiredMtime);
|
|
256
|
+
retimed++;
|
|
257
|
+
}
|
|
258
|
+
catch {
|
|
259
|
+
// best-effort only
|
|
260
|
+
}
|
|
261
|
+
};
|
|
262
|
+
if (!source.trim()) {
|
|
263
|
+
skipped++;
|
|
264
|
+
await maybeRetainMtime();
|
|
265
|
+
drawProgress(i + 1);
|
|
266
|
+
continue;
|
|
267
|
+
}
|
|
268
|
+
const lines = source.split(/\r?\n/);
|
|
269
|
+
const firstNonEmptyIndex = lines.findIndex((line) => line.trim().length > 0);
|
|
270
|
+
if (firstNonEmptyIndex < 0) {
|
|
271
|
+
skipped++;
|
|
272
|
+
drawProgress(i + 1);
|
|
273
|
+
continue;
|
|
274
|
+
}
|
|
275
|
+
let parsed;
|
|
276
|
+
try {
|
|
277
|
+
parsed = JSON.parse(lines[firstNonEmptyIndex] ?? "");
|
|
278
|
+
}
|
|
279
|
+
catch {
|
|
280
|
+
skipped++;
|
|
281
|
+
drawProgress(i + 1);
|
|
282
|
+
continue;
|
|
283
|
+
}
|
|
284
|
+
if ((parsed.type ?? "") !== "session_meta") {
|
|
285
|
+
skipped++;
|
|
286
|
+
drawProgress(i + 1);
|
|
287
|
+
continue;
|
|
288
|
+
}
|
|
289
|
+
const payload = parsed.payload;
|
|
290
|
+
if (!payload || typeof payload !== "object") {
|
|
291
|
+
skipped++;
|
|
292
|
+
drawProgress(i + 1);
|
|
293
|
+
continue;
|
|
294
|
+
}
|
|
295
|
+
const currentProvider = payload.model_provider;
|
|
296
|
+
if (typeof currentProvider !== "string") {
|
|
297
|
+
skipped++;
|
|
298
|
+
drawProgress(i + 1);
|
|
299
|
+
continue;
|
|
300
|
+
}
|
|
301
|
+
if (!HISTORY_PROVIDER_NEUTRALIZE_SOURCES.has(currentProvider)) {
|
|
302
|
+
skipped++;
|
|
303
|
+
drawProgress(i + 1);
|
|
304
|
+
continue;
|
|
305
|
+
}
|
|
306
|
+
// Remove explicit provider tag so the same thread stays visible across provider contexts.
|
|
307
|
+
delete payload.model_provider;
|
|
308
|
+
lines[firstNonEmptyIndex] = JSON.stringify(parsed);
|
|
309
|
+
const next = lines.join("\n");
|
|
310
|
+
if (next === source) {
|
|
311
|
+
skipped++;
|
|
312
|
+
await maybeRetainMtime();
|
|
313
|
+
drawProgress(i + 1);
|
|
314
|
+
continue;
|
|
315
|
+
}
|
|
316
|
+
await promises_1.default.writeFile(file, next, "utf8");
|
|
317
|
+
await maybeRetainMtime();
|
|
318
|
+
rewritten++;
|
|
319
|
+
drawProgress(i + 1);
|
|
320
|
+
}
|
|
321
|
+
await writeMigrationState({
|
|
322
|
+
version: 1,
|
|
323
|
+
fileCount: snapshot.fileCount,
|
|
324
|
+
maxMtimeMs: snapshot.maxMtimeMs,
|
|
325
|
+
});
|
|
326
|
+
return { scanned: files.length, rewritten, skipped, retimed, fastSkipped: false };
|
|
327
|
+
}
|
|
328
|
+
async function fetchBackendModelIds(backendUrl, apiKey) {
|
|
329
|
+
const url = `${trimTrailingSlash(backendUrl)}/api/codex-auth/v1/proxy/v1/models`;
|
|
330
|
+
try {
|
|
331
|
+
const response = await fetch(url, {
|
|
332
|
+
headers: { Authorization: `Bearer ${apiKey}` },
|
|
333
|
+
signal: AbortSignal.timeout(4500),
|
|
334
|
+
});
|
|
335
|
+
if (!response.ok)
|
|
336
|
+
return null;
|
|
337
|
+
const body = (await response.json());
|
|
338
|
+
const ids = (body.data ?? [])
|
|
339
|
+
.map((entry) => (typeof entry.id === "string" ? entry.id.trim() : ""))
|
|
340
|
+
.filter((id) => id.length > 0);
|
|
341
|
+
return ids.length ? ids : null;
|
|
342
|
+
}
|
|
343
|
+
catch {
|
|
344
|
+
return null;
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
async function resolveModels(backendUrl, apiKey) {
|
|
348
|
+
const ids = await fetchBackendModelIds(backendUrl, apiKey);
|
|
349
|
+
const available = new Set(ids ?? []);
|
|
350
|
+
let selected = DEFAULT_CODEX_MODEL;
|
|
351
|
+
let note;
|
|
352
|
+
for (const preferred of PREFERRED_MODELS) {
|
|
353
|
+
if (available.has(preferred)) {
|
|
354
|
+
selected = preferred;
|
|
355
|
+
break;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
if (ids?.length && !available.has(selected)) {
|
|
359
|
+
selected = ids[0] ?? DEFAULT_CODEX_MODEL;
|
|
360
|
+
note = "No preferred Codex model advertised by backend; selected first available model.";
|
|
361
|
+
}
|
|
362
|
+
else if (!ids) {
|
|
363
|
+
note = `Unable to query backend model list; defaulted to ${DEFAULT_CODEX_MODEL}.`;
|
|
364
|
+
}
|
|
365
|
+
const unique = [];
|
|
366
|
+
const pushUnique = (modelId) => {
|
|
367
|
+
if (!modelId || unique.includes(modelId))
|
|
368
|
+
return;
|
|
369
|
+
unique.push(modelId);
|
|
370
|
+
};
|
|
371
|
+
pushUnique(selected);
|
|
372
|
+
for (const modelId of DEFAULT_MODELS)
|
|
373
|
+
pushUnique(modelId);
|
|
374
|
+
for (const modelId of ids ?? []) {
|
|
375
|
+
if (unique.length >= 8)
|
|
376
|
+
break;
|
|
377
|
+
pushUnique(modelId);
|
|
378
|
+
}
|
|
379
|
+
return {
|
|
380
|
+
model: selected,
|
|
381
|
+
models: unique.map((modelId) => ({ id: modelId, name: modelDisplayName(modelId) })),
|
|
382
|
+
note,
|
|
383
|
+
};
|
|
384
|
+
}
|
|
115
385
|
async function writeCodexConfig(params) {
|
|
116
386
|
const configPath = node_path_1.default.join(paths_1.codexDir, "config.toml");
|
|
117
387
|
await promises_1.default.mkdir(paths_1.codexDir, { recursive: true });
|
|
@@ -126,40 +396,82 @@ async function writeCodexConfig(params) {
|
|
|
126
396
|
}
|
|
127
397
|
const proxyRoot = `${trimTrailingSlash(params.backendUrl)}/api/codex-auth/v1/proxy`;
|
|
128
398
|
let next = existing;
|
|
129
|
-
next = removeManagedBlock(next);
|
|
130
|
-
next = removeProviderTable(next,
|
|
131
|
-
next = upsertFirstKeyLine(next, "model_provider", `"${
|
|
132
|
-
|
|
399
|
+
next = removeManagedBlock(next, MANAGED_START, MANAGED_END);
|
|
400
|
+
next = removeProviderTable(next, DEFAULT_PROVIDER_ID);
|
|
401
|
+
next = upsertFirstKeyLine(next, "model_provider", `"${DEFAULT_PROVIDER_ID}"`);
|
|
402
|
+
next = upsertFirstKeyLine(next, "model", `"${params.model}"`);
|
|
403
|
+
const managedBlock = appendManagedBlock("", [
|
|
133
404
|
MANAGED_START,
|
|
134
|
-
`[model_providers.${
|
|
405
|
+
`[model_providers.${DEFAULT_PROVIDER_ID}]`,
|
|
135
406
|
'name = "OpenAI"',
|
|
136
407
|
`base_url = "${proxyRoot}/backend-api/codex"`,
|
|
137
408
|
'wire_api = "responses"',
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
: []),
|
|
409
|
+
"requires_openai_auth = true",
|
|
410
|
+
`experimental_bearer_token = "${params.apiKey}"`,
|
|
141
411
|
MANAGED_END,
|
|
412
|
+
]);
|
|
413
|
+
next = appendManagedBlock(next, managedBlock.trimEnd().split("\n"));
|
|
414
|
+
await promises_1.default.writeFile(configPath, next, "utf8");
|
|
415
|
+
return configPath;
|
|
416
|
+
}
|
|
417
|
+
async function persistApiKeyEnv(apiKey) {
|
|
418
|
+
const envDir = node_path_1.default.dirname(ENV_FILE);
|
|
419
|
+
await promises_1.default.mkdir(envDir, { recursive: true });
|
|
420
|
+
const envContents = [
|
|
421
|
+
"# Generated by theclawbay setup",
|
|
422
|
+
`export ${ENV_KEY_NAME}=${shellQuote(apiKey)}`,
|
|
142
423
|
"",
|
|
143
424
|
].join("\n");
|
|
144
|
-
|
|
145
|
-
|
|
425
|
+
await promises_1.default.writeFile(ENV_FILE, envContents, "utf8");
|
|
426
|
+
await promises_1.default.chmod(ENV_FILE, 0o600);
|
|
427
|
+
const sourceLine = `[ -f "$HOME/.config/theclawbay/env" ] && . "$HOME/.config/theclawbay/env"`;
|
|
428
|
+
const shellRcPaths = [".bashrc", ".zshrc", ".profile"].map((name) => node_path_1.default.join(node_os_1.default.homedir(), name));
|
|
429
|
+
const updated = [];
|
|
430
|
+
for (const rcPath of shellRcPaths) {
|
|
431
|
+
let existing = "";
|
|
432
|
+
try {
|
|
433
|
+
existing = await promises_1.default.readFile(rcPath, "utf8");
|
|
434
|
+
}
|
|
435
|
+
catch (error) {
|
|
436
|
+
const err = error;
|
|
437
|
+
if (err.code !== "ENOENT")
|
|
438
|
+
throw error;
|
|
439
|
+
}
|
|
440
|
+
const cleaned = removeManagedBlock(existing, SHELL_START, SHELL_END);
|
|
441
|
+
const withBlock = appendManagedBlock(cleaned, [SHELL_START, sourceLine, SHELL_END]);
|
|
442
|
+
await promises_1.default.writeFile(rcPath, withBlock, "utf8");
|
|
443
|
+
updated.push(rcPath);
|
|
146
444
|
}
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
return configPath;
|
|
445
|
+
process.env[ENV_KEY_NAME] = apiKey;
|
|
446
|
+
return updated;
|
|
150
447
|
}
|
|
151
|
-
function
|
|
152
|
-
const
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
448
|
+
async function persistVsCodeServerEnvSource() {
|
|
449
|
+
const homes = [".vscode-server", ".vscode-server-insiders"];
|
|
450
|
+
const sourceLine = `[ -f "$HOME/.config/theclawbay/env" ] && . "$HOME/.config/theclawbay/env"`;
|
|
451
|
+
const updated = [];
|
|
452
|
+
for (const home of homes) {
|
|
453
|
+
const setupPath = node_path_1.default.join(node_os_1.default.homedir(), home, "server-env-setup");
|
|
454
|
+
await promises_1.default.mkdir(node_path_1.default.dirname(setupPath), { recursive: true });
|
|
455
|
+
let existing = "";
|
|
456
|
+
try {
|
|
457
|
+
existing = await promises_1.default.readFile(setupPath, "utf8");
|
|
458
|
+
}
|
|
459
|
+
catch (error) {
|
|
460
|
+
const err = error;
|
|
461
|
+
if (err.code !== "ENOENT")
|
|
462
|
+
throw error;
|
|
463
|
+
}
|
|
464
|
+
let next = existing.trimEnd();
|
|
465
|
+
if (!next.startsWith("#!")) {
|
|
466
|
+
next = `#!/usr/bin/env sh\n${next}`.trimEnd();
|
|
467
|
+
}
|
|
468
|
+
next = `${removeManagedBlock(next + "\n", SHELL_START, SHELL_END).trimEnd()}\n`;
|
|
469
|
+
next = appendManagedBlock(next, [SHELL_START, sourceLine, SHELL_END]);
|
|
470
|
+
await promises_1.default.writeFile(setupPath, next, "utf8");
|
|
471
|
+
await promises_1.default.chmod(setupPath, 0o700);
|
|
472
|
+
updated.push(setupPath);
|
|
473
|
+
}
|
|
474
|
+
return updated;
|
|
163
475
|
}
|
|
164
476
|
function runOpenClawConfigCommand(args) {
|
|
165
477
|
const run = (0, node_child_process_1.spawnSync)("openclaw", args, {
|
|
@@ -174,29 +486,78 @@ function runOpenClawConfigCommand(args) {
|
|
|
174
486
|
throw new Error(`failed to update OpenClaw config: ${details}`);
|
|
175
487
|
}
|
|
176
488
|
function setupOpenClaw(params) {
|
|
177
|
-
const base = trimTrailingSlash(params.backendUrl);
|
|
178
|
-
const model = params.model.trim() || DEFAULT_OPENCLAW_MODEL;
|
|
179
489
|
const provider = {
|
|
180
|
-
baseUrl: `${
|
|
181
|
-
apiKey:
|
|
182
|
-
api: "openai-
|
|
183
|
-
models:
|
|
184
|
-
{ id: "gpt-5.3-codex", name: "GPT-5.3 Codex" },
|
|
185
|
-
{ id: "gpt-5.3-codex-spark", name: "GPT-5.3 Codex Spark" },
|
|
186
|
-
{ id: "gpt-5.2-codex", name: "GPT-5.2 Codex" },
|
|
187
|
-
{ id: "gpt-5.1-codex", name: "GPT-5.1 Codex" },
|
|
188
|
-
],
|
|
490
|
+
baseUrl: `${trimTrailingSlash(params.backendUrl)}/api/codex-auth/v1/proxy/v1`,
|
|
491
|
+
apiKey: "${CODEX_LB_API_KEY}",
|
|
492
|
+
api: "openai-completions",
|
|
493
|
+
models: params.models,
|
|
189
494
|
};
|
|
190
|
-
runOpenClawConfigCommand([
|
|
495
|
+
runOpenClawConfigCommand([
|
|
496
|
+
"config",
|
|
497
|
+
"set",
|
|
498
|
+
"agents.defaults.model.primary",
|
|
499
|
+
`${OPENCLAW_PROVIDER_ID}/${params.model.trim() || DEFAULT_CODEX_MODEL}`,
|
|
500
|
+
]);
|
|
191
501
|
runOpenClawConfigCommand(["config", "set", "models.mode", "merge"]);
|
|
192
502
|
runOpenClawConfigCommand([
|
|
193
503
|
"config",
|
|
194
504
|
"set",
|
|
195
|
-
|
|
505
|
+
`models.providers.${OPENCLAW_PROVIDER_ID}`,
|
|
196
506
|
JSON.stringify(provider),
|
|
197
507
|
"--json",
|
|
198
508
|
]);
|
|
199
509
|
}
|
|
510
|
+
function objectRecordOr(value, fallback) {
|
|
511
|
+
if (typeof value === "object" && value !== null && !Array.isArray(value)) {
|
|
512
|
+
return { ...value };
|
|
513
|
+
}
|
|
514
|
+
return fallback;
|
|
515
|
+
}
|
|
516
|
+
function openCodeModelsObject(models) {
|
|
517
|
+
const result = {};
|
|
518
|
+
for (const model of models) {
|
|
519
|
+
if (!model.id)
|
|
520
|
+
continue;
|
|
521
|
+
result[model.id] = { name: model.name || model.id };
|
|
522
|
+
}
|
|
523
|
+
return result;
|
|
524
|
+
}
|
|
525
|
+
async function writeOpenCodeConfig(params) {
|
|
526
|
+
const configPath = node_path_1.default.join(node_os_1.default.homedir(), ".config", "opencode", "opencode.json");
|
|
527
|
+
await promises_1.default.mkdir(node_path_1.default.dirname(configPath), { recursive: true });
|
|
528
|
+
let existingRaw = "";
|
|
529
|
+
try {
|
|
530
|
+
existingRaw = await promises_1.default.readFile(configPath, "utf8");
|
|
531
|
+
}
|
|
532
|
+
catch (error) {
|
|
533
|
+
const err = error;
|
|
534
|
+
if (err.code !== "ENOENT")
|
|
535
|
+
throw error;
|
|
536
|
+
}
|
|
537
|
+
let doc = {};
|
|
538
|
+
if (existingRaw.trim()) {
|
|
539
|
+
try {
|
|
540
|
+
doc = objectRecordOr(JSON.parse(existingRaw), {});
|
|
541
|
+
}
|
|
542
|
+
catch {
|
|
543
|
+
throw new Error(`invalid JSON in OpenCode config: ${configPath}`);
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
const providerRoot = objectRecordOr(doc.provider, {});
|
|
547
|
+
providerRoot[DEFAULT_PROVIDER_ID] = {
|
|
548
|
+
npm: "@ai-sdk/openai-compatible",
|
|
549
|
+
name: DEFAULT_PROVIDER_ID,
|
|
550
|
+
options: {
|
|
551
|
+
baseURL: `${trimTrailingSlash(params.backendUrl)}/api/codex-auth/v1/proxy/v1`,
|
|
552
|
+
apiKey: `{env:${ENV_KEY_NAME}}`,
|
|
553
|
+
},
|
|
554
|
+
models: openCodeModelsObject(params.models),
|
|
555
|
+
};
|
|
556
|
+
doc.provider = providerRoot;
|
|
557
|
+
doc.model = `${DEFAULT_PROVIDER_ID}/${params.model}`;
|
|
558
|
+
await promises_1.default.writeFile(configPath, `${JSON.stringify(doc, null, 2)}\n`, "utf8");
|
|
559
|
+
return configPath;
|
|
560
|
+
}
|
|
200
561
|
class SetupCommand extends base_command_1.BaseCommand {
|
|
201
562
|
async run() {
|
|
202
563
|
await this.runSafe(async () => {
|
|
@@ -210,64 +571,73 @@ class SetupCommand extends base_command_1.BaseCommand {
|
|
|
210
571
|
throw error;
|
|
211
572
|
}
|
|
212
573
|
const apiKey = (flags["api-key"] ?? managed?.apiKey ?? "").trim();
|
|
213
|
-
if (!apiKey)
|
|
574
|
+
if (!apiKey)
|
|
214
575
|
throw new Error('API key is required. Run "theclawbay setup --api-key <key>".');
|
|
215
|
-
}
|
|
216
576
|
const backendRaw = flags.backend ?? (0, api_key_1.tryInferBackendUrlFromApiKey)(apiKey) ?? managed?.backendUrl ?? DEFAULT_BACKEND_URL;
|
|
217
577
|
const backendUrl = normalizeUrl(backendRaw, "--backend");
|
|
218
|
-
const clientChoice = await resolveClientChoice(flags.client);
|
|
219
|
-
const codexWanted = clientChoice === "codex" || clientChoice === "both";
|
|
220
|
-
const openClawWanted = clientChoice === "openclaw" || clientChoice === "both";
|
|
221
578
|
await (0, config_1.writeManagedConfig)({ backendUrl, apiKey });
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
579
|
+
const resolved = await resolveModels(backendUrl, apiKey);
|
|
580
|
+
const codexConfigPath = await writeCodexConfig({ backendUrl, model: resolved.model, apiKey });
|
|
581
|
+
const updatedShellFiles = await persistApiKeyEnv(apiKey);
|
|
582
|
+
const updatedVsCodeEnvFiles = await persistVsCodeServerEnvSource();
|
|
583
|
+
const sessionMigration = await migrateSessionProviders({ codexHome: paths_1.codexDir });
|
|
584
|
+
const hasOpenClaw = hasCommand("openclaw");
|
|
585
|
+
const hasOpenCode = hasCommand("opencode");
|
|
586
|
+
let openCodePath = null;
|
|
587
|
+
if (hasOpenClaw) {
|
|
588
|
+
setupOpenClaw({
|
|
225
589
|
backendUrl,
|
|
226
|
-
|
|
227
|
-
|
|
590
|
+
model: resolved.model,
|
|
591
|
+
models: resolved.models,
|
|
228
592
|
});
|
|
229
593
|
}
|
|
230
|
-
if (
|
|
231
|
-
|
|
232
|
-
throw new Error('codex CLI not found. Install @openai/codex, or run setup with --client openclaw.');
|
|
233
|
-
}
|
|
234
|
-
codexLoginWithApiKey(apiKey);
|
|
235
|
-
}
|
|
236
|
-
if (openClawWanted) {
|
|
237
|
-
if (!hasCommand("openclaw")) {
|
|
238
|
-
throw new Error('openclaw CLI not found. Install OpenClaw, or run setup with --client codex.');
|
|
239
|
-
}
|
|
240
|
-
setupOpenClaw({
|
|
594
|
+
if (hasOpenCode) {
|
|
595
|
+
openCodePath = await writeOpenCodeConfig({
|
|
241
596
|
backendUrl,
|
|
242
|
-
|
|
243
|
-
|
|
597
|
+
model: resolved.model,
|
|
598
|
+
models: resolved.models,
|
|
244
599
|
});
|
|
245
600
|
}
|
|
246
|
-
this.log(
|
|
247
|
-
this.log(
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
601
|
+
this.log("Setup complete");
|
|
602
|
+
this.log(`- Managed config: ${paths_1.managedConfigPath}`);
|
|
603
|
+
this.log(`- Backend: ${backendUrl}`);
|
|
604
|
+
this.log(`- Codex config: ${codexConfigPath}`);
|
|
605
|
+
this.log(`- Codex model: ${resolved.model}`);
|
|
606
|
+
if (resolved.note)
|
|
607
|
+
this.log(resolved.note);
|
|
608
|
+
if (sessionMigration.rewritten > 0) {
|
|
609
|
+
this.log(`- Conversations: updated ${sessionMigration.rewritten}/${sessionMigration.scanned} local sessions for cross-provider visibility.`);
|
|
610
|
+
}
|
|
611
|
+
else if (sessionMigration.fastSkipped) {
|
|
612
|
+
this.log("- Conversations: already migrated (fast check).");
|
|
613
|
+
}
|
|
614
|
+
else {
|
|
615
|
+
this.log("- Conversations: no local sessions required migration.");
|
|
256
616
|
}
|
|
257
|
-
if (
|
|
258
|
-
this.log(
|
|
617
|
+
if (sessionMigration.retimed > 0) {
|
|
618
|
+
this.log(`- Conversation ordering: repaired timestamps on ${sessionMigration.retimed} local sessions.`);
|
|
259
619
|
}
|
|
260
|
-
|
|
261
|
-
|
|
620
|
+
this.log(`- API key env: ${ENV_FILE}`);
|
|
621
|
+
this.log(`- Shell profiles updated: ${updatedShellFiles.join(", ")}`);
|
|
622
|
+
this.log(`- VS Code env hooks updated: ${updatedVsCodeEnvFiles.join(", ")}`);
|
|
623
|
+
if (hasOpenClaw) {
|
|
624
|
+
this.log("- OpenClaw: configured");
|
|
262
625
|
}
|
|
263
|
-
else
|
|
264
|
-
this.log("
|
|
626
|
+
else {
|
|
627
|
+
this.log("- OpenClaw: not detected (skipped)");
|
|
265
628
|
}
|
|
266
|
-
|
|
629
|
+
if (hasOpenCode) {
|
|
630
|
+
this.log(`- OpenCode: configured (${openCodePath})`);
|
|
631
|
+
}
|
|
632
|
+
else {
|
|
633
|
+
this.log("- OpenCode: not detected (skipped)");
|
|
634
|
+
}
|
|
635
|
+
this.log("- Codex login state: unchanged");
|
|
636
|
+
this.log("Next: restart terminal/VS Code window once so OpenClaw/OpenCode pick up CODEX_LB_API_KEY.");
|
|
267
637
|
});
|
|
268
638
|
}
|
|
269
639
|
}
|
|
270
|
-
SetupCommand.description = "One-time
|
|
640
|
+
SetupCommand.description = "One-time setup: configure Codex/OpenClaw/OpenCode to route through The Claw Bay using your API key";
|
|
271
641
|
SetupCommand.flags = {
|
|
272
642
|
backend: core_1.Flags.string({
|
|
273
643
|
required: false,
|
|
@@ -278,31 +648,5 @@ SetupCommand.flags = {
|
|
|
278
648
|
aliases: ["apiKey"],
|
|
279
649
|
description: "API key issued by your The Claw Bay dashboard",
|
|
280
650
|
}),
|
|
281
|
-
provider: core_1.Flags.string({
|
|
282
|
-
required: false,
|
|
283
|
-
default: DEFAULT_PROVIDER_ID,
|
|
284
|
-
description: "Codex model provider id to write into ~/.codex/config.toml",
|
|
285
|
-
}),
|
|
286
|
-
client: core_1.Flags.string({
|
|
287
|
-
required: false,
|
|
288
|
-
default: "auto",
|
|
289
|
-
options: ["auto", "codex", "openclaw", "both"],
|
|
290
|
-
description: "Client target to configure",
|
|
291
|
-
}),
|
|
292
|
-
"openclaw-model": core_1.Flags.string({
|
|
293
|
-
required: false,
|
|
294
|
-
default: DEFAULT_OPENCLAW_MODEL,
|
|
295
|
-
description: "OpenClaw model id to set as default (without provider prefix)",
|
|
296
|
-
}),
|
|
297
|
-
"skip-login": core_1.Flags.boolean({
|
|
298
|
-
required: false,
|
|
299
|
-
default: false,
|
|
300
|
-
description: "Skip `codex login --with-api-key`",
|
|
301
|
-
}),
|
|
302
|
-
"openai-sync": core_1.Flags.boolean({
|
|
303
|
-
required: false,
|
|
304
|
-
default: false,
|
|
305
|
-
description: "Enable OpenAI-synced conversation mode (writes chatgpt_base_url/requires_openai_auth); may prefer remote ChatGPT history over local-only history",
|
|
306
|
-
}),
|
|
307
651
|
};
|
|
308
652
|
exports.default = SetupCommand;
|