@viberaven/cli 1.1.9 → 1.1.10
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/dist/cli.js +1324 -288
- package/dist/extension-icon.png +0 -0
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -51,6 +51,10 @@ function renderLocalUiHtml() {
|
|
|
51
51
|
--line-strong: #d0d5dd;
|
|
52
52
|
--orange: #ff7a00;
|
|
53
53
|
--orange-soft: #fff2e5;
|
|
54
|
+
--purple: #7c3aed;
|
|
55
|
+
--purple-soft: #f3edff;
|
|
56
|
+
--purple-line: #ddd0ff;
|
|
57
|
+
--mint-panel: #f6fffb;
|
|
54
58
|
--green: #24b26b;
|
|
55
59
|
--green-soft: #e9f8f1;
|
|
56
60
|
--red: #e11d1d;
|
|
@@ -58,7 +62,8 @@ function renderLocalUiHtml() {
|
|
|
58
62
|
--black-button: #071018;
|
|
59
63
|
--radius: 8px;
|
|
60
64
|
color-scheme: light;
|
|
61
|
-
|
|
65
|
+
--mono: ui-monospace, SFMono-Regular, "SF Mono", Consolas, "Liberation Mono", monospace;
|
|
66
|
+
font-family: "Geist", "Satoshi", ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
|
62
67
|
}
|
|
63
68
|
* { box-sizing: border-box; }
|
|
64
69
|
html, body { min-height: 100%; }
|
|
@@ -66,8 +71,11 @@ function renderLocalUiHtml() {
|
|
|
66
71
|
margin: 0;
|
|
67
72
|
background: var(--canvas);
|
|
68
73
|
color: var(--ink);
|
|
69
|
-
|
|
74
|
+
height: 100dvh;
|
|
70
75
|
letter-spacing: 0;
|
|
76
|
+
display: grid;
|
|
77
|
+
grid-template-rows: auto minmax(0, 1fr) auto;
|
|
78
|
+
overflow: hidden;
|
|
71
79
|
}
|
|
72
80
|
button, input, textarea {
|
|
73
81
|
font: inherit;
|
|
@@ -92,57 +100,54 @@ function renderLocalUiHtml() {
|
|
|
92
100
|
}
|
|
93
101
|
button.primary:hover { background: #111a22; }
|
|
94
102
|
.topbar {
|
|
95
|
-
height:
|
|
103
|
+
height: 68px;
|
|
96
104
|
border-bottom: 1px solid var(--line);
|
|
97
105
|
background: rgba(251, 251, 250, 0.96);
|
|
98
106
|
display: grid;
|
|
99
|
-
grid-template-columns: minmax(
|
|
107
|
+
grid-template-columns: minmax(340px, 1fr) minmax(240px, 310px) auto;
|
|
100
108
|
gap: 18px;
|
|
101
109
|
align-items: center;
|
|
102
|
-
padding: 0
|
|
110
|
+
padding: 0 26px;
|
|
103
111
|
position: sticky;
|
|
104
112
|
top: 0;
|
|
113
|
+
z-index: 2;
|
|
114
|
+
box-shadow: 0 1px 0 rgba(17, 20, 23, 0.02);
|
|
105
115
|
}
|
|
106
116
|
.brand-lockup {
|
|
107
117
|
display: flex;
|
|
108
118
|
align-items: center;
|
|
109
|
-
gap:
|
|
119
|
+
gap: 14px;
|
|
110
120
|
min-width: 0;
|
|
111
121
|
}
|
|
112
122
|
.brand-lockup strong {
|
|
113
|
-
font-size:
|
|
123
|
+
font-size: 28px;
|
|
114
124
|
line-height: 1;
|
|
115
125
|
white-space: nowrap;
|
|
116
126
|
}
|
|
117
127
|
.raven-mark {
|
|
118
|
-
width:
|
|
119
|
-
height:
|
|
120
|
-
display: inline-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
transform: skewX(-18deg) rotate(-8deg);
|
|
128
|
+
width: 58px;
|
|
129
|
+
height: 46px;
|
|
130
|
+
display: inline-grid;
|
|
131
|
+
place-items: center;
|
|
132
|
+
flex: 0 0 auto;
|
|
133
|
+
overflow: visible;
|
|
125
134
|
}
|
|
126
|
-
.raven-mark
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
height: 5px;
|
|
133
|
-
border-radius: 999px;
|
|
134
|
-
background: var(--orange);
|
|
135
|
-
transform: rotate(-28deg);
|
|
135
|
+
.raven-mark img {
|
|
136
|
+
width: 54px;
|
|
137
|
+
height: 54px;
|
|
138
|
+
object-fit: contain;
|
|
139
|
+
display: block;
|
|
140
|
+
transform: translateY(1px);
|
|
136
141
|
}
|
|
137
142
|
.tagline {
|
|
138
|
-
border: 1px solid
|
|
139
|
-
border-radius:
|
|
140
|
-
padding:
|
|
141
|
-
color: var(--
|
|
142
|
-
font-size:
|
|
143
|
-
font-weight:
|
|
143
|
+
border: 1px solid #eef0f4;
|
|
144
|
+
border-radius: 10px;
|
|
145
|
+
padding: 9px 18px;
|
|
146
|
+
color: var(--muted-strong);
|
|
147
|
+
font-size: 14px;
|
|
148
|
+
font-weight: 600;
|
|
144
149
|
white-space: nowrap;
|
|
145
|
-
background:
|
|
150
|
+
background: #f4f6f8;
|
|
146
151
|
}
|
|
147
152
|
.project-picker {
|
|
148
153
|
display: flex;
|
|
@@ -156,16 +161,23 @@ function renderLocalUiHtml() {
|
|
|
156
161
|
background: var(--surface);
|
|
157
162
|
color: var(--ink);
|
|
158
163
|
border-radius: var(--radius);
|
|
159
|
-
padding: 9px
|
|
164
|
+
padding: 9px 14px;
|
|
165
|
+
cursor: pointer;
|
|
166
|
+
}
|
|
167
|
+
.project-picker:hover {
|
|
168
|
+
border-color: #cdd5df;
|
|
169
|
+
box-shadow: 0 10px 24px rgba(15, 23, 42, 0.06);
|
|
160
170
|
}
|
|
161
171
|
.project-picker::before {
|
|
162
172
|
content: "";
|
|
163
|
-
width:
|
|
164
|
-
height:
|
|
173
|
+
width: 18px;
|
|
174
|
+
height: 13px;
|
|
165
175
|
border: 1.6px solid currentColor;
|
|
166
|
-
border-radius:
|
|
176
|
+
border-radius: 3px;
|
|
167
177
|
display: inline-block;
|
|
168
|
-
|
|
178
|
+
clip-path: polygon(0 28%, 36% 28%, 43% 8%, 100% 8%, 100% 100%, 0 100%);
|
|
179
|
+
background: linear-gradient(180deg, rgba(17, 20, 23, 0.03), rgba(17, 20, 23, 0.08));
|
|
180
|
+
flex: 0 0 auto;
|
|
169
181
|
}
|
|
170
182
|
.project-picker::after {
|
|
171
183
|
content: "";
|
|
@@ -186,14 +198,35 @@ function renderLocalUiHtml() {
|
|
|
186
198
|
.top-actions {
|
|
187
199
|
display: flex;
|
|
188
200
|
align-items: center;
|
|
189
|
-
gap:
|
|
201
|
+
gap: 12px;
|
|
190
202
|
justify-content: flex-end;
|
|
191
203
|
}
|
|
204
|
+
.top-divider {
|
|
205
|
+
width: 1px;
|
|
206
|
+
height: 30px;
|
|
207
|
+
background: var(--line);
|
|
208
|
+
margin: 0 4px 0 6px;
|
|
209
|
+
flex: 0 0 auto;
|
|
210
|
+
}
|
|
192
211
|
.status-button {
|
|
193
212
|
display: inline-flex;
|
|
194
213
|
align-items: center;
|
|
195
214
|
gap: 9px;
|
|
196
215
|
white-space: nowrap;
|
|
216
|
+
min-height: 44px;
|
|
217
|
+
padding-inline: 16px;
|
|
218
|
+
}
|
|
219
|
+
.live-status {
|
|
220
|
+
min-height: 44px;
|
|
221
|
+
display: inline-flex;
|
|
222
|
+
align-items: center;
|
|
223
|
+
gap: 9px;
|
|
224
|
+
white-space: nowrap;
|
|
225
|
+
border: 1px solid var(--line);
|
|
226
|
+
background: var(--surface);
|
|
227
|
+
border-radius: var(--radius);
|
|
228
|
+
padding: 9px 16px;
|
|
229
|
+
color: var(--ink);
|
|
197
230
|
}
|
|
198
231
|
.dot {
|
|
199
232
|
width: 8px;
|
|
@@ -206,53 +239,96 @@ function renderLocalUiHtml() {
|
|
|
206
239
|
.dot.warn { background: var(--orange); }
|
|
207
240
|
.dot.bad { background: var(--red); }
|
|
208
241
|
.icon-button {
|
|
209
|
-
width:
|
|
210
|
-
height:
|
|
242
|
+
width: 44px;
|
|
243
|
+
height: 44px;
|
|
211
244
|
padding: 0;
|
|
212
245
|
display: grid;
|
|
213
246
|
place-items: center;
|
|
214
|
-
color:
|
|
247
|
+
color: #344054;
|
|
248
|
+
border: 1px solid var(--line);
|
|
249
|
+
background: var(--surface);
|
|
250
|
+
border-radius: 999px;
|
|
215
251
|
}
|
|
252
|
+
.icon-button:hover { background: var(--surface-soft); }
|
|
216
253
|
.icon-button::before {
|
|
217
|
-
content:
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
254
|
+
content: none;
|
|
255
|
+
}
|
|
256
|
+
.icon-button svg {
|
|
257
|
+
width: 22px;
|
|
258
|
+
height: 22px;
|
|
259
|
+
display: block;
|
|
260
|
+
fill: none;
|
|
261
|
+
stroke: currentColor;
|
|
262
|
+
stroke-width: 1.9;
|
|
263
|
+
stroke-linecap: round;
|
|
264
|
+
stroke-linejoin: round;
|
|
223
265
|
}
|
|
224
266
|
.verify-button {
|
|
225
|
-
min-width:
|
|
267
|
+
min-width: 128px;
|
|
268
|
+
height: 44px;
|
|
269
|
+
display: inline-flex;
|
|
270
|
+
align-items: center;
|
|
271
|
+
justify-content: center;
|
|
272
|
+
gap: 10px;
|
|
226
273
|
font-size: 15px;
|
|
274
|
+
border-radius: 9px;
|
|
275
|
+
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 8px 18px rgba(7, 16, 24, 0.12);
|
|
276
|
+
}
|
|
277
|
+
.verify-button svg {
|
|
278
|
+
width: 19px;
|
|
279
|
+
height: 19px;
|
|
280
|
+
display: block;
|
|
281
|
+
fill: none;
|
|
282
|
+
stroke: currentColor;
|
|
283
|
+
stroke-width: 1.8;
|
|
284
|
+
stroke-linecap: round;
|
|
285
|
+
stroke-linejoin: round;
|
|
227
286
|
}
|
|
228
287
|
.shell {
|
|
229
288
|
display: grid;
|
|
230
|
-
grid-template-columns:
|
|
231
|
-
min-height:
|
|
289
|
+
grid-template-columns: 360px minmax(600px, 1fr) 520px;
|
|
290
|
+
min-height: 0;
|
|
291
|
+
height: 100%;
|
|
292
|
+
overflow: hidden;
|
|
232
293
|
}
|
|
233
294
|
.rail {
|
|
234
295
|
border-right: 1px solid var(--line);
|
|
235
|
-
padding:
|
|
296
|
+
padding: 36px 28px 28px;
|
|
236
297
|
background: var(--canvas);
|
|
237
298
|
overflow: auto;
|
|
299
|
+
min-height: 0;
|
|
238
300
|
}
|
|
239
301
|
.rail-title {
|
|
240
|
-
margin: 0 0
|
|
241
|
-
color: var(--
|
|
242
|
-
font-size:
|
|
243
|
-
font-weight:
|
|
244
|
-
text-transform:
|
|
302
|
+
margin: 0 0 16px;
|
|
303
|
+
color: var(--ink);
|
|
304
|
+
font-size: 16px;
|
|
305
|
+
font-weight: 750;
|
|
306
|
+
text-transform: none;
|
|
307
|
+
display: flex;
|
|
308
|
+
align-items: center;
|
|
309
|
+
gap: 10px;
|
|
310
|
+
}
|
|
311
|
+
.rail-title::before {
|
|
312
|
+
content: "";
|
|
313
|
+
width: 17px;
|
|
314
|
+
height: 15px;
|
|
315
|
+
display: inline-block;
|
|
316
|
+
background:
|
|
317
|
+
linear-gradient(var(--muted) 0 0) 8px 2px / 9px 1.5px no-repeat,
|
|
318
|
+
linear-gradient(var(--muted) 0 0) 8px 7px / 9px 1.5px no-repeat,
|
|
319
|
+
linear-gradient(var(--muted) 0 0) 8px 12px / 9px 1.5px no-repeat,
|
|
320
|
+
radial-gradient(circle, var(--muted) 1.7px, transparent 2px) 0 0 / 7px 5px repeat-y;
|
|
245
321
|
}
|
|
246
322
|
.search-box {
|
|
247
|
-
height:
|
|
323
|
+
height: 44px;
|
|
248
324
|
border: 1px solid var(--line);
|
|
249
325
|
background: var(--surface);
|
|
250
|
-
border-radius:
|
|
326
|
+
border-radius: 9px;
|
|
251
327
|
display: flex;
|
|
252
328
|
align-items: center;
|
|
253
329
|
gap: 9px;
|
|
254
330
|
padding: 0 10px;
|
|
255
|
-
margin-bottom:
|
|
331
|
+
margin-bottom: 18px;
|
|
256
332
|
}
|
|
257
333
|
.search-box:focus-within {
|
|
258
334
|
border-color: var(--orange);
|
|
@@ -288,52 +364,47 @@ function renderLocalUiHtml() {
|
|
|
288
364
|
}
|
|
289
365
|
.provider-list {
|
|
290
366
|
display: grid;
|
|
291
|
-
gap:
|
|
367
|
+
gap: 10px;
|
|
292
368
|
}
|
|
293
369
|
.provider-button {
|
|
294
370
|
width: 100%;
|
|
295
|
-
min-height:
|
|
371
|
+
min-height: 64px;
|
|
296
372
|
display: grid;
|
|
297
|
-
grid-template-columns:
|
|
298
|
-
gap:
|
|
373
|
+
grid-template-columns: 42px minmax(0, 1fr) 8px;
|
|
374
|
+
gap: 14px;
|
|
299
375
|
align-items: center;
|
|
300
376
|
text-align: left;
|
|
301
|
-
padding:
|
|
377
|
+
padding: 11px 14px;
|
|
302
378
|
background: var(--surface);
|
|
303
379
|
border-color: var(--line);
|
|
380
|
+
border-radius: 10px;
|
|
381
|
+
box-shadow: 0 1px 2px rgba(16, 24, 40, 0.02);
|
|
304
382
|
}
|
|
305
383
|
.provider-button.is-selected {
|
|
306
384
|
border-color: var(--orange);
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
.provider-button::after {
|
|
310
|
-
content: "";
|
|
311
|
-
width: 7px;
|
|
312
|
-
height: 7px;
|
|
313
|
-
border-right: 1.5px solid var(--muted-strong);
|
|
314
|
-
border-bottom: 1.5px solid var(--muted-strong);
|
|
315
|
-
transform: rotate(-45deg);
|
|
385
|
+
background: #fffaf5;
|
|
386
|
+
box-shadow: 0 8px 22px rgba(255, 122, 0, 0.08);
|
|
316
387
|
}
|
|
317
388
|
.provider-icon {
|
|
318
|
-
width:
|
|
319
|
-
height:
|
|
389
|
+
width: 42px;
|
|
390
|
+
height: 42px;
|
|
320
391
|
display: grid;
|
|
321
392
|
place-items: center;
|
|
322
393
|
border: 1px solid var(--line);
|
|
323
|
-
border-radius:
|
|
394
|
+
border-radius: 9px;
|
|
324
395
|
background: var(--surface);
|
|
325
396
|
color: var(--ink);
|
|
326
397
|
overflow: hidden;
|
|
327
398
|
}
|
|
328
399
|
.provider-icon svg, .provider-icon img {
|
|
329
|
-
width:
|
|
330
|
-
height:
|
|
400
|
+
width: 30px;
|
|
401
|
+
height: 30px;
|
|
331
402
|
display: block;
|
|
332
403
|
}
|
|
333
404
|
.provider-name { min-width: 0; }
|
|
334
405
|
.provider-name strong {
|
|
335
406
|
display: block;
|
|
336
|
-
font-size:
|
|
407
|
+
font-size: 14px;
|
|
337
408
|
line-height: 1.2;
|
|
338
409
|
white-space: nowrap;
|
|
339
410
|
overflow: hidden;
|
|
@@ -341,7 +412,7 @@ function renderLocalUiHtml() {
|
|
|
341
412
|
}
|
|
342
413
|
.state {
|
|
343
414
|
display: inline-flex;
|
|
344
|
-
margin-top:
|
|
415
|
+
margin-top: 6px;
|
|
345
416
|
font-size: 12px;
|
|
346
417
|
line-height: 1;
|
|
347
418
|
color: var(--muted);
|
|
@@ -360,11 +431,12 @@ function renderLocalUiHtml() {
|
|
|
360
431
|
.provider-dot[data-state="repo_evidence_found"], .provider-dot[data-state="live_verified"] { background: var(--green); }
|
|
361
432
|
.provider-dot[data-state="blocked"], .provider-dot[data-state="error"] { background: var(--red); }
|
|
362
433
|
.run-local-card {
|
|
363
|
-
margin-top:
|
|
434
|
+
margin-top: 20px;
|
|
364
435
|
border: 1px solid var(--line);
|
|
365
|
-
border-radius:
|
|
436
|
+
border-radius: 10px;
|
|
366
437
|
background: var(--surface);
|
|
367
|
-
padding:
|
|
438
|
+
padding: 18px;
|
|
439
|
+
box-shadow: 0 1px 2px rgba(16, 24, 40, 0.02);
|
|
368
440
|
}
|
|
369
441
|
.run-local-card h2 {
|
|
370
442
|
margin: 0 0 6px;
|
|
@@ -385,7 +457,7 @@ function renderLocalUiHtml() {
|
|
|
385
457
|
color: #ffffff;
|
|
386
458
|
border-radius: 7px;
|
|
387
459
|
padding: 11px 12px;
|
|
388
|
-
font: 13px/1.2
|
|
460
|
+
font: 13px/1.2 var(--mono);
|
|
389
461
|
overflow: hidden;
|
|
390
462
|
}
|
|
391
463
|
.command-pill span {
|
|
@@ -394,39 +466,40 @@ function renderLocalUiHtml() {
|
|
|
394
466
|
white-space: nowrap;
|
|
395
467
|
}
|
|
396
468
|
.main {
|
|
397
|
-
padding:
|
|
469
|
+
padding: 42px 48px 40px;
|
|
398
470
|
overflow: auto;
|
|
399
471
|
background: var(--canvas);
|
|
472
|
+
min-height: 0;
|
|
400
473
|
}
|
|
401
474
|
.provider-header {
|
|
402
475
|
display: grid;
|
|
403
|
-
grid-template-columns: minmax(0, 1fr)
|
|
476
|
+
grid-template-columns: minmax(0, 1fr);
|
|
404
477
|
gap: 18px;
|
|
405
478
|
align-items: center;
|
|
406
|
-
margin-bottom:
|
|
479
|
+
margin-bottom: 26px;
|
|
407
480
|
}
|
|
408
481
|
.provider-heading {
|
|
409
482
|
display: flex;
|
|
410
|
-
gap:
|
|
483
|
+
gap: 20px;
|
|
411
484
|
align-items: center;
|
|
412
485
|
min-width: 0;
|
|
413
486
|
}
|
|
414
487
|
.provider-heading .provider-icon {
|
|
415
|
-
width:
|
|
416
|
-
height:
|
|
488
|
+
width: 58px;
|
|
489
|
+
height: 58px;
|
|
417
490
|
border: 0;
|
|
418
491
|
background: transparent;
|
|
419
492
|
}
|
|
420
493
|
.provider-heading h1 {
|
|
421
494
|
margin: 0;
|
|
422
|
-
font-size:
|
|
495
|
+
font-size: 34px;
|
|
423
496
|
line-height: 1.15;
|
|
424
|
-
font-weight:
|
|
497
|
+
font-weight: 780;
|
|
425
498
|
}
|
|
426
499
|
.provider-heading p {
|
|
427
|
-
margin:
|
|
500
|
+
margin: 8px 0 0;
|
|
428
501
|
color: var(--muted-strong);
|
|
429
|
-
font-size:
|
|
502
|
+
font-size: 16px;
|
|
430
503
|
line-height: 1.35;
|
|
431
504
|
}
|
|
432
505
|
.secondary-action {
|
|
@@ -441,178 +514,346 @@ function renderLocalUiHtml() {
|
|
|
441
514
|
}
|
|
442
515
|
.path-list {
|
|
443
516
|
display: grid;
|
|
444
|
-
gap:
|
|
517
|
+
gap: 14px;
|
|
445
518
|
position: relative;
|
|
446
|
-
padding-left:
|
|
519
|
+
padding-left: 72px;
|
|
447
520
|
}
|
|
448
521
|
.path-list::before {
|
|
449
522
|
content: "";
|
|
450
523
|
position: absolute;
|
|
451
|
-
left:
|
|
452
|
-
top:
|
|
453
|
-
bottom:
|
|
524
|
+
left: 27px;
|
|
525
|
+
top: -16px;
|
|
526
|
+
bottom: 30px;
|
|
454
527
|
width: 1px;
|
|
455
|
-
background:
|
|
528
|
+
background: repeating-linear-gradient(to bottom, #f5b16b 0 9px, transparent 9px 18px);
|
|
456
529
|
}
|
|
457
530
|
.path-row {
|
|
531
|
+
width: 100%;
|
|
458
532
|
border: 1px solid var(--line);
|
|
459
|
-
border-radius:
|
|
533
|
+
border-radius: 12px;
|
|
460
534
|
background: var(--surface);
|
|
461
|
-
padding:
|
|
535
|
+
padding: 22px 24px;
|
|
462
536
|
display: grid;
|
|
463
|
-
grid-template-columns: minmax(0, 1fr) auto;
|
|
464
|
-
gap:
|
|
537
|
+
grid-template-columns: 48px minmax(0, 1fr) auto 18px;
|
|
538
|
+
gap: 18px;
|
|
539
|
+
align-items: center;
|
|
540
|
+
text-align: left;
|
|
465
541
|
position: relative;
|
|
466
|
-
margin-bottom:
|
|
542
|
+
margin-bottom: 0;
|
|
543
|
+
min-height: 90px;
|
|
544
|
+
box-shadow: 0 12px 28px rgba(16, 24, 40, 0.035);
|
|
467
545
|
}
|
|
468
546
|
.path-row::before {
|
|
469
|
-
content:
|
|
547
|
+
content: attr(data-step);
|
|
470
548
|
position: absolute;
|
|
471
|
-
left: -
|
|
472
|
-
top:
|
|
473
|
-
width:
|
|
474
|
-
height:
|
|
549
|
+
left: -72px;
|
|
550
|
+
top: 22px;
|
|
551
|
+
width: 40px;
|
|
552
|
+
height: 40px;
|
|
475
553
|
border: 1px solid var(--line-strong);
|
|
476
554
|
border-radius: 999px;
|
|
477
555
|
background: var(--surface);
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
top: 26px;
|
|
484
|
-
width: 6px;
|
|
485
|
-
height: 6px;
|
|
486
|
-
border-radius: 999px;
|
|
487
|
-
background: var(--muted);
|
|
556
|
+
display: grid;
|
|
557
|
+
place-items: center;
|
|
558
|
+
color: #344054;
|
|
559
|
+
font-size: 15px;
|
|
560
|
+
font-weight: 800;
|
|
488
561
|
}
|
|
489
562
|
.path-row.is-focused {
|
|
490
|
-
border-color:
|
|
491
|
-
box-shadow: 0
|
|
563
|
+
border-color: #ffb36b;
|
|
564
|
+
box-shadow: 0 14px 30px rgba(255, 122, 0, 0.09);
|
|
492
565
|
}
|
|
493
566
|
.path-row.is-focused::before {
|
|
494
567
|
border-color: var(--orange);
|
|
495
568
|
background: var(--orange-soft);
|
|
569
|
+
color: var(--orange);
|
|
570
|
+
}
|
|
571
|
+
.path-icon {
|
|
572
|
+
width: 44px;
|
|
573
|
+
height: 44px;
|
|
574
|
+
border-radius: 10px;
|
|
575
|
+
background: var(--surface-soft);
|
|
576
|
+
color: #344054;
|
|
577
|
+
display: grid;
|
|
578
|
+
place-items: center;
|
|
579
|
+
}
|
|
580
|
+
.path-row.is-focused .path-icon {
|
|
581
|
+
color: var(--orange);
|
|
582
|
+
background: var(--orange-soft);
|
|
583
|
+
}
|
|
584
|
+
.path-icon svg {
|
|
585
|
+
width: 23px;
|
|
586
|
+
height: 23px;
|
|
587
|
+
fill: none;
|
|
588
|
+
stroke: currentColor;
|
|
589
|
+
stroke-width: 2;
|
|
590
|
+
stroke-linecap: round;
|
|
591
|
+
stroke-linejoin: round;
|
|
496
592
|
}
|
|
497
|
-
.path-row.is-focused::after { background: var(--orange); }
|
|
498
593
|
.path-row strong {
|
|
499
|
-
font-size:
|
|
594
|
+
font-size: 15px;
|
|
500
595
|
line-height: 1.25;
|
|
501
596
|
}
|
|
502
597
|
.path-row p {
|
|
503
|
-
margin:
|
|
598
|
+
margin: 6px 0 0;
|
|
504
599
|
color: var(--muted-strong);
|
|
505
|
-
font-size:
|
|
600
|
+
font-size: 14px;
|
|
506
601
|
line-height: 1.4;
|
|
507
602
|
}
|
|
508
603
|
.path-state {
|
|
509
|
-
align-self:
|
|
510
|
-
border:
|
|
604
|
+
align-self: center;
|
|
605
|
+
border: 0;
|
|
511
606
|
border-radius: 999px;
|
|
512
|
-
padding:
|
|
607
|
+
padding: 7px 13px;
|
|
513
608
|
font-size: 12px;
|
|
514
609
|
line-height: 1;
|
|
515
610
|
color: var(--muted);
|
|
516
611
|
white-space: nowrap;
|
|
517
|
-
background: var(--surface);
|
|
612
|
+
background: var(--surface-soft);
|
|
613
|
+
}
|
|
614
|
+
.path-arrow {
|
|
615
|
+
color: var(--muted);
|
|
616
|
+
display: grid;
|
|
617
|
+
place-items: center;
|
|
618
|
+
}
|
|
619
|
+
.path-arrow svg {
|
|
620
|
+
width: 18px;
|
|
621
|
+
height: 18px;
|
|
622
|
+
stroke: currentColor;
|
|
623
|
+
stroke-width: 2;
|
|
624
|
+
fill: none;
|
|
625
|
+
stroke-linecap: round;
|
|
626
|
+
stroke-linejoin: round;
|
|
518
627
|
}
|
|
519
628
|
.path-state[data-state="ready"] { background: var(--green-soft); }
|
|
520
629
|
.path-state[data-state="needs_fix"], .path-state[data-state="needs_connect"] { background: var(--orange-soft); }
|
|
521
630
|
.path-state[data-state="blocked"] { background: var(--red-soft); }
|
|
522
631
|
.next-fix {
|
|
523
|
-
margin:
|
|
632
|
+
margin: 36px 0 0 0;
|
|
524
633
|
border: 1px solid var(--line);
|
|
525
|
-
border-radius:
|
|
634
|
+
border-radius: 14px;
|
|
526
635
|
background: var(--surface);
|
|
527
|
-
padding:
|
|
636
|
+
padding: 28px;
|
|
637
|
+
box-shadow: 0 12px 30px rgba(16, 24, 40, 0.035);
|
|
528
638
|
}
|
|
529
639
|
.next-fix h2 {
|
|
530
|
-
margin: 0 0
|
|
531
|
-
font-size:
|
|
640
|
+
margin: 0 0 4px;
|
|
641
|
+
font-size: 18px;
|
|
532
642
|
line-height: 1.2;
|
|
643
|
+
display: flex;
|
|
644
|
+
align-items: center;
|
|
645
|
+
gap: 12px;
|
|
533
646
|
}
|
|
534
|
-
.fix
|
|
535
|
-
|
|
536
|
-
|
|
647
|
+
.next-fix h2::before {
|
|
648
|
+
content: "";
|
|
649
|
+
width: 18px;
|
|
650
|
+
height: 18px;
|
|
651
|
+
display: inline-block;
|
|
652
|
+
background: var(--purple);
|
|
653
|
+
clip-path: polygon(45% 0, 82% 0, 62% 38%, 94% 38%, 32% 100%, 45% 56%, 10% 56%);
|
|
537
654
|
}
|
|
538
|
-
.fix-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
padding: 13px 0;
|
|
543
|
-
border-top: 1px solid var(--line);
|
|
655
|
+
.next-fix-subtitle {
|
|
656
|
+
margin: 0 0 22px;
|
|
657
|
+
color: var(--muted-strong);
|
|
658
|
+
font-size: 14px;
|
|
544
659
|
}
|
|
545
|
-
.
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
border
|
|
551
|
-
border:
|
|
660
|
+
.next-action-row {
|
|
661
|
+
display: grid;
|
|
662
|
+
grid-template-columns: 44px minmax(0, 1fr) auto;
|
|
663
|
+
gap: 16px;
|
|
664
|
+
align-items: center;
|
|
665
|
+
border: 1px solid var(--purple-line);
|
|
666
|
+
border-radius: 10px;
|
|
667
|
+
padding: 18px;
|
|
668
|
+
background: linear-gradient(100deg, #fbf8ff 0%, #ffffff 58%, #f9f7ff 100%);
|
|
669
|
+
}
|
|
670
|
+
.next-action-icon {
|
|
671
|
+
width: 40px;
|
|
672
|
+
height: 40px;
|
|
673
|
+
border-radius: 9px;
|
|
552
674
|
display: grid;
|
|
553
675
|
place-items: center;
|
|
554
|
-
color: var(--
|
|
555
|
-
background: var(--
|
|
556
|
-
font-size: 12px;
|
|
557
|
-
font-weight: 800;
|
|
676
|
+
color: var(--purple);
|
|
677
|
+
background: var(--purple-soft);
|
|
558
678
|
}
|
|
559
|
-
.
|
|
679
|
+
.next-action-icon svg {
|
|
680
|
+
width: 22px;
|
|
681
|
+
height: 22px;
|
|
682
|
+
fill: none;
|
|
683
|
+
stroke: currentColor;
|
|
684
|
+
stroke-width: 2;
|
|
685
|
+
stroke-linecap: round;
|
|
686
|
+
stroke-linejoin: round;
|
|
687
|
+
}
|
|
688
|
+
.next-action-row h3 {
|
|
560
689
|
margin: 0 0 6px;
|
|
561
|
-
font-size:
|
|
690
|
+
font-size: 14px;
|
|
562
691
|
line-height: 1.2;
|
|
563
692
|
}
|
|
564
|
-
.
|
|
693
|
+
.next-action-row p {
|
|
565
694
|
margin: 0;
|
|
566
695
|
color: var(--muted-strong);
|
|
567
696
|
font-size: 13px;
|
|
568
697
|
line-height: 1.45;
|
|
569
698
|
}
|
|
699
|
+
.open-guide-button {
|
|
700
|
+
display: inline-flex;
|
|
701
|
+
align-items: center;
|
|
702
|
+
gap: 8px;
|
|
703
|
+
border: 0;
|
|
704
|
+
background: var(--purple);
|
|
705
|
+
color: #ffffff;
|
|
706
|
+
border-radius: 8px;
|
|
707
|
+
padding: 10px 14px;
|
|
708
|
+
font-size: 13px;
|
|
709
|
+
font-weight: 750;
|
|
710
|
+
white-space: nowrap;
|
|
711
|
+
}
|
|
712
|
+
.open-guide-button:hover {
|
|
713
|
+
background: #6d28d9;
|
|
714
|
+
color: #ffffff;
|
|
715
|
+
}
|
|
570
716
|
.drawer {
|
|
571
717
|
border-left: 1px solid var(--line);
|
|
572
|
-
padding:
|
|
573
|
-
background: var(--
|
|
718
|
+
padding: 30px 38px 28px 30px;
|
|
719
|
+
background: var(--canvas);
|
|
574
720
|
overflow: auto;
|
|
721
|
+
min-height: 0;
|
|
722
|
+
display: grid;
|
|
723
|
+
align-content: start;
|
|
724
|
+
gap: 24px;
|
|
725
|
+
}
|
|
726
|
+
.prompt-panel, .quick-panel {
|
|
727
|
+
border: 1px solid var(--line);
|
|
728
|
+
border-radius: 14px;
|
|
729
|
+
background: var(--surface);
|
|
730
|
+
padding: 20px 24px;
|
|
731
|
+
box-shadow: 0 12px 30px rgba(16, 24, 40, 0.04);
|
|
732
|
+
}
|
|
733
|
+
.panel-heading {
|
|
734
|
+
display: flex;
|
|
735
|
+
align-items: start;
|
|
736
|
+
justify-content: space-between;
|
|
737
|
+
gap: 16px;
|
|
738
|
+
margin-bottom: 20px;
|
|
739
|
+
}
|
|
740
|
+
.panel-title {
|
|
741
|
+
display: flex;
|
|
742
|
+
align-items: center;
|
|
743
|
+
gap: 12px;
|
|
744
|
+
}
|
|
745
|
+
.panel-glyph {
|
|
746
|
+
color: #344054;
|
|
747
|
+
display: grid;
|
|
748
|
+
place-items: center;
|
|
749
|
+
width: 26px;
|
|
750
|
+
height: 26px;
|
|
751
|
+
}
|
|
752
|
+
.panel-glyph svg {
|
|
753
|
+
width: 24px;
|
|
754
|
+
height: 24px;
|
|
755
|
+
fill: none;
|
|
756
|
+
stroke: currentColor;
|
|
757
|
+
stroke-width: 1.9;
|
|
758
|
+
stroke-linecap: round;
|
|
759
|
+
stroke-linejoin: round;
|
|
760
|
+
}
|
|
761
|
+
.copy-small {
|
|
762
|
+
min-height: 36px;
|
|
763
|
+
display: inline-flex;
|
|
764
|
+
align-items: center;
|
|
765
|
+
gap: 8px;
|
|
766
|
+
border-radius: 9px;
|
|
767
|
+
padding: 8px 12px;
|
|
768
|
+
color: var(--muted-strong);
|
|
769
|
+
font-weight: 650;
|
|
575
770
|
}
|
|
576
771
|
.drawer h2 {
|
|
577
772
|
margin: 0;
|
|
578
|
-
font-size:
|
|
773
|
+
font-size: 18px;
|
|
579
774
|
line-height: 1.2;
|
|
580
775
|
}
|
|
581
776
|
.drawer p {
|
|
582
|
-
margin: 8px 0
|
|
777
|
+
margin: 8px 0 0;
|
|
583
778
|
color: var(--muted-strong);
|
|
584
|
-
font-size:
|
|
779
|
+
font-size: 14px;
|
|
585
780
|
line-height: 1.4;
|
|
586
781
|
}
|
|
587
782
|
.prompt-box {
|
|
783
|
+
position: absolute;
|
|
784
|
+
opacity: 0;
|
|
785
|
+
pointer-events: none;
|
|
786
|
+
width: 1px;
|
|
787
|
+
height: 1px;
|
|
788
|
+
}
|
|
789
|
+
.prompt-card {
|
|
588
790
|
width: 100%;
|
|
589
|
-
min-height: 330px;
|
|
590
|
-
resize: vertical;
|
|
591
791
|
border: 1px solid var(--line);
|
|
592
|
-
border-radius:
|
|
593
|
-
padding:
|
|
594
|
-
background: var(--
|
|
595
|
-
color:
|
|
596
|
-
font: 13px/1.
|
|
792
|
+
border-radius: 10px;
|
|
793
|
+
padding: 22px;
|
|
794
|
+
background: linear-gradient(135deg, var(--mint-panel), #ffffff 74%);
|
|
795
|
+
color: #1f2937;
|
|
796
|
+
font: 13px/1.55 var(--mono);
|
|
797
|
+
min-height: 220px;
|
|
798
|
+
white-space: pre-wrap;
|
|
799
|
+
}
|
|
800
|
+
.prompt-line {
|
|
801
|
+
display: grid;
|
|
802
|
+
grid-template-columns: 20px minmax(0, 1fr);
|
|
803
|
+
gap: 10px;
|
|
804
|
+
margin: 7px 0;
|
|
805
|
+
}
|
|
806
|
+
.prompt-check {
|
|
807
|
+
color: var(--green);
|
|
808
|
+
display: grid;
|
|
809
|
+
place-items: start center;
|
|
810
|
+
padding-top: 3px;
|
|
811
|
+
}
|
|
812
|
+
.prompt-check svg {
|
|
813
|
+
width: 14px;
|
|
814
|
+
height: 14px;
|
|
597
815
|
}
|
|
598
816
|
.drawer-actions {
|
|
599
817
|
display: grid;
|
|
600
|
-
gap:
|
|
601
|
-
margin-top:
|
|
818
|
+
gap: 10px;
|
|
819
|
+
margin-top: 18px;
|
|
602
820
|
}
|
|
603
821
|
.drawer-actions button {
|
|
604
822
|
min-height: 42px;
|
|
605
823
|
font-weight: 650;
|
|
824
|
+
position: relative;
|
|
825
|
+
display: grid;
|
|
826
|
+
grid-template-columns: 24px minmax(0, 1fr) 14px;
|
|
827
|
+
gap: 10px;
|
|
828
|
+
align-items: center;
|
|
829
|
+
text-align: left;
|
|
830
|
+
border-radius: 9px;
|
|
831
|
+
padding: 10px 12px;
|
|
606
832
|
}
|
|
607
|
-
.
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
833
|
+
.drawer-actions button::after {
|
|
834
|
+
content: "";
|
|
835
|
+
width: 8px;
|
|
836
|
+
height: 8px;
|
|
837
|
+
justify-self: end;
|
|
838
|
+
border-right: 1.5px solid var(--muted);
|
|
839
|
+
border-bottom: 1.5px solid var(--muted);
|
|
840
|
+
transform: rotate(-45deg);
|
|
841
|
+
}
|
|
842
|
+
.action-icon {
|
|
843
|
+
width: 24px;
|
|
844
|
+
height: 24px;
|
|
612
845
|
color: var(--muted-strong);
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
846
|
+
display: grid;
|
|
847
|
+
place-items: center;
|
|
848
|
+
}
|
|
849
|
+
.action-icon svg {
|
|
850
|
+
width: 19px;
|
|
851
|
+
height: 19px;
|
|
852
|
+
fill: none;
|
|
853
|
+
stroke: currentColor;
|
|
854
|
+
stroke-width: 1.8;
|
|
855
|
+
stroke-linecap: round;
|
|
856
|
+
stroke-linejoin: round;
|
|
616
857
|
}
|
|
617
858
|
.empty {
|
|
618
859
|
color: var(--muted);
|
|
@@ -623,6 +864,16 @@ function renderLocalUiHtml() {
|
|
|
623
864
|
font-size: 13px;
|
|
624
865
|
line-height: 1.45;
|
|
625
866
|
}
|
|
867
|
+
.tip {
|
|
868
|
+
margin-top: 14px;
|
|
869
|
+
border: 1px solid var(--line);
|
|
870
|
+
border-radius: 9px;
|
|
871
|
+
padding: 12px;
|
|
872
|
+
color: var(--muted-strong);
|
|
873
|
+
background: var(--surface-soft);
|
|
874
|
+
font-size: 13px;
|
|
875
|
+
line-height: 1.4;
|
|
876
|
+
}
|
|
626
877
|
.status-footer {
|
|
627
878
|
min-height: 38px;
|
|
628
879
|
border-top: 1px solid var(--line);
|
|
@@ -647,7 +898,7 @@ function renderLocalUiHtml() {
|
|
|
647
898
|
padding: 7px 12px;
|
|
648
899
|
background: var(--surface);
|
|
649
900
|
color: var(--ink);
|
|
650
|
-
font: 13px/1
|
|
901
|
+
font: 13px/1 var(--mono);
|
|
651
902
|
overflow: hidden;
|
|
652
903
|
text-overflow: ellipsis;
|
|
653
904
|
white-space: nowrap;
|
|
@@ -662,6 +913,207 @@ function renderLocalUiHtml() {
|
|
|
662
913
|
white-space: nowrap;
|
|
663
914
|
}
|
|
664
915
|
.gate-status[data-status="clear"] { color: var(--green); }
|
|
916
|
+
.action-panel-backdrop {
|
|
917
|
+
position: fixed;
|
|
918
|
+
inset: 0;
|
|
919
|
+
z-index: 9;
|
|
920
|
+
background: rgba(17, 20, 23, 0.28);
|
|
921
|
+
opacity: 0;
|
|
922
|
+
visibility: hidden;
|
|
923
|
+
pointer-events: none;
|
|
924
|
+
align-items: stretch;
|
|
925
|
+
justify-content: flex-end;
|
|
926
|
+
transition: opacity 180ms cubic-bezier(0.16, 1, 0.3, 1), visibility 180ms ease;
|
|
927
|
+
}
|
|
928
|
+
.action-panel-backdrop.is-open {
|
|
929
|
+
opacity: 1;
|
|
930
|
+
visibility: visible;
|
|
931
|
+
pointer-events: auto;
|
|
932
|
+
}
|
|
933
|
+
.action-panel {
|
|
934
|
+
width: min(620px, 100%);
|
|
935
|
+
height: 100%;
|
|
936
|
+
background: var(--canvas);
|
|
937
|
+
border-left: 1px solid var(--line);
|
|
938
|
+
box-shadow: -24px 0 60px rgba(16, 24, 40, 0.16);
|
|
939
|
+
display: grid;
|
|
940
|
+
grid-template-rows: auto minmax(0, 1fr);
|
|
941
|
+
transform: translateX(28px);
|
|
942
|
+
opacity: 0.98;
|
|
943
|
+
transition: transform 220ms cubic-bezier(0.16, 1, 0.3, 1), opacity 180ms ease;
|
|
944
|
+
}
|
|
945
|
+
.action-panel-backdrop.is-open .action-panel {
|
|
946
|
+
transform: translateX(0);
|
|
947
|
+
opacity: 1;
|
|
948
|
+
}
|
|
949
|
+
.action-panel-header {
|
|
950
|
+
padding: 24px 28px;
|
|
951
|
+
border-bottom: 1px solid var(--line);
|
|
952
|
+
background: var(--surface);
|
|
953
|
+
display: grid;
|
|
954
|
+
grid-template-columns: minmax(0, 1fr) auto;
|
|
955
|
+
gap: 16px;
|
|
956
|
+
align-items: start;
|
|
957
|
+
}
|
|
958
|
+
.action-panel-header h2 {
|
|
959
|
+
margin: 0;
|
|
960
|
+
font-size: 23px;
|
|
961
|
+
line-height: 1.15;
|
|
962
|
+
}
|
|
963
|
+
.action-panel-header p {
|
|
964
|
+
margin: 8px 0 0;
|
|
965
|
+
color: var(--muted-strong);
|
|
966
|
+
line-height: 1.45;
|
|
967
|
+
}
|
|
968
|
+
.panel-close {
|
|
969
|
+
width: 38px;
|
|
970
|
+
height: 38px;
|
|
971
|
+
border-radius: 999px;
|
|
972
|
+
display: grid;
|
|
973
|
+
place-items: center;
|
|
974
|
+
padding: 0;
|
|
975
|
+
}
|
|
976
|
+
.panel-close::before,
|
|
977
|
+
.panel-close::after {
|
|
978
|
+
content: "";
|
|
979
|
+
width: 15px;
|
|
980
|
+
height: 1.8px;
|
|
981
|
+
background: currentColor;
|
|
982
|
+
grid-area: 1 / 1;
|
|
983
|
+
border-radius: 999px;
|
|
984
|
+
}
|
|
985
|
+
.panel-close::before { transform: rotate(45deg); }
|
|
986
|
+
.panel-close::after { transform: rotate(-45deg); }
|
|
987
|
+
.action-panel-body {
|
|
988
|
+
overflow: auto;
|
|
989
|
+
padding: 24px 28px 30px;
|
|
990
|
+
display: grid;
|
|
991
|
+
gap: 14px;
|
|
992
|
+
align-content: start;
|
|
993
|
+
}
|
|
994
|
+
.guide-card {
|
|
995
|
+
border: 1px solid var(--line);
|
|
996
|
+
border-radius: 12px;
|
|
997
|
+
background: var(--surface);
|
|
998
|
+
padding: 18px;
|
|
999
|
+
box-shadow: 0 10px 22px rgba(16, 24, 40, 0.035);
|
|
1000
|
+
animation: panelCardIn 260ms cubic-bezier(0.16, 1, 0.3, 1) both;
|
|
1001
|
+
animation-delay: calc(var(--panel-index, 0) * 38ms);
|
|
1002
|
+
}
|
|
1003
|
+
.guide-card h3 {
|
|
1004
|
+
margin: 0 0 8px;
|
|
1005
|
+
font-size: 15px;
|
|
1006
|
+
}
|
|
1007
|
+
.guide-card p {
|
|
1008
|
+
margin: 0;
|
|
1009
|
+
color: var(--muted-strong);
|
|
1010
|
+
line-height: 1.5;
|
|
1011
|
+
}
|
|
1012
|
+
.guide-card code {
|
|
1013
|
+
display: inline-flex;
|
|
1014
|
+
max-width: 100%;
|
|
1015
|
+
margin-top: 10px;
|
|
1016
|
+
border: 1px solid var(--line);
|
|
1017
|
+
border-radius: 7px;
|
|
1018
|
+
padding: 8px 10px;
|
|
1019
|
+
background: var(--surface-soft);
|
|
1020
|
+
color: var(--ink);
|
|
1021
|
+
font: 12px/1.35 var(--mono);
|
|
1022
|
+
white-space: pre-wrap;
|
|
1023
|
+
}
|
|
1024
|
+
.guide-actions {
|
|
1025
|
+
display: flex;
|
|
1026
|
+
flex-wrap: wrap;
|
|
1027
|
+
gap: 10px;
|
|
1028
|
+
margin-top: 14px;
|
|
1029
|
+
}
|
|
1030
|
+
.guide-actions button {
|
|
1031
|
+
min-height: 38px;
|
|
1032
|
+
font-weight: 700;
|
|
1033
|
+
}
|
|
1034
|
+
.guide-actions .primary-action {
|
|
1035
|
+
border-color: var(--black-button);
|
|
1036
|
+
background: var(--black-button);
|
|
1037
|
+
color: #ffffff;
|
|
1038
|
+
}
|
|
1039
|
+
.guide-actions .primary-action:hover {
|
|
1040
|
+
background: #111a22;
|
|
1041
|
+
color: #ffffff;
|
|
1042
|
+
}
|
|
1043
|
+
.guide-actions .verify-action {
|
|
1044
|
+
border-color: var(--purple);
|
|
1045
|
+
background: var(--purple);
|
|
1046
|
+
color: #ffffff;
|
|
1047
|
+
}
|
|
1048
|
+
.guide-actions .verify-action:hover {
|
|
1049
|
+
background: #6d28d9;
|
|
1050
|
+
color: #ffffff;
|
|
1051
|
+
}
|
|
1052
|
+
.task-card {
|
|
1053
|
+
display: grid;
|
|
1054
|
+
grid-template-columns: 34px minmax(0, 1fr);
|
|
1055
|
+
gap: 14px;
|
|
1056
|
+
}
|
|
1057
|
+
.task-number {
|
|
1058
|
+
width: 34px;
|
|
1059
|
+
height: 34px;
|
|
1060
|
+
border-radius: 999px;
|
|
1061
|
+
display: grid;
|
|
1062
|
+
place-items: center;
|
|
1063
|
+
color: var(--orange);
|
|
1064
|
+
background: var(--orange-soft);
|
|
1065
|
+
font-weight: 800;
|
|
1066
|
+
font-size: 13px;
|
|
1067
|
+
}
|
|
1068
|
+
.provider-pill-row {
|
|
1069
|
+
display: flex;
|
|
1070
|
+
flex-wrap: wrap;
|
|
1071
|
+
gap: 8px;
|
|
1072
|
+
margin-top: 12px;
|
|
1073
|
+
}
|
|
1074
|
+
.provider-pill {
|
|
1075
|
+
border: 1px solid var(--line);
|
|
1076
|
+
border-radius: 999px;
|
|
1077
|
+
background: var(--surface-soft);
|
|
1078
|
+
padding: 7px 10px;
|
|
1079
|
+
color: var(--muted-strong);
|
|
1080
|
+
font-size: 12px;
|
|
1081
|
+
font-weight: 700;
|
|
1082
|
+
}
|
|
1083
|
+
.report-grid {
|
|
1084
|
+
display: grid;
|
|
1085
|
+
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
1086
|
+
gap: 12px;
|
|
1087
|
+
}
|
|
1088
|
+
.report-metric {
|
|
1089
|
+
border: 1px solid var(--line);
|
|
1090
|
+
border-radius: 10px;
|
|
1091
|
+
background: var(--surface);
|
|
1092
|
+
padding: 14px;
|
|
1093
|
+
}
|
|
1094
|
+
.report-metric span {
|
|
1095
|
+
display: block;
|
|
1096
|
+
color: var(--muted);
|
|
1097
|
+
font-size: 12px;
|
|
1098
|
+
margin-bottom: 6px;
|
|
1099
|
+
}
|
|
1100
|
+
.report-metric strong {
|
|
1101
|
+
font-size: 18px;
|
|
1102
|
+
}
|
|
1103
|
+
.panel-pre {
|
|
1104
|
+
margin: 0;
|
|
1105
|
+
border: 1px solid var(--line);
|
|
1106
|
+
border-radius: 10px;
|
|
1107
|
+
background: #fff;
|
|
1108
|
+
padding: 16px;
|
|
1109
|
+
color: #1f2937;
|
|
1110
|
+
white-space: pre-wrap;
|
|
1111
|
+
font: 13px/1.55 var(--mono);
|
|
1112
|
+
}
|
|
1113
|
+
@keyframes panelCardIn {
|
|
1114
|
+
from { opacity: 0; transform: translateY(10px); }
|
|
1115
|
+
to { opacity: 1; transform: translateY(0); }
|
|
1116
|
+
}
|
|
665
1117
|
@media (max-width: 1240px) {
|
|
666
1118
|
.topbar { grid-template-columns: minmax(220px, 1fr) auto; }
|
|
667
1119
|
.project-picker { display: none; }
|
|
@@ -669,6 +1121,12 @@ function renderLocalUiHtml() {
|
|
|
669
1121
|
.drawer { grid-column: 1 / -1; border-left: 0; border-top: 1px solid var(--line); }
|
|
670
1122
|
}
|
|
671
1123
|
@media (max-width: 820px) {
|
|
1124
|
+
body {
|
|
1125
|
+
height: auto;
|
|
1126
|
+
min-height: 100dvh;
|
|
1127
|
+
overflow: auto;
|
|
1128
|
+
display: block;
|
|
1129
|
+
}
|
|
672
1130
|
.topbar {
|
|
673
1131
|
height: auto;
|
|
674
1132
|
min-height: 58px;
|
|
@@ -680,9 +1138,10 @@ function renderLocalUiHtml() {
|
|
|
680
1138
|
.top-actions {
|
|
681
1139
|
width: 100%;
|
|
682
1140
|
display: grid;
|
|
683
|
-
grid-template-columns: 1fr
|
|
1141
|
+
grid-template-columns: minmax(118px, 1fr) auto 42px minmax(118px, 1fr);
|
|
684
1142
|
}
|
|
685
|
-
.
|
|
1143
|
+
.verify-button { min-width: 118px; }
|
|
1144
|
+
.shell { grid-template-columns: 1fr; min-height: auto; height: auto; overflow: visible; }
|
|
686
1145
|
.rail { border-right: 0; border-bottom: 1px solid var(--line); max-height: 330px; }
|
|
687
1146
|
.main { padding: 20px 14px 28px; }
|
|
688
1147
|
.provider-header { grid-template-columns: 1fr; }
|
|
@@ -696,15 +1155,29 @@ function renderLocalUiHtml() {
|
|
|
696
1155
|
<body>
|
|
697
1156
|
<header class="topbar">
|
|
698
1157
|
<div class="brand-lockup" aria-label="VibeRaven">
|
|
699
|
-
<span class="raven-mark" aria-hidden="true"
|
|
1158
|
+
<span class="raven-mark" aria-hidden="true">
|
|
1159
|
+
<img src="/assets/extension-icon.png" alt="" />
|
|
1160
|
+
</span>
|
|
700
1161
|
<strong>VibeRaven</strong>
|
|
701
1162
|
<span class="tagline">From AI demo to production</span>
|
|
702
1163
|
</div>
|
|
703
|
-
<
|
|
1164
|
+
<button id="project-picker" class="project-picker" type="button" aria-label="Current project"><span>Loading project...</span></button>
|
|
704
1165
|
<div class="top-actions">
|
|
705
|
-
<
|
|
706
|
-
<span
|
|
707
|
-
<button id="
|
|
1166
|
+
<span class="live-status"><span class="dot ok" aria-hidden="true"></span>Connected</span>
|
|
1167
|
+
<span class="top-divider" aria-hidden="true"></span>
|
|
1168
|
+
<button id="settings" class="icon-button" title="Settings" aria-label="Settings" type="button">
|
|
1169
|
+
<svg viewBox="0 0 24 24" aria-hidden="true">
|
|
1170
|
+
<path d="M12 8.25a3.75 3.75 0 1 0 0 7.5 3.75 3.75 0 0 0 0-7.5Z"/>
|
|
1171
|
+
<path d="M19.4 15a1.7 1.7 0 0 0 .34 1.87l.05.05a2 2 0 0 1-2.83 2.83l-.05-.05a1.7 1.7 0 0 0-1.87-.34 1.7 1.7 0 0 0-1.04 1.56V21a2 2 0 0 1-4 0v-.08a1.7 1.7 0 0 0-1.04-1.56 1.7 1.7 0 0 0-1.87.34l-.05.05a2 2 0 0 1-2.83-2.83l.05-.05A1.7 1.7 0 0 0 4.6 15a1.7 1.7 0 0 0-1.56-1.04H3a2 2 0 0 1 0-4h.08A1.7 1.7 0 0 0 4.6 8.9a1.7 1.7 0 0 0-.34-1.87l-.05-.05a2 2 0 0 1 2.83-2.83l.05.05a1.7 1.7 0 0 0 1.87.34A1.7 1.7 0 0 0 10 3.08V3a2 2 0 0 1 4 0v.08a1.7 1.7 0 0 0 1.04 1.56 1.7 1.7 0 0 0 1.87-.34l.05-.05a2 2 0 0 1 2.83 2.83l-.05.05A1.7 1.7 0 0 0 19.4 8.9a1.7 1.7 0 0 0 1.56 1.04H21a2 2 0 0 1 0 4h-.08A1.7 1.7 0 0 0 19.4 15Z"/>
|
|
1172
|
+
</svg>
|
|
1173
|
+
</button>
|
|
1174
|
+
<button id="verify" class="primary verify-button" type="button">
|
|
1175
|
+
<svg viewBox="0 0 24 24" aria-hidden="true">
|
|
1176
|
+
<path d="M12 3.2 19 6v5.3c0 4.4-2.7 7.4-7 9.5-4.3-2.1-7-5.1-7-9.5V6Z"/>
|
|
1177
|
+
<path d="m9.2 12.1 2 2 4-4.3"/>
|
|
1178
|
+
</svg>
|
|
1179
|
+
<span>Verify now</span>
|
|
1180
|
+
</button>
|
|
708
1181
|
</div>
|
|
709
1182
|
</header>
|
|
710
1183
|
<div class="shell">
|
|
@@ -712,9 +1185,9 @@ function renderLocalUiHtml() {
|
|
|
712
1185
|
<p class="rail-title">Providers</p>
|
|
713
1186
|
<label class="search-box"><span aria-hidden="true"></span><input id="provider-search" placeholder="Search providers..." aria-label="Search providers" /></label>
|
|
714
1187
|
<div id="providers" class="provider-list"></div>
|
|
715
|
-
<section class="run-local-card" aria-label="Run VibeRaven
|
|
716
|
-
<h2>Run VibeRaven
|
|
717
|
-
<p>
|
|
1188
|
+
<section class="run-local-card" aria-label="Run VibeRaven">
|
|
1189
|
+
<h2>Run VibeRaven</h2>
|
|
1190
|
+
<p>Check your project from the terminal.</p>
|
|
718
1191
|
<div class="command-pill"><span>$ npx -y viberaven</span></div>
|
|
719
1192
|
</section>
|
|
720
1193
|
</aside>
|
|
@@ -722,34 +1195,66 @@ function renderLocalUiHtml() {
|
|
|
722
1195
|
<section id="provider-header" class="provider-header"></section>
|
|
723
1196
|
<section id="path"></section>
|
|
724
1197
|
<section class="next-fix">
|
|
725
|
-
<h2>Next
|
|
1198
|
+
<h2>Next action</h2>
|
|
726
1199
|
<div id="next-fix"></div>
|
|
727
1200
|
</section>
|
|
728
1201
|
</main>
|
|
729
|
-
<aside class="drawer" aria-label="Agent prompt">
|
|
730
|
-
<
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
1202
|
+
<aside class="drawer" aria-label="Agent prompt and quick actions">
|
|
1203
|
+
<section class="prompt-panel" aria-label="Agent prompt">
|
|
1204
|
+
<div class="panel-heading">
|
|
1205
|
+
<div class="panel-title">
|
|
1206
|
+
<span class="panel-glyph" aria-hidden="true">
|
|
1207
|
+
<svg viewBox="0 0 24 24"><path d="M4 6h16"/><path d="M4 12h10"/><path d="M4 18h7"/><path d="M17 12l3 3-3 3"/><path d="M14 15h6"/></svg>
|
|
1208
|
+
</span>
|
|
1209
|
+
<div>
|
|
1210
|
+
<h2>Agent prompt</h2>
|
|
1211
|
+
<p>Run this with your coding agent.</p>
|
|
1212
|
+
</div>
|
|
1213
|
+
</div>
|
|
1214
|
+
<button id="copy" class="copy-small" type="button" aria-label="Copy prompt">
|
|
1215
|
+
<span class="action-icon" aria-hidden="true"><svg viewBox="0 0 24 24"><rect x="9" y="9" width="11" height="11" rx="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg></span>
|
|
1216
|
+
Copy
|
|
1217
|
+
</button>
|
|
1218
|
+
</div>
|
|
1219
|
+
<div id="prompt-preview" class="prompt-card"></div>
|
|
1220
|
+
<textarea id="prompt" class="prompt-box" readonly></textarea>
|
|
1221
|
+
</section>
|
|
1222
|
+
<section class="quick-panel" aria-label="Quick actions">
|
|
1223
|
+
<div class="panel-title">
|
|
1224
|
+
<span class="action-icon" aria-hidden="true"><svg viewBox="0 0 24 24"><path d="m13 2-8 12h7l-1 8 8-12h-7l1-8Z"/></svg></span>
|
|
1225
|
+
<h2>Quick actions</h2>
|
|
1226
|
+
</div>
|
|
1227
|
+
<div class="drawer-actions">
|
|
1228
|
+
<button id="tasklist" type="button"><span class="action-icon" aria-hidden="true"><svg viewBox="0 0 24 24"><path d="M8 6h13"/><path d="M8 12h13"/><path d="M8 18h13"/><path d="M3 6h.01"/><path d="M3 12h.01"/><path d="M3 18h.01"/></svg></span><span>Open tasklist</span></button>
|
|
1229
|
+
<button id="drawer-verify" type="button" aria-label="Run verify"><span class="action-icon" aria-hidden="true"><svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="9"/><path d="m10 8 6 4-6 4Z"/></svg></span><span>Run verify</span></button>
|
|
1230
|
+
</div>
|
|
1231
|
+
<div class="tip" hidden></div>
|
|
1232
|
+
</section>
|
|
739
1233
|
</aside>
|
|
740
1234
|
</div>
|
|
741
1235
|
<footer class="status-footer">
|
|
742
1236
|
<div class="footer-left">
|
|
743
1237
|
<strong>VibeRaven CLI</strong>
|
|
744
|
-
<span id="footer-project">Local project</span>
|
|
745
|
-
<span id="footer-command" class="footer-command">npx -y viberaven</span>
|
|
746
1238
|
</div>
|
|
747
1239
|
<div class="footer-right">
|
|
1240
|
+
<span>Need help?</span>
|
|
748
1241
|
<span id="footer-gate" class="gate-status" data-status="not_clear"><span class="dot bad" aria-hidden="true"></span>Gate not clear</span>
|
|
749
1242
|
</div>
|
|
750
1243
|
</footer>
|
|
1244
|
+
<div id="action-panel-backdrop" class="action-panel-backdrop" aria-hidden="true">
|
|
1245
|
+
<section class="action-panel" role="dialog" aria-modal="true" aria-labelledby="action-panel-title">
|
|
1246
|
+
<header class="action-panel-header">
|
|
1247
|
+
<div>
|
|
1248
|
+
<h2 id="action-panel-title">Guide</h2>
|
|
1249
|
+
<p id="action-panel-subtitle"></p>
|
|
1250
|
+
</div>
|
|
1251
|
+
<button id="action-panel-close" class="panel-close" type="button" aria-label="Close panel"></button>
|
|
1252
|
+
</header>
|
|
1253
|
+
<div id="action-panel-body" class="action-panel-body"></div>
|
|
1254
|
+
</section>
|
|
1255
|
+
</div>
|
|
751
1256
|
<script>
|
|
752
|
-
const state = { data: null, selectedProviderId: null, providerQuery: '' };
|
|
1257
|
+
const state = { data: null, selectedProviderId: null, selectedPathItemId: null, providerQuery: '', busyAction: '' };
|
|
753
1258
|
const localToken = new URLSearchParams(window.location.search).get('vr_token') || '';
|
|
754
1259
|
const labels = {
|
|
755
1260
|
not_detected: 'Not detected',
|
|
@@ -769,6 +1274,183 @@ function renderLocalUiHtml() {
|
|
|
769
1274
|
function selectedProvider() {
|
|
770
1275
|
return state.data.providers.find((provider) => provider.id === state.selectedProviderId) || state.data.providers[0];
|
|
771
1276
|
}
|
|
1277
|
+
function selectedPathItem(provider) {
|
|
1278
|
+
return provider.launchPath.find((item) => item.id === state.selectedPathItemId)
|
|
1279
|
+
|| (provider.nextFix && provider.launchPath.find((item) => item.id === provider.nextFix.launchPathItemId))
|
|
1280
|
+
|| provider.launchPath[0];
|
|
1281
|
+
}
|
|
1282
|
+
function providerPrompt(provider, item) {
|
|
1283
|
+
const nextFix = provider.nextFix;
|
|
1284
|
+
if (nextFix && item.id === nextFix.launchPathItemId) return nextFix.prompt;
|
|
1285
|
+
return [
|
|
1286
|
+
'Work on the ' + provider.label + ' launch path for ' + (state.data.project.name || 'this project') + '.',
|
|
1287
|
+
'',
|
|
1288
|
+
'Focused check: ' + item.title,
|
|
1289
|
+
'',
|
|
1290
|
+
'Requirements:',
|
|
1291
|
+
'- Understand why it matters: ' + item.whyItMatters,
|
|
1292
|
+
'- Make the repo-owned change: ' + item.whatToChange,
|
|
1293
|
+
'- Keep changes local/repo-only and do not add secret values.',
|
|
1294
|
+
'- Re-run npx -y viberaven --verify when done.',
|
|
1295
|
+
'',
|
|
1296
|
+
'Return the exact files changed and the verification result.'
|
|
1297
|
+
].join('\\n');
|
|
1298
|
+
}
|
|
1299
|
+
function setTip(message, kind) {
|
|
1300
|
+
const tip = document.querySelector('.tip');
|
|
1301
|
+
tip.hidden = !message;
|
|
1302
|
+
tip.textContent = message || '';
|
|
1303
|
+
tip.dataset.kind = kind || 'info';
|
|
1304
|
+
}
|
|
1305
|
+
function escapeHtml(value) {
|
|
1306
|
+
return String(value == null ? '' : value)
|
|
1307
|
+
.replaceAll('&', '&')
|
|
1308
|
+
.replaceAll('<', '<')
|
|
1309
|
+
.replaceAll('>', '>')
|
|
1310
|
+
.replaceAll('"', '"')
|
|
1311
|
+
.replaceAll("'", ''');
|
|
1312
|
+
}
|
|
1313
|
+
function openPanel(title, subtitle, html) {
|
|
1314
|
+
document.getElementById('action-panel-title').textContent = title;
|
|
1315
|
+
document.getElementById('action-panel-subtitle').textContent = subtitle || '';
|
|
1316
|
+
document.getElementById('action-panel-body').innerHTML = html;
|
|
1317
|
+
const backdrop = document.getElementById('action-panel-backdrop');
|
|
1318
|
+
backdrop.classList.add('is-open');
|
|
1319
|
+
backdrop.setAttribute('aria-hidden', 'false');
|
|
1320
|
+
}
|
|
1321
|
+
function closePanel() {
|
|
1322
|
+
const backdrop = document.getElementById('action-panel-backdrop');
|
|
1323
|
+
backdrop.classList.remove('is-open');
|
|
1324
|
+
backdrop.setAttribute('aria-hidden', 'true');
|
|
1325
|
+
}
|
|
1326
|
+
function guideSteps(provider, item) {
|
|
1327
|
+
if (provider.id === 'supabase' && item.id === 'rls-policies') {
|
|
1328
|
+
return [
|
|
1329
|
+
{ title: 'Open Supabase policies', body: 'In Supabase, open Authentication and Database policies for the project tables that store user data.', code: 'https://supabase.com/dashboard/project/_/auth/policies', openUrl: 'https://supabase.com/dashboard/project/_/auth/policies' },
|
|
1330
|
+
{ title: 'Enable RLS in a migration', body: 'Keep proof in the repo. Add SQL that enables row level security for each public table.', code: 'alter table public.your_table enable row level security;' },
|
|
1331
|
+
{ title: 'Scope rows to the owner', body: 'Add policies that only allow authenticated users to read or write their own records. Avoid permissive USING (true) policies.', code: "create policy \\"Users manage own rows\\" on public.your_table\\nfor all to authenticated\\nusing (auth.uid() = user_id)\\nwith check (auth.uid() = user_id);" },
|
|
1332
|
+
{ title: 'Run verify', body: 'Refresh the evidence map after the repo change. VibeRaven does not need secret values.', code: 'npx -y viberaven --verify', verify: true }
|
|
1333
|
+
];
|
|
1334
|
+
}
|
|
1335
|
+
if (provider.id === 'supabase' && item.id === 'production-env') {
|
|
1336
|
+
return [
|
|
1337
|
+
{ title: 'Open .env.example', body: 'Add names only. Never paste real Supabase keys into repo files.', code: 'NEXT_PUBLIC_SUPABASE_URL=\\nNEXT_PUBLIC_SUPABASE_ANON_KEY=\\nSERVER_ONLY_SUPABASE_ADMIN_KEY=<server-only, never expose to browser>', file: '.env.example' },
|
|
1338
|
+
{ title: 'Document the boundary', body: 'Make it obvious which key can be public and which must stay server-only.', code: 'The server-only Supabase admin key must not be used in client components.' },
|
|
1339
|
+
{ title: 'Run verify', body: 'Refresh evidence after the placeholders are committed.', code: 'npx -y viberaven --verify', verify: true }
|
|
1340
|
+
];
|
|
1341
|
+
}
|
|
1342
|
+
if (provider.id === 'stripe') {
|
|
1343
|
+
return [
|
|
1344
|
+
{ title: 'Open env example', body: 'Add payment env names only. Keep server keys out of client code.', code: 'STRIPE_SERVER_KEY=<server-only>\\nSTRIPE_WEBHOOK_SIGNING_SECRET=<server-only>\\nNEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=', file: '.env.example' },
|
|
1345
|
+
{ title: 'Copy webhook target', body: 'Use a stable production webhook route and verify signatures before changing subscription state.', code: '/api/stripe/webhook' },
|
|
1346
|
+
{ title: 'Run verify', body: 'VibeRaven checks repo evidence and does not need live secret values.', code: 'npx -y viberaven --verify', verify: true }
|
|
1347
|
+
];
|
|
1348
|
+
}
|
|
1349
|
+
if (provider.id === 'vercel') {
|
|
1350
|
+
return [
|
|
1351
|
+
{ title: 'Open env example', body: 'Document the env names your app expects in production without values.', code: '.env.example', file: '.env.example' },
|
|
1352
|
+
{ title: 'Confirm deploy command', body: 'Make package scripts and deployment config agree on build and test commands.', code: 'npm run build' },
|
|
1353
|
+
{ title: 'Run verify', body: 'Refresh the launch map after repo evidence changes.', code: 'npx -y viberaven --verify', verify: true }
|
|
1354
|
+
];
|
|
1355
|
+
}
|
|
1356
|
+
return [
|
|
1357
|
+
{ title: item.title, body: item.whyItMatters, code: item.whatToChange },
|
|
1358
|
+
{ title: 'Keep the proof in the repo', body: 'Provider setup can still require manual work, but VibeRaven needs safe local evidence for the coding agent.', code: item.verifyWith },
|
|
1359
|
+
{ title: 'Run verify', body: 'Run verify after the repo-owned fix. No production secrets are required.', code: 'npx -y viberaven --verify', verify: true }
|
|
1360
|
+
];
|
|
1361
|
+
}
|
|
1362
|
+
function renderGuideHtml(provider, item) {
|
|
1363
|
+
const cards = guideSteps(provider, item).map((step, index) => {
|
|
1364
|
+
const actions = [
|
|
1365
|
+
step.code ? '<button type="button" data-copy="' + escapeHtml(step.code) + '">Copy</button>' : '',
|
|
1366
|
+
step.file ? '<button type="button" data-open-file="' + escapeHtml(step.file) + '">Open file</button>' : '',
|
|
1367
|
+
step.openUrl ? '<button type="button" data-open-url="' + escapeHtml(step.openUrl) + '">Open</button>' : '',
|
|
1368
|
+
step.verify ? '<button class="verify-action" type="button" data-run-verify="true">Run verify</button>' : ''
|
|
1369
|
+
].filter(Boolean).join('');
|
|
1370
|
+
return '<article class="guide-card task-card" style="--panel-index:' + index + '"><span class="task-number">' + (index + 1) + '</span><div><h3>' + escapeHtml(step.title) + '</h3><p>' + escapeHtml(step.body) + '</p>' + (step.code ? '<code>' + escapeHtml(step.code) + '</code>' : '') + '<div class="guide-actions">' + actions + '</div></div></article>';
|
|
1371
|
+
}).join('');
|
|
1372
|
+
return cards + '<article class="guide-card" style="--panel-index:8"><h3>Use with your coding agent</h3><p>The prompt on the right is scoped to this provider check. Copy it after selecting the row you want to fix.</p><div class="guide-actions"><button class="primary-action" type="button" data-copy-prompt="true">Copy focused prompt</button><button class="verify-action" type="button" data-run-verify="true">Run verify</button></div></article>';
|
|
1373
|
+
}
|
|
1374
|
+
function projectFiles() {
|
|
1375
|
+
return (state.data && state.data.project && state.data.project.files) || [];
|
|
1376
|
+
}
|
|
1377
|
+
function fileByPath(path) {
|
|
1378
|
+
return projectFiles().find((file) => file.path === path);
|
|
1379
|
+
}
|
|
1380
|
+
function bestEnvFile() {
|
|
1381
|
+
return fileByPath('.env.local')?.exists ? '.env.local'
|
|
1382
|
+
: fileByPath('.env')?.exists ? '.env'
|
|
1383
|
+
: '.env.example';
|
|
1384
|
+
}
|
|
1385
|
+
function projectFileCards() {
|
|
1386
|
+
return projectFiles().map((file, index) => {
|
|
1387
|
+
const stateLabel = file.exists ? 'Found' : 'Missing';
|
|
1388
|
+
const action = file.exists
|
|
1389
|
+
? '<button type="button" data-open-file="' + escapeHtml(file.path) + '">Open</button>'
|
|
1390
|
+
: '<button type="button" data-copy="' + escapeHtml(file.path) + '">Copy path</button>';
|
|
1391
|
+
return '<article class="guide-card" style="--panel-index:' + index + '"><h3>' + escapeHtml(file.label) + '</h3><p>' + escapeHtml(file.path) + ' - ' + stateLabel + '</p><div class="guide-actions">' + action + '</div></article>';
|
|
1392
|
+
}).join('');
|
|
1393
|
+
}
|
|
1394
|
+
function taskSteps(provider, item) {
|
|
1395
|
+
if (provider.id === 'supabase' && item.id === 'production-env') {
|
|
1396
|
+
const envFile = bestEnvFile();
|
|
1397
|
+
return [
|
|
1398
|
+
{ title: 'Open ' + envFile, body: 'Put the required variable names where your app already keeps environment configuration.', file: envFile },
|
|
1399
|
+
{ title: 'Copy Supabase placeholders', body: 'Paste names only. Add real values in your normal local environment, not into committed source.', code: 'NEXT_PUBLIC_SUPABASE_URL=\\nNEXT_PUBLIC_SUPABASE_ANON_KEY=\\nSERVER_ONLY_SUPABASE_ADMIN_KEY=<server-only, never expose to browser>' },
|
|
1400
|
+
{ title: 'Run verify', body: 'Refresh VibeRaven after the file is saved.', verify: true }
|
|
1401
|
+
];
|
|
1402
|
+
}
|
|
1403
|
+
if (provider.id === 'supabase' && item.id === 'rls-policies') {
|
|
1404
|
+
return [
|
|
1405
|
+
{ title: 'Open Supabase policies', body: 'Use Supabase to inspect the affected tables and policy names.', openUrl: 'https://supabase.com/dashboard/project/_/auth/policies' },
|
|
1406
|
+
{ title: 'Open migrations folder', body: 'Keep the durable SQL proof in your repo so Codex or Claude Code can edit it.', file: 'supabase/migrations' },
|
|
1407
|
+
{ title: 'Copy RLS policy template', body: 'Adapt table and owner column names before saving.', code: "alter table public.your_table enable row level security;\\n\\ncreate policy \\"Users manage own rows\\" on public.your_table\\nfor all to authenticated\\nusing (auth.uid() = user_id)\\nwith check (auth.uid() = user_id);" },
|
|
1408
|
+
{ title: 'Run verify', body: 'Refresh VibeRaven after the migration is saved.', verify: true }
|
|
1409
|
+
];
|
|
1410
|
+
}
|
|
1411
|
+
if (provider.id === 'stripe') {
|
|
1412
|
+
const envFile = bestEnvFile();
|
|
1413
|
+
return [
|
|
1414
|
+
{ title: 'Open ' + envFile, body: 'Add payment variable names where your app already expects environment values.', file: envFile },
|
|
1415
|
+
{ title: 'Copy payment placeholders', body: 'Keep server keys server-side. Use real values only in your local/provider environment.', code: 'STRIPE_SERVER_KEY=<server-only>\\nSTRIPE_WEBHOOK_SIGNING_SECRET=<server-only>\\nNEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=' },
|
|
1416
|
+
{ title: 'Run verify', body: 'Refresh VibeRaven after the repo evidence is saved.', verify: true }
|
|
1417
|
+
];
|
|
1418
|
+
}
|
|
1419
|
+
return [
|
|
1420
|
+
{ title: 'Open the likely file', body: 'Start where this project stores launch evidence.', file: bestEnvFile() },
|
|
1421
|
+
{ title: 'Copy the focused change', body: item.whatToChange, code: item.whatToChange },
|
|
1422
|
+
{ title: 'Run verify', body: 'Refresh VibeRaven after the change is saved.', verify: true }
|
|
1423
|
+
];
|
|
1424
|
+
}
|
|
1425
|
+
function renderTasklistHtml() {
|
|
1426
|
+
const provider = selectedProvider();
|
|
1427
|
+
const item = selectedPathItem(provider);
|
|
1428
|
+
const cards = taskSteps(provider, item).map((step, index) => {
|
|
1429
|
+
const actions = [
|
|
1430
|
+
step.code ? '<button type="button" data-copy="' + escapeHtml(step.code) + '">Copy</button>' : '',
|
|
1431
|
+
step.file ? '<button type="button" data-open-file="' + escapeHtml(step.file) + '">Open file</button>' : '',
|
|
1432
|
+
step.openUrl ? '<button type="button" data-open-url="' + escapeHtml(step.openUrl) + '">Open</button>' : '',
|
|
1433
|
+
step.verify ? '<button class="verify-action" type="button" data-run-verify="true">Run verify</button>' : ''
|
|
1434
|
+
].filter(Boolean).join('');
|
|
1435
|
+
return '<article class="guide-card task-card" style="--panel-index:' + index + '"><span class="task-number">' + (index + 1) + '</span><div><h3>' + escapeHtml(step.title) + '</h3><p>' + escapeHtml(step.body) + '</p>' + (step.code ? '<code>' + escapeHtml(step.code) + '</code>' : '') + '<div class="guide-actions">' + actions + '</div></div></article>';
|
|
1436
|
+
}).join('');
|
|
1437
|
+
const providerPills = state.data.providers.map((candidate) => '<span class="provider-pill">' + escapeHtml(candidate.label) + ': ' + escapeHtml(labels[candidate.state] || candidate.state) + '</span>').join('');
|
|
1438
|
+
return '<article class="guide-card" style="--panel-index:0"><h3>Current focus</h3><p>' + escapeHtml(provider.label + ' - ' + item.title) + '</p><div class="provider-pill-row">' + providerPills + '</div></article>' + cards;
|
|
1439
|
+
}
|
|
1440
|
+
async function openTasklistPanel() {
|
|
1441
|
+
setTip('Loading tasklist...');
|
|
1442
|
+
await fetch('/api/tasklist?vr_token=' + encodeURIComponent(localToken));
|
|
1443
|
+
openPanel('Tasklist', 'Focused steps from repo evidence. No secrets are needed.', renderTasklistHtml());
|
|
1444
|
+
setTip('Tasklist loaded inside VibeRaven.');
|
|
1445
|
+
}
|
|
1446
|
+
function openSettingsPanel() {
|
|
1447
|
+
const project = state.data.project;
|
|
1448
|
+
openPanel('Settings', 'Project files and editor shortcuts.', '<article class="guide-card"><h3>Project folder</h3><p>' + escapeHtml(project.workspacePath) + '</p><div class="guide-actions"><button type="button" data-open-file=".">Open folder</button></div></article>' + projectFileCards() + '<article class="guide-card"><h3>Commands</h3><code>npx -y viberaven\\nnpx -y viberaven --verify\\nnpx -y viberaven --agent-mode</code></article>');
|
|
1449
|
+
}
|
|
1450
|
+
function openProjectPanel() {
|
|
1451
|
+
const project = state.data.project;
|
|
1452
|
+
openPanel('Project', project.name, '<article class="guide-card"><h3>Working folder</h3><p>' + escapeHtml(project.workspacePath) + '</p><div class="guide-actions"><button class="primary-action" type="button" data-open-file=".">Open folder</button><button type="button" data-run-verify="true">Run verify</button></div></article>' + projectFileCards());
|
|
1453
|
+
}
|
|
772
1454
|
function gateLabel(status) {
|
|
773
1455
|
return status === 'clear' ? 'Gate clear' : 'Gate not clear';
|
|
774
1456
|
}
|
|
@@ -778,8 +1460,46 @@ function renderLocalUiHtml() {
|
|
|
778
1460
|
if (provider.state === 'needs_repo_fix' || provider.state === 'connect_live' || provider.state === 'requires_user_action') return 'needs_repo_fix';
|
|
779
1461
|
return provider.state;
|
|
780
1462
|
}
|
|
781
|
-
function
|
|
782
|
-
|
|
1463
|
+
function pathIconHtml(index) {
|
|
1464
|
+
const icons = [
|
|
1465
|
+
'<svg viewBox="0 0 24 24"><ellipse cx="12" cy="5" rx="7" ry="3"/><path d="M5 5v6c0 1.7 3.1 3 7 3s7-1.3 7-3V5"/><path d="M5 11v6c0 1.7 3.1 3 7 3s7-1.3 7-3v-6"/></svg>',
|
|
1466
|
+
'<svg viewBox="0 0 24 24"><path d="m12 3 8 4.5v9L12 21l-8-4.5v-9Z"/><path d="m4 7.5 8 4.5 8-4.5"/><path d="M12 12v9"/></svg>',
|
|
1467
|
+
'<svg viewBox="0 0 24 24"><path d="m14 10-4 4"/><path d="M16.5 7.5a3.5 3.5 0 0 1 0 5l-1.5 1.5a3.5 3.5 0 0 1-5 0"/><path d="M7.5 16.5a3.5 3.5 0 0 1 0-5L9 10a3.5 3.5 0 0 1 5 0"/></svg>',
|
|
1468
|
+
'<svg viewBox="0 0 24 24"><path d="m12 3 8 4-8 4-8-4Z"/><path d="m4 12 8 4 8-4"/><path d="m4 17 8 4 8-4"/></svg>'
|
|
1469
|
+
];
|
|
1470
|
+
return icons[index % icons.length];
|
|
1471
|
+
}
|
|
1472
|
+
function renderPromptPreview(prompt) {
|
|
1473
|
+
const preview = document.getElementById('prompt-preview');
|
|
1474
|
+
preview.textContent = '';
|
|
1475
|
+
const lines = prompt ? prompt.split('\\n') : [];
|
|
1476
|
+
for (const line of lines) {
|
|
1477
|
+
const trimmed = line.trim();
|
|
1478
|
+
if (!trimmed) {
|
|
1479
|
+
preview.append(document.createTextNode('\\n'));
|
|
1480
|
+
continue;
|
|
1481
|
+
}
|
|
1482
|
+
if (trimmed.startsWith('- ')) {
|
|
1483
|
+
const row = document.createElement('div');
|
|
1484
|
+
row.className = 'prompt-line';
|
|
1485
|
+
const check = document.createElement('span');
|
|
1486
|
+
check.className = 'prompt-check';
|
|
1487
|
+
check.innerHTML = '<svg viewBox="0 0 16 16" aria-hidden="true"><path d="m3.5 8.5 2.5 2.5 6-7" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>';
|
|
1488
|
+
const copy = document.createElement('span');
|
|
1489
|
+
copy.textContent = trimmed.slice(2);
|
|
1490
|
+
row.append(check, copy);
|
|
1491
|
+
preview.append(row);
|
|
1492
|
+
continue;
|
|
1493
|
+
}
|
|
1494
|
+
const block = document.createElement('div');
|
|
1495
|
+
block.textContent = line;
|
|
1496
|
+
preview.append(block);
|
|
1497
|
+
}
|
|
1498
|
+
}
|
|
1499
|
+
function setPrompt(provider, item) {
|
|
1500
|
+
const prompt = item ? providerPrompt(provider, item) : (provider.nextFix ? provider.nextFix.prompt : '');
|
|
1501
|
+
document.getElementById('prompt').value = prompt;
|
|
1502
|
+
renderPromptPreview(prompt);
|
|
783
1503
|
}
|
|
784
1504
|
function renderProviders() {
|
|
785
1505
|
const container = document.getElementById('providers');
|
|
@@ -801,6 +1521,8 @@ function renderLocalUiHtml() {
|
|
|
801
1521
|
button.type = 'button';
|
|
802
1522
|
button.addEventListener('click', () => {
|
|
803
1523
|
state.selectedProviderId = provider.id;
|
|
1524
|
+
state.selectedPathItemId = null;
|
|
1525
|
+
setTip('');
|
|
804
1526
|
render();
|
|
805
1527
|
});
|
|
806
1528
|
const icon = document.createElement('span');
|
|
@@ -832,82 +1554,92 @@ function renderLocalUiHtml() {
|
|
|
832
1554
|
icon.innerHTML = provider.iconHtml;
|
|
833
1555
|
const copy = document.createElement('div');
|
|
834
1556
|
const title = document.createElement('h1');
|
|
835
|
-
title.textContent =
|
|
1557
|
+
title.textContent = 'Your launch path';
|
|
836
1558
|
const meta = document.createElement('p');
|
|
837
|
-
meta.textContent = '
|
|
1559
|
+
meta.textContent = "We'll guide you through what matters most.";
|
|
838
1560
|
copy.append(title, meta);
|
|
839
1561
|
heading.append(icon, copy);
|
|
840
|
-
|
|
841
|
-
guide.className = 'secondary-action';
|
|
842
|
-
guide.setAttribute('aria-disabled', 'true');
|
|
843
|
-
guide.textContent = 'Provider guide unavailable locally';
|
|
844
|
-
header.append(heading, guide);
|
|
1562
|
+
header.append(heading);
|
|
845
1563
|
}
|
|
846
1564
|
function renderPath(provider) {
|
|
847
1565
|
const path = document.getElementById('path');
|
|
848
1566
|
path.textContent = '';
|
|
849
1567
|
const list = document.createElement('div');
|
|
850
1568
|
list.className = 'path-list';
|
|
851
|
-
|
|
852
|
-
const row = document.createElement('
|
|
853
|
-
|
|
1569
|
+
provider.launchPath.forEach((item, index) => {
|
|
1570
|
+
const row = document.createElement('button');
|
|
1571
|
+
const isFocused = selectedPathItem(provider).id === item.id;
|
|
1572
|
+
row.className = 'path-row' + (isFocused ? ' is-focused' : '');
|
|
1573
|
+
row.type = 'button';
|
|
1574
|
+
row.dataset.step = String(index + 1);
|
|
1575
|
+
row.setAttribute('aria-label', 'Open ' + item.title + ' guidance');
|
|
1576
|
+
row.addEventListener('click', () => {
|
|
1577
|
+
state.selectedPathItemId = item.id;
|
|
1578
|
+
setTip('Opened guidance for ' + item.title + '. Copy the prompt or use Open guide for the focused steps.');
|
|
1579
|
+
render();
|
|
1580
|
+
});
|
|
1581
|
+
const icon = document.createElement('span');
|
|
1582
|
+
icon.className = 'path-icon';
|
|
1583
|
+
icon.innerHTML = pathIconHtml(index);
|
|
854
1584
|
const body = document.createElement('div');
|
|
855
1585
|
const title = document.createElement('strong');
|
|
856
1586
|
title.textContent = item.title;
|
|
857
1587
|
const copy = document.createElement('p');
|
|
858
|
-
copy.textContent = item.whyItMatters;
|
|
1588
|
+
copy.textContent = item.whatToChange || item.whyItMatters;
|
|
859
1589
|
body.append(title, copy);
|
|
860
1590
|
const status = document.createElement('span');
|
|
861
1591
|
status.className = 'path-state';
|
|
862
1592
|
status.dataset.state = item.state;
|
|
863
|
-
status.textContent = labels[item.state] || item.state;
|
|
864
|
-
|
|
1593
|
+
status.textContent = item.state === 'not_checked' ? 'Pending' : (labels[item.state] || item.state);
|
|
1594
|
+
const arrow = document.createElement('span');
|
|
1595
|
+
arrow.className = 'path-arrow';
|
|
1596
|
+
arrow.innerHTML = '<svg viewBox="0 0 24 24"><path d="m9 18 6-6-6-6"/></svg>';
|
|
1597
|
+
row.append(icon, body, status, arrow);
|
|
865
1598
|
list.append(row);
|
|
866
|
-
}
|
|
1599
|
+
});
|
|
867
1600
|
path.append(list);
|
|
868
1601
|
}
|
|
869
1602
|
function renderNextFix(provider) {
|
|
870
1603
|
const container = document.getElementById('next-fix');
|
|
871
1604
|
container.textContent = '';
|
|
872
|
-
|
|
1605
|
+
const item = selectedPathItem(provider);
|
|
1606
|
+
if (!item) {
|
|
873
1607
|
const empty = document.createElement('div');
|
|
874
1608
|
empty.className = 'empty';
|
|
875
|
-
empty.textContent = 'No
|
|
1609
|
+
empty.textContent = 'No launch-path guidance is available for this provider yet. Run scan or verify to refresh VibeRaven evidence.';
|
|
876
1610
|
container.append(empty);
|
|
877
|
-
setPrompt(provider);
|
|
1611
|
+
setPrompt(provider, item);
|
|
878
1612
|
return;
|
|
879
1613
|
}
|
|
880
|
-
const
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
}
|
|
902
|
-
|
|
903
|
-
|
|
1614
|
+
const subtitle = document.createElement('p');
|
|
1615
|
+
subtitle.className = 'next-fix-subtitle';
|
|
1616
|
+
subtitle.textContent = provider.nextFix && item.id === provider.nextFix.launchPathItemId ? 'Start here to move forward.' : 'Manual guidance for the selected launch check.';
|
|
1617
|
+
const row = document.createElement('article');
|
|
1618
|
+
row.className = 'next-action-row';
|
|
1619
|
+
const icon = document.createElement('span');
|
|
1620
|
+
icon.className = 'next-action-icon';
|
|
1621
|
+
icon.innerHTML = pathIconHtml(Math.max(0, provider.launchPath.findIndex((candidate) => candidate.id === item.id)));
|
|
1622
|
+
const body = document.createElement('div');
|
|
1623
|
+
const h = document.createElement('h3');
|
|
1624
|
+
h.textContent = 'Fix ' + item.title.toLowerCase();
|
|
1625
|
+
const p = document.createElement('p');
|
|
1626
|
+
p.textContent = item.whatToChange;
|
|
1627
|
+
body.append(h, p);
|
|
1628
|
+
const guide = document.createElement('button');
|
|
1629
|
+
guide.className = 'open-guide-button';
|
|
1630
|
+
guide.type = 'button';
|
|
1631
|
+
guide.textContent = 'Open guide';
|
|
1632
|
+
guide.addEventListener('click', () => {
|
|
1633
|
+
openPanel(provider.label + ' guide', item.title + ' guidance for this project.', renderGuideHtml(provider, item));
|
|
1634
|
+
setTip('Opened in-app guide for ' + item.title + '.');
|
|
1635
|
+
});
|
|
1636
|
+
row.append(icon, body, guide);
|
|
1637
|
+
container.append(subtitle, row);
|
|
1638
|
+
setPrompt(provider, item);
|
|
904
1639
|
}
|
|
905
1640
|
function renderChrome() {
|
|
906
1641
|
const projectName = state.data.project.name;
|
|
907
|
-
const command = state.data.command || 'npx -y viberaven';
|
|
908
1642
|
document.querySelector('#project-picker span').textContent = projectName;
|
|
909
|
-
document.getElementById('footer-project').textContent = projectName;
|
|
910
|
-
document.getElementById('footer-command').textContent = command;
|
|
911
1643
|
const gate = document.getElementById('footer-gate');
|
|
912
1644
|
gate.dataset.status = state.data.project.gateStatus;
|
|
913
1645
|
gate.textContent = '';
|
|
@@ -944,20 +1676,62 @@ function renderLocalUiHtml() {
|
|
|
944
1676
|
}
|
|
945
1677
|
state.data = payload.state || payload;
|
|
946
1678
|
state.selectedProviderId = state.data.selectedProviderId;
|
|
1679
|
+
state.selectedPathItemId = null;
|
|
1680
|
+
setTip(path === '/api/verify' ? 'Verify finished. The launch map has been refreshed.' : 'Scan finished. The launch map has been refreshed.');
|
|
947
1681
|
render();
|
|
948
1682
|
}
|
|
949
1683
|
document.getElementById('provider-search').addEventListener('input', (event) => {
|
|
950
1684
|
state.providerQuery = event.target.value.toLowerCase();
|
|
951
1685
|
renderProviders();
|
|
952
1686
|
});
|
|
953
|
-
document.getElementById('
|
|
954
|
-
document.getElementById('verify').addEventListener('click', () =>
|
|
955
|
-
|
|
956
|
-
|
|
1687
|
+
document.getElementById('project-picker').addEventListener('click', openProjectPanel);
|
|
1688
|
+
document.getElementById('verify').addEventListener('click', () => {
|
|
1689
|
+
setTip('Running verify...');
|
|
1690
|
+
postAndRefresh('/api/verify');
|
|
1691
|
+
});
|
|
1692
|
+
document.getElementById('drawer-verify').addEventListener('click', () => {
|
|
1693
|
+
setTip('Running verify...');
|
|
1694
|
+
postAndRefresh('/api/verify');
|
|
1695
|
+
});
|
|
1696
|
+
document.getElementById('tasklist').addEventListener('click', () => {
|
|
1697
|
+
openTasklistPanel().catch((error) => setTip(error instanceof Error ? error.message : String(error), 'error'));
|
|
1698
|
+
});
|
|
1699
|
+
document.getElementById('settings').addEventListener('click', openSettingsPanel);
|
|
1700
|
+
document.getElementById('action-panel-close').addEventListener('click', closePanel);
|
|
1701
|
+
document.getElementById('action-panel-backdrop').addEventListener('click', (event) => {
|
|
1702
|
+
if (event.target.id === 'action-panel-backdrop') closePanel();
|
|
1703
|
+
});
|
|
1704
|
+
document.getElementById('action-panel-body').addEventListener('click', async (event) => {
|
|
1705
|
+
const target = event.target.closest('button');
|
|
1706
|
+
if (!target) return;
|
|
1707
|
+
if (target.dataset.copy) {
|
|
1708
|
+
await navigator.clipboard.writeText(target.dataset.copy);
|
|
1709
|
+
setTip('Copied guide snippet.');
|
|
1710
|
+
}
|
|
1711
|
+
if (target.dataset.openUrl) {
|
|
1712
|
+
window.open(target.dataset.openUrl, '_blank', 'noopener,noreferrer');
|
|
1713
|
+
setTip('Opened provider page.');
|
|
1714
|
+
}
|
|
1715
|
+
if (target.dataset.openFile) {
|
|
1716
|
+
const base = state.data.project.workspacePath.replaceAll('\\\\', '/').replace(/\\/$/, '');
|
|
1717
|
+
const rel = target.dataset.openFile.replace(/^[/\\\\]+/, '');
|
|
1718
|
+
window.open('vscode://file/' + encodeURI(base + '/' + rel), '_blank', 'noopener,noreferrer');
|
|
1719
|
+
setTip('Opened ' + rel + ' in your editor if the editor protocol is enabled.');
|
|
1720
|
+
}
|
|
1721
|
+
if (target.dataset.copyPrompt) {
|
|
1722
|
+
await navigator.clipboard.writeText(document.getElementById('prompt').value || '');
|
|
1723
|
+
setTip('Copied focused prompt.');
|
|
1724
|
+
}
|
|
1725
|
+
if (target.dataset.runVerify) {
|
|
1726
|
+
setTip('Running verify...');
|
|
1727
|
+
postAndRefresh('/api/verify');
|
|
1728
|
+
}
|
|
1729
|
+
});
|
|
957
1730
|
document.getElementById('copy').addEventListener('click', async () => {
|
|
958
1731
|
const prompt = document.getElementById('prompt').value;
|
|
959
1732
|
if (!prompt) return;
|
|
960
1733
|
await navigator.clipboard.writeText(prompt);
|
|
1734
|
+
setTip('Prompt copied. Paste it into your coding agent for the selected launch check.');
|
|
961
1735
|
});
|
|
962
1736
|
refresh().catch((error) => {
|
|
963
1737
|
const tip = document.querySelector('.tip');
|
|
@@ -969,9 +1743,139 @@ function renderLocalUiHtml() {
|
|
|
969
1743
|
}
|
|
970
1744
|
|
|
971
1745
|
// src/version.ts
|
|
972
|
-
var VERSION = "1.1.
|
|
1746
|
+
var VERSION = "1.1.10";
|
|
973
1747
|
|
|
974
1748
|
// src/publicLocalCli.ts
|
|
1749
|
+
function brandSvg(title, fill, path) {
|
|
1750
|
+
return `<svg viewBox="0 0 24 24" role="img" aria-label="${title} logo" fill="${fill}"><title>${title}</title><path d="${path}"/></svg>`;
|
|
1751
|
+
}
|
|
1752
|
+
var PUBLIC_PROVIDER_CATALOG = [
|
|
1753
|
+
{
|
|
1754
|
+
id: "supabase",
|
|
1755
|
+
label: "Supabase",
|
|
1756
|
+
area: "database",
|
|
1757
|
+
stateWhenDetected: "needs_repo_fix",
|
|
1758
|
+
aliases: ["supabase", "rls", "row level security", "row-level security", "migration", "database"],
|
|
1759
|
+
iconHtml: brandSvg("Supabase", "#3FCF8E", "M11.9 1.036c-.015-.986-1.26-1.41-1.874-.637L.764 12.05C-.33 13.427.65 15.455 2.409 15.455h9.579l.113 7.51c.014.985 1.259 1.408 1.873.636l9.262-11.653c1.093-1.375.113-3.403-1.645-3.403h-9.642z"),
|
|
1760
|
+
rows: [
|
|
1761
|
+
{ id: "schema-migrations", title: "Schema and migrations", whyItMatters: "Production data shape needs repo-owned evidence before real users write to it.", whatToChange: "Add or update migration files that prove the production schema and indexes.", verifyWith: "Run npx -y viberaven --verify.", keywords: ["schema", "migration", "database"] },
|
|
1762
|
+
{ id: "rls-policies", title: "RLS policies", whyItMatters: "User data must be protected by row ownership rules before launch.", whatToChange: "Add policy SQL that enables RLS and restricts reads and writes to the authenticated owner.", verifyWith: "Run npx -y viberaven --verify.", keywords: ["rls", "policy", "row"] },
|
|
1763
|
+
{ id: "auth-callbacks", title: "Auth callbacks", whyItMatters: "Authentication breaks when production callback URLs are missing or local-only.", whatToChange: "Document production site URL and redirect URL evidence without secrets.", verifyWith: "Run npx -y viberaven --verify.", keywords: ["auth", "callback", "redirect"] },
|
|
1764
|
+
{ id: "production-env", title: "Production env", whyItMatters: "Production database URLs and keys must be explicit and safely scoped.", whatToChange: "Add safe env placeholders for URL, anon key, and server-only service-role boundaries.", verifyWith: "Run npx -y viberaven --verify.", keywords: ["env", "secret", "url"] }
|
|
1765
|
+
]
|
|
1766
|
+
},
|
|
1767
|
+
{
|
|
1768
|
+
id: "vercel",
|
|
1769
|
+
label: "Vercel",
|
|
1770
|
+
area: "deployment",
|
|
1771
|
+
stateWhenDetected: "repo_evidence_found",
|
|
1772
|
+
aliases: ["vercel", "deployment", "deploy", "hosting", "preview"],
|
|
1773
|
+
iconHtml: brandSvg("Vercel", "#000000", "m12 1.608 12 20.784H0Z"),
|
|
1774
|
+
rows: [
|
|
1775
|
+
{ id: "preview-gate", title: "Preview deployment gate", whyItMatters: "Every production change should have a reproducible preview path.", whatToChange: "Add repo evidence for preview deploys, build command, and required checks.", verifyWith: "Run npx -y viberaven --verify.", keywords: ["preview", "deploy", "ci"] },
|
|
1776
|
+
{ id: "production-env", title: "Production env", whyItMatters: "Production runtime variables must be named without leaking values.", whatToChange: "Document required production env names in repo evidence.", verifyWith: "Run npx -y viberaven --verify.", keywords: ["env", "secret"] },
|
|
1777
|
+
{ id: "domain-routing", title: "Domain and routing", whyItMatters: "The launch URL needs canonical HTTPS and predictable routes.", whatToChange: "Add evidence for the production domain, redirects, and framework routing assumptions.", verifyWith: "Run npx -y viberaven --verify.", keywords: ["domain", "route"] }
|
|
1778
|
+
]
|
|
1779
|
+
},
|
|
1780
|
+
{
|
|
1781
|
+
id: "stripe",
|
|
1782
|
+
label: "Stripe",
|
|
1783
|
+
area: "payments",
|
|
1784
|
+
stateWhenDetected: "connect_live",
|
|
1785
|
+
aliases: ["stripe", "payment", "checkout", "subscription", "webhook"],
|
|
1786
|
+
iconHtml: brandSvg("Stripe", "#635BFF", "M13.976 9.15c-2.172-.806-3.356-1.426-3.356-2.409 0-.831.683-1.305 1.901-1.305 2.227 0 4.515.858 6.09 1.631l.89-5.494C18.252.975 15.697 0 12.165 0 9.667 0 7.589.654 6.104 1.872 4.56 3.147 3.757 4.992 3.757 7.218c0 4.039 2.467 5.76 6.476 7.219 2.585.92 3.445 1.574 3.445 2.583 0 .98-.84 1.545-2.354 1.545-1.875 0-4.965-.921-6.99-2.109l-.9 5.555C5.175 22.99 8.385 24 11.714 24c2.641 0 4.843-.624 6.328-1.813 1.664-1.305 2.525-3.236 2.525-5.732 0-4.128-2.524-5.851-6.594-7.305h.003z"),
|
|
1787
|
+
rows: [
|
|
1788
|
+
{ id: "checkout-route", title: "Checkout route", whyItMatters: "Money movement needs a trusted server-side path.", whatToChange: "Add checkout or subscription route evidence with stable price env names.", verifyWith: "Run npx -y viberaven --verify.", keywords: ["checkout", "price"] },
|
|
1789
|
+
{ id: "webhook-authenticity", title: "Webhook authenticity", whyItMatters: "Payment state must be reconciled from trusted server events.", whatToChange: "Add webhook route evidence with authenticity checks and idempotency.", verifyWith: "Run npx -y viberaven --verify.", keywords: ["webhook", "authenticity"] },
|
|
1790
|
+
{ id: "customer-portal", title: "Customer portal", whyItMatters: "Users need a way to manage subscription state without support.", whatToChange: "Add repo evidence for customer portal or account payment routes.", verifyWith: "Run npx -y viberaven --verify.", keywords: ["portal", "customer"] }
|
|
1791
|
+
]
|
|
1792
|
+
},
|
|
1793
|
+
{
|
|
1794
|
+
id: "github",
|
|
1795
|
+
label: "GitHub",
|
|
1796
|
+
area: "testing",
|
|
1797
|
+
stateWhenDetected: "repo_evidence_found",
|
|
1798
|
+
aliases: ["github", "actions", "workflow", "pull request", "ci"],
|
|
1799
|
+
iconHtml: brandSvg("GitHub", "#181717", "M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12"),
|
|
1800
|
+
rows: [
|
|
1801
|
+
{ id: "required-checks", title: "Required checks", whyItMatters: "Unsafe changes should not merge without automated proof.", whatToChange: "Add workflow evidence for tests, typecheck, build, or public export verification.", verifyWith: "Run npx -y viberaven --verify.", keywords: ["actions", "workflow", "ci"] },
|
|
1802
|
+
{ id: "branch-protection", title: "Branch protection", whyItMatters: "CI is advisory unless merge rules require it.", whatToChange: "Document required checks or owner review expectations in repo evidence.", verifyWith: "Run npx -y viberaven --verify.", keywords: ["branch", "review"] }
|
|
1803
|
+
]
|
|
1804
|
+
},
|
|
1805
|
+
{
|
|
1806
|
+
id: "sentry",
|
|
1807
|
+
label: "Sentry",
|
|
1808
|
+
area: "monitoring",
|
|
1809
|
+
stateWhenDetected: "blocked",
|
|
1810
|
+
aliases: ["sentry", "error", "monitoring", "trace", "dsn"],
|
|
1811
|
+
iconHtml: brandSvg("Sentry", "#362D59", "M13.91 2.505c-.873-1.448-2.972-1.448-3.844 0L6.904 7.92a15.478 15.478 0 0 1 8.53 12.811h-2.221A13.301 13.301 0 0 0 5.784 9.814l-2.926 5.06a7.65 7.65 0 0 1 4.435 5.848H2.194a.365.365 0 0 1-.298-.534l1.413-2.402a5.16 5.16 0 0 0-1.614-.913L.296 19.275a2.182 2.182 0 0 0 .812 2.999 2.24 2.24 0 0 0 1.086.288h6.983a9.322 9.322 0 0 0-3.845-8.318l1.11-1.922a11.47 11.47 0 0 1 4.95 10.24h5.915a17.242 17.242 0 0 0-7.885-15.28l2.244-3.845a.37.37 0 0 1 .504-.13c.255.14 9.75 16.708 9.928 16.9a.365.365 0 0 1-.327.543h-2.287c.029.612.029 1.223 0 1.831h2.297a2.206 2.206 0 0 0 1.922-3.31z"),
|
|
1812
|
+
rows: [
|
|
1813
|
+
{ id: "runtime-capture", title: "Runtime capture", whyItMatters: "Production errors need a destination before users report them.", whatToChange: "Add Sentry DSN env evidence and runtime initialization proof.", verifyWith: "Run npx -y viberaven --verify.", keywords: ["dsn", "error"] },
|
|
1814
|
+
{ id: "release-context", title: "Release context", whyItMatters: "Errors need commit and release metadata to be actionable.", whatToChange: "Add release, environment, or source-map evidence.", verifyWith: "Run npx -y viberaven --verify.", keywords: ["release", "source map"] }
|
|
1815
|
+
]
|
|
1816
|
+
},
|
|
1817
|
+
{
|
|
1818
|
+
id: "clerk",
|
|
1819
|
+
label: "Clerk",
|
|
1820
|
+
area: "auth",
|
|
1821
|
+
stateWhenDetected: "connect_live",
|
|
1822
|
+
aliases: ["clerk", "auth", "session", "middleware"],
|
|
1823
|
+
iconHtml: brandSvg("Clerk", "#6C47FF", "m21.47 20.829-2.881-2.881a.572.572 0 0 0-.7-.084 6.854 6.854 0 0 1-7.081 0 .576.576 0 0 0-.7.084l-2.881 2.881a.576.576 0 0 0-.103.69.57.57 0 0 0 .166.186 12 12 0 0 0 14.113 0 .58.58 0 0 0 .239-.423.576.576 0 0 0-.172-.453Zm.002-17.668-2.88 2.88a.569.569 0 0 1-.701.084A6.857 6.857 0 0 0 8.724 8.08a6.862 6.862 0 0 0-1.222 3.692 6.86 6.86 0 0 0 .978 3.764.573.573 0 0 1-.083.699l-2.881 2.88a.567.567 0 0 1-.864-.063A11.993 11.993 0 0 1 6.771 2.7a11.99 11.99 0 0 1 14.637-.405.566.566 0 0 1 .232.418.57.57 0 0 1-.168.448Zm-7.118 12.261a3.427 3.427 0 1 0 0-6.854 3.427 3.427 0 0 0 0 6.854Z"),
|
|
1824
|
+
rows: [
|
|
1825
|
+
{ id: "session-boundary", title: "Session boundary", whyItMatters: "Protected routes need server-side identity checks.", whatToChange: "Add evidence for session middleware and protected API boundaries.", verifyWith: "Run npx -y viberaven --verify.", keywords: ["session", "middleware"] },
|
|
1826
|
+
{ id: "callback-urls", title: "Callback URLs", whyItMatters: "Auth flows fail when production redirects are missing.", whatToChange: "Document production callback and redirect env names.", verifyWith: "Run npx -y viberaven --verify.", keywords: ["callback", "redirect"] }
|
|
1827
|
+
]
|
|
1828
|
+
},
|
|
1829
|
+
{
|
|
1830
|
+
id: "posthog",
|
|
1831
|
+
label: "PostHog",
|
|
1832
|
+
area: "analytics",
|
|
1833
|
+
stateWhenDetected: "connect_live",
|
|
1834
|
+
aliases: ["posthog", "analytics", "event", "funnel"],
|
|
1835
|
+
iconHtml: brandSvg("PostHog", "#000000", "M9.854 14.5 5 9.647.854 5.5A.5.5 0 0 0 0 5.854V8.44a.5.5 0 0 0 .146.353L5 13.647l.147.146L9.854 18.5l.146.147v-.049c.065.03.134.049.207.049h2.586a.5.5 0 0 0 .353-.854L9.854 14.5zm0-5-4-4a.487.487 0 0 0-.409-.144.515.515 0 0 0-.356.21.493.493 0 0 0-.089.288V8.44a.5.5 0 0 0 .147.353l9 9a.5.5 0 0 0 .853-.354v-2.585a.5.5 0 0 0-.146-.354l-5-5zm1-4a.5.5 0 0 0-.854.354V8.44a.5.5 0 0 0 .147.353l4 4a.5.5 0 0 0 .853-.354V9.854a.5.5 0 0 0-.146-.354l-4-4zm12.647 11.515a3.863 3.863 0 0 1-2.232-1.1l-4.708-4.707a.5.5 0 0 0-.854.354v6.585a.5.5 0 0 0 .5.5H23.5a.5.5 0 0 0 .5-.5v-.6c0-.276-.225-.497-.499-.532zm-5.394.032a.8.8 0 1 1 0-1.6.8.8 0 0 1 0 1.6zM.854 15.5a.5.5 0 0 0-.854.354v2.293a.5.5 0 0 0 .5.5h2.293c.222 0 .39-.135.462-.309a.493.493 0 0 0-.109-.545L.854 15.5zM5 14.647.854 10.5a.5.5 0 0 0-.854.353v2.586a.5.5 0 0 0 .146.353L4.854 18.5l.146.147h2.793a.5.5 0 0 0 .353-.854L5 14.647z"),
|
|
1836
|
+
rows: [
|
|
1837
|
+
{ id: "event-taxonomy", title: "Event taxonomy", whyItMatters: "Analytics should answer launch questions, not just count views.", whatToChange: "Add named events for account creation, activation, and conversion points.", verifyWith: "Run npx -y viberaven --verify.", keywords: ["event", "taxonomy"] },
|
|
1838
|
+
{ id: "privacy-boundary", title: "Capture boundary", whyItMatters: "Session capture can expose sensitive fields if unchecked.", whatToChange: "Add masking, opt-out, or capture-boundary evidence.", verifyWith: "Run npx -y viberaven --verify.", keywords: ["privacy", "capture"] }
|
|
1839
|
+
]
|
|
1840
|
+
}
|
|
1841
|
+
];
|
|
1842
|
+
function haystack(value) {
|
|
1843
|
+
return JSON.stringify(value ?? {}).toLowerCase();
|
|
1844
|
+
}
|
|
1845
|
+
function gapMatchesProvider(gap, provider) {
|
|
1846
|
+
const text = [gap.id, gap.title, gap.detail, gap.category, gap.primaryMapCategory].join(" ").toLowerCase();
|
|
1847
|
+
return provider.aliases.some((alias) => text.includes(alias));
|
|
1848
|
+
}
|
|
1849
|
+
function artifactHasEvidenceForProvider(artifact, provider) {
|
|
1850
|
+
if (!artifact) return false;
|
|
1851
|
+
const text = haystack({
|
|
1852
|
+
stackWiring: artifact.stackWiring,
|
|
1853
|
+
providerRegistry: artifact.providerRegistry,
|
|
1854
|
+
missionGraph: artifact.missionGraph
|
|
1855
|
+
});
|
|
1856
|
+
return provider.aliases.some((alias) => text.includes(alias));
|
|
1857
|
+
}
|
|
1858
|
+
function gapForProvider(artifact, provider) {
|
|
1859
|
+
return artifact?.gaps.find((gap) => gapMatchesProvider(gap, provider));
|
|
1860
|
+
}
|
|
1861
|
+
function pathState(providerState, rowId, focusedRowId) {
|
|
1862
|
+
if (focusedRowId === rowId) {
|
|
1863
|
+
if (providerState === "blocked") return "blocked";
|
|
1864
|
+
if (providerState === "connect_live") return "needs_connect";
|
|
1865
|
+
return "needs_fix";
|
|
1866
|
+
}
|
|
1867
|
+
if (providerState === "repo_evidence_found") return "ready";
|
|
1868
|
+
return "not_checked";
|
|
1869
|
+
}
|
|
1870
|
+
function rowForGap(provider, gap) {
|
|
1871
|
+
if (!gap) return provider.id === "supabase" ? provider.rows[1] ?? provider.rows[0] : provider.rows[0];
|
|
1872
|
+
const text = [gap.id, gap.title, gap.detail, gap.category, gap.primaryMapCategory].join(" ").toLowerCase();
|
|
1873
|
+
const scored = provider.rows.map((row) => ({
|
|
1874
|
+
row,
|
|
1875
|
+
score: row.keywords.reduce((total, keyword) => text.includes(keyword.toLowerCase()) ? total + keyword.length : total, 0)
|
|
1876
|
+
})).sort((left, right) => right.score - left.score);
|
|
1877
|
+
return scored[0]?.score ? scored[0].row : provider.rows[0];
|
|
1878
|
+
}
|
|
975
1879
|
function send(res, status, body, contentType) {
|
|
976
1880
|
res.writeHead(status, {
|
|
977
1881
|
"content-type": contentType,
|
|
@@ -1107,6 +2011,62 @@ function tasklist(artifact) {
|
|
|
1107
2011
|
return `${lines.join("\n").trim()}
|
|
1108
2012
|
`;
|
|
1109
2013
|
}
|
|
2014
|
+
function escapeHtml(value) {
|
|
2015
|
+
return String(value ?? "").replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
2016
|
+
}
|
|
2017
|
+
function reportHtml(artifact) {
|
|
2018
|
+
const gaps = artifact.gaps.length ? artifact.gaps.map(
|
|
2019
|
+
(gap) => `<article class="gap">
|
|
2020
|
+
<div><strong>${escapeHtml(gap.title)}</strong><span>${escapeHtml(gap.severity)}</span></div>
|
|
2021
|
+
<p>${escapeHtml(gap.detail)}</p>
|
|
2022
|
+
<code>${escapeHtml(gap.id)}</code>
|
|
2023
|
+
</article>`
|
|
2024
|
+
).join("") : '<p class="empty">No local repo-evidence gaps found.</p>';
|
|
2025
|
+
const areas = artifact.missionGraph.areas.map(
|
|
2026
|
+
(area) => `<li><span>${escapeHtml(area.label)}</span><strong>${escapeHtml(area.readinessPercent)}%</strong></li>`
|
|
2027
|
+
).join("");
|
|
2028
|
+
return `<!doctype html>
|
|
2029
|
+
<html lang="en">
|
|
2030
|
+
<head>
|
|
2031
|
+
<meta charset="utf-8" />
|
|
2032
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
2033
|
+
<title>VibeRaven Local Report</title>
|
|
2034
|
+
<style>
|
|
2035
|
+
body { margin: 0; background: #fbfbfa; color: #111417; font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; }
|
|
2036
|
+
main { max-width: 920px; margin: 0 auto; padding: 42px 24px; }
|
|
2037
|
+
h1 { margin: 0 0 8px; font-size: 32px; line-height: 1.1; }
|
|
2038
|
+
.meta { color: #475467; margin: 0 0 28px; }
|
|
2039
|
+
.summary, .gap, .areas { border: 1px solid #e4e7ec; background: #fff; border-radius: 12px; box-shadow: 0 12px 28px rgba(16, 24, 40, 0.035); }
|
|
2040
|
+
.summary { padding: 22px; margin-bottom: 20px; }
|
|
2041
|
+
.status { display: inline-flex; gap: 8px; align-items: center; padding: 7px 11px; border-radius: 999px; background: #fff2e5; color: #ff7a00; font-weight: 700; }
|
|
2042
|
+
.status[data-clear="true"] { background: #e9f8f1; color: #24b26b; }
|
|
2043
|
+
.areas { list-style: none; padding: 8px 18px; margin: 0 0 22px; }
|
|
2044
|
+
.areas li { display: flex; justify-content: space-between; gap: 18px; padding: 12px 0; border-bottom: 1px solid #eef0f4; }
|
|
2045
|
+
.areas li:last-child { border-bottom: 0; }
|
|
2046
|
+
.gap { padding: 18px; margin: 12px 0; }
|
|
2047
|
+
.gap div { display: flex; justify-content: space-between; gap: 16px; }
|
|
2048
|
+
.gap span { color: #ff7a00; font-size: 13px; font-weight: 700; }
|
|
2049
|
+
.gap p { color: #475467; line-height: 1.45; }
|
|
2050
|
+
code { font-family: ui-monospace, SFMono-Regular, Consolas, monospace; font-size: 13px; }
|
|
2051
|
+
.empty { border: 1px dashed #d0d5dd; border-radius: 10px; padding: 16px; background: #fff; color: #475467; }
|
|
2052
|
+
</style>
|
|
2053
|
+
</head>
|
|
2054
|
+
<body>
|
|
2055
|
+
<main>
|
|
2056
|
+
<h1>VibeRaven Local Report</h1>
|
|
2057
|
+
<p class="meta">${escapeHtml(artifact.workspacePath)} \xB7 ${escapeHtml(artifact.scannedAt)}</p>
|
|
2058
|
+
<section class="summary">
|
|
2059
|
+
<p class="status" data-clear="${artifact.verificationSummary.status === "clear"}">${escapeHtml(artifact.verificationSummary.status)}</p>
|
|
2060
|
+
<h2>${escapeHtml(artifact.scoreLabel)}</h2>
|
|
2061
|
+
<p>${escapeHtml(artifact.summary)}</p>
|
|
2062
|
+
</section>
|
|
2063
|
+
<ul class="areas">${areas}</ul>
|
|
2064
|
+
<h2>Open gaps</h2>
|
|
2065
|
+
${gaps}
|
|
2066
|
+
</main>
|
|
2067
|
+
</body>
|
|
2068
|
+
</html>`;
|
|
2069
|
+
}
|
|
1110
2070
|
async function writeArtifacts(workspacePath, artifact) {
|
|
1111
2071
|
const out = (0, import_node_path.join)(workspacePath, ".viberaven");
|
|
1112
2072
|
await (0, import_promises.mkdir)(out, { recursive: true });
|
|
@@ -1153,37 +2113,91 @@ async function loadArtifact(workspacePath) {
|
|
|
1153
2113
|
return void 0;
|
|
1154
2114
|
}
|
|
1155
2115
|
}
|
|
2116
|
+
function projectFiles(workspacePath) {
|
|
2117
|
+
return [
|
|
2118
|
+
{ label: "Environment", path: ".env", exists: (0, import_node_fs.existsSync)((0, import_node_path.join)(workspacePath, ".env")) },
|
|
2119
|
+
{ label: "Local environment", path: ".env.local", exists: (0, import_node_fs.existsSync)((0, import_node_path.join)(workspacePath, ".env.local")) },
|
|
2120
|
+
{ label: "Env example", path: ".env.example", exists: (0, import_node_fs.existsSync)((0, import_node_path.join)(workspacePath, ".env.example")) },
|
|
2121
|
+
{ label: "Package manifest", path: "package.json", exists: (0, import_node_fs.existsSync)((0, import_node_path.join)(workspacePath, "package.json")) },
|
|
2122
|
+
{ label: "Vercel config", path: "vercel.json", exists: (0, import_node_fs.existsSync)((0, import_node_path.join)(workspacePath, "vercel.json")) },
|
|
2123
|
+
{ label: "Supabase migrations", path: "supabase/migrations", exists: (0, import_node_fs.existsSync)((0, import_node_path.join)(workspacePath, "supabase", "migrations")) }
|
|
2124
|
+
];
|
|
2125
|
+
}
|
|
1156
2126
|
function localState(cwd, artifact) {
|
|
1157
2127
|
const firstGap = artifact?.gaps[0];
|
|
1158
|
-
const
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
launchPath
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
2128
|
+
const providers = PUBLIC_PROVIDER_CATALOG.map((seed) => {
|
|
2129
|
+
const assignedGap = gapForProvider(artifact, seed);
|
|
2130
|
+
const focusedGap = assignedGap ?? (seed.id === "supabase" ? firstGap : void 0);
|
|
2131
|
+
const focusedRow = rowForGap(seed, focusedGap);
|
|
2132
|
+
const hasEvidence = artifactHasEvidenceForProvider(artifact, seed);
|
|
2133
|
+
const providerState = focusedGap ? seed.id === "sentry" ? "blocked" : "needs_repo_fix" : hasEvidence ? "repo_evidence_found" : artifact ? seed.stateWhenDetected : seed.stateWhenDetected;
|
|
2134
|
+
const launchPath = seed.rows.map((row) => ({
|
|
2135
|
+
id: row.id,
|
|
2136
|
+
title: row.title,
|
|
2137
|
+
whyItMatters: row.whyItMatters,
|
|
2138
|
+
whatToChange: row.whatToChange,
|
|
2139
|
+
verifyWith: row.verifyWith,
|
|
2140
|
+
keywords: row.keywords,
|
|
2141
|
+
area: seed.area,
|
|
2142
|
+
state: pathState(providerState, row.id, focusedGap ? focusedRow.id : void 0)
|
|
2143
|
+
}));
|
|
2144
|
+
const defaultNextFix = !artifact && seed.id === "supabase" ? {
|
|
2145
|
+
gapId: "LOCAL-SCAN-001",
|
|
2146
|
+
launchPathItemId: focusedRow.id,
|
|
2147
|
+
launchPathTitle: focusedRow.title,
|
|
2148
|
+
currentIssue: "VibeRaven has not checked this project yet.",
|
|
2149
|
+
whyItMatters: "The launch console needs repo evidence before it can focus the correct provider risk.",
|
|
2150
|
+
whatToChange: "Click Verify so VibeRaven can map package, env, deployment, test, and provider evidence.",
|
|
2151
|
+
verifyWith: "Click Verify or run npx -y viberaven --verify.",
|
|
2152
|
+
prompt: [
|
|
2153
|
+
`Run VibeRaven evidence discovery for ${(0, import_node_path.basename)(cwd) || cwd}.`,
|
|
2154
|
+
"",
|
|
2155
|
+
"Requirements:",
|
|
2156
|
+
"- Inspect package, env example, deployment, test, auth, data, payment, and monitoring evidence.",
|
|
2157
|
+
"- Identify the first repo-owned launch gap.",
|
|
2158
|
+
"- Keep all findings local and do not add secret values.",
|
|
2159
|
+
"- Re-run npx -y viberaven --verify after the first repo-code fix.",
|
|
2160
|
+
"",
|
|
2161
|
+
"Return the first concrete fix to make this app safer to ship."
|
|
2162
|
+
].join("\n")
|
|
2163
|
+
} : void 0;
|
|
2164
|
+
if (defaultNextFix) {
|
|
2165
|
+
const focused = launchPath.find((item) => item.id === defaultNextFix.launchPathItemId);
|
|
2166
|
+
if (focused) focused.state = "needs_fix";
|
|
2167
|
+
}
|
|
2168
|
+
const provider = {
|
|
2169
|
+
id: seed.id,
|
|
2170
|
+
label: seed.label,
|
|
2171
|
+
area: seed.area,
|
|
2172
|
+
state: providerState,
|
|
2173
|
+
iconHtml: seed.iconHtml,
|
|
2174
|
+
launchPath,
|
|
2175
|
+
nextFix: focusedGap ? {
|
|
2176
|
+
gapId: focusedGap.id,
|
|
2177
|
+
launchPathItemId: focusedRow.id,
|
|
2178
|
+
launchPathTitle: focusedRow.title,
|
|
2179
|
+
currentIssue: focusedGap.detail,
|
|
2180
|
+
whyItMatters: focusedRow.whyItMatters,
|
|
2181
|
+
whatToChange: focusedRow.whatToChange,
|
|
2182
|
+
verifyWith: focusedRow.verifyWith,
|
|
2183
|
+
prompt: [
|
|
2184
|
+
`Fix the ${seed.label} launch path for ${(0, import_node_path.basename)(cwd) || cwd}.`,
|
|
2185
|
+
"",
|
|
2186
|
+
`Current VibeRaven gap: ${focusedGap.id} - ${focusedGap.detail}`,
|
|
2187
|
+
"",
|
|
2188
|
+
"Requirements:",
|
|
2189
|
+
`- Address ${focusedRow.title}.`,
|
|
2190
|
+
`- ${focusedRow.whatToChange}`,
|
|
2191
|
+
"- Keep changes repo-only and do not add secret values.",
|
|
2192
|
+
"- Re-run npx -y viberaven --verify when done.",
|
|
2193
|
+
"",
|
|
2194
|
+
"Return a concise summary of what changed."
|
|
2195
|
+
].join("\n")
|
|
2196
|
+
} : defaultNextFix
|
|
2197
|
+
};
|
|
2198
|
+
return provider;
|
|
2199
|
+
});
|
|
2200
|
+
const selectedProvider = providers.find((provider) => provider.nextFix) ?? providers.find((provider) => provider.id === "supabase") ?? providers[0];
|
|
1187
2201
|
return {
|
|
1188
2202
|
version: 1,
|
|
1189
2203
|
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -1193,15 +2207,29 @@ function localState(cwd, artifact) {
|
|
|
1193
2207
|
score: artifact?.score,
|
|
1194
2208
|
scoreLabel: artifact?.scoreLabel,
|
|
1195
2209
|
summary: artifact?.summary,
|
|
1196
|
-
gateStatus: artifact?.verificationSummary.status ?? "not_checked"
|
|
2210
|
+
gateStatus: artifact?.verificationSummary.status ?? "not_checked",
|
|
2211
|
+
files: projectFiles(cwd)
|
|
1197
2212
|
},
|
|
1198
|
-
providers
|
|
1199
|
-
selectedProviderId:
|
|
1200
|
-
command: "viberaven
|
|
2213
|
+
providers,
|
|
2214
|
+
selectedProviderId: selectedProvider.id,
|
|
2215
|
+
command: "npx -y viberaven"
|
|
1201
2216
|
};
|
|
1202
2217
|
}
|
|
1203
2218
|
async function route(req, res, options) {
|
|
1204
2219
|
const url = new URL(req.url ?? "/", "http://127.0.0.1");
|
|
2220
|
+
if (req.method === "GET" && (url.pathname === "/assets/extension-icon.png" || url.pathname === "/favicon.ico")) {
|
|
2221
|
+
try {
|
|
2222
|
+
const icon = await (0, import_promises.readFile)((0, import_node_path.join)(__dirname, "extension-icon.png"));
|
|
2223
|
+
res.writeHead(200, {
|
|
2224
|
+
"content-type": "image/png",
|
|
2225
|
+
"cache-control": "public, max-age=31536000, immutable"
|
|
2226
|
+
});
|
|
2227
|
+
res.end(icon);
|
|
2228
|
+
} catch {
|
|
2229
|
+
sendJson(res, 404, { error: "Asset not found" });
|
|
2230
|
+
}
|
|
2231
|
+
return;
|
|
2232
|
+
}
|
|
1205
2233
|
if (req.method === "GET" && url.pathname === "/") {
|
|
1206
2234
|
send(res, 200, renderLocalUiHtml(), "text/html; charset=utf-8");
|
|
1207
2235
|
return;
|
|
@@ -1227,6 +2255,14 @@ async function route(req, res, options) {
|
|
|
1227
2255
|
send(res, 200, tasklist(artifact), "text/plain; charset=utf-8");
|
|
1228
2256
|
return;
|
|
1229
2257
|
}
|
|
2258
|
+
if (req.method === "GET" && url.pathname === "/api/report") {
|
|
2259
|
+
let artifact = await loadArtifact(options.cwd);
|
|
2260
|
+
if (!artifact) {
|
|
2261
|
+
artifact = await runLocalScan(options.cwd);
|
|
2262
|
+
}
|
|
2263
|
+
send(res, 200, reportHtml(artifact), "text/html; charset=utf-8");
|
|
2264
|
+
return;
|
|
2265
|
+
}
|
|
1230
2266
|
if (req.method === "POST" && (url.pathname === "/api/scan" || url.pathname === "/api/verify")) {
|
|
1231
2267
|
const artifact = await runLocalScan(options.cwd);
|
|
1232
2268
|
sendJson(res, 200, { ...localState(options.cwd, artifact), exitCode: artifact.verificationSummary.status === "clear" ? 0 : 1 });
|