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/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
- async function fetchProjectId(accessToken) {
589
- debug("oauth", "Fetching project ID from Cloud Code API");
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("https://cloudcode-pa.googleapis.com/v1internal:loadCodeAssist", {
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": "antigravity/1.11.3 Darwin/arm64"
687
+ "User-Agent": CLOUDCODE_CONFIG.userAgent
597
688
  },
598
- body: JSON.stringify({ metadata: { ideType: "ANTIGRAVITY" } })
689
+ body: JSON.stringify({ metadata: CLOUDCODE_CONFIG.metadata })
599
690
  });
600
- if (response.ok) {
601
- const data = await response.json();
602
- debug("oauth", `Got project ID: ${data.cloudaicompanionProject}`);
603
- return data.cloudaicompanionProject;
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
- debug("oauth", `Failed to get project ID: ${response.status}`);
708
+ const onboardedProjectId = await tryOnboardUser(accessToken, onboardTier);
709
+ return {
710
+ projectId: onboardedProjectId,
711
+ tierId: onboardTier
712
+ };
606
713
  } catch (err) {
607
- debug("oauth", "Error fetching project ID", err);
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
- projectId = await fetchProjectId(tokenResponse.access_token);
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 fetch project ID during login (will fetch on demand)", err);
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
- constructor(message = "Failed to refresh token. Please login again.") {
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.tokens = loadTokens();
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
- try {
962
- debug("token-manager", "Refreshing token...");
963
- const response = await refreshAccessToken(this.tokens.refreshToken);
964
- this.tokens = {
965
- accessToken: response.access_token,
966
- refreshToken: response.refresh_token || this.tokens.refreshToken,
967
- expiresAt: Date.now() + response.expires_in * 1e3,
968
- email: this.tokens.email,
969
- projectId: this.tokens.projectId
970
- };
971
- if (this.accountEmail) {
972
- saveAccountTokens(this.accountEmail, this.tokens);
973
- updateLastUsed(this.accountEmail);
974
- } else {
975
- saveTokens(this.tokens);
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 BASE_URL = "https://cloudcode-pa.googleapis.com";
1212
- var USER_AGENT = "antigravity/1.11.3 Darwin/arm64";
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: { ideType: "ANTIGRAVITY" }
1468
+ metadata: METADATA
1274
1469
  });
1275
1470
  if (response.cloudaicompanionProject) {
1276
- this.projectId = response.cloudaicompanionProject;
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