@viberaven/cli 1.4.4 → 1.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.
Files changed (42) hide show
  1. package/AGENTS.md +157 -157
  2. package/LICENSE +21 -21
  3. package/README.md +1 -3
  4. package/SECURITY.md +59 -53
  5. package/dist/cli.js +13764 -32046
  6. package/dist/console/app.js +251 -251
  7. package/dist/console/index.html +46 -46
  8. package/dist/console/styles.css +267 -267
  9. package/dist/playbooks/_stack-default.json +11 -11
  10. package/dist/playbooks/auth-supabase.json +31 -31
  11. package/dist/playbooks/stripe.json +66 -66
  12. package/dist/playbooks/supabase.json +51 -51
  13. package/dist/playbooks/vercel.json +42 -42
  14. package/dist/report/report-cli.css +1466 -1466
  15. package/dist/report/station.css +11854 -11854
  16. package/dist/report/station.js +8317 -8317
  17. package/dist/stack/app.js +18 -0
  18. package/dist/stack/stack.css +854 -0
  19. package/package.json +84 -75
  20. package/playbooks/_stack-default.json +11 -11
  21. package/playbooks/auth-supabase.json +31 -31
  22. package/playbooks/stripe.json +66 -66
  23. package/playbooks/supabase.json +51 -51
  24. package/playbooks/vercel.json +42 -42
  25. package/schemas/permissions-matrix.schema.json +90 -0
  26. package/templates/AGENTS.snippet.md +52 -52
  27. package/templates/CLAUDE.snippet.md +17 -17
  28. package/templates/CURSOR.snippet.md +17 -17
  29. package/assets/report/assets/provider-authjs.svg +0 -5
  30. package/assets/report/assets/provider-aws.svg +0 -5
  31. package/assets/report/assets/provider-logrocket.svg +0 -4
  32. package/assets/report/assets/viberaven-favicon.png +0 -0
  33. package/assets/report/assets/viberaven-hero.png +0 -0
  34. package/assets/report/assets/viberaven-logo.png +0 -0
  35. package/assets/report/assets/viberaven-mascot.png +0 -0
  36. package/assets/report/assets/viberaven-table.png +0 -0
  37. package/assets/report/report-cli.css +0 -1466
  38. package/assets/report/station.css +0 -11854
  39. package/assets/report/station.js +0 -8319
  40. package/dist/cli.js.map +0 -7
  41. package/dist/report/assets/viberaven-hero.png +0 -0
  42. package/dist/report/assets/viberaven-table.png +0 -0
@@ -1,251 +1,251 @@
1
- const endpoint = window.__VIBERAVEN_ACTIONS_ENDPOINT__ || '/api/actions';
2
- const commandEndpoint = '/api/command';
3
-
4
- const nodes = {
5
- actions: document.getElementById('actions'),
6
- actionCount: document.getElementById('actionCount'),
7
- artifactPath: document.getElementById('artifactPath'),
8
- gateStatus: document.getElementById('gateStatus'),
9
- generatedAt: document.getElementById('generatedAt'),
10
- message: document.getElementById('message'),
11
- refreshButton: document.getElementById('refreshButton'),
12
- };
13
-
14
- function text(value, fallback = '-') {
15
- if (value === undefined || value === null || value === '') return fallback;
16
- return String(value);
17
- }
18
-
19
- function payloadToText(value) {
20
- if (Array.isArray(value)) return value.join('\n');
21
- if (value && typeof value === 'object') return JSON.stringify(value, null, 2);
22
- return text(value, '');
23
- }
24
-
25
- async function copyPayload(value, button) {
26
- const content = payloadToText(value);
27
- const previous = button ? button.textContent : '';
28
-
29
- try {
30
- await navigator.clipboard.writeText(content);
31
- } catch {
32
- if (!button) return;
33
-
34
- button.textContent = 'Copy failed';
35
- window.setTimeout(() => {
36
- button.textContent = previous;
37
- }, 1200);
38
- return;
39
- }
40
-
41
- if (!button) return;
42
-
43
- button.textContent = 'Copied';
44
- button.disabled = true;
45
- window.setTimeout(() => {
46
- button.textContent = previous;
47
- button.disabled = false;
48
- }, 900);
49
- }
50
-
51
- function clearChildren(node) {
52
- while (node.firstChild) node.removeChild(node.firstChild);
53
- }
54
-
55
- function appendText(parent, className, value) {
56
- const node = document.createElement('p');
57
- node.className = className;
58
- node.textContent = value;
59
- parent.appendChild(node);
60
- return node;
61
- }
62
-
63
- function appendPill(parent, value, className = '') {
64
- const pill = document.createElement('span');
65
- pill.className = `pill ${className}`.trim();
66
- pill.textContent = value;
67
- parent.appendChild(pill);
68
- return pill;
69
- }
70
-
71
- function appendButton(parent, label, value) {
72
- const button = document.createElement('button');
73
- button.className = 'button secondary';
74
- button.type = 'button';
75
- button.textContent = label;
76
- button.addEventListener('click', () => copyPayload(value, button));
77
- parent.appendChild(button);
78
- return button;
79
- }
80
-
81
- function appendVerifyButton(parent, action, output) {
82
- const button = document.createElement('button');
83
- button.className = 'button secondary';
84
- button.type = 'button';
85
- const token = window.__VIBERAVEN_TOKEN__ || '';
86
- button.textContent = token ? 'Verify' : 'Verify unavailable';
87
- button.disabled = !token;
88
- button.addEventListener('click', async () => {
89
- if (!token) return;
90
-
91
- const previous = button.textContent;
92
- button.disabled = true;
93
- button.textContent = 'Verifying';
94
- output.hidden = false;
95
- output.textContent = 'Running verification...';
96
-
97
- try {
98
- const response = await fetch(commandEndpoint, {
99
- method: 'POST',
100
- headers: {
101
- 'Content-Type': 'application/json',
102
- Authorization: `Bearer ${window.__VIBERAVEN_TOKEN__ || ''}`,
103
- },
104
- body: JSON.stringify({ type: 'verify-action', actionId: action.id }),
105
- });
106
- const body = await response.json();
107
- if (!response.ok || !body.ok) {
108
- output.textContent = body && body.error ? body.error : 'Verification failed.';
109
- return;
110
- }
111
-
112
- const result = body.result || {};
113
- const outputParts = [];
114
- outputParts.push(`Exit code: ${text(result.exitCode, 'unknown')}`);
115
- if (result.stdout) outputParts.push(result.stdout);
116
- if (result.stderr) outputParts.push(result.stderr);
117
- output.textContent = outputParts.join('\n\n');
118
- } catch (error) {
119
- output.textContent = error instanceof Error ? error.message : 'Verification failed.';
120
- } finally {
121
- button.disabled = false;
122
- button.textContent = previous;
123
- }
124
- });
125
- parent.appendChild(button);
126
- return button;
127
- }
128
-
129
- function actionProvider(action) {
130
- return action.provider || (action.target && action.target.provider) || '';
131
- }
132
-
133
- function renderAction(action) {
134
- const card = document.createElement('article');
135
- card.className = 'action-card';
136
-
137
- const header = document.createElement('div');
138
- header.className = 'action-header';
139
- card.appendChild(header);
140
-
141
- const titleRow = document.createElement('div');
142
- titleRow.className = 'action-title-row';
143
- header.appendChild(titleRow);
144
-
145
- const title = document.createElement('h2');
146
- title.className = 'action-title';
147
- title.textContent = `${text(action.id)}: ${text(action.title, 'Untitled action')}`;
148
- titleRow.appendChild(title);
149
- appendPill(titleRow, text(action.status), 'status');
150
-
151
- const meta = document.createElement('div');
152
- meta.className = 'meta-row';
153
- header.appendChild(meta);
154
- appendPill(meta, text(action.kind, 'action'));
155
- appendPill(meta, text(action.primaryControl, 'none'));
156
- const provider = actionProvider(action);
157
- if (provider) appendPill(meta, provider);
158
-
159
- const target = document.createElement('section');
160
- card.appendChild(target);
161
- appendText(target, 'section-label', 'Target');
162
- appendText(target, 'target', `${text(action.target && action.target.label)} ${text(action.target && action.target.value, '')}`.trim());
163
-
164
- const readiness = document.createElement('section');
165
- card.appendChild(readiness);
166
- const readinessLabel = appendText(readiness, 'section-label', 'Readiness');
167
- const readinessItems = Array.isArray(action.readiness) ? action.readiness : [];
168
- readinessLabel.textContent = `Readiness (${readinessItems.length})`;
169
- if (readinessItems.length > 0) {
170
- const list = document.createElement('ul');
171
- list.className = 'readiness-list';
172
- readinessItems.slice(0, 5).forEach((item) => {
173
- const li = document.createElement('li');
174
- li.textContent = text(item);
175
- list.appendChild(li);
176
- });
177
- readiness.appendChild(list);
178
- } else {
179
- appendPill(readiness, 'No readiness evidence', 'ready');
180
- }
181
-
182
- const payload = Array.isArray(action.copyPayloads) ? action.copyPayloads[0] : undefined;
183
- const payloadSection = document.createElement('section');
184
- card.appendChild(payloadSection);
185
- appendText(payloadSection, 'section-label', payload ? `Copy: ${text(payload.label)}` : 'Copy payload');
186
- const commandBox = document.createElement('pre');
187
- commandBox.className = 'command-box';
188
- commandBox.textContent = payload ? payloadToText(payload.value) : 'No copy payload provided.';
189
- payloadSection.appendChild(commandBox);
190
-
191
- const footer = document.createElement('div');
192
- footer.className = 'action-footer';
193
- card.appendChild(footer);
194
- if (payload) appendButton(footer, `Copy ${text(payload.label, 'Payload')}`, payload.value);
195
- const verifyOutput = document.createElement('pre');
196
- verifyOutput.className = 'command-box';
197
- verifyOutput.hidden = true;
198
- if (action.verify && action.id) appendVerifyButton(footer, action, verifyOutput);
199
- if (action.resumeInstruction) appendButton(footer, 'Copy Resume', action.resumeInstruction);
200
- card.appendChild(verifyOutput);
201
-
202
- return card;
203
- }
204
-
205
- function renderState(state) {
206
- clearChildren(nodes.actions);
207
- nodes.message.hidden = true;
208
- nodes.message.textContent = '';
209
-
210
- nodes.artifactPath.textContent = text(state.artifactPath);
211
- if (!state.ok) {
212
- nodes.gateStatus.textContent = text(state.reason, 'Unavailable');
213
- nodes.actionCount.textContent = '0';
214
- nodes.generatedAt.textContent = '-';
215
- nodes.message.textContent = text(state.message, 'Unable to load VibeRaven actions.');
216
- nodes.message.hidden = false;
217
- return;
218
- }
219
-
220
- nodes.gateStatus.textContent = text(state.gateStatus);
221
- nodes.actionCount.textContent = String(state.actions.length);
222
- nodes.generatedAt.textContent = text(state.generatedAt);
223
- state.actions.forEach((action) => nodes.actions.appendChild(renderAction(action)));
224
- }
225
-
226
- async function refreshActions() {
227
- nodes.refreshButton.disabled = true;
228
- nodes.refreshButton.textContent = 'Refreshing';
229
- try {
230
- const response = await fetch(endpoint, { headers: { Accept: 'application/json' } });
231
- const state = await response.json();
232
- renderState(state);
233
- } catch (error) {
234
- clearChildren(nodes.actions);
235
- nodes.message.textContent = error instanceof Error ? error.message : 'Unable to load VibeRaven actions.';
236
- nodes.message.hidden = false;
237
- } finally {
238
- nodes.refreshButton.disabled = false;
239
- nodes.refreshButton.textContent = 'Refresh';
240
- }
241
- }
242
-
243
- nodes.refreshButton.addEventListener('click', refreshActions);
244
-
245
- if ('EventSource' in window) {
246
- const events = new EventSource('/api/events');
247
- events.addEventListener('actions', refreshActions);
248
- events.addEventListener('error', () => {});
249
- }
250
-
251
- refreshActions();
1
+ const endpoint = window.__VIBERAVEN_ACTIONS_ENDPOINT__ || '/api/actions';
2
+ const commandEndpoint = '/api/command';
3
+
4
+ const nodes = {
5
+ actions: document.getElementById('actions'),
6
+ actionCount: document.getElementById('actionCount'),
7
+ artifactPath: document.getElementById('artifactPath'),
8
+ gateStatus: document.getElementById('gateStatus'),
9
+ generatedAt: document.getElementById('generatedAt'),
10
+ message: document.getElementById('message'),
11
+ refreshButton: document.getElementById('refreshButton'),
12
+ };
13
+
14
+ function text(value, fallback = '-') {
15
+ if (value === undefined || value === null || value === '') return fallback;
16
+ return String(value);
17
+ }
18
+
19
+ function payloadToText(value) {
20
+ if (Array.isArray(value)) return value.join('\n');
21
+ if (value && typeof value === 'object') return JSON.stringify(value, null, 2);
22
+ return text(value, '');
23
+ }
24
+
25
+ async function copyPayload(value, button) {
26
+ const content = payloadToText(value);
27
+ const previous = button ? button.textContent : '';
28
+
29
+ try {
30
+ await navigator.clipboard.writeText(content);
31
+ } catch {
32
+ if (!button) return;
33
+
34
+ button.textContent = 'Copy failed';
35
+ window.setTimeout(() => {
36
+ button.textContent = previous;
37
+ }, 1200);
38
+ return;
39
+ }
40
+
41
+ if (!button) return;
42
+
43
+ button.textContent = 'Copied';
44
+ button.disabled = true;
45
+ window.setTimeout(() => {
46
+ button.textContent = previous;
47
+ button.disabled = false;
48
+ }, 900);
49
+ }
50
+
51
+ function clearChildren(node) {
52
+ while (node.firstChild) node.removeChild(node.firstChild);
53
+ }
54
+
55
+ function appendText(parent, className, value) {
56
+ const node = document.createElement('p');
57
+ node.className = className;
58
+ node.textContent = value;
59
+ parent.appendChild(node);
60
+ return node;
61
+ }
62
+
63
+ function appendPill(parent, value, className = '') {
64
+ const pill = document.createElement('span');
65
+ pill.className = `pill ${className}`.trim();
66
+ pill.textContent = value;
67
+ parent.appendChild(pill);
68
+ return pill;
69
+ }
70
+
71
+ function appendButton(parent, label, value) {
72
+ const button = document.createElement('button');
73
+ button.className = 'button secondary';
74
+ button.type = 'button';
75
+ button.textContent = label;
76
+ button.addEventListener('click', () => copyPayload(value, button));
77
+ parent.appendChild(button);
78
+ return button;
79
+ }
80
+
81
+ function appendVerifyButton(parent, action, output) {
82
+ const button = document.createElement('button');
83
+ button.className = 'button secondary';
84
+ button.type = 'button';
85
+ const token = window.__VIBERAVEN_TOKEN__ || '';
86
+ button.textContent = token ? 'Verify' : 'Verify unavailable';
87
+ button.disabled = !token;
88
+ button.addEventListener('click', async () => {
89
+ if (!token) return;
90
+
91
+ const previous = button.textContent;
92
+ button.disabled = true;
93
+ button.textContent = 'Verifying';
94
+ output.hidden = false;
95
+ output.textContent = 'Running verification...';
96
+
97
+ try {
98
+ const response = await fetch(commandEndpoint, {
99
+ method: 'POST',
100
+ headers: {
101
+ 'Content-Type': 'application/json',
102
+ Authorization: `Bearer ${window.__VIBERAVEN_TOKEN__ || ''}`,
103
+ },
104
+ body: JSON.stringify({ type: 'verify-action', actionId: action.id }),
105
+ });
106
+ const body = await response.json();
107
+ if (!response.ok || !body.ok) {
108
+ output.textContent = body && body.error ? body.error : 'Verification failed.';
109
+ return;
110
+ }
111
+
112
+ const result = body.result || {};
113
+ const outputParts = [];
114
+ outputParts.push(`Exit code: ${text(result.exitCode, 'unknown')}`);
115
+ if (result.stdout) outputParts.push(result.stdout);
116
+ if (result.stderr) outputParts.push(result.stderr);
117
+ output.textContent = outputParts.join('\n\n');
118
+ } catch (error) {
119
+ output.textContent = error instanceof Error ? error.message : 'Verification failed.';
120
+ } finally {
121
+ button.disabled = false;
122
+ button.textContent = previous;
123
+ }
124
+ });
125
+ parent.appendChild(button);
126
+ return button;
127
+ }
128
+
129
+ function actionProvider(action) {
130
+ return action.provider || (action.target && action.target.provider) || '';
131
+ }
132
+
133
+ function renderAction(action) {
134
+ const card = document.createElement('article');
135
+ card.className = 'action-card';
136
+
137
+ const header = document.createElement('div');
138
+ header.className = 'action-header';
139
+ card.appendChild(header);
140
+
141
+ const titleRow = document.createElement('div');
142
+ titleRow.className = 'action-title-row';
143
+ header.appendChild(titleRow);
144
+
145
+ const title = document.createElement('h2');
146
+ title.className = 'action-title';
147
+ title.textContent = `${text(action.id)}: ${text(action.title, 'Untitled action')}`;
148
+ titleRow.appendChild(title);
149
+ appendPill(titleRow, text(action.status), 'status');
150
+
151
+ const meta = document.createElement('div');
152
+ meta.className = 'meta-row';
153
+ header.appendChild(meta);
154
+ appendPill(meta, text(action.kind, 'action'));
155
+ appendPill(meta, text(action.primaryControl, 'none'));
156
+ const provider = actionProvider(action);
157
+ if (provider) appendPill(meta, provider);
158
+
159
+ const target = document.createElement('section');
160
+ card.appendChild(target);
161
+ appendText(target, 'section-label', 'Target');
162
+ appendText(target, 'target', `${text(action.target && action.target.label)} ${text(action.target && action.target.value, '')}`.trim());
163
+
164
+ const readiness = document.createElement('section');
165
+ card.appendChild(readiness);
166
+ const readinessLabel = appendText(readiness, 'section-label', 'Readiness');
167
+ const readinessItems = Array.isArray(action.readiness) ? action.readiness : [];
168
+ readinessLabel.textContent = `Readiness (${readinessItems.length})`;
169
+ if (readinessItems.length > 0) {
170
+ const list = document.createElement('ul');
171
+ list.className = 'readiness-list';
172
+ readinessItems.slice(0, 5).forEach((item) => {
173
+ const li = document.createElement('li');
174
+ li.textContent = text(item);
175
+ list.appendChild(li);
176
+ });
177
+ readiness.appendChild(list);
178
+ } else {
179
+ appendPill(readiness, 'No readiness evidence', 'ready');
180
+ }
181
+
182
+ const payload = Array.isArray(action.copyPayloads) ? action.copyPayloads[0] : undefined;
183
+ const payloadSection = document.createElement('section');
184
+ card.appendChild(payloadSection);
185
+ appendText(payloadSection, 'section-label', payload ? `Copy: ${text(payload.label)}` : 'Copy payload');
186
+ const commandBox = document.createElement('pre');
187
+ commandBox.className = 'command-box';
188
+ commandBox.textContent = payload ? payloadToText(payload.value) : 'No copy payload provided.';
189
+ payloadSection.appendChild(commandBox);
190
+
191
+ const footer = document.createElement('div');
192
+ footer.className = 'action-footer';
193
+ card.appendChild(footer);
194
+ if (payload) appendButton(footer, `Copy ${text(payload.label, 'Payload')}`, payload.value);
195
+ const verifyOutput = document.createElement('pre');
196
+ verifyOutput.className = 'command-box';
197
+ verifyOutput.hidden = true;
198
+ if (action.verify && action.id) appendVerifyButton(footer, action, verifyOutput);
199
+ if (action.resumeInstruction) appendButton(footer, 'Copy Resume', action.resumeInstruction);
200
+ card.appendChild(verifyOutput);
201
+
202
+ return card;
203
+ }
204
+
205
+ function renderState(state) {
206
+ clearChildren(nodes.actions);
207
+ nodes.message.hidden = true;
208
+ nodes.message.textContent = '';
209
+
210
+ nodes.artifactPath.textContent = text(state.artifactPath);
211
+ if (!state.ok) {
212
+ nodes.gateStatus.textContent = text(state.reason, 'Unavailable');
213
+ nodes.actionCount.textContent = '0';
214
+ nodes.generatedAt.textContent = '-';
215
+ nodes.message.textContent = text(state.message, 'Unable to load VibeRaven actions.');
216
+ nodes.message.hidden = false;
217
+ return;
218
+ }
219
+
220
+ nodes.gateStatus.textContent = text(state.gateStatus);
221
+ nodes.actionCount.textContent = String(state.actions.length);
222
+ nodes.generatedAt.textContent = text(state.generatedAt);
223
+ state.actions.forEach((action) => nodes.actions.appendChild(renderAction(action)));
224
+ }
225
+
226
+ async function refreshActions() {
227
+ nodes.refreshButton.disabled = true;
228
+ nodes.refreshButton.textContent = 'Refreshing';
229
+ try {
230
+ const response = await fetch(endpoint, { headers: { Accept: 'application/json' } });
231
+ const state = await response.json();
232
+ renderState(state);
233
+ } catch (error) {
234
+ clearChildren(nodes.actions);
235
+ nodes.message.textContent = error instanceof Error ? error.message : 'Unable to load VibeRaven actions.';
236
+ nodes.message.hidden = false;
237
+ } finally {
238
+ nodes.refreshButton.disabled = false;
239
+ nodes.refreshButton.textContent = 'Refresh';
240
+ }
241
+ }
242
+
243
+ nodes.refreshButton.addEventListener('click', refreshActions);
244
+
245
+ if ('EventSource' in window) {
246
+ const events = new EventSource('/api/events');
247
+ events.addEventListener('actions', refreshActions);
248
+ events.addEventListener('error', () => {});
249
+ }
250
+
251
+ refreshActions();
@@ -1,46 +1,46 @@
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" />
6
- <title>VibeRaven Console</title>
7
- <link rel="stylesheet" href="/styles.css" />
8
- <script>
9
- window.__VIBERAVEN_ACTIONS_ENDPOINT__ = "/api/actions";
10
- </script>
11
- <script defer src="/app.js"></script>
12
- </head>
13
- <body>
14
- <main class="console-shell">
15
- <header class="topbar">
16
- <div>
17
- <p class="eyebrow">Local readiness console</p>
18
- <h1>VibeRaven Console</h1>
19
- </div>
20
- <button id="refreshButton" class="button" type="button">Refresh</button>
21
- </header>
22
-
23
- <section class="summary-strip" aria-label="Console summary">
24
- <div>
25
- <span class="summary-label">Gate</span>
26
- <strong id="gateStatus">Loading</strong>
27
- </div>
28
- <div>
29
- <span class="summary-label">Actions</span>
30
- <strong id="actionCount">0</strong>
31
- </div>
32
- <div>
33
- <span class="summary-label">Generated</span>
34
- <strong id="generatedAt">-</strong>
35
- </div>
36
- <div>
37
- <span class="summary-label">Artifact</span>
38
- <strong id="artifactPath">-</strong>
39
- </div>
40
- </section>
41
-
42
- <section id="message" class="message" hidden></section>
43
- <section id="actions" class="actions-grid" aria-live="polite"></section>
44
- </main>
45
- </body>
46
- </html>
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" />
6
+ <title>VibeRaven Console</title>
7
+ <link rel="stylesheet" href="/styles.css" />
8
+ <script>
9
+ window.__VIBERAVEN_ACTIONS_ENDPOINT__ = "/api/actions";
10
+ </script>
11
+ <script defer src="/app.js"></script>
12
+ </head>
13
+ <body>
14
+ <main class="console-shell">
15
+ <header class="topbar">
16
+ <div>
17
+ <p class="eyebrow">Local readiness console</p>
18
+ <h1>VibeRaven Console</h1>
19
+ </div>
20
+ <button id="refreshButton" class="button" type="button">Refresh</button>
21
+ </header>
22
+
23
+ <section class="summary-strip" aria-label="Console summary">
24
+ <div>
25
+ <span class="summary-label">Gate</span>
26
+ <strong id="gateStatus">Loading</strong>
27
+ </div>
28
+ <div>
29
+ <span class="summary-label">Actions</span>
30
+ <strong id="actionCount">0</strong>
31
+ </div>
32
+ <div>
33
+ <span class="summary-label">Generated</span>
34
+ <strong id="generatedAt">-</strong>
35
+ </div>
36
+ <div>
37
+ <span class="summary-label">Artifact</span>
38
+ <strong id="artifactPath">-</strong>
39
+ </div>
40
+ </section>
41
+
42
+ <section id="message" class="message" hidden></section>
43
+ <section id="actions" class="actions-grid" aria-live="polite"></section>
44
+ </main>
45
+ </body>
46
+ </html>