@cccarv82/freya 1.0.6 → 1.0.7
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/cli/web.js +494 -221
- package/package.json +1 -1
package/cli/web.js
CHANGED
|
@@ -9,6 +9,10 @@ function guessNpmCmd() {
|
|
|
9
9
|
return process.platform === 'win32' ? 'npm.cmd' : 'npm';
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
+
function guessNpxCmd() {
|
|
13
|
+
return process.platform === 'win32' ? 'npx.cmd' : 'npx';
|
|
14
|
+
}
|
|
15
|
+
|
|
12
16
|
function guessOpenCmd() {
|
|
13
17
|
// Minimal cross-platform opener without extra deps
|
|
14
18
|
if (process.platform === 'win32') return { cmd: 'cmd', args: ['/c', 'start', ''] };
|
|
@@ -69,15 +73,29 @@ function readBody(req) {
|
|
|
69
73
|
|
|
70
74
|
function run(cmd, args, cwd) {
|
|
71
75
|
return new Promise((resolve) => {
|
|
72
|
-
|
|
76
|
+
let child;
|
|
77
|
+
try {
|
|
78
|
+
child = spawn(cmd, args, { cwd, shell: false, env: process.env });
|
|
79
|
+
} catch (e) {
|
|
80
|
+
return resolve({ code: 1, stdout: '', stderr: e.message || String(e) });
|
|
81
|
+
}
|
|
82
|
+
|
|
73
83
|
let stdout = '';
|
|
74
84
|
let stderr = '';
|
|
75
|
-
|
|
85
|
+
|
|
86
|
+
child.stdout && child.stdout.on('data', (d) => {
|
|
76
87
|
stdout += d.toString();
|
|
77
88
|
});
|
|
78
|
-
child.stderr.on('data', (d) => {
|
|
89
|
+
child.stderr && child.stderr.on('data', (d) => {
|
|
79
90
|
stderr += d.toString();
|
|
80
91
|
});
|
|
92
|
+
|
|
93
|
+
// Prevent unhandled error event (e.g., ENOENT on Windows when cmd not found)
|
|
94
|
+
child.on('error', (e) => {
|
|
95
|
+
stderr += `\n${e.message || String(e)}`;
|
|
96
|
+
resolve({ code: 1, stdout, stderr });
|
|
97
|
+
});
|
|
98
|
+
|
|
81
99
|
child.on('close', (code) => resolve({ code: code ?? 0, stdout, stderr }));
|
|
82
100
|
});
|
|
83
101
|
}
|
|
@@ -134,7 +152,7 @@ async function pickDirectoryNative() {
|
|
|
134
152
|
}
|
|
135
153
|
|
|
136
154
|
function html() {
|
|
137
|
-
// Aesthetic: “
|
|
155
|
+
// Aesthetic: “clean workstation” — light-first UI inspired by modern productivity apps.
|
|
138
156
|
return `<!doctype html>
|
|
139
157
|
<html>
|
|
140
158
|
<head>
|
|
@@ -142,336 +160,551 @@ function html() {
|
|
|
142
160
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
143
161
|
<title>FREYA Web</title>
|
|
144
162
|
<style>
|
|
163
|
+
/*
|
|
164
|
+
Design goals:
|
|
165
|
+
- Light theme by default (inspired by your reference screenshots)
|
|
166
|
+
- Dark mode toggle
|
|
167
|
+
- App-like layout: sidebar + main surface
|
|
168
|
+
- Clear onboarding and affordances
|
|
169
|
+
*/
|
|
170
|
+
|
|
145
171
|
:root {
|
|
146
|
-
--
|
|
147
|
-
--
|
|
148
|
-
--
|
|
149
|
-
--
|
|
150
|
-
|
|
172
|
+
--radius: 14px;
|
|
173
|
+
--shadow: 0 18px 55px rgba(16, 24, 40, .10);
|
|
174
|
+
--shadow2: 0 10px 20px rgba(16, 24, 40, .08);
|
|
175
|
+
--ring: 0 0 0 4px rgba(59, 130, 246, .18);
|
|
176
|
+
|
|
177
|
+
/* Light */
|
|
178
|
+
--bg: #f6f7fb;
|
|
179
|
+
--paper: #ffffff;
|
|
180
|
+
--paper2: #fbfbfd;
|
|
181
|
+
--line: rgba(16, 24, 40, .10);
|
|
182
|
+
--line2: rgba(16, 24, 40, .14);
|
|
183
|
+
--text: #0f172a;
|
|
184
|
+
--muted: rgba(15, 23, 42, .68);
|
|
185
|
+
--faint: rgba(15, 23, 42, .50);
|
|
186
|
+
|
|
187
|
+
--primary: #2563eb;
|
|
188
|
+
--primary2: #0ea5e9;
|
|
189
|
+
--accent: #f97316;
|
|
190
|
+
--ok: #16a34a;
|
|
191
|
+
--warn: #f59e0b;
|
|
192
|
+
--danger: #e11d48;
|
|
193
|
+
|
|
194
|
+
--chip: rgba(37, 99, 235, .08);
|
|
195
|
+
--chip2: rgba(249, 115, 22, .10);
|
|
196
|
+
--mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace;
|
|
197
|
+
--sans: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, Arial;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
[data-theme="dark"] {
|
|
201
|
+
--bg: #0b1020;
|
|
202
|
+
--paper: rgba(255,255,255,.06);
|
|
203
|
+
--paper2: rgba(255,255,255,.04);
|
|
204
|
+
--line: rgba(255,255,255,.12);
|
|
205
|
+
--line2: rgba(255,255,255,.18);
|
|
151
206
|
--text: #e9f0ff;
|
|
152
207
|
--muted: rgba(233,240,255,.72);
|
|
153
|
-
--faint: rgba(233,240,255,.
|
|
154
|
-
|
|
155
|
-
--
|
|
156
|
-
--
|
|
157
|
-
--
|
|
158
|
-
--
|
|
208
|
+
--faint: rgba(233,240,255,.54);
|
|
209
|
+
|
|
210
|
+
--primary: #60a5fa;
|
|
211
|
+
--primary2: #22c55e;
|
|
212
|
+
--accent: #fb923c;
|
|
213
|
+
--chip: rgba(96, 165, 250, .14);
|
|
214
|
+
--chip2: rgba(251, 146, 60, .14);
|
|
215
|
+
|
|
159
216
|
--shadow: 0 30px 70px rgba(0,0,0,.55);
|
|
160
|
-
--
|
|
217
|
+
--shadow2: 0 18px 40px rgba(0,0,0,.35);
|
|
218
|
+
--ring: 0 0 0 4px rgba(96, 165, 250, .22);
|
|
161
219
|
}
|
|
162
220
|
|
|
163
221
|
* { box-sizing: border-box; }
|
|
164
222
|
html, body { height: 100%; }
|
|
165
223
|
body {
|
|
166
224
|
margin: 0;
|
|
167
|
-
color: var(--text);
|
|
168
225
|
background:
|
|
169
|
-
radial-gradient(
|
|
170
|
-
radial-gradient(
|
|
171
|
-
radial-gradient(
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
226
|
+
radial-gradient(1200px 800px at 20% -10%, rgba(37,99,235,.12), transparent 55%),
|
|
227
|
+
radial-gradient(900px 600px at 92% 10%, rgba(249,115,22,.12), transparent 55%),
|
|
228
|
+
radial-gradient(1100px 700px at 70% 105%, rgba(14,165,233,.10), transparent 55%),
|
|
229
|
+
var(--bg);
|
|
230
|
+
color: var(--text);
|
|
231
|
+
font-family: var(--sans);
|
|
175
232
|
}
|
|
176
233
|
|
|
177
|
-
/* subtle
|
|
234
|
+
/* subtle grain */
|
|
178
235
|
body:before {
|
|
179
236
|
content: "";
|
|
180
237
|
position: fixed;
|
|
181
238
|
inset: 0;
|
|
182
239
|
pointer-events: none;
|
|
183
240
|
background-image:
|
|
184
|
-
|
|
185
|
-
radial-gradient(circle at
|
|
186
|
-
|
|
187
|
-
background-size:
|
|
241
|
+
radial-gradient(circle at 15% 20%, rgba(255,255,255,.38), transparent 32%),
|
|
242
|
+
radial-gradient(circle at 80% 10%, rgba(255,255,255,.26), transparent 38%),
|
|
243
|
+
linear-gradient(transparent 0, transparent 3px, rgba(0,0,0,.02) 4px);
|
|
244
|
+
background-size: 900px 900px, 900px 900px, 100% 7px;
|
|
245
|
+
opacity: .08;
|
|
188
246
|
mix-blend-mode: overlay;
|
|
189
|
-
opacity: .12;
|
|
190
247
|
}
|
|
191
248
|
|
|
192
|
-
|
|
249
|
+
.app {
|
|
250
|
+
max-width: 1260px;
|
|
251
|
+
margin: 18px auto;
|
|
252
|
+
padding: 0 18px;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
.frame {
|
|
256
|
+
display: grid;
|
|
257
|
+
grid-template-columns: 280px 1fr;
|
|
258
|
+
gap: 14px;
|
|
259
|
+
min-height: calc(100vh - 36px);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
@media (max-width: 980px) {
|
|
263
|
+
.frame { grid-template-columns: 1fr; }
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
.sidebar {
|
|
267
|
+
background: var(--paper);
|
|
268
|
+
border: 1px solid var(--line);
|
|
269
|
+
border-radius: var(--radius);
|
|
270
|
+
box-shadow: var(--shadow2);
|
|
271
|
+
padding: 14px;
|
|
193
272
|
position: sticky;
|
|
194
|
-
top:
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
273
|
+
top: 18px;
|
|
274
|
+
height: fit-content;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
.main {
|
|
278
|
+
background: var(--paper);
|
|
279
|
+
border: 1px solid var(--line);
|
|
280
|
+
border-radius: var(--radius);
|
|
281
|
+
box-shadow: var(--shadow);
|
|
282
|
+
overflow: hidden;
|
|
199
283
|
}
|
|
200
284
|
|
|
201
|
-
.
|
|
202
|
-
max-width: 1140px;
|
|
203
|
-
margin: 0 auto;
|
|
204
|
-
padding: 14px 18px;
|
|
285
|
+
.topbar {
|
|
205
286
|
display: flex;
|
|
206
287
|
align-items: center;
|
|
207
288
|
justify-content: space-between;
|
|
208
|
-
|
|
289
|
+
padding: 14px 16px;
|
|
290
|
+
border-bottom: 1px solid var(--line);
|
|
291
|
+
background: linear-gradient(180deg, var(--paper2), var(--paper));
|
|
209
292
|
}
|
|
210
293
|
|
|
211
294
|
.brand {
|
|
212
295
|
display: flex;
|
|
213
|
-
align-items:
|
|
214
|
-
gap:
|
|
215
|
-
|
|
296
|
+
align-items: center;
|
|
297
|
+
gap: 10px;
|
|
298
|
+
font-weight: 800;
|
|
299
|
+
letter-spacing: .08em;
|
|
216
300
|
text-transform: uppercase;
|
|
217
|
-
font-weight: 700;
|
|
218
301
|
font-size: 12px;
|
|
219
302
|
color: var(--muted);
|
|
220
303
|
}
|
|
221
304
|
|
|
222
|
-
.
|
|
223
|
-
|
|
305
|
+
.spark {
|
|
306
|
+
width: 10px;
|
|
307
|
+
height: 10px;
|
|
308
|
+
border-radius: 4px;
|
|
309
|
+
background: linear-gradient(135deg, var(--accent), var(--primary));
|
|
310
|
+
box-shadow: 0 0 0 6px rgba(249,115,22,.12);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
.actions {
|
|
314
|
+
display: flex;
|
|
315
|
+
align-items: center;
|
|
316
|
+
gap: 10px;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
.chip {
|
|
320
|
+
font-family: var(--mono);
|
|
224
321
|
font-size: 12px;
|
|
225
|
-
padding:
|
|
322
|
+
padding: 7px 10px;
|
|
226
323
|
border-radius: 999px;
|
|
227
324
|
border: 1px solid var(--line);
|
|
228
|
-
background: rgba(255,255,255,.
|
|
325
|
+
background: rgba(255,255,255,.55);
|
|
229
326
|
color: var(--faint);
|
|
230
327
|
}
|
|
231
328
|
|
|
232
|
-
.
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
329
|
+
[data-theme="dark"] .chip { background: rgba(0,0,0,.20); }
|
|
330
|
+
|
|
331
|
+
.toggle {
|
|
332
|
+
border: 1px solid var(--line);
|
|
333
|
+
border-radius: 999px;
|
|
334
|
+
background: var(--paper2);
|
|
335
|
+
padding: 7px 10px;
|
|
336
|
+
cursor: pointer;
|
|
337
|
+
color: var(--muted);
|
|
338
|
+
font-weight: 700;
|
|
339
|
+
font-size: 12px;
|
|
236
340
|
}
|
|
237
341
|
|
|
238
|
-
.
|
|
239
|
-
|
|
240
|
-
grid-template-columns: 1.2fr .8fr;
|
|
241
|
-
gap: 16px;
|
|
242
|
-
align-items: start;
|
|
243
|
-
margin-bottom: 16px;
|
|
342
|
+
.section {
|
|
343
|
+
padding: 16px;
|
|
244
344
|
}
|
|
245
345
|
|
|
246
|
-
|
|
247
|
-
|
|
346
|
+
h1 {
|
|
347
|
+
margin: 0;
|
|
348
|
+
font-size: 22px;
|
|
349
|
+
letter-spacing: -.02em;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
.subtitle {
|
|
353
|
+
margin-top: 6px;
|
|
354
|
+
color: var(--muted);
|
|
355
|
+
font-size: 13px;
|
|
356
|
+
line-height: 1.4;
|
|
357
|
+
max-width: 860px;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
.cards {
|
|
361
|
+
display: grid;
|
|
362
|
+
grid-template-columns: repeat(4, 1fr);
|
|
363
|
+
gap: 12px;
|
|
364
|
+
margin-top: 14px;
|
|
248
365
|
}
|
|
249
366
|
|
|
367
|
+
@media (max-width: 1100px) { .cards { grid-template-columns: repeat(2, 1fr);} }
|
|
368
|
+
@media (max-width: 620px) { .cards { grid-template-columns: 1fr;} }
|
|
369
|
+
|
|
250
370
|
.card {
|
|
251
371
|
border: 1px solid var(--line);
|
|
252
|
-
background:
|
|
253
|
-
border-radius:
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
372
|
+
background: var(--paper2);
|
|
373
|
+
border-radius: 14px;
|
|
374
|
+
padding: 12px;
|
|
375
|
+
display: grid;
|
|
376
|
+
gap: 6px;
|
|
377
|
+
cursor: pointer;
|
|
378
|
+
transition: transform .10s ease, border-color .16s ease, box-shadow .16s ease;
|
|
379
|
+
box-shadow: 0 1px 0 rgba(16,24,40,.04);
|
|
258
380
|
}
|
|
259
381
|
|
|
260
|
-
.card:
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
background:
|
|
265
|
-
radial-gradient(900px 220px at 25% 0%, rgba(94,234,212,.12), transparent 60%),
|
|
266
|
-
radial-gradient(900px 220px at 90% 30%, rgba(96,165,250,.10), transparent 60%);
|
|
267
|
-
opacity: .55;
|
|
268
|
-
pointer-events: none;
|
|
382
|
+
.card:hover {
|
|
383
|
+
transform: translateY(-1px);
|
|
384
|
+
border-color: var(--line2);
|
|
385
|
+
box-shadow: 0 10px 22px rgba(16,24,40,.10);
|
|
269
386
|
}
|
|
270
387
|
|
|
271
|
-
.
|
|
388
|
+
.icon {
|
|
389
|
+
width: 34px;
|
|
390
|
+
height: 34px;
|
|
391
|
+
border-radius: 12px;
|
|
392
|
+
display: grid;
|
|
393
|
+
place-items: center;
|
|
394
|
+
background: var(--chip);
|
|
395
|
+
border: 1px solid var(--line);
|
|
396
|
+
color: var(--primary);
|
|
397
|
+
font-weight: 900;
|
|
398
|
+
}
|
|
272
399
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
color: var(--muted);
|
|
400
|
+
.icon.orange { background: var(--chip2); color: var(--accent); }
|
|
401
|
+
|
|
402
|
+
.title {
|
|
403
|
+
font-weight: 800;
|
|
404
|
+
font-size: 13px;
|
|
279
405
|
}
|
|
280
406
|
|
|
281
|
-
.
|
|
282
|
-
|
|
407
|
+
.desc {
|
|
408
|
+
color: var(--muted);
|
|
283
409
|
font-size: 12px;
|
|
284
|
-
color: var(--faint);
|
|
285
410
|
line-height: 1.35;
|
|
286
411
|
}
|
|
287
412
|
|
|
288
|
-
|
|
289
|
-
display:
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
margin-
|
|
413
|
+
.grid2 {
|
|
414
|
+
display: grid;
|
|
415
|
+
grid-template-columns: 1fr 1fr;
|
|
416
|
+
gap: 14px;
|
|
417
|
+
margin-top: 14px;
|
|
293
418
|
}
|
|
294
419
|
|
|
295
|
-
.
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
420
|
+
@media (max-width: 980px) { .grid2 { grid-template-columns: 1fr; } }
|
|
421
|
+
|
|
422
|
+
.panel {
|
|
423
|
+
border: 1px solid var(--line);
|
|
424
|
+
background: var(--paper);
|
|
425
|
+
border-radius: 14px;
|
|
426
|
+
overflow: hidden;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
.panelHead {
|
|
430
|
+
display: flex;
|
|
299
431
|
align-items: center;
|
|
432
|
+
justify-content: space-between;
|
|
433
|
+
padding: 12px 12px;
|
|
434
|
+
border-bottom: 1px solid var(--line);
|
|
435
|
+
background: linear-gradient(180deg, var(--paper2), var(--paper));
|
|
300
436
|
}
|
|
301
437
|
|
|
438
|
+
.panelHead b { font-size: 12px; letter-spacing: .08em; text-transform: uppercase; color: var(--muted); }
|
|
439
|
+
|
|
440
|
+
.panelBody { padding: 12px; }
|
|
441
|
+
|
|
442
|
+
label { display: block; font-size: 12px; color: var(--muted); margin-bottom: 6px; }
|
|
443
|
+
|
|
302
444
|
input {
|
|
303
445
|
width: 100%;
|
|
304
|
-
padding:
|
|
446
|
+
padding: 11px 12px;
|
|
305
447
|
border-radius: 12px;
|
|
306
|
-
border: 1px solid
|
|
307
|
-
background: rgba(
|
|
448
|
+
border: 1px solid var(--line);
|
|
449
|
+
background: rgba(255,255,255,.72);
|
|
308
450
|
color: var(--text);
|
|
309
451
|
outline: none;
|
|
310
452
|
}
|
|
311
453
|
|
|
312
|
-
input
|
|
454
|
+
[data-theme="dark"] input { background: rgba(0,0,0,.16); }
|
|
313
455
|
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
456
|
+
input:focus { box-shadow: var(--ring); border-color: rgba(37,99,235,.35); }
|
|
457
|
+
|
|
458
|
+
.row {
|
|
459
|
+
display: grid;
|
|
460
|
+
grid-template-columns: 1fr auto;
|
|
317
461
|
gap: 10px;
|
|
318
|
-
|
|
462
|
+
align-items: center;
|
|
319
463
|
}
|
|
320
464
|
|
|
321
|
-
|
|
322
|
-
border: 1px solid
|
|
465
|
+
.btn {
|
|
466
|
+
border: 1px solid var(--line);
|
|
323
467
|
border-radius: 12px;
|
|
324
|
-
background:
|
|
468
|
+
background: var(--paper2);
|
|
325
469
|
color: var(--text);
|
|
326
470
|
padding: 10px 12px;
|
|
327
471
|
cursor: pointer;
|
|
328
|
-
|
|
329
|
-
font-
|
|
330
|
-
|
|
472
|
+
font-weight: 800;
|
|
473
|
+
font-size: 12px;
|
|
474
|
+
transition: transform .10s ease, border-color .16s ease, box-shadow .16s ease;
|
|
331
475
|
}
|
|
332
476
|
|
|
333
|
-
|
|
334
|
-
|
|
477
|
+
.btn:hover { transform: translateY(-1px); border-color: var(--line2); box-shadow: 0 10px 22px rgba(16,24,40,.10); }
|
|
478
|
+
.btn:active { transform: translateY(0); }
|
|
335
479
|
|
|
336
|
-
.primary {
|
|
337
|
-
background: linear-gradient(135deg, rgba(
|
|
338
|
-
border-color: rgba(
|
|
480
|
+
.btn.primary {
|
|
481
|
+
background: linear-gradient(135deg, rgba(37,99,235,.14), rgba(14,165,233,.12));
|
|
482
|
+
border-color: rgba(37,99,235,.22);
|
|
483
|
+
color: var(--text);
|
|
339
484
|
}
|
|
340
485
|
|
|
341
|
-
.
|
|
342
|
-
|
|
343
|
-
|
|
486
|
+
.btn.orange {
|
|
487
|
+
background: linear-gradient(135deg, rgba(249,115,22,.16), rgba(37,99,235,.08));
|
|
488
|
+
border-color: rgba(249,115,22,.24);
|
|
489
|
+
}
|
|
344
490
|
|
|
345
|
-
.
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
padding: 8px 10px;
|
|
350
|
-
border-radius: 999px;
|
|
351
|
-
border: 1px solid rgba(180,210,255,.18);
|
|
352
|
-
background: rgba(0,0,0,.18);
|
|
353
|
-
font-size: 12px;
|
|
354
|
-
color: var(--faint);
|
|
491
|
+
.btn.danger {
|
|
492
|
+
background: rgba(225,29,72,.10);
|
|
493
|
+
border-color: rgba(225,29,72,.28);
|
|
494
|
+
color: var(--text);
|
|
355
495
|
}
|
|
356
496
|
|
|
357
|
-
.
|
|
358
|
-
.dot.ok { background: var(--ok); box-shadow: 0 0 0 5px rgba(52,211,153,.12); }
|
|
359
|
-
.dot.err { background: var(--danger); box-shadow: 0 0 0 5px rgba(251,113,133,.12); }
|
|
497
|
+
.btn.small { padding: 9px 10px; font-weight: 800; }
|
|
360
498
|
|
|
361
|
-
.
|
|
362
|
-
display: grid;
|
|
363
|
-
grid-template-columns: 1fr 1fr;
|
|
364
|
-
gap: 12px;
|
|
365
|
-
}
|
|
366
|
-
@media (max-width: 980px) { .two { grid-template-columns: 1fr; } }
|
|
499
|
+
.stack { display: flex; flex-wrap: wrap; gap: 10px; }
|
|
367
500
|
|
|
368
|
-
.
|
|
501
|
+
.help {
|
|
502
|
+
margin-top: 8px;
|
|
503
|
+
color: var(--faint);
|
|
504
|
+
font-size: 12px;
|
|
505
|
+
line-height: 1.35;
|
|
506
|
+
}
|
|
369
507
|
|
|
370
508
|
.log {
|
|
509
|
+
border: 1px solid var(--line);
|
|
510
|
+
background: rgba(255,255,255,.65);
|
|
371
511
|
border-radius: 14px;
|
|
372
|
-
border: 1px solid rgba(180,210,255,.18);
|
|
373
|
-
background: rgba(7,10,16,.55);
|
|
374
512
|
padding: 12px;
|
|
375
|
-
|
|
376
|
-
max-height: 420px;
|
|
377
|
-
overflow: auto;
|
|
378
|
-
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace;
|
|
513
|
+
font-family: var(--mono);
|
|
379
514
|
font-size: 12px;
|
|
380
515
|
line-height: 1.35;
|
|
381
516
|
white-space: pre-wrap;
|
|
382
|
-
|
|
517
|
+
max-height: 420px;
|
|
518
|
+
overflow: auto;
|
|
519
|
+
color: rgba(15,23,42,.92);
|
|
383
520
|
}
|
|
384
521
|
|
|
385
|
-
.
|
|
522
|
+
[data-theme="dark"] .log { background: rgba(0,0,0,.20); color: rgba(233,240,255,.84); }
|
|
523
|
+
|
|
524
|
+
.statusRow { display:flex; align-items:center; justify-content: space-between; gap: 10px; }
|
|
525
|
+
|
|
526
|
+
.pill {
|
|
527
|
+
display: inline-flex;
|
|
528
|
+
align-items: center;
|
|
529
|
+
gap: 8px;
|
|
530
|
+
padding: 7px 10px;
|
|
531
|
+
border-radius: 999px;
|
|
532
|
+
border: 1px solid var(--line);
|
|
533
|
+
background: rgba(255,255,255,.55);
|
|
534
|
+
font-size: 12px;
|
|
535
|
+
color: var(--muted);
|
|
536
|
+
font-family: var(--mono);
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
[data-theme="dark"] .pill { background: rgba(0,0,0,.18); }
|
|
540
|
+
|
|
541
|
+
.dot { width: 8px; height: 8px; border-radius: 50%; background: var(--warn); box-shadow: 0 0 0 5px rgba(245,158,11,.15); }
|
|
542
|
+
.dot.ok { background: var(--ok); box-shadow: 0 0 0 5px rgba(22,163,74,.12); }
|
|
543
|
+
.dot.err { background: var(--danger); box-shadow: 0 0 0 5px rgba(225,29,72,.14); }
|
|
544
|
+
|
|
545
|
+
.small {
|
|
386
546
|
font-size: 12px;
|
|
387
547
|
color: var(--faint);
|
|
388
|
-
|
|
389
|
-
|
|
548
|
+
font-family: var(--mono);
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
.sidebar h3 {
|
|
552
|
+
margin: 0;
|
|
553
|
+
font-size: 12px;
|
|
554
|
+
letter-spacing: .10em;
|
|
555
|
+
text-transform: uppercase;
|
|
556
|
+
color: var(--muted);
|
|
390
557
|
}
|
|
391
558
|
|
|
392
|
-
.
|
|
393
|
-
|
|
559
|
+
.sideBlock { margin-top: 12px; padding-top: 12px; border-top: 1px dashed var(--line); }
|
|
560
|
+
|
|
561
|
+
.sidePath {
|
|
562
|
+
margin-top: 8px;
|
|
563
|
+
border: 1px solid var(--line);
|
|
564
|
+
background: var(--paper2);
|
|
565
|
+
border-radius: 12px;
|
|
566
|
+
padding: 10px;
|
|
567
|
+
font-family: var(--mono);
|
|
394
568
|
font-size: 12px;
|
|
395
|
-
color:
|
|
569
|
+
color: var(--muted);
|
|
570
|
+
word-break: break-word;
|
|
396
571
|
}
|
|
397
572
|
|
|
398
|
-
|
|
399
|
-
|
|
573
|
+
.sideBtn { width: 100%; margin-top: 8px; }
|
|
574
|
+
|
|
575
|
+
.k { display: inline-block; padding: 2px 6px; border: 1px solid var(--line); border-radius: 8px; background: rgba(255,255,255,.65); font-family: var(--mono); font-size: 12px; color: var(--muted); }
|
|
576
|
+
[data-theme="dark"] .k { background: rgba(0,0,0,.18); }
|
|
577
|
+
|
|
400
578
|
</style>
|
|
401
579
|
</head>
|
|
402
580
|
<body>
|
|
403
|
-
<
|
|
404
|
-
<div class="
|
|
405
|
-
<div class="brand">FREYA <span style="opacity:.55">•</span> web console</div>
|
|
406
|
-
<div class="badge" id="status">ready</div>
|
|
407
|
-
</div>
|
|
408
|
-
</header>
|
|
409
|
-
|
|
410
|
-
<div class="wrap">
|
|
411
|
-
<div class="hero">
|
|
412
|
-
<div class="card">
|
|
413
|
-
<h2>1) Workspace</h2>
|
|
414
|
-
<p class="sub">Escolha onde está (ou onde será criada) sua workspace da FREYA. Se você já tem uma workspace antiga, use <b>Update</b> — seus <b>data/logs</b> ficam preservados.</p>
|
|
415
|
-
|
|
416
|
-
<label>Workspace dir</label>
|
|
417
|
-
<div class="field">
|
|
418
|
-
<input id="dir" placeholder="./freya" />
|
|
419
|
-
<button class="ghost" onclick="pickDir()">Browse…</button>
|
|
420
|
-
</div>
|
|
421
|
-
<div class="hint">Dica: a workspace contém <code>data/</code>, <code>logs/</code> e <code>scripts/</code>.</div>
|
|
581
|
+
<div class="app">
|
|
582
|
+
<div class="frame">
|
|
422
583
|
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
<
|
|
426
|
-
<
|
|
427
|
-
<button onclick="doMigrate()">Migrate</button>
|
|
584
|
+
<aside class="sidebar">
|
|
585
|
+
<div style="display:flex; align-items:center; justify-content: space-between; gap:10px;">
|
|
586
|
+
<h3>FREYA</h3>
|
|
587
|
+
<span class="pill"><span class="dot" id="dot"></span><span id="pill">idle</span></span>
|
|
428
588
|
</div>
|
|
429
589
|
|
|
430
|
-
<div class="
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
<div style="height:10px"></div>
|
|
441
|
-
<label>Teams webhook URL</label>
|
|
442
|
-
<input id="teams" placeholder="https://..." />
|
|
590
|
+
<div class="sideBlock">
|
|
591
|
+
<h3>Workspace</h3>
|
|
592
|
+
<div class="sidePath" id="sidePath">./freya</div>
|
|
593
|
+
<button class="btn sideBtn" onclick="pickDir()">Select workspace…</button>
|
|
594
|
+
<button class="btn primary sideBtn" onclick="doInit()">Init workspace</button>
|
|
595
|
+
<button class="btn sideBtn" onclick="doUpdate()">Update (preserve data/logs)</button>
|
|
596
|
+
<button class="btn sideBtn" onclick="doHealth()">Health</button>
|
|
597
|
+
<button class="btn sideBtn" onclick="doMigrate()">Migrate</button>
|
|
598
|
+
<div class="help">Dica: se você já tem uma workspace antiga, use <b>Update</b>. Por padrão, <b>data/</b> e <b>logs/</b> não são sobrescritos.</div>
|
|
599
|
+
</div>
|
|
443
600
|
|
|
444
|
-
<div class="
|
|
445
|
-
|
|
446
|
-
<button onclick="publish('discord')">Publish
|
|
447
|
-
<button onclick="publish('teams')">Publish
|
|
601
|
+
<div class="sideBlock">
|
|
602
|
+
<h3>Publish</h3>
|
|
603
|
+
<button class="btn sideBtn" onclick="publish('discord')">Publish → Discord</button>
|
|
604
|
+
<button class="btn sideBtn" onclick="publish('teams')">Publish → Teams</button>
|
|
605
|
+
<div class="help">Configure os webhooks no painel principal. O publish envia o último relatório gerado.</div>
|
|
448
606
|
</div>
|
|
449
|
-
<div class="hint">O publish usa o texto do último relatório gerado. Para MVP, limitamos em ~1800 caracteres (evita limites de webhook). Depois a gente melhora para anexos/chunks.</div>
|
|
450
|
-
</div>
|
|
451
|
-
</div>
|
|
452
607
|
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
608
|
+
<div class="sideBlock">
|
|
609
|
+
<h3>Atalhos</h3>
|
|
610
|
+
<div class="help"><span class="k">--dev</span> cria dados de exemplo para testar rápido.</div>
|
|
611
|
+
<div class="help"><span class="k">--port</span> muda a porta (default 3872).</div>
|
|
612
|
+
</div>
|
|
613
|
+
</aside>
|
|
614
|
+
|
|
615
|
+
<main class="main">
|
|
616
|
+
<div class="topbar">
|
|
617
|
+
<div class="brand"><span class="spark"></span> Local-first status assistant</div>
|
|
618
|
+
<div class="actions">
|
|
619
|
+
<span class="chip" id="chipPort">127.0.0.1:3872</span>
|
|
620
|
+
<button class="toggle" id="themeToggle" onclick="toggleTheme()">Theme</button>
|
|
621
|
+
</div>
|
|
622
|
+
</div>
|
|
457
623
|
|
|
458
|
-
<div class="
|
|
459
|
-
<
|
|
460
|
-
<
|
|
461
|
-
|
|
462
|
-
<
|
|
624
|
+
<div class="section">
|
|
625
|
+
<h1>Morning, how can I help?</h1>
|
|
626
|
+
<div class="subtitle">Selecione uma workspace e gere relatórios (Executive / SM / Blockers / Daily). Você pode publicar no Discord/Teams com 1 clique.</div>
|
|
627
|
+
|
|
628
|
+
<div class="cards">
|
|
629
|
+
<div class="card" onclick="runReport('status')">
|
|
630
|
+
<div class="icon">E</div>
|
|
631
|
+
<div class="title">Executive report</div>
|
|
632
|
+
<div class="desc">Status pronto para stakeholders (entregas, projetos, blockers).</div>
|
|
633
|
+
</div>
|
|
634
|
+
<div class="card" onclick="runReport('sm-weekly')">
|
|
635
|
+
<div class="icon">S</div>
|
|
636
|
+
<div class="title">SM weekly</div>
|
|
637
|
+
<div class="desc">Resumo, wins, riscos e foco da próxima semana.</div>
|
|
638
|
+
</div>
|
|
639
|
+
<div class="card" onclick="runReport('blockers')">
|
|
640
|
+
<div class="icon orange">B</div>
|
|
641
|
+
<div class="title">Blockers</div>
|
|
642
|
+
<div class="desc">Lista priorizada por severidade + idade (pra destravar rápido).</div>
|
|
643
|
+
</div>
|
|
644
|
+
<div class="card" onclick="runReport('daily')">
|
|
645
|
+
<div class="icon">D</div>
|
|
646
|
+
<div class="title">Daily</div>
|
|
647
|
+
<div class="desc">Ontem / Hoje / Bloqueios — pronto pra standup.</div>
|
|
648
|
+
</div>
|
|
649
|
+
</div>
|
|
650
|
+
|
|
651
|
+
<div class="grid2">
|
|
652
|
+
<div class="panel">
|
|
653
|
+
<div class="panelHead"><b>Workspace & publish settings</b><span class="small" id="last"></span></div>
|
|
654
|
+
<div class="panelBody">
|
|
655
|
+
<label>Workspace dir</label>
|
|
656
|
+
<div class="row">
|
|
657
|
+
<input id="dir" placeholder="./freya" />
|
|
658
|
+
<button class="btn small" onclick="pickDir()">Browse</button>
|
|
659
|
+
</div>
|
|
660
|
+
<div class="help">Escolha a pasta que contém <code>data/</code>, <code>logs/</code> e <code>scripts/</code>.</div>
|
|
661
|
+
|
|
662
|
+
<div style="height:12px"></div>
|
|
663
|
+
|
|
664
|
+
<div class="stack">
|
|
665
|
+
<button class="btn primary" onclick="doInit()">Init</button>
|
|
666
|
+
<button class="btn" onclick="doUpdate()">Update</button>
|
|
667
|
+
<button class="btn" onclick="doHealth()">Health</button>
|
|
668
|
+
<button class="btn" onclick="doMigrate()">Migrate</button>
|
|
669
|
+
</div>
|
|
670
|
+
|
|
671
|
+
<div style="height:16px"></div>
|
|
672
|
+
|
|
673
|
+
<label>Discord webhook URL</label>
|
|
674
|
+
<input id="discord" placeholder="https://discord.com/api/webhooks/..." />
|
|
675
|
+
<div style="height:10px"></div>
|
|
676
|
+
|
|
677
|
+
<label>Teams webhook URL</label>
|
|
678
|
+
<input id="teams" placeholder="https://..." />
|
|
679
|
+
<div class="help">O publish usa incoming webhooks. (Depois a gente evolui para anexos/chunks.)</div>
|
|
680
|
+
|
|
681
|
+
<div style="height:10px"></div>
|
|
682
|
+
<div class="stack">
|
|
683
|
+
<button class="btn" onclick="publish('discord')">Publish last → Discord</button>
|
|
684
|
+
<button class="btn" onclick="publish('teams')">Publish last → Teams</button>
|
|
685
|
+
</div>
|
|
686
|
+
</div>
|
|
687
|
+
</div>
|
|
688
|
+
|
|
689
|
+
<div class="panel">
|
|
690
|
+
<div class="panelHead">
|
|
691
|
+
<b>Output</b>
|
|
692
|
+
<div class="stack">
|
|
693
|
+
<button class="btn small" onclick="copyOut()">Copy</button>
|
|
694
|
+
<button class="btn small" onclick="clearOut()">Clear</button>
|
|
695
|
+
</div>
|
|
696
|
+
</div>
|
|
697
|
+
<div class="panelBody">
|
|
698
|
+
<div class="log" id="out"></div>
|
|
699
|
+
<div class="help">Dica: quando um report gera arquivo, mostramos o conteúdo real do report aqui (melhor que stdout).</div>
|
|
700
|
+
</div>
|
|
701
|
+
</div>
|
|
702
|
+
|
|
703
|
+
</div>
|
|
463
704
|
</div>
|
|
464
705
|
|
|
465
|
-
|
|
466
|
-
</div>
|
|
706
|
+
</main>
|
|
467
707
|
|
|
468
|
-
<div class="card">
|
|
469
|
-
<h2>Output</h2>
|
|
470
|
-
<div class="pill"><span class="dot" id="dot"></span><span id="pill">idle</span></div>
|
|
471
|
-
<div style="height:10px"></div>
|
|
472
|
-
<div class="log" id="out"></div>
|
|
473
|
-
<div class="footer">Dica: se o report foi salvo em arquivo, ele aparece em “Last report”.</div>
|
|
474
|
-
</div>
|
|
475
708
|
</div>
|
|
476
709
|
</div>
|
|
477
710
|
|
|
@@ -479,12 +712,24 @@ function html() {
|
|
|
479
712
|
const $ = (id) => document.getElementById(id);
|
|
480
713
|
const state = { lastReportPath: null, lastText: '' };
|
|
481
714
|
|
|
715
|
+
function applyTheme(theme) {
|
|
716
|
+
document.documentElement.setAttribute('data-theme', theme);
|
|
717
|
+
localStorage.setItem('freya.theme', theme);
|
|
718
|
+
$('themeToggle').textContent = theme === 'dark' ? 'Light' : 'Dark';
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
function toggleTheme() {
|
|
722
|
+
const t = localStorage.getItem('freya.theme') || 'light';
|
|
723
|
+
applyTheme(t === 'dark' ? 'light' : 'dark');
|
|
724
|
+
}
|
|
725
|
+
|
|
482
726
|
function setPill(kind, text) {
|
|
483
727
|
const dot = $('dot');
|
|
484
728
|
dot.classList.remove('ok','err');
|
|
485
729
|
if (kind === 'ok') dot.classList.add('ok');
|
|
486
730
|
if (kind === 'err') dot.classList.add('err');
|
|
487
731
|
$('pill').textContent = text;
|
|
732
|
+
$('status') && ($('status').textContent = text);
|
|
488
733
|
}
|
|
489
734
|
|
|
490
735
|
function setOut(text) {
|
|
@@ -492,6 +737,21 @@ function html() {
|
|
|
492
737
|
$('out').textContent = text || '';
|
|
493
738
|
}
|
|
494
739
|
|
|
740
|
+
function clearOut() {
|
|
741
|
+
setOut('');
|
|
742
|
+
setPill('ok', 'idle');
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
async function copyOut() {
|
|
746
|
+
try {
|
|
747
|
+
await navigator.clipboard.writeText(state.lastText || '');
|
|
748
|
+
setPill('ok','copied');
|
|
749
|
+
setTimeout(() => setPill('ok','idle'), 800);
|
|
750
|
+
} catch (e) {
|
|
751
|
+
setPill('err','copy failed');
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
|
|
495
755
|
function setLast(p) {
|
|
496
756
|
state.lastReportPath = p;
|
|
497
757
|
$('last').textContent = p ? ('Last report: ' + p) : '';
|
|
@@ -507,6 +767,7 @@ function html() {
|
|
|
507
767
|
$('dir').value = localStorage.getItem('freya.dir') || './freya';
|
|
508
768
|
$('discord').value = localStorage.getItem('freya.discord') || '';
|
|
509
769
|
$('teams').value = localStorage.getItem('freya.teams') || '';
|
|
770
|
+
$('sidePath').textContent = $('dir').value || './freya';
|
|
510
771
|
}
|
|
511
772
|
|
|
512
773
|
async function api(p, body) {
|
|
@@ -527,13 +788,16 @@ function html() {
|
|
|
527
788
|
|
|
528
789
|
async function pickDir() {
|
|
529
790
|
try {
|
|
530
|
-
setPill('run','
|
|
791
|
+
setPill('run','picker…');
|
|
531
792
|
const r = await api('/api/pick-dir', {});
|
|
532
|
-
if (r && r.dir)
|
|
793
|
+
if (r && r.dir) {
|
|
794
|
+
$('dir').value = r.dir;
|
|
795
|
+
$('sidePath').textContent = r.dir;
|
|
796
|
+
}
|
|
533
797
|
saveLocal();
|
|
534
798
|
setPill('ok','ready');
|
|
535
799
|
} catch (e) {
|
|
536
|
-
setPill('err','picker
|
|
800
|
+
setPill('err','picker failed');
|
|
537
801
|
setOut(String(e && e.message ? e.message : e));
|
|
538
802
|
}
|
|
539
803
|
}
|
|
@@ -541,6 +805,7 @@ function html() {
|
|
|
541
805
|
async function doInit() {
|
|
542
806
|
try {
|
|
543
807
|
saveLocal();
|
|
808
|
+
$('sidePath').textContent = dirOrDefault();
|
|
544
809
|
setPill('run','init…');
|
|
545
810
|
setOut('');
|
|
546
811
|
const r = await api('/api/init', { dir: dirOrDefault() });
|
|
@@ -556,6 +821,7 @@ function html() {
|
|
|
556
821
|
async function doUpdate() {
|
|
557
822
|
try {
|
|
558
823
|
saveLocal();
|
|
824
|
+
$('sidePath').textContent = dirOrDefault();
|
|
559
825
|
setPill('run','update…');
|
|
560
826
|
setOut('');
|
|
561
827
|
const r = await api('/api/update', { dir: dirOrDefault() });
|
|
@@ -571,6 +837,7 @@ function html() {
|
|
|
571
837
|
async function doHealth() {
|
|
572
838
|
try {
|
|
573
839
|
saveLocal();
|
|
840
|
+
$('sidePath').textContent = dirOrDefault();
|
|
574
841
|
setPill('run','health…');
|
|
575
842
|
setOut('');
|
|
576
843
|
const r = await api('/api/health', { dir: dirOrDefault() });
|
|
@@ -586,6 +853,7 @@ function html() {
|
|
|
586
853
|
async function doMigrate() {
|
|
587
854
|
try {
|
|
588
855
|
saveLocal();
|
|
856
|
+
$('sidePath').textContent = dirOrDefault();
|
|
589
857
|
setPill('run','migrate…');
|
|
590
858
|
setOut('');
|
|
591
859
|
const r = await api('/api/migrate', { dir: dirOrDefault() });
|
|
@@ -601,6 +869,7 @@ function html() {
|
|
|
601
869
|
async function runReport(name) {
|
|
602
870
|
try {
|
|
603
871
|
saveLocal();
|
|
872
|
+
$('sidePath').textContent = dirOrDefault();
|
|
604
873
|
setPill('run', name + '…');
|
|
605
874
|
setOut('');
|
|
606
875
|
const r = await api('/api/report', { dir: dirOrDefault(), script: name });
|
|
@@ -617,10 +886,10 @@ function html() {
|
|
|
617
886
|
async function publish(target) {
|
|
618
887
|
try {
|
|
619
888
|
saveLocal();
|
|
620
|
-
if (!state.lastText) throw new Error('
|
|
889
|
+
if (!state.lastText) throw new Error('Gere um relatório primeiro.');
|
|
621
890
|
const webhookUrl = target === 'discord' ? $('discord').value.trim() : $('teams').value.trim();
|
|
622
|
-
if (!webhookUrl) throw new Error('Configure
|
|
623
|
-
setPill('run','
|
|
891
|
+
if (!webhookUrl) throw new Error('Configure o webhook antes.');
|
|
892
|
+
setPill('run','publish…');
|
|
624
893
|
await api('/api/publish', { webhookUrl, text: state.lastText });
|
|
625
894
|
setPill('ok','published');
|
|
626
895
|
} catch (e) {
|
|
@@ -629,7 +898,11 @@ function html() {
|
|
|
629
898
|
}
|
|
630
899
|
}
|
|
631
900
|
|
|
901
|
+
// init
|
|
902
|
+
applyTheme(localStorage.getItem('freya.theme') || 'light');
|
|
903
|
+
$('chipPort').textContent = location.host;
|
|
632
904
|
loadLocal();
|
|
905
|
+
setPill('ok','idle');
|
|
633
906
|
</script>
|
|
634
907
|
</body>
|
|
635
908
|
</html>`;
|
|
@@ -743,14 +1016,14 @@ async function cmdWeb({ port, dir, open, dev }) {
|
|
|
743
1016
|
|
|
744
1017
|
if (req.url === '/api/init') {
|
|
745
1018
|
const pkg = '@cccarv82/freya';
|
|
746
|
-
const r = await run(
|
|
1019
|
+
const r = await run(guessNpxCmd(), [pkg, 'init', workspaceDir], process.cwd());
|
|
747
1020
|
return safeJson(res, r.code === 0 ? 200 : 400, { output: (r.stdout + r.stderr).trim() });
|
|
748
1021
|
}
|
|
749
1022
|
|
|
750
1023
|
if (req.url === '/api/update') {
|
|
751
1024
|
const pkg = '@cccarv82/freya';
|
|
752
1025
|
fs.mkdirSync(workspaceDir, { recursive: true });
|
|
753
|
-
const r = await run(
|
|
1026
|
+
const r = await run(guessNpxCmd(), [pkg, 'init', '--here'], workspaceDir);
|
|
754
1027
|
return safeJson(res, r.code === 0 ? 200 : 400, { output: (r.stdout + r.stderr).trim() });
|
|
755
1028
|
}
|
|
756
1029
|
|