antigravity-usage 0.1.0 → 0.2.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/README.md +150 -78
- package/dist/index.js +1501 -40
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -525,6 +525,17 @@ var OAUTH_CONFIG = {
|
|
|
525
525
|
"https://www.googleapis.com/auth/userinfo.email"
|
|
526
526
|
]
|
|
527
527
|
};
|
|
528
|
+
var CLOUDCODE_CONFIG = {
|
|
529
|
+
baseUrl: "https://cloudcode-pa.googleapis.com",
|
|
530
|
+
userAgent: "antigravity",
|
|
531
|
+
metadata: {
|
|
532
|
+
ideType: "ANTIGRAVITY",
|
|
533
|
+
platform: "PLATFORM_UNSPECIFIED",
|
|
534
|
+
pluginType: "GEMINI"
|
|
535
|
+
},
|
|
536
|
+
onboardAttempts: 5,
|
|
537
|
+
onboardDelayMs: 2e3
|
|
538
|
+
};
|
|
528
539
|
function generateState() {
|
|
529
540
|
return Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15);
|
|
530
541
|
}
|
|
@@ -585,28 +596,124 @@ async function getUserEmail(accessToken) {
|
|
|
585
596
|
}
|
|
586
597
|
return void 0;
|
|
587
598
|
}
|
|
588
|
-
|
|
589
|
-
|
|
599
|
+
function extractProjectId(value) {
|
|
600
|
+
if (typeof value === "string" && value.length > 0) {
|
|
601
|
+
return value;
|
|
602
|
+
}
|
|
603
|
+
if (value && typeof value === "object" && "id" in value) {
|
|
604
|
+
const id = value.id;
|
|
605
|
+
if (typeof id === "string" && id.length > 0) {
|
|
606
|
+
return id;
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
return void 0;
|
|
610
|
+
}
|
|
611
|
+
function pickOnboardTier(allowedTiers, tierIdFromLoad) {
|
|
612
|
+
if (!allowedTiers || allowedTiers.length === 0) {
|
|
613
|
+
return tierIdFromLoad;
|
|
614
|
+
}
|
|
615
|
+
const defaultTier = allowedTiers.find((t) => t.isDefault === true && t.id && t.id.length > 0);
|
|
616
|
+
if (defaultTier?.id) {
|
|
617
|
+
return defaultTier.id;
|
|
618
|
+
}
|
|
619
|
+
const firstTier = allowedTiers.find((t) => t.id && t.id.length > 0);
|
|
620
|
+
if (firstTier?.id) {
|
|
621
|
+
return firstTier.id;
|
|
622
|
+
}
|
|
623
|
+
if (allowedTiers.length > 0) {
|
|
624
|
+
return "LEGACY";
|
|
625
|
+
}
|
|
626
|
+
return tierIdFromLoad;
|
|
627
|
+
}
|
|
628
|
+
function sleep(ms) {
|
|
629
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
630
|
+
}
|
|
631
|
+
async function tryOnboardUser(accessToken, tierId) {
|
|
632
|
+
debug("oauth", `Starting onboard flow with tierId: ${tierId}`);
|
|
633
|
+
const payload = {
|
|
634
|
+
tierId,
|
|
635
|
+
metadata: CLOUDCODE_CONFIG.metadata
|
|
636
|
+
};
|
|
637
|
+
for (let attempt = 1; attempt <= CLOUDCODE_CONFIG.onboardAttempts; attempt++) {
|
|
638
|
+
debug("oauth", `Onboard attempt ${attempt}/${CLOUDCODE_CONFIG.onboardAttempts}`);
|
|
639
|
+
try {
|
|
640
|
+
const response = await fetch(`${CLOUDCODE_CONFIG.baseUrl}/v1internal:onboardUser`, {
|
|
641
|
+
method: "POST",
|
|
642
|
+
headers: {
|
|
643
|
+
"Authorization": `Bearer ${accessToken}`,
|
|
644
|
+
"Content-Type": "application/json",
|
|
645
|
+
"User-Agent": CLOUDCODE_CONFIG.userAgent
|
|
646
|
+
},
|
|
647
|
+
body: JSON.stringify(payload)
|
|
648
|
+
});
|
|
649
|
+
if (!response.ok) {
|
|
650
|
+
debug("oauth", `Onboard request failed: ${response.status}`);
|
|
651
|
+
if (response.status === 401 || response.status === 403) {
|
|
652
|
+
debug("oauth", "Onboarding forbidden or unauthorized, stopping retries");
|
|
653
|
+
return void 0;
|
|
654
|
+
}
|
|
655
|
+
} else {
|
|
656
|
+
const data = await response.json();
|
|
657
|
+
debug("oauth", `Onboard response: done=${data.done}`);
|
|
658
|
+
if (data.done === true) {
|
|
659
|
+
const projectId = extractProjectId(data.response?.cloudaicompanionProject);
|
|
660
|
+
if (projectId) {
|
|
661
|
+
debug("oauth", `Onboarding complete, projectId: ${projectId}`);
|
|
662
|
+
return projectId;
|
|
663
|
+
}
|
|
664
|
+
debug("oauth", "Onboarding done but no projectId in response");
|
|
665
|
+
return void 0;
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
} catch (err) {
|
|
669
|
+
debug("oauth", `Onboard attempt ${attempt} error:`, err);
|
|
670
|
+
}
|
|
671
|
+
if (attempt < CLOUDCODE_CONFIG.onboardAttempts) {
|
|
672
|
+
debug("oauth", `Waiting ${CLOUDCODE_CONFIG.onboardDelayMs}ms before next attempt`);
|
|
673
|
+
await sleep(CLOUDCODE_CONFIG.onboardDelayMs);
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
debug("oauth", "Onboarding attempts exhausted");
|
|
677
|
+
return void 0;
|
|
678
|
+
}
|
|
679
|
+
async function resolveProjectId(accessToken) {
|
|
680
|
+
debug("oauth", "Resolving project ID from Cloud Code API");
|
|
590
681
|
try {
|
|
591
|
-
const response = await fetch(
|
|
682
|
+
const response = await fetch(`${CLOUDCODE_CONFIG.baseUrl}/v1internal:loadCodeAssist`, {
|
|
592
683
|
method: "POST",
|
|
593
684
|
headers: {
|
|
594
685
|
"Authorization": `Bearer ${accessToken}`,
|
|
595
686
|
"Content-Type": "application/json",
|
|
596
|
-
"User-Agent":
|
|
687
|
+
"User-Agent": CLOUDCODE_CONFIG.userAgent
|
|
597
688
|
},
|
|
598
|
-
body: JSON.stringify({ metadata:
|
|
689
|
+
body: JSON.stringify({ metadata: CLOUDCODE_CONFIG.metadata })
|
|
599
690
|
});
|
|
600
|
-
if (response.ok) {
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
691
|
+
if (!response.ok) {
|
|
692
|
+
debug("oauth", `loadCodeAssist failed: ${response.status}`);
|
|
693
|
+
return { projectId: void 0, tierId: void 0 };
|
|
694
|
+
}
|
|
695
|
+
const data = await response.json();
|
|
696
|
+
const projectId = extractProjectId(data.cloudaicompanionProject);
|
|
697
|
+
const tierId = data.paidTier?.id || data.currentTier?.id;
|
|
698
|
+
if (projectId) {
|
|
699
|
+
debug("oauth", `Got projectId from loadCodeAssist: ${projectId}`);
|
|
700
|
+
return { projectId, tierId };
|
|
701
|
+
}
|
|
702
|
+
debug("oauth", "No projectId in loadCodeAssist response, initiating onboarding");
|
|
703
|
+
const onboardTier = pickOnboardTier(data.allowedTiers, tierId);
|
|
704
|
+
if (!onboardTier) {
|
|
705
|
+
debug("oauth", "Cannot determine tier for onboarding");
|
|
706
|
+
return { projectId: void 0, tierId };
|
|
604
707
|
}
|
|
605
|
-
|
|
708
|
+
const onboardedProjectId = await tryOnboardUser(accessToken, onboardTier);
|
|
709
|
+
return {
|
|
710
|
+
projectId: onboardedProjectId,
|
|
711
|
+
tierId: onboardTier
|
|
712
|
+
};
|
|
606
713
|
} catch (err) {
|
|
607
|
-
debug("oauth", "Error
|
|
714
|
+
debug("oauth", "Error resolving project ID", err);
|
|
715
|
+
return { projectId: void 0, tierId: void 0 };
|
|
608
716
|
}
|
|
609
|
-
return void 0;
|
|
610
717
|
}
|
|
611
718
|
async function startOAuthFlow(options = {}) {
|
|
612
719
|
const port = await getAvailablePort(options.port);
|
|
@@ -653,9 +760,15 @@ async function startOAuthFlow(options = {}) {
|
|
|
653
760
|
const email = await getUserEmail(tokenResponse.access_token);
|
|
654
761
|
let projectId;
|
|
655
762
|
try {
|
|
656
|
-
|
|
763
|
+
const projectResult = await resolveProjectId(tokenResponse.access_token);
|
|
764
|
+
projectId = projectResult.projectId;
|
|
765
|
+
if (projectId) {
|
|
766
|
+
debug("oauth", `Project ID resolved: ${projectId}`);
|
|
767
|
+
} else {
|
|
768
|
+
debug("oauth", "No project ID obtained (will fetch on demand)");
|
|
769
|
+
}
|
|
657
770
|
} catch (err) {
|
|
658
|
-
debug("oauth", "Failed to
|
|
771
|
+
debug("oauth", "Failed to resolve project ID during login (will fetch on demand)", err);
|
|
659
772
|
}
|
|
660
773
|
const tokens = {
|
|
661
774
|
accessToken: tokenResponse.access_token,
|
|
@@ -852,9 +965,29 @@ var APIError = class extends Error {
|
|
|
852
965
|
}
|
|
853
966
|
};
|
|
854
967
|
var TokenRefreshError = class extends Error {
|
|
855
|
-
|
|
968
|
+
/** Original error that caused the refresh failure */
|
|
969
|
+
cause;
|
|
970
|
+
/** HTTP status code if available */
|
|
971
|
+
statusCode;
|
|
972
|
+
/** Whether the error is retryable (network issues) vs permanent (invalid token) */
|
|
973
|
+
isRetryable;
|
|
974
|
+
constructor(message = "Failed to refresh token. Please login again.", options) {
|
|
856
975
|
super(message);
|
|
857
976
|
this.name = "TokenRefreshError";
|
|
977
|
+
this.cause = options?.cause;
|
|
978
|
+
this.statusCode = options?.statusCode;
|
|
979
|
+
this.isRetryable = options?.isRetryable ?? true;
|
|
980
|
+
}
|
|
981
|
+
/** Get detailed error message including cause */
|
|
982
|
+
getDetailedMessage() {
|
|
983
|
+
let msg = this.message;
|
|
984
|
+
if (this.statusCode) {
|
|
985
|
+
msg += ` (HTTP ${this.statusCode})`;
|
|
986
|
+
}
|
|
987
|
+
if (this.cause) {
|
|
988
|
+
msg += `: ${this.cause.message}`;
|
|
989
|
+
}
|
|
990
|
+
return msg;
|
|
858
991
|
}
|
|
859
992
|
};
|
|
860
993
|
var AntigravityNotRunningError = class extends Error {
|
|
@@ -893,7 +1026,11 @@ var TokenManager = class {
|
|
|
893
1026
|
this.tokens = loadAccountTokens(email);
|
|
894
1027
|
} else {
|
|
895
1028
|
this.accountEmail = getActiveAccountEmail();
|
|
896
|
-
this.
|
|
1029
|
+
if (this.accountEmail) {
|
|
1030
|
+
this.tokens = loadAccountTokens(this.accountEmail);
|
|
1031
|
+
} else {
|
|
1032
|
+
this.tokens = loadTokens();
|
|
1033
|
+
}
|
|
897
1034
|
}
|
|
898
1035
|
}
|
|
899
1036
|
/**
|
|
@@ -930,6 +1067,19 @@ var TokenManager = class {
|
|
|
930
1067
|
getProjectId() {
|
|
931
1068
|
return this.tokens?.projectId;
|
|
932
1069
|
}
|
|
1070
|
+
/**
|
|
1071
|
+
* Set and persist project ID
|
|
1072
|
+
*/
|
|
1073
|
+
setProjectId(projectId) {
|
|
1074
|
+
if (!this.tokens) return;
|
|
1075
|
+
this.tokens.projectId = projectId;
|
|
1076
|
+
if (this.accountEmail) {
|
|
1077
|
+
saveAccountTokens(this.accountEmail, this.tokens);
|
|
1078
|
+
} else {
|
|
1079
|
+
saveTokens(this.tokens);
|
|
1080
|
+
}
|
|
1081
|
+
debug("token-manager", `Project ID saved: ${projectId}`);
|
|
1082
|
+
}
|
|
933
1083
|
/**
|
|
934
1084
|
* Check if token is expired or about to expire
|
|
935
1085
|
*/
|
|
@@ -952,33 +1102,65 @@ var TokenManager = class {
|
|
|
952
1102
|
return this.tokens.accessToken;
|
|
953
1103
|
}
|
|
954
1104
|
/**
|
|
955
|
-
* Refresh the access token
|
|
1105
|
+
* Refresh the access token with retry logic
|
|
1106
|
+
* Retries on transient network errors, fails immediately on permanent errors (invalid_grant)
|
|
956
1107
|
*/
|
|
957
1108
|
async refreshToken() {
|
|
958
1109
|
if (!this.tokens?.refreshToken) {
|
|
959
1110
|
throw new NotLoggedInError("No refresh token available. Please login again.");
|
|
960
1111
|
}
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
1112
|
+
const MAX_RETRIES = 3;
|
|
1113
|
+
const BASE_DELAY_MS = 1e3;
|
|
1114
|
+
let lastError;
|
|
1115
|
+
for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
|
|
1116
|
+
try {
|
|
1117
|
+
debug("token-manager", `Refreshing token (attempt ${attempt}/${MAX_RETRIES})...`);
|
|
1118
|
+
const response = await refreshAccessToken(this.tokens.refreshToken);
|
|
1119
|
+
this.tokens = {
|
|
1120
|
+
accessToken: response.access_token,
|
|
1121
|
+
refreshToken: response.refresh_token || this.tokens.refreshToken,
|
|
1122
|
+
expiresAt: Date.now() + response.expires_in * 1e3,
|
|
1123
|
+
email: this.tokens.email,
|
|
1124
|
+
projectId: this.tokens.projectId
|
|
1125
|
+
};
|
|
1126
|
+
if (this.accountEmail) {
|
|
1127
|
+
saveAccountTokens(this.accountEmail, this.tokens);
|
|
1128
|
+
updateLastUsed(this.accountEmail);
|
|
1129
|
+
} else {
|
|
1130
|
+
saveTokens(this.tokens);
|
|
1131
|
+
}
|
|
1132
|
+
debug("token-manager", "Token refreshed successfully");
|
|
1133
|
+
return;
|
|
1134
|
+
} catch (err) {
|
|
1135
|
+
lastError = err instanceof Error ? err : new Error(String(err));
|
|
1136
|
+
const errorMessage = lastError.message.toLowerCase();
|
|
1137
|
+
const isPermanentError = errorMessage.includes("invalid_grant") || errorMessage.includes("400") || errorMessage.includes("401") || errorMessage.includes("invalid_token") || errorMessage.includes("token has been revoked");
|
|
1138
|
+
if (isPermanentError) {
|
|
1139
|
+
debug("token-manager", `Token refresh failed permanently: ${lastError.message}`);
|
|
1140
|
+
throw new TokenRefreshError(
|
|
1141
|
+
`Refresh token invalid or expired. Please login again.`,
|
|
1142
|
+
{ cause: lastError, isRetryable: false }
|
|
1143
|
+
);
|
|
1144
|
+
}
|
|
1145
|
+
if (attempt < MAX_RETRIES) {
|
|
1146
|
+
const delayMs = BASE_DELAY_MS * Math.pow(2, attempt - 1);
|
|
1147
|
+
debug("token-manager", `Token refresh attempt ${attempt} failed: ${lastError.message}. Retrying in ${delayMs}ms...`);
|
|
1148
|
+
await this.sleep(delayMs);
|
|
1149
|
+
} else {
|
|
1150
|
+
debug("token-manager", `Token refresh failed after ${MAX_RETRIES} attempts: ${lastError.message}`);
|
|
1151
|
+
}
|
|
976
1152
|
}
|
|
977
|
-
debug("token-manager", "Token refreshed successfully");
|
|
978
|
-
} catch (err) {
|
|
979
|
-
debug("token-manager", "Token refresh failed", err);
|
|
980
|
-
throw new TokenRefreshError();
|
|
981
1153
|
}
|
|
1154
|
+
throw new TokenRefreshError(
|
|
1155
|
+
`Failed to refresh token after ${MAX_RETRIES} attempts`,
|
|
1156
|
+
{ cause: lastError, isRetryable: true }
|
|
1157
|
+
);
|
|
1158
|
+
}
|
|
1159
|
+
/**
|
|
1160
|
+
* Sleep helper for retry delays
|
|
1161
|
+
*/
|
|
1162
|
+
sleep(ms) {
|
|
1163
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
982
1164
|
}
|
|
983
1165
|
/**
|
|
984
1166
|
* Reload tokens from disk
|
|
@@ -1208,11 +1390,24 @@ function statusCommand(options = {}) {
|
|
|
1208
1390
|
}
|
|
1209
1391
|
|
|
1210
1392
|
// src/google/cloudcode.ts
|
|
1211
|
-
var
|
|
1212
|
-
|
|
1393
|
+
var BASE_URLS = [
|
|
1394
|
+
"https://cloudcode-pa.googleapis.com",
|
|
1395
|
+
"https://daily-cloudcode-pa.sandbox.googleapis.com"
|
|
1396
|
+
];
|
|
1397
|
+
var BASE_URL = BASE_URLS[0];
|
|
1398
|
+
var USER_AGENT = "antigravity";
|
|
1399
|
+
var MAX_TRIGGER_ATTEMPTS = 3;
|
|
1400
|
+
var STREAM_PATH = "/v1internal:streamGenerateContent?alt=sse";
|
|
1401
|
+
var SYSTEM_PROMPT = "You are Antigravity, a powerful agentic AI coding assistant designed by the Google Deepmind team working on Advanced Agentic Coding. You are pair programming with a USER to solve their coding task. The task may require creating a new codebase, modifying or debugging an existing codebase, or simply answering a question.**Absolute paths only****Proactiveness**";
|
|
1402
|
+
var METADATA = {
|
|
1403
|
+
ideType: "ANTIGRAVITY",
|
|
1404
|
+
platform: "PLATFORM_UNSPECIFIED",
|
|
1405
|
+
pluginType: "GEMINI"
|
|
1406
|
+
};
|
|
1213
1407
|
var CloudCodeClient = class {
|
|
1214
1408
|
constructor(tokenManager) {
|
|
1215
1409
|
this.tokenManager = tokenManager;
|
|
1410
|
+
this.projectId = tokenManager.getProjectId();
|
|
1216
1411
|
}
|
|
1217
1412
|
projectId;
|
|
1218
1413
|
/**
|
|
@@ -1270,14 +1465,85 @@ var CloudCodeClient = class {
|
|
|
1270
1465
|
*/
|
|
1271
1466
|
async loadCodeAssist() {
|
|
1272
1467
|
const response = await this.request("/v1internal:loadCodeAssist", {
|
|
1273
|
-
metadata:
|
|
1468
|
+
metadata: METADATA
|
|
1274
1469
|
});
|
|
1275
1470
|
if (response.cloudaicompanionProject) {
|
|
1276
|
-
|
|
1471
|
+
if (typeof response.cloudaicompanionProject === "string") {
|
|
1472
|
+
this.projectId = response.cloudaicompanionProject;
|
|
1473
|
+
} else if (response.cloudaicompanionProject.id) {
|
|
1474
|
+
this.projectId = response.cloudaicompanionProject.id;
|
|
1475
|
+
}
|
|
1277
1476
|
debug("cloudcode", `Project ID: ${this.projectId}`);
|
|
1278
1477
|
}
|
|
1279
1478
|
return response;
|
|
1280
1479
|
}
|
|
1480
|
+
/**
|
|
1481
|
+
* Extract project ID from loadCodeAssist response
|
|
1482
|
+
*/
|
|
1483
|
+
extractProjectId(response) {
|
|
1484
|
+
const projectId = response.cloudaicompanionProject || response.project || response.projectId || response.cloudProject;
|
|
1485
|
+
if (projectId && typeof projectId === "string" && projectId.length > 0) {
|
|
1486
|
+
this.projectId = projectId;
|
|
1487
|
+
debug("cloudcode", `Project ID extracted: ${this.projectId}`);
|
|
1488
|
+
} else {
|
|
1489
|
+
debug("cloudcode", "No project ID found in response");
|
|
1490
|
+
}
|
|
1491
|
+
}
|
|
1492
|
+
/**
|
|
1493
|
+
* Resolve project ID with onboarding retry if needed
|
|
1494
|
+
* This is the recommended way to get projectId reliably
|
|
1495
|
+
*/
|
|
1496
|
+
async resolveProjectId(maxRetries = 5, retryDelayMs = 2e3) {
|
|
1497
|
+
if (this.projectId) {
|
|
1498
|
+
debug("cloudcode", `Using cached project ID: ${this.projectId}`);
|
|
1499
|
+
return this.projectId;
|
|
1500
|
+
}
|
|
1501
|
+
const loadResponse = await this.loadCodeAssist();
|
|
1502
|
+
if (this.projectId) {
|
|
1503
|
+
return this.projectId;
|
|
1504
|
+
}
|
|
1505
|
+
debug("cloudcode", "Project ID not found, attempting onboarding...");
|
|
1506
|
+
const tiers = loadResponse.allowedTiers || [];
|
|
1507
|
+
let tierId;
|
|
1508
|
+
const defaultTier = tiers.find((t) => t.isDefault);
|
|
1509
|
+
if (defaultTier) {
|
|
1510
|
+
tierId = defaultTier.id;
|
|
1511
|
+
} else if (loadResponse.paidTier?.id) {
|
|
1512
|
+
tierId = loadResponse.paidTier.id;
|
|
1513
|
+
} else if (loadResponse.currentTier?.id) {
|
|
1514
|
+
tierId = loadResponse.currentTier.id;
|
|
1515
|
+
} else if (tiers.length > 0) {
|
|
1516
|
+
tierId = tiers[0].id;
|
|
1517
|
+
}
|
|
1518
|
+
if (!tierId) {
|
|
1519
|
+
debug("cloudcode", "No tier available for onboarding");
|
|
1520
|
+
return void 0;
|
|
1521
|
+
}
|
|
1522
|
+
debug("cloudcode", `Onboarding with tier: ${tierId}`);
|
|
1523
|
+
try {
|
|
1524
|
+
await this.request("/v1internal:onboardUser", {
|
|
1525
|
+
tierId,
|
|
1526
|
+
metadata: {
|
|
1527
|
+
ideType: "ANTIGRAVITY",
|
|
1528
|
+
platform: "PLATFORM_UNSPECIFIED",
|
|
1529
|
+
pluginType: "GEMINI"
|
|
1530
|
+
}
|
|
1531
|
+
});
|
|
1532
|
+
} catch (err) {
|
|
1533
|
+
debug("cloudcode", "Onboarding call failed (may be expected):", err);
|
|
1534
|
+
}
|
|
1535
|
+
for (let i = 0; i < maxRetries; i++) {
|
|
1536
|
+
debug("cloudcode", `Retry ${i + 1}/${maxRetries} for project ID...`);
|
|
1537
|
+
await new Promise((resolve) => setTimeout(resolve, retryDelayMs));
|
|
1538
|
+
await this.loadCodeAssist();
|
|
1539
|
+
if (this.projectId) {
|
|
1540
|
+
debug("cloudcode", `Project ID resolved after ${i + 1} retries: ${this.projectId}`);
|
|
1541
|
+
return this.projectId;
|
|
1542
|
+
}
|
|
1543
|
+
}
|
|
1544
|
+
debug("cloudcode", "Failed to resolve project ID after all retries");
|
|
1545
|
+
return void 0;
|
|
1546
|
+
}
|
|
1281
1547
|
/**
|
|
1282
1548
|
* Fetch available models with quota info
|
|
1283
1549
|
* Requires project ID from loadCodeAssist
|
|
@@ -1286,6 +1552,154 @@ var CloudCodeClient = class {
|
|
|
1286
1552
|
const body = this.projectId ? { project: this.projectId } : {};
|
|
1287
1553
|
return this.request("/v1internal:fetchAvailableModels", body);
|
|
1288
1554
|
}
|
|
1555
|
+
/**
|
|
1556
|
+
* Generate content using a specific model (Agent Request Format)
|
|
1557
|
+
* Used for wake-up triggers to warm up models
|
|
1558
|
+
*
|
|
1559
|
+
* Per docs/trigger.md, must use the agent request format with:
|
|
1560
|
+
* - project: Cloud Code project ID
|
|
1561
|
+
* - requestId: unique ID
|
|
1562
|
+
* - model: model ID
|
|
1563
|
+
* - userAgent: "antigravity"
|
|
1564
|
+
* - requestType: "agent"
|
|
1565
|
+
* - request: contains contents, session_id, systemInstruction, generationConfig
|
|
1566
|
+
*
|
|
1567
|
+
* @param modelId Model ID to use
|
|
1568
|
+
* @param prompt User prompt to send
|
|
1569
|
+
* @param maxOutputTokens Maximum tokens to generate (0 = no limit)
|
|
1570
|
+
* @returns Generated text and optional token usage
|
|
1571
|
+
*/
|
|
1572
|
+
async generateContent(modelId, prompt, maxOutputTokens) {
|
|
1573
|
+
debug("cloudcode", `Generating content with model: ${modelId}`);
|
|
1574
|
+
debug("cloudcode", `Current projectId: ${this.projectId}`);
|
|
1575
|
+
debug("cloudcode", "Warming up session with loadCodeAssist...");
|
|
1576
|
+
try {
|
|
1577
|
+
await this.loadCodeAssist();
|
|
1578
|
+
debug("cloudcode", `Session warmed up, projectId: ${this.projectId}`);
|
|
1579
|
+
} catch (err) {
|
|
1580
|
+
debug("cloudcode", "Warmup failed (continuing anyway):", err);
|
|
1581
|
+
}
|
|
1582
|
+
const requestId = crypto.randomUUID();
|
|
1583
|
+
const sessionId = crypto.randomUUID();
|
|
1584
|
+
const systemInstruction = {
|
|
1585
|
+
parts: [{ text: SYSTEM_PROMPT }]
|
|
1586
|
+
};
|
|
1587
|
+
const generationConfig = {
|
|
1588
|
+
temperature: 0
|
|
1589
|
+
};
|
|
1590
|
+
if (maxOutputTokens && maxOutputTokens > 0) {
|
|
1591
|
+
generationConfig.maxOutputTokens = maxOutputTokens;
|
|
1592
|
+
}
|
|
1593
|
+
const body = {
|
|
1594
|
+
requestId,
|
|
1595
|
+
model: modelId,
|
|
1596
|
+
userAgent: "antigravity",
|
|
1597
|
+
requestType: "agent",
|
|
1598
|
+
request: {
|
|
1599
|
+
contents: [{
|
|
1600
|
+
role: "user",
|
|
1601
|
+
parts: [{ text: prompt }]
|
|
1602
|
+
}],
|
|
1603
|
+
session_id: sessionId,
|
|
1604
|
+
systemInstruction,
|
|
1605
|
+
generationConfig
|
|
1606
|
+
}
|
|
1607
|
+
};
|
|
1608
|
+
if (this.projectId) {
|
|
1609
|
+
body.project = this.projectId;
|
|
1610
|
+
debug("cloudcode", `Using project ID: ${this.projectId}`);
|
|
1611
|
+
} else {
|
|
1612
|
+
debug("cloudcode", "Sending request WITHOUT project ID");
|
|
1613
|
+
}
|
|
1614
|
+
debug("cloudcode", `Request body:`, JSON.stringify(body, null, 2));
|
|
1615
|
+
const token = await this.tokenManager.getValidAccessToken();
|
|
1616
|
+
const getBackoffDelay = (attempt) => {
|
|
1617
|
+
const raw = 500 * Math.pow(2, attempt - 2);
|
|
1618
|
+
const jitter = Math.random() * 100;
|
|
1619
|
+
return Math.min(raw + jitter, 4e3);
|
|
1620
|
+
};
|
|
1621
|
+
const sleep2 = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
1622
|
+
const parseSSEResponse = (sseText) => {
|
|
1623
|
+
let fullText = "";
|
|
1624
|
+
let tokensUsed;
|
|
1625
|
+
for (const line of sseText.split("\n")) {
|
|
1626
|
+
if (line.startsWith("data: ")) {
|
|
1627
|
+
const jsonStr = line.substring(6);
|
|
1628
|
+
if (jsonStr.trim() === "[DONE]") continue;
|
|
1629
|
+
try {
|
|
1630
|
+
const data = JSON.parse(jsonStr);
|
|
1631
|
+
const candidateText = data.candidates?.[0]?.content?.parts?.[0]?.text;
|
|
1632
|
+
if (candidateText) {
|
|
1633
|
+
fullText += candidateText;
|
|
1634
|
+
}
|
|
1635
|
+
if (data.usageMetadata) {
|
|
1636
|
+
tokensUsed = {
|
|
1637
|
+
prompt: data.usageMetadata.promptTokenCount || 0,
|
|
1638
|
+
completion: data.usageMetadata.candidatesTokenCount || 0,
|
|
1639
|
+
total: data.usageMetadata.totalTokenCount || 0
|
|
1640
|
+
};
|
|
1641
|
+
}
|
|
1642
|
+
} catch {
|
|
1643
|
+
}
|
|
1644
|
+
}
|
|
1645
|
+
}
|
|
1646
|
+
return { text: fullText, tokensUsed };
|
|
1647
|
+
};
|
|
1648
|
+
for (const baseUrl of BASE_URLS) {
|
|
1649
|
+
for (let attempt = 1; attempt <= MAX_TRIGGER_ATTEMPTS; attempt++) {
|
|
1650
|
+
if (attempt > 1) {
|
|
1651
|
+
const delay = getBackoffDelay(attempt);
|
|
1652
|
+
debug("cloudcode", `Retry ${attempt}/${MAX_TRIGGER_ATTEMPTS} in ${Math.round(delay)}ms...`);
|
|
1653
|
+
await sleep2(delay);
|
|
1654
|
+
}
|
|
1655
|
+
const url = `${baseUrl}${STREAM_PATH}`;
|
|
1656
|
+
debug("cloudcode", `Attempt ${attempt}/${MAX_TRIGGER_ATTEMPTS} on ${baseUrl}`);
|
|
1657
|
+
try {
|
|
1658
|
+
const response = await fetch(url, {
|
|
1659
|
+
method: "POST",
|
|
1660
|
+
headers: {
|
|
1661
|
+
"Authorization": `Bearer ${token}`,
|
|
1662
|
+
"User-Agent": USER_AGENT,
|
|
1663
|
+
"Content-Type": "application/json",
|
|
1664
|
+
"Accept-Encoding": "gzip"
|
|
1665
|
+
// CRITICAL: Must match example.ts
|
|
1666
|
+
},
|
|
1667
|
+
body: JSON.stringify(body)
|
|
1668
|
+
});
|
|
1669
|
+
const text = await response.text();
|
|
1670
|
+
debug("cloudcode", `Response ${response.status}`);
|
|
1671
|
+
debug("cloudcode", `Response text: ${text.slice(0, 500)}`);
|
|
1672
|
+
if (response.status === 429 || response.status >= 500) {
|
|
1673
|
+
debug("cloudcode", `${response.status} - retryable`);
|
|
1674
|
+
if (attempt === MAX_TRIGGER_ATTEMPTS) {
|
|
1675
|
+
debug("cloudcode", "Max attempts on this URL, trying next...");
|
|
1676
|
+
break;
|
|
1677
|
+
}
|
|
1678
|
+
continue;
|
|
1679
|
+
}
|
|
1680
|
+
if (response.ok) {
|
|
1681
|
+
debug("cloudcode", "Request succeeded!");
|
|
1682
|
+
const parsed = parseSSEResponse(text);
|
|
1683
|
+
debug("cloudcode", `Generated ${parsed.text.length} chars, tokens: ${parsed.tokensUsed?.total || "unknown"}`);
|
|
1684
|
+
return parsed;
|
|
1685
|
+
}
|
|
1686
|
+
debug("cloudcode", `Non-retryable error: ${response.status}`);
|
|
1687
|
+
throw new Error(`API request failed: ${response.status} - ${text}`);
|
|
1688
|
+
} catch (err) {
|
|
1689
|
+
if (err instanceof Error && !err.message.startsWith("API request failed")) {
|
|
1690
|
+
debug("cloudcode", `Network error: ${err.message}`);
|
|
1691
|
+
if (attempt === MAX_TRIGGER_ATTEMPTS) {
|
|
1692
|
+
debug("cloudcode", "Max attempts on this URL, trying next...");
|
|
1693
|
+
break;
|
|
1694
|
+
}
|
|
1695
|
+
continue;
|
|
1696
|
+
}
|
|
1697
|
+
throw err;
|
|
1698
|
+
}
|
|
1699
|
+
}
|
|
1700
|
+
}
|
|
1701
|
+
throw new Error("All trigger attempts failed across all base URLs");
|
|
1702
|
+
}
|
|
1289
1703
|
};
|
|
1290
1704
|
|
|
1291
1705
|
// src/google/parser.ts
|
|
@@ -1976,6 +2390,13 @@ async function fetchQuotaGoogle() {
|
|
|
1976
2390
|
const client = new CloudCodeClient(tokenManager);
|
|
1977
2391
|
const codeAssistResponse = await client.loadCodeAssist();
|
|
1978
2392
|
debug("service", "Code assist response received", JSON.stringify(codeAssistResponse));
|
|
2393
|
+
if (codeAssistResponse?.cloudaicompanionProject) {
|
|
2394
|
+
const projectId = extractProjectId(codeAssistResponse.cloudaicompanionProject);
|
|
2395
|
+
if (projectId) {
|
|
2396
|
+
tokenManager.setProjectId(projectId);
|
|
2397
|
+
debug("service", `Project ID saved: ${projectId}`);
|
|
2398
|
+
}
|
|
2399
|
+
}
|
|
1979
2400
|
let modelsResponse = {};
|
|
1980
2401
|
try {
|
|
1981
2402
|
modelsResponse = await client.fetchAvailableModels();
|
|
@@ -2707,6 +3128,1037 @@ async function accountsCommand(subcommand, args, options) {
|
|
|
2707
3128
|
}
|
|
2708
3129
|
}
|
|
2709
3130
|
|
|
3131
|
+
// src/commands/wakeup.ts
|
|
3132
|
+
import inquirer from "inquirer";
|
|
3133
|
+
import Table4 from "cli-table3";
|
|
3134
|
+
|
|
3135
|
+
// src/wakeup/types.ts
|
|
3136
|
+
function getDefaultConfig() {
|
|
3137
|
+
return {
|
|
3138
|
+
enabled: false,
|
|
3139
|
+
selectedModels: ["claude-sonnet-4-5", "gemini-3-flash"],
|
|
3140
|
+
selectedAccounts: void 0,
|
|
3141
|
+
customPrompt: void 0,
|
|
3142
|
+
maxOutputTokens: 1,
|
|
3143
|
+
// Minimal tokens to save quota
|
|
3144
|
+
scheduleMode: "interval",
|
|
3145
|
+
intervalHours: 6,
|
|
3146
|
+
dailyTimes: ["09:00"],
|
|
3147
|
+
weeklySchedule: {},
|
|
3148
|
+
cronExpression: void 0,
|
|
3149
|
+
wakeOnReset: false,
|
|
3150
|
+
resetCooldownMinutes: 10
|
|
3151
|
+
};
|
|
3152
|
+
}
|
|
3153
|
+
|
|
3154
|
+
// src/wakeup/storage.ts
|
|
3155
|
+
import { join as join3 } from "path";
|
|
3156
|
+
import { readFileSync as readFileSync4, writeFileSync as writeFileSync4, existsSync as existsSync4, mkdirSync as mkdirSync4 } from "fs";
|
|
3157
|
+
var WAKEUP_DIR_NAME = "wakeup";
|
|
3158
|
+
var CONFIG_FILE_NAME = "config.json";
|
|
3159
|
+
var HISTORY_FILE_NAME = "history.json";
|
|
3160
|
+
var MAX_HISTORY_ENTRIES = 100;
|
|
3161
|
+
function getWakeupDir() {
|
|
3162
|
+
return join3(getConfigDir(), WAKEUP_DIR_NAME);
|
|
3163
|
+
}
|
|
3164
|
+
function ensureWakeupDir() {
|
|
3165
|
+
const dir = getWakeupDir();
|
|
3166
|
+
if (!existsSync4(dir)) {
|
|
3167
|
+
mkdirSync4(dir, { recursive: true });
|
|
3168
|
+
debug("wakeup-storage", `Created wakeup directory: ${dir}`);
|
|
3169
|
+
}
|
|
3170
|
+
}
|
|
3171
|
+
function readJsonFile(filename, defaultValue) {
|
|
3172
|
+
const filepath = join3(getWakeupDir(), filename);
|
|
3173
|
+
try {
|
|
3174
|
+
if (existsSync4(filepath)) {
|
|
3175
|
+
const content = readFileSync4(filepath, "utf-8");
|
|
3176
|
+
return JSON.parse(content);
|
|
3177
|
+
}
|
|
3178
|
+
} catch (err) {
|
|
3179
|
+
debug("wakeup-storage", `Error reading ${filename}:`, err);
|
|
3180
|
+
}
|
|
3181
|
+
return defaultValue;
|
|
3182
|
+
}
|
|
3183
|
+
function writeJsonFile(filename, data) {
|
|
3184
|
+
ensureWakeupDir();
|
|
3185
|
+
const filepath = join3(getWakeupDir(), filename);
|
|
3186
|
+
try {
|
|
3187
|
+
writeFileSync4(filepath, JSON.stringify(data, null, 2), "utf-8");
|
|
3188
|
+
debug("wakeup-storage", `Wrote ${filename}`);
|
|
3189
|
+
} catch (err) {
|
|
3190
|
+
debug("wakeup-storage", `Error writing ${filename}:`, err);
|
|
3191
|
+
throw err;
|
|
3192
|
+
}
|
|
3193
|
+
}
|
|
3194
|
+
function loadWakeupConfig() {
|
|
3195
|
+
const config = readJsonFile(CONFIG_FILE_NAME, null);
|
|
3196
|
+
if (config) {
|
|
3197
|
+
debug("wakeup-storage", "Loaded wakeup config");
|
|
3198
|
+
}
|
|
3199
|
+
return config;
|
|
3200
|
+
}
|
|
3201
|
+
function saveWakeupConfig(config) {
|
|
3202
|
+
writeJsonFile(CONFIG_FILE_NAME, config);
|
|
3203
|
+
debug("wakeup-storage", "Saved wakeup config");
|
|
3204
|
+
}
|
|
3205
|
+
function getOrCreateConfig() {
|
|
3206
|
+
const existing = loadWakeupConfig();
|
|
3207
|
+
if (existing) {
|
|
3208
|
+
if (!existing.selectedModels || existing.selectedModels.length === 0) {
|
|
3209
|
+
existing.selectedModels = ["claude-sonnet-4-5", "gemini-3-flash"];
|
|
3210
|
+
saveWakeupConfig(existing);
|
|
3211
|
+
debug("wakeup-storage", "Migrated config to new default models");
|
|
3212
|
+
}
|
|
3213
|
+
return existing;
|
|
3214
|
+
}
|
|
3215
|
+
const defaultConfig = getDefaultConfig();
|
|
3216
|
+
saveWakeupConfig(defaultConfig);
|
|
3217
|
+
return defaultConfig;
|
|
3218
|
+
}
|
|
3219
|
+
function loadTriggerHistory() {
|
|
3220
|
+
return readJsonFile(HISTORY_FILE_NAME, []);
|
|
3221
|
+
}
|
|
3222
|
+
function saveTriggerHistory(history) {
|
|
3223
|
+
writeJsonFile(HISTORY_FILE_NAME, history);
|
|
3224
|
+
}
|
|
3225
|
+
function addTriggerRecord(record) {
|
|
3226
|
+
const history = loadTriggerHistory();
|
|
3227
|
+
history.unshift(record);
|
|
3228
|
+
if (history.length > MAX_HISTORY_ENTRIES) {
|
|
3229
|
+
history.splice(MAX_HISTORY_ENTRIES);
|
|
3230
|
+
}
|
|
3231
|
+
saveTriggerHistory(history);
|
|
3232
|
+
debug("wakeup-storage", `Added trigger record (total: ${history.length})`);
|
|
3233
|
+
}
|
|
3234
|
+
function getRecentHistory(limit = 10) {
|
|
3235
|
+
const history = loadTriggerHistory();
|
|
3236
|
+
return history.slice(0, limit);
|
|
3237
|
+
}
|
|
3238
|
+
function getLastTrigger() {
|
|
3239
|
+
const history = loadTriggerHistory();
|
|
3240
|
+
return history.length > 0 ? history[0] : null;
|
|
3241
|
+
}
|
|
3242
|
+
|
|
3243
|
+
// src/wakeup/account-resolver.ts
|
|
3244
|
+
function resolveAccounts(selectedAccounts) {
|
|
3245
|
+
const accountManager = getAccountManager();
|
|
3246
|
+
if (selectedAccounts !== void 0) {
|
|
3247
|
+
debug("account-resolver", `Explicit account selection: ${selectedAccounts.length} accounts`);
|
|
3248
|
+
const validAccounts = selectedAccounts.filter((email) => {
|
|
3249
|
+
if (!accountManager.hasAccount(email)) {
|
|
3250
|
+
debug("account-resolver", `Account ${email} not found, skipping`);
|
|
3251
|
+
return false;
|
|
3252
|
+
}
|
|
3253
|
+
const status = accountManager.getAccountStatus(email);
|
|
3254
|
+
if (status === "invalid") {
|
|
3255
|
+
debug("account-resolver", `Account ${email} is invalid, skipping`);
|
|
3256
|
+
return false;
|
|
3257
|
+
}
|
|
3258
|
+
return true;
|
|
3259
|
+
});
|
|
3260
|
+
debug("account-resolver", `Resolved ${validAccounts.length} valid accounts from selection`);
|
|
3261
|
+
return validAccounts;
|
|
3262
|
+
}
|
|
3263
|
+
debug("account-resolver", "No explicit selection, using fallback logic");
|
|
3264
|
+
const activeEmail = accountManager.getActiveEmail();
|
|
3265
|
+
if (activeEmail) {
|
|
3266
|
+
const status = accountManager.getAccountStatus(activeEmail);
|
|
3267
|
+
if (status === "valid" || status === "expired") {
|
|
3268
|
+
debug("account-resolver", `Using active account: ${activeEmail}`);
|
|
3269
|
+
return [activeEmail];
|
|
3270
|
+
}
|
|
3271
|
+
debug("account-resolver", `Active account ${activeEmail} is ${status}, trying fallback`);
|
|
3272
|
+
}
|
|
3273
|
+
const allEmails = accountManager.getAccountEmails();
|
|
3274
|
+
for (const email of allEmails) {
|
|
3275
|
+
const status = accountManager.getAccountStatus(email);
|
|
3276
|
+
if (status === "valid" || status === "expired") {
|
|
3277
|
+
debug("account-resolver", `Fallback to first valid account: ${email}`);
|
|
3278
|
+
return [email];
|
|
3279
|
+
}
|
|
3280
|
+
}
|
|
3281
|
+
debug("account-resolver", "No valid accounts found");
|
|
3282
|
+
return [];
|
|
3283
|
+
}
|
|
3284
|
+
function getAccountResolutionStatus(selectedAccounts) {
|
|
3285
|
+
const resolved = resolveAccounts(selectedAccounts);
|
|
3286
|
+
if (resolved.length === 0) {
|
|
3287
|
+
if (selectedAccounts !== void 0 && selectedAccounts.length > 0) {
|
|
3288
|
+
return "Selected accounts are invalid or not found";
|
|
3289
|
+
}
|
|
3290
|
+
return "No valid accounts available";
|
|
3291
|
+
}
|
|
3292
|
+
if (resolved.length === 1) {
|
|
3293
|
+
return `Using account: ${resolved[0]}`;
|
|
3294
|
+
}
|
|
3295
|
+
return `Using ${resolved.length} accounts: ${resolved.join(", ")}`;
|
|
3296
|
+
}
|
|
3297
|
+
|
|
3298
|
+
// src/wakeup/schedule-converter.ts
|
|
3299
|
+
function configToCronExpression(config) {
|
|
3300
|
+
if (config.cronExpression) {
|
|
3301
|
+
return config.cronExpression;
|
|
3302
|
+
}
|
|
3303
|
+
switch (config.scheduleMode) {
|
|
3304
|
+
case "interval":
|
|
3305
|
+
return intervalToCron(config.intervalHours || 6);
|
|
3306
|
+
case "daily":
|
|
3307
|
+
return dailyToCron(config.dailyTimes || ["09:00"]);
|
|
3308
|
+
case "weekly":
|
|
3309
|
+
return weeklyToCron(config.weeklySchedule || {});
|
|
3310
|
+
case "custom":
|
|
3311
|
+
return "0 */6 * * *";
|
|
3312
|
+
default:
|
|
3313
|
+
throw new Error(`Unknown schedule mode: ${config.scheduleMode}`);
|
|
3314
|
+
}
|
|
3315
|
+
}
|
|
3316
|
+
function intervalToCron(hours) {
|
|
3317
|
+
if (hours < 1 || hours > 23) {
|
|
3318
|
+
throw new Error("Interval hours must be between 1 and 23");
|
|
3319
|
+
}
|
|
3320
|
+
return `0 */${hours} * * *`;
|
|
3321
|
+
}
|
|
3322
|
+
function dailyToCron(times) {
|
|
3323
|
+
if (times.length === 0) {
|
|
3324
|
+
throw new Error("Daily mode requires at least one time");
|
|
3325
|
+
}
|
|
3326
|
+
const parsedTimes = times.map(parseTime);
|
|
3327
|
+
const [firstHour, firstMinute] = parsedTimes[0];
|
|
3328
|
+
const hours = parsedTimes.map(([h]) => h);
|
|
3329
|
+
const allSameMinute = parsedTimes.every(([, m]) => m === firstMinute);
|
|
3330
|
+
if (allSameMinute) {
|
|
3331
|
+
return `${firstMinute} ${hours.join(",")} * * *`;
|
|
3332
|
+
}
|
|
3333
|
+
return `${firstMinute} ${firstHour} * * *`;
|
|
3334
|
+
}
|
|
3335
|
+
function weeklyToCron(schedule) {
|
|
3336
|
+
const days = Object.keys(schedule).map(Number).sort();
|
|
3337
|
+
if (days.length === 0) {
|
|
3338
|
+
throw new Error("Weekly mode requires at least one day");
|
|
3339
|
+
}
|
|
3340
|
+
const firstDay = days[0];
|
|
3341
|
+
const firstDayTimes = schedule[firstDay];
|
|
3342
|
+
if (!firstDayTimes || firstDayTimes.length === 0) {
|
|
3343
|
+
throw new Error(`No times specified for day ${firstDay}`);
|
|
3344
|
+
}
|
|
3345
|
+
const [hour, minute] = parseTime(firstDayTimes[0]);
|
|
3346
|
+
const daysStr = days.join(",");
|
|
3347
|
+
return `${minute} ${hour} * * ${daysStr}`;
|
|
3348
|
+
}
|
|
3349
|
+
function parseTime(timeStr) {
|
|
3350
|
+
const match = timeStr.match(/^(\d{1,2}):(\d{2})$/);
|
|
3351
|
+
if (!match) {
|
|
3352
|
+
throw new Error(`Invalid time format: ${timeStr}. Expected HH:MM`);
|
|
3353
|
+
}
|
|
3354
|
+
const hour = parseInt(match[1], 10);
|
|
3355
|
+
const minute = parseInt(match[2], 10);
|
|
3356
|
+
if (hour < 0 || hour > 23 || minute < 0 || minute > 59) {
|
|
3357
|
+
throw new Error(`Invalid time values: ${timeStr}`);
|
|
3358
|
+
}
|
|
3359
|
+
return [hour, minute];
|
|
3360
|
+
}
|
|
3361
|
+
function getScheduleDescription(config) {
|
|
3362
|
+
if (!config.enabled) {
|
|
3363
|
+
return "Disabled";
|
|
3364
|
+
}
|
|
3365
|
+
if (config.wakeOnReset) {
|
|
3366
|
+
const cooldown = config.resetCooldownMinutes || 10;
|
|
3367
|
+
return `Quota-reset based (${cooldown}min cooldown)`;
|
|
3368
|
+
}
|
|
3369
|
+
switch (config.scheduleMode) {
|
|
3370
|
+
case "interval":
|
|
3371
|
+
const hours = config.intervalHours || 6;
|
|
3372
|
+
return `Every ${hours} hour${hours > 1 ? "s" : ""}`;
|
|
3373
|
+
case "daily":
|
|
3374
|
+
const times = config.dailyTimes || ["09:00"];
|
|
3375
|
+
if (times.length === 1) {
|
|
3376
|
+
return `Daily at ${times[0]}`;
|
|
3377
|
+
}
|
|
3378
|
+
return `Daily at ${times.join(", ")}`;
|
|
3379
|
+
case "weekly":
|
|
3380
|
+
const days = Object.keys(config.weeklySchedule || {}).map(Number);
|
|
3381
|
+
const dayNames = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
|
|
3382
|
+
const dayList = days.map((d) => dayNames[d]).join(", ");
|
|
3383
|
+
return `Weekly on ${dayList}`;
|
|
3384
|
+
case "custom":
|
|
3385
|
+
return `Custom: ${config.cronExpression || "Not set"}`;
|
|
3386
|
+
default:
|
|
3387
|
+
return "Unknown schedule";
|
|
3388
|
+
}
|
|
3389
|
+
}
|
|
3390
|
+
function getNextRunEstimate(cronExpression) {
|
|
3391
|
+
try {
|
|
3392
|
+
const parts = cronExpression.trim().split(/\s+/);
|
|
3393
|
+
if (parts.length !== 5) {
|
|
3394
|
+
return "Invalid cron";
|
|
3395
|
+
}
|
|
3396
|
+
const [minute, hour, day, month, weekday] = parts;
|
|
3397
|
+
if (hour.startsWith("*/")) {
|
|
3398
|
+
const interval = parseInt(hour.substring(2), 10);
|
|
3399
|
+
return `Every ${interval} hour${interval > 1 ? "s" : ""}`;
|
|
3400
|
+
}
|
|
3401
|
+
if (day === "*" && month === "*" && weekday === "*") {
|
|
3402
|
+
const displayHour = hour.includes(",") ? hour.split(",")[0] : hour;
|
|
3403
|
+
return `Daily at ${displayHour.padStart(2, "0")}:${minute.padStart(2, "0")}`;
|
|
3404
|
+
}
|
|
3405
|
+
if (weekday !== "*") {
|
|
3406
|
+
const dayNames = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
|
|
3407
|
+
const dayNums = weekday.split(",").map(Number);
|
|
3408
|
+
const dayList = dayNums.map((d) => dayNames[d] || d).join(", ");
|
|
3409
|
+
return `${dayList} at ${hour.padStart(2, "0")}:${minute.padStart(2, "0")}`;
|
|
3410
|
+
}
|
|
3411
|
+
return cronExpression;
|
|
3412
|
+
} catch {
|
|
3413
|
+
return cronExpression;
|
|
3414
|
+
}
|
|
3415
|
+
}
|
|
3416
|
+
|
|
3417
|
+
// src/wakeup/cron-installer.ts
|
|
3418
|
+
import { execSync, exec as exec3 } from "child_process";
|
|
3419
|
+
import { promisify as promisify3 } from "util";
|
|
3420
|
+
var execAsync3 = promisify3(exec3);
|
|
3421
|
+
var CRON_COMMENT_MARKER = "antigravity-usage-wakeup";
|
|
3422
|
+
function getBinaryPath() {
|
|
3423
|
+
try {
|
|
3424
|
+
const path = execSync("which antigravity-usage", {
|
|
3425
|
+
encoding: "utf-8",
|
|
3426
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
3427
|
+
}).trim();
|
|
3428
|
+
if (path) {
|
|
3429
|
+
debug("cron-installer", `Found binary at: ${path}`);
|
|
3430
|
+
return path;
|
|
3431
|
+
}
|
|
3432
|
+
} catch {
|
|
3433
|
+
debug("cron-installer", "Could not find antigravity-usage via which");
|
|
3434
|
+
}
|
|
3435
|
+
if (process.argv[1]) {
|
|
3436
|
+
const nodePath = process.execPath;
|
|
3437
|
+
const scriptPath = process.argv[1];
|
|
3438
|
+
debug("cron-installer", `Using node + script: ${nodePath} ${scriptPath}`);
|
|
3439
|
+
return `${nodePath} ${scriptPath}`;
|
|
3440
|
+
}
|
|
3441
|
+
throw new Error("Could not determine binary path for cron job");
|
|
3442
|
+
}
|
|
3443
|
+
async function loadCrontab() {
|
|
3444
|
+
try {
|
|
3445
|
+
const { stdout } = await execAsync3('crontab -l 2>/dev/null || echo ""');
|
|
3446
|
+
const lines = stdout.split("\n").filter((line) => line.trim());
|
|
3447
|
+
debug("cron-installer", `Loaded ${lines.length} crontab entries`);
|
|
3448
|
+
return lines;
|
|
3449
|
+
} catch {
|
|
3450
|
+
debug("cron-installer", "No existing crontab or error loading");
|
|
3451
|
+
return [];
|
|
3452
|
+
}
|
|
3453
|
+
}
|
|
3454
|
+
async function saveCrontab(lines) {
|
|
3455
|
+
const content = lines.join("\n") + "\n";
|
|
3456
|
+
try {
|
|
3457
|
+
const { exec: execCallback } = await import("child_process");
|
|
3458
|
+
await new Promise((resolve, reject) => {
|
|
3459
|
+
const proc = execCallback("crontab -", (err) => {
|
|
3460
|
+
if (err) reject(err);
|
|
3461
|
+
else resolve();
|
|
3462
|
+
});
|
|
3463
|
+
proc.stdin?.write(content);
|
|
3464
|
+
proc.stdin?.end();
|
|
3465
|
+
});
|
|
3466
|
+
debug("cron-installer", "Saved crontab successfully");
|
|
3467
|
+
} catch (err) {
|
|
3468
|
+
debug("cron-installer", "Error saving crontab:", err);
|
|
3469
|
+
throw err;
|
|
3470
|
+
}
|
|
3471
|
+
}
|
|
3472
|
+
function removeWakeupEntries(lines) {
|
|
3473
|
+
return lines.filter((line) => !line.includes(CRON_COMMENT_MARKER));
|
|
3474
|
+
}
|
|
3475
|
+
function isCronSupported() {
|
|
3476
|
+
return process.platform === "darwin" || process.platform === "linux";
|
|
3477
|
+
}
|
|
3478
|
+
async function installCronJob(cronExpression) {
|
|
3479
|
+
if (!isCronSupported()) {
|
|
3480
|
+
return {
|
|
3481
|
+
success: false,
|
|
3482
|
+
error: `Cron is not supported on ${process.platform}. Windows Task Scheduler support coming soon.`,
|
|
3483
|
+
manualInstructions: getWindowsInstructions(cronExpression)
|
|
3484
|
+
};
|
|
3485
|
+
}
|
|
3486
|
+
try {
|
|
3487
|
+
const binaryPath = getBinaryPath();
|
|
3488
|
+
const lines = await loadCrontab();
|
|
3489
|
+
const filteredLines = removeWakeupEntries(lines);
|
|
3490
|
+
const command = `${binaryPath} wakeup trigger --scheduled`;
|
|
3491
|
+
const cronLine = `${cronExpression} ${command} # ${CRON_COMMENT_MARKER}`;
|
|
3492
|
+
filteredLines.push(cronLine);
|
|
3493
|
+
await saveCrontab(filteredLines);
|
|
3494
|
+
debug("cron-installer", `Installed cron job: ${cronLine}`);
|
|
3495
|
+
return {
|
|
3496
|
+
success: true,
|
|
3497
|
+
cronExpression
|
|
3498
|
+
};
|
|
3499
|
+
} catch (err) {
|
|
3500
|
+
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
3501
|
+
debug("cron-installer", `Failed to install cron job: ${errorMessage}`);
|
|
3502
|
+
const binaryPath = tryGetBinaryPath();
|
|
3503
|
+
return {
|
|
3504
|
+
success: false,
|
|
3505
|
+
error: errorMessage,
|
|
3506
|
+
manualInstructions: getManualInstructions(cronExpression, binaryPath)
|
|
3507
|
+
};
|
|
3508
|
+
}
|
|
3509
|
+
}
|
|
3510
|
+
async function uninstallCronJob() {
|
|
3511
|
+
if (!isCronSupported()) {
|
|
3512
|
+
debug("cron-installer", "Cron not supported on this platform");
|
|
3513
|
+
return false;
|
|
3514
|
+
}
|
|
3515
|
+
try {
|
|
3516
|
+
const lines = await loadCrontab();
|
|
3517
|
+
const filteredLines = removeWakeupEntries(lines);
|
|
3518
|
+
if (filteredLines.length === lines.length) {
|
|
3519
|
+
debug("cron-installer", "No cron job found to uninstall");
|
|
3520
|
+
return true;
|
|
3521
|
+
}
|
|
3522
|
+
await saveCrontab(filteredLines);
|
|
3523
|
+
debug("cron-installer", "Uninstalled cron job successfully");
|
|
3524
|
+
return true;
|
|
3525
|
+
} catch (err) {
|
|
3526
|
+
debug("cron-installer", "Failed to uninstall cron job:", err);
|
|
3527
|
+
return false;
|
|
3528
|
+
}
|
|
3529
|
+
}
|
|
3530
|
+
async function getCronStatus() {
|
|
3531
|
+
if (!isCronSupported()) {
|
|
3532
|
+
return { installed: false };
|
|
3533
|
+
}
|
|
3534
|
+
try {
|
|
3535
|
+
const lines = await loadCrontab();
|
|
3536
|
+
const cronLine = lines.find((line) => line.includes(CRON_COMMENT_MARKER));
|
|
3537
|
+
if (!cronLine) {
|
|
3538
|
+
return { installed: false };
|
|
3539
|
+
}
|
|
3540
|
+
const parts = cronLine.trim().split(/\s+/);
|
|
3541
|
+
const cronExpression = parts.slice(0, 5).join(" ");
|
|
3542
|
+
return {
|
|
3543
|
+
installed: true,
|
|
3544
|
+
cronExpression,
|
|
3545
|
+
nextRun: getNextRunDescription(cronExpression)
|
|
3546
|
+
};
|
|
3547
|
+
} catch {
|
|
3548
|
+
return { installed: false };
|
|
3549
|
+
}
|
|
3550
|
+
}
|
|
3551
|
+
function tryGetBinaryPath() {
|
|
3552
|
+
try {
|
|
3553
|
+
return getBinaryPath();
|
|
3554
|
+
} catch {
|
|
3555
|
+
return "antigravity-usage";
|
|
3556
|
+
}
|
|
3557
|
+
}
|
|
3558
|
+
function getManualInstructions(cronExpression, binaryPath) {
|
|
3559
|
+
return `
|
|
3560
|
+
Failed to automatically install cron job. Please add manually:
|
|
3561
|
+
|
|
3562
|
+
1. Open terminal and run: crontab -e
|
|
3563
|
+
|
|
3564
|
+
2. Add this line at the end:
|
|
3565
|
+
${cronExpression} ${binaryPath} wakeup trigger --scheduled # ${CRON_COMMENT_MARKER}
|
|
3566
|
+
|
|
3567
|
+
3. Save and exit the editor
|
|
3568
|
+
|
|
3569
|
+
To verify, run: crontab -l
|
|
3570
|
+
`.trim();
|
|
3571
|
+
}
|
|
3572
|
+
function getWindowsInstructions(cronExpression) {
|
|
3573
|
+
return `
|
|
3574
|
+
Windows Task Scheduler support is not yet available.
|
|
3575
|
+
|
|
3576
|
+
To set up manually using Task Scheduler:
|
|
3577
|
+
|
|
3578
|
+
1. Open Task Scheduler (taskschd.msc)
|
|
3579
|
+
2. Create a new Basic Task
|
|
3580
|
+
3. Set trigger: Based on your schedule (${cronExpression})
|
|
3581
|
+
4. Set action: Start a program
|
|
3582
|
+
- Program: antigravity-usage
|
|
3583
|
+
- Arguments: wakeup trigger --scheduled
|
|
3584
|
+
5. Save the task
|
|
3585
|
+
|
|
3586
|
+
Alternatively, use Windows Subsystem for Linux (WSL) with cron.
|
|
3587
|
+
`.trim();
|
|
3588
|
+
}
|
|
3589
|
+
function getNextRunDescription(cronExpression) {
|
|
3590
|
+
try {
|
|
3591
|
+
const parts = cronExpression.split(/\s+/);
|
|
3592
|
+
if (parts.length !== 5) return "Unknown";
|
|
3593
|
+
const [minute, hour] = parts;
|
|
3594
|
+
if (hour.startsWith("*/")) {
|
|
3595
|
+
const hours = parseInt(hour.substring(2), 10);
|
|
3596
|
+
const now2 = /* @__PURE__ */ new Date();
|
|
3597
|
+
const currentHour = now2.getHours();
|
|
3598
|
+
const nextHour = Math.ceil((currentHour + 1) / hours) * hours;
|
|
3599
|
+
const isToday = nextHour < 24;
|
|
3600
|
+
return isToday ? `Today around ${nextHour}:00` : "Tomorrow";
|
|
3601
|
+
}
|
|
3602
|
+
const hourNum = parseInt(hour.split(",")[0], 10);
|
|
3603
|
+
const minuteNum = parseInt(minute, 10);
|
|
3604
|
+
const now = /* @__PURE__ */ new Date();
|
|
3605
|
+
const currentMinutes = now.getHours() * 60 + now.getMinutes();
|
|
3606
|
+
const targetMinutes = hourNum * 60 + minuteNum;
|
|
3607
|
+
if (targetMinutes > currentMinutes) {
|
|
3608
|
+
return `Today at ${hour.padStart(2, "0")}:${minute.padStart(2, "0")}`;
|
|
3609
|
+
}
|
|
3610
|
+
return `Tomorrow at ${hour.padStart(2, "0")}:${minute.padStart(2, "0")}`;
|
|
3611
|
+
} catch {
|
|
3612
|
+
return "Unknown";
|
|
3613
|
+
}
|
|
3614
|
+
}
|
|
3615
|
+
|
|
3616
|
+
// src/wakeup/trigger-service.ts
|
|
3617
|
+
var DEFAULT_PROMPT = "hi";
|
|
3618
|
+
var REQUEST_TIMEOUT_MS = 3e4;
|
|
3619
|
+
var MAX_CONCURRENT_REQUESTS = 4;
|
|
3620
|
+
async function executeTrigger(options) {
|
|
3621
|
+
const {
|
|
3622
|
+
models,
|
|
3623
|
+
accountEmail,
|
|
3624
|
+
triggerType,
|
|
3625
|
+
triggerSource,
|
|
3626
|
+
customPrompt,
|
|
3627
|
+
maxOutputTokens
|
|
3628
|
+
} = options;
|
|
3629
|
+
debug("trigger-service", `Executing trigger for ${models.length} models with account ${accountEmail}`);
|
|
3630
|
+
if (models.length === 0) {
|
|
3631
|
+
debug("trigger-service", "No models to trigger");
|
|
3632
|
+
return { success: true, results: [] };
|
|
3633
|
+
}
|
|
3634
|
+
let tokenManager;
|
|
3635
|
+
try {
|
|
3636
|
+
tokenManager = getTokenManagerForAccount(accountEmail);
|
|
3637
|
+
} catch (err) {
|
|
3638
|
+
debug("trigger-service", `Failed to get token manager for ${accountEmail}:`, err);
|
|
3639
|
+
const results2 = models.map((modelId) => ({
|
|
3640
|
+
modelId,
|
|
3641
|
+
success: false,
|
|
3642
|
+
durationMs: 0,
|
|
3643
|
+
error: `Failed to get credentials for ${accountEmail}`
|
|
3644
|
+
}));
|
|
3645
|
+
recordResults(results2, options);
|
|
3646
|
+
return { success: false, results: results2 };
|
|
3647
|
+
}
|
|
3648
|
+
try {
|
|
3649
|
+
await tokenManager.getValidAccessToken();
|
|
3650
|
+
} catch (err) {
|
|
3651
|
+
let errorMessage = `Authentication failed for ${accountEmail}`;
|
|
3652
|
+
if (err && typeof err === "object" && "getDetailedMessage" in err) {
|
|
3653
|
+
errorMessage = err.getDetailedMessage();
|
|
3654
|
+
} else if (err instanceof Error) {
|
|
3655
|
+
errorMessage = `Token refresh failed: ${err.message}`;
|
|
3656
|
+
}
|
|
3657
|
+
debug("trigger-service", `Failed to refresh token for ${accountEmail}:`, err);
|
|
3658
|
+
const results2 = models.map((modelId) => ({
|
|
3659
|
+
modelId,
|
|
3660
|
+
success: false,
|
|
3661
|
+
durationMs: 0,
|
|
3662
|
+
error: errorMessage
|
|
3663
|
+
}));
|
|
3664
|
+
recordResults(results2, options);
|
|
3665
|
+
return { success: false, results: results2 };
|
|
3666
|
+
}
|
|
3667
|
+
const client = new CloudCodeClient(tokenManager);
|
|
3668
|
+
debug("trigger-service", `Account ${accountEmail} projectId from tokenManager: ${tokenManager.getProjectId()}`);
|
|
3669
|
+
try {
|
|
3670
|
+
const projectId = await client.resolveProjectId();
|
|
3671
|
+
if (projectId) {
|
|
3672
|
+
debug("trigger-service", `Project ID resolved: ${projectId}`);
|
|
3673
|
+
tokenManager.setProjectId(projectId);
|
|
3674
|
+
} else {
|
|
3675
|
+
debug("trigger-service", "WARNING: Could not resolve project ID");
|
|
3676
|
+
}
|
|
3677
|
+
} catch (err) {
|
|
3678
|
+
debug("trigger-service", "Failed to resolve project ID:", err);
|
|
3679
|
+
}
|
|
3680
|
+
const userPrompt = customPrompt || DEFAULT_PROMPT;
|
|
3681
|
+
const results = [];
|
|
3682
|
+
for (let i = 0; i < models.length; i += MAX_CONCURRENT_REQUESTS) {
|
|
3683
|
+
const batch = models.slice(i, i + MAX_CONCURRENT_REQUESTS);
|
|
3684
|
+
debug("trigger-service", `Processing batch ${i / MAX_CONCURRENT_REQUESTS + 1}: ${batch.join(", ")}`);
|
|
3685
|
+
const batchResults = await Promise.all(
|
|
3686
|
+
batch.map((modelId) => triggerSingleModel(client, modelId, userPrompt, maxOutputTokens))
|
|
3687
|
+
);
|
|
3688
|
+
results.push(...batchResults);
|
|
3689
|
+
}
|
|
3690
|
+
recordResults(results, options);
|
|
3691
|
+
const allSuccess = results.every((r) => r.success);
|
|
3692
|
+
const successCount = results.filter((r) => r.success).length;
|
|
3693
|
+
debug("trigger-service", `Trigger complete: ${successCount}/${results.length} succeeded`);
|
|
3694
|
+
return { success: allSuccess, results };
|
|
3695
|
+
}
|
|
3696
|
+
async function triggerSingleModel(client, modelId, prompt, maxTokens) {
|
|
3697
|
+
const startTime = Date.now();
|
|
3698
|
+
debug("trigger-service", `Triggering model: ${modelId}`);
|
|
3699
|
+
try {
|
|
3700
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
3701
|
+
setTimeout(() => reject(new Error("Request timed out")), REQUEST_TIMEOUT_MS);
|
|
3702
|
+
});
|
|
3703
|
+
const response = await Promise.race([
|
|
3704
|
+
client.generateContent(modelId, prompt, maxTokens),
|
|
3705
|
+
timeoutPromise
|
|
3706
|
+
]);
|
|
3707
|
+
const durationMs = Date.now() - startTime;
|
|
3708
|
+
debug("trigger-service", `Model ${modelId} responded in ${durationMs}ms`);
|
|
3709
|
+
return {
|
|
3710
|
+
modelId,
|
|
3711
|
+
success: true,
|
|
3712
|
+
durationMs,
|
|
3713
|
+
response: response.text.substring(0, 500),
|
|
3714
|
+
// Truncate to 500 chars
|
|
3715
|
+
tokensUsed: response.tokensUsed
|
|
3716
|
+
};
|
|
3717
|
+
} catch (err) {
|
|
3718
|
+
const durationMs = Date.now() - startTime;
|
|
3719
|
+
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
3720
|
+
debug("trigger-service", `Model ${modelId} failed after ${durationMs}ms: ${errorMessage}`);
|
|
3721
|
+
return {
|
|
3722
|
+
modelId,
|
|
3723
|
+
success: false,
|
|
3724
|
+
durationMs,
|
|
3725
|
+
error: errorMessage
|
|
3726
|
+
};
|
|
3727
|
+
}
|
|
3728
|
+
}
|
|
3729
|
+
function recordResults(results, options) {
|
|
3730
|
+
const { triggerType, triggerSource, accountEmail, customPrompt } = options;
|
|
3731
|
+
const prompt = customPrompt || DEFAULT_PROMPT;
|
|
3732
|
+
for (const result of results) {
|
|
3733
|
+
const record = {
|
|
3734
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3735
|
+
success: result.success,
|
|
3736
|
+
triggerType,
|
|
3737
|
+
triggerSource,
|
|
3738
|
+
models: [result.modelId],
|
|
3739
|
+
accountEmail,
|
|
3740
|
+
durationMs: result.durationMs,
|
|
3741
|
+
prompt,
|
|
3742
|
+
response: result.response,
|
|
3743
|
+
error: result.error,
|
|
3744
|
+
tokensUsed: result.tokensUsed
|
|
3745
|
+
};
|
|
3746
|
+
addTriggerRecord(record);
|
|
3747
|
+
}
|
|
3748
|
+
}
|
|
3749
|
+
async function testTrigger(modelId, accountEmail, prompt) {
|
|
3750
|
+
const result = await executeTrigger({
|
|
3751
|
+
models: [modelId],
|
|
3752
|
+
accountEmail,
|
|
3753
|
+
triggerType: "manual",
|
|
3754
|
+
triggerSource: "manual",
|
|
3755
|
+
customPrompt: prompt
|
|
3756
|
+
});
|
|
3757
|
+
return result.results[0] || {
|
|
3758
|
+
modelId,
|
|
3759
|
+
success: false,
|
|
3760
|
+
durationMs: 0,
|
|
3761
|
+
error: "No result returned"
|
|
3762
|
+
};
|
|
3763
|
+
}
|
|
3764
|
+
|
|
3765
|
+
// src/wakeup/reset-detector.ts
|
|
3766
|
+
var RESET_TIME_MIN_HOURS = 4.5;
|
|
3767
|
+
var RESET_TIME_MAX_HOURS = 5.5;
|
|
3768
|
+
var RESET_TIME_MIN_MS = RESET_TIME_MIN_HOURS * 60 * 60 * 1e3;
|
|
3769
|
+
var RESET_TIME_MAX_MS = RESET_TIME_MAX_HOURS * 60 * 60 * 1e3;
|
|
3770
|
+
var DEFAULT_COOLDOWN_MS = 60 * 60 * 1e3;
|
|
3771
|
+
|
|
3772
|
+
// src/commands/wakeup.ts
|
|
3773
|
+
async function wakeupCommand(subcommand, args, options) {
|
|
3774
|
+
debug("wakeup", `Subcommand: ${subcommand}, options:`, options);
|
|
3775
|
+
switch (subcommand) {
|
|
3776
|
+
case "config":
|
|
3777
|
+
await configureWakeup();
|
|
3778
|
+
break;
|
|
3779
|
+
case "trigger":
|
|
3780
|
+
await runScheduledTrigger(options.scheduled ?? false);
|
|
3781
|
+
break;
|
|
3782
|
+
case "install":
|
|
3783
|
+
await installSchedule();
|
|
3784
|
+
break;
|
|
3785
|
+
case "uninstall":
|
|
3786
|
+
await uninstallSchedule();
|
|
3787
|
+
break;
|
|
3788
|
+
case "test":
|
|
3789
|
+
await runTestTrigger();
|
|
3790
|
+
break;
|
|
3791
|
+
case "history":
|
|
3792
|
+
await showHistory(options);
|
|
3793
|
+
break;
|
|
3794
|
+
case "status":
|
|
3795
|
+
default:
|
|
3796
|
+
await showStatus();
|
|
3797
|
+
break;
|
|
3798
|
+
}
|
|
3799
|
+
}
|
|
3800
|
+
async function configureWakeup() {
|
|
3801
|
+
console.log("\n\u{1F527} Auto Wake-up Configuration\n");
|
|
3802
|
+
const config = getOrCreateConfig();
|
|
3803
|
+
const accountManager = getAccountManager();
|
|
3804
|
+
const accounts = accountManager.getAccountEmails();
|
|
3805
|
+
if (accounts.length === 0) {
|
|
3806
|
+
console.log("\u274C No accounts available. Please login first:");
|
|
3807
|
+
console.log(" antigravity-usage login\n");
|
|
3808
|
+
return;
|
|
3809
|
+
}
|
|
3810
|
+
const { enabled } = await inquirer.prompt([{
|
|
3811
|
+
type: "confirm",
|
|
3812
|
+
name: "enabled",
|
|
3813
|
+
message: "Enable auto wake-up?",
|
|
3814
|
+
default: config.enabled
|
|
3815
|
+
}]);
|
|
3816
|
+
if (!enabled) {
|
|
3817
|
+
config.enabled = false;
|
|
3818
|
+
saveWakeupConfig(config);
|
|
3819
|
+
console.log("\n\u2705 Auto wake-up disabled");
|
|
3820
|
+
return;
|
|
3821
|
+
}
|
|
3822
|
+
const { triggerMode } = await inquirer.prompt([{
|
|
3823
|
+
type: "list",
|
|
3824
|
+
name: "triggerMode",
|
|
3825
|
+
message: "Trigger mode:",
|
|
3826
|
+
choices: [
|
|
3827
|
+
{ name: "Schedule-based (run at specific times)", value: "schedule" },
|
|
3828
|
+
{ name: "Quota-reset-based (trigger when quota resets)", value: "reset" }
|
|
3829
|
+
],
|
|
3830
|
+
default: config.wakeOnReset ? "reset" : "schedule"
|
|
3831
|
+
}]);
|
|
3832
|
+
config.wakeOnReset = triggerMode === "reset";
|
|
3833
|
+
if (!config.wakeOnReset) {
|
|
3834
|
+
const { scheduleMode } = await inquirer.prompt([{
|
|
3835
|
+
type: "list",
|
|
3836
|
+
name: "scheduleMode",
|
|
3837
|
+
message: "Schedule type:",
|
|
3838
|
+
choices: [
|
|
3839
|
+
{ name: "Every N hours", value: "interval" },
|
|
3840
|
+
{ name: "Daily at specific times", value: "daily" },
|
|
3841
|
+
{ name: "Custom cron expression", value: "custom" }
|
|
3842
|
+
],
|
|
3843
|
+
default: config.scheduleMode
|
|
3844
|
+
}]);
|
|
3845
|
+
config.scheduleMode = scheduleMode;
|
|
3846
|
+
if (scheduleMode === "interval") {
|
|
3847
|
+
const { intervalHours } = await inquirer.prompt([{
|
|
3848
|
+
type: "number",
|
|
3849
|
+
name: "intervalHours",
|
|
3850
|
+
message: "Trigger every N hours:",
|
|
3851
|
+
default: config.intervalHours || 6,
|
|
3852
|
+
validate: (val) => val >= 1 && val <= 23 ? true : "Must be 1-23"
|
|
3853
|
+
}]);
|
|
3854
|
+
config.intervalHours = intervalHours;
|
|
3855
|
+
} else if (scheduleMode === "daily") {
|
|
3856
|
+
const { dailyTime } = await inquirer.prompt([{
|
|
3857
|
+
type: "input",
|
|
3858
|
+
name: "dailyTime",
|
|
3859
|
+
message: "Time to trigger (HH:MM):",
|
|
3860
|
+
default: config.dailyTimes?.[0] || "09:00",
|
|
3861
|
+
validate: (val) => /^\d{1,2}:\d{2}$/.test(val) ? true : "Use HH:MM format"
|
|
3862
|
+
}]);
|
|
3863
|
+
config.dailyTimes = [dailyTime];
|
|
3864
|
+
} else if (scheduleMode === "custom") {
|
|
3865
|
+
const { cronExpression } = await inquirer.prompt([{
|
|
3866
|
+
type: "input",
|
|
3867
|
+
name: "cronExpression",
|
|
3868
|
+
message: "Cron expression (min hour day month weekday):",
|
|
3869
|
+
default: config.cronExpression || "0 */6 * * *"
|
|
3870
|
+
}]);
|
|
3871
|
+
config.cronExpression = cronExpression;
|
|
3872
|
+
}
|
|
3873
|
+
} else {
|
|
3874
|
+
const { resetCooldown } = await inquirer.prompt([{
|
|
3875
|
+
type: "number",
|
|
3876
|
+
name: "resetCooldown",
|
|
3877
|
+
message: "Cooldown between triggers (minutes):",
|
|
3878
|
+
default: config.resetCooldownMinutes || 10,
|
|
3879
|
+
validate: (val) => val >= 1 ? true : "Must be at least 1 minute"
|
|
3880
|
+
}]);
|
|
3881
|
+
config.resetCooldownMinutes = resetCooldown;
|
|
3882
|
+
}
|
|
3883
|
+
config.selectedModels = ["claude-sonnet-4-5", "gemini-3-flash"];
|
|
3884
|
+
console.log("\n \u{1F4E6} Models: claude-sonnet-4-5, gemini-3-flash");
|
|
3885
|
+
console.log(" (Triggers both Claude and Gemini families)");
|
|
3886
|
+
if (accounts.length > 1) {
|
|
3887
|
+
const { selectedAccounts } = await inquirer.prompt([{
|
|
3888
|
+
type: "checkbox",
|
|
3889
|
+
name: "selectedAccounts",
|
|
3890
|
+
message: "Select accounts to use:",
|
|
3891
|
+
choices: accounts.map((email) => ({
|
|
3892
|
+
name: email,
|
|
3893
|
+
value: email,
|
|
3894
|
+
checked: !config.selectedAccounts || config.selectedAccounts.includes(email)
|
|
3895
|
+
}))
|
|
3896
|
+
}]);
|
|
3897
|
+
config.selectedAccounts = selectedAccounts.length > 0 ? selectedAccounts : void 0;
|
|
3898
|
+
} else {
|
|
3899
|
+
config.selectedAccounts = void 0;
|
|
3900
|
+
}
|
|
3901
|
+
const { customPrompt } = await inquirer.prompt([{
|
|
3902
|
+
type: "input",
|
|
3903
|
+
name: "customPrompt",
|
|
3904
|
+
message: 'Custom wake-up prompt (leave empty for default "hi"):',
|
|
3905
|
+
default: config.customPrompt || ""
|
|
3906
|
+
}]);
|
|
3907
|
+
config.customPrompt = customPrompt || void 0;
|
|
3908
|
+
const { maxTokens } = await inquirer.prompt([{
|
|
3909
|
+
type: "number",
|
|
3910
|
+
name: "maxTokens",
|
|
3911
|
+
message: "Max output tokens (0 = no limit):",
|
|
3912
|
+
default: config.maxOutputTokens || 0
|
|
3913
|
+
}]);
|
|
3914
|
+
config.maxOutputTokens = maxTokens;
|
|
3915
|
+
config.enabled = true;
|
|
3916
|
+
saveWakeupConfig(config);
|
|
3917
|
+
console.log("\n\u2705 Configuration saved!");
|
|
3918
|
+
console.log(` Mode: ${getScheduleDescription(config)}`);
|
|
3919
|
+
console.log(` Models: ${config.selectedModels.join(", ")}`);
|
|
3920
|
+
console.log(` Accounts: ${config.selectedAccounts?.join(", ") || "Active account"}`);
|
|
3921
|
+
if (!config.wakeOnReset && isCronSupported()) {
|
|
3922
|
+
const { installNow } = await inquirer.prompt([{
|
|
3923
|
+
type: "confirm",
|
|
3924
|
+
name: "installNow",
|
|
3925
|
+
message: "Install to system cron now?",
|
|
3926
|
+
default: true
|
|
3927
|
+
}]);
|
|
3928
|
+
if (installNow) {
|
|
3929
|
+
await installSchedule();
|
|
3930
|
+
} else {
|
|
3931
|
+
console.log("\n\u{1F4CB} To install later, run:");
|
|
3932
|
+
console.log(" antigravity-usage wakeup install");
|
|
3933
|
+
}
|
|
3934
|
+
}
|
|
3935
|
+
console.log("");
|
|
3936
|
+
}
|
|
3937
|
+
async function runScheduledTrigger(isScheduled) {
|
|
3938
|
+
debug("wakeup", `Running trigger (scheduled: ${isScheduled})`);
|
|
3939
|
+
const config = loadWakeupConfig();
|
|
3940
|
+
if (!config || !config.enabled) {
|
|
3941
|
+
debug("wakeup", "Wakeup not configured or disabled");
|
|
3942
|
+
return;
|
|
3943
|
+
}
|
|
3944
|
+
const accounts = resolveAccounts(config.selectedAccounts);
|
|
3945
|
+
if (accounts.length === 0) {
|
|
3946
|
+
debug("wakeup", "No valid accounts");
|
|
3947
|
+
return;
|
|
3948
|
+
}
|
|
3949
|
+
if (config.selectedModels.length === 0) {
|
|
3950
|
+
debug("wakeup", "No models selected");
|
|
3951
|
+
return;
|
|
3952
|
+
}
|
|
3953
|
+
for (const accountEmail of accounts) {
|
|
3954
|
+
const result = await executeTrigger({
|
|
3955
|
+
models: config.selectedModels,
|
|
3956
|
+
accountEmail,
|
|
3957
|
+
triggerType: "auto",
|
|
3958
|
+
triggerSource: isScheduled ? "scheduled" : "manual",
|
|
3959
|
+
customPrompt: config.customPrompt,
|
|
3960
|
+
maxOutputTokens: config.maxOutputTokens
|
|
3961
|
+
});
|
|
3962
|
+
const successCount = result.results.filter((r) => r.success).length;
|
|
3963
|
+
console.log(`[${(/* @__PURE__ */ new Date()).toISOString()}] ${accountEmail}: ${successCount}/${result.results.length} models triggered`);
|
|
3964
|
+
}
|
|
3965
|
+
}
|
|
3966
|
+
async function installSchedule() {
|
|
3967
|
+
console.log("\n\u{1F4C5} Installing wake-up schedule to cron...\n");
|
|
3968
|
+
if (!isCronSupported()) {
|
|
3969
|
+
console.log("\u274C Cron is not supported on this platform.");
|
|
3970
|
+
console.log(" Windows Task Scheduler support coming soon.");
|
|
3971
|
+
return;
|
|
3972
|
+
}
|
|
3973
|
+
const config = loadWakeupConfig();
|
|
3974
|
+
if (!config) {
|
|
3975
|
+
console.log("\u274C No wake-up configuration found.");
|
|
3976
|
+
console.log(" Run: antigravity-usage wakeup config");
|
|
3977
|
+
return;
|
|
3978
|
+
}
|
|
3979
|
+
if (!config.enabled) {
|
|
3980
|
+
console.log("\u274C Wake-up is disabled. Enable it first:");
|
|
3981
|
+
console.log(" antigravity-usage wakeup config");
|
|
3982
|
+
return;
|
|
3983
|
+
}
|
|
3984
|
+
if (config.wakeOnReset) {
|
|
3985
|
+
console.log("\u2139\uFE0F Quota-reset mode does not require cron installation.");
|
|
3986
|
+
console.log(" Triggers happen automatically when you check quota.");
|
|
3987
|
+
return;
|
|
3988
|
+
}
|
|
3989
|
+
try {
|
|
3990
|
+
const cronExpression = configToCronExpression(config);
|
|
3991
|
+
console.log(` Schedule: ${getScheduleDescription(config)}`);
|
|
3992
|
+
console.log(` Cron: ${cronExpression}`);
|
|
3993
|
+
console.log("");
|
|
3994
|
+
const result = await installCronJob(cronExpression);
|
|
3995
|
+
if (result.success) {
|
|
3996
|
+
console.log("\u2705 Cron job installed successfully!");
|
|
3997
|
+
console.log(` Next run: ${getNextRunEstimate(cronExpression)}`);
|
|
3998
|
+
console.log("");
|
|
3999
|
+
console.log(" To check status: antigravity-usage wakeup status");
|
|
4000
|
+
console.log(" To uninstall: antigravity-usage wakeup uninstall");
|
|
4001
|
+
} else {
|
|
4002
|
+
console.log("\u26A0\uFE0F Automatic installation failed.");
|
|
4003
|
+
if (result.manualInstructions) {
|
|
4004
|
+
console.log("");
|
|
4005
|
+
console.log(result.manualInstructions);
|
|
4006
|
+
}
|
|
4007
|
+
}
|
|
4008
|
+
} catch (err) {
|
|
4009
|
+
console.log(`\u274C Error: ${err instanceof Error ? err.message : err}`);
|
|
4010
|
+
}
|
|
4011
|
+
console.log("");
|
|
4012
|
+
}
|
|
4013
|
+
async function uninstallSchedule() {
|
|
4014
|
+
console.log("\n\u{1F5D1}\uFE0F Removing wake-up schedule from cron...\n");
|
|
4015
|
+
const success2 = await uninstallCronJob();
|
|
4016
|
+
if (success2) {
|
|
4017
|
+
console.log("\u2705 Cron job removed successfully!");
|
|
4018
|
+
} else {
|
|
4019
|
+
console.log("\u26A0\uFE0F Could not remove cron job. It may not be installed.");
|
|
4020
|
+
console.log(" Check your crontab: crontab -l");
|
|
4021
|
+
}
|
|
4022
|
+
console.log("");
|
|
4023
|
+
}
|
|
4024
|
+
async function runTestTrigger() {
|
|
4025
|
+
console.log("\n\u{1F9EA} Test Trigger\n");
|
|
4026
|
+
const accountManager = getAccountManager();
|
|
4027
|
+
const accounts = accountManager.getAccountEmails();
|
|
4028
|
+
if (accounts.length === 0) {
|
|
4029
|
+
console.log("\u274C No accounts available. Please login first.");
|
|
4030
|
+
return;
|
|
4031
|
+
}
|
|
4032
|
+
let accountEmail = accounts[0];
|
|
4033
|
+
if (accounts.length > 1) {
|
|
4034
|
+
const { selectedAccount } = await inquirer.prompt([{
|
|
4035
|
+
type: "list",
|
|
4036
|
+
name: "selectedAccount",
|
|
4037
|
+
message: "Select account:",
|
|
4038
|
+
choices: accounts
|
|
4039
|
+
}]);
|
|
4040
|
+
accountEmail = selectedAccount;
|
|
4041
|
+
}
|
|
4042
|
+
const config = loadWakeupConfig();
|
|
4043
|
+
const { modelId } = await inquirer.prompt([{
|
|
4044
|
+
type: "input",
|
|
4045
|
+
name: "modelId",
|
|
4046
|
+
message: "Model ID to test:",
|
|
4047
|
+
default: config?.selectedModels[0] || "claude-sonnet-4-5"
|
|
4048
|
+
}]);
|
|
4049
|
+
const { prompt } = await inquirer.prompt([{
|
|
4050
|
+
type: "input",
|
|
4051
|
+
name: "prompt",
|
|
4052
|
+
message: "Test prompt:",
|
|
4053
|
+
default: "hi"
|
|
4054
|
+
}]);
|
|
4055
|
+
console.log("\n\u23F3 Triggering...");
|
|
4056
|
+
try {
|
|
4057
|
+
const result = await testTrigger(modelId, accountEmail, prompt);
|
|
4058
|
+
if (result.success) {
|
|
4059
|
+
console.log(`
|
|
4060
|
+
\u2705 Success! (${result.durationMs}ms)`);
|
|
4061
|
+
if (result.response) {
|
|
4062
|
+
console.log(`
|
|
4063
|
+
\u{1F4DD} Response:
|
|
4064
|
+
${result.response.substring(0, 200)}...`);
|
|
4065
|
+
}
|
|
4066
|
+
if (result.tokensUsed) {
|
|
4067
|
+
console.log(`
|
|
4068
|
+
\u{1F4CA} Tokens: ${result.tokensUsed.total} (prompt: ${result.tokensUsed.prompt}, completion: ${result.tokensUsed.completion})`);
|
|
4069
|
+
}
|
|
4070
|
+
} else {
|
|
4071
|
+
console.log(`
|
|
4072
|
+
\u274C Failed: ${result.error}`);
|
|
4073
|
+
}
|
|
4074
|
+
} catch (err) {
|
|
4075
|
+
console.log(`
|
|
4076
|
+
\u274C Error: ${err instanceof Error ? err.message : err}`);
|
|
4077
|
+
}
|
|
4078
|
+
console.log("");
|
|
4079
|
+
process.exit(0);
|
|
4080
|
+
}
|
|
4081
|
+
async function showHistory(options) {
|
|
4082
|
+
const limit = parseInt(options.limit || "10", 10);
|
|
4083
|
+
const history = getRecentHistory(limit);
|
|
4084
|
+
if (history.length === 0) {
|
|
4085
|
+
console.log("\n\u{1F4DC} No trigger history yet.\n");
|
|
4086
|
+
return;
|
|
4087
|
+
}
|
|
4088
|
+
if (options.json) {
|
|
4089
|
+
console.log(JSON.stringify(history, null, 2));
|
|
4090
|
+
return;
|
|
4091
|
+
}
|
|
4092
|
+
console.log(`
|
|
4093
|
+
\u{1F4DC} Trigger History (last ${Math.min(limit, history.length)} records)
|
|
4094
|
+
`);
|
|
4095
|
+
const table = new Table4({
|
|
4096
|
+
head: ["Time", "Source", "Model", "Account", "Duration", "Status"],
|
|
4097
|
+
style: { head: ["cyan"] }
|
|
4098
|
+
});
|
|
4099
|
+
for (const record of history) {
|
|
4100
|
+
const time = new Date(record.timestamp).toLocaleString();
|
|
4101
|
+
const status = record.success ? "\u2705" : `\u274C ${record.error?.substring(0, 20) || ""}`;
|
|
4102
|
+
table.push([
|
|
4103
|
+
time,
|
|
4104
|
+
record.triggerSource,
|
|
4105
|
+
record.models[0] || "-",
|
|
4106
|
+
record.accountEmail.split("@")[0],
|
|
4107
|
+
`${record.durationMs}ms`,
|
|
4108
|
+
status
|
|
4109
|
+
]);
|
|
4110
|
+
}
|
|
4111
|
+
console.log(table.toString());
|
|
4112
|
+
console.log("");
|
|
4113
|
+
}
|
|
4114
|
+
async function showStatus() {
|
|
4115
|
+
console.log("\n\u{1F4CA} Auto Wake-up Status\n");
|
|
4116
|
+
const config = loadWakeupConfig();
|
|
4117
|
+
if (!config) {
|
|
4118
|
+
console.log(" Status: Not configured");
|
|
4119
|
+
console.log("");
|
|
4120
|
+
console.log(" To configure: antigravity-usage wakeup config");
|
|
4121
|
+
console.log("");
|
|
4122
|
+
return;
|
|
4123
|
+
}
|
|
4124
|
+
console.log(` Enabled: ${config.enabled ? "\u2705 Yes" : "\u274C No"}`);
|
|
4125
|
+
console.log(` Mode: ${getScheduleDescription(config)}`);
|
|
4126
|
+
if (config.selectedModels.length > 0) {
|
|
4127
|
+
console.log(` Models: ${config.selectedModels.join(", ")}`);
|
|
4128
|
+
} else {
|
|
4129
|
+
console.log(" Models: None selected");
|
|
4130
|
+
}
|
|
4131
|
+
console.log(` Accounts: ${getAccountResolutionStatus(config.selectedAccounts)}`);
|
|
4132
|
+
if (!config.wakeOnReset && config.enabled) {
|
|
4133
|
+
const cronStatus = await getCronStatus();
|
|
4134
|
+
if (cronStatus.installed) {
|
|
4135
|
+
console.log(` Cron: \u2705 Installed (${cronStatus.cronExpression})`);
|
|
4136
|
+
if (cronStatus.nextRun) {
|
|
4137
|
+
console.log(` Next run: ${cronStatus.nextRun}`);
|
|
4138
|
+
}
|
|
4139
|
+
} else {
|
|
4140
|
+
console.log(" Cron: \u274C Not installed");
|
|
4141
|
+
console.log(" Run: antigravity-usage wakeup install");
|
|
4142
|
+
}
|
|
4143
|
+
}
|
|
4144
|
+
const lastTrigger = getLastTrigger();
|
|
4145
|
+
if (lastTrigger) {
|
|
4146
|
+
const ago = getTimeAgo(new Date(lastTrigger.timestamp));
|
|
4147
|
+
const status = lastTrigger.success ? "\u2705 success" : `\u274C ${lastTrigger.error?.substring(0, 30) || "failed"}`;
|
|
4148
|
+
console.log(` Last trigger: ${ago} (${status})`);
|
|
4149
|
+
} else {
|
|
4150
|
+
console.log(" Last trigger: Never");
|
|
4151
|
+
}
|
|
4152
|
+
console.log("");
|
|
4153
|
+
}
|
|
4154
|
+
function getTimeAgo(date) {
|
|
4155
|
+
const seconds = Math.floor((Date.now() - date.getTime()) / 1e3);
|
|
4156
|
+
if (seconds < 60) return "Just now";
|
|
4157
|
+
if (seconds < 3600) return `${Math.floor(seconds / 60)} minutes ago`;
|
|
4158
|
+
if (seconds < 86400) return `${Math.floor(seconds / 3600)} hours ago`;
|
|
4159
|
+
return `${Math.floor(seconds / 86400)} days ago`;
|
|
4160
|
+
}
|
|
4161
|
+
|
|
2710
4162
|
// src/index.ts
|
|
2711
4163
|
var program = new Command();
|
|
2712
4164
|
program.name("antigravity-usage").description("CLI tool to check Antigravity model quota via Google Cloud Code API").version(version).option("--debug", "Enable debug mode").hook("preAction", (thisCommand) => {
|
|
@@ -2728,5 +4180,14 @@ accountsCmd.command("current").description("Show current active account").action
|
|
|
2728
4180
|
accountsCmd.command("refresh [email]").description("Refresh account tokens").option("--all", "Refresh all accounts").action((email, options) => accountsCommand("refresh", email ? [email] : [], options));
|
|
2729
4181
|
accountsCmd.action(() => accountsCommand("list", [], {}));
|
|
2730
4182
|
program.command("doctor").description("Run diagnostics and show configuration").action(doctorCommand);
|
|
4183
|
+
var wakeupCmd = program.command("wakeup").description("Auto wake-up and warm up AI models");
|
|
4184
|
+
wakeupCmd.command("config").description("Configure auto wake-up schedule").action(() => wakeupCommand("config", [], {}));
|
|
4185
|
+
wakeupCmd.command("trigger").description("Execute one trigger cycle (called by cron)").option("--scheduled", "Mark as scheduled trigger").action((options) => wakeupCommand("trigger", [], options));
|
|
4186
|
+
wakeupCmd.command("install").description("Install wake-up schedule to system cron").action(() => wakeupCommand("install", [], {}));
|
|
4187
|
+
wakeupCmd.command("uninstall").description("Remove wake-up schedule from system cron").action(() => wakeupCommand("uninstall", [], {}));
|
|
4188
|
+
wakeupCmd.command("test").description("Test trigger manually").action(() => wakeupCommand("test", [], {}));
|
|
4189
|
+
wakeupCmd.command("history").description("View trigger history").option("--limit <n>", "Number of records to show", "10").option("--json", "Output as JSON").action((options) => wakeupCommand("history", [], options));
|
|
4190
|
+
wakeupCmd.command("status").description("Show wake-up status and configuration").action(() => wakeupCommand("status", [], {}));
|
|
4191
|
+
wakeupCmd.action(() => wakeupCommand("status", [], {}));
|
|
2731
4192
|
program.parse();
|
|
2732
4193
|
//# sourceMappingURL=index.js.map
|