@cccarv82/freya 2.3.13 → 2.4.1
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/.agent/rules/freya/agents/coach.mdc +7 -16
- package/.agent/rules/freya/agents/ingestor.mdc +1 -89
- package/.agent/rules/freya/agents/master.mdc +3 -0
- package/.agent/rules/freya/agents/oracle.mdc +7 -23
- package/cli/web-ui.css +860 -182
- package/cli/web-ui.js +547 -175
- package/cli/web.js +690 -536
- package/package.json +6 -3
- package/scripts/build-vector-index.js +85 -0
- package/scripts/export-obsidian.js +6 -16
- package/scripts/generate-blockers-report.js +5 -17
- package/scripts/generate-daily-summary.js +25 -58
- package/scripts/generate-executive-report.js +22 -204
- package/scripts/generate-sm-weekly-report.js +27 -92
- package/scripts/lib/DataLayer.js +92 -0
- package/scripts/lib/DataManager.js +198 -0
- package/scripts/lib/Embedder.js +59 -0
- package/scripts/lib/schema.js +23 -0
- package/scripts/migrate-v1-v2.js +184 -0
- package/scripts/validate-data.js +48 -51
- package/scripts/validate-structure.js +12 -58
- package/templates/base/scripts/build-vector-index.js +85 -0
- package/templates/base/scripts/export-obsidian.js +143 -0
- package/templates/base/scripts/generate-daily-summary.js +25 -58
- package/templates/base/scripts/generate-executive-report.js +14 -225
- package/templates/base/scripts/generate-sm-weekly-report.js +9 -91
- package/templates/base/scripts/index/build-index.js +13 -0
- package/templates/base/scripts/index/update-index.js +15 -0
- package/templates/base/scripts/lib/DataLayer.js +92 -0
- package/templates/base/scripts/lib/DataManager.js +198 -0
- package/templates/base/scripts/lib/Embedder.js +59 -0
- package/templates/base/scripts/lib/index-utils.js +407 -0
- package/templates/base/scripts/lib/schema.js +23 -0
- package/templates/base/scripts/lib/search-utils.js +183 -0
- package/templates/base/scripts/migrate-v1-v2.js +184 -0
- package/templates/base/scripts/validate-data.js +48 -51
- package/templates/base/scripts/validate-structure.js +10 -32
package/cli/web.js
CHANGED
|
@@ -8,6 +8,8 @@ const { spawn } = require('child_process');
|
|
|
8
8
|
const { searchWorkspace } = require('../scripts/lib/search-utils');
|
|
9
9
|
const { searchIndex } = require('../scripts/lib/index-utils');
|
|
10
10
|
const { initWorkspace } = require('./init');
|
|
11
|
+
const { defaultInstance: dl } = require('../scripts/lib/DataLayer');
|
|
12
|
+
const DataManager = require('../scripts/lib/DataManager');
|
|
11
13
|
|
|
12
14
|
function readAppVersion() {
|
|
13
15
|
const pkgPath = path.join(__dirname, '..', 'package.json');
|
|
@@ -943,6 +945,11 @@ function timelineHtml(defaultDir) {
|
|
|
943
945
|
return buildTimelineHtml(safeDefault, APP_VERSION);
|
|
944
946
|
}
|
|
945
947
|
|
|
948
|
+
function settingsHtml(defaultDir) {
|
|
949
|
+
const safeDefault = String(defaultDir || './freya').replace(/\\/g, '\\\\').replace(/"/g, '\\"');
|
|
950
|
+
return buildSettingsHtml(safeDefault, APP_VERSION);
|
|
951
|
+
}
|
|
952
|
+
|
|
946
953
|
function buildHtml(safeDefault, appVersion) {
|
|
947
954
|
const safeVersion = escapeHtml(appVersion || 'unknown');
|
|
948
955
|
return `<!doctype html>
|
|
@@ -963,15 +970,27 @@ function buildHtml(safeDefault, appVersion) {
|
|
|
963
970
|
<div class="railLogo">F</div>
|
|
964
971
|
</div>
|
|
965
972
|
<div class="railNav">
|
|
966
|
-
<button class="railBtn active" id="railDashboard" type="button" title="Dashboard">
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
<button class="railBtn" id="
|
|
970
|
-
|
|
971
|
-
|
|
973
|
+
<button class="railBtn active" id="railDashboard" type="button" title="Dashboard">
|
|
974
|
+
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="7" height="9"></rect><rect x="14" y="3" width="7" height="5"></rect><rect x="14" y="12" width="7" height="9"></rect><rect x="3" y="16" width="7" height="5"></rect></svg>
|
|
975
|
+
</button>
|
|
976
|
+
<button class="railBtn" id="railReports" type="button" title="Relatórios">
|
|
977
|
+
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path><polyline points="14 2 14 8 20 8"></polyline><line x1="16" y1="13" x2="8" y2="13"></line><line x1="16" y1="17" x2="8" y2="17"></line><polyline points="10 9 9 9 8 9"></polyline></svg>
|
|
978
|
+
</button>
|
|
979
|
+
<button class="railBtn" id="railCompanion" type="button" title="Companion">
|
|
980
|
+
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle><polygon points="16.24 7.76 14.12 14.12 7.76 16.24 9.88 9.88 16.24 7.76"></polygon></svg>
|
|
981
|
+
</button>
|
|
982
|
+
<button class="railBtn" id="railProjects" type="button" title="Projects">
|
|
983
|
+
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"></path></svg>
|
|
984
|
+
</button>
|
|
985
|
+
<button class="railBtn" id="railTimeline" type="button" title="Timeline">
|
|
986
|
+
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle><polyline points="12 6 12 12 16 14"></polyline></svg>
|
|
987
|
+
</button>
|
|
988
|
+
<button class="railBtn" id="railGraph" type="button" title="Grafo">
|
|
989
|
+
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="18" cy="5" r="3"></circle><circle cx="6" cy="12" r="3"></circle><circle cx="18" cy="19" r="3"></circle><line x1="8.59" y1="13.51" x2="15.42" y2="17.49"></line><line x1="15.41" y1="6.51" x2="8.59" y2="10.49"></line></svg>
|
|
990
|
+
</button>
|
|
972
991
|
</div>
|
|
973
|
-
<div class="railBottom">
|
|
974
|
-
|
|
992
|
+
<div class="railBottom">\n <button id="railSettings" class="railBtn" title="Configurações" onclick="if (document.body.dataset.page !== \'settings\') window.location.href=\'/settings\';"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"></circle><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"></path></svg></button>
|
|
993
|
+
|
|
975
994
|
</div>
|
|
976
995
|
</aside>
|
|
977
996
|
|
|
@@ -991,60 +1010,73 @@ function buildHtml(safeDefault, appVersion) {
|
|
|
991
1010
|
</div>
|
|
992
1011
|
|
|
993
1012
|
<div class="centerBody">
|
|
994
|
-
<section
|
|
995
|
-
<div class="promptBar">
|
|
996
|
-
<div
|
|
997
|
-
<div class="
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
<
|
|
1013
|
+
<section style="margin-bottom: 24px; display: grid; grid-template-columns: 1fr 1fr; gap: 16px; height: 380px; max-height: 450px;">
|
|
1014
|
+
<div class="promptBar" style="width: 100%; border-radius: 20px; height: 100%; display: flex; flex-direction: column; justify-content: space-between; overflow: hidden;">
|
|
1015
|
+
<div style="flex: 1; display: flex; flex-direction: column;">
|
|
1016
|
+
<div class="promptMeta">
|
|
1017
|
+
<div class="promptTitle" style="display: flex; align-items: center; gap: 8px;">
|
|
1018
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="color: var(--primary)"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path><polyline points="17 8 12 3 7 8"></polyline><line x1="12" y1="3" x2="12" y2="15"></line></svg>
|
|
1019
|
+
<span>Inbox & Prompt</span>
|
|
1020
|
+
</div>
|
|
1021
|
+
<div id="status" class="small">pronto</div>
|
|
1022
|
+
</div>
|
|
1023
|
+
<textarea id="inboxText" placeholder="Cole updates do dia (status, blockers, decisões, ideias) ou faça uma pergunta para a Freya..." style="resize:none; flex: 1; min-height: 0;"></textarea>
|
|
1005
1024
|
</div>
|
|
1006
|
-
<div
|
|
1007
|
-
<
|
|
1008
|
-
<
|
|
1009
|
-
|
|
1010
|
-
</
|
|
1011
|
-
<
|
|
1012
|
-
<
|
|
1013
|
-
|
|
1014
|
-
|
|
1025
|
+
<div style="display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 12px; margin-top: 12px;">
|
|
1026
|
+
<div class="promptActions" style="margin: 0;">
|
|
1027
|
+
<button class="btn primary small" type="button" onclick="saveAndPlan()">Salvar + Processar</button>
|
|
1028
|
+
<button class="btn small" type="button" onclick="runSuggestedReports()">Rodar relatórios sugeridos</button>
|
|
1029
|
+
</div>
|
|
1030
|
+
<div class="promptToggles" style="margin: 0;">
|
|
1031
|
+
<label class="toggleRow">
|
|
1032
|
+
<input id="autoApply" type="checkbox" checked style="width:auto; margin: 0;" onchange="toggleAutoApply()" />
|
|
1033
|
+
Auto-apply
|
|
1034
|
+
</label>
|
|
1035
|
+
<label class="toggleRow">
|
|
1036
|
+
<input id="autoRunReports" type="checkbox" style="width:auto; margin: 0;" onchange="toggleAutoRunReports()" />
|
|
1037
|
+
Auto-run reports
|
|
1038
|
+
</label>
|
|
1039
|
+
</div>
|
|
1015
1040
|
</div>
|
|
1016
1041
|
</div>
|
|
1017
|
-
</section>
|
|
1018
1042
|
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
<
|
|
1043
|
+
<div class="panel" style="height: 100%; display: flex; flex-direction: column;">
|
|
1044
|
+
<div class="panelHead">
|
|
1045
|
+
<b style="display: flex; align-items: center; gap: 8px;">
|
|
1046
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"></path></svg>
|
|
1047
|
+
Consultar Oracle
|
|
1048
|
+
</b>
|
|
1049
|
+
<div class="stack">
|
|
1050
|
+
<button class="btn small" type="button" onclick="exportChatObsidian()">Exportar Log</button>
|
|
1051
|
+
</div>
|
|
1026
1052
|
</div>
|
|
1027
|
-
<div class="
|
|
1028
|
-
<
|
|
1029
|
-
<
|
|
1053
|
+
<div class="panelBody" style="flex:1; display:flex; flex-direction:column; padding:0; background:var(--glass-bg); min-height:0;">
|
|
1054
|
+
<div id="chatThread" style="flex:1; overflow-y:auto; overflow-x:hidden; padding:12px; display:flex; flex-direction:column; gap:8px;"></div>
|
|
1055
|
+
<div style="padding: 12px; border-top: 1px solid var(--glass-border); display:flex; gap: 8px; background: var(--paper);">
|
|
1056
|
+
<input type="text" id="oracleInput" autocomplete="off" style="flex:1;" placeholder="Pergunte algo à Freya..." onkeydown="if(event.key==='Enter') window.askFreyaInline()" />
|
|
1057
|
+
<button class="btn primary small" type="button" onclick="window.askFreyaInline()">Enviar</button>
|
|
1058
|
+
</div>
|
|
1030
1059
|
</div>
|
|
1031
|
-
<div style="height:10px"></div>
|
|
1032
|
-
<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>
|
|
1033
|
-
<div class="help"><b>Migrate data</b>: ajusta formatos/schemaVersion quando uma versão nova exige.</div>
|
|
1034
1060
|
</div>
|
|
1061
|
+
</section>
|
|
1035
1062
|
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
<div
|
|
1039
|
-
<
|
|
1040
|
-
<
|
|
1041
|
-
|
|
1042
|
-
|
|
1063
|
+
<section class="utilityGrid" id="dashboardControls" style="display:grid; grid-template-columns: 1fr; gap: 16px; margin-bottom: 24px;">
|
|
1064
|
+
<div class="utilityCard" style="display:flex; flex-wrap: wrap; justify-content: space-between; align-items: center; gap: 16px;">
|
|
1065
|
+
<div>
|
|
1066
|
+
<div class="utilityHead" style="margin-bottom: 4px; color: var(--text);">Relatórios Rápidos</div>
|
|
1067
|
+
<div class="help" style="margin: 0;">Gere relatórios sintéticos instantaneamente</div>
|
|
1068
|
+
</div>
|
|
1069
|
+
<div style="display:flex; gap: 8px; flex-wrap: wrap;">
|
|
1070
|
+
<button class="btn" style="min-width: 100px; padding: 6px 12px; font-weight: 500;" type="button" onclick="runReport('status')">Executivo</button>
|
|
1071
|
+
<button class="btn" style="min-width: 100px; padding: 6px 12px; font-weight: 500;" type="button" onclick="runReport('sm-weekly')">SM Semanal</button>
|
|
1072
|
+
<button class="btn" style="min-width: 100px; padding: 6px 12px; font-weight: 500; border-color: rgba(239, 68, 68, 0.4); color: #f87171;" type="button" onclick="runReport('blockers')">Bloqueios</button>
|
|
1073
|
+
<button class="btn" style="min-width: 100px; padding: 6px 12px; font-weight: 500;" type="button" onclick="runReport('daily')">Daily</button>
|
|
1043
1074
|
</div>
|
|
1044
|
-
<div class="help" style="margin-top:8px">Clique para gerar e atualizar o preview/publicação.</div>
|
|
1045
1075
|
</div>
|
|
1046
1076
|
</section>
|
|
1047
1077
|
|
|
1078
|
+
|
|
1079
|
+
|
|
1048
1080
|
<div class="centerHead">
|
|
1049
1081
|
<div>
|
|
1050
1082
|
<h1 style="margin:0">Seu dia em um painel</h1>
|
|
@@ -1056,63 +1088,40 @@ function buildHtml(safeDefault, appVersion) {
|
|
|
1056
1088
|
</div>
|
|
1057
1089
|
|
|
1058
1090
|
<div class="midGrid">
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1091
|
+
<!-- Hoje Panel gets more prominent focus -->
|
|
1092
|
+
<section class="panel midSpan">
|
|
1093
|
+
<div class="panelHead" style="background: linear-gradient(90deg, var(--paper2), var(--paper)); border-left: 4px solid var(--accent)">
|
|
1094
|
+
<b style="color: var(--text); font-size: 14px;">Foco de Hoje</b>
|
|
1062
1095
|
<div class="stack">
|
|
1063
1096
|
<button class="btn small" type="button" onclick="refreshToday()">Atualizar</button>
|
|
1064
1097
|
</div>
|
|
1065
1098
|
</div>
|
|
1066
|
-
<div class="panelBody
|
|
1067
|
-
<div
|
|
1068
|
-
|
|
1069
|
-
<div style="
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1099
|
+
<div class="panelBody" style="display: flex; flex-direction: column; gap: 16px;">
|
|
1100
|
+
<div id="swimlaneContainer" style="display:flex; flex-direction: column; gap: 12px;"></div>
|
|
1101
|
+
|
|
1102
|
+
<div style="border-top: 1px solid var(--border); padding-top: 16px;">
|
|
1103
|
+
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px;">
|
|
1104
|
+
<div class="small" style="opacity:.8; font-weight: 600; text-transform: uppercase;">Insights Globais de Bloqueios</div>
|
|
1105
|
+
<button class="btn small" type="button" onclick="refreshBlockersInsights()">Atualizar insights</button>
|
|
1106
|
+
</div>
|
|
1107
|
+
<div id="blockersInsights" class="help"></div>
|
|
1108
|
+
</div>
|
|
1076
1109
|
</div>
|
|
1077
1110
|
</section>
|
|
1078
1111
|
|
|
1079
1112
|
<section class="panel">
|
|
1080
1113
|
<div class="panelHead">
|
|
1081
|
-
<b>Relatórios</b>
|
|
1114
|
+
<b>Relatórios Recentes</b>
|
|
1082
1115
|
<div class="stack">
|
|
1083
1116
|
<button class="btn small" type="button" onclick="refreshReports()">Atualizar</button>
|
|
1084
1117
|
</div>
|
|
1085
1118
|
</div>
|
|
1086
|
-
<div class="panelBody panelScroll">
|
|
1087
|
-
<input id="reportsFilter" placeholder="
|
|
1119
|
+
<div class="panelBody panelScroll" style="max-height: 300px;">
|
|
1120
|
+
<input id="reportsFilter" placeholder="Filtrar relatórios..." style="width:100%; margin-bottom:10px" oninput="renderReportsList()" />
|
|
1088
1121
|
<div id="reportsList" style="display:grid; gap:8px"></div>
|
|
1089
|
-
<div class="help">Últimos relatórios em <code>docs/reports</code>. Clique para abrir o preview.</div>
|
|
1090
|
-
</div>
|
|
1091
|
-
</section>
|
|
1092
|
-
|
|
1093
|
-
<section class="panel midSpan">
|
|
1094
|
-
<div class="panelHead">
|
|
1095
|
-
<b>Preview</b>
|
|
1096
|
-
<div class="stack">
|
|
1097
|
-
<button class="btn small" type="button" onclick="copyOut()">Copy</button>
|
|
1098
|
-
<button class="btn small" type="button" onclick="applyPlan()">Apply plan</button>
|
|
1099
|
-
<button class="btn small" type="button" onclick="copyPath()">Copy path</button>
|
|
1100
|
-
<button class="btn small" type="button" onclick="openSelected()">Open file</button>
|
|
1101
|
-
<button class="btn small" type="button" onclick="downloadSelected()">Download .md</button>
|
|
1102
|
-
<button class="btn small" type="button" onclick="clearOut()">Clear</button>
|
|
1103
|
-
</div>
|
|
1104
|
-
</div>
|
|
1105
|
-
<div class="panelBody">
|
|
1106
|
-
<div id="reportPreview" class="log md" style="font-family: var(--sans);"></div>
|
|
1107
|
-
<div class="help">O preview renderiza Markdown básico (headers, listas, code). O botão Copy copia o conteúdo completo.</div>
|
|
1108
1122
|
</div>
|
|
1109
1123
|
</section>
|
|
1110
1124
|
</div>
|
|
1111
|
-
|
|
1112
|
-
<details class="devDrawer" id="devDrawer">
|
|
1113
|
-
<summary>Developer (modo avançado)</summary>
|
|
1114
|
-
<div class="devBody">
|
|
1115
|
-
<div class="devGrid">
|
|
1116
1125
|
<div class="panel">
|
|
1117
1126
|
<div class="panelHead"><b>Configurações de publicação</b></div>
|
|
1118
1127
|
<div class="panelBody">
|
|
@@ -1128,61 +1137,6 @@ function buildHtml(safeDefault, appVersion) {
|
|
|
1128
1137
|
<label style="display:flex; align-items:center; gap:10px; user-select:none; margin: 6px 0 12px 0">
|
|
1129
1138
|
<input id="prettyPublish" type="checkbox" checked style="width:auto" onchange="togglePrettyPublish()" />
|
|
1130
1139
|
Publicação bonita (cards/embeds)
|
|
1131
|
-
</label>
|
|
1132
|
-
|
|
1133
|
-
<div class="stack">
|
|
1134
|
-
<button class="btn" type="button" onclick="saveSettings()">Salvar configurações</button>
|
|
1135
|
-
<button class="btn" type="button" onclick="publish('discord')">Publicar selecionado → Discord</button>
|
|
1136
|
-
<button class="btn" type="button" onclick="publish('teams')">Publicar selecionado → Teams</button>
|
|
1137
|
-
</div>
|
|
1138
|
-
</div>
|
|
1139
|
-
</div>
|
|
1140
|
-
|
|
1141
|
-
<div class="panel">
|
|
1142
|
-
<div class="panelHead"><b>Slugs & Export</b></div>
|
|
1143
|
-
<div class="panelBody">
|
|
1144
|
-
<label>Regras de slug do projeto</label>
|
|
1145
|
-
<textarea id="slugRules" rows="8" placeholder="{ \"rules\": [ { \"contains\": \"fideliza\", \"slug\": \"vivo/fidelizacao\" } ] }"></textarea>
|
|
1146
|
-
<div class="help">Regras usadas pra inferir <code>projectSlug</code>. Formato JSON (objeto com <code>rules</code>).</div>
|
|
1147
|
-
<div class="stack" style="margin-top:10px">
|
|
1148
|
-
<button class="btn" type="button" onclick="reloadSlugRules()">Recarregar regras</button>
|
|
1149
|
-
<button class="btn" type="button" onclick="saveSlugRules()">Salvar regras</button>
|
|
1150
|
-
<button class="btn" type="button" onclick="exportObsidian()">Exportar notas (Obsidian)</button>
|
|
1151
|
-
</div>
|
|
1152
|
-
</div>
|
|
1153
|
-
</div>
|
|
1154
|
-
|
|
1155
|
-
<div class="panel">
|
|
1156
|
-
<div class="panelHead"><b>Debug</b></div>
|
|
1157
|
-
<div class="panelBody">
|
|
1158
|
-
<div class="help">Logs ficam em <code>logs/</code> e debug traces em <code>.debuglogs/</code> dentro da workspace.</div>
|
|
1159
|
-
<div class="help">Use <b>Open file</b> / <b>Copy path</b> no Preview para abrir/compartilhar o relatório selecionado.</div>
|
|
1160
|
-
<div class="stack" style="margin-top:10px">
|
|
1161
|
-
<button class="btn" type="button" onclick="rebuildIndex()">Rebuild search index</button>
|
|
1162
|
-
</div>
|
|
1163
|
-
</div>
|
|
1164
|
-
</div>
|
|
1165
|
-
</div>
|
|
1166
|
-
</div>
|
|
1167
|
-
</details>
|
|
1168
|
-
</div>
|
|
1169
|
-
</main>
|
|
1170
|
-
|
|
1171
|
-
<aside class="chatPane">
|
|
1172
|
-
<div class="chatHead">
|
|
1173
|
-
<div>
|
|
1174
|
-
<div class="chatTitle">Conversa</div>
|
|
1175
|
-
<div class="chatSub">Cole seus updates e deixe os Agents planejar/aplicar.</div>
|
|
1176
|
-
</div>
|
|
1177
|
-
</div>
|
|
1178
|
-
|
|
1179
|
-
<div class="chatThread" id="chatThread">
|
|
1180
|
-
<div class="bubble assistant">
|
|
1181
|
-
<div class="bubbleMeta">FREYA</div>
|
|
1182
|
-
<div class="bubbleBody">Cole seus updates (status, blockers, decisões, ideias) e clique em <b>Save + Process</b>.</div>
|
|
1183
|
-
</div>
|
|
1184
|
-
</div>
|
|
1185
|
-
</aside>
|
|
1186
1140
|
|
|
1187
1141
|
</div>
|
|
1188
1142
|
</div>
|
|
@@ -1216,15 +1170,27 @@ function buildReportsHtml(safeDefault, appVersion) {
|
|
|
1216
1170
|
<div class="railLogo">F</div>
|
|
1217
1171
|
</div>
|
|
1218
1172
|
<div class="railNav">
|
|
1219
|
-
<button class="railBtn" id="railDashboard" type="button" title="Dashboard">
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
<button class="railBtn" id="
|
|
1223
|
-
|
|
1224
|
-
|
|
1173
|
+
<button class="railBtn" id="railDashboard" type="button" title="Dashboard">
|
|
1174
|
+
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="7" height="9"></rect><rect x="14" y="3" width="7" height="5"></rect><rect x="14" y="12" width="7" height="9"></rect><rect x="3" y="16" width="7" height="5"></rect></svg>
|
|
1175
|
+
</button>
|
|
1176
|
+
<button class="railBtn active" id="railReports" type="button" title="Relatórios">
|
|
1177
|
+
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path><polyline points="14 2 14 8 20 8"></polyline><line x1="16" y1="13" x2="8" y2="13"></line><line x1="16" y1="17" x2="8" y2="17"></line><polyline points="10 9 9 9 8 9"></polyline></svg>
|
|
1178
|
+
</button>
|
|
1179
|
+
<button class="railBtn" id="railCompanion" type="button" title="Companion">
|
|
1180
|
+
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle><polygon points="16.24 7.76 14.12 14.12 7.76 16.24 9.88 9.88 16.24 7.76"></polygon></svg>
|
|
1181
|
+
</button>
|
|
1182
|
+
<button class="railBtn" id="railProjects" type="button" title="Projects">
|
|
1183
|
+
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"></path></svg>
|
|
1184
|
+
</button>
|
|
1185
|
+
<button class="railBtn" id="railTimeline" type="button" title="Timeline">
|
|
1186
|
+
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle><polyline points="12 6 12 12 16 14"></polyline></svg>
|
|
1187
|
+
</button>
|
|
1188
|
+
<button class="railBtn" id="railGraph" type="button" title="Grafo">
|
|
1189
|
+
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="18" cy="5" r="3"></circle><circle cx="6" cy="12" r="3"></circle><circle cx="18" cy="19" r="3"></circle><line x1="8.59" y1="13.51" x2="15.42" y2="17.49"></line><line x1="15.41" y1="6.51" x2="8.59" y2="10.49"></line></svg>
|
|
1190
|
+
</button>
|
|
1225
1191
|
</div>
|
|
1226
|
-
<div class="railBottom">
|
|
1227
|
-
|
|
1192
|
+
<div class="railBottom">\n <button id="railSettings" class="railBtn" title="Configura��es">\n <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"></circle><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"></path></svg>\n </button>
|
|
1193
|
+
|
|
1228
1194
|
</div>
|
|
1229
1195
|
</aside>
|
|
1230
1196
|
|
|
@@ -1256,8 +1222,12 @@ function buildReportsHtml(safeDefault, appVersion) {
|
|
|
1256
1222
|
</div>
|
|
1257
1223
|
</section>
|
|
1258
1224
|
|
|
1259
|
-
<section class="reportsTools">
|
|
1260
|
-
<
|
|
1225
|
+
<section class="reportsTools" style="display:flex; gap:12px; align-items:center;">
|
|
1226
|
+
<div class="tabs" style="display:flex; gap:8px;" id="reportsTabs">
|
|
1227
|
+
<button class="pill ok" id="tabChrono" onclick="setReportsTab('chrono')">Cronológico</button>
|
|
1228
|
+
<button class="pill" id="tabType" onclick="setReportsTab('type')">Por Tipo</button>
|
|
1229
|
+
</div>
|
|
1230
|
+
<input id="reportsFilter" style="flex:1" placeholder="filtrar (ex: daily, executive, 2026-01-29)" oninput="renderReportsPage()" />
|
|
1261
1231
|
</section>
|
|
1262
1232
|
|
|
1263
1233
|
<section class="reportsGrid" id="reportsGrid"></section>
|
|
@@ -1296,15 +1266,27 @@ function buildProjectsHtml(safeDefault, appVersion) {
|
|
|
1296
1266
|
<div class="railLogo">F</div>
|
|
1297
1267
|
</div>
|
|
1298
1268
|
<div class="railNav">
|
|
1299
|
-
<button class="railBtn" id="railDashboard" type="button" title="Dashboard">
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
<button class="railBtn
|
|
1303
|
-
|
|
1304
|
-
|
|
1269
|
+
<button class="railBtn" id="railDashboard" type="button" title="Dashboard">
|
|
1270
|
+
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="7" height="9"></rect><rect x="14" y="3" width="7" height="5"></rect><rect x="14" y="12" width="7" height="9"></rect><rect x="3" y="16" width="7" height="5"></rect></svg>
|
|
1271
|
+
</button>
|
|
1272
|
+
<button class="railBtn" id="railReports" type="button" title="Relatorios">
|
|
1273
|
+
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path><polyline points="14 2 14 8 20 8"></polyline><line x1="16" y1="13" x2="8" y2="13"></line><line x1="16" y1="17" x2="8" y2="17"></line><polyline points="10 9 9 9 8 9"></polyline></svg>
|
|
1274
|
+
</button>
|
|
1275
|
+
<button class="railBtn" id="railCompanion" type="button" title="Companion">
|
|
1276
|
+
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle><polygon points="16.24 7.76 14.12 14.12 7.76 16.24 9.88 9.88 16.24 7.76"></polygon></svg>
|
|
1277
|
+
</button>
|
|
1278
|
+
<button class="railBtn active" id="railProjects" type="button" title="Projects">
|
|
1279
|
+
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"></path></svg>
|
|
1280
|
+
</button>
|
|
1281
|
+
<button class="railBtn" id="railTimeline" type="button" title="Timeline">
|
|
1282
|
+
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle><polyline points="12 6 12 12 16 14"></polyline></svg>
|
|
1283
|
+
</button>
|
|
1284
|
+
<button class="railBtn" id="railGraph" type="button" title="Grafo">
|
|
1285
|
+
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="18" cy="5" r="3"></circle><circle cx="6" cy="12" r="3"></circle><circle cx="18" cy="19" r="3"></circle><line x1="8.59" y1="13.51" x2="15.42" y2="17.49"></line><line x1="15.41" y1="6.51" x2="8.59" y2="10.49"></line></svg>
|
|
1286
|
+
</button>
|
|
1305
1287
|
</div>
|
|
1306
|
-
<div class="railBottom">
|
|
1307
|
-
|
|
1288
|
+
<div class="railBottom">\n <button id="railSettings" class="railBtn" title="Configura��es">\n <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"></circle><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"></path></svg>\n </button>
|
|
1289
|
+
|
|
1308
1290
|
</div>
|
|
1309
1291
|
</aside>
|
|
1310
1292
|
|
|
@@ -1376,12 +1358,24 @@ function buildTimelineHtml(safeDefault, appVersion) {
|
|
|
1376
1358
|
<div class=\"railLogo\">F</div>
|
|
1377
1359
|
</div>
|
|
1378
1360
|
<div class=\"railNav\">
|
|
1379
|
-
<button class=\"railBtn\" id=\"railDashboard\" type=\"button\" title=\"Dashboard\">
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
<button class=\"railBtn\" id=\"
|
|
1383
|
-
|
|
1384
|
-
|
|
1361
|
+
<button class=\"railBtn\" id=\"railDashboard\" type=\"button\" title=\"Dashboard\">
|
|
1362
|
+
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="7" height="9"></rect><rect x="14" y="3" width="7" height="5"></rect><rect x="14" y="12" width="7" height="9"></rect><rect x="3" y="16" width="7" height="5"></rect></svg>
|
|
1363
|
+
</button>
|
|
1364
|
+
<button class=\"railBtn\" id=\"railReports\" type=\"button\" title=\"Relatorios\">
|
|
1365
|
+
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path><polyline points="14 2 14 8 20 8"></polyline><line x1="16" y1="13" x2="8" y2="13"></line><line x1="16" y1="17" x2="8" y2="17"></line><polyline points="10 9 9 9 8 9"></polyline></svg>
|
|
1366
|
+
</button>
|
|
1367
|
+
<button class=\"railBtn\" id=\"railCompanion\" type=\"button\" title=\"Companion\">
|
|
1368
|
+
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle><polygon points="16.24 7.76 14.12 14.12 7.76 16.24 9.88 9.88 16.24 7.76"></polygon></svg>
|
|
1369
|
+
</button>
|
|
1370
|
+
<button class=\"railBtn\" id=\"railProjects\" type=\"button\" title=\"Projects\">
|
|
1371
|
+
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"></path></svg>
|
|
1372
|
+
</button>
|
|
1373
|
+
<button class=\"railBtn active\" id=\"railTimeline\" type=\"button\" title=\"Timeline\">
|
|
1374
|
+
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle><polyline points="12 6 12 12 16 14"></polyline></svg>
|
|
1375
|
+
</button>
|
|
1376
|
+
<button class=\"railBtn\" id=\"railGraph\" type=\"button\" title=\"Grafo\">
|
|
1377
|
+
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="18" cy="5" r="3"></circle><circle cx="6" cy="12" r="3"></circle><circle cx="18" cy="19" r="3"></circle><line x1="8.59" y1="13.51" x2="15.42" y2="17.49"></line><line x1="15.41" y1="6.51" x2="8.59" y2="10.49"></line></svg>
|
|
1378
|
+
</button>
|
|
1385
1379
|
</div>
|
|
1386
1380
|
<div class=\"railBottom\">
|
|
1387
1381
|
<div class=\"railStatus\" id=\"railStatus\" title=\"status\"></div>
|
|
@@ -1471,15 +1465,27 @@ function buildGraphHtml(safeDefault, appVersion) {
|
|
|
1471
1465
|
<div class="railLogo">F</div>
|
|
1472
1466
|
</div>
|
|
1473
1467
|
<div class="railNav">
|
|
1474
|
-
<button class="railBtn" id="railDashboard" type="button" title="Dashboard">
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
<button class="railBtn" id="
|
|
1478
|
-
|
|
1479
|
-
|
|
1468
|
+
<button class="railBtn" id="railDashboard" type="button" title="Dashboard">
|
|
1469
|
+
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="7" height="9"></rect><rect x="14" y="3" width="7" height="5"></rect><rect x="14" y="12" width="7" height="9"></rect><rect x="3" y="16" width="7" height="5"></rect></svg>
|
|
1470
|
+
</button>
|
|
1471
|
+
<button class="railBtn" id="railReports" type="button" title="Relatorios">
|
|
1472
|
+
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path><polyline points="14 2 14 8 20 8"></polyline><line x1="16" y1="13" x2="8" y2="13"></line><line x1="16" y1="17" x2="8" y2="17"></line><polyline points="10 9 9 9 8 9"></polyline></svg>
|
|
1473
|
+
</button>
|
|
1474
|
+
<button class="railBtn" id="railCompanion" type="button" title="Companion">
|
|
1475
|
+
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle><polygon points="16.24 7.76 14.12 14.12 7.76 16.24 9.88 9.88 16.24 7.76"></polygon></svg>
|
|
1476
|
+
</button>
|
|
1477
|
+
<button class="railBtn" id="railProjects" type="button" title="Projects">
|
|
1478
|
+
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"></path></svg>
|
|
1479
|
+
</button>
|
|
1480
|
+
<button class="railBtn" id="railTimeline" type="button" title="Timeline">
|
|
1481
|
+
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle><polyline points="12 6 12 12 16 14"></polyline></svg>
|
|
1482
|
+
</button>
|
|
1483
|
+
<button class="railBtn active" id="railGraph" type="button" title="Grafo">
|
|
1484
|
+
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="18" cy="5" r="3"></circle><circle cx="6" cy="12" r="3"></circle><circle cx="18" cy="19" r="3"></circle><line x1="8.59" y1="13.51" x2="15.42" y2="17.49"></line><line x1="15.41" y1="6.51" x2="8.59" y2="10.49"></line></svg>
|
|
1485
|
+
</button>
|
|
1480
1486
|
</div>
|
|
1481
|
-
<div class="railBottom">
|
|
1482
|
-
|
|
1487
|
+
<div class="railBottom">\n <button id="railSettings" class="railBtn" title="Configura��es">\n <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"></circle><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"></path></svg>\n </button>
|
|
1488
|
+
|
|
1483
1489
|
</div>
|
|
1484
1490
|
</aside>
|
|
1485
1491
|
|
|
@@ -1549,15 +1555,27 @@ function buildCompanionHtml(safeDefault, appVersion) {
|
|
|
1549
1555
|
<div class="railLogo">F</div>
|
|
1550
1556
|
</div>
|
|
1551
1557
|
<div class="railNav">
|
|
1552
|
-
<button class="railBtn" id="railDashboard" type="button" title="Dashboard">
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
<button class="railBtn" id="
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
+
<button class="railBtn" id="railDashboard" type="button" title="Dashboard">
|
|
1559
|
+
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="7" height="9"></rect><rect x="14" y="3" width="7" height="5"></rect><rect x="14" y="12" width="7" height="9"></rect><rect x="3" y="16" width="7" height="5"></rect></svg>
|
|
1560
|
+
</button>
|
|
1561
|
+
<button class="railBtn" id="railReports" type="button" title="Relatórios">
|
|
1562
|
+
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path><polyline points="14 2 14 8 20 8"></polyline><line x1="16" y1="13" x2="8" y2="13"></line><line x1="16" y1="17" x2="8" y2="17"></line><polyline points="10 9 9 9 8 9"></polyline></svg>
|
|
1563
|
+
</button>
|
|
1564
|
+
<button class="railBtn active" id="railCompanion" type="button" title="Companion">
|
|
1565
|
+
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle><polygon points="16.24 7.76 14.12 14.12 7.76 16.24 9.88 9.88 16.24 7.76"></polygon></svg>
|
|
1566
|
+
</button>
|
|
1567
|
+
<button class="railBtn" id="railProjects" type="button" title="Projects">
|
|
1568
|
+
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"></path></svg>
|
|
1569
|
+
</button>
|
|
1570
|
+
<button class="railBtn" id="railTimeline" type="button" title="Timeline">
|
|
1571
|
+
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle><polyline points="12 6 12 12 16 14"></polyline></svg>
|
|
1572
|
+
</button>
|
|
1573
|
+
<button class="railBtn" id="railGraph" type="button" title="Grafo">
|
|
1574
|
+
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="18" cy="5" r="3"></circle><circle cx="6" cy="12" r="3"></circle><circle cx="18" cy="19" r="3"></circle><line x1="8.59" y1="13.51" x2="15.42" y2="17.49"></line><line x1="15.41" y1="6.51" x2="8.59" y2="10.49"></line></svg>
|
|
1575
|
+
</button>
|
|
1558
1576
|
</div>
|
|
1559
|
-
<div class="railBottom">
|
|
1560
|
-
|
|
1577
|
+
<div class="railBottom">\n <button id="railSettings" class="railBtn" title="Configura��es">\n <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"></circle><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"></path></svg>\n </button>
|
|
1578
|
+
|
|
1561
1579
|
</div>
|
|
1562
1580
|
</aside>
|
|
1563
1581
|
|
|
@@ -1582,81 +1600,107 @@ function buildCompanionHtml(safeDefault, appVersion) {
|
|
|
1582
1600
|
<section class="reportsHeader">
|
|
1583
1601
|
<div>
|
|
1584
1602
|
<div class="reportsTitle">Scrum Master Companion</div>
|
|
1585
|
-
<div class="reportsSubtitle">Painel
|
|
1603
|
+
<div class="reportsSubtitle">Painel rápido para gerar relatórios e checar pendências.</div>
|
|
1586
1604
|
</div>
|
|
1587
1605
|
<div class="reportsActions">
|
|
1588
|
-
<button class="btn small" type="button" onclick="refreshHealthChecklist()">Atualizar</button>
|
|
1606
|
+
<button class="btn small" type="button" onclick="refreshHealthChecklist()">Atualizar Painel</button>
|
|
1607
|
+
</div>
|
|
1589
1608
|
</section>
|
|
1590
1609
|
|
|
1591
|
-
<section class="reportsTools" style="grid-template-columns: repeat(auto-fit,minmax(180px,1fr));">
|
|
1610
|
+
<section class="reportsTools" style="display: grid; grid-template-columns: repeat(auto-fit,minmax(180px,1fr)); gap: 12px; margin-bottom: 24px;">
|
|
1592
1611
|
<button class="btn" type="button" onclick="runReport('daily')">Gerar Daily</button>
|
|
1593
1612
|
<button class="btn" type="button" onclick="runReport('blockers')">Gerar Blockers</button>
|
|
1594
1613
|
<button class="btn" type="button" onclick="runReport('status')">Gerar Status</button>
|
|
1595
1614
|
<button class="btn" type="button" onclick="runReport('sm-weekly')">Gerar SM Weekly</button>
|
|
1596
1615
|
</section>
|
|
1597
1616
|
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
<
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1617
|
+
<!-- BENTO GRID LAYOUT -->
|
|
1618
|
+
<div style="display: grid; grid-template-columns: 1fr 1.5fr; gap: 24px; align-items: start;">
|
|
1619
|
+
|
|
1620
|
+
<!-- Left Column / Block 1 -->
|
|
1621
|
+
<div style="display: flex; flex-direction: column; gap: 16px;">
|
|
1622
|
+
<section class="panel">
|
|
1623
|
+
<div class="panelHead" style="display:flex; align-items:center; justify-content:space-between; gap:10px">
|
|
1624
|
+
<b>Resumo Executivo</b>
|
|
1625
|
+
<button class="btn small" type="button" onclick="refreshExecutiveSummary()">↻</button>
|
|
1626
|
+
</div>
|
|
1627
|
+
<div class="panelBody">
|
|
1628
|
+
<div id="executiveSummary" class="help"></div>
|
|
1629
|
+
</div>
|
|
1630
|
+
</section>
|
|
1631
|
+
|
|
1632
|
+
<section class="panel">
|
|
1633
|
+
<div class="panelHead" style="display:flex; align-items:center; justify-content:space-between; gap:10px">
|
|
1634
|
+
<b>Radar de Risco</b>
|
|
1635
|
+
<button class="btn small" type="button" onclick="refreshRiskRadar()">↻</button>
|
|
1636
|
+
</div>
|
|
1637
|
+
<div class="panelBody">
|
|
1638
|
+
<div id="riskRadarBox"></div>
|
|
1639
|
+
</div>
|
|
1640
|
+
</section>
|
|
1609
1641
|
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1642
|
+
<section class="panel">
|
|
1643
|
+
<div class="panelHead" style="display:flex; align-items:center; justify-content:space-between; gap:10px">
|
|
1644
|
+
<b>Task Heatmap</b>
|
|
1645
|
+
</div>
|
|
1646
|
+
<div class="panelBody">
|
|
1647
|
+
<div id="heatmapGrid"></div>
|
|
1648
|
+
</div>
|
|
1649
|
+
</section>
|
|
1617
1650
|
</div>
|
|
1618
|
-
</section>
|
|
1619
1651
|
|
|
1620
|
-
|
|
1621
|
-
<div
|
|
1622
|
-
<
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1652
|
+
<!-- Right Column / Block 2 -->
|
|
1653
|
+
<div style="display: flex; flex-direction: column; gap: 16px;">
|
|
1654
|
+
<section class="panel">
|
|
1655
|
+
<div class="panelHead" style="display:flex; align-items:center; justify-content:space-between; gap:10px">
|
|
1656
|
+
<b style="color: var(--accent);">Saúde & Checklists</b>
|
|
1657
|
+
</div>
|
|
1658
|
+
<div class="panelBody" style="padding: 0;">
|
|
1659
|
+
<section class="reportsGrid" id="healthChecklist" style="gap: 0; border: none; box-shadow: none;"></section>
|
|
1660
|
+
</div>
|
|
1661
|
+
</section>
|
|
1629
1662
|
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1663
|
+
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 16px;">
|
|
1664
|
+
<section class="panel">
|
|
1665
|
+
<div class="panelHead" style="display:flex; align-items:center; justify-content:space-between; gap:10px">
|
|
1666
|
+
<b>Qualidade de Log</b>
|
|
1667
|
+
<button class="btn small" type="button" onclick="refreshQualityScore()">↻</button>
|
|
1668
|
+
</div>
|
|
1669
|
+
<div class="panelBody">
|
|
1670
|
+
<div id="qualityScoreCard"></div>
|
|
1671
|
+
</div>
|
|
1672
|
+
</section>
|
|
1639
1673
|
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1674
|
+
<section class="panel">
|
|
1675
|
+
<div class="panelHead" style="display:flex; align-items:center; justify-content:space-between; gap:10px">
|
|
1676
|
+
<b>Anomalias</b>
|
|
1677
|
+
<button class="btn small" type="button" onclick="refreshAnomalies()">↻</button>
|
|
1678
|
+
</div>
|
|
1679
|
+
<div class="panelBody">
|
|
1680
|
+
<div id="anomaliesBox"></div>
|
|
1681
|
+
</div>
|
|
1682
|
+
</section>
|
|
1683
|
+
</div>
|
|
1644
1684
|
</div>
|
|
1645
|
-
|
|
1685
|
+
|
|
1686
|
+
<!-- Full Width Span -->
|
|
1687
|
+
<div style="grid-column: 1 / -1; display: flex; flex-direction: column; gap: 16px;">
|
|
1688
|
+
<section class="panel">
|
|
1689
|
+
<div class="panelHead"><b>Incident Radar</b></div>
|
|
1690
|
+
<div class="panelBody">
|
|
1691
|
+
<div id="incidentsBox" class="log md" style="font-family: var(--sans);"></div>
|
|
1692
|
+
</div>
|
|
1693
|
+
</section>
|
|
1646
1694
|
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1695
|
+
<section class="panel">
|
|
1696
|
+
<div class="panelHead"><b>Saída de Relatório</b></div>
|
|
1697
|
+
<div class="panelBody">
|
|
1698
|
+
<div id="reportPreview" class="log md" style="font-family: var(--sans);"></div>
|
|
1699
|
+
</div>
|
|
1700
|
+
</section>
|
|
1651
1701
|
</div>
|
|
1652
|
-
</section>
|
|
1653
1702
|
|
|
1654
|
-
|
|
1655
|
-
<div class="panelHead"><b>Saida</b></div>
|
|
1656
|
-
<div class="panelBody">
|
|
1657
|
-
<div id="reportPreview" class="log md" style="font-family: var(--sans);"></div>
|
|
1658
|
-
</div>
|
|
1659
|
-
</section>
|
|
1703
|
+
</div>
|
|
1660
1704
|
</div>
|
|
1661
1705
|
</main>
|
|
1662
1706
|
|
|
@@ -2073,6 +2117,14 @@ async function cmdWeb({ port, dir, open, dev }) {
|
|
|
2073
2117
|
return;
|
|
2074
2118
|
}
|
|
2075
2119
|
|
|
2120
|
+
if (req.method === 'GET' && req.url === '/settings') {
|
|
2121
|
+
try { res.__freyaDebug.workspaceDir = normalizeWorkspaceDir(dir || './freya'); } catch { }
|
|
2122
|
+
const body = settingsHtml(dir || './freya');
|
|
2123
|
+
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8', 'Cache-Control': 'no-store' });
|
|
2124
|
+
res.end(body);
|
|
2125
|
+
return;
|
|
2126
|
+
}
|
|
2127
|
+
|
|
2076
2128
|
if (req.method === 'GET' && req.url === '/app.css') {
|
|
2077
2129
|
const css = fs.readFileSync(path.join(__dirname, 'web-ui.css'), 'utf8');
|
|
2078
2130
|
res.writeHead(200, { 'Content-Type': 'text/css; charset=utf-8', 'Cache-Control': 'no-store' });
|
|
@@ -2166,34 +2218,23 @@ async function cmdWeb({ port, dir, open, dev }) {
|
|
|
2166
2218
|
}
|
|
2167
2219
|
|
|
2168
2220
|
if (req.url === '/api/projects/list') {
|
|
2169
|
-
const
|
|
2221
|
+
const rawProjects = dl.db.prepare('SELECT * FROM projects').all();
|
|
2170
2222
|
const items = [];
|
|
2171
|
-
|
|
2172
|
-
const
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
stream: doc.stream || null,
|
|
2187
|
-
project: doc.project || null,
|
|
2188
|
-
active: doc.active !== false,
|
|
2189
|
-
currentStatus: doc.currentStatus || '',
|
|
2190
|
-
lastUpdated: doc.lastUpdated || '',
|
|
2191
|
-
tags: Array.isArray(doc.tags) ? doc.tags : [],
|
|
2192
|
-
historyCount: Array.isArray(doc.history) ? doc.history.length : 0
|
|
2193
|
-
});
|
|
2194
|
-
}
|
|
2195
|
-
}
|
|
2196
|
-
}
|
|
2223
|
+
for (const p of rawProjects) {
|
|
2224
|
+
const h = dl.db.prepare('SELECT count(*) as count FROM project_status_history WHERE project_id = ?').get(p.id);
|
|
2225
|
+
const historyCount = h ? h.count : 0;
|
|
2226
|
+
const latestStatus = dl.db.prepare('SELECT status_text FROM project_status_history WHERE project_id = ? ORDER BY date DESC LIMIT 1').get(p.id);
|
|
2227
|
+
|
|
2228
|
+
items.push({
|
|
2229
|
+
slug: p.slug,
|
|
2230
|
+
client: p.client,
|
|
2231
|
+
project: p.name,
|
|
2232
|
+
active: p.is_active === 1,
|
|
2233
|
+
currentStatus: latestStatus ? latestStatus.status_text : '',
|
|
2234
|
+
lastUpdated: p.updated_at,
|
|
2235
|
+
historyCount: historyCount,
|
|
2236
|
+
tags: []
|
|
2237
|
+
});
|
|
2197
2238
|
}
|
|
2198
2239
|
items.sort((a, b) => String(b.lastUpdated || '').localeCompare(String(a.lastUpdated || '')));
|
|
2199
2240
|
return safeJson(res, 200, { ok: true, projects: items });
|
|
@@ -2213,68 +2254,33 @@ async function cmdWeb({ port, dir, open, dev }) {
|
|
|
2213
2254
|
};
|
|
2214
2255
|
|
|
2215
2256
|
// 1. Load Projects
|
|
2216
|
-
const
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
while (stack.length) {
|
|
2220
|
-
const dirp = stack.pop();
|
|
2221
|
-
const entries = fs.readdirSync(dirp, { withFileTypes: true });
|
|
2222
|
-
for (const ent of entries) {
|
|
2223
|
-
const full = path.join(dirp, ent.name);
|
|
2224
|
-
if (ent.isDirectory()) stack.push(full);
|
|
2225
|
-
else if (ent.isFile() && ent.name === 'status.json') {
|
|
2226
|
-
const doc = readJsonOrNull(full) || {};
|
|
2227
|
-
const slug = path.relative(base, path.dirname(full)).replace(/\\/g, '/');
|
|
2228
|
-
addNode(slug, slug, 'project');
|
|
2229
|
-
|
|
2230
|
-
// Link tags
|
|
2231
|
-
const tags = Array.isArray(doc.tags) ? doc.tags : [];
|
|
2232
|
-
for (const t of tags) {
|
|
2233
|
-
const tid = 'tag:' + String(t).trim().toLowerCase();
|
|
2234
|
-
addNode(tid, String(t).trim(), 'tag');
|
|
2235
|
-
edges.push({ from: slug, to: tid });
|
|
2236
|
-
}
|
|
2237
|
-
}
|
|
2238
|
-
}
|
|
2239
|
-
}
|
|
2257
|
+
const rawProjects = dl.db.prepare('SELECT slug FROM projects').all();
|
|
2258
|
+
for (const p of rawProjects) {
|
|
2259
|
+
if (p && p.slug) addNode(p.slug, p.slug, 'project');
|
|
2240
2260
|
}
|
|
2241
2261
|
|
|
2242
2262
|
// 2. Load Tasks
|
|
2243
|
-
const
|
|
2244
|
-
const
|
|
2245
|
-
const tasks = Array.isArray(taskDoc.tasks) ? taskDoc.tasks : [];
|
|
2246
|
-
|
|
2247
|
-
for (const t of tasks) {
|
|
2248
|
-
// Only show pending high/medium tasks for clarity, or unassigned ones.
|
|
2249
|
-
if (t.status === 'COMPLETED') continue;
|
|
2250
|
-
|
|
2263
|
+
const rawTasks = dl.db.prepare('SELECT * FROM tasks WHERE status != "COMPLETED"').all();
|
|
2264
|
+
for (const t of rawTasks) {
|
|
2251
2265
|
const tid = 'task:' + t.id;
|
|
2252
|
-
addNode(tid, t.
|
|
2266
|
+
addNode(tid, t.description || 'Tarefa', 'task');
|
|
2253
2267
|
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
edges.push({ from: tid, to: slug });
|
|
2268
|
+
if (t.project_slug && nodeIds.has(t.project_slug)) {
|
|
2269
|
+
edges.push({ from: tid, to: t.project_slug });
|
|
2257
2270
|
} else {
|
|
2258
|
-
// Connect to an "Unassigned" node
|
|
2259
2271
|
addNode('unassigned', 'Sin Asignar', 'unassigned');
|
|
2260
2272
|
edges.push({ from: tid, to: 'unassigned' });
|
|
2261
2273
|
}
|
|
2262
2274
|
}
|
|
2263
2275
|
|
|
2264
2276
|
// 3. Load Blockers
|
|
2265
|
-
const
|
|
2266
|
-
const
|
|
2267
|
-
const blockers = Array.isArray(blockerDoc.blockers) ? blockerDoc.blockers : [];
|
|
2268
|
-
|
|
2269
|
-
for (const b of blockers) {
|
|
2270
|
-
if (String(b.status || '').toUpperCase() !== 'OPEN') continue;
|
|
2271
|
-
|
|
2277
|
+
const openBlockers = dl.db.prepare('SELECT * FROM blockers WHERE status = "OPEN"').all();
|
|
2278
|
+
for (const b of openBlockers) {
|
|
2272
2279
|
const bid = 'blocker:' + b.id;
|
|
2273
2280
|
addNode(bid, 'BLQ: ' + (b.title || 'Blocker'), 'blocker');
|
|
2274
2281
|
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
edges.push({ from: bid, to: slug });
|
|
2282
|
+
if (b.project_slug && nodeIds.has(b.project_slug)) {
|
|
2283
|
+
edges.push({ from: bid, to: b.project_slug });
|
|
2278
2284
|
} else {
|
|
2279
2285
|
addNode('unassigned_blockers', 'Bloqueios Soltos', 'unassigned');
|
|
2280
2286
|
edges.push({ from: bid, to: 'unassigned_blockers' });
|
|
@@ -2832,8 +2838,8 @@ async function cmdWeb({ port, dir, open, dev }) {
|
|
|
2832
2838
|
const schema = {
|
|
2833
2839
|
actions: [
|
|
2834
2840
|
{ type: 'append_daily_log', text: '<string>' },
|
|
2835
|
-
{ type: 'create_task', description: '<string>', priority: 'HIGH|MEDIUM|LOW', category: 'DO_NOW|SCHEDULE|DELEGATE|IGNORE', projectSlug: '<string optional>' },
|
|
2836
|
-
{ type: 'create_blocker', title: '<string>', severity: 'CRITICAL|HIGH|MEDIUM|LOW', notes: '<string>', projectSlug: '<string optional>' },
|
|
2841
|
+
{ type: 'create_task', description: '<string>', priority: 'HIGH|MEDIUM|LOW', category: 'DO_NOW|SCHEDULE|DELEGATE|IGNORE', projectSlug: '<string optional>', streamSlug: '<string optional, ex: nome da frente ou stream>' },
|
|
2842
|
+
{ type: 'create_blocker', title: '<string>', severity: 'CRITICAL|HIGH|MEDIUM|LOW', notes: '<string>', projectSlug: '<string optional>', streamSlug: '<string optional, ex: nome da frente ou stream>' },
|
|
2837
2843
|
{ type: 'suggest_report', name: 'daily|status|sm-weekly|blockers' },
|
|
2838
2844
|
{ type: 'oracle_query', query: '<string>' }
|
|
2839
2845
|
]
|
|
@@ -2991,18 +2997,6 @@ async function cmdWeb({ port, dir, open, dev }) {
|
|
|
2991
2997
|
if (!Array.isArray(actions) || actions.length === 0) {
|
|
2992
2998
|
return safeJson(res, 400, { error: 'Plan has no actions[]' });
|
|
2993
2999
|
}
|
|
2994
|
-
|
|
2995
|
-
const taskFile = path.join(workspaceDir, 'data', 'tasks', 'task-log.json');
|
|
2996
|
-
const blockerFile = path.join(workspaceDir, 'data', 'blockers', 'blocker-log.json');
|
|
2997
|
-
|
|
2998
|
-
const taskLog = readJsonOrNull(taskFile) || { schemaVersion: 1, tasks: [] };
|
|
2999
|
-
if (!Array.isArray(taskLog.tasks)) taskLog.tasks = [];
|
|
3000
|
-
if (typeof taskLog.schemaVersion !== 'number') taskLog.schemaVersion = 1;
|
|
3001
|
-
|
|
3002
|
-
const blockerLog = readJsonOrNull(blockerFile) || { schemaVersion: 1, blockers: [] };
|
|
3003
|
-
if (!Array.isArray(blockerLog.blockers)) blockerLog.blockers = [];
|
|
3004
|
-
if (typeof blockerLog.schemaVersion !== 'number') blockerLog.schemaVersion = 1;
|
|
3005
|
-
|
|
3006
3000
|
function normalizeWhitespace(t) {
|
|
3007
3001
|
return String(t || '').replace(/\s+/g, ' ').trim();
|
|
3008
3002
|
}
|
|
@@ -3015,27 +3009,11 @@ async function cmdWeb({ port, dir, open, dev }) {
|
|
|
3015
3009
|
return crypto.createHash('sha1').update(String(text || ''), 'utf8').digest('hex');
|
|
3016
3010
|
}
|
|
3017
3011
|
|
|
3018
|
-
|
|
3019
|
-
|
|
3020
|
-
const ms = Date.parse(iso);
|
|
3021
|
-
if (!Number.isFinite(ms)) return false;
|
|
3022
|
-
return (Date.now() - ms) <= 24 * 60 * 60 * 1000;
|
|
3023
|
-
} catch {
|
|
3024
|
-
return false;
|
|
3025
|
-
}
|
|
3026
|
-
}
|
|
3027
|
-
|
|
3028
|
-
const existingTaskKeys24h = new Set(
|
|
3029
|
-
taskLog.tasks
|
|
3030
|
-
.filter((t) => t && within24h(t.createdAt))
|
|
3031
|
-
.map((t) => sha1(normalizeTextForKey(t.description)))
|
|
3032
|
-
);
|
|
3012
|
+
const recentTasks = dl.db.prepare("SELECT description FROM tasks WHERE created_at >= datetime('now', '-1 day')").all();
|
|
3013
|
+
const existingTaskKeys24h = new Set(recentTasks.map(t => sha1(normalizeTextForKey(t.description))));
|
|
3033
3014
|
|
|
3034
|
-
const
|
|
3035
|
-
|
|
3036
|
-
.filter((b) => b && within24h(b.createdAt))
|
|
3037
|
-
.map((b) => sha1(normalizeTextForKey(b.title)))
|
|
3038
|
-
);
|
|
3015
|
+
const recentBlockers = dl.db.prepare("SELECT title FROM blockers WHERE created_at >= datetime('now', '-1 day')").all();
|
|
3016
|
+
const existingBlockerKeys24h = new Set(recentBlockers.map(b => sha1(normalizeTextForKey(b.title))));
|
|
3039
3017
|
|
|
3040
3018
|
const now = new Date().toISOString();
|
|
3041
3019
|
const applyMode = String(payload.mode || 'all').trim();
|
|
@@ -3067,71 +3045,72 @@ async function cmdWeb({ port, dir, open, dev }) {
|
|
|
3067
3045
|
|
|
3068
3046
|
const validTaskCats = new Set(['DO_NOW', 'SCHEDULE', 'DELEGATE', 'IGNORE']);
|
|
3069
3047
|
|
|
3070
|
-
|
|
3071
|
-
|
|
3072
|
-
|
|
3048
|
+
const insertTask = dl.db.prepare(`INSERT INTO tasks (id, project_slug, description, category, status, metadata) VALUES (?, ?, ?, ?, ?, ?)`);
|
|
3049
|
+
const insertBlocker = dl.db.prepare(`INSERT INTO blockers (id, project_slug, title, severity, status, owner, next_action, metadata) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`);
|
|
3050
|
+
|
|
3051
|
+
// Execute everything inside a transaction for the apply process
|
|
3052
|
+
const applyTx = dl.db.transaction((actionsToApply) => {
|
|
3053
|
+
for (const a of actionsToApply) {
|
|
3054
|
+
if (!a || typeof a !== 'object') continue;
|
|
3055
|
+
const type = String(a.type || '').trim();
|
|
3056
|
+
|
|
3057
|
+
if (type === 'create_task') {
|
|
3058
|
+
if (applyMode !== 'all' && applyMode !== 'tasks') continue;
|
|
3059
|
+
const description = normalizeWhitespace(a.description);
|
|
3060
|
+
if (!description) continue;
|
|
3061
|
+
const projectSlug = String(a.projectSlug || '').trim() || inferProjectSlug(description, slugMap);
|
|
3062
|
+
const streamSlug = String(a.streamSlug || '').trim();
|
|
3063
|
+
const key = sha1(normalizeTextForKey((projectSlug ? projectSlug + ' ' : '') + description));
|
|
3064
|
+
if (existingTaskKeys24h.has(key)) { applied.tasksSkipped++; continue; }
|
|
3065
|
+
const category = validTaskCats.has(String(a.category || '').trim()) ? String(a.category).trim() : 'DO_NOW';
|
|
3066
|
+
const priority = normPriority(a.priority);
|
|
3067
|
+
|
|
3068
|
+
const id = makeId('t');
|
|
3069
|
+
const metadata = JSON.stringify({ priority, streamSlug });
|
|
3070
|
+
insertTask.run(id, projectSlug || null, description, category, 'PENDING', metadata);
|
|
3071
|
+
|
|
3072
|
+
applied.tasks++;
|
|
3073
|
+
existingTaskKeys24h.add(key); // prevent duplicates within same batch
|
|
3074
|
+
continue;
|
|
3075
|
+
}
|
|
3073
3076
|
|
|
3074
|
-
|
|
3075
|
-
|
|
3076
|
-
|
|
3077
|
-
|
|
3078
|
-
|
|
3079
|
-
|
|
3080
|
-
|
|
3081
|
-
|
|
3082
|
-
|
|
3083
|
-
|
|
3084
|
-
|
|
3085
|
-
|
|
3086
|
-
|
|
3087
|
-
|
|
3088
|
-
|
|
3089
|
-
|
|
3090
|
-
|
|
3091
|
-
|
|
3092
|
-
|
|
3093
|
-
applied.tasks++;
|
|
3094
|
-
continue;
|
|
3095
|
-
}
|
|
3077
|
+
if (type === 'create_blocker') {
|
|
3078
|
+
if (applyMode !== 'all' && applyMode !== 'blockers') continue;
|
|
3079
|
+
const title = normalizeWhitespace(a.title);
|
|
3080
|
+
const notes = normalizeWhitespace(a.notes);
|
|
3081
|
+
if (!title) continue;
|
|
3082
|
+
const projectSlug = String(a.projectSlug || '').trim() || inferProjectSlug(title + ' ' + notes, slugMap);
|
|
3083
|
+
const streamSlug = String(a.streamSlug || '').trim();
|
|
3084
|
+
const key = sha1(normalizeTextForKey((projectSlug ? projectSlug + ' ' : '') + title));
|
|
3085
|
+
if (existingBlockerKeys24h.has(key)) { applied.blockersSkipped++; continue; }
|
|
3086
|
+
const severity = normSeverity(a.severity);
|
|
3087
|
+
|
|
3088
|
+
const id = makeId('b');
|
|
3089
|
+
const metadata = JSON.stringify({ streamSlug, description: notes || title });
|
|
3090
|
+
insertBlocker.run(id, projectSlug || null, title, severity, 'OPEN', null, null, metadata);
|
|
3091
|
+
|
|
3092
|
+
applied.blockers++;
|
|
3093
|
+
existingBlockerKeys24h.add(key); // prevent duplicates within same batch
|
|
3094
|
+
continue;
|
|
3095
|
+
}
|
|
3096
3096
|
|
|
3097
|
-
|
|
3098
|
-
|
|
3099
|
-
|
|
3100
|
-
|
|
3101
|
-
|
|
3102
|
-
if (existingBlockerKeys24h.has(key)) { applied.blockersSkipped++; continue; }
|
|
3103
|
-
const notes = normalizeWhitespace(a.notes);
|
|
3104
|
-
if (!title) continue;
|
|
3105
|
-
const severity = normSeverity(a.severity);
|
|
3106
|
-
const blocker = {
|
|
3107
|
-
id: makeId('b'),
|
|
3108
|
-
title,
|
|
3109
|
-
description: notes || title,
|
|
3110
|
-
createdAt: now,
|
|
3111
|
-
status: 'OPEN',
|
|
3112
|
-
severity,
|
|
3113
|
-
};
|
|
3114
|
-
if (projectSlug) blocker.projectSlug = projectSlug;
|
|
3115
|
-
blockerLog.blockers.push(blocker);
|
|
3116
|
-
applied.blockers++;
|
|
3117
|
-
continue;
|
|
3118
|
-
}
|
|
3097
|
+
if (type === 'suggest_report') {
|
|
3098
|
+
const name = String(a.name || '').trim();
|
|
3099
|
+
if (name) applied.reportsSuggested.push(name);
|
|
3100
|
+
continue;
|
|
3101
|
+
}
|
|
3119
3102
|
|
|
3120
|
-
|
|
3121
|
-
|
|
3122
|
-
|
|
3123
|
-
|
|
3103
|
+
if (type === 'oracle_query') {
|
|
3104
|
+
const query = String(a.query || '').trim();
|
|
3105
|
+
if (query) applied.oracleQueries.push(query);
|
|
3106
|
+
continue;
|
|
3107
|
+
}
|
|
3124
3108
|
}
|
|
3109
|
+
});
|
|
3125
3110
|
|
|
3126
|
-
|
|
3127
|
-
const query = String(a.query || '').trim();
|
|
3128
|
-
if (query) applied.oracleQueries.push(query);
|
|
3129
|
-
continue;
|
|
3130
|
-
}
|
|
3131
|
-
}
|
|
3111
|
+
applyTx(actions);
|
|
3132
3112
|
|
|
3133
3113
|
// Auto-suggest reports when planner didn't include any
|
|
3134
|
-
// (keeps UX consistent: if you created a blocker, at least suggest blockers)
|
|
3135
3114
|
if (!applied.reportsSuggested.length) {
|
|
3136
3115
|
const sug = [];
|
|
3137
3116
|
sug.push('daily');
|
|
@@ -3143,10 +3122,6 @@ async function cmdWeb({ port, dir, open, dev }) {
|
|
|
3143
3122
|
applied.reportsSuggested = Array.from(new Set(applied.reportsSuggested.map((s) => String(s).trim()).filter(Boolean)));
|
|
3144
3123
|
}
|
|
3145
3124
|
|
|
3146
|
-
// Persist
|
|
3147
|
-
writeJson(taskFile, taskLog);
|
|
3148
|
-
writeJson(blockerFile, blockerLog);
|
|
3149
|
-
|
|
3150
3125
|
return safeJson(res, 200, { ok: true, applied });
|
|
3151
3126
|
}
|
|
3152
3127
|
|
|
@@ -3237,28 +3212,51 @@ async function cmdWeb({ port, dir, open, dev }) {
|
|
|
3237
3212
|
const query = String(payload.query || '').trim();
|
|
3238
3213
|
if (!query) return safeJson(res, 400, { error: 'Missing query' });
|
|
3239
3214
|
|
|
3240
|
-
const
|
|
3241
|
-
const
|
|
3242
|
-
const
|
|
3243
|
-
|
|
3244
|
-
|
|
3245
|
-
|
|
3246
|
-
|
|
3247
|
-
|
|
3248
|
-
|
|
3249
|
-
|
|
3250
|
-
|
|
3251
|
-
|
|
3252
|
-
|
|
3253
|
-
|
|
3254
|
-
|
|
3255
|
-
|
|
3256
|
-
|
|
3215
|
+
const workspaceRulesBase = path.join(workspaceDir, '.agent', 'rules', 'freya');
|
|
3216
|
+
const packagedRulesBase = path.join(__dirname, '..', '.agent', 'rules', 'freya');
|
|
3217
|
+
const rulesBase = exists(workspaceRulesBase) ? workspaceRulesBase : packagedRulesBase;
|
|
3218
|
+
|
|
3219
|
+
const files = [
|
|
3220
|
+
path.join(rulesBase, 'freya.mdc'),
|
|
3221
|
+
path.join(rulesBase, 'agents', 'master.mdc'),
|
|
3222
|
+
path.join(rulesBase, 'agents', 'oracle.mdc')
|
|
3223
|
+
].filter(exists);
|
|
3224
|
+
|
|
3225
|
+
const rulesText = files.map((p) => {
|
|
3226
|
+
const rel = path.relative(workspaceDir, p).replace(/\\/g, '/');
|
|
3227
|
+
return `\n\n---\nFILE: ${rel}\n---\n` + fs.readFileSync(p, 'utf8');
|
|
3228
|
+
}).join('');
|
|
3229
|
+
|
|
3230
|
+
// V2 RAG Context
|
|
3231
|
+
const dm = new DataManager(workspaceDir, path.join(workspaceDir, 'logs'));
|
|
3232
|
+
const ragResults = await dm.semanticSearch(query, 12);
|
|
3233
|
+
let ragContext = '';
|
|
3234
|
+
if (ragResults.length > 0) {
|
|
3235
|
+
ragContext = '\n\n[MEMÓRIA DE LONGO PRAZO RECUPERADA (RAG VIA SQLITE)]\n';
|
|
3236
|
+
for (const r of ragResults) {
|
|
3237
|
+
ragContext += `\n---\nFONTE: ${r.reference_type} -> ID: ${r.reference_id} (Score: ${r.score.toFixed(3)})\nCONTEÚDO:\n${r.text_chunk}\n`;
|
|
3238
|
+
}
|
|
3257
3239
|
}
|
|
3258
3240
|
|
|
3259
|
-
const
|
|
3260
|
-
|
|
3261
|
-
|
|
3241
|
+
const prompt = `Você é o agente Oracle do sistema F.R.E.Y.A.\n\nSiga estritamente os arquivos de regras abaixo.\nResponda de forma analítica e consultiva.\n${ragContext}\n\nREGRAS:${rulesText}\n\nCONSULTA DO USUÁRIO:\n${query}\n`;
|
|
3242
|
+
|
|
3243
|
+
const cmd = process.env.COPILOT_CMD || 'copilot';
|
|
3244
|
+
|
|
3245
|
+
try {
|
|
3246
|
+
// Removed --allow-all-tools and --add-dir to force reliance on RAG context
|
|
3247
|
+
const r = await run(cmd, ['-s', '--no-color', '--stream', 'off', '-p', prompt], workspaceDir);
|
|
3248
|
+
const out = (r.stdout + r.stderr).trim();
|
|
3249
|
+
if (r.code !== 0) {
|
|
3250
|
+
return safeJson(res, 200, { ok: false, answer: 'Falha na busca do agente Oracle:\n' + (out || 'Exit code != 0'), sessionId });
|
|
3251
|
+
}
|
|
3252
|
+
return safeJson(res, 200, { ok: true, answer: out, sessionId });
|
|
3253
|
+
} catch (e) {
|
|
3254
|
+
return safeJson(res, 200, {
|
|
3255
|
+
ok: false,
|
|
3256
|
+
answer: `Agente indisponível (cmd: ${cmd}).\nDetalhes:\n` + (e.message || String(e)),
|
|
3257
|
+
sessionId
|
|
3258
|
+
});
|
|
3259
|
+
}
|
|
3262
3260
|
}
|
|
3263
3261
|
|
|
3264
3262
|
// Chat persistence (per session)
|
|
@@ -3361,52 +3359,48 @@ async function cmdWeb({ port, dir, open, dev }) {
|
|
|
3361
3359
|
const cat = payload.category ? String(payload.category).trim() : null;
|
|
3362
3360
|
const status = payload.status ? String(payload.status).trim() : null;
|
|
3363
3361
|
|
|
3364
|
-
|
|
3365
|
-
const
|
|
3366
|
-
|
|
3367
|
-
|
|
3368
|
-
|
|
3369
|
-
|
|
3370
|
-
|
|
3371
|
-
|
|
3372
|
-
|
|
3373
|
-
|
|
3374
|
-
|
|
3375
|
-
|
|
3376
|
-
|
|
3377
|
-
|
|
3378
|
-
|
|
3379
|
-
|
|
3380
|
-
|
|
3381
|
-
|
|
3382
|
-
|
|
3383
|
-
|
|
3384
|
-
|
|
3385
|
-
|
|
3362
|
+
let query = 'SELECT * FROM tasks WHERE 1=1';
|
|
3363
|
+
const params = [];
|
|
3364
|
+
|
|
3365
|
+
if (cat) { query += ' AND category = ?'; params.push(cat); }
|
|
3366
|
+
if (status) { query += ' AND status = ?'; params.push(status); }
|
|
3367
|
+
|
|
3368
|
+
query += ` ORDER BY
|
|
3369
|
+
CASE JSON_EXTRACT(metadata, '$.priority')
|
|
3370
|
+
WHEN 'high' THEN 0 WHEN 'medium' THEN 1 WHEN 'low' THEN 2 ELSE 3
|
|
3371
|
+
END ASC,
|
|
3372
|
+
created_at DESC
|
|
3373
|
+
LIMIT ?`;
|
|
3374
|
+
params.push(limit);
|
|
3375
|
+
|
|
3376
|
+
const rawTasks = dl.db.prepare(query).all(...params);
|
|
3377
|
+
const tasks = rawTasks.map(t => {
|
|
3378
|
+
const meta = t.metadata ? JSON.parse(t.metadata) : {};
|
|
3379
|
+
return {
|
|
3380
|
+
id: t.id,
|
|
3381
|
+
description: t.description,
|
|
3382
|
+
category: t.category,
|
|
3383
|
+
status: t.status,
|
|
3384
|
+
createdAt: t.created_at,
|
|
3385
|
+
completedAt: t.completed_at,
|
|
3386
|
+
projectSlug: t.project_slug,
|
|
3387
|
+
priority: meta.priority,
|
|
3388
|
+
streamSlug: meta.streamSlug
|
|
3389
|
+
};
|
|
3390
|
+
});
|
|
3391
|
+
|
|
3392
|
+
return safeJson(res, 200, { ok: true, tasks });
|
|
3386
3393
|
}
|
|
3387
3394
|
|
|
3388
3395
|
if (req.url === '/api/tasks/complete') {
|
|
3389
3396
|
const id = String(payload.id || '').trim();
|
|
3390
3397
|
if (!id) return safeJson(res, 400, { error: 'Missing id' });
|
|
3391
3398
|
|
|
3392
|
-
const
|
|
3393
|
-
const
|
|
3394
|
-
const tasks = Array.isArray(doc.tasks) ? doc.tasks : [];
|
|
3395
|
-
|
|
3396
|
-
const now = isoNow();
|
|
3397
|
-
let updated = null;
|
|
3398
|
-
for (const t of tasks) {
|
|
3399
|
-
if (t && t.id === id) {
|
|
3400
|
-
t.status = 'COMPLETED';
|
|
3401
|
-
t.completedAt = now;
|
|
3402
|
-
updated = t;
|
|
3403
|
-
break;
|
|
3404
|
-
}
|
|
3405
|
-
}
|
|
3399
|
+
const now = new Date().toISOString();
|
|
3400
|
+
const info = dl.db.prepare(`UPDATE tasks SET status = 'COMPLETED', completed_at = ? WHERE id = ?`).run(now, id);
|
|
3406
3401
|
|
|
3407
|
-
if (
|
|
3408
|
-
|
|
3409
|
-
return safeJson(res, 200, { ok: true, task: updated });
|
|
3402
|
+
if (info.changes === 0) return safeJson(res, 404, { error: 'Task not found' });
|
|
3403
|
+
return safeJson(res, 200, { ok: true, task: { id, status: 'COMPLETED', completedAt: now } });
|
|
3410
3404
|
}
|
|
3411
3405
|
|
|
3412
3406
|
|
|
@@ -3415,22 +3409,26 @@ async function cmdWeb({ port, dir, open, dev }) {
|
|
|
3415
3409
|
if (!id) return safeJson(res, 400, { error: 'Missing id' });
|
|
3416
3410
|
const patch = payload.patch && typeof payload.patch === 'object' ? payload.patch : {};
|
|
3417
3411
|
|
|
3418
|
-
|
|
3419
|
-
|
|
3420
|
-
const tasks = Array.isArray(doc.tasks) ? doc.tasks : [];
|
|
3412
|
+
let queryUpdates = [];
|
|
3413
|
+
let params = [];
|
|
3421
3414
|
|
|
3422
|
-
|
|
3423
|
-
|
|
3424
|
-
|
|
3425
|
-
if (typeof patch.projectSlug === 'string') t.projectSlug = patch.projectSlug.trim() || undefined;
|
|
3426
|
-
if (typeof patch.category === 'string') t.category = patch.category.trim();
|
|
3427
|
-
updated = t;
|
|
3428
|
-
break;
|
|
3429
|
-
}
|
|
3415
|
+
if (typeof patch.projectSlug === 'string') {
|
|
3416
|
+
queryUpdates.push('project_slug = ?');
|
|
3417
|
+
params.push(patch.projectSlug.trim() || null);
|
|
3430
3418
|
}
|
|
3431
|
-
if (
|
|
3432
|
-
|
|
3433
|
-
|
|
3419
|
+
if (typeof patch.category === 'string') {
|
|
3420
|
+
queryUpdates.push('category = ?');
|
|
3421
|
+
params.push(patch.category.trim());
|
|
3422
|
+
}
|
|
3423
|
+
|
|
3424
|
+
if (queryUpdates.length === 0) return safeJson(res, 200, { ok: true, task: { id } });
|
|
3425
|
+
|
|
3426
|
+
params.push(id);
|
|
3427
|
+
const updateQuery = `UPDATE tasks SET ${queryUpdates.join(', ')} WHERE id = ?`;
|
|
3428
|
+
const info = dl.db.prepare(updateQuery).run(...params);
|
|
3429
|
+
|
|
3430
|
+
if (info.changes === 0) return safeJson(res, 404, { error: 'Task not found' });
|
|
3431
|
+
return safeJson(res, 200, { ok: true, task: { id, ...patch } });
|
|
3434
3432
|
}
|
|
3435
3433
|
|
|
3436
3434
|
if (req.url === '/api/health/checklist') {
|
|
@@ -3442,13 +3440,11 @@ async function cmdWeb({ port, dir, open, dev }) {
|
|
|
3442
3440
|
return safeJson(res, 200, { ok: false, needsInit: true, error: 'Workspace not initialized', items: [] });
|
|
3443
3441
|
}
|
|
3444
3442
|
|
|
3445
|
-
const
|
|
3446
|
-
const
|
|
3447
|
-
const tasksDoc = readJsonOrNull(tasksFile) || { schemaVersion: 1, tasks: [] };
|
|
3448
|
-
const blockersDoc = readJsonOrNull(blockersFile) || { schemaVersion: 1, blockers: [] };
|
|
3443
|
+
const pendingTasksRes = dl.db.prepare('SELECT count(*) as count FROM tasks WHERE status = "PENDING" AND category = "DO_NOW"').get();
|
|
3444
|
+
const openBlockersRes = dl.db.prepare('SELECT count(*) as count FROM blockers WHERE status = "OPEN"').get();
|
|
3449
3445
|
|
|
3450
|
-
const pendingTasks =
|
|
3451
|
-
const openBlockers =
|
|
3446
|
+
const pendingTasks = pendingTasksRes ? pendingTasksRes.count : 0;
|
|
3447
|
+
const openBlockers = openBlockersRes ? openBlockersRes.count : 0;
|
|
3452
3448
|
|
|
3453
3449
|
const today = isoDate();
|
|
3454
3450
|
const reports = listReports(workspaceDir);
|
|
@@ -3457,7 +3453,7 @@ async function cmdWeb({ port, dir, open, dev }) {
|
|
|
3457
3453
|
const items = [
|
|
3458
3454
|
{ label: 'Blockers abertos', status: openBlockers > 0 ? 'warn' : 'ok', detail: `${openBlockers} aberto(s)` },
|
|
3459
3455
|
{ label: 'Tarefas DO_NOW pendentes', status: pendingTasks > 0 ? 'warn' : 'ok', detail: `${pendingTasks} pendente(s)` },
|
|
3460
|
-
{ label: '
|
|
3456
|
+
{ label: 'Relatórios de hoje', status: reportsToday > 0 ? 'ok' : 'warn', detail: `${reportsToday} gerado(s)` }
|
|
3461
3457
|
];
|
|
3462
3458
|
|
|
3463
3459
|
return safeJson(res, 200, { ok: true, items, stats: { pendingTasks, openBlockers, reportsToday, reportsTotal: reports.length } });
|
|
@@ -3507,11 +3503,18 @@ async function cmdWeb({ port, dir, open, dev }) {
|
|
|
3507
3503
|
}
|
|
3508
3504
|
|
|
3509
3505
|
if (req.url === '/api/blockers/summary') {
|
|
3510
|
-
const
|
|
3511
|
-
|
|
3512
|
-
|
|
3506
|
+
const open = dl.db.prepare(`
|
|
3507
|
+
SELECT * FROM blockers
|
|
3508
|
+
WHERE status IN ('OPEN', 'MITIGATING')
|
|
3509
|
+
`).all().map(b => ({
|
|
3510
|
+
id: b.id,
|
|
3511
|
+
title: b.title,
|
|
3512
|
+
severity: b.severity,
|
|
3513
|
+
status: b.status,
|
|
3514
|
+
projectSlug: b.project_slug,
|
|
3515
|
+
createdAt: b.created_at
|
|
3516
|
+
}));
|
|
3513
3517
|
|
|
3514
|
-
const open = blockers.filter((b) => b && String(b.status || '').trim() === 'OPEN');
|
|
3515
3518
|
const sevCount = { CRITICAL: 0, HIGH: 0, MEDIUM: 0, LOW: 0, OTHER: 0 };
|
|
3516
3519
|
const now = Date.now();
|
|
3517
3520
|
let older7 = 0;
|
|
@@ -3530,7 +3533,7 @@ async function cmdWeb({ port, dir, open, dev }) {
|
|
|
3530
3533
|
.slice(0, 5)
|
|
3531
3534
|
.map((b) => ({
|
|
3532
3535
|
id: b.id,
|
|
3533
|
-
title: b.title ||
|
|
3536
|
+
title: b.title || 'Sem titulo',
|
|
3534
3537
|
severity: b.severity || 'UNKNOWN',
|
|
3535
3538
|
projectSlug: b.projectSlug || null
|
|
3536
3539
|
}));
|
|
@@ -3549,34 +3552,40 @@ async function cmdWeb({ port, dir, open, dev }) {
|
|
|
3549
3552
|
const limit = Math.max(1, Math.min(50, Number(payload.limit || 10)));
|
|
3550
3553
|
const status = payload.status ? String(payload.status).trim() : 'OPEN';
|
|
3551
3554
|
|
|
3552
|
-
|
|
3553
|
-
const
|
|
3554
|
-
|
|
3555
|
-
|
|
3556
|
-
|
|
3557
|
-
|
|
3558
|
-
|
|
3559
|
-
if (v === 'HIGH') return 1;
|
|
3560
|
-
if (v === 'MEDIUM') return 2;
|
|
3561
|
-
if (v === 'LOW') return 3;
|
|
3562
|
-
return 9;
|
|
3563
|
-
};
|
|
3555
|
+
let query = 'SELECT * FROM blockers WHERE 1=1';
|
|
3556
|
+
const params = [];
|
|
3557
|
+
|
|
3558
|
+
if (status) {
|
|
3559
|
+
query += ' AND status = ?';
|
|
3560
|
+
params.push(status);
|
|
3561
|
+
}
|
|
3564
3562
|
|
|
3565
|
-
|
|
3566
|
-
|
|
3567
|
-
|
|
3568
|
-
|
|
3569
|
-
|
|
3570
|
-
|
|
3571
|
-
|
|
3572
|
-
|
|
3573
|
-
|
|
3574
|
-
|
|
3575
|
-
|
|
3576
|
-
|
|
3577
|
-
|
|
3578
|
-
|
|
3579
|
-
|
|
3563
|
+
query += ` ORDER BY
|
|
3564
|
+
CASE severity
|
|
3565
|
+
WHEN 'CRITICAL' THEN 0 WHEN 'HIGH' THEN 1 WHEN 'MEDIUM' THEN 2 WHEN 'LOW' THEN 3 ELSE 9
|
|
3566
|
+
END ASC,
|
|
3567
|
+
created_at ASC
|
|
3568
|
+
LIMIT ?`;
|
|
3569
|
+
params.push(limit);
|
|
3570
|
+
|
|
3571
|
+
const rawBlockers = dl.db.prepare(query).all(...params);
|
|
3572
|
+
const blockers = rawBlockers.map(b => {
|
|
3573
|
+
const meta = b.metadata ? JSON.parse(b.metadata) : {};
|
|
3574
|
+
return {
|
|
3575
|
+
id: b.id,
|
|
3576
|
+
title: b.title,
|
|
3577
|
+
severity: b.severity,
|
|
3578
|
+
status: b.status,
|
|
3579
|
+
createdAt: b.created_at,
|
|
3580
|
+
resolvedAt: b.resolved_at,
|
|
3581
|
+
projectSlug: b.project_slug,
|
|
3582
|
+
owner: b.owner,
|
|
3583
|
+
nextAction: b.next_action,
|
|
3584
|
+
streamSlug: meta.streamSlug
|
|
3585
|
+
};
|
|
3586
|
+
});
|
|
3587
|
+
|
|
3588
|
+
return safeJson(res, 200, { ok: true, blockers });
|
|
3580
3589
|
}
|
|
3581
3590
|
|
|
3582
3591
|
if (req.url === '/api/blockers/update') {
|
|
@@ -3584,21 +3593,22 @@ async function cmdWeb({ port, dir, open, dev }) {
|
|
|
3584
3593
|
if (!id) return safeJson(res, 400, { error: 'Missing id' });
|
|
3585
3594
|
const patch = payload.patch && typeof payload.patch === 'object' ? payload.patch : {};
|
|
3586
3595
|
|
|
3587
|
-
|
|
3588
|
-
|
|
3589
|
-
const blockers = Array.isArray(doc.blockers) ? doc.blockers : [];
|
|
3596
|
+
let queryUpdates = [];
|
|
3597
|
+
let params = [];
|
|
3590
3598
|
|
|
3591
|
-
|
|
3592
|
-
|
|
3593
|
-
|
|
3594
|
-
if (typeof patch.projectSlug === 'string') b.projectSlug = patch.projectSlug.trim() || undefined;
|
|
3595
|
-
updated = b;
|
|
3596
|
-
break;
|
|
3597
|
-
}
|
|
3599
|
+
if (typeof patch.projectSlug === 'string') {
|
|
3600
|
+
queryUpdates.push('project_slug = ?');
|
|
3601
|
+
params.push(patch.projectSlug.trim() || null);
|
|
3598
3602
|
}
|
|
3599
|
-
|
|
3600
|
-
|
|
3601
|
-
|
|
3603
|
+
|
|
3604
|
+
if (queryUpdates.length === 0) return safeJson(res, 200, { ok: true, blocker: { id } });
|
|
3605
|
+
|
|
3606
|
+
params.push(id);
|
|
3607
|
+
const updateQuery = `UPDATE blockers SET ${queryUpdates.join(', ')} WHERE id = ?`;
|
|
3608
|
+
const info = dl.db.prepare(updateQuery).run(...params);
|
|
3609
|
+
|
|
3610
|
+
if (info.changes === 0) return safeJson(res, 404, { error: 'Blocker not found' });
|
|
3611
|
+
return safeJson(res, 200, { ok: true, blocker: { id, ...patch } });
|
|
3602
3612
|
}
|
|
3603
3613
|
|
|
3604
3614
|
if (req.url === '/api/report') {
|
|
@@ -3689,4 +3699,148 @@ async function cmdWeb({ port, dir, open, dev }) {
|
|
|
3689
3699
|
if (open) openBrowser(url);
|
|
3690
3700
|
}
|
|
3691
3701
|
|
|
3702
|
+
function buildSettingsHtml(safeDefault, appVersion) {
|
|
3703
|
+
const safeVersion = escapeHtml(appVersion || 'unknown');
|
|
3704
|
+
return `<!doctype html>
|
|
3705
|
+
<html>
|
|
3706
|
+
<head>
|
|
3707
|
+
<meta charset="utf-8" />
|
|
3708
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
3709
|
+
<title>FREYA Settings</title>
|
|
3710
|
+
<link rel="stylesheet" href="/app.css" />
|
|
3711
|
+
</head>
|
|
3712
|
+
<body data-page="settings">
|
|
3713
|
+
<div class="app">
|
|
3714
|
+
<div class="frame">
|
|
3715
|
+
<div class="shell">
|
|
3716
|
+
|
|
3717
|
+
<aside class="rail">
|
|
3718
|
+
<div class="railTop">
|
|
3719
|
+
<div class="railLogo">F</div>
|
|
3720
|
+
</div>
|
|
3721
|
+
<div class="railNav">
|
|
3722
|
+
<button class="railBtn" id="railDashboard" type="button" title="Dashboard">
|
|
3723
|
+
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="7" height="9"></rect><rect x="14" y="3" width="7" height="5"></rect><rect x="14" y="12" width="7" height="9"></rect><rect x="3" y="16" width="7" height="5"></rect></svg>
|
|
3724
|
+
</button>
|
|
3725
|
+
<button class="railBtn" id="railReports" type="button" title="Relatórios">
|
|
3726
|
+
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path><polyline points="14 2 14 8 20 8"></polyline><line x1="16" y1="13" x2="8" y2="13"></line><line x1="16" y1="17" x2="8" y2="17"></line><polyline points="10 9 9 9 8 9"></polyline></svg>
|
|
3727
|
+
</button>
|
|
3728
|
+
<button class="railBtn" id="railCompanion" type="button" title="Companion">
|
|
3729
|
+
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle><polygon points="16.24 7.76 14.12 14.12 7.76 16.24 9.88 9.88 16.24 7.76"></polygon></svg>
|
|
3730
|
+
</button>
|
|
3731
|
+
<button class="railBtn" id="railProjects" type="button" title="Projects">
|
|
3732
|
+
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"></path></svg>
|
|
3733
|
+
</button>
|
|
3734
|
+
<button class="railBtn" id="railTimeline" type="button" title="Timeline">
|
|
3735
|
+
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle><polyline points="12 6 12 12 16 14"></polyline></svg>
|
|
3736
|
+
</button>
|
|
3737
|
+
<button class="railBtn" id="railGraph" type="button" title="Grafo">
|
|
3738
|
+
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="18" cy="5" r="3"></circle><circle cx="6" cy="12" r="3"></circle><circle cx="18" cy="19" r="3"></circle><line x1="8.59" y1="13.51" x2="15.42" y2="17.49"></line><line x1="15.41" y1="6.51" x2="8.59" y2="10.49"></line></svg>
|
|
3739
|
+
</button>
|
|
3740
|
+
</div>
|
|
3741
|
+
<div class="railBottom">\n <button id="railSettings" class="railBtn" title="Configura��es">\n <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"></circle><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"></path></svg>\n </button>
|
|
3742
|
+
<button id="railSettings" class="railBtn active" title="Configurações">
|
|
3743
|
+
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"></circle><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"></path></svg>
|
|
3744
|
+
</button>
|
|
3745
|
+
|
|
3746
|
+
</div>
|
|
3747
|
+
</aside>
|
|
3748
|
+
|
|
3749
|
+
<main class="center" id="settingsPage">
|
|
3750
|
+
<div class="topbar">
|
|
3751
|
+
<div class="brandLine">
|
|
3752
|
+
<span class="spark"></span>
|
|
3753
|
+
<div class="brandStack">
|
|
3754
|
+
<div class="brand">FREYA</div>
|
|
3755
|
+
<div class="brandSub">Configurações Avançadas</div>
|
|
3756
|
+
</div>
|
|
3757
|
+
</div>
|
|
3758
|
+
<div class="topActions">
|
|
3759
|
+
<span class="chip" id="chipVersion">v${safeVersion}</span>
|
|
3760
|
+
<button class="btn icon" type="button" onclick="window.toggleTheme()" title="Alternar tema claro/escuro">
|
|
3761
|
+
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="5"></circle><line x1="12" y1="1" x2="12" y2="3"></line><line x1="12" y1="21" x2="12" y2="23"></line><line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line><line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line><line x1="1" y1="12" x2="3" y2="12"></line><line x1="21" y1="12" x2="23" y2="12"></line><line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line><line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line></svg>
|
|
3762
|
+
</button>
|
|
3763
|
+
</div>
|
|
3764
|
+
</div>
|
|
3765
|
+
|
|
3766
|
+
<div class="centerBody">
|
|
3767
|
+
<section class="utilityGrid" style="margin-bottom: 24px;">
|
|
3768
|
+
<div class="utilityCard">
|
|
3769
|
+
<div class="utilityHead">Área de trabalho local</div>
|
|
3770
|
+
<div class="sidePath" id="sidePath">${safeDefault}</div>
|
|
3771
|
+
<div class="row" style="grid-template-columns: 1fr auto">
|
|
3772
|
+
<input id="dir" placeholder="./freya" value="${safeDefault}" />
|
|
3773
|
+
<button class="btn small" type="button" onclick="window.pickDir()">Procurar</button>
|
|
3774
|
+
</div>
|
|
3775
|
+
<div class="stack" style="margin-top:10px">
|
|
3776
|
+
<button class="btn" type="button" onclick="window.doUpdate()">Sincronizar workspace</button>
|
|
3777
|
+
<button class="btn" type="button" onclick="window.doMigrate()">Migrar dados base</button>
|
|
3778
|
+
</div>
|
|
3779
|
+
<div style="height:10px"></div>
|
|
3780
|
+
<div class="help"><b>Sincronizar workspace</b>: Atualiza scripts/templates da suite FREYA sem afetar <code>data</code>.</div>
|
|
3781
|
+
<div class="help"><b>Migrar dados</b>: Se alguma versão quebrar compatibilidade, ele recria o log.</div>
|
|
3782
|
+
</div>
|
|
3783
|
+
|
|
3784
|
+
<div class="utilityCard">
|
|
3785
|
+
<div class="utilityHead">Interface & Sistema</div>
|
|
3786
|
+
<div style="display:flex; flex-direction:column; gap:8px;">
|
|
3787
|
+
<label style="display:flex; align-items:center; gap:10px; user-select:none; margin: 6px 0 12px 0">
|
|
3788
|
+
<input id="prettyPublish" type="checkbox" checked style="width:auto" onchange="window.togglePrettyPublish()" />
|
|
3789
|
+
Publicação bonita (cards/embeds em RTF)
|
|
3790
|
+
</label>
|
|
3791
|
+
<div class="stack" style="margin-top:10px">
|
|
3792
|
+
<button class="btn" type="button" onclick="window.rebuildIndex()">Reconstruir Índice de Busca (Copilot)</button>
|
|
3793
|
+
<button class="btn" type="button" onclick="window.exportObsidian()">Exportar Notas (Obsidian Markdown)</button>
|
|
3794
|
+
</div>
|
|
3795
|
+
<div class="help">Usa Copilot/RagIndex para buscas conversacionais.</div>
|
|
3796
|
+
</div>
|
|
3797
|
+
</div>
|
|
3798
|
+
</section>
|
|
3799
|
+
|
|
3800
|
+
<div class="devGrid">
|
|
3801
|
+
<div class="panel">
|
|
3802
|
+
<div class="panelHead"><b>Configuração de Notificações</b></div>
|
|
3803
|
+
<div class="panelBody">
|
|
3804
|
+
<label>Discord webhook URL</label>
|
|
3805
|
+
<input id="discord" placeholder="https://discord.com/api/webhooks/..." />
|
|
3806
|
+
<div style="height:10px"></div>
|
|
3807
|
+
|
|
3808
|
+
<label>Teams webhook URL</label>
|
|
3809
|
+
<input id="teams" placeholder="https://..." />
|
|
3810
|
+
|
|
3811
|
+
<div class="stack" style="margin-top:20px;">
|
|
3812
|
+
<button class="btn primary" type="button" onclick="window.saveSettings()">Salvar Configurações</button>
|
|
3813
|
+
</div>
|
|
3814
|
+
</div>
|
|
3815
|
+
</div>
|
|
3816
|
+
|
|
3817
|
+
<div class="panel">
|
|
3818
|
+
<div class="panelHead"><b>Mapeamento Smart de Projetos</b></div>
|
|
3819
|
+
<div class="panelBody">
|
|
3820
|
+
<label>Regras de inferência (JSON)</label>
|
|
3821
|
+
<textarea id="slugRules" rows="8" placeholder="{ \"rules\": [ { \"contains\": \"fideliza\", \"slug\": \"vivo/fidelizacao\" } ] }"></textarea>
|
|
3822
|
+
<div class="help">Usado pra inferir <code>projectSlug</code>.</div>
|
|
3823
|
+
<div class="stack" style="margin-top:10px">
|
|
3824
|
+
<button class="btn" type="button" onclick="window.reloadSlugRules()">Recarregar regras</button>
|
|
3825
|
+
<button class="btn primary" type="button" onclick="window.saveSlugRules()">Salvar regras</button>
|
|
3826
|
+
</div>
|
|
3827
|
+
</div>
|
|
3828
|
+
</div>
|
|
3829
|
+
</div>
|
|
3830
|
+
|
|
3831
|
+
</div>
|
|
3832
|
+
</main>
|
|
3833
|
+
</div>
|
|
3834
|
+
</div>
|
|
3835
|
+
</div>
|
|
3836
|
+
|
|
3837
|
+
<script>
|
|
3838
|
+
window.__FREYA_DEFAULT_DIR = "${safeDefault}";
|
|
3839
|
+
</script>
|
|
3840
|
+
<script src="/app.js"></script>
|
|
3841
|
+
</body>
|
|
3842
|
+
</html>`;
|
|
3843
|
+
}
|
|
3844
|
+
|
|
3692
3845
|
module.exports = { cmdWeb };
|
|
3846
|
+
|