antigravity-usage 0.2.0 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,5 +1,5 @@
1
1
  <div align="center">
2
- <img src="images/icon.png" alt="antigravity-usage logo" width="150" height="150">
2
+ <img src="https://raw.githubusercontent.com/skainguyen1412/antigravity-usage/main/images/icon.png" alt="antigravity-usage logo" width="150" height="150">
3
3
  <h1>antigravity-usage</h1>
4
4
  </div>
5
5
 
@@ -20,7 +20,7 @@ A fast, lightweight, and powerful CLI tool to track your Antigravity model quota
20
20
  </p>
21
21
 
22
22
  <div align="center">
23
- <img src="images/banner.png" alt="Antigravity Usage Screenshot">
23
+ <img src="https://raw.githubusercontent.com/skainguyen1412/antigravity-usage/main/images/banner.png" alt="Antigravity Usage Screenshot">
24
24
  </div>
25
25
 
26
26
 
@@ -96,6 +96,16 @@ To keep the CLI snappy and avoid hitting API rate limits:
96
96
  antigravity-usage quota --refresh
97
97
  ```
98
98
 
99
+ ### 🤖 Auto Wakeup (macOS & Linux)
100
+ Never waste quota again. Automatically wake up your AI models to maximize your daily limits.
101
+ - **Native Cron Integration**: Schedule-based triggers (every N hours, daily, or custom cron)
102
+ - **Smart Quota-Reset Detection**: Zero-waste mode that detects when quota resets
103
+ - **Multi-Account Support**: Trigger all your accounts simultaneously
104
+ - **Built-in Safety**: Cooldown protection, retry logic, detailed history tracking
105
+ - **Platform Support**: Currently available on **macOS and Linux** (Windows support coming soon)
106
+
107
+ See the [Wakeup Command](#antigravity-usage-wakeup-) section for full details.
108
+
99
109
  ### 📱 Responsive UI
100
110
  Tables automatically adapt to your terminal size, switching between "Compact" and "Spacious" views to show you the most relevant data without wrapping.
101
111
 
@@ -107,11 +117,19 @@ Tables automatically adapt to your terminal size, switching between "Compact" an
107
117
  Alias for `quota`. Fetches and displays usage data.
108
118
 
109
119
  ```bash
110
- antigravity-usage # Auto-detect (Local -> Cloud)
111
- antigravity-usage --all # Fetch ALL accounts
112
- antigravity-usage --method local # Force local IDE connection
120
+ antigravity-usage # Auto-detect (Local -> Cloud)
121
+ antigravity-usage --all # Fetch ALL accounts
122
+ antigravity-usage --method local # Force local IDE connection
113
123
  antigravity-usage --method google # Force google IDE connection
114
- antigravity-usage --json # Output JSON for scripts
124
+ antigravity-usage --json # Output JSON for scripts
125
+ antigravity-usage --version # Show version number
126
+ ```
127
+
128
+ ### `antigravity-usage --version`
129
+ Display the current version of the CLI tool.
130
+
131
+ ```bash
132
+ antigravity-usage --version # or -V
115
133
  ```
116
134
 
117
135
  ### `antigravity-usage accounts`
@@ -133,6 +151,8 @@ Quickly check if your auth tokens are valid or expired.
133
151
  ### `antigravity-usage wakeup` 🚀
134
152
  **Never waste quota again.** Automatically wake up your AI models at strategic times to maximize your daily limits.
135
153
 
154
+ > **Platform Support:** Currently available on **macOS** and **Linux**. Windows support (via Task Scheduler) is coming soon.
155
+
136
156
  ```bash
137
157
  antigravity-usage wakeup config # Interactive setup (takes 30 seconds)
138
158
  antigravity-usage wakeup install # Install to native system cron
@@ -158,12 +178,14 @@ Runs locally on your machine with zero dependencies:
158
178
  - **Interval Mode**: Every N hours (e.g., every 6 hours)
159
179
  - **Daily Mode**: At specific times (e.g., 9 AM, 5 PM)
160
180
  - **Custom Mode**: Advanced cron expressions for power users
181
+ - **Portable Design**: Auto-detects Node.js path for seamless operation across different machines
161
182
 
162
183
  ```bash
163
184
  antigravity-usage wakeup install
164
- # ✅ Installs to your system's native crontab
185
+ # ✅ Installs to your system's native crontab (macOS/Linux)
165
186
  # ✅ Runs even when terminal/antigravity is closed
166
187
  # ✅ Persists across reboots
188
+ # ✅ Works on any machine with Node.js installed
167
189
  ```
168
190
 
169
191
  **2. Smart Quota-Reset Detection** (Zero-Waste Mode)
package/dist/index.js CHANGED
@@ -4,7 +4,14 @@
4
4
  import { Command } from "commander";
5
5
 
6
6
  // src/version.ts
7
- var version = "0.1.0";
7
+ import { readFileSync } from "fs";
8
+ import { join, dirname } from "path";
9
+ import { fileURLToPath } from "url";
10
+ var __filename = fileURLToPath(import.meta.url);
11
+ var __dirname = dirname(__filename);
12
+ var packageJsonPath = join(__dirname, "../package.json");
13
+ var packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
14
+ var version = packageJson.version;
8
15
 
9
16
  // src/core/logger.ts
10
17
  var debugMode = false;
@@ -52,12 +59,12 @@ var DEFAULT_CONFIG = {
52
59
  };
53
60
 
54
61
  // src/accounts/storage.ts
55
- import { existsSync, mkdirSync, readFileSync, writeFileSync, readdirSync, rmSync } from "fs";
56
- import { join as join2 } from "path";
62
+ import { existsSync, mkdirSync, readFileSync as readFileSync2, writeFileSync, readdirSync, rmSync } from "fs";
63
+ import { join as join3 } from "path";
57
64
 
58
65
  // src/core/env.ts
59
66
  import { homedir, platform } from "os";
60
- import { join } from "path";
67
+ import { join as join2 } from "path";
61
68
  function getPlatform() {
62
69
  const p = platform();
63
70
  if (p === "win32") return "windows";
@@ -69,26 +76,26 @@ function getConfigDir() {
69
76
  const home = homedir();
70
77
  switch (p) {
71
78
  case "windows":
72
- return join(process.env.APPDATA || join(home, "AppData", "Roaming"), "antigravity-usage");
79
+ return join2(process.env.APPDATA || join2(home, "AppData", "Roaming"), "antigravity-usage");
73
80
  case "macos":
74
- return join(home, "Library", "Application Support", "antigravity-usage");
81
+ return join2(home, "Library", "Application Support", "antigravity-usage");
75
82
  case "linux":
76
83
  default:
77
- return join(process.env.XDG_CONFIG_HOME || join(home, ".config"), "antigravity-usage");
84
+ return join2(process.env.XDG_CONFIG_HOME || join2(home, ".config"), "antigravity-usage");
78
85
  }
79
86
  }
80
87
  function getTokensPath() {
81
- return join(getConfigDir(), "tokens.json");
88
+ return join2(getConfigDir(), "tokens.json");
82
89
  }
83
90
  function getAccountsDir() {
84
- return join(getConfigDir(), "accounts");
91
+ return join2(getConfigDir(), "accounts");
85
92
  }
86
93
  function getAccountDir(email) {
87
94
  const safeName = email.replace(/[^a-zA-Z0-9@._-]/g, "_");
88
- return join(getAccountsDir(), safeName);
95
+ return join2(getAccountsDir(), safeName);
89
96
  }
90
97
  function getGlobalConfigPath() {
91
- return join(getConfigDir(), "config.json");
98
+ return join2(getConfigDir(), "config.json");
92
99
  }
93
100
 
94
101
  // src/accounts/storage.ts
@@ -109,7 +116,7 @@ function ensureAccountDir(email) {
109
116
  }
110
117
  function accountExists(email) {
111
118
  const dir = getAccountDir(email);
112
- return existsSync(dir) && existsSync(join2(dir, "tokens.json"));
119
+ return existsSync(dir) && existsSync(join3(dir, "tokens.json"));
113
120
  }
114
121
  function listAccountEmails() {
115
122
  const accountsDir = getAccountsDir();
@@ -121,7 +128,7 @@ function listAccountEmails() {
121
128
  const emails = [];
122
129
  for (const entry of entries) {
123
130
  if (entry.isDirectory()) {
124
- const tokensPath = join2(accountsDir, entry.name, "tokens.json");
131
+ const tokensPath = join3(accountsDir, entry.name, "tokens.json");
125
132
  if (existsSync(tokensPath)) {
126
133
  emails.push(entry.name);
127
134
  }
@@ -135,18 +142,18 @@ function listAccountEmails() {
135
142
  }
136
143
  function saveAccountTokens(email, tokens) {
137
144
  ensureAccountDir(email);
138
- const path = join2(getAccountDir(email), "tokens.json");
145
+ const path = join3(getAccountDir(email), "tokens.json");
139
146
  debug("accounts-storage", `Saving tokens for ${email}`);
140
147
  writeFileSync(path, JSON.stringify(tokens, null, 2), { mode: 384 });
141
148
  }
142
149
  function loadAccountTokens(email) {
143
- const path = join2(getAccountDir(email), "tokens.json");
150
+ const path = join3(getAccountDir(email), "tokens.json");
144
151
  if (!existsSync(path)) {
145
152
  debug("accounts-storage", `No tokens file for ${email}`);
146
153
  return null;
147
154
  }
148
155
  try {
149
- const content = readFileSync(path, "utf-8");
156
+ const content = readFileSync2(path, "utf-8");
150
157
  return JSON.parse(content);
151
158
  } catch (err) {
152
159
  debug("accounts-storage", `Failed to parse tokens for ${email}`, err);
@@ -155,17 +162,17 @@ function loadAccountTokens(email) {
155
162
  }
156
163
  function saveAccountMetadata(email, metadata) {
157
164
  ensureAccountDir(email);
158
- const path = join2(getAccountDir(email), "metadata.json");
165
+ const path = join3(getAccountDir(email), "metadata.json");
159
166
  debug("accounts-storage", `Saving metadata for ${email}`);
160
167
  writeFileSync(path, JSON.stringify(metadata, null, 2), { mode: 384 });
161
168
  }
162
169
  function loadAccountMetadata(email) {
163
- const path = join2(getAccountDir(email), "metadata.json");
170
+ const path = join3(getAccountDir(email), "metadata.json");
164
171
  if (!existsSync(path)) {
165
172
  return null;
166
173
  }
167
174
  try {
168
- const content = readFileSync(path, "utf-8");
175
+ const content = readFileSync2(path, "utf-8");
169
176
  return JSON.parse(content);
170
177
  } catch (err) {
171
178
  debug("accounts-storage", `Failed to parse metadata for ${email}`, err);
@@ -181,17 +188,17 @@ function updateLastUsed(email) {
181
188
  }
182
189
  function saveAccountCache(email, cache) {
183
190
  ensureAccountDir(email);
184
- const path = join2(getAccountDir(email), "cache.json");
191
+ const path = join3(getAccountDir(email), "cache.json");
185
192
  debug("accounts-storage", `Saving cache for ${email}`);
186
193
  writeFileSync(path, JSON.stringify(cache, null, 2));
187
194
  }
188
195
  function loadAccountCache(email) {
189
- const path = join2(getAccountDir(email), "cache.json");
196
+ const path = join3(getAccountDir(email), "cache.json");
190
197
  if (!existsSync(path)) {
191
198
  return null;
192
199
  }
193
200
  try {
194
- const content = readFileSync(path, "utf-8");
201
+ const content = readFileSync2(path, "utf-8");
195
202
  return JSON.parse(content);
196
203
  } catch (err) {
197
204
  debug("accounts-storage", `Failed to parse cache for ${email}`, err);
@@ -215,8 +222,8 @@ function deleteAccount(email) {
215
222
  }
216
223
 
217
224
  // src/accounts/config.ts
218
- import { existsSync as existsSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2 } from "fs";
219
- import { dirname } from "path";
225
+ import { existsSync as existsSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2 } from "fs";
226
+ import { dirname as dirname2 } from "path";
220
227
  function loadConfig() {
221
228
  const path = getGlobalConfigPath();
222
229
  if (!existsSync2(path)) {
@@ -224,7 +231,7 @@ function loadConfig() {
224
231
  return { ...DEFAULT_CONFIG };
225
232
  }
226
233
  try {
227
- const content = readFileSync2(path, "utf-8");
234
+ const content = readFileSync3(path, "utf-8");
228
235
  const config = JSON.parse(content);
229
236
  return {
230
237
  ...DEFAULT_CONFIG,
@@ -241,7 +248,7 @@ function loadConfig() {
241
248
  }
242
249
  function saveConfig(config) {
243
250
  const path = getGlobalConfigPath();
244
- const dir = dirname(path);
251
+ const dir = dirname2(path);
245
252
  if (!existsSync2(dir)) {
246
253
  mkdirSync2(dir, { recursive: true });
247
254
  }
@@ -858,13 +865,13 @@ async function refreshAccessToken(refreshToken) {
858
865
  }
859
866
 
860
867
  // src/google/storage.ts
861
- import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync3, unlinkSync } from "fs";
862
- import { dirname as dirname2 } from "path";
868
+ import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync4, writeFileSync as writeFileSync3, unlinkSync } from "fs";
869
+ import { dirname as dirname3 } from "path";
863
870
  function saveTokens(tokens) {
864
871
  const email = tokens.email;
865
872
  if (!email) {
866
873
  const path = getTokensPath();
867
- const dir = dirname2(path);
874
+ const dir = dirname3(path);
868
875
  debug("storage", `Saving tokens to legacy path ${path}`);
869
876
  if (!existsSync3(dir)) {
870
877
  mkdirSync3(dir, { recursive: true });
@@ -894,7 +901,7 @@ function loadTokens() {
894
901
  return null;
895
902
  }
896
903
  try {
897
- const content = readFileSync3(legacyPath, "utf-8");
904
+ const content = readFileSync4(legacyPath, "utf-8");
898
905
  const tokens = JSON.parse(content);
899
906
  debug("storage", "Tokens loaded successfully from legacy path");
900
907
  return tokens;
@@ -3152,14 +3159,14 @@ function getDefaultConfig() {
3152
3159
  }
3153
3160
 
3154
3161
  // 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";
3162
+ import { join as join4 } from "path";
3163
+ import { readFileSync as readFileSync5, writeFileSync as writeFileSync4, existsSync as existsSync4, mkdirSync as mkdirSync4 } from "fs";
3157
3164
  var WAKEUP_DIR_NAME = "wakeup";
3158
3165
  var CONFIG_FILE_NAME = "config.json";
3159
3166
  var HISTORY_FILE_NAME = "history.json";
3160
3167
  var MAX_HISTORY_ENTRIES = 100;
3161
3168
  function getWakeupDir() {
3162
- return join3(getConfigDir(), WAKEUP_DIR_NAME);
3169
+ return join4(getConfigDir(), WAKEUP_DIR_NAME);
3163
3170
  }
3164
3171
  function ensureWakeupDir() {
3165
3172
  const dir = getWakeupDir();
@@ -3169,10 +3176,10 @@ function ensureWakeupDir() {
3169
3176
  }
3170
3177
  }
3171
3178
  function readJsonFile(filename, defaultValue) {
3172
- const filepath = join3(getWakeupDir(), filename);
3179
+ const filepath = join4(getWakeupDir(), filename);
3173
3180
  try {
3174
3181
  if (existsSync4(filepath)) {
3175
- const content = readFileSync4(filepath, "utf-8");
3182
+ const content = readFileSync5(filepath, "utf-8");
3176
3183
  return JSON.parse(content);
3177
3184
  }
3178
3185
  } catch (err) {
@@ -3182,7 +3189,7 @@ function readJsonFile(filename, defaultValue) {
3182
3189
  }
3183
3190
  function writeJsonFile(filename, data) {
3184
3191
  ensureWakeupDir();
3185
- const filepath = join3(getWakeupDir(), filename);
3192
+ const filepath = join4(getWakeupDir(), filename);
3186
3193
  try {
3187
3194
  writeFileSync4(filepath, JSON.stringify(data, null, 2), "utf-8");
3188
3195
  debug("wakeup-storage", `Wrote ${filename}`);
@@ -3419,26 +3426,46 @@ import { execSync, exec as exec3 } from "child_process";
3419
3426
  import { promisify as promisify3 } from "util";
3420
3427
  var execAsync3 = promisify3(exec3);
3421
3428
  var CRON_COMMENT_MARKER = "antigravity-usage-wakeup";
3422
- function getBinaryPath() {
3429
+ function getBinDirectories() {
3430
+ const dirs = /* @__PURE__ */ new Set();
3431
+ try {
3432
+ const nodePath = process.execPath;
3433
+ const nodeDir = nodePath.substring(0, nodePath.lastIndexOf("/"));
3434
+ if (nodeDir) {
3435
+ dirs.add(nodeDir);
3436
+ debug("cron-installer", `Found node bin dir: ${nodeDir}`);
3437
+ }
3438
+ } catch {
3439
+ debug("cron-installer", "Could not determine node bin directory");
3440
+ }
3423
3441
  try {
3424
- const path = execSync("which antigravity-usage", {
3442
+ const npmBin = execSync("npm bin -g", {
3425
3443
  encoding: "utf-8",
3426
3444
  stdio: ["pipe", "pipe", "pipe"]
3427
3445
  }).trim();
3428
- if (path) {
3429
- debug("cron-installer", `Found binary at: ${path}`);
3430
- return path;
3446
+ if (npmBin) {
3447
+ dirs.add(npmBin);
3448
+ debug("cron-installer", `Found npm bin dir: ${npmBin}`);
3431
3449
  }
3432
3450
  } catch {
3433
- debug("cron-installer", "Could not find antigravity-usage via which");
3451
+ debug("cron-installer", "Could not determine npm bin directory");
3434
3452
  }
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}`;
3453
+ if (process.env.PATH) {
3454
+ const userPaths = process.env.PATH.split(":").filter((p) => {
3455
+ return p.includes("node") || p.includes("npm") || p.includes("nvm") || p.includes(".local") || p === "/usr/local/bin" || p === "/opt/homebrew/bin";
3456
+ });
3457
+ userPaths.forEach((p) => {
3458
+ if (p) {
3459
+ dirs.add(p);
3460
+ debug("cron-installer", `Added user PATH: ${p}`);
3461
+ }
3462
+ });
3440
3463
  }
3441
- throw new Error("Could not determine binary path for cron job");
3464
+ dirs.add("/usr/local/bin");
3465
+ dirs.add("/usr/bin");
3466
+ dirs.add("/bin");
3467
+ dirs.add("/opt/homebrew/bin");
3468
+ return Array.from(dirs);
3442
3469
  }
3443
3470
  async function loadCrontab() {
3444
3471
  try {
@@ -3484,14 +3511,19 @@ async function installCronJob(cronExpression) {
3484
3511
  };
3485
3512
  }
3486
3513
  try {
3487
- const binaryPath = getBinaryPath();
3514
+ const binDirs = getBinDirectories();
3515
+ const pathValue = binDirs.join(":");
3488
3516
  const lines = await loadCrontab();
3489
3517
  const filteredLines = removeWakeupEntries(lines);
3490
- const command = `${binaryPath} wakeup trigger --scheduled`;
3491
- const cronLine = `${cronExpression} ${command} # ${CRON_COMMENT_MARKER}`;
3518
+ const hasPath = filteredLines.some((line) => line.startsWith("PATH="));
3519
+ if (!hasPath) {
3520
+ filteredLines.unshift(`PATH=${pathValue}`);
3521
+ }
3522
+ const cronLine = `${cronExpression} antigravity-usage wakeup trigger --scheduled # ${CRON_COMMENT_MARKER}`;
3492
3523
  filteredLines.push(cronLine);
3493
3524
  await saveCrontab(filteredLines);
3494
3525
  debug("cron-installer", `Installed cron job: ${cronLine}`);
3526
+ debug("cron-installer", `Using PATH: ${pathValue}`);
3495
3527
  return {
3496
3528
  success: true,
3497
3529
  cronExpression
@@ -3499,11 +3531,10 @@ async function installCronJob(cronExpression) {
3499
3531
  } catch (err) {
3500
3532
  const errorMessage = err instanceof Error ? err.message : String(err);
3501
3533
  debug("cron-installer", `Failed to install cron job: ${errorMessage}`);
3502
- const binaryPath = tryGetBinaryPath();
3503
3534
  return {
3504
3535
  success: false,
3505
3536
  error: errorMessage,
3506
- manualInstructions: getManualInstructions(cronExpression, binaryPath)
3537
+ manualInstructions: getManualInstructions(cronExpression)
3507
3538
  };
3508
3539
  }
3509
3540
  }
@@ -3548,21 +3579,17 @@ async function getCronStatus() {
3548
3579
  return { installed: false };
3549
3580
  }
3550
3581
  }
3551
- function tryGetBinaryPath() {
3552
- try {
3553
- return getBinaryPath();
3554
- } catch {
3555
- return "antigravity-usage";
3556
- }
3557
- }
3558
- function getManualInstructions(cronExpression, binaryPath) {
3582
+ function getManualInstructions(cronExpression) {
3583
+ const binDirs = getBinDirectories();
3584
+ const pathValue = binDirs.join(":");
3559
3585
  return `
3560
3586
  Failed to automatically install cron job. Please add manually:
3561
3587
 
3562
3588
  1. Open terminal and run: crontab -e
3563
3589
 
3564
- 2. Add this line at the end:
3565
- ${cronExpression} ${binaryPath} wakeup trigger --scheduled # ${CRON_COMMENT_MARKER}
3590
+ 2. Add these lines:
3591
+ PATH=${pathValue}
3592
+ ${cronExpression} antigravity-usage wakeup trigger --scheduled # ${CRON_COMMENT_MARKER}
3566
3593
 
3567
3594
  3. Save and exit the editor
3568
3595