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.
Files changed (24) hide show
  1. pisama_claude_code-0.3.2/PKG-INFO +211 -0
  2. pisama_claude_code-0.3.2/README.md +176 -0
  3. {pisama_claude_code-0.3.1 → pisama_claude_code-0.3.2}/pyproject.toml +1 -1
  4. {pisama_claude_code-0.3.1 → pisama_claude_code-0.3.2}/src/pisama_claude_code/__init__.py +1 -1
  5. {pisama_claude_code-0.3.1 → pisama_claude_code-0.3.2}/src/pisama_claude_code/cli.py +459 -1
  6. {pisama_claude_code-0.3.1 → pisama_claude_code-0.3.2}/src/pisama_claude_code/hooks/capture_hook.py +79 -3
  7. pisama_claude_code-0.3.1/PKG-INFO +0 -105
  8. pisama_claude_code-0.3.1/README.md +0 -70
  9. {pisama_claude_code-0.3.1 → pisama_claude_code-0.3.2}/.gitignore +0 -0
  10. {pisama_claude_code-0.3.1 → pisama_claude_code-0.3.2}/LICENSE +0 -0
  11. {pisama_claude_code-0.3.1 → pisama_claude_code-0.3.2}/src/pisama_claude_code/adapter.py +0 -0
  12. {pisama_claude_code-0.3.1 → pisama_claude_code-0.3.2}/src/pisama_claude_code/guardian.py +0 -0
  13. {pisama_claude_code-0.3.1 → pisama_claude_code-0.3.2}/src/pisama_claude_code/hooks/__init__.py +0 -0
  14. {pisama_claude_code-0.3.1 → pisama_claude_code-0.3.2}/src/pisama_claude_code/hooks/guardian_hook.py +0 -0
  15. {pisama_claude_code-0.3.1 → pisama_claude_code-0.3.2}/src/pisama_claude_code/install.py +0 -0
  16. {pisama_claude_code-0.3.1 → pisama_claude_code-0.3.2}/src/pisama_claude_code/storage.py +0 -0
  17. {pisama_claude_code-0.3.1 → pisama_claude_code-0.3.2}/src/pisama_claude_code/trace_converter.py +0 -0
  18. {pisama_claude_code-0.3.1 → pisama_claude_code-0.3.2}/src/pisama_claude_code/trace_types.py +0 -0
  19. {pisama_claude_code-0.3.1 → pisama_claude_code-0.3.2}/tests/__init__.py +0 -0
  20. {pisama_claude_code-0.3.1 → pisama_claude_code-0.3.2}/tests/conftest.py +0 -0
  21. {pisama_claude_code-0.3.1 → pisama_claude_code-0.3.2}/tests/test_adapter.py +0 -0
  22. {pisama_claude_code-0.3.1 → pisama_claude_code-0.3.2}/tests/test_cli.py +0 -0
  23. {pisama_claude_code-0.3.1 → pisama_claude_code-0.3.2}/tests/test_guardian.py +0 -0
  24. {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
+ [![PyPI version](https://badge.fury.io/py/pisama-claude-code.svg)](https://pypi.org/project/pisama-claude-code/)
41
+ [![Python 3.10+](https://img.shields.io/badge/python-3.10+-blue.svg)](https://www.python.org/downloads/)
42
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](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
+ [![PyPI version](https://badge.fury.io/py/pisama-claude-code.svg)](https://pypi.org/project/pisama-claude-code/)
6
+ [![Python 3.10+](https://img.shields.io/badge/python-3.10+-blue.svg)](https://www.python.org/downloads/)
7
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](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
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "pisama-claude-code"
7
- version = "0.3.1"
7
+ version = "0.3.2"
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.1.0"
3
+ __version__ = "0.3.2"
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.1")
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()
@@ -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
- [![Python 3.10+](https://img.shields.io/badge/python-3.10+-blue.svg)](https://www.python.org/downloads/)
43
- [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](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
- [![Python 3.10+](https://img.shields.io/badge/python-3.10+-blue.svg)](https://www.python.org/downloads/)
8
- [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](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