epi-recorder 2.0.0__py3-none-any.whl → 2.1.0__py3-none-any.whl

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_cli/main.py CHANGED
@@ -161,6 +161,115 @@ 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[/bold blue]\n")
235
+
236
+ issues = 0
237
+
238
+ # Check 1: Keys
239
+ console.print("1. Security Keys: ", end="")
240
+ from epi_cli.keys import generate_default_keypair_if_missing
241
+ if generate_default_keypair_if_missing(console_output=False):
242
+ console.print("[green]FIXED (Generated)[/green]")
243
+ issues += 1
244
+ else:
245
+ console.print("[green]OK[/green]")
246
+
247
+ # Check 2: Command on PATH
248
+ console.print("2. 'epi' command: ", end="")
249
+ import shutil
250
+ if shutil.which("epi"):
251
+ console.print("[green]OK[/green]")
252
+ else:
253
+ console.print("[red]MISSING[/red]")
254
+ console.print(" [yellow]Run: python epi_setup.py[/yellow]")
255
+ issues += 1
256
+
257
+ # Check 3: Browser
258
+ console.print("3. Browser Check: ", end="")
259
+ try:
260
+ import webbrowser
261
+ webbrowser.get()
262
+ console.print("[green]OK[/green]")
263
+ except:
264
+ console.print("[yellow]WARNING (Headless?)[/yellow]")
265
+
266
+ print()
267
+ if issues == 0:
268
+ console.print("[bold green][OK] System Healthy![/bold green]")
269
+ else:
270
+ console.print(f"[bold yellow][!] Found {issues} issues.[/bold yellow]")
271
+
272
+
164
273
  # Entry point for CLI
165
274
  def cli_main():
166
275
  """CLI entry point (called by `epi` command)."""
epi_cli/run.py CHANGED
@@ -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(..., help="Python script to record"),
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
- Example:
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
- console.print(f"[red][FAIL] Error:[/red] Script not found: {script}")
180
- raise typer.Exit(1)
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
- with open(stdout_log, "wb") as out_f, open(stderr_log, "wb") as err_f:
224
- proc = subprocess.Popen(cmd, env=child_env, stdout=out_f, stderr=err_f)
225
- rc = proc.wait()
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:
epi_cli/verify.py CHANGED
@@ -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][/red] {filename}: {reason}")
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][/red] {filename}: {reason}")
206
+ content_lines.append(f" [red]-[/red] {filename}: {reason}")
207
207
 
208
208
  content = "\n".join(content_lines)
209
209
 
epi_core/__init__.py CHANGED
@@ -2,7 +2,7 @@
2
2
  EPI Core - Core data structures, serialization, and container management.
3
3
  """
4
4
 
5
- __version__ = "2.0.0"
5
+ __version__ = "2.1.0"
6
6
 
7
7
  from epi_core.schemas import ManifestModel, StepModel
8
8
  from epi_core.serialize import get_canonical_hash
epi_core/container.py CHANGED
@@ -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 = html_with_data.replace(
116
+ html_with_js = html_with_css.replace(
109
117
  '<script src="app.js"></script>',
110
118
  f'<script>{app_js}</script>'
111
119
  )
epi_recorder/__init__.py CHANGED
@@ -4,7 +4,7 @@ EPI Recorder - Runtime interception and workflow capture.
4
4
  Python API for recording AI workflows with cryptographic verification.
5
5
  """
6
6
 
7
- __version__ = "2.0.0"
7
+ __version__ = "2.1.0"
8
8
 
9
9
  # Export Python API
10
10
  from epi_recorder.api import (
epi_recorder/api.py CHANGED
@@ -426,8 +426,15 @@ def _resolve_output_path(output_path: Optional[Path | str]) -> Path:
426
426
  # Add .epi extension if missing
427
427
  if path.suffix != ".epi":
428
428
  path = path.with_suffix(".epi")
429
-
430
- return path
429
+
430
+ # If path is absolute, return it
431
+ if path.is_absolute():
432
+ return path
433
+
434
+ # If path is relative, prepend recordings directory
435
+ recordings_dir = Path(os.getenv("EPI_RECORDINGS_DIR", "epi-recordings"))
436
+ recordings_dir.mkdir(parents=True, exist_ok=True)
437
+ return recordings_dir / path
431
438
 
432
439
 
433
440
  # Convenience function for users (supports zero-config)
@@ -0,0 +1,18 @@
1
+
2
+ try:
3
+ import epi_recorder.api
4
+ print("epi_recorder.api imported successfully")
5
+ except ImportError as e:
6
+ print(f"Failed to import epi_recorder.api: {e}")
7
+
8
+ try:
9
+ import epi_core
10
+ print("epi_core imported successfully")
11
+ except ImportError as e:
12
+ print(f"Failed to import epi_core: {e}")
13
+
14
+ try:
15
+ import epi_cli
16
+ print("epi_cli imported successfully")
17
+ except ImportError as e:
18
+ print(f"Failed to import epi_cli: {e}")
@@ -0,0 +1,4 @@
1
+
2
+ print("Hello from EPI Test Script")
3
+ import sys
4
+ print(f"Python version: {sys.version}")
@@ -0,0 +1,159 @@
1
+ Metadata-Version: 2.4
2
+ Name: epi-recorder
3
+ Version: 2.1.0
4
+ Summary: Executable Package for AI workflows - Record, Verify, and Replay AI runs
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](https://img.shields.io/badge/license-Apache%202.0-blue.svg?style=flat-square)](LICENSE)
50
+ [![Python](https://img.shields.io/badge/python-3.11+-ffe500.svg?style=flat-square&logo=python&logoColor=black)](https://www.python.org/downloads/)
51
+ [![Status](https://img.shields.io/badge/status-MVP-orange.svg?style=flat-square)](https://pypi.org/project/epi-recorder/)
52
+ [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](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) &nbsp;•&nbsp; [🐛 **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,31 @@
1
+ epi_cli/__init__.py,sha256=SSpuDlDqVBbPvR21CCRcNOB61EyB8jAUzNd8XISADa8,98
2
+ epi_cli/__main__.py,sha256=MjqwHrWaEjeXDQf5EK-N4F-1VVTX0BnrtWBhMCkPou4,134
3
+ epi_cli/keys.py,sha256=bHR6knfGR4g7uGuR2cu5VTcGXVyvtZzVtnRBbgPSaTE,9111
4
+ epi_cli/ls.py,sha256=Ip6OyubkXb_Ba9S7p-U1seWMcWpFeiV-fHhl9ocUy9k,5015
5
+ epi_cli/main.py,sha256=qwJnWiOKezKEmdUyPiWeeY6046bbTyEkonzRltEIPoY,9970
6
+ epi_cli/record.py,sha256=QtDrjr-7WbfcYXwPRrDDujrubUNrHhp062EcW8DZPNk,7321
7
+ epi_cli/run.py,sha256=dM_AYC3LzDBc_DzVKlb8obZBE52sAe3wYAnce0PLGT4,14101
8
+ epi_cli/verify.py,sha256=Rq34rw-zM_vZmYDsfg8cLlvW76_wJ99Pu-arjGRE9bw,8238
9
+ epi_cli/view.py,sha256=YZYohgT66Z2UYfN37d5VTPxipNiwSMacD4zHYaNypiU,4021
10
+ epi_core/__init__.py,sha256=8SozL9YglGs5EoBYFZcauai1l9SaDPCj6f3er5VowWw,303
11
+ epi_core/container.py,sha256=Ie1hPGzX761nDdLEJ2BaJJUaJ-9R5NrZo0FDzPR6-G4,12335
12
+ epi_core/redactor.py,sha256=mYVZrIsKJdIJBY2XHE0U_CYqgY4Z2vGRvLW-19_c0WE,9886
13
+ epi_core/schemas.py,sha256=7QC4bTjvQh-nfol3JC7VQpGVcmvH7jHk1By0h4GHpYg,4588
14
+ epi_core/serialize.py,sha256=rx6tEnfXX1gW-AGEUpVWPEB9zsaIZ4qYn65s-7aQQtA,4736
15
+ epi_core/trust.py,sha256=yk-ZrqVl9nTabAdSOfCTNUbAWSJvMds7wxc0ghdtEPM,7551
16
+ epi_recorder/__init__.py,sha256=K_Wu9Qb2P_Pdij6wZ4NlHp2avnvjy1KrB7sF76V9U6c,396
17
+ epi_recorder/api.py,sha256=EZtMF2MVIk8KR1GPJBSxDlEh57nsIc-B2Ok0_j3hZ4U,21561
18
+ epi_recorder/bootstrap.py,sha256=yE1y0rjQ-1D-ZhWFipX_6qXFTA2BPlEm8Ft4Z0iex88,1845
19
+ epi_recorder/environment.py,sha256=NCz_Pckc6GU1kQsYF3oXDsN46k5LVuYWbMEhTj1FRXo,6598
20
+ epi_recorder/patcher.py,sha256=tEAlDfH_rz3TblASn9xvMsadXgobvrenQe2Pq1M_xvA,14570
21
+ epi_recorder/test_import.py,sha256=Dhjkj8qYZR4d4w-bshClV6S-naCcS5srW7wsp7jW-cM,459
22
+ epi_recorder/test_script.py,sha256=4uUheXOfqC28NYICyaAxm3gnhfrkdDZtaXnwfjG-qhQ,92
23
+ epi_recorder-2.1.0.dist-info/licenses/LICENSE,sha256=aUgjAzBUkmKIZy3AZ_XAZWoh3kzkRlkOc3JHLVfyF-A,11540
24
+ epi_viewer_static/app.js,sha256=KLWM68U4hN-fcmkKbnz7mqML5GrdnYJwNeTAgKsmV2I,13023
25
+ epi_viewer_static/index.html,sha256=JxdsgzjJ5nGeDD1ROXI00w8rwsD7wXXOwitizxe8AWs,3298
26
+ epi_viewer_static/viewer_lite.css,sha256=oCEiEYbvdlaMwz-MPlrk3LoTVOad-bDLlP-X49Yw0o0,4691
27
+ epi_recorder-2.1.0.dist-info/METADATA,sha256=2PZhBdcsJp3Wpva5FRUDu6P7P5S9cwk0JDqkL5G8YvM,5894
28
+ epi_recorder-2.1.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
29
+ epi_recorder-2.1.0.dist-info/entry_points.txt,sha256=MfMwqVRx_yMGbuPpiyjz2f8fQp8TUbHmRC1H_bupoyM,41
30
+ epi_recorder-2.1.0.dist-info/top_level.txt,sha256=nKpWVQUoGWK-gyLLVbrxibQUlIzI0TowNzMDondlHpI,48
31
+ epi_recorder-2.1.0.dist-info/RECORD,,
@@ -1,20 +1,49 @@
1
1
  <!DOCTYPE html>
2
2
  <html lang="en">
3
+
3
4
  <head>
4
5
  <meta charset="UTF-8">
5
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
7
  <title>EPI Viewer</title>
7
8
  <script src="https://cdn.tailwindcss.com"></script>
8
9
  <style>
9
- body { font-family: system-ui, -apple-system, sans-serif; }
10
- .chat-bubble { max-width: 80%; }
11
- .step-card { transition: all 0.2s; }
12
- .step-card:hover { transform: translateX(4px); }
13
- pre { white-space: pre-wrap; word-wrap: break-word; }
14
- .trust-badge { animation: fadeIn 0.3s; }
15
- @keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
10
+ body {
11
+ font-family: system-ui, -apple-system, sans-serif;
12
+ }
13
+
14
+ .chat-bubble {
15
+ max-width: 80%;
16
+ }
17
+
18
+ .step-card {
19
+ transition: all 0.2s;
20
+ }
21
+
22
+ .step-card:hover {
23
+ transform: translateX(4px);
24
+ }
25
+
26
+ pre {
27
+ white-space: pre-wrap;
28
+ word-wrap: break-word;
29
+ }
30
+
31
+ .trust-badge {
32
+ animation: fadeIn 0.3s;
33
+ }
34
+
35
+ @keyframes fadeIn {
36
+ from {
37
+ opacity: 0;
38
+ }
39
+
40
+ to {
41
+ opacity: 1;
42
+ }
43
+ }
16
44
  </style>
17
45
  </head>
46
+
18
47
  <body class="bg-gray-50">
19
48
  <!-- EPI Data (injected at pack time) -->
20
49
  <script id="epi-data" type="application/json">
@@ -65,13 +94,12 @@
65
94
  <!-- Footer -->
66
95
  <footer class="mt-12 bg-white border-t border-gray-200">
67
96
  <div class="max-w-7xl mx-auto px-4 py-6 sm:px-6 lg:px-8">
68
- <p class="text-center text-sm text-gray-500">
69
- EPI v1.0-keystone | <span class="font-mono">application/epi+zip</span>
70
- </p>
97
+ EPI v2.1.0 | <span class="font-mono">application/epi+zip</span>
71
98
  </div>
72
99
  </footer>
73
100
  </div>
74
101
 
75
102
  <script src="app.js"></script>
76
103
  </body>
77
- </html>
104
+
105
+ </html>