primocode 8.2.1 → 8.2.2

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/lib/referencia.js CHANGED
@@ -102,10 +102,17 @@ function pegar(url, { timeout = TIMEOUT_MS, saltos = 0 } = {}) {
102
102
  if (hostBloqueado(alvo.hostname)) return resolve(null);
103
103
  const cliente = alvo.protocol === 'http:' ? http : https;
104
104
 
105
+ // O User-Agent precisa parecer navegador. Buscador nenhum responde a
106
+ // agente desconhecido: DuckDuckGo, Bing e Searx devolviam página VAZIA
107
+ // para o 'PrimoCode/1.0' — e o usuário via "nada encontrado (sem
108
+ // internet?)" com a internet funcionando perfeitamente. Site comum não
109
+ // liga para isso, e por isso as páginas abriam: só a BUSCA morria.
105
110
  const opcoes = {
106
111
  headers: {
107
- 'User-Agent': 'PrimoCode/1.0 (+https://github.com/ConectaPrimoAI/primocode)',
108
- Accept: 'text/html,application/json;q=0.9,*/*;q=0.8',
112
+ 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 '
113
+ + '(KHTML, like Gecko) Chrome/122.0 Safari/537.36',
114
+ Accept: 'text/html,application/xhtml+xml,application/json;q=0.9,*/*;q=0.8',
115
+ 'Accept-Language': 'pt-BR,pt;q=0.9,en;q=0.8',
109
116
  },
110
117
  timeout,
111
118
  };
@@ -191,6 +198,59 @@ async function buscarDDG(consulta, limite) {
191
198
  return limparLinks(pares, limite);
192
199
  }
193
200
 
201
+ /**
202
+ * Wikipedia — a fonte que NÃO bloqueia.
203
+ *
204
+ * Buscador raspado por HTML (DDG, Bing, Searx) recusa quem não parece gente:
205
+ * devolve 202/302 e página vazia, e o usuário lê "nada encontrado (sem
206
+ * internet?)" com a internet ótima. Medido: no IP deste servidor os três
207
+ * calaram juntos, enquanto a API da Wikipedia respondeu 200 na hora.
208
+ *
209
+ * É API pública e estável, feita para ser consumida por programa — e para
210
+ * empresa, pessoa, cidade e evento costuma ser a MELHOR fonte, não a pior.
211
+ */
212
+ async function buscarWikipedia(consulta, limite) {
213
+ const achados = [];
214
+ for (const lingua of ['pt', 'en']) {
215
+ if (achados.length >= limite) break;
216
+ const url = `https://${lingua}.wikipedia.org/w/api.php?action=query&list=search`
217
+ + `&srsearch=${encodeURIComponent(consulta)}&format=json&srlimit=${limite}`;
218
+ const bruto = await pegar(url);
219
+ if (!bruto) continue;
220
+ let j;
221
+ try { j = JSON.parse(bruto); } catch { continue; }
222
+ for (const r of (j.query && j.query.search) || []) {
223
+ achados.push({
224
+ titulo: r.title,
225
+ url: `https://${lingua}.wikipedia.org/wiki/${encodeURIComponent(r.title.replace(/ /g, '_'))}`,
226
+ nota: 'Wikipédia',
227
+ });
228
+ if (achados.length >= limite) break;
229
+ }
230
+ }
231
+ return achados;
232
+ }
233
+
234
+ /**
235
+ * A resposta direta do DuckDuckGo (API oficial, sem raspagem).
236
+ *
237
+ * Não é busca de links: é o resumo que ele já tem sobre o assunto, mais os
238
+ * tópicos relacionados. Responde 200 onde a página de busca devolve 202.
239
+ */
240
+ async function buscarRespostaDDG(consulta, limite) {
241
+ const bruto = await pegar(`https://api.duckduckgo.com/?q=${encodeURIComponent(consulta)}&format=json&no_html=1&skip_disambig=1`);
242
+ if (!bruto) return [];
243
+ let j;
244
+ try { j = JSON.parse(bruto); } catch { return []; }
245
+ const achados = [];
246
+ if (j.AbstractURL) achados.push({ titulo: j.Heading || consulta, url: j.AbstractURL, nota: j.AbstractSource });
247
+ for (const t of j.RelatedTopics || []) {
248
+ if (achados.length >= limite) break;
249
+ if (t.FirstURL) achados.push({ titulo: (t.Text || '').slice(0, 120), url: t.FirstURL });
250
+ }
251
+ return limparLinks(achados, limite);
252
+ }
253
+
194
254
  /** Bing (HTML) — a reserva quando o DDG não responde. */
195
255
  async function buscarBing(consulta, limite) {
196
256
  const html = await pegar(`https://www.bing.com/search?q=${encodeURIComponent(consulta)}&setlang=en`);
@@ -218,13 +278,24 @@ async function buscarSearx(consulta, limite) {
218
278
  * pesquisa inteira. Sem chave de API em nenhuma delas.
219
279
  */
220
280
  async function buscar(consulta, limite = MAX_POR_FONTE) {
221
- for (const porta of [buscarDDG, buscarBing, buscarSearx]) {
281
+ // Ordem deliberada: primeiro os buscadores raspados (dão os links mais
282
+ // frescos, quando respondem), depois as APIs que NUNCA bloqueiam. Antes a
283
+ // fila acabava no Searx e, com os três calados, a pesquisa inteira voltava
284
+ // vazia — mesmo havendo resposta boa na Wikipédia.
285
+ const portas = [buscarDDG, buscarBing, buscarSearx, buscarRespostaDDG, buscarWikipedia];
286
+ const juntos = [];
287
+ for (const porta of portas) {
222
288
  try {
223
289
  const achados = await porta(consulta, limite);
224
- if (achados.length) return achados;
290
+ for (const a of achados) {
291
+ if (!juntos.some((j) => j.url === a.url)) juntos.push(a);
292
+ }
293
+ // Os raspados, quando respondem, já bastam. As APIs entram para
294
+ // completar quando eles vieram vazios.
295
+ if (juntos.length >= limite) break;
225
296
  } catch {}
226
297
  }
227
- return [];
298
+ return juntos.slice(0, limite);
228
299
  }
229
300
 
230
301
  /** Respostas do StackOverflow — a API pública, sem chave. */
@@ -284,7 +355,16 @@ async function pesquisar({ consulta, paginas = 3 }) {
284
355
  const candidatos = [...web, ...so, ...docsConhecidas(termo)]
285
356
  .slice(0, Math.max(2, Math.min(8, paginas + 4)));
286
357
  if (!candidatos.length) {
287
- return { ok: false, error: `nada encontrado para "${termo}" (sem internet?)`, consulta: termo };
358
+ return {
359
+ ok: false,
360
+ error: `Nenhuma fonte respondeu sobre "${termo}".`,
361
+ consulta: termo,
362
+ // "sem internet?" era o palpite errado, e mandava o usuário conferir
363
+ // a conexão dele: medido, os buscadores devolvem 202/302 e página
364
+ // vazia para quem não parece navegador, com a rede perfeita.
365
+ provavelCausa: 'Os buscadores costumam recusar chamadas automáticas (bloqueio ou limite por excesso de buscas seguidas), e não a sua conexão.',
366
+ comoCorrigir: 'Tente OUTRA consulta, mais curta e específica. Se souber o site do assunto, vá direto nele com browser_open + browser_read — é o caminho mais confiável para fato de empresa.',
367
+ };
288
368
  }
289
369
 
290
370
  const lidas = [];
@@ -316,4 +396,4 @@ async function pesquisar({ consulta, paginas = 3 }) {
316
396
  };
317
397
  }
318
398
 
319
- module.exports = { pesquisar, buscar, stackoverflow, textoDe };
399
+ module.exports = { pesquisar, buscar, stackoverflow, textoDe, buscarWikipedia, buscarRespostaDDG };
package/lib/studio.js CHANGED
@@ -41,8 +41,31 @@ const BASE = `http://127.0.0.1:${PORTA}`;
41
41
  // criou) nunca são tocados.
42
42
  const COPIAVEIS = new Set(['server.py', 'web', 'ferramentas', 'README.md', 'PRIMOCODE.md']);
43
43
 
44
+ /**
45
+ * Onde está o Python 3 desta máquina.
46
+ *
47
+ * Procurar só por `python3` no PATH não basta, e isso custou uma tarefa
48
+ * inteira num Mac: o comando existe mas é o atalho da Apple, que sem as
49
+ * Ferramentas de Linha de Comando instaladas sai com erro em vez de versão —
50
+ * e o usuário lia "Python 3 não está instalado" com o Python instalado pelo
51
+ * Homebrew ali do lado, fora do PATH daquele terminal.
52
+ *
53
+ * Então: nomes comuns, versões numeradas, e os caminhos absolutos onde o
54
+ * Python realmente mora em cada sistema.
55
+ */
44
56
  function pythonCmd() {
45
- const candidatos = os.platform() === 'win32' ? ['python', 'py', 'python3'] : ['python3', 'python'];
57
+ const numeradas = ['python3.13', 'python3.12', 'python3.11', 'python3.10', 'python3.9'];
58
+ let candidatos;
59
+ if (os.platform() === 'win32') {
60
+ candidatos = ['python', 'py', 'python3', ...numeradas];
61
+ } else {
62
+ candidatos = [
63
+ 'python3', 'python', ...numeradas,
64
+ // Homebrew (Apple Silicon e Intel) e o do sistema — o PATH de um
65
+ // terminal aberto por atalho às vezes não tem nenhum dos dois.
66
+ '/opt/homebrew/bin/python3', '/usr/local/bin/python3', '/usr/bin/python3',
67
+ ];
68
+ }
46
69
  for (const c of candidatos) {
47
70
  try {
48
71
  const v = execSync(`${c} --version`, { stdio: ['ignore', 'pipe', 'pipe'], timeout: 5000 }).toString();
@@ -130,10 +153,16 @@ async function garantirNoAr() {
130
153
  if (!py) {
131
154
  return {
132
155
  ok: false,
133
- error: 'O Studio precisa do Python 3, que não está instalado.',
156
+ error: 'O Studio precisa do Python 3, e não achei nenhum nesta máquina.',
134
157
  comoResolver: os.platform() === 'darwin'
135
- ? 'Instale com: brew install python3 (ou baixe em python.org).'
136
- : 'Instale o Python 3 (python.org) e tente de novo.',
158
+ ? 'No Mac, o mais rápido é: xcode-select --install (traz o Python 3 da Apple). '
159
+ + 'Ou, se você usa Homebrew: brew install python. Depois é só pedir de novo.'
160
+ : os.platform() === 'win32'
161
+ ? 'Instale em python.org/downloads (marque "Add python.exe to PATH" no instalador).'
162
+ : 'Instale o Python 3 pelo gerenciador da sua distribuição (ex: sudo apt install python3).',
163
+ // Sem isto o agente responde "não consegui" e para. O trabalho
164
+ // pedido quase sempre tem um caminho que não depende do Studio.
165
+ enquantoIsso: 'Não desista da tarefa: dá para entregar em HTML com write_file e abrir com open_in_browser.',
137
166
  };
138
167
  }
139
168
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "primocode",
3
- "version": "8.2.1",
3
+ "version": "8.2.2",
4
4
  "description": "PrimoCode — agente de engenharia com IA e cursor próprio. Modelos grátis. Cria arquivos, roda comandos, controla navegador e desktop: abre apps, clica em botões e ícones pelo nome, digita e usa atalhos.",
5
5
  "main": "bin/primocode.js",
6
6
  "bin": {