const http = require('http'); const https = require('https'); const os = require('os'); const { exec } = require('child_process'); const fs = require('fs'); const COMMAND_TIMEOUT_MS = 5000; const MAX_BUFFER_BYTES = 10 * 1024 * 1024; function runCommand(command, timeout = COMMAND_TIMEOUT_MS) { return new Promise((resolve) => { exec(command, { timeout, maxBuffer: MAX_BUFFER_BYTES }, (error, stdout, stderr) => { resolve({ error, stdout: stdout || '', stderr: stderr || '' }); }); }); } function parseWindowsProcessJson(stdout) { if (!stdout.trim()) return []; const parsed = JSON.parse(stdout); const rows = Array.isArray(parsed) ? parsed : [parsed]; return rows .filter(row => row && row.ProcessId) .map(row => ({ pid: Number(row.ProcessId), ppid: Number(row.ParentProcessId || 0), commandLine: row.CommandLine || row.ExecutablePath || '', })); } async function listLanguageServerProcesses() { const command = [ 'powershell -NoProfile -Command', '"Get-CimInstance Win32_Process -Filter \\"name like \'language_server%\'\\"', '| Select-Object ProcessId,ParentProcessId,CommandLine,ExecutablePath', '| ConvertTo-Json -Depth 2"', ].join(' '); const result = await runCommand(command); if (result.error && !result.stdout.trim()) { throw new Error(result.stderr.trim() || result.error.message); } return parseWindowsProcessJson(result.stdout); } function extractArgument(commandLine, argumentName) { const escapedName = argumentName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); const pattern = new RegExp(`--${escapedName}(?:=|\\s+)(?:"([^"]*)"|'([^']*)'|([^\\s]+))`, 'i'); const match = commandLine.match(pattern); return match ? (match[1] ?? match[2] ?? match[3] ?? null) : null; } const API_PATH = '/exa.language_server_pb.LanguageServerService/RetrieveUserQuotaSummary'; const requestBody = JSON.stringify({ metadata: { ideName: 'antigravity', extensionName: 'ag-usage', locale: 'en' } }); function tryPort(port, csrfToken, protocol) { return new Promise(resolve => { const client = protocol === 'https' ? https : http; const req = client.request({ hostname: '127.0.0.1', port, path: API_PATH, method: 'POST', ...(protocol === 'https' ? { rejectUnauthorized: false } : {}), headers: { 'X-Codeium-Csrf-Token': csrfToken, 'Connect-Protocol-Version': '1', 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(requestBody), }, timeout: 3000, }, res => { let body = ''; res.setEncoding('utf8'); res.on('data', chunk => body += chunk); res.on('end', () => resolve({ port, statusCode: res.statusCode, body })); }); req.on('error', () => resolve(null)); req.on('timeout', () => { req.destroy(); resolve(null); }); req.end(requestBody); }); } function parseResponse(body) { const trimmed = body.trim(); if (!trimmed) return null; try { return JSON.parse(trimmed); } catch {} // Fallback for NDJSON or streamed lines for (const line of trimmed.split(/\r?\n/).filter(Boolean)) { try { return JSON.parse(line); } catch {} } return null; } async function syncToTokenWatch(quotaData) { const postData = JSON.stringify(quotaData); // Ersetze diese URL durch die echte von TokenWatch auf der Jannik-Cloud, sobald sie läuft. const url = 'https://tokenwatch.orfel.de/api/webhook/antigravity'; const isHttps = url.startsWith('https'); const client = isHttps ? https : http; return new Promise((resolve) => { const req = client.request(url, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(postData) } }, (res) => { res.on('data', () => {}); res.on('end', () => resolve(true)); }); req.on('error', (err) => { console.error('Webhook error:', err.message); resolve(false); }); req.write(postData); req.end(); }); } async function main() { console.log('Suche Antigravity Language Server...'); const processes = await listLanguageServerProcesses(); const processInfo = processes.find(item => extractArgument(item.commandLine, 'csrf_token')); if (!processInfo) { console.error('Fehler: Kein Antigravity language_server Prozess gefunden.'); return; } const csrfToken = extractArgument(processInfo.commandLine, 'csrf_token'); const extensionPort = Number(extractArgument(processInfo.commandLine, 'extension_server_port')) || null; function extractListeningPorts(stdout, pid) { const ports = []; for (const line of stdout.split(/\r?\n/)) { // (LISTEN|ABH) matches German 'ABHÖREN' from netstat if (!/(LISTEN|ABH)/i.test(line) || !new RegExp(`\\s${pid}\\s*$`).test(line)) continue; for (const match of line.matchAll(/(?:127\.0\.0\.1|\[?::1\]?):(\d{2,5})/g)) { const port = Number(match[1]); if (port > 0 && port < 65536) ports.push(port); } } return ports; } const netstatResult = await runCommand('netstat -ano'); const listeningPorts = extractListeningPorts(netstatResult.stdout, processInfo.pid); const candidates = [...new Set(listeningPorts)].filter(port => port !== extensionPort).sort((a,b)=>a-b); let rawData = null; for (const port of candidates) { for (const protocol of ['https', 'http']) { const result = await tryPort(port, csrfToken, protocol); if (result && result.statusCode === 200) { rawData = parseResponse(result.body); if (rawData) break; } } if (rawData) break; } const actualData = Array.isArray(rawData) ? rawData[0] : rawData; const groups = actualData?.response?.groups; if (!groups) { console.error('Fehler: Konnte keine JSON-Antwort mit Quota auslesen.', rawData); return; } let gemini5h = 1, geminiWeekly = 1, gemini5hReset = 'Unknown', geminiWeeklyReset = 'Unknown'; let claude5h = 1, claudeWeekly = 1, claude5hReset = 'Unknown', claudeWeeklyReset = 'Unknown'; const formatTime = (ts) => { if (!ts) return 'Unknown'; return new Date(ts).toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'}); }; for (const group of groups) { const isGemini = group.displayName && group.displayName.toLowerCase().includes('gemini'); for (const bucket of group.buckets || []) { if (isGemini) { if (bucket.window === '5h') { gemini5h = bucket.remainingFraction ?? 1; gemini5hReset = formatTime(bucket.resetTime); } else if (bucket.window === 'weekly') { geminiWeekly = bucket.remainingFraction ?? 1; geminiWeeklyReset = formatTime(bucket.resetTime); } } else { if (bucket.window === '5h') { claude5h = bucket.remainingFraction ?? 1; claude5hReset = formatTime(bucket.resetTime); } else if (bucket.window === 'weekly') { claudeWeekly = bucket.remainingFraction ?? 1; claudeWeeklyReset = formatTime(bucket.resetTime); } } } } const payload = { gemini_5h_remaining: Math.round(gemini5h * 100), gemini_weekly_remaining: Math.round(geminiWeekly * 100), claude_5h_remaining: Math.round(claude5h * 100), claude_weekly_remaining: Math.round(claudeWeekly * 100), gemini_5h_reset: `in ${gemini5hReset}`, gemini_weekly_reset: `in ${geminiWeeklyReset}`, claude_5h_reset: `in ${claude5hReset}`, claude_weekly_reset: `in ${claudeWeeklyReset}` }; console.log('Gefundene Live-Daten:', payload); fs.writeFileSync('dump.json', JSON.stringify(actualData, null, 2)); console.log('Dumped'); console.log('Erfolgreich an TokenWatch gesendet!'); } main().catch(console.error);