pi-antigravity-rotator 1.2.0 → 1.3.0
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 +1 -1
- package/src/dashboard.ts +104 -0
- package/src/rotator.ts +90 -0
- package/src/types.ts +21 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-antigravity-rotator",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"description": "Multi-account rotation proxy for Google Antigravity with per-model routing, real-time quota tracking, and infringement detection",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
package/src/dashboard.ts
CHANGED
|
@@ -366,6 +366,82 @@ const DASHBOARD_HTML = `<!DOCTYPE html>
|
|
|
366
366
|
|
|
367
367
|
.pulse { animation: pulse 2s ease-in-out infinite; }
|
|
368
368
|
@keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.6; } }
|
|
369
|
+
|
|
370
|
+
.badge-pro { background: rgba(52, 211, 153, 0.15); color: var(--green); }
|
|
371
|
+
.badge-free { background: rgba(110, 110, 130, 0.08); color: var(--text-dim); }
|
|
372
|
+
.badge-fmgr { background: rgba(124, 92, 252, 0.15); color: var(--accent); font-size: 9px; }
|
|
373
|
+
|
|
374
|
+
.advisor-panel {
|
|
375
|
+
background: var(--surface);
|
|
376
|
+
border: 1px solid var(--border);
|
|
377
|
+
border-radius: var(--radius);
|
|
378
|
+
padding: 16px 18px;
|
|
379
|
+
margin-bottom: 24px;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
.advisor-title {
|
|
383
|
+
font-size: 11px;
|
|
384
|
+
text-transform: uppercase;
|
|
385
|
+
letter-spacing: 0.8px;
|
|
386
|
+
color: var(--text-dim);
|
|
387
|
+
margin-bottom: 10px;
|
|
388
|
+
display: flex;
|
|
389
|
+
align-items: center;
|
|
390
|
+
gap: 8px;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
.advisor-slots {
|
|
394
|
+
font-size: 12px;
|
|
395
|
+
font-family: 'JetBrains Mono', monospace;
|
|
396
|
+
color: var(--text);
|
|
397
|
+
margin-left: auto;
|
|
398
|
+
text-transform: none;
|
|
399
|
+
letter-spacing: 0;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
.advisor-action {
|
|
403
|
+
display: flex;
|
|
404
|
+
align-items: center;
|
|
405
|
+
gap: 10px;
|
|
406
|
+
padding: 8px 10px;
|
|
407
|
+
margin-bottom: 6px;
|
|
408
|
+
border-radius: 8px;
|
|
409
|
+
font-size: 12px;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
.advisor-action.add-pro {
|
|
413
|
+
background: rgba(52, 211, 153, 0.06);
|
|
414
|
+
border-left: 3px solid var(--green);
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
.advisor-action.remove-pro {
|
|
418
|
+
background: rgba(251, 191, 36, 0.06);
|
|
419
|
+
border-left: 3px solid var(--yellow);
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
.advisor-action-type {
|
|
423
|
+
font-weight: 600;
|
|
424
|
+
font-size: 10px;
|
|
425
|
+
text-transform: uppercase;
|
|
426
|
+
letter-spacing: 0.5px;
|
|
427
|
+
padding: 2px 6px;
|
|
428
|
+
border-radius: 4px;
|
|
429
|
+
flex-shrink: 0;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
.advisor-action.add-pro .advisor-action-type {
|
|
433
|
+
background: rgba(52, 211, 153, 0.15);
|
|
434
|
+
color: var(--green);
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
.advisor-action.remove-pro .advisor-action-type {
|
|
438
|
+
background: rgba(251, 191, 36, 0.15);
|
|
439
|
+
color: var(--yellow);
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
.advisor-action-label { font-weight: 500; }
|
|
443
|
+
.advisor-action-reason { color: var(--text-dim); font-size: 11px; margin-left: auto; }
|
|
444
|
+
.advisor-empty { color: var(--text-dim); font-size: 12px; font-style: italic; }
|
|
369
445
|
</style>
|
|
370
446
|
</head>
|
|
371
447
|
<body>
|
|
@@ -397,6 +473,8 @@ const DASHBOARD_HTML = `<!DOCTYPE html>
|
|
|
397
473
|
|
|
398
474
|
<div class="model-routing" id="modelRouting"></div>
|
|
399
475
|
|
|
476
|
+
<div class="advisor-panel" id="proAdvisor" style="display:none"></div>
|
|
477
|
+
|
|
400
478
|
<div class="accounts-grid" id="accounts"></div>
|
|
401
479
|
|
|
402
480
|
<script>
|
|
@@ -501,6 +579,8 @@ function renderAccounts(data) {
|
|
|
501
579
|
'<div class="card-header">' +
|
|
502
580
|
'<div class="card-label">' + maskText(a.label) + '</div>' +
|
|
503
581
|
'<div class="card-badges">' +
|
|
582
|
+
(a.proDetected ? '<span class="badge badge-pro">PRO</span>' : '<span class="badge badge-free">FREE</span>') +
|
|
583
|
+
(a.familyManager ? '<span class="badge badge-fmgr">FAMILY MGR</span>' : '') +
|
|
504
584
|
'<span class="badge badge-' + a.status + (isActive ? ' pulse' : '') + '">' + a.status + '</span>' +
|
|
505
585
|
modelBadges +
|
|
506
586
|
'</div>' +
|
|
@@ -529,6 +609,8 @@ function renderAccounts(data) {
|
|
|
529
609
|
(isCooldown && cooldownPercent > 0 ? '<div class="cooldown-bar" style="width:' + cooldownPercent + '%"></div>' : '') +
|
|
530
610
|
'</div>';
|
|
531
611
|
}).join('');
|
|
612
|
+
|
|
613
|
+
renderProAdvisor(data.proAdvisor);
|
|
532
614
|
}
|
|
533
615
|
|
|
534
616
|
var MASK_MODE = new URLSearchParams(window.location.search).has('mask');
|
|
@@ -555,6 +637,28 @@ async function enableAccount(email) {
|
|
|
555
637
|
refresh();
|
|
556
638
|
}
|
|
557
639
|
|
|
640
|
+
function renderProAdvisor(advisor) {
|
|
641
|
+
var panel = document.getElementById('proAdvisor');
|
|
642
|
+
if (!advisor) { panel.style.display = 'none'; return; }
|
|
643
|
+
panel.style.display = 'block';
|
|
644
|
+
var title = '<div class="advisor-title">Pro Family Advisor' +
|
|
645
|
+
'<span class="advisor-slots">Slots: ' + advisor.currentProCount + '/' + advisor.maxProSlots + '</span></div>';
|
|
646
|
+
if (advisor.actions.length === 0) {
|
|
647
|
+
panel.innerHTML = title + '<div class="advisor-empty">No actions recommended</div>';
|
|
648
|
+
return;
|
|
649
|
+
}
|
|
650
|
+
var rows = advisor.actions.map(function(a) {
|
|
651
|
+
var cls = a.type === 'add-pro' ? 'add-pro' : 'remove-pro';
|
|
652
|
+
var typeLabel = a.type === 'add-pro' ? 'Add Pro' : 'Remove Pro';
|
|
653
|
+
return '<div class="advisor-action ' + cls + '">' +
|
|
654
|
+
'<span class="advisor-action-type">' + typeLabel + '</span>' +
|
|
655
|
+
'<span class="advisor-action-label">' + maskText(a.label) + '</span>' +
|
|
656
|
+
'<span class="advisor-action-reason">' + a.reason + '</span>' +
|
|
657
|
+
'</div>';
|
|
658
|
+
}).join('');
|
|
659
|
+
panel.innerHTML = title + rows;
|
|
660
|
+
}
|
|
661
|
+
|
|
558
662
|
async function refresh() {
|
|
559
663
|
try {
|
|
560
664
|
var res = await fetch('/api/status');
|
package/src/rotator.ts
CHANGED
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
type ModelQuota,
|
|
10
10
|
type ModelRotationState,
|
|
11
11
|
type PersistedState,
|
|
12
|
+
type ProAdvisorAction,
|
|
12
13
|
type StatusResponse,
|
|
13
14
|
CLIENT_ID,
|
|
14
15
|
CLIENT_SECRET,
|
|
@@ -614,6 +615,8 @@ export class AccountRotator {
|
|
|
614
615
|
consecutiveErrors: a.consecutiveErrors,
|
|
615
616
|
hasValidToken: !!(a.accessToken && a.tokenExpires > now),
|
|
616
617
|
quota: a.quota,
|
|
618
|
+
proDetected: this.isProAccount(a),
|
|
619
|
+
familyManager: !!a.config.familyManager,
|
|
617
620
|
};
|
|
618
621
|
});
|
|
619
622
|
|
|
@@ -624,6 +627,7 @@ export class AccountRotator {
|
|
|
624
627
|
totalRequestsAllAccounts: this.accounts.reduce((sum, a) => sum + a.totalRequests, 0),
|
|
625
628
|
uptime: now - this.startTime,
|
|
626
629
|
accounts,
|
|
630
|
+
proAdvisor: this.getProAdvisor(),
|
|
627
631
|
};
|
|
628
632
|
}
|
|
629
633
|
|
|
@@ -635,4 +639,90 @@ export class AccountRotator {
|
|
|
635
639
|
const ts = new Date().toISOString().slice(11, 19);
|
|
636
640
|
console.log(`[${ts}] [rotator] ${msg}`);
|
|
637
641
|
}
|
|
642
|
+
|
|
643
|
+
// =========================================================================
|
|
644
|
+
// Pro Family Sharing Advisor
|
|
645
|
+
// =========================================================================
|
|
646
|
+
|
|
647
|
+
// Model keys relevant for Pro advisor decisions (ignore Flash)
|
|
648
|
+
private static PRO_ADVISOR_MODELS = ["gemini-3.1-pro", "claude-opus-4-6-thinking"];
|
|
649
|
+
|
|
650
|
+
private isProAccount(account: AccountRuntime): boolean {
|
|
651
|
+
return account.quota.some((q) => q.timerType === "5h");
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
private getProAdvisor(): StatusResponse["proAdvisor"] {
|
|
655
|
+
const maxSlots = this.config.proSlots ?? 6;
|
|
656
|
+
const proAccounts = this.accounts.filter((a) => !a.disabled && !a.flagged && this.isProAccount(a));
|
|
657
|
+
const currentProCount = proAccounts.length;
|
|
658
|
+
const actions: ProAdvisorAction[] = [];
|
|
659
|
+
|
|
660
|
+
// Suggest "remove-pro" for Pro accounts (not family manager) with 0% on all advisor models
|
|
661
|
+
for (const account of proAccounts) {
|
|
662
|
+
if (account.config.familyManager) continue;
|
|
663
|
+
const advisorQuotas = account.quota.filter((q) =>
|
|
664
|
+
AccountRotator.PRO_ADVISOR_MODELS.some((m) => q.modelKey.includes(m) || m.includes(q.modelKey)),
|
|
665
|
+
);
|
|
666
|
+
if (advisorQuotas.length === 0) continue;
|
|
667
|
+
const allExhausted = advisorQuotas.every((q) => q.percentRemaining === 0);
|
|
668
|
+
if (allExhausted) {
|
|
669
|
+
actions.push({
|
|
670
|
+
type: "remove-pro",
|
|
671
|
+
email: account.config.email,
|
|
672
|
+
label: account.config.label || account.config.email,
|
|
673
|
+
reason: "Pro quota exhausted on G3Pro and Claude",
|
|
674
|
+
});
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
// Suggest "add-pro" for Free accounts with 0% quota and long reset, if slots available
|
|
679
|
+
const slotsAvailable = maxSlots - currentProCount + actions.filter((a) => a.type === "remove-pro").length;
|
|
680
|
+
if (slotsAvailable > 0) {
|
|
681
|
+
const candidates: { account: AccountRuntime; maxResetMs: number }[] = [];
|
|
682
|
+
|
|
683
|
+
for (const account of this.accounts) {
|
|
684
|
+
if (account.disabled || account.flagged) continue;
|
|
685
|
+
if (this.isProAccount(account)) continue;
|
|
686
|
+
|
|
687
|
+
const advisorQuotas = account.quota.filter((q) =>
|
|
688
|
+
AccountRotator.PRO_ADVISOR_MODELS.some((m) => q.modelKey.includes(m) || m.includes(q.modelKey)),
|
|
689
|
+
);
|
|
690
|
+
if (advisorQuotas.length === 0) continue;
|
|
691
|
+
|
|
692
|
+
// Only suggest if at least one advisor model is at 0%
|
|
693
|
+
const hasExhausted = advisorQuotas.some((q) => q.percentRemaining === 0);
|
|
694
|
+
if (!hasExhausted) continue;
|
|
695
|
+
|
|
696
|
+
// Find the longest reset time among exhausted models
|
|
697
|
+
let maxResetMs = 0;
|
|
698
|
+
for (const q of advisorQuotas) {
|
|
699
|
+
if (q.percentRemaining === 0 && q.resetTime) {
|
|
700
|
+
const resetMs = new Date(q.resetTime).getTime() - Date.now();
|
|
701
|
+
if (resetMs > maxResetMs) maxResetMs = resetMs;
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
// Only suggest if reset is > 24h away (otherwise not worth the Pro slot)
|
|
706
|
+
if (maxResetMs > 24 * 60 * 60 * 1000) {
|
|
707
|
+
candidates.push({ account, maxResetMs });
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
// Sort by longest reset time first (maximizes benefit)
|
|
712
|
+
candidates.sort((a, b) => b.maxResetMs - a.maxResetMs);
|
|
713
|
+
|
|
714
|
+
for (const { account, maxResetMs } of candidates.slice(0, slotsAvailable)) {
|
|
715
|
+
const days = Math.floor(maxResetMs / (24 * 60 * 60 * 1000));
|
|
716
|
+
const hours = Math.floor((maxResetMs % (24 * 60 * 60 * 1000)) / (60 * 60 * 1000));
|
|
717
|
+
actions.push({
|
|
718
|
+
type: "add-pro",
|
|
719
|
+
email: account.config.email,
|
|
720
|
+
label: account.config.label || account.config.email,
|
|
721
|
+
reason: `0% quota, resets in ${days}d ${hours}h`,
|
|
722
|
+
});
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
return { currentProCount, maxProSlots: maxSlots, actions };
|
|
727
|
+
}
|
|
638
728
|
}
|
package/src/types.ts
CHANGED
|
@@ -9,6 +9,8 @@ export interface AccountConfig {
|
|
|
9
9
|
label?: string;
|
|
10
10
|
// Optional - pro/free is detected dynamically from quota API reset times
|
|
11
11
|
type?: AccountType;
|
|
12
|
+
// This account owns the family plan and can never be removed from Pro
|
|
13
|
+
familyManager?: boolean;
|
|
12
14
|
}
|
|
13
15
|
|
|
14
16
|
export interface Config {
|
|
@@ -19,6 +21,8 @@ export interface Config {
|
|
|
19
21
|
rotateOnQuotaDrop: number;
|
|
20
22
|
// How often to poll quota (ms). Default: 5min
|
|
21
23
|
quotaPollIntervalMs: number;
|
|
24
|
+
// Max simultaneous Pro accounts (owner + members). Default: 6
|
|
25
|
+
proSlots?: number;
|
|
22
26
|
}
|
|
23
27
|
|
|
24
28
|
// Quota API response from Google
|
|
@@ -134,6 +138,12 @@ export interface StatusResponse {
|
|
|
134
138
|
// Per-model active account
|
|
135
139
|
activeAccounts: Record<string, string>;
|
|
136
140
|
accounts: AccountStatus[];
|
|
141
|
+
// Pro family sharing advisor
|
|
142
|
+
proAdvisor: {
|
|
143
|
+
currentProCount: number;
|
|
144
|
+
maxProSlots: number;
|
|
145
|
+
actions: ProAdvisorAction[];
|
|
146
|
+
};
|
|
137
147
|
}
|
|
138
148
|
|
|
139
149
|
export interface AccountStatus {
|
|
@@ -151,6 +161,17 @@ export interface AccountStatus {
|
|
|
151
161
|
consecutiveErrors: number;
|
|
152
162
|
hasValidToken: boolean;
|
|
153
163
|
quota: ModelQuota[];
|
|
164
|
+
// Pro family sharing
|
|
165
|
+
proDetected: boolean;
|
|
166
|
+
familyManager: boolean;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Pro advisor suggestion
|
|
170
|
+
export interface ProAdvisorAction {
|
|
171
|
+
type: "add-pro" | "remove-pro";
|
|
172
|
+
email: string;
|
|
173
|
+
label: string;
|
|
174
|
+
reason: string;
|
|
154
175
|
}
|
|
155
176
|
|
|
156
177
|
// Antigravity OAuth constants (same as pi-mono)
|