@cccarv82/freya 1.0.30 → 1.0.31
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 +52 -2
- package/cli/web.js +110 -2
- package/package.json +1 -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__')
|
|
@@ -519,6 +526,39 @@
|
|
|
519
526
|
}
|
|
520
527
|
}
|
|
521
528
|
|
|
529
|
+
async function reloadSlugRules() {
|
|
530
|
+
try {
|
|
531
|
+
const r = await api('/api/project-slug-map/get', { dir: dirOrDefault() });
|
|
532
|
+
const el = $('slugRules');
|
|
533
|
+
if (el) el.value = JSON.stringify(r.map || { rules: [] }, null, 2);
|
|
534
|
+
setPill('ok', 'rules loaded');
|
|
535
|
+
setTimeout(() => setPill('ok', 'idle'), 800);
|
|
536
|
+
} catch (e) {
|
|
537
|
+
setPill('err', 'rules load failed');
|
|
538
|
+
setOut(String(e && e.message ? e.message : e));
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
async function saveSlugRules() {
|
|
543
|
+
try {
|
|
544
|
+
const el = $('slugRules');
|
|
545
|
+
if (!el) return;
|
|
546
|
+
const raw = String(el.value || '').trim();
|
|
547
|
+
if (!raw) throw new Error('Rules JSON is empty');
|
|
548
|
+
let map;
|
|
549
|
+
try { map = JSON.parse(raw); } catch (e) { throw new Error('Invalid JSON: ' + (e.message || e)); }
|
|
550
|
+
|
|
551
|
+
setPill('run', 'saving rules…');
|
|
552
|
+
const r = await api('/api/project-slug-map/save', { dir: dirOrDefault(), map });
|
|
553
|
+
if (el) el.value = JSON.stringify(r.map || map, null, 2);
|
|
554
|
+
setPill('ok', 'rules saved');
|
|
555
|
+
setTimeout(() => setPill('ok', 'idle'), 800);
|
|
556
|
+
} catch (e) {
|
|
557
|
+
setPill('err', 'rules save failed');
|
|
558
|
+
setOut(String(e && e.message ? e.message : e));
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
|
|
522
562
|
async function saveSettings() {
|
|
523
563
|
try {
|
|
524
564
|
saveLocal();
|
|
@@ -560,7 +600,8 @@
|
|
|
560
600
|
const webhookUrl = target === 'discord' ? $('discord').value.trim() : $('teams').value.trim();
|
|
561
601
|
if (!webhookUrl) throw new Error('Configure o webhook antes.');
|
|
562
602
|
setPill('run', 'publish…');
|
|
563
|
-
|
|
603
|
+
const mode = state.prettyPublish ? 'pretty' : 'chunks';
|
|
604
|
+
await api('/api/publish', { webhookUrl, text: state.lastText, mode, allowSecrets: true });
|
|
564
605
|
setPill('ok', 'published');
|
|
565
606
|
} catch (e) {
|
|
566
607
|
setPill('err', 'publish failed');
|
|
@@ -587,6 +628,12 @@
|
|
|
587
628
|
}
|
|
588
629
|
}
|
|
589
630
|
|
|
631
|
+
function togglePrettyPublish() {
|
|
632
|
+
const cb = $('prettyPublish');
|
|
633
|
+
state.prettyPublish = cb ? !!cb.checked : true;
|
|
634
|
+
try { localStorage.setItem('freya.prettyPublish', state.prettyPublish ? '1' : '0'); } catch {}
|
|
635
|
+
}
|
|
636
|
+
|
|
590
637
|
function toggleAutoRunReports() {
|
|
591
638
|
const cb = $('autoRunReports');
|
|
592
639
|
state.autoRunReports = cb ? !!cb.checked : false;
|
|
@@ -758,6 +805,8 @@
|
|
|
758
805
|
window.saveSettings = saveSettings;
|
|
759
806
|
window.refreshReports = refreshReports;
|
|
760
807
|
window.refreshToday = refreshToday;
|
|
808
|
+
window.reloadSlugRules = reloadSlugRules;
|
|
809
|
+
window.saveSlugRules = saveSlugRules;
|
|
761
810
|
window.renderReportsList = renderReportsList;
|
|
762
811
|
window.copyOut = copyOut;
|
|
763
812
|
window.copyPath = copyPath;
|
|
@@ -769,6 +818,7 @@
|
|
|
769
818
|
window.saveAndPlan = saveAndPlan;
|
|
770
819
|
window.toggleAutoApply = toggleAutoApply;
|
|
771
820
|
window.toggleAutoRunReports = toggleAutoRunReports;
|
|
821
|
+
window.togglePrettyPublish = togglePrettyPublish;
|
|
772
822
|
window.applyPlan = applyPlan;
|
|
773
823
|
window.runSuggestedReports = runSuggestedReports;
|
|
774
824
|
})();
|
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) {
|
|
@@ -682,6 +753,11 @@ function buildHtml(safeDefault) {
|
|
|
682
753
|
<div class="help">Os webhooks ficam salvos na workspace em <code>data/settings/settings.json</code>.</div>
|
|
683
754
|
|
|
684
755
|
<div style="height:10px"></div>
|
|
756
|
+
<label style="display:flex; align-items:center; gap:10px; user-select:none; margin: 6px 0 12px 0">
|
|
757
|
+
<input id="prettyPublish" type="checkbox" checked style="width:auto" onchange="togglePrettyPublish()" />
|
|
758
|
+
Pretty publish (cards/embeds)
|
|
759
|
+
</label>
|
|
760
|
+
|
|
685
761
|
<div class="stack">
|
|
686
762
|
<button class="btn" onclick="saveSettings()">Save settings</button>
|
|
687
763
|
<button class="btn" onclick="publish('discord')">Publish selected → Discord</button>
|
|
@@ -691,6 +767,15 @@ function buildHtml(safeDefault) {
|
|
|
691
767
|
<div style="height:14px"></div>
|
|
692
768
|
|
|
693
769
|
<div class="help"><b>Dica:</b> clique em um relatório em <i>Reports</i> para ver o preview e habilitar publish/copy.</div>
|
|
770
|
+
|
|
771
|
+
<div style="height:14px"></div>
|
|
772
|
+
<label>Project slug rules</label>
|
|
773
|
+
<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>
|
|
774
|
+
<div class="help">Regras usadas pra inferir <code>projectSlug</code>. Formato JSON (objeto com <code>rules</code>). Editável no estilo Obsidian-friendly.</div>
|
|
775
|
+
<div class="stack" style="margin-top:10px">
|
|
776
|
+
<button class="btn" onclick="reloadSlugRules()">Reload rules</button>
|
|
777
|
+
<button class="btn" onclick="saveSlugRules()">Save rules</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 });
|