pisama-claude-code 0.3.3__tar.gz → 0.3.5__tar.gz

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.
Files changed (37) hide show
  1. {pisama_claude_code-0.3.3 → pisama_claude_code-0.3.5}/CHANGELOG.md +15 -1
  2. {pisama_claude_code-0.3.3 → pisama_claude_code-0.3.5}/PKG-INFO +1 -1
  3. pisama_claude_code-0.3.5/demo/README.md +78 -0
  4. pisama_claude_code-0.3.5/demo/demo-simulated.sh +131 -0
  5. pisama_claude_code-0.3.5/demo/demo.cast +153 -0
  6. pisama_claude_code-0.3.5/demo/record-demo.sh +82 -0
  7. {pisama_claude_code-0.3.3 → pisama_claude_code-0.3.5}/pyproject.toml +1 -1
  8. {pisama_claude_code-0.3.3 → pisama_claude_code-0.3.5}/src/pisama_claude_code/__init__.py +1 -1
  9. {pisama_claude_code-0.3.3 → pisama_claude_code-0.3.5}/src/pisama_claude_code/cli.py +2 -2
  10. {pisama_claude_code-0.3.3 → pisama_claude_code-0.3.5}/src/pisama_claude_code/hooks/capture_hook.py +91 -36
  11. {pisama_claude_code-0.3.3 → pisama_claude_code-0.3.5}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
  12. {pisama_claude_code-0.3.3 → pisama_claude_code-0.3.5}/.github/ISSUE_TEMPLATE/config.yml +0 -0
  13. {pisama_claude_code-0.3.3 → pisama_claude_code-0.3.5}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
  14. {pisama_claude_code-0.3.3 → pisama_claude_code-0.3.5}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  15. {pisama_claude_code-0.3.3 → pisama_claude_code-0.3.5}/.github/workflows/ci.yml +0 -0
  16. {pisama_claude_code-0.3.3 → pisama_claude_code-0.3.5}/.github/workflows/publish.yml +0 -0
  17. {pisama_claude_code-0.3.3 → pisama_claude_code-0.3.5}/.gitignore +0 -0
  18. {pisama_claude_code-0.3.3 → pisama_claude_code-0.3.5}/CODE_OF_CONDUCT.md +0 -0
  19. {pisama_claude_code-0.3.3 → pisama_claude_code-0.3.5}/CONTRIBUTING.md +0 -0
  20. {pisama_claude_code-0.3.3 → pisama_claude_code-0.3.5}/LICENSE +0 -0
  21. {pisama_claude_code-0.3.3 → pisama_claude_code-0.3.5}/README.md +0 -0
  22. {pisama_claude_code-0.3.3 → pisama_claude_code-0.3.5}/SECURITY.md +0 -0
  23. {pisama_claude_code-0.3.3 → pisama_claude_code-0.3.5}/src/pisama_claude_code/adapter.py +0 -0
  24. {pisama_claude_code-0.3.3 → pisama_claude_code-0.3.5}/src/pisama_claude_code/guardian.py +0 -0
  25. {pisama_claude_code-0.3.3 → pisama_claude_code-0.3.5}/src/pisama_claude_code/hooks/__init__.py +0 -0
  26. {pisama_claude_code-0.3.3 → pisama_claude_code-0.3.5}/src/pisama_claude_code/hooks/guardian_hook.py +0 -0
  27. {pisama_claude_code-0.3.3 → pisama_claude_code-0.3.5}/src/pisama_claude_code/install.py +0 -0
  28. {pisama_claude_code-0.3.3 → pisama_claude_code-0.3.5}/src/pisama_claude_code/py.typed +0 -0
  29. {pisama_claude_code-0.3.3 → pisama_claude_code-0.3.5}/src/pisama_claude_code/storage.py +0 -0
  30. {pisama_claude_code-0.3.3 → pisama_claude_code-0.3.5}/src/pisama_claude_code/trace_converter.py +0 -0
  31. {pisama_claude_code-0.3.3 → pisama_claude_code-0.3.5}/src/pisama_claude_code/trace_types.py +0 -0
  32. {pisama_claude_code-0.3.3 → pisama_claude_code-0.3.5}/tests/__init__.py +0 -0
  33. {pisama_claude_code-0.3.3 → pisama_claude_code-0.3.5}/tests/conftest.py +0 -0
  34. {pisama_claude_code-0.3.3 → pisama_claude_code-0.3.5}/tests/test_adapter.py +0 -0
  35. {pisama_claude_code-0.3.3 → pisama_claude_code-0.3.5}/tests/test_cli.py +0 -0
  36. {pisama_claude_code-0.3.3 → pisama_claude_code-0.3.5}/tests/test_guardian.py +0 -0
  37. {pisama_claude_code-0.3.3 → pisama_claude_code-0.3.5}/tests/test_storage.py +0 -0
@@ -7,6 +7,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.3.5] - 2025-01-05
11
+
12
+ ### Fixed
13
+ - Correctly extract user input from transcript (skip tool_result blocks)
14
+ - Collect reasoning/output from multiple assistant messages (Claude Code splits them)
15
+ - Look back 200 lines to find user prompts
16
+
17
+ ## [0.3.4] - 2025-01-05
18
+
19
+ ### Fixed
20
+ - Version string in CLI now matches package version
21
+
10
22
  ## [0.3.3] - 2025-01-04
11
23
 
12
24
  ### Added
@@ -79,7 +91,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
79
91
  - Support for Bash, Read, Write, Edit, Grep, Glob tools
80
92
  - Local storage in `~/.claude/pisama/traces/`
81
93
 
82
- [Unreleased]: https://github.com/tn-pisama/pisama-claude-code/compare/v0.3.3...HEAD
94
+ [Unreleased]: https://github.com/tn-pisama/pisama-claude-code/compare/v0.3.5...HEAD
95
+ [0.3.5]: https://github.com/tn-pisama/pisama-claude-code/compare/v0.3.4...v0.3.5
96
+ [0.3.4]: https://github.com/tn-pisama/pisama-claude-code/compare/v0.3.3...v0.3.4
83
97
  [0.3.3]: https://github.com/tn-pisama/pisama-claude-code/compare/v0.3.2...v0.3.3
84
98
  [0.3.2]: https://github.com/tn-pisama/pisama-claude-code/compare/v0.3.1...v0.3.2
85
99
  [0.3.1]: https://github.com/tn-pisama/pisama-claude-code/compare/v0.3.0...v0.3.1
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pisama-claude-code
3
- Version: 0.3.3
3
+ Version: 0.3.5
4
4
  Summary: Trace capture for Claude Code sessions - sync to PISAMA platform for analysis
5
5
  Project-URL: Homepage, https://pisama.dev
6
6
  Project-URL: Documentation, https://pisama.dev/docs/claude-code
@@ -0,0 +1,78 @@
1
+ # Demo Recording
2
+
3
+ Scripts to create a GIF demo for the README.
4
+
5
+ ## Quick Start (Recommended)
6
+
7
+ ```bash
8
+ # Install asciinema and agg (ASCII to GIF)
9
+ brew install asciinema
10
+ cargo install --git https://github.com/asciinema/agg
11
+
12
+ # Record the simulated demo
13
+ cd demo
14
+ chmod +x demo-simulated.sh
15
+ asciinema rec demo.cast -c ./demo-simulated.sh
16
+
17
+ # Convert to GIF
18
+ agg demo.cast ../assets/demo.gif \
19
+ --theme monokai \
20
+ --cols 70 \
21
+ --rows 28 \
22
+ --speed 1.2
23
+
24
+ # Or use gif-for-cli for smaller file size
25
+ # pip install gif-for-cli
26
+ ```
27
+
28
+ ## Files
29
+
30
+ | File | Purpose |
31
+ |------|---------|
32
+ | `demo-simulated.sh` | Pre-scripted demo with realistic output (use this) |
33
+ | `record-demo.sh` | Live demo using actual `pisama-cc` commands |
34
+
35
+ ## Recording Tips
36
+
37
+ 1. **Use simulated demo** - Consistent, reproducible output
38
+ 2. **Terminal size** - Set to 70x28 before recording
39
+ 3. **Clean terminal** - No distracting prompts or colors
40
+ 4. **Speed** - 1.2x makes it watchable without being too fast
41
+
42
+ ## Alternative: Terminalizer
43
+
44
+ ```bash
45
+ npm install -g terminalizer
46
+
47
+ # Record
48
+ terminalizer record demo --config terminalizer.yml
49
+
50
+ # Render
51
+ terminalizer render demo -o demo.gif
52
+ ```
53
+
54
+ ## Alternative: VHS (by Charm)
55
+
56
+ ```bash
57
+ brew install vhs
58
+
59
+ # Create a .tape file and run
60
+ vhs demo.tape
61
+ ```
62
+
63
+ ## Adding to README
64
+
65
+ Once you have `demo.gif`, add to README.md:
66
+
67
+ ```markdown
68
+ ## Demo
69
+
70
+ ![pisama-claude-code demo](assets/demo.gif)
71
+ ```
72
+
73
+ ## Optimal GIF Settings
74
+
75
+ - **Width**: 600-800px (readable on GitHub)
76
+ - **FPS**: 10-15 (smaller file size)
77
+ - **Duration**: 15-25 seconds (attention span)
78
+ - **File size**: Under 5MB (GitHub displays inline)
@@ -0,0 +1,131 @@
1
+ #!/bin/bash
2
+ # Simulated demo for GIF recording
3
+ # Shows realistic output without needing real traces
4
+ #
5
+ # Record with: asciinema rec demo.cast
6
+ # Convert with: agg demo.cast demo.gif --theme monokai --cols 80 --rows 24
7
+
8
+ set -e
9
+
10
+ # Typing effect
11
+ type_cmd() {
12
+ local cmd="$1"
13
+ printf '\033[0;32m$\033[0m '
14
+ for ((i=0; i<${#cmd}; i++)); do
15
+ printf '%s' "${cmd:$i:1}"
16
+ sleep 0.04
17
+ done
18
+ printf '\n'
19
+ sleep 0.2
20
+ }
21
+
22
+ pause() { sleep "${1:-1}"; }
23
+
24
+ clear
25
+ printf '\033[1;36m'
26
+ cat << 'EOF'
27
+ ╔═══════════════════════════════════════════════════╗
28
+ ║ pisama-claude-code ║
29
+ ║ Track token costs for Claude Code sessions ║
30
+ ╚═══════════════════════════════════════════════════╝
31
+ EOF
32
+ printf '\033[0m\n'
33
+ pause 2
34
+
35
+ # Install
36
+ printf '\033[1;33m# Install\033[0m\n'
37
+ type_cmd "pip install pisama-claude-code"
38
+ cat << 'EOF'
39
+ Successfully installed pisama-claude-code-0.3.2
40
+ EOF
41
+ pause 1.5
42
+
43
+ printf '\n\033[1;33m# Set up hooks\033[0m\n'
44
+ type_cmd "pisama-cc install"
45
+ cat << 'EOF'
46
+ ✅ Installed pisama-capture.py
47
+ ✅ Installed pisama-pre.sh
48
+ ✅ Installed pisama-post.sh
49
+ ✅ Updated ~/.claude/settings.local.json
50
+
51
+ Hooks installed! Traces will be captured automatically.
52
+ EOF
53
+ pause 2
54
+
55
+ printf '\n\033[1;33m# After a coding session, check status\033[0m\n'
56
+ type_cmd "pisama-cc status"
57
+ cat << 'EOF'
58
+ 📊 PISAMA Status
59
+ ========================================
60
+
61
+ 🔧 Hook Installation:
62
+ ✅ pisama-capture.py
63
+ ✅ pisama-pre.sh
64
+ ✅ pisama-post.sh
65
+ All hooks installed
66
+
67
+ 🔗 Platform Connection:
68
+ ❌ Not connected (local-only mode)
69
+
70
+ 📁 Local Traces: 1,847
71
+ Input tokens: 142,580
72
+ Output tokens: 891,234
73
+ Total cost: $52.34
74
+ Models: claude-opus-4-5-20251101
75
+
76
+ 📝 Claude Code Settings:
77
+ ✅ PISAMA hooks configured in settings
78
+ EOF
79
+ pause 2.5
80
+
81
+ printf '\n\033[1;33m# View recent tool calls\033[0m\n'
82
+ type_cmd "pisama-cc traces -v --last 5"
83
+ cat << 'EOF'
84
+ 📋 Recent Traces (5 shown)
85
+ ======================================================================
86
+ 2025-01-04T14:23:45 | Bash | a3f8c2d1 | post | 2341i 8923o | $0.7012
87
+ 2025-01-04T14:23:38 | Read | a3f8c2d1 | post | 1823i 2341o | $0.1892
88
+ 2025-01-04T14:23:31 | Edit | a3f8c2d1 | post | 3421i 12893o | $1.0234
89
+ 2025-01-04T14:23:22 | Grep | a3f8c2d1 | post | 892i 1234o | $0.1023
90
+ 2025-01-04T14:23:15 | Glob | a3f8c2d1 | post | 423i 891o | $0.0712
91
+ ──────────────────────────────────────────────────────────────────────
92
+ Totals: 8,900 input + 26,282 output tokens = $2.0873
93
+ EOF
94
+ pause 2.5
95
+
96
+ printf '\n\033[1;33m# Detailed breakdown\033[0m\n'
97
+ type_cmd "pisama-cc usage --by-model --by-tool"
98
+ cat << 'EOF'
99
+ 📊 Token Usage Summary (last 100 traces)
100
+ ==================================================
101
+ Input tokens: 142,580
102
+ Output tokens: 891,234
103
+ Cache read tokens: 1,234,567
104
+ Total tokens: 1,033,814
105
+ Total cost: $ 52.34
106
+
107
+ 📈 By Model:
108
+ --------------------------------------------------
109
+ claude-opus-4-5-20251101 $52.34
110
+
111
+ 🔧 By Tool:
112
+ --------------------------------------------------
113
+ Bash 145 calls $23.45
114
+ Read 89 calls $12.34
115
+ Edit 67 calls $8.92
116
+ Write 34 calls $4.21
117
+ Grep 23 calls $2.12
118
+ Glob 12 calls $1.30
119
+ EOF
120
+ pause 2
121
+
122
+ printf '\n\033[1;36m'
123
+ cat << 'EOF'
124
+ ════════════════════════════════════════════════════
125
+ ✓ Track every token. Know your costs.
126
+
127
+ pip install pisama-claude-code
128
+ ════════════════════════════════════════════════════
129
+ EOF
130
+ printf '\033[0m'
131
+ pause 3
@@ -0,0 +1,153 @@
1
+ {"version":3,"term":{"cols":80,"rows":24,"type":"xterm-256color"},"timestamp":1767597795,"command":"./demo/demo-simulated.sh","env":{"SHELL":"/bin/zsh"}}
2
+ [0.011, "o", "\u001b[3J\u001b[H\u001b[2J"]
3
+ [0.000, "o", "\u001b[1;36m"]
4
+ [0.003, "o", "╔═══════════════════════════════════════════════════╗\r\n║ pisama-claude-code ║\r\n║ Track token costs for Claude Code sessions ║\r\n╚═══════════════════════════════════════════════════╝\r\n"]
5
+ [0.000, "o", "\u001b[0m\r\n"]
6
+ [2.004, "o", "\u001b[1;33m# Install\u001b[0m\r\n"]
7
+ [0.000, "o", "\u001b[0;32m$\u001b[0m "]
8
+ [0.000, "o", "p"]
9
+ [0.048, "o", "i"]
10
+ [0.046, "o", "p"]
11
+ [0.046, "o", " "]
12
+ [0.043, "o", "i"]
13
+ [0.043, "o", "n"]
14
+ [0.048, "o", "s"]
15
+ [0.043, "o", "t"]
16
+ [0.045, "o", "a"]
17
+ [0.047, "o", "l"]
18
+ [0.047, "o", "l"]
19
+ [0.046, "o", " "]
20
+ [0.048, "o", "p"]
21
+ [0.046, "o", "i"]
22
+ [0.047, "o", "s"]
23
+ [0.047, "o", "a"]
24
+ [0.043, "o", "m"]
25
+ [0.044, "o", "a"]
26
+ [0.046, "o", "-"]
27
+ [0.049, "o", "c"]
28
+ [0.045, "o", "l"]
29
+ [0.047, "o", "a"]
30
+ [0.048, "o", "u"]
31
+ [0.043, "o", "d"]
32
+ [0.048, "o", "e"]
33
+ [0.047, "o", "-"]
34
+ [0.045, "o", "c"]
35
+ [0.047, "o", "o"]
36
+ [0.048, "o", "d"]
37
+ [0.044, "o", "e"]
38
+ [0.044, "o", "\r\n"]
39
+ [0.207, "o", "Successfully installed pisama-claude-code-0.3.2\r\n"]
40
+ [1.508, "o", "\r\n\u001b[1;33m# Set up hooks\u001b[0m\r\n\u001b[0;32m$\u001b[0m "]
41
+ [0.000, "o", "p"]
42
+ [0.047, "o", "i"]
43
+ [0.045, "o", "s"]
44
+ [0.046, "o", "a"]
45
+ [0.046, "o", "m"]
46
+ [0.045, "o", "a"]
47
+ [0.047, "o", "-"]
48
+ [0.043, "o", "c"]
49
+ [0.047, "o", "c"]
50
+ [0.047, "o", " "]
51
+ [0.048, "o", "i"]
52
+ [0.043, "o", "n"]
53
+ [0.046, "o", "s"]
54
+ [0.044, "o", "t"]
55
+ [0.046, "o", "a"]
56
+ [0.047, "o", "l"]
57
+ [0.045, "o", "l"]
58
+ [0.047, "o", "\r\n"]
59
+ [0.209, "o", "✅ Installed pisama-capture.py\r\n✅ Installed pisama-pre.sh\r\n✅ Installed pisama-post.sh\r\n✅ Updated ~/.claude/settings.local.json\r\n\r\nHooks installed! Traces will be captured automatically.\r\n"]
60
+ [2.005, "o", "\r\n\u001b[1;33m# After a coding session, check status\u001b[0m\r\n\u001b[0;32m$\u001b[0m "]
61
+ [0.000, "o", "p"]
62
+ [0.045, "o", "i"]
63
+ [0.047, "o", "s"]
64
+ [0.046, "o", "a"]
65
+ [0.048, "o", "m"]
66
+ [0.045, "o", "a"]
67
+ [0.045, "o", "-"]
68
+ [0.045, "o", "c"]
69
+ [0.048, "o", "c"]
70
+ [0.048, "o", " "]
71
+ [0.048, "o", "s"]
72
+ [0.043, "o", "t"]
73
+ [0.044, "o", "a"]
74
+ [0.049, "o", "t"]
75
+ [0.047, "o", "u"]
76
+ [0.045, "o", "s"]
77
+ [0.043, "o", "\r\n"]
78
+ [0.211, "o", "📊 PISAMA Status\r\n========================================\r\n\r\n🔧 Hook Installation:\r\n ✅ pisama-capture.py\r\n ✅ pisama-pre.sh\r\n ✅ pisama-post.sh\r\n All hooks installed\r\n\r\n🔗 Platform Connection:\r\n ❌ Not connected (local-only mode)\r\n\r\n📁 Local Traces: 1,847\r\n Input tokens: 142,580\r\n Output tokens: 891,234\r\n Total cost: $52.34\r\n Models: claude-opus-4-5-20251101\r\n\r\n📝 Claude Code Settings:\r\n ✅ PISAMA hooks configured in settings\r\n"]
79
+ [2.509, "o", "\r\n\u001b[1;33m# View recent tool calls\u001b[0m\r\n\u001b[0;32m$\u001b[0m "]
80
+ [0.000, "o", "p"]
81
+ [0.046, "o", "i"]
82
+ [0.043, "o", "s"]
83
+ [0.043, "o", "a"]
84
+ [0.043, "o", "m"]
85
+ [0.043, "o", "a"]
86
+ [0.044, "o", "-"]
87
+ [0.047, "o", "c"]
88
+ [0.046, "o", "c"]
89
+ [0.046, "o", " "]
90
+ [0.042, "o", "t"]
91
+ [0.043, "o", "r"]
92
+ [0.046, "o", "a"]
93
+ [0.047, "o", "c"]
94
+ [0.048, "o", "e"]
95
+ [0.045, "o", "s"]
96
+ [0.045, "o", " "]
97
+ [0.047, "o", "-"]
98
+ [0.047, "o", "v"]
99
+ [0.047, "o", " "]
100
+ [0.043, "o", "-"]
101
+ [0.045, "o", "-"]
102
+ [0.048, "o", "l"]
103
+ [0.047, "o", "a"]
104
+ [0.046, "o", "s"]
105
+ [0.046, "o", "t"]
106
+ [0.048, "o", " "]
107
+ [0.043, "o", "5"]
108
+ [0.045, "o", "\r\n"]
109
+ [0.207, "o", "📋 Recent Traces (5 shown)\r\n======================================================================\r\n2025-01-04T14:23:45 | Bash | a3f8c2d1 | post | 2341i 8923o | $0.7012\r\n2025-01-04T14:23:38 | Read | a3f8c2d1 | post | 1823i 2341o | $0.1892\r\n2025-01-04T14:23:31 | Edit | a3f8c2d1 | post | 3421i 12893o | $1.0234\r\n2025-01-04T14:23:22 | Grep | a3f8c2d1 | post | 892i 1234o | $0.1023\r\n2025-01-04T14:23:15 | Glob | a3f8c2d1 | post | 423i 891o | $0.0712\r\n──────────────────────────────────────────────────────────────────────\r\nTotals: 8,900 input + 26,282 output tokens = $2.0873\r\n"]
110
+ [2.504, "o", "\r\n\u001b[1;33m# Detailed breakdown\u001b[0m\r\n"]
111
+ [0.000, "o", "\u001b[0;32m$\u001b[0m "]
112
+ [0.000, "o", "p"]
113
+ [0.045, "o", "i"]
114
+ [0.043, "o", "s"]
115
+ [0.045, "o", "a"]
116
+ [0.044, "o", "m"]
117
+ [0.045, "o", "a"]
118
+ [0.048, "o", "-"]
119
+ [0.044, "o", "c"]
120
+ [0.047, "o", "c"]
121
+ [0.043, "o", " "]
122
+ [0.043, "o", "u"]
123
+ [0.043, "o", "s"]
124
+ [0.047, "o", "a"]
125
+ [0.048, "o", "g"]
126
+ [0.046, "o", "e"]
127
+ [0.042, "o", " "]
128
+ [0.044, "o", "-"]
129
+ [0.043, "o", "-"]
130
+ [0.046, "o", "b"]
131
+ [0.046, "o", "y"]
132
+ [0.045, "o", "-"]
133
+ [0.046, "o", "m"]
134
+ [0.046, "o", "o"]
135
+ [0.046, "o", "d"]
136
+ [0.047, "o", "e"]
137
+ [0.044, "o", "l"]
138
+ [0.048, "o", " "]
139
+ [0.044, "o", "-"]
140
+ [0.048, "o", "-"]
141
+ [0.045, "o", "b"]
142
+ [0.045, "o", "y"]
143
+ [0.044, "o", "-"]
144
+ [0.048, "o", "t"]
145
+ [0.042, "o", "o"]
146
+ [0.047, "o", "o"]
147
+ [0.046, "o", "l"]
148
+ [0.046, "o", "\r\n"]
149
+ [0.210, "o", "📊 Token Usage Summary (last 100 traces)\r\n==================================================\r\nInput tokens: 142,580\r\nOutput tokens: 891,234\r\nCache read tokens: 1,234,567\r\nTotal tokens: 1,033,814\r\nTotal cost: $ 52.34\r\n\r\n📈 By Model:\r\n--------------------------------------------------\r\n claude-opus-4-5-20251101 $52.34\r\n\r\n🔧 By Tool:\r\n--------------------------------------------------\r\n Bash 145 calls $23.45\r\n Read 89 calls $12.34\r\n Edit 67 calls $8.92\r\n Write 34 calls $4.21\r\n Grep 23 calls $2.12\r\n Glob 12 calls $1.30\r\n"]
150
+ [2.002, "o", "\r\n\u001b[1;36m"]
151
+ [0.003, "o", "════════════════════════════════════════════════════\r\n ✓ Track every token. Know your costs.\r\n\r\n pip install pisama-claude-code\r\n════════════════════════════════════════════════════\r\n"]
152
+ [0.000, "o", "\u001b[0m"]
153
+ [3.008, "x", "0"]
@@ -0,0 +1,82 @@
1
+ #!/bin/bash
2
+ # Demo recording script for pisama-claude-code
3
+ #
4
+ # Recording options:
5
+ # 1. asciinema (recommended): asciinema rec demo.cast
6
+ # 2. terminalizer: terminalizer record demo
7
+ # 3. ttygif: script + ttyrec + ttygif
8
+ #
9
+ # Then convert to GIF:
10
+ # asciinema: agg demo.cast demo.gif --theme monokai
11
+ # terminalizer: terminalizer render demo -o demo.gif
12
+
13
+ set -e
14
+
15
+ # Colors
16
+ GREEN='\033[0;32m'
17
+ BLUE='\033[0;34m'
18
+ YELLOW='\033[1;33m'
19
+ NC='\033[0m' # No Color
20
+
21
+ # Typing effect - simulates human typing
22
+ type_cmd() {
23
+ local cmd="$1"
24
+ echo -ne "${GREEN}\$${NC} "
25
+ for ((i=0; i<${#cmd}; i++)); do
26
+ echo -n "${cmd:$i:1}"
27
+ sleep 0.05
28
+ done
29
+ echo ""
30
+ sleep 0.3
31
+ }
32
+
33
+ # Run command with typing effect
34
+ run_cmd() {
35
+ type_cmd "$1"
36
+ eval "$1"
37
+ sleep 1.5
38
+ }
39
+
40
+ # Clear and set title
41
+ clear
42
+ echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
43
+ echo -e "${BLUE} pisama-claude-code - Track Claude Code costs${NC}"
44
+ echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
45
+ echo ""
46
+ sleep 2
47
+
48
+ # Step 1: Install
49
+ echo -e "${YELLOW}# Install the package${NC}"
50
+ sleep 1
51
+ run_cmd "pip install pisama-claude-code"
52
+ echo ""
53
+
54
+ # Step 2: Install hooks
55
+ echo -e "${YELLOW}# Set up capture hooks${NC}"
56
+ sleep 1
57
+ run_cmd "pisama-cc install"
58
+ echo ""
59
+
60
+ # Step 3: Show status (after some usage)
61
+ echo -e "${YELLOW}# Check status and costs${NC}"
62
+ sleep 1
63
+ run_cmd "pisama-cc status"
64
+ echo ""
65
+
66
+ # Step 4: View traces
67
+ echo -e "${YELLOW}# View recent tool calls${NC}"
68
+ sleep 1
69
+ run_cmd "pisama-cc traces -v --last 10"
70
+ echo ""
71
+
72
+ # Step 5: Usage breakdown
73
+ echo -e "${YELLOW}# Detailed cost breakdown${NC}"
74
+ sleep 1
75
+ run_cmd "pisama-cc usage --by-model --by-tool"
76
+ echo ""
77
+
78
+ # End
79
+ echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
80
+ echo -e "${GREEN}✓${NC} Track every token. Know your costs."
81
+ echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
82
+ sleep 3
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "pisama-claude-code"
7
- version = "0.3.3"
7
+ version = "0.3.5"
8
8
  description = "Trace capture for Claude Code sessions - sync to PISAMA platform for analysis"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -1,6 +1,6 @@
1
1
  """PISAMA Claude Code Integration - Trace capture, failure detection, and self-healing."""
2
2
 
3
- __version__ = "0.3.2"
3
+ __version__ = "0.3.5"
4
4
 
5
5
  # Lazy imports to avoid loading everything at startup
6
6
  def install(force: bool = False):
@@ -53,7 +53,7 @@ def save_config(config: dict):
53
53
 
54
54
 
55
55
  @click.group()
56
- @click.version_option(version="0.3.2")
56
+ @click.version_option(version="0.3.5")
57
57
  def main():
58
58
  """PISAMA Claude Code - Trace capture and sync."""
59
59
  pass
@@ -675,7 +675,7 @@ def prepare_sync_payload(traces: list, include_outputs: bool) -> dict:
675
675
 
676
676
  return {
677
677
  "source": "claude-code",
678
- "version": "0.3.3",
678
+ "version": "0.3.5",
679
679
  "uploaded_at": datetime.now(timezone.utc).isoformat(),
680
680
  "trace_count": len(clean_traces),
681
681
  "traces": clean_traces,
@@ -185,7 +185,14 @@ def extract_content_parts(content_blocks: list) -> dict:
185
185
 
186
186
 
187
187
  def get_last_user_message(transcript_path: str) -> dict:
188
- """Read transcript and get the last user message (input)."""
188
+ """Read transcript and get the last user text message (input).
189
+
190
+ Note: Claude Code "user" messages can be either:
191
+ 1. Actual user text input (content is a string or has type="text" blocks)
192
+ 2. Tool results (content has type="tool_result" blocks)
193
+
194
+ We want the actual user input, not tool results.
195
+ """
189
196
  try:
190
197
  from pathlib import Path
191
198
  transcript = Path(transcript_path)
@@ -193,29 +200,47 @@ def get_last_user_message(transcript_path: str) -> dict:
193
200
  return {}
194
201
 
195
202
  with open(transcript) as f:
196
- lines = f.readlines()[-50:] # Look back further for user message
203
+ lines = f.readlines()[-200:] # Look back further for user message
197
204
 
198
205
  for line in reversed(lines):
199
206
  try:
200
207
  entry = json.loads(line)
201
- if entry.get("type") == "human" and "message" in entry:
208
+ # Claude Code uses "user" type
209
+ if entry.get("type") in ("user", "human") and "message" in entry:
202
210
  msg = entry["message"]
203
211
  content = msg.get("content", [])
204
212
 
205
- # Extract text from content blocks
206
- if isinstance(content, list):
213
+ # Content can be a direct string (actual user input)
214
+ if isinstance(content, str):
215
+ # Skip system messages and interrupts
216
+ if not content.startswith("[Request") and not content.startswith("[System"):
217
+ return {"content": content, "role": "user"}
218
+
219
+ # Content can be a list of blocks
220
+ elif isinstance(content, list):
207
221
  text_parts = []
222
+ is_tool_result = False
223
+
208
224
  for block in content:
209
- if isinstance(block, dict) and block.get("type") == "text":
210
- text_parts.append(block.get("text", ""))
225
+ if isinstance(block, dict):
226
+ block_type = block.get("type", "")
227
+ if block_type == "tool_result":
228
+ is_tool_result = True
229
+ break # This is a tool result, not user input
230
+ elif block_type == "text":
231
+ text = block.get("text", "")
232
+ if text and not text.startswith("[Request"):
233
+ text_parts.append(text)
211
234
  elif isinstance(block, str):
212
235
  text_parts.append(block)
213
- return {
214
- "content": "\n".join(text_parts) if text_parts else None,
215
- "role": "user",
216
- }
217
- elif isinstance(content, str):
218
- return {"content": content, "role": "user"}
236
+
237
+ # Only return if this is actual user text, not tool results
238
+ if text_parts and not is_tool_result:
239
+ return {
240
+ "content": "\n".join(text_parts),
241
+ "role": "user",
242
+ }
243
+
219
244
  except json.JSONDecodeError:
220
245
  continue
221
246
  return {}
@@ -224,14 +249,17 @@ def get_last_user_message(transcript_path: str) -> dict:
224
249
 
225
250
 
226
251
  def get_last_assistant_message(transcript_path: str) -> dict:
227
- """Read transcript and get the last assistant message with usage.
252
+ """Read transcript and get recent assistant messages with usage.
253
+
254
+ Claude Code separates thinking, text, and tool_use into different messages,
255
+ so we need to collect content from multiple recent assistant messages.
228
256
 
229
- Now extracts:
257
+ Extracts:
230
258
  - model: The Claude model used
231
259
  - usage: Token counts (input, output, cache)
232
260
  - input: User's input message
233
- - reasoning: Extended thinking content
234
- - output: Text response content
261
+ - reasoning: Extended thinking content (collected from recent messages)
262
+ - output: Text response content (collected from recent messages)
235
263
  - tool_calls: Any tool calls made
236
264
  """
237
265
  try:
@@ -240,45 +268,72 @@ def get_last_assistant_message(transcript_path: str) -> dict:
240
268
  if not transcript.exists():
241
269
  return {}
242
270
 
243
- # Read last 50 lines to find messages
271
+ # Read last 100 lines to find messages
244
272
  with open(transcript) as f:
245
- lines = f.readlines()[-50:]
273
+ lines = f.readlines()[-100:]
274
+
275
+ # Collect content from recent assistant messages (up to 10)
276
+ # Claude Code sends thinking, text, tool_use as separate messages
277
+ all_reasoning = []
278
+ all_output = []
279
+ all_tool_calls = []
280
+ model = None
281
+ usage = {}
282
+ stop_reason = None
283
+ messages_checked = 0
284
+ max_messages = 10
246
285
 
247
- assistant_msg = None
248
286
  for line in reversed(lines):
287
+ if messages_checked >= max_messages:
288
+ break
249
289
  try:
250
290
  entry = json.loads(line)
251
291
  if entry.get("type") == "assistant" and "message" in entry:
292
+ messages_checked += 1
252
293
  msg = entry["message"]
253
- assistant_msg = msg
294
+
295
+ # Get model and usage from first (most recent) message
296
+ if model is None:
297
+ model = msg.get("model")
298
+ usage = msg.get("usage", {})
299
+ stop_reason = msg.get("stop_reason")
300
+
301
+ # Extract content from this message
302
+ content = msg.get("content", [])
303
+ if isinstance(content, list):
304
+ parts = extract_content_parts(content)
305
+ if parts.get("reasoning", {}).get("content"):
306
+ all_reasoning.append(parts["reasoning"]["content"])
307
+ if parts.get("output", {}).get("content"):
308
+ all_output.append(parts["output"]["content"])
309
+ if parts.get("tool_calls"):
310
+ all_tool_calls.extend(parts["tool_calls"])
311
+
312
+ # Stop if we hit a user message (different turn)
313
+ elif entry.get("type") == "human":
254
314
  break
315
+
255
316
  except json.JSONDecodeError:
256
317
  continue
257
318
 
258
- if not assistant_msg:
259
- return {}
260
-
261
- # Extract content parts (reasoning, output, tool_calls)
262
- content = assistant_msg.get("content", [])
263
- content_parts = extract_content_parts(content) if isinstance(content, list) else {}
264
-
265
319
  # Get user input
266
320
  user_msg = get_last_user_message(transcript_path)
267
321
 
268
- # Get usage breakdown
269
- usage = assistant_msg.get("usage", {})
322
+ # Combine collected content (reverse to get chronological order)
323
+ reasoning_text = "\n\n".join(reversed(all_reasoning)) if all_reasoning else None
324
+ output_text = "\n\n".join(reversed(all_output)) if all_output else None
270
325
 
271
326
  return {
272
- "model": assistant_msg.get("model"),
327
+ "model": model,
273
328
  "usage": usage,
274
- "stop_reason": assistant_msg.get("stop_reason"),
329
+ "stop_reason": stop_reason,
275
330
  # Structured content
276
331
  "input": user_msg.get("content"),
277
- "reasoning": content_parts.get("reasoning", {}).get("content"),
278
- "output": content_parts.get("output", {}).get("content"),
279
- "tool_calls": content_parts.get("tool_calls"),
332
+ "reasoning": reasoning_text,
333
+ "output": output_text,
334
+ "tool_calls": all_tool_calls if all_tool_calls else None,
280
335
  # Legacy field for backward compatibility
281
- "content": content,
336
+ "content": [],
282
337
  }
283
338
  except Exception:
284
339
  return {}