@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 +58 -0
- package/dist/cli.js +14 -9
- package/dist/index.cjs +14 -9
- package/dist/index.d.cts +3 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +14 -9
- package/package.json +1 -1
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 =
|
|
504
|
-
const spanId =
|
|
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
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
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 =
|
|
585
|
-
const spanId =
|
|
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
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
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
package/dist/index.d.ts
CHANGED
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 =
|
|
545
|
-
const spanId =
|
|
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
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
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.
|
|
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",
|