theclawbay 0.2.1 → 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 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 installed (`@openai/codex`)
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
- ## Link
20
+ ## One-Time Setup (Recommended)
26
21
 
27
22
  Use your purchased API key from your dashboard:
28
23
 
29
24
  ```sh
30
- theclawbay link --api-key <apiKey>
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
- This defaults to `https://theclawbay.com`.
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 link --api-key <apiKey> --backend https://your-domain.com
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
 
@@ -51,17 +63,6 @@ By default this starts a local relay on `http://127.0.0.1:2455` and forwards to:
51
63
  The command now auto-detects whether `codex`, `openclaw`, or both are installed and prints setup steps for the detected client(s).
52
64
  If both are installed and you're in an interactive terminal, it asks which one you want to configure.
53
65
 
54
- ## Common Flags
55
-
56
- `theclawbay proxy` supports:
57
-
58
- - `--host` local bind host (default `127.0.0.1`)
59
- - `--port` local bind port (default `2455`)
60
- - `--backend` override backend URL from linked config
61
- - `--api-key` override API key from linked config
62
- - `--base-path` override server proxy path (default `/api/codex-auth/v1/proxy`)
63
- - `--client` setup target: `auto` | `codex` | `openclaw` | `both` (default `auto`)
64
-
65
66
  ## Notes
66
67
 
67
68
  - End users only need API keys. They do not need upstream account credentials.
@@ -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.1",
4
- "description": "The Claw Bay CLI: link an API key and run a local relay to The Claw Bay backend.",
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"