#!/usr/bin/env node /** * Claude Usage Fetcher (Standalone) * * Fetches token usage from claude.ai API using a session key. * Designed to run as a cron job on headless servers. * * Session key source (checked in order): * 1. CLAUDE_SESSION_KEY env var * 2. ~/.config/claude-statusline/session-key (plain text file) * * To get your session key: * 1. Log into claude.ai in any browser * 2. Open DevTools → Application → Cookies → claude.ai * 3. Copy the value of the "sessionKey" cookie * 4. Save it: echo "sk-ant-..." > ~/.config/claude-statusline/session-key * * Output: Writes JSON to $CLAUDE_USAGE_CACHE or /tmp/claude_usage.json * * Cron example (every 5 min): * */5 * * * * /usr/bin/node /path/to/fetch-usage.js 2>/dev/null */ const fs = require('fs'); const path = require('path'); // --- Config --- const CONFIG_DIR = process.env.CLAUDE_STATUSLINE_CONFIG || path.join(process.env.HOME || '/root', '.config', 'claude-statusline'); const CACHE_FILE = process.env.CLAUDE_USAGE_CACHE || path.join(process.env.TMPDIR || '/tmp', 'claude_usage.json'); const ORG_ID = process.env.CLAUDE_ORG_ID || ''; // --- Session Key --- function getSessionKey() { // env var first if (process.env.CLAUDE_SESSION_KEY) return process.env.CLAUDE_SESSION_KEY.trim(); // config file const keyFile = path.join(CONFIG_DIR, 'session-key'); try { return fs.readFileSync(keyFile, 'utf8').trim(); } catch { return null; } } // --- Discover org ID --- async function getOrgId(sessionKey) { if (ORG_ID) return ORG_ID; const resp = await fetch('https://claude.ai/api/organizations', { headers: headers(sessionKey), signal: AbortSignal.timeout(10000), }); if (!resp.ok) throw new Error('Failed to list orgs: ' + resp.status); const orgs = await resp.json(); if (!orgs.length) throw new Error('No organizations found'); return orgs[0].uuid; } function headers(sessionKey) { return { 'Cookie': 'sessionKey=' + sessionKey, 'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64; rv:135.0) Gecko/20100101 Firefox/135.0', 'Accept': 'application/json', 'Referer': 'https://claude.ai/', 'Origin': 'https://claude.ai', }; } // --- Fetch --- async function main() { const sessionKey = getSessionKey(); if (!sessionKey) { console.error('No session key found. Set CLAUDE_SESSION_KEY or create ' + path.join(CONFIG_DIR, 'session-key')); process.exit(1); } try { const orgId = await getOrgId(sessionKey); const resp = await fetch('https://claude.ai/api/organizations/' + orgId + '/usage', { headers: headers(sessionKey), signal: AbortSignal.timeout(10000), }); if (!resp.ok) { console.error('API error: ' + resp.status); process.exit(1); } const data = await resp.json(); // Ensure cache dir exists const cacheDir = path.dirname(CACHE_FILE); if (!fs.existsSync(cacheDir)) fs.mkdirSync(cacheDir, { recursive: true }); fs.writeFileSync(CACHE_FILE, JSON.stringify(data, null, 2)); console.log('OK — wrote ' + CACHE_FILE); } catch (e) { console.error('Fetch error: ' + e.message); process.exit(1); } } main();