@hackersbaby/plugin 0.5.0 → 0.5.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.
@@ -6,9 +6,16 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
6
  var __getOwnPropNames = Object.getOwnPropertyNames;
7
7
  var __getProtoOf = Object.getPrototypeOf;
8
8
  var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __esm = (fn, res) => function __init() {
10
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
11
+ };
9
12
  var __commonJS = (cb, mod) => function __require() {
10
13
  return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
11
14
  };
15
+ var __export = (target, all) => {
16
+ for (var name in all)
17
+ __defProp(target, name, { get: all[name], enumerable: true });
18
+ };
12
19
  var __copyProps = (to, from, except, desc) => {
13
20
  if (from && typeof from === "object" || typeof from === "function") {
14
21
  for (let key of __getOwnPropNames(from))
@@ -25,6 +32,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
25
32
  isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
33
  mod
27
34
  ));
35
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
28
36
 
29
37
  // ../shared/dist/types.js
30
38
  var require_types = __commonJS({
@@ -164,19 +172,7 @@ var require_dist = __commonJS({
164
172
  }
165
173
  });
166
174
 
167
- // src/hooks/shared/tool-call.ts
168
- var import_shared2 = __toESM(require_dist());
169
-
170
- // src/collector.ts
171
- var import_uuid = require("uuid");
172
-
173
175
  // src/config.ts
174
- var import_fs = __toESM(require("fs"));
175
- var import_path = __toESM(require("path"));
176
- var import_os = __toESM(require("os"));
177
- var CONFIG_DIR = import_path.default.join(import_os.default.homedir(), ".hackersbaby");
178
- var CONFIG_FILE = import_path.default.join(CONFIG_DIR, "config.json");
179
- var QUEUE_FILE = import_path.default.join(CONFIG_DIR, "queue.jsonl");
180
176
  function sessionStateFile(source) {
181
177
  return import_path.default.join(CONFIG_DIR, `active-session-${source}.json`);
182
178
  }
@@ -198,154 +194,188 @@ function saveConfig(config) {
198
194
  ensureConfigDir();
199
195
  import_fs.default.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2), "utf-8");
200
196
  }
197
+ var import_fs, import_path, import_os, CONFIG_DIR, CONFIG_FILE, QUEUE_FILE;
198
+ var init_config = __esm({
199
+ "src/config.ts"() {
200
+ "use strict";
201
+ import_fs = __toESM(require("fs"));
202
+ import_path = __toESM(require("path"));
203
+ import_os = __toESM(require("os"));
204
+ CONFIG_DIR = import_path.default.join(import_os.default.homedir(), ".hackersbaby");
205
+ CONFIG_FILE = import_path.default.join(CONFIG_DIR, "config.json");
206
+ QUEUE_FILE = import_path.default.join(CONFIG_DIR, "queue.jsonl");
207
+ }
208
+ });
201
209
 
202
210
  // src/api-client.ts
203
- var import_shared = __toESM(require_dist());
204
- var APIClient = class {
205
- config = loadConfig();
206
- sessionSecret = null;
207
- chainHash = "0000000000000000";
208
- get headers() {
209
- return {
210
- "Content-Type": "application/json",
211
- Authorization: `Bearer ${this.config?.token || ""}`
212
- };
213
- }
214
- /** Register session with server and get cryptographic secret */
215
- async startSession(sessionId) {
216
- try {
217
- const server = this.config?.server || "https://hackers.baby";
218
- const res = await fetch(`${server}/api/sessions/start`, {
219
- method: "POST",
220
- headers: this.headers,
221
- body: JSON.stringify({ session_id: sessionId })
222
- });
223
- if (res.ok) {
224
- const data = await res.json();
225
- this.sessionSecret = data.session_secret;
226
- this.chainHash = data.initial_chain_hash;
227
- return true;
211
+ var import_shared, APIClient;
212
+ var init_api_client = __esm({
213
+ "src/api-client.ts"() {
214
+ "use strict";
215
+ init_config();
216
+ import_shared = __toESM(require_dist());
217
+ APIClient = class {
218
+ config = loadConfig();
219
+ sessionSecret = null;
220
+ chainHash = "0000000000000000";
221
+ onChainUpdate = null;
222
+ /** Set callback to persist chain state between hook processes */
223
+ setChainUpdateCallback(cb) {
224
+ this.onChainUpdate = cb;
228
225
  }
229
- return false;
230
- } catch {
231
- return false;
232
- }
233
- }
234
- async sendBatch(batch) {
235
- try {
236
- const server = this.config?.server || "https://hackers.baby";
237
- if (this.sessionSecret) {
238
- const prevChainHash = this.chainHash;
239
- const newChainHash = (0, import_shared.computeChainHash)(batch.events, prevChainHash);
240
- const signature = (0, import_shared.signBatch)(batch.session_id, batch.events, newChainHash, this.sessionSecret);
241
- batch.signature = signature;
242
- batch.chain_hash = newChainHash;
243
- batch.prev_chain_hash = prevChainHash;
244
- this.chainHash = newChainHash;
226
+ /** Restore crypto state from a previous session */
227
+ restoreCryptoState(secret, chainHash) {
228
+ this.sessionSecret = secret;
229
+ this.chainHash = chainHash;
245
230
  }
246
- const res = await fetch(`${server}/api/scores/batch`, {
247
- method: "POST",
248
- headers: this.headers,
249
- body: JSON.stringify(batch)
250
- });
251
- return res.ok;
252
- } catch {
253
- return false;
254
- }
255
- }
256
- async refreshTokenIfNeeded() {
257
- if (!this.config) return;
258
- try {
259
- const parts = this.config.token.split(".");
260
- if (parts.length !== 3) return;
261
- const payload = JSON.parse(Buffer.from(parts[1], "base64").toString("utf-8"));
262
- const expMs = payload.exp * 1e3;
263
- const oneDayMs = 24 * 60 * 60 * 1e3;
264
- if (Date.now() + oneDayMs < expMs) return;
265
- const res = await fetch(`${this.config.server}/api/auth/cli-token/refresh`, {
266
- method: "POST",
267
- headers: this.headers,
268
- body: JSON.stringify({ refresh_token: this.config.refresh_token })
269
- });
270
- if (res.ok) {
271
- const data = await res.json();
272
- if (data.token) {
273
- this.config.token = data.token;
274
- if (data.refresh_token) this.config.refresh_token = data.refresh_token;
275
- saveConfig(this.config);
231
+ get headers() {
232
+ return {
233
+ "Content-Type": "application/json",
234
+ Authorization: `Bearer ${this.config?.token || ""}`
235
+ };
236
+ }
237
+ /** Get current crypto state for persistence */
238
+ getCryptoState() {
239
+ if (!this.sessionSecret) return null;
240
+ return { secret: this.sessionSecret, chainHash: this.chainHash };
241
+ }
242
+ /** Register session with server and get cryptographic secret */
243
+ async startSession(sessionId) {
244
+ try {
245
+ const server = this.config?.server || "https://hackers.baby";
246
+ const res = await fetch(`${server}/api/sessions/start`, {
247
+ method: "POST",
248
+ headers: this.headers,
249
+ body: JSON.stringify({ session_id: sessionId })
250
+ });
251
+ if (res.ok) {
252
+ const data = await res.json();
253
+ this.sessionSecret = data.session_secret;
254
+ this.chainHash = data.initial_chain_hash;
255
+ return true;
256
+ }
257
+ return false;
258
+ } catch {
259
+ return false;
276
260
  }
277
261
  }
278
- } catch {
279
- }
280
- }
281
- async getStatus() {
282
- const server = this.config?.server || "https://hackers.baby";
283
- const res = await fetch(`${server}/api/users/me`, { headers: this.headers });
284
- if (!res.ok) throw new Error(`getStatus failed: ${res.status}`);
285
- return res.json();
286
- }
287
- async getRank() {
288
- const server = this.config?.server || "https://hackers.baby";
289
- const res = await fetch(`${server}/api/users/me/rank`, { headers: this.headers });
290
- if (!res.ok) throw new Error(`getRank failed: ${res.status}`);
291
- return res.json();
292
- }
293
- async getLeaderboard() {
294
- const server = this.config?.server || "https://hackers.baby";
295
- const res = await fetch(`${server}/api/leaderboard/global?limit=10`, { headers: this.headers });
296
- if (!res.ok) throw new Error(`getLeaderboard failed: ${res.status}`);
297
- return res.json();
298
- }
299
- async getPublicStats() {
300
- try {
301
- const server = this.config?.server || "https://hackers.baby";
302
- const res = await fetch(`${server}/api/stats/public`);
303
- if (!res.ok) return null;
304
- return res.json();
305
- } catch {
306
- return null;
307
- }
308
- }
309
- async getNotifications() {
310
- try {
311
- const server = this.config?.server || "https://hackers.baby";
312
- const res = await fetch(`${server}/api/users/me/notifications`, { headers: this.headers });
313
- if (!res.ok) return null;
314
- return res.json();
315
- } catch {
316
- return null;
317
- }
318
- }
319
- async claimReferral(code) {
320
- try {
321
- const server = this.config?.server || "https://hackers.baby";
322
- const res = await fetch(`${server}/api/referral/claim`, {
323
- method: "POST",
324
- headers: this.headers,
325
- body: JSON.stringify({ referral_code: code })
326
- });
327
- if (!res.ok) return null;
328
- return res.json();
329
- } catch {
330
- return null;
331
- }
332
- }
333
- async getReferralStats() {
334
- try {
335
- const server = this.config?.server || "https://hackers.baby";
336
- const res = await fetch(`${server}/api/referral/stats`, { headers: this.headers });
337
- if (!res.ok) return null;
338
- return res.json();
339
- } catch {
340
- return null;
341
- }
262
+ async sendBatch(batch) {
263
+ try {
264
+ const server = this.config?.server || "https://hackers.baby";
265
+ if (this.sessionSecret) {
266
+ const prevChainHash = this.chainHash;
267
+ const newChainHash = (0, import_shared.computeChainHash)(batch.events, prevChainHash);
268
+ const signature = (0, import_shared.signBatch)(batch.session_id, batch.events, newChainHash, this.sessionSecret);
269
+ batch.signature = signature;
270
+ batch.chain_hash = newChainHash;
271
+ batch.prev_chain_hash = prevChainHash;
272
+ this.chainHash = newChainHash;
273
+ if (this.onChainUpdate) {
274
+ this.onChainUpdate(this.chainHash, this.sessionSecret);
275
+ }
276
+ }
277
+ const res = await fetch(`${server}/api/scores/batch`, {
278
+ method: "POST",
279
+ headers: this.headers,
280
+ body: JSON.stringify(batch)
281
+ });
282
+ return res.ok;
283
+ } catch {
284
+ return false;
285
+ }
286
+ }
287
+ async refreshTokenIfNeeded() {
288
+ if (!this.config) return;
289
+ try {
290
+ const parts = this.config.token.split(".");
291
+ if (parts.length !== 3) return;
292
+ const payload = JSON.parse(Buffer.from(parts[1], "base64").toString("utf-8"));
293
+ const expMs = payload.exp * 1e3;
294
+ const oneDayMs = 24 * 60 * 60 * 1e3;
295
+ if (Date.now() + oneDayMs < expMs) return;
296
+ const res = await fetch(`${this.config.server}/api/auth/cli-token/refresh`, {
297
+ method: "POST",
298
+ headers: this.headers,
299
+ body: JSON.stringify({ refresh_token: this.config.refresh_token })
300
+ });
301
+ if (res.ok) {
302
+ const data = await res.json();
303
+ if (data.token) {
304
+ this.config.token = data.token;
305
+ if (data.refresh_token) this.config.refresh_token = data.refresh_token;
306
+ saveConfig(this.config);
307
+ }
308
+ }
309
+ } catch {
310
+ }
311
+ }
312
+ async getStatus() {
313
+ const server = this.config?.server || "https://hackers.baby";
314
+ const res = await fetch(`${server}/api/users/me`, { headers: this.headers });
315
+ if (!res.ok) throw new Error(`getStatus failed: ${res.status}`);
316
+ return res.json();
317
+ }
318
+ async getRank() {
319
+ const server = this.config?.server || "https://hackers.baby";
320
+ const res = await fetch(`${server}/api/users/me/rank`, { headers: this.headers });
321
+ if (!res.ok) throw new Error(`getRank failed: ${res.status}`);
322
+ return res.json();
323
+ }
324
+ async getLeaderboard() {
325
+ const server = this.config?.server || "https://hackers.baby";
326
+ const res = await fetch(`${server}/api/leaderboard/global?limit=10`, { headers: this.headers });
327
+ if (!res.ok) throw new Error(`getLeaderboard failed: ${res.status}`);
328
+ return res.json();
329
+ }
330
+ async getPublicStats() {
331
+ try {
332
+ const server = this.config?.server || "https://hackers.baby";
333
+ const res = await fetch(`${server}/api/stats/public`);
334
+ if (!res.ok) return null;
335
+ return res.json();
336
+ } catch {
337
+ return null;
338
+ }
339
+ }
340
+ async getNotifications() {
341
+ try {
342
+ const server = this.config?.server || "https://hackers.baby";
343
+ const res = await fetch(`${server}/api/users/me/notifications`, { headers: this.headers });
344
+ if (!res.ok) return null;
345
+ return res.json();
346
+ } catch {
347
+ return null;
348
+ }
349
+ }
350
+ async claimReferral(code) {
351
+ try {
352
+ const server = this.config?.server || "https://hackers.baby";
353
+ const res = await fetch(`${server}/api/referral/claim`, {
354
+ method: "POST",
355
+ headers: this.headers,
356
+ body: JSON.stringify({ referral_code: code })
357
+ });
358
+ if (!res.ok) return null;
359
+ return res.json();
360
+ } catch {
361
+ return null;
362
+ }
363
+ }
364
+ async getReferralStats() {
365
+ try {
366
+ const server = this.config?.server || "https://hackers.baby";
367
+ const res = await fetch(`${server}/api/referral/stats`, { headers: this.headers });
368
+ if (!res.ok) return null;
369
+ return res.json();
370
+ } catch {
371
+ return null;
372
+ }
373
+ }
374
+ };
342
375
  }
343
- };
376
+ });
344
377
 
345
378
  // src/queue.ts
346
- var import_fs2 = __toESM(require("fs"));
347
- var MAX_QUEUE_SIZE = 1e4;
348
- var MAX_AGE_MS = 24 * 60 * 60 * 1e3;
349
379
  function enqueueEvents(events) {
350
380
  ensureConfigDir();
351
381
  const lines = events.map((e) => JSON.stringify(e)).join("\n");
@@ -383,76 +413,132 @@ function drainQueue() {
383
413
  return [];
384
414
  }
385
415
  }
416
+ var import_fs2, MAX_QUEUE_SIZE, MAX_AGE_MS;
417
+ var init_queue = __esm({
418
+ "src/queue.ts"() {
419
+ "use strict";
420
+ import_fs2 = __toESM(require("fs"));
421
+ init_config();
422
+ MAX_QUEUE_SIZE = 1e4;
423
+ MAX_AGE_MS = 24 * 60 * 60 * 1e3;
424
+ }
425
+ });
386
426
 
387
427
  // src/collector.ts
388
- var EventCollector = class {
389
- sessionId;
390
- buffer = [];
391
- flushInterval = null;
392
- client;
393
- source;
394
- constructor(sessionId, source) {
395
- this.sessionId = sessionId ?? (0, import_uuid.v4)();
396
- this.client = new APIClient();
397
- this.source = source;
398
- }
399
- addEvent(event) {
400
- const metadata = this.source ? { ...event.metadata, source: this.source } : event.metadata;
401
- this.buffer.push({
402
- ...event,
403
- metadata,
404
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
405
- });
406
- }
407
- async start() {
408
- await this.client.refreshTokenIfNeeded();
409
- await this.client.startSession(this.sessionId);
410
- const queued = drainQueue();
411
- if (queued.length > 0) {
412
- const batch = { session_id: this.sessionId, events: queued };
413
- const ok = await this.client.sendBatch(batch);
414
- if (!ok) {
415
- enqueueEvents(queued);
428
+ var collector_exports = {};
429
+ __export(collector_exports, {
430
+ EventCollector: () => EventCollector
431
+ });
432
+ var import_uuid, EventCollector;
433
+ var init_collector = __esm({
434
+ "src/collector.ts"() {
435
+ "use strict";
436
+ import_uuid = require("uuid");
437
+ init_api_client();
438
+ init_queue();
439
+ init_config();
440
+ EventCollector = class {
441
+ sessionId;
442
+ buffer = [];
443
+ flushInterval = null;
444
+ client;
445
+ source;
446
+ constructor(sessionId, source) {
447
+ this.sessionId = sessionId ?? (0, import_uuid.v4)();
448
+ this.client = new APIClient();
449
+ this.source = source;
416
450
  }
417
- }
418
- const config = loadConfig();
419
- const intervalMs = config?.preferences?.batch_interval_ms ?? 1e4;
420
- this.flushInterval = setInterval(() => {
421
- this.flush().catch(() => {
422
- });
423
- }, intervalMs);
424
- }
425
- async flush() {
426
- if (this.buffer.length === 0) return;
427
- const events = this.buffer.splice(0);
428
- const batch = { session_id: this.sessionId, events };
429
- const ok = await this.client.sendBatch(batch);
430
- if (!ok) {
431
- enqueueEvents(events);
432
- }
433
- }
434
- async stop() {
435
- if (this.flushInterval !== null) {
436
- clearInterval(this.flushInterval);
437
- this.flushInterval = null;
438
- }
439
- this.addEvent({ action_type: "session_completed", value: 1, metadata: {} });
440
- await this.flush();
451
+ /** Restore crypto state from persisted session data */
452
+ restoreCrypto(secret, chainHash) {
453
+ this.client.restoreCryptoState(secret, chainHash);
454
+ }
455
+ /** Set callback to persist crypto state after each batch */
456
+ setCryptoUpdateCallback(cb) {
457
+ this.client.setChainUpdateCallback(cb);
458
+ }
459
+ addEvent(event) {
460
+ const metadata = this.source ? { ...event.metadata, source: this.source } : event.metadata;
461
+ this.buffer.push({
462
+ ...event,
463
+ metadata,
464
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
465
+ });
466
+ }
467
+ async start() {
468
+ await this.client.refreshTokenIfNeeded();
469
+ await this.client.startSession(this.sessionId);
470
+ const queued = drainQueue();
471
+ if (queued.length > 0) {
472
+ const batch = { session_id: this.sessionId, events: queued };
473
+ const ok = await this.client.sendBatch(batch);
474
+ if (!ok) {
475
+ enqueueEvents(queued);
476
+ }
477
+ }
478
+ const config = loadConfig();
479
+ const intervalMs = config?.preferences?.batch_interval_ms ?? 1e4;
480
+ this.flushInterval = setInterval(() => {
481
+ this.flush().catch(() => {
482
+ });
483
+ }, intervalMs);
484
+ }
485
+ async flush() {
486
+ if (this.buffer.length === 0) return;
487
+ const events = this.buffer.splice(0);
488
+ const batch = { session_id: this.sessionId, events };
489
+ const ok = await this.client.sendBatch(batch);
490
+ if (!ok) {
491
+ enqueueEvents(events);
492
+ }
493
+ }
494
+ async stop() {
495
+ if (this.flushInterval !== null) {
496
+ clearInterval(this.flushInterval);
497
+ this.flushInterval = null;
498
+ }
499
+ this.addEvent({ action_type: "session_completed", value: 1, metadata: {} });
500
+ await this.flush();
501
+ }
502
+ };
441
503
  }
442
- };
504
+ });
505
+
506
+ // src/hooks/shared/tool-call.ts
507
+ var import_shared2 = __toESM(require_dist());
443
508
 
444
509
  // src/hooks/shared/utils.ts
445
510
  var import_fs3 = __toESM(require("fs"));
446
- function loadSessionId(source) {
511
+ init_config();
512
+ function loadSessionState(source) {
447
513
  try {
448
514
  const file = sessionStateFile(source);
449
515
  if (!import_fs3.default.existsSync(file)) return void 0;
450
- const data = JSON.parse(import_fs3.default.readFileSync(file, "utf-8"));
451
- return data.sessionId;
516
+ return JSON.parse(import_fs3.default.readFileSync(file, "utf-8"));
452
517
  } catch {
453
518
  return void 0;
454
519
  }
455
520
  }
521
+ function loadSessionId(source) {
522
+ return loadSessionState(source)?.sessionId;
523
+ }
524
+ function updateSessionState(source, updates) {
525
+ const state = loadSessionState(source);
526
+ if (!state) return;
527
+ const file = sessionStateFile(source);
528
+ import_fs3.default.writeFileSync(file, JSON.stringify({ ...state, ...updates }));
529
+ }
530
+ function createCollector(sessionId, source) {
531
+ const { EventCollector: EventCollector2 } = (init_collector(), __toCommonJS(collector_exports));
532
+ const collector = new EventCollector2(sessionId, source);
533
+ const state = loadSessionState(source);
534
+ if (state?.sessionSecret && state?.chainHash && state.sessionId === sessionId) {
535
+ collector.restoreCrypto(state.sessionSecret, state.chainHash);
536
+ collector.setCryptoUpdateCallback((chainHash, secret) => {
537
+ updateSessionState(source, { chainHash, sessionSecret: secret });
538
+ });
539
+ }
540
+ return collector;
541
+ }
456
542
  function readStdin() {
457
543
  return new Promise((resolve) => {
458
544
  let input = "";
@@ -484,7 +570,7 @@ async function handleToolCall(source) {
484
570
  const toolName = hookData.tool_name || "unknown";
485
571
  const toolInput = hookData.tool_input || {};
486
572
  const sessionId = hookData.session_id || loadSessionId(detected);
487
- const collector = new EventCollector(sessionId, detected);
573
+ const collector = createCollector(sessionId, detected);
488
574
  collector.addEvent({ action_type: "tool_call", value: 1, metadata: { tool_name: toolName } });
489
575
  if (hookData.input_tokens) {
490
576
  collector.addEvent({ action_type: "token_input", value: hookData.input_tokens, metadata: {} });