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 +14 -0
- package/dist/cli.js +1 -1
- package/dist/index.js +60 -51
- package/package.json +1 -1
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 =
|
|
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 =
|
|
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
|
-
|
|
694
|
-
|
|
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:
|
|
730
|
+
account: best,
|
|
719
731
|
switched: true,
|
|
720
|
-
reason: `Primary exceeded threshold
|
|
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) {
|