epi-recorder 2.1.2__py3-none-any.whl → 2.2.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_analyzer/__init__.py +9 -0
- epi_analyzer/detector.py +337 -0
- epi_cli/__init__.py +4 -0
- epi_cli/__main__.py +4 -0
- epi_cli/chat.py +211 -0
- epi_cli/debug.py +107 -0
- epi_cli/keys.py +4 -0
- epi_cli/ls.py +5 -1
- epi_cli/main.py +15 -1
- epi_cli/record.py +4 -0
- epi_cli/run.py +12 -4
- epi_cli/verify.py +4 -0
- epi_cli/view.py +4 -0
- epi_core/__init__.py +5 -1
- epi_core/container.py +68 -55
- epi_core/redactor.py +4 -0
- epi_core/schemas.py +6 -2
- epi_core/serialize.py +4 -0
- epi_core/storage.py +186 -0
- epi_core/trust.py +4 -0
- epi_recorder/__init__.py +5 -1
- epi_recorder/api.py +28 -2
- epi_recorder/async_api.py +151 -0
- epi_recorder/bootstrap.py +4 -0
- epi_recorder/environment.py +4 -0
- epi_recorder/patcher.py +143 -14
- epi_recorder/test_import.py +2 -0
- epi_recorder/test_script.py +2 -0
- epi_recorder-2.2.0.dist-info/METADATA +162 -0
- epi_recorder-2.2.0.dist-info/RECORD +38 -0
- {epi_recorder-2.1.2.dist-info → epi_recorder-2.2.0.dist-info}/WHEEL +1 -1
- {epi_recorder-2.1.2.dist-info → epi_recorder-2.2.0.dist-info}/licenses/LICENSE +4 -29
- {epi_recorder-2.1.2.dist-info → epi_recorder-2.2.0.dist-info}/top_level.txt +1 -0
- epi_viewer_static/app.js +38 -7
- epi_viewer_static/crypto.js +3 -0
- epi_viewer_static/index.html +4 -2
- epi_viewer_static/viewer_lite.css +3 -1
- epi_postinstall.py +0 -197
- epi_recorder-2.1.2.dist-info/METADATA +0 -574
- epi_recorder-2.1.2.dist-info/RECORD +0 -33
- {epi_recorder-2.1.2.dist-info → epi_recorder-2.2.0.dist-info}/entry_points.txt +0 -0
epi_viewer_static/app.js
CHANGED
|
@@ -37,18 +37,20 @@ async function renderTrustBadge(manifest) {
|
|
|
37
37
|
`;
|
|
38
38
|
|
|
39
39
|
// Check verification logic availability
|
|
40
|
+
const hasSignature = manifest.signature && manifest.signature !== "null" && manifest.signature.trim() !== "";
|
|
41
|
+
|
|
40
42
|
if (typeof window.verifyManifestSignature !== 'function') {
|
|
41
|
-
renderBadgeResult(false, 'Missing crypto lib',
|
|
43
|
+
renderBadgeResult(false, 'Missing crypto lib', hasSignature);
|
|
42
44
|
return;
|
|
43
45
|
}
|
|
44
46
|
|
|
45
47
|
try {
|
|
46
48
|
const result = await window.verifyManifestSignature(manifest);
|
|
47
49
|
console.log("Verification Result:", result);
|
|
48
|
-
renderBadgeResult(result.valid, result.reason,
|
|
50
|
+
renderBadgeResult(result.valid, result.reason, hasSignature);
|
|
49
51
|
} catch (e) {
|
|
50
52
|
console.error("Verification error:", e);
|
|
51
|
-
renderBadgeResult(false, e.message,
|
|
53
|
+
renderBadgeResult(false, e.message, hasSignature);
|
|
52
54
|
}
|
|
53
55
|
}
|
|
54
56
|
|
|
@@ -211,7 +213,12 @@ function renderStep(step) {
|
|
|
211
213
|
</span>
|
|
212
214
|
<span class="text-sm font-medium text-gray-900">${kind}</span>
|
|
213
215
|
</div>
|
|
214
|
-
<
|
|
216
|
+
<div class="flex items-center space-x-2">
|
|
217
|
+
<span class="text-xs text-gray-500">${time}</span>
|
|
218
|
+
<button onclick='copyStepData(${JSON.stringify(JSON.stringify(content))})' class="text-gray-400 hover:text-blue-600 transition-colors" title="Copy Raw JSON">
|
|
219
|
+
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 5H6a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2v-1M8 5a2 2 0 002 2h2a2 2 0 002-2M8 5a2 2 0 012-2h2a2 2 0 012 2m0 0h2a2 2 0 012 2v3m2 4H10m0 0l3-3m-3 3l3 3"></path></svg>
|
|
220
|
+
</button>
|
|
221
|
+
</div>
|
|
215
222
|
`;
|
|
216
223
|
wrapper.appendChild(header);
|
|
217
224
|
|
|
@@ -283,7 +290,7 @@ function renderLLMResponse(content) {
|
|
|
283
290
|
html += `
|
|
284
291
|
<div class="chat-bubble mr-auto bg-green-100 text-green-900 rounded-lg px-4 py-2 text-sm">
|
|
285
292
|
<div class="text-xs font-medium mb-1 uppercase">Assistant</div>
|
|
286
|
-
<div class="whitespace-pre-wrap">${
|
|
293
|
+
<div class="whitespace-pre-wrap">${formatMessageContent(choice.message.content)}</div>
|
|
287
294
|
${choice.finish_reason ? `<div class="text-xs text-green-700 mt-2">• ${choice.finish_reason}</div>` : ''}
|
|
288
295
|
</div>
|
|
289
296
|
`;
|
|
@@ -296,7 +303,7 @@ function renderLLMResponse(content) {
|
|
|
296
303
|
html += `
|
|
297
304
|
<div class="mt-3 text-xs text-gray-600 flex items-center space-x-4">
|
|
298
305
|
<span>📊 ${content.usage.total_tokens} tokens</span>
|
|
299
|
-
|
|
306
|
+
${content.latency_seconds ? `<span>⚡ ${content.latency_seconds}s</span>` : ''}
|
|
300
307
|
</div>
|
|
301
308
|
`;
|
|
302
309
|
}
|
|
@@ -348,6 +355,28 @@ function renderTimeline(steps) {
|
|
|
348
355
|
}
|
|
349
356
|
}
|
|
350
357
|
|
|
358
|
+
// Helper: Format message content with bolding
|
|
359
|
+
function formatMessageContent(text) {
|
|
360
|
+
if (!text) return '';
|
|
361
|
+
// Escape HTML first
|
|
362
|
+
let escaped = escapeHTML(text);
|
|
363
|
+
// Apply bold formatting for **text**
|
|
364
|
+
return escaped.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>');
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
// Helper: Copy to clipboard
|
|
368
|
+
window.copyStepData = function (dataStr) {
|
|
369
|
+
try {
|
|
370
|
+
const data = JSON.parse(dataStr); // It was doubly stringified
|
|
371
|
+
navigator.clipboard.writeText(JSON.stringify(data, null, 2)).then(() => {
|
|
372
|
+
// Visual feedback could be added here
|
|
373
|
+
console.log('Copied to clipboard');
|
|
374
|
+
});
|
|
375
|
+
} catch (e) {
|
|
376
|
+
console.error('Copy failed', e);
|
|
377
|
+
}
|
|
378
|
+
};
|
|
379
|
+
|
|
351
380
|
// Initialize viewer
|
|
352
381
|
async function init() {
|
|
353
382
|
const data = loadEPIData();
|
|
@@ -374,4 +403,6 @@ if (document.readyState === 'loading') {
|
|
|
374
403
|
document.addEventListener('DOMContentLoaded', init);
|
|
375
404
|
} else {
|
|
376
405
|
init();
|
|
377
|
-
}
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
|
epi_viewer_static/crypto.js
CHANGED
epi_viewer_static/index.html
CHANGED
|
@@ -94,7 +94,7 @@
|
|
|
94
94
|
<!-- Footer -->
|
|
95
95
|
<footer class="mt-12 bg-white border-t border-gray-200">
|
|
96
96
|
<div class="max-w-7xl mx-auto px-4 py-6 sm:px-6 lg:px-8">
|
|
97
|
-
EPI v2.
|
|
97
|
+
EPI v2.2.0 | <span class="font-mono">application/epi+zip</span>
|
|
98
98
|
</div>
|
|
99
99
|
</footer>
|
|
100
100
|
</div>
|
|
@@ -102,4 +102,6 @@
|
|
|
102
102
|
<script src="app.js"></script>
|
|
103
103
|
</body>
|
|
104
104
|
|
|
105
|
-
</html>
|
|
105
|
+
</html>
|
|
106
|
+
|
|
107
|
+
|
epi_postinstall.py
DELETED
|
@@ -1,197 +0,0 @@
|
|
|
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()
|