@cccarv82/freya 1.0.30 → 1.0.32
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.js +118 -4
- package/cli/web.js +165 -3
- package/package.json +2 -1
package/cli/web-ui.js
CHANGED
|
@@ -12,7 +12,8 @@
|
|
|
12
12
|
lastPlan: '',
|
|
13
13
|
lastApplied: null,
|
|
14
14
|
autoApply: true,
|
|
15
|
-
autoRunReports: false
|
|
15
|
+
autoRunReports: false,
|
|
16
|
+
prettyPublish: true
|
|
16
17
|
};
|
|
17
18
|
|
|
18
19
|
function applyTheme(theme) {
|
|
@@ -205,6 +206,7 @@
|
|
|
205
206
|
localStorage.setItem('freya.dir', $('dir').value);
|
|
206
207
|
try { localStorage.setItem('freya.autoApply', state.autoApply ? '1' : '0'); } catch {}
|
|
207
208
|
try { localStorage.setItem('freya.autoRunReports', state.autoRunReports ? '1' : '0'); } catch {}
|
|
209
|
+
try { localStorage.setItem('freya.prettyPublish', state.prettyPublish ? '1' : '0'); } catch {}
|
|
208
210
|
}
|
|
209
211
|
|
|
210
212
|
function loadLocal() {
|
|
@@ -218,6 +220,11 @@
|
|
|
218
220
|
if (v2 !== null) state.autoRunReports = v2 === '1';
|
|
219
221
|
const cb2 = $('autoRunReports');
|
|
220
222
|
if (cb2) cb2.checked = !!state.autoRunReports;
|
|
223
|
+
|
|
224
|
+
const v3 = localStorage.getItem('freya.prettyPublish');
|
|
225
|
+
if (v3 !== null) state.prettyPublish = v3 === '1';
|
|
226
|
+
const cb3 = $('prettyPublish');
|
|
227
|
+
if (cb3) cb3.checked = !!state.prettyPublish;
|
|
221
228
|
} catch {}
|
|
222
229
|
|
|
223
230
|
const def = (window.__FREYA_DEFAULT_DIR && window.__FREYA_DEFAULT_DIR !== '__FREYA_DEFAULT_DIR__')
|
|
@@ -318,7 +325,7 @@
|
|
|
318
325
|
async function refreshReports(opts = {}) {
|
|
319
326
|
try {
|
|
320
327
|
const r = await api('/api/reports/list', { dir: dirOrDefault() });
|
|
321
|
-
state.reports = (r.reports || []).slice(0,
|
|
328
|
+
state.reports = (r.reports || []).slice(0, 10);
|
|
322
329
|
renderReportsList();
|
|
323
330
|
|
|
324
331
|
const latest = state.reports && state.reports[0] ? state.reports[0] : null;
|
|
@@ -337,6 +344,44 @@
|
|
|
337
344
|
}
|
|
338
345
|
}
|
|
339
346
|
|
|
347
|
+
async function editTask(t) {
|
|
348
|
+
try {
|
|
349
|
+
const currentSlug = t.projectSlug ? String(t.projectSlug) : '';
|
|
350
|
+
const slug = prompt('projectSlug (ex: vivo/fidelizacao/chg0178682):', currentSlug);
|
|
351
|
+
if (slug === null) return;
|
|
352
|
+
|
|
353
|
+
const currentCat = String(t.category || 'DO_NOW');
|
|
354
|
+
const cat = prompt('category (DO_NOW|SCHEDULE|DELEGATE|IGNORE):', currentCat);
|
|
355
|
+
if (cat === null) return;
|
|
356
|
+
|
|
357
|
+
setPill('run', 'updating…');
|
|
358
|
+
await api('/api/tasks/update', { dir: dirOrDefault(), id: t.id, patch: { projectSlug: slug, category: cat } });
|
|
359
|
+
await refreshToday();
|
|
360
|
+
setPill('ok', 'updated');
|
|
361
|
+
setTimeout(() => setPill('ok', 'idle'), 800);
|
|
362
|
+
} catch (e) {
|
|
363
|
+
setPill('err', 'update failed');
|
|
364
|
+
setOut(String(e && e.message ? e.message : e));
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
async function editBlocker(b) {
|
|
369
|
+
try {
|
|
370
|
+
const currentSlug = b.projectSlug ? String(b.projectSlug) : '';
|
|
371
|
+
const slug = prompt('projectSlug (ex: vivo/bnpl/dpgc):', currentSlug);
|
|
372
|
+
if (slug === null) return;
|
|
373
|
+
|
|
374
|
+
setPill('run', 'updating…');
|
|
375
|
+
await api('/api/blockers/update', { dir: dirOrDefault(), id: b.id, patch: { projectSlug: slug } });
|
|
376
|
+
await refreshToday();
|
|
377
|
+
setPill('ok', 'updated');
|
|
378
|
+
setTimeout(() => setPill('ok', 'idle'), 800);
|
|
379
|
+
} catch (e) {
|
|
380
|
+
setPill('err', 'update failed');
|
|
381
|
+
setOut(String(e && e.message ? e.message : e));
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
340
385
|
function renderTasks(list) {
|
|
341
386
|
const el = $('tasksList');
|
|
342
387
|
if (!el) return;
|
|
@@ -350,9 +395,14 @@
|
|
|
350
395
|
+ '<div style="opacity:.7; font-size:11px; margin-top:4px">' + escapeHtml(String(t.category || ''))
|
|
351
396
|
+ (t.projectSlug ? (' · <span style="font-family:var(--mono); opacity:.9">[' + escapeHtml(String(t.projectSlug)) + ']</span>') : '')
|
|
352
397
|
+ (pri ? (' · ' + escapeHtml(pri)) : '') + '</div></div>'
|
|
398
|
+
+ '<div style="display:flex; gap:8px">'
|
|
353
399
|
+ '<button class="btn small" type="button">Complete</button>'
|
|
400
|
+
+ '<button class="btn small" type="button">Edit</button>'
|
|
401
|
+
+ '</div>'
|
|
354
402
|
+ '</div>';
|
|
355
|
-
const
|
|
403
|
+
const btns = row.querySelectorAll('button');
|
|
404
|
+
const btn = btns[0];
|
|
405
|
+
if (btns[1]) btns[1].onclick = () => editTask(t);
|
|
356
406
|
btn.onclick = async () => {
|
|
357
407
|
try {
|
|
358
408
|
setPill('run', 'completing…');
|
|
@@ -389,8 +439,13 @@
|
|
|
389
439
|
+ (b.projectSlug ? (' <span style="font-family:var(--mono); opacity:.8">[' + escapeHtml(String(b.projectSlug)) + ']</span>') : '')
|
|
390
440
|
+ '</div>'
|
|
391
441
|
+ '</div>'
|
|
442
|
+
+ '<div style="display:flex; gap:8px; align-items:center">'
|
|
392
443
|
+ '<div style="opacity:.7; font-size:11px; white-space:nowrap">' + escapeHtml(fmtWhen(new Date(b.createdAt || Date.now()).getTime())) + '</div>'
|
|
444
|
+
+ '<button class="btn small" type="button">Edit</button>'
|
|
445
|
+
+ '</div>'
|
|
393
446
|
+ '</div>';
|
|
447
|
+
const ebtn = row.querySelector('button');
|
|
448
|
+
if (ebtn) ebtn.onclick = () => editBlocker(b);
|
|
394
449
|
el.appendChild(row);
|
|
395
450
|
}
|
|
396
451
|
if (!el.childElementCount) {
|
|
@@ -519,6 +574,54 @@
|
|
|
519
574
|
}
|
|
520
575
|
}
|
|
521
576
|
|
|
577
|
+
async function exportObsidian() {
|
|
578
|
+
try {
|
|
579
|
+
setPill('run', 'exporting…');
|
|
580
|
+
const r = await api('/api/obsidian/export', { dir: dirOrDefault() });
|
|
581
|
+
setOut('## Obsidian export
|
|
582
|
+
|
|
583
|
+
' + (r.output || 'ok'));
|
|
584
|
+
setPill('ok', 'exported');
|
|
585
|
+
setTimeout(() => setPill('ok', 'idle'), 800);
|
|
586
|
+
} catch (e) {
|
|
587
|
+
setPill('err', 'export failed');
|
|
588
|
+
setOut(String(e && e.message ? e.message : e));
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
async function reloadSlugRules() {
|
|
593
|
+
try {
|
|
594
|
+
const r = await api('/api/project-slug-map/get', { dir: dirOrDefault() });
|
|
595
|
+
const el = $('slugRules');
|
|
596
|
+
if (el) el.value = JSON.stringify(r.map || { rules: [] }, null, 2);
|
|
597
|
+
setPill('ok', 'rules loaded');
|
|
598
|
+
setTimeout(() => setPill('ok', 'idle'), 800);
|
|
599
|
+
} catch (e) {
|
|
600
|
+
setPill('err', 'rules load failed');
|
|
601
|
+
setOut(String(e && e.message ? e.message : e));
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
async function saveSlugRules() {
|
|
606
|
+
try {
|
|
607
|
+
const el = $('slugRules');
|
|
608
|
+
if (!el) return;
|
|
609
|
+
const raw = String(el.value || '').trim();
|
|
610
|
+
if (!raw) throw new Error('Rules JSON is empty');
|
|
611
|
+
let map;
|
|
612
|
+
try { map = JSON.parse(raw); } catch (e) { throw new Error('Invalid JSON: ' + (e.message || e)); }
|
|
613
|
+
|
|
614
|
+
setPill('run', 'saving rules…');
|
|
615
|
+
const r = await api('/api/project-slug-map/save', { dir: dirOrDefault(), map });
|
|
616
|
+
if (el) el.value = JSON.stringify(r.map || map, null, 2);
|
|
617
|
+
setPill('ok', 'rules saved');
|
|
618
|
+
setTimeout(() => setPill('ok', 'idle'), 800);
|
|
619
|
+
} catch (e) {
|
|
620
|
+
setPill('err', 'rules save failed');
|
|
621
|
+
setOut(String(e && e.message ? e.message : e));
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
|
|
522
625
|
async function saveSettings() {
|
|
523
626
|
try {
|
|
524
627
|
saveLocal();
|
|
@@ -560,7 +663,8 @@
|
|
|
560
663
|
const webhookUrl = target === 'discord' ? $('discord').value.trim() : $('teams').value.trim();
|
|
561
664
|
if (!webhookUrl) throw new Error('Configure o webhook antes.');
|
|
562
665
|
setPill('run', 'publish…');
|
|
563
|
-
|
|
666
|
+
const mode = state.prettyPublish ? 'pretty' : 'chunks';
|
|
667
|
+
await api('/api/publish', { webhookUrl, text: state.lastText, mode, allowSecrets: true });
|
|
564
668
|
setPill('ok', 'published');
|
|
565
669
|
} catch (e) {
|
|
566
670
|
setPill('err', 'publish failed');
|
|
@@ -587,6 +691,12 @@
|
|
|
587
691
|
}
|
|
588
692
|
}
|
|
589
693
|
|
|
694
|
+
function togglePrettyPublish() {
|
|
695
|
+
const cb = $('prettyPublish');
|
|
696
|
+
state.prettyPublish = cb ? !!cb.checked : true;
|
|
697
|
+
try { localStorage.setItem('freya.prettyPublish', state.prettyPublish ? '1' : '0'); } catch {}
|
|
698
|
+
}
|
|
699
|
+
|
|
590
700
|
function toggleAutoRunReports() {
|
|
591
701
|
const cb = $('autoRunReports');
|
|
592
702
|
state.autoRunReports = cb ? !!cb.checked : false;
|
|
@@ -758,6 +868,9 @@
|
|
|
758
868
|
window.saveSettings = saveSettings;
|
|
759
869
|
window.refreshReports = refreshReports;
|
|
760
870
|
window.refreshToday = refreshToday;
|
|
871
|
+
window.reloadSlugRules = reloadSlugRules;
|
|
872
|
+
window.saveSlugRules = saveSlugRules;
|
|
873
|
+
window.exportObsidian = exportObsidian;
|
|
761
874
|
window.renderReportsList = renderReportsList;
|
|
762
875
|
window.copyOut = copyOut;
|
|
763
876
|
window.copyPath = copyPath;
|
|
@@ -769,6 +882,7 @@
|
|
|
769
882
|
window.saveAndPlan = saveAndPlan;
|
|
770
883
|
window.toggleAutoApply = toggleAutoApply;
|
|
771
884
|
window.toggleAutoRunReports = toggleAutoRunReports;
|
|
885
|
+
window.togglePrettyPublish = togglePrettyPublish;
|
|
772
886
|
window.applyPlan = applyPlan;
|
|
773
887
|
window.runSuggestedReports = runSuggestedReports;
|
|
774
888
|
})();
|
package/cli/web.js
CHANGED
|
@@ -181,6 +181,36 @@ function listReports(workspaceDir) {
|
|
|
181
181
|
}));
|
|
182
182
|
}
|
|
183
183
|
|
|
184
|
+
function extractTitleFromMarkdown(md) {
|
|
185
|
+
const t = String(md || '');
|
|
186
|
+
const m = /^#\s+(.+)$/m.exec(t);
|
|
187
|
+
if (m && m[1]) return m[1].trim();
|
|
188
|
+
// fallback: first non-empty line
|
|
189
|
+
const line = t.split(/\r?\n/).map((l) => l.trim()).find((l) => l);
|
|
190
|
+
return line ? line.slice(0, 80) : 'Freya report';
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function stripFirstH1(md) {
|
|
194
|
+
const t = String(md || '');
|
|
195
|
+
return t.replace(/^#\s+.+\r?\n/, '').trim();
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function splitForEmbed(text, limit = 3900) {
|
|
199
|
+
const t = String(text || '');
|
|
200
|
+
if (t.length <= limit) return [t];
|
|
201
|
+
const chunks = [];
|
|
202
|
+
let i = 0;
|
|
203
|
+
while (i < t.length) {
|
|
204
|
+
let end = Math.min(t.length, i + limit);
|
|
205
|
+
// prefer splitting at newline
|
|
206
|
+
const nl = t.lastIndexOf('\n', end);
|
|
207
|
+
if (nl > i + 200) end = nl;
|
|
208
|
+
chunks.push(t.slice(i, end));
|
|
209
|
+
i = end;
|
|
210
|
+
}
|
|
211
|
+
return chunks;
|
|
212
|
+
}
|
|
213
|
+
|
|
184
214
|
function splitForDiscord(text, limit = 1900) {
|
|
185
215
|
const t = String(text || '');
|
|
186
216
|
if (t.length <= limit) return [t];
|
|
@@ -234,14 +264,19 @@ function postJson(url, bodyObj) {
|
|
|
234
264
|
});
|
|
235
265
|
}
|
|
236
266
|
|
|
237
|
-
function postDiscordWebhook(url,
|
|
238
|
-
return postJson(url, { content });
|
|
267
|
+
function postDiscordWebhook(url, payload) {
|
|
268
|
+
if (typeof payload === 'string') return postJson(url, { content: payload });
|
|
269
|
+
return postJson(url, payload);
|
|
239
270
|
}
|
|
240
271
|
|
|
241
272
|
function postTeamsWebhook(url, text) {
|
|
242
273
|
return postJson(url, { text });
|
|
243
274
|
}
|
|
244
275
|
|
|
276
|
+
function postTeamsCard(url, card) {
|
|
277
|
+
return postJson(url, card);
|
|
278
|
+
}
|
|
279
|
+
|
|
245
280
|
function escapeJsonControlChars(jsonText) {
|
|
246
281
|
// Replace unescaped control chars inside JSON string literals with safe escapes.
|
|
247
282
|
// Handles Copilot outputs where newlines/tabs leak into string values.
|
|
@@ -389,6 +424,42 @@ async function publishRobust(webhookUrl, text, opts = {}) {
|
|
|
389
424
|
const u = new URL(webhookUrl);
|
|
390
425
|
const isDiscord = u.hostname.includes('discord.com') || u.hostname.includes('discordapp.com');
|
|
391
426
|
|
|
427
|
+
const mode = String(opts.mode || 'chunks');
|
|
428
|
+
|
|
429
|
+
if (mode === 'pretty') {
|
|
430
|
+
const title = extractTitleFromMarkdown(text);
|
|
431
|
+
const body = stripFirstH1(text);
|
|
432
|
+
|
|
433
|
+
if (isDiscord) {
|
|
434
|
+
const parts = splitForEmbed(body, 3900);
|
|
435
|
+
for (let i = 0; i < parts.length; i++) {
|
|
436
|
+
const payload = {
|
|
437
|
+
embeds: [
|
|
438
|
+
{
|
|
439
|
+
title: i === 0 ? title : undefined,
|
|
440
|
+
description: parts[i],
|
|
441
|
+
color: 0x5865F2
|
|
442
|
+
}
|
|
443
|
+
]
|
|
444
|
+
};
|
|
445
|
+
await postDiscordWebhook(webhookUrl, payload);
|
|
446
|
+
}
|
|
447
|
+
return { ok: true, chunks: parts.length, mode: 'pretty' };
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
// Teams (MessageCard)
|
|
451
|
+
const card = {
|
|
452
|
+
'@type': 'MessageCard',
|
|
453
|
+
'@context': 'http://schema.org/extensions',
|
|
454
|
+
summary: title,
|
|
455
|
+
themeColor: '0078D7',
|
|
456
|
+
title,
|
|
457
|
+
text: body
|
|
458
|
+
};
|
|
459
|
+
await postTeamsCard(webhookUrl, card);
|
|
460
|
+
return { ok: true, chunks: 1, mode: 'pretty' };
|
|
461
|
+
}
|
|
462
|
+
|
|
392
463
|
const chunks = isDiscord ? splitForDiscord(text, 1900) : splitForDiscord(text, 1800);
|
|
393
464
|
|
|
394
465
|
for (const chunk of chunks) {
|
|
@@ -604,7 +675,6 @@ function buildHtml(safeDefault) {
|
|
|
604
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>
|
|
605
676
|
<div style="height:10px"></div>
|
|
606
677
|
<div class="stack">
|
|
607
|
-
<button class="btn sideBtn" onclick="saveInbox()">Save to Daily Log</button>
|
|
608
678
|
<button class="btn primary sideBtn" onclick="saveAndPlan()">Save + Process (Agents)</button>
|
|
609
679
|
<button class="btn sideBtn" onclick="runSuggestedReports()">Run suggested reports</button>
|
|
610
680
|
</div>
|
|
@@ -682,6 +752,11 @@ function buildHtml(safeDefault) {
|
|
|
682
752
|
<div class="help">Os webhooks ficam salvos na workspace em <code>data/settings/settings.json</code>.</div>
|
|
683
753
|
|
|
684
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
|
+
|
|
685
760
|
<div class="stack">
|
|
686
761
|
<button class="btn" onclick="saveSettings()">Save settings</button>
|
|
687
762
|
<button class="btn" onclick="publish('discord')">Publish selected → Discord</button>
|
|
@@ -691,6 +766,16 @@ function buildHtml(safeDefault) {
|
|
|
691
766
|
<div style="height:14px"></div>
|
|
692
767
|
|
|
693
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>
|
|
778
|
+
</div>
|
|
694
779
|
</div>
|
|
695
780
|
</div>
|
|
696
781
|
|
|
@@ -994,6 +1079,29 @@ async function cmdWeb({ port, dir, open, dev }) {
|
|
|
994
1079
|
return safeJson(res, 200, { ok: true, settings: { discordWebhookUrl: saved.discordWebhookUrl, teamsWebhookUrl: saved.teamsWebhookUrl } });
|
|
995
1080
|
}
|
|
996
1081
|
|
|
1082
|
+
|
|
1083
|
+
if (req.url === '/api/project-slug-map/get') {
|
|
1084
|
+
const map = readProjectSlugMap(workspaceDir);
|
|
1085
|
+
return safeJson(res, 200, { ok: true, map });
|
|
1086
|
+
}
|
|
1087
|
+
|
|
1088
|
+
if (req.url === '/api/project-slug-map/save') {
|
|
1089
|
+
const map = payload.map;
|
|
1090
|
+
if (!map || typeof map !== 'object') return safeJson(res, 400, { error: 'Missing map' });
|
|
1091
|
+
if (!Array.isArray(map.rules)) return safeJson(res, 400, { error: 'map.rules must be an array' });
|
|
1092
|
+
|
|
1093
|
+
// normalize + basic validation
|
|
1094
|
+
const rules = map.rules
|
|
1095
|
+
.map((r) => ({ contains: String(r.contains || '').trim(), slug: String(r.slug || '').trim() }))
|
|
1096
|
+
.filter((r) => r.contains && r.slug);
|
|
1097
|
+
|
|
1098
|
+
const out = { schemaVersion: 1, updatedAt: new Date().toISOString(), rules };
|
|
1099
|
+
const p = projectSlugMapPath(workspaceDir);
|
|
1100
|
+
ensureDir(require('path').dirname(p));
|
|
1101
|
+
fs.writeFileSync(p, JSON.stringify(out, null, 2) + '\n', 'utf8');
|
|
1102
|
+
return safeJson(res, 200, { ok: true, map: out });
|
|
1103
|
+
}
|
|
1104
|
+
|
|
997
1105
|
if (req.url === '/api/reports/list') {
|
|
998
1106
|
const reports = listReports(workspaceDir);
|
|
999
1107
|
return safeJson(res, 200, { reports });
|
|
@@ -1445,6 +1553,13 @@ async function cmdWeb({ port, dir, open, dev }) {
|
|
|
1445
1553
|
}
|
|
1446
1554
|
|
|
1447
1555
|
|
|
1556
|
+
|
|
1557
|
+
if (req.url === '/api/obsidian/export') {
|
|
1558
|
+
const r = await run(npmCmd, ['run', 'export-obsidian'], workspaceDir);
|
|
1559
|
+
const out = (r.stdout + r.stderr).trim();
|
|
1560
|
+
return safeJson(res, r.code === 0 ? 200 : 400, r.code === 0 ? { ok: true, output: out } : { error: out || 'export failed', output: out });
|
|
1561
|
+
}
|
|
1562
|
+
|
|
1448
1563
|
if (req.url === '/api/tasks/list') {
|
|
1449
1564
|
const limit = Math.max(1, Math.min(50, Number(payload.limit || 10)));
|
|
1450
1565
|
const cat = payload.category ? String(payload.category).trim() : null;
|
|
@@ -1498,6 +1613,30 @@ async function cmdWeb({ port, dir, open, dev }) {
|
|
|
1498
1613
|
return safeJson(res, 200, { ok: true, task: updated });
|
|
1499
1614
|
}
|
|
1500
1615
|
|
|
1616
|
+
|
|
1617
|
+
if (req.url === '/api/tasks/update') {
|
|
1618
|
+
const id = String(payload.id || '').trim();
|
|
1619
|
+
if (!id) return safeJson(res, 400, { error: 'Missing id' });
|
|
1620
|
+
const patch = payload.patch && typeof payload.patch === 'object' ? payload.patch : {};
|
|
1621
|
+
|
|
1622
|
+
const file = path.join(workspaceDir, 'data', 'tasks', 'task-log.json');
|
|
1623
|
+
const doc = readJsonOrNull(file) || { schemaVersion: 1, tasks: [] };
|
|
1624
|
+
const tasks = Array.isArray(doc.tasks) ? doc.tasks : [];
|
|
1625
|
+
|
|
1626
|
+
let updated = null;
|
|
1627
|
+
for (const t of tasks) {
|
|
1628
|
+
if (t && t.id === id) {
|
|
1629
|
+
if (typeof patch.projectSlug === 'string') t.projectSlug = patch.projectSlug.trim() || undefined;
|
|
1630
|
+
if (typeof patch.category === 'string') t.category = patch.category.trim();
|
|
1631
|
+
updated = t;
|
|
1632
|
+
break;
|
|
1633
|
+
}
|
|
1634
|
+
}
|
|
1635
|
+
if (!updated) return safeJson(res, 404, { error: 'Task not found' });
|
|
1636
|
+
writeJson(file, doc);
|
|
1637
|
+
return safeJson(res, 200, { ok: true, task: updated });
|
|
1638
|
+
}
|
|
1639
|
+
|
|
1501
1640
|
if (req.url === '/api/blockers/list') {
|
|
1502
1641
|
const limit = Math.max(1, Math.min(50, Number(payload.limit || 10)));
|
|
1503
1642
|
const status = payload.status ? String(payload.status).trim() : 'OPEN';
|
|
@@ -1531,6 +1670,29 @@ async function cmdWeb({ port, dir, open, dev }) {
|
|
|
1531
1670
|
|
|
1532
1671
|
return safeJson(res, 200, { ok: true, blockers: filtered });
|
|
1533
1672
|
}
|
|
1673
|
+
|
|
1674
|
+
if (req.url === '/api/blockers/update') {
|
|
1675
|
+
const id = String(payload.id || '').trim();
|
|
1676
|
+
if (!id) return safeJson(res, 400, { error: 'Missing id' });
|
|
1677
|
+
const patch = payload.patch && typeof payload.patch === 'object' ? payload.patch : {};
|
|
1678
|
+
|
|
1679
|
+
const file = path.join(workspaceDir, 'data', 'blockers', 'blocker-log.json');
|
|
1680
|
+
const doc = readJsonOrNull(file) || { schemaVersion: 1, blockers: [] };
|
|
1681
|
+
const blockers = Array.isArray(doc.blockers) ? doc.blockers : [];
|
|
1682
|
+
|
|
1683
|
+
let updated = null;
|
|
1684
|
+
for (const b of blockers) {
|
|
1685
|
+
if (b && b.id === id) {
|
|
1686
|
+
if (typeof patch.projectSlug === 'string') b.projectSlug = patch.projectSlug.trim() || undefined;
|
|
1687
|
+
updated = b;
|
|
1688
|
+
break;
|
|
1689
|
+
}
|
|
1690
|
+
}
|
|
1691
|
+
if (!updated) return safeJson(res, 404, { error: 'Blocker not found' });
|
|
1692
|
+
writeJson(file, doc);
|
|
1693
|
+
return safeJson(res, 200, { ok: true, blocker: updated });
|
|
1694
|
+
}
|
|
1695
|
+
|
|
1534
1696
|
if (req.url === '/api/report') {
|
|
1535
1697
|
const script = payload.script;
|
|
1536
1698
|
if (!script) return safeJson(res, 400, { error: 'Missing script' });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cccarv82/freya",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.32",
|
|
4
4
|
"description": "Personal AI Assistant with local-first persistence",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"health": "node scripts/validate-data.js",
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
"daily": "node scripts/generate-daily-summary.js",
|
|
11
11
|
"status": "node scripts/generate-executive-report.js",
|
|
12
12
|
"blockers": "node scripts/generate-blockers-report.js",
|
|
13
|
+
"export-obsidian": "node scripts/export-obsidian.js",
|
|
13
14
|
"test": "node tests/unit/test-package-config.js && node tests/unit/test-cli-init.js && node tests/unit/test-cli-web-help.js && node tests/unit/test-web-static-assets.js && node tests/unit/test-fs-utils.js && node tests/unit/test-task-schema.js && node tests/unit/test-daily-generation.js && node tests/unit/test-report-generation.js && node tests/unit/test-oracle-retrieval.js && node tests/unit/test-task-completion.js && node tests/unit/test-migrate-data.js && node tests/unit/test-blockers-validation.js && node tests/unit/test-blockers-report.js && node tests/unit/test-sm-weekly-report.js && node tests/integration/test-ingestor-task.js"
|
|
14
15
|
},
|
|
15
16
|
"keywords": [],
|