@cryptoquant_official/mcp 0.0.1

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 (55) hide show
  1. package/README.md +88 -0
  2. package/dist/auth/server.d.ts +3 -0
  3. package/dist/auth/server.d.ts.map +1 -0
  4. package/dist/auth/server.js +405 -0
  5. package/dist/auth/server.js.map +1 -0
  6. package/dist/auth/storage.d.ts +11 -0
  7. package/dist/auth/storage.d.ts.map +1 -0
  8. package/dist/auth/storage.js +53 -0
  9. package/dist/auth/storage.js.map +1 -0
  10. package/dist/cache/storage.d.ts +47 -0
  11. package/dist/cache/storage.d.ts.map +1 -0
  12. package/dist/cache/storage.js +140 -0
  13. package/dist/cache/storage.js.map +1 -0
  14. package/dist/cache/summary.d.ts +16 -0
  15. package/dist/cache/summary.d.ts.map +1 -0
  16. package/dist/cache/summary.js +85 -0
  17. package/dist/cache/summary.js.map +1 -0
  18. package/dist/cache/types.d.ts +76 -0
  19. package/dist/cache/types.d.ts.map +1 -0
  20. package/dist/cache/types.js +6 -0
  21. package/dist/cache/types.js.map +1 -0
  22. package/dist/config.d.ts +18 -0
  23. package/dist/config.d.ts.map +1 -0
  24. package/dist/config.js +23 -0
  25. package/dist/config.js.map +1 -0
  26. package/dist/data/metrics.toon +58 -0
  27. package/dist/discovery.d.ts +70 -0
  28. package/dist/discovery.d.ts.map +1 -0
  29. package/dist/discovery.js +159 -0
  30. package/dist/discovery.js.map +1 -0
  31. package/dist/index.d.ts +3 -0
  32. package/dist/index.d.ts.map +1 -0
  33. package/dist/index.js +23 -0
  34. package/dist/index.js.map +1 -0
  35. package/dist/permissions.d.ts +36 -0
  36. package/dist/permissions.d.ts.map +1 -0
  37. package/dist/permissions.js +135 -0
  38. package/dist/permissions.js.map +1 -0
  39. package/dist/plan-limits.d.ts +71 -0
  40. package/dist/plan-limits.d.ts.map +1 -0
  41. package/dist/plan-limits.js +400 -0
  42. package/dist/plan-limits.js.map +1 -0
  43. package/dist/tools/auth.d.ts +3 -0
  44. package/dist/tools/auth.d.ts.map +1 -0
  45. package/dist/tools/auth.js +157 -0
  46. package/dist/tools/auth.js.map +1 -0
  47. package/dist/tools/core.d.ts +3 -0
  48. package/dist/tools/core.d.ts.map +1 -0
  49. package/dist/tools/core.js +472 -0
  50. package/dist/tools/core.js.map +1 -0
  51. package/dist/utils.d.ts +32 -0
  52. package/dist/utils.d.ts.map +1 -0
  53. package/dist/utils.js +42 -0
  54. package/dist/utils.js.map +1 -0
  55. package/package.json +62 -0
package/README.md ADDED
@@ -0,0 +1,88 @@
1
+ # @cryptoquant_official/mcp
2
+
3
+ CryptoQuant MCP Server for on-chain analytics in Claude and AI coding agents.
4
+
5
+ ## Installation
6
+
7
+ ### Claude Code / Claude Desktop
8
+
9
+ Add to your MCP configuration:
10
+
11
+ **Claude Code** (`~/.claude/mcp.json` or project `.mcp.json`):
12
+ ```json
13
+ {
14
+ "mcpServers": {
15
+ "cryptoquant": {
16
+ "command": "npx",
17
+ "args": ["-y", "@cryptoquant_official/mcp"]
18
+ }
19
+ }
20
+ }
21
+ ```
22
+
23
+ **Claude Desktop** (`~/Library/Application Support/Claude/claude_desktop_config.json`):
24
+ ```json
25
+ {
26
+ "mcpServers": {
27
+ "cryptoquant": {
28
+ "command": "npx",
29
+ "args": ["-y", "@cryptoquant_official/mcp"]
30
+ }
31
+ }
32
+ }
33
+ ```
34
+
35
+ Then restart your app.
36
+
37
+ ### Other MCP-Compatible Apps (Cursor, etc.)
38
+
39
+ Add the same configuration to your app's MCP settings file.
40
+
41
+ ## Authentication
42
+
43
+ On first use, call `initialize()` - a browser window opens automatically for API key setup.
44
+
45
+ Your API key is saved securely to `~/.cryptoquant/credentials`.
46
+
47
+ **Get your API key**: [cryptoquant.com](https://cryptoquant.com) → Dashboard → API
48
+
49
+ ## Available Tools
50
+
51
+ | Tool | Description |
52
+ |------|-------------|
53
+ | `initialize` | Start session, auto-opens browser for API key |
54
+ | `discover_endpoints` | Browse 245+ available API endpoints |
55
+ | `get_endpoint_info` | Get endpoint parameter details |
56
+ | `query_data` | Query raw on-chain data |
57
+ | `describe_metric` | Get metric descriptions and thresholds |
58
+ | `list_assets` | List supported assets (BTC, ETH, etc.) |
59
+ | `reset_session` | Clear session / switch accounts |
60
+
61
+ ## Supported Assets
62
+
63
+ BTC, ETH, ALT, Stablecoin, ERC20, TRX, XRP
64
+
65
+ ## Example Usage
66
+
67
+ ```
68
+ User: "Is Bitcoin overvalued?"
69
+
70
+ Claude: Let me check the MVRV ratio...
71
+ → Calls query_data({ endpoint: "/v1/btc/market-data/mvrv" })
72
+ → Returns analysis with current valuation status
73
+ ```
74
+
75
+ ## Requirements
76
+
77
+ - Node.js 18+
78
+ - CryptoQuant API key
79
+
80
+ ## Links
81
+
82
+ - [Full Documentation](https://github.com/CryptoQuantOfficial/cryptoquant-skills)
83
+ - [CryptoQuant](https://cryptoquant.com)
84
+ - [API Docs](https://docs.cryptoquant.com)
85
+
86
+ ## License
87
+
88
+ MIT
@@ -0,0 +1,3 @@
1
+ export declare function startAuthServer(): Promise<string>;
2
+ export declare function getAuthUrl(): string;
3
+ //# sourceMappingURL=server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/auth/server.ts"],"names":[],"mappings":"AAoTA,wBAAgB,eAAe,IAAI,OAAO,CAAC,MAAM,CAAC,CAgHjD;AAED,wBAAgB,UAAU,IAAI,MAAM,CAEnC"}
@@ -0,0 +1,405 @@
1
+ import { createServer } from "http";
2
+ import { exec } from "child_process";
3
+ import { platform } from "os";
4
+ import { saveApiKey } from "./storage.js";
5
+ import { getApiBaseUrl } from "../config.js";
6
+ const AUTH_PORT = 9876;
7
+ const AUTH_TIMEOUT_MS = 5 * 60 * 1000;
8
+ const AUTH_PAGE_HTML = `
9
+ <!DOCTYPE html>
10
+ <html lang="en">
11
+ <head>
12
+ <meta charset="UTF-8">
13
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
14
+ <title>CryptoQuant - API Key Setup</title>
15
+ <style>
16
+ * { box-sizing: border-box; margin: 0; padding: 0; }
17
+ body {
18
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
19
+ background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
20
+ min-height: 100vh;
21
+ display: flex;
22
+ align-items: center;
23
+ justify-content: center;
24
+ padding: 20px;
25
+ }
26
+ .container {
27
+ background: #fff;
28
+ padding: 40px;
29
+ border-radius: 16px;
30
+ box-shadow: 0 20px 60px rgba(0,0,0,0.3);
31
+ max-width: 460px;
32
+ width: 100%;
33
+ }
34
+ .logo {
35
+ font-size: 40px;
36
+ margin-bottom: 8px;
37
+ }
38
+ h1 {
39
+ color: #1a1a2e;
40
+ font-size: 24px;
41
+ margin-bottom: 8px;
42
+ }
43
+ .subtitle {
44
+ color: #666;
45
+ margin-bottom: 24px;
46
+ font-size: 14px;
47
+ }
48
+ .link-box {
49
+ background: #f5f7fa;
50
+ padding: 12px 16px;
51
+ border-radius: 8px;
52
+ margin-bottom: 24px;
53
+ }
54
+ .link-box p {
55
+ color: #666;
56
+ font-size: 13px;
57
+ margin-bottom: 6px;
58
+ }
59
+ .link-box a {
60
+ color: #0066cc;
61
+ text-decoration: none;
62
+ font-weight: 500;
63
+ word-break: break-all;
64
+ }
65
+ .link-box a:hover { text-decoration: underline; }
66
+ .input-group {
67
+ margin-bottom: 20px;
68
+ }
69
+ label {
70
+ display: block;
71
+ color: #333;
72
+ font-size: 14px;
73
+ font-weight: 500;
74
+ margin-bottom: 8px;
75
+ }
76
+ input[type="password"] {
77
+ width: 100%;
78
+ padding: 14px 16px;
79
+ border: 2px solid #e1e5eb;
80
+ border-radius: 8px;
81
+ font-size: 15px;
82
+ font-family: monospace;
83
+ transition: border-color 0.2s, box-shadow 0.2s;
84
+ }
85
+ input[type="password"]:focus {
86
+ outline: none;
87
+ border-color: #0066cc;
88
+ box-shadow: 0 0 0 3px rgba(0,102,204,0.1);
89
+ }
90
+ button {
91
+ width: 100%;
92
+ padding: 14px;
93
+ background: linear-gradient(135deg, #0066cc 0%, #0052a3 100%);
94
+ color: white;
95
+ border: none;
96
+ border-radius: 8px;
97
+ font-size: 16px;
98
+ font-weight: 600;
99
+ cursor: pointer;
100
+ transition: transform 0.1s, box-shadow 0.2s;
101
+ }
102
+ button:hover {
103
+ transform: translateY(-1px);
104
+ box-shadow: 0 4px 12px rgba(0,102,204,0.4);
105
+ }
106
+ button:active { transform: translateY(0); }
107
+ button:disabled {
108
+ background: #ccc;
109
+ cursor: not-allowed;
110
+ transform: none;
111
+ box-shadow: none;
112
+ }
113
+ .error {
114
+ background: #fff5f5;
115
+ color: #c53030;
116
+ padding: 12px 16px;
117
+ border-radius: 8px;
118
+ margin-top: 16px;
119
+ font-size: 14px;
120
+ display: none;
121
+ }
122
+ .error.show { display: block; }
123
+ .success-container {
124
+ text-align: center;
125
+ padding: 20px 0;
126
+ }
127
+ .success-icon {
128
+ font-size: 64px;
129
+ margin-bottom: 16px;
130
+ }
131
+ .success-container h2 {
132
+ color: #1a1a2e;
133
+ margin-bottom: 8px;
134
+ }
135
+ .success-container p {
136
+ color: #666;
137
+ font-size: 15px;
138
+ }
139
+ .spinner {
140
+ display: inline-block;
141
+ width: 16px;
142
+ height: 16px;
143
+ border: 2px solid #fff;
144
+ border-top-color: transparent;
145
+ border-radius: 50%;
146
+ animation: spin 0.8s linear infinite;
147
+ margin-right: 8px;
148
+ vertical-align: middle;
149
+ }
150
+ @keyframes spin { to { transform: rotate(360deg); } }
151
+ </style>
152
+ </head>
153
+ <body>
154
+ <div class="container" id="authForm">
155
+ <div class="logo">&#128272;</div>
156
+ <h1>CryptoQuant API Key</h1>
157
+ <p class="subtitle">Connect your CryptoQuant account to Claude</p>
158
+
159
+ <div class="link-box">
160
+ <p>Get your API key at:</p>
161
+ <a href="https://cryptoquant.com/settings/api" target="_blank" rel="noopener">
162
+ cryptoquant.com/settings/api
163
+ </a>
164
+ </div>
165
+
166
+ <form id="form">
167
+ <div class="input-group">
168
+ <label for="apiKey">API Key</label>
169
+ <input
170
+ type="password"
171
+ id="apiKey"
172
+ placeholder="cq_xxxxxxxxxxxxxxxxxx"
173
+ required
174
+ autocomplete="off"
175
+ spellcheck="false"
176
+ />
177
+ </div>
178
+ <button type="submit" id="submitBtn">
179
+ <span id="btnText">Save & Connect</span>
180
+ </button>
181
+ </form>
182
+ <div id="error" class="error"></div>
183
+ </div>
184
+
185
+ <div class="container success-container" id="successView" style="display:none;">
186
+ <div class="success-icon">&#9989;</div>
187
+ <h2>Connected!</h2>
188
+ <p>Your API key has been saved securely.<br>You can close this tab and return to Claude.</p>
189
+ </div>
190
+
191
+ <script>
192
+ const form = document.getElementById('form');
193
+ const apiKeyInput = document.getElementById('apiKey');
194
+ const submitBtn = document.getElementById('submitBtn');
195
+ const btnText = document.getElementById('btnText');
196
+ const errorDiv = document.getElementById('error');
197
+ const authForm = document.getElementById('authForm');
198
+ const successView = document.getElementById('successView');
199
+
200
+ form.addEventListener('submit', async (e) => {
201
+ e.preventDefault();
202
+
203
+ const apiKey = apiKeyInput.value.trim();
204
+ if (!apiKey) return;
205
+
206
+ // Show loading state
207
+ submitBtn.disabled = true;
208
+ btnText.innerHTML = '<span class="spinner"></span>Validating...';
209
+ errorDiv.classList.remove('show');
210
+
211
+ try {
212
+ const res = await fetch('/auth/save', {
213
+ method: 'POST',
214
+ headers: { 'Content-Type': 'application/json' },
215
+ body: JSON.stringify({ api_key: apiKey })
216
+ });
217
+
218
+ const data = await res.json();
219
+
220
+ if (data.success) {
221
+ authForm.style.display = 'none';
222
+ successView.style.display = 'block';
223
+ } else {
224
+ errorDiv.textContent = data.error || 'Invalid API key. Please check and try again.';
225
+ errorDiv.classList.add('show');
226
+ submitBtn.disabled = false;
227
+ btnText.textContent = 'Save & Connect';
228
+ }
229
+ } catch (err) {
230
+ errorDiv.textContent = 'Connection error. Please try again.';
231
+ errorDiv.classList.add('show');
232
+ submitBtn.disabled = false;
233
+ btnText.textContent = 'Save & Connect';
234
+ }
235
+ });
236
+
237
+ // Focus input on load
238
+ apiKeyInput.focus();
239
+ </script>
240
+ </body>
241
+ </html>
242
+ `;
243
+ async function validateApiKey(apiKey) {
244
+ try {
245
+ const res = await fetch(`${getApiBaseUrl()}/v1/discovery/endpoints?source=mcp`, {
246
+ method: "GET",
247
+ headers: { Authorization: `Bearer ${apiKey}` },
248
+ });
249
+ return res.ok;
250
+ }
251
+ catch {
252
+ return false;
253
+ }
254
+ }
255
+ function getBrowserCommand(url) {
256
+ const plat = platform();
257
+ switch (plat) {
258
+ case "darwin":
259
+ return `open "${url}"`;
260
+ case "win32":
261
+ return `start "" "${url}"`;
262
+ default:
263
+ return `xdg-open "${url}"`;
264
+ }
265
+ }
266
+ function openBrowser(url) {
267
+ return new Promise((resolve, reject) => {
268
+ const cmd = getBrowserCommand(url);
269
+ exec(cmd, (error) => {
270
+ if (error) {
271
+ reject(new Error(`Failed to open browser: ${error.message}\nPlease open manually: ${url}`));
272
+ }
273
+ else {
274
+ resolve();
275
+ }
276
+ });
277
+ });
278
+ }
279
+ function sendJson(res, status, data) {
280
+ res.writeHead(status, { "Content-Type": "application/json" });
281
+ res.end(JSON.stringify(data));
282
+ }
283
+ const RATE_LIMIT_WINDOW_MS = 60 * 1000;
284
+ const RATE_LIMIT_MAX_ATTEMPTS = 5;
285
+ const rateLimitMap = new Map();
286
+ function checkRateLimit(ip) {
287
+ const now = Date.now();
288
+ const entry = rateLimitMap.get(ip);
289
+ if (!entry || now > entry.resetAt) {
290
+ rateLimitMap.set(ip, { count: 1, resetAt: now + RATE_LIMIT_WINDOW_MS });
291
+ return true;
292
+ }
293
+ if (entry.count >= RATE_LIMIT_MAX_ATTEMPTS) {
294
+ return false;
295
+ }
296
+ entry.count++;
297
+ return true;
298
+ }
299
+ export function startAuthServer() {
300
+ return new Promise((resolve, reject) => {
301
+ let server = null;
302
+ let timeoutId = null;
303
+ const cleanup = () => {
304
+ if (timeoutId) {
305
+ clearTimeout(timeoutId);
306
+ timeoutId = null;
307
+ }
308
+ if (server) {
309
+ server.close();
310
+ server = null;
311
+ }
312
+ rateLimitMap.clear();
313
+ };
314
+ server = createServer(async (req, res) => {
315
+ const origin = req.headers.origin || "";
316
+ const allowedOrigin = origin.startsWith("http://localhost:") || origin.startsWith("http://127.0.0.1:")
317
+ ? origin
318
+ : `http://localhost:${AUTH_PORT}`;
319
+ res.setHeader("Access-Control-Allow-Origin", allowedOrigin);
320
+ res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
321
+ res.setHeader("Access-Control-Allow-Headers", "Content-Type");
322
+ if (req.method === "OPTIONS") {
323
+ res.writeHead(200);
324
+ res.end();
325
+ return;
326
+ }
327
+ if (req.url === "/auth" && req.method === "GET") {
328
+ res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
329
+ res.end(AUTH_PAGE_HTML);
330
+ return;
331
+ }
332
+ if (req.url === "/auth/save" && req.method === "POST") {
333
+ const clientIp = req.socket.remoteAddress || "unknown";
334
+ if (!checkRateLimit(clientIp)) {
335
+ sendJson(res, 429, { success: false, error: "Too many attempts. Please wait and try again." });
336
+ return;
337
+ }
338
+ let body = "";
339
+ req.on("data", (chunk) => (body += chunk));
340
+ req.on("end", async () => {
341
+ try {
342
+ const { api_key } = JSON.parse(body);
343
+ if (!api_key || typeof api_key !== "string") {
344
+ sendJson(res, 400, { success: false, error: "API key required" });
345
+ return;
346
+ }
347
+ const isValid = await validateApiKey(api_key);
348
+ if (isValid) {
349
+ saveApiKey(api_key);
350
+ sendJson(res, 200, { success: true });
351
+ setTimeout(() => {
352
+ cleanup();
353
+ resolve(api_key);
354
+ }, 500);
355
+ }
356
+ else {
357
+ sendJson(res, 400, {
358
+ success: false,
359
+ error: "Invalid API key. Please check your key at cryptoquant.com/settings/api",
360
+ });
361
+ }
362
+ }
363
+ catch {
364
+ sendJson(res, 400, { success: false, error: "Invalid request" });
365
+ }
366
+ });
367
+ return;
368
+ }
369
+ if (req.url === "/health" && req.method === "GET") {
370
+ sendJson(res, 200, { status: "ok" });
371
+ return;
372
+ }
373
+ res.writeHead(404);
374
+ res.end("Not found");
375
+ });
376
+ server.on("error", (err) => {
377
+ cleanup();
378
+ if (err.code === "EADDRINUSE") {
379
+ reject(new Error(`Port ${AUTH_PORT} is already in use. Please close other applications using this port and try again.`));
380
+ }
381
+ else {
382
+ reject(err);
383
+ }
384
+ });
385
+ server.listen(AUTH_PORT, "127.0.0.1", async () => {
386
+ const url = `http://localhost:${AUTH_PORT}/auth`;
387
+ console.error(`Auth server started at ${url}`);
388
+ try {
389
+ await openBrowser(url);
390
+ }
391
+ catch (error) {
392
+ cleanup();
393
+ reject(error);
394
+ }
395
+ });
396
+ timeoutId = setTimeout(() => {
397
+ cleanup();
398
+ reject(new Error("Authentication timeout. Please try again by calling initialize()."));
399
+ }, AUTH_TIMEOUT_MS);
400
+ });
401
+ }
402
+ export function getAuthUrl() {
403
+ return `http://localhost:${AUTH_PORT}/auth`;
404
+ }
405
+ //# sourceMappingURL=server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../../src/auth/server.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ export interface StoredCredentials {
2
+ api_key: string;
3
+ created_at: string;
4
+ validated_at: string;
5
+ }
6
+ export declare function getStoredApiKey(): string | null;
7
+ export declare function saveApiKey(apiKey: string): void;
8
+ export declare function updateValidatedAt(): void;
9
+ export declare function clearCredentials(): void;
10
+ export declare function getCredentialsPath(): string;
11
+ //# sourceMappingURL=storage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"storage.d.ts","sourceRoot":"","sources":["../../src/auth/storage.ts"],"names":[],"mappings":"AAOA,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,wBAAgB,eAAe,IAAI,MAAM,GAAG,IAAI,CAQ/C;AAED,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAa/C;AAED,wBAAgB,iBAAiB,IAAI,IAAI,CASxC;AAED,wBAAgB,gBAAgB,IAAI,IAAI,CAMvC;AAED,wBAAgB,kBAAkB,IAAI,MAAM,CAE3C"}
@@ -0,0 +1,53 @@
1
+ import { homedir } from "os";
2
+ import { join } from "path";
3
+ import { readFileSync, writeFileSync, mkdirSync, existsSync, unlinkSync, chmodSync } from "fs";
4
+ const CREDENTIALS_DIR = join(homedir(), ".cryptoquant");
5
+ const CREDENTIALS_FILE = join(CREDENTIALS_DIR, "credentials");
6
+ export function getStoredApiKey() {
7
+ try {
8
+ if (!existsSync(CREDENTIALS_FILE))
9
+ return null;
10
+ const data = JSON.parse(readFileSync(CREDENTIALS_FILE, "utf-8"));
11
+ return data.api_key || null;
12
+ }
13
+ catch {
14
+ return null;
15
+ }
16
+ }
17
+ export function saveApiKey(apiKey) {
18
+ if (!existsSync(CREDENTIALS_DIR)) {
19
+ mkdirSync(CREDENTIALS_DIR, { recursive: true, mode: 0o700 });
20
+ }
21
+ const data = {
22
+ api_key: apiKey,
23
+ created_at: new Date().toISOString(),
24
+ validated_at: new Date().toISOString(),
25
+ };
26
+ writeFileSync(CREDENTIALS_FILE, JSON.stringify(data, null, 2), { encoding: "utf-8" });
27
+ chmodSync(CREDENTIALS_FILE, 0o600);
28
+ }
29
+ export function updateValidatedAt() {
30
+ try {
31
+ if (!existsSync(CREDENTIALS_FILE))
32
+ return;
33
+ const data = JSON.parse(readFileSync(CREDENTIALS_FILE, "utf-8"));
34
+ data.validated_at = new Date().toISOString();
35
+ writeFileSync(CREDENTIALS_FILE, JSON.stringify(data, null, 2), { encoding: "utf-8" });
36
+ }
37
+ catch {
38
+ // Validation timestamp update is non-critical
39
+ }
40
+ }
41
+ export function clearCredentials() {
42
+ try {
43
+ if (existsSync(CREDENTIALS_FILE))
44
+ unlinkSync(CREDENTIALS_FILE);
45
+ }
46
+ catch {
47
+ // Already deleted or inaccessible
48
+ }
49
+ }
50
+ export function getCredentialsPath() {
51
+ return CREDENTIALS_FILE;
52
+ }
53
+ //# sourceMappingURL=storage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"storage.js","sourceRoot":"","sources":["../../src/auth/storage.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,IAAI,CAAC;AAC7B,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAC5B,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,SAAS,EAAE,UAAU,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,IAAI,CAAC;AAE/F,MAAM,eAAe,GAAG,IAAI,CAAC,OAAO,EAAE,EAAE,cAAc,CAAC,CAAC;AACxD,MAAM,gBAAgB,GAAG,IAAI,CAAC,eAAe,EAAE,aAAa,CAAC,CAAC;AAQ9D,MAAM,UAAU,eAAe;IAC7B,IAAI,CAAC;QACH,IAAI,CAAC,UAAU,CAAC,gBAAgB,CAAC;YAAE,OAAO,IAAI,CAAC;QAC/C,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,gBAAgB,EAAE,OAAO,CAAC,CAAC,CAAC;QACjE,OAAO,IAAI,CAAC,OAAO,IAAI,IAAI,CAAC;IAC9B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,MAAc;IACvC,IAAI,CAAC,UAAU,CAAC,eAAe,CAAC,EAAE,CAAC;QACjC,SAAS,CAAC,eAAe,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IAC/D,CAAC;IAED,MAAM,IAAI,GAAsB;QAC9B,OAAO,EAAE,MAAM;QACf,UAAU,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;QACpC,YAAY,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;KACvC,CAAC;IAEF,aAAa,CAAC,gBAAgB,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC,CAAC;IACtF,SAAS,CAAC,gBAAgB,EAAE,KAAK,CAAC,CAAC;AACrC,CAAC;AAED,MAAM,UAAU,iBAAiB;IAC/B,IAAI,CAAC;QACH,IAAI,CAAC,UAAU,CAAC,gBAAgB,CAAC;YAAE,OAAO;QAC1C,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,gBAAgB,EAAE,OAAO,CAAC,CAAsB,CAAC;QACtF,IAAI,CAAC,YAAY,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;QAC7C,aAAa,CAAC,gBAAgB,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC,CAAC;IACxF,CAAC;IAAC,MAAM,CAAC;QACP,8CAA8C;IAChD,CAAC;AACH,CAAC;AAED,MAAM,UAAU,gBAAgB;IAC9B,IAAI,CAAC;QACH,IAAI,UAAU,CAAC,gBAAgB,CAAC;YAAE,UAAU,CAAC,gBAAgB,CAAC,CAAC;IACjE,CAAC;IAAC,MAAM,CAAC;QACP,kCAAkC;IACpC,CAAC;AACH,CAAC;AAED,MAAM,UAAU,kBAAkB;IAChC,OAAO,gBAAgB,CAAC;AAC1B,CAAC"}
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Cache storage operations for discovery response caching
3
+ */
4
+ import { type DiscoveryCacheSchema, type MyDiscoveryRawResponse, type DiscoverySummaryData } from "./types.js";
5
+ import type { UserPlan, PlanLimits, ApiRateLimit } from "../plan-limits.js";
6
+ /**
7
+ * Get cache file path.
8
+ */
9
+ export declare function getCacheFilePath(_apiUrl: string): string;
10
+ /**
11
+ * Check if cache is still valid (not expired and version matches).
12
+ */
13
+ export declare function isCacheValid(cache: DiscoveryCacheSchema, apiUrl: string, apiKeyPrefix: string): boolean;
14
+ /**
15
+ * Read cache from file.
16
+ * Returns null if cache doesn't exist or is invalid JSON.
17
+ */
18
+ export declare function readCache(apiUrl: string): DiscoveryCacheSchema | null;
19
+ /**
20
+ * Write cache to file with secure permissions.
21
+ */
22
+ export declare function writeCache(apiUrl: string, apiKey: string, rawResponse: MyDiscoveryRawResponse, parsed: {
23
+ limits: PlanLimits | null;
24
+ statics: string[];
25
+ apiRateLimit: ApiRateLimit | null;
26
+ }, summary: DiscoverySummaryData, plan: UserPlan): void;
27
+ /**
28
+ * Delete cache file.
29
+ */
30
+ export declare function invalidateCache(apiUrl: string): void;
31
+ /**
32
+ * Delete the cache file.
33
+ */
34
+ export declare function clearAllCaches(): void;
35
+ /**
36
+ * Calculate cache age in days.
37
+ */
38
+ export declare function getCacheAgeDays(cache: DiscoveryCacheSchema): number;
39
+ /**
40
+ * Get human-readable cache status string.
41
+ */
42
+ export declare function getCacheStatus(cache: DiscoveryCacheSchema | null, fromCache: boolean): string;
43
+ /**
44
+ * Get cache file path for user display.
45
+ */
46
+ export declare function getCachePath(): string;
47
+ //# sourceMappingURL=storage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"storage.d.ts","sourceRoot":"","sources":["../../src/cache/storage.ts"],"names":[],"mappings":"AAAA;;GAEG;AAKH,OAAO,EACL,KAAK,oBAAoB,EACzB,KAAK,sBAAsB,EAC3B,KAAK,oBAAoB,EAG1B,MAAM,YAAY,CAAC;AACpB,OAAO,KAAK,EAAE,QAAQ,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAK5E;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAExD;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,oBAAoB,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,OAAO,CAuBvG;AAED;;;GAGG;AACH,wBAAgB,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,oBAAoB,GAAG,IAAI,CAmBrE;AAED;;GAEG;AACH,wBAAgB,UAAU,CACxB,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,EACd,WAAW,EAAE,sBAAsB,EACnC,MAAM,EAAE;IACN,MAAM,EAAE,UAAU,GAAG,IAAI,CAAC;IAC1B,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,YAAY,EAAE,YAAY,GAAG,IAAI,CAAC;CACnC,EACD,OAAO,EAAE,oBAAoB,EAC7B,IAAI,EAAE,QAAQ,GACb,IAAI,CA2BN;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAUpD;AAED;;GAEG;AACH,wBAAgB,cAAc,IAAI,IAAI,CAQrC;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,oBAAoB,GAAG,MAAM,CAKnE;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,oBAAoB,GAAG,IAAI,EAAE,SAAS,EAAE,OAAO,GAAG,MAAM,CAM7F;AAED;;GAEG;AACH,wBAAgB,YAAY,IAAI,MAAM,CAErC"}