@hackersbaby/plugin 0.4.3 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +174 -1
- package/dist/cli.js.map +1 -1
- package/dist/hooks/compact.js +174 -0
- package/dist/hooks/compact.js.map +1 -1
- package/dist/hooks/cursor/compact.js +174 -0
- package/dist/hooks/cursor/compact.js.map +1 -1
- package/dist/hooks/cursor/prompt-submit.js +174 -0
- package/dist/hooks/cursor/prompt-submit.js.map +1 -1
- package/dist/hooks/cursor/session-end.js +174 -0
- package/dist/hooks/cursor/session-end.js.map +1 -1
- package/dist/hooks/cursor/session-start.js +174 -0
- package/dist/hooks/cursor/session-start.js.map +1 -1
- package/dist/hooks/cursor/stop.js +174 -0
- package/dist/hooks/cursor/stop.js.map +1 -1
- package/dist/hooks/cursor/subagent.js +174 -0
- package/dist/hooks/cursor/subagent.js.map +1 -1
- package/dist/hooks/cursor/tool-call.js +75 -2
- package/dist/hooks/cursor/tool-call.js.map +1 -1
- package/dist/hooks/prompt-submit.js +174 -0
- package/dist/hooks/prompt-submit.js.map +1 -1
- package/dist/hooks/session-end.js +174 -0
- package/dist/hooks/session-end.js.map +1 -1
- package/dist/hooks/session-start.js +174 -0
- package/dist/hooks/session-start.js.map +1 -1
- package/dist/hooks/stop.js +174 -0
- package/dist/hooks/stop.js.map +1 -1
- package/dist/hooks/subagent.js +174 -0
- package/dist/hooks/subagent.js.map +1 -1
- package/dist/hooks/tool-call.js +75 -2
- package/dist/hooks/tool-call.js.map +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.js +174 -0
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -6,6 +6,9 @@ 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 __commonJS = (cb, mod) => function __require() {
|
|
10
|
+
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
|
11
|
+
};
|
|
9
12
|
var __copyProps = (to, from, except, desc) => {
|
|
10
13
|
if (from && typeof from === "object" || typeof from === "function") {
|
|
11
14
|
for (let key of __getOwnPropNames(from))
|
|
@@ -23,6 +26,144 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
23
26
|
mod
|
|
24
27
|
));
|
|
25
28
|
|
|
29
|
+
// ../shared/dist/types.js
|
|
30
|
+
var require_types = __commonJS({
|
|
31
|
+
"../shared/dist/types.js"(exports2) {
|
|
32
|
+
"use strict";
|
|
33
|
+
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
// ../shared/dist/scoring.js
|
|
38
|
+
var require_scoring = __commonJS({
|
|
39
|
+
"../shared/dist/scoring.js"(exports2) {
|
|
40
|
+
"use strict";
|
|
41
|
+
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
42
|
+
exports2.DEPLOY_PATTERNS = exports2.BASE_POINTS = exports2.MIN_SESSION_TOKENS = exports2.TOOL_CALLS_PER_HOUR_CAP = exports2.SESSION_BASE_POINT_CAP = void 0;
|
|
43
|
+
exports2.getBasePoints = getBasePoints;
|
|
44
|
+
exports2.calculateMultiplier = calculateMultiplier;
|
|
45
|
+
exports2.calculateSessionScore = calculateSessionScore;
|
|
46
|
+
exports2.isDeployCommand = isDeployCommand;
|
|
47
|
+
exports2.SESSION_BASE_POINT_CAP = 2e4;
|
|
48
|
+
exports2.TOOL_CALLS_PER_HOUR_CAP = 4e3;
|
|
49
|
+
exports2.MIN_SESSION_TOKENS = 2e3;
|
|
50
|
+
exports2.BASE_POINTS = {
|
|
51
|
+
token_input: { perUnit: 1, unitSize: 1e3 },
|
|
52
|
+
token_output: { perUnit: 2, unitSize: 1e3 },
|
|
53
|
+
tool_call: { perUnit: 5, unitSize: 1 },
|
|
54
|
+
file_created: { perUnit: 15, unitSize: 1 },
|
|
55
|
+
file_edited: { perUnit: 10, unitSize: 1 },
|
|
56
|
+
commit: { perUnit: 50, unitSize: 1 },
|
|
57
|
+
session_completed: { perUnit: 100, unitSize: 1 },
|
|
58
|
+
deployment: { perUnit: 200, unitSize: 1 },
|
|
59
|
+
prompt_submitted: { perUnit: 15, unitSize: 1 },
|
|
60
|
+
subagent_spawned: { perUnit: 40, unitSize: 1 },
|
|
61
|
+
context_compacted: { perUnit: 30, unitSize: 1 },
|
|
62
|
+
stop_response: { perUnit: 10, unitSize: 1 }
|
|
63
|
+
};
|
|
64
|
+
exports2.DEPLOY_PATTERNS = [
|
|
65
|
+
/^vercel\s+(--prod|deploy)/,
|
|
66
|
+
/^netlify\s+deploy/,
|
|
67
|
+
/^fly\s+deploy/,
|
|
68
|
+
/^railway\s+up/,
|
|
69
|
+
/^git\s+push\s+\S+\s+(main|master|production)/,
|
|
70
|
+
/^(npm|yarn|pnpm)\s+run\s+deploy/,
|
|
71
|
+
/^(yarn|pnpm)\s+deploy/
|
|
72
|
+
];
|
|
73
|
+
function getBasePoints(actionType, value) {
|
|
74
|
+
const config = exports2.BASE_POINTS[actionType];
|
|
75
|
+
return Math.floor(value / config.unitSize) * config.perUnit;
|
|
76
|
+
}
|
|
77
|
+
function calculateMultiplier(input) {
|
|
78
|
+
let streak = 1;
|
|
79
|
+
if (input.streak_days >= 30)
|
|
80
|
+
streak = 3;
|
|
81
|
+
else if (input.streak_days >= 7)
|
|
82
|
+
streak = 2;
|
|
83
|
+
else if (input.streak_days >= 3)
|
|
84
|
+
streak = 1.5;
|
|
85
|
+
const quality = input.commit_quality ? 1.5 : 1;
|
|
86
|
+
const diversity = input.language_count >= 3 ? 1.2 : 1;
|
|
87
|
+
return streak * quality * diversity;
|
|
88
|
+
}
|
|
89
|
+
function calculateSessionScore(events, multiplier) {
|
|
90
|
+
let totalBase = 0;
|
|
91
|
+
for (const e of events)
|
|
92
|
+
totalBase += getBasePoints(e.action_type, e.value);
|
|
93
|
+
return Math.floor(Math.min(totalBase, exports2.SESSION_BASE_POINT_CAP) * multiplier);
|
|
94
|
+
}
|
|
95
|
+
function isDeployCommand(command) {
|
|
96
|
+
return exports2.DEPLOY_PATTERNS.some((p) => p.test(command.trim()));
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
// ../shared/dist/anticheat.js
|
|
102
|
+
var require_anticheat = __commonJS({
|
|
103
|
+
"../shared/dist/anticheat.js"(exports2) {
|
|
104
|
+
"use strict";
|
|
105
|
+
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
106
|
+
exports2.hashEvent = hashEvent;
|
|
107
|
+
exports2.computeChainHash = computeChainHash2;
|
|
108
|
+
exports2.signBatch = signBatch2;
|
|
109
|
+
exports2.verifyBatchSignature = verifyBatchSignature;
|
|
110
|
+
var crypto_1 = require("crypto");
|
|
111
|
+
function hashEvent(event, prevHash) {
|
|
112
|
+
const payload = `${prevHash}|${event.action_type}|${event.value}|${event.timestamp}`;
|
|
113
|
+
return (0, crypto_1.createHash)("sha256").update(payload).digest("hex").slice(0, 16);
|
|
114
|
+
}
|
|
115
|
+
function computeChainHash2(events, initialHash = "0000000000000000") {
|
|
116
|
+
let hash = initialHash;
|
|
117
|
+
for (const event of events) {
|
|
118
|
+
hash = hashEvent(event, hash);
|
|
119
|
+
}
|
|
120
|
+
return hash;
|
|
121
|
+
}
|
|
122
|
+
function signBatch2(sessionId, events, chainHash, secret) {
|
|
123
|
+
const valueSum = events.reduce((s, e) => s + e.value, 0);
|
|
124
|
+
const message = `${sessionId}|${chainHash}|${events.length}|${valueSum}`;
|
|
125
|
+
return (0, crypto_1.createHmac)("sha256", secret).update(message).digest("hex");
|
|
126
|
+
}
|
|
127
|
+
function verifyBatchSignature(sessionId, events, chainHash, signature, secret) {
|
|
128
|
+
const expected = signBatch2(sessionId, events, chainHash, secret);
|
|
129
|
+
if (expected.length !== signature.length)
|
|
130
|
+
return false;
|
|
131
|
+
let mismatch = 0;
|
|
132
|
+
for (let i = 0; i < expected.length; i++) {
|
|
133
|
+
mismatch |= expected.charCodeAt(i) ^ signature.charCodeAt(i);
|
|
134
|
+
}
|
|
135
|
+
return mismatch === 0;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
// ../shared/dist/index.js
|
|
141
|
+
var require_dist = __commonJS({
|
|
142
|
+
"../shared/dist/index.js"(exports2) {
|
|
143
|
+
"use strict";
|
|
144
|
+
var __createBinding = exports2 && exports2.__createBinding || (Object.create ? (function(o, m, k, k2) {
|
|
145
|
+
if (k2 === void 0) k2 = k;
|
|
146
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
147
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
148
|
+
desc = { enumerable: true, get: function() {
|
|
149
|
+
return m[k];
|
|
150
|
+
} };
|
|
151
|
+
}
|
|
152
|
+
Object.defineProperty(o, k2, desc);
|
|
153
|
+
}) : (function(o, m, k, k2) {
|
|
154
|
+
if (k2 === void 0) k2 = k;
|
|
155
|
+
o[k2] = m[k];
|
|
156
|
+
}));
|
|
157
|
+
var __exportStar = exports2 && exports2.__exportStar || function(m, exports3) {
|
|
158
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports3, p)) __createBinding(exports3, m, p);
|
|
159
|
+
};
|
|
160
|
+
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
161
|
+
__exportStar(require_types(), exports2);
|
|
162
|
+
__exportStar(require_scoring(), exports2);
|
|
163
|
+
__exportStar(require_anticheat(), exports2);
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
|
|
26
167
|
// src/collector.ts
|
|
27
168
|
var import_uuid = require("uuid");
|
|
28
169
|
|
|
@@ -56,17 +197,49 @@ function saveConfig(config) {
|
|
|
56
197
|
}
|
|
57
198
|
|
|
58
199
|
// src/api-client.ts
|
|
200
|
+
var import_shared = __toESM(require_dist());
|
|
59
201
|
var APIClient = class {
|
|
60
202
|
config = loadConfig();
|
|
203
|
+
sessionSecret = null;
|
|
204
|
+
chainHash = "0000000000000000";
|
|
61
205
|
get headers() {
|
|
62
206
|
return {
|
|
63
207
|
"Content-Type": "application/json",
|
|
64
208
|
Authorization: `Bearer ${this.config?.token || ""}`
|
|
65
209
|
};
|
|
66
210
|
}
|
|
211
|
+
/** Register session with server and get cryptographic secret */
|
|
212
|
+
async startSession(sessionId) {
|
|
213
|
+
try {
|
|
214
|
+
const server = this.config?.server || "https://hackers.baby";
|
|
215
|
+
const res = await fetch(`${server}/api/sessions/start`, {
|
|
216
|
+
method: "POST",
|
|
217
|
+
headers: this.headers,
|
|
218
|
+
body: JSON.stringify({ session_id: sessionId })
|
|
219
|
+
});
|
|
220
|
+
if (res.ok) {
|
|
221
|
+
const data = await res.json();
|
|
222
|
+
this.sessionSecret = data.session_secret;
|
|
223
|
+
this.chainHash = data.initial_chain_hash;
|
|
224
|
+
return true;
|
|
225
|
+
}
|
|
226
|
+
return false;
|
|
227
|
+
} catch {
|
|
228
|
+
return false;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
67
231
|
async sendBatch(batch) {
|
|
68
232
|
try {
|
|
69
233
|
const server = this.config?.server || "https://hackers.baby";
|
|
234
|
+
if (this.sessionSecret) {
|
|
235
|
+
const prevChainHash = this.chainHash;
|
|
236
|
+
const newChainHash = (0, import_shared.computeChainHash)(batch.events, prevChainHash);
|
|
237
|
+
const signature = (0, import_shared.signBatch)(batch.session_id, batch.events, newChainHash, this.sessionSecret);
|
|
238
|
+
batch.signature = signature;
|
|
239
|
+
batch.chain_hash = newChainHash;
|
|
240
|
+
batch.prev_chain_hash = prevChainHash;
|
|
241
|
+
this.chainHash = newChainHash;
|
|
242
|
+
}
|
|
70
243
|
const res = await fetch(`${server}/api/scores/batch`, {
|
|
71
244
|
method: "POST",
|
|
72
245
|
headers: this.headers,
|
|
@@ -230,6 +403,7 @@ var EventCollector = class {
|
|
|
230
403
|
}
|
|
231
404
|
async start() {
|
|
232
405
|
await this.client.refreshTokenIfNeeded();
|
|
406
|
+
await this.client.startSession(this.sessionId);
|
|
233
407
|
const queued = drainQueue();
|
|
234
408
|
if (queued.length > 0) {
|
|
235
409
|
const batch = { session_id: this.sessionId, events: queued };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/collector.ts","../../../src/config.ts","../../../src/api-client.ts","../../../src/queue.ts","../../../src/hooks/shared/utils.ts","../../../src/hooks/shared/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 * Detect source at runtime. Cursor sets CURSOR_VERSION env var\n * and includes cursor_version in the hook payload.\n * Since Cursor reads ~/.claude/settings.json directly, both tools\n * fire the same hooks — so we detect rather than relying on entry points.\n */\nexport function detectSource(payload?: Record<string, unknown>): Source {\n if (process.env.CURSOR_VERSION) return 'cursor';\n if (payload?.cursor_version) return 'cursor';\n return 'claude';\n}\n\n/**\n * Normalize hook payload across tools.\n * Currently an identity function — Cursor's payload format matches Claude Code's.\n * If formats diverge, add field mappings here (e.g., raw.toolName → tool_name).\n */\nexport function normalizePayload(source: Source, raw: Record<string, unknown>): Record<string, unknown> {\n if (source === 'cursor') {\n return { ...raw };\n }\n return raw;\n}\n","import { EventCollector } from '../../collector';\nimport type { Source } from '../../config';\nimport { loadSessionId, readStdin, normalizePayload, detectSource } from './utils';\n\nexport async function handleStop(source: Source): Promise<void> {\n const input = await readStdin();\n try {\n const raw = JSON.parse(input);\n const detected = detectSource(raw);\n const hookData = normalizePayload(detected, raw);\n const sessionId = hookData.session_id || loadSessionId(detected);\n const collector = new EventCollector(sessionId, detected);\n\n // PRIVACY: Only track that 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"]}
|
|
1
|
+
{"version":3,"sources":["../../../../shared/dist/types.js","../../../../shared/src/scoring.ts","../../../../shared/src/anticheat.ts","../../../../shared/src/index.ts","../../../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":["\"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 { 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 // Register session with server for cryptographic signing\n await this.client.startSession(this.sessionId);\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 { 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\n private get headers(): Record<string, string> {\n return {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${this.config?.token || ''}`,\n };\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 // Update local chain hash for next batch\n this.chainHash = newChainHash;\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 fs from 'fs';\nimport type { Source } from '../../config';\nimport { sessionStateFile } from '../../config';\n\nexport interface SessionState {\n sessionId: string;\n pid: number;\n source: Source;\n}\n\nexport function loadSessionId(source: Source): string | undefined {\n try {\n const file = sessionStateFile(source);\n if (!fs.existsSync(file)) return undefined;\n const data = JSON.parse(fs.readFileSync(file, 'utf-8'));\n return data.sessionId;\n } catch {\n return undefined;\n }\n}\n\nexport function saveSessionState(state: SessionState): void {\n const file = sessionStateFile(state.source);\n fs.writeFileSync(file, JSON.stringify(state));\n}\n\nexport function removeSessionState(source: Source): void {\n const file = sessionStateFile(source);\n if (fs.existsSync(file)) fs.unlinkSync(file);\n}\n\nexport function readStdin(): Promise<string> {\n return new Promise((resolve) => {\n let input = '';\n process.stdin.on('data', (chunk) => { input += chunk; });\n process.stdin.on('end', () => resolve(input));\n });\n}\n\n/**\n * Detect source at runtime. Cursor sets CURSOR_VERSION env var\n * and includes cursor_version in the hook payload.\n * Since Cursor reads ~/.claude/settings.json directly, both tools\n * fire the same hooks — so we detect rather than relying on entry points.\n */\nexport function detectSource(payload?: Record<string, unknown>): Source {\n if (process.env.CURSOR_VERSION) return 'cursor';\n if (payload?.cursor_version) return 'cursor';\n return 'claude';\n}\n\n/**\n * Normalize hook payload across tools.\n * Currently an identity function — Cursor's payload format matches Claude Code's.\n * If formats diverge, add field mappings here (e.g., raw.toolName → tool_name).\n */\nexport function normalizePayload(source: Source, raw: Record<string, unknown>): Record<string, unknown> {\n if (source === 'cursor') {\n return { ...raw };\n }\n return raw;\n}\n","import { EventCollector } from '../../collector';\nimport type { Source } from '../../config';\nimport { loadSessionId, readStdin, normalizePayload, detectSource } from './utils';\n\nexport async function handleStop(source: Source): Promise<void> {\n const input = await readStdin();\n try {\n const raw = JSON.parse(input);\n const detected = detectSource(raw);\n const hookData = normalizePayload(detected, raw);\n const sessionId = hookData.session_id || loadSessionId(detected);\n const collector = new EventCollector(sessionId, detected);\n\n // PRIVACY: Only track that 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":["exports","exports","exports","computeChainHash","signBatch","exports","path","os","fs","import_fs","fs","uuidv4","import_fs","fs"]}
|
|
@@ -6,6 +6,9 @@ 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 __commonJS = (cb, mod) => function __require() {
|
|
10
|
+
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
|
11
|
+
};
|
|
9
12
|
var __copyProps = (to, from, except, desc) => {
|
|
10
13
|
if (from && typeof from === "object" || typeof from === "function") {
|
|
11
14
|
for (let key of __getOwnPropNames(from))
|
|
@@ -23,6 +26,144 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
23
26
|
mod
|
|
24
27
|
));
|
|
25
28
|
|
|
29
|
+
// ../shared/dist/types.js
|
|
30
|
+
var require_types = __commonJS({
|
|
31
|
+
"../shared/dist/types.js"(exports2) {
|
|
32
|
+
"use strict";
|
|
33
|
+
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
// ../shared/dist/scoring.js
|
|
38
|
+
var require_scoring = __commonJS({
|
|
39
|
+
"../shared/dist/scoring.js"(exports2) {
|
|
40
|
+
"use strict";
|
|
41
|
+
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
42
|
+
exports2.DEPLOY_PATTERNS = exports2.BASE_POINTS = exports2.MIN_SESSION_TOKENS = exports2.TOOL_CALLS_PER_HOUR_CAP = exports2.SESSION_BASE_POINT_CAP = void 0;
|
|
43
|
+
exports2.getBasePoints = getBasePoints;
|
|
44
|
+
exports2.calculateMultiplier = calculateMultiplier;
|
|
45
|
+
exports2.calculateSessionScore = calculateSessionScore;
|
|
46
|
+
exports2.isDeployCommand = isDeployCommand;
|
|
47
|
+
exports2.SESSION_BASE_POINT_CAP = 2e4;
|
|
48
|
+
exports2.TOOL_CALLS_PER_HOUR_CAP = 4e3;
|
|
49
|
+
exports2.MIN_SESSION_TOKENS = 2e3;
|
|
50
|
+
exports2.BASE_POINTS = {
|
|
51
|
+
token_input: { perUnit: 1, unitSize: 1e3 },
|
|
52
|
+
token_output: { perUnit: 2, unitSize: 1e3 },
|
|
53
|
+
tool_call: { perUnit: 5, unitSize: 1 },
|
|
54
|
+
file_created: { perUnit: 15, unitSize: 1 },
|
|
55
|
+
file_edited: { perUnit: 10, unitSize: 1 },
|
|
56
|
+
commit: { perUnit: 50, unitSize: 1 },
|
|
57
|
+
session_completed: { perUnit: 100, unitSize: 1 },
|
|
58
|
+
deployment: { perUnit: 200, unitSize: 1 },
|
|
59
|
+
prompt_submitted: { perUnit: 15, unitSize: 1 },
|
|
60
|
+
subagent_spawned: { perUnit: 40, unitSize: 1 },
|
|
61
|
+
context_compacted: { perUnit: 30, unitSize: 1 },
|
|
62
|
+
stop_response: { perUnit: 10, unitSize: 1 }
|
|
63
|
+
};
|
|
64
|
+
exports2.DEPLOY_PATTERNS = [
|
|
65
|
+
/^vercel\s+(--prod|deploy)/,
|
|
66
|
+
/^netlify\s+deploy/,
|
|
67
|
+
/^fly\s+deploy/,
|
|
68
|
+
/^railway\s+up/,
|
|
69
|
+
/^git\s+push\s+\S+\s+(main|master|production)/,
|
|
70
|
+
/^(npm|yarn|pnpm)\s+run\s+deploy/,
|
|
71
|
+
/^(yarn|pnpm)\s+deploy/
|
|
72
|
+
];
|
|
73
|
+
function getBasePoints(actionType, value) {
|
|
74
|
+
const config = exports2.BASE_POINTS[actionType];
|
|
75
|
+
return Math.floor(value / config.unitSize) * config.perUnit;
|
|
76
|
+
}
|
|
77
|
+
function calculateMultiplier(input) {
|
|
78
|
+
let streak = 1;
|
|
79
|
+
if (input.streak_days >= 30)
|
|
80
|
+
streak = 3;
|
|
81
|
+
else if (input.streak_days >= 7)
|
|
82
|
+
streak = 2;
|
|
83
|
+
else if (input.streak_days >= 3)
|
|
84
|
+
streak = 1.5;
|
|
85
|
+
const quality = input.commit_quality ? 1.5 : 1;
|
|
86
|
+
const diversity = input.language_count >= 3 ? 1.2 : 1;
|
|
87
|
+
return streak * quality * diversity;
|
|
88
|
+
}
|
|
89
|
+
function calculateSessionScore(events, multiplier) {
|
|
90
|
+
let totalBase = 0;
|
|
91
|
+
for (const e of events)
|
|
92
|
+
totalBase += getBasePoints(e.action_type, e.value);
|
|
93
|
+
return Math.floor(Math.min(totalBase, exports2.SESSION_BASE_POINT_CAP) * multiplier);
|
|
94
|
+
}
|
|
95
|
+
function isDeployCommand(command) {
|
|
96
|
+
return exports2.DEPLOY_PATTERNS.some((p) => p.test(command.trim()));
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
// ../shared/dist/anticheat.js
|
|
102
|
+
var require_anticheat = __commonJS({
|
|
103
|
+
"../shared/dist/anticheat.js"(exports2) {
|
|
104
|
+
"use strict";
|
|
105
|
+
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
106
|
+
exports2.hashEvent = hashEvent;
|
|
107
|
+
exports2.computeChainHash = computeChainHash2;
|
|
108
|
+
exports2.signBatch = signBatch2;
|
|
109
|
+
exports2.verifyBatchSignature = verifyBatchSignature;
|
|
110
|
+
var crypto_1 = require("crypto");
|
|
111
|
+
function hashEvent(event, prevHash) {
|
|
112
|
+
const payload = `${prevHash}|${event.action_type}|${event.value}|${event.timestamp}`;
|
|
113
|
+
return (0, crypto_1.createHash)("sha256").update(payload).digest("hex").slice(0, 16);
|
|
114
|
+
}
|
|
115
|
+
function computeChainHash2(events, initialHash = "0000000000000000") {
|
|
116
|
+
let hash = initialHash;
|
|
117
|
+
for (const event of events) {
|
|
118
|
+
hash = hashEvent(event, hash);
|
|
119
|
+
}
|
|
120
|
+
return hash;
|
|
121
|
+
}
|
|
122
|
+
function signBatch2(sessionId, events, chainHash, secret) {
|
|
123
|
+
const valueSum = events.reduce((s, e) => s + e.value, 0);
|
|
124
|
+
const message = `${sessionId}|${chainHash}|${events.length}|${valueSum}`;
|
|
125
|
+
return (0, crypto_1.createHmac)("sha256", secret).update(message).digest("hex");
|
|
126
|
+
}
|
|
127
|
+
function verifyBatchSignature(sessionId, events, chainHash, signature, secret) {
|
|
128
|
+
const expected = signBatch2(sessionId, events, chainHash, secret);
|
|
129
|
+
if (expected.length !== signature.length)
|
|
130
|
+
return false;
|
|
131
|
+
let mismatch = 0;
|
|
132
|
+
for (let i = 0; i < expected.length; i++) {
|
|
133
|
+
mismatch |= expected.charCodeAt(i) ^ signature.charCodeAt(i);
|
|
134
|
+
}
|
|
135
|
+
return mismatch === 0;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
// ../shared/dist/index.js
|
|
141
|
+
var require_dist = __commonJS({
|
|
142
|
+
"../shared/dist/index.js"(exports2) {
|
|
143
|
+
"use strict";
|
|
144
|
+
var __createBinding = exports2 && exports2.__createBinding || (Object.create ? (function(o, m, k, k2) {
|
|
145
|
+
if (k2 === void 0) k2 = k;
|
|
146
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
147
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
148
|
+
desc = { enumerable: true, get: function() {
|
|
149
|
+
return m[k];
|
|
150
|
+
} };
|
|
151
|
+
}
|
|
152
|
+
Object.defineProperty(o, k2, desc);
|
|
153
|
+
}) : (function(o, m, k, k2) {
|
|
154
|
+
if (k2 === void 0) k2 = k;
|
|
155
|
+
o[k2] = m[k];
|
|
156
|
+
}));
|
|
157
|
+
var __exportStar = exports2 && exports2.__exportStar || function(m, exports3) {
|
|
158
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports3, p)) __createBinding(exports3, m, p);
|
|
159
|
+
};
|
|
160
|
+
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
161
|
+
__exportStar(require_types(), exports2);
|
|
162
|
+
__exportStar(require_scoring(), exports2);
|
|
163
|
+
__exportStar(require_anticheat(), exports2);
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
|
|
26
167
|
// src/collector.ts
|
|
27
168
|
var import_uuid = require("uuid");
|
|
28
169
|
|
|
@@ -56,17 +197,49 @@ function saveConfig(config) {
|
|
|
56
197
|
}
|
|
57
198
|
|
|
58
199
|
// src/api-client.ts
|
|
200
|
+
var import_shared = __toESM(require_dist());
|
|
59
201
|
var APIClient = class {
|
|
60
202
|
config = loadConfig();
|
|
203
|
+
sessionSecret = null;
|
|
204
|
+
chainHash = "0000000000000000";
|
|
61
205
|
get headers() {
|
|
62
206
|
return {
|
|
63
207
|
"Content-Type": "application/json",
|
|
64
208
|
Authorization: `Bearer ${this.config?.token || ""}`
|
|
65
209
|
};
|
|
66
210
|
}
|
|
211
|
+
/** Register session with server and get cryptographic secret */
|
|
212
|
+
async startSession(sessionId) {
|
|
213
|
+
try {
|
|
214
|
+
const server = this.config?.server || "https://hackers.baby";
|
|
215
|
+
const res = await fetch(`${server}/api/sessions/start`, {
|
|
216
|
+
method: "POST",
|
|
217
|
+
headers: this.headers,
|
|
218
|
+
body: JSON.stringify({ session_id: sessionId })
|
|
219
|
+
});
|
|
220
|
+
if (res.ok) {
|
|
221
|
+
const data = await res.json();
|
|
222
|
+
this.sessionSecret = data.session_secret;
|
|
223
|
+
this.chainHash = data.initial_chain_hash;
|
|
224
|
+
return true;
|
|
225
|
+
}
|
|
226
|
+
return false;
|
|
227
|
+
} catch {
|
|
228
|
+
return false;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
67
231
|
async sendBatch(batch) {
|
|
68
232
|
try {
|
|
69
233
|
const server = this.config?.server || "https://hackers.baby";
|
|
234
|
+
if (this.sessionSecret) {
|
|
235
|
+
const prevChainHash = this.chainHash;
|
|
236
|
+
const newChainHash = (0, import_shared.computeChainHash)(batch.events, prevChainHash);
|
|
237
|
+
const signature = (0, import_shared.signBatch)(batch.session_id, batch.events, newChainHash, this.sessionSecret);
|
|
238
|
+
batch.signature = signature;
|
|
239
|
+
batch.chain_hash = newChainHash;
|
|
240
|
+
batch.prev_chain_hash = prevChainHash;
|
|
241
|
+
this.chainHash = newChainHash;
|
|
242
|
+
}
|
|
70
243
|
const res = await fetch(`${server}/api/scores/batch`, {
|
|
71
244
|
method: "POST",
|
|
72
245
|
headers: this.headers,
|
|
@@ -230,6 +403,7 @@ var EventCollector = class {
|
|
|
230
403
|
}
|
|
231
404
|
async start() {
|
|
232
405
|
await this.client.refreshTokenIfNeeded();
|
|
406
|
+
await this.client.startSession(this.sessionId);
|
|
233
407
|
const queued = drainQueue();
|
|
234
408
|
if (queued.length > 0) {
|
|
235
409
|
const batch = { session_id: this.sessionId, events: queued };
|