@cccarv82/freya 2.14.1 → 2.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/cli/web.js CHANGED
@@ -624,7 +624,7 @@ function safeJson(res, code, obj) {
624
624
  function looksEmptyWorkspace(dir) {
625
625
  try {
626
626
  if (!exists(dir)) return true;
627
- const entries = fs.readdirSync(dir).filter((n) => !['.debuglogs', '.DS_Store'].includes(n));
627
+ const entries = fs.readdirSync(dir).filter((n) => !['.debuglogs', '.DS_Store', 'data', 'logs'].includes(n));
628
628
  return entries.length === 0;
629
629
  } catch {
630
630
  return true;
@@ -983,6 +983,11 @@ function docsHtml(defaultDir) {
983
983
  return buildDocsHtml(safeDefault, APP_VERSION);
984
984
  }
985
985
 
986
+ function kanbanHtml(defaultDir) {
987
+ const safeDefault = String(defaultDir || './freya').replace(/\\/g, '\\\\').replace(/"/g, '\\"');
988
+ return buildKanbanHtml(safeDefault, APP_VERSION);
989
+ }
990
+
986
991
  function buildDocsHtml(safeDefault, appVersion) {
987
992
  const safeVersion = escapeHtml(appVersion || 'unknown');
988
993
  return `<!doctype html>
@@ -1035,6 +1040,9 @@ function buildDocsHtml(safeDefault, appVersion) {
1035
1040
  <button class="railBtn" id="railCompanion" type="button" title="Companion">
1036
1041
  <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>
1037
1042
  </button>
1043
+ <button class="railBtn" id="railKanban" type="button" title="Kanban">
1044
+ <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="5" height="18" rx="1"></rect><rect x="10" y="3" width="5" height="12" rx="1"></rect><rect x="17" y="3" width="5" height="15" rx="1"></rect></svg>
1045
+ </button>
1038
1046
  <button class="railBtn" id="railProjects" type="button" title="Projects">
1039
1047
  <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>
1040
1048
  </button>
@@ -1174,6 +1182,9 @@ function buildHtml(safeDefault, appVersion) {
1174
1182
  <button class="railBtn" id="railCompanion" type="button" title="Companion">
1175
1183
  <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>
1176
1184
  </button>
1185
+ <button class="railBtn" id="railKanban" type="button" title="Kanban">
1186
+ <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="5" height="18" rx="1"></rect><rect x="10" y="3" width="5" height="12" rx="1"></rect><rect x="17" y="3" width="5" height="15" rx="1"></rect></svg>
1187
+ </button>
1177
1188
  <button class="railBtn" id="railProjects" type="button" title="Projects">
1178
1189
  <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>
1179
1190
  </button>
@@ -1209,7 +1220,7 @@ function buildHtml(safeDefault, appVersion) {
1209
1220
  <div class="centerBody">
1210
1221
  <!-- Unified Input Panel -->
1211
1222
  <div class="promptShell">
1212
- <div class="promptBar" style="border-radius: 16px; display: flex; flex-direction: column; overflow: hidden;">
1223
+ <div class="promptBar" style="border-radius: 16px; display: flex; flex-direction: column; overflow: hidden; flex: 1;">
1213
1224
  <!-- Header -->
1214
1225
  <div class="promptMeta" style="flex-shrink:0;">
1215
1226
  <div class="promptTitle" style="display: flex; align-items: center; gap: 8px;">
@@ -1223,7 +1234,7 @@ function buildHtml(safeDefault, appVersion) {
1223
1234
  </div>
1224
1235
 
1225
1236
  <!-- Textarea -->
1226
- <textarea id="inboxText" aria-label="Entrada de texto para processar ou perguntar" placeholder="Cole updates, decisões, blockers... ou faça uma pergunta à Freya.&#10;&#10;▸ Salvar & Processar → extrai tarefas e blockers do texto&#10;▸ Perguntar → consulta o histórico via busca semântica (RAG)" style="resize:none; min-height: 160px; border-radius: 0; border-left: none; border-right: none; border-top: none; border-bottom: 1px solid var(--border); padding: 14px 16px; font-size: 13px; line-height: 1.6;"
1237
+ <textarea id="inboxText" aria-label="Entrada de texto para processar ou perguntar" placeholder="Cole updates, decisões, blockers... ou faça uma pergunta à Freya.&#10;&#10;▸ Salvar & Processar → extrai tarefas e blockers do texto&#10;▸ Perguntar → consulta o histórico via busca semântica (RAG)" style="resize:none; min-height: 200px; flex: 1; border-radius: 0; border-left: none; border-right: none; border-top: none; border-bottom: 1px solid var(--border); padding: 14px 16px; font-size: 13px; line-height: 1.6;"
1227
1238
  onkeydown="if((event.metaKey||event.ctrlKey)&&event.key==='Enter'){event.preventDefault();window.saveAndPlan();}"></textarea>
1228
1239
 
1229
1240
  <!-- Actions bar -->
@@ -1257,7 +1268,7 @@ function buildHtml(safeDefault, appVersion) {
1257
1268
  </div>
1258
1269
 
1259
1270
  <!-- Chat thread: responses appear here after actions -->
1260
- <div id="chatThread" style="max-height: 280px; overflow-y:auto; overflow-x:hidden; padding:0 12px; display:flex; flex-direction:column; gap:8px; border-top: 1px solid var(--border);"></div>
1271
+ <div id="chatThread" style="flex: 0 1 auto; min-height: 0; max-height: 40vh; overflow-y:auto; overflow-x:hidden; padding:0 12px; display:none; flex-direction:column; gap:8px; border-top: 1px solid var(--border);"></div>
1261
1272
  </div>
1262
1273
  </div>
1263
1274
 
@@ -1317,6 +1328,39 @@ function buildHtml(safeDefault, appVersion) {
1317
1328
  </div>
1318
1329
  </div>
1319
1330
 
1331
+ <!-- Quick-add modal (Ctrl+K) -->
1332
+ <div id="quickAddOverlay" class="qa-overlay" style="display:none;">
1333
+ <div class="qa-modal">
1334
+ <div class="qa-header">
1335
+ <span style="font-weight:700; font-size:14px;">Nova Task</span>
1336
+ <button class="btn small" type="button" onclick="window.closeQuickAdd()" style="padding:2px 8px;">&times;</button>
1337
+ </div>
1338
+ <textarea id="qaDesc" class="qa-input" placeholder="Descri\u00e7\u00e3o da task..." rows="3"></textarea>
1339
+ <div class="qa-row">
1340
+ <select id="qaCat" class="qa-select">
1341
+ <option value="DO_NOW">DO_NOW</option>
1342
+ <option value="SCHEDULE">SCHEDULE</option>
1343
+ <option value="DELEGATE">DELEGATE</option>
1344
+ </select>
1345
+ <select id="qaPriority" class="qa-select">
1346
+ <option value="">Prioridade</option>
1347
+ <option value="critical">Cr\u00edtica</option>
1348
+ <option value="high">Alta</option>
1349
+ <option value="medium">M\u00e9dia</option>
1350
+ <option value="low">Baixa</option>
1351
+ </select>
1352
+ </div>
1353
+ <div class="qa-row">
1354
+ <input id="qaSlug" class="qa-input" placeholder="Projeto (slug)" style="flex:1;" />
1355
+ <input id="qaDue" type="date" class="qa-input" style="width:160px;" />
1356
+ </div>
1357
+ <div class="qa-row" style="justify-content:flex-end;">
1358
+ <button class="btn small" type="button" onclick="window.closeQuickAdd()">Cancelar</button>
1359
+ <button class="btn primary small" type="button" onclick="window.submitQuickAdd()">Criar Task</button>
1360
+ </div>
1361
+ </div>
1362
+ </div>
1363
+
1320
1364
  <script>
1321
1365
  window.__FREYA_DEFAULT_DIR = "${safeDefault}";
1322
1366
  </script>
@@ -1354,6 +1398,9 @@ function buildReportsHtml(safeDefault, appVersion) {
1354
1398
  <button class="railBtn" id="railCompanion" type="button" title="Companion">
1355
1399
  <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>
1356
1400
  </button>
1401
+ <button class="railBtn" id="railKanban" type="button" title="Kanban">
1402
+ <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="5" height="18" rx="1"></rect><rect x="10" y="3" width="5" height="12" rx="1"></rect><rect x="17" y="3" width="5" height="15" rx="1"></rect></svg>
1403
+ </button>
1357
1404
  <button class="railBtn" id="railProjects" type="button" title="Projects">
1358
1405
  <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>
1359
1406
  </button>
@@ -1467,6 +1514,9 @@ function buildProjectsHtml(safeDefault, appVersion) {
1467
1514
  <button class="railBtn" id="railCompanion" type="button" title="Companion">
1468
1515
  <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>
1469
1516
  </button>
1517
+ <button class="railBtn" id="railKanban" type="button" title="Kanban">
1518
+ <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="5" height="18" rx="1"></rect><rect x="10" y="3" width="5" height="12" rx="1"></rect><rect x="17" y="3" width="5" height="15" rx="1"></rect></svg>
1519
+ </button>
1470
1520
  <button class="railBtn active" id="railProjects" type="button" title="Projects">
1471
1521
  <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>
1472
1522
  </button>
@@ -1560,6 +1610,9 @@ function buildTimelineHtml(safeDefault, appVersion) {
1560
1610
  <button class=\"railBtn\" id=\"railCompanion\" type=\"button\" title=\"Companion\">
1561
1611
  <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>
1562
1612
  </button>
1613
+ <button class=\"railBtn\" id=\"railKanban\" type=\"button\" title=\"Kanban\">
1614
+ <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="5" height="18" rx="1"></rect><rect x="10" y="3" width="5" height="12" rx="1"></rect><rect x="17" y="3" width="5" height="15" rx="1"></rect></svg>
1615
+ </button>
1563
1616
  <button class=\"railBtn\" id=\"railProjects\" type=\"button\" title=\"Projects\">
1564
1617
  <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>
1565
1618
  </button>
@@ -1669,6 +1722,9 @@ function buildGraphHtml(safeDefault, appVersion) {
1669
1722
  <button class="railBtn" id="railCompanion" type="button" title="Companion">
1670
1723
  <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>
1671
1724
  </button>
1725
+ <button class="railBtn" id="railKanban" type="button" title="Kanban">
1726
+ <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="5" height="18" rx="1"></rect><rect x="10" y="3" width="5" height="12" rx="1"></rect><rect x="17" y="3" width="5" height="15" rx="1"></rect></svg>
1727
+ </button>
1672
1728
  <button class="railBtn" id="railProjects" type="button" title="Projects">
1673
1729
  <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>
1674
1730
  </button>
@@ -1760,6 +1816,9 @@ function buildCompanionHtml(safeDefault, appVersion) {
1760
1816
  <button class="railBtn active" id="railCompanion" type="button" title="Companion">
1761
1817
  <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>
1762
1818
  </button>
1819
+ <button class="railBtn" id="railKanban" type="button" title="Kanban">
1820
+ <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="5" height="18" rx="1"></rect><rect x="10" y="3" width="5" height="12" rx="1"></rect><rect x="17" y="3" width="5" height="15" rx="1"></rect></svg>
1821
+ </button>
1763
1822
  <button class="railBtn" id="railProjects" type="button" title="Projects">
1764
1823
  <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>
1765
1824
  </button>
@@ -2322,6 +2381,14 @@ async function cmdWeb({ port, dir, open, dev }) {
2322
2381
  return;
2323
2382
  }
2324
2383
 
2384
+ if (req.method === 'GET' && req.url === '/kanban') {
2385
+ try { res.__freyaDebug.workspaceDir = normalizeWorkspaceDir(dir || './freya'); } catch { }
2386
+ const body = injectPort(kanbanHtml(dir || './freya'));
2387
+ res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8', 'Cache-Control': 'no-store' });
2388
+ res.end(body);
2389
+ return;
2390
+ }
2391
+
2325
2392
  if (req.method === 'GET' && req.url === '/app.css') {
2326
2393
  const css = fs.readFileSync(path.join(__dirname, 'web-ui.css'), 'utf8');
2327
2394
  res.writeHead(200, { 'Content-Type': 'text/css; charset=utf-8', 'Cache-Control': 'no-store' });
@@ -3724,15 +3791,19 @@ async function cmdWeb({ port, dir, open, dev }) {
3724
3791
  return `\n\n---\nFILE: ${rel}\n---\n` + fs.readFileSync(p, 'utf8');
3725
3792
  }).join('');
3726
3793
 
3727
- // V2 RAG Context
3794
+ // V2 RAG Context (graceful fallback if embedder/sharp not available)
3728
3795
  const dm = new DataManager(workspaceDir, path.join(workspaceDir, 'logs'));
3729
- const ragResults = await dm.semanticSearch(query, 12);
3730
3796
  let ragContext = '';
3731
- if (ragResults.length > 0) {
3732
- ragContext = '\n\n[MEMÓRIA DE LONGO PRAZO RECUPERADA (RAG VIA SQLITE)]\n';
3733
- for (const r of ragResults) {
3734
- ragContext += `\n---\nFONTE: ${r.reference_type} -> ID: ${r.reference_id} (Score: ${r.score.toFixed(3)})\nCONTEÚDO:\n${r.text_chunk}\n`;
3797
+ try {
3798
+ const ragResults = await dm.semanticSearch(query, 12);
3799
+ if (ragResults.length > 0) {
3800
+ ragContext = '\n\n[MEMÓRIA DE LONGO PRAZO RECUPERADA (RAG VIA SQLITE)]\n';
3801
+ for (const r of ragResults) {
3802
+ ragContext += `\n---\nFONTE: ${r.reference_type} -> ID: ${r.reference_id} (Score: ${r.score.toFixed(3)})\nCONTEÚDO:\n${r.text_chunk}\n`;
3803
+ }
3735
3804
  }
3805
+ } catch (ragErr) {
3806
+ console.error('[oracle] RAG search failed (embedder/sharp unavailable), continuing without context:', ragErr.message);
3736
3807
  }
3737
3808
 
3738
3809
  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`;
@@ -3882,6 +3953,7 @@ async function cmdWeb({ port, dir, open, dev }) {
3882
3953
  status: t.status,
3883
3954
  createdAt: t.created_at,
3884
3955
  completedAt: t.completed_at,
3956
+ dueDate: t.due_date || null,
3885
3957
  projectSlug: t.project_slug,
3886
3958
  priority: meta.priority,
3887
3959
  streamSlug: meta.streamSlug,
@@ -3934,6 +4006,33 @@ async function cmdWeb({ port, dir, open, dev }) {
3934
4006
  queryUpdates.push('category = ?');
3935
4007
  params.push(patch.category.trim());
3936
4008
  }
4009
+ if (typeof patch.dueDate === 'string') {
4010
+ queryUpdates.push('due_date = ?');
4011
+ params.push(patch.dueDate.trim() || null);
4012
+ }
4013
+ if (patch.dueDate === null) {
4014
+ queryUpdates.push('due_date = NULL');
4015
+ }
4016
+ if (typeof patch.status === 'string') {
4017
+ queryUpdates.push('status = ?');
4018
+ params.push(patch.status.trim());
4019
+ if (patch.status === 'COMPLETED') {
4020
+ queryUpdates.push('completed_at = ?');
4021
+ params.push(new Date().toISOString());
4022
+ }
4023
+ }
4024
+ if (typeof patch.description === 'string') {
4025
+ queryUpdates.push('description = ?');
4026
+ params.push(patch.description.trim());
4027
+ }
4028
+ if (typeof patch.priority === 'string') {
4029
+ const row = dl.db.prepare('SELECT metadata FROM tasks WHERE id = ?').get(id);
4030
+ let meta = {};
4031
+ try { meta = row && row.metadata ? JSON.parse(row.metadata) : {}; } catch { meta = {}; }
4032
+ meta.priority = patch.priority.trim().toLowerCase();
4033
+ queryUpdates.push('metadata = ?');
4034
+ params.push(JSON.stringify(meta));
4035
+ }
3937
4036
 
3938
4037
  if (queryUpdates.length === 0) return safeJson(res, 200, { ok: true, task: { id } });
3939
4038
 
@@ -4085,6 +4184,100 @@ async function cmdWeb({ port, dir, open, dev }) {
4085
4184
  return safeJson(res, 200, { ok: true, blocker: { id, ...patch } });
4086
4185
  }
4087
4186
 
4187
+ // --- Quick-add: create a single task directly ---
4188
+ if (req.url === '/api/tasks/create') {
4189
+ const desc = String(payload.description || '').trim();
4190
+ if (!desc) return safeJson(res, 400, { error: 'Missing description' });
4191
+
4192
+ const cat = String(payload.category || 'DO_NOW').trim().toUpperCase();
4193
+ const validCats = new Set(['DO_NOW', 'SCHEDULE', 'DELEGATE', 'IGNORE']);
4194
+ if (!validCats.has(cat)) return safeJson(res, 400, { error: 'Invalid category' });
4195
+
4196
+ const slug = typeof payload.projectSlug === 'string' ? payload.projectSlug.trim() || null : null;
4197
+ const dueDate = typeof payload.dueDate === 'string' ? payload.dueDate.trim() || null : null;
4198
+ const pri = typeof payload.priority === 'string' ? payload.priority.trim().toLowerCase() : undefined;
4199
+ const meta = {};
4200
+ if (pri) meta.priority = pri;
4201
+
4202
+ const id = `task-${Date.now()}-${Math.random().toString(16).slice(2, 8)}`;
4203
+ dl.db.prepare('INSERT INTO tasks (id, project_slug, description, category, status, due_date, metadata) VALUES (?, ?, ?, ?, ?, ?, ?)')
4204
+ .run(id, slug, desc, cat, 'PENDING', dueDate, Object.keys(meta).length ? JSON.stringify(meta) : null);
4205
+
4206
+ return safeJson(res, 200, { ok: true, task: { id, description: desc, category: cat, status: 'PENDING', projectSlug: slug, dueDate, priority: pri } });
4207
+ }
4208
+
4209
+ // --- Delta summary: what changed since yesterday ---
4210
+ if (req.url === '/api/summary/delta') {
4211
+ const now = new Date();
4212
+ const yesterday = new Date(now);
4213
+ yesterday.setDate(yesterday.getDate() - 1);
4214
+ const yIso = yesterday.toISOString();
4215
+
4216
+ const completed = dl.db.prepare("SELECT count(*) as c FROM tasks WHERE status = 'COMPLETED' AND completed_at >= ?").get(yIso);
4217
+ const newTasks = dl.db.prepare("SELECT count(*) as c FROM tasks WHERE created_at >= ?").get(yIso);
4218
+ const resolvedBlockers = dl.db.prepare("SELECT count(*) as c FROM blockers WHERE resolved_at >= ?").get(yIso);
4219
+ const newBlockers = dl.db.prepare("SELECT count(*) as c FROM blockers WHERE created_at >= ?").get(yIso);
4220
+ const overdue = dl.db.prepare("SELECT count(*) as c FROM tasks WHERE status = 'PENDING' AND due_date IS NOT NULL AND due_date < ?")
4221
+ .get(now.toISOString().slice(0, 10));
4222
+
4223
+ return safeJson(res, 200, {
4224
+ ok: true,
4225
+ delta: {
4226
+ completedTasks: completed ? completed.c : 0,
4227
+ newTasks: newTasks ? newTasks.c : 0,
4228
+ resolvedBlockers: resolvedBlockers ? resolvedBlockers.c : 0,
4229
+ newBlockers: newBlockers ? newBlockers.c : 0,
4230
+ overdueTasks: overdue ? overdue.c : 0
4231
+ }
4232
+ });
4233
+ }
4234
+
4235
+ // --- Kanban: all active tasks for board view ---
4236
+ if (req.url === '/api/tasks/kanban') {
4237
+ const rawTasks = dl.db.prepare(`
4238
+ SELECT * FROM tasks WHERE status != 'ARCHIVED'
4239
+ ORDER BY
4240
+ CASE JSON_EXTRACT(metadata, '$.priority')
4241
+ WHEN 'critical' THEN 0 WHEN 'high' THEN 1 WHEN 'medium' THEN 2 WHEN 'low' THEN 3 ELSE 4
4242
+ END ASC,
4243
+ created_at DESC
4244
+ `).all();
4245
+
4246
+ const tasks = rawTasks.map(t => {
4247
+ let meta = {};
4248
+ try { meta = t.metadata ? JSON.parse(t.metadata) : {}; } catch { meta = {}; }
4249
+ return {
4250
+ id: t.id,
4251
+ description: t.description,
4252
+ category: t.category,
4253
+ status: t.status,
4254
+ createdAt: t.created_at,
4255
+ completedAt: t.completed_at,
4256
+ dueDate: t.due_date || null,
4257
+ projectSlug: t.project_slug,
4258
+ priority: meta.priority,
4259
+ streamSlug: meta.streamSlug
4260
+ };
4261
+ });
4262
+
4263
+ const openBlockers = dl.db.prepare(`
4264
+ SELECT * FROM blockers WHERE status IN ('OPEN', 'MITIGATING')
4265
+ ORDER BY
4266
+ CASE severity WHEN 'CRITICAL' THEN 0 WHEN 'HIGH' THEN 1 WHEN 'MEDIUM' THEN 2 WHEN 'LOW' THEN 3 ELSE 9 END ASC,
4267
+ created_at ASC
4268
+ `).all().map(b => ({
4269
+ id: b.id,
4270
+ title: b.title,
4271
+ severity: b.severity,
4272
+ status: b.status,
4273
+ projectSlug: b.project_slug,
4274
+ owner: b.owner,
4275
+ createdAt: b.created_at
4276
+ }));
4277
+
4278
+ return safeJson(res, 200, { ok: true, tasks, blockers: openBlockers });
4279
+ }
4280
+
4088
4281
  if (req.url === '/api/report') {
4089
4282
  const script = payload.script;
4090
4283
  if (!script) return safeJson(res, 400, { error: 'Missing script' });
@@ -4211,6 +4404,9 @@ function buildSettingsHtml(safeDefault, appVersion) {
4211
4404
  <button class="railBtn" id="railCompanion" type="button" title="Companion">
4212
4405
  <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>
4213
4406
  </button>
4407
+ <button class="railBtn" id="railKanban" type="button" title="Kanban">
4408
+ <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="5" height="18" rx="1"></rect><rect x="10" y="3" width="5" height="12" rx="1"></rect><rect x="17" y="3" width="5" height="15" rx="1"></rect></svg>
4409
+ </button>
4214
4410
  <button class="railBtn" id="railProjects" type="button" title="Projects">
4215
4411
  <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>
4216
4412
  </button>
@@ -4326,5 +4522,175 @@ function buildSettingsHtml(safeDefault, appVersion) {
4326
4522
  </html>`;
4327
4523
  }
4328
4524
 
4525
+ function buildKanbanHtml(safeDefault, appVersion) {
4526
+ const safeVersion = escapeHtml(appVersion || 'unknown');
4527
+ const kanbanSvg = '<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="5" height="18" rx="1"></rect><rect x="10" y="3" width="5" height="12" rx="1"></rect><rect x="17" y="3" width="5" height="15" rx="1"></rect></svg>';
4528
+ return `<!doctype html>
4529
+ <html>
4530
+ <head>
4531
+ <meta charset="utf-8" />
4532
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
4533
+ <title>FREYA Kanban</title>
4534
+ <link rel="stylesheet" href="/app.css" />
4535
+ </head>
4536
+ <body data-page="kanban">
4537
+ <div class="app">
4538
+ <div class="frame">
4539
+ <div class="shell">
4540
+
4541
+ <aside class="rail" role="navigation" aria-label="Menu principal">
4542
+ <div class="railTop">
4543
+ <div class="railLogo">F</div>
4544
+ </div>
4545
+ <div class="railNav">
4546
+ <button class="railBtn" id="railDashboard" type="button" title="Dashboard">
4547
+ <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>
4548
+ </button>
4549
+ <button class="railBtn" id="railReports" type="button" title="Relat\\u00f3rios">
4550
+ <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>
4551
+ </button>
4552
+ <button class="railBtn" id="railCompanion" type="button" title="Companion">
4553
+ <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>
4554
+ </button>
4555
+ <button class="railBtn active" id="railKanban" type="button" title="Kanban">
4556
+ ${kanbanSvg}
4557
+ </button>
4558
+ <button class="railBtn" id="railProjects" type="button" title="Projects">
4559
+ <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>
4560
+ </button>
4561
+ <button class="railBtn" id="railTimeline" type="button" title="Timeline">
4562
+ <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>
4563
+ </button>
4564
+ <button class="railBtn" id="railGraph" type="button" title="Grafo">
4565
+ <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>
4566
+ </button>
4567
+ </div>
4568
+ <div class="railBottom">
4569
+ <button id="railSettings" class="railBtn" title="Configura\\u00e7\\u00f5es" onclick="if (document.body.dataset.page !== 'settings') window.location.href='/settings';">
4570
+ <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>
4571
+ </button>
4572
+ </div>
4573
+ </aside>
4574
+
4575
+ <main class="center" role="main">
4576
+ <div class="topbar">
4577
+ <div class="brandLine">
4578
+ <span class="spark"></span>
4579
+ <div class="brandStack">
4580
+ <div class="brand">FREYA</div>
4581
+ <div class="brandSub">Kanban Board &mdash; Vis\\u00e3o SM</div>
4582
+ </div>
4583
+ </div>
4584
+ <div class="topActions">
4585
+ <span class="chip clickable" onclick="window.location.href='/docs'">Docs</span>
4586
+ <span class="chip" id="chipVersion">v${safeVersion}</span>
4587
+ </div>
4588
+ </div>
4589
+
4590
+ <div class="centerBody">
4591
+ <!-- Kanban toolbar -->
4592
+ <div class="kanban-toolbar" style="display:flex; justify-content:space-between; align-items:center; padding:0 0 16px; gap:12px; flex-wrap:wrap;">
4593
+ <div style="display:flex; gap:8px; align-items:center;">
4594
+ <select id="kanbanFilterProject" class="kanban-filter" onchange="window.filterKanban()">
4595
+ <option value="">Todos os projetos</option>
4596
+ </select>
4597
+ <button class="btn small" type="button" onclick="window.loadKanban()">
4598
+ <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" style="display:inline;vertical-align:-2px;margin-right:4px"><polyline points="23 4 23 10 17 10"></polyline><path d="M20.49 15a9 9 0 1 1-2.12-9.36L23 10"></path></svg>
4599
+ Atualizar
4600
+ </button>
4601
+ </div>
4602
+ <button class="btn primary small" type="button" onclick="window.openQuickAdd()">+ Nova Task</button>
4603
+ </div>
4604
+
4605
+ <!-- Delta banner -->
4606
+ <div id="kanbanDelta" style="display:none;"></div>
4607
+
4608
+ <!-- Kanban columns -->
4609
+ <div id="kanbanBoard" class="kanban-board">
4610
+ <div class="kanban-col" data-category="DO_NOW">
4611
+ <div class="kanban-col-head do-now">
4612
+ <span class="kanban-col-title">DO NOW</span>
4613
+ <span class="kanban-col-count" id="countDoNow">0</span>
4614
+ </div>
4615
+ <div class="kanban-col-body" id="colDoNow"></div>
4616
+ </div>
4617
+ <div class="kanban-col" data-category="SCHEDULE">
4618
+ <div class="kanban-col-head schedule">
4619
+ <span class="kanban-col-title">SCHEDULE</span>
4620
+ <span class="kanban-col-count" id="countSchedule">0</span>
4621
+ </div>
4622
+ <div class="kanban-col-body" id="colSchedule"></div>
4623
+ </div>
4624
+ <div class="kanban-col" data-category="DELEGATE">
4625
+ <div class="kanban-col-head delegate">
4626
+ <span class="kanban-col-title">DELEGATE</span>
4627
+ <span class="kanban-col-count" id="countDelegate">0</span>
4628
+ </div>
4629
+ <div class="kanban-col-body" id="colDelegate"></div>
4630
+ </div>
4631
+ <div class="kanban-col" data-category="COMPLETED">
4632
+ <div class="kanban-col-head done">
4633
+ <span class="kanban-col-title">DONE (7d)</span>
4634
+ <span class="kanban-col-count" id="countDone">0</span>
4635
+ </div>
4636
+ <div class="kanban-col-body" id="colDone"></div>
4637
+ </div>
4638
+ </div>
4639
+
4640
+ <!-- Blockers strip below kanban -->
4641
+ <div id="kanbanBlockers" style="margin-top:20px; display:none;">
4642
+ <div style="font-size:13px; font-weight:700; color:var(--text); margin-bottom:8px; display:flex; align-items:center; gap:6px;">
4643
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="#ef4444" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg>
4644
+ Blockers Ativos
4645
+ </div>
4646
+ <div id="kanbanBlockersList" class="kanban-blockers-list"></div>
4647
+ </div>
4648
+ </div>
4649
+ </main>
4650
+ </div>
4651
+ </div>
4652
+ </div>
4653
+
4654
+ <!-- Quick-add modal -->
4655
+ <div id="quickAddOverlay" class="qa-overlay" style="display:none;">
4656
+ <div class="qa-modal">
4657
+ <div class="qa-header">
4658
+ <span style="font-weight:700; font-size:14px;">Nova Task</span>
4659
+ <button class="btn small" type="button" onclick="window.closeQuickAdd()" style="padding:2px 8px;">&times;</button>
4660
+ </div>
4661
+ <textarea id="qaDesc" class="qa-input" placeholder="Descri\\u00e7\\u00e3o da task..." rows="3"></textarea>
4662
+ <div class="qa-row">
4663
+ <select id="qaCat" class="qa-select">
4664
+ <option value="DO_NOW">DO_NOW</option>
4665
+ <option value="SCHEDULE">SCHEDULE</option>
4666
+ <option value="DELEGATE">DELEGATE</option>
4667
+ </select>
4668
+ <select id="qaPriority" class="qa-select">
4669
+ <option value="">Prioridade</option>
4670
+ <option value="critical">Cr\\u00edtica</option>
4671
+ <option value="high">Alta</option>
4672
+ <option value="medium">M\\u00e9dia</option>
4673
+ <option value="low">Baixa</option>
4674
+ </select>
4675
+ </div>
4676
+ <div class="qa-row">
4677
+ <input id="qaSlug" class="qa-input" placeholder="Projeto (slug)" style="flex:1;" />
4678
+ <input id="qaDue" type="date" class="qa-input" style="width:160px;" />
4679
+ </div>
4680
+ <div class="qa-row" style="justify-content:flex-end;">
4681
+ <button class="btn small" type="button" onclick="window.closeQuickAdd()">Cancelar</button>
4682
+ <button class="btn primary small" type="button" onclick="window.submitQuickAdd()">Criar Task</button>
4683
+ </div>
4684
+ </div>
4685
+ </div>
4686
+
4687
+ <script>
4688
+ window.__FREYA_DEFAULT_DIR = "${safeDefault}";
4689
+ </script>
4690
+ <script src="/app.js"></script>
4691
+ </body>
4692
+ </html>`;
4693
+ }
4694
+
4329
4695
  module.exports = { cmdWeb };
4330
4696
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cccarv82/freya",
3
- "version": "2.14.1",
3
+ "version": "2.15.0",
4
4
  "description": "Personal AI Assistant with local-first persistence",
5
5
  "scripts": {
6
6
  "health": "node scripts/validate-data.js && node scripts/validate-structure.js",
@@ -31,7 +31,7 @@
31
31
  ],
32
32
  "preferGlobal": true,
33
33
  "dependencies": {
34
- "@xenova/transformers": "^2.17.2",
34
+ "@huggingface/transformers": "^3.8.1",
35
35
  "pdf-lib": "^1.17.1",
36
36
  "sql.js": "^1.12.0"
37
37
  }
@@ -319,13 +319,22 @@ class DataLayer {
319
319
  CREATE TABLE IF NOT EXISTS document_embeddings (
320
320
  id INTEGER PRIMARY KEY AUTOINCREMENT,
321
321
  reference_type TEXT NOT NULL, /* 'daily_log', 'task', 'blocker' */
322
- reference_id TEXT NOT NULL,
322
+ reference_id TEXT NOT NULL,
323
323
  chunk_index INTEGER DEFAULT 0,
324
324
  text_chunk TEXT NOT NULL,
325
325
  embedding BLOB NOT NULL, /* Stored as Buffer of Float32Array */
326
326
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP
327
327
  );
328
328
  `);
329
+
330
+ // --- Migrations for existing databases ---
331
+ // Add due_date column to tasks (safe to run multiple times)
332
+ try {
333
+ const cols = this.db.prepare("PRAGMA table_info(tasks)").all();
334
+ if (!cols.some(c => c.name === 'due_date')) {
335
+ this.db.exec("ALTER TABLE tasks ADD COLUMN due_date TEXT");
336
+ }
337
+ } catch { /* ignore if PRAGMA not supported */ }
329
338
  }
330
339
 
331
340
  // Helper close method
@@ -40,6 +40,7 @@ class DataManager {
40
40
  status: t.status,
41
41
  createdAt: t.created_at,
42
42
  completedAt: t.completed_at,
43
+ dueDate: t.due_date || null,
43
44
  priority: meta.priority,
44
45
  streamSlug: meta.streamSlug
45
46
  };
@@ -1,5 +1,6 @@
1
1
  class Embedder {
2
2
  constructor() {
3
+ // V3: use the official HuggingFace model name (same weights, new namespace)
3
4
  this.modelName = 'Xenova/all-MiniLM-L6-v2';
4
5
  this.extractorInfo = null;
5
6
  this.initPromise = null;
@@ -9,7 +10,9 @@ class Embedder {
9
10
  if (this.extractorInfo) return;
10
11
  if (!this.initPromise) {
11
12
  this.initPromise = (async () => {
12
- const { pipeline } = await import('@xenova/transformers');
13
+ // V3: @huggingface/transformers replaces @xenova/transformers
14
+ // sharp 0.34+ uses prebuilt platform binaries (no node-gyp needed)
15
+ const { pipeline } = await import('@huggingface/transformers');
13
16
  this.extractorInfo = await pipeline('feature-extraction', this.modelName, { quantized: true });
14
17
  })().catch((err) => {
15
18
  this.initPromise = null;
@@ -25,6 +28,7 @@ class Embedder {
25
28
  if (!cleanText) return new Float32Array(384); // Empty zero vector for model
26
29
 
27
30
  const output = await this.extractorInfo(cleanText, { pooling: 'mean', normalize: true });
31
+ // V3: output.data may be a typed array or need .tolist()
28
32
  return new Float32Array(output.data);
29
33
  }
30
34