theclawbay 0.3.46 → 0.3.47

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.
@@ -43,7 +43,10 @@ const HISTORY_PROVIDER_NEUTRALIZE_SOURCES = new Set([
43
43
  DEFAULT_PROVIDER_ID,
44
44
  ]);
45
45
  const HISTORY_PROVIDER_DB_MIGRATE_SOURCES = [DEFAULT_PROVIDER_ID, WAN_PROVIDER_ID];
46
- const THECLAWBAY_OPENAI_PROXY_SUFFIX = "/api/codex-auth/v1/proxy/v1";
46
+ const LEGACY_THECLAWBAY_OPENAI_PROXY_SUFFIX = "/api/codex-auth/v1/proxy/v1";
47
+ const CANONICAL_THECLAWBAY_OPENAI_PROXY_SUFFIX = "/v1";
48
+ const THECLAWBAY_CANONICAL_API_HOST = "api.theclawbay.com";
49
+ const THECLAWBAY_WEBSITE_HOSTS = new Set(["theclawbay.com", "www.theclawbay.com"]);
47
50
  const SUPPORTED_MODEL_IDS = new Set((0, supported_models_1.getSupportedModelIds)());
48
51
  const CONTINUE_MODEL_NAME = "The Claw Bay";
49
52
  const TRAE_PATCH_MARKER = "theclawbay-trae-patch";
@@ -166,7 +169,28 @@ function objectRecordOr(value, fallback) {
166
169
  return fallback;
167
170
  }
168
171
  function isTheClawBayOpenAiCompatibleBaseUrl(value) {
169
- return typeof value === "string" && value.trim().endsWith(THECLAWBAY_OPENAI_PROXY_SUFFIX);
172
+ if (typeof value !== "string")
173
+ return false;
174
+ const normalized = value.trim();
175
+ if (!normalized)
176
+ return false;
177
+ try {
178
+ const parsed = new URL(normalized);
179
+ const hostname = parsed.hostname.toLowerCase();
180
+ const pathname = parsed.pathname.replace(/\/+$/g, "");
181
+ if (pathname === LEGACY_THECLAWBAY_OPENAI_PROXY_SUFFIX &&
182
+ (THECLAWBAY_WEBSITE_HOSTS.has(hostname) ||
183
+ hostname === "localhost" ||
184
+ hostname === "127.0.0.1" ||
185
+ hostname === "::1")) {
186
+ return true;
187
+ }
188
+ return (hostname === THECLAWBAY_CANONICAL_API_HOST &&
189
+ pathname === CANONICAL_THECLAWBAY_OPENAI_PROXY_SUFFIX);
190
+ }
191
+ catch {
192
+ return false;
193
+ }
170
194
  }
171
195
  function uniqueStrings(values) {
172
196
  const output = [];
@@ -39,6 +39,8 @@ const DEFAULT_ZO_MODEL = DEFAULT_CODEX_MODEL;
39
39
  const DEFAULT_MODELS = [...SUPPORTED_MODEL_IDS];
40
40
  const PREFERRED_MODELS = [...SUPPORTED_MODEL_IDS];
41
41
  const ENV_KEY_NAME = "THECLAWBAY_API_KEY";
42
+ const CLAUDE_ENV_API_KEY_NAME = "ANTHROPIC_API_KEY";
43
+ const CLAUDE_ENV_BASE_URL_NAME = "ANTHROPIC_BASE_URL";
42
44
  const ENV_FILE = node_path_1.default.join(paths_1.theclawbayConfigDir, "env");
43
45
  const CONTINUE_CONFIG_PATH = node_path_1.default.join(node_os_1.default.homedir(), ".continue", "config.yaml");
44
46
  const CLINE_GLOBAL_STATE_PATH = node_path_1.default.join(node_os_1.default.homedir(), ".cline", "data", "globalState.json");
@@ -60,7 +62,11 @@ const OPENCLAW_PROVIDER_ID = DEFAULT_PROVIDER_ID;
60
62
  const HISTORY_PROVIDER_NEUTRALIZE_SOURCES = new Set(["openai", "theclawbay-wan", DEFAULT_PROVIDER_ID]);
61
63
  const HISTORY_PROVIDER_DB_MIGRATE_SOURCES = ["openai", "theclawbay-wan"];
62
64
  const SETUP_CLIENT_IDS = ["codex", "continue", "cline", "openclaw", "opencode", "kilo", "roo", "trae", "aider", "zo"];
63
- const THECLAWBAY_OPENAI_PROXY_SUFFIX = "/api/codex-auth/v1/proxy/v1";
65
+ const LEGACY_THECLAWBAY_OPENAI_PROXY_SUFFIX = "/api/codex-auth/v1/proxy/v1";
66
+ const CANONICAL_THECLAWBAY_OPENAI_PROXY_SUFFIX = "/v1";
67
+ const CANONICAL_CODEX_NATIVE_PROXY_SUFFIX = "/backend-api/codex";
68
+ const THECLAWBAY_CANONICAL_API_HOST = "api.theclawbay.com";
69
+ const THECLAWBAY_WEBSITE_HOSTS = new Set(["theclawbay.com", "www.theclawbay.com"]);
64
70
  const CONTINUE_MODEL_NAME = "The Claw Bay";
65
71
  const ROO_PROFILE_NAME = "The Claw Bay";
66
72
  const ROO_PROFILE_ID = "theclawbay-openai-compatible";
@@ -303,7 +309,7 @@ async function configureZoByok(params) {
303
309
  throw new Error("Zo was detected, but no active Zo desktop session token could be read. Open Zo, sign in, and rerun setup.");
304
310
  }
305
311
  const desiredName = `${ZO_CONFIG_NAME_PREFIX} ${modelDisplayName(params.model)}`;
306
- const desiredBaseUrl = `${trimTrailingSlash(params.backendUrl)}${THECLAWBAY_OPENAI_PROXY_SUFFIX}`;
312
+ const desiredBaseUrl = openAiCompatibleProxyUrl(params.backendUrl);
307
313
  const body = {
308
314
  provider: "openai-style",
309
315
  format: "openai",
@@ -449,11 +455,51 @@ function shellQuote(value) {
449
455
  function modelDisplayName(modelId) {
450
456
  return MODEL_DISPLAY_NAMES[modelId] ?? modelId;
451
457
  }
458
+ function publicApiOriginForBackendUrl(backendUrl) {
459
+ const trimmed = trimTrailingSlash(backendUrl);
460
+ try {
461
+ const parsed = new URL(trimmed);
462
+ parsed.hash = "";
463
+ parsed.search = "";
464
+ parsed.pathname = "";
465
+ if (THECLAWBAY_WEBSITE_HOSTS.has(parsed.hostname.toLowerCase())) {
466
+ parsed.hostname = THECLAWBAY_CANONICAL_API_HOST;
467
+ }
468
+ return trimTrailingSlash(parsed.toString());
469
+ }
470
+ catch {
471
+ return trimmed;
472
+ }
473
+ }
452
474
  function openAiCompatibleProxyUrl(backendUrl) {
453
- return `${trimTrailingSlash(backendUrl)}${THECLAWBAY_OPENAI_PROXY_SUFFIX}`;
475
+ return `${publicApiOriginForBackendUrl(backendUrl)}${CANONICAL_THECLAWBAY_OPENAI_PROXY_SUFFIX}`;
476
+ }
477
+ function anthropicCompatibleProxyUrl(backendUrl) {
478
+ return `${publicApiOriginForBackendUrl(backendUrl)}/anthropic`;
454
479
  }
455
480
  function isTheClawBayOpenAiCompatibleBaseUrl(value) {
456
- return typeof value === "string" && value.trim().endsWith(THECLAWBAY_OPENAI_PROXY_SUFFIX);
481
+ if (typeof value !== "string")
482
+ return false;
483
+ const normalized = value.trim();
484
+ if (!normalized)
485
+ return false;
486
+ try {
487
+ const parsed = new URL(normalized);
488
+ const hostname = parsed.hostname.toLowerCase();
489
+ const pathname = trimTrailingSlash(parsed.pathname);
490
+ if (pathname === LEGACY_THECLAWBAY_OPENAI_PROXY_SUFFIX &&
491
+ (THECLAWBAY_WEBSITE_HOSTS.has(hostname) ||
492
+ hostname === "localhost" ||
493
+ hostname === "127.0.0.1" ||
494
+ hostname === "::1")) {
495
+ return true;
496
+ }
497
+ return (hostname === THECLAWBAY_CANONICAL_API_HOST &&
498
+ pathname === CANONICAL_THECLAWBAY_OPENAI_PROXY_SUFFIX);
499
+ }
500
+ catch {
501
+ return false;
502
+ }
457
503
  }
458
504
  function uniqueStrings(values) {
459
505
  const output = [];
@@ -922,7 +968,7 @@ function summarizeModelFetchFailure(detail) {
922
968
  return `${normalized.slice(0, 137).trimEnd()}...`;
923
969
  }
924
970
  async function fetchBackendModelIds(backendUrl, apiKey) {
925
- const url = `${trimTrailingSlash(backendUrl)}/api/codex-auth/v1/proxy/v1/models`;
971
+ const url = `${openAiCompatibleProxyUrl(backendUrl)}/models`;
926
972
  try {
927
973
  const response = await fetch(url, {
928
974
  headers: {
@@ -971,6 +1017,56 @@ async function fetchBackendModelIds(backendUrl, apiKey) {
971
1017
  };
972
1018
  }
973
1019
  }
1020
+ async function fetchClaudeModelIds(backendUrl, apiKey) {
1021
+ const url = `${anthropicCompatibleProxyUrl(backendUrl)}/v1/models`;
1022
+ try {
1023
+ const response = await fetch(url, {
1024
+ headers: {
1025
+ "x-api-key": apiKey,
1026
+ Accept: "application/json",
1027
+ "User-Agent": CLI_HTTP_USER_AGENT,
1028
+ },
1029
+ signal: AbortSignal.timeout(10000),
1030
+ });
1031
+ if (!response.ok) {
1032
+ const responseText = await response.text().catch(() => "");
1033
+ let detail = responseText.trim();
1034
+ try {
1035
+ const parsed = JSON.parse(responseText);
1036
+ if (typeof parsed.error === "string" && parsed.error.trim()) {
1037
+ detail = parsed.error.trim();
1038
+ }
1039
+ else if (typeof parsed.code === "string" && parsed.code.trim()) {
1040
+ detail = parsed.code.trim();
1041
+ }
1042
+ }
1043
+ catch {
1044
+ // Keep the raw response text when the body is not JSON.
1045
+ }
1046
+ return {
1047
+ ids: null,
1048
+ failure: `HTTP ${response.status}${detail ? `: ${summarizeModelFetchFailure(detail)}` : ""}`,
1049
+ };
1050
+ }
1051
+ const body = (await response.json());
1052
+ const ids = (body.data ?? [])
1053
+ .map((entry) => (typeof entry.id === "string" ? entry.id.trim() : ""))
1054
+ .filter((id) => id.length > 0);
1055
+ if (!ids.length) {
1056
+ return {
1057
+ ids: null,
1058
+ failure: "backend returned an empty Claude model list",
1059
+ };
1060
+ }
1061
+ return { ids };
1062
+ }
1063
+ catch (error) {
1064
+ return {
1065
+ ids: null,
1066
+ failure: summarizeModelFetchFailure(error instanceof Error ? error.message : String(error)),
1067
+ };
1068
+ }
1069
+ }
974
1070
  function kiloStorageCandidates() {
975
1071
  const home = node_os_1.default.homedir();
976
1072
  const candidates = [node_path_1.default.join(home, ".config", "kilo"), node_path_1.default.join(home, ".kilo")];
@@ -1113,6 +1209,20 @@ async function resolveModels(backendUrl, apiKey) {
1113
1209
  note,
1114
1210
  };
1115
1211
  }
1212
+ async function resolveClaudeAccess(backendUrl, apiKey) {
1213
+ const { ids, failure } = await fetchClaudeModelIds(backendUrl, apiKey);
1214
+ if (!ids?.length) {
1215
+ return {
1216
+ enabled: false,
1217
+ models: [],
1218
+ note: failure ? `Claude Code auto-setup skipped (${failure}).` : undefined,
1219
+ };
1220
+ }
1221
+ return {
1222
+ enabled: true,
1223
+ models: ids,
1224
+ };
1225
+ }
1116
1226
  async function writeCodexConfig(params) {
1117
1227
  const configPath = node_path_1.default.join(paths_1.codexDir, "config.toml");
1118
1228
  await promises_1.default.mkdir(paths_1.codexDir, { recursive: true });
@@ -1125,7 +1235,8 @@ async function writeCodexConfig(params) {
1125
1235
  if (err.code !== "ENOENT")
1126
1236
  throw error;
1127
1237
  }
1128
- const proxyRoot = `${trimTrailingSlash(params.backendUrl)}/api/codex-auth/v1/proxy`;
1238
+ const proxyRoot = publicApiOriginForBackendUrl(params.backendUrl);
1239
+ const nativeCodexBaseUrl = `${proxyRoot}${CANONICAL_CODEX_NATIVE_PROXY_SUFFIX}`;
1129
1240
  let next = existing;
1130
1241
  next = removeManagedBlock(next, MANAGED_START, MANAGED_END);
1131
1242
  next = removeProviderTable(next, DEFAULT_PROVIDER_ID);
@@ -1136,7 +1247,7 @@ async function writeCodexConfig(params) {
1136
1247
  MANAGED_START,
1137
1248
  `[model_providers.${DEFAULT_PROVIDER_ID}]`,
1138
1249
  'name = "OpenAI"',
1139
- `base_url = "${proxyRoot}/backend-api/codex"`,
1250
+ `base_url = "${nativeCodexBaseUrl}"`,
1140
1251
  'wire_api = "responses"',
1141
1252
  "requires_openai_auth = true",
1142
1253
  "supports_websockets = true",
@@ -1287,14 +1398,17 @@ async function writeAiderConfig(params) {
1287
1398
  await promises_1.default.writeFile(AIDER_CONFIG_PATH, next, "utf8");
1288
1399
  return AIDER_CONFIG_PATH;
1289
1400
  }
1290
- async function persistApiKeyEnv(apiKey) {
1401
+ async function persistApiKeyEnv(params) {
1291
1402
  const envDir = node_path_1.default.dirname(ENV_FILE);
1292
1403
  await promises_1.default.mkdir(envDir, { recursive: true });
1293
- const envContents = [
1404
+ const envLines = [
1294
1405
  "# Generated by theclawbay setup",
1295
- `export ${ENV_KEY_NAME}=${shellQuote(apiKey)}`,
1296
- "",
1297
- ].join("\n");
1406
+ `export ${ENV_KEY_NAME}=${shellQuote(params.apiKey)}`,
1407
+ ];
1408
+ if (params.claudeEnabled) {
1409
+ envLines.push("", "# Official Claude Code CLI", `export ${CLAUDE_ENV_API_KEY_NAME}=${shellQuote(params.apiKey)}`, `export ${CLAUDE_ENV_BASE_URL_NAME}=${shellQuote(anthropicCompatibleProxyUrl(params.backendUrl))}`);
1410
+ }
1411
+ const envContents = `${envLines.join("\n")}\n`;
1298
1412
  await promises_1.default.writeFile(ENV_FILE, envContents, "utf8");
1299
1413
  await promises_1.default.chmod(ENV_FILE, 0o600);
1300
1414
  const sourceLine = `[ -f "$HOME/.config/theclawbay/env" ] && . "$HOME/.config/theclawbay/env"`;
@@ -1315,7 +1429,15 @@ async function persistApiKeyEnv(apiKey) {
1315
1429
  await promises_1.default.writeFile(rcPath, withBlock, "utf8");
1316
1430
  updated.push(rcPath);
1317
1431
  }
1318
- process.env[ENV_KEY_NAME] = apiKey;
1432
+ process.env[ENV_KEY_NAME] = params.apiKey;
1433
+ if (params.claudeEnabled) {
1434
+ process.env[CLAUDE_ENV_API_KEY_NAME] = params.apiKey;
1435
+ process.env[CLAUDE_ENV_BASE_URL_NAME] = anthropicCompatibleProxyUrl(params.backendUrl);
1436
+ }
1437
+ else {
1438
+ delete process.env[CLAUDE_ENV_API_KEY_NAME];
1439
+ delete process.env[CLAUDE_ENV_BASE_URL_NAME];
1440
+ }
1319
1441
  return updated;
1320
1442
  }
1321
1443
  async function persistVsCodeServerEnvSource() {
@@ -1371,7 +1493,7 @@ function resolveOpenClawPrimaryModel(params) {
1371
1493
  }
1372
1494
  async function patchOpenClawConfigFile(params) {
1373
1495
  const provider = {
1374
- baseUrl: `${trimTrailingSlash(params.backendUrl)}/api/codex-auth/v1/proxy/v1`,
1496
+ baseUrl: openAiCompatibleProxyUrl(params.backendUrl),
1375
1497
  apiKey: params.apiKey,
1376
1498
  api: "openai-responses",
1377
1499
  models: buildOpenClawModels(params.models),
@@ -1684,7 +1806,7 @@ async function writeOpenCodeFamilyConfig(params) {
1684
1806
  }
1685
1807
  const managedOpenAiProvider = objectRecordOr(providerRoot[OPENAI_PROVIDER_ID], {});
1686
1808
  const optionsRoot = objectRecordOr(managedOpenAiProvider.options, {});
1687
- optionsRoot.baseURL = `${trimTrailingSlash(params.backendUrl)}/api/codex-auth/v1/proxy/v1`;
1809
+ optionsRoot.baseURL = openAiCompatibleProxyUrl(params.backendUrl);
1688
1810
  optionsRoot.apiKey = params.apiKey;
1689
1811
  managedOpenAiProvider.options = optionsRoot;
1690
1812
  managedOpenAiProvider.models = buildOpenCodeModelsObject(params.models);
@@ -1726,7 +1848,7 @@ function traePatchSnippet(params) {
1726
1848
  const patchedModels = params.models.length
1727
1849
  ? params.models
1728
1850
  : [{ id: params.model.trim() || DEFAULT_TRAE_MODEL, name: modelDisplayName(params.model.trim() || DEFAULT_TRAE_MODEL) }];
1729
- const proxyBaseUrl = `${trimTrailingSlash(params.backendUrl)}/api/codex-auth/v1/proxy/v1`;
1851
+ const proxyBaseUrl = openAiCompatibleProxyUrl(params.backendUrl);
1730
1852
  return `async setOriginModelListMapAndCache(e,t=!0){let{userProfile:r}=this.credentialStore.getState(),i=r?.scope!==v9.BYTEDANCE,n={},o=${JSON.stringify(patchedModels)};Object.keys(e).forEach(t=>{let r=[...(e[t]||[])],s=r[0]&&"object"==typeof r[0]?r[0]:null;if([Mh.Builder,Mh.SoloCoder].includes(t)&&s){let a=o.map((e,l)=>{let o={...s};return o.id=${JSON.stringify(TRAE_PATCH_MARKER)}+"-"+t+"-"+e.id,o.name=e.id,o.display_name=e.name,o.provider="theclawbay",o.icon={dark:"https://theclawbay.com/favicon.ico",light:"https://theclawbay.com/favicon.ico"},o.ak=${JSON.stringify(params.apiKey)},o.base_url=${JSON.stringify(proxyBaseUrl)},o.is_custom_base_url=!0,o.custom_model_id=${JSON.stringify(TRAE_PATCH_MARKER)}+"-"+e.id,o.selectable=!0,o.status=!0,o.is_default=0===l,o.config_source=MK.Personal,o.model_type=s.model_type??"chat_model",o.builder=s.builder??null,o.client_connect=!0,o.multimodal=!1!==s.multimodal,o.tags=Array.isArray(s.tags)?s.tags:[],o.auth_type="number"==typeof s.auth_type?s.auth_type:0,o.region=s.region??null,o.max_turns=s.max_turns??{default:50,max:50},o.context_window_size=s.context_window_size??{max:[128000],default:64000},o.features=s.features??{memory:{enable:!1},cost:{enable:!1,data:{manual_usage:0}},multimodal:{enable:!0},context_windows:{enable:!0,data:{dev_context:64000,max_context:128000,max_context_list:[128000],dev_turns:50,max_turns:50}}},o.commercial_info=s.commercial_info??{manual_usage:0,info:""},o.saas_usage=s.saas_usage??{max:0,default:null},o});n[t]=a;return}!i&&[Mh.SoloCoder,Mh.UIBuilder].includes(t)?n[t]=r?.filter(e=>e.config_source!==MK.Personal)||[]:n[t]=r}),this._modelStore.actions.setOriginModelListMap(n),t&&this._modelStorageService.storeModelListMap(n),this._logService.info("[ModelService.setOriginModelListMapAndCache] officialModels",Object.entries(n).map(([e,t])=>({scene:e,models:(t||[]).map(e=>e.display_name||e.name)})))}`;
1731
1853
  }
1732
1854
  async function patchTraeBundle(params) {
@@ -1932,6 +2054,7 @@ class SetupCommand extends base_command_1.BaseCommand {
1932
2054
  }
1933
2055
  const progress = this.createProgressHandle(!debugOutput);
1934
2056
  let resolved = null;
2057
+ let claudeAccess = null;
1935
2058
  let updatedShellFiles = [];
1936
2059
  let codexConfigPath = null;
1937
2060
  let updatedVsCodeEnvFiles = [];
@@ -1955,6 +2078,8 @@ class SetupCommand extends base_command_1.BaseCommand {
1955
2078
  progress.update("Resolving supported models");
1956
2079
  resolved = await resolveModels(backendUrl, authCredential);
1957
2080
  }
2081
+ progress.update("Checking Claude access");
2082
+ claudeAccess = await resolveClaudeAccess(backendUrl, authCredential);
1958
2083
  progress.update("Saving shared machine config");
1959
2084
  await (0, config_1.writeManagedConfig)({
1960
2085
  backendUrl,
@@ -1963,7 +2088,11 @@ class SetupCommand extends base_command_1.BaseCommand {
1963
2088
  deviceSessionId,
1964
2089
  deviceLabel,
1965
2090
  });
1966
- updatedShellFiles = await persistApiKeyEnv(authCredential);
2091
+ updatedShellFiles = await persistApiKeyEnv({
2092
+ apiKey: authCredential,
2093
+ backendUrl,
2094
+ claudeEnabled: claudeAccess.enabled,
2095
+ });
1967
2096
  if (selectedSetupClients.has("codex")) {
1968
2097
  progress.update("Configuring Codex");
1969
2098
  codexConfigPath = await writeCodexConfig({
@@ -2089,6 +2218,12 @@ class SetupCommand extends base_command_1.BaseCommand {
2089
2218
  const summaryNotes = new Set();
2090
2219
  if (resolved?.note)
2091
2220
  summaryNotes.add(resolved.note);
2221
+ if (claudeAccess?.enabled) {
2222
+ summaryNotes.add("Claude Code: exported ANTHROPIC_BASE_URL and ANTHROPIC_API_KEY for the official client.");
2223
+ }
2224
+ else if (claudeAccess?.note) {
2225
+ summaryNotes.add(claudeAccess.note);
2226
+ }
2092
2227
  if (selectedSetupClients.has("trae")) {
2093
2228
  summaryNotes.add("Trae uses an experimental patch. Restart Trae and pick a The Claw Bay model to test it.");
2094
2229
  }
@@ -2138,6 +2273,12 @@ class SetupCommand extends base_command_1.BaseCommand {
2138
2273
  }
2139
2274
  if (resolved?.note)
2140
2275
  this.log(resolved.note);
2276
+ if (claudeAccess?.enabled) {
2277
+ this.log(`- Claude Code base URL: ${anthropicCompatibleProxyUrl(backendUrl)}`);
2278
+ }
2279
+ else if (claudeAccess?.note) {
2280
+ this.log(`- Claude Code: ${claudeAccess.note}`);
2281
+ }
2141
2282
  this.log(`- Local credential env: ${ENV_FILE}`);
2142
2283
  this.log(`- Shell profiles updated: ${updatedShellFiles.join(", ")}`);
2143
2284
  if (selectedSetupClients.has("codex")) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "theclawbay",
3
- "version": "0.3.46",
3
+ "version": "0.3.47",
4
4
  "description": "CLI for connecting Codex, Continue, Cline, OpenClaw, OpenCode, Kilo, Roo Code, Aider, experimental Trae, and experimental Zo to The Claw Bay.",
5
5
  "license": "MIT",
6
6
  "bin": {