epi-recorder 2.0.0__tar.gz → 2.1.1__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.
- {epi_recorder-2.0.0 → epi_recorder-2.1.1}/CHANGELOG.md +14 -0
- epi_recorder-2.1.1/PKG-INFO +159 -0
- epi_recorder-2.1.1/README.md +120 -0
- epi_recorder-2.1.1/epi_cli/__main__.py +12 -0
- {epi_recorder-2.0.0 → epi_recorder-2.1.1}/epi_cli/main.py +145 -0
- {epi_recorder-2.0.0 → epi_recorder-2.1.1}/epi_cli/run.py +87 -8
- {epi_recorder-2.0.0 → epi_recorder-2.1.1}/epi_cli/verify.py +2 -2
- {epi_recorder-2.0.0 → epi_recorder-2.1.1}/epi_core/__init__.py +1 -1
- {epi_recorder-2.0.0 → epi_recorder-2.1.1}/epi_core/container.py +10 -2
- epi_recorder-2.1.1/epi_postinstall.py +197 -0
- {epi_recorder-2.0.0 → epi_recorder-2.1.1}/epi_recorder/__init__.py +1 -1
- {epi_recorder-2.0.0 → epi_recorder-2.1.1}/epi_recorder/api.py +9 -2
- epi_recorder-2.1.1/epi_recorder/test_import.py +18 -0
- epi_recorder-2.1.1/epi_recorder/test_script.py +4 -0
- epi_recorder-2.1.1/epi_recorder.egg-info/PKG-INFO +159 -0
- epi_recorder-2.1.1/epi_recorder.egg-info/SOURCES.txt +113 -0
- {epi_recorder-2.0.0 → epi_recorder-2.1.1}/epi_recorder.egg-info/top_level.txt +1 -0
- {epi_recorder-2.0.0 → epi_recorder-2.1.1}/epi_viewer_static/index.html +39 -11
- epi_recorder-2.1.1/epi_viewer_static/viewer_lite.css +365 -0
- epi_recorder-2.1.1/examples/advanced_demo.py +64 -0
- epi_recorder-2.1.1/examples/complete_demo_workflow.py +127 -0
- epi_recorder-2.1.1/examples/complete_example.py +193 -0
- epi_recorder-2.1.1/examples/complex_rag_demo.py +38 -0
- epi_recorder-2.1.1/examples/demo_python_api.py +161 -0
- epi_recorder-2.1.1/examples/demo_script.py +21 -0
- epi_recorder-2.1.1/examples/demo_workflow.py +34 -0
- epi_recorder-2.1.1/examples/live_demo_workflow.py +81 -0
- epi_recorder-2.1.1/examples/openai_example.py +142 -0
- epi_recorder-2.1.1/examples/quick_demo.py +70 -0
- epi_recorder-2.1.1/examples/sentiment_analysis.py +242 -0
- epi_recorder-2.1.1/examples/view_example.py +50 -0
- epi_recorder-2.1.1/examples/visualization_script.py +23 -0
- {epi_recorder-2.0.0 → epi_recorder-2.1.1}/pyproject.toml +10 -7
- epi_recorder-2.1.1/setup.py +34 -0
- epi_recorder-2.1.1/tests/COMPREHENSIVE_REAL_TEST.py +362 -0
- epi_recorder-2.1.1/tests/FINAL_COMPREHENSIVE_TEST.py +205 -0
- epi_recorder-2.1.1/tests/REAL_USER_TEST.py +314 -0
- epi_recorder-2.1.1/tests/ULTIMATE_COMPLETE_TEST.py +353 -0
- epi_recorder-2.1.1/tests/comprehensive_test_v1.1.py +174 -0
- epi_recorder-2.1.1/tests/edge_case_simple.py +162 -0
- epi_recorder-2.1.1/tests/edge_case_test.py +229 -0
- epi_recorder-2.1.1/tests/full_system_test.py +256 -0
- epi_recorder-2.1.1/tests/my_test.py +46 -0
- epi_recorder-2.1.1/tests/quick_api_test.py +15 -0
- epi_recorder-2.1.1/tests/stress_test.py +151 -0
- epi_recorder-2.1.1/tests/test_all_cli_commands.py +165 -0
- epi_recorder-2.1.1/tests/test_api_integration.py +98 -0
- epi_recorder-2.1.1/tests/test_cli_record.py +11 -0
- epi_recorder-2.1.1/tests/test_cli_workflow.py +33 -0
- epi_recorder-2.1.1/tests/test_complete_workflow.py +197 -0
- epi_recorder-2.1.1/tests/test_comprehensive_e2e.py +157 -0
- epi_recorder-2.1.1/tests/test_context_debug.py +56 -0
- epi_recorder-2.1.1/tests/test_debug_record_path.py +40 -0
- epi_recorder-2.1.1/tests/test_decorator_debug.py +19 -0
- epi_recorder-2.1.1/tests/test_detailed_debug.py +54 -0
- epi_recorder-2.1.1/tests/test_final_validation.py +159 -0
- epi_recorder-2.1.1/tests/test_magic.py +20 -0
- epi_recorder-2.1.1/tests/test_matching_validation.py +29 -0
- epi_recorder-2.1.1/tests/test_metadata_fix.py +37 -0
- epi_recorder-2.1.1/tests/test_real_workflow_api.py +147 -0
- epi_recorder-2.1.1/tests/test_record_vs_class.py +42 -0
- epi_recorder-2.1.1/tests/test_redaction_only.py +23 -0
- epi_recorder-2.1.1/tests/test_signing.py +26 -0
- epi_recorder-2.1.1/tests/test_signing_debug.py +63 -0
- epi_recorder-2.1.1/tests/test_signing_verbose.py +35 -0
- epi_recorder-2.1.1/tests/test_simple.py +17 -0
- epi_recorder-2.1.1/tests/test_simple_context.py +24 -0
- epi_recorder-2.1.1/tests/test_steps_format.py +29 -0
- epi_recorder-2.1.1/tests/test_trace_exit.py +48 -0
- epi_recorder-2.1.1/tests/test_user_demo.py +31 -0
- epi_recorder-2.1.1/tests/test_user_workflow.py +294 -0
- epi_recorder-2.1.1/tests/test_user_workflow_simple.py +183 -0
- epi_recorder-2.1.1/tests/torture_test.py +66 -0
- epi_recorder-2.1.1/tests/user_journey_test.py +124 -0
- epi_recorder-2.0.0/PKG-INFO +0 -569
- epi_recorder-2.0.0/README.md +0 -530
- epi_recorder-2.0.0/epi_cli/__main__.py +0 -7
- epi_recorder-2.0.0/epi_recorder.egg-info/PKG-INFO +0 -569
- epi_recorder-2.0.0/epi_recorder.egg-info/SOURCES.txt +0 -56
- epi_recorder-2.0.0/tests/test_cli_record.py +0 -307
- {epi_recorder-2.0.0 → epi_recorder-2.1.1}/LICENSE +0 -0
- {epi_recorder-2.0.0 → epi_recorder-2.1.1}/MANIFEST.in +0 -0
- {epi_recorder-2.0.0 → epi_recorder-2.1.1}/epi_cli/__init__.py +0 -0
- {epi_recorder-2.0.0 → epi_recorder-2.1.1}/epi_cli/keys.py +0 -0
- {epi_recorder-2.0.0 → epi_recorder-2.1.1}/epi_cli/ls.py +0 -0
- {epi_recorder-2.0.0 → epi_recorder-2.1.1}/epi_cli/record.py +0 -0
- {epi_recorder-2.0.0 → epi_recorder-2.1.1}/epi_cli/view.py +0 -0
- {epi_recorder-2.0.0 → epi_recorder-2.1.1}/epi_core/redactor.py +0 -0
- {epi_recorder-2.0.0 → epi_recorder-2.1.1}/epi_core/schemas.py +0 -0
- {epi_recorder-2.0.0 → epi_recorder-2.1.1}/epi_core/serialize.py +0 -0
- {epi_recorder-2.0.0 → epi_recorder-2.1.1}/epi_core/trust.py +0 -0
- {epi_recorder-2.0.0 → epi_recorder-2.1.1}/epi_recorder/bootstrap.py +0 -0
- {epi_recorder-2.0.0 → epi_recorder-2.1.1}/epi_recorder/environment.py +0 -0
- {epi_recorder-2.0.0 → epi_recorder-2.1.1}/epi_recorder/patcher.py +0 -0
- {epi_recorder-2.0.0 → epi_recorder-2.1.1}/epi_recorder.egg-info/dependency_links.txt +0 -0
- {epi_recorder-2.0.0 → epi_recorder-2.1.1}/epi_recorder.egg-info/entry_points.txt +0 -0
- {epi_recorder-2.0.0 → epi_recorder-2.1.1}/epi_recorder.egg-info/requires.txt +0 -0
- {epi_recorder-2.0.0 → epi_recorder-2.1.1}/epi_viewer_static/app.js +0 -0
- {epi_recorder-2.0.0 → epi_recorder-2.1.1}/examples/api_example.py +0 -0
- {epi_recorder-2.0.0 → epi_recorder-2.1.1}/examples/decorator_example.py +0 -0
- {epi_recorder-2.0.0 → epi_recorder-2.1.1}/examples/hello_simple.py +0 -0
- {epi_recorder-2.0.0 → epi_recorder-2.1.1}/examples/metadata_example.py +0 -0
- {epi_recorder-2.0.0 → epi_recorder-2.1.1}/examples/zero_config_example.py +0 -0
- {epi_recorder-2.0.0 → epi_recorder-2.1.1}/setup.cfg +0 -0
- {epi_recorder-2.0.0 → epi_recorder-2.1.1}/tests/__init__.py +0 -0
- {epi_recorder-2.0.0 → epi_recorder-2.1.1}/tests/simulate_user.py +0 -0
- {epi_recorder-2.0.0 → epi_recorder-2.1.1}/tests/test_100_percent.py +0 -0
- {epi_recorder-2.0.0 → epi_recorder-2.1.1}/tests/test_absolute_100.py +0 -0
- {epi_recorder-2.0.0 → epi_recorder-2.1.1}/tests/test_api.py +0 -0
- {epi_recorder-2.0.0 → epi_recorder-2.1.1}/tests/test_cli_comprehensive.py +0 -0
- {epi_recorder-2.0.0 → epi_recorder-2.1.1}/tests/test_cli_integration.py +0 -0
- {epi_recorder-2.0.0 → epi_recorder-2.1.1}/tests/test_container.py +0 -0
- {epi_recorder-2.0.0 → epi_recorder-2.1.1}/tests/test_coverage_100.py +0 -0
- {epi_recorder-2.0.0 → epi_recorder-2.1.1}/tests/test_final_100.py +0 -0
- {epi_recorder-2.0.0 → epi_recorder-2.1.1}/tests/test_metadata_features.py +0 -0
- {epi_recorder-2.0.0 → epi_recorder-2.1.1}/tests/test_new_ux.py +0 -0
- {epi_recorder-2.0.0 → epi_recorder-2.1.1}/tests/test_patcher.py +0 -0
- {epi_recorder-2.0.0 → epi_recorder-2.1.1}/tests/test_redactor.py +0 -0
- {epi_recorder-2.0.0 → epi_recorder-2.1.1}/tests/test_serialize.py +0 -0
- {epi_recorder-2.0.0 → epi_recorder-2.1.1}/tests/test_trust.py +0 -0
- {epi_recorder-2.0.0 → epi_recorder-2.1.1}/tests/verify_improvements.py +0 -0
|
@@ -5,6 +5,20 @@ All notable changes to the EPI Recorder project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [2.1.0] - 2025-12-14
|
|
9
|
+
|
|
10
|
+
### Usability (The "Foolproof" Update)
|
|
11
|
+
- **New `epi init` command**: A wizard that sets up keys, creates a demo script, and verifies the environment automatically.
|
|
12
|
+
- **New `epi doctor` command**: Self-diagnosis tool to check Python environment, keys, browser, and system paths.
|
|
13
|
+
- **Auto-Keys**: `epi run` now silently auto-generates keys if they are missing (no more "Unsigned" errors).
|
|
14
|
+
- **Empty Script Detection**: `epi run` warns if a script executed but recorded zero steps.
|
|
15
|
+
- **Windows Reliability**: Fixed Unicode/Emoji crashes on Windows terminals and path handling issues.
|
|
16
|
+
- **Smart UX**: Fixed interactive file picker to verify user inputs and properly list new scripts.
|
|
17
|
+
- **Path Fixer**: Included `epi_setup.py` to fix "command not recognized" errors on Windows.
|
|
18
|
+
|
|
19
|
+
## [2.0.0] - 2025-12-07
|
|
20
|
+
- Major release with full CLI and verification system.
|
|
21
|
+
|
|
8
22
|
## [1.1.0] - 2025-11-23
|
|
9
23
|
|
|
10
24
|
### Added
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: epi-recorder
|
|
3
|
+
Version: 2.1.1
|
|
4
|
+
Summary: Executable Package for AI workflows - Record, Verify, and Replay AI runs with automatic PATH setup
|
|
5
|
+
Author-email: Mohd Ibrahim Afridi <epitechforworld@outlook.com>
|
|
6
|
+
Maintainer-email: Mohd Ibrahim Afridi <epitechforworld@outlook.com>
|
|
7
|
+
License: Apache-2.0
|
|
8
|
+
Project-URL: Homepage, https://github.com/mohdibrahimaiml/EPI-V2.1.0
|
|
9
|
+
Project-URL: Documentation, https://github.com/mohdibrahimaiml/EPI-V2.1.0#readme
|
|
10
|
+
Project-URL: Repository, https://github.com/mohdibrahimaiml/EPI-V2.1.0
|
|
11
|
+
Project-URL: Issues, https://github.com/mohdibrahimaiml/EPI-V2.1.0/issues
|
|
12
|
+
Keywords: ai,reproducibility,verification,llm,evidence,openai,cryptography,workflow,audit
|
|
13
|
+
Classifier: Development Status :: 4 - Beta
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: Intended Audience :: Science/Research
|
|
16
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
17
|
+
Classifier: Operating System :: OS Independent
|
|
18
|
+
Classifier: Programming Language :: Python :: 3
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
22
|
+
Classifier: Topic :: Security :: Cryptography
|
|
23
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
24
|
+
Requires-Python: >=3.11
|
|
25
|
+
Description-Content-Type: text/markdown
|
|
26
|
+
License-File: LICENSE
|
|
27
|
+
Requires-Dist: pydantic>=2.0.0
|
|
28
|
+
Requires-Dist: cryptography>=41.0.0
|
|
29
|
+
Requires-Dist: cbor2>=5.6.0
|
|
30
|
+
Requires-Dist: typer[all]>=0.12.0
|
|
31
|
+
Requires-Dist: rich>=13.0.0
|
|
32
|
+
Provides-Extra: dev
|
|
33
|
+
Requires-Dist: pytest>=8.0.0; extra == "dev"
|
|
34
|
+
Requires-Dist: pytest-asyncio>=0.23.0; extra == "dev"
|
|
35
|
+
Requires-Dist: pytest-cov>=4.1.0; extra == "dev"
|
|
36
|
+
Requires-Dist: black>=24.0.0; extra == "dev"
|
|
37
|
+
Requires-Dist: ruff>=0.3.0; extra == "dev"
|
|
38
|
+
Dynamic: license-file
|
|
39
|
+
|
|
40
|
+
<div align="center">
|
|
41
|
+
|
|
42
|
+
# 📦 EPI
|
|
43
|
+
### Evidence Packaged Infrastructure
|
|
44
|
+
|
|
45
|
+
> **"Don't just log it. Sign it."**
|
|
46
|
+
>
|
|
47
|
+
> *The Standard for Verifiable AI Evidence.*
|
|
48
|
+
|
|
49
|
+
[](LICENSE)
|
|
50
|
+
[](https://www.python.org/downloads/)
|
|
51
|
+
[](https://pypi.org/project/epi-recorder/)
|
|
52
|
+
[](https://colab.research.google.com/github/mohdibrahimaiml/EPI-V2.1.0/blob/main/colab_demo.ipynb)
|
|
53
|
+
|
|
54
|
+
<br/>
|
|
55
|
+
|
|
56
|
+
[🎥 **Watch the Demo**](https://colab.research.google.com/github/mohdibrahimaiml/EPI-V2.1.0/blob/main/colab_demo.ipynb)
|
|
57
|
+
|
|
58
|
+
> **See the Proof:** Watch how EPI transforms a standard Python script into an **immutable, cryptographically signed evidence package**.
|
|
59
|
+
> *It's not just a recording. It's the "PDF" for AI Evidence.*
|
|
60
|
+
|
|
61
|
+
<br/>
|
|
62
|
+
|
|
63
|
+
[📚 **Read the Docs**](docs/CLI.md) • [🐛 **Report Bug**](https://github.com/mohdibrahimaiml/EPI-V2.1.0/issues)
|
|
64
|
+
|
|
65
|
+
</div>
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
## ⚡ The Problem: AI is a Black Box
|
|
70
|
+
|
|
71
|
+
When an AI Agent takes an action (spends money, signs a contract, or diagnoses a patient), **logs are not enough**.
|
|
72
|
+
Logs can be faked. Screenshots can be edited.
|
|
73
|
+
|
|
74
|
+
**If you can't prove it happened, it didn't happen.**
|
|
75
|
+
|
|
76
|
+
## 💎 The Solution: The "PDF" for Execution
|
|
77
|
+
|
|
78
|
+
**EPI** is a new file format (`.epi`) that acts as a **cryptographically signed receipt** for any AI workflow.
|
|
79
|
+
It captures the code, the data, the API calls, and the environment into a single, sealed evidence package.
|
|
80
|
+
|
|
81
|
+
| Feature | 📄 PDF (Document Standard) | 📦 EPI (Execution Standard) |
|
|
82
|
+
| :--- | :--- | :--- |
|
|
83
|
+
| **Purpose** | Visual Consistency | Computational Integrity |
|
|
84
|
+
| **Captures** | Text, Fonts, Images | Code, API Calls, OS State |
|
|
85
|
+
| **Trust** | "Looks Correct" | **"Cryptographically Proven"** |
|
|
86
|
+
| **Security** | ⚠️ Can run JS (Unsafe) | ✅ **Static HTML (Safe)** |
|
|
87
|
+
| **Analogy** | A digital photo | A flight recorder |
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
## 🚀 Quick Start (Zero Config)
|
|
92
|
+
|
|
93
|
+
### 1️⃣ Install
|
|
94
|
+
```bash
|
|
95
|
+
pip install epi-recorder
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### 2️⃣ Record
|
|
99
|
+
Wrap any script. EPI intercepts shell commands, file I/O, and LLM calls (OpenAI, Anthropic, Ollama).
|
|
100
|
+
```bash
|
|
101
|
+
epi record --out evidence.epi -- python agent.py
|
|
102
|
+
```
|
|
103
|
+
*> Creates `evidence.epi` (a ZIP containing the code, logs, and signatures)*
|
|
104
|
+
|
|
105
|
+
### 3️⃣ View
|
|
106
|
+
Open the evidence in your browser. **Zero-install, works offline.**
|
|
107
|
+
```bash
|
|
108
|
+
epi view evidence.epi
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
## 🧩 Architecture
|
|
114
|
+
|
|
115
|
+
```mermaid
|
|
116
|
+
graph LR
|
|
117
|
+
User[User Script] -->|Intercepts| Recorder
|
|
118
|
+
Recorder -->|Writes| Evidence[.EPI File]
|
|
119
|
+
|
|
120
|
+
subgraph "The .EPI Container"
|
|
121
|
+
Evidence --> Manifest[Manifest]
|
|
122
|
+
Evidence --> Timeline[Steps & Logs]
|
|
123
|
+
Evidence --> Artifacts[Files & Data]
|
|
124
|
+
Evidence --> Sig[Signature]
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
Evidence -->|Reads| Verifier
|
|
128
|
+
Evidence -->|Renders| Viewer
|
|
129
|
+
|
|
130
|
+
Verifier -->|Outputs| Report[Integrity Report]
|
|
131
|
+
Viewer -->|Displays| UI[Browser Interface]
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
---
|
|
135
|
+
|
|
136
|
+
## 🔐 Security & Privacy
|
|
137
|
+
|
|
138
|
+
* **Safe by Design**: The viewer is **100% static HTML/JSON**. It never executes the recorded code, making it safe to open files from untrusted sources.
|
|
139
|
+
* **Privacy First**: API keys are automatically detected and **redacted** from logs.
|
|
140
|
+
* **No Lock-In**: The format is open (ZIP + JSON). You can unzip it and audit the raw data anytime.
|
|
141
|
+
|
|
142
|
+
---
|
|
143
|
+
|
|
144
|
+
## 📚 Documentation
|
|
145
|
+
|
|
146
|
+
* **[CLI Reference](docs/CLI.md)**: Master the `init`, `run`, `doctor`, and `keys` commands.
|
|
147
|
+
* **[File Specification](docs/EPI-SPEC.md)**: Deep dive into the V2.1.0 format mechanics.
|
|
148
|
+
|
|
149
|
+
---
|
|
150
|
+
|
|
151
|
+
## 📄 License
|
|
152
|
+
|
|
153
|
+
**Apache 2.0** — Open for commercial and private use.
|
|
154
|
+
|
|
155
|
+
<div align="center">
|
|
156
|
+
<br/>
|
|
157
|
+
<b>Built for the future of the AI Economy.</b><br>
|
|
158
|
+
<i>Turning opaque runs into verifiable proofs.</i>
|
|
159
|
+
</div>
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
# 📦 EPI
|
|
4
|
+
### Evidence Packaged Infrastructure
|
|
5
|
+
|
|
6
|
+
> **"Don't just log it. Sign it."**
|
|
7
|
+
>
|
|
8
|
+
> *The Standard for Verifiable AI Evidence.*
|
|
9
|
+
|
|
10
|
+
[](LICENSE)
|
|
11
|
+
[](https://www.python.org/downloads/)
|
|
12
|
+
[](https://pypi.org/project/epi-recorder/)
|
|
13
|
+
[](https://colab.research.google.com/github/mohdibrahimaiml/EPI-V2.1.0/blob/main/colab_demo.ipynb)
|
|
14
|
+
|
|
15
|
+
<br/>
|
|
16
|
+
|
|
17
|
+
[🎥 **Watch the Demo**](https://colab.research.google.com/github/mohdibrahimaiml/EPI-V2.1.0/blob/main/colab_demo.ipynb)
|
|
18
|
+
|
|
19
|
+
> **See the Proof:** Watch how EPI transforms a standard Python script into an **immutable, cryptographically signed evidence package**.
|
|
20
|
+
> *It's not just a recording. It's the "PDF" for AI Evidence.*
|
|
21
|
+
|
|
22
|
+
<br/>
|
|
23
|
+
|
|
24
|
+
[📚 **Read the Docs**](docs/CLI.md) • [🐛 **Report Bug**](https://github.com/mohdibrahimaiml/EPI-V2.1.0/issues)
|
|
25
|
+
|
|
26
|
+
</div>
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## ⚡ The Problem: AI is a Black Box
|
|
31
|
+
|
|
32
|
+
When an AI Agent takes an action (spends money, signs a contract, or diagnoses a patient), **logs are not enough**.
|
|
33
|
+
Logs can be faked. Screenshots can be edited.
|
|
34
|
+
|
|
35
|
+
**If you can't prove it happened, it didn't happen.**
|
|
36
|
+
|
|
37
|
+
## 💎 The Solution: The "PDF" for Execution
|
|
38
|
+
|
|
39
|
+
**EPI** is a new file format (`.epi`) that acts as a **cryptographically signed receipt** for any AI workflow.
|
|
40
|
+
It captures the code, the data, the API calls, and the environment into a single, sealed evidence package.
|
|
41
|
+
|
|
42
|
+
| Feature | 📄 PDF (Document Standard) | 📦 EPI (Execution Standard) |
|
|
43
|
+
| :--- | :--- | :--- |
|
|
44
|
+
| **Purpose** | Visual Consistency | Computational Integrity |
|
|
45
|
+
| **Captures** | Text, Fonts, Images | Code, API Calls, OS State |
|
|
46
|
+
| **Trust** | "Looks Correct" | **"Cryptographically Proven"** |
|
|
47
|
+
| **Security** | ⚠️ Can run JS (Unsafe) | ✅ **Static HTML (Safe)** |
|
|
48
|
+
| **Analogy** | A digital photo | A flight recorder |
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## 🚀 Quick Start (Zero Config)
|
|
53
|
+
|
|
54
|
+
### 1️⃣ Install
|
|
55
|
+
```bash
|
|
56
|
+
pip install epi-recorder
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### 2️⃣ Record
|
|
60
|
+
Wrap any script. EPI intercepts shell commands, file I/O, and LLM calls (OpenAI, Anthropic, Ollama).
|
|
61
|
+
```bash
|
|
62
|
+
epi record --out evidence.epi -- python agent.py
|
|
63
|
+
```
|
|
64
|
+
*> Creates `evidence.epi` (a ZIP containing the code, logs, and signatures)*
|
|
65
|
+
|
|
66
|
+
### 3️⃣ View
|
|
67
|
+
Open the evidence in your browser. **Zero-install, works offline.**
|
|
68
|
+
```bash
|
|
69
|
+
epi view evidence.epi
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
## 🧩 Architecture
|
|
75
|
+
|
|
76
|
+
```mermaid
|
|
77
|
+
graph LR
|
|
78
|
+
User[User Script] -->|Intercepts| Recorder
|
|
79
|
+
Recorder -->|Writes| Evidence[.EPI File]
|
|
80
|
+
|
|
81
|
+
subgraph "The .EPI Container"
|
|
82
|
+
Evidence --> Manifest[Manifest]
|
|
83
|
+
Evidence --> Timeline[Steps & Logs]
|
|
84
|
+
Evidence --> Artifacts[Files & Data]
|
|
85
|
+
Evidence --> Sig[Signature]
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
Evidence -->|Reads| Verifier
|
|
89
|
+
Evidence -->|Renders| Viewer
|
|
90
|
+
|
|
91
|
+
Verifier -->|Outputs| Report[Integrity Report]
|
|
92
|
+
Viewer -->|Displays| UI[Browser Interface]
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
---
|
|
96
|
+
|
|
97
|
+
## 🔐 Security & Privacy
|
|
98
|
+
|
|
99
|
+
* **Safe by Design**: The viewer is **100% static HTML/JSON**. It never executes the recorded code, making it safe to open files from untrusted sources.
|
|
100
|
+
* **Privacy First**: API keys are automatically detected and **redacted** from logs.
|
|
101
|
+
* **No Lock-In**: The format is open (ZIP + JSON). You can unzip it and audit the raw data anytime.
|
|
102
|
+
|
|
103
|
+
---
|
|
104
|
+
|
|
105
|
+
## 📚 Documentation
|
|
106
|
+
|
|
107
|
+
* **[CLI Reference](docs/CLI.md)**: Master the `init`, `run`, `doctor`, and `keys` commands.
|
|
108
|
+
* **[File Specification](docs/EPI-SPEC.md)**: Deep dive into the V2.1.0 format mechanics.
|
|
109
|
+
|
|
110
|
+
---
|
|
111
|
+
|
|
112
|
+
## 📄 License
|
|
113
|
+
|
|
114
|
+
**Apache 2.0** — Open for commercial and private use.
|
|
115
|
+
|
|
116
|
+
<div align="center">
|
|
117
|
+
<br/>
|
|
118
|
+
<b>Built for the future of the AI Economy.</b><br>
|
|
119
|
+
<i>Turning opaque runs into verifiable proofs.</i>
|
|
120
|
+
</div>
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"""
|
|
2
|
+
EPI CLI - Main entry point for python -m epi_cli
|
|
3
|
+
|
|
4
|
+
This allows users to run the CLI even if 'epi' is not in PATH:
|
|
5
|
+
python -m epi_cli run script.py
|
|
6
|
+
python -m epi_cli view recording.epi
|
|
7
|
+
etc.
|
|
8
|
+
"""
|
|
9
|
+
from epi_cli.main import cli_main
|
|
10
|
+
|
|
11
|
+
if __name__ == "__main__":
|
|
12
|
+
cli_main()
|
|
@@ -161,6 +161,151 @@ def keys(
|
|
|
161
161
|
raise typer.Exit(1)
|
|
162
162
|
|
|
163
163
|
|
|
164
|
+
@app.command()
|
|
165
|
+
def init(
|
|
166
|
+
demo_filename: str = typer.Option("epi_demo.py", "--name", "-n", help="Name of the demo script"),
|
|
167
|
+
no_open: bool = typer.Option(False, "--no-open", help="Don't open viewer automatically (for testing)")
|
|
168
|
+
):
|
|
169
|
+
"""
|
|
170
|
+
[Wizard] First-time setup wizard! Creates keys, demo script, and runs it.
|
|
171
|
+
"""
|
|
172
|
+
console.print("\n[bold magenta]EPI Setup Wizard[/bold magenta]\n")
|
|
173
|
+
|
|
174
|
+
# 1. Keys
|
|
175
|
+
from epi_cli.keys import generate_default_keypair_if_missing
|
|
176
|
+
console.print("1. [dim]Checking security keys...[/dim]", end=" ")
|
|
177
|
+
if generate_default_keypair_if_missing(console_output=False):
|
|
178
|
+
console.print("[green]Created![/green]")
|
|
179
|
+
else:
|
|
180
|
+
console.print("[green]Found! [OK][/green]")
|
|
181
|
+
|
|
182
|
+
# 2. Demo Script
|
|
183
|
+
console.print(f"2. [dim]Creating demo script '{demo_filename}'...[/dim]", end=" ")
|
|
184
|
+
script_content = '''# Welcome to EPI!
|
|
185
|
+
|
|
186
|
+
import time
|
|
187
|
+
|
|
188
|
+
print("="*40)
|
|
189
|
+
print(" Hello from your first EPI recording!")
|
|
190
|
+
print("="*40)
|
|
191
|
+
|
|
192
|
+
print("\\n1. Doing some math...")
|
|
193
|
+
result = 123 * 456
|
|
194
|
+
print(f" 123 * 456 = {result}")
|
|
195
|
+
|
|
196
|
+
print("\\n2. Creating a file...")
|
|
197
|
+
with open("epi_hello.txt", "w") as f:
|
|
198
|
+
f.write(f"Calculation result: {result}")
|
|
199
|
+
print(" Saved 'epi_hello.txt'")
|
|
200
|
+
|
|
201
|
+
print("\\n3. Finishing up...")
|
|
202
|
+
time.sleep(0.5)
|
|
203
|
+
print("[OK] Done! Now check the browser!")
|
|
204
|
+
'''
|
|
205
|
+
import os
|
|
206
|
+
if not os.path.exists(demo_filename):
|
|
207
|
+
with open(demo_filename, "w") as f:
|
|
208
|
+
f.write(script_content)
|
|
209
|
+
console.print("[green]Created![/green]")
|
|
210
|
+
else:
|
|
211
|
+
console.print("[yellow]Exists (Skipped) >>[/yellow]")
|
|
212
|
+
|
|
213
|
+
# 3. Running
|
|
214
|
+
console.print("\n3. [bold cyan]Running the demo now...[/bold cyan]\n")
|
|
215
|
+
|
|
216
|
+
# Call run command programmatically
|
|
217
|
+
# We use subprocess to keep it clean separate process
|
|
218
|
+
import subprocess
|
|
219
|
+
import sys
|
|
220
|
+
cmd = [sys.executable, "-m", "epi_cli.main", "run", demo_filename]
|
|
221
|
+
if no_open:
|
|
222
|
+
cmd.append("--no-open")
|
|
223
|
+
subprocess.run(cmd)
|
|
224
|
+
|
|
225
|
+
console.print("\n[bold green]You are all set![/bold green]")
|
|
226
|
+
console.print(f"[dim]Next time just run:[/dim] epi run {demo_filename}")
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
@app.command()
|
|
230
|
+
def doctor():
|
|
231
|
+
"""
|
|
232
|
+
[Doctor] Self-healing doctor. Fixes common issues silently.
|
|
233
|
+
"""
|
|
234
|
+
console.print("\n[bold blue]EPI Doctor - System Health Check[/bold blue]\n")
|
|
235
|
+
|
|
236
|
+
issues = 0
|
|
237
|
+
fixed = 0
|
|
238
|
+
|
|
239
|
+
# Check 1: Keys
|
|
240
|
+
console.print("1. Security Keys: ", end="")
|
|
241
|
+
from epi_cli.keys import generate_default_keypair_if_missing
|
|
242
|
+
if generate_default_keypair_if_missing(console_output=False):
|
|
243
|
+
console.print("[green][OK] FIXED (Generated)[/green]")
|
|
244
|
+
fixed += 1
|
|
245
|
+
else:
|
|
246
|
+
console.print("[green][OK][/green]")
|
|
247
|
+
|
|
248
|
+
# Check 2: Command on PATH
|
|
249
|
+
console.print("2. 'epi' command: ", end="")
|
|
250
|
+
import shutil
|
|
251
|
+
if shutil.which("epi"):
|
|
252
|
+
console.print("[green][OK][/green]")
|
|
253
|
+
else:
|
|
254
|
+
console.print("[red][X] NOT IN PATH[/red]")
|
|
255
|
+
issues += 1
|
|
256
|
+
|
|
257
|
+
# Try to auto-fix on Windows
|
|
258
|
+
import platform
|
|
259
|
+
if platform.system() == "Windows":
|
|
260
|
+
console.print(" [cyan]→ Attempting automatic PATH fix...[/cyan]")
|
|
261
|
+
try:
|
|
262
|
+
import epi_postinstall
|
|
263
|
+
from pathlib import Path
|
|
264
|
+
|
|
265
|
+
scripts_dir = epi_postinstall.get_scripts_dir()
|
|
266
|
+
if scripts_dir and scripts_dir.exists():
|
|
267
|
+
console.print(f" [dim]Scripts directory: {scripts_dir}[/dim]")
|
|
268
|
+
|
|
269
|
+
if epi_postinstall.add_to_user_path_windows(scripts_dir):
|
|
270
|
+
console.print(" [green][OK] PATH updated successfully![/green]")
|
|
271
|
+
console.print(" [yellow][!] Please restart your terminal for changes to take effect[/yellow]")
|
|
272
|
+
fixed += 1
|
|
273
|
+
else:
|
|
274
|
+
console.print(" [yellow][!] Could not update PATH automatically[/yellow]")
|
|
275
|
+
console.print(" [dim]Manual fix: Use 'python -m epi_cli' instead[/dim]")
|
|
276
|
+
else:
|
|
277
|
+
console.print(" [red][X] Could not locate Scripts directory[/red]")
|
|
278
|
+
except Exception as e:
|
|
279
|
+
console.print(f" [red][X] Auto-fix failed: {e}[/red]")
|
|
280
|
+
console.print(" [dim]Workaround: Use 'python -m epi_cli' instead[/dim]")
|
|
281
|
+
else:
|
|
282
|
+
console.print(" [dim]Workaround: Use 'python -m epi_cli' instead[/dim]")
|
|
283
|
+
|
|
284
|
+
# Check 3: Browser
|
|
285
|
+
console.print("3. Browser Check: ", end="")
|
|
286
|
+
try:
|
|
287
|
+
import webbrowser
|
|
288
|
+
webbrowser.get()
|
|
289
|
+
console.print("[green][OK][/green]")
|
|
290
|
+
except:
|
|
291
|
+
console.print("[yellow][!] WARNING (Headless?)[/yellow]")
|
|
292
|
+
|
|
293
|
+
# Summary
|
|
294
|
+
print()
|
|
295
|
+
console.print("[bold]" + "="*70 + "[/bold]")
|
|
296
|
+
if issues == 0:
|
|
297
|
+
console.print("[bold green][OK] System Healthy![/bold green]")
|
|
298
|
+
else:
|
|
299
|
+
if fixed > 0:
|
|
300
|
+
console.print(f"[bold yellow][!] Fixed {fixed}/{issues} issues[/bold yellow]")
|
|
301
|
+
if fixed < issues:
|
|
302
|
+
console.print("[dim]Some issues require manual attention (see above)[/dim]")
|
|
303
|
+
else:
|
|
304
|
+
console.print(f"[bold yellow][!] Found {issues} issues[/bold yellow]")
|
|
305
|
+
console.print("[dim]See suggestions above[/dim]")
|
|
306
|
+
console.print("[bold]" + "="*70 + "[/bold]\n")
|
|
307
|
+
|
|
308
|
+
|
|
164
309
|
# Entry point for CLI
|
|
165
310
|
def cli_main():
|
|
166
311
|
"""CLI entry point (called by `epi` command)."""
|
|
@@ -157,7 +157,7 @@ def _open_viewer(epi_file: Path) -> bool:
|
|
|
157
157
|
|
|
158
158
|
@app.command()
|
|
159
159
|
def run(
|
|
160
|
-
script: Path = typer.Argument(
|
|
160
|
+
script: Optional[Path] = typer.Argument(None, help="Python script to record (Optional - Interactive if missing)"),
|
|
161
161
|
no_verify: bool = typer.Option(False, "--no-verify", help="Skip verification"),
|
|
162
162
|
no_open: bool = typer.Option(False, "--no-open", help="Don't open viewer automatically"),
|
|
163
163
|
# New metadata options
|
|
@@ -170,14 +170,65 @@ def run(
|
|
|
170
170
|
"""
|
|
171
171
|
Zero-config recording: record + verify + view.
|
|
172
172
|
|
|
173
|
-
|
|
173
|
+
Interactive:
|
|
174
|
+
epi run (Selects script from list)
|
|
175
|
+
|
|
176
|
+
Direct:
|
|
174
177
|
epi run my_script.py
|
|
175
|
-
epi run script.py --goal "improve accuracy" --notes "test run" --metric accuracy=0.92 --metric latency=210 --approved-by "bob" --tag test --tag v1
|
|
176
178
|
"""
|
|
179
|
+
|
|
180
|
+
# --- SMART UX 1: INTERACTIVE MODE ---
|
|
181
|
+
if script is None:
|
|
182
|
+
# Find Python files in current directory
|
|
183
|
+
py_files = list(Path.cwd().glob("*.py"))
|
|
184
|
+
# Only exclude specific setup files, not everything starting with epi_
|
|
185
|
+
py_files = [f for f in py_files if f.name not in ["setup.py", "epi_setup.py"]]
|
|
186
|
+
|
|
187
|
+
if not py_files:
|
|
188
|
+
console.print("[yellow]No Python scripts found in this directory.[/yellow]")
|
|
189
|
+
console.print("Create one or specify path: epi run [path/to/script.py]")
|
|
190
|
+
raise typer.Exit(1)
|
|
191
|
+
|
|
192
|
+
console.print("\n[bold cyan]Select a script to record:[/bold cyan]")
|
|
193
|
+
for idx, f in enumerate(py_files, 1):
|
|
194
|
+
console.print(f" [green]{idx}.[/green] {f.name}")
|
|
195
|
+
|
|
196
|
+
from rich.prompt import Prompt
|
|
197
|
+
choice = Prompt.ask("\nNumber", default="1")
|
|
198
|
+
|
|
199
|
+
try:
|
|
200
|
+
choice_idx = int(choice) - 1
|
|
201
|
+
if 0 <= choice_idx < len(py_files):
|
|
202
|
+
script = py_files[choice_idx]
|
|
203
|
+
else:
|
|
204
|
+
console.print("[red]Invalid selection.[/red]")
|
|
205
|
+
raise typer.Exit(1)
|
|
206
|
+
except ValueError:
|
|
207
|
+
console.print("[red]Invalid input.[/red]")
|
|
208
|
+
raise typer.Exit(1)
|
|
209
|
+
|
|
210
|
+
console.print(f"[dim]Selected:[/dim] {script.name}\n")
|
|
211
|
+
|
|
212
|
+
# --- SMART UX 2: TYPO FIXER ---
|
|
177
213
|
# Validate script exists
|
|
178
214
|
if not script.exists():
|
|
179
|
-
|
|
180
|
-
|
|
215
|
+
# Check for typos (simple close match)
|
|
216
|
+
import difflib
|
|
217
|
+
candidates = list(Path.cwd().glob("*.py"))
|
|
218
|
+
candidate_names = [c.name for c in candidates]
|
|
219
|
+
matches = difflib.get_close_matches(script.name, candidate_names, n=1, cutoff=0.6)
|
|
220
|
+
|
|
221
|
+
if matches:
|
|
222
|
+
from rich.prompt import Confirm
|
|
223
|
+
suggestion = matches[0]
|
|
224
|
+
if Confirm.ask(f"[yellow]Script '{script}' not found. Did you mean '{suggestion}'?[/yellow]"):
|
|
225
|
+
script = Path(suggestion)
|
|
226
|
+
else:
|
|
227
|
+
console.print(f"[red][FAIL] Error:[/red] Script not found: {script}")
|
|
228
|
+
raise typer.Exit(1)
|
|
229
|
+
else:
|
|
230
|
+
console.print(f"[red][FAIL] Error:[/red] Script not found: {script}")
|
|
231
|
+
raise typer.Exit(1)
|
|
181
232
|
|
|
182
233
|
# Parse metrics if provided
|
|
183
234
|
metrics_dict = None
|
|
@@ -217,12 +268,25 @@ def run(
|
|
|
217
268
|
|
|
218
269
|
console.print(f"[dim]Recording:[/dim] {script.name}")
|
|
219
270
|
|
|
271
|
+
# --- AUTO-FIX 1: KEYS ---
|
|
272
|
+
# Ensure default keys exist so it's never "Unsigned"
|
|
273
|
+
from epi_cli.keys import generate_default_keypair_if_missing
|
|
274
|
+
generated = generate_default_keypair_if_missing(console_output=False)
|
|
275
|
+
if generated:
|
|
276
|
+
console.print("[green]Created your secure cryptographic identity (keys/default)[/green]")
|
|
277
|
+
# -----------------------
|
|
278
|
+
|
|
220
279
|
import subprocess
|
|
221
280
|
|
|
222
281
|
start = time.time()
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
282
|
+
try:
|
|
283
|
+
with open(stdout_log, "wb") as out_f, open(stderr_log, "wb") as err_f:
|
|
284
|
+
proc = subprocess.Popen(cmd, env=child_env, stdout=out_f, stderr=err_f)
|
|
285
|
+
rc = proc.wait()
|
|
286
|
+
except Exception as e:
|
|
287
|
+
console.print(f"\n[bold red][FAIL] Could not execute command:[/bold red] {cmd[0]}")
|
|
288
|
+
console.print(f"[dim]Error detail: {e}[/dim]")
|
|
289
|
+
raise typer.Exit(1)
|
|
226
290
|
duration = round(time.time() - start, 3)
|
|
227
291
|
|
|
228
292
|
# Build manifest with metadata
|
|
@@ -238,6 +302,21 @@ def run(
|
|
|
238
302
|
# Package into .epi
|
|
239
303
|
EPIContainer.pack(temp_workspace, manifest, out)
|
|
240
304
|
|
|
305
|
+
# --- AUTO-FIX 2: EMPTY CHECK ---
|
|
306
|
+
# Check if we actually recorded anything
|
|
307
|
+
import json
|
|
308
|
+
timeline_path = temp_workspace / "timeline.json"
|
|
309
|
+
if timeline_path.exists():
|
|
310
|
+
try:
|
|
311
|
+
with open(timeline_path) as f:
|
|
312
|
+
timeline_data = json.load(f)
|
|
313
|
+
if not timeline_data:
|
|
314
|
+
console.print("\n[bold yellow][!] Warning: Your script ran but didn't record any steps![/bold yellow]")
|
|
315
|
+
console.print("[dim]Did you forget to print anything? Try adding: print('Hello EPI')[/dim]\n")
|
|
316
|
+
except:
|
|
317
|
+
pass
|
|
318
|
+
# -----------------------------
|
|
319
|
+
|
|
241
320
|
# Auto-sign
|
|
242
321
|
signed = False
|
|
243
322
|
try:
|
|
@@ -84,7 +84,7 @@ def verify(
|
|
|
84
84
|
else:
|
|
85
85
|
console.print(f" [red][FAIL][/red] {len(mismatches)} file(s) failed verification")
|
|
86
86
|
for filename, reason in mismatches.items():
|
|
87
|
-
console.print(f" [red]
|
|
87
|
+
console.print(f" [red]-[/red] {filename}: {reason}")
|
|
88
88
|
|
|
89
89
|
# ========== STEP 3: AUTHENTICITY CHECKS ==========
|
|
90
90
|
if verbose:
|
|
@@ -203,7 +203,7 @@ def print_trust_report(report: dict, epi_file: Path, verbose: bool = False):
|
|
|
203
203
|
content_lines.append("")
|
|
204
204
|
content_lines.append("[bold red]File Mismatches:[/bold red]")
|
|
205
205
|
for filename, reason in report["mismatches"].items():
|
|
206
|
-
content_lines.append(f" [red]
|
|
206
|
+
content_lines.append(f" [red]-[/red] {filename}: {reason}")
|
|
207
207
|
|
|
208
208
|
content = "\n".join(content_lines)
|
|
209
209
|
|
|
@@ -71,14 +71,16 @@ class EPIContainer:
|
|
|
71
71
|
viewer_static_dir = Path(__file__).parent.parent / "epi_viewer_static"
|
|
72
72
|
template_path = viewer_static_dir / "index.html"
|
|
73
73
|
app_js_path = viewer_static_dir / "app.js"
|
|
74
|
+
css_path = viewer_static_dir / "viewer_lite.css"
|
|
74
75
|
|
|
75
76
|
if not template_path.exists():
|
|
76
77
|
# Fallback: minimal viewer if template not found
|
|
77
78
|
return EPIContainer._create_minimal_viewer(manifest)
|
|
78
79
|
|
|
79
|
-
# Read template
|
|
80
|
+
# Read template and assets
|
|
80
81
|
template_html = template_path.read_text(encoding="utf-8")
|
|
81
82
|
app_js = app_js_path.read_text(encoding="utf-8") if app_js_path.exists() else ""
|
|
83
|
+
css_styles = css_path.read_text(encoding="utf-8") if css_path.exists() else ""
|
|
82
84
|
|
|
83
85
|
# Read steps from steps.jsonl
|
|
84
86
|
steps = []
|
|
@@ -104,8 +106,14 @@ class EPIContainer:
|
|
|
104
106
|
f'<script id="epi-data" type="application/json">{data_json}</script>'
|
|
105
107
|
)
|
|
106
108
|
|
|
109
|
+
# Replaces Tailwind CDN with local CSS
|
|
110
|
+
html_with_css = html_with_data.replace(
|
|
111
|
+
'<script src="https://cdn.tailwindcss.com"></script>',
|
|
112
|
+
f'<style>{css_styles}</style>'
|
|
113
|
+
)
|
|
114
|
+
|
|
107
115
|
# Inline app.js
|
|
108
|
-
html_with_js =
|
|
116
|
+
html_with_js = html_with_css.replace(
|
|
109
117
|
'<script src="app.js"></script>',
|
|
110
118
|
f'<script>{app_js}</script>'
|
|
111
119
|
)
|