@cccarv82/freya 1.0.46 → 1.0.49

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.
Files changed (3) hide show
  1. package/cli/index.js +3 -2
  2. package/cli/web.js +113 -20
  3. package/package.json +1 -1
package/cli/index.js CHANGED
@@ -1,5 +1,6 @@
1
1
  'use strict';
2
2
 
3
+ const fs = require('fs');
3
4
  const path = require('path');
4
5
 
5
6
  const { cmdInit } = require('./init');
@@ -59,7 +60,7 @@ async function run(argv) {
59
60
  const command = args[0];
60
61
 
61
62
  if (!command || command === 'help' || flags.has('--help') || flags.has('-h')) {
62
- process.stdout.write(usage());
63
+ fs.writeSync(process.stdout.fd, usage());
63
64
  return;
64
65
  }
65
66
 
@@ -95,7 +96,7 @@ async function run(argv) {
95
96
  }
96
97
 
97
98
  process.stderr.write(`Unknown command: ${command}\n`);
98
- process.stdout.write(usage());
99
+ fs.writeSync(process.stdout.fd, usage());
99
100
  process.exitCode = 1;
100
101
  }
101
102
 
package/cli/web.js CHANGED
@@ -608,6 +608,107 @@ function run(cmd, args, cwd) {
608
608
  });
609
609
  }
610
610
 
611
+ function isAllowedChatSearchPath(relPath) {
612
+ if (!relPath) return false;
613
+ if (relPath.startsWith('..')) return false;
614
+ return relPath.startsWith('data/') || relPath.startsWith('logs/') || relPath.startsWith('docs/');
615
+ }
616
+
617
+ async function copilotSearch(workspaceDir, query, opts = {}) {
618
+ const q = String(query || '').trim();
619
+ if (!q) return { ok: false, error: 'Missing query' };
620
+
621
+ const limit = Math.max(1, Math.min(20, Number(opts.limit || 8)));
622
+ const cmd = process.env.COPILOT_CMD || 'copilot';
623
+
624
+ const prompt = [
625
+ 'Você é um buscador local de arquivos.',
626
+ 'Objetivo: encontrar registros relevantes para a consulta do usuário.',
627
+ 'Escopo: procure SOMENTE nos diretórios data/, logs/ e docs/ do workspace.',
628
+ 'Use ferramentas para ler/consultar arquivos, mas não modifique nada.',
629
+ `Consulta do usuário: "${q}"`,
630
+ '',
631
+ 'Responda APENAS com JSON válido (sem code fences) no formato:',
632
+ '{"summary":"<1-2 frases humanas>","matches":[{"file":"<caminho relativo>","date":"YYYY-MM-DD ou vazio","snippet":"<trecho curto>"}]}',
633
+ `Limite de matches: ${limit}.`,
634
+ 'O resumo deve soar humano e mencionar a quantidade de registros encontrados.',
635
+ 'A lista deve estar ordenada por relevância.'
636
+ ].join('\n');
637
+
638
+ const args = [
639
+ '-s',
640
+ '--no-color',
641
+ '--stream',
642
+ 'off',
643
+ '-p',
644
+ prompt,
645
+ '--allow-all-tools',
646
+ '--add-dir',
647
+ workspaceDir
648
+ ];
649
+
650
+ const r = await run(cmd, args, workspaceDir);
651
+ const out = (r.stdout + r.stderr).trim();
652
+ if (r.code !== 0) return { ok: false, error: out || 'Copilot returned non-zero exit code.' };
653
+
654
+ const jsonText = extractFirstJsonObject(out) || out;
655
+ let parsed;
656
+ try {
657
+ parsed = JSON.parse(jsonText);
658
+ } catch {
659
+ try {
660
+ parsed = JSON.parse(escapeJsonControlChars(jsonText));
661
+ } catch (e) {
662
+ return { ok: false, error: e.message || 'Copilot output not valid JSON.' };
663
+ }
664
+ }
665
+
666
+ const matchesRaw = Array.isArray(parsed.matches) ? parsed.matches : [];
667
+ const matches = matchesRaw
668
+ .map((m) => {
669
+ const fileRaw = String(m && m.file ? m.file : '').trim();
670
+ const dateRaw = String(m && m.date ? m.date : '').trim();
671
+ const snippetRaw = String(m && m.snippet ? m.snippet : '').trim();
672
+ let rel = fileRaw;
673
+ if (fileRaw.startsWith(workspaceDir)) {
674
+ rel = path.relative(workspaceDir, fileRaw).replace(/\\/g, '/');
675
+ }
676
+ return { file: rel.replace(/\\/g, '/'), date: dateRaw, snippet: snippetRaw };
677
+ })
678
+ .filter((m) => m.file && isAllowedChatSearchPath(m.file))
679
+ .slice(0, limit);
680
+
681
+ const summary = String(parsed.summary || '').trim();
682
+ return { ok: true, summary, matches };
683
+ }
684
+
685
+ function buildChatAnswer(query, matches, summary) {
686
+ const count = matches.length;
687
+ let summaryText = String(summary || '').trim();
688
+ if (!summaryText) {
689
+ if (count === 0) {
690
+ summaryText = `Não encontrei registros relacionados a "${query}".`;
691
+ } else {
692
+ summaryText = `Encontrei ${count} registro(s) relacionados a "${query}".`;
693
+ }
694
+ }
695
+
696
+ const lines = [];
697
+ lines.push(`Encontrei ${count} registro(s).`);
698
+ lines.push(`Resumo (${count} registro(s)): ${summaryText}`);
699
+
700
+ for (const m of matches) {
701
+ const parts = [];
702
+ if (m.date) parts.push(`**${m.date}**`);
703
+ if (m.file) parts.push('`' + m.file + '`');
704
+ const prefix = parts.length ? parts.join(' — ') + ':' : '';
705
+ const snippet = m.snippet ? String(m.snippet).trim() : '';
706
+ lines.push(`- ${prefix} ${snippet}`);
707
+ }
708
+
709
+ return lines.join('\n');
710
+ }
711
+
611
712
  function openBrowser(url) {
612
713
  const { cmd, args } = guessOpenCmd();
613
714
  try {
@@ -1623,26 +1724,18 @@ async function cmdWeb({ port, dir, open, dev }) {
1623
1724
  const query = String(payload.query || '').trim();
1624
1725
  if (!query) return safeJson(res, 400, { error: 'Missing query' });
1625
1726
 
1727
+ const copilotResult = await copilotSearch(workspaceDir, query, { limit: 8 });
1728
+ if (copilotResult.ok) {
1729
+ const matches = copilotResult.matches || [];
1730
+ const answer = buildChatAnswer(query, matches, copilotResult.summary);
1731
+ return safeJson(res, 200, { ok: true, sessionId, answer, matches });
1732
+ }
1733
+
1626
1734
  const indexMatches = searchIndex(workspaceDir, query, { limit: 8 });
1627
1735
  const matches = indexMatches.length
1628
1736
  ? indexMatches
1629
1737
  : searchWorkspace(workspaceDir, query, { limit: 8 });
1630
- if (!matches.length) {
1631
- return safeJson(res, 200, { ok: true, sessionId, answer: 'Não encontrei registro', matches: [] });
1632
- }
1633
-
1634
- const lines = [];
1635
- lines.push(`Encontrei ${matches.length} registro(s):`);
1636
- for (const m of matches) {
1637
- const parts = [];
1638
- if (m.date) parts.push(`**${m.date}**`);
1639
- if (m.file) parts.push('`' + m.file + '`');
1640
- const prefix = parts.length ? parts.join(' — ') + ':' : '';
1641
- const snippet = m.snippet ? String(m.snippet).trim() : '';
1642
- lines.push(`- ${prefix} ${snippet}`);
1643
- }
1644
-
1645
- const answer = lines.join('\n');
1738
+ const answer = buildChatAnswer(query, matches, '');
1646
1739
  return safeJson(res, 200, { ok: true, sessionId, answer, matches });
1647
1740
  }
1648
1741
 
@@ -1948,17 +2041,17 @@ async function cmdWeb({ port, dir, open, dev }) {
1948
2041
  const targetOk = looksLikeFreyaWorkspace(target);
1949
2042
  const empty = looksEmptyWorkspace(target);
1950
2043
  if (!targetOk && !empty) {
1951
- process.stdout.write(`Dev seed: skipped (workspace not empty and not initialized) -> ${target}\n`);
2044
+ fs.writeSync(process.stdout.fd, `Dev seed: skipped (workspace not empty and not initialized) -> ${target}\n`);
1952
2045
  } else {
1953
2046
  seedDevWorkspace(target);
1954
- process.stdout.write(`Dev seed: created demo files in ${target}\n`);
2047
+ fs.writeSync(process.stdout.fd, `Dev seed: created demo files in ${target}\n`);
1955
2048
  }
1956
2049
  } catch (e) {
1957
- process.stdout.write(`Dev seed failed: ${e.message || String(e)}\n`);
2050
+ fs.writeSync(process.stdout.fd, `Dev seed failed: ${e.message || String(e)}\n`);
1958
2051
  }
1959
2052
  }
1960
2053
 
1961
- process.stdout.write(`FREYA web running at ${url}\n`);
2054
+ fs.writeSync(process.stdout.fd, `FREYA web running at ${url}\n`);
1962
2055
  if (open) openBrowser(url);
1963
2056
  }
1964
2057
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cccarv82/freya",
3
- "version": "1.0.46",
3
+ "version": "1.0.49",
4
4
  "description": "Personal AI Assistant with local-first persistence",
5
5
  "scripts": {
6
6
  "health": "node scripts/validate-data.js",