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/dist/commands/proxy.js
DELETED
|
@@ -1,306 +0,0 @@
|
|
|
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 node_http_1 = __importDefault(require("node:http"));
|
|
7
|
-
const node_stream_1 = require("node:stream");
|
|
8
|
-
const node_child_process_1 = require("node:child_process");
|
|
9
|
-
const promises_1 = require("node:readline/promises");
|
|
10
|
-
const core_1 = require("@oclif/core");
|
|
11
|
-
const base_command_1 = require("../lib/base-command");
|
|
12
|
-
const config_1 = require("../lib/managed/config");
|
|
13
|
-
function trimTrailingSlash(value) {
|
|
14
|
-
return value.replace(/\/+$/g, "");
|
|
15
|
-
}
|
|
16
|
-
function ensureLeadingSlash(value) {
|
|
17
|
-
return value.startsWith("/") ? value : `/${value}`;
|
|
18
|
-
}
|
|
19
|
-
function joinUrl(base, pathname) {
|
|
20
|
-
const url = new URL(base);
|
|
21
|
-
url.pathname = `${url.pathname.replace(/\/+$/g, "")}${pathname.startsWith("/") ? "" : "/"}${pathname}`;
|
|
22
|
-
return url.toString();
|
|
23
|
-
}
|
|
24
|
-
function normalizeUrl(raw, label) {
|
|
25
|
-
try {
|
|
26
|
-
const parsed = new URL(raw.trim());
|
|
27
|
-
return trimTrailingSlash(parsed.toString());
|
|
28
|
-
}
|
|
29
|
-
catch {
|
|
30
|
-
throw new Error(`invalid ${label} URL: ${raw}`);
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
function headersFromIncoming(source) {
|
|
34
|
-
const headers = new Headers();
|
|
35
|
-
for (const [key, value] of Object.entries(source)) {
|
|
36
|
-
if (!value)
|
|
37
|
-
continue;
|
|
38
|
-
const lower = key.toLowerCase();
|
|
39
|
-
if (lower === "host" || lower === "content-length" || lower === "authorization" || lower === "connection") {
|
|
40
|
-
continue;
|
|
41
|
-
}
|
|
42
|
-
if (Array.isArray(value)) {
|
|
43
|
-
headers.set(key, value.join(", "));
|
|
44
|
-
}
|
|
45
|
-
else {
|
|
46
|
-
headers.set(key, value);
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
return headers;
|
|
50
|
-
}
|
|
51
|
-
async function readIncomingBody(req) {
|
|
52
|
-
if (!req.method || req.method === "GET" || req.method === "HEAD")
|
|
53
|
-
return null;
|
|
54
|
-
const chunks = [];
|
|
55
|
-
for await (const chunk of req) {
|
|
56
|
-
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk)));
|
|
57
|
-
}
|
|
58
|
-
if (!chunks.length)
|
|
59
|
-
return null;
|
|
60
|
-
return Buffer.concat(chunks);
|
|
61
|
-
}
|
|
62
|
-
function bufferToArrayBuffer(buffer) {
|
|
63
|
-
const bytes = new Uint8Array(buffer.length);
|
|
64
|
-
bytes.set(buffer);
|
|
65
|
-
return bytes.buffer;
|
|
66
|
-
}
|
|
67
|
-
function writeUpstreamHeaders(res, source) {
|
|
68
|
-
source.forEach((value, key) => {
|
|
69
|
-
const lower = key.toLowerCase();
|
|
70
|
-
if (lower === "connection" || lower === "transfer-encoding" || lower === "keep-alive")
|
|
71
|
-
return;
|
|
72
|
-
res.setHeader(key, value);
|
|
73
|
-
});
|
|
74
|
-
}
|
|
75
|
-
function hasCommand(name) {
|
|
76
|
-
const result = (0, node_child_process_1.spawnSync)("which", [name], { stdio: "ignore" });
|
|
77
|
-
return result.status === 0;
|
|
78
|
-
}
|
|
79
|
-
async function askClientChoice() {
|
|
80
|
-
const rl = (0, promises_1.createInterface)({
|
|
81
|
-
input: process.stdin,
|
|
82
|
-
output: process.stdout,
|
|
83
|
-
});
|
|
84
|
-
try {
|
|
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
|
-
.trim()
|
|
87
|
-
.toLowerCase();
|
|
88
|
-
if (answer === "2" || answer === "codex")
|
|
89
|
-
return "codex";
|
|
90
|
-
if (answer === "3" || answer === "openclaw")
|
|
91
|
-
return "openclaw";
|
|
92
|
-
if (answer === "4" || answer === "opencode")
|
|
93
|
-
return "opencode";
|
|
94
|
-
if (answer === "5" || answer === "both")
|
|
95
|
-
return "both";
|
|
96
|
-
return "all";
|
|
97
|
-
}
|
|
98
|
-
finally {
|
|
99
|
-
rl.close();
|
|
100
|
-
}
|
|
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
|
-
}
|
|
123
|
-
async function resolveClientChoice(mode) {
|
|
124
|
-
if (mode === "all") {
|
|
125
|
-
const detected = detectedClientTargets();
|
|
126
|
-
return countEnabledTargets(detected) > 0 ? detected : { codex: true, openclaw: false, opencode: false };
|
|
127
|
-
}
|
|
128
|
-
if (mode === "codex" || mode === "openclaw" || mode === "opencode" || mode === "both") {
|
|
129
|
-
return clientTargetsForChoice(mode);
|
|
130
|
-
}
|
|
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;
|
|
149
|
-
}
|
|
150
|
-
class ProxyCommand extends base_command_1.BaseCommand {
|
|
151
|
-
async run() {
|
|
152
|
-
await this.runSafe(async () => {
|
|
153
|
-
const { flags } = await this.parse(ProxyCommand);
|
|
154
|
-
const managed = await (0, config_1.readManagedConfig)();
|
|
155
|
-
if (!managed.apiKey?.trim()) {
|
|
156
|
-
throw new Error('API key auth is required. Re-link using "theclawbay link --api-key <key>".');
|
|
157
|
-
}
|
|
158
|
-
const backendUrl = normalizeUrl(flags.backend ?? managed.backendUrl, "--backend");
|
|
159
|
-
const apiKey = (flags["api-key"] ?? managed.apiKey ?? "").trim();
|
|
160
|
-
if (!apiKey) {
|
|
161
|
-
throw new Error("API key is required for WAN proxy mode.");
|
|
162
|
-
}
|
|
163
|
-
const basePath = ensureLeadingSlash(flags["base-path"].trim()).replace(/\/+$/g, "");
|
|
164
|
-
const proxyBase = `${trimTrailingSlash(backendUrl)}${basePath}`;
|
|
165
|
-
const targets = await resolveClientChoice(flags.client);
|
|
166
|
-
const server = node_http_1.default.createServer(async (req, res) => {
|
|
167
|
-
const method = req.method?.toUpperCase() ?? "GET";
|
|
168
|
-
const incomingUrl = new URL(req.url || "/", "http://local-proxy.invalid");
|
|
169
|
-
const routePath = incomingUrl.pathname.replace(/^\/+/, "");
|
|
170
|
-
const upstreamUrl = joinUrl(proxyBase, routePath);
|
|
171
|
-
const urlWithQuery = `${upstreamUrl}${incomingUrl.search || ""}`;
|
|
172
|
-
try {
|
|
173
|
-
const headers = headersFromIncoming(req.headers);
|
|
174
|
-
headers.set("Authorization", `Bearer ${apiKey}`);
|
|
175
|
-
const bodyBuffer = await readIncomingBody(req);
|
|
176
|
-
const body = bodyBuffer ? bufferToArrayBuffer(bodyBuffer) : undefined;
|
|
177
|
-
const upstream = await fetch(urlWithQuery, {
|
|
178
|
-
method,
|
|
179
|
-
headers,
|
|
180
|
-
body,
|
|
181
|
-
redirect: "manual",
|
|
182
|
-
});
|
|
183
|
-
res.statusCode = upstream.status;
|
|
184
|
-
res.statusMessage = upstream.statusText;
|
|
185
|
-
writeUpstreamHeaders(res, upstream.headers);
|
|
186
|
-
if (!upstream.body || method === "HEAD") {
|
|
187
|
-
res.end();
|
|
188
|
-
return;
|
|
189
|
-
}
|
|
190
|
-
const stream = node_stream_1.Readable.fromWeb(upstream.body);
|
|
191
|
-
stream.on("error", () => {
|
|
192
|
-
res.destroy();
|
|
193
|
-
});
|
|
194
|
-
stream.pipe(res);
|
|
195
|
-
}
|
|
196
|
-
catch (error) {
|
|
197
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
198
|
-
res.statusCode = 502;
|
|
199
|
-
res.setHeader("Content-Type", "application/json");
|
|
200
|
-
res.end(JSON.stringify({ error: `proxy relay failed: ${message}` }));
|
|
201
|
-
}
|
|
202
|
-
});
|
|
203
|
-
await new Promise((resolve, reject) => {
|
|
204
|
-
server.once("error", reject);
|
|
205
|
-
server.listen(flags.port, flags.host, () => {
|
|
206
|
-
server.off("error", reject);
|
|
207
|
-
resolve();
|
|
208
|
-
});
|
|
209
|
-
});
|
|
210
|
-
const localBase = `http://${flags.host}:${flags.port}`;
|
|
211
|
-
this.log(`theclawbay WAN relay listening at ${localBase}`);
|
|
212
|
-
this.log(`Forwarding to ${proxyBase}`);
|
|
213
|
-
this.log("");
|
|
214
|
-
if (targets.codex) {
|
|
215
|
-
this.log("Codex CLI config snippet:");
|
|
216
|
-
this.log('model_provider = "theclawbay-wan"');
|
|
217
|
-
this.log("");
|
|
218
|
-
this.log("[model_providers.theclawbay-wan]");
|
|
219
|
-
this.log('name = "OpenAI"');
|
|
220
|
-
this.log(`base_url = "${localBase}/backend-api/codex"`);
|
|
221
|
-
this.log('wire_api = "responses"');
|
|
222
|
-
this.log(`chatgpt_base_url = "${localBase}"`);
|
|
223
|
-
this.log("requires_openai_auth = true");
|
|
224
|
-
this.log("");
|
|
225
|
-
}
|
|
226
|
-
if (targets.openclaw) {
|
|
227
|
-
const openClawProviderJson = JSON.stringify({
|
|
228
|
-
baseUrl: `${localBase}/v1`,
|
|
229
|
-
apiKey: "theclawbay-local",
|
|
230
|
-
api: "openai-responses",
|
|
231
|
-
models: [
|
|
232
|
-
{ id: "gpt-5.3-codex", name: "GPT-5.3 Codex" },
|
|
233
|
-
{ id: "gpt-5.3-codex-spark", name: "GPT-5.3 Codex Spark" },
|
|
234
|
-
{ id: "gpt-5.2-codex", name: "GPT-5.2 Codex" },
|
|
235
|
-
{ id: "gpt-5.1-codex", name: "GPT-5.1 Codex" },
|
|
236
|
-
],
|
|
237
|
-
});
|
|
238
|
-
this.log("OpenClaw setup:");
|
|
239
|
-
this.log("Run these once:");
|
|
240
|
-
this.log('openclaw config set agents.defaults.model.primary "theclawbay/gpt-5.3-codex"');
|
|
241
|
-
this.log('openclaw config set models.mode "merge"');
|
|
242
|
-
this.log(`openclaw config set models.providers.theclawbay '${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
|
-
theclawbay: {
|
|
251
|
-
npm: "@ai-sdk/openai-compatible",
|
|
252
|
-
name: "theclawbay",
|
|
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: "theclawbay/gpt-5.3-codex",
|
|
263
|
-
}, null, 2));
|
|
264
|
-
this.log("");
|
|
265
|
-
}
|
|
266
|
-
await new Promise((resolve) => {
|
|
267
|
-
const stop = () => {
|
|
268
|
-
server.close(() => resolve());
|
|
269
|
-
};
|
|
270
|
-
process.once("SIGINT", stop);
|
|
271
|
-
process.once("SIGTERM", stop);
|
|
272
|
-
});
|
|
273
|
-
});
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
ProxyCommand.description = "Run a local relay that forwards Codex/OpenAI requests to your The Claw Bay backend using API-key auth";
|
|
277
|
-
ProxyCommand.flags = {
|
|
278
|
-
host: core_1.Flags.string({
|
|
279
|
-
default: "127.0.0.1",
|
|
280
|
-
description: "Local bind host",
|
|
281
|
-
}),
|
|
282
|
-
port: core_1.Flags.integer({
|
|
283
|
-
default: 2455,
|
|
284
|
-
description: "Local bind port",
|
|
285
|
-
}),
|
|
286
|
-
backend: core_1.Flags.string({
|
|
287
|
-
required: false,
|
|
288
|
-
description: "Override backend URL (defaults to linked managed backend)",
|
|
289
|
-
}),
|
|
290
|
-
"api-key": core_1.Flags.string({
|
|
291
|
-
required: false,
|
|
292
|
-
aliases: ["apiKey"],
|
|
293
|
-
description: "Override API key (defaults to linked managed API key)",
|
|
294
|
-
}),
|
|
295
|
-
"base-path": core_1.Flags.string({
|
|
296
|
-
default: "/api/codex-auth/v1/proxy",
|
|
297
|
-
description: "Server proxy base path",
|
|
298
|
-
}),
|
|
299
|
-
client: core_1.Flags.string({
|
|
300
|
-
required: false,
|
|
301
|
-
default: "auto",
|
|
302
|
-
options: ["auto", "codex", "openclaw", "opencode", "both", "all"],
|
|
303
|
-
description: "Client target to guide setup for",
|
|
304
|
-
}),
|
|
305
|
-
};
|
|
306
|
-
exports.default = ProxyCommand;
|