clawmoney 0.15.62 → 0.15.64
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.
|
@@ -261,6 +261,7 @@ export async function relaySetupCommand() {
|
|
|
261
261
|
return Promise.all(tasks);
|
|
262
262
|
};
|
|
263
263
|
const registrations = [];
|
|
264
|
+
const cliSummary = [];
|
|
264
265
|
for (const cli of selectedClis) {
|
|
265
266
|
const allModels = modelsForCli(cli);
|
|
266
267
|
const recommended = (RECOMMENDED_MODELS[cli] ?? []).filter((m) => allModels.includes(m));
|
|
@@ -268,7 +269,7 @@ export async function relaySetupCommand() {
|
|
|
268
269
|
log.warn(`${cli}: no recommended models found — skipping`);
|
|
269
270
|
continue;
|
|
270
271
|
}
|
|
271
|
-
|
|
272
|
+
cliSummary.push(`${chalk.bold(cli)} ${chalk.dim(`(${recommended.length})`)}`);
|
|
272
273
|
for (const model of recommended) {
|
|
273
274
|
const p = API_PRICES[model];
|
|
274
275
|
registrations.push({
|
|
@@ -279,6 +280,9 @@ export async function relaySetupCommand() {
|
|
|
279
280
|
});
|
|
280
281
|
}
|
|
281
282
|
}
|
|
283
|
+
if (cliSummary.length > 0) {
|
|
284
|
+
log.success(`Registering: ${cliSummary.join(chalk.dim(" · "))}`);
|
|
285
|
+
}
|
|
282
286
|
if (registrations.length === 0) {
|
|
283
287
|
cancel("No models selected — nothing to register");
|
|
284
288
|
process.exit(0);
|
|
@@ -501,7 +505,7 @@ export async function relaySetupCommand() {
|
|
|
501
505
|
// Non-fatal: worst case is the daemon preflights extra cli_types,
|
|
502
506
|
// which is annoying but doesn't break anything.
|
|
503
507
|
log.warn(`Could not prune old providers: ${err.message} — ` +
|
|
504
|
-
`run \`clawmoney relay status\` and clean manually if needed`);
|
|
508
|
+
`run \`npx clawmoney relay status\` and clean manually if needed`);
|
|
505
509
|
}
|
|
506
510
|
// ── Step 8: auto-start the daemon ──
|
|
507
511
|
//
|
|
@@ -527,10 +531,10 @@ export async function relaySetupCommand() {
|
|
|
527
531
|
// instead of 6.
|
|
528
532
|
log.message(chalk.dim("Next:") +
|
|
529
533
|
"\n" +
|
|
530
|
-
` ${chalk.cyan("clawmoney relay status")} daemon + provider list\n` +
|
|
531
|
-
` ${chalk.cyan("clawmoney relay logs")} tail daemon log\n` +
|
|
532
|
-
` ${chalk.cyan("clawmoney wallet balance")} on-chain + relay earnings\n` +
|
|
533
|
-
` ${chalk.cyan("clawmoney relay stop")} stop daemon`);
|
|
534
|
+
` ${chalk.cyan("npx clawmoney relay status")} daemon + provider list\n` +
|
|
535
|
+
` ${chalk.cyan("npx clawmoney relay logs")} tail daemon log\n` +
|
|
536
|
+
` ${chalk.cyan("npx clawmoney wallet balance")} on-chain + relay earnings\n` +
|
|
537
|
+
` ${chalk.cyan("npx clawmoney relay stop")} stop daemon`);
|
|
534
538
|
const cliLabel = uniqueClis.length === 1
|
|
535
539
|
? `${uniqueClis[0]} daemon running`
|
|
536
540
|
: `daemon serving ${uniqueClis.join(" + ")}`;
|
package/dist/commands/setup.js
CHANGED
|
@@ -179,6 +179,9 @@ export async function setupCommand() {
|
|
|
179
179
|
if (!checkData.exists || agentStatus === 'UNCLAIMED') {
|
|
180
180
|
// Step 6: Register new agent
|
|
181
181
|
agentSpinner.text = 'Registering agent...';
|
|
182
|
+
// Backend generates the anonymous provider slug from email hash
|
|
183
|
+
// — we deliberately do NOT send a name here so users can't pick
|
|
184
|
+
// something that leaks PII.
|
|
182
185
|
const registerBody = { email };
|
|
183
186
|
if (walletAddress) {
|
|
184
187
|
registerBody.wallet_address = walletAddress;
|
|
@@ -519,6 +519,38 @@ async function refreshUpstreamToken(refreshToken) {
|
|
|
519
519
|
let cachedCreds = null;
|
|
520
520
|
let refreshInflight = null;
|
|
521
521
|
const REFRESH_SKEW_MS = 3 * 60 * 1000;
|
|
522
|
+
// ── Auth-broken circuit breaker ─────────────────────────────────────────
|
|
523
|
+
//
|
|
524
|
+
// If Anthropic's OAuth refresh endpoint rejects our refresh_token as
|
|
525
|
+
// invalid (400 invalid_grant, 401, 403 "Request not allowed", etc.),
|
|
526
|
+
// that's a persistent condition — the token is not going to start
|
|
527
|
+
// working again on its own. Every subsequent buyer request would burn
|
|
528
|
+
// another refresh attempt on Anthropic, which looks like brute-forcing
|
|
529
|
+
// from their anti-abuse side and risks getting the provider's account
|
|
530
|
+
// flagged.
|
|
531
|
+
//
|
|
532
|
+
// Cache the "broken" state for AUTH_BROKEN_CACHE_MS. During that window
|
|
533
|
+
// ALL calls to getFreshCreds() short-circuit with the cached error
|
|
534
|
+
// WITHOUT hitting Anthropic. After the window expires we allow exactly
|
|
535
|
+
// one probe refresh — if it succeeds, we're unbroken; if it fails
|
|
536
|
+
// again, the window is extended by another interval.
|
|
537
|
+
//
|
|
538
|
+
// Transient 5xx responses are NOT cached — those are "maybe the server
|
|
539
|
+
// is having a moment" and worth retrying. Only 4xx "no, really, the
|
|
540
|
+
// token is bad" responses trip the breaker.
|
|
541
|
+
const AUTH_BROKEN_CACHE_MS = 5 * 60 * 1000;
|
|
542
|
+
let authBrokenUntilMs = 0;
|
|
543
|
+
let authBrokenError = null;
|
|
544
|
+
function isAuthBrokenError(err) {
|
|
545
|
+
const msg = err.message.toLowerCase();
|
|
546
|
+
// Matches messages produced by refreshUpstreamToken:
|
|
547
|
+
// "Token refresh failed: 400 ...invalid_grant..."
|
|
548
|
+
// "Token refresh failed: 401 ..."
|
|
549
|
+
// "Token refresh failed: 403 ...Request not allowed..."
|
|
550
|
+
return (msg.includes("invalid_grant") ||
|
|
551
|
+
msg.includes("request not allowed") ||
|
|
552
|
+
/token refresh failed:\s*40[0134]/.test(msg));
|
|
553
|
+
}
|
|
522
554
|
async function doRefreshAndPersist(current) {
|
|
523
555
|
logger.info("[claude-api] refreshing OAuth token...");
|
|
524
556
|
const fresh = await refreshUpstreamToken(current.refreshToken);
|
|
@@ -569,6 +601,14 @@ async function doRefreshAndPersist(current) {
|
|
|
569
601
|
return next;
|
|
570
602
|
}
|
|
571
603
|
async function getFreshCreds() {
|
|
604
|
+
// Circuit breaker: if the OAuth endpoint is known-broken for this
|
|
605
|
+
// daemon, throw the cached error immediately without touching
|
|
606
|
+
// Anthropic again. This is what keeps a retry storm from burning
|
|
607
|
+
// one refresh attempt per buyer request and getting the account
|
|
608
|
+
// flagged.
|
|
609
|
+
if (authBrokenUntilMs && Date.now() < authBrokenUntilMs && authBrokenError) {
|
|
610
|
+
throw authBrokenError;
|
|
611
|
+
}
|
|
572
612
|
if (!cachedCreds) {
|
|
573
613
|
cachedCreds = loadClaudeOAuth();
|
|
574
614
|
}
|
|
@@ -582,7 +622,24 @@ async function getFreshCreds() {
|
|
|
582
622
|
refreshInflight = null;
|
|
583
623
|
});
|
|
584
624
|
}
|
|
585
|
-
|
|
625
|
+
try {
|
|
626
|
+
cachedCreds = await refreshInflight;
|
|
627
|
+
}
|
|
628
|
+
catch (err) {
|
|
629
|
+
const e = err;
|
|
630
|
+
if (isAuthBrokenError(e)) {
|
|
631
|
+
authBrokenUntilMs = Date.now() + AUTH_BROKEN_CACHE_MS;
|
|
632
|
+
authBrokenError = e;
|
|
633
|
+
logger.error(`[claude-api] OAuth refresh rejected by Anthropic — caching auth-broken state for ${AUTH_BROKEN_CACHE_MS / 1000}s. Subsequent requests will fail fast without hitting the OAuth endpoint. Fix: re-login with 'claude /login' and restart the daemon. Root cause: ${e.message.slice(0, 200)}`);
|
|
634
|
+
}
|
|
635
|
+
throw err;
|
|
636
|
+
}
|
|
637
|
+
// Successful refresh — clear any cached broken state.
|
|
638
|
+
if (authBrokenUntilMs) {
|
|
639
|
+
authBrokenUntilMs = 0;
|
|
640
|
+
authBrokenError = null;
|
|
641
|
+
logger.info("[claude-api] OAuth refresh recovered — auth-broken cache cleared");
|
|
642
|
+
}
|
|
586
643
|
return cachedCreds;
|
|
587
644
|
}
|
|
588
645
|
// ── Version drift check ──
|