session-intelligence-cli 0.1.3 → 0.1.4

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,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -0,0 +1,61 @@
1
+ #!/usr/bin/env node
2
+ import { readFileSync } from "node:fs";
3
+ import { readGitState, readGitDiffStats } from "./git.js";
4
+ import { sendEvent } from "./api.js";
5
+ // Called as: node report-bg.js <json-payload>
6
+ // Runs detached from the parent process to avoid being killed on session teardown.
7
+ const raw = process.argv[2];
8
+ if (!raw)
9
+ process.exit(0);
10
+ const stdinData = JSON.parse(raw);
11
+ const sessionId = stdinData.session_id ?? "";
12
+ const cwd = stdinData.cwd ?? process.cwd();
13
+ const payload = {
14
+ session_id: sessionId,
15
+ cwd,
16
+ hook_event_name: "SessionEnd",
17
+ model: stdinData.model,
18
+ };
19
+ const transcriptPath = stdinData.transcript_path;
20
+ if (transcriptPath) {
21
+ try {
22
+ const content = readFileSync(transcriptPath, "utf-8");
23
+ const usage = { input_tokens: 0, output_tokens: 0, cache_read_tokens: 0, cache_creation_tokens: 0 };
24
+ for (const line of content.split("\n")) {
25
+ const trimmed = line.trim();
26
+ if (!trimmed)
27
+ continue;
28
+ try {
29
+ const entry = JSON.parse(trimmed);
30
+ if (entry.type !== "assistant" || !entry.message?.usage)
31
+ continue;
32
+ const u = entry.message.usage;
33
+ usage.input_tokens += u.input_tokens ?? 0;
34
+ usage.output_tokens += u.output_tokens ?? 0;
35
+ usage.cache_read_tokens += u.cache_read_input_tokens ?? 0;
36
+ usage.cache_creation_tokens += u.cache_creation_input_tokens ?? 0;
37
+ }
38
+ catch {
39
+ // skip malformed lines
40
+ }
41
+ }
42
+ payload.usage = usage;
43
+ }
44
+ catch {
45
+ // transcript unreadable
46
+ }
47
+ }
48
+ const git = readGitState(cwd);
49
+ if (git) {
50
+ const stats = readGitDiffStats(cwd);
51
+ payload.git = {
52
+ branch: git.branch,
53
+ lastCommitSha: git.lastCommitSha,
54
+ lastCommitMessage: git.lastCommitMessage,
55
+ hasUncommittedChanges: git.hasUncommittedChanges,
56
+ filesChanged: stats.filesChanged,
57
+ insertions: stats.insertions,
58
+ deletions: stats.deletions,
59
+ };
60
+ }
61
+ await sendEvent(payload);
package/dist/report.js CHANGED
@@ -1,4 +1,6 @@
1
- import { readFileSync } from "node:fs";
1
+ import { spawn } from "node:child_process";
2
+ import { fileURLToPath } from "node:url";
3
+ import { dirname, join } from "node:path";
2
4
  import { readGitState, readGitDiffStats } from "./git.js";
3
5
  import { sendEvent } from "./api.js";
4
6
  const EVENT_MAP = {
@@ -9,47 +11,35 @@ const EVENT_MAP = {
9
11
  "notification": "Notification",
10
12
  "subagent-stop": "SubagentStop",
11
13
  };
12
- function parseTranscriptTokens(transcriptPath) {
13
- const zero = { input_tokens: 0, output_tokens: 0, cache_read_tokens: 0, cache_creation_tokens: 0 };
14
- try {
15
- const content = readFileSync(transcriptPath, "utf-8");
16
- for (const line of content.split("\n")) {
17
- const trimmed = line.trim();
18
- if (!trimmed)
19
- continue;
14
+ function readStdin() {
15
+ return new Promise((resolve) => {
16
+ const chunks = [];
17
+ process.stdin.on("data", (chunk) => chunks.push(chunk));
18
+ process.stdin.on("end", () => {
20
19
  try {
21
- const entry = JSON.parse(trimmed);
22
- if (entry.type !== "assistant" || !entry.message?.usage)
23
- continue;
24
- const u = entry.message.usage;
25
- zero.input_tokens += u.input_tokens ?? 0;
26
- zero.output_tokens += u.output_tokens ?? 0;
27
- zero.cache_read_tokens += u.cache_read_input_tokens ?? 0;
28
- zero.cache_creation_tokens += u.cache_creation_input_tokens ?? 0;
20
+ const raw = Buffer.concat(chunks).toString("utf-8").trim();
21
+ resolve(raw ? JSON.parse(raw) : {});
29
22
  }
30
23
  catch {
31
- // skip malformed lines
24
+ resolve({});
32
25
  }
33
- }
34
- }
35
- catch {
36
- // file not found or unreadable
37
- }
38
- return zero;
26
+ });
27
+ process.stdin.on("error", () => resolve({}));
28
+ });
39
29
  }
40
30
  export async function report(eventType) {
41
- let stdinData = {};
42
- try {
43
- const chunks = [];
44
- for await (const chunk of process.stdin) {
45
- chunks.push(chunk);
46
- }
47
- const raw = Buffer.concat(chunks).toString("utf-8").trim();
48
- if (raw)
49
- stdinData = JSON.parse(raw);
50
- }
51
- catch {
52
- // No stdin or invalid JSON
31
+ const stdinData = await readStdin();
32
+ // For session-end, spawn a detached background process so the hook
33
+ // can exit immediately and avoid being killed during session teardown.
34
+ if (eventType === "session-end") {
35
+ const scriptDir = dirname(fileURLToPath(import.meta.url));
36
+ const bgScript = join(scriptDir, "report-bg.js");
37
+ const child = spawn(process.execPath, [bgScript, JSON.stringify(stdinData)], {
38
+ detached: true,
39
+ stdio: "ignore",
40
+ });
41
+ child.unref();
42
+ return;
53
43
  }
54
44
  const sessionId = stdinData.session_id ?? "";
55
45
  const cwd = stdinData.cwd ?? process.cwd();
@@ -60,12 +50,6 @@ export async function report(eventType) {
60
50
  model: stdinData.model,
61
51
  tool_name: stdinData.tool_name,
62
52
  };
63
- if (eventType === "session-end") {
64
- const transcriptPath = stdinData.transcript_path;
65
- if (transcriptPath) {
66
- payload.usage = parseTranscriptTokens(transcriptPath);
67
- }
68
- }
69
53
  const git = readGitState(cwd);
70
54
  if (git) {
71
55
  const gitPayload = {
@@ -74,7 +58,7 @@ export async function report(eventType) {
74
58
  lastCommitMessage: git.lastCommitMessage,
75
59
  hasUncommittedChanges: git.hasUncommittedChanges,
76
60
  };
77
- if (eventType === "stop" || eventType === "session-end") {
61
+ if (eventType === "stop") {
78
62
  const stats = readGitDiffStats(cwd);
79
63
  gitPayload.filesChanged = stats.filesChanged;
80
64
  gitPayload.insertions = stats.insertions;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "session-intelligence-cli",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
4
4
  "description": "Real-time dashboard for Claude Code sessions — track activity, git changes, token usage, and session history across all your projects",
5
5
  "type": "module",
6
6
  "license": "MIT",