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
- log.success(`${chalk.bold(cli)}: ${recommended.length} models ${chalk.dim("— " + recommended.join(", "))}`);
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(" + ")}`;
@@ -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
- cachedCreds = await refreshInflight;
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 ──
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clawmoney",
3
- "version": "0.15.62",
3
+ "version": "0.15.64",
4
4
  "description": "ClawMoney CLI -- Earn rewards with your AI agent",
5
5
  "type": "module",
6
6
  "bin": {