methodproof 0.7.4__tar.gz → 0.7.6__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.
- {methodproof-0.7.4 → methodproof-0.7.6}/CHANGELOG.md +6 -0
- {methodproof-0.7.4 → methodproof-0.7.6}/PKG-INFO +35 -10
- {methodproof-0.7.4 → methodproof-0.7.6}/README.md +34 -9
- {methodproof-0.7.4 → methodproof-0.7.6}/methodproof/__init__.py +1 -1
- {methodproof-0.7.4 → methodproof-0.7.6}/methodproof/analysis.py +3 -2
- {methodproof-0.7.4 → methodproof-0.7.6}/methodproof/cli.py +41 -1
- {methodproof-0.7.4 → methodproof-0.7.6}/methodproof/graph.py +13 -13
- {methodproof-0.7.4 → methodproof-0.7.6}/methodproof/migrate_db.py +3 -4
- {methodproof-0.7.4 → methodproof-0.7.6}/methodproof/store.py +57 -4
- {methodproof-0.7.4 → methodproof-0.7.6}/methodproof/sync.py +7 -1
- {methodproof-0.7.4 → methodproof-0.7.6}/pyproject.toml +1 -1
- {methodproof-0.7.4 → methodproof-0.7.6}/tests/test_security.py +7 -4
- {methodproof-0.7.4 → methodproof-0.7.6}/.github/workflows/ci.yml +0 -0
- {methodproof-0.7.4 → methodproof-0.7.6}/.gitignore +0 -0
- {methodproof-0.7.4 → methodproof-0.7.6}/LICENSE +0 -0
- {methodproof-0.7.4 → methodproof-0.7.6}/methodproof/__main__.py +0 -0
- {methodproof-0.7.4 → methodproof-0.7.6}/methodproof/_daemon.py +0 -0
- {methodproof-0.7.4 → methodproof-0.7.6}/methodproof/agents/__init__.py +0 -0
- {methodproof-0.7.4 → methodproof-0.7.6}/methodproof/agents/base.py +0 -0
- {methodproof-0.7.4 → methodproof-0.7.6}/methodproof/agents/music.py +0 -0
- {methodproof-0.7.4 → methodproof-0.7.6}/methodproof/agents/terminal.py +0 -0
- {methodproof-0.7.4 → methodproof-0.7.6}/methodproof/agents/watcher.py +0 -0
- {methodproof-0.7.4 → methodproof-0.7.6}/methodproof/binding.py +0 -0
- {methodproof-0.7.4 → methodproof-0.7.6}/methodproof/bip39.py +0 -0
- {methodproof-0.7.4 → methodproof-0.7.6}/methodproof/bridge.py +0 -0
- {methodproof-0.7.4 → methodproof-0.7.6}/methodproof/config.py +0 -0
- {methodproof-0.7.4 → methodproof-0.7.6}/methodproof/crypto.py +0 -0
- {methodproof-0.7.4 → methodproof-0.7.6}/methodproof/e2e.py +0 -0
- {methodproof-0.7.4 → methodproof-0.7.6}/methodproof/hook.py +0 -0
- {methodproof-0.7.4 → methodproof-0.7.6}/methodproof/hooks/__init__.py +0 -0
- {methodproof-0.7.4 → methodproof-0.7.6}/methodproof/hooks/claude_code.py +0 -0
- {methodproof-0.7.4 → methodproof-0.7.6}/methodproof/hooks/claude_code.sh +0 -0
- {methodproof-0.7.4 → methodproof-0.7.6}/methodproof/hooks/cline_hook.sh +0 -0
- {methodproof-0.7.4 → methodproof-0.7.6}/methodproof/hooks/codex_hook.sh +0 -0
- {methodproof-0.7.4 → methodproof-0.7.6}/methodproof/hooks/gemini_hook.sh +0 -0
- {methodproof-0.7.4 → methodproof-0.7.6}/methodproof/hooks/install.py +0 -0
- {methodproof-0.7.4 → methodproof-0.7.6}/methodproof/hooks/kiro_hook.sh +0 -0
- {methodproof-0.7.4 → methodproof-0.7.6}/methodproof/hooks/mcp_register.py +0 -0
- {methodproof-0.7.4 → methodproof-0.7.6}/methodproof/hooks/openclaw/HOOK.md +0 -0
- {methodproof-0.7.4 → methodproof-0.7.6}/methodproof/hooks/openclaw/handler.ts +0 -0
- {methodproof-0.7.4 → methodproof-0.7.6}/methodproof/hooks/openclaw_install.py +0 -0
- {methodproof-0.7.4 → methodproof-0.7.6}/methodproof/hooks/opencode_plugin.js +0 -0
- {methodproof-0.7.4 → methodproof-0.7.6}/methodproof/hooks/wrappers.py +0 -0
- {methodproof-0.7.4 → methodproof-0.7.6}/methodproof/integrity.py +0 -0
- {methodproof-0.7.4 → methodproof-0.7.6}/methodproof/kdf.py +0 -0
- {methodproof-0.7.4 → methodproof-0.7.6}/methodproof/keychain.py +0 -0
- {methodproof-0.7.4 → methodproof-0.7.6}/methodproof/live.py +0 -0
- {methodproof-0.7.4 → methodproof-0.7.6}/methodproof/lock.py +0 -0
- {methodproof-0.7.4 → methodproof-0.7.6}/methodproof/mcp.py +0 -0
- {methodproof-0.7.4 → methodproof-0.7.6}/methodproof/proxy.py +0 -0
- {methodproof-0.7.4 → methodproof-0.7.6}/methodproof/proxy_daemon.py +0 -0
- {methodproof-0.7.4 → methodproof-0.7.6}/methodproof/repos.py +0 -0
- {methodproof-0.7.4 → methodproof-0.7.6}/methodproof/skills/methodproof/SKILL.md +0 -0
- {methodproof-0.7.4 → methodproof-0.7.6}/methodproof/viewer.py +0 -0
- {methodproof-0.7.4 → methodproof-0.7.6}/methodproof/wordlist.py +0 -0
- {methodproof-0.7.4 → methodproof-0.7.6}/test_windows_compat.py +0 -0
- {methodproof-0.7.4 → methodproof-0.7.6}/tests/__init__.py +0 -0
- {methodproof-0.7.4 → methodproof-0.7.6}/tests/test_analysis.py +0 -0
- {methodproof-0.7.4 → methodproof-0.7.6}/tests/test_graph.py +0 -0
- {methodproof-0.7.4 → methodproof-0.7.6}/tests/test_hooks.py +0 -0
- {methodproof-0.7.4 → methodproof-0.7.6}/tests/test_live.py +0 -0
- {methodproof-0.7.4 → methodproof-0.7.6}/tests/test_openclaw_hooks.py +0 -0
- {methodproof-0.7.4 → methodproof-0.7.6}/tests/test_store.py +0 -0
- {methodproof-0.7.4 → methodproof-0.7.6}/tests/test_wrappers.py +0 -0
- {methodproof-0.7.4 → methodproof-0.7.6}/uv.lock +0 -0
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.7.6] — 2026-04-08
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
- **Metadata compression** — event metadata stored as zlib-compressed BLOBs in SQLite (~80% storage reduction for journal-mode sessions). Existing uncompressed rows are silently migrated on next startup.
|
|
7
|
+
- **Gzip transfer encoding** — `mp push` sends gzip-compressed request bodies to the platform. Reduces upload bandwidth ~70% for large batches.
|
|
8
|
+
|
|
3
9
|
## [0.7.1] — 2026-04-07
|
|
4
10
|
|
|
5
11
|
### Fixed
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: methodproof
|
|
3
|
-
Version: 0.7.
|
|
3
|
+
Version: 0.7.6
|
|
4
4
|
Summary: See how you code. Capture and visualize your engineering process.
|
|
5
5
|
License-Expression: Apache-2.0
|
|
6
6
|
License-File: LICENSE
|
|
@@ -60,7 +60,7 @@ methodproof view # explore your session in the browser
|
|
|
60
60
|
- **Environment profiling** — structural analysis of your AI dev environment (instruction files, tool counts, MCP servers) captured at session start
|
|
61
61
|
- **Outcome metrics** — first-shot apply rate, follow-up sequences, phase transitions computed at session end
|
|
62
62
|
- **Granular consent** — 10 standard capture categories + 1 premium, each independently toggled. Nothing records without your opt-in
|
|
63
|
-
- **Local-first** — SQLite database at `~/.methodproof/`, `chmod 600
|
|
63
|
+
- **Local-first** — SQLite database at `~/.methodproof/`, `chmod 600`, zlib-compressed metadata. No network calls unless you choose
|
|
64
64
|
- **Live streaming** — `methodproof start --live` streams events to the platform in real-time over WebSocket
|
|
65
65
|
- **Integrity verification** — hash-chained events + Ed25519 attestation prove sessions haven't been tampered with
|
|
66
66
|
- **E2E encryption** — optional company-held AES-256-GCM encryption the platform cannot decrypt
|
|
@@ -99,7 +99,7 @@ flowchart TB
|
|
|
99
99
|
CHAIN --> BUF["Batched Flush"]
|
|
100
100
|
end
|
|
101
101
|
|
|
102
|
-
BUF
|
|
102
|
+
BUF -->|"zlib compress"| DB[("SQLite WAL")]
|
|
103
103
|
BUF -.->|"--live"| WS["WebSocket Stream"]
|
|
104
104
|
|
|
105
105
|
subgraph KEYS["Key Vault"]
|
|
@@ -115,7 +115,7 @@ flowchart TB
|
|
|
115
115
|
|
|
116
116
|
subgraph PUSH["PUSH PROTOCOL"]
|
|
117
117
|
direction TB
|
|
118
|
-
DB
|
|
118
|
+
DB -->|"gzip"| BATCH["Batched Upload"]
|
|
119
119
|
BATCH --> BIND["Session Binding: HMAC over session metadata"]
|
|
120
120
|
BIND --> SIGN["Ed25519 Sign: session summary"]
|
|
121
121
|
end
|
|
@@ -153,18 +153,24 @@ flowchart TB
|
|
|
153
153
|
| Command | What it does |
|
|
154
154
|
|---------|-------------|
|
|
155
155
|
| `init` | Interactive consent selector, install hooks, create data directory |
|
|
156
|
-
| `start [--dir .] [--tags t1,t2] [--public] [--live]` | Start recording |
|
|
156
|
+
| `start [--dir .] [--tags t1,t2] [--public] [--live] [--journal] [--e2e]` | Start recording |
|
|
157
157
|
| `stop` | Stop recording, build process graph |
|
|
158
158
|
| `view [session_id]` | Open session graph in browser |
|
|
159
159
|
| `log` | List sessions with sync status, visibility, tags |
|
|
160
|
-
| `login` | Authenticate with the platform |
|
|
161
|
-
| `push [session_id]` | Upload session |
|
|
162
|
-
| `publish [session_id]` | Set public + push |
|
|
160
|
+
| `login [--api-url URL]` | Authenticate with the platform |
|
|
161
|
+
| `push [session_id] [--local]` | Upload session (`--local` targets localhost:8000) |
|
|
162
|
+
| `publish [session_id] [--anonymous]` | Set public + push (redaction applied) |
|
|
163
163
|
| `tag <session_id> <tags>` | Add tags |
|
|
164
164
|
| `delete <session_id> [-f]` | Delete session and all its data |
|
|
165
|
-
| `consent` | Change capture
|
|
165
|
+
| `consent` | Change capture, research, and redaction settings |
|
|
166
166
|
| `review` | Inspect session data before pushing |
|
|
167
|
-
| `
|
|
167
|
+
| `journal on/off/status` | Toggle journal mode (full content capture) |
|
|
168
|
+
| `e2e on/off/status/recover/release` | Manage personal E2E encryption keys |
|
|
169
|
+
| `extension pair/status/install` | Browser extension pairing |
|
|
170
|
+
| `proxy start/stop/status/cert` | Local AI API proxy (deep capture) |
|
|
171
|
+
| `update [--auto/--no-auto]` | Check for and install CLI updates |
|
|
172
|
+
| `lock [--purge]` | Destroy local encryption key (recoverable) |
|
|
173
|
+
| `uninstall [--keep-sessions]` | Remove all hooks, data, and config |
|
|
168
174
|
|
|
169
175
|
## Privacy & Consent
|
|
170
176
|
|
|
@@ -307,6 +313,25 @@ methodproof start
|
|
|
307
313
|
|
|
308
314
|
**Git commits** are detected automatically — only commits in a git repo rooted at (or above) the watch directory are captured.
|
|
309
315
|
|
|
316
|
+
## Local Development
|
|
317
|
+
|
|
318
|
+
Push sessions to a local API for testing:
|
|
319
|
+
|
|
320
|
+
```bash
|
|
321
|
+
# One-time: login to your local API
|
|
322
|
+
mp login --api-url http://localhost:8000
|
|
323
|
+
|
|
324
|
+
# Push with --local flag (overrides stored URL for this command)
|
|
325
|
+
mp push --local
|
|
326
|
+
|
|
327
|
+
# Or set the env var (works with any command)
|
|
328
|
+
METHODPROOF_API_URL=http://localhost:8000 mp push
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
`--local` is a shorthand for `http://localhost:8000`. It does not clobber your production token — it only overrides the API URL for that invocation.
|
|
332
|
+
|
|
333
|
+
You still need a valid JWT from the local API. Run `mp login --api-url http://localhost:8000` once to authenticate against your local platform.
|
|
334
|
+
|
|
310
335
|
## Data Directory
|
|
311
336
|
|
|
312
337
|
`~/.methodproof/`
|
|
@@ -45,7 +45,7 @@ methodproof view # explore your session in the browser
|
|
|
45
45
|
- **Environment profiling** — structural analysis of your AI dev environment (instruction files, tool counts, MCP servers) captured at session start
|
|
46
46
|
- **Outcome metrics** — first-shot apply rate, follow-up sequences, phase transitions computed at session end
|
|
47
47
|
- **Granular consent** — 10 standard capture categories + 1 premium, each independently toggled. Nothing records without your opt-in
|
|
48
|
-
- **Local-first** — SQLite database at `~/.methodproof/`, `chmod 600
|
|
48
|
+
- **Local-first** — SQLite database at `~/.methodproof/`, `chmod 600`, zlib-compressed metadata. No network calls unless you choose
|
|
49
49
|
- **Live streaming** — `methodproof start --live` streams events to the platform in real-time over WebSocket
|
|
50
50
|
- **Integrity verification** — hash-chained events + Ed25519 attestation prove sessions haven't been tampered with
|
|
51
51
|
- **E2E encryption** — optional company-held AES-256-GCM encryption the platform cannot decrypt
|
|
@@ -84,7 +84,7 @@ flowchart TB
|
|
|
84
84
|
CHAIN --> BUF["Batched Flush"]
|
|
85
85
|
end
|
|
86
86
|
|
|
87
|
-
BUF
|
|
87
|
+
BUF -->|"zlib compress"| DB[("SQLite WAL")]
|
|
88
88
|
BUF -.->|"--live"| WS["WebSocket Stream"]
|
|
89
89
|
|
|
90
90
|
subgraph KEYS["Key Vault"]
|
|
@@ -100,7 +100,7 @@ flowchart TB
|
|
|
100
100
|
|
|
101
101
|
subgraph PUSH["PUSH PROTOCOL"]
|
|
102
102
|
direction TB
|
|
103
|
-
DB
|
|
103
|
+
DB -->|"gzip"| BATCH["Batched Upload"]
|
|
104
104
|
BATCH --> BIND["Session Binding: HMAC over session metadata"]
|
|
105
105
|
BIND --> SIGN["Ed25519 Sign: session summary"]
|
|
106
106
|
end
|
|
@@ -138,18 +138,24 @@ flowchart TB
|
|
|
138
138
|
| Command | What it does |
|
|
139
139
|
|---------|-------------|
|
|
140
140
|
| `init` | Interactive consent selector, install hooks, create data directory |
|
|
141
|
-
| `start [--dir .] [--tags t1,t2] [--public] [--live]` | Start recording |
|
|
141
|
+
| `start [--dir .] [--tags t1,t2] [--public] [--live] [--journal] [--e2e]` | Start recording |
|
|
142
142
|
| `stop` | Stop recording, build process graph |
|
|
143
143
|
| `view [session_id]` | Open session graph in browser |
|
|
144
144
|
| `log` | List sessions with sync status, visibility, tags |
|
|
145
|
-
| `login` | Authenticate with the platform |
|
|
146
|
-
| `push [session_id]` | Upload session |
|
|
147
|
-
| `publish [session_id]` | Set public + push |
|
|
145
|
+
| `login [--api-url URL]` | Authenticate with the platform |
|
|
146
|
+
| `push [session_id] [--local]` | Upload session (`--local` targets localhost:8000) |
|
|
147
|
+
| `publish [session_id] [--anonymous]` | Set public + push (redaction applied) |
|
|
148
148
|
| `tag <session_id> <tags>` | Add tags |
|
|
149
149
|
| `delete <session_id> [-f]` | Delete session and all its data |
|
|
150
|
-
| `consent` | Change capture
|
|
150
|
+
| `consent` | Change capture, research, and redaction settings |
|
|
151
151
|
| `review` | Inspect session data before pushing |
|
|
152
|
-
| `
|
|
152
|
+
| `journal on/off/status` | Toggle journal mode (full content capture) |
|
|
153
|
+
| `e2e on/off/status/recover/release` | Manage personal E2E encryption keys |
|
|
154
|
+
| `extension pair/status/install` | Browser extension pairing |
|
|
155
|
+
| `proxy start/stop/status/cert` | Local AI API proxy (deep capture) |
|
|
156
|
+
| `update [--auto/--no-auto]` | Check for and install CLI updates |
|
|
157
|
+
| `lock [--purge]` | Destroy local encryption key (recoverable) |
|
|
158
|
+
| `uninstall [--keep-sessions]` | Remove all hooks, data, and config |
|
|
153
159
|
|
|
154
160
|
## Privacy & Consent
|
|
155
161
|
|
|
@@ -292,6 +298,25 @@ methodproof start
|
|
|
292
298
|
|
|
293
299
|
**Git commits** are detected automatically — only commits in a git repo rooted at (or above) the watch directory are captured.
|
|
294
300
|
|
|
301
|
+
## Local Development
|
|
302
|
+
|
|
303
|
+
Push sessions to a local API for testing:
|
|
304
|
+
|
|
305
|
+
```bash
|
|
306
|
+
# One-time: login to your local API
|
|
307
|
+
mp login --api-url http://localhost:8000
|
|
308
|
+
|
|
309
|
+
# Push with --local flag (overrides stored URL for this command)
|
|
310
|
+
mp push --local
|
|
311
|
+
|
|
312
|
+
# Or set the env var (works with any command)
|
|
313
|
+
METHODPROOF_API_URL=http://localhost:8000 mp push
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
`--local` is a shorthand for `http://localhost:8000`. It does not clobber your production token — it only overrides the API URL for that invocation.
|
|
317
|
+
|
|
318
|
+
You still need a valid JWT from the local API. Run `mp login --api-url http://localhost:8000` once to authenticate against your local platform.
|
|
319
|
+
|
|
295
320
|
## Data Directory
|
|
296
321
|
|
|
297
322
|
`~/.methodproof/`
|
|
@@ -606,8 +606,9 @@ def compute_outcomes(session_id: str) -> dict[str, Any]:
|
|
|
606
606
|
if e["type"] not in prompt_types:
|
|
607
607
|
continue
|
|
608
608
|
try:
|
|
609
|
-
|
|
610
|
-
|
|
609
|
+
from methodproof.store import _decompress_meta
|
|
610
|
+
meta = _decompress_meta(e["metadata"])
|
|
611
|
+
except (json.JSONDecodeError, TypeError, Exception) as exc:
|
|
611
612
|
_warn("analysis.metadata_parse_failed", event_id=e["id"] if hasattr(e, "__getitem__") else "?", error=str(exc))
|
|
612
613
|
continue
|
|
613
614
|
intent = meta.get("sa_intent")
|
|
@@ -466,6 +466,7 @@ def _print_commands() -> None:
|
|
|
466
466
|
print(f" {_G}mp start --live{R} Stream your graph privately (only you can view)")
|
|
467
467
|
print(f" {_G}mp start --live-public{R} Stream your graph publicly (shareable link)")
|
|
468
468
|
print(f" {_G}mp start --journal{R} Full content capture (2 free credits, then Pro)")
|
|
469
|
+
print(f" {_G}mp start --e2e{R} Encrypt session with your personal key {_D}(Pro){R}")
|
|
469
470
|
print(f" {_G}mp journal on{R} Enable persistent journal mode")
|
|
470
471
|
print(f" {_G}mp journal status{R} Check journal mode and remaining credits")
|
|
471
472
|
print()
|
|
@@ -476,15 +477,29 @@ def _print_commands() -> None:
|
|
|
476
477
|
print()
|
|
477
478
|
print(f" {_W}SHARE{R}")
|
|
478
479
|
print(f" {_Y}mp push{R} {_D}[id]{R} Upload privately to your account")
|
|
480
|
+
print(f" {_Y}mp push --local{R} Push to local dev API {_D}(localhost:8000){R}")
|
|
479
481
|
print(f" {_Y}mp publish{R} {_D}[id]{R} Make session public (redaction applied)")
|
|
480
482
|
print(f" {_Y}mp publish --anonymous{R} Public but identity hidden {_D}(Pro){R}")
|
|
481
483
|
print(f" {_Y}mp tag{R} {_D}<id> <tags>{R} Tag a session")
|
|
482
484
|
print()
|
|
485
|
+
print(f" {_W}ENCRYPTION{R}")
|
|
486
|
+
print(f" {_C}mp e2e on{R} Enable E2E encryption (generates key on first use)")
|
|
487
|
+
print(f" {_C}mp e2e off{R} Disable E2E mode (key stays in keychain)")
|
|
488
|
+
print(f" {_C}mp e2e status{R} Show E2E mode and key status")
|
|
489
|
+
print(f" {_C}mp e2e recover{R} Recover key from recovery passphrase")
|
|
490
|
+
print(f" {_C}mp e2e release{R} {_D}<id>{R} Release a session from E2E encryption")
|
|
491
|
+
print()
|
|
483
492
|
print(f" {_W}EXTENSION{R}")
|
|
484
493
|
print(f" {_C}mp extension pair{R} Pair browser extension to active session")
|
|
485
494
|
print(f" {_C}mp extension status{R} Check extension connection")
|
|
486
495
|
print(f" {_C}mp extension install{R} Open Chrome Web Store")
|
|
487
496
|
print()
|
|
497
|
+
print(f" {_W}PROXY{R}")
|
|
498
|
+
print(f" {_C}mp proxy start{R} Start local AI API proxy {_D}(deep capture){R}")
|
|
499
|
+
print(f" {_C}mp proxy stop{R} Stop proxy")
|
|
500
|
+
print(f" {_C}mp proxy status{R} Show proxy status")
|
|
501
|
+
print(f" {_C}mp proxy cert{R} CA certificate install instructions")
|
|
502
|
+
print()
|
|
488
503
|
print(f" {_W}ACCOUNT{R}")
|
|
489
504
|
print(f" {_M}mp login{R} Connect to platform (opens browser)")
|
|
490
505
|
print(f" {_M}mp consent{R} Change capture, research, and redaction settings")
|
|
@@ -497,6 +512,9 @@ def _print_commands() -> None:
|
|
|
497
512
|
print(f" {_M}mp update --no-auto{R} Toggle auto-update off")
|
|
498
513
|
print(f" {_M}mp uninstall{R} Remove all hooks, data, and config")
|
|
499
514
|
print()
|
|
515
|
+
print(f" {_W}ENVIRONMENT{R}")
|
|
516
|
+
print(f" {_D}METHODPROOF_API_URL{R} Override API endpoint {_D}(e.g. http://localhost:8000){R}")
|
|
517
|
+
print()
|
|
500
518
|
print(f" {_D}To view this at any time run: mp help{R}\n")
|
|
501
519
|
|
|
502
520
|
|
|
@@ -504,7 +522,12 @@ def _print_commands_plain() -> None:
|
|
|
504
522
|
print(" RECORD")
|
|
505
523
|
print(" mp start Start recording a session")
|
|
506
524
|
print(" mp stop Stop recording, build process graph")
|
|
507
|
-
print(" mp start --live Stream your graph
|
|
525
|
+
print(" mp start --live Stream your graph privately")
|
|
526
|
+
print(" mp start --live-public Stream your graph publicly (shareable link)")
|
|
527
|
+
print(" mp start --journal Full content capture (2 free credits, then Pro)")
|
|
528
|
+
print(" mp start --e2e Encrypt session with your personal key (Pro)")
|
|
529
|
+
print(" mp journal on Enable persistent journal mode")
|
|
530
|
+
print(" mp journal status Check journal mode and remaining credits")
|
|
508
531
|
print()
|
|
509
532
|
print(" REVIEW")
|
|
510
533
|
print(" mp view [id] View session graph in browser")
|
|
@@ -513,15 +536,29 @@ def _print_commands_plain() -> None:
|
|
|
513
536
|
print()
|
|
514
537
|
print(" SHARE")
|
|
515
538
|
print(" mp push [id] Upload privately to your account")
|
|
539
|
+
print(" mp push --local Push to local dev API (localhost:8000)")
|
|
516
540
|
print(" mp publish [id] Make session public (redaction applied)")
|
|
517
541
|
print(" mp publish --anonymous Public but identity hidden (Pro)")
|
|
518
542
|
print(" mp tag <id> <tags> Tag a session")
|
|
519
543
|
print()
|
|
544
|
+
print(" ENCRYPTION")
|
|
545
|
+
print(" mp e2e on Enable E2E encryption (generates key on first use)")
|
|
546
|
+
print(" mp e2e off Disable E2E mode (key stays in keychain)")
|
|
547
|
+
print(" mp e2e status Show E2E mode and key status")
|
|
548
|
+
print(" mp e2e recover Recover key from recovery passphrase")
|
|
549
|
+
print(" mp e2e release <id> Release a session from E2E encryption")
|
|
550
|
+
print()
|
|
520
551
|
print(" EXTENSION")
|
|
521
552
|
print(" mp extension pair Pair browser extension to active session")
|
|
522
553
|
print(" mp extension status Check extension connection")
|
|
523
554
|
print(" mp extension install Open Chrome Web Store")
|
|
524
555
|
print()
|
|
556
|
+
print(" PROXY")
|
|
557
|
+
print(" mp proxy start Start local AI API proxy (deep capture)")
|
|
558
|
+
print(" mp proxy stop Stop proxy")
|
|
559
|
+
print(" mp proxy status Show proxy status")
|
|
560
|
+
print(" mp proxy cert CA certificate install instructions")
|
|
561
|
+
print()
|
|
525
562
|
print(" ACCOUNT")
|
|
526
563
|
print(" mp login Connect to platform (opens browser)")
|
|
527
564
|
print(" mp consent Change capture, research, and redaction settings")
|
|
@@ -534,6 +571,9 @@ def _print_commands_plain() -> None:
|
|
|
534
571
|
print(" mp update --no-auto Toggle auto-update off")
|
|
535
572
|
print(" mp uninstall Remove all hooks, data, and config")
|
|
536
573
|
print()
|
|
574
|
+
print(" ENVIRONMENT")
|
|
575
|
+
print(" METHODPROOF_API_URL Override API endpoint (e.g. http://localhost:8000)")
|
|
576
|
+
print()
|
|
537
577
|
print(" To view this at any time run: mp help\n")
|
|
538
578
|
|
|
539
579
|
|
|
@@ -5,7 +5,7 @@ import time
|
|
|
5
5
|
import uuid
|
|
6
6
|
from typing import Any
|
|
7
7
|
|
|
8
|
-
from methodproof.store import _db
|
|
8
|
+
from methodproof.store import _compress_meta, _db, _decompress_meta
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
def build(session_id: str) -> dict[str, int]:
|
|
@@ -53,7 +53,7 @@ def build(session_id: str) -> dict[str, int]:
|
|
|
53
53
|
|
|
54
54
|
# Resources
|
|
55
55
|
for e in events:
|
|
56
|
-
meta =
|
|
56
|
+
meta = _decompress_meta(e["metadata"])
|
|
57
57
|
if e["type"] in ("llm_prompt", "llm_completion") and "model" in meta:
|
|
58
58
|
_ensure_resource(db, "llm_model", meta["model"])
|
|
59
59
|
stats["resources"] += 1
|
|
@@ -63,7 +63,7 @@ def build(session_id: str) -> dict[str, int]:
|
|
|
63
63
|
|
|
64
64
|
# Artifacts
|
|
65
65
|
for e in events:
|
|
66
|
-
meta =
|
|
66
|
+
meta = _decompress_meta(e["metadata"])
|
|
67
67
|
if e["type"] in ("file_create", "file_edit") and "path" in meta:
|
|
68
68
|
_ensure_artifact(db, meta["path"], meta.get("size", 0))
|
|
69
69
|
stats["artifacts"] += 1
|
|
@@ -84,7 +84,7 @@ def build(session_id: str) -> dict[str, int]:
|
|
|
84
84
|
"(id, session_id, type, timestamp, duration_ms, metadata) "
|
|
85
85
|
"VALUES (?, ?, ?, ?, ?, ?)",
|
|
86
86
|
(uuid.uuid4().hex, session_id, "prompt_outcomes",
|
|
87
|
-
time.time(), 0,
|
|
87
|
+
time.time(), 0, _compress_meta(outcomes)),
|
|
88
88
|
)
|
|
89
89
|
stats["outcomes"] = 1
|
|
90
90
|
except Exception as exc:
|
|
@@ -100,7 +100,7 @@ def _link(
|
|
|
100
100
|
rel: str, window_sec: int, match_model: bool = False,
|
|
101
101
|
) -> int:
|
|
102
102
|
model_clause = (
|
|
103
|
-
"AND json_extract(s.metadata, '$.model') = json_extract(t.metadata, '$.model')"
|
|
103
|
+
"AND json_extract(mp_json(s.metadata), '$.model') = json_extract(mp_json(t.metadata), '$.model')"
|
|
104
104
|
if match_model else ""
|
|
105
105
|
)
|
|
106
106
|
sql = f"""
|
|
@@ -123,9 +123,9 @@ def _link_pasted(db: object, sid: str) -> int:
|
|
|
123
123
|
FROM events s JOIN events t ON t.session_id = s.session_id
|
|
124
124
|
WHERE s.session_id = ? AND s.type = 'browser_copy' AND t.type = 'file_edit'
|
|
125
125
|
AND t.timestamp > s.timestamp AND (t.timestamp - s.timestamp) <= 30
|
|
126
|
-
AND abs(json_extract(t.metadata, '$.lines_added') * 40.0
|
|
127
|
-
- json_extract(s.metadata, '$.text_length'))
|
|
128
|
-
< json_extract(s.metadata, '$.text_length') * 0.2
|
|
126
|
+
AND abs(json_extract(mp_json(t.metadata), '$.lines_added') * 40.0
|
|
127
|
+
- json_extract(mp_json(s.metadata), '$.text_length'))
|
|
128
|
+
< json_extract(mp_json(s.metadata), '$.text_length') * 0.2
|
|
129
129
|
"""
|
|
130
130
|
return db.execute(sql, (sid,)).rowcount
|
|
131
131
|
|
|
@@ -135,20 +135,20 @@ def _link_action_resources(db: object, sid: str) -> None:
|
|
|
135
135
|
db.execute("""
|
|
136
136
|
INSERT OR IGNORE INTO action_resources (action_id, resource_id, relation_type, metadata)
|
|
137
137
|
SELECT e.id, r.id, 'SENT_TO', '{}'
|
|
138
|
-
FROM events e JOIN resources r ON r.identifier = json_extract(e.metadata, '$.model')
|
|
138
|
+
FROM events e JOIN resources r ON r.identifier = json_extract(mp_json(e.metadata), '$.model')
|
|
139
139
|
WHERE e.session_id = ? AND e.type = 'llm_prompt' AND r.type = 'llm_model'
|
|
140
140
|
""", (sid,))
|
|
141
141
|
db.execute("""
|
|
142
142
|
INSERT OR IGNORE INTO action_resources (action_id, resource_id, relation_type, metadata)
|
|
143
143
|
SELECT e.id, r.id, 'CONSUMED', '{}'
|
|
144
|
-
FROM events e JOIN resources r ON r.identifier = json_extract(e.metadata, '$.model')
|
|
144
|
+
FROM events e JOIN resources r ON r.identifier = json_extract(mp_json(e.metadata), '$.model')
|
|
145
145
|
WHERE e.session_id = ? AND e.type = 'llm_completion' AND r.type = 'llm_model'
|
|
146
146
|
""", (sid,))
|
|
147
147
|
# Agent gateway links
|
|
148
148
|
db.execute("""
|
|
149
149
|
INSERT OR IGNORE INTO action_resources (action_id, resource_id, relation_type, metadata)
|
|
150
150
|
SELECT e.id, r.id, 'SENT_TO', '{}'
|
|
151
|
-
FROM events e JOIN resources r ON r.identifier = json_extract(e.metadata, '$.gateway')
|
|
151
|
+
FROM events e JOIN resources r ON r.identifier = json_extract(mp_json(e.metadata), '$.gateway')
|
|
152
152
|
WHERE e.session_id = ? AND e.type = 'agent_prompt' AND r.type = 'agent_gateway'
|
|
153
153
|
""", (sid,))
|
|
154
154
|
|
|
@@ -158,13 +158,13 @@ def _link_action_artifacts(db: object, sid: str) -> None:
|
|
|
158
158
|
db.execute("""
|
|
159
159
|
INSERT OR IGNORE INTO action_artifacts (action_id, artifact_id, relation_type)
|
|
160
160
|
SELECT e.id, a.id, 'PRODUCED'
|
|
161
|
-
FROM events e JOIN artifacts a ON a.path = json_extract(e.metadata, '$.path')
|
|
161
|
+
FROM events e JOIN artifacts a ON a.path = json_extract(mp_json(e.metadata), '$.path')
|
|
162
162
|
WHERE e.session_id = ? AND e.type = 'file_create'
|
|
163
163
|
""", (sid,))
|
|
164
164
|
db.execute("""
|
|
165
165
|
INSERT OR IGNORE INTO action_artifacts (action_id, artifact_id, relation_type)
|
|
166
166
|
SELECT e.id, a.id, 'MODIFIED'
|
|
167
|
-
FROM events e JOIN artifacts a ON a.path = json_extract(e.metadata, '$.path')
|
|
167
|
+
FROM events e JOIN artifacts a ON a.path = json_extract(mp_json(e.metadata), '$.path')
|
|
168
168
|
WHERE e.session_id = ? AND e.type = 'file_edit'
|
|
169
169
|
""", (sid,))
|
|
170
170
|
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
"""Encrypt existing plaintext events in local DB after key setup."""
|
|
2
2
|
|
|
3
|
-
import json
|
|
4
|
-
|
|
5
3
|
from methodproof import store
|
|
6
4
|
from methodproof.crypto import SENSITIVE_FIELDS, encrypt_field
|
|
5
|
+
from methodproof.store import _compress_meta, _decompress_meta
|
|
7
6
|
|
|
8
7
|
|
|
9
8
|
def migrate_encrypt(db_key: bytes) -> int:
|
|
@@ -18,7 +17,7 @@ def migrate_encrypt(db_key: bytes) -> int:
|
|
|
18
17
|
encrypted = 0
|
|
19
18
|
batch = []
|
|
20
19
|
for row in rows:
|
|
21
|
-
meta =
|
|
20
|
+
meta = _decompress_meta(row["metadata"])
|
|
22
21
|
changed = False
|
|
23
22
|
for field in SENSITIVE_FIELDS:
|
|
24
23
|
val = meta.get(field)
|
|
@@ -26,7 +25,7 @@ def migrate_encrypt(db_key: bytes) -> int:
|
|
|
26
25
|
meta[field] = encrypt_field(val, db_key)
|
|
27
26
|
changed = True
|
|
28
27
|
if changed:
|
|
29
|
-
batch.append((
|
|
28
|
+
batch.append((_compress_meta(meta), row["id"]))
|
|
30
29
|
encrypted += 1
|
|
31
30
|
if len(batch) >= 500:
|
|
32
31
|
_flush_batch(db, batch)
|
|
@@ -4,6 +4,7 @@ import json
|
|
|
4
4
|
import sqlite3
|
|
5
5
|
import time
|
|
6
6
|
import uuid
|
|
7
|
+
import zlib
|
|
7
8
|
from typing import Any
|
|
8
9
|
|
|
9
10
|
from methodproof import config
|
|
@@ -62,12 +63,25 @@ CREATE TABLE IF NOT EXISTS action_artifacts (
|
|
|
62
63
|
_conn: sqlite3.Connection | None = None
|
|
63
64
|
|
|
64
65
|
|
|
66
|
+
def _sqlite_decompress(raw: bytes | str | None) -> str | None:
|
|
67
|
+
"""SQLite UDF: decompress zlib BLOBs to JSON text for json_extract()."""
|
|
68
|
+
if raw is None:
|
|
69
|
+
return None
|
|
70
|
+
if isinstance(raw, bytes):
|
|
71
|
+
try:
|
|
72
|
+
return zlib.decompress(raw).decode()
|
|
73
|
+
except zlib.error:
|
|
74
|
+
return raw.decode() if raw else "{}"
|
|
75
|
+
return raw
|
|
76
|
+
|
|
77
|
+
|
|
65
78
|
def _db() -> sqlite3.Connection:
|
|
66
79
|
global _conn
|
|
67
80
|
if _conn is None:
|
|
68
81
|
_conn = sqlite3.connect(str(config.DB_PATH), check_same_thread=False, timeout=10)
|
|
69
82
|
_conn.execute("PRAGMA journal_mode=WAL")
|
|
70
83
|
_conn.row_factory = sqlite3.Row
|
|
84
|
+
_conn.create_function("mp_json", 1, _sqlite_decompress)
|
|
71
85
|
return _conn
|
|
72
86
|
|
|
73
87
|
|
|
@@ -113,9 +127,30 @@ def _migrate() -> None:
|
|
|
113
127
|
db.execute("DELETE FROM action_artifacts")
|
|
114
128
|
db.execute("DELETE FROM artifacts WHERE rowid NOT IN (SELECT MIN(rowid) FROM artifacts GROUP BY path)")
|
|
115
129
|
db.execute("CREATE UNIQUE INDEX IF NOT EXISTS idx_artifacts_path ON artifacts(path)")
|
|
130
|
+
# Compress legacy uncompressed metadata (TEXT → zlib BLOB)
|
|
131
|
+
_migrate_compress_metadata(db)
|
|
116
132
|
db.commit()
|
|
117
133
|
|
|
118
134
|
|
|
135
|
+
def _migrate_compress_metadata(db: sqlite3.Connection) -> None:
|
|
136
|
+
"""Silently compress any uncompressed TEXT metadata rows to zlib BLOBs."""
|
|
137
|
+
rows = db.execute("SELECT id, metadata FROM events WHERE typeof(metadata) = 'text'").fetchall()
|
|
138
|
+
if not rows:
|
|
139
|
+
return
|
|
140
|
+
batch, skipped = [], 0
|
|
141
|
+
for r in rows:
|
|
142
|
+
try:
|
|
143
|
+
compressed = zlib.compress(r["metadata"].encode())
|
|
144
|
+
batch.append((compressed, r["id"]))
|
|
145
|
+
except Exception:
|
|
146
|
+
skipped += 1
|
|
147
|
+
if batch:
|
|
148
|
+
db.executemany("UPDATE events SET metadata = ? WHERE id = ?", batch)
|
|
149
|
+
if skipped:
|
|
150
|
+
import sys
|
|
151
|
+
sys.stderr.write(f"[methodproof] migration: compressed {len(batch)} events, skipped {skipped}\n")
|
|
152
|
+
|
|
153
|
+
|
|
119
154
|
def create_session(
|
|
120
155
|
session_id: str, watch_dir: str,
|
|
121
156
|
repo_url: str | None = None, tags: str = "[]", visibility: str = "private",
|
|
@@ -142,14 +177,27 @@ def complete_session(session_id: str) -> None:
|
|
|
142
177
|
db.commit()
|
|
143
178
|
|
|
144
179
|
|
|
180
|
+
def _compress_meta(meta: dict[str, Any]) -> bytes:
|
|
181
|
+
return zlib.compress(json.dumps(meta, default=str).encode())
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def _decompress_meta(raw: bytes | str) -> dict[str, Any]:
|
|
185
|
+
if isinstance(raw, str):
|
|
186
|
+
return json.loads(raw)
|
|
187
|
+
try:
|
|
188
|
+
return json.loads(zlib.decompress(raw))
|
|
189
|
+
except zlib.error:
|
|
190
|
+
return json.loads(raw)
|
|
191
|
+
|
|
192
|
+
|
|
145
193
|
def insert_events(session_id: str, events: list[dict[str, Any]]) -> None:
|
|
146
194
|
db = _db()
|
|
147
195
|
rows = []
|
|
148
196
|
for e in events:
|
|
149
197
|
try:
|
|
150
|
-
meta =
|
|
198
|
+
meta = _compress_meta(e.get("metadata", {}))
|
|
151
199
|
except (TypeError, ValueError):
|
|
152
|
-
meta =
|
|
200
|
+
meta = _compress_meta({})
|
|
153
201
|
rows.append((
|
|
154
202
|
e["id"], session_id, e["type"], e["timestamp"],
|
|
155
203
|
e.get("duration_ms", 0), meta,
|
|
@@ -176,7 +224,12 @@ def get_events(session_id: str) -> list[dict[str, Any]]:
|
|
|
176
224
|
"SELECT * FROM events WHERE session_id = ? ORDER BY timestamp",
|
|
177
225
|
(session_id,),
|
|
178
226
|
).fetchall()
|
|
179
|
-
|
|
227
|
+
result = []
|
|
228
|
+
for r in rows:
|
|
229
|
+
d = dict(r)
|
|
230
|
+
d["metadata"] = json.dumps(_decompress_meta(d["metadata"]))
|
|
231
|
+
result.append(d)
|
|
232
|
+
return result
|
|
180
233
|
|
|
181
234
|
|
|
182
235
|
def get_graph(session_id: str) -> dict[str, Any]:
|
|
@@ -184,7 +237,7 @@ def get_graph(session_id: str) -> dict[str, Any]:
|
|
|
184
237
|
events = get_events(session_id)
|
|
185
238
|
nodes = [{"id": e["id"], "type": "Action", "label": e["type"],
|
|
186
239
|
"properties": {"timestamp": e["timestamp"], "duration_ms": e["duration_ms"],
|
|
187
|
-
"metadata":
|
|
240
|
+
"metadata": _decompress_meta(e["metadata"])}}
|
|
188
241
|
for e in events]
|
|
189
242
|
|
|
190
243
|
edges = []
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"""Push local sessions to the MethodProof platform."""
|
|
2
2
|
|
|
3
|
+
import gzip
|
|
3
4
|
import json
|
|
4
5
|
import urllib.error
|
|
5
6
|
import urllib.request
|
|
@@ -30,8 +31,13 @@ def _raw_request(
|
|
|
30
31
|
method: str, url: str, token: str,
|
|
31
32
|
body: dict[str, Any] | None = None,
|
|
32
33
|
) -> dict[str, Any]:
|
|
33
|
-
|
|
34
|
+
if body is not None:
|
|
35
|
+
data = gzip.compress(json.dumps(body).encode())
|
|
36
|
+
else:
|
|
37
|
+
data = None
|
|
34
38
|
headers = {"Content-Type": "application/json", "Authorization": f"Bearer {token}"}
|
|
39
|
+
if data is not None:
|
|
40
|
+
headers["Content-Encoding"] = "gzip"
|
|
35
41
|
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
|
36
42
|
with urllib.request.urlopen(req, timeout=15) as resp:
|
|
37
43
|
return json.loads(resp.read())
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "methodproof"
|
|
3
|
-
version = "0.7.
|
|
3
|
+
version = "0.7.6"
|
|
4
4
|
description = "See how you code. Capture and visualize your engineering process."
|
|
5
5
|
requires-python = ">=3.11"
|
|
6
6
|
dependencies = ["watchdog>=4.0", "websocket-client>=1.7", "cryptography>=43.0", "keyring>=25.0"]
|
|
@@ -106,10 +106,12 @@ def test_bip39_known_vector():
|
|
|
106
106
|
|
|
107
107
|
|
|
108
108
|
def test_bip39_bad_checksum():
|
|
109
|
-
|
|
109
|
+
# Use fixed entropy so the checksum corruption is deterministic
|
|
110
|
+
phrase = entropy_to_phrase(b"\x00" * 16)
|
|
110
111
|
words = phrase.split()
|
|
111
|
-
|
|
112
|
-
|
|
112
|
+
# Flip the last word to a word that changes the checksum bits
|
|
113
|
+
words[-1] = "zone"
|
|
114
|
+
with pytest.raises(ValueError, match="checksum"):
|
|
113
115
|
phrase_to_entropy(" ".join(words))
|
|
114
116
|
|
|
115
117
|
|
|
@@ -257,7 +259,8 @@ def test_migrate_encrypts_plaintext():
|
|
|
257
259
|
count = migrate_encrypt(key)
|
|
258
260
|
assert count == 1
|
|
259
261
|
row = store._db().execute("SELECT metadata FROM events WHERE id = 'e1'").fetchone()
|
|
260
|
-
|
|
262
|
+
meta = store._decompress_meta(row["metadata"])
|
|
263
|
+
assert "e2e:v1:" in meta["prompt_text"]
|
|
261
264
|
|
|
262
265
|
|
|
263
266
|
def test_migrate_idempotent():
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|