@jagit/hook-claude-code 0.0.1 → 0.0.3

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/index.d.ts CHANGED
@@ -20,5 +20,10 @@ interface TranscriptEntry {
20
20
  };
21
21
  };
22
22
  }
23
- export declare function buildPayload(stdin: StopStdin, read?: (path: string) => TranscriptEntry[]): AgentSessionPayload;
23
+ export declare function calculateBaseTokens(costUsd: number | null): number | null;
24
+ export declare function parseGitDiff(output: string): {
25
+ linesAdded: number;
26
+ linesRemoved: number;
27
+ };
28
+ export declare function buildPayload(stdin: StopStdin, read?: (path: string) => TranscriptEntry[], checkExists?: (path: string) => boolean, readFile?: (path: string, enc: BufferEncoding) => string): AgentSessionPayload;
24
29
  export {};
package/dist/index.js CHANGED
@@ -1,8 +1,10 @@
1
1
  #!/usr/bin/env node
2
- import { readFileSync } from "node:fs";
2
+ import { readFileSync, existsSync, rmSync } from "node:fs";
3
+ import { execSync } from "node:child_process";
3
4
  import { realpathSync } from "node:fs";
4
5
  import { fileURLToPath } from "node:url";
5
6
  import { resolveGitUsername, reportSession } from "@jagit/agent-reporter";
7
+ import { createJiraWorklog } from "@jagit/shared";
6
8
  function readTranscript(path) {
7
9
  return readFileSync(path, "utf-8")
8
10
  .split("\n")
@@ -17,10 +19,58 @@ function readTranscript(path) {
17
19
  function hasToolUse(content) {
18
20
  return Array.isArray(content) && content.some((b) => b?.type === "tool_use");
19
21
  }
20
- export function buildPayload(stdin, read = readTranscript) {
22
+ export function calculateBaseTokens(costUsd) {
23
+ if (costUsd === null)
24
+ return null;
25
+ // Fixed rate: 1 USD = 4,000,000 BT
26
+ return costUsd * 4000000;
27
+ }
28
+ export function parseGitDiff(output) {
29
+ let linesAdded = 0;
30
+ let linesRemoved = 0;
31
+ const lines = output.split("\n").filter((l) => l.trim().length > 0);
32
+ for (const line of lines) {
33
+ const parts = line.split("\t");
34
+ if (parts.length >= 2) {
35
+ if (parts[0] !== "-")
36
+ linesAdded += parseInt(parts[0], 10);
37
+ if (parts[1] !== "-")
38
+ linesRemoved += parseInt(parts[1], 10);
39
+ }
40
+ }
41
+ return { linesAdded, linesRemoved };
42
+ }
43
+ function getLocFromCommit(cwd, initialCommitSha) {
44
+ try {
45
+ const output = execSync(`git diff --numstat ${initialCommitSha} HEAD`, {
46
+ cwd,
47
+ encoding: "utf-8",
48
+ stdio: ["pipe", "pipe", "pipe"],
49
+ });
50
+ return parseGitDiff(output);
51
+ }
52
+ catch (err) {
53
+ return undefined;
54
+ }
55
+ }
56
+ export function buildPayload(stdin, read = readTranscript, checkExists = existsSync, readFile = readFileSync) {
21
57
  const entries = read(stdin.transcript_path);
22
- let inputTokens = 0, cachedInputTokens = 0, outputTokens = 0, toolCallCount = 0;
58
+ let inputTokens = 0, cachedInputTokens = 0, cacheCreationInputTokens = 0, outputTokens = 0, toolCallCount = 0;
23
59
  let model = "unknown";
60
+ let initialCommitSha = null;
61
+ let durationMs = undefined;
62
+ const statePath = stdin.cwd ? `${stdin.cwd}/.jigit-session-${stdin.session_id}.json` : "";
63
+ if (statePath && checkExists(statePath)) {
64
+ try {
65
+ const stateRaw = readFile(statePath, "utf-8");
66
+ const state = JSON.parse(stateRaw);
67
+ if (state.initialCommitSha)
68
+ initialCommitSha = state.initialCommitSha;
69
+ if (state.totalDurationMs !== undefined)
70
+ durationMs = state.totalDurationMs;
71
+ }
72
+ catch { }
73
+ }
24
74
  for (const e of entries) {
25
75
  if (e.message?.role !== "assistant")
26
76
  continue;
@@ -31,11 +81,21 @@ export function buildPayload(stdin, read = readTranscript) {
31
81
  const u = e.message.usage;
32
82
  if (u) {
33
83
  inputTokens += u.input_tokens ?? 0;
34
- cachedInputTokens += (u.cache_read_input_tokens ?? 0) + (u.cache_creation_input_tokens ?? 0);
84
+ cachedInputTokens += u.cache_read_input_tokens ?? 0;
85
+ cacheCreationInputTokens += u.cache_creation_input_tokens ?? 0;
35
86
  outputTokens += u.output_tokens ?? 0;
36
87
  }
37
88
  }
38
89
  const startedAt = entries.find((e) => e.timestamp)?.timestamp ?? new Date().toISOString();
90
+ let linesAdded = undefined;
91
+ let linesRemoved = undefined;
92
+ if (stdin.cwd && initialCommitSha) {
93
+ const loc = getLocFromCommit(stdin.cwd, initialCommitSha);
94
+ if (loc) {
95
+ linesAdded = loc.linesAdded;
96
+ linesRemoved = loc.linesRemoved;
97
+ }
98
+ }
39
99
  return {
40
100
  tool: "claude-code",
41
101
  sessionId: stdin.session_id,
@@ -43,17 +103,58 @@ export function buildPayload(stdin, read = readTranscript) {
43
103
  model,
44
104
  inputTokens,
45
105
  cachedInputTokens,
106
+ cacheCreationInputTokens,
46
107
  outputTokens,
47
108
  costUsd: null,
48
109
  toolCallCount,
49
110
  startedAt,
111
+ initialCommitSha: initialCommitSha ?? undefined,
112
+ durationMs,
113
+ linesAdded,
114
+ linesRemoved,
50
115
  };
51
116
  }
52
117
  async function main() {
53
118
  try {
54
119
  const raw = readFileSync(0, "utf-8");
55
120
  const stdin = JSON.parse(raw);
56
- await reportSession(buildPayload(stdin));
121
+ const payload = buildPayload(stdin);
122
+ await reportSession(payload);
123
+ if (payload.durationMs && stdin.cwd) {
124
+ const statePath = `${stdin.cwd}/.jigit-session-${stdin.session_id}.json`;
125
+ if (existsSync(statePath)) {
126
+ const baseUrl = process.env.JAGIT_BASE_URL?.trim();
127
+ const apiKey = process.env.JAGIT_API_KEY?.trim();
128
+ if (baseUrl && apiKey) {
129
+ try {
130
+ const res = await fetch(`${baseUrl}/api/agent-sessions?sessionId=${stdin.session_id}`, {
131
+ headers: { "x-api-key": apiKey },
132
+ });
133
+ if (res.ok) {
134
+ const data = await res.json();
135
+ const session = data.rows?.[0];
136
+ if (session?.jiraTicketId && session.costUsd) {
137
+ const baseTokens = calculateBaseTokens(session.costUsd);
138
+ if (baseTokens) {
139
+ await createJiraWorklog({
140
+ ticketId: session.jiraTicketId,
141
+ durationMs: payload.durationMs,
142
+ baseTokens,
143
+ });
144
+ }
145
+ }
146
+ }
147
+ }
148
+ catch (err) {
149
+ console.error("[hook-claude-code] Failed to create worklog:", err);
150
+ }
151
+ }
152
+ try {
153
+ rmSync(statePath, { force: true });
154
+ }
155
+ catch { }
156
+ }
157
+ }
57
158
  }
58
159
  catch (err) {
59
160
  console.error("[hook-claude-code]", err instanceof Error ? err.message : err);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jagit/hook-claude-code",
3
- "version": "0.0.1",
3
+ "version": "0.0.3",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "jagit-hook-claude-code": "dist/index.js"
@@ -9,7 +9,8 @@
9
9
  "dist"
10
10
  ],
11
11
  "dependencies": {
12
- "@jagit/agent-reporter": "0.0.1"
12
+ "@jagit/shared": "^0.0.3",
13
+ "@jagit/agent-reporter": "0.0.3"
13
14
  },
14
15
  "devDependencies": {
15
16
  "@types/node": "^25.9.3",