@formigio/fazemos-cli 0.2.1 → 0.2.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,533 @@
1
+ import { createServer } from 'http';
2
+ import { getEnv, getToken, getActiveOrgId } from './config.js';
3
+ import { refreshSession } from './auth.js';
4
+ async function proxyApi(path, method = 'GET', body) {
5
+ const env = getEnv();
6
+ let token = getToken();
7
+ if (!token) {
8
+ token = await refreshSession();
9
+ if (!token)
10
+ throw new Error('Session expired');
11
+ }
12
+ const headers = { 'Content-Type': 'application/json' };
13
+ if (token)
14
+ headers['Authorization'] = `Bearer ${token}`;
15
+ const orgId = getActiveOrgId();
16
+ if (orgId)
17
+ headers['X-Org-Id'] = orgId;
18
+ const options = { method, headers };
19
+ if (body && method !== 'GET')
20
+ options.body = JSON.stringify(body);
21
+ const res = await fetch(`${env.apiUrl}${path}`, options);
22
+ const text = await res.text();
23
+ try {
24
+ return JSON.parse(text);
25
+ }
26
+ catch {
27
+ return text;
28
+ }
29
+ }
30
+ function readBody(req) {
31
+ return new Promise((resolve, reject) => {
32
+ const chunks = [];
33
+ req.on('data', (c) => chunks.push(c));
34
+ req.on('end', () => resolve(Buffer.concat(chunks).toString()));
35
+ req.on('error', reject);
36
+ });
37
+ }
38
+ export async function startMonitor(port) {
39
+ const server = createServer(async (req, res) => {
40
+ const url = new URL(req.url || '/', `http://localhost:${port}`);
41
+ // API proxy
42
+ if (url.pathname.startsWith('/api/')) {
43
+ try {
44
+ let body;
45
+ if (req.method !== 'GET' && req.method !== 'HEAD') {
46
+ const raw = await readBody(req);
47
+ if (raw)
48
+ body = JSON.parse(raw);
49
+ }
50
+ const data = await proxyApi(url.pathname + url.search, req.method || 'GET', body);
51
+ res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
52
+ res.end(JSON.stringify(data));
53
+ }
54
+ catch (err) {
55
+ res.writeHead(500, { 'Content-Type': 'application/json' });
56
+ res.end(JSON.stringify({ error: err.message }));
57
+ }
58
+ return;
59
+ }
60
+ // Serve the SPA
61
+ res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
62
+ res.end(MONITOR_HTML);
63
+ });
64
+ server.listen(port, () => {
65
+ // printed by caller
66
+ });
67
+ return server;
68
+ }
69
+ const MONITOR_HTML = `<!DOCTYPE html>
70
+ <html lang="en">
71
+ <head>
72
+ <meta charset="utf-8">
73
+ <meta name="viewport" content="width=device-width, initial-scale=1">
74
+ <title>Fazemos Execution Monitor</title>
75
+ <style>
76
+ :root {
77
+ --bg: #0d1117; --surface: #161b22; --border: #30363d;
78
+ --text: #e6edf3; --text-dim: #8b949e; --text-muted: #484f58;
79
+ --green: #3fb950; --yellow: #d29922; --red: #f85149; --blue: #58a6ff; --cyan: #39d353;
80
+ --font: -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif;
81
+ --mono: 'SF Mono', 'Fira Code', 'Fira Mono', Menlo, Consolas, monospace;
82
+ }
83
+ * { margin: 0; padding: 0; box-sizing: border-box; }
84
+ body { background: var(--bg); color: var(--text); font-family: var(--font); font-size: 14px; }
85
+
86
+ .app { display: flex; height: 100vh; }
87
+
88
+ /* Sidebar */
89
+ .sidebar { width: 360px; min-width: 320px; border-right: 1px solid var(--border); display: flex; flex-direction: column; }
90
+ .sidebar-header { padding: 16px; border-bottom: 1px solid var(--border); display: flex; align-items: center; gap: 10px; }
91
+ .sidebar-header h1 { font-size: 16px; font-weight: 600; }
92
+ .sidebar-header .logo { color: var(--cyan); }
93
+ .filters { padding: 10px 16px; border-bottom: 1px solid var(--border); display: flex; gap: 6px; flex-wrap: wrap; }
94
+ .filters button {
95
+ background: var(--surface); border: 1px solid var(--border); color: var(--text-dim);
96
+ padding: 4px 10px; border-radius: 20px; font-size: 12px; cursor: pointer; transition: all 0.15s;
97
+ }
98
+ .filters button:hover { border-color: var(--text-dim); color: var(--text); }
99
+ .filters button.active { background: var(--blue); border-color: var(--blue); color: #fff; }
100
+ .exec-list { flex: 1; overflow-y: auto; }
101
+ .exec-item {
102
+ padding: 12px 16px; border-bottom: 1px solid var(--border); cursor: pointer; transition: background 0.1s;
103
+ display: flex; flex-direction: column; gap: 4px;
104
+ }
105
+ .exec-item:hover { background: var(--surface); }
106
+ .exec-item.selected { background: #1f2937; border-left: 3px solid var(--blue); }
107
+ .exec-item .row { display: flex; align-items: center; gap: 8px; }
108
+ .exec-item .agent { font-weight: 600; font-size: 13px; }
109
+ .exec-item .id { font-family: var(--mono); font-size: 11px; color: var(--text-muted); }
110
+ .exec-item .meta { font-size: 12px; color: var(--text-dim); }
111
+
112
+ .status-badge {
113
+ display: inline-flex; align-items: center; gap: 4px; padding: 2px 8px;
114
+ border-radius: 12px; font-size: 11px; font-weight: 600; text-transform: uppercase;
115
+ }
116
+ .status-pending { background: #1c1e26; color: var(--text-dim); }
117
+ .status-running { background: #2a1f00; color: var(--yellow); }
118
+ .status-completed { background: #0d2818; color: var(--green); }
119
+ .status-failed { background: #2d0f0f; color: var(--red); }
120
+ .status-cancelled { background: #1c1e26; color: var(--text-muted); }
121
+
122
+ .pulse { display: inline-block; width: 6px; height: 6px; border-radius: 50%; }
123
+ .pulse-running { background: var(--yellow); animation: pulse 1.5s ease-in-out infinite; }
124
+ .pulse-pending { background: var(--text-dim); }
125
+ .pulse-completed { background: var(--green); }
126
+ .pulse-failed { background: var(--red); }
127
+
128
+ @keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.3; } }
129
+
130
+ /* Main panel */
131
+ .main { flex: 1; display: flex; flex-direction: column; overflow: hidden; }
132
+ .main-empty { display: flex; align-items: center; justify-content: center; flex: 1; color: var(--text-dim); font-size: 15px; }
133
+
134
+ .detail-header { padding: 16px 20px; border-bottom: 1px solid var(--border); }
135
+ .detail-header h2 { font-size: 18px; margin-bottom: 8px; }
136
+ .detail-meta { display: flex; gap: 20px; flex-wrap: wrap; }
137
+ .detail-meta .item { display: flex; flex-direction: column; gap: 2px; }
138
+ .detail-meta .label { font-size: 11px; color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.5px; }
139
+ .detail-meta .value { font-size: 13px; color: var(--text); }
140
+ .detail-meta .value.mono { font-family: var(--mono); }
141
+
142
+ .tabs { display: flex; border-bottom: 1px solid var(--border); padding: 0 20px; }
143
+ .tabs button {
144
+ background: none; border: none; border-bottom: 2px solid transparent;
145
+ color: var(--text-dim); padding: 10px 16px; cursor: pointer; font-size: 13px; font-weight: 500;
146
+ }
147
+ .tabs button:hover { color: var(--text); }
148
+ .tabs button.active { color: var(--blue); border-bottom-color: var(--blue); }
149
+
150
+ .tab-content { flex: 1; overflow: hidden; display: flex; flex-direction: column; }
151
+
152
+ /* Logs panel */
153
+ .logs-container {
154
+ flex: 1; overflow-y: auto; padding: 12px 20px; font-family: var(--mono); font-size: 12px;
155
+ line-height: 1.7; white-space: pre-wrap; word-break: break-all; background: var(--bg);
156
+ }
157
+ .logs-container::-webkit-scrollbar { width: 8px; }
158
+ .logs-container::-webkit-scrollbar-thumb { background: var(--border); border-radius: 4px; }
159
+ .log-line { padding: 1px 0; }
160
+ .log-time { color: var(--text-muted); margin-right: 8px; }
161
+ .log-empty { color: var(--text-dim); font-style: italic; padding: 20px; font-family: var(--font); }
162
+ .autoscroll-bar {
163
+ padding: 6px 20px; border-top: 1px solid var(--border); display: flex; align-items: center;
164
+ justify-content: space-between; font-size: 12px; color: var(--text-dim);
165
+ }
166
+ .autoscroll-bar label { display: flex; align-items: center; gap: 6px; cursor: pointer; }
167
+
168
+ /* Info panel */
169
+ .info-panel { flex: 1; overflow-y: auto; padding: 20px; }
170
+ .info-grid { display: grid; grid-template-columns: 140px 1fr; gap: 8px 16px; font-size: 13px; }
171
+ .info-grid .label { color: var(--text-muted); }
172
+ .info-grid .value { color: var(--text); word-break: break-all; }
173
+ .info-grid .value.mono { font-family: var(--mono); font-size: 12px; }
174
+
175
+ /* Events panel */
176
+ .events-list { flex: 1; overflow-y: auto; padding: 12px 20px; }
177
+ .event-row { display: flex; gap: 12px; padding: 6px 0; border-bottom: 1px solid var(--border); font-size: 12px; }
178
+ .event-time { color: var(--text-muted); font-family: var(--mono); white-space: nowrap; min-width: 140px; }
179
+ .event-type { font-weight: 600; color: var(--blue); min-width: 120px; }
180
+ .event-detail { color: var(--text-dim); font-family: var(--mono); font-size: 11px; }
181
+
182
+ /* Agents bar */
183
+ .agents-bar { padding: 10px 16px; border-bottom: 1px solid var(--border); display: flex; gap: 8px; overflow-x: auto; }
184
+ .agent-chip {
185
+ display: flex; align-items: center; gap: 6px; padding: 4px 10px;
186
+ background: var(--surface); border: 1px solid var(--border); border-radius: 16px;
187
+ font-size: 12px; white-space: nowrap;
188
+ }
189
+ .agent-dot { width: 8px; height: 8px; border-radius: 50%; }
190
+ .agent-dot.idle { background: var(--green); }
191
+ .agent-dot.busy { background: var(--yellow); }
192
+ .agent-dot.error { background: var(--red); }
193
+ .agent-dot.offline { background: var(--text-muted); }
194
+
195
+ /* Refresh indicator */
196
+ .refresh-indicator { font-size: 11px; color: var(--text-muted); padding: 8px 16px; text-align: center; }
197
+
198
+ /* Cancel button */
199
+ .btn-cancel {
200
+ background: none; border: 1px solid var(--red); color: var(--red); padding: 4px 12px;
201
+ border-radius: 6px; font-size: 12px; cursor: pointer; margin-left: auto;
202
+ }
203
+ .btn-cancel:hover { background: var(--red); color: #fff; }
204
+
205
+ /* Error/loading output */
206
+ .error-msg { color: var(--red); background: #2d0f0f; padding: 12px 16px; border-radius: 6px; margin: 12px 20px; font-size: 13px; }
207
+ </style>
208
+ </head>
209
+ <body>
210
+ <div class="app" id="app">
211
+ <div class="sidebar">
212
+ <div class="sidebar-header">
213
+ <span class="logo">◆</span>
214
+ <h1>Execution Monitor</h1>
215
+ </div>
216
+ <div class="agents-bar" id="agents-bar"></div>
217
+ <div class="filters" id="filters">
218
+ <button class="active" data-status="">All</button>
219
+ <button data-status="running">Running</button>
220
+ <button data-status="pending">Pending</button>
221
+ <button data-status="completed">Completed</button>
222
+ <button data-status="failed">Failed</button>
223
+ </div>
224
+ <div class="exec-list" id="exec-list"></div>
225
+ <div class="refresh-indicator" id="refresh-indicator">Loading...</div>
226
+ </div>
227
+ <div class="main" id="main-panel">
228
+ <div class="main-empty">Select an execution to view details</div>
229
+ </div>
230
+ </div>
231
+
232
+ <script>
233
+ (function() {
234
+ // State
235
+ let executions = [];
236
+ let agents = [];
237
+ let selectedId = null;
238
+ let selectedExec = null;
239
+ let selectedOutput = '';
240
+ let selectedEvents = [];
241
+ let statusFilter = '';
242
+ let activeTab = 'logs';
243
+ let autoScroll = true;
244
+ let refreshInterval = null;
245
+ let detailInterval = null;
246
+
247
+ const POLL_MS = 3000;
248
+ const LIST_POLL_MS = 5000;
249
+
250
+ // API
251
+ async function fetchJSON(url) {
252
+ const res = await fetch(url);
253
+ return res.json();
254
+ }
255
+
256
+ async function postJSON(url, body) {
257
+ const res = await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) });
258
+ return res.json();
259
+ }
260
+
261
+ // Formatters
262
+ function shortId(id) { return id ? id.slice(0, 8) : ''; }
263
+ function fmtTime(ts) { return ts ? new Date(ts).toLocaleString() : '—'; }
264
+ function fmtDuration(ms) {
265
+ if (!ms) return '—';
266
+ if (ms < 1000) return ms + 'ms';
267
+ if (ms < 60000) return (ms / 1000).toFixed(1) + 's';
268
+ return (ms / 60000).toFixed(1) + 'm';
269
+ }
270
+ function fmtCost(usd) { return usd ? '$' + parseFloat(usd).toFixed(4) : '—'; }
271
+ function escHtml(s) { const d = document.createElement('div'); d.textContent = s; return d.innerHTML; }
272
+
273
+ // Load executions list
274
+ async function loadExecutions() {
275
+ try {
276
+ const qs = statusFilter ? '?status=' + statusFilter : '';
277
+ const data = await fetchJSON('/api/executions' + qs);
278
+ executions = data.executions || [];
279
+ renderList();
280
+ document.getElementById('refresh-indicator').textContent =
281
+ 'Updated ' + new Date().toLocaleTimeString() + ' · polling every ' + (LIST_POLL_MS/1000) + 's';
282
+ } catch (err) {
283
+ document.getElementById('refresh-indicator').textContent = 'Error: ' + err.message;
284
+ }
285
+ }
286
+
287
+ // Load agents
288
+ async function loadAgents() {
289
+ try {
290
+ const data = await fetchJSON('/api/agents');
291
+ agents = data.agents || [];
292
+ renderAgents();
293
+ } catch (err) { /* ignore */ }
294
+ }
295
+
296
+ // Load execution detail
297
+ async function loadDetail(id) {
298
+ try {
299
+ const data = await fetchJSON('/api/executions/' + id);
300
+ selectedExec = data.execution;
301
+ selectedOutput = selectedExec.output_text || '';
302
+ renderDetail();
303
+ } catch (err) {
304
+ document.getElementById('main-panel').innerHTML = '<div class="error-msg">Failed to load: ' + escHtml(err.message) + '</div>';
305
+ }
306
+ }
307
+
308
+ // Load events for execution's agent
309
+ async function loadEvents(agentId, execId) {
310
+ try {
311
+ let url = '/api/agents/' + agentId + '/events?limit=50';
312
+ if (execId) url += '&execution_id=' + execId;
313
+ const data = await fetchJSON(url);
314
+ selectedEvents = data.events || [];
315
+ if (activeTab === 'events') renderDetail();
316
+ } catch (err) { /* ignore */ }
317
+ }
318
+
319
+ // Render agents bar
320
+ function renderAgents() {
321
+ const bar = document.getElementById('agents-bar');
322
+ if (!agents.length) { bar.innerHTML = '<span style="color:var(--text-dim);font-size:12px">No agents</span>'; return; }
323
+ bar.innerHTML = agents.map(a => {
324
+ const st = a.agent_status || 'offline';
325
+ const runs = a.total_executions ? ' (' + a.total_executions + ')' : '';
326
+ return '<div class="agent-chip"><div class="agent-dot ' + st + '"></div>' + escHtml(a.display_name) + runs + '</div>';
327
+ }).join('');
328
+ }
329
+
330
+ // Render execution list
331
+ function renderList() {
332
+ const list = document.getElementById('exec-list');
333
+ if (!executions.length) {
334
+ list.innerHTML = '<div style="padding:20px;color:var(--text-dim);text-align:center">No executions found</div>';
335
+ return;
336
+ }
337
+ list.innerHTML = executions.map(e => {
338
+ const sel = e.id === selectedId ? ' selected' : '';
339
+ const st = e.status || 'pending';
340
+ const time = e.started_at || e.queued_at || e.created_at;
341
+ const cost = e.cost_usd ? ' · ' + fmtCost(e.cost_usd) : '';
342
+ const dur = e.duration_ms ? ' · ' + fmtDuration(e.duration_ms) : '';
343
+ return '<div class="exec-item' + sel + '" data-id="' + e.id + '">'
344
+ + '<div class="row"><span class="status-badge status-' + st + '"><span class="pulse pulse-' + st + '"></span>' + st + '</span>'
345
+ + '<span class="agent">' + escHtml(e.agent_name || 'unknown') + '</span></div>'
346
+ + '<div class="row"><span class="meta">' + escHtml(e.source_type || '') + cost + dur + '</span></div>'
347
+ + '<div class="row"><span class="id">' + shortId(e.id) + '</span>'
348
+ + '<span class="meta" style="margin-left:auto">' + fmtTime(time) + '</span></div>'
349
+ + '</div>';
350
+ }).join('');
351
+
352
+ list.querySelectorAll('.exec-item').forEach(el => {
353
+ el.addEventListener('click', () => selectExecution(el.dataset.id));
354
+ });
355
+ }
356
+
357
+ // Select execution
358
+ function selectExecution(id) {
359
+ selectedId = id;
360
+ selectedExec = null;
361
+ selectedOutput = '';
362
+ selectedEvents = [];
363
+ activeTab = 'logs';
364
+ renderList();
365
+ loadDetail(id);
366
+ // Start polling detail
367
+ if (detailInterval) clearInterval(detailInterval);
368
+ detailInterval = setInterval(() => {
369
+ if (selectedId) loadDetail(selectedId);
370
+ }, POLL_MS);
371
+ }
372
+
373
+ // Render detail panel
374
+ function renderDetail() {
375
+ const panel = document.getElementById('main-panel');
376
+ if (!selectedExec) { panel.innerHTML = '<div class="main-empty">Loading...</div>'; return; }
377
+ const e = selectedExec;
378
+ const st = e.status || 'pending';
379
+ const isRunning = st === 'running' || st === 'pending';
380
+
381
+ let cancelBtn = '';
382
+ if (isRunning) {
383
+ cancelBtn = '<button class="btn-cancel" id="btn-cancel">Cancel</button>';
384
+ }
385
+
386
+ let tabContent = '';
387
+ if (activeTab === 'logs') {
388
+ tabContent = renderLogsTab();
389
+ } else if (activeTab === 'info') {
390
+ tabContent = renderInfoTab();
391
+ } else if (activeTab === 'events') {
392
+ tabContent = renderEventsTab();
393
+ }
394
+
395
+ panel.innerHTML =
396
+ '<div class="detail-header">'
397
+ + '<div style="display:flex;align-items:center;gap:12px">'
398
+ + '<h2>' + escHtml(e.agent_name || 'Execution') + '</h2>'
399
+ + '<span class="status-badge status-' + st + '"><span class="pulse pulse-' + st + '"></span>' + st + '</span>'
400
+ + cancelBtn
401
+ + '</div>'
402
+ + '<div class="detail-meta" style="margin-top:10px">'
403
+ + metaItem('ID', shortId(e.id), true) + metaItem('Source', e.source_type + ' ' + shortId(e.source_id))
404
+ + metaItem('Cost', fmtCost(e.cost_usd)) + metaItem('Duration', fmtDuration(e.duration_ms))
405
+ + metaItem('Tokens', (e.input_tokens || 0) + ' in / ' + (e.output_tokens || 0) + ' out')
406
+ + '</div></div>'
407
+ + '<div class="tabs" id="tabs">'
408
+ + tabBtn('logs', 'Live Logs') + tabBtn('info', 'Details') + tabBtn('events', 'Events')
409
+ + '</div>'
410
+ + '<div class="tab-content">' + tabContent + '</div>';
411
+
412
+ // Tab listeners
413
+ panel.querySelectorAll('.tabs button').forEach(btn => {
414
+ btn.addEventListener('click', () => {
415
+ activeTab = btn.dataset.tab;
416
+ if (activeTab === 'events' && e.agent_member_id) loadEvents(e.agent_member_id, e.id);
417
+ renderDetail();
418
+ });
419
+ });
420
+
421
+ // Cancel listener
422
+ const cancelEl = document.getElementById('btn-cancel');
423
+ if (cancelEl) {
424
+ cancelEl.addEventListener('click', async () => {
425
+ try {
426
+ await postJSON('/api/executions/' + e.id + '/cancel', {});
427
+ loadDetail(e.id);
428
+ loadExecutions();
429
+ } catch (err) { alert('Cancel failed: ' + err.message); }
430
+ });
431
+ }
432
+
433
+ // Auto-scroll logs
434
+ if (activeTab === 'logs' && autoScroll) {
435
+ const lc = document.getElementById('logs-container');
436
+ if (lc) lc.scrollTop = lc.scrollHeight;
437
+ }
438
+
439
+ // Auto-scroll checkbox
440
+ const asCb = document.getElementById('autoscroll-cb');
441
+ if (asCb) {
442
+ asCb.addEventListener('change', (ev) => { autoScroll = ev.target.checked; });
443
+ }
444
+ }
445
+
446
+ function metaItem(label, value, mono) {
447
+ return '<div class="item"><span class="label">' + label + '</span><span class="value' + (mono ? ' mono' : '') + '">' + escHtml(String(value || '—')) + '</span></div>';
448
+ }
449
+
450
+ function tabBtn(name, label) {
451
+ return '<button data-tab="' + name + '"' + (activeTab === name ? ' class="active"' : '') + '>' + label + '</button>';
452
+ }
453
+
454
+ function renderLogsTab() {
455
+ if (!selectedOutput) {
456
+ const st = selectedExec?.status;
457
+ if (st === 'pending') return '<div class="log-empty">Execution is pending — logs will appear when it starts running.</div>';
458
+ if (st === 'running') return '<div class="log-empty">Waiting for output...</div>';
459
+ return '<div class="log-empty">No output recorded for this execution.</div>';
460
+ }
461
+ const lines = selectedOutput.split('\\n');
462
+ const html = lines.map((line, i) => {
463
+ return '<div class="log-line"><span class="log-time">' + String(i + 1).padStart(4, ' ') + '</span>' + escHtml(line) + '</div>';
464
+ }).join('');
465
+ return '<div class="logs-container" id="logs-container">' + html + '</div>'
466
+ + '<div class="autoscroll-bar">'
467
+ + '<label><input type="checkbox" id="autoscroll-cb"' + (autoScroll ? ' checked' : '') + '> Auto-scroll</label>'
468
+ + '<span>' + lines.length + ' lines</span>'
469
+ + '</div>';
470
+ }
471
+
472
+ function renderInfoTab() {
473
+ const e = selectedExec;
474
+ const rows = [
475
+ ['Execution ID', e.id, true],
476
+ ['Agent', e.agent_name],
477
+ ['Agent Member ID', e.agent_member_id, true],
478
+ ['Source Type', e.source_type],
479
+ ['Source ID', e.source_id, true],
480
+ ['Status', e.status],
481
+ ['Cost (USD)', fmtCost(e.cost_usd)],
482
+ ['Input Tokens', e.input_tokens || '—'],
483
+ ['Output Tokens', e.output_tokens || '—'],
484
+ ['Duration', fmtDuration(e.duration_ms)],
485
+ ['Fargate Task ARN', e.fargate_task_arn || '—', true],
486
+ ['Queued', fmtTime(e.queued_at)],
487
+ ['Started', fmtTime(e.started_at)],
488
+ ['Completed', fmtTime(e.completed_at)],
489
+ ];
490
+ if (e.error) rows.push(['Error', e.error]);
491
+ if (e.context) rows.push(['Context', JSON.stringify(e.context, null, 2), true]);
492
+ const html = rows.map(([label, value, mono]) =>
493
+ '<div class="label">' + escHtml(label) + '</div><div class="value' + (mono ? ' mono' : '') + '">' + escHtml(String(value ?? '—')) + '</div>'
494
+ ).join('');
495
+ return '<div class="info-panel"><div class="info-grid">' + html + '</div></div>';
496
+ }
497
+
498
+ function renderEventsTab() {
499
+ if (!selectedEvents.length) {
500
+ return '<div class="log-empty">No events found for this execution.</div>';
501
+ }
502
+ const html = selectedEvents.map(ev => {
503
+ const det = ev.details && Object.keys(ev.details).length > 0 ? JSON.stringify(ev.details) : '';
504
+ return '<div class="event-row">'
505
+ + '<span class="event-time">' + fmtTime(ev.created_at) + '</span>'
506
+ + '<span class="event-type">' + escHtml(ev.event_type) + '</span>'
507
+ + (det ? '<span class="event-detail">' + escHtml(det) + '</span>' : '')
508
+ + '</div>';
509
+ }).join('');
510
+ return '<div class="events-list">' + html + '</div>';
511
+ }
512
+
513
+ // Filter buttons
514
+ document.getElementById('filters').addEventListener('click', (e) => {
515
+ if (e.target.tagName !== 'BUTTON') return;
516
+ statusFilter = e.target.dataset.status;
517
+ document.querySelectorAll('#filters button').forEach(b => b.classList.remove('active'));
518
+ e.target.classList.add('active');
519
+ loadExecutions();
520
+ });
521
+
522
+ // Initial load
523
+ loadExecutions();
524
+ loadAgents();
525
+
526
+ // Polling
527
+ refreshInterval = setInterval(loadExecutions, LIST_POLL_MS);
528
+ setInterval(loadAgents, 15000);
529
+ })();
530
+ </script>
531
+ </body>
532
+ </html>`;
533
+ //# sourceMappingURL=monitor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"monitor.js","sourceRoot":"","sources":["../src/monitor.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@formigio/fazemos-cli",
3
- "version": "0.2.1",
3
+ "version": "0.2.3",
4
4
  "description": "CLI for the Fazemos Team Accomplishment Platform",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -37,6 +37,7 @@
37
37
  },
38
38
  "devDependencies": {
39
39
  "@types/node": "^20.17.0",
40
+ "@vitest/coverage-v8": "^4.1.0",
40
41
  "tsx": "^4.19.2",
41
42
  "typescript": "^5.6.3",
42
43
  "vitest": "^4.1.0"