@hackersbaby/plugin 0.1.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.
@@ -0,0 +1,39 @@
1
+ import { RawEvent, EventBatch } from '@hackersbaby/shared';
2
+
3
+ type RawEventInput = Omit<RawEvent, 'timestamp'>;
4
+ declare class EventCollector {
5
+ readonly sessionId: string;
6
+ private buffer;
7
+ private flushInterval;
8
+ private client;
9
+ constructor(sessionId?: string);
10
+ addEvent(event: RawEventInput): void;
11
+ start(): Promise<void>;
12
+ flush(): Promise<void>;
13
+ stop(): Promise<void>;
14
+ }
15
+
16
+ declare class APIClient {
17
+ private config;
18
+ private get headers();
19
+ sendBatch(batch: EventBatch): Promise<boolean>;
20
+ refreshTokenIfNeeded(): Promise<void>;
21
+ getStatus(): Promise<Record<string, unknown>>;
22
+ getRank(): Promise<Record<string, unknown>>;
23
+ getLeaderboard(): Promise<Record<string, unknown>>;
24
+ }
25
+
26
+ interface PluginConfig {
27
+ token: string;
28
+ refresh_token: string;
29
+ user_id: string;
30
+ server: string;
31
+ preferences: {
32
+ dashboard_port: number;
33
+ batch_interval_ms: number;
34
+ };
35
+ }
36
+ declare function loadConfig(): PluginConfig | null;
37
+ declare function saveConfig(config: PluginConfig): void;
38
+
39
+ export { APIClient, EventCollector, loadConfig, saveConfig };
package/dist/index.js ADDED
@@ -0,0 +1,236 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var src_exports = {};
32
+ __export(src_exports, {
33
+ APIClient: () => APIClient,
34
+ EventCollector: () => EventCollector,
35
+ loadConfig: () => loadConfig,
36
+ saveConfig: () => saveConfig
37
+ });
38
+ module.exports = __toCommonJS(src_exports);
39
+
40
+ // src/collector.ts
41
+ var import_uuid = require("uuid");
42
+
43
+ // src/config.ts
44
+ var import_fs = __toESM(require("fs"));
45
+ var import_path = __toESM(require("path"));
46
+ var import_os = __toESM(require("os"));
47
+ var CONFIG_DIR = import_path.default.join(import_os.default.homedir(), ".hackersbaby");
48
+ var CONFIG_FILE = import_path.default.join(CONFIG_DIR, "config.json");
49
+ var QUEUE_FILE = import_path.default.join(CONFIG_DIR, "queue.jsonl");
50
+ function ensureConfigDir() {
51
+ if (!import_fs.default.existsSync(CONFIG_DIR)) {
52
+ import_fs.default.mkdirSync(CONFIG_DIR, { recursive: true });
53
+ }
54
+ }
55
+ function loadConfig() {
56
+ try {
57
+ if (!import_fs.default.existsSync(CONFIG_FILE)) return null;
58
+ const raw = import_fs.default.readFileSync(CONFIG_FILE, "utf-8");
59
+ return JSON.parse(raw);
60
+ } catch {
61
+ return null;
62
+ }
63
+ }
64
+ function saveConfig(config) {
65
+ ensureConfigDir();
66
+ import_fs.default.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2), "utf-8");
67
+ }
68
+
69
+ // src/api-client.ts
70
+ var APIClient = class {
71
+ config = loadConfig();
72
+ get headers() {
73
+ return {
74
+ "Content-Type": "application/json",
75
+ Authorization: `Bearer ${this.config?.token || ""}`
76
+ };
77
+ }
78
+ async sendBatch(batch) {
79
+ try {
80
+ const server = this.config?.server || "https://hackers.baby";
81
+ const res = await fetch(`${server}/api/scores/batch`, {
82
+ method: "POST",
83
+ headers: this.headers,
84
+ body: JSON.stringify(batch)
85
+ });
86
+ return res.ok;
87
+ } catch {
88
+ return false;
89
+ }
90
+ }
91
+ async refreshTokenIfNeeded() {
92
+ if (!this.config) return;
93
+ try {
94
+ const parts = this.config.token.split(".");
95
+ if (parts.length !== 3) return;
96
+ const payload = JSON.parse(Buffer.from(parts[1], "base64").toString("utf-8"));
97
+ const expMs = payload.exp * 1e3;
98
+ const oneDayMs = 24 * 60 * 60 * 1e3;
99
+ if (Date.now() + oneDayMs < expMs) return;
100
+ const res = await fetch(`${this.config.server}/api/auth/cli-token/refresh`, {
101
+ method: "POST",
102
+ headers: this.headers,
103
+ body: JSON.stringify({ refresh_token: this.config.refresh_token })
104
+ });
105
+ if (res.ok) {
106
+ const data = await res.json();
107
+ if (data.token) {
108
+ this.config.token = data.token;
109
+ if (data.refresh_token) this.config.refresh_token = data.refresh_token;
110
+ saveConfig(this.config);
111
+ }
112
+ }
113
+ } catch {
114
+ }
115
+ }
116
+ async getStatus() {
117
+ const server = this.config?.server || "https://hackers.baby";
118
+ const res = await fetch(`${server}/api/users/me`, { headers: this.headers });
119
+ if (!res.ok) throw new Error(`getStatus failed: ${res.status}`);
120
+ return res.json();
121
+ }
122
+ async getRank() {
123
+ const server = this.config?.server || "https://hackers.baby";
124
+ const res = await fetch(`${server}/api/users/me/rank`, { headers: this.headers });
125
+ if (!res.ok) throw new Error(`getRank failed: ${res.status}`);
126
+ return res.json();
127
+ }
128
+ async getLeaderboard() {
129
+ const server = this.config?.server || "https://hackers.baby";
130
+ const res = await fetch(`${server}/api/leaderboard/global?limit=10`, { headers: this.headers });
131
+ if (!res.ok) throw new Error(`getLeaderboard failed: ${res.status}`);
132
+ return res.json();
133
+ }
134
+ };
135
+
136
+ // src/queue.ts
137
+ var import_fs2 = __toESM(require("fs"));
138
+ var MAX_QUEUE_SIZE = 1e4;
139
+ var MAX_AGE_MS = 24 * 60 * 60 * 1e3;
140
+ function enqueueEvents(events) {
141
+ ensureConfigDir();
142
+ const lines = events.map((e) => JSON.stringify(e)).join("\n");
143
+ import_fs2.default.appendFileSync(QUEUE_FILE, lines + "\n", "utf-8");
144
+ try {
145
+ const content = import_fs2.default.readFileSync(QUEUE_FILE, "utf-8");
146
+ const allLines = content.split("\n").filter((l) => l.trim() !== "");
147
+ if (allLines.length > MAX_QUEUE_SIZE) {
148
+ const trimmed = allLines.slice(allLines.length - MAX_QUEUE_SIZE);
149
+ import_fs2.default.writeFileSync(QUEUE_FILE, trimmed.join("\n") + "\n", "utf-8");
150
+ }
151
+ } catch {
152
+ }
153
+ }
154
+ function drainQueue() {
155
+ try {
156
+ if (!import_fs2.default.existsSync(QUEUE_FILE)) return [];
157
+ const content = import_fs2.default.readFileSync(QUEUE_FILE, "utf-8");
158
+ import_fs2.default.writeFileSync(QUEUE_FILE, "", "utf-8");
159
+ const now = Date.now();
160
+ const events = [];
161
+ for (const line of content.split("\n")) {
162
+ if (!line.trim()) continue;
163
+ try {
164
+ const event = JSON.parse(line);
165
+ const age = now - new Date(event.timestamp).getTime();
166
+ if (age <= MAX_AGE_MS) {
167
+ events.push(event);
168
+ }
169
+ } catch {
170
+ }
171
+ }
172
+ return events;
173
+ } catch {
174
+ return [];
175
+ }
176
+ }
177
+
178
+ // src/collector.ts
179
+ var EventCollector = class {
180
+ sessionId;
181
+ buffer = [];
182
+ flushInterval = null;
183
+ client;
184
+ constructor(sessionId) {
185
+ this.sessionId = sessionId ?? (0, import_uuid.v4)();
186
+ this.client = new APIClient();
187
+ }
188
+ addEvent(event) {
189
+ this.buffer.push({
190
+ ...event,
191
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
192
+ });
193
+ }
194
+ async start() {
195
+ await this.client.refreshTokenIfNeeded();
196
+ const queued = drainQueue();
197
+ if (queued.length > 0) {
198
+ const batch = { session_id: this.sessionId, events: queued };
199
+ const ok = await this.client.sendBatch(batch);
200
+ if (!ok) {
201
+ enqueueEvents(queued);
202
+ }
203
+ }
204
+ const config = loadConfig();
205
+ const intervalMs = config?.preferences?.batch_interval_ms ?? 1e4;
206
+ this.flushInterval = setInterval(() => {
207
+ this.flush().catch(() => {
208
+ });
209
+ }, intervalMs);
210
+ }
211
+ async flush() {
212
+ if (this.buffer.length === 0) return;
213
+ const events = this.buffer.splice(0);
214
+ const batch = { session_id: this.sessionId, events };
215
+ const ok = await this.client.sendBatch(batch);
216
+ if (!ok) {
217
+ enqueueEvents(events);
218
+ }
219
+ }
220
+ async stop() {
221
+ if (this.flushInterval !== null) {
222
+ clearInterval(this.flushInterval);
223
+ this.flushInterval = null;
224
+ }
225
+ this.addEvent({ action_type: "session_completed", value: 1, metadata: {} });
226
+ await this.flush();
227
+ }
228
+ };
229
+ // Annotate the CommonJS export names for ESM import in node:
230
+ 0 && (module.exports = {
231
+ APIClient,
232
+ EventCollector,
233
+ loadConfig,
234
+ saveConfig
235
+ });
236
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/collector.ts","../src/config.ts","../src/api-client.ts","../src/queue.ts"],"sourcesContent":["export { EventCollector } from './collector';\nexport { APIClient } from './api-client';\nexport { loadConfig, saveConfig } from './config';\n","import { v4 as uuidv4 } from 'uuid';\nimport { APIClient } from './api-client';\nimport { enqueueEvents, drainQueue } from './queue';\nimport { loadConfig } from './config';\nimport type { 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\n constructor(sessionId?: string) {\n this.sessionId = sessionId ?? uuidv4();\n this.client = new APIClient();\n }\n\n addEvent(event: RawEventInput): void {\n this.buffer.push({\n ...event,\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 interface PluginConfig {\n token: string;\n refresh_token: string;\n user_id: string;\n server: string;\n preferences: {\n dashboard_port: number;\n batch_interval_ms: number;\n };\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","import fs from 'fs';\nimport { QUEUE_FILE, ensureConfigDir } from './config';\nimport type { RawEvent } from '@hackersbaby/shared';\n\nconst MAX_QUEUE_SIZE = 10_000;\nconst MAX_AGE_MS = 24 * 60 * 60 * 1000; // 24 hours\n\nexport function enqueueEvents(events: RawEvent[]): void {\n ensureConfigDir();\n const lines = events.map((e) => JSON.stringify(e)).join('\\n');\n fs.appendFileSync(QUEUE_FILE, lines + '\\n', 'utf-8');\n\n // Trim to MAX_QUEUE_SIZE\n try {\n const content = fs.readFileSync(QUEUE_FILE, 'utf-8');\n const allLines = content.split('\\n').filter((l) => l.trim() !== '');\n if (allLines.length > MAX_QUEUE_SIZE) {\n const trimmed = allLines.slice(allLines.length - MAX_QUEUE_SIZE);\n fs.writeFileSync(QUEUE_FILE, trimmed.join('\\n') + '\\n', 'utf-8');\n }\n } catch {\n // ignore read/write errors on trim\n }\n}\n\nexport function drainQueue(): RawEvent[] {\n try {\n if (!fs.existsSync(QUEUE_FILE)) return [];\n const content = fs.readFileSync(QUEUE_FILE, 'utf-8');\n fs.writeFileSync(QUEUE_FILE, '', 'utf-8');\n\n const now = Date.now();\n const events: RawEvent[] = [];\n for (const line of content.split('\\n')) {\n if (!line.trim()) continue;\n try {\n const event = JSON.parse(line) as RawEvent;\n const age = now - new Date(event.timestamp).getTime();\n if (age <= MAX_AGE_MS) {\n events.push(event);\n }\n } catch {\n // skip malformed lines\n }\n }\n return events;\n } catch {\n return [];\n }\n}\n"],"mappings":"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","names":["path","os","fs","import_fs","fs","uuidv4"]}
package/package.json ADDED
@@ -0,0 +1,37 @@
1
+ {
2
+ "name": "@hackersbaby/plugin",
3
+ "version": "0.1.0",
4
+ "description": "hackers.baby CLI — gamified developer leaderboard plugin for Claude Code",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/hackersbaby/hackersbaby.git",
9
+ "directory": "packages/plugin"
10
+ },
11
+ "bin": {
12
+ "hackersbaby": "dist/cli.js"
13
+ },
14
+ "main": "./dist/index.js",
15
+ "types": "./dist/index.d.ts",
16
+ "files": [
17
+ "dist"
18
+ ],
19
+ "publishConfig": {
20
+ "access": "public"
21
+ },
22
+ "dependencies": {
23
+ "uuid": "^13.0.0"
24
+ },
25
+ "devDependencies": {
26
+ "@types/node": "^20",
27
+ "@types/uuid": "^11.0.0",
28
+ "tsup": "^8.5.1",
29
+ "vitest": "^3",
30
+ "@hackersbaby/shared": "0.1.0"
31
+ },
32
+ "scripts": {
33
+ "build": "tsup",
34
+ "test": "vitest run",
35
+ "dev": "tsc --watch"
36
+ }
37
+ }