theclawbay 0.2.0 → 0.2.3
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 +25 -22
- package/dist/commands/proxy.d.ts +1 -0
- package/dist/commands/proxy.js +81 -9
- package/dist/commands/setup.d.ts +13 -0
- package/dist/commands/setup.js +295 -0
- package/package.json +9 -3
package/README.md
CHANGED
|
@@ -2,11 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
`theclawbay` is a customer CLI for The Claw Bay.
|
|
4
4
|
|
|
5
|
-
It now does one job:
|
|
6
|
-
|
|
7
|
-
- link your purchased API key
|
|
8
|
-
- run a local relay that forwards Codex/OpenAI requests to your The Claw Bay backend
|
|
9
|
-
|
|
10
5
|
This package no longer runs local account login automation or local account switching flows.
|
|
11
6
|
|
|
12
7
|
> Not affiliated with OpenAI or Codex. Not an official tool.
|
|
@@ -14,7 +9,7 @@ This package no longer runs local account login automation or local account swit
|
|
|
14
9
|
## Requirements
|
|
15
10
|
|
|
16
11
|
- Node.js 18+
|
|
17
|
-
- Codex CLI
|
|
12
|
+
- Codex CLI (`@openai/codex`) and/or OpenClaw CLI
|
|
18
13
|
|
|
19
14
|
## Install
|
|
20
15
|
|
|
@@ -22,25 +17,42 @@ This package no longer runs local account login automation or local account swit
|
|
|
22
17
|
npm i -g theclawbay
|
|
23
18
|
```
|
|
24
19
|
|
|
25
|
-
##
|
|
20
|
+
## One-Time Setup (Recommended)
|
|
26
21
|
|
|
27
22
|
Use your purchased API key from your dashboard:
|
|
28
23
|
|
|
29
24
|
```sh
|
|
30
|
-
theclawbay
|
|
25
|
+
theclawbay setup --api-key <apiKey>
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
This auto-detects installed clients and writes direct WAN API-key config so users can run directly.
|
|
29
|
+
|
|
30
|
+
Explicit client targeting:
|
|
31
|
+
|
|
32
|
+
```sh
|
|
33
|
+
theclawbay setup --api-key <apiKey> --client codex
|
|
34
|
+
theclawbay setup --api-key <apiKey> --client openclaw
|
|
35
|
+
theclawbay setup --api-key <apiKey> --client both
|
|
31
36
|
```
|
|
32
37
|
|
|
33
|
-
|
|
38
|
+
If needed, skip the automatic Codex login step:
|
|
39
|
+
|
|
40
|
+
```sh
|
|
41
|
+
theclawbay setup --api-key <apiKey> --skip-login
|
|
42
|
+
```
|
|
34
43
|
|
|
35
44
|
If you operate a custom backend, pass it explicitly:
|
|
36
45
|
|
|
37
46
|
```sh
|
|
38
|
-
theclawbay
|
|
47
|
+
theclawbay setup --api-key <apiKey> --backend https://your-domain.com
|
|
39
48
|
```
|
|
40
49
|
|
|
41
|
-
## Run Relay
|
|
50
|
+
## Run Relay (Optional)
|
|
51
|
+
|
|
52
|
+
Only needed as a fallback compatibility mode:
|
|
42
53
|
|
|
43
54
|
```sh
|
|
55
|
+
theclawbay link --api-key <apiKey>
|
|
44
56
|
theclawbay proxy
|
|
45
57
|
```
|
|
46
58
|
|
|
@@ -48,17 +60,8 @@ By default this starts a local relay on `http://127.0.0.1:2455` and forwards to:
|
|
|
48
60
|
|
|
49
61
|
- `https://theclawbay.com/api/codex-auth/v1/proxy/...`
|
|
50
62
|
|
|
51
|
-
The command prints
|
|
52
|
-
|
|
53
|
-
## Common Flags
|
|
54
|
-
|
|
55
|
-
`theclawbay proxy` supports:
|
|
56
|
-
|
|
57
|
-
- `--host` local bind host (default `127.0.0.1`)
|
|
58
|
-
- `--port` local bind port (default `2455`)
|
|
59
|
-
- `--backend` override backend URL from linked config
|
|
60
|
-
- `--api-key` override API key from linked config
|
|
61
|
-
- `--base-path` override server proxy path (default `/api/codex-auth/v1/proxy`)
|
|
63
|
+
The command now auto-detects whether `codex`, `openclaw`, or both are installed and prints setup steps for the detected client(s).
|
|
64
|
+
If both are installed and you're in an interactive terminal, it asks which one you want to configure.
|
|
62
65
|
|
|
63
66
|
## Notes
|
|
64
67
|
|
package/dist/commands/proxy.d.ts
CHANGED
|
@@ -7,6 +7,7 @@ export default class ProxyCommand extends BaseCommand {
|
|
|
7
7
|
backend: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
8
8
|
"api-key": import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
9
9
|
"base-path": import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
10
|
+
client: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
10
11
|
};
|
|
11
12
|
run(): Promise<void>;
|
|
12
13
|
}
|
package/dist/commands/proxy.js
CHANGED
|
@@ -5,6 +5,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
const node_http_1 = __importDefault(require("node:http"));
|
|
7
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");
|
|
8
10
|
const core_1 = require("@oclif/core");
|
|
9
11
|
const base_command_1 = require("../lib/base-command");
|
|
10
12
|
const config_1 = require("../lib/managed/config");
|
|
@@ -70,6 +72,47 @@ function writeUpstreamHeaders(res, source) {
|
|
|
70
72
|
res.setHeader(key, value);
|
|
71
73
|
});
|
|
72
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 client do you want to configure? [1] Codex [2] OpenClaw [3] Both: "))
|
|
86
|
+
.trim()
|
|
87
|
+
.toLowerCase();
|
|
88
|
+
if (answer === "2" || answer === "openclaw")
|
|
89
|
+
return "openclaw";
|
|
90
|
+
if (answer === "3" || answer === "both")
|
|
91
|
+
return "both";
|
|
92
|
+
return "codex";
|
|
93
|
+
}
|
|
94
|
+
finally {
|
|
95
|
+
rl.close();
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
async function resolveClientChoice(mode) {
|
|
99
|
+
if (mode === "codex" || mode === "openclaw" || mode === "both") {
|
|
100
|
+
return mode;
|
|
101
|
+
}
|
|
102
|
+
const codexInstalled = hasCommand("codex");
|
|
103
|
+
const openclawInstalled = hasCommand("openclaw");
|
|
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";
|
|
113
|
+
}
|
|
114
|
+
return "codex";
|
|
115
|
+
}
|
|
73
116
|
class ProxyCommand extends base_command_1.BaseCommand {
|
|
74
117
|
async run() {
|
|
75
118
|
await this.runSafe(async () => {
|
|
@@ -85,6 +128,7 @@ class ProxyCommand extends base_command_1.BaseCommand {
|
|
|
85
128
|
}
|
|
86
129
|
const basePath = ensureLeadingSlash(flags["base-path"].trim()).replace(/\/+$/g, "");
|
|
87
130
|
const proxyBase = `${trimTrailingSlash(backendUrl)}${basePath}`;
|
|
131
|
+
const clientChoice = await resolveClientChoice(flags.client);
|
|
88
132
|
const server = node_http_1.default.createServer(async (req, res) => {
|
|
89
133
|
const method = req.method?.toUpperCase() ?? "GET";
|
|
90
134
|
const incomingUrl = new URL(req.url || "/", "http://local-proxy.invalid");
|
|
@@ -133,15 +177,37 @@ class ProxyCommand extends base_command_1.BaseCommand {
|
|
|
133
177
|
this.log(`theclawbay WAN relay listening at ${localBase}`);
|
|
134
178
|
this.log(`Forwarding to ${proxyBase}`);
|
|
135
179
|
this.log("");
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
180
|
+
if (clientChoice === "codex" || clientChoice === "both") {
|
|
181
|
+
this.log("Codex CLI config snippet:");
|
|
182
|
+
this.log('model_provider = "theclawbay-wan"');
|
|
183
|
+
this.log("");
|
|
184
|
+
this.log("[model_providers.theclawbay-wan]");
|
|
185
|
+
this.log('name = "OpenAI"');
|
|
186
|
+
this.log(`base_url = "${localBase}/backend-api/codex"`);
|
|
187
|
+
this.log('wire_api = "responses"');
|
|
188
|
+
this.log(`chatgpt_base_url = "${localBase}"`);
|
|
189
|
+
this.log("requires_openai_auth = true");
|
|
190
|
+
this.log("");
|
|
191
|
+
}
|
|
192
|
+
if (clientChoice === "openclaw" || clientChoice === "both") {
|
|
193
|
+
const openClawProviderJson = JSON.stringify({
|
|
194
|
+
baseUrl: `${localBase}/v1`,
|
|
195
|
+
apiKey: "theclawbay-local",
|
|
196
|
+
api: "openai-responses",
|
|
197
|
+
models: [
|
|
198
|
+
{ id: "gpt-5.3-codex", name: "GPT-5.3 Codex" },
|
|
199
|
+
{ id: "gpt-5.3-codex-spark", name: "GPT-5.3 Codex Spark" },
|
|
200
|
+
{ id: "gpt-5.2-codex", name: "GPT-5.2 Codex" },
|
|
201
|
+
{ id: "gpt-5.1-codex", name: "GPT-5.1 Codex" },
|
|
202
|
+
],
|
|
203
|
+
});
|
|
204
|
+
this.log("OpenClaw setup:");
|
|
205
|
+
this.log("Run these once:");
|
|
206
|
+
this.log('openclaw config set agents.defaults.model.primary "openai/gpt-5.3-codex"');
|
|
207
|
+
this.log('openclaw config set models.mode "merge"');
|
|
208
|
+
this.log(`openclaw config set models.providers.openai '${openClawProviderJson}' --json`);
|
|
209
|
+
this.log("");
|
|
210
|
+
}
|
|
145
211
|
await new Promise((resolve) => {
|
|
146
212
|
const stop = () => {
|
|
147
213
|
server.close(() => resolve());
|
|
@@ -175,5 +241,11 @@ ProxyCommand.flags = {
|
|
|
175
241
|
default: "/api/codex-auth/v1/proxy",
|
|
176
242
|
description: "Server proxy base path",
|
|
177
243
|
}),
|
|
244
|
+
client: core_1.Flags.string({
|
|
245
|
+
required: false,
|
|
246
|
+
default: "auto",
|
|
247
|
+
options: ["auto", "codex", "openclaw", "both"],
|
|
248
|
+
description: "Client target to guide setup for",
|
|
249
|
+
}),
|
|
178
250
|
};
|
|
179
251
|
exports.default = ProxyCommand;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { BaseCommand } from "../lib/base-command";
|
|
2
|
+
export default class SetupCommand extends BaseCommand {
|
|
3
|
+
static description: string;
|
|
4
|
+
static flags: {
|
|
5
|
+
backend: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
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
|
+
};
|
|
12
|
+
run(): Promise<void>;
|
|
13
|
+
}
|
|
@@ -0,0 +1,295 @@
|
|
|
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_path_1 = __importDefault(require("node:path"));
|
|
8
|
+
const node_child_process_1 = require("node:child_process");
|
|
9
|
+
const promises_2 = require("node:readline/promises");
|
|
10
|
+
const core_1 = require("@oclif/core");
|
|
11
|
+
const base_command_1 = require("../lib/base-command");
|
|
12
|
+
const paths_1 = require("../lib/config/paths");
|
|
13
|
+
const api_key_1 = require("../lib/managed/api-key");
|
|
14
|
+
const config_1 = require("../lib/managed/config");
|
|
15
|
+
const errors_1 = require("../lib/managed/errors");
|
|
16
|
+
const DEFAULT_BACKEND_URL = "https://theclawbay.com";
|
|
17
|
+
const DEFAULT_PROVIDER_ID = "theclawbay-wan";
|
|
18
|
+
const MANAGED_START = "# theclawbay-managed:start";
|
|
19
|
+
const MANAGED_END = "# theclawbay-managed:end";
|
|
20
|
+
const DEFAULT_OPENCLAW_MODEL = "gpt-5.3-codex";
|
|
21
|
+
function trimTrailingSlash(value) {
|
|
22
|
+
return value.replace(/\/+$/g, "");
|
|
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 hasCommand(name) {
|
|
34
|
+
const result = (0, node_child_process_1.spawnSync)("which", [name], { stdio: "ignore" });
|
|
35
|
+
return result.status === 0;
|
|
36
|
+
}
|
|
37
|
+
async function askClientChoice() {
|
|
38
|
+
const rl = (0, promises_2.createInterface)({
|
|
39
|
+
input: process.stdin,
|
|
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)
|
|
77
|
+
return source;
|
|
78
|
+
const end = source.indexOf(MANAGED_END, start);
|
|
79
|
+
if (end < 0)
|
|
80
|
+
return source.slice(0, start).trimEnd() + "\n";
|
|
81
|
+
return (source.slice(0, start) + source.slice(end + MANAGED_END.length)).trimEnd() + "\n";
|
|
82
|
+
}
|
|
83
|
+
function removeProviderTable(source, providerId) {
|
|
84
|
+
const header = `[model_providers.${providerId}]`;
|
|
85
|
+
const lines = source.split(/\r?\n/);
|
|
86
|
+
const output = [];
|
|
87
|
+
for (let i = 0; i < lines.length; i++) {
|
|
88
|
+
if (lines[i]?.trim() !== header) {
|
|
89
|
+
output.push(lines[i] ?? "");
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
i++;
|
|
93
|
+
while (i < lines.length && !/^\s*\[[^\]]+\]\s*$/.test(lines[i] ?? "")) {
|
|
94
|
+
i++;
|
|
95
|
+
}
|
|
96
|
+
i--;
|
|
97
|
+
}
|
|
98
|
+
return `${output.join("\n").trimEnd()}\n`;
|
|
99
|
+
}
|
|
100
|
+
function upsertFirstKeyLine(source, key, tomlValue) {
|
|
101
|
+
const lines = source.split(/\r?\n/);
|
|
102
|
+
for (let i = 0; i < lines.length; i++) {
|
|
103
|
+
const line = lines[i] ?? "";
|
|
104
|
+
if (/^\s*#/.test(line))
|
|
105
|
+
continue;
|
|
106
|
+
if (/^\s*\[/.test(line))
|
|
107
|
+
break;
|
|
108
|
+
if (new RegExp(`^\\s*${key}\\s*=`).test(line)) {
|
|
109
|
+
lines[i] = `${key} = ${tomlValue}`;
|
|
110
|
+
return `${lines.join("\n").trimEnd()}\n`;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return `${`${key} = ${tomlValue}\n${source}`.trimEnd()}\n`;
|
|
114
|
+
}
|
|
115
|
+
async function writeCodexConfig(params) {
|
|
116
|
+
const configPath = node_path_1.default.join(paths_1.codexDir, "config.toml");
|
|
117
|
+
await promises_1.default.mkdir(paths_1.codexDir, { recursive: true });
|
|
118
|
+
let existing = "";
|
|
119
|
+
try {
|
|
120
|
+
existing = await promises_1.default.readFile(configPath, "utf8");
|
|
121
|
+
}
|
|
122
|
+
catch (error) {
|
|
123
|
+
const err = error;
|
|
124
|
+
if (err.code !== "ENOENT")
|
|
125
|
+
throw error;
|
|
126
|
+
}
|
|
127
|
+
const proxyRoot = `${trimTrailingSlash(params.backendUrl)}/api/codex-auth/v1/proxy`;
|
|
128
|
+
let next = existing;
|
|
129
|
+
next = removeManagedBlock(next);
|
|
130
|
+
next = removeProviderTable(next, params.providerId);
|
|
131
|
+
next = upsertFirstKeyLine(next, "model_provider", `"${params.providerId}"`);
|
|
132
|
+
const managedBlock = [
|
|
133
|
+
MANAGED_START,
|
|
134
|
+
`[model_providers.${params.providerId}]`,
|
|
135
|
+
'name = "OpenAI"',
|
|
136
|
+
`base_url = "${proxyRoot}/backend-api/codex"`,
|
|
137
|
+
'wire_api = "responses"',
|
|
138
|
+
`chatgpt_base_url = "${proxyRoot}"`,
|
|
139
|
+
"requires_openai_auth = true",
|
|
140
|
+
MANAGED_END,
|
|
141
|
+
"",
|
|
142
|
+
].join("\n");
|
|
143
|
+
if (next.trim().length) {
|
|
144
|
+
next = `${next.trimEnd()}\n\n`;
|
|
145
|
+
}
|
|
146
|
+
next += managedBlock;
|
|
147
|
+
await promises_1.default.writeFile(configPath, next, "utf8");
|
|
148
|
+
return configPath;
|
|
149
|
+
}
|
|
150
|
+
function codexLoginWithApiKey(apiKey) {
|
|
151
|
+
const run = (0, node_child_process_1.spawnSync)("codex", ["login", "--with-api-key"], {
|
|
152
|
+
input: `${apiKey}\n`,
|
|
153
|
+
encoding: "utf8",
|
|
154
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
155
|
+
});
|
|
156
|
+
if (run.status === 0)
|
|
157
|
+
return;
|
|
158
|
+
const stderr = (run.stderr ?? "").trim();
|
|
159
|
+
const stdout = (run.stdout ?? "").trim();
|
|
160
|
+
const details = stderr || stdout || "codex login returned a non-zero exit status";
|
|
161
|
+
throw new Error(`failed to store API key in Codex CLI: ${details}`);
|
|
162
|
+
}
|
|
163
|
+
function runOpenClawConfigCommand(args) {
|
|
164
|
+
const run = (0, node_child_process_1.spawnSync)("openclaw", args, {
|
|
165
|
+
encoding: "utf8",
|
|
166
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
167
|
+
});
|
|
168
|
+
if (run.status === 0)
|
|
169
|
+
return;
|
|
170
|
+
const stderr = (run.stderr ?? "").trim();
|
|
171
|
+
const stdout = (run.stdout ?? "").trim();
|
|
172
|
+
const details = stderr || stdout || "openclaw command returned a non-zero exit status";
|
|
173
|
+
throw new Error(`failed to update OpenClaw config: ${details}`);
|
|
174
|
+
}
|
|
175
|
+
function setupOpenClaw(params) {
|
|
176
|
+
const base = trimTrailingSlash(params.backendUrl);
|
|
177
|
+
const model = params.model.trim() || DEFAULT_OPENCLAW_MODEL;
|
|
178
|
+
const provider = {
|
|
179
|
+
baseUrl: `${base}/api/codex-auth/v1/proxy/v1`,
|
|
180
|
+
apiKey: params.apiKey,
|
|
181
|
+
api: "openai-responses",
|
|
182
|
+
models: [
|
|
183
|
+
{ id: "gpt-5.3-codex", name: "GPT-5.3 Codex" },
|
|
184
|
+
{ id: "gpt-5.3-codex-spark", name: "GPT-5.3 Codex Spark" },
|
|
185
|
+
{ id: "gpt-5.2-codex", name: "GPT-5.2 Codex" },
|
|
186
|
+
{ id: "gpt-5.1-codex", name: "GPT-5.1 Codex" },
|
|
187
|
+
],
|
|
188
|
+
};
|
|
189
|
+
runOpenClawConfigCommand(["config", "set", "agents.defaults.model.primary", `openai/${model}`]);
|
|
190
|
+
runOpenClawConfigCommand(["config", "set", "models.mode", "merge"]);
|
|
191
|
+
runOpenClawConfigCommand([
|
|
192
|
+
"config",
|
|
193
|
+
"set",
|
|
194
|
+
"models.providers.openai",
|
|
195
|
+
JSON.stringify(provider),
|
|
196
|
+
"--json",
|
|
197
|
+
]);
|
|
198
|
+
}
|
|
199
|
+
class SetupCommand extends base_command_1.BaseCommand {
|
|
200
|
+
async run() {
|
|
201
|
+
await this.runSafe(async () => {
|
|
202
|
+
const { flags } = await this.parse(SetupCommand);
|
|
203
|
+
let managed = null;
|
|
204
|
+
try {
|
|
205
|
+
managed = await (0, config_1.readManagedConfig)();
|
|
206
|
+
}
|
|
207
|
+
catch (error) {
|
|
208
|
+
if (!(error instanceof errors_1.ManagedConfigMissingError))
|
|
209
|
+
throw error;
|
|
210
|
+
}
|
|
211
|
+
const apiKey = (flags["api-key"] ?? managed?.apiKey ?? "").trim();
|
|
212
|
+
if (!apiKey) {
|
|
213
|
+
throw new Error('API key is required. Run "theclawbay setup --api-key <key>".');
|
|
214
|
+
}
|
|
215
|
+
const backendRaw = flags.backend ?? (0, api_key_1.tryInferBackendUrlFromApiKey)(apiKey) ?? managed?.backendUrl ?? DEFAULT_BACKEND_URL;
|
|
216
|
+
const backendUrl = normalizeUrl(backendRaw, "--backend");
|
|
217
|
+
const clientChoice = await resolveClientChoice(flags.client);
|
|
218
|
+
const codexWanted = clientChoice === "codex" || clientChoice === "both";
|
|
219
|
+
const openClawWanted = clientChoice === "openclaw" || clientChoice === "both";
|
|
220
|
+
await (0, config_1.writeManagedConfig)({ backendUrl, apiKey });
|
|
221
|
+
let codexConfigPath = null;
|
|
222
|
+
if (codexWanted) {
|
|
223
|
+
codexConfigPath = await writeCodexConfig({
|
|
224
|
+
backendUrl,
|
|
225
|
+
providerId: flags.provider.trim() || DEFAULT_PROVIDER_ID,
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
if (codexWanted && !flags["skip-login"]) {
|
|
229
|
+
if (!hasCommand("codex")) {
|
|
230
|
+
throw new Error('codex CLI not found. Install @openai/codex, or run setup with --client openclaw.');
|
|
231
|
+
}
|
|
232
|
+
codexLoginWithApiKey(apiKey);
|
|
233
|
+
}
|
|
234
|
+
if (openClawWanted) {
|
|
235
|
+
if (!hasCommand("openclaw")) {
|
|
236
|
+
throw new Error('openclaw CLI not found. Install OpenClaw, or run setup with --client codex.');
|
|
237
|
+
}
|
|
238
|
+
setupOpenClaw({
|
|
239
|
+
backendUrl,
|
|
240
|
+
apiKey,
|
|
241
|
+
model: flags["openclaw-model"],
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
this.log(`Linked. Managed config written to ${paths_1.managedConfigPath}`);
|
|
245
|
+
this.log(`Backend: ${backendUrl}`);
|
|
246
|
+
if (codexConfigPath) {
|
|
247
|
+
this.log(`Codex config updated at ${codexConfigPath}`);
|
|
248
|
+
}
|
|
249
|
+
if (openClawWanted) {
|
|
250
|
+
this.log("OpenClaw model provider updated for direct WAN API-key routing.");
|
|
251
|
+
}
|
|
252
|
+
if (codexWanted && flags["skip-login"]) {
|
|
253
|
+
this.log("Skipped Codex login. Run: printenv OPENAI_API_KEY | codex login --with-api-key");
|
|
254
|
+
}
|
|
255
|
+
else if (codexWanted) {
|
|
256
|
+
this.log("Codex API-key login updated.");
|
|
257
|
+
}
|
|
258
|
+
this.log('Done. Users can run directly (no "theclawbay proxy" required in recommended setup).');
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
SetupCommand.description = "One-time direct setup for Codex/OpenClaw: link key and route directly to The Claw Bay backend";
|
|
263
|
+
SetupCommand.flags = {
|
|
264
|
+
backend: core_1.Flags.string({
|
|
265
|
+
required: false,
|
|
266
|
+
description: "Backend base URL override (default: https://theclawbay.com)",
|
|
267
|
+
}),
|
|
268
|
+
"api-key": core_1.Flags.string({
|
|
269
|
+
required: false,
|
|
270
|
+
aliases: ["apiKey"],
|
|
271
|
+
description: "API key issued by your The Claw Bay dashboard",
|
|
272
|
+
}),
|
|
273
|
+
provider: core_1.Flags.string({
|
|
274
|
+
required: false,
|
|
275
|
+
default: DEFAULT_PROVIDER_ID,
|
|
276
|
+
description: "Codex model provider id to write into ~/.codex/config.toml",
|
|
277
|
+
}),
|
|
278
|
+
client: core_1.Flags.string({
|
|
279
|
+
required: false,
|
|
280
|
+
default: "auto",
|
|
281
|
+
options: ["auto", "codex", "openclaw", "both"],
|
|
282
|
+
description: "Client target to configure",
|
|
283
|
+
}),
|
|
284
|
+
"openclaw-model": core_1.Flags.string({
|
|
285
|
+
required: false,
|
|
286
|
+
default: DEFAULT_OPENCLAW_MODEL,
|
|
287
|
+
description: "OpenClaw model id to set as default (without provider prefix)",
|
|
288
|
+
}),
|
|
289
|
+
"skip-login": core_1.Flags.boolean({
|
|
290
|
+
required: false,
|
|
291
|
+
default: false,
|
|
292
|
+
description: "Skip `codex login --with-api-key`",
|
|
293
|
+
}),
|
|
294
|
+
};
|
|
295
|
+
exports.default = SetupCommand;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "theclawbay",
|
|
3
|
-
"version": "0.2.
|
|
4
|
-
"description": "The Claw Bay CLI:
|
|
3
|
+
"version": "0.2.3",
|
|
4
|
+
"description": "The Claw Bay CLI: one-time API-key setup for direct Codex access, with optional relay fallback.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"bin": {
|
|
7
7
|
"theclawbay": "dist/index.js"
|
|
@@ -10,7 +10,11 @@
|
|
|
10
10
|
"types": "dist/index.d.ts",
|
|
11
11
|
"scripts": {
|
|
12
12
|
"build": "node -e \"require('node:fs').rmSync('dist',{recursive:true,force:true})\" && tsc -p tsconfig.json && node -e \"require('node:fs').chmodSync('dist/index.js',0o755)\"",
|
|
13
|
-
"prepublishOnly": "npm run build"
|
|
13
|
+
"prepublishOnly": "npm run build",
|
|
14
|
+
"release:patch": "./scripts/release-npm.sh patch",
|
|
15
|
+
"release:minor": "./scripts/release-npm.sh minor",
|
|
16
|
+
"release:major": "./scripts/release-npm.sh major",
|
|
17
|
+
"release:dry-run": "./scripts/release-npm.sh patch --dry-run"
|
|
14
18
|
},
|
|
15
19
|
"engines": {
|
|
16
20
|
"node": ">=18"
|
|
@@ -24,6 +28,8 @@
|
|
|
24
28
|
"codex",
|
|
25
29
|
"claw-bay",
|
|
26
30
|
"cli",
|
|
31
|
+
"setup",
|
|
32
|
+
"wan",
|
|
27
33
|
"proxy",
|
|
28
34
|
"api-key",
|
|
29
35
|
"relay"
|