@jagit/hook-claude-code 0.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.
- package/README.md +26 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.js +68 -0
- package/package.json +23 -0
package/README.md
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# @jagit/hook-claude-code
|
|
2
|
+
|
|
3
|
+
Reports per-session Claude Code usage to JaGit.
|
|
4
|
+
|
|
5
|
+
## Setup
|
|
6
|
+
|
|
7
|
+
Set in your shell rc:
|
|
8
|
+
|
|
9
|
+
export JAGIT_BASE_URL="https://your-jagit-host"
|
|
10
|
+
export JAGIT_API_KEY="<your DASHBOARD_API_TOKEN>"
|
|
11
|
+
|
|
12
|
+
Add to `~/.claude/settings.json` (or per-project `.claude/settings.json`):
|
|
13
|
+
|
|
14
|
+
{
|
|
15
|
+
"hooks": {
|
|
16
|
+
"Stop": [{
|
|
17
|
+
"matcher": "",
|
|
18
|
+
"hooks": [{ "type": "command", "command": "npx -y @jagit/hook-claude-code" }]
|
|
19
|
+
}]
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
No install needed — `npx -y` fetches on demand. For a permanent binary:
|
|
24
|
+
`npm i -g @jagit/hook-claude-code`, then use `jagit-hook-claude-code` as the command.
|
|
25
|
+
|
|
26
|
+
Identity defaults to `git config user.email`; override with `JAGIT_GIT_USERNAME`.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { type AgentSessionPayload } from "@jagit/agent-reporter";
|
|
3
|
+
interface StopStdin {
|
|
4
|
+
session_id: string;
|
|
5
|
+
transcript_path: string;
|
|
6
|
+
cwd?: string;
|
|
7
|
+
}
|
|
8
|
+
interface TranscriptEntry {
|
|
9
|
+
type?: string;
|
|
10
|
+
timestamp?: string;
|
|
11
|
+
message?: {
|
|
12
|
+
role?: string;
|
|
13
|
+
model?: string;
|
|
14
|
+
content?: unknown;
|
|
15
|
+
usage?: {
|
|
16
|
+
input_tokens?: number;
|
|
17
|
+
cache_read_input_tokens?: number;
|
|
18
|
+
cache_creation_input_tokens?: number;
|
|
19
|
+
output_tokens?: number;
|
|
20
|
+
};
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
export declare function buildPayload(stdin: StopStdin, read?: (path: string) => TranscriptEntry[]): AgentSessionPayload;
|
|
24
|
+
export {};
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { readFileSync } from "node:fs";
|
|
3
|
+
import { realpathSync } from "node:fs";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { resolveGitUsername, reportSession } from "@jagit/agent-reporter";
|
|
6
|
+
function readTranscript(path) {
|
|
7
|
+
return readFileSync(path, "utf-8")
|
|
8
|
+
.split("\n")
|
|
9
|
+
.filter((l) => l.trim().length > 0)
|
|
10
|
+
.map((l) => { try {
|
|
11
|
+
return JSON.parse(l);
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
return {};
|
|
15
|
+
} });
|
|
16
|
+
}
|
|
17
|
+
function hasToolUse(content) {
|
|
18
|
+
return Array.isArray(content) && content.some((b) => b?.type === "tool_use");
|
|
19
|
+
}
|
|
20
|
+
export function buildPayload(stdin, read = readTranscript) {
|
|
21
|
+
const entries = read(stdin.transcript_path);
|
|
22
|
+
let inputTokens = 0, cachedInputTokens = 0, outputTokens = 0, toolCallCount = 0;
|
|
23
|
+
let model = "unknown";
|
|
24
|
+
for (const e of entries) {
|
|
25
|
+
if (e.message?.role !== "assistant")
|
|
26
|
+
continue;
|
|
27
|
+
if (e.message.model)
|
|
28
|
+
model = e.message.model;
|
|
29
|
+
if (hasToolUse(e.message.content))
|
|
30
|
+
toolCallCount += 1;
|
|
31
|
+
const u = e.message.usage;
|
|
32
|
+
if (u) {
|
|
33
|
+
inputTokens += u.input_tokens ?? 0;
|
|
34
|
+
cachedInputTokens += (u.cache_read_input_tokens ?? 0) + (u.cache_creation_input_tokens ?? 0);
|
|
35
|
+
outputTokens += u.output_tokens ?? 0;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
const startedAt = entries.find((e) => e.timestamp)?.timestamp ?? new Date().toISOString();
|
|
39
|
+
return {
|
|
40
|
+
tool: "claude-code",
|
|
41
|
+
sessionId: stdin.session_id,
|
|
42
|
+
gitUsername: resolveGitUsername(stdin.cwd),
|
|
43
|
+
model,
|
|
44
|
+
inputTokens,
|
|
45
|
+
cachedInputTokens,
|
|
46
|
+
outputTokens,
|
|
47
|
+
costUsd: null,
|
|
48
|
+
toolCallCount,
|
|
49
|
+
startedAt,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
async function main() {
|
|
53
|
+
try {
|
|
54
|
+
const raw = readFileSync(0, "utf-8");
|
|
55
|
+
const stdin = JSON.parse(raw);
|
|
56
|
+
await reportSession(buildPayload(stdin));
|
|
57
|
+
}
|
|
58
|
+
catch (err) {
|
|
59
|
+
console.error("[hook-claude-code]", err instanceof Error ? err.message : err);
|
|
60
|
+
}
|
|
61
|
+
finally {
|
|
62
|
+
process.exit(0);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
const isMain = import.meta.url.startsWith("file://") &&
|
|
66
|
+
realpathSync(fileURLToPath(import.meta.url)) === realpathSync(process.argv[1]);
|
|
67
|
+
if (isMain)
|
|
68
|
+
void main();
|
package/package.json
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@jagit/hook-claude-code",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"bin": {
|
|
6
|
+
"jagit-hook-claude-code": "dist/index.js"
|
|
7
|
+
},
|
|
8
|
+
"files": [
|
|
9
|
+
"dist"
|
|
10
|
+
],
|
|
11
|
+
"scripts": {
|
|
12
|
+
"build": "tsc -p tsconfig.json",
|
|
13
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
14
|
+
"test": "vitest run"
|
|
15
|
+
},
|
|
16
|
+
"dependencies": {
|
|
17
|
+
"@jagit/agent-reporter": "workspace:*"
|
|
18
|
+
},
|
|
19
|
+
"devDependencies": {
|
|
20
|
+
"@types/node": "^25.9.3",
|
|
21
|
+
"vitest": "^2.1.9"
|
|
22
|
+
}
|
|
23
|
+
}
|