@hackersbaby/plugin 0.3.0 → 0.4.1

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.
@@ -0,0 +1,316 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __create = Object.create;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __getProtoOf = Object.getPrototypeOf;
8
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __copyProps = (to, from, except, desc) => {
10
+ if (from && typeof from === "object" || typeof from === "function") {
11
+ for (let key of __getOwnPropNames(from))
12
+ if (!__hasOwnProp.call(to, key) && key !== except)
13
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
14
+ }
15
+ return to;
16
+ };
17
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
18
+ // If the importer is in node compatibility mode or this is not an ESM
19
+ // file that has been converted to a CommonJS file using a Babel-
20
+ // compatible transform (i.e. "__esModule" has not been set), then set
21
+ // "default" to the CommonJS "module.exports" for node compatibility.
22
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
23
+ mod
24
+ ));
25
+
26
+ // src/collector.ts
27
+ var import_uuid = require("uuid");
28
+
29
+ // src/config.ts
30
+ var import_fs = __toESM(require("fs"));
31
+ var import_path = __toESM(require("path"));
32
+ var import_os = __toESM(require("os"));
33
+ var CONFIG_DIR = import_path.default.join(import_os.default.homedir(), ".hackersbaby");
34
+ var CONFIG_FILE = import_path.default.join(CONFIG_DIR, "config.json");
35
+ var QUEUE_FILE = import_path.default.join(CONFIG_DIR, "queue.jsonl");
36
+ function sessionStateFile(source) {
37
+ return import_path.default.join(CONFIG_DIR, `active-session-${source}.json`);
38
+ }
39
+ function ensureConfigDir() {
40
+ if (!import_fs.default.existsSync(CONFIG_DIR)) {
41
+ import_fs.default.mkdirSync(CONFIG_DIR, { recursive: true });
42
+ }
43
+ }
44
+ function loadConfig() {
45
+ try {
46
+ if (!import_fs.default.existsSync(CONFIG_FILE)) return null;
47
+ const raw = import_fs.default.readFileSync(CONFIG_FILE, "utf-8");
48
+ return JSON.parse(raw);
49
+ } catch {
50
+ return null;
51
+ }
52
+ }
53
+ function saveConfig(config) {
54
+ ensureConfigDir();
55
+ import_fs.default.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2), "utf-8");
56
+ }
57
+
58
+ // src/api-client.ts
59
+ var APIClient = class {
60
+ config = loadConfig();
61
+ get headers() {
62
+ return {
63
+ "Content-Type": "application/json",
64
+ Authorization: `Bearer ${this.config?.token || ""}`
65
+ };
66
+ }
67
+ async sendBatch(batch) {
68
+ try {
69
+ const server = this.config?.server || "https://hackers.baby";
70
+ const res = await fetch(`${server}/api/scores/batch`, {
71
+ method: "POST",
72
+ headers: this.headers,
73
+ body: JSON.stringify(batch)
74
+ });
75
+ return res.ok;
76
+ } catch {
77
+ return false;
78
+ }
79
+ }
80
+ async refreshTokenIfNeeded() {
81
+ if (!this.config) return;
82
+ try {
83
+ const parts = this.config.token.split(".");
84
+ if (parts.length !== 3) return;
85
+ const payload = JSON.parse(Buffer.from(parts[1], "base64").toString("utf-8"));
86
+ const expMs = payload.exp * 1e3;
87
+ const oneDayMs = 24 * 60 * 60 * 1e3;
88
+ if (Date.now() + oneDayMs < expMs) return;
89
+ const res = await fetch(`${this.config.server}/api/auth/cli-token/refresh`, {
90
+ method: "POST",
91
+ headers: this.headers,
92
+ body: JSON.stringify({ refresh_token: this.config.refresh_token })
93
+ });
94
+ if (res.ok) {
95
+ const data = await res.json();
96
+ if (data.token) {
97
+ this.config.token = data.token;
98
+ if (data.refresh_token) this.config.refresh_token = data.refresh_token;
99
+ saveConfig(this.config);
100
+ }
101
+ }
102
+ } catch {
103
+ }
104
+ }
105
+ async getStatus() {
106
+ const server = this.config?.server || "https://hackers.baby";
107
+ const res = await fetch(`${server}/api/users/me`, { headers: this.headers });
108
+ if (!res.ok) throw new Error(`getStatus failed: ${res.status}`);
109
+ return res.json();
110
+ }
111
+ async getRank() {
112
+ const server = this.config?.server || "https://hackers.baby";
113
+ const res = await fetch(`${server}/api/users/me/rank`, { headers: this.headers });
114
+ if (!res.ok) throw new Error(`getRank failed: ${res.status}`);
115
+ return res.json();
116
+ }
117
+ async getLeaderboard() {
118
+ const server = this.config?.server || "https://hackers.baby";
119
+ const res = await fetch(`${server}/api/leaderboard/global?limit=10`, { headers: this.headers });
120
+ if (!res.ok) throw new Error(`getLeaderboard failed: ${res.status}`);
121
+ return res.json();
122
+ }
123
+ async getPublicStats() {
124
+ try {
125
+ const server = this.config?.server || "https://hackers.baby";
126
+ const res = await fetch(`${server}/api/stats/public`);
127
+ if (!res.ok) return null;
128
+ return res.json();
129
+ } catch {
130
+ return null;
131
+ }
132
+ }
133
+ async getNotifications() {
134
+ try {
135
+ const server = this.config?.server || "https://hackers.baby";
136
+ const res = await fetch(`${server}/api/users/me/notifications`, { headers: this.headers });
137
+ if (!res.ok) return null;
138
+ return res.json();
139
+ } catch {
140
+ return null;
141
+ }
142
+ }
143
+ async claimReferral(code) {
144
+ try {
145
+ const server = this.config?.server || "https://hackers.baby";
146
+ const res = await fetch(`${server}/api/referral/claim`, {
147
+ method: "POST",
148
+ headers: this.headers,
149
+ body: JSON.stringify({ referral_code: code })
150
+ });
151
+ if (!res.ok) return null;
152
+ return res.json();
153
+ } catch {
154
+ return null;
155
+ }
156
+ }
157
+ async getReferralStats() {
158
+ try {
159
+ const server = this.config?.server || "https://hackers.baby";
160
+ const res = await fetch(`${server}/api/referral/stats`, { headers: this.headers });
161
+ if (!res.ok) return null;
162
+ return res.json();
163
+ } catch {
164
+ return null;
165
+ }
166
+ }
167
+ };
168
+
169
+ // src/queue.ts
170
+ var import_fs2 = __toESM(require("fs"));
171
+ var MAX_QUEUE_SIZE = 1e4;
172
+ var MAX_AGE_MS = 24 * 60 * 60 * 1e3;
173
+ function enqueueEvents(events) {
174
+ ensureConfigDir();
175
+ const lines = events.map((e) => JSON.stringify(e)).join("\n");
176
+ import_fs2.default.appendFileSync(QUEUE_FILE, lines + "\n", "utf-8");
177
+ try {
178
+ const content = import_fs2.default.readFileSync(QUEUE_FILE, "utf-8");
179
+ const allLines = content.split("\n").filter((l) => l.trim() !== "");
180
+ if (allLines.length > MAX_QUEUE_SIZE) {
181
+ const trimmed = allLines.slice(allLines.length - MAX_QUEUE_SIZE);
182
+ import_fs2.default.writeFileSync(QUEUE_FILE, trimmed.join("\n") + "\n", "utf-8");
183
+ }
184
+ } catch {
185
+ }
186
+ }
187
+ function drainQueue() {
188
+ try {
189
+ if (!import_fs2.default.existsSync(QUEUE_FILE)) return [];
190
+ const content = import_fs2.default.readFileSync(QUEUE_FILE, "utf-8");
191
+ import_fs2.default.writeFileSync(QUEUE_FILE, "", "utf-8");
192
+ const now = Date.now();
193
+ const events = [];
194
+ for (const line of content.split("\n")) {
195
+ if (!line.trim()) continue;
196
+ try {
197
+ const event = JSON.parse(line);
198
+ const age = now - new Date(event.timestamp).getTime();
199
+ if (age <= MAX_AGE_MS) {
200
+ events.push(event);
201
+ }
202
+ } catch {
203
+ }
204
+ }
205
+ return events;
206
+ } catch {
207
+ return [];
208
+ }
209
+ }
210
+
211
+ // src/collector.ts
212
+ var EventCollector = class {
213
+ sessionId;
214
+ buffer = [];
215
+ flushInterval = null;
216
+ client;
217
+ source;
218
+ constructor(sessionId, source) {
219
+ this.sessionId = sessionId ?? (0, import_uuid.v4)();
220
+ this.client = new APIClient();
221
+ this.source = source;
222
+ }
223
+ addEvent(event) {
224
+ const metadata = this.source ? { ...event.metadata, source: this.source } : event.metadata;
225
+ this.buffer.push({
226
+ ...event,
227
+ metadata,
228
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
229
+ });
230
+ }
231
+ async start() {
232
+ await this.client.refreshTokenIfNeeded();
233
+ const queued = drainQueue();
234
+ if (queued.length > 0) {
235
+ const batch = { session_id: this.sessionId, events: queued };
236
+ const ok = await this.client.sendBatch(batch);
237
+ if (!ok) {
238
+ enqueueEvents(queued);
239
+ }
240
+ }
241
+ const config = loadConfig();
242
+ const intervalMs = config?.preferences?.batch_interval_ms ?? 1e4;
243
+ this.flushInterval = setInterval(() => {
244
+ this.flush().catch(() => {
245
+ });
246
+ }, intervalMs);
247
+ }
248
+ async flush() {
249
+ if (this.buffer.length === 0) return;
250
+ const events = this.buffer.splice(0);
251
+ const batch = { session_id: this.sessionId, events };
252
+ const ok = await this.client.sendBatch(batch);
253
+ if (!ok) {
254
+ enqueueEvents(events);
255
+ }
256
+ }
257
+ async stop() {
258
+ if (this.flushInterval !== null) {
259
+ clearInterval(this.flushInterval);
260
+ this.flushInterval = null;
261
+ }
262
+ this.addEvent({ action_type: "session_completed", value: 1, metadata: {} });
263
+ await this.flush();
264
+ }
265
+ };
266
+
267
+ // src/hooks/shared/utils.ts
268
+ var import_fs3 = __toESM(require("fs"));
269
+ function loadSessionId(source) {
270
+ try {
271
+ const file = sessionStateFile(source);
272
+ if (!import_fs3.default.existsSync(file)) return void 0;
273
+ const data = JSON.parse(import_fs3.default.readFileSync(file, "utf-8"));
274
+ return data.sessionId;
275
+ } catch {
276
+ return void 0;
277
+ }
278
+ }
279
+ function readStdin() {
280
+ return new Promise((resolve) => {
281
+ let input = "";
282
+ process.stdin.on("data", (chunk) => {
283
+ input += chunk;
284
+ });
285
+ process.stdin.on("end", () => resolve(input));
286
+ });
287
+ }
288
+ function normalizePayload(source, raw) {
289
+ if (source === "cursor") {
290
+ return { ...raw };
291
+ }
292
+ return raw;
293
+ }
294
+
295
+ // src/hooks/shared/stop.ts
296
+ async function handleStop(source) {
297
+ const input = await readStdin();
298
+ try {
299
+ const hookData = normalizePayload(source, JSON.parse(input));
300
+ const sessionId = hookData.session_id || loadSessionId(source);
301
+ const collector = new EventCollector(sessionId, source);
302
+ collector.addEvent({
303
+ action_type: "stop_response",
304
+ value: 1,
305
+ metadata: {
306
+ stop_reason: hookData.stop_reason || "end_turn"
307
+ }
308
+ });
309
+ await collector.flush();
310
+ } catch {
311
+ }
312
+ }
313
+
314
+ // src/hooks/cursor/stop.ts
315
+ handleStop("cursor");
316
+ //# sourceMappingURL=stop.js.map
@@ -0,0 +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/stop.ts","../../../src/hooks/cursor/stop.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 handleStop(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 a response turn ended. No content sent.\n collector.addEvent({\n action_type: 'stop_response',\n value: 1,\n metadata: {\n stop_reason: hookData.stop_reason || 'end_turn',\n },\n });\n\n await collector.flush();\n } catch {}\n}\n","import { handleStop } from '../shared/stop';\nhandleStop('cursor');\n"],"mappings":"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","names":["path","os","fs","import_fs","fs","uuidv4","import_fs","fs"]}
@@ -0,0 +1,316 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __create = Object.create;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __getProtoOf = Object.getPrototypeOf;
8
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __copyProps = (to, from, except, desc) => {
10
+ if (from && typeof from === "object" || typeof from === "function") {
11
+ for (let key of __getOwnPropNames(from))
12
+ if (!__hasOwnProp.call(to, key) && key !== except)
13
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
14
+ }
15
+ return to;
16
+ };
17
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
18
+ // If the importer is in node compatibility mode or this is not an ESM
19
+ // file that has been converted to a CommonJS file using a Babel-
20
+ // compatible transform (i.e. "__esModule" has not been set), then set
21
+ // "default" to the CommonJS "module.exports" for node compatibility.
22
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
23
+ mod
24
+ ));
25
+
26
+ // src/collector.ts
27
+ var import_uuid = require("uuid");
28
+
29
+ // src/config.ts
30
+ var import_fs = __toESM(require("fs"));
31
+ var import_path = __toESM(require("path"));
32
+ var import_os = __toESM(require("os"));
33
+ var CONFIG_DIR = import_path.default.join(import_os.default.homedir(), ".hackersbaby");
34
+ var CONFIG_FILE = import_path.default.join(CONFIG_DIR, "config.json");
35
+ var QUEUE_FILE = import_path.default.join(CONFIG_DIR, "queue.jsonl");
36
+ function sessionStateFile(source) {
37
+ return import_path.default.join(CONFIG_DIR, `active-session-${source}.json`);
38
+ }
39
+ function ensureConfigDir() {
40
+ if (!import_fs.default.existsSync(CONFIG_DIR)) {
41
+ import_fs.default.mkdirSync(CONFIG_DIR, { recursive: true });
42
+ }
43
+ }
44
+ function loadConfig() {
45
+ try {
46
+ if (!import_fs.default.existsSync(CONFIG_FILE)) return null;
47
+ const raw = import_fs.default.readFileSync(CONFIG_FILE, "utf-8");
48
+ return JSON.parse(raw);
49
+ } catch {
50
+ return null;
51
+ }
52
+ }
53
+ function saveConfig(config) {
54
+ ensureConfigDir();
55
+ import_fs.default.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2), "utf-8");
56
+ }
57
+
58
+ // src/api-client.ts
59
+ var APIClient = class {
60
+ config = loadConfig();
61
+ get headers() {
62
+ return {
63
+ "Content-Type": "application/json",
64
+ Authorization: `Bearer ${this.config?.token || ""}`
65
+ };
66
+ }
67
+ async sendBatch(batch) {
68
+ try {
69
+ const server = this.config?.server || "https://hackers.baby";
70
+ const res = await fetch(`${server}/api/scores/batch`, {
71
+ method: "POST",
72
+ headers: this.headers,
73
+ body: JSON.stringify(batch)
74
+ });
75
+ return res.ok;
76
+ } catch {
77
+ return false;
78
+ }
79
+ }
80
+ async refreshTokenIfNeeded() {
81
+ if (!this.config) return;
82
+ try {
83
+ const parts = this.config.token.split(".");
84
+ if (parts.length !== 3) return;
85
+ const payload = JSON.parse(Buffer.from(parts[1], "base64").toString("utf-8"));
86
+ const expMs = payload.exp * 1e3;
87
+ const oneDayMs = 24 * 60 * 60 * 1e3;
88
+ if (Date.now() + oneDayMs < expMs) return;
89
+ const res = await fetch(`${this.config.server}/api/auth/cli-token/refresh`, {
90
+ method: "POST",
91
+ headers: this.headers,
92
+ body: JSON.stringify({ refresh_token: this.config.refresh_token })
93
+ });
94
+ if (res.ok) {
95
+ const data = await res.json();
96
+ if (data.token) {
97
+ this.config.token = data.token;
98
+ if (data.refresh_token) this.config.refresh_token = data.refresh_token;
99
+ saveConfig(this.config);
100
+ }
101
+ }
102
+ } catch {
103
+ }
104
+ }
105
+ async getStatus() {
106
+ const server = this.config?.server || "https://hackers.baby";
107
+ const res = await fetch(`${server}/api/users/me`, { headers: this.headers });
108
+ if (!res.ok) throw new Error(`getStatus failed: ${res.status}`);
109
+ return res.json();
110
+ }
111
+ async getRank() {
112
+ const server = this.config?.server || "https://hackers.baby";
113
+ const res = await fetch(`${server}/api/users/me/rank`, { headers: this.headers });
114
+ if (!res.ok) throw new Error(`getRank failed: ${res.status}`);
115
+ return res.json();
116
+ }
117
+ async getLeaderboard() {
118
+ const server = this.config?.server || "https://hackers.baby";
119
+ const res = await fetch(`${server}/api/leaderboard/global?limit=10`, { headers: this.headers });
120
+ if (!res.ok) throw new Error(`getLeaderboard failed: ${res.status}`);
121
+ return res.json();
122
+ }
123
+ async getPublicStats() {
124
+ try {
125
+ const server = this.config?.server || "https://hackers.baby";
126
+ const res = await fetch(`${server}/api/stats/public`);
127
+ if (!res.ok) return null;
128
+ return res.json();
129
+ } catch {
130
+ return null;
131
+ }
132
+ }
133
+ async getNotifications() {
134
+ try {
135
+ const server = this.config?.server || "https://hackers.baby";
136
+ const res = await fetch(`${server}/api/users/me/notifications`, { headers: this.headers });
137
+ if (!res.ok) return null;
138
+ return res.json();
139
+ } catch {
140
+ return null;
141
+ }
142
+ }
143
+ async claimReferral(code) {
144
+ try {
145
+ const server = this.config?.server || "https://hackers.baby";
146
+ const res = await fetch(`${server}/api/referral/claim`, {
147
+ method: "POST",
148
+ headers: this.headers,
149
+ body: JSON.stringify({ referral_code: code })
150
+ });
151
+ if (!res.ok) return null;
152
+ return res.json();
153
+ } catch {
154
+ return null;
155
+ }
156
+ }
157
+ async getReferralStats() {
158
+ try {
159
+ const server = this.config?.server || "https://hackers.baby";
160
+ const res = await fetch(`${server}/api/referral/stats`, { headers: this.headers });
161
+ if (!res.ok) return null;
162
+ return res.json();
163
+ } catch {
164
+ return null;
165
+ }
166
+ }
167
+ };
168
+
169
+ // src/queue.ts
170
+ var import_fs2 = __toESM(require("fs"));
171
+ var MAX_QUEUE_SIZE = 1e4;
172
+ var MAX_AGE_MS = 24 * 60 * 60 * 1e3;
173
+ function enqueueEvents(events) {
174
+ ensureConfigDir();
175
+ const lines = events.map((e) => JSON.stringify(e)).join("\n");
176
+ import_fs2.default.appendFileSync(QUEUE_FILE, lines + "\n", "utf-8");
177
+ try {
178
+ const content = import_fs2.default.readFileSync(QUEUE_FILE, "utf-8");
179
+ const allLines = content.split("\n").filter((l) => l.trim() !== "");
180
+ if (allLines.length > MAX_QUEUE_SIZE) {
181
+ const trimmed = allLines.slice(allLines.length - MAX_QUEUE_SIZE);
182
+ import_fs2.default.writeFileSync(QUEUE_FILE, trimmed.join("\n") + "\n", "utf-8");
183
+ }
184
+ } catch {
185
+ }
186
+ }
187
+ function drainQueue() {
188
+ try {
189
+ if (!import_fs2.default.existsSync(QUEUE_FILE)) return [];
190
+ const content = import_fs2.default.readFileSync(QUEUE_FILE, "utf-8");
191
+ import_fs2.default.writeFileSync(QUEUE_FILE, "", "utf-8");
192
+ const now = Date.now();
193
+ const events = [];
194
+ for (const line of content.split("\n")) {
195
+ if (!line.trim()) continue;
196
+ try {
197
+ const event = JSON.parse(line);
198
+ const age = now - new Date(event.timestamp).getTime();
199
+ if (age <= MAX_AGE_MS) {
200
+ events.push(event);
201
+ }
202
+ } catch {
203
+ }
204
+ }
205
+ return events;
206
+ } catch {
207
+ return [];
208
+ }
209
+ }
210
+
211
+ // src/collector.ts
212
+ var EventCollector = class {
213
+ sessionId;
214
+ buffer = [];
215
+ flushInterval = null;
216
+ client;
217
+ source;
218
+ constructor(sessionId, source) {
219
+ this.sessionId = sessionId ?? (0, import_uuid.v4)();
220
+ this.client = new APIClient();
221
+ this.source = source;
222
+ }
223
+ addEvent(event) {
224
+ const metadata = this.source ? { ...event.metadata, source: this.source } : event.metadata;
225
+ this.buffer.push({
226
+ ...event,
227
+ metadata,
228
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
229
+ });
230
+ }
231
+ async start() {
232
+ await this.client.refreshTokenIfNeeded();
233
+ const queued = drainQueue();
234
+ if (queued.length > 0) {
235
+ const batch = { session_id: this.sessionId, events: queued };
236
+ const ok = await this.client.sendBatch(batch);
237
+ if (!ok) {
238
+ enqueueEvents(queued);
239
+ }
240
+ }
241
+ const config = loadConfig();
242
+ const intervalMs = config?.preferences?.batch_interval_ms ?? 1e4;
243
+ this.flushInterval = setInterval(() => {
244
+ this.flush().catch(() => {
245
+ });
246
+ }, intervalMs);
247
+ }
248
+ async flush() {
249
+ if (this.buffer.length === 0) return;
250
+ const events = this.buffer.splice(0);
251
+ const batch = { session_id: this.sessionId, events };
252
+ const ok = await this.client.sendBatch(batch);
253
+ if (!ok) {
254
+ enqueueEvents(events);
255
+ }
256
+ }
257
+ async stop() {
258
+ if (this.flushInterval !== null) {
259
+ clearInterval(this.flushInterval);
260
+ this.flushInterval = null;
261
+ }
262
+ this.addEvent({ action_type: "session_completed", value: 1, metadata: {} });
263
+ await this.flush();
264
+ }
265
+ };
266
+
267
+ // src/hooks/shared/utils.ts
268
+ var import_fs3 = __toESM(require("fs"));
269
+ function loadSessionId(source) {
270
+ try {
271
+ const file = sessionStateFile(source);
272
+ if (!import_fs3.default.existsSync(file)) return void 0;
273
+ const data = JSON.parse(import_fs3.default.readFileSync(file, "utf-8"));
274
+ return data.sessionId;
275
+ } catch {
276
+ return void 0;
277
+ }
278
+ }
279
+ function readStdin() {
280
+ return new Promise((resolve) => {
281
+ let input = "";
282
+ process.stdin.on("data", (chunk) => {
283
+ input += chunk;
284
+ });
285
+ process.stdin.on("end", () => resolve(input));
286
+ });
287
+ }
288
+ function normalizePayload(source, raw) {
289
+ if (source === "cursor") {
290
+ return { ...raw };
291
+ }
292
+ return raw;
293
+ }
294
+
295
+ // src/hooks/shared/subagent.ts
296
+ async function handleSubagent(source) {
297
+ const input = await readStdin();
298
+ try {
299
+ const hookData = normalizePayload(source, JSON.parse(input));
300
+ const sessionId = hookData.session_id || loadSessionId(source);
301
+ const collector = new EventCollector(sessionId, source);
302
+ collector.addEvent({
303
+ action_type: "subagent_spawned",
304
+ value: 1,
305
+ metadata: {
306
+ agent_type: hookData.agent_type || "unknown"
307
+ }
308
+ });
309
+ await collector.flush();
310
+ } catch {
311
+ }
312
+ }
313
+
314
+ // src/hooks/cursor/subagent.ts
315
+ handleSubagent("cursor");
316
+ //# sourceMappingURL=subagent.js.map