issue-flow 0.4.4 → 0.5.1
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.
- package/LICENSE +21 -0
- package/README.md +38 -1
- package/dist/{analyze-PDDIU7HD.js → analyze-IJ2SYXVM.js} +7 -9
- package/dist/{analyze-PDDIU7HD.js.map → analyze-IJ2SYXVM.js.map} +1 -1
- package/dist/{chunk-4OQ2ARLP.js → chunk-2XWXVWVD.js} +10 -2
- package/dist/chunk-2XWXVWVD.js.map +1 -0
- package/dist/{chunk-ZJB2IMPU.js → chunk-AY7UZAGX.js} +8 -10
- package/dist/{chunk-ZJB2IMPU.js.map → chunk-AY7UZAGX.js.map} +1 -1
- package/dist/chunk-FNLXZXWP.js +846 -0
- package/dist/chunk-FNLXZXWP.js.map +1 -0
- package/dist/{chunk-LTPXKMPE.js → chunk-GYR4YHEB.js} +100 -113
- package/dist/chunk-GYR4YHEB.js.map +1 -0
- package/dist/{chunk-65KDGHLM.js → chunk-MKQIP7YW.js} +7 -9
- package/dist/{chunk-65KDGHLM.js.map → chunk-MKQIP7YW.js.map} +1 -1
- package/dist/{chunk-CSSXTPWE.js → chunk-Q2TQCJDN.js} +2 -2
- package/dist/{chunk-SKTTLX7R.js → chunk-SI4ALORB.js} +7 -9
- package/dist/{chunk-SKTTLX7R.js.map → chunk-SI4ALORB.js.map} +1 -1
- package/dist/{chunk-OZVHOVDT.js → chunk-SLHZPB3C.js} +15 -8
- package/dist/chunk-SLHZPB3C.js.map +1 -0
- package/dist/chunk-TYOFZ6DD.js +240 -0
- package/dist/chunk-TYOFZ6DD.js.map +1 -0
- package/dist/{chunk-HPSU5GGE.js → chunk-XJJP4TSK.js} +7 -9
- package/dist/{chunk-HPSU5GGE.js.map → chunk-XJJP4TSK.js.map} +1 -1
- package/dist/cli.js +55 -18
- package/dist/cli.js.map +1 -1
- package/dist/execute-DALVYZIH.js +11 -0
- package/dist/{generate-HP4CPMJG.js → generate-5OWWNRIB.js} +4 -4
- package/dist/init-6AWQI3IE.js +9 -0
- package/dist/plan-RQXK2OCP.js +11 -0
- package/dist/pr-UU4H7ZZR.js +11 -0
- package/dist/prd-WB3QH6QF.js +11 -0
- package/dist/review-2WHYJG7M.js +11 -0
- package/dist/{run-A44XQ77Z.js → run-UCSNYSQO.js} +290 -26
- package/dist/run-UCSNYSQO.js.map +1 -0
- package/package.json +13 -3
- package/web/public/app.css +481 -0
- package/web/public/app.js +522 -0
- package/web/public/index.html +113 -0
- package/dist/chunk-4OQ2ARLP.js.map +0 -1
- package/dist/chunk-LTPXKMPE.js.map +0 -1
- package/dist/chunk-OZVHOVDT.js.map +0 -1
- package/dist/chunk-X5QCISNM.js +0 -180
- package/dist/chunk-X5QCISNM.js.map +0 -1
- package/dist/chunk-YAFHVFV5.js +0 -199
- package/dist/chunk-YAFHVFV5.js.map +0 -1
- package/dist/execute-WKYSGIGH.js +0 -11
- package/dist/init-SXFGOCML.js +0 -9
- package/dist/plan-JUOBYTCD.js +0 -12
- package/dist/pr-CYZYJZZV.js +0 -12
- package/dist/prd-PZOY3FCC.js +0 -12
- package/dist/review-MK5FTVUE.js +0 -12
- package/dist/run-A44XQ77Z.js.map +0 -1
- /package/dist/{chunk-CSSXTPWE.js.map → chunk-Q2TQCJDN.js.map} +0 -0
- /package/dist/{execute-WKYSGIGH.js.map → execute-DALVYZIH.js.map} +0 -0
- /package/dist/{generate-HP4CPMJG.js.map → generate-5OWWNRIB.js.map} +0 -0
- /package/dist/{init-SXFGOCML.js.map → init-6AWQI3IE.js.map} +0 -0
- /package/dist/{plan-JUOBYTCD.js.map → plan-RQXK2OCP.js.map} +0 -0
- /package/dist/{pr-CYZYJZZV.js.map → pr-UU4H7ZZR.js.map} +0 -0
- /package/dist/{prd-PZOY3FCC.js.map → prd-WB3QH6QF.js.map} +0 -0
- /package/dist/{review-MK5FTVUE.js.map → review-2WHYJG7M.js.map} +0 -0
|
@@ -0,0 +1,522 @@
|
|
|
1
|
+
// Interface do monitoramento web do issue-flow.
|
|
2
|
+
// JS puro, sem framework e sem recursos externos — funciona offline.
|
|
3
|
+
// Consome apenas GET api/status (ETag/304) e GET api/health; todo texto
|
|
4
|
+
// dinâmico entra via textContent (nunca innerHTML com dados do snapshot).
|
|
5
|
+
(() => {
|
|
6
|
+
'use strict';
|
|
7
|
+
|
|
8
|
+
const REFRESH_OPTIONS = [3, 5, 10, 30];
|
|
9
|
+
const PAUSED = 0;
|
|
10
|
+
const STORAGE_KEY = 'issue-flow:refresh-seconds';
|
|
11
|
+
const MAX_BACKOFF_MS = 60000;
|
|
12
|
+
const ALERT_PREVIEW = 3;
|
|
13
|
+
|
|
14
|
+
const STATUS_LABELS = {
|
|
15
|
+
idle: 'aguardando',
|
|
16
|
+
running: 'executando',
|
|
17
|
+
completed: 'concluído',
|
|
18
|
+
failed: 'falhou',
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const PHASE_ICONS = {
|
|
22
|
+
pending: '○',
|
|
23
|
+
running: '●',
|
|
24
|
+
completed: '✓',
|
|
25
|
+
failed: '✗',
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const els = {
|
|
29
|
+
banner: document.getElementById('banner-disconnected'),
|
|
30
|
+
issueLink: document.getElementById('issue-link'),
|
|
31
|
+
branchLine: document.getElementById('branch-line'),
|
|
32
|
+
statusBadge: document.getElementById('status-badge'),
|
|
33
|
+
elapsed: document.getElementById('elapsed'),
|
|
34
|
+
estimate: document.getElementById('estimate'),
|
|
35
|
+
refreshSelect: document.getElementById('refresh-select'),
|
|
36
|
+
alerts: document.getElementById('alerts'),
|
|
37
|
+
alertsBody: document.getElementById('alerts-body'),
|
|
38
|
+
progressBar: document.getElementById('progress-bar'),
|
|
39
|
+
progressPercent: document.getElementById('progress-percent'),
|
|
40
|
+
progressCounters: document.getElementById('progress-counters'),
|
|
41
|
+
now: document.getElementById('now'),
|
|
42
|
+
phases: document.getElementById('phases'),
|
|
43
|
+
nextSteps: document.getElementById('next-steps'),
|
|
44
|
+
stories: document.getElementById('stories'),
|
|
45
|
+
commits: document.getElementById('commits'),
|
|
46
|
+
pullRequests: document.getElementById('pull-requests'),
|
|
47
|
+
logFilter: document.getElementById('log-filter'),
|
|
48
|
+
logs: document.getElementById('logs'),
|
|
49
|
+
sessionMeta: document.getElementById('session-meta'),
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const state = {
|
|
53
|
+
snapshot: null,
|
|
54
|
+
etag: null,
|
|
55
|
+
refreshSeconds: 5,
|
|
56
|
+
failures: 0,
|
|
57
|
+
timer: null,
|
|
58
|
+
polling: false,
|
|
59
|
+
logFilter: 'all',
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
// ---- Utilitários ----------------------------------------------------------
|
|
63
|
+
|
|
64
|
+
function el(tag, className, text) {
|
|
65
|
+
const node = document.createElement(tag);
|
|
66
|
+
if (className) node.className = className;
|
|
67
|
+
if (text !== undefined) node.textContent = text;
|
|
68
|
+
return node;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function link(href, text, className) {
|
|
72
|
+
const node = el('a', className, text);
|
|
73
|
+
node.href = href;
|
|
74
|
+
node.target = '_blank';
|
|
75
|
+
node.rel = 'noopener';
|
|
76
|
+
return node;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function clear(node) {
|
|
80
|
+
while (node.firstChild) node.removeChild(node.firstChild);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function parseIso(iso) {
|
|
84
|
+
if (!iso) return null;
|
|
85
|
+
const ms = Date.parse(iso);
|
|
86
|
+
return Number.isNaN(ms) ? null : ms;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function formatDuration(totalSeconds) {
|
|
90
|
+
if (totalSeconds === null || totalSeconds === undefined || totalSeconds < 0) return '—';
|
|
91
|
+
const seconds = Math.round(totalSeconds);
|
|
92
|
+
if (seconds < 60) return seconds + 's';
|
|
93
|
+
const minutes = Math.floor(seconds / 60);
|
|
94
|
+
if (minutes < 60) return minutes + 'min ' + String(seconds % 60).padStart(2, '0') + 's';
|
|
95
|
+
const hours = Math.floor(minutes / 60);
|
|
96
|
+
return hours + 'h ' + String(minutes % 60).padStart(2, '0') + 'min';
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function formatAgo(iso) {
|
|
100
|
+
const ms = parseIso(iso);
|
|
101
|
+
if (ms === null) return '';
|
|
102
|
+
return 'há ' + formatDuration((Date.now() - ms) / 1000);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function formatClock(iso) {
|
|
106
|
+
const ms = parseIso(iso);
|
|
107
|
+
if (ms === null) return '';
|
|
108
|
+
return new Date(ms).toLocaleTimeString('pt-BR');
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// URL do repositório derivada da URL da issue (…/issues/N → raiz do repo).
|
|
112
|
+
function repoUrl(snapshot) {
|
|
113
|
+
const url = snapshot.issue && snapshot.issue.url;
|
|
114
|
+
if (!url) return null;
|
|
115
|
+
const match = url.match(/^(https?:\/\/\S+?)\/issues\/\d+\/?$/);
|
|
116
|
+
return match ? match[1] : null;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// ---- Polling: intervalo configurável, aba oculta e backoff ----------------
|
|
120
|
+
|
|
121
|
+
function readStoredRefresh() {
|
|
122
|
+
let raw = null;
|
|
123
|
+
try {
|
|
124
|
+
raw = window.localStorage.getItem(STORAGE_KEY);
|
|
125
|
+
} catch (err) {
|
|
126
|
+
// localStorage indisponível (ex.: bloqueado) — segue com o default.
|
|
127
|
+
}
|
|
128
|
+
if (raw === null) return null;
|
|
129
|
+
const value = Number(raw);
|
|
130
|
+
return Number.isFinite(value) && value >= 0 ? value : null;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function storeRefresh(value) {
|
|
134
|
+
try {
|
|
135
|
+
window.localStorage.setItem(STORAGE_KEY, String(value));
|
|
136
|
+
} catch (err) {
|
|
137
|
+
// Persistência é conveniência; falha é ignorada.
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function buildRefreshSelect() {
|
|
142
|
+
const values = REFRESH_OPTIONS.slice();
|
|
143
|
+
if (state.refreshSeconds !== PAUSED && values.indexOf(state.refreshSeconds) === -1) {
|
|
144
|
+
values.push(state.refreshSeconds);
|
|
145
|
+
values.sort((a, b) => a - b);
|
|
146
|
+
}
|
|
147
|
+
clear(els.refreshSelect);
|
|
148
|
+
for (const value of values) {
|
|
149
|
+
const option = el('option', null, value + 's');
|
|
150
|
+
option.value = String(value);
|
|
151
|
+
els.refreshSelect.appendChild(option);
|
|
152
|
+
}
|
|
153
|
+
const pause = el('option', null, 'pausar');
|
|
154
|
+
pause.value = String(PAUSED);
|
|
155
|
+
els.refreshSelect.appendChild(pause);
|
|
156
|
+
els.refreshSelect.value = String(state.refreshSeconds);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function clearTimer() {
|
|
160
|
+
if (state.timer !== null) {
|
|
161
|
+
window.clearTimeout(state.timer);
|
|
162
|
+
state.timer = null;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function schedule() {
|
|
167
|
+
clearTimer();
|
|
168
|
+
if (document.hidden || state.refreshSeconds === PAUSED) return;
|
|
169
|
+
const base = state.refreshSeconds * 1000;
|
|
170
|
+
const backoff = base * 2 ** Math.min(state.failures, 5);
|
|
171
|
+
state.timer = window.setTimeout(poll, Math.min(backoff, MAX_BACKOFF_MS));
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
async function poll() {
|
|
175
|
+
if (state.polling) return;
|
|
176
|
+
state.polling = true;
|
|
177
|
+
try {
|
|
178
|
+
const headers = {};
|
|
179
|
+
if (state.etag) headers['If-None-Match'] = state.etag;
|
|
180
|
+
const res = await fetch('api/status', { headers, cache: 'no-store' });
|
|
181
|
+
if (res.status !== 304) {
|
|
182
|
+
if (!res.ok) throw new Error('HTTP ' + res.status);
|
|
183
|
+
state.etag = res.headers.get('ETag');
|
|
184
|
+
state.snapshot = await res.json();
|
|
185
|
+
render();
|
|
186
|
+
}
|
|
187
|
+
state.failures = 0;
|
|
188
|
+
els.banner.hidden = true;
|
|
189
|
+
} catch (err) {
|
|
190
|
+
state.failures += 1;
|
|
191
|
+
els.banner.hidden = false;
|
|
192
|
+
} finally {
|
|
193
|
+
state.polling = false;
|
|
194
|
+
schedule();
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// ---- Renderização ---------------------------------------------------------
|
|
199
|
+
|
|
200
|
+
function render() {
|
|
201
|
+
const snapshot = state.snapshot;
|
|
202
|
+
if (!snapshot) return;
|
|
203
|
+
renderHeader(snapshot);
|
|
204
|
+
renderAlerts(snapshot);
|
|
205
|
+
renderProgress(snapshot);
|
|
206
|
+
renderNow(snapshot);
|
|
207
|
+
renderPhases(snapshot);
|
|
208
|
+
renderNextSteps(snapshot);
|
|
209
|
+
renderStories(snapshot);
|
|
210
|
+
renderGit(snapshot);
|
|
211
|
+
renderLogs(snapshot);
|
|
212
|
+
renderMeta(snapshot);
|
|
213
|
+
renderTimers();
|
|
214
|
+
renderTitle(snapshot);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function renderTitle(snapshot) {
|
|
218
|
+
const issue = snapshot.issue.number !== null ? '#' + snapshot.issue.number : '';
|
|
219
|
+
let prefix = '';
|
|
220
|
+
if (snapshot.status === 'running') prefix = snapshot.progress.percent + '% · ';
|
|
221
|
+
else if (snapshot.status === 'completed') prefix = '✓ ';
|
|
222
|
+
else if (snapshot.status === 'failed') prefix = '✗ ';
|
|
223
|
+
document.title = (prefix + issue + ' · issue-flow').replace(/^ · /, '');
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function renderHeader(snapshot) {
|
|
227
|
+
if (snapshot.issue.number !== null) {
|
|
228
|
+
els.issueLink.hidden = false;
|
|
229
|
+
els.issueLink.textContent = '#' + snapshot.issue.number;
|
|
230
|
+
if (snapshot.issue.url) els.issueLink.href = snapshot.issue.url;
|
|
231
|
+
else els.issueLink.removeAttribute('href');
|
|
232
|
+
} else {
|
|
233
|
+
els.issueLink.hidden = true;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
const branch = snapshot.git.branch;
|
|
237
|
+
const base = snapshot.git.baseBranch;
|
|
238
|
+
els.branchLine.textContent = branch ? (base ? branch + ' ← ' + base : branch) : '';
|
|
239
|
+
|
|
240
|
+
els.statusBadge.textContent = STATUS_LABELS[snapshot.status] || snapshot.status;
|
|
241
|
+
els.statusBadge.className = 'badge status-' + snapshot.status;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function renderAlerts(snapshot) {
|
|
245
|
+
const errors = snapshot.errors || [];
|
|
246
|
+
const warnings = snapshot.warnings || [];
|
|
247
|
+
const lastError = snapshot.lastError;
|
|
248
|
+
const any = errors.length > 0 || warnings.length > 0 || lastError !== null;
|
|
249
|
+
els.alerts.hidden = !any;
|
|
250
|
+
clear(els.alertsBody);
|
|
251
|
+
if (!any) return;
|
|
252
|
+
|
|
253
|
+
const counts = [];
|
|
254
|
+
if (errors.length > 0) counts.push(errors.length + ' erro(s)');
|
|
255
|
+
if (warnings.length > 0) counts.push(warnings.length + ' aviso(s)');
|
|
256
|
+
if (counts.length > 0) {
|
|
257
|
+
els.alertsBody.appendChild(el('p', 'alert-count', counts.join(' · ')));
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
if (lastError) {
|
|
261
|
+
const entry = el('div', 'alert-entry level-error');
|
|
262
|
+
entry.appendChild(el('strong', null, 'Último erro: '));
|
|
263
|
+
entry.appendChild(document.createTextNode(lastError.message));
|
|
264
|
+
els.alertsBody.appendChild(entry);
|
|
265
|
+
}
|
|
266
|
+
for (const log of errors.slice(-ALERT_PREVIEW)) {
|
|
267
|
+
const entry = el('div', 'alert-entry level-error');
|
|
268
|
+
entry.appendChild(el('span', 'mono', formatClock(log.at) + ' '));
|
|
269
|
+
entry.appendChild(document.createTextNode(log.message));
|
|
270
|
+
els.alertsBody.appendChild(entry);
|
|
271
|
+
}
|
|
272
|
+
for (const log of warnings.slice(-ALERT_PREVIEW)) {
|
|
273
|
+
const entry = el('div', 'alert-entry level-warn');
|
|
274
|
+
entry.appendChild(el('span', 'mono', formatClock(log.at) + ' '));
|
|
275
|
+
entry.appendChild(document.createTextNode(log.message));
|
|
276
|
+
els.alertsBody.appendChild(entry);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
function renderProgress(snapshot) {
|
|
281
|
+
const progress = snapshot.progress;
|
|
282
|
+
els.progressBar.value = progress.percent;
|
|
283
|
+
els.progressPercent.textContent = progress.percent + '%';
|
|
284
|
+
els.progressCounters.textContent =
|
|
285
|
+
'Fases ' +
|
|
286
|
+
progress.phasesCompleted +
|
|
287
|
+
'/' +
|
|
288
|
+
progress.phasesTotal +
|
|
289
|
+
' · Stories ' +
|
|
290
|
+
progress.storiesCompleted +
|
|
291
|
+
'/' +
|
|
292
|
+
progress.storiesTotal;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
function nowRow(grid, label, value) {
|
|
296
|
+
grid.appendChild(el('dt', null, label));
|
|
297
|
+
grid.appendChild(el('dd', null, value));
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
function renderNow(snapshot) {
|
|
301
|
+
clear(els.now);
|
|
302
|
+
if (snapshot.status !== 'running') {
|
|
303
|
+
const messages = {
|
|
304
|
+
idle: 'Nenhuma execução em andamento.',
|
|
305
|
+
completed: 'Execução concluída.',
|
|
306
|
+
failed: 'Execução falhou — veja os erros acima.',
|
|
307
|
+
};
|
|
308
|
+
els.now.appendChild(el('p', 'empty', messages[snapshot.status] || '—'));
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
const grid = el('dl', 'now-grid');
|
|
313
|
+
const phase = el('dd');
|
|
314
|
+
if (snapshot.currentPhase) {
|
|
315
|
+
phase.appendChild(el('span', 'pulse'));
|
|
316
|
+
phase.appendChild(document.createTextNode(snapshot.currentPhase));
|
|
317
|
+
} else {
|
|
318
|
+
phase.textContent = '—';
|
|
319
|
+
}
|
|
320
|
+
grid.appendChild(el('dt', null, 'Fase'));
|
|
321
|
+
grid.appendChild(phase);
|
|
322
|
+
|
|
323
|
+
const activity = snapshot.currentActivity;
|
|
324
|
+
if (activity) {
|
|
325
|
+
if (activity.story) nowRow(grid, 'Story', activity.story);
|
|
326
|
+
if (activity.tool) nowRow(grid, 'Ferramenta', activity.tool);
|
|
327
|
+
if (activity.detail) nowRow(grid, 'Detalhe', activity.detail);
|
|
328
|
+
const since = el('dd', null, formatAgo(activity.since));
|
|
329
|
+
since.dataset.since = activity.since;
|
|
330
|
+
grid.appendChild(el('dt', null, 'Há quanto tempo'));
|
|
331
|
+
grid.appendChild(since);
|
|
332
|
+
}
|
|
333
|
+
els.now.appendChild(grid);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
function renderPhases(snapshot) {
|
|
337
|
+
clear(els.phases);
|
|
338
|
+
if (snapshot.phases.length === 0) {
|
|
339
|
+
els.phases.appendChild(el('p', 'empty', 'Nenhuma fase registrada ainda.'));
|
|
340
|
+
return;
|
|
341
|
+
}
|
|
342
|
+
for (const phase of snapshot.phases) {
|
|
343
|
+
const item = el('li');
|
|
344
|
+
item.appendChild(el('span', 'item-icon icon-' + phase.status, PHASE_ICONS[phase.status] || '○'));
|
|
345
|
+
const main = el('div', 'item-main');
|
|
346
|
+
main.appendChild(el('div', 'item-title', phase.name));
|
|
347
|
+
if (phase.error) main.appendChild(el('div', 'item-error', phase.error));
|
|
348
|
+
item.appendChild(main);
|
|
349
|
+
if (phase.durationSeconds !== null) {
|
|
350
|
+
item.appendChild(el('span', 'item-side', formatDuration(phase.durationSeconds)));
|
|
351
|
+
}
|
|
352
|
+
els.phases.appendChild(item);
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
function renderNextSteps(snapshot) {
|
|
357
|
+
clear(els.nextSteps);
|
|
358
|
+
if (snapshot.nextSteps.length === 0) {
|
|
359
|
+
const message =
|
|
360
|
+
snapshot.status === 'completed' ? 'Pipeline concluído.' : 'Nenhum passo pendente.';
|
|
361
|
+
els.nextSteps.appendChild(el('p', 'empty', message));
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
const list = el('ol');
|
|
365
|
+
for (const step of snapshot.nextSteps) {
|
|
366
|
+
list.appendChild(el('li', null, step));
|
|
367
|
+
}
|
|
368
|
+
els.nextSteps.appendChild(list);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
function renderStories(snapshot) {
|
|
372
|
+
clear(els.stories);
|
|
373
|
+
if (snapshot.stories.length === 0) {
|
|
374
|
+
els.stories.appendChild(el('p', 'empty', 'Nenhuma user story registrada ainda.'));
|
|
375
|
+
return;
|
|
376
|
+
}
|
|
377
|
+
for (const story of snapshot.stories) {
|
|
378
|
+
const item = el('li');
|
|
379
|
+
const status = story.passes ? 'completed' : 'pending';
|
|
380
|
+
item.appendChild(el('span', 'item-icon icon-' + status, story.passes ? '✓' : '○'));
|
|
381
|
+
const main = el('div', 'item-main');
|
|
382
|
+
const title = el('div', 'item-title');
|
|
383
|
+
title.appendChild(el('span', 'story-id', story.id));
|
|
384
|
+
title.appendChild(document.createTextNode(story.title));
|
|
385
|
+
main.appendChild(title);
|
|
386
|
+
item.appendChild(main);
|
|
387
|
+
if (story.completedAt) {
|
|
388
|
+
item.appendChild(el('span', 'item-side', 'concluída ' + formatClock(story.completedAt)));
|
|
389
|
+
}
|
|
390
|
+
els.stories.appendChild(item);
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
function renderGit(snapshot) {
|
|
395
|
+
const repo = repoUrl(snapshot);
|
|
396
|
+
|
|
397
|
+
clear(els.commits);
|
|
398
|
+
if (snapshot.git.commits.length === 0) {
|
|
399
|
+
els.commits.appendChild(el('p', 'empty', 'Nenhum commit ainda.'));
|
|
400
|
+
} else {
|
|
401
|
+
for (const commit of snapshot.git.commits) {
|
|
402
|
+
const item = el('li');
|
|
403
|
+
const hash = repo
|
|
404
|
+
? link(repo + '/commit/' + commit.hash, commit.hash, 'mono commit-hash')
|
|
405
|
+
: el('span', 'mono commit-hash', commit.hash);
|
|
406
|
+
item.appendChild(hash);
|
|
407
|
+
const subject = el('div', 'item-main item-title', commit.subject);
|
|
408
|
+
item.appendChild(subject);
|
|
409
|
+
els.commits.appendChild(item);
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
clear(els.pullRequests);
|
|
414
|
+
if (snapshot.pullRequests.length === 0) {
|
|
415
|
+
els.pullRequests.appendChild(el('p', 'empty', 'Nenhum pull request ainda.'));
|
|
416
|
+
} else {
|
|
417
|
+
for (const pr of snapshot.pullRequests) {
|
|
418
|
+
const item = el('li');
|
|
419
|
+
item.appendChild(link(pr.url, '#' + pr.number));
|
|
420
|
+
item.appendChild(el('div', 'item-main item-title', pr.title));
|
|
421
|
+
els.pullRequests.appendChild(item);
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
function renderLogs(snapshot) {
|
|
427
|
+
clear(els.logs);
|
|
428
|
+
const filter = state.logFilter;
|
|
429
|
+
const entries = snapshot.logs.filter(
|
|
430
|
+
(entry) => filter === 'all' || entry.level === filter,
|
|
431
|
+
);
|
|
432
|
+
if (entries.length === 0) {
|
|
433
|
+
els.logs.appendChild(el('p', 'empty', 'Nenhum log para exibir.'));
|
|
434
|
+
return;
|
|
435
|
+
}
|
|
436
|
+
// Mais recentes primeiro: monitoramento lê o topo, sem gerenciar scroll.
|
|
437
|
+
for (const entry of entries.slice().reverse()) {
|
|
438
|
+
const item = el('li', 'level-' + entry.level);
|
|
439
|
+
item.appendChild(el('span', 'log-time', formatClock(entry.at)));
|
|
440
|
+
item.appendChild(el('span', 'log-level', entry.level));
|
|
441
|
+
item.appendChild(el('span', 'log-message', entry.message));
|
|
442
|
+
els.logs.appendChild(item);
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
function renderMeta(snapshot) {
|
|
447
|
+
const parts = [];
|
|
448
|
+
if (snapshot.sessionId) parts.push('sessão ' + snapshot.sessionId);
|
|
449
|
+
if (snapshot.updatedAt) parts.push('atualizado ' + formatClock(snapshot.updatedAt));
|
|
450
|
+
parts.push('somente leitura');
|
|
451
|
+
els.sessionMeta.textContent = parts.join(' · ');
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
// Relógios em tempo real (1s): tempo decorrido, estimativa e "há quanto
|
|
455
|
+
// tempo" da atividade — sem esperar o próximo poll.
|
|
456
|
+
function renderTimers() {
|
|
457
|
+
const snapshot = state.snapshot;
|
|
458
|
+
if (!snapshot) return;
|
|
459
|
+
|
|
460
|
+
const startMs = parseIso(snapshot.startedAt);
|
|
461
|
+
if (startMs === null) {
|
|
462
|
+
els.elapsed.textContent = '—';
|
|
463
|
+
} else {
|
|
464
|
+
const endMs = parseIso(snapshot.endedAt);
|
|
465
|
+
const end = snapshot.status === 'running' || endMs === null ? Date.now() : endMs;
|
|
466
|
+
els.elapsed.textContent = formatDuration((end - startMs) / 1000);
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
const estimate = snapshot.estimatedRemainingSeconds;
|
|
470
|
+
if (snapshot.status === 'running' && estimate !== null) {
|
|
471
|
+
els.estimate.hidden = false;
|
|
472
|
+
els.estimate.textContent = '~' + formatDuration(estimate) + ' restantes (estimativa)';
|
|
473
|
+
} else {
|
|
474
|
+
els.estimate.hidden = true;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
const since = els.now.querySelector('[data-since]');
|
|
478
|
+
if (since) since.textContent = formatAgo(since.dataset.since);
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
// ---- Inicialização --------------------------------------------------------
|
|
482
|
+
|
|
483
|
+
async function init() {
|
|
484
|
+
els.refreshSelect.addEventListener('change', () => {
|
|
485
|
+
state.refreshSeconds = Number(els.refreshSelect.value);
|
|
486
|
+
storeRefresh(state.refreshSeconds);
|
|
487
|
+
clearTimer();
|
|
488
|
+
if (state.refreshSeconds !== PAUSED) poll();
|
|
489
|
+
});
|
|
490
|
+
|
|
491
|
+
els.logFilter.addEventListener('change', () => {
|
|
492
|
+
state.logFilter = els.logFilter.value;
|
|
493
|
+
if (state.snapshot) renderLogs(state.snapshot);
|
|
494
|
+
});
|
|
495
|
+
|
|
496
|
+
document.addEventListener('visibilitychange', () => {
|
|
497
|
+
if (document.hidden) clearTimer();
|
|
498
|
+
else poll();
|
|
499
|
+
});
|
|
500
|
+
|
|
501
|
+
// Default do seletor vem da configuração do servidor (/api/health);
|
|
502
|
+
// a escolha do usuário em localStorage tem precedência.
|
|
503
|
+
const stored = readStoredRefresh();
|
|
504
|
+
if (stored !== null) {
|
|
505
|
+
state.refreshSeconds = stored;
|
|
506
|
+
} else {
|
|
507
|
+
try {
|
|
508
|
+
const health = await fetch('api/health', { cache: 'no-store' }).then((r) => r.json());
|
|
509
|
+
const suggested = Number(health.refreshSeconds);
|
|
510
|
+
if (Number.isFinite(suggested) && suggested > 0) state.refreshSeconds = suggested;
|
|
511
|
+
} catch (err) {
|
|
512
|
+
// Sem /api/health segue o default local (5s).
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
buildRefreshSelect();
|
|
516
|
+
|
|
517
|
+
window.setInterval(renderTimers, 1000);
|
|
518
|
+
poll();
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
init();
|
|
522
|
+
})();
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="pt-BR">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
6
|
+
<meta name="color-scheme" content="light dark" />
|
|
7
|
+
<meta name="robots" content="noindex, nofollow" />
|
|
8
|
+
<link rel="icon" href="data:," />
|
|
9
|
+
<title>issue-flow</title>
|
|
10
|
+
<link rel="stylesheet" href="app.css" />
|
|
11
|
+
</head>
|
|
12
|
+
<body>
|
|
13
|
+
<div id="banner-disconnected" class="banner" role="alert" hidden>
|
|
14
|
+
Desconectado do servidor — tentando reconectar…
|
|
15
|
+
</div>
|
|
16
|
+
|
|
17
|
+
<main>
|
|
18
|
+
<header class="card header">
|
|
19
|
+
<div class="header-main">
|
|
20
|
+
<h1>
|
|
21
|
+
<span class="brand">issue-flow</span>
|
|
22
|
+
<a id="issue-link" class="issue-link" target="_blank" rel="noopener" hidden>#—</a>
|
|
23
|
+
</h1>
|
|
24
|
+
<p id="branch-line" class="muted mono"></p>
|
|
25
|
+
</div>
|
|
26
|
+
<div class="header-side">
|
|
27
|
+
<span id="status-badge" class="badge status-idle">aguardando</span>
|
|
28
|
+
<div class="timers">
|
|
29
|
+
<span id="elapsed" title="Tempo decorrido">—</span>
|
|
30
|
+
<span id="estimate" class="muted" hidden></span>
|
|
31
|
+
</div>
|
|
32
|
+
<label class="refresh-control">
|
|
33
|
+
Atualizar
|
|
34
|
+
<select id="refresh-select" aria-label="Intervalo de atualização"></select>
|
|
35
|
+
</label>
|
|
36
|
+
</div>
|
|
37
|
+
</header>
|
|
38
|
+
|
|
39
|
+
<section id="alerts" class="card alerts" aria-live="polite" hidden>
|
|
40
|
+
<h2>Erros e avisos</h2>
|
|
41
|
+
<div id="alerts-body"></div>
|
|
42
|
+
</section>
|
|
43
|
+
|
|
44
|
+
<section class="card" aria-live="polite">
|
|
45
|
+
<h2>Progresso</h2>
|
|
46
|
+
<div class="progress-row">
|
|
47
|
+
<progress id="progress-bar" max="100" value="0"></progress>
|
|
48
|
+
<span id="progress-percent" class="progress-percent">0%</span>
|
|
49
|
+
</div>
|
|
50
|
+
<p id="progress-counters" class="muted"></p>
|
|
51
|
+
</section>
|
|
52
|
+
|
|
53
|
+
<section class="card" aria-live="polite">
|
|
54
|
+
<h2>Executando agora</h2>
|
|
55
|
+
<div id="now"></div>
|
|
56
|
+
</section>
|
|
57
|
+
|
|
58
|
+
<div class="columns">
|
|
59
|
+
<section class="card">
|
|
60
|
+
<h2>Fases</h2>
|
|
61
|
+
<ol id="phases" class="plain-list"></ol>
|
|
62
|
+
</section>
|
|
63
|
+
<section class="card">
|
|
64
|
+
<h2>Próximos passos</h2>
|
|
65
|
+
<div id="next-steps"></div>
|
|
66
|
+
</section>
|
|
67
|
+
</div>
|
|
68
|
+
|
|
69
|
+
<section class="card">
|
|
70
|
+
<h2>User stories</h2>
|
|
71
|
+
<ol id="stories" class="plain-list"></ol>
|
|
72
|
+
</section>
|
|
73
|
+
|
|
74
|
+
<div class="columns">
|
|
75
|
+
<section class="card">
|
|
76
|
+
<h2>Commits</h2>
|
|
77
|
+
<ul id="commits" class="plain-list"></ul>
|
|
78
|
+
</section>
|
|
79
|
+
<section class="card">
|
|
80
|
+
<h2>Pull requests</h2>
|
|
81
|
+
<ul id="pull-requests" class="plain-list"></ul>
|
|
82
|
+
</section>
|
|
83
|
+
</div>
|
|
84
|
+
|
|
85
|
+
<section class="card">
|
|
86
|
+
<div class="section-head">
|
|
87
|
+
<h2>Logs recentes</h2>
|
|
88
|
+
<label class="log-filter-control">
|
|
89
|
+
Nível
|
|
90
|
+
<select id="log-filter" aria-label="Filtro por nível de log">
|
|
91
|
+
<option value="all">todos</option>
|
|
92
|
+
<option value="info">info</option>
|
|
93
|
+
<option value="warn">avisos</option>
|
|
94
|
+
<option value="error">erros</option>
|
|
95
|
+
</select>
|
|
96
|
+
</label>
|
|
97
|
+
</div>
|
|
98
|
+
<ol id="logs" class="plain-list logs"></ol>
|
|
99
|
+
</section>
|
|
100
|
+
|
|
101
|
+
<footer class="muted footer">
|
|
102
|
+
<span id="session-meta"></span>
|
|
103
|
+
</footer>
|
|
104
|
+
</main>
|
|
105
|
+
|
|
106
|
+
<noscript>
|
|
107
|
+
<p class="noscript">Esta interface precisa de JavaScript. O estado bruto está disponível em
|
|
108
|
+
<a href="status.json">status.json</a>.</p>
|
|
109
|
+
</noscript>
|
|
110
|
+
|
|
111
|
+
<script src="app.js"></script>
|
|
112
|
+
</body>
|
|
113
|
+
</html>
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/core/headless.ts"],"sourcesContent":["import { createInterface } from 'node:readline';\nimport chalk from 'chalk';\nimport { execa } from 'execa';\nimport { createSpinner, ElapsedTimer, formatDuration, getIcons, useColor } from '../ui/logger.js';\nimport { getOutputCallback, isVerbose } from './verbose.js';\n\nexport interface HeadlessOptions {\n prompt: string;\n maxTurns?: number;\n timeout?: number;\n outputFormat?: 'json' | 'text' | 'stream-json';\n allowedTools?: string[];\n /** Status message displayed as spinner (non-verbose) or header (verbose). */\n statusMessage?: string;\n /** Optional callback for routing verbose output (e.g., through listr2 task.output). When provided, verbose stream events are sent here instead of directly to stderr. */\n onOutput?: (line: string) => void;\n}\n\nexport interface HeadlessCost {\n inputTokens?: number;\n outputTokens?: number;\n}\n\nexport interface HeadlessResult {\n success: boolean;\n result: string;\n cost: HeadlessCost | null;\n error: string | null;\n}\n\n/* ── verbose stream formatting ──────────────────────────────────────────── */\n\n/**\n * Extract a short context string from a tool_use input object.\n */\nfunction getToolContext(name: string, input: Record<string, unknown>): string {\n switch (name) {\n case 'Read':\n return shortPath(input.file_path as string);\n case 'Write':\n case 'Edit':\n return shortPath(input.file_path as string);\n case 'Glob':\n return (input.pattern as string) ?? '';\n case 'Grep':\n return (input.pattern as string) ?? '';\n case 'Bash': {\n const cmd = (input.command as string) ?? '';\n return cmd.length > 60 ? `${cmd.substring(0, 57)}...` : cmd;\n }\n default:\n return '';\n }\n}\n\n/**\n * Shorten a file path to be relative-friendly.\n */\nfunction shortPath(filePath: string | undefined): string {\n if (!filePath) return '';\n const cwd = process.cwd();\n if (filePath.startsWith(cwd)) {\n return filePath.substring(cwd.length + 1);\n }\n // Show last 2 segments for absolute paths\n const parts = filePath.split('/');\n if (parts.length > 2) {\n return `.../${parts.slice(-2).join('/')}`;\n }\n return filePath;\n}\n\n/**\n * Print a formatted stream event line to stderr.\n */\nfunction printStreamEvent(\n line: string,\n state: { turnCount: number },\n onOutput?: (line: string) => void,\n): void {\n let event: {\n type?: string;\n subtype?: string;\n message?: {\n content?: { type: string; text?: string; name?: string; input?: Record<string, unknown> }[];\n };\n };\n\n try {\n event = JSON.parse(line);\n } catch {\n return;\n }\n\n const icons = getIcons();\n const colored = useColor();\n const emit = onOutput ?? ((msg: string) => process.stderr.write(`${msg}\\n`));\n\n if (event.type === 'assistant' && event.message?.content) {\n state.turnCount++;\n\n for (const block of event.message.content) {\n if (block.type === 'text' && block.text) {\n // Wrap text with connector prefix\n const lines = block.text.split('\\n');\n for (const textLine of lines) {\n if (!textLine.trim()) continue;\n const prefix = colored ? chalk.dim(` ${icons.connector} `) : ` ${icons.connector} `;\n const text = colored ? chalk.dim(textLine) : textLine;\n emit(`${prefix}${text}`);\n }\n }\n\n if (block.type === 'tool_use' && block.name) {\n const context = block.input ? getToolContext(block.name, block.input) : '';\n const toolName = block.name.padEnd(12);\n\n const prefix = colored ? chalk.dim(` ${icons.connector} `) : ` ${icons.connector} `;\n const toolIcon = colored ? chalk.cyan(icons.tool) : icons.tool;\n const toolLabel = colored ? chalk.cyan(toolName) : toolName;\n const contextText = context ? (colored ? chalk.dim(context) : context) : '';\n\n emit(`${prefix}${toolIcon} ${toolLabel} ${contextText}`);\n }\n }\n }\n}\n\n/* ── verbose execution ──────────────────────────────────────────────────── */\n\n/**\n * Run headless in verbose mode using stream-json to display real-time progress.\n */\nasync function runHeadlessVerbose(\n prompt: string,\n maxTurns: number,\n timeout: number,\n allowedTools?: string[],\n statusMessage?: string,\n onOutput?: (line: string) => void,\n): Promise<HeadlessResult> {\n const icons = getIcons();\n const colored = useColor();\n const emit = onOutput ?? ((msg: string) => process.stderr.write(`${msg}\\n`));\n\n // Print header\n if (statusMessage) {\n const msg = colored\n ? chalk.blue(`${icons.start} ${statusMessage}`)\n : `${icons.start} ${statusMessage}`;\n emit(msg);\n }\n\n // Print opening connector\n const connectorLine = colored ? chalk.dim(` ${icons.connector}`) : ` ${icons.connector}`;\n emit(connectorLine);\n\n const startTime = Date.now();\n\n const args: string[] = [\n '-p',\n prompt,\n '--output-format',\n 'stream-json',\n '--verbose',\n '--max-turns',\n String(maxTurns),\n ];\n\n if (allowedTools && allowedTools.length > 0) {\n for (const tool of allowedTools) {\n args.push('--allowedTools', tool);\n }\n }\n\n const subprocess = execa('claude', args, {\n stdin: 'ignore',\n reject: false,\n timeout,\n stripFinalNewline: false,\n });\n\n let resultText = '';\n let isError = false;\n let costData: HeadlessCost | null = null;\n const state = { turnCount: 0 };\n\n if (subprocess.stdout) {\n const rl = createInterface({ input: subprocess.stdout });\n for await (const line of rl) {\n printStreamEvent(line, state, onOutput);\n\n try {\n const event = JSON.parse(line);\n if (event.type === 'result') {\n resultText = event.result ?? '';\n isError = event.is_error === true;\n if (event.total_cost_usd != null && event.usage) {\n costData = {\n inputTokens: event.usage.input_tokens,\n outputTokens: event.usage.output_tokens,\n };\n }\n }\n } catch {\n // ignore malformed lines\n }\n }\n }\n\n // Wait for the process to finish\n const proc = await subprocess;\n\n // Close connector with elapsed time\n const elapsedSec = Math.floor((Date.now() - startTime) / 1000);\n const durationStr = formatDuration(elapsedSec);\n const doneLine = colored\n ? chalk.dim(` ${icons.connector} ${chalk.italic(`Done in ${durationStr}`)}`)\n : ` ${icons.connector} Done in ${durationStr}`;\n emit(doneLine);\n emit(connectorLine);\n\n if (proc.exitCode !== 0 && !resultText) {\n const stderr = proc.stderr?.toString() ?? '';\n const isTimeout =\n proc.exitCode === 143 || ('signalName' in proc && proc.signalName === 'SIGTERM');\n const errorMsg = isTimeout\n ? `Headless invocation timed out after ${timeout}ms`\n : stderr || `claude exited with code ${proc.exitCode}`;\n return {\n success: false,\n result: '',\n cost: null,\n error: errorMsg,\n };\n }\n\n return {\n success: !isError,\n result: resultText,\n cost: costData,\n error: isError ? resultText : null,\n };\n}\n\n/* ── standard execution ─────────────────────────────────────────────────── */\n\n/**\n * Invoke Claude Code in headless mode via `claude -p`.\n *\n * Each invocation is an isolated session — no context is shared between calls.\n * Output is parsed as JSON when outputFormat is 'json' (default).\n *\n * When verbose mode is active, uses stream-json to display real-time progress.\n * Otherwise, shows a spinner while waiting.\n */\nexport async function runHeadless(options: HeadlessOptions): Promise<HeadlessResult> {\n const {\n prompt,\n maxTurns = 10,\n timeout = 300_000,\n outputFormat = 'json',\n allowedTools,\n statusMessage,\n onOutput,\n } = options;\n\n if (isVerbose()) {\n // Use explicit onOutput, fall back to global output callback, or default to stderr\n const effectiveOnOutput = onOutput ?? getOutputCallback();\n return runHeadlessVerbose(\n prompt,\n maxTurns,\n timeout,\n allowedTools,\n statusMessage,\n effectiveOnOutput,\n );\n }\n\n // Non-verbose: use spinner with elapsed timer\n const spinner = statusMessage ? createSpinner(statusMessage).start() : null;\n\n let timer: ElapsedTimer | null = null;\n if (spinner) {\n timer = new ElapsedTimer((elapsed) => {\n spinner.suffixText = useColor() ? chalk.dim(`(${elapsed})`) : `(${elapsed})`;\n }).start();\n }\n\n const args: string[] = [\n '-p',\n prompt,\n '--output-format',\n outputFormat,\n '--max-turns',\n String(maxTurns),\n ];\n\n if (allowedTools && allowedTools.length > 0) {\n for (const tool of allowedTools) {\n args.push('--allowedTools', tool);\n }\n }\n\n try {\n const proc = await execa('claude', args, {\n stdin: 'ignore',\n reject: false,\n timeout,\n stripFinalNewline: false,\n });\n\n const stdout = proc.stdout?.toString() ?? '';\n const stderr = proc.stderr?.toString() ?? '';\n\n if (proc.exitCode !== 0) {\n const elapsed = timer?.stop() ?? 0;\n const dur = useColor()\n ? chalk.dim(` (${formatDuration(elapsed)})`)\n : ` (${formatDuration(elapsed)})`;\n spinner?.fail(`${statusMessage}${dur}`);\n return {\n success: false,\n result: '',\n cost: null,\n error: stderr || stdout || `claude exited with code ${proc.exitCode}`,\n };\n }\n\n const elapsed = timer?.stop() ?? 0;\n const dur = useColor()\n ? chalk.dim(` (${formatDuration(elapsed)})`)\n : ` (${formatDuration(elapsed)})`;\n spinner?.succeed(`${statusMessage}${dur}`);\n\n if (outputFormat === 'json') {\n try {\n const parsed = JSON.parse(stdout);\n return {\n success: true,\n result: parsed.result ?? stdout,\n cost:\n parsed.cost_usd != null\n ? { inputTokens: parsed.num_input_tokens, outputTokens: parsed.num_output_tokens }\n : null,\n error: null,\n };\n } catch {\n return {\n success: true,\n result: stdout,\n cost: null,\n error: null,\n };\n }\n }\n\n return {\n success: true,\n result: stdout,\n cost: null,\n error: null,\n };\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n const catchElapsed = timer?.stop() ?? 0;\n const catchDur = useColor()\n ? chalk.dim(` (${formatDuration(catchElapsed)})`)\n : ` (${formatDuration(catchElapsed)})`;\n spinner?.fail(`${statusMessage}${catchDur}`);\n\n if (message.includes('timed out') || message.includes('ETIMEDOUT')) {\n return {\n success: false,\n result: '',\n cost: null,\n error: `Headless invocation timed out after ${timeout}ms`,\n };\n }\n\n return {\n success: false,\n result: '',\n cost: null,\n error: message,\n };\n }\n}\n"],"mappings":"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","names":["elapsed","dur"]}
|