@danhachuel/leadmaster 0.1.15 → 0.1.17

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 (27) hide show
  1. package/CHANGELOG.md +63 -0
  2. package/backend/__pycache__/main.cpython-311.pyc +0 -0
  3. package/backend/main.py +24 -0
  4. package/backend/services/__pycache__/__init__.cpython-311.pyc +0 -0
  5. package/backend/services/__pycache__/agents.cpython-311.pyc +0 -0
  6. package/backend/services/__pycache__/crm_agent.cpython-311.pyc +0 -0
  7. package/backend/services/__pycache__/extractors.cpython-311.pyc +0 -0
  8. package/backend/services/__pycache__/flags.cpython-311.pyc +0 -0
  9. package/backend/services/__pycache__/llm.cpython-311.pyc +0 -0
  10. package/backend/services/__pycache__/storage.cpython-311.pyc +0 -0
  11. package/backend/services/agents.py +4 -1
  12. package/backend/tests/__pycache__/conftest.cpython-311-pytest-8.4.2.pyc +0 -0
  13. package/backend/tests/__pycache__/test_campaigns_routes.cpython-311-pytest-8.4.2.pyc +0 -0
  14. package/backend/tests/__pycache__/test_cnpj_extractor.cpython-311-pytest-8.4.2.pyc +0 -0
  15. package/backend/tests/__pycache__/test_crm_routes.cpython-311-pytest-8.4.2.pyc +0 -0
  16. package/backend/tests/__pycache__/test_instagram_extractor.cpython-311-pytest-8.4.2.pyc +0 -0
  17. package/backend/tests/__pycache__/test_launcher_protections.cpython-311-pytest-8.4.2.pyc +0 -0
  18. package/backend/tests/__pycache__/test_maps_extractor.cpython-311-pytest-8.4.2.pyc +0 -0
  19. package/backend/tests/__pycache__/test_process_flags.cpython-311-pytest-8.4.2.pyc +0 -0
  20. package/backend/tests/test_campaigns_routes.py +71 -0
  21. package/backend/tests/test_launcher_protections.py +25 -0
  22. package/frontend/dist/assets/{index-Bf28IekR.css → index-BKo_OphM.css} +1 -1
  23. package/frontend/dist/assets/index-BWyDEt5a.js +90 -0
  24. package/frontend/dist/index.html +2 -2
  25. package/package.json +1 -1
  26. package/scripts/cli.mjs +66 -5
  27. package/frontend/dist/assets/index-CAvEEMWg.js +0 -90
@@ -5,8 +5,8 @@
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
6
  <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
7
7
  <title>LeadMaster — Painel</title>
8
- <script type="module" crossorigin src="/assets/index-CAvEEMWg.js"></script>
9
- <link rel="stylesheet" crossorigin href="/assets/index-Bf28IekR.css">
8
+ <script type="module" crossorigin src="/assets/index-BWyDEt5a.js"></script>
9
+ <link rel="stylesheet" crossorigin href="/assets/index-BKo_OphM.css">
10
10
  </head>
11
11
  <body class="bg-gray-100 min-h-screen">
12
12
  <div id="root"></div>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@danhachuel/leadmaster",
3
- "version": "0.1.15",
3
+ "version": "0.1.17",
4
4
  "description": "SaaS local de prospecção e automação: CRM, extratores de leads e agentes de IA — 100% local.",
5
5
  "license": "MIT",
6
6
  "author": "Dan Hachuel",
package/scripts/cli.mjs CHANGED
@@ -23,7 +23,7 @@
23
23
  */
24
24
 
25
25
  import { spawn, spawnSync, execSync } from 'node:child_process';
26
- import { existsSync, mkdirSync, readFileSync, appendFileSync } from 'node:fs';
26
+ import { existsSync, mkdirSync, readFileSync, appendFileSync, rmSync, cpSync } from 'node:fs';
27
27
  import { homedir, platform } from 'node:os';
28
28
  import net from 'node:net';
29
29
  import path from 'node:path';
@@ -87,6 +87,41 @@ function ensureHome(home) {
87
87
  }
88
88
  }
89
89
 
90
+ // ---------------------------------------------------------------------------
91
+ // Runtime permanente (regra inviolável do AGENTS.md)
92
+ //
93
+ // O servidor NUNCA executa a partir do cache do npx (`_npx\...`): um processo
94
+ // rodando de lá segura o diretório e o npx falha com EBUSY na próxima
95
+ // instalação — antes mesmo de qualquer código do launcher rodar (por isso o
96
+ // kill switch sozinho não resolvia). Solução: `install` copia backend/ e
97
+ // frontend/dist/ para <home>/.runtime e `start` executa de lá. O cache do npx
98
+ // nunca é tocado por processos nossos.
99
+ // ---------------------------------------------------------------------------
100
+
101
+ function runtimeDir(home) {
102
+ return path.join(home, '.runtime');
103
+ }
104
+
105
+ function runtimePronto(home) {
106
+ return existsSync(path.join(runtimeDir(home), 'backend', 'main.py'));
107
+ }
108
+
109
+ /** Copia backend/ e frontend/dist/ do pacote para o runtime permanente. */
110
+ function copiarParaRuntime(home) {
111
+ const runtime = runtimeDir(home);
112
+ rmSync(runtime, { recursive: true, force: true });
113
+ mkdirSync(runtime, { recursive: true });
114
+ cpSync(path.join(PKG_ROOT, 'backend'), path.join(runtime, 'backend'), {
115
+ recursive: true,
116
+ filter: (src) => !src.includes('__pycache__') && !src.endsWith('.pyc') && !src.includes(`${path.sep}.venv`),
117
+ });
118
+ const dist = path.join(PKG_ROOT, 'frontend', 'dist');
119
+ if (existsSync(path.join(dist, 'index.html'))) {
120
+ cpSync(dist, path.join(runtime, 'frontend', 'dist'), { recursive: true });
121
+ }
122
+ return runtime;
123
+ }
124
+
90
125
  // ---------------------------------------------------------------------------
91
126
  // Python
92
127
  // ---------------------------------------------------------------------------
@@ -471,6 +506,14 @@ async function cmdInstall(args) {
471
506
  // Frontend (modo repositório): idempotente, só instala o que faltar.
472
507
  instalarModoFonte();
473
508
 
509
+ // Runtime permanente: reutiliza a porta inicial já validada pelo kill switch
510
+ // acima, apaga o .runtime anterior (arquivos liberados) e copia esta versão
511
+ // do pacote para lá. Daqui em diante o servidor NUNCA roda de dentro do
512
+ // cache do npx — o EBUSY acaba.
513
+ await encerrarInstanciaAnterior(portaInicial);
514
+ const runtime = copiarParaRuntime(home);
515
+ console.log(`✓ Runtime permanente preparado: ${runtime}`);
516
+
474
517
  console.log('');
475
518
  console.log('Instalação concluída. Para usar (comando único):');
476
519
  console.log(' npx --yes @danhachuel/leadmaster@latest');
@@ -524,20 +567,28 @@ function cmdStart(args) {
524
567
 
525
568
  // Comando único (estilo Thunderbolt): o start também se auto-prepara.
526
569
  // Modo fonte sem deps/build? Instala só o que faltar antes de subir.
527
- // Pacote npx: UI já vem pronta (dist) e nada disto roda.
528
570
  if (existsSync(path.join(PKG_ROOT, 'frontend', 'package.json'))) {
529
571
  instalarModoFonte();
530
- } else if (!existsSync(path.join(PKG_ROOT, 'frontend', 'dist', 'index.html'))) {
531
- console.warn('⚠ Interface construída não encontrada (frontend/dist).');
532
572
  }
533
573
 
574
+ // Kill switch: se uma instância ANTIGA (runtime anterior) ainda ocupar a
575
+ // porta, ela é encerrada antes da nova assumir — nunca reutilizamos.
576
+ await encerrarInstanciaAnterior(portaInicial);
577
+
578
+ // Runtime permanente (regra inviolável): o servidor NUNCA roda de dentro
579
+ // do cache do npx (EBUSY na próxima instalação). Sem runtime pronto, o
580
+ // start se auto-aprovisiona copiando para <home>/.runtime; com runtime
581
+ // pronto, reutiliza (sem recopiar nada).
582
+ const runtime = runtimePronto(home) ? runtimeDir(home) : copiarParaRuntime(home);
583
+ console.log(`✓ Executando a partir do runtime permanente: ${runtime}`);
584
+
534
585
  // Workers nascem em grupo de processos próprio (detached: true =
535
586
  // CREATE_NEW_PROCESS_GROUP no Windows): sinais de Ctrl+C do terminal
536
587
  // (MobaXterm, VS Code, perda de foco) matam apenas o launcher — o
537
588
  // servidor Python continua rodando e só é encerrado pelo kill()
538
589
  // controlado abaixo (ou sobrevive para a próxima execução detectar).
539
590
  const child = spawn(python.cmd, [...python.args, '-m', 'uvicorn', 'main:app', '--host', '127.0.0.1', '--port', String(porta), '--no-access-log'], {
540
- cwd: path.join(PKG_ROOT, 'backend'),
591
+ cwd: path.join(runtime, 'backend'),
541
592
  env: {
542
593
  ...process.env,
543
594
  LEADMASTER_HOME: home,
@@ -674,6 +725,7 @@ async function cmdDoctor(args) {
674
725
  console.log(`Servidor UI ........ ${ativaEm ? `rodando em http://127.0.0.1:${ativaEm}` : `parado (porta inicial ${portaInicial})`}`);
675
726
  const hasDist = existsSync(path.join(PKG_ROOT, 'frontend', 'dist', 'index.html'));
676
727
  console.log(`Interface (dist) ... ${hasDist ? 'presente' : 'ausente (modo dev ou rebuild necessário)'}`);
728
+ console.log(`Runtime permanente . ${runtimePronto(home) ? `pronto (${runtimeDir(home)})` : 'ausente — rode install'}`);
677
729
  console.log('');
678
730
  console.log('Diagnóstico concluído.');
679
731
  }
@@ -721,6 +773,15 @@ mkdirSync(LOG_DIR, { recursive: true });
721
773
  const CONSOLE_LOG = path.join(LOG_DIR, 'launcher_console.log');
722
774
  [process.stdout, process.stderr].forEach((stream) => guardConsoleStream(stream, CONSOLE_LOG));
723
775
 
776
+ // Nunca manter o cwd dentro do cache do npx: o launcher nasce com o cwd de
777
+ // onde o usuário rodou o comando; assim que sabemos o home, saímos de lá —
778
+ // nenhum processo nosso segura diretório do cache (fim do EBUSY no npx).
779
+ try {
780
+ process.chdir(homeLauncher);
781
+ } catch {
782
+ /* melhor esforço */
783
+ }
784
+
724
785
  switch (command) {
725
786
  case 'install':
726
787
  // cmdInstall é async (kill switch): a promise mantém o evento vivo até o