theclawbay 0.3.49 → 0.3.51

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.
@@ -970,6 +970,23 @@ function summarizeModelFetchFailure(detail) {
970
970
  return normalized;
971
971
  return `${normalized.slice(0, 137).trimEnd()}...`;
972
972
  }
973
+ function isCredentialRejectedFailure(detail) {
974
+ if (!detail)
975
+ return false;
976
+ const normalized = detail.toLowerCase();
977
+ return (normalized.includes("http 401") ||
978
+ normalized.includes("invalid api key") ||
979
+ normalized.includes("missing bearer token") ||
980
+ normalized.includes("device session not found") ||
981
+ normalized.includes("billing access required"));
982
+ }
983
+ function describeCredentialRejection(authType, failure) {
984
+ const detail = failure?.trim() || "credential rejected by backend";
985
+ if (authType === "device-session") {
986
+ return `The saved device-session credential was rejected by the backend (${detail}). Re-link this machine in the browser or run \`theclawbay logout\` before rerunning setup.`;
987
+ }
988
+ return `The provided API key was rejected by the backend (${detail}). Update the key and rerun setup.`;
989
+ }
973
990
  async function fetchBackendModelIds(backendUrl, apiKey) {
974
991
  const url = `${openAiCompatibleProxyUrl(backendUrl)}/models`;
975
992
  try {
@@ -1194,6 +1211,7 @@ async function resolveModels(backendUrl, apiKey) {
1194
1211
  const available = new Set(ids ?? []);
1195
1212
  let selected = DEFAULT_CODEX_MODEL;
1196
1213
  let note;
1214
+ const authRejected = isCredentialRejectedFailure(failure);
1197
1215
  for (const preferred of PREFERRED_MODELS) {
1198
1216
  if (available.has(preferred)) {
1199
1217
  selected = preferred;
@@ -1224,20 +1242,27 @@ async function resolveModels(backendUrl, apiKey) {
1224
1242
  model: selected,
1225
1243
  models: unique.map((modelId) => ({ id: modelId, name: modelDisplayName(modelId) })),
1226
1244
  note,
1245
+ failure,
1246
+ authRejected,
1227
1247
  };
1228
1248
  }
1229
1249
  async function resolveClaudeAccess(backendUrl, apiKey) {
1230
1250
  const { ids, failure } = await fetchClaudeModelIds(backendUrl, apiKey);
1251
+ const authRejected = isCredentialRejectedFailure(failure);
1231
1252
  if (!ids?.length) {
1232
1253
  return {
1233
1254
  enabled: false,
1234
1255
  models: [],
1235
1256
  note: failure ? `Claude Code auto-setup skipped (${failure}).` : undefined,
1257
+ failure,
1258
+ authRejected,
1236
1259
  };
1237
1260
  }
1238
1261
  return {
1239
1262
  enabled: true,
1240
1263
  models: ids,
1264
+ failure,
1265
+ authRejected,
1241
1266
  };
1242
1267
  }
1243
1268
  async function writeCodexConfig(params) {
@@ -1429,7 +1454,15 @@ async function persistApiKeyEnv(params) {
1429
1454
  await promises_1.default.writeFile(ENV_FILE, envContents, "utf8");
1430
1455
  await promises_1.default.chmod(ENV_FILE, 0o600);
1431
1456
  const sourceLine = `[ -f "$HOME/.config/theclawbay/env" ] && . "$HOME/.config/theclawbay/env"`;
1432
- const shellRcPaths = [".bashrc", ".zshrc", ".profile"].map((name) => node_path_1.default.join(node_os_1.default.homedir(), name));
1457
+ const shellRcPaths = [
1458
+ ".bashrc",
1459
+ ".bash_profile",
1460
+ ".bash_login",
1461
+ ".zshrc",
1462
+ ".zprofile",
1463
+ ".zlogin",
1464
+ ".profile",
1465
+ ].map((name) => node_path_1.default.join(node_os_1.default.homedir(), name));
1433
1466
  const updated = [];
1434
1467
  for (const rcPath of shellRcPaths) {
1435
1468
  let existing = "";
@@ -1457,6 +1490,19 @@ async function persistApiKeyEnv(params) {
1457
1490
  }
1458
1491
  return updated;
1459
1492
  }
1493
+ async function persistFishEnv(params) {
1494
+ const fishConfPath = node_path_1.default.join(node_os_1.default.homedir(), ".config", "fish", "conf.d", "theclawbay.fish");
1495
+ await promises_1.default.mkdir(node_path_1.default.dirname(fishConfPath), { recursive: true });
1496
+ const lines = [
1497
+ "# Generated by theclawbay setup",
1498
+ `set -gx ${ENV_KEY_NAME} ${shellQuote(params.apiKey)}`,
1499
+ ];
1500
+ if (params.claudeEnabled) {
1501
+ lines.push("", "# Official Claude Code CLI", `set -gx ${CLAUDE_ENV_API_KEY_NAME} ${shellQuote(params.apiKey)}`, `set -gx ${CLAUDE_ENV_BASE_URL_NAME} ${shellQuote(anthropicCompatibleProxyUrl(params.backendUrl))}`);
1502
+ }
1503
+ await promises_1.default.writeFile(fishConfPath, `${lines.join("\n")}\n`, "utf8");
1504
+ return [fishConfPath];
1505
+ }
1460
1506
  function powerShellProfilePaths() {
1461
1507
  if (node_os_1.default.platform() !== "win32")
1462
1508
  return [];
@@ -2091,7 +2137,7 @@ class SetupCommand extends base_command_1.BaseCommand {
2091
2137
  if (flags["migrate-conversations"] !== undefined && !selectedSetupClients.has("codex")) {
2092
2138
  throw new Error("--migrate-conversations requires Codex to be selected for setup.");
2093
2139
  }
2094
- if (!authCredential) {
2140
+ const linkFreshDeviceSession = async () => {
2095
2141
  deviceLabel = await resolveDeviceLabel({
2096
2142
  flagValue: flags["device-name"],
2097
2143
  managedValue: managed?.deviceLabel,
@@ -2107,11 +2153,22 @@ class SetupCommand extends base_command_1.BaseCommand {
2107
2153
  authCredential = browserAuth.credential;
2108
2154
  deviceSessionId = browserAuth.deviceSessionId;
2109
2155
  deviceLabel = browserAuth.deviceLabel;
2156
+ };
2157
+ if (!authCredential) {
2158
+ await linkFreshDeviceSession();
2159
+ }
2160
+ else if (!explicitApiKey && managedAuthType === "device-session") {
2161
+ const managedProbe = await fetchBackendModelIds(backendUrl, authCredential);
2162
+ if (isCredentialRejectedFailure(managedProbe.failure)) {
2163
+ this.log(`Saved device-session credential was rejected by the backend (${managedProbe.failure}). Re-linking this machine now.`);
2164
+ await linkFreshDeviceSession();
2165
+ }
2110
2166
  }
2111
2167
  const progress = this.createProgressHandle(!debugOutput);
2112
2168
  let resolved = null;
2113
2169
  let claudeAccess = null;
2114
2170
  let updatedShellFiles = [];
2171
+ let updatedFishFiles = [];
2115
2172
  let updatedPowerShellProfiles = [];
2116
2173
  let codexConfigPath = null;
2117
2174
  let updatedVsCodeEnvFiles = [];
@@ -2134,9 +2191,15 @@ class SetupCommand extends base_command_1.BaseCommand {
2134
2191
  if (selectedSetupClients.size > 0) {
2135
2192
  progress.update("Resolving supported models");
2136
2193
  resolved = await resolveModels(backendUrl, authCredential);
2194
+ if (resolved.authRejected) {
2195
+ throw new Error(describeCredentialRejection(authType, resolved.failure));
2196
+ }
2137
2197
  }
2138
2198
  progress.update("Checking Claude access");
2139
2199
  claudeAccess = await resolveClaudeAccess(backendUrl, authCredential);
2200
+ if (!resolved?.authRejected && claudeAccess.authRejected) {
2201
+ throw new Error(describeCredentialRejection(authType, claudeAccess.failure));
2202
+ }
2140
2203
  progress.update("Saving shared machine config");
2141
2204
  await (0, config_1.writeManagedConfig)({
2142
2205
  backendUrl,
@@ -2150,11 +2213,19 @@ class SetupCommand extends base_command_1.BaseCommand {
2150
2213
  backendUrl,
2151
2214
  claudeEnabled: claudeAccess.enabled,
2152
2215
  });
2216
+ updatedFishFiles = await persistFishEnv({
2217
+ apiKey: authCredential,
2218
+ backendUrl,
2219
+ claudeEnabled: claudeAccess.enabled,
2220
+ });
2153
2221
  updatedPowerShellProfiles = await persistPowerShellEnv({
2154
2222
  apiKey: authCredential,
2155
2223
  backendUrl,
2156
2224
  claudeEnabled: claudeAccess.enabled,
2157
2225
  });
2226
+ if (selectedSetupClients.has("codex") || selectedSetupClients.has("claude")) {
2227
+ updatedVsCodeEnvFiles = await persistVsCodeServerEnvSource();
2228
+ }
2158
2229
  if (selectedSetupClients.has("codex")) {
2159
2230
  progress.update("Configuring Codex");
2160
2231
  codexConfigPath = await writeCodexConfig({
@@ -2162,7 +2233,6 @@ class SetupCommand extends base_command_1.BaseCommand {
2162
2233
  model: resolved?.model ?? DEFAULT_CODEX_MODEL,
2163
2234
  apiKey: authCredential,
2164
2235
  });
2165
- updatedVsCodeEnvFiles = await persistVsCodeServerEnvSource();
2166
2236
  if (migrateCodexConversations) {
2167
2237
  sessionMigration = await (0, codex_history_migration_1.migrateSessionProviders)({
2168
2238
  codexHome: paths_1.codexDir,
@@ -2345,6 +2415,9 @@ class SetupCommand extends base_command_1.BaseCommand {
2345
2415
  }
2346
2416
  this.log(`- Local credential env: ${ENV_FILE}`);
2347
2417
  this.log(`- Shell profiles updated: ${updatedShellFiles.join(", ")}`);
2418
+ if (updatedFishFiles.length > 0) {
2419
+ this.log(`- Fish profiles updated: ${updatedFishFiles.join(", ")}`);
2420
+ }
2348
2421
  if (updatedPowerShellProfiles.length > 0) {
2349
2422
  this.log(`- PowerShell profiles updated: ${updatedPowerShellProfiles.join(", ")}`);
2350
2423
  }
@@ -2438,6 +2511,7 @@ class SetupCommand extends base_command_1.BaseCommand {
2438
2511
  const openCodeDetected = setupClients.find((client) => client.id === "opencode")?.detected ?? false;
2439
2512
  if (selectedSetupClients.has("opencode")) {
2440
2513
  this.log(`- OpenCode: configured (${openCodeConfigPaths.join(", ")})`);
2514
+ this.log("- OpenCode note: plain OpenAI-compatible config is preferred. If you add a quota/auth plugin manually, use /api/codex-auth/v1/quota?format=legacy_codex for legacy Codex-style quota responses.");
2441
2515
  const openCodeProjectConfig = findOpenCodeProjectConfigFile();
2442
2516
  const openCodeConfigEnv = process.env.OPENCODE_CONFIG?.trim() || "";
2443
2517
  if (openCodeConfigEnv) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "theclawbay",
3
- "version": "0.3.49",
3
+ "version": "0.3.51",
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": {