theclawbay 0.3.50 → 0.3.52
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/dist/commands/setup.js +43 -1
- package/dist/lib/device-session-auth.js +18 -11
- package/package.json +1 -1
package/dist/commands/setup.js
CHANGED
|
@@ -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) {
|
|
@@ -2112,7 +2137,7 @@ class SetupCommand extends base_command_1.BaseCommand {
|
|
|
2112
2137
|
if (flags["migrate-conversations"] !== undefined && !selectedSetupClients.has("codex")) {
|
|
2113
2138
|
throw new Error("--migrate-conversations requires Codex to be selected for setup.");
|
|
2114
2139
|
}
|
|
2115
|
-
|
|
2140
|
+
const linkFreshDeviceSession = async () => {
|
|
2116
2141
|
deviceLabel = await resolveDeviceLabel({
|
|
2117
2142
|
flagValue: flags["device-name"],
|
|
2118
2143
|
managedValue: managed?.deviceLabel,
|
|
@@ -2128,6 +2153,16 @@ class SetupCommand extends base_command_1.BaseCommand {
|
|
|
2128
2153
|
authCredential = browserAuth.credential;
|
|
2129
2154
|
deviceSessionId = browserAuth.deviceSessionId;
|
|
2130
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
|
+
}
|
|
2131
2166
|
}
|
|
2132
2167
|
const progress = this.createProgressHandle(!debugOutput);
|
|
2133
2168
|
let resolved = null;
|
|
@@ -2156,9 +2191,15 @@ class SetupCommand extends base_command_1.BaseCommand {
|
|
|
2156
2191
|
if (selectedSetupClients.size > 0) {
|
|
2157
2192
|
progress.update("Resolving supported models");
|
|
2158
2193
|
resolved = await resolveModels(backendUrl, authCredential);
|
|
2194
|
+
if (resolved.authRejected) {
|
|
2195
|
+
throw new Error(describeCredentialRejection(authType, resolved.failure));
|
|
2196
|
+
}
|
|
2159
2197
|
}
|
|
2160
2198
|
progress.update("Checking Claude access");
|
|
2161
2199
|
claudeAccess = await resolveClaudeAccess(backendUrl, authCredential);
|
|
2200
|
+
if (!resolved?.authRejected && claudeAccess.authRejected) {
|
|
2201
|
+
throw new Error(describeCredentialRejection(authType, claudeAccess.failure));
|
|
2202
|
+
}
|
|
2162
2203
|
progress.update("Saving shared machine config");
|
|
2163
2204
|
await (0, config_1.writeManagedConfig)({
|
|
2164
2205
|
backendUrl,
|
|
@@ -2470,6 +2511,7 @@ class SetupCommand extends base_command_1.BaseCommand {
|
|
|
2470
2511
|
const openCodeDetected = setupClients.find((client) => client.id === "opencode")?.detected ?? false;
|
|
2471
2512
|
if (selectedSetupClients.has("opencode")) {
|
|
2472
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.");
|
|
2473
2515
|
const openCodeProjectConfig = findOpenCodeProjectConfigFile();
|
|
2474
2516
|
const openCodeConfigEnv = process.env.OPENCODE_CONFIG?.trim() || "";
|
|
2475
2517
|
if (openCodeConfigEnv) {
|
|
@@ -237,6 +237,7 @@ async function createBrowserLinkedDeviceSession(params) {
|
|
|
237
237
|
const callbackServer = await createLocalCallbackServer(state);
|
|
238
238
|
const exchangeAbortController = new AbortController();
|
|
239
239
|
const exchangeSignal = exchangeAbortController.signal;
|
|
240
|
+
let exchangeInFlight = null;
|
|
240
241
|
try {
|
|
241
242
|
const startResponse = await fetch(`${params.backendUrl}/api/setup/device-session/start`, {
|
|
242
243
|
method: "POST",
|
|
@@ -261,25 +262,31 @@ async function createBrowserLinkedDeviceSession(params) {
|
|
|
261
262
|
const deadlineMs = Date.now() + BROWSER_SETUP_TIMEOUT_MS;
|
|
262
263
|
let fallbackNoticeShown = false;
|
|
263
264
|
const timeoutError = new Error("browser setup timed out");
|
|
264
|
-
const
|
|
265
|
-
|
|
266
|
-
|
|
265
|
+
const performExchange = (code) => {
|
|
266
|
+
if (exchangeInFlight)
|
|
267
|
+
return exchangeInFlight;
|
|
268
|
+
const promise = exchangeBrowserSetupSession({
|
|
267
269
|
backendUrl: params.backendUrl,
|
|
268
|
-
sessionId:
|
|
269
|
-
state
|
|
270
|
-
code
|
|
270
|
+
sessionId: setupSessionId,
|
|
271
|
+
state,
|
|
272
|
+
code,
|
|
273
|
+
});
|
|
274
|
+
exchangeInFlight = promise.catch((error) => {
|
|
275
|
+
exchangeInFlight = null;
|
|
276
|
+
throw error;
|
|
271
277
|
});
|
|
278
|
+
return exchangeInFlight;
|
|
279
|
+
};
|
|
280
|
+
const waitForLocalCallbackExchange = async () => {
|
|
281
|
+
const callback = await withTimeout(callbackServer.waitForCallback(), BROWSER_SETUP_TIMEOUT_MS, exchangeSignal);
|
|
282
|
+
return performExchange(callback.code);
|
|
272
283
|
};
|
|
273
284
|
const waitForPolledExchange = async () => {
|
|
274
285
|
while (Date.now() < deadlineMs) {
|
|
275
286
|
if (exchangeSignal.aborted)
|
|
276
287
|
throw createSetupAbortError();
|
|
277
288
|
try {
|
|
278
|
-
return await
|
|
279
|
-
backendUrl: params.backendUrl,
|
|
280
|
-
sessionId: setupSessionId,
|
|
281
|
-
state,
|
|
282
|
-
});
|
|
289
|
+
return await performExchange();
|
|
283
290
|
}
|
|
284
291
|
catch (error) {
|
|
285
292
|
const message = error.message || "";
|
package/package.json
CHANGED