pisama-claude-code 0.3.1__tar.gz → 0.3.2__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.
- pisama_claude_code-0.3.2/PKG-INFO +211 -0
- pisama_claude_code-0.3.2/README.md +176 -0
- {pisama_claude_code-0.3.1 → pisama_claude_code-0.3.2}/pyproject.toml +1 -1
- {pisama_claude_code-0.3.1 → pisama_claude_code-0.3.2}/src/pisama_claude_code/__init__.py +1 -1
- {pisama_claude_code-0.3.1 → pisama_claude_code-0.3.2}/src/pisama_claude_code/cli.py +459 -1
- {pisama_claude_code-0.3.1 → pisama_claude_code-0.3.2}/src/pisama_claude_code/hooks/capture_hook.py +79 -3
- pisama_claude_code-0.3.1/PKG-INFO +0 -105
- pisama_claude_code-0.3.1/README.md +0 -70
- {pisama_claude_code-0.3.1 → pisama_claude_code-0.3.2}/.gitignore +0 -0
- {pisama_claude_code-0.3.1 → pisama_claude_code-0.3.2}/LICENSE +0 -0
- {pisama_claude_code-0.3.1 → pisama_claude_code-0.3.2}/src/pisama_claude_code/adapter.py +0 -0
- {pisama_claude_code-0.3.1 → pisama_claude_code-0.3.2}/src/pisama_claude_code/guardian.py +0 -0
- {pisama_claude_code-0.3.1 → pisama_claude_code-0.3.2}/src/pisama_claude_code/hooks/__init__.py +0 -0
- {pisama_claude_code-0.3.1 → pisama_claude_code-0.3.2}/src/pisama_claude_code/hooks/guardian_hook.py +0 -0
- {pisama_claude_code-0.3.1 → pisama_claude_code-0.3.2}/src/pisama_claude_code/install.py +0 -0
- {pisama_claude_code-0.3.1 → pisama_claude_code-0.3.2}/src/pisama_claude_code/storage.py +0 -0
- {pisama_claude_code-0.3.1 → pisama_claude_code-0.3.2}/src/pisama_claude_code/trace_converter.py +0 -0
- {pisama_claude_code-0.3.1 → pisama_claude_code-0.3.2}/src/pisama_claude_code/trace_types.py +0 -0
- {pisama_claude_code-0.3.1 → pisama_claude_code-0.3.2}/tests/__init__.py +0 -0
- {pisama_claude_code-0.3.1 → pisama_claude_code-0.3.2}/tests/conftest.py +0 -0
- {pisama_claude_code-0.3.1 → pisama_claude_code-0.3.2}/tests/test_adapter.py +0 -0
- {pisama_claude_code-0.3.1 → pisama_claude_code-0.3.2}/tests/test_cli.py +0 -0
- {pisama_claude_code-0.3.1 → pisama_claude_code-0.3.2}/tests/test_guardian.py +0 -0
- {pisama_claude_code-0.3.1 → pisama_claude_code-0.3.2}/tests/test_storage.py +0 -0
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pisama-claude-code
|
|
3
|
+
Version: 0.3.2
|
|
4
|
+
Summary: Trace capture for Claude Code sessions - sync to PISAMA platform for analysis
|
|
5
|
+
Project-URL: Homepage, https://pisama.dev
|
|
6
|
+
Project-URL: Documentation, https://pisama.dev/docs/claude-code
|
|
7
|
+
Project-URL: Repository, https://github.com/tn-pisama/pisama-claude-code
|
|
8
|
+
Author-email: PISAMA Team <team@pisama.dev>
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: agent,claude,claude-code,forensics,llm,observability,pisama,self-healing
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
20
|
+
Classifier: Topic :: System :: Monitoring
|
|
21
|
+
Requires-Python: >=3.10
|
|
22
|
+
Requires-Dist: click>=8.0.0
|
|
23
|
+
Requires-Dist: httpx>=0.25.0
|
|
24
|
+
Requires-Dist: pydantic>=2.0.0
|
|
25
|
+
Requires-Dist: rich>=13.0.0
|
|
26
|
+
Provides-Extra: all
|
|
27
|
+
Requires-Dist: pisama-core>=0.1.0; extra == 'all'
|
|
28
|
+
Provides-Extra: core
|
|
29
|
+
Requires-Dist: pisama-core>=0.1.0; extra == 'core'
|
|
30
|
+
Provides-Extra: dev
|
|
31
|
+
Requires-Dist: pytest-asyncio>=0.21.0; extra == 'dev'
|
|
32
|
+
Requires-Dist: pytest-cov>=4.0.0; extra == 'dev'
|
|
33
|
+
Requires-Dist: pytest>=7.0.0; extra == 'dev'
|
|
34
|
+
Description-Content-Type: text/markdown
|
|
35
|
+
|
|
36
|
+
# pisama-claude-code
|
|
37
|
+
|
|
38
|
+
Trace capture for Claude Code sessions with token usage and cost tracking.
|
|
39
|
+
|
|
40
|
+
[](https://pypi.org/project/pisama-claude-code/)
|
|
41
|
+
[](https://www.python.org/downloads/)
|
|
42
|
+
[](https://opensource.org/licenses/MIT)
|
|
43
|
+
|
|
44
|
+
## Installation
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
pip install pisama-claude-code
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Quick Start
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
# Install capture hooks
|
|
54
|
+
pisama-cc install
|
|
55
|
+
|
|
56
|
+
# View recent traces
|
|
57
|
+
pisama-cc traces
|
|
58
|
+
|
|
59
|
+
# Show token usage and cost
|
|
60
|
+
pisama-cc usage --by-model --by-tool
|
|
61
|
+
|
|
62
|
+
# Export traces
|
|
63
|
+
pisama-cc export -o traces.jsonl
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Overview
|
|
67
|
+
|
|
68
|
+
Lightweight trace capture client for Claude Code. Captures tool calls, token usage, and costs. Optionally syncs to PISAMA platform for analysis.
|
|
69
|
+
|
|
70
|
+
**This package captures traces. Analysis happens on the platform.**
|
|
71
|
+
|
|
72
|
+
```
|
|
73
|
+
┌─────────────────────┐ ┌─────────────────────┐
|
|
74
|
+
│ Claude Code │ │ PISAMA Platform │
|
|
75
|
+
│ + pisama-cc │ ──────▶ │ (analysis) │
|
|
76
|
+
│ (capture) │ sync │ (detection) │
|
|
77
|
+
└─────────────────────┘ │ (self-healing) │
|
|
78
|
+
└─────────────────────┘
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Features
|
|
82
|
+
|
|
83
|
+
### Local Features (no platform needed)
|
|
84
|
+
|
|
85
|
+
| Feature | Description |
|
|
86
|
+
|---------|-------------|
|
|
87
|
+
| Trace capture | Capture all tool calls (Bash, Read, Write, etc.) |
|
|
88
|
+
| Token tracking | Input, output, and cache tokens per call |
|
|
89
|
+
| Cost calculation | USD cost per trace and session totals |
|
|
90
|
+
| Model tracking | Track which Claude model is used |
|
|
91
|
+
| Local storage | SQLite + JSONL in `~/.claude/pisama/traces/` |
|
|
92
|
+
| Export | JSONL and gzip formats |
|
|
93
|
+
|
|
94
|
+
### Platform Features (requires connection)
|
|
95
|
+
|
|
96
|
+
| Feature | Description |
|
|
97
|
+
|---------|-------------|
|
|
98
|
+
| Failure detection | 28 MAST failure modes |
|
|
99
|
+
| Severity scores | Confidence and severity ratings |
|
|
100
|
+
| Fix suggestions | AI-powered remediation |
|
|
101
|
+
| Self-healing | Automatic intervention |
|
|
102
|
+
| Dashboard | Visual analytics |
|
|
103
|
+
|
|
104
|
+
## CLI Commands
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
pisama-cc install # Install capture hooks to ~/.claude/hooks/
|
|
108
|
+
pisama-cc uninstall # Remove hooks
|
|
109
|
+
pisama-cc status # Show status, token totals, and cost
|
|
110
|
+
pisama-cc traces # View recent traces
|
|
111
|
+
pisama-cc traces -v # Verbose: show tokens and cost per trace
|
|
112
|
+
pisama-cc usage # Token usage and cost breakdown
|
|
113
|
+
pisama-cc usage --by-model --by-tool # Grouped breakdown
|
|
114
|
+
pisama-cc export -o FILE # Export to JSONL
|
|
115
|
+
pisama-cc export --compress # Export to gzip
|
|
116
|
+
pisama-cc connect # Connect to PISAMA platform
|
|
117
|
+
pisama-cc sync # Upload traces to platform
|
|
118
|
+
pisama-cc analyze # Run failure detection (requires platform)
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
## Example Output
|
|
122
|
+
|
|
123
|
+
### Status
|
|
124
|
+
```
|
|
125
|
+
📊 PISAMA Status
|
|
126
|
+
========================================
|
|
127
|
+
|
|
128
|
+
🔧 Hook Installation:
|
|
129
|
+
✅ pisama-capture.py
|
|
130
|
+
✅ pisama-pre.sh
|
|
131
|
+
✅ pisama-post.sh
|
|
132
|
+
All hooks installed
|
|
133
|
+
|
|
134
|
+
📁 Local Traces: 1400
|
|
135
|
+
Input tokens: 9,580
|
|
136
|
+
Output tokens: 79,569
|
|
137
|
+
Total cost: $43.22
|
|
138
|
+
Models: claude-opus-4-5-20251101
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### Usage Breakdown
|
|
142
|
+
```
|
|
143
|
+
📊 Token Usage Summary (last 100 traces)
|
|
144
|
+
==================================================
|
|
145
|
+
Input tokens: 10,234
|
|
146
|
+
Output tokens: 85,421
|
|
147
|
+
Cache read tokens: 1,234,567
|
|
148
|
+
Total tokens: 95,655
|
|
149
|
+
Total cost: $ 52.34
|
|
150
|
+
|
|
151
|
+
📈 By Model:
|
|
152
|
+
--------------------------------------------------
|
|
153
|
+
claude-opus-4-5-20251101 $52.34
|
|
154
|
+
|
|
155
|
+
🔧 By Tool:
|
|
156
|
+
--------------------------------------------------
|
|
157
|
+
Bash 45 calls $25.12
|
|
158
|
+
Read 30 calls $15.34
|
|
159
|
+
Write 20 calls $8.45
|
|
160
|
+
Edit 5 calls $3.43
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
## Model Pricing
|
|
164
|
+
|
|
165
|
+
Supported models and pricing (per 1M tokens):
|
|
166
|
+
|
|
167
|
+
| Model | Input | Output | Cache Read |
|
|
168
|
+
|-------|-------|--------|------------|
|
|
169
|
+
| claude-opus-4-5 | $15.00 | $75.00 | $1.50 |
|
|
170
|
+
| claude-sonnet-4 | $3.00 | $15.00 | $0.30 |
|
|
171
|
+
| claude-3-5-sonnet | $3.00 | $15.00 | $0.30 |
|
|
172
|
+
| claude-3-5-haiku | $0.80 | $4.00 | $0.08 |
|
|
173
|
+
|
|
174
|
+
## Privacy
|
|
175
|
+
|
|
176
|
+
- Traces stored locally in `~/.claude/pisama/traces/`
|
|
177
|
+
- Secrets automatically redacted (API keys, passwords, tokens)
|
|
178
|
+
- File paths anonymized (home directory → `~`)
|
|
179
|
+
- Platform sync is opt-in
|
|
180
|
+
|
|
181
|
+
## Requirements
|
|
182
|
+
|
|
183
|
+
- Python 3.10+
|
|
184
|
+
- Claude Code CLI
|
|
185
|
+
|
|
186
|
+
## Configuration
|
|
187
|
+
|
|
188
|
+
After installation, add hooks to `~/.claude/settings.local.json`:
|
|
189
|
+
|
|
190
|
+
```json
|
|
191
|
+
{
|
|
192
|
+
"hooks": {
|
|
193
|
+
"PreToolCall": [
|
|
194
|
+
{
|
|
195
|
+
"command": "~/.claude/hooks/pisama-pre.sh",
|
|
196
|
+
"timeout": 2000
|
|
197
|
+
}
|
|
198
|
+
],
|
|
199
|
+
"PostToolCall": [
|
|
200
|
+
{
|
|
201
|
+
"command": "~/.claude/hooks/pisama-post.sh",
|
|
202
|
+
"timeout": 2000
|
|
203
|
+
}
|
|
204
|
+
]
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
## License
|
|
210
|
+
|
|
211
|
+
MIT
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
# pisama-claude-code
|
|
2
|
+
|
|
3
|
+
Trace capture for Claude Code sessions with token usage and cost tracking.
|
|
4
|
+
|
|
5
|
+
[](https://pypi.org/project/pisama-claude-code/)
|
|
6
|
+
[](https://www.python.org/downloads/)
|
|
7
|
+
[](https://opensource.org/licenses/MIT)
|
|
8
|
+
|
|
9
|
+
## Installation
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
pip install pisama-claude-code
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Quick Start
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
# Install capture hooks
|
|
19
|
+
pisama-cc install
|
|
20
|
+
|
|
21
|
+
# View recent traces
|
|
22
|
+
pisama-cc traces
|
|
23
|
+
|
|
24
|
+
# Show token usage and cost
|
|
25
|
+
pisama-cc usage --by-model --by-tool
|
|
26
|
+
|
|
27
|
+
# Export traces
|
|
28
|
+
pisama-cc export -o traces.jsonl
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Overview
|
|
32
|
+
|
|
33
|
+
Lightweight trace capture client for Claude Code. Captures tool calls, token usage, and costs. Optionally syncs to PISAMA platform for analysis.
|
|
34
|
+
|
|
35
|
+
**This package captures traces. Analysis happens on the platform.**
|
|
36
|
+
|
|
37
|
+
```
|
|
38
|
+
┌─────────────────────┐ ┌─────────────────────┐
|
|
39
|
+
│ Claude Code │ │ PISAMA Platform │
|
|
40
|
+
│ + pisama-cc │ ──────▶ │ (analysis) │
|
|
41
|
+
│ (capture) │ sync │ (detection) │
|
|
42
|
+
└─────────────────────┘ │ (self-healing) │
|
|
43
|
+
└─────────────────────┘
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Features
|
|
47
|
+
|
|
48
|
+
### Local Features (no platform needed)
|
|
49
|
+
|
|
50
|
+
| Feature | Description |
|
|
51
|
+
|---------|-------------|
|
|
52
|
+
| Trace capture | Capture all tool calls (Bash, Read, Write, etc.) |
|
|
53
|
+
| Token tracking | Input, output, and cache tokens per call |
|
|
54
|
+
| Cost calculation | USD cost per trace and session totals |
|
|
55
|
+
| Model tracking | Track which Claude model is used |
|
|
56
|
+
| Local storage | SQLite + JSONL in `~/.claude/pisama/traces/` |
|
|
57
|
+
| Export | JSONL and gzip formats |
|
|
58
|
+
|
|
59
|
+
### Platform Features (requires connection)
|
|
60
|
+
|
|
61
|
+
| Feature | Description |
|
|
62
|
+
|---------|-------------|
|
|
63
|
+
| Failure detection | 28 MAST failure modes |
|
|
64
|
+
| Severity scores | Confidence and severity ratings |
|
|
65
|
+
| Fix suggestions | AI-powered remediation |
|
|
66
|
+
| Self-healing | Automatic intervention |
|
|
67
|
+
| Dashboard | Visual analytics |
|
|
68
|
+
|
|
69
|
+
## CLI Commands
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
pisama-cc install # Install capture hooks to ~/.claude/hooks/
|
|
73
|
+
pisama-cc uninstall # Remove hooks
|
|
74
|
+
pisama-cc status # Show status, token totals, and cost
|
|
75
|
+
pisama-cc traces # View recent traces
|
|
76
|
+
pisama-cc traces -v # Verbose: show tokens and cost per trace
|
|
77
|
+
pisama-cc usage # Token usage and cost breakdown
|
|
78
|
+
pisama-cc usage --by-model --by-tool # Grouped breakdown
|
|
79
|
+
pisama-cc export -o FILE # Export to JSONL
|
|
80
|
+
pisama-cc export --compress # Export to gzip
|
|
81
|
+
pisama-cc connect # Connect to PISAMA platform
|
|
82
|
+
pisama-cc sync # Upload traces to platform
|
|
83
|
+
pisama-cc analyze # Run failure detection (requires platform)
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## Example Output
|
|
87
|
+
|
|
88
|
+
### Status
|
|
89
|
+
```
|
|
90
|
+
📊 PISAMA Status
|
|
91
|
+
========================================
|
|
92
|
+
|
|
93
|
+
🔧 Hook Installation:
|
|
94
|
+
✅ pisama-capture.py
|
|
95
|
+
✅ pisama-pre.sh
|
|
96
|
+
✅ pisama-post.sh
|
|
97
|
+
All hooks installed
|
|
98
|
+
|
|
99
|
+
📁 Local Traces: 1400
|
|
100
|
+
Input tokens: 9,580
|
|
101
|
+
Output tokens: 79,569
|
|
102
|
+
Total cost: $43.22
|
|
103
|
+
Models: claude-opus-4-5-20251101
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### Usage Breakdown
|
|
107
|
+
```
|
|
108
|
+
📊 Token Usage Summary (last 100 traces)
|
|
109
|
+
==================================================
|
|
110
|
+
Input tokens: 10,234
|
|
111
|
+
Output tokens: 85,421
|
|
112
|
+
Cache read tokens: 1,234,567
|
|
113
|
+
Total tokens: 95,655
|
|
114
|
+
Total cost: $ 52.34
|
|
115
|
+
|
|
116
|
+
📈 By Model:
|
|
117
|
+
--------------------------------------------------
|
|
118
|
+
claude-opus-4-5-20251101 $52.34
|
|
119
|
+
|
|
120
|
+
🔧 By Tool:
|
|
121
|
+
--------------------------------------------------
|
|
122
|
+
Bash 45 calls $25.12
|
|
123
|
+
Read 30 calls $15.34
|
|
124
|
+
Write 20 calls $8.45
|
|
125
|
+
Edit 5 calls $3.43
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
## Model Pricing
|
|
129
|
+
|
|
130
|
+
Supported models and pricing (per 1M tokens):
|
|
131
|
+
|
|
132
|
+
| Model | Input | Output | Cache Read |
|
|
133
|
+
|-------|-------|--------|------------|
|
|
134
|
+
| claude-opus-4-5 | $15.00 | $75.00 | $1.50 |
|
|
135
|
+
| claude-sonnet-4 | $3.00 | $15.00 | $0.30 |
|
|
136
|
+
| claude-3-5-sonnet | $3.00 | $15.00 | $0.30 |
|
|
137
|
+
| claude-3-5-haiku | $0.80 | $4.00 | $0.08 |
|
|
138
|
+
|
|
139
|
+
## Privacy
|
|
140
|
+
|
|
141
|
+
- Traces stored locally in `~/.claude/pisama/traces/`
|
|
142
|
+
- Secrets automatically redacted (API keys, passwords, tokens)
|
|
143
|
+
- File paths anonymized (home directory → `~`)
|
|
144
|
+
- Platform sync is opt-in
|
|
145
|
+
|
|
146
|
+
## Requirements
|
|
147
|
+
|
|
148
|
+
- Python 3.10+
|
|
149
|
+
- Claude Code CLI
|
|
150
|
+
|
|
151
|
+
## Configuration
|
|
152
|
+
|
|
153
|
+
After installation, add hooks to `~/.claude/settings.local.json`:
|
|
154
|
+
|
|
155
|
+
```json
|
|
156
|
+
{
|
|
157
|
+
"hooks": {
|
|
158
|
+
"PreToolCall": [
|
|
159
|
+
{
|
|
160
|
+
"command": "~/.claude/hooks/pisama-pre.sh",
|
|
161
|
+
"timeout": 2000
|
|
162
|
+
}
|
|
163
|
+
],
|
|
164
|
+
"PostToolCall": [
|
|
165
|
+
{
|
|
166
|
+
"command": "~/.claude/hooks/pisama-post.sh",
|
|
167
|
+
"timeout": 2000
|
|
168
|
+
}
|
|
169
|
+
]
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
## License
|
|
175
|
+
|
|
176
|
+
MIT
|
|
@@ -53,7 +53,7 @@ def save_config(config: dict):
|
|
|
53
53
|
|
|
54
54
|
|
|
55
55
|
@click.group()
|
|
56
|
-
@click.version_option(version="0.3.
|
|
56
|
+
@click.version_option(version="0.3.2")
|
|
57
57
|
def main():
|
|
58
58
|
"""PISAMA Claude Code - Trace capture and sync."""
|
|
59
59
|
pass
|
|
@@ -680,5 +680,463 @@ def mark_synced(traces: list):
|
|
|
680
680
|
}) + "\n")
|
|
681
681
|
|
|
682
682
|
|
|
683
|
+
# =============================================================================
|
|
684
|
+
# VAULT COMMANDS - PII Tokenization Vault Management
|
|
685
|
+
# =============================================================================
|
|
686
|
+
|
|
687
|
+
@main.group()
|
|
688
|
+
def vault():
|
|
689
|
+
"""Manage the PII tokenization vault."""
|
|
690
|
+
pass
|
|
691
|
+
|
|
692
|
+
|
|
693
|
+
@vault.command("status")
|
|
694
|
+
def vault_status():
|
|
695
|
+
"""Show vault status and statistics."""
|
|
696
|
+
try:
|
|
697
|
+
from pisama_core.tokenization import TokenVault, KeychainManager
|
|
698
|
+
except ImportError:
|
|
699
|
+
click.echo("❌ pisama-core not installed or tokenization not available")
|
|
700
|
+
click.echo(" Install with: pip install pisama-core[tokenization]")
|
|
701
|
+
return
|
|
702
|
+
|
|
703
|
+
vault_path = CONFIG_DIR / "vault.db"
|
|
704
|
+
|
|
705
|
+
click.echo("🔐 PISAMA Vault Status")
|
|
706
|
+
click.echo("=" * 50)
|
|
707
|
+
|
|
708
|
+
# Check keychain
|
|
709
|
+
click.echo("\n🔑 Encryption Key:")
|
|
710
|
+
try:
|
|
711
|
+
keychain = KeychainManager(allow_file_fallback=True)
|
|
712
|
+
status = keychain.get_status()
|
|
713
|
+
if status["available"]:
|
|
714
|
+
click.echo(f" Backend: {status['backend']}")
|
|
715
|
+
if status["key_exists"]:
|
|
716
|
+
click.echo(" ✅ Key stored")
|
|
717
|
+
if not status["is_secure"]:
|
|
718
|
+
click.echo(" ⚠️ Using file fallback (less secure)")
|
|
719
|
+
else:
|
|
720
|
+
click.echo(" ❌ No key found")
|
|
721
|
+
click.echo(" Key will be auto-generated on first use")
|
|
722
|
+
else:
|
|
723
|
+
click.echo(" ❌ Keychain not available")
|
|
724
|
+
except Exception as e:
|
|
725
|
+
click.echo(f" ❌ Error: {e}")
|
|
726
|
+
|
|
727
|
+
# Check vault database
|
|
728
|
+
click.echo("\n💾 Vault Database:")
|
|
729
|
+
if vault_path.exists():
|
|
730
|
+
size_kb = vault_path.stat().st_size // 1024
|
|
731
|
+
click.echo(f" Path: {vault_path}")
|
|
732
|
+
click.echo(f" Size: {size_kb} KB")
|
|
733
|
+
|
|
734
|
+
try:
|
|
735
|
+
token_vault = TokenVault(vault_path)
|
|
736
|
+
token_vault.initialize()
|
|
737
|
+
stats = token_vault.get_stats()
|
|
738
|
+
|
|
739
|
+
click.echo(f" Total tokens: {stats.get('total_tokens', 0):,}")
|
|
740
|
+
click.echo(f" Sessions: {stats.get('unique_sessions', 0)}")
|
|
741
|
+
|
|
742
|
+
by_type = stats.get("by_type", {})
|
|
743
|
+
if by_type:
|
|
744
|
+
click.echo(" By type:")
|
|
745
|
+
for pii_type, count in sorted(by_type.items()):
|
|
746
|
+
click.echo(f" {pii_type}: {count:,}")
|
|
747
|
+
|
|
748
|
+
if not stats.get("encryption_available"):
|
|
749
|
+
click.echo(" ⚠️ Encryption not available (install cryptography)")
|
|
750
|
+
|
|
751
|
+
token_vault.close()
|
|
752
|
+
except Exception as e:
|
|
753
|
+
click.echo(f" ❌ Error reading vault: {e}")
|
|
754
|
+
else:
|
|
755
|
+
click.echo(f" Path: {vault_path}")
|
|
756
|
+
click.echo(" Status: Not created yet")
|
|
757
|
+
click.echo(" Will be created on first tokenization")
|
|
758
|
+
|
|
759
|
+
# Tokenization config
|
|
760
|
+
click.echo("\n⚙️ Configuration:")
|
|
761
|
+
config_data = get_config()
|
|
762
|
+
tokenization = config_data.get("tokenization", {})
|
|
763
|
+
enabled = tokenization.get("enabled", True)
|
|
764
|
+
click.echo(f" Tokenization: {'enabled' if enabled else 'disabled'}")
|
|
765
|
+
|
|
766
|
+
|
|
767
|
+
@vault.command("lookup")
|
|
768
|
+
@click.argument("token")
|
|
769
|
+
@click.option("--reason", "-r", required=True, help="Reason for lookup (audit log)")
|
|
770
|
+
@click.option("--ticket", "-t", help="Incident ticket reference")
|
|
771
|
+
def vault_lookup(token: str, reason: str, ticket: Optional[str]):
|
|
772
|
+
"""Look up a token to reveal original value (with audit logging)."""
|
|
773
|
+
try:
|
|
774
|
+
from pisama_core.tokenization import TokenVault, KeychainManager, TokenParser
|
|
775
|
+
except ImportError:
|
|
776
|
+
click.echo("❌ pisama-core not installed")
|
|
777
|
+
return
|
|
778
|
+
|
|
779
|
+
vault_path = CONFIG_DIR / "vault.db"
|
|
780
|
+
|
|
781
|
+
if not vault_path.exists():
|
|
782
|
+
click.echo("❌ Vault not found")
|
|
783
|
+
return
|
|
784
|
+
|
|
785
|
+
# Validate token format
|
|
786
|
+
parser = TokenParser()
|
|
787
|
+
if not parser.is_valid_token(token):
|
|
788
|
+
click.echo(f"❌ Invalid token format: {token}")
|
|
789
|
+
click.echo(" Expected: [TYPE:sess:random]")
|
|
790
|
+
return
|
|
791
|
+
|
|
792
|
+
# Get encryption key
|
|
793
|
+
keychain = KeychainManager(allow_file_fallback=True)
|
|
794
|
+
key = keychain.get_key()
|
|
795
|
+
|
|
796
|
+
if key is None:
|
|
797
|
+
click.echo("❌ No encryption key found")
|
|
798
|
+
return
|
|
799
|
+
|
|
800
|
+
# Lookup in vault
|
|
801
|
+
token_vault = TokenVault(vault_path)
|
|
802
|
+
token_vault.initialize()
|
|
803
|
+
|
|
804
|
+
try:
|
|
805
|
+
original = token_vault.retrieve(token, key)
|
|
806
|
+
|
|
807
|
+
if original:
|
|
808
|
+
click.echo(f"Token: {token}")
|
|
809
|
+
click.echo(f"Value: {original}")
|
|
810
|
+
|
|
811
|
+
# Get token info
|
|
812
|
+
info = token_vault.get_token_info(token)
|
|
813
|
+
if info:
|
|
814
|
+
click.echo(f"Type: {info.pii_type}")
|
|
815
|
+
click.echo(f"Session: {info.session_id}")
|
|
816
|
+
click.echo(f"Created: {info.created_at}")
|
|
817
|
+
|
|
818
|
+
# Log to audit
|
|
819
|
+
_log_vault_access(token, reason, ticket, "lookup")
|
|
820
|
+
click.echo(f"\n✅ Access logged (reason: {reason})")
|
|
821
|
+
else:
|
|
822
|
+
click.echo(f"❌ Token not found: {token}")
|
|
823
|
+
finally:
|
|
824
|
+
token_vault.close()
|
|
825
|
+
|
|
826
|
+
|
|
827
|
+
@vault.command("delete")
|
|
828
|
+
@click.option("--token", help="Delete specific token")
|
|
829
|
+
@click.option("--session", help="Delete all tokens for session")
|
|
830
|
+
@click.option("--value-hash", help="Delete by value hash (GDPR erasure)")
|
|
831
|
+
@click.option("--reason", "-r", required=True, help="Reason for deletion")
|
|
832
|
+
@click.option("--confirm", is_flag=True, help="Skip confirmation prompt")
|
|
833
|
+
def vault_delete(
|
|
834
|
+
token: Optional[str],
|
|
835
|
+
session: Optional[str],
|
|
836
|
+
value_hash: Optional[str],
|
|
837
|
+
reason: str,
|
|
838
|
+
confirm: bool,
|
|
839
|
+
):
|
|
840
|
+
"""Delete tokens from vault (GDPR erasure support)."""
|
|
841
|
+
try:
|
|
842
|
+
from pisama_core.tokenization import TokenVault
|
|
843
|
+
except ImportError:
|
|
844
|
+
click.echo("❌ pisama-core not installed")
|
|
845
|
+
return
|
|
846
|
+
|
|
847
|
+
if not any([token, session, value_hash]):
|
|
848
|
+
click.echo("❌ Specify --token, --session, or --value-hash")
|
|
849
|
+
return
|
|
850
|
+
|
|
851
|
+
vault_path = CONFIG_DIR / "vault.db"
|
|
852
|
+
if not vault_path.exists():
|
|
853
|
+
click.echo("❌ Vault not found")
|
|
854
|
+
return
|
|
855
|
+
|
|
856
|
+
token_vault = TokenVault(vault_path)
|
|
857
|
+
token_vault.initialize()
|
|
858
|
+
|
|
859
|
+
try:
|
|
860
|
+
if token:
|
|
861
|
+
if not confirm:
|
|
862
|
+
click.confirm(f"Delete token {token}?", abort=True)
|
|
863
|
+
deleted = token_vault.delete_token(token)
|
|
864
|
+
if deleted:
|
|
865
|
+
click.echo(f"✅ Deleted token: {token}")
|
|
866
|
+
_log_vault_access(token, reason, None, "delete")
|
|
867
|
+
else:
|
|
868
|
+
click.echo(f"❌ Token not found: {token}")
|
|
869
|
+
|
|
870
|
+
elif session:
|
|
871
|
+
tokens = token_vault.list_session_tokens(session)
|
|
872
|
+
if not tokens:
|
|
873
|
+
click.echo(f"No tokens found for session: {session}")
|
|
874
|
+
return
|
|
875
|
+
|
|
876
|
+
if not confirm:
|
|
877
|
+
click.confirm(f"Delete {len(tokens)} tokens for session {session}?", abort=True)
|
|
878
|
+
|
|
879
|
+
count = token_vault.delete_session(session)
|
|
880
|
+
click.echo(f"✅ Deleted {count} tokens for session {session}")
|
|
881
|
+
_log_vault_access(f"session:{session}", reason, None, "delete_session")
|
|
882
|
+
|
|
883
|
+
elif value_hash:
|
|
884
|
+
if not confirm:
|
|
885
|
+
click.confirm(f"Delete all tokens with value hash {value_hash[:16]}...?", abort=True)
|
|
886
|
+
|
|
887
|
+
count = token_vault.delete_by_value_hash(value_hash)
|
|
888
|
+
if count > 0:
|
|
889
|
+
click.echo(f"✅ Deleted {count} tokens (GDPR erasure)")
|
|
890
|
+
_log_vault_access(f"hash:{value_hash[:16]}", reason, None, "gdpr_erasure")
|
|
891
|
+
else:
|
|
892
|
+
click.echo("No matching tokens found")
|
|
893
|
+
|
|
894
|
+
finally:
|
|
895
|
+
token_vault.close()
|
|
896
|
+
|
|
897
|
+
|
|
898
|
+
@vault.command("health")
|
|
899
|
+
def vault_health():
|
|
900
|
+
"""Check vault health and integrity."""
|
|
901
|
+
try:
|
|
902
|
+
from pisama_core.tokenization import TokenVault, KeychainManager
|
|
903
|
+
except ImportError:
|
|
904
|
+
click.echo("❌ pisama-core not installed")
|
|
905
|
+
return
|
|
906
|
+
|
|
907
|
+
vault_path = CONFIG_DIR / "vault.db"
|
|
908
|
+
|
|
909
|
+
click.echo("🏥 Vault Health Check")
|
|
910
|
+
click.echo("=" * 50)
|
|
911
|
+
|
|
912
|
+
issues = []
|
|
913
|
+
|
|
914
|
+
# Check vault file
|
|
915
|
+
click.echo("\n1. Vault Database:")
|
|
916
|
+
if vault_path.exists():
|
|
917
|
+
click.echo(" ✅ Database exists")
|
|
918
|
+
size = vault_path.stat().st_size
|
|
919
|
+
if size > 100 * 1024 * 1024: # 100MB
|
|
920
|
+
click.echo(f" ⚠️ Large database ({size // 1024 // 1024} MB)")
|
|
921
|
+
issues.append("Consider running 'pisama-cc vault vacuum'")
|
|
922
|
+
else:
|
|
923
|
+
click.echo(" ⚠️ Database not created yet")
|
|
924
|
+
|
|
925
|
+
# Check encryption key
|
|
926
|
+
click.echo("\n2. Encryption Key:")
|
|
927
|
+
try:
|
|
928
|
+
keychain = KeychainManager(allow_file_fallback=True)
|
|
929
|
+
if keychain.key_exists():
|
|
930
|
+
click.echo(" ✅ Key available")
|
|
931
|
+
status = keychain.get_status()
|
|
932
|
+
if not status["is_secure"]:
|
|
933
|
+
click.echo(" ⚠️ Using file fallback (less secure)")
|
|
934
|
+
issues.append("Consider enabling OS keychain for better security")
|
|
935
|
+
else:
|
|
936
|
+
click.echo(" ⚠️ No key (will be created on first use)")
|
|
937
|
+
except Exception as e:
|
|
938
|
+
click.echo(f" ❌ Error: {e}")
|
|
939
|
+
issues.append(f"Keychain error: {e}")
|
|
940
|
+
|
|
941
|
+
# Check database integrity
|
|
942
|
+
click.echo("\n3. Database Integrity:")
|
|
943
|
+
if vault_path.exists():
|
|
944
|
+
try:
|
|
945
|
+
import sqlite3
|
|
946
|
+
conn = sqlite3.connect(str(vault_path))
|
|
947
|
+
result = conn.execute("PRAGMA integrity_check").fetchone()
|
|
948
|
+
if result[0] == "ok":
|
|
949
|
+
click.echo(" ✅ Integrity check passed")
|
|
950
|
+
else:
|
|
951
|
+
click.echo(f" ❌ Integrity issue: {result[0]}")
|
|
952
|
+
issues.append("Database integrity issue detected")
|
|
953
|
+
conn.close()
|
|
954
|
+
except Exception as e:
|
|
955
|
+
click.echo(f" ❌ Error: {e}")
|
|
956
|
+
issues.append(f"Database error: {e}")
|
|
957
|
+
else:
|
|
958
|
+
click.echo(" ⚠️ No database to check")
|
|
959
|
+
|
|
960
|
+
# Summary
|
|
961
|
+
click.echo("\n" + "=" * 50)
|
|
962
|
+
if issues:
|
|
963
|
+
click.echo(f"⚠️ {len(issues)} issue(s) found:")
|
|
964
|
+
for issue in issues:
|
|
965
|
+
click.echo(f" • {issue}")
|
|
966
|
+
else:
|
|
967
|
+
click.echo("✅ All checks passed")
|
|
968
|
+
|
|
969
|
+
|
|
970
|
+
@vault.command("backup")
|
|
971
|
+
@click.option("--output", "-o", help="Output path (default: vault-backup-{date}.db)")
|
|
972
|
+
def vault_backup(output: Optional[str]):
|
|
973
|
+
"""Create a backup of the vault database."""
|
|
974
|
+
vault_path = CONFIG_DIR / "vault.db"
|
|
975
|
+
|
|
976
|
+
if not vault_path.exists():
|
|
977
|
+
click.echo("❌ No vault to backup")
|
|
978
|
+
return
|
|
979
|
+
|
|
980
|
+
# Generate backup path
|
|
981
|
+
if output:
|
|
982
|
+
backup_path = Path(output)
|
|
983
|
+
else:
|
|
984
|
+
date_str = datetime.now().strftime("%Y%m%d-%H%M%S")
|
|
985
|
+
backup_path = CONFIG_DIR / f"vault-backup-{date_str}.db"
|
|
986
|
+
|
|
987
|
+
# Copy vault
|
|
988
|
+
import shutil
|
|
989
|
+
try:
|
|
990
|
+
shutil.copy2(vault_path, backup_path)
|
|
991
|
+
size_kb = backup_path.stat().st_size // 1024
|
|
992
|
+
click.echo(f"✅ Backup created: {backup_path} ({size_kb} KB)")
|
|
993
|
+
except Exception as e:
|
|
994
|
+
click.echo(f"❌ Backup failed: {e}")
|
|
995
|
+
|
|
996
|
+
|
|
997
|
+
@vault.command("restore")
|
|
998
|
+
@click.argument("backup_path")
|
|
999
|
+
@click.option("--confirm", is_flag=True, help="Skip confirmation")
|
|
1000
|
+
def vault_restore(backup_path: str, confirm: bool):
|
|
1001
|
+
"""Restore vault from a backup."""
|
|
1002
|
+
backup = Path(backup_path)
|
|
1003
|
+
|
|
1004
|
+
if not backup.exists():
|
|
1005
|
+
click.echo(f"❌ Backup not found: {backup_path}")
|
|
1006
|
+
return
|
|
1007
|
+
|
|
1008
|
+
vault_path = CONFIG_DIR / "vault.db"
|
|
1009
|
+
|
|
1010
|
+
if vault_path.exists() and not confirm:
|
|
1011
|
+
click.confirm("This will overwrite the current vault. Continue?", abort=True)
|
|
1012
|
+
|
|
1013
|
+
import shutil
|
|
1014
|
+
try:
|
|
1015
|
+
shutil.copy2(backup, vault_path)
|
|
1016
|
+
click.echo(f"✅ Vault restored from {backup_path}")
|
|
1017
|
+
except Exception as e:
|
|
1018
|
+
click.echo(f"❌ Restore failed: {e}")
|
|
1019
|
+
|
|
1020
|
+
|
|
1021
|
+
@vault.command("vacuum")
|
|
1022
|
+
def vault_vacuum():
|
|
1023
|
+
"""Compact the vault database to reclaim space."""
|
|
1024
|
+
try:
|
|
1025
|
+
from pisama_core.tokenization import TokenVault
|
|
1026
|
+
except ImportError:
|
|
1027
|
+
click.echo("❌ pisama-core not installed")
|
|
1028
|
+
return
|
|
1029
|
+
|
|
1030
|
+
vault_path = CONFIG_DIR / "vault.db"
|
|
1031
|
+
|
|
1032
|
+
if not vault_path.exists():
|
|
1033
|
+
click.echo("❌ No vault to vacuum")
|
|
1034
|
+
return
|
|
1035
|
+
|
|
1036
|
+
size_before = vault_path.stat().st_size
|
|
1037
|
+
|
|
1038
|
+
token_vault = TokenVault(vault_path)
|
|
1039
|
+
token_vault.initialize()
|
|
1040
|
+
token_vault.vacuum()
|
|
1041
|
+
token_vault.close()
|
|
1042
|
+
|
|
1043
|
+
size_after = vault_path.stat().st_size
|
|
1044
|
+
saved = size_before - size_after
|
|
1045
|
+
|
|
1046
|
+
click.echo(f"✅ Vault compacted")
|
|
1047
|
+
click.echo(f" Before: {size_before // 1024} KB")
|
|
1048
|
+
click.echo(f" After: {size_after // 1024} KB")
|
|
1049
|
+
click.echo(f" Saved: {saved // 1024} KB")
|
|
1050
|
+
|
|
1051
|
+
|
|
1052
|
+
def _log_vault_access(target: str, reason: str, ticket: Optional[str], action: str):
|
|
1053
|
+
"""Log vault access to audit log."""
|
|
1054
|
+
import os
|
|
1055
|
+
|
|
1056
|
+
audit_log = CONFIG_DIR / "audit_log.jsonl"
|
|
1057
|
+
audit_log.parent.mkdir(parents=True, exist_ok=True)
|
|
1058
|
+
|
|
1059
|
+
entry = {
|
|
1060
|
+
"timestamp": datetime.now(timezone.utc).isoformat(),
|
|
1061
|
+
"action": action,
|
|
1062
|
+
"target": target,
|
|
1063
|
+
"reason": reason,
|
|
1064
|
+
"ticket": ticket,
|
|
1065
|
+
"principal": os.environ.get("USER", "unknown"),
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1068
|
+
with open(audit_log, "a") as f:
|
|
1069
|
+
f.write(json.dumps(entry) + "\n")
|
|
1070
|
+
|
|
1071
|
+
|
|
1072
|
+
# =============================================================================
|
|
1073
|
+
# TOKENIZE COMMAND - Manual tokenization testing
|
|
1074
|
+
# =============================================================================
|
|
1075
|
+
|
|
1076
|
+
@main.group()
|
|
1077
|
+
def tokenize():
|
|
1078
|
+
"""PII tokenization tools."""
|
|
1079
|
+
pass
|
|
1080
|
+
|
|
1081
|
+
|
|
1082
|
+
@tokenize.command("test")
|
|
1083
|
+
@click.argument("text")
|
|
1084
|
+
@click.option("--patterns", help="Comma-separated list of patterns to use")
|
|
1085
|
+
def tokenize_test(text: str, patterns: Optional[str]):
|
|
1086
|
+
"""Test PII detection on a string."""
|
|
1087
|
+
try:
|
|
1088
|
+
from pisama_core.tokenization import PIIDetector
|
|
1089
|
+
except ImportError:
|
|
1090
|
+
click.echo("❌ pisama-core not installed")
|
|
1091
|
+
return
|
|
1092
|
+
|
|
1093
|
+
detector = PIIDetector()
|
|
1094
|
+
|
|
1095
|
+
# Filter patterns if specified
|
|
1096
|
+
if patterns:
|
|
1097
|
+
pattern_list = [p.strip() for p in patterns.split(",")]
|
|
1098
|
+
for name in list(detector.patterns.keys()):
|
|
1099
|
+
if name not in pattern_list:
|
|
1100
|
+
detector.disable_pattern(name)
|
|
1101
|
+
|
|
1102
|
+
matches = detector.detect(text)
|
|
1103
|
+
|
|
1104
|
+
click.echo("PII Detection Results")
|
|
1105
|
+
click.echo("=" * 50)
|
|
1106
|
+
click.echo(f"\nInput: \"{text}\"")
|
|
1107
|
+
|
|
1108
|
+
if matches:
|
|
1109
|
+
click.echo("\nDetected:")
|
|
1110
|
+
for m in matches:
|
|
1111
|
+
click.echo(f" [{m.pii_type}] \"{m.value}\" at position {m.start}-{m.end}")
|
|
1112
|
+
else:
|
|
1113
|
+
click.echo("\nNo PII detected")
|
|
1114
|
+
|
|
1115
|
+
|
|
1116
|
+
@tokenize.command("config")
|
|
1117
|
+
def tokenize_config():
|
|
1118
|
+
"""Show tokenization configuration."""
|
|
1119
|
+
config_data = get_config()
|
|
1120
|
+
tokenization = config_data.get("tokenization", {})
|
|
1121
|
+
|
|
1122
|
+
click.echo("⚙️ Tokenization Configuration")
|
|
1123
|
+
click.echo("=" * 50)
|
|
1124
|
+
click.echo(f"Enabled: {tokenization.get('enabled', True)}")
|
|
1125
|
+
click.echo(f"Fail-open: {tokenization.get('fail_open', True)}")
|
|
1126
|
+
|
|
1127
|
+
custom = tokenization.get("custom_patterns", {})
|
|
1128
|
+
if custom:
|
|
1129
|
+
click.echo("\nCustom Patterns:")
|
|
1130
|
+
for name, pattern in custom.items():
|
|
1131
|
+
if not name.startswith("_"):
|
|
1132
|
+
click.echo(f" {name}: {pattern}")
|
|
1133
|
+
|
|
1134
|
+
exclusions = tokenization.get("exclusions", [])
|
|
1135
|
+
if exclusions:
|
|
1136
|
+
click.echo("\nExclusions:")
|
|
1137
|
+
for e in exclusions:
|
|
1138
|
+
click.echo(f" {e}")
|
|
1139
|
+
|
|
1140
|
+
|
|
683
1141
|
if __name__ == "__main__":
|
|
684
1142
|
main()
|
{pisama_claude_code-0.3.1 → pisama_claude_code-0.3.2}/src/pisama_claude_code/hooks/capture_hook.py
RENAMED
|
@@ -8,6 +8,7 @@ Now includes:
|
|
|
8
8
|
- AI response capture from transcript
|
|
9
9
|
- Token usage tracking
|
|
10
10
|
- Cost calculation
|
|
11
|
+
- PII tokenization (optional, configurable)
|
|
11
12
|
|
|
12
13
|
Usage:
|
|
13
14
|
Install in ~/.claude/hooks/ and configure in settings.local.json
|
|
@@ -16,6 +17,11 @@ Usage:
|
|
|
16
17
|
import json
|
|
17
18
|
import os
|
|
18
19
|
import sys
|
|
20
|
+
from typing import Any
|
|
21
|
+
|
|
22
|
+
# PII Tokenization configuration
|
|
23
|
+
TOKENIZATION_ENABLED = os.environ.get("PISAMA_TOKENIZATION", "1") == "1"
|
|
24
|
+
TOKENIZATION_FIELDS = ["tool_input", "tool_output", "ai_response"]
|
|
19
25
|
|
|
20
26
|
# Claude model pricing (per 1M tokens) - as of Jan 2025
|
|
21
27
|
MODEL_PRICING = {
|
|
@@ -47,6 +53,71 @@ def calculate_cost(model: str, usage: dict) -> float:
|
|
|
47
53
|
return round(cost, 6)
|
|
48
54
|
|
|
49
55
|
|
|
56
|
+
def get_tokenizer(session_id: str) -> Any:
|
|
57
|
+
"""Get a Tokenizer instance for PII protection.
|
|
58
|
+
|
|
59
|
+
Returns None if tokenization is disabled or unavailable.
|
|
60
|
+
"""
|
|
61
|
+
if not TOKENIZATION_ENABLED:
|
|
62
|
+
return None
|
|
63
|
+
|
|
64
|
+
try:
|
|
65
|
+
from pisama_core.tokenization import Tokenizer
|
|
66
|
+
return Tokenizer(
|
|
67
|
+
session_id=session_id,
|
|
68
|
+
enabled=True,
|
|
69
|
+
fail_open=True, # Don't fail if tokenization has issues
|
|
70
|
+
)
|
|
71
|
+
except ImportError:
|
|
72
|
+
return None
|
|
73
|
+
except Exception:
|
|
74
|
+
return None
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def tokenize_trace_data(
|
|
78
|
+
trace: dict,
|
|
79
|
+
session_id: str,
|
|
80
|
+
fields: list[str] | None = None,
|
|
81
|
+
) -> dict:
|
|
82
|
+
"""Tokenize sensitive fields in trace data.
|
|
83
|
+
|
|
84
|
+
Args:
|
|
85
|
+
trace: The trace dictionary to tokenize.
|
|
86
|
+
session_id: Session ID for token scoping.
|
|
87
|
+
fields: Fields to tokenize (defaults to TOKENIZATION_FIELDS).
|
|
88
|
+
|
|
89
|
+
Returns:
|
|
90
|
+
Trace with PII tokenized (or original if tokenization unavailable).
|
|
91
|
+
"""
|
|
92
|
+
if not TOKENIZATION_ENABLED:
|
|
93
|
+
return trace
|
|
94
|
+
|
|
95
|
+
tokenizer = get_tokenizer(session_id)
|
|
96
|
+
if tokenizer is None:
|
|
97
|
+
return trace
|
|
98
|
+
|
|
99
|
+
fields = fields or TOKENIZATION_FIELDS
|
|
100
|
+
result = trace.copy()
|
|
101
|
+
|
|
102
|
+
try:
|
|
103
|
+
for field in fields:
|
|
104
|
+
if field in result and result[field]:
|
|
105
|
+
value = result[field]
|
|
106
|
+
if isinstance(value, str):
|
|
107
|
+
result[field] = tokenizer.tokenize_string(value)
|
|
108
|
+
elif isinstance(value, dict):
|
|
109
|
+
result[field] = tokenizer.tokenize_dict(value)
|
|
110
|
+
return result
|
|
111
|
+
except Exception:
|
|
112
|
+
# Fail open - return original trace if tokenization fails
|
|
113
|
+
return trace
|
|
114
|
+
finally:
|
|
115
|
+
try:
|
|
116
|
+
tokenizer.close()
|
|
117
|
+
except Exception:
|
|
118
|
+
pass
|
|
119
|
+
|
|
120
|
+
|
|
50
121
|
def get_last_assistant_message(transcript_path: str) -> dict:
|
|
51
122
|
"""Read transcript and get the last assistant message with usage."""
|
|
52
123
|
try:
|
|
@@ -178,6 +249,10 @@ def _fallback_capture(hook_data: dict, hook_type: str) -> None:
|
|
|
178
249
|
"raw": hook_data,
|
|
179
250
|
}
|
|
180
251
|
|
|
252
|
+
# Tokenize PII before storage (PostToolUse only to avoid double-tokenization)
|
|
253
|
+
if hook_type in ("post", "PostToolUse"):
|
|
254
|
+
trace = tokenize_trace_data(trace, session_id)
|
|
255
|
+
|
|
181
256
|
with open(jsonl_path, "a") as f:
|
|
182
257
|
f.write(json.dumps(trace) + "\n")
|
|
183
258
|
|
|
@@ -221,6 +296,7 @@ def _fallback_capture(hook_data: dict, hook_type: str) -> None:
|
|
|
221
296
|
except sqlite3.OperationalError:
|
|
222
297
|
pass # Column already exists
|
|
223
298
|
|
|
299
|
+
# Use tokenized values from trace dict
|
|
224
300
|
conn.execute("""
|
|
225
301
|
INSERT INTO traces (
|
|
226
302
|
session_id, timestamp, hook_type, tool_name, tool_input, tool_output,
|
|
@@ -232,15 +308,15 @@ def _fallback_capture(hook_data: dict, hook_type: str) -> None:
|
|
|
232
308
|
timestamp,
|
|
233
309
|
hook_type,
|
|
234
310
|
tool_name,
|
|
235
|
-
json.dumps(tool_input) if tool_input else None,
|
|
236
|
-
json.dumps(tool_output) if tool_output else None,
|
|
311
|
+
json.dumps(trace.get("tool_input")) if trace.get("tool_input") else None,
|
|
312
|
+
json.dumps(trace.get("tool_output")) if trace.get("tool_output") else None,
|
|
237
313
|
os.getcwd(),
|
|
238
314
|
model,
|
|
239
315
|
usage.get("input_tokens"),
|
|
240
316
|
usage.get("output_tokens"),
|
|
241
317
|
usage.get("cache_read_input_tokens"),
|
|
242
318
|
cost,
|
|
243
|
-
ai_response,
|
|
319
|
+
trace.get("ai_response"),
|
|
244
320
|
))
|
|
245
321
|
conn.commit()
|
|
246
322
|
conn.close()
|
|
@@ -1,105 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: pisama-claude-code
|
|
3
|
-
Version: 0.3.1
|
|
4
|
-
Summary: Trace capture for Claude Code sessions - sync to PISAMA platform for analysis
|
|
5
|
-
Project-URL: Homepage, https://pisama.dev
|
|
6
|
-
Project-URL: Documentation, https://pisama.dev/docs/claude-code
|
|
7
|
-
Project-URL: Repository, https://github.com/tn-pisama/pisama-claude-code
|
|
8
|
-
Author-email: PISAMA Team <team@pisama.dev>
|
|
9
|
-
License-Expression: MIT
|
|
10
|
-
License-File: LICENSE
|
|
11
|
-
Keywords: agent,claude,claude-code,forensics,llm,observability,pisama,self-healing
|
|
12
|
-
Classifier: Development Status :: 4 - Beta
|
|
13
|
-
Classifier: Intended Audience :: Developers
|
|
14
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
-
Classifier: Programming Language :: Python :: 3
|
|
16
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
-
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
-
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
-
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
20
|
-
Classifier: Topic :: System :: Monitoring
|
|
21
|
-
Requires-Python: >=3.10
|
|
22
|
-
Requires-Dist: click>=8.0.0
|
|
23
|
-
Requires-Dist: httpx>=0.25.0
|
|
24
|
-
Requires-Dist: pydantic>=2.0.0
|
|
25
|
-
Requires-Dist: rich>=13.0.0
|
|
26
|
-
Provides-Extra: all
|
|
27
|
-
Requires-Dist: pisama-core>=0.1.0; extra == 'all'
|
|
28
|
-
Provides-Extra: core
|
|
29
|
-
Requires-Dist: pisama-core>=0.1.0; extra == 'core'
|
|
30
|
-
Provides-Extra: dev
|
|
31
|
-
Requires-Dist: pytest-asyncio>=0.21.0; extra == 'dev'
|
|
32
|
-
Requires-Dist: pytest-cov>=4.0.0; extra == 'dev'
|
|
33
|
-
Requires-Dist: pytest>=7.0.0; extra == 'dev'
|
|
34
|
-
Description-Content-Type: text/markdown
|
|
35
|
-
|
|
36
|
-
# pisama-claude-code
|
|
37
|
-
|
|
38
|
-
> **Coming Soon** - This package is in private development.
|
|
39
|
-
|
|
40
|
-
Trace capture for Claude Code sessions.
|
|
41
|
-
|
|
42
|
-
[](https://www.python.org/downloads/)
|
|
43
|
-
[](https://opensource.org/licenses/MIT)
|
|
44
|
-
|
|
45
|
-
## Overview
|
|
46
|
-
|
|
47
|
-
Lightweight trace capture client for Claude Code. Captures tool calls and syncs to PISAMA platform for analysis.
|
|
48
|
-
|
|
49
|
-
**This package captures traces. Analysis happens on the platform.**
|
|
50
|
-
|
|
51
|
-
```
|
|
52
|
-
┌─────────────────────┐ ┌─────────────────────┐
|
|
53
|
-
│ Claude Code │ │ PISAMA Platform │
|
|
54
|
-
│ + pisama-cc │ ──────▶ │ (analysis) │
|
|
55
|
-
│ (capture) │ sync │ (detection) │
|
|
56
|
-
└─────────────────────┘ │ (self-healing) │
|
|
57
|
-
└─────────────────────┘
|
|
58
|
-
```
|
|
59
|
-
|
|
60
|
-
## Features
|
|
61
|
-
|
|
62
|
-
| Feature | pisama-claude-code | Platform |
|
|
63
|
-
|---------|-------------------|----------|
|
|
64
|
-
| Trace capture | ✅ | - |
|
|
65
|
-
| Local storage | ✅ | - |
|
|
66
|
-
| Export to JSONL | ✅ | - |
|
|
67
|
-
| Sync to platform | ✅ | ✅ |
|
|
68
|
-
| Failure detection (28 modes) | - | ✅ |
|
|
69
|
-
| Severity & explanations | - | ✅ |
|
|
70
|
-
| Fix suggestions | - | ✅ |
|
|
71
|
-
| Self-healing | - | ✅ |
|
|
72
|
-
| Dashboard | - | ✅ |
|
|
73
|
-
|
|
74
|
-
## CLI Commands
|
|
75
|
-
|
|
76
|
-
```bash
|
|
77
|
-
pisama-cc install # Install capture hooks
|
|
78
|
-
pisama-cc uninstall # Remove hooks
|
|
79
|
-
pisama-cc status # Show status
|
|
80
|
-
pisama-cc traces # View local traces
|
|
81
|
-
pisama-cc export # Export to file
|
|
82
|
-
pisama-cc connect # Connect to platform
|
|
83
|
-
pisama-cc sync # Upload traces
|
|
84
|
-
pisama-cc analyze # Run analysis (requires platform)
|
|
85
|
-
```
|
|
86
|
-
|
|
87
|
-
## Privacy
|
|
88
|
-
|
|
89
|
-
- Traces stored locally in `~/.claude/pisama/traces/`
|
|
90
|
-
- Secrets automatically redacted
|
|
91
|
-
- Paths anonymized
|
|
92
|
-
- Platform sync is opt-in
|
|
93
|
-
|
|
94
|
-
## Requirements
|
|
95
|
-
|
|
96
|
-
- Python 3.10+
|
|
97
|
-
- Claude Code CLI
|
|
98
|
-
|
|
99
|
-
## Status
|
|
100
|
-
|
|
101
|
-
Private beta. Contact team@pisama.dev for early access.
|
|
102
|
-
|
|
103
|
-
## License
|
|
104
|
-
|
|
105
|
-
MIT
|
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
# pisama-claude-code
|
|
2
|
-
|
|
3
|
-
> **Coming Soon** - This package is in private development.
|
|
4
|
-
|
|
5
|
-
Trace capture for Claude Code sessions.
|
|
6
|
-
|
|
7
|
-
[](https://www.python.org/downloads/)
|
|
8
|
-
[](https://opensource.org/licenses/MIT)
|
|
9
|
-
|
|
10
|
-
## Overview
|
|
11
|
-
|
|
12
|
-
Lightweight trace capture client for Claude Code. Captures tool calls and syncs to PISAMA platform for analysis.
|
|
13
|
-
|
|
14
|
-
**This package captures traces. Analysis happens on the platform.**
|
|
15
|
-
|
|
16
|
-
```
|
|
17
|
-
┌─────────────────────┐ ┌─────────────────────┐
|
|
18
|
-
│ Claude Code │ │ PISAMA Platform │
|
|
19
|
-
│ + pisama-cc │ ──────▶ │ (analysis) │
|
|
20
|
-
│ (capture) │ sync │ (detection) │
|
|
21
|
-
└─────────────────────┘ │ (self-healing) │
|
|
22
|
-
└─────────────────────┘
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
## Features
|
|
26
|
-
|
|
27
|
-
| Feature | pisama-claude-code | Platform |
|
|
28
|
-
|---------|-------------------|----------|
|
|
29
|
-
| Trace capture | ✅ | - |
|
|
30
|
-
| Local storage | ✅ | - |
|
|
31
|
-
| Export to JSONL | ✅ | - |
|
|
32
|
-
| Sync to platform | ✅ | ✅ |
|
|
33
|
-
| Failure detection (28 modes) | - | ✅ |
|
|
34
|
-
| Severity & explanations | - | ✅ |
|
|
35
|
-
| Fix suggestions | - | ✅ |
|
|
36
|
-
| Self-healing | - | ✅ |
|
|
37
|
-
| Dashboard | - | ✅ |
|
|
38
|
-
|
|
39
|
-
## CLI Commands
|
|
40
|
-
|
|
41
|
-
```bash
|
|
42
|
-
pisama-cc install # Install capture hooks
|
|
43
|
-
pisama-cc uninstall # Remove hooks
|
|
44
|
-
pisama-cc status # Show status
|
|
45
|
-
pisama-cc traces # View local traces
|
|
46
|
-
pisama-cc export # Export to file
|
|
47
|
-
pisama-cc connect # Connect to platform
|
|
48
|
-
pisama-cc sync # Upload traces
|
|
49
|
-
pisama-cc analyze # Run analysis (requires platform)
|
|
50
|
-
```
|
|
51
|
-
|
|
52
|
-
## Privacy
|
|
53
|
-
|
|
54
|
-
- Traces stored locally in `~/.claude/pisama/traces/`
|
|
55
|
-
- Secrets automatically redacted
|
|
56
|
-
- Paths anonymized
|
|
57
|
-
- Platform sync is opt-in
|
|
58
|
-
|
|
59
|
-
## Requirements
|
|
60
|
-
|
|
61
|
-
- Python 3.10+
|
|
62
|
-
- Claude Code CLI
|
|
63
|
-
|
|
64
|
-
## Status
|
|
65
|
-
|
|
66
|
-
Private beta. Contact team@pisama.dev for early access.
|
|
67
|
-
|
|
68
|
-
## License
|
|
69
|
-
|
|
70
|
-
MIT
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pisama_claude_code-0.3.1 → pisama_claude_code-0.3.2}/src/pisama_claude_code/hooks/__init__.py
RENAMED
|
File without changes
|
{pisama_claude_code-0.3.1 → pisama_claude_code-0.3.2}/src/pisama_claude_code/hooks/guardian_hook.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pisama_claude_code-0.3.1 → pisama_claude_code-0.3.2}/src/pisama_claude_code/trace_converter.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|