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
JavaScript
#!/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