oc-auth-switcher 0.1.1 → 0.1.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/README.md CHANGED
@@ -89,6 +89,20 @@ Both files use atomic writes with `.bak` fallback for crash safety.
89
89
  - Auth failures trigger a 1-hour cooldown per account
90
90
  - Token refresh is handled automatically with retry and fallback to other accounts
91
91
 
92
+ ## Running the CLI
93
+
94
+ When installed via npm (as part of the OpenCode plugin):
95
+
96
+ ```bash
97
+ npx oc-auth-switcher <command>
98
+ ```
99
+
100
+ From the project directory (development):
101
+
102
+ ```bash
103
+ bun dist/cli.js <command>
104
+ ```
105
+
92
106
  ## Building from Source
93
107
 
94
108
  ```bash
package/dist/cli.js CHANGED
@@ -13,7 +13,7 @@ var ACCOUNTS_FILE = path.join(configDir, "auth-switcher-accounts.json");
13
13
  var STATE_FILE = path.join(configDir, "auth-switcher-state.json");
14
14
  var DEFAULT_THRESHOLD = 0.9;
15
15
  var DEFAULT_CHECK_INTERVAL = 60 * 60 * 1000;
16
- var AUTH_FAILURE_COOLDOWN = 60 * 60 * 1000;
16
+ var AUTH_FAILURE_COOLDOWN = 10 * 60 * 1000;
17
17
 
18
18
  // src/accounts.ts
19
19
  function normalizeAccount(raw) {
package/dist/index.js CHANGED
@@ -415,7 +415,7 @@ var ACCOUNTS_FILE = path.join(configDir, "auth-switcher-accounts.json");
415
415
  var STATE_FILE = path.join(configDir, "auth-switcher-state.json");
416
416
  var DEFAULT_THRESHOLD = 0.9;
417
417
  var DEFAULT_CHECK_INTERVAL = 60 * 60 * 1000;
418
- var AUTH_FAILURE_COOLDOWN = 60 * 60 * 1000;
418
+ var AUTH_FAILURE_COOLDOWN = 10 * 60 * 1000;
419
419
 
420
420
  // src/accounts.ts
421
421
  function normalizeAccount(raw) {
@@ -659,10 +659,44 @@ function getEarliestReset(usage) {
659
659
  ].filter((r) => r > 0);
660
660
  return resets.length > 0 ? Math.min(...resets) * 1000 : 0;
661
661
  }
662
+ function purgeExpiredCooldowns(state) {
663
+ const now = Date.now();
664
+ for (const name of Object.keys(state.authFailures)) {
665
+ if (state.authFailures[name] <= now) {
666
+ delete state.authFailures[name];
667
+ }
668
+ }
669
+ }
670
+ function findBestAvailable(candidates, state, exclude) {
671
+ for (const acct of candidates) {
672
+ if (exclude.has(acct.name))
673
+ continue;
674
+ if (isTemporarilyUnavailable(state, acct.name))
675
+ continue;
676
+ if (!isOverThreshold(state.usage[acct.name], state)) {
677
+ return acct;
678
+ }
679
+ }
680
+ let best = null;
681
+ let bestScore = Infinity;
682
+ for (const acct of candidates) {
683
+ if (exclude.has(acct.name))
684
+ continue;
685
+ if (isTemporarilyUnavailable(state, acct.name))
686
+ continue;
687
+ const score = getUtilizationScore(state.usage[acct.name], state);
688
+ if (score < bestScore) {
689
+ bestScore = score;
690
+ best = acct;
691
+ }
692
+ }
693
+ return best;
694
+ }
662
695
  function selectAccount(accounts, state) {
663
696
  if (accounts.length === 0) {
664
697
  throw new Error("No accounts available");
665
698
  }
699
+ purgeExpiredCooldowns(state);
666
700
  if (accounts.length === 1) {
667
701
  return { account: accounts[0], switched: false };
668
702
  }
@@ -690,40 +724,39 @@ function selectAccount(accounts, state) {
690
724
  if (isPrimary) {
691
725
  if (isOverThreshold(primaryUsage, state)) {
692
726
  const exceededMetric = getExceededMetric(primaryUsage, state);
693
- for (const fb of fallbacks) {
694
- if (isTemporarilyUnavailable(state, fb.name))
695
- continue;
696
- const fbUsage = state.usage[fb.name];
697
- if (!isOverThreshold(fbUsage, state)) {
698
- return {
699
- account: fb,
700
- switched: true,
701
- reason: `Primary exceeded ${exceededMetric} threshold — switching to ${fb.name}`
702
- };
703
- }
704
- }
705
- let bestFb = null;
706
- let bestScore = Infinity;
707
- for (const fb of fallbacks) {
708
- if (isTemporarilyUnavailable(state, fb.name))
709
- continue;
710
- const score = getUtilizationScore(state.usage[fb.name], state);
711
- if (score < bestScore) {
712
- bestScore = score;
713
- bestFb = fb;
714
- }
715
- }
716
- if (bestFb) {
727
+ const best = findBestAvailable(fallbacks, state, new Set);
728
+ if (best) {
717
729
  return {
718
- account: bestFb,
730
+ account: best,
719
731
  switched: true,
720
- reason: `Primary exceeded threshold, all fallbacks busy using least loaded: ${bestFb.name}`
732
+ reason: `Primary exceeded ${exceededMetric} threshold — switching to ${best.name}`
721
733
  };
722
734
  }
723
735
  return { account: primary, switched: false };
724
736
  }
725
737
  return { account: primary, switched: false };
726
738
  }
739
+ const currentOverThreshold = isOverThreshold(currentUsage, state);
740
+ const currentInCooldown = isTemporarilyUnavailable(state, current.name);
741
+ if (currentOverThreshold || currentInCooldown) {
742
+ const reason = currentInCooldown ? `${current.name} in auth-failure cooldown` : `${current.name} exceeded threshold`;
743
+ if (!isOverThreshold(primaryUsage, state) && !isTemporarilyUnavailable(state, primary.name)) {
744
+ return {
745
+ account: primary,
746
+ switched: true,
747
+ reason: `${reason} — switching back to primary`
748
+ };
749
+ }
750
+ const best = findBestAvailable(accounts, state, new Set([current.name]));
751
+ if (best && best.name !== current.name) {
752
+ return {
753
+ account: best,
754
+ switched: true,
755
+ reason: `${reason} — switching to ${best.name}`
756
+ };
757
+ }
758
+ return { account: current, switched: false };
759
+ }
727
760
  const now = Date.now();
728
761
  const checkInterval = state.config.checkInterval;
729
762
  const earliestReset = getEarliestReset(primaryUsage);
@@ -739,30 +772,6 @@ function selectAccount(accounts, state) {
739
772
  };
740
773
  }
741
774
  }
742
- if (!isTemporarilyUnavailable(state, current.name)) {
743
- return { account: current, switched: false };
744
- }
745
- for (const fb of fallbacks) {
746
- if (fb.name === current.name)
747
- continue;
748
- if (isTemporarilyUnavailable(state, fb.name))
749
- continue;
750
- const fbUsage = state.usage[fb.name];
751
- if (!isOverThreshold(fbUsage, state)) {
752
- return {
753
- account: fb,
754
- switched: true,
755
- reason: `Current account ${current.name} in cooldown — switching to ${fb.name}`
756
- };
757
- }
758
- }
759
- if (!isTemporarilyUnavailable(state, primary.name)) {
760
- return {
761
- account: primary,
762
- switched: true,
763
- reason: "All fallbacks unavailable — falling back to primary"
764
- };
765
- }
766
775
  return { account: current, switched: false };
767
776
  }
768
777
  function markAuthFailure(state, accountName) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oc-auth-switcher",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "OpenCode auth plugin for multi-account Anthropic Claude Max rotation with automatic failover.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",