@pan-sec/notebooklm-mcp 1.10.5 → 1.10.8

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.
Files changed (43) hide show
  1. package/dist/auth/auth-manager.d.ts +3 -0
  2. package/dist/auth/auth-manager.d.ts.map +1 -1
  3. package/dist/auth/auth-manager.js +87 -70
  4. package/dist/auth/auth-manager.js.map +1 -1
  5. package/dist/index.js +5 -1
  6. package/dist/index.js.map +1 -1
  7. package/dist/logging/index.d.ts +7 -0
  8. package/dist/logging/index.d.ts.map +1 -0
  9. package/dist/logging/index.js +7 -0
  10. package/dist/logging/index.js.map +1 -0
  11. package/dist/logging/query-logger.d.ts +144 -0
  12. package/dist/logging/query-logger.d.ts.map +1 -0
  13. package/dist/logging/query-logger.js +282 -0
  14. package/dist/logging/query-logger.js.map +1 -0
  15. package/dist/quota/quota-manager.d.ts +66 -2
  16. package/dist/quota/quota-manager.d.ts.map +1 -1
  17. package/dist/quota/quota-manager.js +220 -2
  18. package/dist/quota/quota-manager.js.map +1 -1
  19. package/dist/session/shared-context-manager.d.ts +4 -0
  20. package/dist/session/shared-context-manager.d.ts.map +1 -1
  21. package/dist/session/shared-context-manager.js +43 -11
  22. package/dist/session/shared-context-manager.js.map +1 -1
  23. package/dist/tools/definitions/query-history.d.ts +9 -0
  24. package/dist/tools/definitions/query-history.d.ts.map +1 -0
  25. package/dist/tools/definitions/query-history.js +44 -0
  26. package/dist/tools/definitions/query-history.js.map +1 -0
  27. package/dist/tools/definitions/system.d.ts.map +1 -1
  28. package/dist/tools/definitions/system.js +12 -4
  29. package/dist/tools/definitions/system.js.map +1 -1
  30. package/dist/tools/definitions.d.ts.map +1 -1
  31. package/dist/tools/definitions.js +2 -0
  32. package/dist/tools/definitions.js.map +1 -1
  33. package/dist/tools/handlers.d.ts +47 -1
  34. package/dist/tools/handlers.d.ts.map +1 -1
  35. package/dist/tools/handlers.js +152 -10
  36. package/dist/tools/handlers.js.map +1 -1
  37. package/dist/types.d.ts +13 -0
  38. package/dist/types.d.ts.map +1 -1
  39. package/dist/utils/file-lock.d.ts +79 -0
  40. package/dist/utils/file-lock.d.ts.map +1 -0
  41. package/dist/utils/file-lock.js +222 -0
  42. package/dist/utils/file-lock.js.map +1 -0
  43. package/package.json +1 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"query-logger.d.ts","sourceRoot":"","sources":["../../src/logging/query-logger.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,282 @@
1
+ /**
2
+ * Query Logger for NotebookLM MCP Server
3
+ *
4
+ * Provides persistent logging of all Q&A interactions with NotebookLM:
5
+ * - Full question and answer content
6
+ * - Session and notebook context
7
+ * - Quota information at time of query
8
+ * - Duration and metadata
9
+ *
10
+ * Features:
11
+ * - JSONL format with daily rotation
12
+ * - 90-day retention (configurable)
13
+ * - Search and retrieval by session, notebook, date
14
+ * - Full content storage for research review
15
+ */
16
+ import fs from "fs";
17
+ import path from "path";
18
+ import crypto from "crypto";
19
+ import { CONFIG } from "../config.js";
20
+ import { mkdirSecure, appendFileSecure, PERMISSION_MODES, } from "../utils/file-permissions.js";
21
+ import { log } from "../utils/logger.js";
22
+ /**
23
+ * Get query logger configuration from environment
24
+ */
25
+ function getQueryLoggerConfig() {
26
+ return {
27
+ enabled: process.env.NLMCP_QUERY_LOG_ENABLED !== "false",
28
+ logDir: process.env.NLMCP_QUERY_LOG_DIR || path.join(CONFIG.dataDir, "query_logs"),
29
+ retentionDays: parseInt(process.env.NLMCP_QUERY_LOG_RETENTION_DAYS || "90", 10),
30
+ };
31
+ }
32
+ /**
33
+ * Generate unique query ID
34
+ */
35
+ function generateQueryId() {
36
+ return crypto.randomBytes(8).toString("hex");
37
+ }
38
+ /**
39
+ * Query Logger Class
40
+ *
41
+ * Logs all Q&A interactions to JSONL files for later review.
42
+ */
43
+ export class QueryLogger {
44
+ config;
45
+ currentLogFile = "";
46
+ writeQueue = [];
47
+ isWriting = false;
48
+ stats = {
49
+ totalQueries: 0,
50
+ queriesThisSession: 0,
51
+ };
52
+ constructor(config) {
53
+ this.config = { ...getQueryLoggerConfig(), ...config };
54
+ if (this.config.enabled) {
55
+ this.ensureLogDirectory();
56
+ this.initializeLogFile();
57
+ this.cleanOldLogs();
58
+ }
59
+ }
60
+ /**
61
+ * Ensure query log directory exists
62
+ */
63
+ ensureLogDirectory() {
64
+ mkdirSecure(this.config.logDir, PERMISSION_MODES.OWNER_FULL);
65
+ }
66
+ /**
67
+ * Initialize log file for today
68
+ */
69
+ initializeLogFile() {
70
+ const today = new Date().toISOString().split("T")[0];
71
+ this.currentLogFile = path.join(this.config.logDir, `query-log-${today}.jsonl`);
72
+ }
73
+ /**
74
+ * Clean up old log files based on retention policy
75
+ */
76
+ cleanOldLogs() {
77
+ try {
78
+ const files = fs.readdirSync(this.config.logDir);
79
+ const cutoffDate = new Date();
80
+ cutoffDate.setDate(cutoffDate.getDate() - this.config.retentionDays);
81
+ let deletedCount = 0;
82
+ for (const file of files) {
83
+ if (!file.startsWith("query-log-") || !file.endsWith(".jsonl"))
84
+ continue;
85
+ // Extract date from filename (query-log-YYYY-MM-DD.jsonl)
86
+ const dateStr = file.slice(10, 20);
87
+ const fileDate = new Date(dateStr);
88
+ if (fileDate < cutoffDate) {
89
+ fs.unlinkSync(path.join(this.config.logDir, file));
90
+ deletedCount++;
91
+ }
92
+ }
93
+ if (deletedCount > 0) {
94
+ log.info(`🗑️ Cleaned ${deletedCount} old query log files`);
95
+ }
96
+ }
97
+ catch {
98
+ // Ignore cleanup errors
99
+ }
100
+ }
101
+ /**
102
+ * Write entry to log file
103
+ */
104
+ async writeEntry(entry) {
105
+ this.writeQueue.push(entry);
106
+ if (this.isWriting)
107
+ return;
108
+ this.isWriting = true;
109
+ try {
110
+ while (this.writeQueue.length > 0) {
111
+ const batch = this.writeQueue.splice(0, 100);
112
+ const lines = batch.map(e => JSON.stringify(e)).join("\n") + "\n";
113
+ // Check if we need to rotate to new day's file
114
+ const today = new Date().toISOString().split("T")[0];
115
+ const expectedFile = path.join(this.config.logDir, `query-log-${today}.jsonl`);
116
+ if (this.currentLogFile !== expectedFile) {
117
+ this.currentLogFile = expectedFile;
118
+ }
119
+ appendFileSecure(this.currentLogFile, lines, PERMISSION_MODES.OWNER_READ_WRITE);
120
+ }
121
+ }
122
+ finally {
123
+ this.isWriting = false;
124
+ }
125
+ }
126
+ /**
127
+ * Log a query (Q&A pair)
128
+ */
129
+ async logQuery(entry) {
130
+ if (!this.config.enabled)
131
+ return "";
132
+ const queryId = generateQueryId();
133
+ const fullEntry = {
134
+ timestamp: new Date().toISOString(),
135
+ queryId,
136
+ ...entry,
137
+ };
138
+ this.stats.totalQueries++;
139
+ this.stats.queriesThisSession++;
140
+ await this.writeEntry(fullEntry);
141
+ log.debug(`📝 Logged query ${queryId} (${entry.question.slice(0, 50)}...)`);
142
+ return queryId;
143
+ }
144
+ /**
145
+ * Get all queries for a specific session
146
+ */
147
+ async getQueriesForSession(sessionId) {
148
+ return this.filterQueries(entry => entry.sessionId === sessionId);
149
+ }
150
+ /**
151
+ * Get all queries for a specific notebook URL
152
+ */
153
+ async getQueriesForNotebook(notebookUrl) {
154
+ return this.filterQueries(entry => entry.notebookUrl === notebookUrl);
155
+ }
156
+ /**
157
+ * Get all queries for a specific notebook ID
158
+ */
159
+ async getQueriesForNotebookId(notebookId) {
160
+ return this.filterQueries(entry => entry.notebookId === notebookId);
161
+ }
162
+ /**
163
+ * Get all queries for a specific date (YYYY-MM-DD)
164
+ */
165
+ async getQueriesForDate(date) {
166
+ const logFile = path.join(this.config.logDir, `query-log-${date}.jsonl`);
167
+ return this.readLogFile(logFile);
168
+ }
169
+ /**
170
+ * Get recent queries
171
+ */
172
+ async getRecentQueries(limit = 50) {
173
+ const allQueries = await this.getAllQueries();
174
+ // Sort by timestamp descending (most recent first)
175
+ allQueries.sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime());
176
+ return allQueries.slice(0, limit);
177
+ }
178
+ /**
179
+ * Search queries by pattern in question or answer
180
+ */
181
+ async searchQueries(pattern, options) {
182
+ const limit = options?.limit ?? 100;
183
+ const caseSensitive = options?.caseSensitive ?? false;
184
+ const searchPattern = caseSensitive ? pattern : pattern.toLowerCase();
185
+ const matches = await this.filterQueries(entry => {
186
+ const question = caseSensitive ? entry.question : entry.question.toLowerCase();
187
+ const answer = caseSensitive ? entry.answer : entry.answer.toLowerCase();
188
+ return question.includes(searchPattern) || answer.includes(searchPattern);
189
+ });
190
+ // Sort by timestamp descending
191
+ matches.sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime());
192
+ return matches.slice(0, limit);
193
+ }
194
+ /**
195
+ * Get all available log files
196
+ */
197
+ getLogFiles() {
198
+ try {
199
+ const files = fs.readdirSync(this.config.logDir);
200
+ return files
201
+ .filter(f => f.startsWith("query-log-") && f.endsWith(".jsonl"))
202
+ .sort()
203
+ .reverse(); // Most recent first
204
+ }
205
+ catch {
206
+ return [];
207
+ }
208
+ }
209
+ /**
210
+ * Get statistics
211
+ */
212
+ getStats() {
213
+ return { ...this.stats };
214
+ }
215
+ /**
216
+ * Force flush any pending writes
217
+ */
218
+ async flush() {
219
+ while (this.isWriting || this.writeQueue.length > 0) {
220
+ await new Promise(resolve => setTimeout(resolve, 10));
221
+ }
222
+ }
223
+ // ============================================================================
224
+ // Private Helper Methods
225
+ // ============================================================================
226
+ /**
227
+ * Read and parse a log file
228
+ */
229
+ readLogFile(filePath) {
230
+ if (!fs.existsSync(filePath))
231
+ return [];
232
+ try {
233
+ const content = fs.readFileSync(filePath, "utf-8");
234
+ const lines = content.trim().split("\n").filter(l => l.length > 0);
235
+ return lines.map(line => JSON.parse(line));
236
+ }
237
+ catch (error) {
238
+ log.warning(`⚠️ Failed to read query log ${filePath}: ${error}`);
239
+ return [];
240
+ }
241
+ }
242
+ /**
243
+ * Get all queries from all log files
244
+ */
245
+ async getAllQueries() {
246
+ const logFiles = this.getLogFiles();
247
+ const allQueries = [];
248
+ for (const file of logFiles) {
249
+ const filePath = path.join(this.config.logDir, file);
250
+ const entries = this.readLogFile(filePath);
251
+ allQueries.push(...entries);
252
+ }
253
+ return allQueries;
254
+ }
255
+ /**
256
+ * Filter queries across all log files
257
+ */
258
+ async filterQueries(predicate) {
259
+ const allQueries = await this.getAllQueries();
260
+ return allQueries.filter(predicate);
261
+ }
262
+ }
263
+ /**
264
+ * Global query logger instance
265
+ */
266
+ let globalQueryLogger = null;
267
+ /**
268
+ * Get or create the global query logger
269
+ */
270
+ export function getQueryLogger() {
271
+ if (!globalQueryLogger) {
272
+ globalQueryLogger = new QueryLogger();
273
+ }
274
+ return globalQueryLogger;
275
+ }
276
+ /**
277
+ * Convenience function for quick query logging
278
+ */
279
+ export async function logQuery(entry) {
280
+ return getQueryLogger().logQuery(entry);
281
+ }
282
+ //# sourceMappingURL=query-logger.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"query-logger.js","sourceRoot":"","sources":["../../src/logging/query-logger.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAAE,MAAM,IAAI,CAAC;AACpB,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,MAAM,MAAM,QAAQ,CAAC;AAC5B,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AACtC,OAAO,EACL,WAAW,EACX,gBAAgB,EAChB,gBAAgB,GACjB,MAAM,8BAA8B,CAAC;AACtC,OAAO,EAAE,GAAG,EAAE,MAAM,oBAAoB,CAAC;AA4CzC;;GAEG;AACH,SAAS,oBAAoB;IAC3B,OAAO;QACL,OAAO,EAAE,OAAO,CAAC,GAAG,CAAC,uBAAuB,KAAK,OAAO;QACxD,MAAM,EAAE,OAAO,CAAC,GAAG,CAAC,mBAAmB,IAAI,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,YAAY,CAAC;QAClF,aAAa,EAAE,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,8BAA8B,IAAI,IAAI,EAAE,EAAE,CAAC;KAChF,CAAC;AACJ,CAAC;AAED;;GAEG;AACH,SAAS,eAAe;IACtB,OAAO,MAAM,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;AAC/C,CAAC;AAED;;;;GAIG;AACH,MAAM,OAAO,WAAW;IACd,MAAM,CAAoB;IAC1B,cAAc,GAAW,EAAE,CAAC;IAC5B,UAAU,GAAoB,EAAE,CAAC;IACjC,SAAS,GAAY,KAAK,CAAC;IAC3B,KAAK,GAAG;QACd,YAAY,EAAE,CAAC;QACf,kBAAkB,EAAE,CAAC;KACtB,CAAC;IAEF,YAAY,MAAmC;QAC7C,IAAI,CAAC,MAAM,GAAG,EAAE,GAAG,oBAAoB,EAAE,EAAE,GAAG,MAAM,EAAE,CAAC;QAEvD,IAAI,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;YACxB,IAAI,CAAC,kBAAkB,EAAE,CAAC;YAC1B,IAAI,CAAC,iBAAiB,EAAE,CAAC;YACzB,IAAI,CAAC,YAAY,EAAE,CAAC;QACtB,CAAC;IACH,CAAC;IAED;;OAEG;IACK,kBAAkB;QACxB,WAAW,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,UAAU,CAAC,CAAC;IAC/D,CAAC;IAED;;OAEG;IACK,iBAAiB;QACvB,MAAM,KAAK,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;QACrD,IAAI,CAAC,cAAc,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,aAAa,KAAK,QAAQ,CAAC,CAAC;IAClF,CAAC;IAED;;OAEG;IACK,YAAY;QAClB,IAAI,CAAC;YACH,MAAM,KAAK,GAAG,EAAE,CAAC,WAAW,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;YACjD,MAAM,UAAU,GAAG,IAAI,IAAI,EAAE,CAAC;YAC9B,UAAU,CAAC,OAAO,CAAC,UAAU,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC;YAErE,IAAI,YAAY,GAAG,CAAC,CAAC;YACrB,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;gBACzB,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC;oBAAE,SAAS;gBAEzE,0DAA0D;gBAC1D,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;gBACnC,MAAM,QAAQ,GAAG,IAAI,IAAI,CAAC,OAAO,CAAC,CAAC;gBAEnC,IAAI,QAAQ,GAAG,UAAU,EAAE,CAAC;oBAC1B,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC,CAAC;oBACnD,YAAY,EAAE,CAAC;gBACjB,CAAC;YACH,CAAC;YAED,IAAI,YAAY,GAAG,CAAC,EAAE,CAAC;gBACrB,GAAG,CAAC,IAAI,CAAC,eAAe,YAAY,sBAAsB,CAAC,CAAC;YAC9D,CAAC;QACH,CAAC;QAAC,MAAM,CAAC;YACP,wBAAwB;QAC1B,CAAC;IACH,CAAC;IAED;;OAEG;IACK,KAAK,CAAC,UAAU,CAAC,KAAoB;QAC3C,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAE5B,IAAI,IAAI,CAAC,SAAS;YAAE,OAAO;QAE3B,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC;QAEtB,IAAI,CAAC;YACH,OAAO,IAAI,CAAC,UAAU,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBAClC,MAAM,KAAK,GAAG,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;gBAC7C,MAAM,KAAK,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;gBAElE,+CAA+C;gBAC/C,MAAM,KAAK,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;gBACrD,MAAM,YAAY,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,aAAa,KAAK,QAAQ,CAAC,CAAC;gBAC/E,IAAI,IAAI,CAAC,cAAc,KAAK,YAAY,EAAE,CAAC;oBACzC,IAAI,CAAC,cAAc,GAAG,YAAY,CAAC;gBACrC,CAAC;gBAED,gBAAgB,CAAC,IAAI,CAAC,cAAc,EAAE,KAAK,EAAE,gBAAgB,CAAC,gBAAgB,CAAC,CAAC;YAClF,CAAC;QACH,CAAC;gBAAS,CAAC;YACT,IAAI,CAAC,SAAS,GAAG,KAAK,CAAC;QACzB,CAAC;IACH,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,QAAQ,CAAC,KAAmD;QAChE,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO;YAAE,OAAO,EAAE,CAAC;QAEpC,MAAM,OAAO,GAAG,eAAe,EAAE,CAAC;QAClC,MAAM,SAAS,GAAkB;YAC/B,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;YACnC,OAAO;YACP,GAAG,KAAK;SACT,CAAC;QAEF,IAAI,CAAC,KAAK,CAAC,YAAY,EAAE,CAAC;QAC1B,IAAI,CAAC,KAAK,CAAC,kBAAkB,EAAE,CAAC;QAEhC,MAAM,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC;QAEjC,GAAG,CAAC,KAAK,CAAC,mBAAmB,OAAO,KAAK,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,CAAC;QAE5E,OAAO,OAAO,CAAC;IACjB,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,oBAAoB,CAAC,SAAiB;QAC1C,OAAO,IAAI,CAAC,aAAa,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,CAAC,SAAS,KAAK,SAAS,CAAC,CAAC;IACpE,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,qBAAqB,CAAC,WAAmB;QAC7C,OAAO,IAAI,CAAC,aAAa,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,CAAC,WAAW,KAAK,WAAW,CAAC,CAAC;IACxE,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,uBAAuB,CAAC,UAAkB;QAC9C,OAAO,IAAI,CAAC,aAAa,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,CAAC,UAAU,KAAK,UAAU,CAAC,CAAC;IACtE,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,iBAAiB,CAAC,IAAY;QAClC,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,aAAa,IAAI,QAAQ,CAAC,CAAC;QACzE,OAAO,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC;IACnC,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,gBAAgB,CAAC,QAAgB,EAAE;QACvC,MAAM,UAAU,GAAG,MAAM,IAAI,CAAC,aAAa,EAAE,CAAC;QAC9C,mDAAmD;QACnD,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,OAAO,EAAE,GAAG,IAAI,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC;QAC7F,OAAO,UAAU,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC;IACpC,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,aAAa,CAAC,OAAe,EAAE,OAA4B;QAC/D,MAAM,KAAK,GAAG,OAAO,EAAE,KAAK,IAAI,GAAG,CAAC;QACpC,MAAM,aAAa,GAAG,OAAO,EAAE,aAAa,IAAI,KAAK,CAAC;QAEtD,MAAM,aAAa,GAAG,aAAa,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,WAAW,EAAE,CAAC;QAEtE,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,aAAa,CAAC,KAAK,CAAC,EAAE;YAC/C,MAAM,QAAQ,GAAG,aAAa,CAAC,CAAC,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,QAAQ,CAAC,WAAW,EAAE,CAAC;YAC/E,MAAM,MAAM,GAAG,aAAa,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,WAAW,EAAE,CAAC;YACzE,OAAO,QAAQ,CAAC,QAAQ,CAAC,aAAa,CAAC,IAAI,MAAM,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAC;QAC5E,CAAC,CAAC,CAAC;QAEH,+BAA+B;QAC/B,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,OAAO,EAAE,GAAG,IAAI,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC;QAE1F,OAAO,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC;IACjC,CAAC;IAED;;OAEG;IACH,WAAW;QACT,IAAI,CAAC;YACH,MAAM,KAAK,GAAG,EAAE,CAAC,WAAW,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;YACjD,OAAO,KAAK;iBACT,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;iBAC/D,IAAI,EAAE;iBACN,OAAO,EAAE,CAAC,CAAC,oBAAoB;QACpC,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,EAAE,CAAC;QACZ,CAAC;IACH,CAAC;IAED;;OAEG;IACH,QAAQ;QACN,OAAO,EAAE,GAAG,IAAI,CAAC,KAAK,EAAE,CAAC;IAC3B,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,KAAK;QACT,OAAO,IAAI,CAAC,SAAS,IAAI,IAAI,CAAC,UAAU,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACpD,MAAM,IAAI,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC;QACxD,CAAC;IACH,CAAC;IAED,+EAA+E;IAC/E,yBAAyB;IACzB,+EAA+E;IAE/E;;OAEG;IACK,WAAW,CAAC,QAAgB;QAClC,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC;YAAE,OAAO,EAAE,CAAC;QAExC,IAAI,CAAC;YACH,MAAM,OAAO,GAAG,EAAE,CAAC,YAAY,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;YACnD,MAAM,KAAK,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;YACnE,OAAO,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAkB,CAAC,CAAC;QAC9D,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,GAAG,CAAC,OAAO,CAAC,+BAA+B,QAAQ,KAAK,KAAK,EAAE,CAAC,CAAC;YACjE,OAAO,EAAE,CAAC;QACZ,CAAC;IACH,CAAC;IAED;;OAEG;IACK,KAAK,CAAC,aAAa;QACzB,MAAM,QAAQ,GAAG,IAAI,CAAC,WAAW,EAAE,CAAC;QACpC,MAAM,UAAU,GAAoB,EAAE,CAAC;QAEvC,KAAK,MAAM,IAAI,IAAI,QAAQ,EAAE,CAAC;YAC5B,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;YACrD,MAAM,OAAO,GAAG,IAAI,CAAC,WAAW,CAAC,QAAQ,CAAC,CAAC;YAC3C,UAAU,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,CAAC;QAC9B,CAAC;QAED,OAAO,UAAU,CAAC;IACpB,CAAC;IAED;;OAEG;IACK,KAAK,CAAC,aAAa,CACzB,SAA4C;QAE5C,MAAM,UAAU,GAAG,MAAM,IAAI,CAAC,aAAa,EAAE,CAAC;QAC9C,OAAO,UAAU,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;IACtC,CAAC;CACF;AAED;;GAEG;AACH,IAAI,iBAAiB,GAAuB,IAAI,CAAC;AAEjD;;GAEG;AACH,MAAM,UAAU,cAAc;IAC5B,IAAI,CAAC,iBAAiB,EAAE,CAAC;QACvB,iBAAiB,GAAG,IAAI,WAAW,EAAE,CAAC;IACxC,CAAC;IACD,OAAO,iBAAiB,CAAC;AAC3B,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,QAAQ,CAC5B,KAAmD;IAEnD,OAAO,cAAc,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;AAC1C,CAAC"}
@@ -48,6 +48,24 @@ export declare class QuotaManager {
48
48
  * Extract source limit from source dialog (e.g., "0/300")
49
49
  */
50
50
  extractSourceLimitFromDialog(page: Page): Promise<number | null>;
51
+ /**
52
+ * Extract query usage from NotebookLM UI
53
+ *
54
+ * Looks for patterns like:
55
+ * - "X/50 queries" or "X of 50 queries"
56
+ * - "X queries remaining"
57
+ * - Usage indicators in settings/account area
58
+ *
59
+ * Returns { used, limit } or null if not found
60
+ */
61
+ extractQueryUsageFromUI(page: Page): Promise<{
62
+ used: number;
63
+ limit: number;
64
+ } | null>;
65
+ /**
66
+ * Check for rate limit error message on page
67
+ */
68
+ checkForRateLimitError(page: Page): Promise<boolean>;
51
69
  /**
52
70
  * Count notebooks from homepage
53
71
  */
@@ -55,7 +73,14 @@ export declare class QuotaManager {
55
73
  /**
56
74
  * Update quota from UI scraping
57
75
  */
58
- updateFromUI(page: Page): Promise<void>;
76
+ updateFromUI(page: Page): Promise<{
77
+ tier: LicenseTier;
78
+ queryUsageFromGoogle: {
79
+ used: number;
80
+ limit: number;
81
+ } | null;
82
+ rateLimitDetected: boolean;
83
+ }>;
59
84
  /**
60
85
  * Manually set tier (for user override)
61
86
  */
@@ -77,9 +102,23 @@ export declare class QuotaManager {
77
102
  */
78
103
  incrementNotebookCount(): void;
79
104
  /**
80
- * Increment query count
105
+ * Increment query count (synchronous, for backwards compatibility)
106
+ * Note: For concurrent safety, use incrementQueryCountAtomic() instead
81
107
  */
82
108
  incrementQueryCount(): void;
109
+ /**
110
+ * Increment query count atomically with file locking
111
+ *
112
+ * This method is safe for concurrent access from multiple processes/sessions.
113
+ * It reloads settings from disk before incrementing to ensure accuracy.
114
+ */
115
+ incrementQueryCountAtomic(): Promise<void>;
116
+ /**
117
+ * Refresh settings from disk with file locking
118
+ *
119
+ * Use this to ensure you have the latest quota state from disk.
120
+ */
121
+ refreshSettings(): Promise<QuotaSettings>;
83
122
  /**
84
123
  * Check if can create notebook
85
124
  */
@@ -120,6 +159,31 @@ export declare class QuotaManager {
120
159
  percent: number;
121
160
  };
122
161
  };
162
+ /**
163
+ * Get detailed quota status with remaining counts, warnings, and stop signals
164
+ * Used to provide visibility to users about when to stop querying for the day
165
+ */
166
+ getDetailedStatus(): {
167
+ tier: LicenseTier;
168
+ queries: {
169
+ used: number;
170
+ limit: number;
171
+ remaining: number;
172
+ percentUsed: number;
173
+ shouldStop: boolean;
174
+ resetTime: string;
175
+ };
176
+ notebooks: {
177
+ used: number;
178
+ limit: number;
179
+ remaining: number;
180
+ percentUsed: number;
181
+ };
182
+ sources: {
183
+ limit: number;
184
+ };
185
+ warnings: string[];
186
+ };
123
187
  }
124
188
  export declare function getQuotaManager(): QuotaManager;
125
189
  //# sourceMappingURL=quota-manager.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"quota-manager.d.ts","sourceRoot":"","sources":["../../src/quota/quota-manager.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAMvC,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG,KAAK,GAAG,OAAO,GAAG,SAAS,CAAC;AAE/D,MAAM,WAAW,WAAW;IAC1B,SAAS,EAAE,MAAM,CAAC;IAClB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,cAAc,EAAE,MAAM,CAAC;IACvB,aAAa,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,UAAU;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,gBAAgB,EAAE,MAAM,CAAC;IACzB,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,WAAW,CAAC;IAClB,MAAM,EAAE,WAAW,CAAC;IACpB,KAAK,EAAE,UAAU,CAAC;IAClB,YAAY,EAAE,OAAO,CAAC;CACvB;AAgCD,qBAAa,YAAY;IACvB,OAAO,CAAC,QAAQ,CAAgB;IAChC,OAAO,CAAC,YAAY,CAAS;;IAO7B;;OAEG;IACH,OAAO,CAAC,YAAY;IAgBpB;;OAEG;IACH,OAAO,CAAC,kBAAkB;IAc1B;;OAEG;IACH,OAAO,CAAC,YAAY;IAiBpB;;;OAGG;IACG,kBAAkB,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,WAAW,CAAC;IA0C1D;;OAEG;IACG,4BAA4B,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAetE;;OAEG;IACG,sBAAsB,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,MAAM,CAAC;IAiBzD;;OAEG;IACG,YAAY,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;IA6B7C;;OAEG;IACH,OAAO,CAAC,IAAI,EAAE,WAAW,GAAG,IAAI;IAQhC;;OAEG;IACH,WAAW,IAAI,aAAa;IAI5B;;OAEG;IACH,SAAS,IAAI,WAAW;IAIxB;;OAEG;IACH,QAAQ,IAAI,UAAU;IAItB;;OAEG;IACH,sBAAsB,IAAI,IAAI;IAM9B;;OAEG;IACH,mBAAmB,IAAI,IAAI;IAc3B;;OAEG;IACH,iBAAiB,IAAI;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE;IAmB1D;;OAEG;IACH,YAAY,CAAC,kBAAkB,EAAE,MAAM,GAAG;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE;IAa/E;;OAEG;IACH,YAAY,IAAI;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE;IA2BrD;;OAEG;IACH,SAAS,IAAI;QACX,IAAI,EAAE,WAAW,CAAC;QAClB,SAAS,EAAE;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,KAAK,EAAE,MAAM,CAAC;YAAC,OAAO,EAAE,MAAM,CAAA;SAAE,CAAC;QAC5D,OAAO,EAAE;YAAE,KAAK,EAAE,MAAM,CAAA;SAAE,CAAC;QAC3B,OAAO,EAAE;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,KAAK,EAAE,MAAM,CAAC;YAAC,OAAO,EAAE,MAAM,CAAA;SAAE,CAAC;KAC3D;CAoBF;AAKD,wBAAgB,eAAe,IAAI,YAAY,CAK9C"}
1
+ {"version":3,"file":"quota-manager.d.ts","sourceRoot":"","sources":["../../src/quota/quota-manager.ts"],"names":[],"mappings":"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"}
@@ -5,6 +5,7 @@
5
5
  */
6
6
  import { log } from "../utils/logger.js";
7
7
  import { CONFIG } from "../config.js";
8
+ import { withLock } from "../utils/file-lock.js";
8
9
  import fs from "fs";
9
10
  import path from "path";
10
11
  // Known limits by tier (based on NotebookLM documentation Dec 2025)
@@ -147,6 +148,100 @@ export class QuotaManager {
147
148
  });
148
149
  return limitInfo;
149
150
  }
151
+ /**
152
+ * Extract query usage from NotebookLM UI
153
+ *
154
+ * Looks for patterns like:
155
+ * - "X/50 queries" or "X of 50 queries"
156
+ * - "X queries remaining"
157
+ * - Usage indicators in settings/account area
158
+ *
159
+ * Returns { used, limit } or null if not found
160
+ */
161
+ async extractQueryUsageFromUI(page) {
162
+ log.info("🔍 Looking for query usage in UI...");
163
+ const usageInfo = await page.evaluate(() => {
164
+ // @ts-expect-error - DOM types
165
+ const allText = document.body.innerText;
166
+ // Pattern 1: "X/Y queries" or "X / Y queries"
167
+ const slashPattern = allText.match(/(\d+)\s*\/\s*(\d+)\s*quer(?:y|ies)/i);
168
+ if (slashPattern) {
169
+ return {
170
+ used: parseInt(slashPattern[1], 10),
171
+ limit: parseInt(slashPattern[2], 10),
172
+ };
173
+ }
174
+ // Pattern 2: "X of Y queries"
175
+ const ofPattern = allText.match(/(\d+)\s+of\s+(\d+)\s*quer(?:y|ies)/i);
176
+ if (ofPattern) {
177
+ return {
178
+ used: parseInt(ofPattern[1], 10),
179
+ limit: parseInt(ofPattern[2], 10),
180
+ };
181
+ }
182
+ // Pattern 3: "X queries remaining" with known limits
183
+ const remainingPattern = allText.match(/(\d+)\s*quer(?:y|ies)\s*remaining/i);
184
+ if (remainingPattern) {
185
+ const remaining = parseInt(remainingPattern[1], 10);
186
+ // Infer limit from known tiers
187
+ let limit = 50; // default free
188
+ if (remaining > 50)
189
+ limit = 500; // pro
190
+ if (remaining > 500)
191
+ limit = 5000; // ultra
192
+ return {
193
+ used: limit - remaining,
194
+ limit,
195
+ };
196
+ }
197
+ // Pattern 4: "You have used X queries today"
198
+ const usedPattern = allText.match(/(?:used|made)\s*(\d+)\s*quer(?:y|ies)/i);
199
+ if (usedPattern) {
200
+ const used = parseInt(usedPattern[1], 10);
201
+ // Infer tier from usage
202
+ let limit = 50;
203
+ if (used > 50)
204
+ limit = 500;
205
+ if (used > 500)
206
+ limit = 5000;
207
+ return { used, limit };
208
+ }
209
+ // Pattern 5: Look for rate limit message
210
+ const rateLimitPattern = allText.match(/(?:limit|quota)\s*(?:reached|exceeded)/i);
211
+ if (rateLimitPattern) {
212
+ // At limit - try to find the number
213
+ const limitNum = allText.match(/(\d+)\s*(?:daily|per day)/i);
214
+ const limit = limitNum ? parseInt(limitNum[1], 10) : 50;
215
+ return { used: limit, limit };
216
+ }
217
+ return null;
218
+ });
219
+ if (usageInfo) {
220
+ log.info(` Found query usage: ${usageInfo.used}/${usageInfo.limit}`);
221
+ }
222
+ else {
223
+ log.info(" No query usage found in UI");
224
+ }
225
+ return usageInfo;
226
+ }
227
+ /**
228
+ * Check for rate limit error message on page
229
+ */
230
+ async checkForRateLimitError(page) {
231
+ const isRateLimited = await page.evaluate(() => {
232
+ // @ts-expect-error - DOM types
233
+ const allText = document.body.innerText.toLowerCase();
234
+ return (allText.includes("rate limit") ||
235
+ allText.includes("quota exceeded") ||
236
+ allText.includes("too many requests") ||
237
+ allText.includes("daily limit reached") ||
238
+ allText.includes("try again tomorrow"));
239
+ });
240
+ if (isRateLimited) {
241
+ log.warning("⚠️ Rate limit detected in UI!");
242
+ }
243
+ return isRateLimited;
244
+ }
150
245
  /**
151
246
  * Count notebooks from homepage
152
247
  */
@@ -187,9 +282,29 @@ export class QuotaManager {
187
282
  if (sourceLimit) {
188
283
  this.settings.limits.sourcesPerNotebook = sourceLimit;
189
284
  }
285
+ // Try to extract query usage from UI
286
+ const queryUsage = await this.extractQueryUsageFromUI(page);
287
+ if (queryUsage) {
288
+ // Update local tracking with Google's numbers
289
+ this.settings.usage.queriesUsedToday = queryUsage.used;
290
+ this.settings.limits.queriesPerDay = queryUsage.limit;
291
+ this.settings.usage.lastQueryDate = new Date().toISOString().split("T")[0];
292
+ log.info(` Synced query usage from Google: ${queryUsage.used}/${queryUsage.limit}`);
293
+ }
294
+ // Check for rate limit
295
+ const rateLimitDetected = await this.checkForRateLimitError(page);
296
+ if (rateLimitDetected) {
297
+ // Mark as at limit
298
+ this.settings.usage.queriesUsedToday = this.settings.limits.queriesPerDay;
299
+ }
190
300
  this.settings.usage.lastUpdated = new Date().toISOString();
191
301
  this.saveSettings();
192
- log.success(`✅ Quota updated: tier=${this.settings.tier}, notebooks=${this.settings.usage.notebooks}`);
302
+ log.success(`✅ Quota updated: tier=${this.settings.tier}, notebooks=${this.settings.usage.notebooks}, queries=${this.settings.usage.queriesUsedToday}/${this.settings.limits.queriesPerDay}`);
303
+ return {
304
+ tier: this.settings.tier,
305
+ queryUsageFromGoogle: queryUsage,
306
+ rateLimitDetected,
307
+ };
193
308
  }
194
309
  /**
195
310
  * Manually set tier (for user override)
@@ -228,7 +343,8 @@ export class QuotaManager {
228
343
  this.saveSettings();
229
344
  }
230
345
  /**
231
- * Increment query count
346
+ * Increment query count (synchronous, for backwards compatibility)
347
+ * Note: For concurrent safety, use incrementQueryCountAtomic() instead
232
348
  */
233
349
  incrementQueryCount() {
234
350
  const today = new Date().toISOString().split("T")[0];
@@ -241,6 +357,49 @@ export class QuotaManager {
241
357
  this.settings.usage.lastUpdated = new Date().toISOString();
242
358
  this.saveSettings();
243
359
  }
360
+ /**
361
+ * Increment query count atomically with file locking
362
+ *
363
+ * This method is safe for concurrent access from multiple processes/sessions.
364
+ * It reloads settings from disk before incrementing to ensure accuracy.
365
+ */
366
+ async incrementQueryCountAtomic() {
367
+ await withLock(this.settingsPath, async () => {
368
+ // Reload latest settings from disk (another process may have updated)
369
+ this.settings = this.loadSettings();
370
+ const today = new Date().toISOString().split("T")[0];
371
+ // Reset if new day
372
+ if (this.settings.usage.lastQueryDate !== today) {
373
+ this.settings.usage.queriesUsedToday = 0;
374
+ this.settings.usage.lastQueryDate = today;
375
+ }
376
+ this.settings.usage.queriesUsedToday++;
377
+ this.settings.usage.lastUpdated = new Date().toISOString();
378
+ // Save with lock held
379
+ try {
380
+ const dir = path.dirname(this.settingsPath);
381
+ if (!fs.existsSync(dir)) {
382
+ fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
383
+ }
384
+ fs.writeFileSync(this.settingsPath, JSON.stringify(this.settings, null, 2), { mode: 0o600 });
385
+ log.debug(`💾 Quota incremented atomically (${this.settings.usage.queriesUsedToday} queries today)`);
386
+ }
387
+ catch (error) {
388
+ log.error(`❌ Could not save quota settings: ${error}`);
389
+ }
390
+ });
391
+ }
392
+ /**
393
+ * Refresh settings from disk with file locking
394
+ *
395
+ * Use this to ensure you have the latest quota state from disk.
396
+ */
397
+ async refreshSettings() {
398
+ return await withLock(this.settingsPath, async () => {
399
+ this.settings = this.loadSettings();
400
+ return { ...this.settings };
401
+ });
402
+ }
244
403
  /**
245
404
  * Check if can create notebook
246
405
  */
@@ -318,6 +477,65 @@ export class QuotaManager {
318
477
  },
319
478
  };
320
479
  }
480
+ /**
481
+ * Get detailed quota status with remaining counts, warnings, and stop signals
482
+ * Used to provide visibility to users about when to stop querying for the day
483
+ */
484
+ getDetailedStatus() {
485
+ const today = new Date().toISOString().split("T")[0];
486
+ // Reset if new day
487
+ if (this.settings.usage.lastQueryDate !== today) {
488
+ this.settings.usage.queriesUsedToday = 0;
489
+ this.settings.usage.lastQueryDate = today;
490
+ }
491
+ const { tier, limits, usage } = this.settings;
492
+ const queriesRemaining = limits.queriesPerDay - usage.queriesUsedToday;
493
+ const queriesPercentUsed = Math.round((usage.queriesUsedToday / limits.queriesPerDay) * 100);
494
+ const notebooksRemaining = limits.notebooks - usage.notebooks;
495
+ const notebooksPercentUsed = Math.round((usage.notebooks / limits.notebooks) * 100);
496
+ // Calculate next reset time (midnight local time)
497
+ const tomorrow = new Date();
498
+ tomorrow.setDate(tomorrow.getDate() + 1);
499
+ tomorrow.setHours(0, 0, 0, 0);
500
+ // Build warnings list
501
+ const warnings = [];
502
+ if (queriesRemaining <= 0) {
503
+ warnings.push(`CRITICAL: Daily query limit reached (${usage.queriesUsedToday}/${limits.queriesPerDay}). Wait until tomorrow or upgrade your plan.`);
504
+ }
505
+ else if (queriesRemaining <= 5) {
506
+ warnings.push(`CRITICAL: Only ${queriesRemaining} queries remaining today! Consider stopping soon.`);
507
+ }
508
+ else if (queriesRemaining <= 10) {
509
+ warnings.push(`WARNING: Only ${queriesRemaining} queries remaining today.`);
510
+ }
511
+ else if (queriesPercentUsed >= 80) {
512
+ warnings.push(`INFO: ${queriesPercentUsed}% of daily queries used (${queriesRemaining} remaining).`);
513
+ }
514
+ if (notebooksRemaining <= 5) {
515
+ warnings.push(`WARNING: Only ${notebooksRemaining} notebook slots remaining.`);
516
+ }
517
+ return {
518
+ tier,
519
+ queries: {
520
+ used: usage.queriesUsedToday,
521
+ limit: limits.queriesPerDay,
522
+ remaining: queriesRemaining,
523
+ percentUsed: queriesPercentUsed,
524
+ shouldStop: queriesRemaining <= 5,
525
+ resetTime: tomorrow.toISOString(),
526
+ },
527
+ notebooks: {
528
+ used: usage.notebooks,
529
+ limit: limits.notebooks,
530
+ remaining: notebooksRemaining,
531
+ percentUsed: notebooksPercentUsed,
532
+ },
533
+ sources: {
534
+ limit: limits.sourcesPerNotebook,
535
+ },
536
+ warnings,
537
+ };
538
+ }
321
539
  }
322
540
  // Singleton instance
323
541
  let quotaManagerInstance = null;