@burnguard/agent 1.0.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,11 @@
1
+ export interface AgentConfig {
2
+ apiKey: string;
3
+ backendUrl: string;
4
+ logDirectory: string;
5
+ reconnectInterval: number;
6
+ maxReconnectAttempts: number;
7
+ batchSize: number;
8
+ batchInterval: number;
9
+ }
10
+ export declare function loadConfig(): AgentConfig;
11
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,oBAAoB,EAAE,MAAM,CAAC;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,MAAM,CAAC;CACvB;AAeD,wBAAgB,UAAU,IAAI,WAAW,CA8CxC"}
package/dist/config.js ADDED
@@ -0,0 +1,48 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.loadConfig = loadConfig;
7
+ const os_1 = __importDefault(require("os"));
8
+ const path_1 = __importDefault(require("path"));
9
+ function resolveHome(filepath) {
10
+ if (filepath.startsWith("~")) {
11
+ return path_1.default.join(os_1.default.homedir(), filepath.slice(1));
12
+ }
13
+ return filepath;
14
+ }
15
+ function getCliArg(name) {
16
+ const prefix = `--${name}=`;
17
+ const arg = process.argv.find((a) => a.startsWith(prefix));
18
+ return arg ? arg.slice(prefix.length) : undefined;
19
+ }
20
+ function loadConfig() {
21
+ const apiKey = getCliArg("api-key") ??
22
+ process.env.BURNGUARD_API_KEY ??
23
+ "";
24
+ if (!apiKey) {
25
+ console.error("Error: API key is required. Set BURNGUARD_API_KEY or pass --api-key=<key>");
26
+ process.exit(1);
27
+ }
28
+ const backendUrl = getCliArg("backend-url") ??
29
+ process.env.BURNGUARD_BACKEND_URL ??
30
+ "wss://api.burnguard.dev/api/ws";
31
+ const logDirectory = resolveHome(getCliArg("log-dir") ??
32
+ process.env.BURNGUARD_LOG_DIR ??
33
+ "~/.openclaw/sessions");
34
+ const reconnectInterval = parseInt(getCliArg("reconnect-interval") ?? "5000", 10);
35
+ const maxReconnectAttempts = parseInt(getCliArg("max-reconnect") ?? "10", 10);
36
+ const batchSize = parseInt(getCliArg("batch-size") ?? "50", 10);
37
+ const batchInterval = parseInt(getCliArg("batch-interval") ?? "2000", 10);
38
+ return {
39
+ apiKey,
40
+ backendUrl,
41
+ logDirectory,
42
+ reconnectInterval,
43
+ maxReconnectAttempts,
44
+ batchSize,
45
+ batchInterval,
46
+ };
47
+ }
48
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":";;;;;AA0BA,gCA8CC;AAxED,4CAAoB;AACpB,gDAAwB;AAYxB,SAAS,WAAW,CAAC,QAAgB;IACnC,IAAI,QAAQ,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;QAC7B,OAAO,cAAI,CAAC,IAAI,CAAC,YAAE,CAAC,OAAO,EAAE,EAAE,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;IACpD,CAAC;IACD,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,SAAS,SAAS,CAAC,IAAY;IAC7B,MAAM,MAAM,GAAG,KAAK,IAAI,GAAG,CAAC;IAC5B,MAAM,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC;IAC3D,OAAO,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;AACpD,CAAC;AAED,SAAgB,UAAU;IACxB,MAAM,MAAM,GACV,SAAS,CAAC,SAAS,CAAC;QACpB,OAAO,CAAC,GAAG,CAAC,iBAAiB;QAC7B,EAAE,CAAC;IAEL,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,OAAO,CAAC,KAAK,CACX,2EAA2E,CAC5E,CAAC;QACF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,MAAM,UAAU,GACd,SAAS,CAAC,aAAa,CAAC;QACxB,OAAO,CAAC,GAAG,CAAC,qBAAqB;QACjC,gCAAgC,CAAC;IAEnC,MAAM,YAAY,GAAG,WAAW,CAC9B,SAAS,CAAC,SAAS,CAAC;QACpB,OAAO,CAAC,GAAG,CAAC,iBAAiB;QAC7B,sBAAsB,CACvB,CAAC;IAEF,MAAM,iBAAiB,GAAG,QAAQ,CAChC,SAAS,CAAC,oBAAoB,CAAC,IAAI,MAAM,EACzC,EAAE,CACH,CAAC;IAEF,MAAM,oBAAoB,GAAG,QAAQ,CACnC,SAAS,CAAC,eAAe,CAAC,IAAI,IAAI,EAClC,EAAE,CACH,CAAC;IAEF,MAAM,SAAS,GAAG,QAAQ,CAAC,SAAS,CAAC,YAAY,CAAC,IAAI,IAAI,EAAE,EAAE,CAAC,CAAC;IAChE,MAAM,aAAa,GAAG,QAAQ,CAAC,SAAS,CAAC,gBAAgB,CAAC,IAAI,MAAM,EAAE,EAAE,CAAC,CAAC;IAE1E,OAAO;QACL,MAAM;QACN,UAAU;QACV,YAAY;QACZ,iBAAiB;QACjB,oBAAoB;QACpB,SAAS;QACT,aAAa;KACd,CAAC;AACJ,CAAC"}
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
package/dist/index.js ADDED
@@ -0,0 +1,34 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ const config_1 = require("./config");
5
+ const watcher_1 = require("./watcher");
6
+ const sender_1 = require("./sender");
7
+ console.log("=================================");
8
+ console.log(" BurnGuard Agent v1.0.0");
9
+ console.log(" Real-Time Cost Monitoring");
10
+ console.log("=================================");
11
+ console.log();
12
+ const config = (0, config_1.loadConfig)();
13
+ console.log(`[agent] Backend URL: ${config.backendUrl}`);
14
+ console.log(`[agent] Log directory: ${config.logDirectory}`);
15
+ console.log(`[agent] Batch size: ${config.batchSize}, interval: ${config.batchInterval}ms`);
16
+ console.log();
17
+ const sender = (0, sender_1.createSender)(config);
18
+ const watcher = (0, watcher_1.createWatcher)(config.logDirectory, (metrics) => {
19
+ console.log(`[agent] Parsed ${metrics.length} metric(s), sending to backend...`);
20
+ sender.send(metrics);
21
+ });
22
+ console.log(`[agent] Watching for JSONL files in: ${config.logDirectory}`);
23
+ console.log("[agent] Press Ctrl+C to stop.");
24
+ console.log();
25
+ function shutdown() {
26
+ console.log("\n[agent] Shutting down...");
27
+ watcher.close();
28
+ sender.close();
29
+ console.log("[agent] Goodbye.");
30
+ process.exit(0);
31
+ }
32
+ process.on("SIGINT", shutdown);
33
+ process.on("SIGTERM", shutdown);
34
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AACA,qCAAsC;AACtC,uCAA0C;AAC1C,qCAAwC;AAExC,OAAO,CAAC,GAAG,CAAC,mCAAmC,CAAC,CAAC;AACjD,OAAO,CAAC,GAAG,CAAC,0BAA0B,CAAC,CAAC;AACxC,OAAO,CAAC,GAAG,CAAC,6BAA6B,CAAC,CAAC;AAC3C,OAAO,CAAC,GAAG,CAAC,mCAAmC,CAAC,CAAC;AACjD,OAAO,CAAC,GAAG,EAAE,CAAC;AAEd,MAAM,MAAM,GAAG,IAAA,mBAAU,GAAE,CAAC;AAE5B,OAAO,CAAC,GAAG,CAAC,wBAAwB,MAAM,CAAC,UAAU,EAAE,CAAC,CAAC;AACzD,OAAO,CAAC,GAAG,CAAC,0BAA0B,MAAM,CAAC,YAAY,EAAE,CAAC,CAAC;AAC7D,OAAO,CAAC,GAAG,CAAC,uBAAuB,MAAM,CAAC,SAAS,eAAe,MAAM,CAAC,aAAa,IAAI,CAAC,CAAC;AAC5F,OAAO,CAAC,GAAG,EAAE,CAAC;AAEd,MAAM,MAAM,GAAG,IAAA,qBAAY,EAAC,MAAM,CAAC,CAAC;AAEpC,MAAM,OAAO,GAAG,IAAA,uBAAa,EAAC,MAAM,CAAC,YAAY,EAAE,CAAC,OAAO,EAAE,EAAE;IAC7D,OAAO,CAAC,GAAG,CAAC,kBAAkB,OAAO,CAAC,MAAM,mCAAmC,CAAC,CAAC;IACjF,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;AACvB,CAAC,CAAC,CAAC;AAEH,OAAO,CAAC,GAAG,CAAC,wCAAwC,MAAM,CAAC,YAAY,EAAE,CAAC,CAAC;AAC3E,OAAO,CAAC,GAAG,CAAC,+BAA+B,CAAC,CAAC;AAC7C,OAAO,CAAC,GAAG,EAAE,CAAC;AAEd,SAAS,QAAQ;IACf,OAAO,CAAC,GAAG,CAAC,4BAA4B,CAAC,CAAC;IAC1C,OAAO,CAAC,KAAK,EAAE,CAAC;IAChB,MAAM,CAAC,KAAK,EAAE,CAAC;IACf,OAAO,CAAC,GAAG,CAAC,kBAAkB,CAAC,CAAC;IAChC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC;AAED,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;AAC/B,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC"}
@@ -0,0 +1,11 @@
1
+ export interface ParsedMetric {
2
+ sessionId: string;
3
+ timestamp: number;
4
+ model: string;
5
+ category: string;
6
+ inputTokens: number;
7
+ outputTokens: number;
8
+ costCents: number;
9
+ }
10
+ export declare function parseJsonlLine(line: string): ParsedMetric | null;
11
+ //# sourceMappingURL=parser.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parser.d.ts","sourceRoot":"","sources":["../src/parser.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,YAAY;IAC3B,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;CACnB;AA6CD,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,YAAY,GAAG,IAAI,CAiDhE"}
package/dist/parser.js ADDED
@@ -0,0 +1,86 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.parseJsonlLine = parseJsonlLine;
4
+ /** Model pricing: [input_per_1M_tokens_cents, output_per_1M_tokens_cents] */
5
+ const MODEL_PRICING = {
6
+ "claude-opus-4": [1500, 7500],
7
+ "claude-sonnet-4": [300, 1500],
8
+ "claude-haiku-3.5": [25, 125],
9
+ };
10
+ function calculateCost(model, inputTokens, outputTokens) {
11
+ const pricing = MODEL_PRICING[model];
12
+ if (!pricing)
13
+ return 0;
14
+ const [inputRate, outputRate] = pricing;
15
+ const cost = (inputTokens / 1_000_000) * inputRate +
16
+ (outputTokens / 1_000_000) * outputRate;
17
+ return Math.round(cost);
18
+ }
19
+ function detectCategory(data) {
20
+ if (typeof data.type === "string") {
21
+ const t = data.type.toLowerCase();
22
+ if (t.includes("heartbeat") || t.includes("ping"))
23
+ return "heartbeat";
24
+ if (t.includes("tool") || t.includes("function"))
25
+ return "tool_use";
26
+ if (t.includes("error"))
27
+ return "error";
28
+ return t;
29
+ }
30
+ if (data.heartbeat || data.is_heartbeat)
31
+ return "heartbeat";
32
+ if (data.tool_name || data.function_call)
33
+ return "tool_use";
34
+ return "conversation";
35
+ }
36
+ function extractSessionId(data) {
37
+ if (typeof data.session_id === "string")
38
+ return data.session_id;
39
+ if (typeof data.sessionId === "string")
40
+ return data.sessionId;
41
+ if (typeof data.id === "string")
42
+ return data.id;
43
+ return "unknown";
44
+ }
45
+ function parseJsonlLine(line) {
46
+ const trimmed = line.trim();
47
+ if (!trimmed)
48
+ return null;
49
+ try {
50
+ const data = JSON.parse(trimmed);
51
+ const model = data.model ??
52
+ data.model_name ??
53
+ "";
54
+ if (!model)
55
+ return null;
56
+ const inputTokens = data.input_tokens ??
57
+ data.prompt_tokens ??
58
+ data.tokens_input ??
59
+ 0;
60
+ const outputTokens = data.output_tokens ??
61
+ data.completion_tokens ??
62
+ data.tokens_output ??
63
+ 0;
64
+ if (inputTokens === 0 && outputTokens === 0)
65
+ return null;
66
+ const costCents = calculateCost(model, inputTokens, outputTokens);
67
+ const category = detectCategory(data);
68
+ const sessionId = extractSessionId(data);
69
+ const timestamp = typeof data.timestamp === "number"
70
+ ? data.timestamp
71
+ : Math.floor(Date.now() / 1000);
72
+ return {
73
+ sessionId,
74
+ timestamp,
75
+ model,
76
+ category,
77
+ inputTokens,
78
+ outputTokens,
79
+ costCents,
80
+ };
81
+ }
82
+ catch {
83
+ return null;
84
+ }
85
+ }
86
+ //# sourceMappingURL=parser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parser.js","sourceRoot":"","sources":["../src/parser.ts"],"names":[],"mappings":";;AAqDA,wCAiDC;AA5FD,6EAA6E;AAC7E,MAAM,aAAa,GAAqC;IACtD,eAAe,EAAE,CAAC,IAAI,EAAE,IAAI,CAAC;IAC7B,iBAAiB,EAAE,CAAC,GAAG,EAAE,IAAI,CAAC;IAC9B,kBAAkB,EAAE,CAAC,EAAE,EAAE,GAAG,CAAC;CAC9B,CAAC;AAEF,SAAS,aAAa,CACpB,KAAa,EACb,WAAmB,EACnB,YAAoB;IAEpB,MAAM,OAAO,GAAG,aAAa,CAAC,KAAK,CAAC,CAAC;IACrC,IAAI,CAAC,OAAO;QAAE,OAAO,CAAC,CAAC;IACvB,MAAM,CAAC,SAAS,EAAE,UAAU,CAAC,GAAG,OAAO,CAAC;IACxC,MAAM,IAAI,GACR,CAAC,WAAW,GAAG,SAAS,CAAC,GAAG,SAAS;QACrC,CAAC,YAAY,GAAG,SAAS,CAAC,GAAG,UAAU,CAAC;IAC1C,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC;AAED,SAAS,cAAc,CAAC,IAA6B;IACnD,IAAI,OAAO,IAAI,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;QAClC,MAAM,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC;QAClC,IAAI,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC;YAAE,OAAO,WAAW,CAAC;QACtE,IAAI,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC,UAAU,CAAC;YAAE,OAAO,UAAU,CAAC;QACpE,IAAI,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC;YAAE,OAAO,OAAO,CAAC;QACxC,OAAO,CAAC,CAAC;IACX,CAAC;IAED,IAAI,IAAI,CAAC,SAAS,IAAI,IAAI,CAAC,YAAY;QAAE,OAAO,WAAW,CAAC;IAC5D,IAAI,IAAI,CAAC,SAAS,IAAI,IAAI,CAAC,aAAa;QAAE,OAAO,UAAU,CAAC;IAE5D,OAAO,cAAc,CAAC;AACxB,CAAC;AAED,SAAS,gBAAgB,CAAC,IAA6B;IACrD,IAAI,OAAO,IAAI,CAAC,UAAU,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC,UAAU,CAAC;IAChE,IAAI,OAAO,IAAI,CAAC,SAAS,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC,SAAS,CAAC;IAC9D,IAAI,OAAO,IAAI,CAAC,EAAE,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC,EAAE,CAAC;IAChD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,SAAgB,cAAc,CAAC,IAAY;IACzC,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;IAC5B,IAAI,CAAC,OAAO;QAAE,OAAO,IAAI,CAAC;IAE1B,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAA4B,CAAC;QAE5D,MAAM,KAAK,GACR,IAAI,CAAC,KAAgB;YACrB,IAAI,CAAC,UAAqB;YAC3B,EAAE,CAAC;QAEL,IAAI,CAAC,KAAK;YAAE,OAAO,IAAI,CAAC;QAExB,MAAM,WAAW,GACd,IAAI,CAAC,YAAuB;YAC5B,IAAI,CAAC,aAAwB;YAC7B,IAAI,CAAC,YAAuB;YAC7B,CAAC,CAAC;QAEJ,MAAM,YAAY,GACf,IAAI,CAAC,aAAwB;YAC7B,IAAI,CAAC,iBAA4B;YACjC,IAAI,CAAC,aAAwB;YAC9B,CAAC,CAAC;QAEJ,IAAI,WAAW,KAAK,CAAC,IAAI,YAAY,KAAK,CAAC;YAAE,OAAO,IAAI,CAAC;QAEzD,MAAM,SAAS,GAAG,aAAa,CAAC,KAAK,EAAE,WAAW,EAAE,YAAY,CAAC,CAAC;QAClE,MAAM,QAAQ,GAAG,cAAc,CAAC,IAAI,CAAC,CAAC;QACtC,MAAM,SAAS,GAAG,gBAAgB,CAAC,IAAI,CAAC,CAAC;QAEzC,MAAM,SAAS,GACb,OAAO,IAAI,CAAC,SAAS,KAAK,QAAQ;YAChC,CAAC,CAAC,IAAI,CAAC,SAAS;YAChB,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC;QAEpC,OAAO;YACL,SAAS;YACT,SAAS;YACT,KAAK;YACL,QAAQ;YACR,WAAW;YACX,YAAY;YACZ,SAAS;SACV,CAAC;IACJ,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC"}
@@ -0,0 +1,8 @@
1
+ import { AgentConfig } from "./config";
2
+ import { ParsedMetric } from "./parser";
3
+ export interface Sender {
4
+ send(metrics: ParsedMetric[]): void;
5
+ close(): void;
6
+ }
7
+ export declare function createSender(config: AgentConfig): Sender;
8
+ //# sourceMappingURL=sender.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sender.d.ts","sourceRoot":"","sources":["../src/sender.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AACvC,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAExC,MAAM,WAAW,MAAM;IACrB,IAAI,CAAC,OAAO,EAAE,YAAY,EAAE,GAAG,IAAI,CAAC;IACpC,KAAK,IAAI,IAAI,CAAC;CACf;AAED,wBAAgB,YAAY,CAAC,MAAM,EAAE,WAAW,GAAG,MAAM,CAyGxD"}
package/dist/sender.js ADDED
@@ -0,0 +1,101 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.createSender = createSender;
7
+ const ws_1 = __importDefault(require("ws"));
8
+ function createSender(config) {
9
+ let ws = null;
10
+ let reconnectAttempts = 0;
11
+ let reconnectTimer = null;
12
+ let batchTimer = null;
13
+ let batch = [];
14
+ let closed = false;
15
+ function connect() {
16
+ if (closed)
17
+ return;
18
+ const url = `${config.backendUrl}?api_key=${config.apiKey}`;
19
+ ws = new ws_1.default(url);
20
+ ws.on("open", () => {
21
+ console.log("[sender] Connected to backend");
22
+ reconnectAttempts = 0;
23
+ });
24
+ ws.on("message", (data) => {
25
+ try {
26
+ const msg = JSON.parse(data.toString());
27
+ if (msg.error) {
28
+ console.error("[sender] Server error:", msg.error);
29
+ }
30
+ }
31
+ catch {
32
+ // Ignore unparseable messages
33
+ }
34
+ });
35
+ ws.on("close", () => {
36
+ console.log("[sender] Connection closed");
37
+ ws = null;
38
+ scheduleReconnect();
39
+ });
40
+ ws.on("error", (err) => {
41
+ console.error("[sender] Connection error:", err.message);
42
+ ws?.close();
43
+ });
44
+ }
45
+ function scheduleReconnect() {
46
+ if (closed)
47
+ return;
48
+ if (reconnectAttempts >= config.maxReconnectAttempts) {
49
+ console.error(`[sender] Max reconnect attempts (${config.maxReconnectAttempts}) reached. Giving up.`);
50
+ return;
51
+ }
52
+ const delay = config.reconnectInterval * Math.pow(2, reconnectAttempts);
53
+ reconnectAttempts++;
54
+ console.log(`[sender] Reconnecting in ${delay}ms (attempt ${reconnectAttempts}/${config.maxReconnectAttempts})`);
55
+ reconnectTimer = setTimeout(() => {
56
+ connect();
57
+ }, delay);
58
+ }
59
+ function flush() {
60
+ if (batch.length === 0)
61
+ return;
62
+ if (!ws || ws.readyState !== ws_1.default.OPEN)
63
+ return;
64
+ const toSend = batch.splice(0, batch.length);
65
+ for (const metric of toSend) {
66
+ const message = JSON.stringify({
67
+ type: "metric",
68
+ session_id: metric.sessionId,
69
+ model: metric.model,
70
+ category: metric.category,
71
+ input_tokens: metric.inputTokens,
72
+ output_tokens: metric.outputTokens,
73
+ cost_cents: metric.costCents,
74
+ timestamp: metric.timestamp,
75
+ });
76
+ ws.send(message);
77
+ }
78
+ }
79
+ // Start batch flush interval
80
+ batchTimer = setInterval(flush, config.batchInterval);
81
+ // Initial connection
82
+ connect();
83
+ return {
84
+ send(metrics) {
85
+ batch.push(...metrics);
86
+ if (batch.length >= config.batchSize) {
87
+ flush();
88
+ }
89
+ },
90
+ close() {
91
+ closed = true;
92
+ if (reconnectTimer)
93
+ clearTimeout(reconnectTimer);
94
+ if (batchTimer)
95
+ clearInterval(batchTimer);
96
+ flush();
97
+ ws?.close();
98
+ },
99
+ };
100
+ }
101
+ //# sourceMappingURL=sender.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sender.js","sourceRoot":"","sources":["../src/sender.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ import * as chokidar from "chokidar";
2
+ import { ParsedMetric } from "./parser";
3
+ type MetricsCallback = (metrics: ParsedMetric[]) => void;
4
+ export declare function createWatcher(logDir: string, onMetrics: MetricsCallback): chokidar.FSWatcher;
5
+ export {};
6
+ //# sourceMappingURL=watcher.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"watcher.d.ts","sourceRoot":"","sources":["../src/watcher.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,QAAQ,MAAM,UAAU,CAAC;AACrC,OAAO,EAAkB,YAAY,EAAE,MAAM,UAAU,CAAC;AAExD,KAAK,eAAe,GAAG,CAAC,OAAO,EAAE,YAAY,EAAE,KAAK,IAAI,CAAC;AA4CzD,wBAAgB,aAAa,CAC3B,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,eAAe,GACzB,QAAQ,CAAC,SAAS,CA4BpB"}
@@ -0,0 +1,103 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.createWatcher = createWatcher;
40
+ const fs_1 = __importDefault(require("fs"));
41
+ const path_1 = __importDefault(require("path"));
42
+ const chokidar = __importStar(require("chokidar"));
43
+ const parser_1 = require("./parser");
44
+ const filePositions = new Map();
45
+ function readNewLines(filePath) {
46
+ const currentPos = filePositions.get(filePath) ?? 0;
47
+ let stat;
48
+ try {
49
+ stat = fs_1.default.statSync(filePath);
50
+ }
51
+ catch {
52
+ return [];
53
+ }
54
+ if (stat.size <= currentPos)
55
+ return [];
56
+ const fd = fs_1.default.openSync(filePath, "r");
57
+ const buffer = Buffer.alloc(stat.size - currentPos);
58
+ fs_1.default.readSync(fd, buffer, 0, buffer.length, currentPos);
59
+ fs_1.default.closeSync(fd);
60
+ filePositions.set(filePath, stat.size);
61
+ const content = buffer.toString("utf-8");
62
+ return content.split("\n").filter((line) => line.trim().length > 0);
63
+ }
64
+ function processFile(filePath, onMetrics) {
65
+ const lines = readNewLines(filePath);
66
+ if (lines.length === 0)
67
+ return;
68
+ const metrics = [];
69
+ for (const line of lines) {
70
+ const metric = (0, parser_1.parseJsonlLine)(line);
71
+ if (metric) {
72
+ metrics.push(metric);
73
+ }
74
+ }
75
+ if (metrics.length > 0) {
76
+ onMetrics(metrics);
77
+ }
78
+ }
79
+ function createWatcher(logDir, onMetrics) {
80
+ const globPattern = path_1.default.join(logDir, "**", "*.jsonl");
81
+ const watcher = chokidar.watch(globPattern, {
82
+ persistent: true,
83
+ ignoreInitial: false,
84
+ awaitWriteFinish: {
85
+ stabilityThreshold: 200,
86
+ pollInterval: 100,
87
+ },
88
+ });
89
+ watcher.on("add", (filePath) => {
90
+ console.log(`[watcher] Tracking new file: ${filePath}`);
91
+ filePositions.set(filePath, 0);
92
+ processFile(filePath, onMetrics);
93
+ });
94
+ watcher.on("change", (filePath) => {
95
+ processFile(filePath, onMetrics);
96
+ });
97
+ watcher.on("error", (error) => {
98
+ const msg = error instanceof Error ? error.message : String(error);
99
+ console.error("[watcher] Error:", msg);
100
+ });
101
+ return watcher;
102
+ }
103
+ //# sourceMappingURL=watcher.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"watcher.js","sourceRoot":"","sources":["../src/watcher.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,27 @@
1
+ {
2
+ "name": "@burnguard/agent",
3
+ "version": "1.0.0",
4
+ "description": "BurnGuard local agent - monitors OpenClaw session logs",
5
+ "main": "dist/index.js",
6
+ "bin": {
7
+ "burnguard": "dist/index.js"
8
+ },
9
+ "files": [
10
+ "dist"
11
+ ],
12
+ "scripts": {
13
+ "dev": "tsx watch src/index.ts",
14
+ "build": "tsc",
15
+ "start": "node dist/index.js"
16
+ },
17
+ "dependencies": {
18
+ "chokidar": "^4.0.3",
19
+ "ws": "^8.18.0"
20
+ },
21
+ "devDependencies": {
22
+ "@types/node": "^22.13.1",
23
+ "@types/ws": "^8.5.14",
24
+ "tsx": "^4.19.2",
25
+ "typescript": "^5.7.3"
26
+ }
27
+ }