maxpool 1.21.0 → 1.21.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "maxpool",
3
- "version": "1.21.0",
3
+ "version": "1.21.2",
4
4
  "description": "Multi-account Claude Code proxy with adaptive, rate-aware load balancing across Claude accounts",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
package/src/index.js CHANGED
@@ -29,7 +29,7 @@ net.setDefaultAutoSelectFamilyAttemptTimeout(
29
29
  );
30
30
  import { Prober } from './prober.js';
31
31
  import { CapacityLedger } from './capacity-ledger.js';
32
- import { loginOAuth, fetchProfile, refreshAccessToken, isTokenExpiringSoon, tokenFingerprint } from './oauth.js';
32
+ import { loginOAuth, fetchProfile, refreshAccessToken, isTokenExpiringSoon, tokenFingerprint, isLoginCancelled } from './oauth.js';
33
33
  import { TUI } from './tui.js';
34
34
  import { RestartController } from './restart-controller.js';
35
35
  import { resolveAccounts } from './account-config.js';
@@ -1637,6 +1637,10 @@ async function loginOAuthCommand() {
1637
1637
  try {
1638
1638
  creds = await loginOAuth();
1639
1639
  } catch (err) {
1640
+ if (isLoginCancelled(err)) {
1641
+ console.error('Login cancelled.');
1642
+ process.exit(0);
1643
+ }
1640
1644
  console.error(`OAuth login failed: ${err.message}`);
1641
1645
  console.error('');
1642
1646
  console.error('Alternatives:');
package/src/oauth.js CHANGED
@@ -520,6 +520,17 @@ export async function loginOAuth() {
520
520
  * Race the callback server promise against manual code entry from stdin.
521
521
  * The user can paste the full callback URL or just the authorization code.
522
522
  */
523
+ // Rejection sentinel for a user-cancelled login — `err === LOGIN_CANCELLED` (or
524
+ // isLoginCancelled(err)) distinguishes "changed my mind" from a real failure so the
525
+ // UI can say 'cancelled' instead of 'failed' and CLI flows can exit 0 quietly.
526
+ export const LOGIN_CANCELLED = Symbol.for('maxpool.login.cancelled');
527
+ export function isLoginCancelled(err) { return err === LOGIN_CANCELLED; }
528
+
529
+ export function isLoginCancelAnswer(answer) {
530
+ const t = String(answer || '').trim().toLowerCase();
531
+ return t === 'q' || t === 'quit' || t === 'cancel' || t === 'abort' || t === ':q';
532
+ }
533
+
523
534
  function raceWithStdinCode(callbackPromise, expectedState) {
524
535
  if (!process.stdin.isTTY) return callbackPromise;
525
536
 
@@ -534,9 +545,20 @@ function raceWithStdinCode(callbackPromise, expectedState) {
534
545
  fn(val);
535
546
  };
536
547
 
537
- rl.question('Paste authorization code here (or wait for browser callback): ', answer => {
548
+ // ESCAPE HATCH (2026-09-20). The login prompt had NO way out: a free-plan account
549
+ // can't complete the OAuth consent, the callback never arrives, and Ctrl+C — with
550
+ // no SIGINT listener on the readline — killed the whole maxpool process (measured:
551
+ // the owner was stuck on this exact screen and had to be told to kill the app).
552
+ // Now: Ctrl+C (first press) and typing q/quit/cancel/abort/:q both reject with a
553
+ // Cancelled sentinel the callers translate into "login cancelled", leaving the
554
+ // app alive. The TUI restarts itself from _doLogin's finally block.
555
+ const cancel = () => settle(reject, LOGIN_CANCELLED);
556
+ rl.on('SIGINT', cancel);
557
+
558
+ rl.question('Paste authorization code here (q or Ctrl+C to cancel): ', answer => {
538
559
  const trimmed = answer.trim();
539
560
  if (!trimmed) return; // empty input, keep waiting for callback
561
+ if (isLoginCancelAnswer(trimmed)) return cancel();
540
562
 
541
563
  // Try to parse as a URL with ?code= parameter
542
564
  try {
package/src/tui.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { createInterface } from 'node:readline';
2
- import { fetchProfile, loginOAuth, tokenFingerprint } from './oauth.js';
2
+ import { fetchProfile, loginOAuth, tokenFingerprint, isLoginCancelled } from './oauth.js';
3
3
  import { appendEventLog, setConsoleStdoutSuppressed } from './event-log.js';
4
4
 
5
5
  // ── ANSI helpers ─────────────────────────────────────────────
@@ -1335,7 +1335,11 @@ export class TUI {
1335
1335
  ? `\nRe-authenticated "${name}". Returning to maxpool…\n`
1336
1336
  : `\nAdded new account "${name}". Returning to maxpool…\n`);
1337
1337
  } catch (e) {
1338
- process.stdout.write(`\nLogin failed: ${e.message}\n`);
1338
+ if (isLoginCancelled(e)) {
1339
+ process.stdout.write('\nLogin cancelled.\n');
1340
+ } else {
1341
+ process.stdout.write(`\nLogin failed: ${e.message}\n`);
1342
+ }
1339
1343
  } finally {
1340
1344
  setConsoleStdoutSuppressed(false); // restore on every path (incl. the catch)
1341
1345
  if (wasRunning) this.start();
@@ -2380,7 +2384,7 @@ export class TUI {
2380
2384
  return ` ${bold('c')} Check & apply now ${bold('t')} Automatic updates: ${state} ↻ ${bold('Esc')} Back`;
2381
2385
  }
2382
2386
  case 'accounts':
2383
- return ` ${bold('a')} Add account ${bold('l')} Re-auth (browser) ${bold('n')} Rename ${bold('t')} Enable/disable ${bold('d')} Delete ${bold('Esc')} Back`;
2387
+ return ` ${bold('a')} Add account ${bold('l')} Re-auth (browser) ${bold('n')} Rename ${bold('t')} Enable/disable ${bold('u')} Usage cap ${bold('d')} Delete ${bold('Esc')} Back`;
2384
2388
  case 'addtype':
2385
2389
  return ` ${bold('1')}-${bold('4')} pick a type ${bold('Esc')} Back`;
2386
2390
  case 'routing': {