when-does-my-quota-refresh 1.0.1 → 1.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.
@@ -0,0 +1,27 @@
1
+ #!/usr/bin/env node
2
+ import "./chunk-H7BEO7IF.js";
3
+ import {
4
+ completeManualLogin,
5
+ getAccountTokens,
6
+ getActiveAccountTokens,
7
+ getAllAccountTokens,
8
+ getManualLoginUrl,
9
+ listAccounts,
10
+ loginAccount,
11
+ removeAccount,
12
+ saveAccountTokens,
13
+ setActiveAccount
14
+ } from "./chunk-VLP22VAR.js";
15
+ export {
16
+ completeManualLogin,
17
+ getAccountTokens,
18
+ getActiveAccountTokens,
19
+ getAllAccountTokens,
20
+ getManualLoginUrl,
21
+ listAccounts,
22
+ loginAccount,
23
+ removeAccount,
24
+ saveAccountTokens,
25
+ setActiveAccount
26
+ };
27
+ //# sourceMappingURL=accounts-FH6AKZT7.js.map
@@ -3,10 +3,10 @@ import {
3
3
  completeManualLogin,
4
4
  getManualLoginUrl,
5
5
  loginAccount
6
- } from "./chunk-24ILGZJ2.js";
6
+ } from "./chunk-VLP22VAR.js";
7
7
  export {
8
8
  completeManualLogin,
9
9
  getManualLoginUrl,
10
10
  loginAccount
11
11
  };
12
- //# sourceMappingURL=auth-K5ETZYGY.js.map
12
+ //# sourceMappingURL=auth-GX4ZIG5O.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ //# sourceMappingURL=chunk-H7BEO7IF.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -4,6 +4,21 @@
4
4
  import https from "https";
5
5
  import { URL } from "url";
6
6
 
7
+ // src/core/logger.ts
8
+ var _verbose = false;
9
+ function setVerbose(verbose) {
10
+ _verbose = verbose;
11
+ }
12
+ function debug(module, msg, data) {
13
+ if (!_verbose) return;
14
+ const prefix = `[${module}]`;
15
+ if (data !== void 0) {
16
+ console.error(`${prefix} ${msg}`, data);
17
+ } else {
18
+ console.error(`${prefix} ${msg}`);
19
+ }
20
+ }
21
+
7
22
  // src/sources/cloud-code-api.ts
8
23
  var CLOUD_CODE_ENDPOINTS = {
9
24
  /** Load code assist configuration */
@@ -30,6 +45,163 @@ var OAUTH_CLIENT_SECRET = "d-FL95Q19q7MQmFpd7hHD0Ty";
30
45
  var OAUTH_SCOPES = [
31
46
  "https://www.googleapis.com/auth/cloud-platform"
32
47
  ].join(" ");
48
+ var OAUTH_REDIRECT_URI = "http://127.0.0.1:0/callback";
49
+
50
+ // src/sdk/accounts/storage.ts
51
+ import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, existsSync as existsSync2, mkdirSync as mkdirSync2 } from "fs";
52
+ import { join as join2, dirname } from "path";
53
+
54
+ // src/core/config.ts
55
+ import { readFileSync, writeFileSync, mkdirSync, existsSync } from "fs";
56
+ import { join } from "path";
57
+ import { homedir } from "os";
58
+ var CONFIG_DIR_NAME = "when-does-my-quota-refresh";
59
+ var DEFAULT_CONFIG = {
60
+ defaultMode: "local",
61
+ cacheTtlMs: 5 * 60 * 1e3,
62
+ // 5 minutes
63
+ refreshIntervalMs: 5 * 60 * 1e3,
64
+ wakeupModels: ["claude-sonnet-4-5", "gemini-3-flash", "gemini-3-pro-low"],
65
+ wakeupAccounts: [],
66
+ wakeupMode: "smart",
67
+ wakeupIntervalHours: 6,
68
+ wakeupDailyTimes: "9,17",
69
+ notifications: true,
70
+ plugins: [],
71
+ daemon: {
72
+ pollIntervalMs: 60 * 1e3,
73
+ enabled: false
74
+ }
75
+ };
76
+ function getConfigDir() {
77
+ const home = homedir();
78
+ switch (process.platform) {
79
+ case "darwin":
80
+ return join(home, "Library", "Application Support", CONFIG_DIR_NAME);
81
+ case "win32":
82
+ return join(process.env.APPDATA || join(home, "AppData", "Roaming"), CONFIG_DIR_NAME);
83
+ default:
84
+ return join(home, ".config", CONFIG_DIR_NAME);
85
+ }
86
+ }
87
+ function getConfigPath() {
88
+ return join(getConfigDir(), "config.json");
89
+ }
90
+ function getDataDir() {
91
+ return join(getConfigDir(), "data");
92
+ }
93
+ function ensureDirs() {
94
+ const configDir = getConfigDir();
95
+ const dataDir = getDataDir();
96
+ if (!existsSync(configDir)) mkdirSync(configDir, { recursive: true });
97
+ if (!existsSync(dataDir)) mkdirSync(dataDir, { recursive: true });
98
+ }
99
+ function loadConfig() {
100
+ try {
101
+ const path = getConfigPath();
102
+ if (!existsSync(path)) return DEFAULT_CONFIG;
103
+ const raw = readFileSync(path, "utf-8");
104
+ const parsed = JSON.parse(raw);
105
+ debug("config", `Loaded config from ${path}`);
106
+ return { ...DEFAULT_CONFIG, ...parsed };
107
+ } catch (err) {
108
+ debug("config", "Failed to load config, using defaults", err);
109
+ return DEFAULT_CONFIG;
110
+ }
111
+ }
112
+ function saveConfig(config) {
113
+ ensureDirs();
114
+ const path = getConfigPath();
115
+ writeFileSync(path, JSON.stringify(config, null, 2));
116
+ debug("config", `Saved config to ${path}`);
117
+ }
118
+ function getConfigDirPath() {
119
+ return getConfigDir();
120
+ }
121
+ function getDataDirPath() {
122
+ return getDataDir();
123
+ }
124
+
125
+ // src/sdk/accounts/storage.ts
126
+ var TOKENS_DIR = "accounts";
127
+ function getStorePath() {
128
+ return join2(getConfigDirPath(), TOKENS_DIR, "tokens.json");
129
+ }
130
+ function loadStore() {
131
+ const path = getStorePath();
132
+ if (!existsSync2(path)) return { accounts: [] };
133
+ try {
134
+ return JSON.parse(readFileSync2(path, "utf-8"));
135
+ } catch {
136
+ return { accounts: [] };
137
+ }
138
+ }
139
+ function saveStore(store) {
140
+ ensureDirs();
141
+ const storePath = getStorePath();
142
+ mkdirSync2(dirname(storePath), { recursive: true });
143
+ writeFileSync2(storePath, JSON.stringify(store, null, 2));
144
+ debug("account-storage", `Saved ${store.accounts.length} account(s)`);
145
+ }
146
+ function saveAccountTokens(email, tokens) {
147
+ const store = loadStore();
148
+ const existing = store.accounts.findIndex((a) => a.email === email);
149
+ const account = {
150
+ email,
151
+ tokens,
152
+ addedAt: (/* @__PURE__ */ new Date()).toISOString()
153
+ };
154
+ if (existing >= 0) {
155
+ store.accounts[existing] = account;
156
+ } else {
157
+ store.accounts.push(account);
158
+ }
159
+ if (store.accounts.length === 1) {
160
+ store.activeEmail = email;
161
+ }
162
+ saveStore(store);
163
+ }
164
+ function getAccountTokens(email) {
165
+ const store = loadStore();
166
+ const account = store.accounts.find((a) => a.email === email);
167
+ return account?.tokens || null;
168
+ }
169
+ function getActiveAccountTokens() {
170
+ const store = loadStore();
171
+ if (!store.activeEmail) return null;
172
+ const account = store.accounts.find((a) => a.email === store.activeEmail);
173
+ return account?.tokens || null;
174
+ }
175
+ function setActiveAccount(email) {
176
+ const store = loadStore();
177
+ if (!store.accounts.find((a) => a.email === email)) return false;
178
+ store.activeEmail = email;
179
+ saveStore(store);
180
+ return true;
181
+ }
182
+ function removeAccount(email) {
183
+ const store = loadStore();
184
+ const idx = store.accounts.findIndex((a) => a.email === email);
185
+ if (idx < 0) return false;
186
+ store.accounts.splice(idx, 1);
187
+ if (store.activeEmail === email) {
188
+ store.activeEmail = store.accounts[0]?.email;
189
+ }
190
+ saveStore(store);
191
+ return true;
192
+ }
193
+ function listAccounts() {
194
+ const store = loadStore();
195
+ return store.accounts.map((a) => ({
196
+ email: a.email,
197
+ isActive: a.email === store.activeEmail,
198
+ addedAt: a.addedAt
199
+ }));
200
+ }
201
+ function getAllAccountTokens() {
202
+ const store = loadStore();
203
+ return store.accounts.map((a) => a.tokens);
204
+ }
33
205
 
34
206
  // src/sources/oauth/client.ts
35
207
  var OAuthClient = class {
@@ -40,10 +212,10 @@ var OAuthClient = class {
40
212
  /**
41
213
  * Generate the OAuth authorization URL for user login
42
214
  */
43
- getAuthUrl() {
215
+ getAuthUrl(redirectUri) {
44
216
  const params = new URLSearchParams({
45
217
  client_id: OAUTH_CLIENT_ID,
46
- redirect_uri: "http://127.0.0.1:0/callback",
218
+ redirect_uri: redirectUri || OAUTH_REDIRECT_URI,
47
219
  response_type: "code",
48
220
  scope: OAUTH_SCOPES,
49
221
  access_type: "offline",
@@ -54,12 +226,12 @@ var OAuthClient = class {
54
226
  /**
55
227
  * Exchange an authorization code for tokens
56
228
  */
57
- async exchangeCode(code) {
229
+ async exchangeCode(code, redirectUri) {
58
230
  const body = new URLSearchParams({
59
231
  code,
60
232
  client_id: OAUTH_CLIENT_ID,
61
233
  client_secret: OAUTH_CLIENT_SECRET,
62
- redirect_uri: "http://127.0.0.1:0/callback",
234
+ redirect_uri: redirectUri || OAUTH_REDIRECT_URI,
63
235
  grant_type: "authorization_code"
64
236
  });
65
237
  const response = await this.postUrlEncoded("https://oauth2.googleapis.com/token", body);
@@ -91,6 +263,14 @@ var OAuthClient = class {
91
263
  accessToken: data.access_token,
92
264
  expiresAt: Date.now() + data.expires_in * 1e3
93
265
  };
266
+ if (this.tokens.email) {
267
+ try {
268
+ saveAccountTokens(this.tokens.email, this.tokens);
269
+ debug("oauth", `Persisted refreshed token for ${this.tokens.email}`);
270
+ } catch (err) {
271
+ debug("oauth", `Failed to persist refreshed token: ${err}`);
272
+ }
273
+ }
94
274
  return this.tokens;
95
275
  }
96
276
  /**
@@ -136,6 +316,43 @@ var OAuthClient = class {
136
316
  token
137
317
  );
138
318
  }
319
+ /**
320
+ * Fetch the authenticated user's profile (email, name, etc.)
321
+ */
322
+ async fetchUserInfo() {
323
+ const token = await this.getAccessToken();
324
+ return new Promise((resolve, reject) => {
325
+ const options = {
326
+ hostname: "www.googleapis.com",
327
+ path: "/oauth2/v3/userinfo",
328
+ method: "GET",
329
+ headers: {
330
+ "Authorization": `Bearer ${token}`
331
+ },
332
+ timeout: 1e4
333
+ };
334
+ const req = https.request(options, (res) => {
335
+ let data = "";
336
+ res.on("data", (chunk) => {
337
+ data += chunk;
338
+ });
339
+ res.on("end", () => {
340
+ try {
341
+ const info = JSON.parse(data);
342
+ resolve({ email: info.email, name: info.name });
343
+ } catch {
344
+ resolve({});
345
+ }
346
+ });
347
+ });
348
+ req.on("error", () => resolve({}));
349
+ req.on("timeout", () => {
350
+ req.destroy();
351
+ resolve({});
352
+ });
353
+ req.end();
354
+ });
355
+ }
139
356
  // ── HTTP Helpers ────────────────────────────────────────────────
140
357
  authenticatedPost(url, body, token) {
141
358
  return new Promise((resolve, reject) => {
@@ -158,6 +375,10 @@ var OAuthClient = class {
158
375
  responseData += chunk;
159
376
  });
160
377
  res.on("end", () => {
378
+ if (res.statusCode && (res.statusCode < 200 || res.statusCode >= 300)) {
379
+ reject(new Error(`HTTP ${res.statusCode}: ${responseData.slice(0, 200)}`));
380
+ return;
381
+ }
161
382
  try {
162
383
  resolve(JSON.parse(responseData));
163
384
  } catch {
@@ -194,6 +415,10 @@ var OAuthClient = class {
194
415
  responseData += chunk;
195
416
  });
196
417
  res.on("end", () => {
418
+ if (res.statusCode && (res.statusCode < 200 || res.statusCode >= 300)) {
419
+ reject(new Error(`HTTP ${res.statusCode}: ${responseData.slice(0, 200)}`));
420
+ return;
421
+ }
197
422
  try {
198
423
  resolve(JSON.parse(responseData));
199
424
  } catch {
@@ -212,169 +437,6 @@ var OAuthClient = class {
212
437
  }
213
438
  };
214
439
 
215
- // src/sdk/accounts/storage.ts
216
- import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, existsSync as existsSync2 } from "fs";
217
- import { join as join2 } from "path";
218
-
219
- // src/core/config.ts
220
- import { readFileSync, writeFileSync, mkdirSync, existsSync } from "fs";
221
- import { join } from "path";
222
- import { homedir } from "os";
223
-
224
- // src/core/logger.ts
225
- var _verbose = false;
226
- function setVerbose(verbose) {
227
- _verbose = verbose;
228
- }
229
- function debug(module, msg, data) {
230
- if (!_verbose) return;
231
- const prefix = `[${module}]`;
232
- if (data !== void 0) {
233
- console.error(`${prefix} ${msg}`, data);
234
- } else {
235
- console.error(`${prefix} ${msg}`);
236
- }
237
- }
238
-
239
- // src/core/config.ts
240
- var CONFIG_DIR_NAME = "when-does-my-quota-refresh";
241
- var DEFAULT_CONFIG = {
242
- defaultMode: "local",
243
- cacheTtlMs: 5 * 60 * 1e3,
244
- // 5 minutes
245
- refreshIntervalMs: 5 * 60 * 1e3,
246
- wakeupModels: ["claude-sonnet-4-5", "gemini-3-flash", "gemini-3-pro-low"],
247
- wakeupAccounts: [],
248
- notifications: true,
249
- plugins: [],
250
- daemon: {
251
- pollIntervalMs: 60 * 1e3,
252
- enabled: false
253
- }
254
- };
255
- function getConfigDir() {
256
- const home = homedir();
257
- switch (process.platform) {
258
- case "darwin":
259
- return join(home, "Library", "Application Support", CONFIG_DIR_NAME);
260
- case "win32":
261
- return join(process.env.APPDATA || join(home, "AppData", "Roaming"), CONFIG_DIR_NAME);
262
- default:
263
- return join(home, ".config", CONFIG_DIR_NAME);
264
- }
265
- }
266
- function getConfigPath() {
267
- return join(getConfigDir(), "config.json");
268
- }
269
- function getDataDir() {
270
- return join(getConfigDir(), "data");
271
- }
272
- function ensureDirs() {
273
- const configDir = getConfigDir();
274
- const dataDir = getDataDir();
275
- if (!existsSync(configDir)) mkdirSync(configDir, { recursive: true });
276
- if (!existsSync(dataDir)) mkdirSync(dataDir, { recursive: true });
277
- }
278
- function loadConfig() {
279
- try {
280
- const path = getConfigPath();
281
- if (!existsSync(path)) return DEFAULT_CONFIG;
282
- const raw = readFileSync(path, "utf-8");
283
- const parsed = JSON.parse(raw);
284
- debug("config", `Loaded config from ${path}`);
285
- return { ...DEFAULT_CONFIG, ...parsed };
286
- } catch (err) {
287
- debug("config", "Failed to load config, using defaults", err);
288
- return DEFAULT_CONFIG;
289
- }
290
- }
291
- function saveConfig(config) {
292
- ensureDirs();
293
- const path = getConfigPath();
294
- writeFileSync(path, JSON.stringify(config, null, 2));
295
- debug("config", `Saved config to ${path}`);
296
- }
297
- function getConfigDirPath() {
298
- return getConfigDir();
299
- }
300
- function getDataDirPath() {
301
- return getDataDir();
302
- }
303
-
304
- // src/sdk/accounts/storage.ts
305
- var TOKENS_DIR = "accounts";
306
- function getStorePath() {
307
- return join2(getConfigDirPath(), TOKENS_DIR, "tokens.json");
308
- }
309
- function loadStore() {
310
- const path = getStorePath();
311
- if (!existsSync2(path)) return { accounts: [] };
312
- try {
313
- return JSON.parse(readFileSync2(path, "utf-8"));
314
- } catch {
315
- return { accounts: [] };
316
- }
317
- }
318
- function saveStore(store) {
319
- ensureDirs();
320
- writeFileSync2(getStorePath(), JSON.stringify(store, null, 2));
321
- debug("account-storage", `Saved ${store.accounts.length} account(s)`);
322
- }
323
- function saveAccountTokens(email, tokens) {
324
- const store = loadStore();
325
- const existing = store.accounts.findIndex((a) => a.email === email);
326
- const account = {
327
- email,
328
- tokens,
329
- addedAt: (/* @__PURE__ */ new Date()).toISOString()
330
- };
331
- if (existing >= 0) {
332
- store.accounts[existing] = account;
333
- } else {
334
- store.accounts.push(account);
335
- }
336
- if (store.accounts.length === 1) {
337
- store.activeEmail = email;
338
- }
339
- saveStore(store);
340
- }
341
- function getActiveAccountTokens() {
342
- const store = loadStore();
343
- if (!store.activeEmail) return null;
344
- const account = store.accounts.find((a) => a.email === store.activeEmail);
345
- return account?.tokens || null;
346
- }
347
- function setActiveAccount(email) {
348
- const store = loadStore();
349
- if (!store.accounts.find((a) => a.email === email)) return false;
350
- store.activeEmail = email;
351
- saveStore(store);
352
- return true;
353
- }
354
- function removeAccount(email) {
355
- const store = loadStore();
356
- const idx = store.accounts.findIndex((a) => a.email === email);
357
- if (idx < 0) return false;
358
- store.accounts.splice(idx, 1);
359
- if (store.activeEmail === email) {
360
- store.activeEmail = store.accounts[0]?.email;
361
- }
362
- saveStore(store);
363
- return true;
364
- }
365
- function listAccounts() {
366
- const store = loadStore();
367
- return store.accounts.map((a) => ({
368
- email: a.email,
369
- isActive: a.email === store.activeEmail,
370
- addedAt: a.addedAt
371
- }));
372
- }
373
- function getAllAccountTokens() {
374
- const store = loadStore();
375
- return store.accounts.map((a) => a.tokens);
376
- }
377
-
378
440
  // src/core/errors.ts
379
441
  var SourceUnavailableError = class extends Error {
380
442
  constructor(source, reason) {
@@ -395,14 +457,14 @@ import http from "http";
395
457
  import { URL as URL2 } from "url";
396
458
  async function loginAccount() {
397
459
  const client = new OAuthClient();
398
- const authUrl = client.getAuthUrl();
399
460
  debug("auth", "Starting OAuth login flow");
461
+ let capturedRedirectUri = "http://127.0.0.1:0/callback";
400
462
  const code = await new Promise((resolve, reject) => {
401
463
  const server = http.createServer(async (req, res) => {
402
464
  const url = new URL2(req.url || "/", `http://127.0.0.1`);
403
465
  const code2 = url.searchParams.get("code");
404
466
  if (code2) {
405
- res.writeHead(200, { "Content-Type": "text/html" });
467
+ res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
406
468
  res.end(`
407
469
  <html><body style="font-family: sans-serif; text-align: center; padding: 50px;">
408
470
  <h1>\u2705 Login successful!</h1>
@@ -414,7 +476,7 @@ async function loginAccount() {
414
476
  } else {
415
477
  const error = url.searchParams.get("error");
416
478
  if (error) {
417
- res.writeHead(400, { "Content-Type": "text/html" });
479
+ res.writeHead(400, { "Content-Type": "text/html; charset=utf-8" });
418
480
  res.end(`<h1>Login failed: ${error}</h1>`);
419
481
  server.close();
420
482
  reject(new AuthError(`OAuth error: ${error}`));
@@ -427,7 +489,9 @@ async function loginAccount() {
427
489
  server.listen(0, "127.0.0.1", () => {
428
490
  const addr = server.address();
429
491
  if (typeof addr === "object" && addr) {
492
+ capturedRedirectUri = `http://127.0.0.1:${addr.port}/callback`;
430
493
  debug("auth", `Callback server on port ${addr.port}`);
494
+ const authUrl = client.getAuthUrl(capturedRedirectUri);
431
495
  open(authUrl).catch(() => {
432
496
  console.log(`
433
497
  Open this URL in your browser:
@@ -442,8 +506,19 @@ ${authUrl}
442
506
  reject(new AuthError("Login timed out after 5 minutes"));
443
507
  }, 5 * 60 * 1e3);
444
508
  });
445
- const tokens = await client.exchangeCode(code);
446
- const email = tokens.email || "unknown@gmail.com";
509
+ const tokens = await client.exchangeCode(code, capturedRedirectUri);
510
+ let email = tokens.email;
511
+ if (!email) {
512
+ try {
513
+ const userInfo = await client.fetchUserInfo();
514
+ email = userInfo.email;
515
+ } catch (err) {
516
+ debug("auth", `Failed to fetch user info: ${err}`);
517
+ }
518
+ }
519
+ if (!email) {
520
+ throw new AuthError("Could not determine user email from Google account");
521
+ }
447
522
  tokens.email = email;
448
523
  saveAccountTokens(email, tokens);
449
524
  setActiveAccount(email);
@@ -457,7 +532,18 @@ function getManualLoginUrl() {
457
532
  async function completeManualLogin(code) {
458
533
  const client = new OAuthClient();
459
534
  const tokens = await client.exchangeCode(code);
460
- const email = tokens.email || "unknown@gmail.com";
535
+ let email = tokens.email;
536
+ if (!email) {
537
+ try {
538
+ const userInfo = await client.fetchUserInfo();
539
+ email = userInfo.email;
540
+ } catch (err) {
541
+ debug("auth", `Failed to fetch user info: ${err}`);
542
+ }
543
+ }
544
+ if (!email) {
545
+ throw new AuthError("Could not determine user email from Google account");
546
+ }
461
547
  tokens.email = email;
462
548
  saveAccountTokens(email, tokens);
463
549
  setActiveAccount(email);
@@ -468,19 +554,21 @@ export {
468
554
  setVerbose,
469
555
  debug,
470
556
  SourceUnavailableError,
471
- OAuthClient,
472
557
  ensureDirs,
473
558
  loadConfig,
474
559
  saveConfig,
475
560
  getConfigDirPath,
476
561
  getDataDirPath,
562
+ saveAccountTokens,
563
+ getAccountTokens,
477
564
  getActiveAccountTokens,
478
565
  setActiveAccount,
479
566
  removeAccount,
480
567
  listAccounts,
481
568
  getAllAccountTokens,
569
+ OAuthClient,
482
570
  loginAccount,
483
571
  getManualLoginUrl,
484
572
  completeManualLogin
485
573
  };
486
- //# sourceMappingURL=chunk-24ILGZJ2.js.map
574
+ //# sourceMappingURL=chunk-VLP22VAR.js.map