@cccarv82/freya 1.0.33 → 1.0.35

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-ui.css CHANGED
@@ -2,30 +2,33 @@
2
2
  Keep this file plain CSS to avoid escaping issues in inline template literals. */
3
3
 
4
4
  :root {
5
- --radius: 14px;
6
- --shadow: 0 18px 55px rgba(16, 24, 40, .10);
7
- --shadow2: 0 10px 20px rgba(16, 24, 40, .08);
8
- --ring: 0 0 0 4px rgba(59, 130, 246, .18);
9
-
10
- --bg: #f3f6fb;
11
- --paper: #fff;
12
- --paper2: rgba(255,255,255,.78);
13
- --line: rgba(15, 23, 42, .10);
14
- --line2: rgba(15, 23, 42, .18);
15
- --text: #0f172a;
16
- --muted: rgba(15, 23, 42, .72);
17
- --faint: rgba(15, 23, 42, .55);
5
+ --radius: 16px;
6
+ --radius2: 12px;
7
+ --shadow: 0 20px 60px rgba(16, 24, 40, .12);
8
+ --shadow2: 0 10px 22px rgba(16, 24, 40, .10);
9
+ --ring: 0 0 0 4px rgba(37, 99, 235, .18);
10
+
11
+ /* Warm cream palette (light-first) */
12
+ --bg: #f7f0e6;
13
+ --paper: #fffaf2;
14
+ --paper2: rgba(255, 250, 242, .78);
15
+ --line: rgba(30, 41, 59, .10);
16
+ --line2: rgba(30, 41, 59, .18);
17
+ --text: #111827;
18
+ --muted: rgba(17, 24, 39, .74);
19
+ --faint: rgba(17, 24, 39, .56);
20
+
18
21
  --accent: #f97316;
19
22
  --primary: #2563eb;
20
- --chip: rgba(37, 99, 235, .09);
21
- --chip2: rgba(249, 115, 22, .12);
23
+ --chip: rgba(37, 99, 235, .10);
24
+ --chip2: rgba(249, 115, 22, .14);
22
25
 
23
26
  --mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
24
27
  --sans: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, "Helvetica Neue", Arial, "Noto Sans", "Liberation Sans", sans-serif;
25
28
  }
26
29
 
27
30
  [data-theme="dark"] {
28
- --bg: #0b1220;
31
+ --bg: #0b111c;
29
32
  --paper: rgba(17, 24, 39, .92);
30
33
  --paper2: rgba(17, 24, 39, .78);
31
34
  --line: rgba(148, 163, 184, .16);
@@ -42,35 +45,44 @@
42
45
  body {
43
46
  margin: 0;
44
47
  font-family: var(--sans);
45
- background: radial-gradient(900px 400px at 20% 0%, rgba(37,99,235,.14), transparent 60%),
46
- radial-gradient(900px 450px at 90% 10%, rgba(249,115,22,.12), transparent 60%),
47
- var(--bg);
48
+ background:
49
+ radial-gradient(900px 400px at 12% 0%, rgba(249,115,22,.12), transparent 60%),
50
+ radial-gradient(900px 450px at 90% 8%, rgba(37,99,235,.14), transparent 60%),
51
+ var(--bg);
48
52
  color: var(--text);
49
53
  }
50
54
 
51
- .app { padding: 20px; }
55
+ .app { padding: 18px; }
52
56
 
53
57
  .frame {
54
- max-width: 1200px;
58
+ max-width: 1400px;
55
59
  margin: 0 auto;
56
60
  border-radius: 22px;
57
61
  overflow: hidden;
58
62
  box-shadow: var(--shadow);
59
63
  border: 1px solid var(--line);
60
- background: rgba(255,255,255,.35);
64
+ background: rgba(255, 250, 242, .35);
61
65
  }
62
66
 
63
67
  .shell {
64
68
  display: grid;
65
- grid-template-columns: 290px 1fr;
69
+ grid-template-columns: 290px minmax(520px, 1fr) 380px;
70
+ min-height: calc(100vh - 36px);
66
71
  }
67
72
 
68
- @media (max-width: 980px) {
73
+ @media (max-width: 1200px) {
74
+ .shell { grid-template-columns: 280px 1fr; }
75
+ .chatPane { display: none; }
76
+ }
77
+
78
+ @media (max-width: 860px) {
69
79
  .shell { grid-template-columns: 1fr; }
80
+ .sidebar { border-right: none; border-bottom: 1px solid var(--line); }
70
81
  }
71
82
 
83
+ /* LEFT */
72
84
  .sidebar {
73
- background: linear-gradient(180deg, rgba(255,255,255,.75), rgba(255,255,255,.45));
85
+ background: linear-gradient(180deg, rgba(255,250,242,.85), rgba(255,250,242,.60));
74
86
  border-right: 1px solid var(--line);
75
87
  padding: 14px;
76
88
  }
@@ -88,7 +100,7 @@ body {
88
100
 
89
101
  .sideHeader .logo {
90
102
  font-weight: 900;
91
- letter-spacing: .08em;
103
+ letter-spacing: .10em;
92
104
  font-size: 12px;
93
105
  color: var(--muted);
94
106
  }
@@ -119,39 +131,41 @@ body {
119
131
  }
120
132
 
121
133
  .sideGroup { margin-top: 10px; padding: 10px 6px; border-top: 1px solid var(--line); }
122
- .sideTitle { font-weight: 800; font-size: 11px; letter-spacing: .08em; text-transform: uppercase; color: var(--muted); margin-bottom: 8px; }
134
+ .sideTitle { font-weight: 800; font-size: 11px; letter-spacing: .08em; text-transform: uppercase; color: var(--muted); margin-bottom: 10px; }
123
135
 
124
- .btn {
136
+ .cardsMini { display: grid; gap: 8px; }
137
+ .miniCard {
138
+ width: 100%;
139
+ text-align: left;
125
140
  border: 1px solid var(--line);
126
- border-radius: 12px;
141
+ border-radius: 14px;
127
142
  background: var(--paper2);
128
143
  padding: 10px 12px;
129
144
  cursor: pointer;
145
+ display: flex;
146
+ align-items: center;
147
+ gap: 10px;
130
148
  font-weight: 800;
131
149
  color: var(--muted);
132
150
  }
151
+ .miniCard:hover { border-color: var(--line2); box-shadow: var(--shadow2); }
133
152
 
134
- .btn:hover { border-color: var(--line2); box-shadow: var(--shadow2); }
135
- .btn.small { padding: 7px 10px; border-radius: 10px; font-size: 12px; }
136
- .btn.primary { background: rgba(37,99,235,.10); border-color: rgba(37,99,235,.30); color: rgba(37,99,235,.95); }
137
-
138
- .sideBtn { width: 100%; text-align: left; margin-top: 8px; }
139
-
140
- .k {
141
- display: inline-block;
142
- padding: 2px 6px;
153
+ .miniIcon {
154
+ width: 26px;
155
+ height: 26px;
156
+ border-radius: 10px;
157
+ display: grid;
158
+ place-items: center;
159
+ background: var(--chip);
143
160
  border: 1px solid var(--line);
144
- border-radius: 8px;
145
- background: rgba(255,255,255,.65);
146
- font-family: var(--mono);
147
- font-size: 12px;
148
- color: var(--muted);
161
+ color: var(--primary);
162
+ font-weight: 900;
149
163
  }
164
+ .miniIcon.warn { background: var(--chip2); color: var(--accent); }
150
165
 
151
- [data-theme="dark"] .k { background: rgba(0,0,0,.18); }
152
-
153
- .main { background: rgba(255,255,255,.58); }
154
- [data-theme="dark"] .main { background: rgba(10, 14, 26, .55); }
166
+ /* CENTER */
167
+ .center { background: rgba(255, 250, 242, .58); }
168
+ [data-theme="dark"] .center { background: rgba(10, 14, 26, .55); }
155
169
 
156
170
  .topbar {
157
171
  display: flex;
@@ -159,67 +173,177 @@ body {
159
173
  align-items: center;
160
174
  padding: 14px 16px;
161
175
  border-bottom: 1px solid var(--line);
162
- background: linear-gradient(90deg, rgba(255,255,255,.65), rgba(255,255,255,.35));
176
+ background: linear-gradient(90deg, rgba(255,250,242,.75), rgba(255,250,242,.45));
163
177
  }
164
178
 
165
179
  [data-theme="dark"] .topbar { background: linear-gradient(90deg, rgba(17,24,39,.88), rgba(17,24,39,.55)); }
166
180
 
167
181
  .brand { display: flex; align-items: center; gap: 10px; font-weight: 900; font-size: 12px; letter-spacing: .08em; text-transform: uppercase; color: var(--muted); }
168
- .spark { width: 10px; height: 10px; border-radius: 4px; background: linear-gradient(135deg, var(--accent), var(--primary)); box-shadow: 0 0 0 6px rgba(249,115,22,.12); }
182
+ .spark { width: 10px; height: 10px; border-radius: 4px; background: linear-gradient(135deg, var(--accent), var(--primary)); box-shadow: 0 0 0 6px rgba(249,115,22,.10); }
169
183
  .actions { display: flex; align-items: center; gap: 10px; }
170
- .chip { font-family: var(--mono); font-size: 12px; padding: 7px 10px; border-radius: 999px; border: 1px solid var(--line); background: rgba(255,255,255,.55); color: var(--faint); }
184
+ .chip { font-family: var(--mono); font-size: 12px; padding: 7px 10px; border-radius: 999px; border: 1px solid var(--line); background: rgba(255,250,242,.65); color: var(--faint); }
171
185
  [data-theme="dark"] .chip { background: rgba(0,0,0,.20); }
172
- .toggle { border: 1px solid var(--line); border-radius: 999px; background: var(--paper2); padding: 7px 10px; cursor: pointer; color: var(--muted); font-weight: 700; font-size: 12px; }
186
+ .toggle { border: 1px solid var(--line); border-radius: 999px; background: var(--paper2); padding: 7px 10px; cursor: pointer; color: var(--muted); font-weight: 800; font-size: 12px; }
173
187
 
174
- .section { padding: 16px; }
188
+ .centerBody { padding: 16px; }
189
+ .centerHead { display: flex; justify-content: space-between; align-items: flex-end; gap: 18px; margin-bottom: 14px; }
190
+ .statusLine { display:flex; align-items:center; justify-content:flex-end; gap: 12px; }
175
191
 
176
192
  h1 { margin: 0; font-size: 22px; letter-spacing: -.02em; }
177
193
  .subtitle { margin-top: 6px; color: var(--muted); font-size: 13px; line-height: 1.4; max-width: 860px; }
178
194
 
179
- .cards { display: grid; grid-template-columns: repeat(4, 1fr); gap: 12px; margin-top: 14px; }
180
- @media (max-width: 1100px) { .cards { grid-template-columns: repeat(2, 1fr);} }
181
- @media (max-width: 620px) { .cards { grid-template-columns: 1fr;} }
182
-
183
- .card { border: 1px solid var(--line); background: var(--paper2); border-radius: 14px; padding: 12px; display: grid; gap: 6px; cursor: pointer; transition: transform .10s ease, border-color .16s ease, box-shadow .16s ease; box-shadow: 0 1px 0 rgba(16,24,40,.04); }
184
- .card:hover { transform: translateY(-1px); border-color: var(--line2); box-shadow: 0 10px 22px rgba(16,24,40,.10); }
185
-
186
- .icon { width: 34px; height: 34px; border-radius: 12px; display: grid; place-items: center; background: var(--chip); border: 1px solid var(--line); color: var(--primary); font-weight: 900; }
187
- .icon.orange { background: var(--chip2); color: var(--accent); }
188
-
189
- .title { font-weight: 800; font-size: 13px; }
190
- .desc { color: var(--muted); font-size: 12px; line-height: 1.35; }
191
-
192
- .grid2 { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; margin-top: 14px; }
193
- @media (max-width: 980px) { .grid2 { grid-template-columns: 1fr; } }
195
+ .midGrid { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; }
196
+ .midSpan { grid-column: 1 / -1; }
197
+ @media (max-width: 980px) {
198
+ .midGrid { grid-template-columns: 1fr; }
199
+ .midSpan { grid-column: auto; }
200
+ }
194
201
 
195
- .panel { border: 1px solid var(--line); background: var(--paper); border-radius: 14px; overflow: hidden; }
202
+ .panel { border: 1px solid var(--line); background: var(--paper); border-radius: var(--radius); overflow: hidden; box-shadow: 0 1px 0 rgba(16,24,40,.04); }
196
203
  .panelHead { display: flex; align-items: center; justify-content: space-between; padding: 12px 12px; border-bottom: 1px solid var(--line); background: linear-gradient(180deg, var(--paper2), var(--paper)); }
197
204
  .panelHead b { font-size: 12px; letter-spacing: .08em; text-transform: uppercase; color: var(--muted); }
198
205
  .panelBody { padding: 12px; }
199
206
 
207
+ /* DEV DRAWER */
208
+ .devDrawer { margin-top: 14px; border: 1px dashed var(--line2); border-radius: var(--radius); background: rgba(255,250,242,.42); }
209
+ [data-theme="dark"] .devDrawer { background: rgba(0,0,0,.18); }
210
+ .devDrawer > summary {
211
+ cursor: pointer;
212
+ list-style: none;
213
+ padding: 12px 14px;
214
+ font-weight: 900;
215
+ color: var(--muted);
216
+ }
217
+ .devDrawer > summary::-webkit-details-marker { display:none; }
218
+ .devBody { padding: 0 14px 14px; }
219
+ .devGrid { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 14px; }
220
+ @media (max-width: 1100px) { .devGrid { grid-template-columns: 1fr; } }
221
+
222
+ /* FORMS */
200
223
  label { display: block; font-size: 12px; color: var(--muted); margin-bottom: 6px; }
201
224
  input { width: 100%; padding: 11px 12px; border-radius: 12px; border: 1px solid var(--line); background: rgba(255,255,255,.72); color: var(--text); outline: none; }
202
225
  [data-theme="dark"] input { background: rgba(0,0,0,.16); }
203
226
  input:focus { box-shadow: var(--ring); border-color: rgba(37,99,235,.35); }
227
+ textarea {
228
+ width: 100%;
229
+ padding: 10px 12px;
230
+ border-radius: 12px;
231
+ border: 1px solid var(--line);
232
+ background: rgba(255,255,255,.72);
233
+ color: var(--text);
234
+ outline: none;
235
+ resize: vertical;
236
+ }
237
+ [data-theme="dark"] textarea { background: rgba(0,0,0,.16); }
238
+ textarea:focus { box-shadow: var(--ring); border-color: rgba(37,99,235,.35); }
204
239
 
205
240
  .row { display: grid; grid-template-columns: 1fr auto; gap: 10px; align-items: center; }
206
241
  .stack { display: flex; gap: 10px; flex-wrap: wrap; }
207
242
  .help { font-size: 12px; color: var(--faint); line-height: 1.4; }
243
+ .small { font-size: 12px; color: var(--faint); }
208
244
 
209
- .log { padding: 12px; border: 1px solid var(--line); border-radius: 14px; background: rgba(255,255,255,.65); font-family: var(--mono); font-size: 12px; color: var(--muted); white-space: pre-wrap; word-break: break-word; max-height: 340px; overflow: auto; }
245
+ /* BUTTONS */
246
+ .btn {
247
+ border: 1px solid var(--line);
248
+ border-radius: 12px;
249
+ background: var(--paper2);
250
+ padding: 10px 12px;
251
+ cursor: pointer;
252
+ font-weight: 900;
253
+ color: var(--muted);
254
+ }
255
+
256
+ .btn:hover { border-color: var(--line2); box-shadow: var(--shadow2); }
257
+ .btn.small { padding: 7px 10px; border-radius: 10px; font-size: 12px; }
258
+ .btn.primary { background: rgba(37,99,235,.12); border-color: rgba(37,99,235,.34); color: rgba(37,99,235,.95); }
259
+ .sideBtn { width: 100%; text-align: left; margin-top: 8px; }
260
+
261
+ .k {
262
+ display: inline-block;
263
+ padding: 2px 6px;
264
+ border: 1px solid var(--line);
265
+ border-radius: 8px;
266
+ background: rgba(255,250,242,.75);
267
+ font-family: var(--mono);
268
+ font-size: 12px;
269
+ color: var(--muted);
270
+ }
271
+ [data-theme="dark"] .k { background: rgba(0,0,0,.18); }
272
+
273
+ /* REPORT/TODAY ITEMS + PREVIEW */
274
+ .log { padding: 12px; border: 1px solid var(--line); border-radius: var(--radius); background: rgba(255,250,242,.65); font-family: var(--mono); font-size: 12px; color: var(--muted); white-space: pre-wrap; word-break: break-word; max-height: 360px; overflow: auto; }
210
275
  [data-theme="dark"] .log { background: rgba(0,0,0,.18); }
211
276
 
212
277
  .rep { width: 100%; text-align: left; border: 1px solid var(--line); border-radius: 12px; background: var(--paper2); padding: 10px 12px; cursor: pointer; font-family: var(--mono); font-size: 12px; color: var(--muted); }
213
278
  .rep:hover { border-color: var(--line2); box-shadow: 0 10px 22px rgba(16,24,40,.10); }
214
279
  .repActive { border-color: rgba(59,130,246,.55); box-shadow: 0 0 0 4px rgba(59,130,246,.12); }
215
280
 
281
+ /* Markdown */
216
282
  .md-h1{ font-size: 20px; margin: 10px 0 6px; }
217
283
  .md-h2{ font-size: 16px; margin: 10px 0 6px; }
218
284
  .md-h3{ font-size: 14px; margin: 10px 0 6px; }
219
285
  .md-p{ margin: 6px 0; color: var(--muted); line-height: 1.5; }
220
286
  .md-ul{ margin: 6px 0 6px 18px; color: var(--muted); }
221
- .md-inline{ font-family: var(--mono); font-size: 12px; padding: 2px 6px; border: 1px solid var(--line); border-radius: 8px; background: rgba(255,255,255,.55); }
287
+ .md-inline{ font-family: var(--mono); font-size: 12px; padding: 2px 6px; border: 1px solid var(--line); border-radius: 8px; background: rgba(255,250,242,.60); }
222
288
  [data-theme="dark"] .md-inline{ background: rgba(0,0,0,.18); }
223
- .md-code{ background: rgba(0,0,0,.05); border: 1px solid var(--line); border-radius: 14px; padding: 12px; overflow:auto; }
289
+ .md-code{ background: rgba(0,0,0,.05); border: 1px solid var(--line); border-radius: var(--radius); padding: 12px; overflow:auto; }
224
290
  [data-theme="dark"] .md-code{ background: rgba(0,0,0,.22); }
225
291
  .md-sp{ height: 10px; }
292
+
293
+ /* RIGHT: CHAT */
294
+ .chatPane {
295
+ border-left: 1px solid var(--line);
296
+ background: linear-gradient(180deg, rgba(255,250,242,.82), rgba(255,250,242,.60));
297
+ display: grid;
298
+ grid-template-rows: auto 1fr auto;
299
+ }
300
+ [data-theme="dark"] .chatPane {
301
+ background: linear-gradient(180deg, rgba(17,24,39,.92), rgba(17,24,39,.68));
302
+ }
303
+
304
+ .chatHead { padding: 14px; border-bottom: 1px solid var(--line); }
305
+ .chatTitle { font-weight: 950; letter-spacing: -.01em; }
306
+ .chatSub { margin-top: 2px; font-size: 12px; color: var(--faint); }
307
+
308
+ .chatThread {
309
+ padding: 14px;
310
+ overflow: auto;
311
+ display: flex;
312
+ flex-direction: column;
313
+ gap: 10px;
314
+ }
315
+
316
+ .bubble {
317
+ max-width: 92%;
318
+ border-radius: 18px;
319
+ padding: 10px 12px;
320
+ border: 1px solid var(--line);
321
+ box-shadow: 0 1px 0 rgba(16,24,40,.03);
322
+ }
323
+
324
+ .bubble .bubbleMeta { font-size: 11px; font-weight: 900; color: var(--faint); margin-bottom: 6px; }
325
+ .bubble .bubbleBody { font-size: 13px; line-height: 1.45; color: var(--text); }
326
+
327
+ .bubble.user {
328
+ align-self: flex-end;
329
+ background: rgba(37,99,235,.10);
330
+ border-color: rgba(37,99,235,.22);
331
+ }
332
+
333
+ .bubble.assistant {
334
+ align-self: flex-start;
335
+ background: rgba(255,250,242,.82);
336
+ }
337
+ [data-theme="dark"] .bubble.assistant { background: rgba(0,0,0,.18); }
338
+
339
+ .chatComposer {
340
+ padding: 12px 14px 14px;
341
+ border-top: 1px solid var(--line);
342
+ background: rgba(255,250,242,.55);
343
+ }
344
+ [data-theme="dark"] .chatComposer { background: rgba(0,0,0,.12); }
345
+
346
+ .composerActions { display: flex; gap: 10px; margin-top: 10px; flex-wrap: wrap; }
347
+ .composerToggles { margin-top: 10px; display: grid; gap: 6px; }
348
+ .toggleRow { display:flex; align-items:center; gap:10px; user-select:none; font-size: 12px; color: var(--muted); }
349
+ .statusFooter { margin-top: 10px; display:flex; justify-content:flex-end; }
package/cli/web-ui.js CHANGED
@@ -114,6 +114,37 @@
114
114
  return html;
115
115
  }
116
116
 
117
+ function chatAppend(role, text, opts = {}) {
118
+ const thread = $('chatThread');
119
+ if (!thread) return;
120
+
121
+ const bubble = document.createElement('div');
122
+ bubble.className = 'bubble ' + (role === 'user' ? 'user' : 'assistant');
123
+
124
+ const meta = document.createElement('div');
125
+ meta.className = 'bubbleMeta';
126
+ meta.textContent = role === 'user' ? 'You' : 'FREYA';
127
+
128
+ const body = document.createElement('div');
129
+ body.className = 'bubbleBody';
130
+
131
+ const raw = String(text || '');
132
+ if (opts.markdown) {
133
+ body.innerHTML = renderMarkdown(raw);
134
+ } else {
135
+ body.innerHTML = escapeHtml(raw).replace(/\n/g, '<br>');
136
+ }
137
+
138
+ bubble.appendChild(meta);
139
+ bubble.appendChild(body);
140
+ thread.appendChild(bubble);
141
+
142
+ // keep newest in view
143
+ try {
144
+ thread.scrollTop = thread.scrollHeight;
145
+ } catch {}
146
+ }
147
+
117
148
  function setOut(text) {
118
149
  state.lastText = text || '';
119
150
  const el = $('reportPreview');
@@ -717,6 +748,8 @@
717
748
  return;
718
749
  }
719
750
 
751
+ chatAppend('user', text);
752
+
720
753
  setPill('run', 'saving…');
721
754
  await api('/api/inbox/add', { dir: dirOrDefault(), text });
722
755
 
@@ -727,7 +760,9 @@
727
760
 
728
761
  // Show plan output in Preview panel
729
762
  const header = r.ok === false ? '## Agent Plan (planner unavailable)\n\n' : '## Agent Plan (draft)\n\n';
730
- setOut(header + (r.plan || ''));
763
+ const planOut = header + (r.plan || '');
764
+ setOut(planOut);
765
+ chatAppend('assistant', planOut, { markdown: true });
731
766
  ta.value = '';
732
767
 
733
768
  if (r.ok === false) {
@@ -807,6 +842,7 @@
807
842
  }
808
843
 
809
844
  setOut(msg);
845
+ chatAppend('assistant', msg, { markdown: true });
810
846
  setPill('ok', 'applied');
811
847
  setTimeout(() => setPill('ok', 'idle'), 800);
812
848
  } catch (e) {
@@ -821,6 +857,18 @@
821
857
  $('chipPort').textContent = location.host;
822
858
  loadLocal();
823
859
 
860
+ // Developer drawer (persist open/close)
861
+ try {
862
+ const d = $('devDrawer');
863
+ if (d) {
864
+ const open = localStorage.getItem('freya.devDrawer') === '1';
865
+ if (open) d.open = true;
866
+ d.addEventListener('toggle', () => {
867
+ try { localStorage.setItem('freya.devDrawer', d.open ? '1' : '0'); } catch {}
868
+ });
869
+ }
870
+ } catch {}
871
+
824
872
  // Load persisted settings from the workspace + bootstrap (auto-init + auto-health)
825
873
  (async () => {
826
874
  let defaults = null;
package/cli/web.js CHANGED
@@ -277,6 +277,38 @@ function postTeamsCard(url, card) {
277
277
  return postJson(url, card);
278
278
  }
279
279
 
280
+ function extractFirstJsonObject(text) {
281
+ const t = String(text || '');
282
+ const start = t.indexOf('{');
283
+ if (start === -1) return null;
284
+
285
+ let depth = 0;
286
+ let inString = false;
287
+ let esc = false;
288
+
289
+ for (let i = start; i < t.length; i++) {
290
+ const ch = t[i];
291
+
292
+ if (esc) { esc = false; continue; }
293
+ if (ch === '\\') { esc = true; continue; }
294
+
295
+ if (ch === '"') {
296
+ inString = !inString;
297
+ continue;
298
+ }
299
+
300
+ if (inString) continue;
301
+
302
+ if (ch === '{') depth++;
303
+ if (ch === '}') {
304
+ depth--;
305
+ if (depth === 0) return t.slice(start, i + 1);
306
+ }
307
+ }
308
+
309
+ return null;
310
+ }
311
+
280
312
  function escapeJsonControlChars(jsonText) {
281
313
  // Replace unescaped control chars inside JSON string literals with safe escapes.
282
314
  // Handles Copilot outputs where newlines/tabs leak into string values.
@@ -645,6 +677,7 @@ function buildHtml(safeDefault) {
645
677
  <div class="frame">
646
678
  <div class="shell">
647
679
 
680
+ <!-- LEFT: Workspaces / Topics -->
648
681
  <aside class="sidebar">
649
682
  <div class="sideHeader">
650
683
  <div class="logo">FREYA</div>
@@ -654,47 +687,41 @@ function buildHtml(safeDefault) {
654
687
  <div class="sidePath" id="sidePath">./freya</div>
655
688
 
656
689
  <div class="sideGroup">
657
- <div class="sideTitle">Workspace</div>
658
- <button class="btn sideBtn" onclick="pickDir()">Select workspace…</button>
659
- <button class="btn sideBtn" onclick="doUpdate()">Sync workspace</button>
660
- <button class="btn sideBtn" onclick="doMigrate()">Migrate data</button>
690
+ <div class="sideTitle">Workspaces</div>
691
+ <div class="row" style="grid-template-columns: 1fr auto">
692
+ <input id="dir" placeholder="./freya" />
693
+ <button class="btn small" onclick="pickDir()">Browse</button>
694
+ </div>
695
+ <div class="stack" style="margin-top:10px">
696
+ <button class="btn sideBtn" onclick="doUpdate()">Sync workspace</button>
697
+ <button class="btn sideBtn" onclick="doMigrate()">Migrate data</button>
698
+ </div>
661
699
  <div style="height:10px"></div>
662
700
  <div class="help"><b>Sync workspace</b>: atualiza scripts/templates/agents na pasta <code>freya</code> sem sobrescrever <code>data/</code> e <code>logs/</code>.</div>
663
701
  <div class="help"><b>Migrate data</b>: ajusta formatos/schemaVersion quando uma versão nova exige.</div>
664
702
  </div>
665
703
 
666
704
  <div class="sideGroup">
667
- <div class="sideTitle">Atalhos</div>
668
- <div class="help"><span class="k">--dev</span> cria dados de exemplo para testar rápido.</div>
669
- <div style="height:8px"></div>
670
- <div class="help"><span class="k">--port</span> muda a porta (default 3872).</div>
705
+ <div class="sideTitle">Quick reports</div>
706
+ <div class="cardsMini">
707
+ <button class="miniCard" type="button" onclick="runReport('status')"><span class="miniIcon">E</span><span>Executive</span></button>
708
+ <button class="miniCard" type="button" onclick="runReport('sm-weekly')"><span class="miniIcon">S</span><span>SM weekly</span></button>
709
+ <button class="miniCard" type="button" onclick="runReport('blockers')"><span class="miniIcon warn">B</span><span>Blockers</span></button>
710
+ <button class="miniCard" type="button" onclick="runReport('daily')"><span class="miniIcon">D</span><span>Daily</span></button>
711
+ </div>
712
+ <div class="help" style="margin-top:8px">Clique para gerar e atualizar o preview/publicação.</div>
671
713
  </div>
672
714
 
673
715
  <div class="sideGroup">
674
- <div class="sideTitle">Daily Input</div>
675
- <textarea id="inboxText" rows="6" placeholder="Cole aqui updates do dia (status, blockers, decisões, ideias)…" style="width:100%; padding:10px 12px; border-radius:12px; border:1px solid var(--line); background: rgba(255,255,255,.72); color: var(--text); outline:none; resize: vertical;"></textarea>
676
- <div style="height:10px"></div>
677
- <div class="stack">
678
- <button class="btn primary sideBtn" onclick="saveAndPlan()">Save + Process (Agents)</button>
679
- <button class="btn sideBtn" onclick="runSuggestedReports()">Run suggested reports</button>
680
- </div>
681
-
682
- <div style="height:10px"></div>
683
- <label style="display:flex; align-items:center; gap:10px; user-select:none">
684
- <input id="autoApply" type="checkbox" checked style="width:auto" onchange="toggleAutoApply()" />
685
- Auto-apply plan
686
- </label>
687
- <div class="help">Quando ligado, o Save+Process já aplica tasks/blockers automaticamente.</div>
688
-
689
- <label style="display:flex; align-items:center; gap:10px; user-select:none; margin-top:10px">
690
- <input id="autoRunReports" type="checkbox" style="width:auto" onchange="toggleAutoRunReports()" />
691
- Auto-run suggested reports
692
- </label>
693
- <div class="help">Quando ligado, após aplicar o plano, ele também roda os reports sugeridos automaticamente.</div>
716
+ <div class="sideTitle">Tips</div>
717
+ <div class="help"><span class="k">--dev</span> cria dados de exemplo para testar rápido.</div>
718
+ <div style="height:8px"></div>
719
+ <div class="help"><span class="k">--port</span> muda a porta (default 3872).</div>
694
720
  </div>
695
721
  </aside>
696
722
 
697
- <main class="main">
723
+ <!-- MIDDLE: Reports / Today -->
724
+ <main class="center">
698
725
  <div class="topbar">
699
726
  <div class="brand"><span class="spark"></span> Local-first status assistant</div>
700
727
  <div class="actions">
@@ -703,83 +730,35 @@ function buildHtml(safeDefault) {
703
730
  </div>
704
731
  </div>
705
732
 
706
- <div class="section">
707
- <h1>Morning, how can I help?</h1>
708
- <div class="subtitle">Selecione uma workspace e gere relatórios (Executive / SM / Blockers / Daily). Você pode publicar no Discord/Teams com 1 clique.</div>
709
-
710
- <div class="cards">
711
- <div class="card" onclick="runReport('status')">
712
- <div class="icon">E</div>
713
- <div class="title">Executive report</div>
714
- <div class="desc">Status pronto para stakeholders (entregas, projetos, blockers).</div>
733
+ <div class="centerBody">
734
+ <div class="centerHead">
735
+ <div>
736
+ <h1 style="margin:0">Your day at a glance</h1>
737
+ <div class="subtitle">Workspaces, Today (tasks/blockers), reports & preview. Use the right panel as a chat-style capture.</div>
715
738
  </div>
716
- <div class="card" onclick="runReport('sm-weekly')">
717
- <div class="icon">S</div>
718
- <div class="title">SM weekly</div>
719
- <div class="desc">Resumo, wins, riscos e foco da próxima semana.</div>
720
- </div>
721
- <div class="card" onclick="runReport('blockers')">
722
- <div class="icon orange">B</div>
723
- <div class="title">Blockers</div>
724
- <div class="desc">Lista priorizada por severidade + idade (pra destravar rápido).</div>
725
- </div>
726
- <div class="card" onclick="runReport('daily')">
727
- <div class="icon">D</div>
728
- <div class="title">Daily</div>
729
- <div class="desc">Ontem / Hoje / Bloqueios — pronto pra standup.</div>
739
+ <div class="statusLine">
740
+ <span class="small" id="last"></span>
730
741
  </div>
731
742
  </div>
732
743
 
733
- <div class="grid2">
734
- <div class="panel">
735
- <div class="panelHead"><b>Workspace & publish settings</b><span class="small" id="last"></span></div>
736
- <div class="panelBody">
737
- <label>Workspace dir</label>
738
- <div class="row">
739
- <input id="dir" placeholder="./freya" />
740
- <button class="btn small" onclick="pickDir()">Browse</button>
741
- </div>
742
- <div class="help">Escolha a pasta que contém <code>data/</code>, <code>logs/</code> e <code>scripts/</code>.</div>
743
-
744
- <div style="height:12px"></div>
745
-
746
- <label>Discord webhook URL</label>
747
- <input id="discord" placeholder="https://discord.com/api/webhooks/..." />
748
- <div style="height:10px"></div>
749
-
750
- <label>Teams webhook URL</label>
751
- <input id="teams" placeholder="https://..." />
752
- <div class="help">Os webhooks ficam salvos na workspace em <code>data/settings/settings.json</code>.</div>
753
-
754
- <div style="height:10px"></div>
755
- <label style="display:flex; align-items:center; gap:10px; user-select:none; margin: 6px 0 12px 0">
756
- <input id="prettyPublish" type="checkbox" checked style="width:auto" onchange="togglePrettyPublish()" />
757
- Pretty publish (cards/embeds)
758
- </label>
759
-
744
+ <div class="midGrid">
745
+ <section class="panel">
746
+ <div class="panelHead">
747
+ <b>Today</b>
760
748
  <div class="stack">
761
- <button class="btn" onclick="saveSettings()">Save settings</button>
762
- <button class="btn" onclick="publish('discord')">Publish selected → Discord</button>
763
- <button class="btn" onclick="publish('teams')">Publish selected → Teams</button>
764
- </div>
765
-
766
- <div style="height:14px"></div>
767
-
768
- <div class="help"><b>Dica:</b> clique em um relatório em <i>Reports</i> para ver o preview e habilitar publish/copy.</div>
769
-
770
- <div style="height:14px"></div>
771
- <label>Project slug rules</label>
772
- <textarea id="slugRules" rows="8" placeholder="{ \"rules\": [ { \"contains\": \"fideliza\", \"slug\": \"vivo/fidelizacao\" } ] }" style="width:100%; padding:10px 12px; border-radius:12px; border:1px solid var(--line); background: rgba(255,255,255,.72); color: var(--text); outline:none; resize: vertical; font-family: var(--mono);"></textarea>
773
- <div class="help">Regras usadas pra inferir <code>projectSlug</code>. Formato JSON (objeto com <code>rules</code>). Editável no estilo Obsidian-friendly.</div>
774
- <div class="stack" style="margin-top:10px">
775
- <button class="btn" onclick="reloadSlugRules()">Reload rules</button>
776
- <button class="btn" onclick="saveSlugRules()">Save rules</button>
777
- <button class="btn" onclick="exportObsidian()">Export Obsidian notes</button>
749
+ <button class="btn small" onclick="refreshToday()">Refresh</button>
778
750
  </div>
779
751
  </div>
780
- </div>
752
+ <div class="panelBody">
753
+ <div class="small" style="margin-bottom:8px; opacity:.8">Do Now</div>
754
+ <div id="tasksList" style="display:grid; gap:8px"></div>
755
+ <div style="height:12px"></div>
756
+ <div class="small" style="margin-bottom:8px; opacity:.8">Open blockers</div>
757
+ <div id="blockersList" style="display:grid; gap:8px"></div>
758
+ </div>
759
+ </section>
781
760
 
782
- <div class="panel">
761
+ <section class="panel">
783
762
  <div class="panelHead">
784
763
  <b>Reports</b>
785
764
  <div class="stack">
@@ -791,25 +770,9 @@ function buildHtml(safeDefault) {
791
770
  <div id="reportsList" style="display:grid; gap:8px"></div>
792
771
  <div class="help">Últimos relatórios em <code>docs/reports</code>. Clique para abrir preview.</div>
793
772
  </div>
794
- </div>
795
-
796
- <div class="panel">
797
- <div class="panelHead">
798
- <b>Today</b>
799
- <div class="stack">
800
- <button class="btn small" onclick="refreshToday()">Refresh</button>
801
- </div>
802
- </div>
803
- <div class="panelBody">
804
- <div class="small" style="margin-bottom:8px; opacity:.8">Do Now</div>
805
- <div id="tasksList" style="display:grid; gap:8px"></div>
806
- <div style="height:12px"></div>
807
- <div class="small" style="margin-bottom:8px; opacity:.8">Open blockers</div>
808
- <div id="blockersList" style="display:grid; gap:8px"></div>
809
- </div>
810
- </div>
773
+ </section>
811
774
 
812
- <div class="panel">
775
+ <section class="panel midSpan">
813
776
  <div class="panelHead">
814
777
  <b>Preview</b>
815
778
  <div class="stack">
@@ -825,11 +788,106 @@ function buildHtml(safeDefault) {
825
788
  <div id="reportPreview" class="log md" style="font-family: var(--sans);"></div>
826
789
  <div class="help">O preview renderiza Markdown básico (headers, listas, code). O botão Copy copia o conteúdo completo.</div>
827
790
  </div>
828
- </div>
791
+ </section>
829
792
  </div>
793
+
794
+ <details class="devDrawer" id="devDrawer">
795
+ <summary>Developer</summary>
796
+ <div class="devBody">
797
+ <div class="devGrid">
798
+ <div class="panel">
799
+ <div class="panelHead"><b>Publish settings</b></div>
800
+ <div class="panelBody">
801
+ <label>Discord webhook URL</label>
802
+ <input id="discord" placeholder="https://discord.com/api/webhooks/..." />
803
+ <div style="height:10px"></div>
804
+
805
+ <label>Teams webhook URL</label>
806
+ <input id="teams" placeholder="https://..." />
807
+ <div class="help">Os webhooks ficam salvos na workspace em <code>data/settings/settings.json</code>.</div>
808
+
809
+ <div style="height:10px"></div>
810
+ <label style="display:flex; align-items:center; gap:10px; user-select:none; margin: 6px 0 12px 0">
811
+ <input id="prettyPublish" type="checkbox" checked style="width:auto" onchange="togglePrettyPublish()" />
812
+ Pretty publish (cards/embeds)
813
+ </label>
814
+
815
+ <div class="stack">
816
+ <button class="btn" onclick="saveSettings()">Save settings</button>
817
+ <button class="btn" onclick="publish('discord')">Publish selected → Discord</button>
818
+ <button class="btn" onclick="publish('teams')">Publish selected → Teams</button>
819
+ </div>
820
+ </div>
821
+ </div>
822
+
823
+ <div class="panel">
824
+ <div class="panelHead"><b>Slug rules & export</b></div>
825
+ <div class="panelBody">
826
+ <label>Project slug rules</label>
827
+ <textarea id="slugRules" rows="8" placeholder="{ \"rules\": [ { \"contains\": \"fideliza\", \"slug\": \"vivo/fidelizacao\" } ] }" style="width:100%; padding:10px 12px; border-radius:12px; border:1px solid var(--line); background: rgba(255,255,255,.72); color: var(--text); outline:none; resize: vertical; font-family: var(--mono);"></textarea>
828
+ <div class="help">Regras usadas pra inferir <code>projectSlug</code>. Formato JSON (objeto com <code>rules</code>).</div>
829
+ <div class="stack" style="margin-top:10px">
830
+ <button class="btn" onclick="reloadSlugRules()">Reload rules</button>
831
+ <button class="btn" onclick="saveSlugRules()">Save rules</button>
832
+ <button class="btn" onclick="exportObsidian()">Export Obsidian notes</button>
833
+ </div>
834
+ </div>
835
+ </div>
836
+
837
+ <div class="panel">
838
+ <div class="panelHead"><b>Debug</b></div>
839
+ <div class="panelBody">
840
+ <div class="help">Logs ficam em <code>logs/</code> e debug traces em <code>.debuglogs/</code> dentro da workspace.</div>
841
+ <div class="help">Use <b>Open file</b> / <b>Copy path</b> no Preview para abrir/compartilhar o relatório selecionado.</div>
842
+ </div>
843
+ </div>
844
+ </div>
845
+ </div>
846
+ </details>
830
847
  </div>
831
848
  </main>
832
849
 
850
+ <!-- RIGHT: Chat -->
851
+ <aside class="chatPane">
852
+ <div class="chatHead">
853
+ <div>
854
+ <div class="chatTitle">Chat</div>
855
+ <div class="chatSub">Capture updates, then let Agents plan/apply.</div>
856
+ </div>
857
+ </div>
858
+
859
+ <div class="chatThread" id="chatThread">
860
+ <div class="bubble assistant">
861
+ <div class="bubbleMeta">FREYA</div>
862
+ <div class="bubbleBody">Cole seus updates (status, blockers, decisões, ideias) e clique em <b>Save + Process</b>.</div>
863
+ </div>
864
+ </div>
865
+
866
+ <div class="chatComposer">
867
+ <textarea id="inboxText" rows="5" placeholder="Cole aqui updates do dia (status, blockers, decisões, ideias)…"></textarea>
868
+
869
+ <div class="composerActions">
870
+ <button class="btn primary" type="button" onclick="saveAndPlan()">Save + Process (Agents)</button>
871
+ <button class="btn" type="button" onclick="runSuggestedReports()">Run suggested reports</button>
872
+ </div>
873
+
874
+ <div class="composerToggles">
875
+ <label class="toggleRow">
876
+ <input id="autoApply" type="checkbox" checked style="width:auto" onchange="toggleAutoApply()" />
877
+ Auto-apply plan
878
+ </label>
879
+ <label class="toggleRow">
880
+ <input id="autoRunReports" type="checkbox" style="width:auto" onchange="toggleAutoRunReports()" />
881
+ Auto-run suggested reports
882
+ </label>
883
+ </div>
884
+
885
+ <div class="statusFooter">
886
+ <span id="status" class="small">idle</span>
887
+ </div>
888
+ </div>
889
+ </aside>
890
+
833
891
  </div>
834
892
  </div>
835
893
  </div>
@@ -1206,14 +1264,7 @@ async function cmdWeb({ port, dir, open, dev }) {
1206
1264
  const planRaw = String(payload.plan || '').trim();
1207
1265
  if (!planRaw) return safeJson(res, 400, { error: 'Missing plan' });
1208
1266
 
1209
- function extractJson(text) {
1210
- const start = text.indexOf('{');
1211
- const end = text.lastIndexOf('}');
1212
- if (start === -1 || end === -1 || end <= start) return null;
1213
- return text.slice(start, end + 1);
1214
- }
1215
-
1216
- const jsonText = extractJson(planRaw) || planRaw;
1267
+ const jsonText = extractFirstJsonObject(planRaw) || planRaw;
1217
1268
  let plan;
1218
1269
  try {
1219
1270
  plan = JSON.parse(jsonText);
@@ -1298,14 +1349,7 @@ async function cmdWeb({ port, dir, open, dev }) {
1298
1349
  const planRaw = String(payload.plan || '').trim();
1299
1350
  if (!planRaw) return safeJson(res, 400, { error: 'Missing plan' });
1300
1351
 
1301
- function extractJson(text) {
1302
- const start = text.indexOf('{');
1303
- const end = text.lastIndexOf('}');
1304
- if (start === -1 || end === -1 || end <= start) return null;
1305
- return text.slice(start, end + 1);
1306
- }
1307
-
1308
- const jsonText = extractJson(planRaw) || planRaw;
1352
+ const jsonText = extractFirstJsonObject(planRaw) || planRaw;
1309
1353
 
1310
1354
  function errorSnippet(text, pos) {
1311
1355
  const p = Number.isFinite(pos) ? pos : 0;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cccarv82/freya",
3
- "version": "1.0.33",
3
+ "version": "1.0.35",
4
4
  "description": "Personal AI Assistant with local-first persistence",
5
5
  "scripts": {
6
6
  "health": "node scripts/validate-data.js",