@veolab/discoverylab 1.4.3 → 1.6.3

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.
@@ -0,0 +1,181 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Flow Diagram</title>
7
+ <style>
8
+ * { margin: 0; padding: 0; box-sizing: border-box; }
9
+ body {
10
+ background: linear-gradient(160deg, #0f0f23, #1a1a3e);
11
+ color: #fff;
12
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
13
+ min-height: 100vh;
14
+ display: flex;
15
+ flex-direction: column;
16
+ }
17
+ .container {
18
+ flex: 1;
19
+ display: flex;
20
+ flex-direction: column;
21
+ padding: 24px;
22
+ max-width: 100%;
23
+ overflow: hidden;
24
+ }
25
+ .header { text-align: center; margin-bottom: 20px; flex-shrink: 0; }
26
+ .header h1 { font-size: 22px; font-weight: 700; margin-bottom: 4px; }
27
+ .header p { font-size: 13px; color: rgba(255,255,255,0.5); }
28
+ .flow {
29
+ flex: 1;
30
+ display: flex;
31
+ flex-direction: row;
32
+ align-items: center;
33
+ justify-content: center;
34
+ gap: 0;
35
+ padding: 10px 0;
36
+ min-height: 0;
37
+ }
38
+ .flow.vertical {
39
+ flex-direction: column;
40
+ overflow-y: auto;
41
+ overflow-x: hidden;
42
+ }
43
+ .step {
44
+ display: flex;
45
+ flex-direction: column;
46
+ align-items: center;
47
+ flex-shrink: 1;
48
+ min-width: 0;
49
+ max-width: 220px;
50
+ opacity: 0;
51
+ transform: translateY(20px);
52
+ animation: fadeInUp 0.5s forwards;
53
+ /* delay set dynamically via JS for correct sequential order */
54
+ }
55
+ @keyframes fadeInUp {
56
+ to { opacity: 1; transform: translateY(0); }
57
+ }
58
+ .step-badge {
59
+ width: 28px; height: 28px; border-radius: 50%;
60
+ background: #6366f1; color: #fff;
61
+ display: flex; align-items: center; justify-content: center;
62
+ font-size: 12px; font-weight: 700;
63
+ margin-bottom: 8px;
64
+ box-shadow: 0 3px 10px rgba(99,102,241,0.4);
65
+ flex-shrink: 0;
66
+ }
67
+ .step-screenshot {
68
+ width: 100%;
69
+ max-height: 55vh;
70
+ border-radius: 10px;
71
+ overflow: hidden;
72
+ box-shadow: 0 6px 20px rgba(0,0,0,0.3);
73
+ border: 1px solid rgba(255,255,255,0.1);
74
+ transition: transform 0.3s, box-shadow 0.3s;
75
+ flex-shrink: 1;
76
+ }
77
+ .step-screenshot:hover {
78
+ transform: scale(1.03);
79
+ box-shadow: 0 10px 30px rgba(99,102,241,0.25);
80
+ }
81
+ .step-screenshot img {
82
+ width: 100%;
83
+ height: auto;
84
+ display: block;
85
+ max-height: 55vh;
86
+ object-fit: contain;
87
+ }
88
+ .step-label {
89
+ font-size: 11px;
90
+ color: rgba(255,255,255,0.7);
91
+ margin-top: 8px;
92
+ text-align: center;
93
+ max-width: 100%;
94
+ overflow: hidden;
95
+ text-overflow: ellipsis;
96
+ white-space: nowrap;
97
+ flex-shrink: 0;
98
+ }
99
+ .arrow {
100
+ display: flex;
101
+ align-items: center;
102
+ justify-content: center;
103
+ flex-shrink: 0;
104
+ padding: 0 4px;
105
+ opacity: 0;
106
+ animation: fadeInArrow 0.3s forwards;
107
+ /* delay set dynamically via JS */
108
+ }
109
+ @keyframes fadeInArrow { to { opacity: 1; } }
110
+ .arrow svg {
111
+ width: 20px; height: 20px;
112
+ stroke: rgba(255,255,255,0.35);
113
+ fill: none;
114
+ stroke-width: 2;
115
+ }
116
+ .flow.vertical .arrow { transform: rotate(90deg); }
117
+ .footer {
118
+ text-align: center;
119
+ padding: 12px;
120
+ font-size: 10px;
121
+ color: rgba(255,255,255,0.25);
122
+ flex-shrink: 0;
123
+ }
124
+ </style>
125
+ </head>
126
+ <body>
127
+ <div class="container">
128
+ <div class="header">
129
+ <h1 id="vizTitle">App Flow</h1>
130
+ <p id="vizSubtitle"></p>
131
+ </div>
132
+ <div class="flow" id="flowContainer"></div>
133
+ <div class="footer" id="vizFooter">Generated by DiscoveryLab</div>
134
+ </div>
135
+ <script>
136
+ const DATA = window.__VISUALIZATION_DATA__ || {};
137
+ const steps = DATA.steps || [];
138
+ const direction = DATA.direction || 'horizontal';
139
+ const count = steps.length;
140
+
141
+ if (DATA.title) document.getElementById('vizTitle').textContent = DATA.title;
142
+ if (DATA.subtitle) document.getElementById('vizSubtitle').textContent = DATA.subtitle;
143
+
144
+ const container = document.getElementById('flowContainer');
145
+ if (direction === 'vertical') container.classList.add('vertical');
146
+
147
+ // Auto-size steps based on count and viewport
148
+ const maxStepWidth = Math.min(220, Math.floor((window.innerWidth - 80 - (count - 1) * 28) / count));
149
+
150
+ // Sequential animation: step1 → arrow1 → step2 → arrow2 → step3 ...
151
+ const baseDelay = 0.2; // seconds
152
+ const stepDuration = 0.3; // time between each element appearing
153
+
154
+ steps.forEach((step, i) => {
155
+ const elementIndex = i * 2; // step0=0, arrow0=1, step1=2, arrow1=3, step2=4...
156
+
157
+ if (i > 0) {
158
+ const arrowIndex = (i * 2) - 1;
159
+ const arrow = document.createElement('div');
160
+ arrow.className = 'arrow';
161
+ arrow.style.animationDelay = (baseDelay + arrowIndex * stepDuration) + 's';
162
+ arrow.innerHTML = '<svg viewBox="0 0 24 24"><path d="M5 12h14M12 5l7 7-7 7" stroke-linecap="round" stroke-linejoin="round"/></svg>';
163
+ container.appendChild(arrow);
164
+ }
165
+
166
+ const el = document.createElement('div');
167
+ el.className = 'step';
168
+ el.style.maxWidth = maxStepWidth + 'px';
169
+ el.style.animationDelay = (baseDelay + elementIndex * stepDuration) + 's';
170
+ el.innerHTML = `
171
+ <div class="step-badge">${step.number || i + 1}</div>
172
+ <div class="step-screenshot">
173
+ <img src="${step.imageUrl}" alt="Step ${i + 1}">
174
+ </div>
175
+ <div class="step-label">${step.label || ''}</div>
176
+ `;
177
+ container.appendChild(el);
178
+ });
179
+ </script>
180
+ </body>
181
+ </html>
@@ -0,0 +1,263 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Metrics Dashboard</title>
7
+ <style>
8
+ * { margin: 0; padding: 0; box-sizing: border-box; }
9
+ body {
10
+ background: #0f0f1a;
11
+ color: #fff;
12
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
13
+ min-height: 100vh;
14
+ padding: 24px;
15
+ }
16
+ .dashboard { max-width: 960px; margin: 0 auto; }
17
+ .header { margin-bottom: 24px; }
18
+ .header h1 { font-size: 22px; font-weight: 700; }
19
+ .header p { font-size: 13px; color: rgba(255,255,255,0.4); margin-top: 4px; }
20
+
21
+ /* Metrics cards */
22
+ .metrics-grid {
23
+ display: grid;
24
+ grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
25
+ gap: 12px;
26
+ margin-bottom: 24px;
27
+ }
28
+ .metric-card {
29
+ background: rgba(255,255,255,0.04);
30
+ border: 1px solid rgba(255,255,255,0.08);
31
+ border-radius: 12px;
32
+ padding: 16px;
33
+ opacity: 0;
34
+ transform: scale(0.95);
35
+ animation: popIn 0.4s forwards;
36
+ }
37
+ .metric-card:nth-child(1) { animation-delay: 0.1s; }
38
+ .metric-card:nth-child(2) { animation-delay: 0.2s; }
39
+ .metric-card:nth-child(3) { animation-delay: 0.3s; }
40
+ .metric-card:nth-child(4) { animation-delay: 0.4s; }
41
+ @keyframes popIn { to { opacity: 1; transform: scale(1); } }
42
+ .metric-value {
43
+ font-size: 32px; font-weight: 800;
44
+ background: linear-gradient(135deg, #6366f1, #a78bfa);
45
+ -webkit-background-clip: text; -webkit-text-fill-color: transparent;
46
+ background-clip: text;
47
+ }
48
+ .metric-label { font-size: 11px; color: rgba(255,255,255,0.5); margin-top: 2px; }
49
+
50
+ /* Two-column layout for charts + screens */
51
+ .content-grid {
52
+ display: grid;
53
+ grid-template-columns: 1fr 1fr;
54
+ gap: 16px;
55
+ margin-bottom: 24px;
56
+ }
57
+ @media (max-width: 700px) { .content-grid { grid-template-columns: 1fr; } }
58
+
59
+ .section-card {
60
+ background: rgba(255,255,255,0.03);
61
+ border: 1px solid rgba(255,255,255,0.06);
62
+ border-radius: 12px;
63
+ padding: 16px;
64
+ }
65
+ .section-title { font-size: 13px; font-weight: 600; margin-bottom: 12px; color: rgba(255,255,255,0.8); }
66
+
67
+ /* Bar chart */
68
+ .bar-chart { display: flex; flex-direction: column; gap: 6px; }
69
+ .bar-row {
70
+ display: grid; grid-template-columns: 90px 1fr 32px;
71
+ gap: 8px; align-items: center;
72
+ opacity: 0; animation: slideIn 0.5s forwards;
73
+ }
74
+ .bar-row:nth-child(1) { animation-delay: 0.5s; }
75
+ .bar-row:nth-child(2) { animation-delay: 0.6s; }
76
+ .bar-row:nth-child(3) { animation-delay: 0.7s; }
77
+ .bar-row:nth-child(4) { animation-delay: 0.8s; }
78
+ @keyframes slideIn { from { opacity: 0; transform: translateX(-16px); } to { opacity: 1; transform: translateX(0); } }
79
+ .bar-label { font-size: 10px; color: rgba(255,255,255,0.6); text-align: right; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
80
+ .bar-track { height: 20px; background: rgba(255,255,255,0.06); border-radius: 5px; overflow: hidden; }
81
+ .bar-fill { height: 100%; border-radius: 5px; background: linear-gradient(90deg, #6366f1, #818cf8); transition: width 1s ease; width: 0; }
82
+ .bar-value { font-size: 11px; color: rgba(255,255,255,0.7); font-weight: 600; }
83
+
84
+ /* Screen thumbnails strip */
85
+ .screens-strip {
86
+ display: flex;
87
+ gap: 8px;
88
+ overflow-x: auto;
89
+ padding: 4px 0;
90
+ }
91
+ .screen-thumb {
92
+ flex-shrink: 0;
93
+ width: 80px;
94
+ border-radius: 8px;
95
+ overflow: hidden;
96
+ border: 1px solid rgba(255,255,255,0.1);
97
+ box-shadow: 0 4px 12px rgba(0,0,0,0.3);
98
+ opacity: 0;
99
+ animation: thumbIn 0.4s forwards;
100
+ position: relative;
101
+ transition: transform 0.2s;
102
+ }
103
+ .screen-thumb:hover { transform: scale(1.08); }
104
+ .screen-thumb img { width: 100%; height: auto; display: block; }
105
+ .screen-thumb-label {
106
+ position: absolute; bottom: 0; left: 0; right: 0;
107
+ background: linear-gradient(transparent, rgba(0,0,0,0.8));
108
+ padding: 12px 4px 4px;
109
+ font-size: 8px; color: rgba(255,255,255,0.8);
110
+ text-align: center;
111
+ overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
112
+ }
113
+ .screen-thumb:nth-child(1) { animation-delay: 0.3s; }
114
+ .screen-thumb:nth-child(2) { animation-delay: 0.4s; }
115
+ .screen-thumb:nth-child(3) { animation-delay: 0.5s; }
116
+ .screen-thumb:nth-child(4) { animation-delay: 0.6s; }
117
+ .screen-thumb:nth-child(5) { animation-delay: 0.7s; }
118
+ .screen-thumb:nth-child(6) { animation-delay: 0.8s; }
119
+ @keyframes thumbIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } }
120
+
121
+ /* Flow steps with thumbnails */
122
+ .flow-steps { display: flex; flex-direction: column; gap: 8px; margin-top: 8px; }
123
+ .flow-step {
124
+ display: flex; align-items: center; gap: 10px;
125
+ padding: 8px 10px;
126
+ background: rgba(99,102,241,0.06);
127
+ border: 1px solid rgba(99,102,241,0.12);
128
+ border-radius: 10px;
129
+ opacity: 0;
130
+ animation: fadeIn 0.3s forwards;
131
+ }
132
+ @keyframes fadeIn { to { opacity: 1; } }
133
+ .flow-step-num {
134
+ width: 22px; height: 22px; border-radius: 50%;
135
+ background: #6366f1; color: #fff;
136
+ display: flex; align-items: center; justify-content: center;
137
+ font-size: 10px; font-weight: 700; flex-shrink: 0;
138
+ }
139
+ .flow-step-thumb {
140
+ width: 36px; height: 64px; border-radius: 4px;
141
+ overflow: hidden; flex-shrink: 0;
142
+ border: 1px solid rgba(255,255,255,0.1);
143
+ }
144
+ .flow-step-thumb img { width: 100%; height: 100%; object-fit: cover; }
145
+ .flow-step-label { font-size: 11px; color: rgba(255,255,255,0.7); flex: 1; }
146
+
147
+ .footer {
148
+ text-align: center; margin-top: 24px;
149
+ font-size: 10px; color: rgba(255,255,255,0.2);
150
+ }
151
+ </style>
152
+ </head>
153
+ <body>
154
+ <div class="dashboard">
155
+ <div class="header">
156
+ <h1 id="vizTitle">Analysis Dashboard</h1>
157
+ <p id="vizSubtitle"></p>
158
+ </div>
159
+
160
+ <div class="metrics-grid" id="metricsGrid"></div>
161
+
162
+ <!-- Screen thumbnails strip -->
163
+ <div class="section-card" style="margin-bottom: 16px;" id="screensSection">
164
+ <div class="section-title">Screens Captured</div>
165
+ <div class="screens-strip" id="screensStrip"></div>
166
+ </div>
167
+
168
+ <div class="content-grid">
169
+ <!-- Left: Bar chart -->
170
+ <div class="section-card">
171
+ <div class="section-title">UI Elements Distribution</div>
172
+ <div class="bar-chart" id="barChart"></div>
173
+ </div>
174
+
175
+ <!-- Right: Flow steps with thumbnails -->
176
+ <div class="section-card">
177
+ <div class="section-title">User Flow</div>
178
+ <div class="flow-steps" id="flowSteps"></div>
179
+ </div>
180
+ </div>
181
+
182
+ <div class="footer" id="vizFooter">Generated by DiscoveryLab</div>
183
+ </div>
184
+ <script>
185
+ const DATA = window.__VISUALIZATION_DATA__ || {};
186
+ const screens = DATA.screens || [];
187
+
188
+ if (DATA.title) document.getElementById('vizTitle').textContent = DATA.title;
189
+ if (DATA.subtitle) document.getElementById('vizSubtitle').textContent = DATA.subtitle;
190
+
191
+ // Metrics cards
192
+ const metrics = DATA.metrics || [
193
+ { value: DATA.screenCount || 0, label: 'Screens Analyzed' },
194
+ { value: DATA.flowSteps || 0, label: 'Flow Steps' },
195
+ { value: DATA.uiElements || 0, label: 'UI Elements' },
196
+ { value: DATA.platform || 'N/A', label: 'Platform' },
197
+ ];
198
+ const metricsGrid = document.getElementById('metricsGrid');
199
+ metrics.forEach(m => {
200
+ const card = document.createElement('div');
201
+ card.className = 'metric-card';
202
+ card.innerHTML = `
203
+ <div class="metric-value">${m.value}</div>
204
+ <div class="metric-label">${m.label}</div>
205
+ `;
206
+ metricsGrid.appendChild(card);
207
+ });
208
+
209
+ // Screen thumbnails strip
210
+ const screensStrip = document.getElementById('screensStrip');
211
+ const screensSection = document.getElementById('screensSection');
212
+ if (screens.length > 0) {
213
+ screens.forEach((screen, i) => {
214
+ const el = document.createElement('div');
215
+ el.className = 'screen-thumb';
216
+ el.style.animationDelay = (0.3 + i * 0.1) + 's';
217
+ el.innerHTML = `
218
+ <img src="${screen.imageUrl}" alt="Screen ${i + 1}">
219
+ <div class="screen-thumb-label">${screen.label || 'Screen ' + (i + 1)}</div>
220
+ `;
221
+ screensStrip.appendChild(el);
222
+ });
223
+ } else {
224
+ screensSection.style.display = 'none';
225
+ }
226
+
227
+ // Bar chart
228
+ const categories = DATA.categories || [];
229
+ const barChart = document.getElementById('barChart');
230
+ const maxVal = Math.max(...categories.map(c => c.count), 1);
231
+ categories.forEach(cat => {
232
+ const pct = Math.round((cat.count / maxVal) * 100);
233
+ const row = document.createElement('div');
234
+ row.className = 'bar-row';
235
+ row.innerHTML = `
236
+ <div class="bar-label" title="${cat.name}">${cat.name}</div>
237
+ <div class="bar-track"><div class="bar-fill" style="width: 0%"></div></div>
238
+ <div class="bar-value">${cat.count}</div>
239
+ `;
240
+ barChart.appendChild(row);
241
+ requestAnimationFrame(() => {
242
+ setTimeout(() => { row.querySelector('.bar-fill').style.width = pct + '%'; }, 800);
243
+ });
244
+ });
245
+
246
+ // Flow steps with thumbnails
247
+ const steps = DATA.steps || [];
248
+ const flowSteps = document.getElementById('flowSteps');
249
+ steps.forEach((step, i) => {
250
+ const screen = screens[i];
251
+ const el = document.createElement('div');
252
+ el.className = 'flow-step';
253
+ el.style.animationDelay = (0.8 + i * 0.1) + 's';
254
+ el.innerHTML = `
255
+ <div class="flow-step-num">${i + 1}</div>
256
+ ${screen ? `<div class="flow-step-thumb"><img src="${screen.imageUrl}" alt=""></div>` : ''}
257
+ <div class="flow-step-label">${step.label || step}</div>
258
+ `;
259
+ flowSteps.appendChild(el);
260
+ });
261
+ </script>
262
+ </body>
263
+ </html>
@@ -0,0 +1,116 @@
1
+ # ESVP Protocol Integration
2
+
3
+ DiscoveryLab includes a built-in client for the open [ESVP protocol](https://esvp.dev) by Entropy Lab — enabling reproducible mobile sessions, automated replay, and network-aware validation.
4
+
5
+ ## Configuration
6
+
7
+ | Mode | How |
8
+ |------|-----|
9
+ | **Remote** | Set `ESVP_BASE_URL=http://your-esvp-host:8787` |
10
+ | **Local** | Leave `ESVP_BASE_URL` unset — DiscoveryLab boots an embedded local runtime |
11
+ | **Dev (custom module)** | Set `DISCOVERYLAB_ESVP_LOCAL_MODULE=/path/to/esvp-server-reference/server.js` |
12
+
13
+ ## MCP Tools
14
+
15
+ | Tool | Description |
16
+ |------|-------------|
17
+ | `dlab.esvp.status` | Check control-plane health |
18
+ | `dlab.esvp.devices` | List available devices |
19
+ | `dlab.esvp.sessions.list` | List sessions |
20
+ | `dlab.esvp.session.create` | Create session (ios-sim, maestro-ios, adb) |
21
+ | `dlab.esvp.session.get` | Get session details |
22
+ | `dlab.esvp.session.inspect` | Inspect session state |
23
+ | `dlab.esvp.session.transcript` | Get session transcript |
24
+ | `dlab.esvp.session.artifacts.list` | List artifacts |
25
+ | `dlab.esvp.session.artifact.get` | Get specific artifact |
26
+ | `dlab.esvp.session.actions` | Run actions on session |
27
+ | `dlab.esvp.session.checkpoint` | Create checkpoint |
28
+ | `dlab.esvp.session.finish` | Finish session |
29
+ | `dlab.esvp.replay.run` | Replay recording |
30
+ | `dlab.esvp.replay.validate` | Validate replay |
31
+ | `dlab.esvp.session.network` | Get network data |
32
+ | `dlab.esvp.network.configure` | Configure network proxy |
33
+ | `dlab.esvp.network.trace.attach` | Attach network trace |
34
+ | `dlab.project.esvp.current` | Get current project ESVP state |
35
+ | `dlab.project.esvp.validate` | Validate project recording |
36
+ | `dlab.project.esvp.replay` | Replay project recording |
37
+ | `dlab.project.esvp.sync_network` | Sync network traces |
38
+ | `dlab.project.esvp.app_trace_bootstrap` | Bootstrap app tracing |
39
+
40
+ ## CLI Commands
41
+
42
+ ```bash
43
+ discoverylab esvp status
44
+ discoverylab esvp devices
45
+ discoverylab esvp sessions
46
+ discoverylab esvp create
47
+ discoverylab esvp get <sessionId>
48
+ discoverylab esvp inspect <sessionId>
49
+ discoverylab esvp transcript <sessionId>
50
+ discoverylab esvp artifacts <sessionId>
51
+ discoverylab esvp artifact <sessionId> <artifactPath>
52
+ discoverylab esvp actions <sessionId>
53
+ discoverylab esvp checkpoint <sessionId>
54
+ discoverylab esvp finish <sessionId>
55
+ discoverylab esvp replay-run <sessionId>
56
+ discoverylab esvp replay-validate <sessionId>
57
+ discoverylab esvp replay-consistency <sessionId>
58
+ discoverylab esvp network <sessionId>
59
+ discoverylab esvp network-configure <sessionId>
60
+ discoverylab esvp network-clear <sessionId>
61
+ discoverylab esvp trace-attach <sessionId>
62
+ ```
63
+
64
+ ## Network Proxy
65
+
66
+ DiscoveryLab defaults to `external-proxy` mode — the proxy runs locally and ESVP only persists traces.
67
+
68
+ ### Host Rules
69
+
70
+ | Setup | Proxy Address |
71
+ |-------|--------------|
72
+ | macOS + iOS Simulator | `127.0.0.1` |
73
+ | macOS/Linux + Android Emulator | `10.0.2.2` |
74
+ | Physical Android | Host LAN IP |
75
+
76
+ ### Environment Variables
77
+
78
+ | Variable | Purpose |
79
+ |----------|---------|
80
+ | `DISCOVERYLAB_NETWORK_PROXY_PORT` | Proxy port |
81
+ | `DISCOVERYLAB_NETWORK_PROXY_HOST` | Proxy host for device |
82
+ | `DISCOVERYLAB_NETWORK_PROXY_BIND_HOST` | Bind address (LAN) |
83
+ | `DISCOVERYLAB_NETWORK_PROXY_PROTOCOL` | Protocol (http/https) |
84
+ | `DISCOVERYLAB_NETWORK_PROXY_BYPASS` | Bypass patterns |
85
+ | `DISCOVERYLAB_NETWORK_PROXY_MAX_DURATION_MS` | Auto-finalize timeout (default: 15min) |
86
+
87
+ ### Safety
88
+
89
+ - Proxies auto-finalize after 15 minutes to prevent stale proxy state
90
+ - Settings UI has an emergency lock and "Disable Active Proxy Now" button
91
+ - Server shutdown auto-cleans active proxies
92
+
93
+ ## Example Prompts
94
+
95
+ ```
96
+ Check my ESVP control-plane health
97
+ Create an ios-sim ESVP session and take a screenshot
98
+ Replay this iOS recording with an external proxy
99
+ Configure a proxy on this session and attach the HTTP trace
100
+ ```
101
+
102
+ ## Programmatic Usage
103
+
104
+ ```ts
105
+ import { createESVPSession, runESVPActions } from '@veolab/discoverylab';
106
+
107
+ const created = await createESVPSession({
108
+ executor: 'ios-sim',
109
+ meta: { source: 'demo' },
110
+ });
111
+
112
+ await runESVPActions(created.session.id, {
113
+ actions: [{ name: 'screenshot' }],
114
+ finish: true,
115
+ });
116
+ ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@veolab/discoverylab",
3
- "version": "1.4.3",
3
+ "version": "1.6.3",
4
4
  "description": "AI-powered app testing & evidence generator - Claude Code Plugin",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -10,7 +10,7 @@
10
10
  },
11
11
  "scripts": {
12
12
  "dev": "tsx watch src/cli.ts serve",
13
- "build": "tsup src/index.ts src/cli.ts --format esm --dts --clean && cp src/web/index.html dist/index.html && node scripts/build-host-runtime.mjs --best-effort",
13
+ "build": "tsup src/index.ts src/cli.ts --format esm --dts --clean && cp src/web/index.html dist/index.html && mkdir -p dist/visualizations dist/export && cp src/core/visualizations/templates/*.html dist/visualizations/ && cp src/core/export/infographic-template.html dist/export/ && node scripts/build-host-runtime.mjs --best-effort",
14
14
  "build:host-runtime": "node scripts/build-host-runtime.mjs",
15
15
  "pack:local": "npm run build && npm run build:host-runtime && node scripts/verify-host-runtime-bundle.mjs && npm pack",
16
16
  "prepack": "npm run build && npm run build:host-runtime && node scripts/verify-host-runtime-bundle.mjs",
@@ -68,7 +68,7 @@
68
68
  "claude-plugin": {
69
69
  "name": "DiscoveryLab",
70
70
  "description": "AI-powered app testing & evidence generator",
71
- "version": "1.4.3",
71
+ "version": "1.6.3",
72
72
  "tools": [
73
73
  "dlab.capture.screen",
74
74
  "dlab.capture.emulator",