epi-recorder 2.0.0__py3-none-any.whl → 2.1.1__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
@@ -1,5 +1,10 @@
1
1
  """
2
- EPI CLI entry point for python -m epi_cli
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.
3
8
  """
4
9
  from epi_cli.main import cli_main
5
10
 
epi_cli/main.py CHANGED
@@ -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)."""
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_postinstall.py ADDED
@@ -0,0 +1,197 @@
1
+ """
2
+ Post-installation script for EPI Recorder
3
+ Automatically fixes PATH issues on Windows for better UX
4
+ """
5
+ import sys
6
+ import os
7
+ import platform
8
+ import subprocess
9
+ from pathlib import Path
10
+
11
+
12
+ def get_scripts_dir():
13
+ """Get the Scripts directory where pip installs executables"""
14
+ if platform.system() == "Windows":
15
+ # Get the site-packages directory
16
+ import site
17
+ user_site = site.getusersitepackages()
18
+ if user_site:
19
+ # Scripts is typically ../Scripts relative to site-packages
20
+ scripts_dir = Path(user_site).parent / "Scripts"
21
+ if scripts_dir.exists():
22
+ return scripts_dir
23
+
24
+ # Fallback: try to find it from pip
25
+ try:
26
+ result = subprocess.run(
27
+ [sys.executable, "-m", "pip", "show", "epi-recorder"],
28
+ capture_output=True,
29
+ text=True
30
+ )
31
+ if result.returncode == 0:
32
+ for line in result.stdout.split('\n'):
33
+ if line.startswith('Location:'):
34
+ location = line.split(':', 1)[1].strip()
35
+ scripts_dir = Path(location).parent / "Scripts"
36
+ if scripts_dir.exists():
37
+ return scripts_dir
38
+ except Exception:
39
+ pass
40
+
41
+ return None
42
+
43
+
44
+ def is_in_path(directory):
45
+ """Check if directory is in PATH"""
46
+ path_env = os.environ.get('PATH', '')
47
+ path_dirs = path_env.split(os.pathsep)
48
+ dir_str = str(directory)
49
+ return any(os.path.normcase(p) == os.path.normcase(dir_str) for p in path_dirs)
50
+
51
+
52
+ def add_to_user_path_windows(directory):
53
+ """Add directory to user PATH on Windows"""
54
+ try:
55
+ import winreg
56
+
57
+ # Open the user environment variables key
58
+ key = winreg.OpenKey(
59
+ winreg.HKEY_CURRENT_USER,
60
+ 'Environment',
61
+ 0,
62
+ winreg.KEY_READ | winreg.KEY_WRITE
63
+ )
64
+
65
+ try:
66
+ # Get current PATH
67
+ current_path, _ = winreg.QueryValueEx(key, 'Path')
68
+ except WindowsError:
69
+ current_path = ''
70
+
71
+ # Add our directory if not already there
72
+ path_parts = current_path.split(os.pathsep)
73
+ dir_str = str(directory)
74
+
75
+ if not any(os.path.normcase(p) == os.path.normcase(dir_str) for p in path_parts):
76
+ new_path = current_path + os.pathsep + dir_str
77
+ winreg.SetValueEx(key, 'Path', 0, winreg.REG_EXPAND_SZ, new_path)
78
+ winreg.CloseKey(key)
79
+
80
+ # Broadcast WM_SETTINGCHANGE to notify the system
81
+ try:
82
+ import ctypes
83
+ HWND_BROADCAST = 0xFFFF
84
+ WM_SETTINGCHANGE = 0x1A
85
+ ctypes.windll.user32.SendMessageW(
86
+ HWND_BROADCAST, WM_SETTINGCHANGE, 0, 'Environment'
87
+ )
88
+ except Exception:
89
+ pass
90
+
91
+ return True
92
+
93
+ winreg.CloseKey(key)
94
+ return False
95
+
96
+ except Exception as e:
97
+ print(f"Warning: Could not modify PATH: {e}")
98
+ return False
99
+
100
+
101
+ def check_epi_command():
102
+ """Check if 'epi' command is accessible"""
103
+ try:
104
+ result = subprocess.run(
105
+ ['epi', '--version'],
106
+ capture_output=True,
107
+ timeout=2
108
+ )
109
+ return result.returncode == 0
110
+ except (FileNotFoundError, subprocess.TimeoutExpired):
111
+ return False
112
+
113
+
114
+ def post_install():
115
+ """Main post-install function"""
116
+ print("\n" + "="*70)
117
+ print("🎉 EPI Recorder Installation Complete!")
118
+ print("="*70)
119
+
120
+ # Check if epi command works
121
+ if check_epi_command():
122
+ print("\n✅ The 'epi' command is ready to use!")
123
+ print("\nTry it now:")
124
+ print(" epi --help")
125
+ print(" epi init")
126
+ return
127
+
128
+ # If not, try to fix it (Windows only for now)
129
+ if platform.system() == "Windows":
130
+ print("\n⚠️ 'epi' command not found in PATH")
131
+ print("🔧 Attempting automatic fix...")
132
+
133
+ scripts_dir = get_scripts_dir()
134
+
135
+ if scripts_dir and scripts_dir.exists():
136
+ print(f"📁 Found Scripts directory: {scripts_dir}")
137
+
138
+ if not is_in_path(scripts_dir):
139
+ print("➕ Adding to your user PATH...")
140
+
141
+ try:
142
+ if add_to_user_path_windows(scripts_dir):
143
+ print("\n✅ SUCCESS! PATH updated.")
144
+ print("\n⚠️ IMPORTANT: You must restart your terminal for changes to take effect!")
145
+ print("\nAfter restarting your terminal, try:")
146
+ print(" epi --help")
147
+ print(" epi init")
148
+ else:
149
+ print("\n⚠️ Scripts directory already in PATH, but 'epi' not found.")
150
+ print("This might require a terminal restart.")
151
+ print("\nIf 'epi' still doesn't work after restarting, use:")
152
+ print(f" python -m epi_cli")
153
+ except Exception as e:
154
+ print(f"\n❌ Automatic fix failed: {e}")
155
+ show_manual_instructions(scripts_dir)
156
+ else:
157
+ print("✅ Scripts directory is in PATH")
158
+ print("⚠️ You may need to restart your terminal for the command to work.")
159
+ else:
160
+ print("❌ Could not locate Scripts directory")
161
+ show_fallback_instructions()
162
+ else:
163
+ # Linux/Mac
164
+ print("\n⚠️ 'epi' command not found in PATH")
165
+ print("\nIf 'epi' doesn't work, use:")
166
+ print(" python -m epi_cli")
167
+ print("\nOr add pip's user base bin directory to PATH:")
168
+ print(" export PATH=$PATH:$(python -m site --user-base)/bin")
169
+
170
+ print("\n" + "="*70 + "\n")
171
+
172
+
173
+ def show_manual_instructions(scripts_dir):
174
+ """Show manual PATH update instructions"""
175
+ print("\n📖 MANUAL FIX REQUIRED:")
176
+ print("\nOption 1: Update PATH (Permanent)")
177
+ print(" 1. Press Win + R, type: sysdm.cpl")
178
+ print(" 2. Advanced → Environment Variables")
179
+ print(" 3. Under 'User variables', select 'Path' → Edit")
180
+ print(" 4. Click 'New' and add:")
181
+ print(f" {scripts_dir}")
182
+ print(" 5. Click OK, restart your terminal")
183
+ print("\nOption 2: Use python -m (Always works)")
184
+ print(" python -m epi_cli run script.py")
185
+
186
+
187
+ def show_fallback_instructions():
188
+ """Show fallback instructions if automatic fix fails"""
189
+ print("\n📖 WORKAROUND:")
190
+ print("\nUse 'python -m epi_cli' instead of 'epi':")
191
+ print(" python -m epi_cli --help")
192
+ print(" python -m epi_cli run script.py")
193
+ print(" python -m epi_cli view recording.epi")
194
+
195
+
196
+ if __name__ == "__main__":
197
+ post_install()
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}")