claude-code-analytics 0.1.0__cp311-abi3-macosx_11_0_arm64.whl
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,690 @@
|
|
1
|
+
Metadata-Version: 2.4
|
2
|
+
Name: claude-code-analytics
|
3
|
+
Version: 0.1.0
|
4
|
+
Classifier: Development Status :: 4 - Beta
|
5
|
+
Classifier: Intended Audience :: Developers
|
6
|
+
Classifier: License :: OSI Approved :: MIT License
|
7
|
+
Classifier: Programming Language :: Python :: 3
|
8
|
+
Classifier: Programming Language :: Python :: 3.11
|
9
|
+
Classifier: Programming Language :: Python :: 3.12
|
10
|
+
Classifier: Programming Language :: Python :: 3.13
|
11
|
+
Classifier: Programming Language :: Rust
|
12
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
13
|
+
License-File: LICENSE
|
14
|
+
Summary: Python SDK for Claude Code with Rust core
|
15
|
+
Author-email: Darin Kishore <darinkishore@protonmail.com>
|
16
|
+
License: MIT
|
17
|
+
Requires-Python: >=3.11
|
18
|
+
Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
|
19
|
+
Project-URL: Homepage, https://github.com/darinkishore/rust_sdk
|
20
|
+
Project-URL: Documentation, https://github.com/darinkishore/rust_sdk#readme
|
21
|
+
Project-URL: Repository, https://github.com/darinkishore/rust_sdk.git
|
22
|
+
Project-URL: Issues, https://github.com/darinkishore/rust_sdk/issues
|
23
|
+
|
24
|
+
# Claude SDK for Python
|
25
|
+
|
26
|
+
A high-performance Python library for parsing and analyzing Claude Code session data. Built with Rust for speed, designed with Python developers in mind.
|
27
|
+
|
28
|
+
## Table of Contents
|
29
|
+
|
30
|
+
- [Installation](#installation)
|
31
|
+
- [Quick Start](#quick-start)
|
32
|
+
- [Core Concepts](#core-concepts)
|
33
|
+
- [API Reference](#api-reference)
|
34
|
+
- [Functions](#functions)
|
35
|
+
- [Classes](#classes)
|
36
|
+
- [Exceptions](#exceptions)
|
37
|
+
- [Examples](#examples)
|
38
|
+
- [Performance](#performance)
|
39
|
+
- [Troubleshooting](#troubleshooting)
|
40
|
+
- [Development](#development)
|
41
|
+
|
42
|
+
## Installation
|
43
|
+
|
44
|
+
### Prerequisites
|
45
|
+
|
46
|
+
- Python 3.8 or higher
|
47
|
+
- pip or uv package manager
|
48
|
+
|
49
|
+
### Install from PyPI (when published)
|
50
|
+
|
51
|
+
```bash
|
52
|
+
pip install claude-code-analytics
|
53
|
+
```
|
54
|
+
|
55
|
+
### Install from source
|
56
|
+
|
57
|
+
```bash
|
58
|
+
# Clone the repository
|
59
|
+
git clone https://github.com/darinkishore/claude-code-analytics.git
|
60
|
+
cd claude-code-analytics
|
61
|
+
|
62
|
+
# Or using uv (recommended)
|
63
|
+
uv pip install ./python
|
64
|
+
```
|
65
|
+
|
66
|
+
### Development installation
|
67
|
+
|
68
|
+
```bash
|
69
|
+
cd python
|
70
|
+
uv build
|
71
|
+
```
|
72
|
+
|
73
|
+
## Quick Start
|
74
|
+
|
75
|
+
```python
|
76
|
+
import claude_sdk
|
77
|
+
|
78
|
+
# Load a session from a JSONL file
|
79
|
+
session = claude_sdk.load("~/.claude/projects/myproject/session_20240101_120000.jsonl")
|
80
|
+
|
81
|
+
# Basic session info
|
82
|
+
print(f"Session ID: {session.session_id}")
|
83
|
+
print(f"Total cost: ${session.total_cost:.4f}")
|
84
|
+
print(f"Message count: {len(session.messages)}")
|
85
|
+
print(f"Tools used: {', '.join(session.tools_used)}")
|
86
|
+
|
87
|
+
# Iterate through messages
|
88
|
+
for message in session:
|
89
|
+
print(f"{message.role}: {message.text[:100]}...")
|
90
|
+
|
91
|
+
# Find all your sessions
|
92
|
+
sessions = claude_sdk.find_sessions()
|
93
|
+
for session_path in sessions:
|
94
|
+
print(f"Found session: {session_path}")
|
95
|
+
```
|
96
|
+
|
97
|
+
## Core Concepts
|
98
|
+
|
99
|
+
### Sessions
|
100
|
+
|
101
|
+
A **Session** represents a complete conversation with Claude, loaded from a JSONL file. Each session contains:
|
102
|
+
|
103
|
+
- Messages exchanged between user and assistant
|
104
|
+
- Tool executions and their results
|
105
|
+
- Token usage and cost information
|
106
|
+
- Conversation structure (including branches and sidechains)
|
107
|
+
- Metadata and statistics
|
108
|
+
|
109
|
+
### Messages
|
110
|
+
|
111
|
+
**Messages** are the individual exchanges in a conversation. Each message has:
|
112
|
+
|
113
|
+
- `role`: Either "user" or "assistant"
|
114
|
+
- `text`: The complete text content
|
115
|
+
- `tools`: List of tools used (if any)
|
116
|
+
- `cost`: Cost in USD for this specific message
|
117
|
+
- `timestamp`: When the message was created
|
118
|
+
- Threading information (`uuid`, `parent_uuid`)
|
119
|
+
|
120
|
+
### Projects
|
121
|
+
|
122
|
+
A **Project** is a collection of related sessions, typically stored in the same directory. Projects provide aggregate statistics across all sessions.
|
123
|
+
|
124
|
+
### Conversation Trees
|
125
|
+
|
126
|
+
The SDK automatically reconstructs the conversation structure, handling:
|
127
|
+
|
128
|
+
- Linear conversations
|
129
|
+
- Branching (when you retry or edit messages)
|
130
|
+
- Sidechains (alternate conversation paths)
|
131
|
+
- Orphaned messages (missing parents)
|
132
|
+
|
133
|
+
## API Reference
|
134
|
+
|
135
|
+
### Functions
|
136
|
+
|
137
|
+
#### `load(file_path: str | Path) -> Session`
|
138
|
+
|
139
|
+
Load a Claude Code session from a JSONL file.
|
140
|
+
|
141
|
+
```python
|
142
|
+
session = claude_sdk.load("path/to/session.jsonl")
|
143
|
+
```
|
144
|
+
|
145
|
+
**Parameters:**
|
146
|
+
- `file_path`: Path to the JSONL session file
|
147
|
+
|
148
|
+
**Returns:** `Session` object
|
149
|
+
|
150
|
+
**Raises:**
|
151
|
+
- `FileNotFoundError`: If the file doesn't exist
|
152
|
+
- `ParseError`: If the JSONL is malformed
|
153
|
+
- `ValidationError`: If the session data is invalid
|
154
|
+
|
155
|
+
#### `find_sessions(base_path: Optional[str] = None, project: Optional[str] = None) -> List[Path]`
|
156
|
+
|
157
|
+
Discover Claude Code session files.
|
158
|
+
|
159
|
+
```python
|
160
|
+
# Find all sessions
|
161
|
+
all_sessions = claude_sdk.find_sessions()
|
162
|
+
|
163
|
+
# Find sessions in a specific project
|
164
|
+
project_sessions = claude_sdk.find_sessions(project="myproject")
|
165
|
+
|
166
|
+
# Search in a custom location
|
167
|
+
custom_sessions = claude_sdk.find_sessions(base_path="/custom/path")
|
168
|
+
```
|
169
|
+
|
170
|
+
**Parameters:**
|
171
|
+
- `base_path`: Root directory to search (default: `~/.claude/projects/`)
|
172
|
+
- `project`: Filter by specific project name
|
173
|
+
|
174
|
+
**Returns:** List of `Path` objects to session files
|
175
|
+
|
176
|
+
#### `find_projects(base_path: Optional[str] = None) -> List[Path]`
|
177
|
+
|
178
|
+
Find all Claude Code projects.
|
179
|
+
|
180
|
+
```python
|
181
|
+
projects = claude_sdk.find_projects()
|
182
|
+
for project_path in projects:
|
183
|
+
print(f"Project: {project_path.name}")
|
184
|
+
```
|
185
|
+
|
186
|
+
**Parameters:**
|
187
|
+
- `base_path`: Root directory to search (default: `~/.claude/projects/`)
|
188
|
+
|
189
|
+
**Returns:** List of `Path` objects to project directories
|
190
|
+
|
191
|
+
#### `load_project(project_identifier: str | Path, base_path: Optional[str] = None) -> Project`
|
192
|
+
|
193
|
+
Load an entire project with all its sessions.
|
194
|
+
|
195
|
+
```python
|
196
|
+
# Load by project name
|
197
|
+
project = claude_sdk.load_project("myproject")
|
198
|
+
|
199
|
+
# Load by path
|
200
|
+
project = claude_sdk.load_project("/path/to/project")
|
201
|
+
|
202
|
+
print(f"Total sessions: {len(project.sessions)}")
|
203
|
+
print(f"Total cost: ${project.total_cost:.2f}")
|
204
|
+
```
|
205
|
+
|
206
|
+
**Parameters:**
|
207
|
+
- `project_identifier`: Project name or path
|
208
|
+
- `base_path`: Base path for project lookup (if using name)
|
209
|
+
|
210
|
+
**Returns:** `Project` object
|
211
|
+
|
212
|
+
**Raises:**
|
213
|
+
- `FileNotFoundError`: If project doesn't exist
|
214
|
+
- `SessionError`: If no valid sessions found
|
215
|
+
|
216
|
+
### Classes
|
217
|
+
|
218
|
+
#### Session
|
219
|
+
|
220
|
+
Primary container for Claude Code session data.
|
221
|
+
|
222
|
+
**Properties:**
|
223
|
+
|
224
|
+
| Property | Type | Description |
|
225
|
+
|----------|------|-------------|
|
226
|
+
| `session_id` | `str` | Unique session identifier |
|
227
|
+
| `messages` | `List[Message]` | All messages in conversation order |
|
228
|
+
| `total_cost` | `float` | Total cost in USD |
|
229
|
+
| `tools_used` | `List[str]` | Unique tool names used |
|
230
|
+
| `duration` | `Optional[float]` | Session duration in seconds |
|
231
|
+
| `conversation_tree` | `ConversationTree` | Message threading structure |
|
232
|
+
| `metadata` | `SessionMetadata` | Detailed statistics |
|
233
|
+
| `tool_executions` | `List[ToolExecution]` | All tool runs |
|
234
|
+
| `tool_costs` | `Dict[str, float]` | Cost breakdown by tool |
|
235
|
+
| `cost_by_turn` | `List[float]` | Cost per message |
|
236
|
+
| `project_path` | `Optional[Path]` | Project directory |
|
237
|
+
| `project_name` | `Optional[str]` | Project name |
|
238
|
+
|
239
|
+
**Methods:**
|
240
|
+
|
241
|
+
```python
|
242
|
+
# Get main conversation (excluding sidechains)
|
243
|
+
main_messages = session.get_main_chain()
|
244
|
+
|
245
|
+
# Filter by role
|
246
|
+
user_messages = session.get_messages_by_role("user")
|
247
|
+
assistant_messages = session.get_messages_by_role("assistant")
|
248
|
+
|
249
|
+
# Find messages using specific tools
|
250
|
+
bash_messages = session.get_messages_by_tool("bash")
|
251
|
+
|
252
|
+
# Get a specific message
|
253
|
+
message = session.get_message_by_uuid("msg-uuid-123")
|
254
|
+
|
255
|
+
# Custom filtering
|
256
|
+
long_messages = session.filter_messages(lambda m: len(m.text) > 1000)
|
257
|
+
|
258
|
+
# Get conversation thread
|
259
|
+
thread = session.get_thread("msg-uuid-789") # Returns path from root
|
260
|
+
|
261
|
+
# Iteration and length
|
262
|
+
for msg in session:
|
263
|
+
print(msg.text)
|
264
|
+
|
265
|
+
print(f"Total messages: {len(session)}")
|
266
|
+
```
|
267
|
+
|
268
|
+
#### Message
|
269
|
+
|
270
|
+
Represents a single message in the conversation.
|
271
|
+
|
272
|
+
**Properties:**
|
273
|
+
|
274
|
+
| Property | Type | Description |
|
275
|
+
|----------|------|-------------|
|
276
|
+
| `role` | `str` | "user" or "assistant" |
|
277
|
+
| `text` | `str` | Complete text content |
|
278
|
+
| `model` | `Optional[str]` | Model used (e.g., "claude-3-sonnet-20240229") |
|
279
|
+
| `cost` | `Optional[float]` | Cost in USD |
|
280
|
+
| `tools` | `List[str]` | Tool names used |
|
281
|
+
| `stop_reason` | `Optional[str]` | Why generation stopped |
|
282
|
+
| `usage` | `Optional[TokenUsage]` | Token usage details |
|
283
|
+
| `timestamp` | `str` | RFC3339 timestamp |
|
284
|
+
| `uuid` | `str` | Unique identifier |
|
285
|
+
| `parent_uuid` | `Optional[str]` | Parent message UUID |
|
286
|
+
| `is_sidechain` | `bool` | Whether part of a sidechain |
|
287
|
+
| `cwd` | `Optional[Path]` | Working directory |
|
288
|
+
| `total_tokens` | `int` | Total token count |
|
289
|
+
| `input_tokens` | `int` | Input token count |
|
290
|
+
| `output_tokens` | `int` | Output token count |
|
291
|
+
|
292
|
+
**Methods:**
|
293
|
+
|
294
|
+
```python
|
295
|
+
# Check for tool usage
|
296
|
+
if message.has_tool_use():
|
297
|
+
tools = message.get_tool_blocks()
|
298
|
+
for tool in tools:
|
299
|
+
print(f"Tool: {tool.name}, Input: {tool.input}")
|
300
|
+
|
301
|
+
# Get text content blocks
|
302
|
+
text_blocks = message.get_text_blocks()
|
303
|
+
|
304
|
+
# Get all content blocks with proper typing
|
305
|
+
for block in message.get_content_blocks():
|
306
|
+
if isinstance(block, claude_sdk.TextBlock):
|
307
|
+
print(f"Text: {block.text}")
|
308
|
+
elif isinstance(block, claude_sdk.ToolUseBlock):
|
309
|
+
print(f"Tool: {block.name}")
|
310
|
+
```
|
311
|
+
|
312
|
+
#### Project
|
313
|
+
|
314
|
+
Container for multiple sessions in a project.
|
315
|
+
|
316
|
+
**Properties:**
|
317
|
+
|
318
|
+
| Property | Type | Description |
|
319
|
+
|----------|------|-------------|
|
320
|
+
| `name` | `str` | Project name |
|
321
|
+
| `sessions` | `List[Session]` | All sessions in project |
|
322
|
+
| `total_cost` | `float` | Aggregate cost |
|
323
|
+
| `total_messages` | `int` | Total message count |
|
324
|
+
| `tool_usage_count` | `Dict[str, int]` | Tool usage frequency |
|
325
|
+
| `total_duration` | `Optional[float]` | Total time in seconds |
|
326
|
+
|
327
|
+
```python
|
328
|
+
project = claude_sdk.load_project("myproject")
|
329
|
+
|
330
|
+
# Analyze tool usage patterns
|
331
|
+
for tool, count in project.tool_usage_count.items():
|
332
|
+
avg_per_session = count / len(project.sessions)
|
333
|
+
print(f"{tool}: {count} uses ({avg_per_session:.1f} per session)")
|
334
|
+
|
335
|
+
# Find expensive sessions
|
336
|
+
expensive = [s for s in project.sessions if s.total_cost > 1.0]
|
337
|
+
```
|
338
|
+
|
339
|
+
#### ToolExecution
|
340
|
+
|
341
|
+
Complete record of a tool invocation.
|
342
|
+
|
343
|
+
**Properties:**
|
344
|
+
|
345
|
+
| Property | Type | Description |
|
346
|
+
|----------|------|-------------|
|
347
|
+
| `tool_name` | `str` | Name of the tool |
|
348
|
+
| `input` | `Dict[str, Any]` | Input parameters |
|
349
|
+
| `output` | `ToolResult` | Execution result |
|
350
|
+
| `duration_ms` | `Optional[int]` | Execution time |
|
351
|
+
| `timestamp` | `str` | When executed |
|
352
|
+
|
353
|
+
**Methods:**
|
354
|
+
|
355
|
+
```python
|
356
|
+
# Check success
|
357
|
+
if execution.is_success():
|
358
|
+
print(f"{execution.tool_name} completed in {execution.duration_ms}ms")
|
359
|
+
else:
|
360
|
+
print(f"Failed: {execution.output.stderr}")
|
361
|
+
```
|
362
|
+
|
363
|
+
#### ConversationTree
|
364
|
+
|
365
|
+
Tree structure representing conversation flow.
|
366
|
+
|
367
|
+
**Properties:**
|
368
|
+
|
369
|
+
| Property | Type | Description |
|
370
|
+
|----------|------|-------------|
|
371
|
+
| `root_messages` | `List[ConversationNode]` | Root nodes |
|
372
|
+
| `orphaned_messages` | `List[str]` | Messages with missing parents |
|
373
|
+
| `circular_references` | `List[str]` | Circular reference UUIDs |
|
374
|
+
| `stats` | `ConversationStats` | Tree statistics |
|
375
|
+
|
376
|
+
**Methods:**
|
377
|
+
|
378
|
+
```python
|
379
|
+
tree = session.conversation_tree
|
380
|
+
|
381
|
+
# Get tree metrics
|
382
|
+
print(f"Max depth: {tree.max_depth()}")
|
383
|
+
print(f"Branch points: {tree.count_branches()}")
|
384
|
+
|
385
|
+
# Traverse tree
|
386
|
+
def walk_tree(node, depth=0):
|
387
|
+
print(" " * depth + node.message.text[:50])
|
388
|
+
for child in node.children:
|
389
|
+
walk_tree(child, depth + 1)
|
390
|
+
|
391
|
+
for root in tree.root_messages:
|
392
|
+
walk_tree(root)
|
393
|
+
```
|
394
|
+
|
395
|
+
### Exceptions
|
396
|
+
|
397
|
+
```python
|
398
|
+
# Exception hierarchy
|
399
|
+
claude_sdk.ClaudeSDKError # Base exception
|
400
|
+
├── claude_sdk.ParseError # JSONL parsing failed
|
401
|
+
├── claude_sdk.ValidationError # Invalid data
|
402
|
+
└── claude_sdk.SessionError # Session-specific issues
|
403
|
+
|
404
|
+
# Example handling
|
405
|
+
try:
|
406
|
+
session = claude_sdk.load("session.jsonl")
|
407
|
+
except claude_sdk.ParseError as e:
|
408
|
+
print(f"Failed to parse: {e}")
|
409
|
+
except claude_sdk.ClaudeSDKError as e:
|
410
|
+
print(f"SDK error: {e}")
|
411
|
+
```
|
412
|
+
|
413
|
+
## Examples
|
414
|
+
|
415
|
+
### Basic Session Analysis
|
416
|
+
|
417
|
+
```python
|
418
|
+
import claude_sdk
|
419
|
+
|
420
|
+
# Load session
|
421
|
+
session = claude_sdk.load("session.jsonl")
|
422
|
+
|
423
|
+
# Print summary
|
424
|
+
print(f"Session: {session.session_id}")
|
425
|
+
print(f"Duration: {session.duration / 60:.1f} minutes" if session.duration else "Duration unknown")
|
426
|
+
print(f"Messages: {len(session)} ({len(session.get_messages_by_role('user'))} from user)")
|
427
|
+
print(f"Cost: ${session.total_cost:.4f}")
|
428
|
+
print(f"Tools: {', '.join(session.tools_used) or 'None'}")
|
429
|
+
|
430
|
+
# Analyze token usage
|
431
|
+
total_tokens = sum(msg.total_tokens for msg in session.messages)
|
432
|
+
print(f"Total tokens: {total_tokens:,}")
|
433
|
+
```
|
434
|
+
|
435
|
+
### Tool Usage Patterns
|
436
|
+
|
437
|
+
```python
|
438
|
+
import claude_sdk
|
439
|
+
from collections import defaultdict
|
440
|
+
|
441
|
+
session = claude_sdk.load("session.jsonl")
|
442
|
+
|
443
|
+
# Count tool usage by message
|
444
|
+
tool_messages = defaultdict(list)
|
445
|
+
for msg in session.messages:
|
446
|
+
if msg.has_tool_use():
|
447
|
+
for tool in msg.tools:
|
448
|
+
tool_messages[tool].append(msg)
|
449
|
+
|
450
|
+
# Print tool usage summary
|
451
|
+
for tool, messages in sorted(tool_messages.items()):
|
452
|
+
print(f"\n{tool}: {len(messages)} uses")
|
453
|
+
|
454
|
+
# Show first few uses
|
455
|
+
for msg in messages[:3]:
|
456
|
+
preview = msg.text[:100].replace('\n', ' ')
|
457
|
+
print(f" - {preview}...")
|
458
|
+
```
|
459
|
+
|
460
|
+
### Cost Analysis Across Projects
|
461
|
+
|
462
|
+
```python
|
463
|
+
import claude_sdk
|
464
|
+
|
465
|
+
# Find all projects
|
466
|
+
projects = claude_sdk.find_projects()
|
467
|
+
|
468
|
+
# Analyze costs
|
469
|
+
project_costs = []
|
470
|
+
for project_path in projects:
|
471
|
+
try:
|
472
|
+
project = claude_sdk.load_project(project_path)
|
473
|
+
project_costs.append((project.name, project.total_cost, len(project.sessions)))
|
474
|
+
except Exception as e:
|
475
|
+
print(f"Failed to load {project_path}: {e}")
|
476
|
+
|
477
|
+
# Sort by cost
|
478
|
+
project_costs.sort(key=lambda x: x[1], reverse=True)
|
479
|
+
|
480
|
+
# Print report
|
481
|
+
print("Project Cost Analysis")
|
482
|
+
print("-" * 50)
|
483
|
+
for name, cost, session_count in project_costs:
|
484
|
+
avg_cost = cost / session_count if session_count > 0 else 0
|
485
|
+
print(f"{name:20} ${cost:8.2f} ({session_count:3} sessions, avg ${avg_cost:.2f})")
|
486
|
+
```
|
487
|
+
|
488
|
+
### Conversation Flow Analysis
|
489
|
+
|
490
|
+
```python
|
491
|
+
import claude_sdk
|
492
|
+
|
493
|
+
session = claude_sdk.load("session.jsonl")
|
494
|
+
tree = session.conversation_tree
|
495
|
+
|
496
|
+
# Find branching points
|
497
|
+
for root in tree.root_messages:
|
498
|
+
def find_branches(node, path=[]):
|
499
|
+
current_path = path + [node.message.uuid]
|
500
|
+
|
501
|
+
if len(node.children) > 1:
|
502
|
+
print(f"\nBranch point at message {len(current_path)}:")
|
503
|
+
print(f" {node.message.text[:100]}...")
|
504
|
+
print(f" Branches into {len(node.children)} paths")
|
505
|
+
|
506
|
+
for child in node.children:
|
507
|
+
find_branches(child, current_path)
|
508
|
+
|
509
|
+
find_branches(root)
|
510
|
+
|
511
|
+
# Analyze sidechains
|
512
|
+
sidechain_messages = [m for m in session.messages if m.is_sidechain]
|
513
|
+
if sidechain_messages:
|
514
|
+
print(f"\nFound {len(sidechain_messages)} sidechain messages")
|
515
|
+
```
|
516
|
+
|
517
|
+
### Exporting Session Data
|
518
|
+
|
519
|
+
```python
|
520
|
+
import claude_sdk
|
521
|
+
import json
|
522
|
+
import csv
|
523
|
+
|
524
|
+
session = claude_sdk.load("session.jsonl")
|
525
|
+
|
526
|
+
# Export to JSON
|
527
|
+
export_data = {
|
528
|
+
"session_id": session.session_id,
|
529
|
+
"total_cost": session.total_cost,
|
530
|
+
"messages": [
|
531
|
+
{
|
532
|
+
"role": msg.role,
|
533
|
+
"text": msg.text,
|
534
|
+
"cost": msg.cost,
|
535
|
+
"timestamp": msg.timestamp,
|
536
|
+
"tools": msg.tools
|
537
|
+
}
|
538
|
+
for msg in session.messages
|
539
|
+
]
|
540
|
+
}
|
541
|
+
|
542
|
+
with open("session_export.json", "w") as f:
|
543
|
+
json.dump(export_data, f, indent=2)
|
544
|
+
|
545
|
+
# Export tool usage to CSV
|
546
|
+
with open("tool_usage.csv", "w", newline="") as f:
|
547
|
+
writer = csv.writer(f)
|
548
|
+
writer.writerow(["Timestamp", "Tool", "Duration (ms)", "Success"])
|
549
|
+
|
550
|
+
for exec in session.tool_executions:
|
551
|
+
writer.writerow([
|
552
|
+
exec.timestamp,
|
553
|
+
exec.tool_name,
|
554
|
+
exec.duration_ms or "N/A",
|
555
|
+
exec.is_success()
|
556
|
+
])
|
557
|
+
```
|
558
|
+
|
559
|
+
## Performance
|
560
|
+
|
561
|
+
The Claude SDK is built with Rust for exceptional performance:
|
562
|
+
|
563
|
+
- **Parsing speed**: 1000+ messages per second
|
564
|
+
- **Memory efficient**: Streaming parser for large files
|
565
|
+
- **Zero-copy strings**: Minimal memory allocation
|
566
|
+
- **Thread safe**: Can be used in multi-threaded applications
|
567
|
+
|
568
|
+
### Benchmarks
|
569
|
+
|
570
|
+
| File Size | Messages | Parse Time | Memory Usage |
|
571
|
+
|-----------|----------|------------|--------------|
|
572
|
+
| 100 KB | 50 | <10ms | 2 MB |
|
573
|
+
| 1 MB | 500 | <50ms | 8 MB |
|
574
|
+
| 10 MB | 5000 | <300ms | 35 MB |
|
575
|
+
| 100 MB | 50000 | <3s | 350 MB |
|
576
|
+
|
577
|
+
## Troubleshooting
|
578
|
+
|
579
|
+
### Common Issues
|
580
|
+
|
581
|
+
#### ImportError: No module named 'claude_sdk'
|
582
|
+
|
583
|
+
**Solution**: Ensure you've installed the package:
|
584
|
+
```bash
|
585
|
+
pip install claude-code-analytics
|
586
|
+
# or for development
|
587
|
+
uv build
|
588
|
+
```
|
589
|
+
|
590
|
+
#### FileNotFoundError when loading sessions
|
591
|
+
|
592
|
+
**Solution**: Check the file path and ensure you have read permissions:
|
593
|
+
```python
|
594
|
+
import os
|
595
|
+
path = os.path.expanduser("~/.claude/projects/myproject/session.jsonl")
|
596
|
+
if os.path.exists(path):
|
597
|
+
session = claude_sdk.load(path)
|
598
|
+
```
|
599
|
+
|
600
|
+
#### ParseError: Invalid JSONL format
|
601
|
+
|
602
|
+
**Solution**: Ensure the file is a valid Claude Code session file:
|
603
|
+
```bash
|
604
|
+
# Check first few lines
|
605
|
+
head -n 5 session.jsonl
|
606
|
+
|
607
|
+
# Validate JSON
|
608
|
+
python -m json.tool session.jsonl
|
609
|
+
```
|
610
|
+
|
611
|
+
#### High memory usage with large files
|
612
|
+
|
613
|
+
**Solution**: Process sessions in batches:
|
614
|
+
```python
|
615
|
+
# Instead of loading all sessions at once
|
616
|
+
sessions = []
|
617
|
+
for path in claude_sdk.find_sessions(project="large_project"):
|
618
|
+
session = claude_sdk.load(path)
|
619
|
+
# Process session
|
620
|
+
del session # Free memory
|
621
|
+
```
|
622
|
+
|
623
|
+
### Debug Mode
|
624
|
+
|
625
|
+
Enable detailed logging for troubleshooting:
|
626
|
+
|
627
|
+
```python
|
628
|
+
import logging
|
629
|
+
logging.basicConfig(level=logging.DEBUG)
|
630
|
+
|
631
|
+
# Now SDK operations will print debug info
|
632
|
+
session = claude_sdk.load("session.jsonl")
|
633
|
+
```
|
634
|
+
|
635
|
+
## Development
|
636
|
+
|
637
|
+
### Building from source
|
638
|
+
|
639
|
+
```bash
|
640
|
+
# Clone repository
|
641
|
+
git clone https://github.com/yourusername/claude-code-analytics.git
|
642
|
+
cd claude-code-analytics
|
643
|
+
|
644
|
+
# Build Rust library
|
645
|
+
cargo build --release
|
646
|
+
|
647
|
+
# Build Python package
|
648
|
+
uv build
|
649
|
+
```
|
650
|
+
|
651
|
+
### Running tests
|
652
|
+
|
653
|
+
```bash
|
654
|
+
# Rust tests
|
655
|
+
cargo test
|
656
|
+
|
657
|
+
# Python tests
|
658
|
+
uv build
|
659
|
+
uv run -m pytest tests/
|
660
|
+
```
|
661
|
+
|
662
|
+
The Python test suite includes fixtures for malformed JSONL and a multi-megabyte
|
663
|
+
session to ensure `ParseError` is raised correctly and large files load
|
664
|
+
successfully.
|
665
|
+
|
666
|
+
### Contributing
|
667
|
+
|
668
|
+
1. Fork the repository
|
669
|
+
2. Create a feature branch (`git checkout -b feature/amazing-feature`)
|
670
|
+
3. Commit your changes (`git commit -m 'Add amazing feature'`)
|
671
|
+
4. Push to the branch (`git push origin feature/amazing-feature`)
|
672
|
+
5. Open a Pull Request
|
673
|
+
|
674
|
+
## License
|
675
|
+
|
676
|
+
This project is licensed under the MIT License - see the LICENSE file for details.
|
677
|
+
|
678
|
+
## Acknowledgments
|
679
|
+
|
680
|
+
Built with:
|
681
|
+
- [PyO3](https://pyo3.rs/) - Rust bindings for Python
|
682
|
+
- [Maturin](https://maturin.rs/) - Build and publish Rust Python extensions
|
683
|
+
- [Serde](https://serde.rs/) - Serialization framework for Rust
|
684
|
+
|
685
|
+
## Support
|
686
|
+
|
687
|
+
- **Issues**: [GitHub Issues](https://github.com/yourusername/claude-code-analytics/issues)
|
688
|
+
- **Discussions**: [GitHub Discussions](https://github.com/yourusername/claude-code-analytics/discussions)
|
689
|
+
- **Documentation**: [Full API Docs](https://yourusername.github.io/claude-code-analytics/)
|
690
|
+
|
@@ -0,0 +1,6 @@
|
|
1
|
+
claude_code_analytics-0.1.0.dist-info/METADATA,sha256=gJa9SdZOx4sNnWFQuQ8ESQ_xvIr9Ov4IVgy8xxzKX8s,18530
|
2
|
+
claude_code_analytics-0.1.0.dist-info/WHEEL,sha256=l9jBLnRRly5LD7TW-oNpYWU1DRojZivxrl_VDrtEJF4,103
|
3
|
+
claude_code_analytics-0.1.0.dist-info/licenses/LICENSE,sha256=VOy5LfrbCUMxLGMwcxF3KTEyMO8xzEBm713UCA6sQng,1069
|
4
|
+
claude_sdk/__init__.py,sha256=ZOdoUccrl-IPf563jSRbyHBfzhTDScrE_qVRSZ7GiB8,3571
|
5
|
+
claude_sdk/_core.abi3.so,sha256=hQccQ4bfAyqY6d5hN8tIERNBWzMIcn_fAH0SzQBI31Q,1087808
|
6
|
+
claude_code_analytics-0.1.0.dist-info/RECORD,,
|
@@ -0,0 +1,21 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2024 Darin Kishore
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
claude_sdk/__init__.py
ADDED
@@ -0,0 +1,139 @@
|
|
1
|
+
"""Claude SDK - Python wrapper for Claude Code sessions.
|
2
|
+
|
3
|
+
This SDK provides a clean interface for parsing and analyzing Claude Code
|
4
|
+
JSONL session files. It allows you to load session data, access messages, and analyze
|
5
|
+
costs, tool usage, and conversation patterns.
|
6
|
+
"""
|
7
|
+
|
8
|
+
from pathlib import Path
|
9
|
+
from typing import Optional, Union, List
|
10
|
+
|
11
|
+
# Import from Rust core
|
12
|
+
try:
|
13
|
+
from claude_sdk._core import (
|
14
|
+
# Main functions
|
15
|
+
load,
|
16
|
+
find_sessions as _find_sessions_internal,
|
17
|
+
find_projects as _find_projects_internal,
|
18
|
+
load_project as _load_project_internal,
|
19
|
+
# Classes
|
20
|
+
Session,
|
21
|
+
Message,
|
22
|
+
Project,
|
23
|
+
# Model classes
|
24
|
+
SessionMetadata,
|
25
|
+
ToolResult,
|
26
|
+
ToolExecution,
|
27
|
+
ConversationStats,
|
28
|
+
ConversationNode,
|
29
|
+
ConversationTree,
|
30
|
+
TextBlock,
|
31
|
+
ToolUseBlock,
|
32
|
+
ThinkingBlock,
|
33
|
+
ImageBlock,
|
34
|
+
ToolResultBlock,
|
35
|
+
TokenUsage,
|
36
|
+
# Exceptions
|
37
|
+
ClaudeSDKError,
|
38
|
+
ParseError,
|
39
|
+
ValidationError,
|
40
|
+
SessionError,
|
41
|
+
)
|
42
|
+
except ImportError as e:
|
43
|
+
raise ImportError(
|
44
|
+
"Failed to import Rust core module. Make sure the package was built with maturin."
|
45
|
+
) from e
|
46
|
+
|
47
|
+
__version__ = "0.1.0"
|
48
|
+
|
49
|
+
# All classes are now imported from Rust
|
50
|
+
|
51
|
+
|
52
|
+
# The load function is already imported from Rust, no need to redefine it
|
53
|
+
|
54
|
+
|
55
|
+
def find_sessions(
|
56
|
+
base_path: Optional[Union[str, Path]] = None,
|
57
|
+
project: Optional[Union[str, Path]] = None
|
58
|
+
) -> List[Path]:
|
59
|
+
"""Find Claude Code session files.
|
60
|
+
|
61
|
+
Args:
|
62
|
+
base_path: Directory to search (defaults to ~/.claude/projects/)
|
63
|
+
project: Optional project name/path to filter by
|
64
|
+
|
65
|
+
Returns:
|
66
|
+
List of paths to JSONL session files
|
67
|
+
"""
|
68
|
+
base_path_str = str(base_path) if base_path else None
|
69
|
+
project_str = str(project) if project else None
|
70
|
+
|
71
|
+
paths = _find_sessions_internal(base_path_str, project_str)
|
72
|
+
return [Path(p) for p in paths]
|
73
|
+
|
74
|
+
|
75
|
+
def find_projects(base_path: Optional[Union[str, Path]] = None) -> List[Path]:
|
76
|
+
"""Find Claude Code project directories.
|
77
|
+
|
78
|
+
Args:
|
79
|
+
base_path: Directory to search (defaults to ~/.claude/projects/)
|
80
|
+
|
81
|
+
Returns:
|
82
|
+
List of paths to project directories
|
83
|
+
"""
|
84
|
+
base_path_str = str(base_path) if base_path else None
|
85
|
+
paths = _find_projects_internal(base_path_str)
|
86
|
+
return [Path(p) for p in paths]
|
87
|
+
|
88
|
+
|
89
|
+
def load_project(
|
90
|
+
project_identifier: Union[str, Path],
|
91
|
+
base_path: Optional[Union[str, Path]] = None
|
92
|
+
) -> Project:
|
93
|
+
"""Load a Claude Code project by name or path.
|
94
|
+
|
95
|
+
Args:
|
96
|
+
project_identifier: Project name or full path
|
97
|
+
base_path: Base directory to search in
|
98
|
+
|
99
|
+
Returns:
|
100
|
+
Project: Project object with all sessions loaded
|
101
|
+
"""
|
102
|
+
project_str = str(project_identifier)
|
103
|
+
base_path_str = str(base_path) if base_path else None
|
104
|
+
|
105
|
+
return _load_project_internal(project_str, base_path_str)
|
106
|
+
|
107
|
+
|
108
|
+
# Type exports for static analysis
|
109
|
+
__all__ = [
|
110
|
+
# Error handling
|
111
|
+
"ClaudeSDKError",
|
112
|
+
"ParseError",
|
113
|
+
"ValidationError",
|
114
|
+
"SessionError",
|
115
|
+
# Main classes
|
116
|
+
"Session",
|
117
|
+
"Message",
|
118
|
+
"Project",
|
119
|
+
# Model classes
|
120
|
+
"SessionMetadata",
|
121
|
+
"ToolResult",
|
122
|
+
"ToolExecution",
|
123
|
+
"ConversationStats",
|
124
|
+
"ConversationNode",
|
125
|
+
"ConversationTree",
|
126
|
+
"TextBlock",
|
127
|
+
"ToolUseBlock",
|
128
|
+
"ThinkingBlock",
|
129
|
+
"ImageBlock",
|
130
|
+
"ToolResultBlock",
|
131
|
+
"TokenUsage",
|
132
|
+
# Functions
|
133
|
+
"load",
|
134
|
+
"find_sessions",
|
135
|
+
"find_projects",
|
136
|
+
"load_project",
|
137
|
+
# Version
|
138
|
+
"__version__",
|
139
|
+
]
|
claude_sdk/_core.abi3.so
ADDED
Binary file
|