@neatlogs/claude-code 0.1.0 → 0.1.2

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 ADDED
@@ -0,0 +1,58 @@
1
+ # @neatlogs/claude-code
2
+
3
+ Automatic observability for Claude Code sessions. Captures prompts, tool calls, thinking, and LLM responses as traces in Neatlogs via Claude Code hooks.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install -g @neatlogs/claude-code
9
+ ```
10
+
11
+ ## Setup
12
+
13
+ ```bash
14
+ neatlogs-claude-code setup --global --api-key YOUR_PROJECT_KEY
15
+ ```
16
+
17
+ This does two things:
18
+ 1. Saves your API key to `~/.config/neatlogs/config.json`
19
+ 2. Registers hooks in `~/.claude/settings.json` for all Claude Code lifecycle events
20
+
21
+ That's it. Restart Claude Code and all sessions are automatically traced.
22
+
23
+ ## What gets captured
24
+
25
+ Each Claude Code session produces a trace with:
26
+
27
+ - **Workflow span** — root span for the session
28
+ - **Turn spans** — one per user prompt (named with truncated prompt text)
29
+ - **Tool call spans** — Read, Edit, Bash, etc. with inputs, outputs, and duration
30
+ - **LLM spans** — model responses with token counts and thinking content
31
+ - **Subagent spans** — spawned agent work
32
+
33
+ ## Commands
34
+
35
+ ```bash
36
+ neatlogs-claude-code setup [--global|--project] [--api-key KEY] # Register hooks
37
+ neatlogs-claude-code hook # Process hook event (called by Claude Code)
38
+ neatlogs-claude-code --version # Print version
39
+ ```
40
+
41
+ ## Troubleshooting
42
+
43
+ **Hooks not firing:** Restart Claude Code after running setup.
44
+
45
+ **Permission error on install:** If you get EACCES, either use `nvm` (which handles permissions automatically) or configure npm to use a user-writable directory:
46
+ ```bash
47
+ npm config set prefix ~/.npm-global
48
+ echo 'export PATH="$HOME/.npm-global/bin:$PATH"' >> ~/.zshrc
49
+ source ~/.zshrc
50
+ ```
51
+
52
+ ## Uninstall
53
+
54
+ ```bash
55
+ npm uninstall -g @neatlogs/claude-code
56
+ ```
57
+
58
+ Hooks remain in `~/.claude/settings.json` but will be no-ops since the binary is gone. To clean them up, remove the `neatlogs-claude-code` entries from that file.
package/dist/cli.js CHANGED
@@ -7,6 +7,7 @@ import {
7
7
  import { appendFileSync as appendFileSync2 } from "fs";
8
8
 
9
9
  // src/trace-shipper.ts
10
+ import { createHash } from "crypto";
10
11
  import protobuf from "protobufjs";
11
12
 
12
13
  // src/package-info.ts
@@ -113,6 +114,10 @@ function generateTraceId() {
113
114
  function generateSpanId() {
114
115
  return randomBytes(8);
115
116
  }
117
+ function deterministicId(input, length) {
118
+ const hash = createHash("sha256").update(input).digest();
119
+ return new Uint8Array(hash.buffer, hash.byteOffset, length);
120
+ }
116
121
  function bytesToHex(bytes) {
117
122
  return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
118
123
  }
@@ -500,8 +505,8 @@ function ensureSessionTrace(payload, config, shipper) {
500
505
  const model = readState(`model_${payload.session_id}`) || void 0;
501
506
  const workflowName = deriveWorkflowName(payload);
502
507
  writeState(`workflow_${payload.session_id}`, workflowName);
503
- const traceId = generateTraceId();
504
- const spanId = generateSpanId();
508
+ const traceId = deterministicId(payload.session_id, 16);
509
+ const spanId = deterministicId(payload.session_id + ":root", 8);
505
510
  const now = nowNanoString();
506
511
  const rootSpan = {
507
512
  traceId,
@@ -518,6 +523,7 @@ function ensureSessionTrace(payload, config, shipper) {
518
523
  attrString("neatlogs.user_id", config.userId),
519
524
  attrString("neatlogs.session_id", payload.session_id),
520
525
  attrString("neatlogs.workflow_name", workflowName),
526
+ attrString("neatlogs.llm.effort_level", payload.effort?.level),
521
527
  attrString("neatlogs.input.value", payload.prompt),
522
528
  attrString("cwd", payload.cwd),
523
529
  { key: "neatlogs.sdk.name", value: { stringValue: PACKAGE_NAME } },
@@ -621,6 +627,7 @@ function handleUserPromptSubmit(payload, config, shipper) {
621
627
  endTimeUnixNano: now,
622
628
  attributes: [
623
629
  { key: "neatlogs.span.kind", value: { stringValue: "CHAIN" } },
630
+ attrString("neatlogs.llm.effort_level", payload.effort?.level),
624
631
  attrString("neatlogs.input.value", payload.prompt),
625
632
  attrString("cwd", payload.cwd)
626
633
  ].filter((a) => a !== void 0)
@@ -652,13 +659,11 @@ function handlePostToolUse(payload, config, shipper, error) {
652
659
  error ? { code: SpanStatusCode.ERROR, message: error } : void 0
653
660
  );
654
661
  shipper.enqueue(span);
655
- if (payload.permission_mode === "plan") {
656
- const sessionCtx = getSessionTrace(payload.session_id);
657
- if (sessionCtx) {
658
- const markerNow = nowNanoString();
659
- const marker = createSpan("neatlogs.trace.complete", sessionCtx, markerNow, markerNow, []);
660
- shipper.enqueue(marker);
661
- }
662
+ const sessionCtx = getSessionTrace(payload.session_id);
663
+ if (sessionCtx) {
664
+ const markerNow = nowNanoString();
665
+ const marker = createSpan("neatlogs.trace.complete", sessionCtx, markerNow, markerNow, []);
666
+ shipper.enqueue(marker);
662
667
  }
663
668
  }
664
669
  function handleSubagentStart(payload, config, shipper) {
package/dist/index.cjs CHANGED
@@ -88,6 +88,7 @@ function updateConfig(patch) {
88
88
  }
89
89
 
90
90
  // src/trace-shipper.ts
91
+ var import_crypto = require("crypto");
91
92
  var import_protobufjs = __toESM(require("protobufjs"), 1);
92
93
 
93
94
  // src/package-info.ts
@@ -194,6 +195,10 @@ function generateTraceId() {
194
195
  function generateSpanId() {
195
196
  return randomBytes(8);
196
197
  }
198
+ function deterministicId(input, length) {
199
+ const hash = (0, import_crypto.createHash)("sha256").update(input).digest();
200
+ return new Uint8Array(hash.buffer, hash.byteOffset, length);
201
+ }
197
202
  function bytesToHex(bytes) {
198
203
  return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
199
204
  }
@@ -581,8 +586,8 @@ function ensureSessionTrace(payload, config, shipper) {
581
586
  const model = readState(`model_${payload.session_id}`) || void 0;
582
587
  const workflowName = deriveWorkflowName(payload);
583
588
  writeState(`workflow_${payload.session_id}`, workflowName);
584
- const traceId = generateTraceId();
585
- const spanId = generateSpanId();
589
+ const traceId = deterministicId(payload.session_id, 16);
590
+ const spanId = deterministicId(payload.session_id + ":root", 8);
586
591
  const now = nowNanoString();
587
592
  const rootSpan = {
588
593
  traceId,
@@ -599,6 +604,7 @@ function ensureSessionTrace(payload, config, shipper) {
599
604
  attrString("neatlogs.user_id", config.userId),
600
605
  attrString("neatlogs.session_id", payload.session_id),
601
606
  attrString("neatlogs.workflow_name", workflowName),
607
+ attrString("neatlogs.llm.effort_level", payload.effort?.level),
602
608
  attrString("neatlogs.input.value", payload.prompt),
603
609
  attrString("cwd", payload.cwd),
604
610
  { key: "neatlogs.sdk.name", value: { stringValue: PACKAGE_NAME } },
@@ -702,6 +708,7 @@ function handleUserPromptSubmit(payload, config, shipper) {
702
708
  endTimeUnixNano: now,
703
709
  attributes: [
704
710
  { key: "neatlogs.span.kind", value: { stringValue: "CHAIN" } },
711
+ attrString("neatlogs.llm.effort_level", payload.effort?.level),
705
712
  attrString("neatlogs.input.value", payload.prompt),
706
713
  attrString("cwd", payload.cwd)
707
714
  ].filter((a) => a !== void 0)
@@ -733,13 +740,11 @@ function handlePostToolUse(payload, config, shipper, error) {
733
740
  error ? { code: SpanStatusCode.ERROR, message: error } : void 0
734
741
  );
735
742
  shipper.enqueue(span);
736
- if (payload.permission_mode === "plan") {
737
- const sessionCtx = getSessionTrace(payload.session_id);
738
- if (sessionCtx) {
739
- const markerNow = nowNanoString();
740
- const marker = createSpan("neatlogs.trace.complete", sessionCtx, markerNow, markerNow, []);
741
- shipper.enqueue(marker);
742
- }
743
+ const sessionCtx = getSessionTrace(payload.session_id);
744
+ if (sessionCtx) {
745
+ const markerNow = nowNanoString();
746
+ const marker = createSpan("neatlogs.trace.complete", sessionCtx, markerNow, markerNow, []);
747
+ shipper.enqueue(marker);
743
748
  }
744
749
  }
745
750
  function handleSubagentStart(payload, config, shipper) {
package/dist/index.d.cts CHANGED
@@ -31,6 +31,9 @@ interface HookPayload {
31
31
  tool_response?: unknown;
32
32
  duration_ms?: number;
33
33
  tool_use_id?: string;
34
+ effort?: {
35
+ level?: string;
36
+ };
34
37
  error?: string;
35
38
  is_interrupt?: boolean;
36
39
  last_assistant_message?: string;
package/dist/index.d.ts CHANGED
@@ -31,6 +31,9 @@ interface HookPayload {
31
31
  tool_response?: unknown;
32
32
  duration_ms?: number;
33
33
  tool_use_id?: string;
34
+ effort?: {
35
+ level?: string;
36
+ };
34
37
  error?: string;
35
38
  is_interrupt?: boolean;
36
39
  last_assistant_message?: string;
package/dist/index.js CHANGED
@@ -48,6 +48,7 @@ function updateConfig(patch) {
48
48
  }
49
49
 
50
50
  // src/trace-shipper.ts
51
+ import { createHash } from "crypto";
51
52
  import protobuf from "protobufjs";
52
53
 
53
54
  // src/package-info.ts
@@ -154,6 +155,10 @@ function generateTraceId() {
154
155
  function generateSpanId() {
155
156
  return randomBytes(8);
156
157
  }
158
+ function deterministicId(input, length) {
159
+ const hash = createHash("sha256").update(input).digest();
160
+ return new Uint8Array(hash.buffer, hash.byteOffset, length);
161
+ }
157
162
  function bytesToHex(bytes) {
158
163
  return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
159
164
  }
@@ -541,8 +546,8 @@ function ensureSessionTrace(payload, config, shipper) {
541
546
  const model = readState(`model_${payload.session_id}`) || void 0;
542
547
  const workflowName = deriveWorkflowName(payload);
543
548
  writeState(`workflow_${payload.session_id}`, workflowName);
544
- const traceId = generateTraceId();
545
- const spanId = generateSpanId();
549
+ const traceId = deterministicId(payload.session_id, 16);
550
+ const spanId = deterministicId(payload.session_id + ":root", 8);
546
551
  const now = nowNanoString();
547
552
  const rootSpan = {
548
553
  traceId,
@@ -559,6 +564,7 @@ function ensureSessionTrace(payload, config, shipper) {
559
564
  attrString("neatlogs.user_id", config.userId),
560
565
  attrString("neatlogs.session_id", payload.session_id),
561
566
  attrString("neatlogs.workflow_name", workflowName),
567
+ attrString("neatlogs.llm.effort_level", payload.effort?.level),
562
568
  attrString("neatlogs.input.value", payload.prompt),
563
569
  attrString("cwd", payload.cwd),
564
570
  { key: "neatlogs.sdk.name", value: { stringValue: PACKAGE_NAME } },
@@ -662,6 +668,7 @@ function handleUserPromptSubmit(payload, config, shipper) {
662
668
  endTimeUnixNano: now,
663
669
  attributes: [
664
670
  { key: "neatlogs.span.kind", value: { stringValue: "CHAIN" } },
671
+ attrString("neatlogs.llm.effort_level", payload.effort?.level),
665
672
  attrString("neatlogs.input.value", payload.prompt),
666
673
  attrString("cwd", payload.cwd)
667
674
  ].filter((a) => a !== void 0)
@@ -693,13 +700,11 @@ function handlePostToolUse(payload, config, shipper, error) {
693
700
  error ? { code: SpanStatusCode.ERROR, message: error } : void 0
694
701
  );
695
702
  shipper.enqueue(span);
696
- if (payload.permission_mode === "plan") {
697
- const sessionCtx = getSessionTrace(payload.session_id);
698
- if (sessionCtx) {
699
- const markerNow = nowNanoString();
700
- const marker = createSpan("neatlogs.trace.complete", sessionCtx, markerNow, markerNow, []);
701
- shipper.enqueue(marker);
702
- }
703
+ const sessionCtx = getSessionTrace(payload.session_id);
704
+ if (sessionCtx) {
705
+ const markerNow = nowNanoString();
706
+ const marker = createSpan("neatlogs.trace.complete", sessionCtx, markerNow, markerNow, []);
707
+ shipper.enqueue(marker);
703
708
  }
704
709
  }
705
710
  function handleSubagentStart(payload, config, shipper) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@neatlogs/claude-code",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "Neatlogs observability for Claude Code sessions — automatic tracing of prompts, tool calls, thinking, and LLM responses via hooks",
5
5
  "license": "MIT",
6
6
  "type": "module",