@hackersbaby/plugin 0.5.3 → 0.5.5

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.
@@ -173,7 +173,10 @@ var require_dist = __commonJS({
173
173
  });
174
174
 
175
175
  // src/config.ts
176
- function sessionStateFile(source) {
176
+ function sessionStateFile(sessionId) {
177
+ return import_path.default.join(SESSIONS_DIR, `${sessionId}.json`);
178
+ }
179
+ function legacySessionStateFile(source) {
177
180
  return import_path.default.join(CONFIG_DIR, `active-session-${source}.json`);
178
181
  }
179
182
  function ensureConfigDir() {
@@ -194,7 +197,7 @@ function saveConfig(config) {
194
197
  ensureConfigDir();
195
198
  import_fs.default.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2), "utf-8");
196
199
  }
197
- var import_fs, import_path, import_os, CONFIG_DIR, CONFIG_FILE, QUEUE_FILE;
200
+ var import_fs, import_path, import_os, CONFIG_DIR, CONFIG_FILE, QUEUE_FILE, SESSIONS_DIR;
198
201
  var init_config = __esm({
199
202
  "src/config.ts"() {
200
203
  "use strict";
@@ -204,6 +207,7 @@ var init_config = __esm({
204
207
  CONFIG_DIR = import_path.default.join(import_os.default.homedir(), ".hackersbaby");
205
208
  CONFIG_FILE = import_path.default.join(CONFIG_DIR, "config.json");
206
209
  QUEUE_FILE = import_path.default.join(CONFIG_DIR, "queue.jsonl");
210
+ SESSIONS_DIR = import_path.default.join(CONFIG_DIR, "sessions");
207
211
  }
208
212
  });
209
213
 
@@ -511,40 +515,69 @@ var import_shared2 = __toESM(require_dist());
511
515
  var import_fs3 = __toESM(require("fs"));
512
516
  var import_path2 = __toESM(require("path"));
513
517
  init_config();
514
- function loadSessionState(source) {
518
+ function ensureSessionsDir() {
519
+ if (!import_fs3.default.existsSync(SESSIONS_DIR)) {
520
+ import_fs3.default.mkdirSync(SESSIONS_DIR, { recursive: true });
521
+ }
522
+ }
523
+ function loadSessionState(sessionId) {
515
524
  try {
516
- const file = sessionStateFile(source);
525
+ const file = sessionStateFile(sessionId);
517
526
  if (!import_fs3.default.existsSync(file)) return void 0;
518
527
  return JSON.parse(import_fs3.default.readFileSync(file, "utf-8"));
519
528
  } catch {
520
529
  return void 0;
521
530
  }
522
531
  }
532
+ function findSessionBySource(source) {
533
+ try {
534
+ if (!import_fs3.default.existsSync(SESSIONS_DIR)) return void 0;
535
+ const files = import_fs3.default.readdirSync(SESSIONS_DIR).filter((f) => f.endsWith(".json"));
536
+ for (const f of files) {
537
+ try {
538
+ const state = JSON.parse(import_fs3.default.readFileSync(import_path2.default.join(SESSIONS_DIR, f), "utf-8"));
539
+ if (state.source === source) return state;
540
+ } catch {
541
+ }
542
+ }
543
+ } catch {
544
+ }
545
+ try {
546
+ const legacyFile = legacySessionStateFile(source);
547
+ if (import_fs3.default.existsSync(legacyFile)) {
548
+ return JSON.parse(import_fs3.default.readFileSync(legacyFile, "utf-8"));
549
+ }
550
+ } catch {
551
+ }
552
+ return void 0;
553
+ }
523
554
  function loadSessionId(source) {
524
- return loadSessionState(source)?.sessionId;
555
+ return findSessionBySource(source)?.sessionId;
525
556
  }
526
- function updateSessionState(source, updates) {
527
- const state = loadSessionState(source);
557
+ function updateSessionState(sessionId, updates) {
558
+ const state = loadSessionState(sessionId);
528
559
  if (!state) return;
529
- const file = sessionStateFile(source);
560
+ const file = sessionStateFile(sessionId);
530
561
  import_fs3.default.writeFileSync(file, JSON.stringify({ ...state, ...updates }));
531
562
  }
532
563
  function createCollector(sessionId, source) {
533
564
  const { EventCollector: EventCollector2 } = (init_collector(), __toCommonJS(collector_exports));
534
565
  const collector = new EventCollector2(sessionId, source);
535
- const state = loadSessionState(source);
536
- if (state?.sessionSecret && state?.chainHash && state.sessionId === sessionId) {
566
+ const resolvedId = collector.sessionId;
567
+ const state = loadSessionState(resolvedId);
568
+ if (state?.sessionSecret && state?.chainHash && state.sessionId === resolvedId) {
537
569
  collector.restoreCrypto(state.sessionSecret, state.chainHash);
538
570
  collector.setCryptoUpdateCallback((chainHash, secret) => {
539
- updateSessionState(source, { chainHash, sessionSecret: secret });
571
+ updateSessionState(resolvedId, { chainHash, sessionSecret: secret });
540
572
  });
541
573
  }
542
574
  return collector;
543
575
  }
544
576
  var LOCK_STALE_MS = 5e3;
545
577
  var LOCK_POLL_MS = 20;
546
- function lockFilePath(source) {
547
- return import_path2.default.join(CONFIG_DIR, `lock-${source}`);
578
+ function lockFilePath(sessionId) {
579
+ ensureSessionsDir();
580
+ return import_path2.default.join(SESSIONS_DIR, `lock-${sessionId}`);
548
581
  }
549
582
  function tryAcquireLock(lockPath) {
550
583
  try {
@@ -577,8 +610,8 @@ function clearStaleLock(lockPath) {
577
610
  function sleep(ms) {
578
611
  return new Promise((r) => setTimeout(r, ms));
579
612
  }
580
- async function acquireLock(source) {
581
- const lockPath = lockFilePath(source);
613
+ async function acquireLock(sessionId) {
614
+ const lockPath = lockFilePath(sessionId);
582
615
  const deadline = Date.now() + LOCK_STALE_MS;
583
616
  while (Date.now() < deadline) {
584
617
  if (tryAcquireLock(lockPath)) return;
@@ -592,18 +625,18 @@ async function acquireLock(source) {
592
625
  if (!tryAcquireLock(lockPath)) {
593
626
  }
594
627
  }
595
- function releaseLock(source) {
628
+ function releaseLock(sessionId) {
596
629
  try {
597
- import_fs3.default.unlinkSync(lockFilePath(source));
630
+ import_fs3.default.unlinkSync(lockFilePath(sessionId));
598
631
  } catch {
599
632
  }
600
633
  }
601
- async function withSessionLock(source, fn) {
602
- await acquireLock(source);
634
+ async function withSessionLock(sessionId, fn) {
635
+ await acquireLock(sessionId);
603
636
  try {
604
637
  return await fn();
605
638
  } finally {
606
- releaseLock(source);
639
+ releaseLock(sessionId);
607
640
  }
608
641
  }
609
642
  function readStdin() {
@@ -637,7 +670,8 @@ async function handleToolCall(source) {
637
670
  const toolName = hookData.tool_name || "unknown";
638
671
  const toolInput = hookData.tool_input || {};
639
672
  const sessionId = hookData.session_id || loadSessionId(detected);
640
- await withSessionLock(detected, async () => {
673
+ if (!sessionId) return;
674
+ await withSessionLock(sessionId, async () => {
641
675
  const collector = createCollector(sessionId, detected);
642
676
  collector.addEvent({ action_type: "tool_call", value: 1, metadata: { tool_name: toolName } });
643
677
  if (hookData.input_tokens) {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../shared/dist/types.js","../../../shared/src/scoring.ts","../../../shared/src/anticheat.ts","../../../shared/src/index.ts","../../src/config.ts","../../src/api-client.ts","../../src/queue.ts","../../src/collector.ts","../../src/hooks/shared/tool-call.ts","../../src/hooks/shared/utils.ts","../../src/hooks/tool-call.ts"],"sourcesContent":["\"use strict\";\nObject.defineProperty(exports, \"__esModule\", { value: true });\n//# sourceMappingURL=types.js.map","import type { ActionType } from './types';\n\nexport const SESSION_BASE_POINT_CAP = 20000;\nexport const TOOL_CALLS_PER_HOUR_CAP = 4000;\nexport const MIN_SESSION_TOKENS = 2000;\n\nexport const BASE_POINTS: Record<ActionType, { perUnit: number; unitSize: number }> = {\n token_input: { perUnit: 1, unitSize: 1000 },\n token_output: { perUnit: 2, unitSize: 1000 },\n tool_call: { perUnit: 5, unitSize: 1 },\n file_created: { perUnit: 15, unitSize: 1 },\n file_edited: { perUnit: 10, unitSize: 1 },\n commit: { perUnit: 50, unitSize: 1 },\n session_completed: { perUnit: 100, unitSize: 1 },\n deployment: { perUnit: 200, unitSize: 1 },\n prompt_submitted: { perUnit: 15, unitSize: 1 },\n subagent_spawned: { perUnit: 40, unitSize: 1 },\n context_compacted: { perUnit: 30, unitSize: 1 },\n stop_response: { perUnit: 10, unitSize: 1 },\n};\n\nexport const DEPLOY_PATTERNS = [\n /^vercel\\s+(--prod|deploy)/, /^netlify\\s+deploy/, /^fly\\s+deploy/, /^railway\\s+up/,\n /^git\\s+push\\s+\\S+\\s+(main|master|production)/,\n /^(npm|yarn|pnpm)\\s+run\\s+deploy/, /^(yarn|pnpm)\\s+deploy/,\n];\n\nexport function getBasePoints(actionType: ActionType, value: number): number {\n const config = BASE_POINTS[actionType];\n return Math.floor(value / config.unitSize) * config.perUnit;\n}\n\ninterface MultiplierInput { streak_days: number; commit_quality: boolean; language_count: number; }\n\nexport function calculateMultiplier(input: MultiplierInput): number {\n let streak = 1.0;\n if (input.streak_days >= 30) streak = 3.0;\n else if (input.streak_days >= 7) streak = 2.0;\n else if (input.streak_days >= 3) streak = 1.5;\n const quality = input.commit_quality ? 1.5 : 1.0;\n const diversity = input.language_count >= 3 ? 1.2 : 1.0;\n return streak * quality * diversity;\n}\n\nexport function calculateSessionScore(events: Array<{ action_type: ActionType; value: number }>, multiplier: number): number {\n let totalBase = 0;\n for (const e of events) totalBase += getBasePoints(e.action_type, e.value);\n return Math.floor(Math.min(totalBase, SESSION_BASE_POINT_CAP) * multiplier);\n}\n\nexport function isDeployCommand(command: string): boolean {\n return DEPLOY_PATTERNS.some((p) => p.test(command.trim()));\n}\n","/**\n * Anti-cheat cryptographic primitives.\n * Shared between plugin (client) and server.\n *\n * - Event chain: each event hash includes the previous, creating a tamper-evident chain\n * - Batch HMAC: the entire batch is signed with a server-issued session secret\n */\n\nimport { createHmac, createHash } from 'crypto';\n\n/** Hash a single event, chaining it to the previous hash */\nexport function hashEvent(\n event: { action_type: string; value: number; timestamp: string },\n prevHash: string,\n): string {\n const payload = `${prevHash}|${event.action_type}|${event.value}|${event.timestamp}`;\n return createHash('sha256').update(payload).digest('hex').slice(0, 16);\n}\n\n/** Compute the chain hash for an array of events */\nexport function computeChainHash(\n events: Array<{ action_type: string; value: number; timestamp: string }>,\n initialHash: string = '0000000000000000',\n): string {\n let hash = initialHash;\n for (const event of events) {\n hash = hashEvent(event, hash);\n }\n return hash;\n}\n\n/** Sign a batch with HMAC-SHA256 using the session secret */\nexport function signBatch(\n sessionId: string,\n events: Array<{ action_type: string; value: number; timestamp: string }>,\n chainHash: string,\n secret: string,\n): string {\n // Canonical form: sessionId + chainHash + event count + sum of values\n const valueSum = events.reduce((s, e) => s + e.value, 0);\n const message = `${sessionId}|${chainHash}|${events.length}|${valueSum}`;\n return createHmac('sha256', secret).update(message).digest('hex');\n}\n\n/** Verify a batch HMAC signature */\nexport function verifyBatchSignature(\n sessionId: string,\n events: Array<{ action_type: string; value: number; timestamp: string }>,\n chainHash: string,\n signature: string,\n secret: string,\n): boolean {\n const expected = signBatch(sessionId, events, chainHash, secret);\n // Constant-time comparison\n if (expected.length !== signature.length) return false;\n let mismatch = 0;\n for (let i = 0; i < expected.length; i++) {\n mismatch |= expected.charCodeAt(i) ^ signature.charCodeAt(i);\n }\n return mismatch === 0;\n}\n","export * from './types';\nexport * from './scoring';\nexport * from './anticheat';\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 { computeChainHash, signBatch } from '@hackersbaby/shared';\nimport type { EventBatch } from '@hackersbaby/shared';\n\nexport class APIClient {\n private config = loadConfig();\n private sessionSecret: string | null = null;\n private chainHash: string = '0000000000000000';\n private onChainUpdate: ((chainHash: string, secret: string) => void) | null = null;\n\n /** Set callback to persist chain state between hook processes */\n setChainUpdateCallback(cb: (chainHash: string, secret: string) => void) {\n this.onChainUpdate = cb;\n }\n\n /** Restore crypto state from a previous session */\n restoreCryptoState(secret: string, chainHash: string) {\n this.sessionSecret = secret;\n this.chainHash = chainHash;\n }\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 /** Get current crypto state for persistence */\n getCryptoState(): { secret: string; chainHash: string } | null {\n if (!this.sessionSecret) return null;\n return { secret: this.sessionSecret, chainHash: this.chainHash };\n }\n\n /** Register session with server and get cryptographic secret */\n async startSession(sessionId: string): Promise<boolean> {\n try {\n const server = this.config?.server || 'https://hackers.baby';\n const res = await fetch(`${server}/api/sessions/start`, {\n method: 'POST',\n headers: this.headers,\n body: JSON.stringify({ session_id: sessionId }),\n });\n if (res.ok) {\n const data = await res.json() as { session_secret: string; initial_chain_hash: string };\n this.sessionSecret = data.session_secret;\n this.chainHash = data.initial_chain_hash;\n return true;\n }\n return false;\n } catch {\n return false;\n }\n }\n\n async sendBatch(batch: EventBatch): Promise<boolean> {\n try {\n const server = this.config?.server || 'https://hackers.baby';\n\n // Sign the batch if we have a session secret\n if (this.sessionSecret) {\n const prevChainHash = this.chainHash;\n const newChainHash = computeChainHash(batch.events, prevChainHash);\n const signature = signBatch(batch.session_id, batch.events, newChainHash, this.sessionSecret);\n\n batch.signature = signature;\n batch.chain_hash = newChainHash;\n batch.prev_chain_hash = prevChainHash;\n\n const res = await fetch(`${server}/api/scores/batch`, {\n method: 'POST',\n headers: this.headers,\n body: JSON.stringify(batch),\n });\n\n if (res.ok) {\n // Only advance chain hash after server confirms acceptance\n this.chainHash = newChainHash;\n if (this.onChainUpdate) {\n this.onChainUpdate(this.chainHash, this.sessionSecret);\n }\n }\n\n return res.ok;\n }\n\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 { 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 readonly 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 /** Restore crypto state from persisted session data */\n restoreCrypto(secret: string, chainHash: string) {\n this.client.restoreCryptoState(secret, chainHash);\n }\n\n /** Set callback to persist crypto state after each batch */\n setCryptoUpdateCallback(cb: (chainHash: string, secret: string) => void) {\n this.client.setChainUpdateCallback(cb);\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 // Register session with server for cryptographic signing\n await this.client.startSession(this.sessionId);\n\n // Clear stale queue — events from previous sessions have old timestamps\n // that the server will reject, and retrying them risks chain desync.\n drainQueue();\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 { isDeployCommand } from '@hackersbaby/shared';\nimport type { Source } from '../../config';\nimport { loadSessionId, readStdin, normalizePayload, detectSource, createCollector, withSessionLock } from './utils';\n\nexport async function handleToolCall(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 toolName = hookData.tool_name || 'unknown';\n const toolInput = hookData.tool_input || {};\n const sessionId = hookData.session_id || loadSessionId(detected);\n\n await withSessionLock(detected, async () => {\n const collector = createCollector(sessionId, detected);\n\n // PRIVACY: Only send tool name — never send tool_input content, file paths, or commands\n collector.addEvent({ action_type: 'tool_call', value: 1, metadata: { tool_name: toolName } });\n\n // Track token usage from hook payload\n if (hookData.input_tokens) {\n collector.addEvent({ action_type: 'token_input', value: hookData.input_tokens, metadata: {} });\n }\n if (hookData.output_tokens) {\n collector.addEvent({ action_type: 'token_output', value: hookData.output_tokens, metadata: {} });\n }\n\n // Track Bash commands for deployment detection — PRIVACY: only send boolean, not the command\n if (toolName === 'Bash' && toolInput.command && isDeployCommand(toolInput.command)) {\n collector.addEvent({ action_type: 'deployment', value: 1, metadata: {} });\n }\n\n // Track file creation — PRIVACY: no file path sent\n if (toolName === 'Write') {\n collector.addEvent({ action_type: 'file_created', value: 1, metadata: {} });\n }\n\n // Track file edits — PRIVACY: no file path sent\n if (toolName === 'Edit') {\n collector.addEvent({ action_type: 'file_edited', value: 1, metadata: {} });\n }\n\n await collector.flush();\n });\n } catch {}\n}\n","import fs from 'fs';\nimport path from 'path';\nimport type { Source } from '../../config';\nimport { sessionStateFile, CONFIG_DIR } from '../../config';\n\nexport interface SessionState {\n sessionId: string;\n pid: number;\n source: Source;\n sessionSecret?: string;\n chainHash?: string;\n}\n\nexport function loadSessionState(source: Source): SessionState | undefined {\n try {\n const file = sessionStateFile(source);\n if (!fs.existsSync(file)) return undefined;\n return JSON.parse(fs.readFileSync(file, 'utf-8')) as SessionState;\n } catch {\n return undefined;\n }\n}\n\nexport function loadSessionId(source: Source): string | undefined {\n return loadSessionState(source)?.sessionId;\n}\n\nexport function updateSessionState(source: Source, updates: Partial<SessionState>): void {\n const state = loadSessionState(source);\n if (!state) return;\n const file = sessionStateFile(source);\n fs.writeFileSync(file, JSON.stringify({ ...state, ...updates }));\n}\n\nexport function saveSessionState(state: SessionState): void {\n const file = sessionStateFile(state.source);\n fs.writeFileSync(file, JSON.stringify(state));\n}\n\n/** Create an EventCollector with crypto state restored from the session file */\nexport function createCollector(sessionId: string | undefined, source: Source): import('../../collector').EventCollector {\n const { EventCollector } = require('../../collector');\n const collector = new EventCollector(sessionId, source);\n\n // Restore crypto state from session file\n const state = loadSessionState(source);\n if (state?.sessionSecret && state?.chainHash && state.sessionId === sessionId) {\n collector.restoreCrypto(state.sessionSecret, state.chainHash);\n\n // Update session file after each signed batch\n collector.setCryptoUpdateCallback((chainHash: string, secret: string) => {\n updateSessionState(source, { chainHash, sessionSecret: secret });\n });\n }\n\n return collector;\n}\n\nexport function removeSessionState(source: Source): void {\n const file = sessionStateFile(source);\n if (fs.existsSync(file)) fs.unlinkSync(file);\n}\n\n// --- File-based lock to serialize batch sending across concurrent hook processes ---\n\nconst LOCK_STALE_MS = 5_000; // 5 seconds — force-remove stale locks\nconst LOCK_POLL_MS = 20; // polling interval\n\nfunction lockFilePath(source: Source): string {\n return path.join(CONFIG_DIR, `lock-${source}`);\n}\n\nfunction tryAcquireLock(lockPath: string): boolean {\n try {\n const fd = fs.openSync(lockPath, 'wx');\n fs.writeSync(fd, `${process.pid}\\n${Date.now()}`);\n fs.closeSync(fd);\n return true;\n } catch (e: any) {\n if (e.code === 'EEXIST') return false;\n throw e;\n }\n}\n\nfunction clearStaleLock(lockPath: string): boolean {\n try {\n const content = fs.readFileSync(lockPath, 'utf-8');\n const lockTime = parseInt(content.split('\\n')[1] || '0', 10);\n if (Date.now() - lockTime > LOCK_STALE_MS) {\n try { fs.unlinkSync(lockPath); } catch {}\n return true; // was stale, removed\n }\n } catch { return true; /* vanished — retry */ }\n return false;\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((r) => setTimeout(r, ms));\n}\n\nasync function acquireLock(source: Source): Promise<void> {\n const lockPath = lockFilePath(source);\n const deadline = Date.now() + LOCK_STALE_MS;\n\n while (Date.now() < deadline) {\n if (tryAcquireLock(lockPath)) return;\n clearStaleLock(lockPath);\n await sleep(LOCK_POLL_MS + Math.random() * 30);\n }\n\n // Timeout — force-remove and take lock\n try { fs.unlinkSync(lockPath); } catch {}\n if (!tryAcquireLock(lockPath)) {\n // Another process grabbed it — proceed without lock (best-effort)\n }\n}\n\nfunction releaseLock(source: Source): void {\n try { fs.unlinkSync(lockFilePath(source)); } catch {}\n}\n\n/**\n * Execute a callback while holding a file lock for the given source.\n * Serializes all hook processes for the same source (claude/cursor)\n * to prevent concurrent batch sends from causing chain hash desync.\n */\nexport async function withSessionLock<T>(source: Source, fn: () => Promise<T>): Promise<T> {\n await acquireLock(source);\n try {\n return await fn();\n } finally {\n releaseLock(source);\n }\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 { handleToolCall } from './shared/tool-call';\nhandleToolCall('claude');\n"],"mappings":"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","names":["exports","exports","isDeployCommand","exports","computeChainHash","signBatch","exports","path","fs","os","res","fs","import_fs","uuidv4","import_shared","import_fs","import_path","fs","EventCollector","path","fs"]}
1
+ {"version":3,"sources":["../../../shared/dist/types.js","../../../shared/src/scoring.ts","../../../shared/src/anticheat.ts","../../../shared/src/index.ts","../../src/config.ts","../../src/api-client.ts","../../src/queue.ts","../../src/collector.ts","../../src/hooks/shared/tool-call.ts","../../src/hooks/shared/utils.ts","../../src/hooks/tool-call.ts"],"sourcesContent":["\"use strict\";\nObject.defineProperty(exports, \"__esModule\", { value: true });\n//# sourceMappingURL=types.js.map","import type { ActionType } from './types';\n\nexport const SESSION_BASE_POINT_CAP = 20000;\nexport const TOOL_CALLS_PER_HOUR_CAP = 4000;\nexport const MIN_SESSION_TOKENS = 2000;\n\nexport const BASE_POINTS: Record<ActionType, { perUnit: number; unitSize: number }> = {\n token_input: { perUnit: 1, unitSize: 1000 },\n token_output: { perUnit: 2, unitSize: 1000 },\n tool_call: { perUnit: 5, unitSize: 1 },\n file_created: { perUnit: 15, unitSize: 1 },\n file_edited: { perUnit: 10, unitSize: 1 },\n commit: { perUnit: 50, unitSize: 1 },\n session_completed: { perUnit: 100, unitSize: 1 },\n deployment: { perUnit: 200, unitSize: 1 },\n prompt_submitted: { perUnit: 15, unitSize: 1 },\n subagent_spawned: { perUnit: 40, unitSize: 1 },\n context_compacted: { perUnit: 30, unitSize: 1 },\n stop_response: { perUnit: 10, unitSize: 1 },\n};\n\nexport const DEPLOY_PATTERNS = [\n /^vercel\\s+(--prod|deploy)/, /^netlify\\s+deploy/, /^fly\\s+deploy/, /^railway\\s+up/,\n /^git\\s+push\\s+\\S+\\s+(main|master|production)/,\n /^(npm|yarn|pnpm)\\s+run\\s+deploy/, /^(yarn|pnpm)\\s+deploy/,\n];\n\nexport function getBasePoints(actionType: ActionType, value: number): number {\n const config = BASE_POINTS[actionType];\n return Math.floor(value / config.unitSize) * config.perUnit;\n}\n\ninterface MultiplierInput { streak_days: number; commit_quality: boolean; language_count: number; }\n\nexport function calculateMultiplier(input: MultiplierInput): number {\n let streak = 1.0;\n if (input.streak_days >= 30) streak = 3.0;\n else if (input.streak_days >= 7) streak = 2.0;\n else if (input.streak_days >= 3) streak = 1.5;\n const quality = input.commit_quality ? 1.5 : 1.0;\n const diversity = input.language_count >= 3 ? 1.2 : 1.0;\n return streak * quality * diversity;\n}\n\nexport function calculateSessionScore(events: Array<{ action_type: ActionType; value: number }>, multiplier: number): number {\n let totalBase = 0;\n for (const e of events) totalBase += getBasePoints(e.action_type, e.value);\n return Math.floor(Math.min(totalBase, SESSION_BASE_POINT_CAP) * multiplier);\n}\n\nexport function isDeployCommand(command: string): boolean {\n return DEPLOY_PATTERNS.some((p) => p.test(command.trim()));\n}\n","/**\n * Anti-cheat cryptographic primitives.\n * Shared between plugin (client) and server.\n *\n * - Event chain: each event hash includes the previous, creating a tamper-evident chain\n * - Batch HMAC: the entire batch is signed with a server-issued session secret\n */\n\nimport { createHmac, createHash } from 'crypto';\n\n/** Hash a single event, chaining it to the previous hash */\nexport function hashEvent(\n event: { action_type: string; value: number; timestamp: string },\n prevHash: string,\n): string {\n const payload = `${prevHash}|${event.action_type}|${event.value}|${event.timestamp}`;\n return createHash('sha256').update(payload).digest('hex').slice(0, 16);\n}\n\n/** Compute the chain hash for an array of events */\nexport function computeChainHash(\n events: Array<{ action_type: string; value: number; timestamp: string }>,\n initialHash: string = '0000000000000000',\n): string {\n let hash = initialHash;\n for (const event of events) {\n hash = hashEvent(event, hash);\n }\n return hash;\n}\n\n/** Sign a batch with HMAC-SHA256 using the session secret */\nexport function signBatch(\n sessionId: string,\n events: Array<{ action_type: string; value: number; timestamp: string }>,\n chainHash: string,\n secret: string,\n): string {\n // Canonical form: sessionId + chainHash + event count + sum of values\n const valueSum = events.reduce((s, e) => s + e.value, 0);\n const message = `${sessionId}|${chainHash}|${events.length}|${valueSum}`;\n return createHmac('sha256', secret).update(message).digest('hex');\n}\n\n/** Verify a batch HMAC signature */\nexport function verifyBatchSignature(\n sessionId: string,\n events: Array<{ action_type: string; value: number; timestamp: string }>,\n chainHash: string,\n signature: string,\n secret: string,\n): boolean {\n const expected = signBatch(sessionId, events, chainHash, secret);\n // Constant-time comparison\n if (expected.length !== signature.length) return false;\n let mismatch = 0;\n for (let i = 0; i < expected.length; i++) {\n mismatch |= expected.charCodeAt(i) ^ signature.charCodeAt(i);\n }\n return mismatch === 0;\n}\n","export * from './types';\nexport * from './scoring';\nexport * from './anticheat';\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 const SESSIONS_DIR = path.join(CONFIG_DIR, 'sessions');\n\nexport function sessionStateFile(sessionId: string): string {\n return path.join(SESSIONS_DIR, `${sessionId}.json`);\n}\n\n/** @deprecated — only used for migrating old per-source state files */\nexport function legacySessionStateFile(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 { computeChainHash, signBatch } from '@hackersbaby/shared';\nimport type { EventBatch } from '@hackersbaby/shared';\n\nexport class APIClient {\n private config = loadConfig();\n private sessionSecret: string | null = null;\n private chainHash: string = '0000000000000000';\n private onChainUpdate: ((chainHash: string, secret: string) => void) | null = null;\n\n /** Set callback to persist chain state between hook processes */\n setChainUpdateCallback(cb: (chainHash: string, secret: string) => void) {\n this.onChainUpdate = cb;\n }\n\n /** Restore crypto state from a previous session */\n restoreCryptoState(secret: string, chainHash: string) {\n this.sessionSecret = secret;\n this.chainHash = chainHash;\n }\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 /** Get current crypto state for persistence */\n getCryptoState(): { secret: string; chainHash: string } | null {\n if (!this.sessionSecret) return null;\n return { secret: this.sessionSecret, chainHash: this.chainHash };\n }\n\n /** Register session with server and get cryptographic secret */\n async startSession(sessionId: string): Promise<boolean> {\n try {\n const server = this.config?.server || 'https://hackers.baby';\n const res = await fetch(`${server}/api/sessions/start`, {\n method: 'POST',\n headers: this.headers,\n body: JSON.stringify({ session_id: sessionId }),\n });\n if (res.ok) {\n const data = await res.json() as { session_secret: string; initial_chain_hash: string };\n this.sessionSecret = data.session_secret;\n this.chainHash = data.initial_chain_hash;\n return true;\n }\n return false;\n } catch {\n return false;\n }\n }\n\n async sendBatch(batch: EventBatch): Promise<boolean> {\n try {\n const server = this.config?.server || 'https://hackers.baby';\n\n // Sign the batch if we have a session secret\n if (this.sessionSecret) {\n const prevChainHash = this.chainHash;\n const newChainHash = computeChainHash(batch.events, prevChainHash);\n const signature = signBatch(batch.session_id, batch.events, newChainHash, this.sessionSecret);\n\n batch.signature = signature;\n batch.chain_hash = newChainHash;\n batch.prev_chain_hash = prevChainHash;\n\n const res = await fetch(`${server}/api/scores/batch`, {\n method: 'POST',\n headers: this.headers,\n body: JSON.stringify(batch),\n });\n\n if (res.ok) {\n // Only advance chain hash after server confirms acceptance\n this.chainHash = newChainHash;\n if (this.onChainUpdate) {\n this.onChainUpdate(this.chainHash, this.sessionSecret);\n }\n }\n\n return res.ok;\n }\n\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 { 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 readonly 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 /** Restore crypto state from persisted session data */\n restoreCrypto(secret: string, chainHash: string) {\n this.client.restoreCryptoState(secret, chainHash);\n }\n\n /** Set callback to persist crypto state after each batch */\n setCryptoUpdateCallback(cb: (chainHash: string, secret: string) => void) {\n this.client.setChainUpdateCallback(cb);\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 // Register session with server for cryptographic signing\n await this.client.startSession(this.sessionId);\n\n // Clear stale queue — events from previous sessions have old timestamps\n // that the server will reject, and retrying them risks chain desync.\n drainQueue();\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 { isDeployCommand } from '@hackersbaby/shared';\nimport type { Source } from '../../config';\nimport { loadSessionId, readStdin, normalizePayload, detectSource, createCollector, withSessionLock } from './utils';\n\nexport async function handleToolCall(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 toolName = hookData.tool_name || 'unknown';\n const toolInput = hookData.tool_input || {};\n const sessionId = hookData.session_id || loadSessionId(detected);\n if (!sessionId) return; // No active session\n\n await withSessionLock(sessionId, async () => {\n const collector = createCollector(sessionId, detected);\n\n // PRIVACY: Only send tool name — never send tool_input content, file paths, or commands\n collector.addEvent({ action_type: 'tool_call', value: 1, metadata: { tool_name: toolName } });\n\n // Track token usage from hook payload\n if (hookData.input_tokens) {\n collector.addEvent({ action_type: 'token_input', value: hookData.input_tokens, metadata: {} });\n }\n if (hookData.output_tokens) {\n collector.addEvent({ action_type: 'token_output', value: hookData.output_tokens, metadata: {} });\n }\n\n // Track Bash commands for deployment detection — PRIVACY: only send boolean, not the command\n if (toolName === 'Bash' && toolInput.command && isDeployCommand(toolInput.command)) {\n collector.addEvent({ action_type: 'deployment', value: 1, metadata: {} });\n }\n\n // Track file creation — PRIVACY: no file path sent\n if (toolName === 'Write') {\n collector.addEvent({ action_type: 'file_created', value: 1, metadata: {} });\n }\n\n // Track file edits — PRIVACY: no file path sent\n if (toolName === 'Edit') {\n collector.addEvent({ action_type: 'file_edited', value: 1, metadata: {} });\n }\n\n await collector.flush();\n });\n } catch {}\n}\n","import fs from 'fs';\nimport path from 'path';\nimport type { Source } from '../../config';\nimport { sessionStateFile, SESSIONS_DIR, CONFIG_DIR, legacySessionStateFile } from '../../config';\n\nexport interface SessionState {\n sessionId: string;\n pid: number;\n source: Source;\n sessionSecret?: string;\n chainHash?: string;\n}\n\nfunction ensureSessionsDir(): void {\n if (!fs.existsSync(SESSIONS_DIR)) {\n fs.mkdirSync(SESSIONS_DIR, { recursive: true });\n }\n}\n\nexport function loadSessionState(sessionId: string): SessionState | undefined {\n try {\n const file = sessionStateFile(sessionId);\n if (!fs.existsSync(file)) return undefined;\n return JSON.parse(fs.readFileSync(file, 'utf-8')) as SessionState;\n } catch {\n return undefined;\n }\n}\n\n/**\n * Fallback: find any session file for a given source.\n * Used when session_id is missing from hook payload (legacy/edge case).\n */\nexport function findSessionBySource(source: Source): SessionState | undefined {\n try {\n if (!fs.existsSync(SESSIONS_DIR)) return undefined;\n const files = fs.readdirSync(SESSIONS_DIR).filter(f => f.endsWith('.json'));\n for (const f of files) {\n try {\n const state = JSON.parse(fs.readFileSync(path.join(SESSIONS_DIR, f), 'utf-8')) as SessionState;\n if (state.source === source) return state;\n } catch {}\n }\n } catch {}\n // Also check legacy file as last resort\n try {\n const legacyFile = legacySessionStateFile(source);\n if (fs.existsSync(legacyFile)) {\n return JSON.parse(fs.readFileSync(legacyFile, 'utf-8')) as SessionState;\n }\n } catch {}\n return undefined;\n}\n\nexport function loadSessionId(source: Source): string | undefined {\n return findSessionBySource(source)?.sessionId;\n}\n\nexport function updateSessionState(sessionId: string, updates: Partial<SessionState>): void {\n const state = loadSessionState(sessionId);\n if (!state) return;\n const file = sessionStateFile(sessionId);\n fs.writeFileSync(file, JSON.stringify({ ...state, ...updates }));\n}\n\nexport function saveSessionState(state: SessionState): void {\n ensureSessionsDir();\n const file = sessionStateFile(state.sessionId);\n fs.writeFileSync(file, JSON.stringify(state));\n}\n\n/** Create an EventCollector with crypto state restored from the session file */\nexport function createCollector(sessionId: string | undefined, source: Source): import('../../collector').EventCollector {\n const { EventCollector } = require('../../collector');\n const collector = new EventCollector(sessionId, source);\n\n // Restore crypto state from session file (keyed by sessionId)\n const resolvedId = collector.sessionId;\n const state = loadSessionState(resolvedId);\n if (state?.sessionSecret && state?.chainHash && state.sessionId === resolvedId) {\n collector.restoreCrypto(state.sessionSecret, state.chainHash);\n\n // Update session file after each signed batch\n collector.setCryptoUpdateCallback((chainHash: string, secret: string) => {\n updateSessionState(resolvedId, { chainHash, sessionSecret: secret });\n });\n }\n\n return collector;\n}\n\nexport function removeSessionState(sessionId: string): void {\n try {\n const file = sessionStateFile(sessionId);\n if (fs.existsSync(file)) fs.unlinkSync(file);\n } catch {}\n}\n\n/**\n * Clean up stale session files older than maxAge (default 24h).\n * Called during session-start to prevent buildup.\n */\nexport function cleanStaleSessionFiles(maxAgeMs: number = 7 * 24 * 60 * 60 * 1000): void {\n try {\n if (!fs.existsSync(SESSIONS_DIR)) return;\n const now = Date.now();\n for (const f of fs.readdirSync(SESSIONS_DIR)) {\n if (!f.endsWith('.json')) continue;\n const filePath = path.join(SESSIONS_DIR, f);\n try {\n const stat = fs.statSync(filePath);\n if (now - stat.mtimeMs > maxAgeMs) {\n fs.unlinkSync(filePath);\n }\n } catch {}\n }\n } catch {}\n}\n\n// --- File-based lock to serialize batch sending across concurrent hook processes ---\n\nconst LOCK_STALE_MS = 5_000; // 5 seconds — force-remove stale locks\nconst LOCK_POLL_MS = 20; // polling interval\n\nfunction lockFilePath(sessionId: string): string {\n ensureSessionsDir();\n return path.join(SESSIONS_DIR, `lock-${sessionId}`);\n}\n\nfunction tryAcquireLock(lockPath: string): boolean {\n try {\n const fd = fs.openSync(lockPath, 'wx');\n fs.writeSync(fd, `${process.pid}\\n${Date.now()}`);\n fs.closeSync(fd);\n return true;\n } catch (e: any) {\n if (e.code === 'EEXIST') return false;\n throw e;\n }\n}\n\nfunction clearStaleLock(lockPath: string): boolean {\n try {\n const content = fs.readFileSync(lockPath, 'utf-8');\n const lockTime = parseInt(content.split('\\n')[1] || '0', 10);\n if (Date.now() - lockTime > LOCK_STALE_MS) {\n try { fs.unlinkSync(lockPath); } catch {}\n return true; // was stale, removed\n }\n } catch { return true; /* vanished — retry */ }\n return false;\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((r) => setTimeout(r, ms));\n}\n\nasync function acquireLock(sessionId: string): Promise<void> {\n const lockPath = lockFilePath(sessionId);\n const deadline = Date.now() + LOCK_STALE_MS;\n\n while (Date.now() < deadline) {\n if (tryAcquireLock(lockPath)) return;\n clearStaleLock(lockPath);\n await sleep(LOCK_POLL_MS + Math.random() * 30);\n }\n\n // Timeout — force-remove and take lock\n try { fs.unlinkSync(lockPath); } catch {}\n if (!tryAcquireLock(lockPath)) {\n // Another process grabbed it — proceed without lock (best-effort)\n }\n}\n\nfunction releaseLock(sessionId: string): void {\n try { fs.unlinkSync(lockFilePath(sessionId)); } catch {}\n}\n\n/**\n * Execute a callback while holding a file lock for the given session.\n * Serializes all hook processes for the same session\n * to prevent concurrent batch sends from causing chain hash desync.\n */\nexport async function withSessionLock<T>(sessionId: string, fn: () => Promise<T>): Promise<T> {\n await acquireLock(sessionId);\n try {\n return await fn();\n } finally {\n releaseLock(sessionId);\n }\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 { handleToolCall } from './shared/tool-call';\nhandleToolCall('claude');\n"],"mappings":"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","names":["exports","exports","isDeployCommand","exports","computeChainHash","signBatch","exports","path","fs","os","res","fs","import_fs","uuidv4","import_shared","import_fs","import_path","fs","path","EventCollector","path","fs"]}
package/dist/index.js CHANGED
@@ -189,6 +189,7 @@ var import_os = __toESM(require("os"));
189
189
  var CONFIG_DIR = import_path.default.join(import_os.default.homedir(), ".hackersbaby");
190
190
  var CONFIG_FILE = import_path.default.join(CONFIG_DIR, "config.json");
191
191
  var QUEUE_FILE = import_path.default.join(CONFIG_DIR, "queue.jsonl");
192
+ var SESSIONS_DIR = import_path.default.join(CONFIG_DIR, "sessions");
192
193
  function ensureConfigDir() {
193
194
  if (!import_fs.default.existsSync(CONFIG_DIR)) {
194
195
  import_fs.default.mkdirSync(CONFIG_DIR, { recursive: true });
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../../shared/dist/types.js","../../shared/src/scoring.ts","../../shared/src/anticheat.ts","../../shared/src/index.ts","../src/index.ts","../src/collector.ts","../src/config.ts","../src/api-client.ts","../src/queue.ts"],"sourcesContent":["\"use strict\";\nObject.defineProperty(exports, \"__esModule\", { value: true });\n//# sourceMappingURL=types.js.map","import type { ActionType } from './types';\n\nexport const SESSION_BASE_POINT_CAP = 20000;\nexport const TOOL_CALLS_PER_HOUR_CAP = 4000;\nexport const MIN_SESSION_TOKENS = 2000;\n\nexport const BASE_POINTS: Record<ActionType, { perUnit: number; unitSize: number }> = {\n token_input: { perUnit: 1, unitSize: 1000 },\n token_output: { perUnit: 2, unitSize: 1000 },\n tool_call: { perUnit: 5, unitSize: 1 },\n file_created: { perUnit: 15, unitSize: 1 },\n file_edited: { perUnit: 10, unitSize: 1 },\n commit: { perUnit: 50, unitSize: 1 },\n session_completed: { perUnit: 100, unitSize: 1 },\n deployment: { perUnit: 200, unitSize: 1 },\n prompt_submitted: { perUnit: 15, unitSize: 1 },\n subagent_spawned: { perUnit: 40, unitSize: 1 },\n context_compacted: { perUnit: 30, unitSize: 1 },\n stop_response: { perUnit: 10, unitSize: 1 },\n};\n\nexport const DEPLOY_PATTERNS = [\n /^vercel\\s+(--prod|deploy)/, /^netlify\\s+deploy/, /^fly\\s+deploy/, /^railway\\s+up/,\n /^git\\s+push\\s+\\S+\\s+(main|master|production)/,\n /^(npm|yarn|pnpm)\\s+run\\s+deploy/, /^(yarn|pnpm)\\s+deploy/,\n];\n\nexport function getBasePoints(actionType: ActionType, value: number): number {\n const config = BASE_POINTS[actionType];\n return Math.floor(value / config.unitSize) * config.perUnit;\n}\n\ninterface MultiplierInput { streak_days: number; commit_quality: boolean; language_count: number; }\n\nexport function calculateMultiplier(input: MultiplierInput): number {\n let streak = 1.0;\n if (input.streak_days >= 30) streak = 3.0;\n else if (input.streak_days >= 7) streak = 2.0;\n else if (input.streak_days >= 3) streak = 1.5;\n const quality = input.commit_quality ? 1.5 : 1.0;\n const diversity = input.language_count >= 3 ? 1.2 : 1.0;\n return streak * quality * diversity;\n}\n\nexport function calculateSessionScore(events: Array<{ action_type: ActionType; value: number }>, multiplier: number): number {\n let totalBase = 0;\n for (const e of events) totalBase += getBasePoints(e.action_type, e.value);\n return Math.floor(Math.min(totalBase, SESSION_BASE_POINT_CAP) * multiplier);\n}\n\nexport function isDeployCommand(command: string): boolean {\n return DEPLOY_PATTERNS.some((p) => p.test(command.trim()));\n}\n","/**\n * Anti-cheat cryptographic primitives.\n * Shared between plugin (client) and server.\n *\n * - Event chain: each event hash includes the previous, creating a tamper-evident chain\n * - Batch HMAC: the entire batch is signed with a server-issued session secret\n */\n\nimport { createHmac, createHash } from 'crypto';\n\n/** Hash a single event, chaining it to the previous hash */\nexport function hashEvent(\n event: { action_type: string; value: number; timestamp: string },\n prevHash: string,\n): string {\n const payload = `${prevHash}|${event.action_type}|${event.value}|${event.timestamp}`;\n return createHash('sha256').update(payload).digest('hex').slice(0, 16);\n}\n\n/** Compute the chain hash for an array of events */\nexport function computeChainHash(\n events: Array<{ action_type: string; value: number; timestamp: string }>,\n initialHash: string = '0000000000000000',\n): string {\n let hash = initialHash;\n for (const event of events) {\n hash = hashEvent(event, hash);\n }\n return hash;\n}\n\n/** Sign a batch with HMAC-SHA256 using the session secret */\nexport function signBatch(\n sessionId: string,\n events: Array<{ action_type: string; value: number; timestamp: string }>,\n chainHash: string,\n secret: string,\n): string {\n // Canonical form: sessionId + chainHash + event count + sum of values\n const valueSum = events.reduce((s, e) => s + e.value, 0);\n const message = `${sessionId}|${chainHash}|${events.length}|${valueSum}`;\n return createHmac('sha256', secret).update(message).digest('hex');\n}\n\n/** Verify a batch HMAC signature */\nexport function verifyBatchSignature(\n sessionId: string,\n events: Array<{ action_type: string; value: number; timestamp: string }>,\n chainHash: string,\n signature: string,\n secret: string,\n): boolean {\n const expected = signBatch(sessionId, events, chainHash, secret);\n // Constant-time comparison\n if (expected.length !== signature.length) return false;\n let mismatch = 0;\n for (let i = 0; i < expected.length; i++) {\n mismatch |= expected.charCodeAt(i) ^ signature.charCodeAt(i);\n }\n return mismatch === 0;\n}\n","export * from './types';\nexport * from './scoring';\nexport * from './anticheat';\n","export { EventCollector } from './collector';\nexport { APIClient } from './api-client';\nexport { loadConfig, saveConfig } from './config';\n","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 readonly 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 /** Restore crypto state from persisted session data */\n restoreCrypto(secret: string, chainHash: string) {\n this.client.restoreCryptoState(secret, chainHash);\n }\n\n /** Set callback to persist crypto state after each batch */\n setCryptoUpdateCallback(cb: (chainHash: string, secret: string) => void) {\n this.client.setChainUpdateCallback(cb);\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 // Register session with server for cryptographic signing\n await this.client.startSession(this.sessionId);\n\n // Clear stale queue — events from previous sessions have old timestamps\n // that the server will reject, and retrying them risks chain desync.\n drainQueue();\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 { computeChainHash, signBatch } from '@hackersbaby/shared';\nimport type { EventBatch } from '@hackersbaby/shared';\n\nexport class APIClient {\n private config = loadConfig();\n private sessionSecret: string | null = null;\n private chainHash: string = '0000000000000000';\n private onChainUpdate: ((chainHash: string, secret: string) => void) | null = null;\n\n /** Set callback to persist chain state between hook processes */\n setChainUpdateCallback(cb: (chainHash: string, secret: string) => void) {\n this.onChainUpdate = cb;\n }\n\n /** Restore crypto state from a previous session */\n restoreCryptoState(secret: string, chainHash: string) {\n this.sessionSecret = secret;\n this.chainHash = chainHash;\n }\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 /** Get current crypto state for persistence */\n getCryptoState(): { secret: string; chainHash: string } | null {\n if (!this.sessionSecret) return null;\n return { secret: this.sessionSecret, chainHash: this.chainHash };\n }\n\n /** Register session with server and get cryptographic secret */\n async startSession(sessionId: string): Promise<boolean> {\n try {\n const server = this.config?.server || 'https://hackers.baby';\n const res = await fetch(`${server}/api/sessions/start`, {\n method: 'POST',\n headers: this.headers,\n body: JSON.stringify({ session_id: sessionId }),\n });\n if (res.ok) {\n const data = await res.json() as { session_secret: string; initial_chain_hash: string };\n this.sessionSecret = data.session_secret;\n this.chainHash = data.initial_chain_hash;\n return true;\n }\n return false;\n } catch {\n return false;\n }\n }\n\n async sendBatch(batch: EventBatch): Promise<boolean> {\n try {\n const server = this.config?.server || 'https://hackers.baby';\n\n // Sign the batch if we have a session secret\n if (this.sessionSecret) {\n const prevChainHash = this.chainHash;\n const newChainHash = computeChainHash(batch.events, prevChainHash);\n const signature = signBatch(batch.session_id, batch.events, newChainHash, this.sessionSecret);\n\n batch.signature = signature;\n batch.chain_hash = newChainHash;\n batch.prev_chain_hash = prevChainHash;\n\n const res = await fetch(`${server}/api/scores/batch`, {\n method: 'POST',\n headers: this.headers,\n body: JSON.stringify(batch),\n });\n\n if (res.ok) {\n // Only advance chain hash after server confirms acceptance\n this.chainHash = newChainHash;\n if (this.onChainUpdate) {\n this.onChainUpdate(this.chainHash, this.sessionSecret);\n }\n }\n\n return res.ok;\n }\n\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"],"mappings":"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","names":["exports","exports","exports","computeChainHash","signBatch","exports","path","os","fs","res","import_fs","fs","uuidv4"]}
1
+ {"version":3,"sources":["../../shared/dist/types.js","../../shared/src/scoring.ts","../../shared/src/anticheat.ts","../../shared/src/index.ts","../src/index.ts","../src/collector.ts","../src/config.ts","../src/api-client.ts","../src/queue.ts"],"sourcesContent":["\"use strict\";\nObject.defineProperty(exports, \"__esModule\", { value: true });\n//# sourceMappingURL=types.js.map","import type { ActionType } from './types';\n\nexport const SESSION_BASE_POINT_CAP = 20000;\nexport const TOOL_CALLS_PER_HOUR_CAP = 4000;\nexport const MIN_SESSION_TOKENS = 2000;\n\nexport const BASE_POINTS: Record<ActionType, { perUnit: number; unitSize: number }> = {\n token_input: { perUnit: 1, unitSize: 1000 },\n token_output: { perUnit: 2, unitSize: 1000 },\n tool_call: { perUnit: 5, unitSize: 1 },\n file_created: { perUnit: 15, unitSize: 1 },\n file_edited: { perUnit: 10, unitSize: 1 },\n commit: { perUnit: 50, unitSize: 1 },\n session_completed: { perUnit: 100, unitSize: 1 },\n deployment: { perUnit: 200, unitSize: 1 },\n prompt_submitted: { perUnit: 15, unitSize: 1 },\n subagent_spawned: { perUnit: 40, unitSize: 1 },\n context_compacted: { perUnit: 30, unitSize: 1 },\n stop_response: { perUnit: 10, unitSize: 1 },\n};\n\nexport const DEPLOY_PATTERNS = [\n /^vercel\\s+(--prod|deploy)/, /^netlify\\s+deploy/, /^fly\\s+deploy/, /^railway\\s+up/,\n /^git\\s+push\\s+\\S+\\s+(main|master|production)/,\n /^(npm|yarn|pnpm)\\s+run\\s+deploy/, /^(yarn|pnpm)\\s+deploy/,\n];\n\nexport function getBasePoints(actionType: ActionType, value: number): number {\n const config = BASE_POINTS[actionType];\n return Math.floor(value / config.unitSize) * config.perUnit;\n}\n\ninterface MultiplierInput { streak_days: number; commit_quality: boolean; language_count: number; }\n\nexport function calculateMultiplier(input: MultiplierInput): number {\n let streak = 1.0;\n if (input.streak_days >= 30) streak = 3.0;\n else if (input.streak_days >= 7) streak = 2.0;\n else if (input.streak_days >= 3) streak = 1.5;\n const quality = input.commit_quality ? 1.5 : 1.0;\n const diversity = input.language_count >= 3 ? 1.2 : 1.0;\n return streak * quality * diversity;\n}\n\nexport function calculateSessionScore(events: Array<{ action_type: ActionType; value: number }>, multiplier: number): number {\n let totalBase = 0;\n for (const e of events) totalBase += getBasePoints(e.action_type, e.value);\n return Math.floor(Math.min(totalBase, SESSION_BASE_POINT_CAP) * multiplier);\n}\n\nexport function isDeployCommand(command: string): boolean {\n return DEPLOY_PATTERNS.some((p) => p.test(command.trim()));\n}\n","/**\n * Anti-cheat cryptographic primitives.\n * Shared between plugin (client) and server.\n *\n * - Event chain: each event hash includes the previous, creating a tamper-evident chain\n * - Batch HMAC: the entire batch is signed with a server-issued session secret\n */\n\nimport { createHmac, createHash } from 'crypto';\n\n/** Hash a single event, chaining it to the previous hash */\nexport function hashEvent(\n event: { action_type: string; value: number; timestamp: string },\n prevHash: string,\n): string {\n const payload = `${prevHash}|${event.action_type}|${event.value}|${event.timestamp}`;\n return createHash('sha256').update(payload).digest('hex').slice(0, 16);\n}\n\n/** Compute the chain hash for an array of events */\nexport function computeChainHash(\n events: Array<{ action_type: string; value: number; timestamp: string }>,\n initialHash: string = '0000000000000000',\n): string {\n let hash = initialHash;\n for (const event of events) {\n hash = hashEvent(event, hash);\n }\n return hash;\n}\n\n/** Sign a batch with HMAC-SHA256 using the session secret */\nexport function signBatch(\n sessionId: string,\n events: Array<{ action_type: string; value: number; timestamp: string }>,\n chainHash: string,\n secret: string,\n): string {\n // Canonical form: sessionId + chainHash + event count + sum of values\n const valueSum = events.reduce((s, e) => s + e.value, 0);\n const message = `${sessionId}|${chainHash}|${events.length}|${valueSum}`;\n return createHmac('sha256', secret).update(message).digest('hex');\n}\n\n/** Verify a batch HMAC signature */\nexport function verifyBatchSignature(\n sessionId: string,\n events: Array<{ action_type: string; value: number; timestamp: string }>,\n chainHash: string,\n signature: string,\n secret: string,\n): boolean {\n const expected = signBatch(sessionId, events, chainHash, secret);\n // Constant-time comparison\n if (expected.length !== signature.length) return false;\n let mismatch = 0;\n for (let i = 0; i < expected.length; i++) {\n mismatch |= expected.charCodeAt(i) ^ signature.charCodeAt(i);\n }\n return mismatch === 0;\n}\n","export * from './types';\nexport * from './scoring';\nexport * from './anticheat';\n","export { EventCollector } from './collector';\nexport { APIClient } from './api-client';\nexport { loadConfig, saveConfig } from './config';\n","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 readonly 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 /** Restore crypto state from persisted session data */\n restoreCrypto(secret: string, chainHash: string) {\n this.client.restoreCryptoState(secret, chainHash);\n }\n\n /** Set callback to persist crypto state after each batch */\n setCryptoUpdateCallback(cb: (chainHash: string, secret: string) => void) {\n this.client.setChainUpdateCallback(cb);\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 // Register session with server for cryptographic signing\n await this.client.startSession(this.sessionId);\n\n // Clear stale queue — events from previous sessions have old timestamps\n // that the server will reject, and retrying them risks chain desync.\n drainQueue();\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 const SESSIONS_DIR = path.join(CONFIG_DIR, 'sessions');\n\nexport function sessionStateFile(sessionId: string): string {\n return path.join(SESSIONS_DIR, `${sessionId}.json`);\n}\n\n/** @deprecated — only used for migrating old per-source state files */\nexport function legacySessionStateFile(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 { computeChainHash, signBatch } from '@hackersbaby/shared';\nimport type { EventBatch } from '@hackersbaby/shared';\n\nexport class APIClient {\n private config = loadConfig();\n private sessionSecret: string | null = null;\n private chainHash: string = '0000000000000000';\n private onChainUpdate: ((chainHash: string, secret: string) => void) | null = null;\n\n /** Set callback to persist chain state between hook processes */\n setChainUpdateCallback(cb: (chainHash: string, secret: string) => void) {\n this.onChainUpdate = cb;\n }\n\n /** Restore crypto state from a previous session */\n restoreCryptoState(secret: string, chainHash: string) {\n this.sessionSecret = secret;\n this.chainHash = chainHash;\n }\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 /** Get current crypto state for persistence */\n getCryptoState(): { secret: string; chainHash: string } | null {\n if (!this.sessionSecret) return null;\n return { secret: this.sessionSecret, chainHash: this.chainHash };\n }\n\n /** Register session with server and get cryptographic secret */\n async startSession(sessionId: string): Promise<boolean> {\n try {\n const server = this.config?.server || 'https://hackers.baby';\n const res = await fetch(`${server}/api/sessions/start`, {\n method: 'POST',\n headers: this.headers,\n body: JSON.stringify({ session_id: sessionId }),\n });\n if (res.ok) {\n const data = await res.json() as { session_secret: string; initial_chain_hash: string };\n this.sessionSecret = data.session_secret;\n this.chainHash = data.initial_chain_hash;\n return true;\n }\n return false;\n } catch {\n return false;\n }\n }\n\n async sendBatch(batch: EventBatch): Promise<boolean> {\n try {\n const server = this.config?.server || 'https://hackers.baby';\n\n // Sign the batch if we have a session secret\n if (this.sessionSecret) {\n const prevChainHash = this.chainHash;\n const newChainHash = computeChainHash(batch.events, prevChainHash);\n const signature = signBatch(batch.session_id, batch.events, newChainHash, this.sessionSecret);\n\n batch.signature = signature;\n batch.chain_hash = newChainHash;\n batch.prev_chain_hash = prevChainHash;\n\n const res = await fetch(`${server}/api/scores/batch`, {\n method: 'POST',\n headers: this.headers,\n body: JSON.stringify(batch),\n });\n\n if (res.ok) {\n // Only advance chain hash after server confirms acceptance\n this.chainHash = newChainHash;\n if (this.onChainUpdate) {\n this.onChainUpdate(this.chainHash, this.sessionSecret);\n }\n }\n\n return res.ok;\n }\n\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"],"mappings":"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","names":["exports","exports","exports","computeChainHash","signBatch","exports","path","os","fs","res","import_fs","fs","uuidv4"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hackersbaby/plugin",
3
- "version": "0.5.3",
3
+ "version": "0.5.5",
4
4
  "description": "hackers.baby CLI — gamified developer leaderboard plugin for Claude Code",
5
5
  "license": "MIT",
6
6
  "repository": {