@aaa-sentinel/ai-usage 0.0.0-stage → 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.
package/dist/hook.js ADDED
@@ -0,0 +1,67 @@
1
+ // src/hook.ts
2
+ import { randomBytes } from "node:crypto";
3
+ import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
4
+ import { join } from "node:path";
5
+
6
+ // src/cursor.ts
7
+ var MODEL_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,79}$/;
8
+ var TOKEN_MAX = 1e12;
9
+ function finiteInt(value) {
10
+ if (typeof value !== "number" || !Number.isFinite(value) || value < 0 || value > TOKEN_MAX) return void 0;
11
+ const rounded = Math.round(value);
12
+ if (rounded > TOKEN_MAX) return void 0;
13
+ return rounded;
14
+ }
15
+ function parseStopPayload(payload2, at) {
16
+ if (!payload2 || typeof payload2 !== "object" || Array.isArray(payload2)) return null;
17
+ const record = payload2;
18
+ if (record["hook_event_name"] !== "stop") return null;
19
+ const modelRaw = record["model"];
20
+ const model = typeof modelRaw === "string" && MODEL_PATTERN.test(modelRaw) ? modelRaw : void 0;
21
+ const input = finiteInt(record["input_tokens"]);
22
+ const output = finiteInt(record["output_tokens"]);
23
+ const cacheRead = finiteInt(record["cache_read_tokens"]);
24
+ const cacheWrite = finiteInt(record["cache_write_tokens"]);
25
+ const event2 = { at, tokensKnown: false };
26
+ if (model) event2.model = model;
27
+ if (input !== void 0 && output !== void 0 && cacheRead !== void 0 && cacheWrite !== void 0) {
28
+ event2.tokensKnown = true;
29
+ event2.inputTokens = Math.max(0, input - cacheRead - cacheWrite);
30
+ event2.outputTokens = output;
31
+ event2.cacheReadTokens = cacheRead;
32
+ event2.cacheWriteTokens = cacheWrite;
33
+ }
34
+ return event2;
35
+ }
36
+ function serializeSpoolEvent(event2) {
37
+ const line = { at: event2.at, tokensKnown: event2.tokensKnown };
38
+ if (event2.model) line["model"] = event2.model;
39
+ if (event2.tokensKnown) {
40
+ line["inputTokens"] = event2.inputTokens ?? 0;
41
+ line["outputTokens"] = event2.outputTokens ?? 0;
42
+ line["cacheReadTokens"] = event2.cacheReadTokens ?? 0;
43
+ line["cacheWriteTokens"] = event2.cacheWriteTokens ?? 0;
44
+ }
45
+ return JSON.stringify(line);
46
+ }
47
+
48
+ // src/hook.ts
49
+ var spool = process.argv[2];
50
+ if (!spool) process.exit(0);
51
+ var payload;
52
+ try {
53
+ payload = JSON.parse(readFileSync(0, "utf8"));
54
+ } catch {
55
+ process.exit(0);
56
+ }
57
+ var event = parseStopPayload(payload, (/* @__PURE__ */ new Date()).toISOString());
58
+ if (!event) process.exit(0);
59
+ try {
60
+ mkdirSync(spool, { recursive: true, mode: 448 });
61
+ writeFileSync(join(spool, `${Date.now()}-${randomBytes(4).toString("hex")}.json`), serializeSpoolEvent(event), {
62
+ flag: "wx",
63
+ mode: 384
64
+ });
65
+ } catch {
66
+ process.exit(0);
67
+ }
package/package.json CHANGED
@@ -1,6 +1,42 @@
1
- {
2
- "name": "@aaa-sentinel/ai-usage",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
1
+ {
2
+ "name": "@aaa-sentinel/ai-usage",
3
+ "version": "0.1.0",
4
+ "description": "Report individual Cursor, Claude Code, Copilot and Codex usage to AAA Sentinel; prompts and tokens stay on the laptop",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "bin": {
8
+ "sentinel-usage": "dist/cli.js"
9
+ },
10
+ "files": [
11
+ "dist"
12
+ ],
13
+ "keywords": [
14
+ "cursor",
15
+ "claude-code",
16
+ "github-copilot",
17
+ "codex",
18
+ "finops",
19
+ "ai-usage"
20
+ ],
21
+ "publishConfig": {
22
+ "access": "public"
23
+ },
24
+ "homepage": "https://app.aaa-sentinel.com/developer/ai-usage",
25
+ "bugs": {
26
+ "url": "https://app.aaa-sentinel.com/contact"
27
+ },
28
+ "engines": {
29
+ "node": ">=20"
30
+ },
31
+ "scripts": {
32
+ "build": "esbuild src/cli.ts --bundle --platform=node --format=esm --target=node20 --outfile=dist/cli.js --banner:js=\"#!/usr/bin/env node\" && esbuild src/hook.ts --bundle --platform=node --format=esm --target=node20 --outfile=dist/hook.js",
33
+ "prepublishOnly": "npm run build",
34
+ "test": "vitest run"
35
+ },
36
+ "devDependencies": {
37
+ "@types/node": "^22.0.0",
38
+ "esbuild": "^0.24.0",
39
+ "typescript": "^5.7.0",
40
+ "vitest": "^2.0.0"
41
+ }
42
+ }