@hackersbaby/plugin 0.4.1 → 0.4.3

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.
package/dist/cli.js CHANGED
@@ -464,6 +464,7 @@ function registerCursorHooks() {
464
464
  if (import_fs2.default.existsSync(cursorHooksPath)) {
465
465
  hooksConfig = JSON.parse(import_fs2.default.readFileSync(cursorHooksPath, "utf-8"));
466
466
  }
467
+ if (!hooksConfig.version) hooksConfig.version = 1;
467
468
  if (!hooksConfig.hooks) hooksConfig.hooks = {};
468
469
  const distDir = import_path2.default.resolve(__dirname);
469
470
  const hookEntries = {
package/dist/cli.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/config.ts","../src/api-client.ts","../src/cli.ts","../src/commands/status.ts","../src/commands/leaderboard.ts","../src/commands/dashboard.ts","../src/commands/flex.ts"],"sourcesContent":["import fs from 'fs';\nimport path from 'path';\nimport os from 'os';\n\nexport const CONFIG_DIR = path.join(os.homedir(), '.hackersbaby');\nexport const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');\nexport const QUEUE_FILE = path.join(CONFIG_DIR, 'queue.jsonl');\n\nexport type Source = 'claude' | 'cursor';\n\nexport interface PluginConfig {\n token: string;\n refresh_token: string;\n user_id: string;\n server: string;\n targets?: Source[];\n preferences: {\n dashboard_port: number;\n batch_interval_ms: number;\n };\n}\n\nexport function sessionStateFile(source: Source): string {\n return path.join(CONFIG_DIR, `active-session-${source}.json`);\n}\n\nexport function ensureConfigDir(): void {\n if (!fs.existsSync(CONFIG_DIR)) {\n fs.mkdirSync(CONFIG_DIR, { recursive: true });\n }\n}\n\nexport function loadConfig(): PluginConfig | null {\n try {\n if (!fs.existsSync(CONFIG_FILE)) return null;\n const raw = fs.readFileSync(CONFIG_FILE, 'utf-8');\n return JSON.parse(raw) as PluginConfig;\n } catch {\n return null;\n }\n}\n\nexport function saveConfig(config: PluginConfig): void {\n ensureConfigDir();\n fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2), 'utf-8');\n}\n","import { loadConfig, saveConfig } from './config';\nimport type { EventBatch } from '@hackersbaby/shared';\n\nexport class APIClient {\n private config = loadConfig();\n\n private get headers(): Record<string, string> {\n return {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${this.config?.token || ''}`,\n };\n }\n\n async sendBatch(batch: EventBatch): Promise<boolean> {\n try {\n const server = this.config?.server || 'https://hackers.baby';\n const res = await fetch(`${server}/api/scores/batch`, {\n method: 'POST',\n headers: this.headers,\n body: JSON.stringify(batch),\n });\n return res.ok;\n } catch {\n return false;\n }\n }\n\n async refreshTokenIfNeeded(): Promise<void> {\n if (!this.config) return;\n try {\n const parts = this.config.token.split('.');\n if (parts.length !== 3) return;\n const payload = JSON.parse(Buffer.from(parts[1], 'base64').toString('utf-8'));\n const expMs = payload.exp * 1000;\n const oneDayMs = 24 * 60 * 60 * 1000;\n if (Date.now() + oneDayMs < expMs) return; // more than 1 day remaining, no refresh needed\n\n const res = await fetch(`${this.config.server}/api/auth/cli-token/refresh`, {\n method: 'POST',\n headers: this.headers,\n body: JSON.stringify({ refresh_token: this.config.refresh_token }),\n });\n if (res.ok) {\n const data = (await res.json()) as { token?: string; refresh_token?: string };\n if (data.token) {\n this.config.token = data.token;\n if (data.refresh_token) this.config.refresh_token = data.refresh_token;\n saveConfig(this.config);\n }\n }\n } catch {\n // silently ignore refresh errors\n }\n }\n\n async getStatus(): Promise<Record<string, unknown>> {\n const server = this.config?.server || 'https://hackers.baby';\n const res = await fetch(`${server}/api/users/me`, { headers: this.headers });\n if (!res.ok) throw new Error(`getStatus failed: ${res.status}`);\n return res.json() as Promise<Record<string, unknown>>;\n }\n\n async getRank(): Promise<Record<string, unknown>> {\n const server = this.config?.server || 'https://hackers.baby';\n const res = await fetch(`${server}/api/users/me/rank`, { headers: this.headers });\n if (!res.ok) throw new Error(`getRank failed: ${res.status}`);\n return res.json() as Promise<Record<string, unknown>>;\n }\n\n async getLeaderboard(): Promise<Record<string, unknown>> {\n const server = this.config?.server || 'https://hackers.baby';\n const res = await fetch(`${server}/api/leaderboard/global?limit=10`, { headers: this.headers });\n if (!res.ok) throw new Error(`getLeaderboard failed: ${res.status}`);\n return res.json() as Promise<Record<string, unknown>>;\n }\n\n async getPublicStats(): Promise<Record<string, unknown> | null> {\n try {\n const server = this.config?.server || 'https://hackers.baby';\n const res = await fetch(`${server}/api/stats/public`);\n if (!res.ok) return null;\n return res.json() as Promise<Record<string, unknown>>;\n } catch {\n return null;\n }\n }\n\n async getNotifications(): Promise<{ notifications: Array<{ type: string; message: string }> } | null> {\n try {\n const server = this.config?.server || 'https://hackers.baby';\n const res = await fetch(`${server}/api/users/me/notifications`, { headers: this.headers });\n if (!res.ok) return null;\n return res.json() as Promise<{ notifications: Array<{ type: string; message: string }> }>;\n } catch {\n return null;\n }\n }\n\n async claimReferral(code: string): Promise<Record<string, unknown> | null> {\n try {\n const server = this.config?.server || 'https://hackers.baby';\n const res = await fetch(`${server}/api/referral/claim`, {\n method: 'POST',\n headers: this.headers,\n body: JSON.stringify({ referral_code: code }),\n });\n if (!res.ok) return null;\n return res.json() as Promise<Record<string, unknown>>;\n } catch {\n return null;\n }\n }\n\n async getReferralStats(): Promise<Record<string, unknown> | null> {\n try {\n const server = this.config?.server || 'https://hackers.baby';\n const res = await fetch(`${server}/api/referral/stats`, { headers: this.headers });\n if (!res.ok) return null;\n return res.json() as Promise<Record<string, unknown>>;\n } catch {\n return null;\n }\n }\n}\n","import { loadConfig, saveConfig, ensureConfigDir } from './config';\nimport type { Source } from './config';\nimport { statusCommand } from './commands/status';\nimport { leaderboardCommand } from './commands/leaderboard';\nimport { dashboardCommand } from './commands/dashboard';\nimport { flexCommand } from './commands/flex';\nimport fs from 'fs';\nimport path from 'path';\nimport os from 'os';\nimport { execFile } from 'child_process';\nimport readline from 'readline';\n\nconst args = process.argv.slice(2);\nconst command = args[0];\n\nasync function main() {\n switch (command) {\n case 'init': await init(); break;\n case 'status': console.log(await statusCommand()); break;\n case 'leaderboard': console.log(await leaderboardCommand()); break;\n case 'dashboard': console.log(await dashboardCommand()); break;\n case 'flex': await flexCommand(process.argv.slice(3)); break;\n default: console.log('Usage: hackersbaby <init|status|leaderboard|dashboard|flex>'); break;\n }\n}\n\nasync function init() {\n ensureConfigDir();\n const server = process.env.HACKERSBABY_SERVER || 'https://hackers.baby';\n console.log('Opening browser for GitHub authentication...');\n\n const cmd = process.platform === 'darwin' ? 'open' : process.platform === 'win32' ? 'cmd' : 'xdg-open';\n const browserArgs = process.platform === 'win32' ? ['/c', 'start', `${server}/cli-setup`] : [`${server}/cli-setup`];\n execFile(cmd, browserArgs, () => {});\n\n const rl = readline.createInterface({ input: process.stdin, output: process.stdout });\n const question = (q: string): Promise<string> => new Promise((resolve) => rl.question(q, resolve));\n\n console.log('After authenticating, paste your CLI token:');\n const token = await question('Token: ');\n const refresh_token = await question('Refresh Token: ');\n\n try {\n const payload = JSON.parse(Buffer.from(token.split('.')[1], 'base64').toString());\n\n // Ask which tool(s) to integrate with\n console.log('\\nWhich tool do you use?');\n console.log(' 1) Claude Code');\n console.log(' 2) Cursor');\n console.log(' 3) Both');\n const choice = await question('> ');\n\n let targets: Source[];\n switch (choice.trim()) {\n case '2': targets = ['cursor']; break;\n case '3': targets = ['claude', 'cursor']; break;\n default: targets = ['claude']; break;\n }\n\n saveConfig({\n token, refresh_token,\n user_id: payload.userId,\n server,\n targets,\n preferences: { dashboard_port: 3847, batch_interval_ms: 10000 },\n });\n console.log('Configuration saved! hackers.baby is active.');\n\n if (targets.includes('claude')) {\n registerClaudeHooks();\n }\n if (targets.includes('cursor')) {\n registerCursorHooks();\n }\n\n // Auto-claim referral if --ref flag or HACKERSBABY_REF env var is set\n const refFlag = args.find((a) => a.startsWith('--ref='))?.split('=')[1];\n const refCode = refFlag || process.env.HACKERSBABY_REF;\n if (refCode) {\n try {\n const { APIClient } = await import('./api-client');\n const client = new APIClient();\n const result = await client.claimReferral(refCode);\n if (result) {\n console.log(`\\x1b[32m🎁 Referral claimed! 2x multiplier active for 24 hours.\\x1b[0m`);\n }\n } catch {}\n }\n } catch {\n console.error('Invalid token format');\n }\n rl.close();\n}\n\nfunction registerClaudeHooks() {\n const claudeSettingsPath = path.join(os.homedir(), '.claude', 'settings.json');\n let settings: Record<string, any> = {};\n if (fs.existsSync(claudeSettingsPath)) {\n settings = JSON.parse(fs.readFileSync(claudeSettingsPath, 'utf-8'));\n }\n if (!settings.hooks) settings.hooks = {};\n\n const distDir = path.resolve(__dirname);\n const hookEntries: Record<string, { command: string; timeout: number; matcher?: string }> = {\n SessionStart: { command: `node ${path.join(distDir, 'hooks', 'session-start.js')}`, timeout: 10 },\n UserPromptSubmit: { command: `node ${path.join(distDir, 'hooks', 'prompt-submit.js')}`, timeout: 5 },\n PostToolUse: { command: `node ${path.join(distDir, 'hooks', 'tool-call.js')}`, timeout: 5 },\n Stop: { command: `node ${path.join(distDir, 'hooks', 'stop.js')}`, timeout: 5 },\n SubagentStart: { command: `node ${path.join(distDir, 'hooks', 'subagent.js')}`, timeout: 5 },\n PreCompact: { command: `node ${path.join(distDir, 'hooks', 'compact.js')}`, timeout: 5 },\n SessionEnd: { command: `node ${path.join(distDir, 'hooks', 'session-end.js')}`, timeout: 30 },\n };\n\n for (const [event, cfg] of Object.entries(hookEntries)) {\n if (!settings.hooks[event]) settings.hooks[event] = [];\n const hookDef = {\n matcher: cfg.matcher || '',\n hooks: [{ type: 'command', command: cfg.command, timeout: cfg.timeout }],\n };\n const exists = settings.hooks[event].some((entry: any) =>\n entry.hooks?.some((h: any) => h.command === cfg.command)\n );\n if (!exists) settings.hooks[event].push(hookDef);\n }\n\n fs.writeFileSync(claudeSettingsPath, JSON.stringify(settings, null, 2));\n console.log('Claude Code hooks registered.');\n}\n\nfunction registerCursorHooks() {\n const cursorDir = path.join(os.homedir(), '.cursor');\n if (!fs.existsSync(cursorDir)) {\n fs.mkdirSync(cursorDir, { recursive: true });\n }\n\n const cursorHooksPath = path.join(cursorDir, 'hooks.json');\n let hooksConfig: Record<string, any> = {};\n if (fs.existsSync(cursorHooksPath)) {\n hooksConfig = JSON.parse(fs.readFileSync(cursorHooksPath, 'utf-8'));\n }\n if (!hooksConfig.hooks) hooksConfig.hooks = {};\n\n const distDir = path.resolve(__dirname);\n // Cursor uses camelCase hook names and millisecond timeouts\n const hookEntries: Record<string, { command: string; timeout: number }> = {\n sessionStart: { command: `node ${path.join(distDir, 'hooks', 'cursor', 'session-start.js')}`, timeout: 10000 },\n beforeSubmitPrompt: { command: `node ${path.join(distDir, 'hooks', 'cursor', 'prompt-submit.js')}`, timeout: 5000 },\n postToolUse: { command: `node ${path.join(distDir, 'hooks', 'cursor', 'tool-call.js')}`, timeout: 5000 },\n stop: { command: `node ${path.join(distDir, 'hooks', 'cursor', 'stop.js')}`, timeout: 5000 },\n subagentStart: { command: `node ${path.join(distDir, 'hooks', 'cursor', 'subagent.js')}`, timeout: 5000 },\n preCompact: { command: `node ${path.join(distDir, 'hooks', 'cursor', 'compact.js')}`, timeout: 5000 },\n sessionEnd: { command: `node ${path.join(distDir, 'hooks', 'cursor', 'session-end.js')}`, timeout: 30000 },\n };\n\n for (const [event, cfg] of Object.entries(hookEntries)) {\n if (!hooksConfig.hooks[event]) hooksConfig.hooks[event] = [];\n const hookDef = { command: cfg.command, timeout: cfg.timeout };\n // Avoid duplicates\n const exists = hooksConfig.hooks[event].some((entry: any) => entry.command === cfg.command);\n if (!exists) hooksConfig.hooks[event].push(hookDef);\n }\n\n fs.writeFileSync(cursorHooksPath, JSON.stringify(hooksConfig, null, 2));\n console.log('Cursor hooks registered.');\n}\n\nmain().catch(console.error);\n","import { APIClient } from '../api-client';\n\nexport async function statusCommand(): Promise<string> {\n const client = new APIClient();\n try {\n const [me, rank] = await Promise.all([client.getStatus(), client.getRank()]);\n return [\n 'hackers.baby Status',\n ` Score: ${(me as any).total_score?.toLocaleString() || 0}`,\n ` Global Rank: #${(rank as any).global || '-'}`,\n ` Streak: ${(me as any).streak_days || 0} days`,\n ` Languages: ${((me as any).languages || []).join(', ') || 'none set'}`,\n ].join('\\n');\n } catch {\n return 'Error fetching status. Run `hackersbaby init` to set up.';\n }\n}\n","import { APIClient } from '../api-client';\n\nexport async function leaderboardCommand(): Promise<string> {\n const client = new APIClient();\n try {\n const data = await client.getLeaderboard();\n const lines = ['Global Leaderboard', ''];\n for (const entry of (data as any).entries || []) {\n const marker = entry.rank <= 3 ? ['', '#1', '#2', '#3'][entry.rank] : `#${entry.rank}`;\n lines.push(` ${marker.padEnd(4)} ${entry.username.padEnd(20)} ${entry.score.toLocaleString()}`);\n }\n return lines.join('\\n');\n } catch {\n return 'Error fetching leaderboard.';\n }\n}\n","import { loadConfig } from '../config';\nimport { execFile } from 'child_process';\n\nexport async function dashboardCommand(): Promise<string> {\n const config = loadConfig();\n const url = `${config?.server || 'https://hackers.baby'}/dashboard`;\n\n const cmd = process.platform === 'darwin' ? 'open' : process.platform === 'win32' ? 'cmd' : 'xdg-open';\n const args = process.platform === 'win32' ? ['/c', 'start', url] : [url];\n execFile(cmd, args, () => {});\n\n return `Opening dashboard at ${url}`;\n}\n","import { loadConfig } from '../config';\nimport { APIClient } from '../api-client';\nimport { execFileSync } from 'child_process';\n\nexport async function flexCommand(args: string[]) {\n const config = loadConfig();\n if (!config?.token) {\n console.log('Not logged in. Run: hackersbaby init');\n return;\n }\n\n const client = new APIClient();\n const [status, rankData, stats] = await Promise.all([\n client.getStatus().catch(() => null),\n client.getRank().catch(() => null),\n client.getPublicStats(),\n ]);\n\n if (!status) {\n console.log('Could not fetch your stats. Are you logged in?');\n return;\n }\n\n const username = String(status.github_username ?? 'unknown');\n const score = Number(status.total_score ?? 0).toLocaleString();\n const rank = rankData?.global_rank ?? '—';\n const streak = Number(status.streak_days ?? 0);\n const totalUsers = stats?.total_users ?? '?';\n const season = stats?.current_season ?? 0;\n\n const refCode = `${username}-${(config.user_id ?? '').slice(0, 3)}`;\n const refUrl = `hackers.baby/ref/${refCode}`;\n\n const c = '\\x1b[36m'; // cyan\n const g = '\\x1b[32m'; // green\n const y = '\\x1b[33m'; // yellow\n const b = '\\x1b[1m'; // bold\n const d = '\\x1b[2m'; // dim\n const r = '\\x1b[0m'; // reset\n\n const pad = (s: string, n: number) => s.length >= n ? s.slice(0, n) : s + ' '.repeat(n - s.length);\n\n const card = [\n '',\n `${c}╔══════════════════════════════════════╗${r}`,\n `${c}║${r} ${b}${g}hackers.baby${r} /// ${y}SEASON ${season}${r} ${c}║${r}`,\n `${c}╠══════════════════════════════════════╣${r}`,\n `${c}║${r} ${b}@${pad(username, 24)}${r}${b}RANK #${pad(String(rank), 4)}${r} ${c}║${r}`,\n `${c}║${r} Score: ${g}${pad(score, 14)}${r} pts ${c}║${r}`,\n `${c}║${r} Streak: ${y}${pad(String(streak), 3)}${r} days ${c}║${r}`,\n `${c}║${r} Players: ${pad(String(totalUsers), 26)}${c}║${r}`,\n `${c}╠══════════════════════════════════════╣${r}`,\n `${c}║${r} ${d}npm i -g @hackersbaby/plugin${r} ${c}║${r}`,\n `${c}║${r} ${d}${pad(refUrl, 34)}${r} ${c}║${r}`,\n `${c}╚══════════════════════════════════════╝${r}`,\n '',\n ].join('\\n');\n\n console.log(card);\n\n if (args.includes('--share')) {\n const shareText = [\n `I'm ranked #${rank} on hackers.baby with ${score} points`,\n '',\n `${streak}-day streak | Season ${season}`,\n '',\n 'Think you can beat me?',\n 'npm i -g @hackersbaby/plugin',\n `https://${refUrl}`,\n ].join('\\n');\n\n let copied = false;\n try {\n const platform = process.platform;\n if (platform === 'darwin') {\n execFileSync('pbcopy', { input: shareText });\n copied = true;\n } else if (platform === 'win32') {\n execFileSync('clip', { input: shareText });\n copied = true;\n } else {\n // Linux: try xclip, fall back to xsel\n try {\n execFileSync('xclip', ['-selection', 'clipboard'], { input: shareText });\n copied = true;\n } catch {\n try {\n execFileSync('xsel', ['--clipboard', '--input'], { input: shareText });\n copied = true;\n } catch {\n // no clipboard tool available\n }\n }\n }\n } catch {\n // clipboard failed\n }\n\n if (copied) {\n console.log(`${g}Copied to clipboard! Paste it on X, Discord, or wherever devs hang out.${r}`);\n } else {\n console.log('\\nShare this:\\n');\n console.log(shareText);\n }\n }\n}\n"],"mappings":"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","names":["fs","path","os","args","import_child_process","args","import_fs","import_path","import_os","import_child_process","readline","APIClient","path","os","fs"]}
1
+ {"version":3,"sources":["../src/config.ts","../src/api-client.ts","../src/cli.ts","../src/commands/status.ts","../src/commands/leaderboard.ts","../src/commands/dashboard.ts","../src/commands/flex.ts"],"sourcesContent":["import fs from 'fs';\nimport path from 'path';\nimport os from 'os';\n\nexport const CONFIG_DIR = path.join(os.homedir(), '.hackersbaby');\nexport const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');\nexport const QUEUE_FILE = path.join(CONFIG_DIR, 'queue.jsonl');\n\nexport type Source = 'claude' | 'cursor';\n\nexport interface PluginConfig {\n token: string;\n refresh_token: string;\n user_id: string;\n server: string;\n targets?: Source[];\n preferences: {\n dashboard_port: number;\n batch_interval_ms: number;\n };\n}\n\nexport function sessionStateFile(source: Source): string {\n return path.join(CONFIG_DIR, `active-session-${source}.json`);\n}\n\nexport function ensureConfigDir(): void {\n if (!fs.existsSync(CONFIG_DIR)) {\n fs.mkdirSync(CONFIG_DIR, { recursive: true });\n }\n}\n\nexport function loadConfig(): PluginConfig | null {\n try {\n if (!fs.existsSync(CONFIG_FILE)) return null;\n const raw = fs.readFileSync(CONFIG_FILE, 'utf-8');\n return JSON.parse(raw) as PluginConfig;\n } catch {\n return null;\n }\n}\n\nexport function saveConfig(config: PluginConfig): void {\n ensureConfigDir();\n fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2), 'utf-8');\n}\n","import { loadConfig, saveConfig } from './config';\nimport type { EventBatch } from '@hackersbaby/shared';\n\nexport class APIClient {\n private config = loadConfig();\n\n private get headers(): Record<string, string> {\n return {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${this.config?.token || ''}`,\n };\n }\n\n async sendBatch(batch: EventBatch): Promise<boolean> {\n try {\n const server = this.config?.server || 'https://hackers.baby';\n const res = await fetch(`${server}/api/scores/batch`, {\n method: 'POST',\n headers: this.headers,\n body: JSON.stringify(batch),\n });\n return res.ok;\n } catch {\n return false;\n }\n }\n\n async refreshTokenIfNeeded(): Promise<void> {\n if (!this.config) return;\n try {\n const parts = this.config.token.split('.');\n if (parts.length !== 3) return;\n const payload = JSON.parse(Buffer.from(parts[1], 'base64').toString('utf-8'));\n const expMs = payload.exp * 1000;\n const oneDayMs = 24 * 60 * 60 * 1000;\n if (Date.now() + oneDayMs < expMs) return; // more than 1 day remaining, no refresh needed\n\n const res = await fetch(`${this.config.server}/api/auth/cli-token/refresh`, {\n method: 'POST',\n headers: this.headers,\n body: JSON.stringify({ refresh_token: this.config.refresh_token }),\n });\n if (res.ok) {\n const data = (await res.json()) as { token?: string; refresh_token?: string };\n if (data.token) {\n this.config.token = data.token;\n if (data.refresh_token) this.config.refresh_token = data.refresh_token;\n saveConfig(this.config);\n }\n }\n } catch {\n // silently ignore refresh errors\n }\n }\n\n async getStatus(): Promise<Record<string, unknown>> {\n const server = this.config?.server || 'https://hackers.baby';\n const res = await fetch(`${server}/api/users/me`, { headers: this.headers });\n if (!res.ok) throw new Error(`getStatus failed: ${res.status}`);\n return res.json() as Promise<Record<string, unknown>>;\n }\n\n async getRank(): Promise<Record<string, unknown>> {\n const server = this.config?.server || 'https://hackers.baby';\n const res = await fetch(`${server}/api/users/me/rank`, { headers: this.headers });\n if (!res.ok) throw new Error(`getRank failed: ${res.status}`);\n return res.json() as Promise<Record<string, unknown>>;\n }\n\n async getLeaderboard(): Promise<Record<string, unknown>> {\n const server = this.config?.server || 'https://hackers.baby';\n const res = await fetch(`${server}/api/leaderboard/global?limit=10`, { headers: this.headers });\n if (!res.ok) throw new Error(`getLeaderboard failed: ${res.status}`);\n return res.json() as Promise<Record<string, unknown>>;\n }\n\n async getPublicStats(): Promise<Record<string, unknown> | null> {\n try {\n const server = this.config?.server || 'https://hackers.baby';\n const res = await fetch(`${server}/api/stats/public`);\n if (!res.ok) return null;\n return res.json() as Promise<Record<string, unknown>>;\n } catch {\n return null;\n }\n }\n\n async getNotifications(): Promise<{ notifications: Array<{ type: string; message: string }> } | null> {\n try {\n const server = this.config?.server || 'https://hackers.baby';\n const res = await fetch(`${server}/api/users/me/notifications`, { headers: this.headers });\n if (!res.ok) return null;\n return res.json() as Promise<{ notifications: Array<{ type: string; message: string }> }>;\n } catch {\n return null;\n }\n }\n\n async claimReferral(code: string): Promise<Record<string, unknown> | null> {\n try {\n const server = this.config?.server || 'https://hackers.baby';\n const res = await fetch(`${server}/api/referral/claim`, {\n method: 'POST',\n headers: this.headers,\n body: JSON.stringify({ referral_code: code }),\n });\n if (!res.ok) return null;\n return res.json() as Promise<Record<string, unknown>>;\n } catch {\n return null;\n }\n }\n\n async getReferralStats(): Promise<Record<string, unknown> | null> {\n try {\n const server = this.config?.server || 'https://hackers.baby';\n const res = await fetch(`${server}/api/referral/stats`, { headers: this.headers });\n if (!res.ok) return null;\n return res.json() as Promise<Record<string, unknown>>;\n } catch {\n return null;\n }\n }\n}\n","import { loadConfig, saveConfig, ensureConfigDir } from './config';\nimport type { Source } from './config';\nimport { statusCommand } from './commands/status';\nimport { leaderboardCommand } from './commands/leaderboard';\nimport { dashboardCommand } from './commands/dashboard';\nimport { flexCommand } from './commands/flex';\nimport fs from 'fs';\nimport path from 'path';\nimport os from 'os';\nimport { execFile } from 'child_process';\nimport readline from 'readline';\n\nconst args = process.argv.slice(2);\nconst command = args[0];\n\nasync function main() {\n switch (command) {\n case 'init': await init(); break;\n case 'status': console.log(await statusCommand()); break;\n case 'leaderboard': console.log(await leaderboardCommand()); break;\n case 'dashboard': console.log(await dashboardCommand()); break;\n case 'flex': await flexCommand(process.argv.slice(3)); break;\n default: console.log('Usage: hackersbaby <init|status|leaderboard|dashboard|flex>'); break;\n }\n}\n\nasync function init() {\n ensureConfigDir();\n const server = process.env.HACKERSBABY_SERVER || 'https://hackers.baby';\n console.log('Opening browser for GitHub authentication...');\n\n const cmd = process.platform === 'darwin' ? 'open' : process.platform === 'win32' ? 'cmd' : 'xdg-open';\n const browserArgs = process.platform === 'win32' ? ['/c', 'start', `${server}/cli-setup`] : [`${server}/cli-setup`];\n execFile(cmd, browserArgs, () => {});\n\n const rl = readline.createInterface({ input: process.stdin, output: process.stdout });\n const question = (q: string): Promise<string> => new Promise((resolve) => rl.question(q, resolve));\n\n console.log('After authenticating, paste your CLI token:');\n const token = await question('Token: ');\n const refresh_token = await question('Refresh Token: ');\n\n try {\n const payload = JSON.parse(Buffer.from(token.split('.')[1], 'base64').toString());\n\n // Ask which tool(s) to integrate with\n console.log('\\nWhich tool do you use?');\n console.log(' 1) Claude Code');\n console.log(' 2) Cursor');\n console.log(' 3) Both');\n const choice = await question('> ');\n\n let targets: Source[];\n switch (choice.trim()) {\n case '2': targets = ['cursor']; break;\n case '3': targets = ['claude', 'cursor']; break;\n default: targets = ['claude']; break;\n }\n\n saveConfig({\n token, refresh_token,\n user_id: payload.userId,\n server,\n targets,\n preferences: { dashboard_port: 3847, batch_interval_ms: 10000 },\n });\n console.log('Configuration saved! hackers.baby is active.');\n\n if (targets.includes('claude')) {\n registerClaudeHooks();\n }\n if (targets.includes('cursor')) {\n registerCursorHooks();\n }\n\n // Auto-claim referral if --ref flag or HACKERSBABY_REF env var is set\n const refFlag = args.find((a) => a.startsWith('--ref='))?.split('=')[1];\n const refCode = refFlag || process.env.HACKERSBABY_REF;\n if (refCode) {\n try {\n const { APIClient } = await import('./api-client');\n const client = new APIClient();\n const result = await client.claimReferral(refCode);\n if (result) {\n console.log(`\\x1b[32m🎁 Referral claimed! 2x multiplier active for 24 hours.\\x1b[0m`);\n }\n } catch {}\n }\n } catch {\n console.error('Invalid token format');\n }\n rl.close();\n}\n\nfunction registerClaudeHooks() {\n const claudeSettingsPath = path.join(os.homedir(), '.claude', 'settings.json');\n let settings: Record<string, any> = {};\n if (fs.existsSync(claudeSettingsPath)) {\n settings = JSON.parse(fs.readFileSync(claudeSettingsPath, 'utf-8'));\n }\n if (!settings.hooks) settings.hooks = {};\n\n const distDir = path.resolve(__dirname);\n const hookEntries: Record<string, { command: string; timeout: number; matcher?: string }> = {\n SessionStart: { command: `node ${path.join(distDir, 'hooks', 'session-start.js')}`, timeout: 10 },\n UserPromptSubmit: { command: `node ${path.join(distDir, 'hooks', 'prompt-submit.js')}`, timeout: 5 },\n PostToolUse: { command: `node ${path.join(distDir, 'hooks', 'tool-call.js')}`, timeout: 5 },\n Stop: { command: `node ${path.join(distDir, 'hooks', 'stop.js')}`, timeout: 5 },\n SubagentStart: { command: `node ${path.join(distDir, 'hooks', 'subagent.js')}`, timeout: 5 },\n PreCompact: { command: `node ${path.join(distDir, 'hooks', 'compact.js')}`, timeout: 5 },\n SessionEnd: { command: `node ${path.join(distDir, 'hooks', 'session-end.js')}`, timeout: 30 },\n };\n\n for (const [event, cfg] of Object.entries(hookEntries)) {\n if (!settings.hooks[event]) settings.hooks[event] = [];\n const hookDef = {\n matcher: cfg.matcher || '',\n hooks: [{ type: 'command', command: cfg.command, timeout: cfg.timeout }],\n };\n const exists = settings.hooks[event].some((entry: any) =>\n entry.hooks?.some((h: any) => h.command === cfg.command)\n );\n if (!exists) settings.hooks[event].push(hookDef);\n }\n\n fs.writeFileSync(claudeSettingsPath, JSON.stringify(settings, null, 2));\n console.log('Claude Code hooks registered.');\n}\n\nfunction registerCursorHooks() {\n const cursorDir = path.join(os.homedir(), '.cursor');\n if (!fs.existsSync(cursorDir)) {\n fs.mkdirSync(cursorDir, { recursive: true });\n }\n\n const cursorHooksPath = path.join(cursorDir, 'hooks.json');\n let hooksConfig: Record<string, any> = {};\n if (fs.existsSync(cursorHooksPath)) {\n hooksConfig = JSON.parse(fs.readFileSync(cursorHooksPath, 'utf-8'));\n }\n if (!hooksConfig.version) hooksConfig.version = 1;\n if (!hooksConfig.hooks) hooksConfig.hooks = {};\n\n const distDir = path.resolve(__dirname);\n // Cursor uses camelCase hook names and millisecond timeouts\n const hookEntries: Record<string, { command: string; timeout: number }> = {\n sessionStart: { command: `node ${path.join(distDir, 'hooks', 'cursor', 'session-start.js')}`, timeout: 10000 },\n beforeSubmitPrompt: { command: `node ${path.join(distDir, 'hooks', 'cursor', 'prompt-submit.js')}`, timeout: 5000 },\n postToolUse: { command: `node ${path.join(distDir, 'hooks', 'cursor', 'tool-call.js')}`, timeout: 5000 },\n stop: { command: `node ${path.join(distDir, 'hooks', 'cursor', 'stop.js')}`, timeout: 5000 },\n subagentStart: { command: `node ${path.join(distDir, 'hooks', 'cursor', 'subagent.js')}`, timeout: 5000 },\n preCompact: { command: `node ${path.join(distDir, 'hooks', 'cursor', 'compact.js')}`, timeout: 5000 },\n sessionEnd: { command: `node ${path.join(distDir, 'hooks', 'cursor', 'session-end.js')}`, timeout: 30000 },\n };\n\n for (const [event, cfg] of Object.entries(hookEntries)) {\n if (!hooksConfig.hooks[event]) hooksConfig.hooks[event] = [];\n const hookDef = { command: cfg.command, timeout: cfg.timeout };\n // Avoid duplicates\n const exists = hooksConfig.hooks[event].some((entry: any) => entry.command === cfg.command);\n if (!exists) hooksConfig.hooks[event].push(hookDef);\n }\n\n fs.writeFileSync(cursorHooksPath, JSON.stringify(hooksConfig, null, 2));\n console.log('Cursor hooks registered.');\n}\n\nmain().catch(console.error);\n","import { APIClient } from '../api-client';\n\nexport async function statusCommand(): Promise<string> {\n const client = new APIClient();\n try {\n const [me, rank] = await Promise.all([client.getStatus(), client.getRank()]);\n return [\n 'hackers.baby Status',\n ` Score: ${(me as any).total_score?.toLocaleString() || 0}`,\n ` Global Rank: #${(rank as any).global || '-'}`,\n ` Streak: ${(me as any).streak_days || 0} days`,\n ` Languages: ${((me as any).languages || []).join(', ') || 'none set'}`,\n ].join('\\n');\n } catch {\n return 'Error fetching status. Run `hackersbaby init` to set up.';\n }\n}\n","import { APIClient } from '../api-client';\n\nexport async function leaderboardCommand(): Promise<string> {\n const client = new APIClient();\n try {\n const data = await client.getLeaderboard();\n const lines = ['Global Leaderboard', ''];\n for (const entry of (data as any).entries || []) {\n const marker = entry.rank <= 3 ? ['', '#1', '#2', '#3'][entry.rank] : `#${entry.rank}`;\n lines.push(` ${marker.padEnd(4)} ${entry.username.padEnd(20)} ${entry.score.toLocaleString()}`);\n }\n return lines.join('\\n');\n } catch {\n return 'Error fetching leaderboard.';\n }\n}\n","import { loadConfig } from '../config';\nimport { execFile } from 'child_process';\n\nexport async function dashboardCommand(): Promise<string> {\n const config = loadConfig();\n const url = `${config?.server || 'https://hackers.baby'}/dashboard`;\n\n const cmd = process.platform === 'darwin' ? 'open' : process.platform === 'win32' ? 'cmd' : 'xdg-open';\n const args = process.platform === 'win32' ? ['/c', 'start', url] : [url];\n execFile(cmd, args, () => {});\n\n return `Opening dashboard at ${url}`;\n}\n","import { loadConfig } from '../config';\nimport { APIClient } from '../api-client';\nimport { execFileSync } from 'child_process';\n\nexport async function flexCommand(args: string[]) {\n const config = loadConfig();\n if (!config?.token) {\n console.log('Not logged in. Run: hackersbaby init');\n return;\n }\n\n const client = new APIClient();\n const [status, rankData, stats] = await Promise.all([\n client.getStatus().catch(() => null),\n client.getRank().catch(() => null),\n client.getPublicStats(),\n ]);\n\n if (!status) {\n console.log('Could not fetch your stats. Are you logged in?');\n return;\n }\n\n const username = String(status.github_username ?? 'unknown');\n const score = Number(status.total_score ?? 0).toLocaleString();\n const rank = rankData?.global_rank ?? '—';\n const streak = Number(status.streak_days ?? 0);\n const totalUsers = stats?.total_users ?? '?';\n const season = stats?.current_season ?? 0;\n\n const refCode = `${username}-${(config.user_id ?? '').slice(0, 3)}`;\n const refUrl = `hackers.baby/ref/${refCode}`;\n\n const c = '\\x1b[36m'; // cyan\n const g = '\\x1b[32m'; // green\n const y = '\\x1b[33m'; // yellow\n const b = '\\x1b[1m'; // bold\n const d = '\\x1b[2m'; // dim\n const r = '\\x1b[0m'; // reset\n\n const pad = (s: string, n: number) => s.length >= n ? s.slice(0, n) : s + ' '.repeat(n - s.length);\n\n const card = [\n '',\n `${c}╔══════════════════════════════════════╗${r}`,\n `${c}║${r} ${b}${g}hackers.baby${r} /// ${y}SEASON ${season}${r} ${c}║${r}`,\n `${c}╠══════════════════════════════════════╣${r}`,\n `${c}║${r} ${b}@${pad(username, 24)}${r}${b}RANK #${pad(String(rank), 4)}${r} ${c}║${r}`,\n `${c}║${r} Score: ${g}${pad(score, 14)}${r} pts ${c}║${r}`,\n `${c}║${r} Streak: ${y}${pad(String(streak), 3)}${r} days ${c}║${r}`,\n `${c}║${r} Players: ${pad(String(totalUsers), 26)}${c}║${r}`,\n `${c}╠══════════════════════════════════════╣${r}`,\n `${c}║${r} ${d}npm i -g @hackersbaby/plugin${r} ${c}║${r}`,\n `${c}║${r} ${d}${pad(refUrl, 34)}${r} ${c}║${r}`,\n `${c}╚══════════════════════════════════════╝${r}`,\n '',\n ].join('\\n');\n\n console.log(card);\n\n if (args.includes('--share')) {\n const shareText = [\n `I'm ranked #${rank} on hackers.baby with ${score} points`,\n '',\n `${streak}-day streak | Season ${season}`,\n '',\n 'Think you can beat me?',\n 'npm i -g @hackersbaby/plugin',\n `https://${refUrl}`,\n ].join('\\n');\n\n let copied = false;\n try {\n const platform = process.platform;\n if (platform === 'darwin') {\n execFileSync('pbcopy', { input: shareText });\n copied = true;\n } else if (platform === 'win32') {\n execFileSync('clip', { input: shareText });\n copied = true;\n } else {\n // Linux: try xclip, fall back to xsel\n try {\n execFileSync('xclip', ['-selection', 'clipboard'], { input: shareText });\n copied = true;\n } catch {\n try {\n execFileSync('xsel', ['--clipboard', '--input'], { input: shareText });\n copied = true;\n } catch {\n // no clipboard tool available\n }\n }\n }\n } catch {\n // clipboard failed\n }\n\n if (copied) {\n console.log(`${g}Copied to clipboard! Paste it on X, Discord, or wherever devs hang out.${r}`);\n } else {\n console.log('\\nShare this:\\n');\n console.log(shareText);\n }\n }\n}\n"],"mappings":"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","names":["fs","path","os","args","import_child_process","args","import_fs","import_path","import_os","import_child_process","readline","APIClient","path","os","fs"]}
@@ -285,6 +285,11 @@ function readStdin() {
285
285
  process.stdin.on("end", () => resolve(input));
286
286
  });
287
287
  }
288
+ function detectSource(payload) {
289
+ if (process.env.CURSOR_VERSION) return "cursor";
290
+ if (payload?.cursor_version) return "cursor";
291
+ return "claude";
292
+ }
288
293
  function normalizePayload(source, raw) {
289
294
  if (source === "cursor") {
290
295
  return { ...raw };
@@ -296,9 +301,11 @@ function normalizePayload(source, raw) {
296
301
  async function handleCompact(source) {
297
302
  const input = await readStdin();
298
303
  try {
299
- const hookData = normalizePayload(source, JSON.parse(input));
300
- const sessionId = hookData.session_id || loadSessionId(source);
301
- const collector = new EventCollector(sessionId, source);
304
+ const raw = JSON.parse(input);
305
+ const detected = detectSource(raw);
306
+ const hookData = normalizePayload(detected, raw);
307
+ const sessionId = hookData.session_id || loadSessionId(detected);
308
+ const collector = new EventCollector(sessionId, detected);
302
309
  collector.addEvent({
303
310
  action_type: "context_compacted",
304
311
  value: 1,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/collector.ts","../../src/config.ts","../../src/api-client.ts","../../src/queue.ts","../../src/hooks/shared/utils.ts","../../src/hooks/shared/compact.ts","../../src/hooks/compact.ts"],"sourcesContent":["import { v4 as uuidv4 } from 'uuid';\nimport { APIClient } from './api-client';\nimport { enqueueEvents, drainQueue } from './queue';\nimport { loadConfig } from './config';\nimport type { Source } from './config';\nimport type { RawEvent, ActionType } from '@hackersbaby/shared';\n\ntype RawEventInput = Omit<RawEvent, 'timestamp'>;\n\nexport class EventCollector {\n readonly sessionId: string;\n private buffer: RawEvent[] = [];\n private flushInterval: ReturnType<typeof setInterval> | null = null;\n private client: APIClient;\n private source?: Source;\n\n constructor(sessionId?: string, source?: Source) {\n this.sessionId = sessionId ?? uuidv4();\n this.client = new APIClient();\n this.source = source;\n }\n\n addEvent(event: RawEventInput): void {\n const metadata = this.source\n ? { ...event.metadata, source: this.source }\n : event.metadata;\n this.buffer.push({\n ...event,\n metadata,\n timestamp: new Date().toISOString(),\n });\n }\n\n async start(): Promise<void> {\n await this.client.refreshTokenIfNeeded();\n\n const queued = drainQueue();\n if (queued.length > 0) {\n const batch = { session_id: this.sessionId, events: queued };\n const ok = await this.client.sendBatch(batch);\n if (!ok) {\n enqueueEvents(queued);\n }\n }\n\n const config = loadConfig();\n const intervalMs = config?.preferences?.batch_interval_ms ?? 10_000;\n this.flushInterval = setInterval(() => {\n this.flush().catch(() => {});\n }, intervalMs);\n }\n\n async flush(): Promise<void> {\n if (this.buffer.length === 0) return;\n const events = this.buffer.splice(0);\n const batch = { session_id: this.sessionId, events };\n const ok = await this.client.sendBatch(batch);\n if (!ok) {\n enqueueEvents(events);\n }\n }\n\n async stop(): Promise<void> {\n if (this.flushInterval !== null) {\n clearInterval(this.flushInterval);\n this.flushInterval = null;\n }\n this.addEvent({ action_type: 'session_completed' as ActionType, value: 1, metadata: {} });\n await this.flush();\n }\n}\n","import fs from 'fs';\nimport path from 'path';\nimport os from 'os';\n\nexport const CONFIG_DIR = path.join(os.homedir(), '.hackersbaby');\nexport const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');\nexport const QUEUE_FILE = path.join(CONFIG_DIR, 'queue.jsonl');\n\nexport type Source = 'claude' | 'cursor';\n\nexport interface PluginConfig {\n token: string;\n refresh_token: string;\n user_id: string;\n server: string;\n targets?: Source[];\n preferences: {\n dashboard_port: number;\n batch_interval_ms: number;\n };\n}\n\nexport function sessionStateFile(source: Source): string {\n return path.join(CONFIG_DIR, `active-session-${source}.json`);\n}\n\nexport function ensureConfigDir(): void {\n if (!fs.existsSync(CONFIG_DIR)) {\n fs.mkdirSync(CONFIG_DIR, { recursive: true });\n }\n}\n\nexport function loadConfig(): PluginConfig | null {\n try {\n if (!fs.existsSync(CONFIG_FILE)) return null;\n const raw = fs.readFileSync(CONFIG_FILE, 'utf-8');\n return JSON.parse(raw) as PluginConfig;\n } catch {\n return null;\n }\n}\n\nexport function saveConfig(config: PluginConfig): void {\n ensureConfigDir();\n fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2), 'utf-8');\n}\n","import { loadConfig, saveConfig } from './config';\nimport type { EventBatch } from '@hackersbaby/shared';\n\nexport class APIClient {\n private config = loadConfig();\n\n private get headers(): Record<string, string> {\n return {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${this.config?.token || ''}`,\n };\n }\n\n async sendBatch(batch: EventBatch): Promise<boolean> {\n try {\n const server = this.config?.server || 'https://hackers.baby';\n const res = await fetch(`${server}/api/scores/batch`, {\n method: 'POST',\n headers: this.headers,\n body: JSON.stringify(batch),\n });\n return res.ok;\n } catch {\n return false;\n }\n }\n\n async refreshTokenIfNeeded(): Promise<void> {\n if (!this.config) return;\n try {\n const parts = this.config.token.split('.');\n if (parts.length !== 3) return;\n const payload = JSON.parse(Buffer.from(parts[1], 'base64').toString('utf-8'));\n const expMs = payload.exp * 1000;\n const oneDayMs = 24 * 60 * 60 * 1000;\n if (Date.now() + oneDayMs < expMs) return; // more than 1 day remaining, no refresh needed\n\n const res = await fetch(`${this.config.server}/api/auth/cli-token/refresh`, {\n method: 'POST',\n headers: this.headers,\n body: JSON.stringify({ refresh_token: this.config.refresh_token }),\n });\n if (res.ok) {\n const data = (await res.json()) as { token?: string; refresh_token?: string };\n if (data.token) {\n this.config.token = data.token;\n if (data.refresh_token) this.config.refresh_token = data.refresh_token;\n saveConfig(this.config);\n }\n }\n } catch {\n // silently ignore refresh errors\n }\n }\n\n async getStatus(): Promise<Record<string, unknown>> {\n const server = this.config?.server || 'https://hackers.baby';\n const res = await fetch(`${server}/api/users/me`, { headers: this.headers });\n if (!res.ok) throw new Error(`getStatus failed: ${res.status}`);\n return res.json() as Promise<Record<string, unknown>>;\n }\n\n async getRank(): Promise<Record<string, unknown>> {\n const server = this.config?.server || 'https://hackers.baby';\n const res = await fetch(`${server}/api/users/me/rank`, { headers: this.headers });\n if (!res.ok) throw new Error(`getRank failed: ${res.status}`);\n return res.json() as Promise<Record<string, unknown>>;\n }\n\n async getLeaderboard(): Promise<Record<string, unknown>> {\n const server = this.config?.server || 'https://hackers.baby';\n const res = await fetch(`${server}/api/leaderboard/global?limit=10`, { headers: this.headers });\n if (!res.ok) throw new Error(`getLeaderboard failed: ${res.status}`);\n return res.json() as Promise<Record<string, unknown>>;\n }\n\n async getPublicStats(): Promise<Record<string, unknown> | null> {\n try {\n const server = this.config?.server || 'https://hackers.baby';\n const res = await fetch(`${server}/api/stats/public`);\n if (!res.ok) return null;\n return res.json() as Promise<Record<string, unknown>>;\n } catch {\n return null;\n }\n }\n\n async getNotifications(): Promise<{ notifications: Array<{ type: string; message: string }> } | null> {\n try {\n const server = this.config?.server || 'https://hackers.baby';\n const res = await fetch(`${server}/api/users/me/notifications`, { headers: this.headers });\n if (!res.ok) return null;\n return res.json() as Promise<{ notifications: Array<{ type: string; message: string }> }>;\n } catch {\n return null;\n }\n }\n\n async claimReferral(code: string): Promise<Record<string, unknown> | null> {\n try {\n const server = this.config?.server || 'https://hackers.baby';\n const res = await fetch(`${server}/api/referral/claim`, {\n method: 'POST',\n headers: this.headers,\n body: JSON.stringify({ referral_code: code }),\n });\n if (!res.ok) return null;\n return res.json() as Promise<Record<string, unknown>>;\n } catch {\n return null;\n }\n }\n\n async getReferralStats(): Promise<Record<string, unknown> | null> {\n try {\n const server = this.config?.server || 'https://hackers.baby';\n const res = await fetch(`${server}/api/referral/stats`, { headers: this.headers });\n if (!res.ok) return null;\n return res.json() as Promise<Record<string, unknown>>;\n } catch {\n return null;\n }\n }\n}\n","import fs from 'fs';\nimport { QUEUE_FILE, ensureConfigDir } from './config';\nimport type { RawEvent } from '@hackersbaby/shared';\n\nconst MAX_QUEUE_SIZE = 10_000;\nconst MAX_AGE_MS = 24 * 60 * 60 * 1000; // 24 hours\n\nexport function enqueueEvents(events: RawEvent[]): void {\n ensureConfigDir();\n const lines = events.map((e) => JSON.stringify(e)).join('\\n');\n fs.appendFileSync(QUEUE_FILE, lines + '\\n', 'utf-8');\n\n // Trim to MAX_QUEUE_SIZE\n try {\n const content = fs.readFileSync(QUEUE_FILE, 'utf-8');\n const allLines = content.split('\\n').filter((l) => l.trim() !== '');\n if (allLines.length > MAX_QUEUE_SIZE) {\n const trimmed = allLines.slice(allLines.length - MAX_QUEUE_SIZE);\n fs.writeFileSync(QUEUE_FILE, trimmed.join('\\n') + '\\n', 'utf-8');\n }\n } catch {\n // ignore read/write errors on trim\n }\n}\n\nexport function drainQueue(): RawEvent[] {\n try {\n if (!fs.existsSync(QUEUE_FILE)) return [];\n const content = fs.readFileSync(QUEUE_FILE, 'utf-8');\n fs.writeFileSync(QUEUE_FILE, '', 'utf-8');\n\n const now = Date.now();\n const events: RawEvent[] = [];\n for (const line of content.split('\\n')) {\n if (!line.trim()) continue;\n try {\n const event = JSON.parse(line) as RawEvent;\n const age = now - new Date(event.timestamp).getTime();\n if (age <= MAX_AGE_MS) {\n events.push(event);\n }\n } catch {\n // skip malformed lines\n }\n }\n return events;\n } catch {\n return [];\n }\n}\n","import fs from 'fs';\nimport type { Source } from '../../config';\nimport { sessionStateFile } from '../../config';\n\nexport interface SessionState {\n sessionId: string;\n pid: number;\n source: Source;\n}\n\nexport function loadSessionId(source: Source): string | undefined {\n try {\n const file = sessionStateFile(source);\n if (!fs.existsSync(file)) return undefined;\n const data = JSON.parse(fs.readFileSync(file, 'utf-8'));\n return data.sessionId;\n } catch {\n return undefined;\n }\n}\n\nexport function saveSessionState(state: SessionState): void {\n const file = sessionStateFile(state.source);\n fs.writeFileSync(file, JSON.stringify(state));\n}\n\nexport function removeSessionState(source: Source): void {\n const file = sessionStateFile(source);\n if (fs.existsSync(file)) fs.unlinkSync(file);\n}\n\nexport function readStdin(): Promise<string> {\n return new Promise((resolve) => {\n let input = '';\n process.stdin.on('data', (chunk) => { input += chunk; });\n process.stdin.on('end', () => resolve(input));\n });\n}\n\n/**\n * Normalize hook payload across tools.\n * Currently an identity function — Cursor's payload format matches Claude Code's.\n * If formats diverge, add field mappings here (e.g., raw.toolName → tool_name).\n */\nexport function normalizePayload(source: Source, raw: Record<string, unknown>): Record<string, unknown> {\n if (source === 'cursor') {\n return { ...raw };\n }\n return raw;\n}\n","import { EventCollector } from '../../collector';\nimport type { Source } from '../../config';\nimport { loadSessionId, readStdin, normalizePayload } from './utils';\n\nexport async function handleCompact(source: Source): Promise<void> {\n const input = await readStdin();\n try {\n const hookData = normalizePayload(source, JSON.parse(input));\n const sessionId = hookData.session_id || loadSessionId(source);\n const collector = new EventCollector(sessionId, source);\n\n // PRIVACY: Only track that context compaction occurred. No transcript content sent.\n collector.addEvent({\n action_type: 'context_compacted',\n value: 1,\n metadata: {},\n });\n\n await collector.flush();\n } catch {}\n}\n","import { handleCompact } from './shared/compact';\nhandleCompact('claude');\n"],"mappings":"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","names":["path","os","fs","import_fs","fs","uuidv4","import_fs","fs"]}
1
+ {"version":3,"sources":["../../src/collector.ts","../../src/config.ts","../../src/api-client.ts","../../src/queue.ts","../../src/hooks/shared/utils.ts","../../src/hooks/shared/compact.ts","../../src/hooks/compact.ts"],"sourcesContent":["import { v4 as uuidv4 } from 'uuid';\nimport { APIClient } from './api-client';\nimport { enqueueEvents, drainQueue } from './queue';\nimport { loadConfig } from './config';\nimport type { Source } from './config';\nimport type { RawEvent, ActionType } from '@hackersbaby/shared';\n\ntype RawEventInput = Omit<RawEvent, 'timestamp'>;\n\nexport class EventCollector {\n readonly sessionId: string;\n private buffer: RawEvent[] = [];\n private flushInterval: ReturnType<typeof setInterval> | null = null;\n private client: APIClient;\n private source?: Source;\n\n constructor(sessionId?: string, source?: Source) {\n this.sessionId = sessionId ?? uuidv4();\n this.client = new APIClient();\n this.source = source;\n }\n\n addEvent(event: RawEventInput): void {\n const metadata = this.source\n ? { ...event.metadata, source: this.source }\n : event.metadata;\n this.buffer.push({\n ...event,\n metadata,\n timestamp: new Date().toISOString(),\n });\n }\n\n async start(): Promise<void> {\n await this.client.refreshTokenIfNeeded();\n\n const queued = drainQueue();\n if (queued.length > 0) {\n const batch = { session_id: this.sessionId, events: queued };\n const ok = await this.client.sendBatch(batch);\n if (!ok) {\n enqueueEvents(queued);\n }\n }\n\n const config = loadConfig();\n const intervalMs = config?.preferences?.batch_interval_ms ?? 10_000;\n this.flushInterval = setInterval(() => {\n this.flush().catch(() => {});\n }, intervalMs);\n }\n\n async flush(): Promise<void> {\n if (this.buffer.length === 0) return;\n const events = this.buffer.splice(0);\n const batch = { session_id: this.sessionId, events };\n const ok = await this.client.sendBatch(batch);\n if (!ok) {\n enqueueEvents(events);\n }\n }\n\n async stop(): Promise<void> {\n if (this.flushInterval !== null) {\n clearInterval(this.flushInterval);\n this.flushInterval = null;\n }\n this.addEvent({ action_type: 'session_completed' as ActionType, value: 1, metadata: {} });\n await this.flush();\n }\n}\n","import fs from 'fs';\nimport path from 'path';\nimport os from 'os';\n\nexport const CONFIG_DIR = path.join(os.homedir(), '.hackersbaby');\nexport const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');\nexport const QUEUE_FILE = path.join(CONFIG_DIR, 'queue.jsonl');\n\nexport type Source = 'claude' | 'cursor';\n\nexport interface PluginConfig {\n token: string;\n refresh_token: string;\n user_id: string;\n server: string;\n targets?: Source[];\n preferences: {\n dashboard_port: number;\n batch_interval_ms: number;\n };\n}\n\nexport function sessionStateFile(source: Source): string {\n return path.join(CONFIG_DIR, `active-session-${source}.json`);\n}\n\nexport function ensureConfigDir(): void {\n if (!fs.existsSync(CONFIG_DIR)) {\n fs.mkdirSync(CONFIG_DIR, { recursive: true });\n }\n}\n\nexport function loadConfig(): PluginConfig | null {\n try {\n if (!fs.existsSync(CONFIG_FILE)) return null;\n const raw = fs.readFileSync(CONFIG_FILE, 'utf-8');\n return JSON.parse(raw) as PluginConfig;\n } catch {\n return null;\n }\n}\n\nexport function saveConfig(config: PluginConfig): void {\n ensureConfigDir();\n fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2), 'utf-8');\n}\n","import { loadConfig, saveConfig } from './config';\nimport type { EventBatch } from '@hackersbaby/shared';\n\nexport class APIClient {\n private config = loadConfig();\n\n private get headers(): Record<string, string> {\n return {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${this.config?.token || ''}`,\n };\n }\n\n async sendBatch(batch: EventBatch): Promise<boolean> {\n try {\n const server = this.config?.server || 'https://hackers.baby';\n const res = await fetch(`${server}/api/scores/batch`, {\n method: 'POST',\n headers: this.headers,\n body: JSON.stringify(batch),\n });\n return res.ok;\n } catch {\n return false;\n }\n }\n\n async refreshTokenIfNeeded(): Promise<void> {\n if (!this.config) return;\n try {\n const parts = this.config.token.split('.');\n if (parts.length !== 3) return;\n const payload = JSON.parse(Buffer.from(parts[1], 'base64').toString('utf-8'));\n const expMs = payload.exp * 1000;\n const oneDayMs = 24 * 60 * 60 * 1000;\n if (Date.now() + oneDayMs < expMs) return; // more than 1 day remaining, no refresh needed\n\n const res = await fetch(`${this.config.server}/api/auth/cli-token/refresh`, {\n method: 'POST',\n headers: this.headers,\n body: JSON.stringify({ refresh_token: this.config.refresh_token }),\n });\n if (res.ok) {\n const data = (await res.json()) as { token?: string; refresh_token?: string };\n if (data.token) {\n this.config.token = data.token;\n if (data.refresh_token) this.config.refresh_token = data.refresh_token;\n saveConfig(this.config);\n }\n }\n } catch {\n // silently ignore refresh errors\n }\n }\n\n async getStatus(): Promise<Record<string, unknown>> {\n const server = this.config?.server || 'https://hackers.baby';\n const res = await fetch(`${server}/api/users/me`, { headers: this.headers });\n if (!res.ok) throw new Error(`getStatus failed: ${res.status}`);\n return res.json() as Promise<Record<string, unknown>>;\n }\n\n async getRank(): Promise<Record<string, unknown>> {\n const server = this.config?.server || 'https://hackers.baby';\n const res = await fetch(`${server}/api/users/me/rank`, { headers: this.headers });\n if (!res.ok) throw new Error(`getRank failed: ${res.status}`);\n return res.json() as Promise<Record<string, unknown>>;\n }\n\n async getLeaderboard(): Promise<Record<string, unknown>> {\n const server = this.config?.server || 'https://hackers.baby';\n const res = await fetch(`${server}/api/leaderboard/global?limit=10`, { headers: this.headers });\n if (!res.ok) throw new Error(`getLeaderboard failed: ${res.status}`);\n return res.json() as Promise<Record<string, unknown>>;\n }\n\n async getPublicStats(): Promise<Record<string, unknown> | null> {\n try {\n const server = this.config?.server || 'https://hackers.baby';\n const res = await fetch(`${server}/api/stats/public`);\n if (!res.ok) return null;\n return res.json() as Promise<Record<string, unknown>>;\n } catch {\n return null;\n }\n }\n\n async getNotifications(): Promise<{ notifications: Array<{ type: string; message: string }> } | null> {\n try {\n const server = this.config?.server || 'https://hackers.baby';\n const res = await fetch(`${server}/api/users/me/notifications`, { headers: this.headers });\n if (!res.ok) return null;\n return res.json() as Promise<{ notifications: Array<{ type: string; message: string }> }>;\n } catch {\n return null;\n }\n }\n\n async claimReferral(code: string): Promise<Record<string, unknown> | null> {\n try {\n const server = this.config?.server || 'https://hackers.baby';\n const res = await fetch(`${server}/api/referral/claim`, {\n method: 'POST',\n headers: this.headers,\n body: JSON.stringify({ referral_code: code }),\n });\n if (!res.ok) return null;\n return res.json() as Promise<Record<string, unknown>>;\n } catch {\n return null;\n }\n }\n\n async getReferralStats(): Promise<Record<string, unknown> | null> {\n try {\n const server = this.config?.server || 'https://hackers.baby';\n const res = await fetch(`${server}/api/referral/stats`, { headers: this.headers });\n if (!res.ok) return null;\n return res.json() as Promise<Record<string, unknown>>;\n } catch {\n return null;\n }\n }\n}\n","import fs from 'fs';\nimport { QUEUE_FILE, ensureConfigDir } from './config';\nimport type { RawEvent } from '@hackersbaby/shared';\n\nconst MAX_QUEUE_SIZE = 10_000;\nconst MAX_AGE_MS = 24 * 60 * 60 * 1000; // 24 hours\n\nexport function enqueueEvents(events: RawEvent[]): void {\n ensureConfigDir();\n const lines = events.map((e) => JSON.stringify(e)).join('\\n');\n fs.appendFileSync(QUEUE_FILE, lines + '\\n', 'utf-8');\n\n // Trim to MAX_QUEUE_SIZE\n try {\n const content = fs.readFileSync(QUEUE_FILE, 'utf-8');\n const allLines = content.split('\\n').filter((l) => l.trim() !== '');\n if (allLines.length > MAX_QUEUE_SIZE) {\n const trimmed = allLines.slice(allLines.length - MAX_QUEUE_SIZE);\n fs.writeFileSync(QUEUE_FILE, trimmed.join('\\n') + '\\n', 'utf-8');\n }\n } catch {\n // ignore read/write errors on trim\n }\n}\n\nexport function drainQueue(): RawEvent[] {\n try {\n if (!fs.existsSync(QUEUE_FILE)) return [];\n const content = fs.readFileSync(QUEUE_FILE, 'utf-8');\n fs.writeFileSync(QUEUE_FILE, '', 'utf-8');\n\n const now = Date.now();\n const events: RawEvent[] = [];\n for (const line of content.split('\\n')) {\n if (!line.trim()) continue;\n try {\n const event = JSON.parse(line) as RawEvent;\n const age = now - new Date(event.timestamp).getTime();\n if (age <= MAX_AGE_MS) {\n events.push(event);\n }\n } catch {\n // skip malformed lines\n }\n }\n return events;\n } catch {\n return [];\n }\n}\n","import fs from 'fs';\nimport type { Source } from '../../config';\nimport { sessionStateFile } from '../../config';\n\nexport interface SessionState {\n sessionId: string;\n pid: number;\n source: Source;\n}\n\nexport function loadSessionId(source: Source): string | undefined {\n try {\n const file = sessionStateFile(source);\n if (!fs.existsSync(file)) return undefined;\n const data = JSON.parse(fs.readFileSync(file, 'utf-8'));\n return data.sessionId;\n } catch {\n return undefined;\n }\n}\n\nexport function saveSessionState(state: SessionState): void {\n const file = sessionStateFile(state.source);\n fs.writeFileSync(file, JSON.stringify(state));\n}\n\nexport function removeSessionState(source: Source): void {\n const file = sessionStateFile(source);\n if (fs.existsSync(file)) fs.unlinkSync(file);\n}\n\nexport function readStdin(): Promise<string> {\n return new Promise((resolve) => {\n let input = '';\n process.stdin.on('data', (chunk) => { input += chunk; });\n process.stdin.on('end', () => resolve(input));\n });\n}\n\n/**\n * Detect source at runtime. Cursor sets CURSOR_VERSION env var\n * and includes cursor_version in the hook payload.\n * Since Cursor reads ~/.claude/settings.json directly, both tools\n * fire the same hooks — so we detect rather than relying on entry points.\n */\nexport function detectSource(payload?: Record<string, unknown>): Source {\n if (process.env.CURSOR_VERSION) return 'cursor';\n if (payload?.cursor_version) return 'cursor';\n return 'claude';\n}\n\n/**\n * Normalize hook payload across tools.\n * Currently an identity function — Cursor's payload format matches Claude Code's.\n * If formats diverge, add field mappings here (e.g., raw.toolName → tool_name).\n */\nexport function normalizePayload(source: Source, raw: Record<string, unknown>): Record<string, unknown> {\n if (source === 'cursor') {\n return { ...raw };\n }\n return raw;\n}\n","import { EventCollector } from '../../collector';\nimport type { Source } from '../../config';\nimport { loadSessionId, readStdin, normalizePayload, detectSource } from './utils';\n\nexport async function handleCompact(source: Source): Promise<void> {\n const input = await readStdin();\n try {\n const raw = JSON.parse(input);\n const detected = detectSource(raw);\n const hookData = normalizePayload(detected, raw);\n const sessionId = hookData.session_id || loadSessionId(detected);\n const collector = new EventCollector(sessionId, detected);\n\n // PRIVACY: Only track that context compaction occurred. No transcript content sent.\n collector.addEvent({\n action_type: 'context_compacted',\n value: 1,\n metadata: {},\n });\n\n await collector.flush();\n } catch {}\n}\n","import { handleCompact } from './shared/compact';\nhandleCompact('claude');\n"],"mappings":"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","names":["path","os","fs","import_fs","fs","uuidv4","import_fs","fs"]}
@@ -285,6 +285,11 @@ function readStdin() {
285
285
  process.stdin.on("end", () => resolve(input));
286
286
  });
287
287
  }
288
+ function detectSource(payload) {
289
+ if (process.env.CURSOR_VERSION) return "cursor";
290
+ if (payload?.cursor_version) return "cursor";
291
+ return "claude";
292
+ }
288
293
  function normalizePayload(source, raw) {
289
294
  if (source === "cursor") {
290
295
  return { ...raw };
@@ -296,9 +301,11 @@ function normalizePayload(source, raw) {
296
301
  async function handleCompact(source) {
297
302
  const input = await readStdin();
298
303
  try {
299
- const hookData = normalizePayload(source, JSON.parse(input));
300
- const sessionId = hookData.session_id || loadSessionId(source);
301
- const collector = new EventCollector(sessionId, source);
304
+ const raw = JSON.parse(input);
305
+ const detected = detectSource(raw);
306
+ const hookData = normalizePayload(detected, raw);
307
+ const sessionId = hookData.session_id || loadSessionId(detected);
308
+ const collector = new EventCollector(sessionId, detected);
302
309
  collector.addEvent({
303
310
  action_type: "context_compacted",
304
311
  value: 1,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/collector.ts","../../../src/config.ts","../../../src/api-client.ts","../../../src/queue.ts","../../../src/hooks/shared/utils.ts","../../../src/hooks/shared/compact.ts","../../../src/hooks/cursor/compact.ts"],"sourcesContent":["import { v4 as uuidv4 } from 'uuid';\nimport { APIClient } from './api-client';\nimport { enqueueEvents, drainQueue } from './queue';\nimport { loadConfig } from './config';\nimport type { Source } from './config';\nimport type { RawEvent, ActionType } from '@hackersbaby/shared';\n\ntype RawEventInput = Omit<RawEvent, 'timestamp'>;\n\nexport class EventCollector {\n readonly sessionId: string;\n private buffer: RawEvent[] = [];\n private flushInterval: ReturnType<typeof setInterval> | null = null;\n private client: APIClient;\n private source?: Source;\n\n constructor(sessionId?: string, source?: Source) {\n this.sessionId = sessionId ?? uuidv4();\n this.client = new APIClient();\n this.source = source;\n }\n\n addEvent(event: RawEventInput): void {\n const metadata = this.source\n ? { ...event.metadata, source: this.source }\n : event.metadata;\n this.buffer.push({\n ...event,\n metadata,\n timestamp: new Date().toISOString(),\n });\n }\n\n async start(): Promise<void> {\n await this.client.refreshTokenIfNeeded();\n\n const queued = drainQueue();\n if (queued.length > 0) {\n const batch = { session_id: this.sessionId, events: queued };\n const ok = await this.client.sendBatch(batch);\n if (!ok) {\n enqueueEvents(queued);\n }\n }\n\n const config = loadConfig();\n const intervalMs = config?.preferences?.batch_interval_ms ?? 10_000;\n this.flushInterval = setInterval(() => {\n this.flush().catch(() => {});\n }, intervalMs);\n }\n\n async flush(): Promise<void> {\n if (this.buffer.length === 0) return;\n const events = this.buffer.splice(0);\n const batch = { session_id: this.sessionId, events };\n const ok = await this.client.sendBatch(batch);\n if (!ok) {\n enqueueEvents(events);\n }\n }\n\n async stop(): Promise<void> {\n if (this.flushInterval !== null) {\n clearInterval(this.flushInterval);\n this.flushInterval = null;\n }\n this.addEvent({ action_type: 'session_completed' as ActionType, value: 1, metadata: {} });\n await this.flush();\n }\n}\n","import fs from 'fs';\nimport path from 'path';\nimport os from 'os';\n\nexport const CONFIG_DIR = path.join(os.homedir(), '.hackersbaby');\nexport const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');\nexport const QUEUE_FILE = path.join(CONFIG_DIR, 'queue.jsonl');\n\nexport type Source = 'claude' | 'cursor';\n\nexport interface PluginConfig {\n token: string;\n refresh_token: string;\n user_id: string;\n server: string;\n targets?: Source[];\n preferences: {\n dashboard_port: number;\n batch_interval_ms: number;\n };\n}\n\nexport function sessionStateFile(source: Source): string {\n return path.join(CONFIG_DIR, `active-session-${source}.json`);\n}\n\nexport function ensureConfigDir(): void {\n if (!fs.existsSync(CONFIG_DIR)) {\n fs.mkdirSync(CONFIG_DIR, { recursive: true });\n }\n}\n\nexport function loadConfig(): PluginConfig | null {\n try {\n if (!fs.existsSync(CONFIG_FILE)) return null;\n const raw = fs.readFileSync(CONFIG_FILE, 'utf-8');\n return JSON.parse(raw) as PluginConfig;\n } catch {\n return null;\n }\n}\n\nexport function saveConfig(config: PluginConfig): void {\n ensureConfigDir();\n fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2), 'utf-8');\n}\n","import { loadConfig, saveConfig } from './config';\nimport type { EventBatch } from '@hackersbaby/shared';\n\nexport class APIClient {\n private config = loadConfig();\n\n private get headers(): Record<string, string> {\n return {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${this.config?.token || ''}`,\n };\n }\n\n async sendBatch(batch: EventBatch): Promise<boolean> {\n try {\n const server = this.config?.server || 'https://hackers.baby';\n const res = await fetch(`${server}/api/scores/batch`, {\n method: 'POST',\n headers: this.headers,\n body: JSON.stringify(batch),\n });\n return res.ok;\n } catch {\n return false;\n }\n }\n\n async refreshTokenIfNeeded(): Promise<void> {\n if (!this.config) return;\n try {\n const parts = this.config.token.split('.');\n if (parts.length !== 3) return;\n const payload = JSON.parse(Buffer.from(parts[1], 'base64').toString('utf-8'));\n const expMs = payload.exp * 1000;\n const oneDayMs = 24 * 60 * 60 * 1000;\n if (Date.now() + oneDayMs < expMs) return; // more than 1 day remaining, no refresh needed\n\n const res = await fetch(`${this.config.server}/api/auth/cli-token/refresh`, {\n method: 'POST',\n headers: this.headers,\n body: JSON.stringify({ refresh_token: this.config.refresh_token }),\n });\n if (res.ok) {\n const data = (await res.json()) as { token?: string; refresh_token?: string };\n if (data.token) {\n this.config.token = data.token;\n if (data.refresh_token) this.config.refresh_token = data.refresh_token;\n saveConfig(this.config);\n }\n }\n } catch {\n // silently ignore refresh errors\n }\n }\n\n async getStatus(): Promise<Record<string, unknown>> {\n const server = this.config?.server || 'https://hackers.baby';\n const res = await fetch(`${server}/api/users/me`, { headers: this.headers });\n if (!res.ok) throw new Error(`getStatus failed: ${res.status}`);\n return res.json() as Promise<Record<string, unknown>>;\n }\n\n async getRank(): Promise<Record<string, unknown>> {\n const server = this.config?.server || 'https://hackers.baby';\n const res = await fetch(`${server}/api/users/me/rank`, { headers: this.headers });\n if (!res.ok) throw new Error(`getRank failed: ${res.status}`);\n return res.json() as Promise<Record<string, unknown>>;\n }\n\n async getLeaderboard(): Promise<Record<string, unknown>> {\n const server = this.config?.server || 'https://hackers.baby';\n const res = await fetch(`${server}/api/leaderboard/global?limit=10`, { headers: this.headers });\n if (!res.ok) throw new Error(`getLeaderboard failed: ${res.status}`);\n return res.json() as Promise<Record<string, unknown>>;\n }\n\n async getPublicStats(): Promise<Record<string, unknown> | null> {\n try {\n const server = this.config?.server || 'https://hackers.baby';\n const res = await fetch(`${server}/api/stats/public`);\n if (!res.ok) return null;\n return res.json() as Promise<Record<string, unknown>>;\n } catch {\n return null;\n }\n }\n\n async getNotifications(): Promise<{ notifications: Array<{ type: string; message: string }> } | null> {\n try {\n const server = this.config?.server || 'https://hackers.baby';\n const res = await fetch(`${server}/api/users/me/notifications`, { headers: this.headers });\n if (!res.ok) return null;\n return res.json() as Promise<{ notifications: Array<{ type: string; message: string }> }>;\n } catch {\n return null;\n }\n }\n\n async claimReferral(code: string): Promise<Record<string, unknown> | null> {\n try {\n const server = this.config?.server || 'https://hackers.baby';\n const res = await fetch(`${server}/api/referral/claim`, {\n method: 'POST',\n headers: this.headers,\n body: JSON.stringify({ referral_code: code }),\n });\n if (!res.ok) return null;\n return res.json() as Promise<Record<string, unknown>>;\n } catch {\n return null;\n }\n }\n\n async getReferralStats(): Promise<Record<string, unknown> | null> {\n try {\n const server = this.config?.server || 'https://hackers.baby';\n const res = await fetch(`${server}/api/referral/stats`, { headers: this.headers });\n if (!res.ok) return null;\n return res.json() as Promise<Record<string, unknown>>;\n } catch {\n return null;\n }\n }\n}\n","import fs from 'fs';\nimport { QUEUE_FILE, ensureConfigDir } from './config';\nimport type { RawEvent } from '@hackersbaby/shared';\n\nconst MAX_QUEUE_SIZE = 10_000;\nconst MAX_AGE_MS = 24 * 60 * 60 * 1000; // 24 hours\n\nexport function enqueueEvents(events: RawEvent[]): void {\n ensureConfigDir();\n const lines = events.map((e) => JSON.stringify(e)).join('\\n');\n fs.appendFileSync(QUEUE_FILE, lines + '\\n', 'utf-8');\n\n // Trim to MAX_QUEUE_SIZE\n try {\n const content = fs.readFileSync(QUEUE_FILE, 'utf-8');\n const allLines = content.split('\\n').filter((l) => l.trim() !== '');\n if (allLines.length > MAX_QUEUE_SIZE) {\n const trimmed = allLines.slice(allLines.length - MAX_QUEUE_SIZE);\n fs.writeFileSync(QUEUE_FILE, trimmed.join('\\n') + '\\n', 'utf-8');\n }\n } catch {\n // ignore read/write errors on trim\n }\n}\n\nexport function drainQueue(): RawEvent[] {\n try {\n if (!fs.existsSync(QUEUE_FILE)) return [];\n const content = fs.readFileSync(QUEUE_FILE, 'utf-8');\n fs.writeFileSync(QUEUE_FILE, '', 'utf-8');\n\n const now = Date.now();\n const events: RawEvent[] = [];\n for (const line of content.split('\\n')) {\n if (!line.trim()) continue;\n try {\n const event = JSON.parse(line) as RawEvent;\n const age = now - new Date(event.timestamp).getTime();\n if (age <= MAX_AGE_MS) {\n events.push(event);\n }\n } catch {\n // skip malformed lines\n }\n }\n return events;\n } catch {\n return [];\n }\n}\n","import fs from 'fs';\nimport type { Source } from '../../config';\nimport { sessionStateFile } from '../../config';\n\nexport interface SessionState {\n sessionId: string;\n pid: number;\n source: Source;\n}\n\nexport function loadSessionId(source: Source): string | undefined {\n try {\n const file = sessionStateFile(source);\n if (!fs.existsSync(file)) return undefined;\n const data = JSON.parse(fs.readFileSync(file, 'utf-8'));\n return data.sessionId;\n } catch {\n return undefined;\n }\n}\n\nexport function saveSessionState(state: SessionState): void {\n const file = sessionStateFile(state.source);\n fs.writeFileSync(file, JSON.stringify(state));\n}\n\nexport function removeSessionState(source: Source): void {\n const file = sessionStateFile(source);\n if (fs.existsSync(file)) fs.unlinkSync(file);\n}\n\nexport function readStdin(): Promise<string> {\n return new Promise((resolve) => {\n let input = '';\n process.stdin.on('data', (chunk) => { input += chunk; });\n process.stdin.on('end', () => resolve(input));\n });\n}\n\n/**\n * Normalize hook payload across tools.\n * Currently an identity function — Cursor's payload format matches Claude Code's.\n * If formats diverge, add field mappings here (e.g., raw.toolName → tool_name).\n */\nexport function normalizePayload(source: Source, raw: Record<string, unknown>): Record<string, unknown> {\n if (source === 'cursor') {\n return { ...raw };\n }\n return raw;\n}\n","import { EventCollector } from '../../collector';\nimport type { Source } from '../../config';\nimport { loadSessionId, readStdin, normalizePayload } from './utils';\n\nexport async function handleCompact(source: Source): Promise<void> {\n const input = await readStdin();\n try {\n const hookData = normalizePayload(source, JSON.parse(input));\n const sessionId = hookData.session_id || loadSessionId(source);\n const collector = new EventCollector(sessionId, source);\n\n // PRIVACY: Only track that context compaction occurred. No transcript content sent.\n collector.addEvent({\n action_type: 'context_compacted',\n value: 1,\n metadata: {},\n });\n\n await collector.flush();\n } catch {}\n}\n","import { handleCompact } from '../shared/compact';\nhandleCompact('cursor');\n"],"mappings":"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","names":["path","os","fs","import_fs","fs","uuidv4","import_fs","fs"]}
1
+ {"version":3,"sources":["../../../src/collector.ts","../../../src/config.ts","../../../src/api-client.ts","../../../src/queue.ts","../../../src/hooks/shared/utils.ts","../../../src/hooks/shared/compact.ts","../../../src/hooks/cursor/compact.ts"],"sourcesContent":["import { v4 as uuidv4 } from 'uuid';\nimport { APIClient } from './api-client';\nimport { enqueueEvents, drainQueue } from './queue';\nimport { loadConfig } from './config';\nimport type { Source } from './config';\nimport type { RawEvent, ActionType } from '@hackersbaby/shared';\n\ntype RawEventInput = Omit<RawEvent, 'timestamp'>;\n\nexport class EventCollector {\n readonly sessionId: string;\n private buffer: RawEvent[] = [];\n private flushInterval: ReturnType<typeof setInterval> | null = null;\n private client: APIClient;\n private source?: Source;\n\n constructor(sessionId?: string, source?: Source) {\n this.sessionId = sessionId ?? uuidv4();\n this.client = new APIClient();\n this.source = source;\n }\n\n addEvent(event: RawEventInput): void {\n const metadata = this.source\n ? { ...event.metadata, source: this.source }\n : event.metadata;\n this.buffer.push({\n ...event,\n metadata,\n timestamp: new Date().toISOString(),\n });\n }\n\n async start(): Promise<void> {\n await this.client.refreshTokenIfNeeded();\n\n const queued = drainQueue();\n if (queued.length > 0) {\n const batch = { session_id: this.sessionId, events: queued };\n const ok = await this.client.sendBatch(batch);\n if (!ok) {\n enqueueEvents(queued);\n }\n }\n\n const config = loadConfig();\n const intervalMs = config?.preferences?.batch_interval_ms ?? 10_000;\n this.flushInterval = setInterval(() => {\n this.flush().catch(() => {});\n }, intervalMs);\n }\n\n async flush(): Promise<void> {\n if (this.buffer.length === 0) return;\n const events = this.buffer.splice(0);\n const batch = { session_id: this.sessionId, events };\n const ok = await this.client.sendBatch(batch);\n if (!ok) {\n enqueueEvents(events);\n }\n }\n\n async stop(): Promise<void> {\n if (this.flushInterval !== null) {\n clearInterval(this.flushInterval);\n this.flushInterval = null;\n }\n this.addEvent({ action_type: 'session_completed' as ActionType, value: 1, metadata: {} });\n await this.flush();\n }\n}\n","import fs from 'fs';\nimport path from 'path';\nimport os from 'os';\n\nexport const CONFIG_DIR = path.join(os.homedir(), '.hackersbaby');\nexport const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');\nexport const QUEUE_FILE = path.join(CONFIG_DIR, 'queue.jsonl');\n\nexport type Source = 'claude' | 'cursor';\n\nexport interface PluginConfig {\n token: string;\n refresh_token: string;\n user_id: string;\n server: string;\n targets?: Source[];\n preferences: {\n dashboard_port: number;\n batch_interval_ms: number;\n };\n}\n\nexport function sessionStateFile(source: Source): string {\n return path.join(CONFIG_DIR, `active-session-${source}.json`);\n}\n\nexport function ensureConfigDir(): void {\n if (!fs.existsSync(CONFIG_DIR)) {\n fs.mkdirSync(CONFIG_DIR, { recursive: true });\n }\n}\n\nexport function loadConfig(): PluginConfig | null {\n try {\n if (!fs.existsSync(CONFIG_FILE)) return null;\n const raw = fs.readFileSync(CONFIG_FILE, 'utf-8');\n return JSON.parse(raw) as PluginConfig;\n } catch {\n return null;\n }\n}\n\nexport function saveConfig(config: PluginConfig): void {\n ensureConfigDir();\n fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2), 'utf-8');\n}\n","import { loadConfig, saveConfig } from './config';\nimport type { EventBatch } from '@hackersbaby/shared';\n\nexport class APIClient {\n private config = loadConfig();\n\n private get headers(): Record<string, string> {\n return {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${this.config?.token || ''}`,\n };\n }\n\n async sendBatch(batch: EventBatch): Promise<boolean> {\n try {\n const server = this.config?.server || 'https://hackers.baby';\n const res = await fetch(`${server}/api/scores/batch`, {\n method: 'POST',\n headers: this.headers,\n body: JSON.stringify(batch),\n });\n return res.ok;\n } catch {\n return false;\n }\n }\n\n async refreshTokenIfNeeded(): Promise<void> {\n if (!this.config) return;\n try {\n const parts = this.config.token.split('.');\n if (parts.length !== 3) return;\n const payload = JSON.parse(Buffer.from(parts[1], 'base64').toString('utf-8'));\n const expMs = payload.exp * 1000;\n const oneDayMs = 24 * 60 * 60 * 1000;\n if (Date.now() + oneDayMs < expMs) return; // more than 1 day remaining, no refresh needed\n\n const res = await fetch(`${this.config.server}/api/auth/cli-token/refresh`, {\n method: 'POST',\n headers: this.headers,\n body: JSON.stringify({ refresh_token: this.config.refresh_token }),\n });\n if (res.ok) {\n const data = (await res.json()) as { token?: string; refresh_token?: string };\n if (data.token) {\n this.config.token = data.token;\n if (data.refresh_token) this.config.refresh_token = data.refresh_token;\n saveConfig(this.config);\n }\n }\n } catch {\n // silently ignore refresh errors\n }\n }\n\n async getStatus(): Promise<Record<string, unknown>> {\n const server = this.config?.server || 'https://hackers.baby';\n const res = await fetch(`${server}/api/users/me`, { headers: this.headers });\n if (!res.ok) throw new Error(`getStatus failed: ${res.status}`);\n return res.json() as Promise<Record<string, unknown>>;\n }\n\n async getRank(): Promise<Record<string, unknown>> {\n const server = this.config?.server || 'https://hackers.baby';\n const res = await fetch(`${server}/api/users/me/rank`, { headers: this.headers });\n if (!res.ok) throw new Error(`getRank failed: ${res.status}`);\n return res.json() as Promise<Record<string, unknown>>;\n }\n\n async getLeaderboard(): Promise<Record<string, unknown>> {\n const server = this.config?.server || 'https://hackers.baby';\n const res = await fetch(`${server}/api/leaderboard/global?limit=10`, { headers: this.headers });\n if (!res.ok) throw new Error(`getLeaderboard failed: ${res.status}`);\n return res.json() as Promise<Record<string, unknown>>;\n }\n\n async getPublicStats(): Promise<Record<string, unknown> | null> {\n try {\n const server = this.config?.server || 'https://hackers.baby';\n const res = await fetch(`${server}/api/stats/public`);\n if (!res.ok) return null;\n return res.json() as Promise<Record<string, unknown>>;\n } catch {\n return null;\n }\n }\n\n async getNotifications(): Promise<{ notifications: Array<{ type: string; message: string }> } | null> {\n try {\n const server = this.config?.server || 'https://hackers.baby';\n const res = await fetch(`${server}/api/users/me/notifications`, { headers: this.headers });\n if (!res.ok) return null;\n return res.json() as Promise<{ notifications: Array<{ type: string; message: string }> }>;\n } catch {\n return null;\n }\n }\n\n async claimReferral(code: string): Promise<Record<string, unknown> | null> {\n try {\n const server = this.config?.server || 'https://hackers.baby';\n const res = await fetch(`${server}/api/referral/claim`, {\n method: 'POST',\n headers: this.headers,\n body: JSON.stringify({ referral_code: code }),\n });\n if (!res.ok) return null;\n return res.json() as Promise<Record<string, unknown>>;\n } catch {\n return null;\n }\n }\n\n async getReferralStats(): Promise<Record<string, unknown> | null> {\n try {\n const server = this.config?.server || 'https://hackers.baby';\n const res = await fetch(`${server}/api/referral/stats`, { headers: this.headers });\n if (!res.ok) return null;\n return res.json() as Promise<Record<string, unknown>>;\n } catch {\n return null;\n }\n }\n}\n","import fs from 'fs';\nimport { QUEUE_FILE, ensureConfigDir } from './config';\nimport type { RawEvent } from '@hackersbaby/shared';\n\nconst MAX_QUEUE_SIZE = 10_000;\nconst MAX_AGE_MS = 24 * 60 * 60 * 1000; // 24 hours\n\nexport function enqueueEvents(events: RawEvent[]): void {\n ensureConfigDir();\n const lines = events.map((e) => JSON.stringify(e)).join('\\n');\n fs.appendFileSync(QUEUE_FILE, lines + '\\n', 'utf-8');\n\n // Trim to MAX_QUEUE_SIZE\n try {\n const content = fs.readFileSync(QUEUE_FILE, 'utf-8');\n const allLines = content.split('\\n').filter((l) => l.trim() !== '');\n if (allLines.length > MAX_QUEUE_SIZE) {\n const trimmed = allLines.slice(allLines.length - MAX_QUEUE_SIZE);\n fs.writeFileSync(QUEUE_FILE, trimmed.join('\\n') + '\\n', 'utf-8');\n }\n } catch {\n // ignore read/write errors on trim\n }\n}\n\nexport function drainQueue(): RawEvent[] {\n try {\n if (!fs.existsSync(QUEUE_FILE)) return [];\n const content = fs.readFileSync(QUEUE_FILE, 'utf-8');\n fs.writeFileSync(QUEUE_FILE, '', 'utf-8');\n\n const now = Date.now();\n const events: RawEvent[] = [];\n for (const line of content.split('\\n')) {\n if (!line.trim()) continue;\n try {\n const event = JSON.parse(line) as RawEvent;\n const age = now - new Date(event.timestamp).getTime();\n if (age <= MAX_AGE_MS) {\n events.push(event);\n }\n } catch {\n // skip malformed lines\n }\n }\n return events;\n } catch {\n return [];\n }\n}\n","import fs from 'fs';\nimport type { Source } from '../../config';\nimport { sessionStateFile } from '../../config';\n\nexport interface SessionState {\n sessionId: string;\n pid: number;\n source: Source;\n}\n\nexport function loadSessionId(source: Source): string | undefined {\n try {\n const file = sessionStateFile(source);\n if (!fs.existsSync(file)) return undefined;\n const data = JSON.parse(fs.readFileSync(file, 'utf-8'));\n return data.sessionId;\n } catch {\n return undefined;\n }\n}\n\nexport function saveSessionState(state: SessionState): void {\n const file = sessionStateFile(state.source);\n fs.writeFileSync(file, JSON.stringify(state));\n}\n\nexport function removeSessionState(source: Source): void {\n const file = sessionStateFile(source);\n if (fs.existsSync(file)) fs.unlinkSync(file);\n}\n\nexport function readStdin(): Promise<string> {\n return new Promise((resolve) => {\n let input = '';\n process.stdin.on('data', (chunk) => { input += chunk; });\n process.stdin.on('end', () => resolve(input));\n });\n}\n\n/**\n * Detect source at runtime. Cursor sets CURSOR_VERSION env var\n * and includes cursor_version in the hook payload.\n * Since Cursor reads ~/.claude/settings.json directly, both tools\n * fire the same hooks — so we detect rather than relying on entry points.\n */\nexport function detectSource(payload?: Record<string, unknown>): Source {\n if (process.env.CURSOR_VERSION) return 'cursor';\n if (payload?.cursor_version) return 'cursor';\n return 'claude';\n}\n\n/**\n * Normalize hook payload across tools.\n * Currently an identity function — Cursor's payload format matches Claude Code's.\n * If formats diverge, add field mappings here (e.g., raw.toolName → tool_name).\n */\nexport function normalizePayload(source: Source, raw: Record<string, unknown>): Record<string, unknown> {\n if (source === 'cursor') {\n return { ...raw };\n }\n return raw;\n}\n","import { EventCollector } from '../../collector';\nimport type { Source } from '../../config';\nimport { loadSessionId, readStdin, normalizePayload, detectSource } from './utils';\n\nexport async function handleCompact(source: Source): Promise<void> {\n const input = await readStdin();\n try {\n const raw = JSON.parse(input);\n const detected = detectSource(raw);\n const hookData = normalizePayload(detected, raw);\n const sessionId = hookData.session_id || loadSessionId(detected);\n const collector = new EventCollector(sessionId, detected);\n\n // PRIVACY: Only track that context compaction occurred. No transcript content sent.\n collector.addEvent({\n action_type: 'context_compacted',\n value: 1,\n metadata: {},\n });\n\n await collector.flush();\n } catch {}\n}\n","import { handleCompact } from '../shared/compact';\nhandleCompact('cursor');\n"],"mappings":"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","names":["path","os","fs","import_fs","fs","uuidv4","import_fs","fs"]}
@@ -285,6 +285,11 @@ function readStdin() {
285
285
  process.stdin.on("end", () => resolve(input));
286
286
  });
287
287
  }
288
+ function detectSource(payload) {
289
+ if (process.env.CURSOR_VERSION) return "cursor";
290
+ if (payload?.cursor_version) return "cursor";
291
+ return "claude";
292
+ }
288
293
  function normalizePayload(source, raw) {
289
294
  if (source === "cursor") {
290
295
  return { ...raw };
@@ -296,9 +301,11 @@ function normalizePayload(source, raw) {
296
301
  async function handlePromptSubmit(source) {
297
302
  const input = await readStdin();
298
303
  try {
299
- const hookData = normalizePayload(source, JSON.parse(input));
300
- const sessionId = hookData.session_id || loadSessionId(source);
301
- const collector = new EventCollector(sessionId, source);
304
+ const raw = JSON.parse(input);
305
+ const detected = detectSource(raw);
306
+ const hookData = normalizePayload(detected, raw);
307
+ const sessionId = hookData.session_id || loadSessionId(detected);
308
+ const collector = new EventCollector(sessionId, detected);
302
309
  const promptLength = typeof hookData.prompt === "string" ? hookData.prompt.length : 0;
303
310
  collector.addEvent({
304
311
  action_type: "prompt_submitted",
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/collector.ts","../../../src/config.ts","../../../src/api-client.ts","../../../src/queue.ts","../../../src/hooks/shared/utils.ts","../../../src/hooks/shared/prompt-submit.ts","../../../src/hooks/cursor/prompt-submit.ts"],"sourcesContent":["import { v4 as uuidv4 } from 'uuid';\nimport { APIClient } from './api-client';\nimport { enqueueEvents, drainQueue } from './queue';\nimport { loadConfig } from './config';\nimport type { Source } from './config';\nimport type { RawEvent, ActionType } from '@hackersbaby/shared';\n\ntype RawEventInput = Omit<RawEvent, 'timestamp'>;\n\nexport class EventCollector {\n readonly sessionId: string;\n private buffer: RawEvent[] = [];\n private flushInterval: ReturnType<typeof setInterval> | null = null;\n private client: APIClient;\n private source?: Source;\n\n constructor(sessionId?: string, source?: Source) {\n this.sessionId = sessionId ?? uuidv4();\n this.client = new APIClient();\n this.source = source;\n }\n\n addEvent(event: RawEventInput): void {\n const metadata = this.source\n ? { ...event.metadata, source: this.source }\n : event.metadata;\n this.buffer.push({\n ...event,\n metadata,\n timestamp: new Date().toISOString(),\n });\n }\n\n async start(): Promise<void> {\n await this.client.refreshTokenIfNeeded();\n\n const queued = drainQueue();\n if (queued.length > 0) {\n const batch = { session_id: this.sessionId, events: queued };\n const ok = await this.client.sendBatch(batch);\n if (!ok) {\n enqueueEvents(queued);\n }\n }\n\n const config = loadConfig();\n const intervalMs = config?.preferences?.batch_interval_ms ?? 10_000;\n this.flushInterval = setInterval(() => {\n this.flush().catch(() => {});\n }, intervalMs);\n }\n\n async flush(): Promise<void> {\n if (this.buffer.length === 0) return;\n const events = this.buffer.splice(0);\n const batch = { session_id: this.sessionId, events };\n const ok = await this.client.sendBatch(batch);\n if (!ok) {\n enqueueEvents(events);\n }\n }\n\n async stop(): Promise<void> {\n if (this.flushInterval !== null) {\n clearInterval(this.flushInterval);\n this.flushInterval = null;\n }\n this.addEvent({ action_type: 'session_completed' as ActionType, value: 1, metadata: {} });\n await this.flush();\n }\n}\n","import fs from 'fs';\nimport path from 'path';\nimport os from 'os';\n\nexport const CONFIG_DIR = path.join(os.homedir(), '.hackersbaby');\nexport const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');\nexport const QUEUE_FILE = path.join(CONFIG_DIR, 'queue.jsonl');\n\nexport type Source = 'claude' | 'cursor';\n\nexport interface PluginConfig {\n token: string;\n refresh_token: string;\n user_id: string;\n server: string;\n targets?: Source[];\n preferences: {\n dashboard_port: number;\n batch_interval_ms: number;\n };\n}\n\nexport function sessionStateFile(source: Source): string {\n return path.join(CONFIG_DIR, `active-session-${source}.json`);\n}\n\nexport function ensureConfigDir(): void {\n if (!fs.existsSync(CONFIG_DIR)) {\n fs.mkdirSync(CONFIG_DIR, { recursive: true });\n }\n}\n\nexport function loadConfig(): PluginConfig | null {\n try {\n if (!fs.existsSync(CONFIG_FILE)) return null;\n const raw = fs.readFileSync(CONFIG_FILE, 'utf-8');\n return JSON.parse(raw) as PluginConfig;\n } catch {\n return null;\n }\n}\n\nexport function saveConfig(config: PluginConfig): void {\n ensureConfigDir();\n fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2), 'utf-8');\n}\n","import { loadConfig, saveConfig } from './config';\nimport type { EventBatch } from '@hackersbaby/shared';\n\nexport class APIClient {\n private config = loadConfig();\n\n private get headers(): Record<string, string> {\n return {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${this.config?.token || ''}`,\n };\n }\n\n async sendBatch(batch: EventBatch): Promise<boolean> {\n try {\n const server = this.config?.server || 'https://hackers.baby';\n const res = await fetch(`${server}/api/scores/batch`, {\n method: 'POST',\n headers: this.headers,\n body: JSON.stringify(batch),\n });\n return res.ok;\n } catch {\n return false;\n }\n }\n\n async refreshTokenIfNeeded(): Promise<void> {\n if (!this.config) return;\n try {\n const parts = this.config.token.split('.');\n if (parts.length !== 3) return;\n const payload = JSON.parse(Buffer.from(parts[1], 'base64').toString('utf-8'));\n const expMs = payload.exp * 1000;\n const oneDayMs = 24 * 60 * 60 * 1000;\n if (Date.now() + oneDayMs < expMs) return; // more than 1 day remaining, no refresh needed\n\n const res = await fetch(`${this.config.server}/api/auth/cli-token/refresh`, {\n method: 'POST',\n headers: this.headers,\n body: JSON.stringify({ refresh_token: this.config.refresh_token }),\n });\n if (res.ok) {\n const data = (await res.json()) as { token?: string; refresh_token?: string };\n if (data.token) {\n this.config.token = data.token;\n if (data.refresh_token) this.config.refresh_token = data.refresh_token;\n saveConfig(this.config);\n }\n }\n } catch {\n // silently ignore refresh errors\n }\n }\n\n async getStatus(): Promise<Record<string, unknown>> {\n const server = this.config?.server || 'https://hackers.baby';\n const res = await fetch(`${server}/api/users/me`, { headers: this.headers });\n if (!res.ok) throw new Error(`getStatus failed: ${res.status}`);\n return res.json() as Promise<Record<string, unknown>>;\n }\n\n async getRank(): Promise<Record<string, unknown>> {\n const server = this.config?.server || 'https://hackers.baby';\n const res = await fetch(`${server}/api/users/me/rank`, { headers: this.headers });\n if (!res.ok) throw new Error(`getRank failed: ${res.status}`);\n return res.json() as Promise<Record<string, unknown>>;\n }\n\n async getLeaderboard(): Promise<Record<string, unknown>> {\n const server = this.config?.server || 'https://hackers.baby';\n const res = await fetch(`${server}/api/leaderboard/global?limit=10`, { headers: this.headers });\n if (!res.ok) throw new Error(`getLeaderboard failed: ${res.status}`);\n return res.json() as Promise<Record<string, unknown>>;\n }\n\n async getPublicStats(): Promise<Record<string, unknown> | null> {\n try {\n const server = this.config?.server || 'https://hackers.baby';\n const res = await fetch(`${server}/api/stats/public`);\n if (!res.ok) return null;\n return res.json() as Promise<Record<string, unknown>>;\n } catch {\n return null;\n }\n }\n\n async getNotifications(): Promise<{ notifications: Array<{ type: string; message: string }> } | null> {\n try {\n const server = this.config?.server || 'https://hackers.baby';\n const res = await fetch(`${server}/api/users/me/notifications`, { headers: this.headers });\n if (!res.ok) return null;\n return res.json() as Promise<{ notifications: Array<{ type: string; message: string }> }>;\n } catch {\n return null;\n }\n }\n\n async claimReferral(code: string): Promise<Record<string, unknown> | null> {\n try {\n const server = this.config?.server || 'https://hackers.baby';\n const res = await fetch(`${server}/api/referral/claim`, {\n method: 'POST',\n headers: this.headers,\n body: JSON.stringify({ referral_code: code }),\n });\n if (!res.ok) return null;\n return res.json() as Promise<Record<string, unknown>>;\n } catch {\n return null;\n }\n }\n\n async getReferralStats(): Promise<Record<string, unknown> | null> {\n try {\n const server = this.config?.server || 'https://hackers.baby';\n const res = await fetch(`${server}/api/referral/stats`, { headers: this.headers });\n if (!res.ok) return null;\n return res.json() as Promise<Record<string, unknown>>;\n } catch {\n return null;\n }\n }\n}\n","import fs from 'fs';\nimport { QUEUE_FILE, ensureConfigDir } from './config';\nimport type { RawEvent } from '@hackersbaby/shared';\n\nconst MAX_QUEUE_SIZE = 10_000;\nconst MAX_AGE_MS = 24 * 60 * 60 * 1000; // 24 hours\n\nexport function enqueueEvents(events: RawEvent[]): void {\n ensureConfigDir();\n const lines = events.map((e) => JSON.stringify(e)).join('\\n');\n fs.appendFileSync(QUEUE_FILE, lines + '\\n', 'utf-8');\n\n // Trim to MAX_QUEUE_SIZE\n try {\n const content = fs.readFileSync(QUEUE_FILE, 'utf-8');\n const allLines = content.split('\\n').filter((l) => l.trim() !== '');\n if (allLines.length > MAX_QUEUE_SIZE) {\n const trimmed = allLines.slice(allLines.length - MAX_QUEUE_SIZE);\n fs.writeFileSync(QUEUE_FILE, trimmed.join('\\n') + '\\n', 'utf-8');\n }\n } catch {\n // ignore read/write errors on trim\n }\n}\n\nexport function drainQueue(): RawEvent[] {\n try {\n if (!fs.existsSync(QUEUE_FILE)) return [];\n const content = fs.readFileSync(QUEUE_FILE, 'utf-8');\n fs.writeFileSync(QUEUE_FILE, '', 'utf-8');\n\n const now = Date.now();\n const events: RawEvent[] = [];\n for (const line of content.split('\\n')) {\n if (!line.trim()) continue;\n try {\n const event = JSON.parse(line) as RawEvent;\n const age = now - new Date(event.timestamp).getTime();\n if (age <= MAX_AGE_MS) {\n events.push(event);\n }\n } catch {\n // skip malformed lines\n }\n }\n return events;\n } catch {\n return [];\n }\n}\n","import fs from 'fs';\nimport type { Source } from '../../config';\nimport { sessionStateFile } from '../../config';\n\nexport interface SessionState {\n sessionId: string;\n pid: number;\n source: Source;\n}\n\nexport function loadSessionId(source: Source): string | undefined {\n try {\n const file = sessionStateFile(source);\n if (!fs.existsSync(file)) return undefined;\n const data = JSON.parse(fs.readFileSync(file, 'utf-8'));\n return data.sessionId;\n } catch {\n return undefined;\n }\n}\n\nexport function saveSessionState(state: SessionState): void {\n const file = sessionStateFile(state.source);\n fs.writeFileSync(file, JSON.stringify(state));\n}\n\nexport function removeSessionState(source: Source): void {\n const file = sessionStateFile(source);\n if (fs.existsSync(file)) fs.unlinkSync(file);\n}\n\nexport function readStdin(): Promise<string> {\n return new Promise((resolve) => {\n let input = '';\n process.stdin.on('data', (chunk) => { input += chunk; });\n process.stdin.on('end', () => resolve(input));\n });\n}\n\n/**\n * Normalize hook payload across tools.\n * Currently an identity function — Cursor's payload format matches Claude Code's.\n * If formats diverge, add field mappings here (e.g., raw.toolName → tool_name).\n */\nexport function normalizePayload(source: Source, raw: Record<string, unknown>): Record<string, unknown> {\n if (source === 'cursor') {\n return { ...raw };\n }\n return raw;\n}\n","import { EventCollector } from '../../collector';\nimport type { Source } from '../../config';\nimport { loadSessionId, readStdin, normalizePayload } from './utils';\n\nexport async function handlePromptSubmit(source: Source): Promise<void> {\n const input = await readStdin();\n try {\n const hookData = normalizePayload(source, JSON.parse(input));\n const sessionId = hookData.session_id || loadSessionId(source);\n const collector = new EventCollector(sessionId, source);\n\n // PRIVACY: We only track that a prompt was submitted and its character length.\n // The actual prompt text is NEVER sent to the server.\n const promptLength = typeof hookData.prompt === 'string' ? hookData.prompt.length : 0;\n\n collector.addEvent({\n action_type: 'prompt_submitted',\n value: 1,\n metadata: {\n char_length: promptLength,\n has_images: Array.isArray(hookData.images) && hookData.images.length > 0,\n },\n });\n\n await collector.flush();\n } catch {}\n}\n","import { handlePromptSubmit } from '../shared/prompt-submit';\nhandlePromptSubmit('cursor');\n"],"mappings":"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","names":["path","os","fs","import_fs","fs","uuidv4","import_fs","fs"]}
1
+ {"version":3,"sources":["../../../src/collector.ts","../../../src/config.ts","../../../src/api-client.ts","../../../src/queue.ts","../../../src/hooks/shared/utils.ts","../../../src/hooks/shared/prompt-submit.ts","../../../src/hooks/cursor/prompt-submit.ts"],"sourcesContent":["import { v4 as uuidv4 } from 'uuid';\nimport { APIClient } from './api-client';\nimport { enqueueEvents, drainQueue } from './queue';\nimport { loadConfig } from './config';\nimport type { Source } from './config';\nimport type { RawEvent, ActionType } from '@hackersbaby/shared';\n\ntype RawEventInput = Omit<RawEvent, 'timestamp'>;\n\nexport class EventCollector {\n readonly sessionId: string;\n private buffer: RawEvent[] = [];\n private flushInterval: ReturnType<typeof setInterval> | null = null;\n private client: APIClient;\n private source?: Source;\n\n constructor(sessionId?: string, source?: Source) {\n this.sessionId = sessionId ?? uuidv4();\n this.client = new APIClient();\n this.source = source;\n }\n\n addEvent(event: RawEventInput): void {\n const metadata = this.source\n ? { ...event.metadata, source: this.source }\n : event.metadata;\n this.buffer.push({\n ...event,\n metadata,\n timestamp: new Date().toISOString(),\n });\n }\n\n async start(): Promise<void> {\n await this.client.refreshTokenIfNeeded();\n\n const queued = drainQueue();\n if (queued.length > 0) {\n const batch = { session_id: this.sessionId, events: queued };\n const ok = await this.client.sendBatch(batch);\n if (!ok) {\n enqueueEvents(queued);\n }\n }\n\n const config = loadConfig();\n const intervalMs = config?.preferences?.batch_interval_ms ?? 10_000;\n this.flushInterval = setInterval(() => {\n this.flush().catch(() => {});\n }, intervalMs);\n }\n\n async flush(): Promise<void> {\n if (this.buffer.length === 0) return;\n const events = this.buffer.splice(0);\n const batch = { session_id: this.sessionId, events };\n const ok = await this.client.sendBatch(batch);\n if (!ok) {\n enqueueEvents(events);\n }\n }\n\n async stop(): Promise<void> {\n if (this.flushInterval !== null) {\n clearInterval(this.flushInterval);\n this.flushInterval = null;\n }\n this.addEvent({ action_type: 'session_completed' as ActionType, value: 1, metadata: {} });\n await this.flush();\n }\n}\n","import fs from 'fs';\nimport path from 'path';\nimport os from 'os';\n\nexport const CONFIG_DIR = path.join(os.homedir(), '.hackersbaby');\nexport const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');\nexport const QUEUE_FILE = path.join(CONFIG_DIR, 'queue.jsonl');\n\nexport type Source = 'claude' | 'cursor';\n\nexport interface PluginConfig {\n token: string;\n refresh_token: string;\n user_id: string;\n server: string;\n targets?: Source[];\n preferences: {\n dashboard_port: number;\n batch_interval_ms: number;\n };\n}\n\nexport function sessionStateFile(source: Source): string {\n return path.join(CONFIG_DIR, `active-session-${source}.json`);\n}\n\nexport function ensureConfigDir(): void {\n if (!fs.existsSync(CONFIG_DIR)) {\n fs.mkdirSync(CONFIG_DIR, { recursive: true });\n }\n}\n\nexport function loadConfig(): PluginConfig | null {\n try {\n if (!fs.existsSync(CONFIG_FILE)) return null;\n const raw = fs.readFileSync(CONFIG_FILE, 'utf-8');\n return JSON.parse(raw) as PluginConfig;\n } catch {\n return null;\n }\n}\n\nexport function saveConfig(config: PluginConfig): void {\n ensureConfigDir();\n fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2), 'utf-8');\n}\n","import { loadConfig, saveConfig } from './config';\nimport type { EventBatch } from '@hackersbaby/shared';\n\nexport class APIClient {\n private config = loadConfig();\n\n private get headers(): Record<string, string> {\n return {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${this.config?.token || ''}`,\n };\n }\n\n async sendBatch(batch: EventBatch): Promise<boolean> {\n try {\n const server = this.config?.server || 'https://hackers.baby';\n const res = await fetch(`${server}/api/scores/batch`, {\n method: 'POST',\n headers: this.headers,\n body: JSON.stringify(batch),\n });\n return res.ok;\n } catch {\n return false;\n }\n }\n\n async refreshTokenIfNeeded(): Promise<void> {\n if (!this.config) return;\n try {\n const parts = this.config.token.split('.');\n if (parts.length !== 3) return;\n const payload = JSON.parse(Buffer.from(parts[1], 'base64').toString('utf-8'));\n const expMs = payload.exp * 1000;\n const oneDayMs = 24 * 60 * 60 * 1000;\n if (Date.now() + oneDayMs < expMs) return; // more than 1 day remaining, no refresh needed\n\n const res = await fetch(`${this.config.server}/api/auth/cli-token/refresh`, {\n method: 'POST',\n headers: this.headers,\n body: JSON.stringify({ refresh_token: this.config.refresh_token }),\n });\n if (res.ok) {\n const data = (await res.json()) as { token?: string; refresh_token?: string };\n if (data.token) {\n this.config.token = data.token;\n if (data.refresh_token) this.config.refresh_token = data.refresh_token;\n saveConfig(this.config);\n }\n }\n } catch {\n // silently ignore refresh errors\n }\n }\n\n async getStatus(): Promise<Record<string, unknown>> {\n const server = this.config?.server || 'https://hackers.baby';\n const res = await fetch(`${server}/api/users/me`, { headers: this.headers });\n if (!res.ok) throw new Error(`getStatus failed: ${res.status}`);\n return res.json() as Promise<Record<string, unknown>>;\n }\n\n async getRank(): Promise<Record<string, unknown>> {\n const server = this.config?.server || 'https://hackers.baby';\n const res = await fetch(`${server}/api/users/me/rank`, { headers: this.headers });\n if (!res.ok) throw new Error(`getRank failed: ${res.status}`);\n return res.json() as Promise<Record<string, unknown>>;\n }\n\n async getLeaderboard(): Promise<Record<string, unknown>> {\n const server = this.config?.server || 'https://hackers.baby';\n const res = await fetch(`${server}/api/leaderboard/global?limit=10`, { headers: this.headers });\n if (!res.ok) throw new Error(`getLeaderboard failed: ${res.status}`);\n return res.json() as Promise<Record<string, unknown>>;\n }\n\n async getPublicStats(): Promise<Record<string, unknown> | null> {\n try {\n const server = this.config?.server || 'https://hackers.baby';\n const res = await fetch(`${server}/api/stats/public`);\n if (!res.ok) return null;\n return res.json() as Promise<Record<string, unknown>>;\n } catch {\n return null;\n }\n }\n\n async getNotifications(): Promise<{ notifications: Array<{ type: string; message: string }> } | null> {\n try {\n const server = this.config?.server || 'https://hackers.baby';\n const res = await fetch(`${server}/api/users/me/notifications`, { headers: this.headers });\n if (!res.ok) return null;\n return res.json() as Promise<{ notifications: Array<{ type: string; message: string }> }>;\n } catch {\n return null;\n }\n }\n\n async claimReferral(code: string): Promise<Record<string, unknown> | null> {\n try {\n const server = this.config?.server || 'https://hackers.baby';\n const res = await fetch(`${server}/api/referral/claim`, {\n method: 'POST',\n headers: this.headers,\n body: JSON.stringify({ referral_code: code }),\n });\n if (!res.ok) return null;\n return res.json() as Promise<Record<string, unknown>>;\n } catch {\n return null;\n }\n }\n\n async getReferralStats(): Promise<Record<string, unknown> | null> {\n try {\n const server = this.config?.server || 'https://hackers.baby';\n const res = await fetch(`${server}/api/referral/stats`, { headers: this.headers });\n if (!res.ok) return null;\n return res.json() as Promise<Record<string, unknown>>;\n } catch {\n return null;\n }\n }\n}\n","import fs from 'fs';\nimport { QUEUE_FILE, ensureConfigDir } from './config';\nimport type { RawEvent } from '@hackersbaby/shared';\n\nconst MAX_QUEUE_SIZE = 10_000;\nconst MAX_AGE_MS = 24 * 60 * 60 * 1000; // 24 hours\n\nexport function enqueueEvents(events: RawEvent[]): void {\n ensureConfigDir();\n const lines = events.map((e) => JSON.stringify(e)).join('\\n');\n fs.appendFileSync(QUEUE_FILE, lines + '\\n', 'utf-8');\n\n // Trim to MAX_QUEUE_SIZE\n try {\n const content = fs.readFileSync(QUEUE_FILE, 'utf-8');\n const allLines = content.split('\\n').filter((l) => l.trim() !== '');\n if (allLines.length > MAX_QUEUE_SIZE) {\n const trimmed = allLines.slice(allLines.length - MAX_QUEUE_SIZE);\n fs.writeFileSync(QUEUE_FILE, trimmed.join('\\n') + '\\n', 'utf-8');\n }\n } catch {\n // ignore read/write errors on trim\n }\n}\n\nexport function drainQueue(): RawEvent[] {\n try {\n if (!fs.existsSync(QUEUE_FILE)) return [];\n const content = fs.readFileSync(QUEUE_FILE, 'utf-8');\n fs.writeFileSync(QUEUE_FILE, '', 'utf-8');\n\n const now = Date.now();\n const events: RawEvent[] = [];\n for (const line of content.split('\\n')) {\n if (!line.trim()) continue;\n try {\n const event = JSON.parse(line) as RawEvent;\n const age = now - new Date(event.timestamp).getTime();\n if (age <= MAX_AGE_MS) {\n events.push(event);\n }\n } catch {\n // skip malformed lines\n }\n }\n return events;\n } catch {\n return [];\n }\n}\n","import fs from 'fs';\nimport type { Source } from '../../config';\nimport { sessionStateFile } from '../../config';\n\nexport interface SessionState {\n sessionId: string;\n pid: number;\n source: Source;\n}\n\nexport function loadSessionId(source: Source): string | undefined {\n try {\n const file = sessionStateFile(source);\n if (!fs.existsSync(file)) return undefined;\n const data = JSON.parse(fs.readFileSync(file, 'utf-8'));\n return data.sessionId;\n } catch {\n return undefined;\n }\n}\n\nexport function saveSessionState(state: SessionState): void {\n const file = sessionStateFile(state.source);\n fs.writeFileSync(file, JSON.stringify(state));\n}\n\nexport function removeSessionState(source: Source): void {\n const file = sessionStateFile(source);\n if (fs.existsSync(file)) fs.unlinkSync(file);\n}\n\nexport function readStdin(): Promise<string> {\n return new Promise((resolve) => {\n let input = '';\n process.stdin.on('data', (chunk) => { input += chunk; });\n process.stdin.on('end', () => resolve(input));\n });\n}\n\n/**\n * Detect source at runtime. Cursor sets CURSOR_VERSION env var\n * and includes cursor_version in the hook payload.\n * Since Cursor reads ~/.claude/settings.json directly, both tools\n * fire the same hooks — so we detect rather than relying on entry points.\n */\nexport function detectSource(payload?: Record<string, unknown>): Source {\n if (process.env.CURSOR_VERSION) return 'cursor';\n if (payload?.cursor_version) return 'cursor';\n return 'claude';\n}\n\n/**\n * Normalize hook payload across tools.\n * Currently an identity function — Cursor's payload format matches Claude Code's.\n * If formats diverge, add field mappings here (e.g., raw.toolName → tool_name).\n */\nexport function normalizePayload(source: Source, raw: Record<string, unknown>): Record<string, unknown> {\n if (source === 'cursor') {\n return { ...raw };\n }\n return raw;\n}\n","import { EventCollector } from '../../collector';\nimport type { Source } from '../../config';\nimport { loadSessionId, readStdin, normalizePayload, detectSource } from './utils';\n\nexport async function handlePromptSubmit(source: Source): Promise<void> {\n const input = await readStdin();\n try {\n const raw = JSON.parse(input);\n const detected = detectSource(raw);\n const hookData = normalizePayload(detected, raw);\n const sessionId = hookData.session_id || loadSessionId(detected);\n const collector = new EventCollector(sessionId, detected);\n\n // PRIVACY: We only track that a prompt was submitted and its character length.\n // The actual prompt text is NEVER sent to the server.\n const promptLength = typeof hookData.prompt === 'string' ? hookData.prompt.length : 0;\n\n collector.addEvent({\n action_type: 'prompt_submitted',\n value: 1,\n metadata: {\n char_length: promptLength,\n has_images: Array.isArray(hookData.images) && hookData.images.length > 0,\n },\n });\n\n await collector.flush();\n } catch {}\n}\n","import { handlePromptSubmit } from '../shared/prompt-submit';\nhandlePromptSubmit('cursor');\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,kBAA6B;;;ACA7B,gBAAe;AACf,kBAAiB;AACjB,gBAAe;AAER,IAAM,aAAa,YAAAA,QAAK,KAAK,UAAAC,QAAG,QAAQ,GAAG,cAAc;AACzD,IAAM,cAAc,YAAAD,QAAK,KAAK,YAAY,aAAa;AACvD,IAAM,aAAa,YAAAA,QAAK,KAAK,YAAY,aAAa;AAgBtD,SAAS,iBAAiB,QAAwB;AACvD,SAAO,YAAAA,QAAK,KAAK,YAAY,kBAAkB,MAAM,OAAO;AAC9D;AAEO,SAAS,kBAAwB;AACtC,MAAI,CAAC,UAAAE,QAAG,WAAW,UAAU,GAAG;AAC9B,cAAAA,QAAG,UAAU,YAAY,EAAE,WAAW,KAAK,CAAC;AAAA,EAC9C;AACF;AAEO,SAAS,aAAkC;AAChD,MAAI;AACF,QAAI,CAAC,UAAAA,QAAG,WAAW,WAAW,EAAG,QAAO;AACxC,UAAM,MAAM,UAAAA,QAAG,aAAa,aAAa,OAAO;AAChD,WAAO,KAAK,MAAM,GAAG;AAAA,EACvB,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEO,SAAS,WAAW,QAA4B;AACrD,kBAAgB;AAChB,YAAAA,QAAG,cAAc,aAAa,KAAK,UAAU,QAAQ,MAAM,CAAC,GAAG,OAAO;AACxE;;;AC1CO,IAAM,YAAN,MAAgB;AAAA,EACb,SAAS,WAAW;AAAA,EAE5B,IAAY,UAAkC;AAC5C,WAAO;AAAA,MACL,gBAAgB;AAAA,MAChB,eAAe,UAAU,KAAK,QAAQ,SAAS,EAAE;AAAA,IACnD;AAAA,EACF;AAAA,EAEA,MAAM,UAAU,OAAqC;AACnD,QAAI;AACF,YAAM,SAAS,KAAK,QAAQ,UAAU;AACtC,YAAM,MAAM,MAAM,MAAM,GAAG,MAAM,qBAAqB;AAAA,QACpD,QAAQ;AAAA,QACR,SAAS,KAAK;AAAA,QACd,MAAM,KAAK,UAAU,KAAK;AAAA,MAC5B,CAAC;AACD,aAAO,IAAI;AAAA,IACb,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF;AAAA,EAEA,MAAM,uBAAsC;AAC1C,QAAI,CAAC,KAAK,OAAQ;AAClB,QAAI;AACF,YAAM,QAAQ,KAAK,OAAO,MAAM,MAAM,GAAG;AACzC,UAAI,MAAM,WAAW,EAAG;AACxB,YAAM,UAAU,KAAK,MAAM,OAAO,KAAK,MAAM,CAAC,GAAG,QAAQ,EAAE,SAAS,OAAO,CAAC;AAC5E,YAAM,QAAQ,QAAQ,MAAM;AAC5B,YAAM,WAAW,KAAK,KAAK,KAAK;AAChC,UAAI,KAAK,IAAI,IAAI,WAAW,MAAO;AAEnC,YAAM,MAAM,MAAM,MAAM,GAAG,KAAK,OAAO,MAAM,+BAA+B;AAAA,QAC1E,QAAQ;AAAA,QACR,SAAS,KAAK;AAAA,QACd,MAAM,KAAK,UAAU,EAAE,eAAe,KAAK,OAAO,cAAc,CAAC;AAAA,MACnE,CAAC;AACD,UAAI,IAAI,IAAI;AACV,cAAM,OAAQ,MAAM,IAAI,KAAK;AAC7B,YAAI,KAAK,OAAO;AACd,eAAK,OAAO,QAAQ,KAAK;AACzB,cAAI,KAAK,cAAe,MAAK,OAAO,gBAAgB,KAAK;AACzD,qBAAW,KAAK,MAAM;AAAA,QACxB;AAAA,MACF;AAAA,IACF,QAAQ;AAAA,IAER;AAAA,EACF;AAAA,EAEA,MAAM,YAA8C;AAClD,UAAM,SAAS,KAAK,QAAQ,UAAU;AACtC,UAAM,MAAM,MAAM,MAAM,GAAG,MAAM,iBAAiB,EAAE,SAAS,KAAK,QAAQ,CAAC;AAC3E,QAAI,CAAC,IAAI,GAAI,OAAM,IAAI,MAAM,qBAAqB,IAAI,MAAM,EAAE;AAC9D,WAAO,IAAI,KAAK;AAAA,EAClB;AAAA,EAEA,MAAM,UAA4C;AAChD,UAAM,SAAS,KAAK,QAAQ,UAAU;AACtC,UAAM,MAAM,MAAM,MAAM,GAAG,MAAM,sBAAsB,EAAE,SAAS,KAAK,QAAQ,CAAC;AAChF,QAAI,CAAC,IAAI,GAAI,OAAM,IAAI,MAAM,mBAAmB,IAAI,MAAM,EAAE;AAC5D,WAAO,IAAI,KAAK;AAAA,EAClB;AAAA,EAEA,MAAM,iBAAmD;AACvD,UAAM,SAAS,KAAK,QAAQ,UAAU;AACtC,UAAM,MAAM,MAAM,MAAM,GAAG,MAAM,oCAAoC,EAAE,SAAS,KAAK,QAAQ,CAAC;AAC9F,QAAI,CAAC,IAAI,GAAI,OAAM,IAAI,MAAM,0BAA0B,IAAI,MAAM,EAAE;AACnE,WAAO,IAAI,KAAK;AAAA,EAClB;AAAA,EAEA,MAAM,iBAA0D;AAC9D,QAAI;AACF,YAAM,SAAS,KAAK,QAAQ,UAAU;AACtC,YAAM,MAAM,MAAM,MAAM,GAAG,MAAM,mBAAmB;AACpD,UAAI,CAAC,IAAI,GAAI,QAAO;AACpB,aAAO,IAAI,KAAK;AAAA,IAClB,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF;AAAA,EAEA,MAAM,mBAAgG;AACpG,QAAI;AACF,YAAM,SAAS,KAAK,QAAQ,UAAU;AACtC,YAAM,MAAM,MAAM,MAAM,GAAG,MAAM,+BAA+B,EAAE,SAAS,KAAK,QAAQ,CAAC;AACzF,UAAI,CAAC,IAAI,GAAI,QAAO;AACpB,aAAO,IAAI,KAAK;AAAA,IAClB,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF;AAAA,EAEA,MAAM,cAAc,MAAuD;AACzE,QAAI;AACF,YAAM,SAAS,KAAK,QAAQ,UAAU;AACtC,YAAM,MAAM,MAAM,MAAM,GAAG,MAAM,uBAAuB;AAAA,QACtD,QAAQ;AAAA,QACR,SAAS,KAAK;AAAA,QACd,MAAM,KAAK,UAAU,EAAE,eAAe,KAAK,CAAC;AAAA,MAC9C,CAAC;AACD,UAAI,CAAC,IAAI,GAAI,QAAO;AACpB,aAAO,IAAI,KAAK;AAAA,IAClB,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF;AAAA,EAEA,MAAM,mBAA4D;AAChE,QAAI;AACF,YAAM,SAAS,KAAK,QAAQ,UAAU;AACtC,YAAM,MAAM,MAAM,MAAM,GAAG,MAAM,uBAAuB,EAAE,SAAS,KAAK,QAAQ,CAAC;AACjF,UAAI,CAAC,IAAI,GAAI,QAAO;AACpB,aAAO,IAAI,KAAK;AAAA,IAClB,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF;AACF;;;AC3HA,IAAAC,aAAe;AAIf,IAAM,iBAAiB;AACvB,IAAM,aAAa,KAAK,KAAK,KAAK;AAE3B,SAAS,cAAc,QAA0B;AACtD,kBAAgB;AAChB,QAAM,QAAQ,OAAO,IAAI,CAAC,MAAM,KAAK,UAAU,CAAC,CAAC,EAAE,KAAK,IAAI;AAC5D,aAAAC,QAAG,eAAe,YAAY,QAAQ,MAAM,OAAO;AAGnD,MAAI;AACF,UAAM,UAAU,WAAAA,QAAG,aAAa,YAAY,OAAO;AACnD,UAAM,WAAW,QAAQ,MAAM,IAAI,EAAE,OAAO,CAAC,MAAM,EAAE,KAAK,MAAM,EAAE;AAClE,QAAI,SAAS,SAAS,gBAAgB;AACpC,YAAM,UAAU,SAAS,MAAM,SAAS,SAAS,cAAc;AAC/D,iBAAAA,QAAG,cAAc,YAAY,QAAQ,KAAK,IAAI,IAAI,MAAM,OAAO;AAAA,IACjE;AAAA,EACF,QAAQ;AAAA,EAER;AACF;AAEO,SAAS,aAAyB;AACvC,MAAI;AACF,QAAI,CAAC,WAAAA,QAAG,WAAW,UAAU,EAAG,QAAO,CAAC;AACxC,UAAM,UAAU,WAAAA,QAAG,aAAa,YAAY,OAAO;AACnD,eAAAA,QAAG,cAAc,YAAY,IAAI,OAAO;AAExC,UAAM,MAAM,KAAK,IAAI;AACrB,UAAM,SAAqB,CAAC;AAC5B,eAAW,QAAQ,QAAQ,MAAM,IAAI,GAAG;AACtC,UAAI,CAAC,KAAK,KAAK,EAAG;AAClB,UAAI;AACF,cAAM,QAAQ,KAAK,MAAM,IAAI;AAC7B,cAAM,MAAM,MAAM,IAAI,KAAK,MAAM,SAAS,EAAE,QAAQ;AACpD,YAAI,OAAO,YAAY;AACrB,iBAAO,KAAK,KAAK;AAAA,QACnB;AAAA,MACF,QAAQ;AAAA,MAER;AAAA,IACF;AACA,WAAO;AAAA,EACT,QAAQ;AACN,WAAO,CAAC;AAAA,EACV;AACF;;;AHxCO,IAAM,iBAAN,MAAqB;AAAA,EACjB;AAAA,EACD,SAAqB,CAAC;AAAA,EACtB,gBAAuD;AAAA,EACvD;AAAA,EACA;AAAA,EAER,YAAY,WAAoB,QAAiB;AAC/C,SAAK,YAAY,iBAAa,YAAAC,IAAO;AACrC,SAAK,SAAS,IAAI,UAAU;AAC5B,SAAK,SAAS;AAAA,EAChB;AAAA,EAEA,SAAS,OAA4B;AACnC,UAAM,WAAW,KAAK,SAClB,EAAE,GAAG,MAAM,UAAU,QAAQ,KAAK,OAAO,IACzC,MAAM;AACV,SAAK,OAAO,KAAK;AAAA,MACf,GAAG;AAAA,MACH;AAAA,MACA,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,IACpC,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,QAAuB;AAC3B,UAAM,KAAK,OAAO,qBAAqB;AAEvC,UAAM,SAAS,WAAW;AAC1B,QAAI,OAAO,SAAS,GAAG;AACrB,YAAM,QAAQ,EAAE,YAAY,KAAK,WAAW,QAAQ,OAAO;AAC3D,YAAM,KAAK,MAAM,KAAK,OAAO,UAAU,KAAK;AAC5C,UAAI,CAAC,IAAI;AACP,sBAAc,MAAM;AAAA,MACtB;AAAA,IACF;AAEA,UAAM,SAAS,WAAW;AAC1B,UAAM,aAAa,QAAQ,aAAa,qBAAqB;AAC7D,SAAK,gBAAgB,YAAY,MAAM;AACrC,WAAK,MAAM,EAAE,MAAM,MAAM;AAAA,MAAC,CAAC;AAAA,IAC7B,GAAG,UAAU;AAAA,EACf;AAAA,EAEA,MAAM,QAAuB;AAC3B,QAAI,KAAK,OAAO,WAAW,EAAG;AAC9B,UAAM,SAAS,KAAK,OAAO,OAAO,CAAC;AACnC,UAAM,QAAQ,EAAE,YAAY,KAAK,WAAW,OAAO;AACnD,UAAM,KAAK,MAAM,KAAK,OAAO,UAAU,KAAK;AAC5C,QAAI,CAAC,IAAI;AACP,oBAAc,MAAM;AAAA,IACtB;AAAA,EACF;AAAA,EAEA,MAAM,OAAsB;AAC1B,QAAI,KAAK,kBAAkB,MAAM;AAC/B,oBAAc,KAAK,aAAa;AAChC,WAAK,gBAAgB;AAAA,IACvB;AACA,SAAK,SAAS,EAAE,aAAa,qBAAmC,OAAO,GAAG,UAAU,CAAC,EAAE,CAAC;AACxF,UAAM,KAAK,MAAM;AAAA,EACnB;AACF;;;AItEA,IAAAC,aAAe;AAUR,SAAS,cAAc,QAAoC;AAChE,MAAI;AACF,UAAM,OAAO,iBAAiB,MAAM;AACpC,QAAI,CAAC,WAAAC,QAAG,WAAW,IAAI,EAAG,QAAO;AACjC,UAAM,OAAO,KAAK,MAAM,WAAAA,QAAG,aAAa,MAAM,OAAO,CAAC;AACtD,WAAO,KAAK;AAAA,EACd,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAYO,SAAS,YAA6B;AAC3C,SAAO,IAAI,QAAQ,CAAC,YAAY;AAC9B,QAAI,QAAQ;AACZ,YAAQ,MAAM,GAAG,QAAQ,CAAC,UAAU;AAAE,eAAS;AAAA,IAAO,CAAC;AACvD,YAAQ,MAAM,GAAG,OAAO,MAAM,QAAQ,KAAK,CAAC;AAAA,EAC9C,CAAC;AACH;AAQO,SAAS,aAAa,SAA2C;AACtE,MAAI,QAAQ,IAAI,eAAgB,QAAO;AACvC,MAAI,SAAS,eAAgB,QAAO;AACpC,SAAO;AACT;AAOO,SAAS,iBAAiB,QAAgB,KAAuD;AACtG,MAAI,WAAW,UAAU;AACvB,WAAO,EAAE,GAAG,IAAI;AAAA,EAClB;AACA,SAAO;AACT;;;ACzDA,eAAsB,mBAAmB,QAA+B;AACtE,QAAM,QAAQ,MAAM,UAAU;AAC9B,MAAI;AACF,UAAM,MAAM,KAAK,MAAM,KAAK;AAC5B,UAAM,WAAW,aAAa,GAAG;AACjC,UAAM,WAAW,iBAAiB,UAAU,GAAG;AAC/C,UAAM,YAAY,SAAS,cAAc,cAAc,QAAQ;AAC/D,UAAM,YAAY,IAAI,eAAe,WAAW,QAAQ;AAIxD,UAAM,eAAe,OAAO,SAAS,WAAW,WAAW,SAAS,OAAO,SAAS;AAEpF,cAAU,SAAS;AAAA,MACjB,aAAa;AAAA,MACb,OAAO;AAAA,MACP,UAAU;AAAA,QACR,aAAa;AAAA,QACb,YAAY,MAAM,QAAQ,SAAS,MAAM,KAAK,SAAS,OAAO,SAAS;AAAA,MACzE;AAAA,IACF,CAAC;AAED,UAAM,UAAU,MAAM;AAAA,EACxB,QAAQ;AAAA,EAAC;AACX;;;AC3BA,mBAAmB,QAAQ;","names":["path","os","fs","import_fs","fs","uuidv4","import_fs","fs"]}
@@ -280,12 +280,18 @@ function removeSessionState(source) {
280
280
  const file = sessionStateFile(source);
281
281
  if (import_fs3.default.existsSync(file)) import_fs3.default.unlinkSync(file);
282
282
  }
283
+ function detectSource(payload) {
284
+ if (process.env.CURSOR_VERSION) return "cursor";
285
+ if (payload?.cursor_version) return "cursor";
286
+ return "claude";
287
+ }
283
288
 
284
289
  // src/hooks/shared/session-end.ts
285
290
  async function handleSessionEnd(source) {
286
- const collector = new EventCollector(loadSessionId(source), source);
291
+ const detected = detectSource();
292
+ const collector = new EventCollector(loadSessionId(detected), detected);
287
293
  await collector.stop();
288
- removeSessionState(source);
294
+ removeSessionState(detected);
289
295
  }
290
296
 
291
297
  // src/hooks/cursor/session-end.ts