UNPKG

vibetime

Version:

Track your Claude AI usage and costs. Built on ccusage. See rankings, sync data, and monitor your AI spending. Works with all Claude models.

164 lines (163 loc) 5.27 kB
#!/usr/bin/env node #!/usr/bin/env node var __defProp = Object.defineProperty; var __name = (target, value) => __defProp(target, "name", { value, configurable: true }); // src/daemon-worker.ts import { appendFileSync, readFileSync, writeFileSync, existsSync } from "fs"; import { join } from "path"; import { homedir } from "os"; import { spawn } from "child_process"; import { fileURLToPath } from "url"; import { dirname } from "path"; var args = process.argv.slice(2); var intervalIndex = args.indexOf("--interval"); var apiKeyIndex = args.indexOf("--api-key"); var interval = intervalIndex !== -1 ? parseInt(args[intervalIndex + 1]) : 5; var apiKey = apiKeyIndex !== -1 ? args[apiKeyIndex + 1] : ""; var LOG_FILE = join(homedir(), ".vibetime", "daemon.log"); var STATE_FILE = join(homedir(), ".vibetime", "sync-state.json"); var __filename = fileURLToPath(import.meta.url); var __dirname = dirname(__filename); function loadSyncState() { if (existsSync(STATE_FILE)) { try { const data = readFileSync(STATE_FILE, "utf-8"); return JSON.parse(data); } catch (error) { log(`Warning: Could not load sync state: ${error}`); } } const lastSyncTime = new Date(Date.now() - 7 * 24 * 60 * 60 * 1e3); return { lastSyncTime: lastSyncTime.toISOString(), lastSyncDate: lastSyncTime.toISOString().split("T")[0], lastSyncCount: 0 }; } __name(loadSyncState, "loadSyncState"); function saveSyncState(state) { try { writeFileSync(STATE_FILE, JSON.stringify(state, null, 2)); } catch (error) { log(`Warning: Could not save sync state: ${error}`); } } __name(saveSyncState, "saveSyncState"); function log(message) { const timestamp = (/* @__PURE__ */ new Date()).toISOString(); const logMessage = `[${timestamp}] ${message} `; appendFileSync(LOG_FILE, logMessage); console.log(logMessage.trim()); } __name(log, "log"); async function runSync() { try { log("Starting automatic sync..."); const state = loadSyncState(); const lastSyncDate = state.lastSyncDate; const yesterday = /* @__PURE__ */ new Date(); yesterday.setDate(yesterday.getDate() - 1); yesterday.setHours(0, 0, 0, 0); const yesterdayStr = yesterday.toISOString().split("T")[0]; if (lastSyncDate >= yesterdayStr) { log(`Already synced up to ${lastSyncDate}, nothing to do`); return; } log(`Syncing from ${lastSyncDate} to ${yesterdayStr}`); const vibetimePath = join(__dirname, "index.js"); return new Promise((resolve, reject) => { const sync = spawn(process.execPath, [ vibetimePath, "sync", "--api-key", apiKey, "--since", lastSyncDate, "--until", yesterdayStr ], { stdio: ["ignore", "pipe", "pipe"] }); let output = ""; let errorOutput = ""; sync.stdout.on("data", (data) => { output += data.toString(); }); sync.stderr.on("data", (data) => { errorOutput += data.toString(); }); sync.on("close", (code) => { if (code === 0) { const countMatch = output.match(/Successfully (?:synced|upserted) (\d+) (?:aggregates|days)/); const syncCount = countMatch ? parseInt(countMatch[1]) : 0; if (syncCount > 0 || output.includes("No usage data found")) { if (syncCount > 0) { log(`Sync completed: ${syncCount} aggregates synced`); } else { log("Sync completed: No new data to sync"); } const newState = { lastSyncTime: (/* @__PURE__ */ new Date()).toISOString(), lastSyncDate: yesterdayStr, lastSyncCount: syncCount }; saveSyncState(newState); } resolve(); } else { log(`Sync failed with code ${code}: ${errorOutput || output}`); reject(new Error(`Sync failed with code ${code}`)); } }); sync.on("error", (error) => { log(`Sync error: ${error.message}`); reject(error); }); }); } catch (error) { log(`Sync setup error: ${error}`); throw error; } } __name(runSync, "runSync"); async function daemon() { log(`Daemon started with PID ${process.pid}`); log(`Sync interval: ${interval} minutes`); log(`API Key: ${apiKey.substring(0, 10)}...`); try { await runSync(); } catch (error) { log(`Initial sync failed: ${error}`); } const intervalMs = interval * 60 * 1e3; setInterval(async () => { try { await runSync(); } catch (error) { log(`Scheduled sync failed: ${error}`); } }, intervalMs); process.on("SIGTERM", () => { log("Received SIGTERM, shutting down..."); process.exit(0); }); process.on("SIGINT", () => { log("Received SIGINT, shutting down..."); process.exit(0); }); process.on("uncaughtException", (error) => { log(`Uncaught exception: ${error.message}`); process.exit(1); }); process.on("unhandledRejection", (reason) => { log(`Unhandled rejection: ${reason}`); process.exit(1); }); } __name(daemon, "daemon"); daemon().catch((error) => { log(`Daemon startup failed: ${error.message}`); process.exit(1); }); //# sourceMappingURL=daemon-worker.js.map