agroplan-ai-cli 1.0.20 → 1.0.22

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.
@@ -0,0 +1,13 @@
1
+ {
2
+ "cli_version": "1.0.22",
3
+ "backend_template_version": "1.0.22",
4
+ "zarc_index_version": "2025-2026-fast-index-v2",
5
+ "features": [
6
+ "zarc_fast_index",
7
+ "zarc_fallback_sorgo_mandioca",
8
+ "soil_normalization_misto_siltoso",
9
+ "climate_real_data",
10
+ "hybrid_mode"
11
+ ],
12
+ "generated_at": "2026-05-09T18:30:00Z"
13
+ }
@@ -151,7 +151,15 @@ def health():
151
151
  from providers.zarc_provider import get_zarc_status
152
152
  zarc_status = get_zarc_status()
153
153
 
154
- return {
154
+ # Carregar VERSION.json se existir
155
+ version_info = {}
156
+ version_path = os.path.join(os.path.dirname(__file__), "VERSION.json")
157
+ if os.path.exists(version_path):
158
+ import json
159
+ with open(version_path, 'r') as f:
160
+ version_info = json.load(f)
161
+
162
+ response = {
155
163
  "status": "healthy",
156
164
  "culturas": len(culturas),
157
165
  "talhoes": len(talhoes),
@@ -164,9 +172,169 @@ def health():
164
172
  },
165
173
  "provider_cache": provider_cache_stats
166
174
  }
175
+
176
+ # Adicionar info de versão se disponível
177
+ if version_info:
178
+ response["backend_template_version"] = version_info.get("backend_template_version")
179
+ response["zarc_index_version"] = version_info.get("zarc_index_version")
180
+
181
+ return response
167
182
  except Exception as e:
168
183
  raise HTTPException(status_code=500, detail=str(e))
169
184
 
185
+ @app.get("/debug/version")
186
+ def debug_version():
187
+ """Retorna informações detalhadas de versão e configuração do backend"""
188
+ try:
189
+ import json
190
+ from providers.zarc_provider import (
191
+ ZARC_FAST_INDEX_ENABLED,
192
+ ZARC_ALLOW_FULL_SCAN,
193
+ load_zarc_index,
194
+ get_zarc_fallback
195
+ )
196
+
197
+ # Carregar VERSION.json
198
+ version_info = {}
199
+ version_path = os.path.join(os.path.dirname(__file__), "VERSION.json")
200
+ if os.path.exists(version_path):
201
+ with open(version_path, 'r') as f:
202
+ version_info = json.load(f)
203
+
204
+ # Verificar índice ZARC
205
+ zarc_index = load_zarc_index()
206
+ zarc_index_info = {}
207
+ zarc_index_keys_sample = []
208
+
209
+ if zarc_index:
210
+ records = zarc_index.get("records", {})
211
+ zarc_index_info = {
212
+ "exists": True,
213
+ "total_records": len(records),
214
+ "generated_at": zarc_index.get("generated_at"),
215
+ "source": zarc_index.get("source")
216
+ }
217
+ # Sample de 10 primeiras chaves
218
+ zarc_index_keys_sample = list(records.keys())[:10]
219
+ else:
220
+ zarc_index_info = {"exists": False}
221
+
222
+ # Verificar fallbacks
223
+ fallback_data = get_zarc_fallback()
224
+ culturas_fallback = set(item.get("cultura") for item in fallback_data)
225
+
226
+ return {
227
+ "api_version": "5.0.0",
228
+ "backend_file": __file__,
229
+ "backend_template_version": version_info.get("backend_template_version", "unknown"),
230
+ "cli_version": version_info.get("cli_version", "unknown"),
231
+ "zarc_index_version": version_info.get("zarc_index_version", "unknown"),
232
+ "features": version_info.get("features", []),
233
+ "generated_at": version_info.get("generated_at"),
234
+ "zarc_config": {
235
+ "fast_index_enabled": ZARC_FAST_INDEX_ENABLED,
236
+ "allow_full_scan": ZARC_ALLOW_FULL_SCAN,
237
+ "index": zarc_index_info,
238
+ "index_keys_sample": zarc_index_keys_sample
239
+ },
240
+ "zarc_fallback": {
241
+ "total_records": len(fallback_data),
242
+ "culturas": sorted(list(culturas_fallback)),
243
+ "has_sorgo": "sorgo" in culturas_fallback,
244
+ "has_mandioca": "mandioca" in culturas_fallback
245
+ },
246
+ "data_mode": DATA_MODE,
247
+ "weather_provider": WEATHER_PROVIDER
248
+ }
249
+ except Exception as e:
250
+ raise HTTPException(status_code=500, detail=str(e))
251
+
252
+ @app.get("/debug/zarc-coverage")
253
+ def debug_zarc_coverage(
254
+ uf: Optional[str] = Query(None, description="UF"),
255
+ municipio: Optional[str] = Query(None, description="Município"),
256
+ safra: str = Query("2025/2026", description="Safra")
257
+ ):
258
+ """Retorna diagnóstico detalhado de cobertura ZARC"""
259
+ try:
260
+ import json
261
+ from core.zarc_adapter import enriquecer_plano_com_zarc
262
+
263
+ # Usar o mesmo plano do dashboard
264
+ resultado_ag = get_ag_cacheado(objetivo="equilibrado")
265
+
266
+ # Enriquecer com ZARC
267
+ resultado_enriquecido = enriquecer_plano_com_zarc(
268
+ resultado_ag,
269
+ uf=uf,
270
+ municipio=municipio,
271
+ safra=safra
272
+ )
273
+
274
+ # Analisar cobertura
275
+ detalhes = []
276
+ culturas_com_zarc = 0
277
+ culturas_fallback = 0
278
+ culturas_unavailable = 0
279
+
280
+ for item in resultado_enriquecido["plano"]:
281
+ zarc = item.get("zarc", {})
282
+
283
+ detalhes.append({
284
+ "talhao": item.get("talhao"),
285
+ "cultura": item.get("cultura"),
286
+ "solo_original": item.get("solo"),
287
+ "zarc_ativo": zarc.get("ativo", False),
288
+ "zarc_source": zarc.get("source"),
289
+ "zarc_fallback": zarc.get("fallback", False),
290
+ "zarc_message": zarc.get("message"),
291
+ "zarc_janela": zarc.get("janela_plantio")
292
+ })
293
+
294
+ if zarc.get("ativo"):
295
+ culturas_com_zarc += 1
296
+ if zarc.get("fallback"):
297
+ culturas_fallback += 1
298
+ else:
299
+ culturas_unavailable += 1
300
+
301
+ total_culturas = len(resultado_enriquecido["plano"])
302
+ coverage_percent = (culturas_com_zarc / total_culturas * 100) if total_culturas > 0 else 0
303
+
304
+ # Carregar VERSION.json
305
+ version_info = {}
306
+ version_path = os.path.join(os.path.dirname(__file__), "VERSION.json")
307
+ if os.path.exists(version_path):
308
+ with open(version_path, 'r') as f:
309
+ version_info = json.load(f)
310
+
311
+ # Verificar índice ZARC
312
+ from providers.zarc_provider import load_zarc_index
313
+ zarc_index = load_zarc_index()
314
+ zarc_index_total = len(zarc_index.get("records", {})) if zarc_index else 0
315
+
316
+ return {
317
+ "uf": uf,
318
+ "municipio": municipio,
319
+ "safra": safra,
320
+ "summary": {
321
+ "culturas_com_zarc": culturas_com_zarc,
322
+ "culturas_fallback": culturas_fallback,
323
+ "culturas_unavailable": culturas_unavailable,
324
+ "total_culturas": total_culturas,
325
+ "coverage_percent": round(coverage_percent, 1)
326
+ },
327
+ "backend_info": {
328
+ "backend_template_version": version_info.get("backend_template_version", "unknown"),
329
+ "zarc_index_version": version_info.get("zarc_index_version", "unknown"),
330
+ "zarc_index_total_records": zarc_index_total
331
+ },
332
+ "details": detalhes
333
+ }
334
+ except Exception as e:
335
+ import traceback
336
+ raise HTTPException(status_code=500, detail=f"{str(e)}\n{traceback.format_exc()}")
337
+
170
338
  @app.get("/dados/clima")
171
339
  def get_clima(
172
340
  lat: Optional[float] = Query(None, description="Latitude"),
@@ -850,14 +1018,37 @@ def limpar_cache(request: Request):
850
1018
  detail="Token de administração inválido ou ausente. Use header X-Cache-Token."
851
1019
  )
852
1020
 
853
- # Limpa cache
1021
+ # Limpa cache de resultados
854
1022
  global _resultados_cache
855
1023
  items_removidos = len(_resultados_cache)
856
1024
  _resultados_cache.clear()
857
1025
 
1026
+ # Limpa cache de provedores (weather, etc)
1027
+ provider_items_removidos = 0
1028
+ try:
1029
+ from providers.cache import clear_provider_cache
1030
+ provider_items_removidos = clear_provider_cache()
1031
+ except Exception:
1032
+ pass
1033
+
1034
+ # Limpa cache do índice ZARC em memória
1035
+ zarc_cache_cleared = False
1036
+ try:
1037
+ from providers import zarc_provider
1038
+ if hasattr(zarc_provider, '_zarc_index_cache'):
1039
+ zarc_provider._zarc_index_cache.clear()
1040
+ zarc_cache_cleared = True
1041
+ except Exception:
1042
+ pass
1043
+
858
1044
  return {
859
1045
  "status": "ok",
860
- "message": f"Cache limpo. {items_removidos} itens removidos.",
1046
+ "message": f"Cache limpo completamente.",
1047
+ "details": {
1048
+ "resultados_cache": items_removidos,
1049
+ "provider_cache": provider_items_removidos,
1050
+ "zarc_index_cache": "cleared" if zarc_cache_cleared else "not_found"
1051
+ },
861
1052
  "protected": bool(cache_admin_token)
862
1053
  }
863
1054
 
@@ -78,10 +78,10 @@ def enriquecer_plano_com_zarc(
78
78
  if zarc_data.get("fallback"):
79
79
  tem_fallback = True
80
80
  else:
81
- # ZARC não encontrado
81
+ # ZARC não encontrado - usar mensagem honesta
82
82
  item["zarc"] = {
83
83
  "ativo": False,
84
- "message": zarc_data.get("message") if zarc_data else "ZARC não consultado"
84
+ "message": zarc_data.get("message", "ZARC consultado, mas sem recomendação disponível para esta cultura/região.")
85
85
  }
86
86
 
87
87
  # Determinar source geral
@@ -304,6 +304,36 @@ def normalizar_solo(solo: str) -> str:
304
304
  """Normaliza tipo de solo"""
305
305
  return normalizar_texto(solo)
306
306
 
307
+ def normalizar_solo_zarc(solo: str) -> str:
308
+ """
309
+ Normaliza tipo de solo para busca ZARC
310
+
311
+ Mapeia variações de solo para os tipos reconhecidos pelo ZARC:
312
+ - misto -> medio
313
+ - siltoso -> medio
314
+
315
+ Args:
316
+ solo: Tipo de solo original
317
+
318
+ Returns:
319
+ Tipo de solo normalizado para ZARC
320
+ """
321
+ if not solo:
322
+ return ""
323
+
324
+ solo_norm = normalizar_solo(solo)
325
+
326
+ # Mapeamento de solos para ZARC
327
+ mapa_zarc = {
328
+ "arenoso": "arenoso",
329
+ "medio": "medio",
330
+ "misto": "medio", # misto -> medio
331
+ "siltoso": "medio", # siltoso -> medio
332
+ "argiloso": "argiloso"
333
+ }
334
+
335
+ return mapa_zarc.get(solo_norm, solo_norm)
336
+
307
337
  def get_cache_path(safra: str) -> str:
308
338
  """Retorna caminho do arquivo de cache para a safra"""
309
339
  os.makedirs(ZARC_CACHE_DIR, exist_ok=True)
@@ -427,15 +457,15 @@ def buscar_zarc_indexado(
427
457
  # Tentar diferentes combinações de solo
428
458
  solos_tentar = []
429
459
  if solo:
430
- solo_norm = normalizar_solo(solo)
460
+ solo_norm = normalizar_solo_zarc(solo) # Usa normalização ZARC (misto->medio, siltoso->medio)
431
461
  # Tentar o solo especificado primeiro, depois outros como fallback
432
- solos_tentar = [solo_norm, "medio", "arenoso", "argiloso", "misto"]
462
+ solos_tentar = [solo_norm, "medio", "arenoso", "argiloso"]
433
463
  # Remover duplicatas mantendo ordem
434
464
  seen = set()
435
465
  solos_tentar = [s for s in solos_tentar if not (s in seen or seen.add(s))]
436
466
  else:
437
467
  # Se não especificou solo, tentar todos (preferir medio/argiloso)
438
- solos_tentar = ["medio", "argiloso", "arenoso", "misto"]
468
+ solos_tentar = ["medio", "argiloso", "arenoso"]
439
469
 
440
470
  # Buscar no índice
441
471
  for solo_test in solos_tentar:
@@ -680,6 +710,48 @@ def get_zarc_fallback() -> List[Dict[str, Any]]:
680
710
  "janela_fim": "31/03",
681
711
  "risco": "baixo",
682
712
  "safra": "2025/2026"
713
+ },
714
+ # Sorgo
715
+ {
716
+ "cultura": "sorgo",
717
+ "uf": "SP",
718
+ "municipio": "ribeirao preto",
719
+ "solo": "medio",
720
+ "janela_inicio": "15/10",
721
+ "janela_fim": "15/12",
722
+ "risco": "medio",
723
+ "safra": "2025/2026"
724
+ },
725
+ {
726
+ "cultura": "sorgo",
727
+ "uf": "MG",
728
+ "municipio": "uberlandia",
729
+ "solo": "medio",
730
+ "janela_inicio": "01/10",
731
+ "janela_fim": "30/11",
732
+ "risco": "medio",
733
+ "safra": "2025/2026"
734
+ },
735
+ # Mandioca
736
+ {
737
+ "cultura": "mandioca",
738
+ "uf": "SP",
739
+ "municipio": "sao paulo",
740
+ "solo": "medio",
741
+ "janela_inicio": "01/09",
742
+ "janela_fim": "31/03",
743
+ "risco": "baixo",
744
+ "safra": "2025/2026"
745
+ },
746
+ {
747
+ "cultura": "mandioca",
748
+ "uf": "PR",
749
+ "municipio": "londrina",
750
+ "solo": "medio",
751
+ "janela_inicio": "15/08",
752
+ "janela_fim": "31/03",
753
+ "risco": "baixo",
754
+ "safra": "2025/2026"
683
755
  }
684
756
  ]
685
757
 
@@ -689,12 +761,14 @@ def buscar_zarc(
689
761
  municipio: Optional[str] = None,
690
762
  solo: Optional[str] = None,
691
763
  safra: str = ZARC_SAFRA_DEFAULT
692
- ) -> Optional[Dict[str, Any]]:
764
+ ) -> Dict[str, Any]:
693
765
  """
694
766
  Busca dados ZARC para cultura/região específica
695
767
 
696
768
  PERFORMANCE: Tenta índice primeiro (rápido), depois streaming (lento)
697
769
 
770
+ SEMPRE retorna um dicionário, nunca None
771
+
698
772
  Args:
699
773
  cultura: Nome da cultura
700
774
  uf: Unidade Federativa (opcional)
@@ -703,7 +777,7 @@ def buscar_zarc(
703
777
  safra: Safra (padrão: 2025/2026)
704
778
 
705
779
  Returns:
706
- Dicionário com dados ZARC ou None se não encontrar
780
+ Dicionário com dados ZARC (sempre retorna, nunca None)
707
781
  """
708
782
  # FAST PATH: Tentar índice primeiro (O(1) lookup)
709
783
  if ZARC_FAST_INDEX_ENABLED:
@@ -718,7 +792,14 @@ def buscar_zarc(
718
792
  return buscar_zarc_fallback(cultura, uf, municipio, solo, safra)
719
793
 
720
794
  # Full scan permitido (desenvolvimento local)
721
- return buscar_zarc_streaming(cultura, uf, municipio, solo, safra)
795
+ resultado_streaming = buscar_zarc_streaming(cultura, uf, municipio, solo, safra)
796
+
797
+ # buscar_zarc_streaming pode retornar None se arquivo não disponível
798
+ # Nesse caso, usar fallback
799
+ if resultado_streaming is None:
800
+ return buscar_zarc_fallback(cultura, uf, municipio, solo, safra)
801
+
802
+ return resultado_streaming
722
803
 
723
804
  def buscar_zarc_streaming(
724
805
  cultura: str,
@@ -737,7 +818,7 @@ def buscar_zarc_streaming(
737
818
  cultura_norm = normalizar_cultura(cultura)
738
819
  uf_norm = normalizar_uf(uf) if uf else None
739
820
  municipio_norm = normalizar_municipio(municipio) if municipio else None
740
- solo_norm = normalizar_solo(solo) if solo else None
821
+ solo_norm = normalizar_solo_zarc(solo) if solo else None # Usa normalização ZARC
741
822
 
742
823
  # Tentar obter arquivo ZARC
743
824
  file_info = ensure_zarc_file(safra)
@@ -768,7 +849,7 @@ def buscar_zarc_streaming(
768
849
  # Solo (se fornecido)
769
850
  if solo_norm:
770
851
  solo_registro = mapear_codigo_solo(registro.get("Cod_Solo", ""))
771
- if normalizar_solo(solo_registro) == solo_norm:
852
+ if normalizar_solo_zarc(solo_registro) == solo_norm: # Usa normalização ZARC
772
853
  score += 2
773
854
 
774
855
  # Manter apenas o melhor match (não acumula lista)
@@ -837,15 +918,17 @@ def buscar_zarc_fallback(
837
918
  municipio: Optional[str] = None,
838
919
  solo: Optional[str] = None,
839
920
  safra: str = ZARC_SAFRA_DEFAULT
840
- ) -> Optional[Dict[str, Any]]:
921
+ ) -> Dict[str, Any]:
841
922
  """
842
923
  Busca ZARC em dados simplificados (fallback)
924
+
925
+ SEMPRE retorna um dicionário, nunca None
843
926
  """
844
927
  # Normalizar parâmetros
845
928
  cultura_norm = normalizar_cultura(cultura)
846
929
  uf_norm = normalizar_uf(uf) if uf else None
847
930
  municipio_norm = normalizar_municipio(municipio) if municipio else None
848
- solo_norm = normalizar_solo(solo) if solo else None
931
+ solo_norm = normalizar_solo_zarc(solo) if solo else None # Usa normalização ZARC
849
932
 
850
933
  # Fallback: usar dados simplificados (lista pequena em memória)
851
934
  fallback_data = get_zarc_fallback()
@@ -869,7 +952,7 @@ def buscar_zarc_fallback(
869
952
  score += 3
870
953
 
871
954
  # Solo (se fornecido)
872
- if solo_norm and normalizar_solo(registro.get("solo", "")) == solo_norm:
955
+ if solo_norm and normalizar_solo_zarc(registro.get("solo", "")) == solo_norm: # Usa normalização ZARC
873
956
  score += 2
874
957
 
875
958
  if score > melhor_score:
@@ -894,4 +977,11 @@ def buscar_zarc_fallback(
894
977
  "observacao": "Dados simplificados locais usados porque o CSV oficial não estava disponível."
895
978
  }
896
979
 
897
- return None
980
+ # Nenhum match encontrado - retornar estado "unavailable" em vez de None
981
+ return {
982
+ "encontrado": False,
983
+ "source": "zarc-unavailable",
984
+ "fallback": False,
985
+ "message": "ZARC consultado, mas nenhuma recomendação foi encontrada para esta cultura, solo e região.",
986
+ "observacao": "A cultura pode não estar disponível no índice ZARC compacto para a região selecionada."
987
+ }
package/dist/index.js CHANGED
@@ -41,6 +41,13 @@ var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
41
41
  var __require = import.meta.require;
42
42
 
43
43
  // src/utils/paths.ts
44
+ var exports_paths = {};
45
+ __export(exports_paths, {
46
+ getProjectPaths: () => getProjectPaths,
47
+ getHomeAgroplanDir: () => getHomeAgroplanDir,
48
+ findProjectRoot: () => findProjectRoot,
49
+ ensureAgroplanDir: () => ensureAgroplanDir
50
+ });
44
51
  import { join, dirname } from "path";
45
52
  import { existsSync, mkdirSync } from "fs";
46
53
  import { homedir } from "os";
@@ -113,6 +120,77 @@ function ensureAgroplanDir() {
113
120
  }
114
121
  var init_paths = () => {};
115
122
 
123
+ // src/utils/process.ts
124
+ var exports_process = {};
125
+ __export(exports_process, {
126
+ savePid: () => savePid,
127
+ removePidFile: () => removePidFile,
128
+ readPid: () => readPid,
129
+ killProcess: () => killProcess,
130
+ isProcessRunning: () => isProcessRunning,
131
+ checkPort: () => checkPort
132
+ });
133
+ import { existsSync as existsSync2, readFileSync, writeFileSync, unlinkSync } from "fs";
134
+ function savePid(pid) {
135
+ const paths = getProjectPaths();
136
+ writeFileSync(paths.pidFile, pid.toString());
137
+ }
138
+ function readPid() {
139
+ const paths = getProjectPaths();
140
+ if (!existsSync2(paths.pidFile)) {
141
+ return null;
142
+ }
143
+ try {
144
+ const pidStr = readFileSync(paths.pidFile, "utf-8").trim();
145
+ return parseInt(pidStr, 10);
146
+ } catch {
147
+ return null;
148
+ }
149
+ }
150
+ function removePidFile() {
151
+ const paths = getProjectPaths();
152
+ if (existsSync2(paths.pidFile)) {
153
+ unlinkSync(paths.pidFile);
154
+ }
155
+ }
156
+ function isProcessRunning(pid) {
157
+ try {
158
+ process.kill(pid, 0);
159
+ return true;
160
+ } catch {
161
+ return false;
162
+ }
163
+ }
164
+ function killProcess(pid) {
165
+ try {
166
+ process.kill(pid, "SIGTERM");
167
+ setTimeout(() => {
168
+ if (isProcessRunning(pid)) {
169
+ try {
170
+ process.kill(pid, "SIGKILL");
171
+ } catch {}
172
+ }
173
+ }, 2000);
174
+ return true;
175
+ } catch {
176
+ return false;
177
+ }
178
+ }
179
+ function checkPort(port) {
180
+ return new Promise((resolve) => {
181
+ try {
182
+ fetch(`http://localhost:${port}/health`, {
183
+ signal: AbortSignal.timeout(1000)
184
+ }).then(() => resolve(true)).catch(() => resolve(false));
185
+ } catch {
186
+ resolve(false);
187
+ }
188
+ });
189
+ }
190
+ var init_process = __esm(() => {
191
+ init_paths();
192
+ });
193
+
116
194
  // src/utils/setup-state.ts
117
195
  var exports_setup_state = {};
118
196
  __export(exports_setup_state, {
@@ -213,68 +291,7 @@ function checkPip() {
213
291
 
214
292
  // src/commands/doctor.ts
215
293
  init_paths();
216
-
217
- // src/utils/process.ts
218
- init_paths();
219
- import { existsSync as existsSync2, readFileSync, writeFileSync, unlinkSync } from "fs";
220
- function savePid(pid) {
221
- const paths = getProjectPaths();
222
- writeFileSync(paths.pidFile, pid.toString());
223
- }
224
- function readPid() {
225
- const paths = getProjectPaths();
226
- if (!existsSync2(paths.pidFile)) {
227
- return null;
228
- }
229
- try {
230
- const pidStr = readFileSync(paths.pidFile, "utf-8").trim();
231
- return parseInt(pidStr, 10);
232
- } catch {
233
- return null;
234
- }
235
- }
236
- function removePidFile() {
237
- const paths = getProjectPaths();
238
- if (existsSync2(paths.pidFile)) {
239
- unlinkSync(paths.pidFile);
240
- }
241
- }
242
- function isProcessRunning(pid) {
243
- try {
244
- process.kill(pid, 0);
245
- return true;
246
- } catch {
247
- return false;
248
- }
249
- }
250
- function killProcess(pid) {
251
- try {
252
- process.kill(pid, "SIGTERM");
253
- setTimeout(() => {
254
- if (isProcessRunning(pid)) {
255
- try {
256
- process.kill(pid, "SIGKILL");
257
- } catch {}
258
- }
259
- }, 2000);
260
- return true;
261
- } catch {
262
- return false;
263
- }
264
- }
265
- function checkPort(port) {
266
- return new Promise((resolve) => {
267
- try {
268
- fetch(`http://localhost:${port}/health`, {
269
- signal: AbortSignal.timeout(1000)
270
- }).then(() => resolve(true)).catch(() => resolve(false));
271
- } catch {
272
- resolve(false);
273
- }
274
- });
275
- }
276
-
277
- // src/commands/doctor.ts
294
+ init_process();
278
295
  init_setup_state();
279
296
  async function doctorCommand() {
280
297
  console.log(`\uD83D\uDD0D AgroPlan AI - Diagn\xF3stico do Sistema
@@ -305,20 +322,37 @@ async function doctorCommand() {
305
322
  \uD83D\uDEE0\uFE0F Setup Local:`);
306
323
  const setupComplete = isSetupComplete();
307
324
  const setupState = readSetupState();
308
- let currentVersion = "1.0.20";
325
+ let currentVersion = "1.0.21";
309
326
  try {
310
327
  const packagePath = __require.resolve("agroplan-ai-cli/package.json");
311
328
  const packageJson = __require(packagePath);
312
- currentVersion = packageJson.version || "1.0.20";
329
+ currentVersion = packageJson.version || "1.0.21";
313
330
  } catch {
314
- currentVersion = "1.0.20";
331
+ currentVersion = "1.0.21";
315
332
  }
316
333
  if (setupComplete && setupState) {
317
334
  const installedVersion = setupState.version || "unknown";
318
335
  console.log(" \u2705 Setup conclu\xEDdo");
319
336
  console.log(` \uD83D\uDCC5 Instalado em: ${new Date(setupState.installedAt).toLocaleString()}`);
320
- console.log(` \uD83D\uDCE6 Vers\xE3o: ${installedVersion}`);
337
+ console.log(` \uD83D\uDCE6 Vers\xE3o CLI: ${installedVersion}`);
321
338
  console.log(` \uD83D\uDC0D Python: ${setupState.python}`);
339
+ try {
340
+ const { getHomeAgroplanDir: getHomeAgroplanDir2 } = await Promise.resolve().then(() => (init_paths(), exports_paths));
341
+ const homeDir = getHomeAgroplanDir2();
342
+ const versionPath = `${homeDir}/backend/VERSION.json`;
343
+ const { existsSync: existsSync5, readFileSync: readFileSync3 } = await import("fs");
344
+ if (existsSync5(versionPath)) {
345
+ const versionContent = readFileSync3(versionPath, "utf-8");
346
+ const versionInfo = JSON.parse(versionContent);
347
+ console.log(` \uD83D\uDCE6 Backend template: ${versionInfo.backend_template_version || "unknown"}`);
348
+ console.log(` \uD83D\uDDC2\uFE0F ZARC index: ${versionInfo.zarc_index_version || "unknown"}`);
349
+ if (versionInfo.features && versionInfo.features.length > 0) {
350
+ console.log(` \u2728 Features: ${versionInfo.features.slice(0, 3).join(", ")}${versionInfo.features.length > 3 ? "..." : ""}`);
351
+ }
352
+ } else {
353
+ console.log(" \u26A0\uFE0F VERSION.json n\xE3o encontrado no backend local");
354
+ }
355
+ } catch (error) {}
322
356
  if (installedVersion !== currentVersion) {
323
357
  console.log(`
324
358
  \u26A0\uFE0F API local desatualizada!`);
@@ -452,6 +486,7 @@ Correja os problemas acima:`);
452
486
 
453
487
  // src/commands/serve.ts
454
488
  init_paths();
489
+ init_process();
455
490
  import { existsSync as existsSync5, readFileSync as readFileSync3, openSync, closeSync } from "fs";
456
491
  import { spawn } from "child_process";
457
492
  async function serveOnCommand() {
@@ -471,13 +506,13 @@ async function serveOnCommand() {
471
506
  const { readSetupState: readSetupState2 } = await Promise.resolve().then(() => (init_setup_state(), exports_setup_state));
472
507
  const setupState = readSetupState2();
473
508
  if (setupState) {
474
- let currentVersion = "1.0.20";
509
+ let currentVersion = "1.0.21";
475
510
  try {
476
511
  const packagePath = __require.resolve("agroplan-ai-cli/package.json");
477
512
  const packageJson = __require(packagePath);
478
- currentVersion = packageJson.version || "1.0.20";
513
+ currentVersion = packageJson.version || "1.0.21";
479
514
  } catch {
480
- currentVersion = "1.0.20";
515
+ currentVersion = "1.0.21";
481
516
  }
482
517
  const installedVersion = setupState.version || "unknown";
483
518
  if (installedVersion !== currentVersion) {
@@ -738,13 +773,13 @@ async function setupCommand(force = false, pythonPath) {
738
773
  const homeDir = getHomeAgroplanDir();
739
774
  const backendDir = join3(homeDir, "backend");
740
775
  console.log(`\uD83D\uDCC1 Diret\xF3rio de instala\xE7\xE3o: ${homeDir}`);
741
- let currentVersion = "1.0.20";
776
+ let currentVersion = "1.0.21";
742
777
  try {
743
778
  const packagePath = __require.resolve("agroplan-ai-cli/package.json");
744
779
  const packageJson = __require(packagePath);
745
- currentVersion = packageJson.version || "1.0.20";
780
+ currentVersion = packageJson.version || "1.0.21";
746
781
  } catch {
747
- currentVersion = "1.0.20";
782
+ currentVersion = "1.0.21";
748
783
  }
749
784
  if (!force && isSetupComplete()) {
750
785
  const setupState = (init_setup_state(), __toCommonJS(exports_setup_state)).readSetupState();
@@ -885,13 +920,13 @@ async function setupCommand(force = false, pythonPath) {
885
920
  return;
886
921
  }
887
922
  console.log(" \u2705 Servidor web configurado");
888
- let version = "1.0.20";
923
+ let version = "1.0.21";
889
924
  try {
890
925
  const packagePath = __require.resolve("agroplan-ai-cli/package.json");
891
926
  const packageJson = __require(packagePath);
892
- version = packageJson.version || "1.0.20";
927
+ version = packageJson.version || "1.0.21";
893
928
  } catch {
894
- version = "1.0.20";
929
+ version = "1.0.21";
895
930
  }
896
931
  saveSetupState({
897
932
  version,
@@ -914,6 +949,7 @@ async function setupCommand(force = false, pythonPath) {
914
949
 
915
950
  // src/commands/update.ts
916
951
  init_setup_state();
952
+ init_process();
917
953
  async function updateCommand() {
918
954
  console.log(`\uD83D\uDD04 Atualizando API local do AgroPlan AI...
919
955
  `);
@@ -932,13 +968,13 @@ async function updateCommand() {
932
968
  console.log(" agroplan setup --force");
933
969
  return;
934
970
  }
935
- let currentVersion = "1.0.20";
971
+ let currentVersion = "1.0.21";
936
972
  try {
937
973
  const packagePath = __require.resolve("agroplan-ai-cli/package.json");
938
974
  const packageJson = __require(packagePath);
939
- currentVersion = packageJson.version || "1.0.20";
975
+ currentVersion = packageJson.version || "1.0.21";
940
976
  } catch {
941
- currentVersion = "1.0.20";
977
+ currentVersion = "1.0.21";
942
978
  }
943
979
  const installedVersion = setupState.version || "unknown";
944
980
  console.log(`\uD83D\uDCE6 Vers\xE3o instalada: ${installedVersion}`);
@@ -962,12 +998,62 @@ async function updateCommand() {
962
998
  } else {
963
999
  console.log("1\uFE0F\u20E3 API local n\xE3o est\xE1 rodando");
964
1000
  }
965
- console.log(`2\uFE0F\u20E3 Reinstalando backend atualizado...
1001
+ const { checkPort: checkPort2 } = await Promise.resolve().then(() => (init_process(), exports_process));
1002
+ const portInUse = await checkPort2(8000);
1003
+ if (portInUse) {
1004
+ console.log(`
1005
+ \u26A0\uFE0F Porta 8000 ainda est\xE1 ocupada por outro processo!`);
1006
+ console.log(`
1007
+ \uD83D\uDCA1 Para identificar e encerrar o processo:`);
1008
+ console.log(" netstat -ano | findstr :8000");
1009
+ console.log(" taskkill /PID <PID> /F");
1010
+ console.log(`
1011
+ Depois rode novamente: agroplan update`);
1012
+ return;
1013
+ }
1014
+ console.log("2\uFE0F\u20E3 Removendo backend antigo...");
1015
+ try {
1016
+ const { getHomeAgroplanDir: getHomeAgroplanDir2 } = await Promise.resolve().then(() => (init_paths(), exports_paths));
1017
+ const { existsSync: existsSync7, rmSync: rmSync2 } = await import("fs");
1018
+ const { join: join4 } = await import("path");
1019
+ const homeDir = getHomeAgroplanDir2();
1020
+ const backendDir = join4(homeDir, "backend");
1021
+ if (existsSync7(backendDir)) {
1022
+ rmSync2(backendDir, { recursive: true, force: true });
1023
+ console.log(" \u2705 Backend antigo removido");
1024
+ } else {
1025
+ console.log(" \u2139\uFE0F Nenhum backend antigo encontrado");
1026
+ }
1027
+ } catch (error) {
1028
+ console.log(` \u26A0\uFE0F Erro ao remover backend: ${error}`);
1029
+ }
1030
+ console.log("");
1031
+ console.log(`3\uFE0F\u20E3 Instalando backend atualizado...
966
1032
  `);
967
1033
  const pythonPath = setupState.pythonPath || undefined;
968
1034
  await setupCommand(true, pythonPath);
969
1035
  console.log(`
970
- 3\uFE0F\u20E3 Atualiza\xE7\xE3o conclu\xEDda!`);
1036
+ 4\uFE0F\u20E3 Verificando instala\xE7\xE3o...`);
1037
+ try {
1038
+ const { getHomeAgroplanDir: getHomeAgroplanDir2 } = await Promise.resolve().then(() => (init_paths(), exports_paths));
1039
+ const { existsSync: existsSync7, readFileSync: readFileSync4 } = await import("fs");
1040
+ const { join: join4 } = await import("path");
1041
+ const homeDir = getHomeAgroplanDir2();
1042
+ const versionPath = join4(homeDir, "backend", "VERSION.json");
1043
+ if (existsSync7(versionPath)) {
1044
+ const versionContent = readFileSync4(versionPath, "utf-8");
1045
+ const versionInfo = JSON.parse(versionContent);
1046
+ console.log(` \u2705 Backend template: ${versionInfo.backend_template_version}`);
1047
+ console.log(` \u2705 ZARC index: ${versionInfo.zarc_index_version}`);
1048
+ console.log(` \u2705 Features: ${versionInfo.features.length} ativas`);
1049
+ } else {
1050
+ console.log(" \u26A0\uFE0F VERSION.json n\xE3o encontrado");
1051
+ }
1052
+ } catch (error) {
1053
+ console.log(` \u26A0\uFE0F Erro ao verificar vers\xE3o: ${error}`);
1054
+ }
1055
+ console.log(`
1056
+ 5\uFE0F\u20E3 Atualiza\xE7\xE3o conclu\xEDda!`);
971
1057
  if (isRunning) {
972
1058
  console.log(`
973
1059
  \uD83D\uDCA1 Para reiniciar a API:`);
@@ -977,6 +1063,9 @@ async function updateCommand() {
977
1063
  \uD83D\uDCA1 Para iniciar a API:`);
978
1064
  console.log(" agroplan serve on");
979
1065
  }
1066
+ console.log(`
1067
+ \uD83D\uDD0D Para verificar a vers\xE3o da API rodando:`);
1068
+ console.log(" http://localhost:8000/debug/version");
980
1069
  }
981
1070
 
982
1071
  // src/index.ts
@@ -993,7 +1082,7 @@ var COMMANDS = {
993
1082
  open: "Abre o AgroPlan AI no navegador"
994
1083
  };
995
1084
  function showHelp() {
996
- console.log("\uD83C\uDF31 AgroPlan AI - CLI Local v1.0.20");
1085
+ console.log("\uD83C\uDF31 AgroPlan AI - CLI Local v1.0.21");
997
1086
  console.log(` Launcher para modo local acelerado
998
1087
  `);
999
1088
  console.log("\uD83D\uDCCB Comandos dispon\xEDveis:");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agroplan-ai-cli",
3
- "version": "1.0.20",
3
+ "version": "1.0.22",
4
4
  "description": "CLI global para AgroPlan AI - modo local acelerado",
5
5
  "type": "module",
6
6
  "bin": {