agroplan-ai-cli 1.0.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/README.md +95 -0
- package/backend-template/.env.example +21 -0
- package/backend-template/Dockerfile +21 -0
- package/backend-template/README.md +274 -0
- package/backend-template/api.py +538 -0
- package/backend-template/core/bruteforce_validator.py +248 -0
- package/backend-template/core/genetic_optimizer.py +328 -0
- package/backend-template/core/loader.py +8 -0
- package/backend-template/core/planner.py +79 -0
- package/backend-template/core/report_generator.py +785 -0
- package/backend-template/core/scenario_simulator.py +286 -0
- package/backend-template/core/scorer.py +101 -0
- package/backend-template/core/terrain_analyzer.py +123 -0
- package/backend-template/data/culturas.csv +11 -0
- package/backend-template/data/regras_culturas.csv +11 -0
- package/backend-template/data/talhoes.csv +11 -0
- package/backend-template/main.py +487 -0
- package/backend-template/reports/.gitkeep +1 -0
- package/backend-template/requirements.txt +6 -0
- package/dist/index.js +719 -0
- package/package.json +51 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,719 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
// @bun
|
|
3
|
+
|
|
4
|
+
// src/commands/doctor.ts
|
|
5
|
+
import { existsSync as existsSync4 } from "fs";
|
|
6
|
+
|
|
7
|
+
// src/utils/python.ts
|
|
8
|
+
import { existsSync as existsSync2 } from "fs";
|
|
9
|
+
|
|
10
|
+
// src/utils/paths.ts
|
|
11
|
+
import { join, dirname } from "path";
|
|
12
|
+
import { existsSync } from "fs";
|
|
13
|
+
import { homedir } from "os";
|
|
14
|
+
function getHomeAgroplanDir() {
|
|
15
|
+
return join(homedir(), ".agroplan");
|
|
16
|
+
}
|
|
17
|
+
function findProjectRoot() {
|
|
18
|
+
let currentDir = process.cwd();
|
|
19
|
+
while (currentDir !== dirname(currentDir)) {
|
|
20
|
+
const backendPath = join(currentDir, "backend");
|
|
21
|
+
const apiPath = join(backendPath, "api.py");
|
|
22
|
+
if (existsSync(backendPath) && existsSync(apiPath)) {
|
|
23
|
+
return currentDir;
|
|
24
|
+
}
|
|
25
|
+
currentDir = dirname(currentDir);
|
|
26
|
+
}
|
|
27
|
+
throw new Error("N\xE3o foi poss\xEDvel encontrar a raiz do projeto AgroPlan AI");
|
|
28
|
+
}
|
|
29
|
+
function getProjectPaths() {
|
|
30
|
+
const homeDir = getHomeAgroplanDir();
|
|
31
|
+
const homeBackend = join(homeDir, "backend");
|
|
32
|
+
if (existsSync(homeBackend)) {
|
|
33
|
+
return {
|
|
34
|
+
root: homeDir,
|
|
35
|
+
backend: homeBackend,
|
|
36
|
+
api: join(homeBackend, "api.py"),
|
|
37
|
+
requirements: join(homeBackend, "requirements.txt"),
|
|
38
|
+
venv: join(homeBackend, ".venv"),
|
|
39
|
+
agroplanDir: homeDir,
|
|
40
|
+
pidFile: join(homeDir, "agroplan-api.pid"),
|
|
41
|
+
logsDir: join(homeDir, "logs"),
|
|
42
|
+
logFile: join(homeDir, "logs", "api.log")
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
try {
|
|
46
|
+
const root = findProjectRoot();
|
|
47
|
+
return {
|
|
48
|
+
root,
|
|
49
|
+
backend: join(root, "backend"),
|
|
50
|
+
api: join(root, "backend", "api.py"),
|
|
51
|
+
requirements: join(root, "backend", "requirements.txt"),
|
|
52
|
+
venv: join(root, "backend", ".venv"),
|
|
53
|
+
agroplanDir: join(root, ".agroplan"),
|
|
54
|
+
pidFile: join(root, ".agroplan", "agroplan-api.pid"),
|
|
55
|
+
logsDir: join(root, ".agroplan", "logs"),
|
|
56
|
+
logFile: join(root, ".agroplan", "logs", "api.log")
|
|
57
|
+
};
|
|
58
|
+
} catch {
|
|
59
|
+
return {
|
|
60
|
+
root: homeDir,
|
|
61
|
+
backend: homeBackend,
|
|
62
|
+
api: join(homeBackend, "api.py"),
|
|
63
|
+
requirements: join(homeBackend, "requirements.txt"),
|
|
64
|
+
venv: join(homeBackend, ".venv"),
|
|
65
|
+
agroplanDir: homeDir,
|
|
66
|
+
pidFile: join(homeDir, "agroplan-api.pid"),
|
|
67
|
+
logsDir: join(homeDir, "logs"),
|
|
68
|
+
logFile: join(homeDir, "logs", "api.log")
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
function ensureAgroplanDir() {
|
|
73
|
+
const paths = getProjectPaths();
|
|
74
|
+
if (!existsSync(paths.agroplanDir)) {
|
|
75
|
+
Bun.spawnSync(["mkdir", "-p", paths.agroplanDir]);
|
|
76
|
+
}
|
|
77
|
+
if (!existsSync(paths.logsDir)) {
|
|
78
|
+
Bun.spawnSync(["mkdir", "-p", paths.logsDir]);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// src/utils/python.ts
|
|
83
|
+
function checkPython() {
|
|
84
|
+
try {
|
|
85
|
+
const result = Bun.spawnSync(["python", "--version"]);
|
|
86
|
+
if (result.success) {
|
|
87
|
+
return {
|
|
88
|
+
available: true,
|
|
89
|
+
version: result.stdout.toString().trim(),
|
|
90
|
+
path: "python"
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
} catch {}
|
|
94
|
+
try {
|
|
95
|
+
const result = Bun.spawnSync(["python3", "--version"]);
|
|
96
|
+
if (result.success) {
|
|
97
|
+
return {
|
|
98
|
+
available: true,
|
|
99
|
+
version: result.stdout.toString().trim(),
|
|
100
|
+
path: "python3"
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
} catch {}
|
|
104
|
+
return { available: false };
|
|
105
|
+
}
|
|
106
|
+
function checkPip() {
|
|
107
|
+
try {
|
|
108
|
+
const result = Bun.spawnSync(["pip", "--version"]);
|
|
109
|
+
if (result.success) {
|
|
110
|
+
return {
|
|
111
|
+
available: true,
|
|
112
|
+
version: result.stdout.toString().trim()
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
} catch {}
|
|
116
|
+
return { available: false };
|
|
117
|
+
}
|
|
118
|
+
function createVenv() {
|
|
119
|
+
const paths = getProjectPaths();
|
|
120
|
+
const python = checkPython();
|
|
121
|
+
if (!python.available || !python.path) {
|
|
122
|
+
return false;
|
|
123
|
+
}
|
|
124
|
+
try {
|
|
125
|
+
console.log("\uD83D\uDC0D Criando ambiente virtual...");
|
|
126
|
+
const result = Bun.spawnSync([python.path, "-m", "venv", ".venv"], {
|
|
127
|
+
cwd: paths.backend
|
|
128
|
+
});
|
|
129
|
+
return result.success;
|
|
130
|
+
} catch {
|
|
131
|
+
return false;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
function installRequirements() {
|
|
135
|
+
const paths = getProjectPaths();
|
|
136
|
+
if (!existsSync2(paths.venv)) {
|
|
137
|
+
console.log("\u274C Ambiente virtual n\xE3o encontrado");
|
|
138
|
+
return false;
|
|
139
|
+
}
|
|
140
|
+
try {
|
|
141
|
+
console.log("\uD83D\uDCE6 Instalando depend\xEAncias...");
|
|
142
|
+
const isWindows = process.platform === "win32";
|
|
143
|
+
const pipPath = isWindows ? `${paths.venv}/Scripts/pip.exe` : `${paths.venv}/bin/pip`;
|
|
144
|
+
const result = Bun.spawnSync([pipPath, "install", "-r", "requirements.txt"], {
|
|
145
|
+
cwd: paths.backend
|
|
146
|
+
});
|
|
147
|
+
return result.success;
|
|
148
|
+
} catch {
|
|
149
|
+
return false;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
function getVenvPython() {
|
|
153
|
+
const paths = getProjectPaths();
|
|
154
|
+
if (!existsSync2(paths.venv)) {
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
157
|
+
const isWindows = process.platform === "win32";
|
|
158
|
+
const pythonPath = isWindows ? `${paths.venv}/Scripts/python.exe` : `${paths.venv}/bin/python`;
|
|
159
|
+
return existsSync2(pythonPath) ? pythonPath : null;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// src/utils/process.ts
|
|
163
|
+
import { existsSync as existsSync3, readFileSync, writeFileSync, unlinkSync } from "fs";
|
|
164
|
+
function savePid(pid) {
|
|
165
|
+
const paths = getProjectPaths();
|
|
166
|
+
writeFileSync(paths.pidFile, pid.toString());
|
|
167
|
+
}
|
|
168
|
+
function readPid() {
|
|
169
|
+
const paths = getProjectPaths();
|
|
170
|
+
if (!existsSync3(paths.pidFile)) {
|
|
171
|
+
return null;
|
|
172
|
+
}
|
|
173
|
+
try {
|
|
174
|
+
const pidStr = readFileSync(paths.pidFile, "utf-8").trim();
|
|
175
|
+
return parseInt(pidStr, 10);
|
|
176
|
+
} catch {
|
|
177
|
+
return null;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
function removePidFile() {
|
|
181
|
+
const paths = getProjectPaths();
|
|
182
|
+
if (existsSync3(paths.pidFile)) {
|
|
183
|
+
unlinkSync(paths.pidFile);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
function isProcessRunning(pid) {
|
|
187
|
+
try {
|
|
188
|
+
process.kill(pid, 0);
|
|
189
|
+
return true;
|
|
190
|
+
} catch {
|
|
191
|
+
return false;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
function killProcess(pid) {
|
|
195
|
+
try {
|
|
196
|
+
process.kill(pid, "SIGTERM");
|
|
197
|
+
setTimeout(() => {
|
|
198
|
+
if (isProcessRunning(pid)) {
|
|
199
|
+
try {
|
|
200
|
+
process.kill(pid, "SIGKILL");
|
|
201
|
+
} catch {}
|
|
202
|
+
}
|
|
203
|
+
}, 2000);
|
|
204
|
+
return true;
|
|
205
|
+
} catch {
|
|
206
|
+
return false;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
function checkPort(port) {
|
|
210
|
+
return new Promise((resolve) => {
|
|
211
|
+
try {
|
|
212
|
+
fetch(`http://localhost:${port}/health`, {
|
|
213
|
+
signal: AbortSignal.timeout(1000)
|
|
214
|
+
}).then(() => resolve(true)).catch(() => resolve(false));
|
|
215
|
+
} catch {
|
|
216
|
+
resolve(false);
|
|
217
|
+
}
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// src/commands/doctor.ts
|
|
222
|
+
async function doctorCommand() {
|
|
223
|
+
console.log(`\uD83D\uDD0D AgroPlan AI - Diagn\xF3stico do Sistema
|
|
224
|
+
`);
|
|
225
|
+
let allGood = true;
|
|
226
|
+
console.log("\uD83D\uDC0D Python:");
|
|
227
|
+
const python = checkPython();
|
|
228
|
+
if (python.available) {
|
|
229
|
+
console.log(` \u2705 ${python.version} (${python.path})`);
|
|
230
|
+
} else {
|
|
231
|
+
console.log(" \u274C Python n\xE3o encontrado");
|
|
232
|
+
console.log(" Instale Python 3.8+ em https://python.org");
|
|
233
|
+
allGood = false;
|
|
234
|
+
}
|
|
235
|
+
console.log(`
|
|
236
|
+
\uD83D\uDCE6 pip:`);
|
|
237
|
+
const pip = checkPip();
|
|
238
|
+
if (pip.available) {
|
|
239
|
+
console.log(` \u2705 ${pip.version}`);
|
|
240
|
+
} else {
|
|
241
|
+
console.log(" \u274C pip n\xE3o encontrado");
|
|
242
|
+
allGood = false;
|
|
243
|
+
}
|
|
244
|
+
console.log(`
|
|
245
|
+
\uD83D\uDCC1 Estrutura do Projeto:`);
|
|
246
|
+
try {
|
|
247
|
+
const paths = getProjectPaths();
|
|
248
|
+
if (existsSync4(paths.backend)) {
|
|
249
|
+
console.log(" \u2705 Pasta backend/ encontrada");
|
|
250
|
+
} else {
|
|
251
|
+
console.log(" \u274C Pasta backend/ n\xE3o encontrada");
|
|
252
|
+
allGood = false;
|
|
253
|
+
}
|
|
254
|
+
if (existsSync4(paths.api)) {
|
|
255
|
+
console.log(" \u2705 Arquivo api.py encontrado");
|
|
256
|
+
} else {
|
|
257
|
+
console.log(" \u274C Arquivo api.py n\xE3o encontrado");
|
|
258
|
+
allGood = false;
|
|
259
|
+
}
|
|
260
|
+
if (existsSync4(paths.requirements)) {
|
|
261
|
+
console.log(" \u2705 Arquivo requirements.txt encontrado");
|
|
262
|
+
} else {
|
|
263
|
+
console.log(" \u274C Arquivo requirements.txt n\xE3o encontrado");
|
|
264
|
+
allGood = false;
|
|
265
|
+
}
|
|
266
|
+
if (existsSync4(paths.venv)) {
|
|
267
|
+
console.log(" \u2705 Ambiente virtual (.venv) encontrado");
|
|
268
|
+
} else {
|
|
269
|
+
console.log(" \u26A0\uFE0F Ambiente virtual n\xE3o encontrado (ser\xE1 criado automaticamente)");
|
|
270
|
+
}
|
|
271
|
+
} catch (error) {
|
|
272
|
+
console.log(" \u274C Erro ao verificar estrutura do projeto");
|
|
273
|
+
console.log(` ${error}`);
|
|
274
|
+
allGood = false;
|
|
275
|
+
}
|
|
276
|
+
console.log(`
|
|
277
|
+
\uD83C\uDF10 Conectividade:`);
|
|
278
|
+
const localRunning = await checkPort(8000);
|
|
279
|
+
if (localRunning) {
|
|
280
|
+
console.log(" \u2705 API local rodando em http://localhost:8000");
|
|
281
|
+
} else {
|
|
282
|
+
console.log(" \u26A0\uFE0F API local n\xE3o est\xE1 rodando");
|
|
283
|
+
}
|
|
284
|
+
try {
|
|
285
|
+
const renderResponse = await fetch("https://agroplan-ai-api.onrender.com/health", {
|
|
286
|
+
signal: AbortSignal.timeout(5000)
|
|
287
|
+
});
|
|
288
|
+
if (renderResponse.ok) {
|
|
289
|
+
const data = await renderResponse.json();
|
|
290
|
+
console.log(" \u2705 API Render online");
|
|
291
|
+
console.log(` Culturas: ${data.culturas}, Talh\xF5es: ${data.talhoes}`);
|
|
292
|
+
} else {
|
|
293
|
+
console.log(" \u26A0\uFE0F API Render com problemas");
|
|
294
|
+
}
|
|
295
|
+
} catch {
|
|
296
|
+
console.log(" \u274C API Render n\xE3o acess\xEDvel");
|
|
297
|
+
}
|
|
298
|
+
console.log(`
|
|
299
|
+
` + "=".repeat(50));
|
|
300
|
+
if (allGood) {
|
|
301
|
+
console.log("\u2705 Sistema pronto para uso!");
|
|
302
|
+
console.log(`
|
|
303
|
+
Pr\xF3ximos passos:`);
|
|
304
|
+
console.log(" bun run agroplan serve on # Iniciar API local");
|
|
305
|
+
console.log(" bun run agroplan open # Abrir no navegador");
|
|
306
|
+
} else {
|
|
307
|
+
console.log("\u274C Alguns problemas foram encontrados");
|
|
308
|
+
console.log(`
|
|
309
|
+
Correja os problemas acima e execute novamente:`);
|
|
310
|
+
console.log(" bun run agroplan doctor");
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// src/commands/serve.ts
|
|
315
|
+
import { existsSync as existsSync5, readFileSync as readFileSync2 } from "fs";
|
|
316
|
+
import { spawn } from "child_process";
|
|
317
|
+
async function serveOnCommand() {
|
|
318
|
+
console.log(`\uD83D\uDE80 Iniciando API local do AgroPlan AI...
|
|
319
|
+
`);
|
|
320
|
+
const paths = getProjectPaths();
|
|
321
|
+
if (!existsSync5(paths.backend) || !existsSync5(paths.api)) {
|
|
322
|
+
console.log("\u274C API local ainda n\xE3o configurada");
|
|
323
|
+
console.log(`
|
|
324
|
+
\uD83D\uDCA1 Execute primeiro:`);
|
|
325
|
+
console.log(" agroplan setup");
|
|
326
|
+
console.log(`
|
|
327
|
+
Isso criar\xE1 a instala\xE7\xE3o local em:`);
|
|
328
|
+
console.log(` ${paths.backend}`);
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
const existingPid = readPid();
|
|
332
|
+
if (existingPid && isProcessRunning(existingPid)) {
|
|
333
|
+
console.log("\u2705 API local j\xE1 est\xE1 rodando!");
|
|
334
|
+
console.log(` PID: ${existingPid}`);
|
|
335
|
+
console.log(" URL: http://localhost:8000");
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
338
|
+
if (existingPid) {
|
|
339
|
+
removePidFile();
|
|
340
|
+
}
|
|
341
|
+
ensureAgroplanDir();
|
|
342
|
+
if (!existsSync5(paths.venv)) {
|
|
343
|
+
console.log("\uD83D\uDC0D Criando ambiente virtual...");
|
|
344
|
+
if (!createVenv()) {
|
|
345
|
+
console.log("\u274C Falha ao criar ambiente virtual");
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
const venvPython = getVenvPython();
|
|
350
|
+
if (!venvPython) {
|
|
351
|
+
console.log("\u274C Ambiente virtual n\xE3o encontrado");
|
|
352
|
+
return;
|
|
353
|
+
}
|
|
354
|
+
console.log("\uD83D\uDCE6 Verificando depend\xEAncias...");
|
|
355
|
+
if (!installRequirements()) {
|
|
356
|
+
console.log("\u274C Falha ao instalar depend\xEAncias");
|
|
357
|
+
return;
|
|
358
|
+
}
|
|
359
|
+
const portInUse = await checkPort(8000);
|
|
360
|
+
if (portInUse) {
|
|
361
|
+
console.log("\u274C Porta 8000 j\xE1 est\xE1 em uso");
|
|
362
|
+
console.log(" Verifique se outro processo est\xE1 usando a porta");
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
365
|
+
console.log("\uD83C\uDF10 Iniciando servidor uvicorn...");
|
|
366
|
+
const isWindows = process.platform === "win32";
|
|
367
|
+
const uvicornPath = isWindows ? `${paths.venv}/Scripts/uvicorn.exe` : `${paths.venv}/bin/uvicorn`;
|
|
368
|
+
const child = spawn(uvicornPath, [
|
|
369
|
+
"api:app",
|
|
370
|
+
"--host",
|
|
371
|
+
"127.0.0.1",
|
|
372
|
+
"--port",
|
|
373
|
+
"8000",
|
|
374
|
+
"--log-level",
|
|
375
|
+
"info"
|
|
376
|
+
], {
|
|
377
|
+
cwd: paths.backend,
|
|
378
|
+
detached: true,
|
|
379
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
380
|
+
env: {
|
|
381
|
+
...process.env,
|
|
382
|
+
CORS_ORIGINS: "https://agroplan-ai.vercel.app,http://localhost:3000,http://127.0.0.1:3000"
|
|
383
|
+
}
|
|
384
|
+
});
|
|
385
|
+
savePid(child.pid);
|
|
386
|
+
const logStream = Bun.file(paths.logFile).writer();
|
|
387
|
+
child.stdout?.on("data", (data) => {
|
|
388
|
+
logStream.write(data);
|
|
389
|
+
});
|
|
390
|
+
child.stderr?.on("data", (data) => {
|
|
391
|
+
logStream.write(data);
|
|
392
|
+
});
|
|
393
|
+
child.unref();
|
|
394
|
+
console.log("\u23F3 Aguardando inicializa\xE7\xE3o...");
|
|
395
|
+
let attempts = 0;
|
|
396
|
+
const maxAttempts = 10;
|
|
397
|
+
while (attempts < maxAttempts) {
|
|
398
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
399
|
+
const running = await checkPort(8000);
|
|
400
|
+
if (running) {
|
|
401
|
+
console.log("\u2705 API local iniciada com sucesso!");
|
|
402
|
+
console.log(` PID: ${child.pid}`);
|
|
403
|
+
console.log(" URL: http://localhost:8000");
|
|
404
|
+
console.log(" Health: http://localhost:8000/health");
|
|
405
|
+
console.log(`
|
|
406
|
+
\uD83D\uDCA1 Use 'bun run agroplan serve off' para parar`);
|
|
407
|
+
return;
|
|
408
|
+
}
|
|
409
|
+
attempts++;
|
|
410
|
+
}
|
|
411
|
+
console.log("\u274C Falha ao iniciar API local");
|
|
412
|
+
console.log(" Verifique os logs: bun run agroplan serve logs");
|
|
413
|
+
}
|
|
414
|
+
async function serveOffCommand() {
|
|
415
|
+
console.log(`\uD83D\uDED1 Parando API local...
|
|
416
|
+
`);
|
|
417
|
+
const pid = readPid();
|
|
418
|
+
if (!pid) {
|
|
419
|
+
console.log("\u26A0\uFE0F Nenhuma API local encontrada");
|
|
420
|
+
return;
|
|
421
|
+
}
|
|
422
|
+
if (!isProcessRunning(pid)) {
|
|
423
|
+
console.log("\u26A0\uFE0F Processo n\xE3o est\xE1 rodando");
|
|
424
|
+
removePidFile();
|
|
425
|
+
return;
|
|
426
|
+
}
|
|
427
|
+
console.log(`\uD83D\uDD04 Encerrando processo ${pid}...`);
|
|
428
|
+
if (killProcess(pid)) {
|
|
429
|
+
removePidFile();
|
|
430
|
+
console.log("\u2705 API local parada com sucesso!");
|
|
431
|
+
} else {
|
|
432
|
+
console.log("\u274C Falha ao parar API local");
|
|
433
|
+
console.log(" Tente encerrar manualmente o processo");
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
async function serveStatusCommand() {
|
|
437
|
+
console.log(`\uD83D\uDCCA Status da API Local
|
|
438
|
+
`);
|
|
439
|
+
const pid = readPid();
|
|
440
|
+
const running = pid ? isProcessRunning(pid) : false;
|
|
441
|
+
const portActive = await checkPort(8000);
|
|
442
|
+
if (running && portActive) {
|
|
443
|
+
console.log("\u2705 API Local: ONLINE");
|
|
444
|
+
console.log(` PID: ${pid}`);
|
|
445
|
+
console.log(" URL: http://localhost:8000");
|
|
446
|
+
try {
|
|
447
|
+
const response = await fetch("http://localhost:8000/health");
|
|
448
|
+
if (response.ok) {
|
|
449
|
+
const data = await response.json();
|
|
450
|
+
console.log(` Status: ${data.status}`);
|
|
451
|
+
console.log(` Culturas: ${data.culturas}`);
|
|
452
|
+
console.log(` Talh\xF5es: ${data.talhoes}`);
|
|
453
|
+
console.log(` Cache items: ${data.cache_items}`);
|
|
454
|
+
}
|
|
455
|
+
} catch {
|
|
456
|
+
console.log(" \u26A0\uFE0F Health check falhou");
|
|
457
|
+
}
|
|
458
|
+
} else if (pid && !running) {
|
|
459
|
+
console.log("\u274C API Local: PROCESSO MORTO");
|
|
460
|
+
console.log(` PID antigo: ${pid} (n\xE3o est\xE1 rodando)`);
|
|
461
|
+
removePidFile();
|
|
462
|
+
} else if (!pid && portActive) {
|
|
463
|
+
console.log("\u26A0\uFE0F API Local: PORTA OCUPADA");
|
|
464
|
+
console.log(" Porta 8000 est\xE1 em uso por outro processo");
|
|
465
|
+
} else {
|
|
466
|
+
console.log("\u2B55 API Local: OFFLINE");
|
|
467
|
+
}
|
|
468
|
+
console.log(`
|
|
469
|
+
\uD83C\uDF10 API Render:`);
|
|
470
|
+
try {
|
|
471
|
+
const response = await fetch("https://agroplan-ai-api.onrender.com/health", {
|
|
472
|
+
signal: AbortSignal.timeout(3000)
|
|
473
|
+
});
|
|
474
|
+
if (response.ok) {
|
|
475
|
+
const data = await response.json();
|
|
476
|
+
console.log(" \u2705 ONLINE");
|
|
477
|
+
console.log(` Cache items: ${data.cache_items}`);
|
|
478
|
+
} else {
|
|
479
|
+
console.log(" \u274C OFFLINE ou com problemas");
|
|
480
|
+
}
|
|
481
|
+
} catch {
|
|
482
|
+
console.log(" \u274C INACESS\xCDVEL");
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
function serveLogsCommand() {
|
|
486
|
+
console.log(`\uD83D\uDCCB Logs da API Local
|
|
487
|
+
`);
|
|
488
|
+
const paths = getProjectPaths();
|
|
489
|
+
if (!existsSync5(paths.logFile)) {
|
|
490
|
+
console.log("\u26A0\uFE0F Arquivo de log n\xE3o encontrado");
|
|
491
|
+
console.log(` Esperado em: ${paths.logFile}`);
|
|
492
|
+
return;
|
|
493
|
+
}
|
|
494
|
+
try {
|
|
495
|
+
const logs = readFileSync2(paths.logFile, "utf-8");
|
|
496
|
+
const lines = logs.split(`
|
|
497
|
+
`);
|
|
498
|
+
const recentLines = lines.slice(-50).filter((line) => line.trim());
|
|
499
|
+
if (recentLines.length === 0) {
|
|
500
|
+
console.log("\uD83D\uDCC4 Log vazio");
|
|
501
|
+
return;
|
|
502
|
+
}
|
|
503
|
+
console.log("\uD83D\uDCC4 \xDAltimas linhas do log:");
|
|
504
|
+
console.log("-".repeat(60));
|
|
505
|
+
recentLines.forEach((line) => console.log(line));
|
|
506
|
+
console.log("-".repeat(60));
|
|
507
|
+
console.log(`
|
|
508
|
+
\uD83D\uDCA1 Log completo: ${paths.logFile}`);
|
|
509
|
+
} catch (error) {
|
|
510
|
+
console.log("\u274C Erro ao ler arquivo de log");
|
|
511
|
+
console.log(` ${error}`);
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
// src/commands/open.ts
|
|
516
|
+
import { spawn as spawn2 } from "child_process";
|
|
517
|
+
function openCommand() {
|
|
518
|
+
console.log(`\uD83C\uDF10 Abrindo AgroPlan AI no navegador...
|
|
519
|
+
`);
|
|
520
|
+
const url = "https://agroplan-ai.vercel.app/dashboard";
|
|
521
|
+
try {
|
|
522
|
+
let command;
|
|
523
|
+
let args;
|
|
524
|
+
switch (process.platform) {
|
|
525
|
+
case "win32":
|
|
526
|
+
command = "cmd";
|
|
527
|
+
args = ["/c", "start", url];
|
|
528
|
+
break;
|
|
529
|
+
case "darwin":
|
|
530
|
+
command = "open";
|
|
531
|
+
args = [url];
|
|
532
|
+
break;
|
|
533
|
+
default:
|
|
534
|
+
command = "xdg-open";
|
|
535
|
+
args = [url];
|
|
536
|
+
break;
|
|
537
|
+
}
|
|
538
|
+
const child = spawn2(command, args, {
|
|
539
|
+
detached: true,
|
|
540
|
+
stdio: "ignore"
|
|
541
|
+
});
|
|
542
|
+
child.unref();
|
|
543
|
+
console.log("\u2705 Navegador aberto!");
|
|
544
|
+
console.log(` URL: ${url}`);
|
|
545
|
+
console.log(`
|
|
546
|
+
\uD83D\uDCA1 O frontend detectar\xE1 automaticamente se a API local est\xE1 rodando`);
|
|
547
|
+
} catch (error) {
|
|
548
|
+
console.log("\u274C Erro ao abrir navegador");
|
|
549
|
+
console.log(` Abra manualmente: ${url}`);
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
// src/commands/setup.ts
|
|
554
|
+
import { existsSync as existsSync6, cpSync } from "fs";
|
|
555
|
+
import { join as join2, dirname as dirname2 } from "path";
|
|
556
|
+
async function setupCommand() {
|
|
557
|
+
console.log(`\uD83D\uDEE0\uFE0F Configurando AgroPlan AI - API Local
|
|
558
|
+
`);
|
|
559
|
+
const homeDir = getHomeAgroplanDir();
|
|
560
|
+
const backendDir = join2(homeDir, "backend");
|
|
561
|
+
console.log(`\uD83D\uDCC1 Diret\xF3rio de instala\xE7\xE3o: ${homeDir}`);
|
|
562
|
+
console.log(`
|
|
563
|
+
\uD83D\uDC0D Verificando Python...`);
|
|
564
|
+
const python = checkPython();
|
|
565
|
+
if (!python.available) {
|
|
566
|
+
console.log("\u274C Python n\xE3o encontrado");
|
|
567
|
+
console.log(" Instale Python 3.8+ em https://python.org/downloads");
|
|
568
|
+
return;
|
|
569
|
+
}
|
|
570
|
+
console.log(` \u2705 ${python.version}`);
|
|
571
|
+
console.log(`
|
|
572
|
+
\uD83D\uDCC2 Criando estrutura de diret\xF3rios...`);
|
|
573
|
+
ensureAgroplanDir();
|
|
574
|
+
console.log("\uD83D\uDCCB Copiando arquivos do backend...");
|
|
575
|
+
const cliDir = dirname2(dirname2(dirname2(import.meta.url.replace("file://", ""))));
|
|
576
|
+
const templateDir = join2(cliDir, "backend-template");
|
|
577
|
+
if (!existsSync6(templateDir)) {
|
|
578
|
+
console.log("\u274C Template do backend n\xE3o encontrado");
|
|
579
|
+
console.log(` Esperado em: ${templateDir}`);
|
|
580
|
+
console.log(" Reinstale a CLI: bun add -g @kuuhaku-allan/agroplan-cli");
|
|
581
|
+
return;
|
|
582
|
+
}
|
|
583
|
+
try {
|
|
584
|
+
if (existsSync6(backendDir)) {
|
|
585
|
+
console.log(" \uD83D\uDDD1\uFE0F Removendo instala\xE7\xE3o anterior...");
|
|
586
|
+
Bun.spawnSync(["rm", "-rf", backendDir]);
|
|
587
|
+
}
|
|
588
|
+
cpSync(templateDir, backendDir, { recursive: true });
|
|
589
|
+
console.log(" \u2705 Arquivos copiados com sucesso");
|
|
590
|
+
} catch (error) {
|
|
591
|
+
console.log("\u274C Erro ao copiar arquivos do backend");
|
|
592
|
+
console.log(` ${error}`);
|
|
593
|
+
return;
|
|
594
|
+
}
|
|
595
|
+
console.log(`
|
|
596
|
+
\uD83D\uDC0D Criando ambiente virtual...`);
|
|
597
|
+
const venvPath = join2(backendDir, ".venv");
|
|
598
|
+
if (!existsSync6(venvPath)) {
|
|
599
|
+
const result = Bun.spawnSync([python.path, "-m", "venv", ".venv"], {
|
|
600
|
+
cwd: backendDir
|
|
601
|
+
});
|
|
602
|
+
if (!result.success) {
|
|
603
|
+
console.log("\u274C Falha ao criar ambiente virtual");
|
|
604
|
+
return;
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
console.log(" \u2705 Ambiente virtual criado");
|
|
608
|
+
console.log(`
|
|
609
|
+
\uD83D\uDCE6 Instalando depend\xEAncias Python...`);
|
|
610
|
+
const isWindows = process.platform === "win32";
|
|
611
|
+
const pipPath = isWindows ? join2(venvPath, "Scripts", "pip.exe") : join2(venvPath, "bin", "pip");
|
|
612
|
+
const installResult = Bun.spawnSync([pipPath, "install", "-r", "requirements.txt"], {
|
|
613
|
+
cwd: backendDir
|
|
614
|
+
});
|
|
615
|
+
if (!installResult.success) {
|
|
616
|
+
console.log("\u274C Falha ao instalar depend\xEAncias");
|
|
617
|
+
console.log(" Verifique sua conex\xE3o com a internet");
|
|
618
|
+
return;
|
|
619
|
+
}
|
|
620
|
+
console.log(" \u2705 Depend\xEAncias instaladas");
|
|
621
|
+
console.log(`
|
|
622
|
+
\uD83C\uDF10 Verificando servidor web...`);
|
|
623
|
+
const uvicornPath = isWindows ? join2(venvPath, "Scripts", "uvicorn.exe") : join2(venvPath, "bin", "uvicorn");
|
|
624
|
+
if (!existsSync6(uvicornPath)) {
|
|
625
|
+
console.log("\u274C Uvicorn n\xE3o encontrado");
|
|
626
|
+
return;
|
|
627
|
+
}
|
|
628
|
+
console.log(" \u2705 Servidor web configurado");
|
|
629
|
+
console.log(`
|
|
630
|
+
` + "=".repeat(50));
|
|
631
|
+
console.log("\u2705 Configura\xE7\xE3o conclu\xEDda com sucesso!");
|
|
632
|
+
console.log(`
|
|
633
|
+
\uD83D\uDE80 Pr\xF3ximos passos:`);
|
|
634
|
+
console.log(" agroplan serve on # Iniciar API local");
|
|
635
|
+
console.log(" agroplan open # Abrir no navegador");
|
|
636
|
+
console.log(`
|
|
637
|
+
\uD83D\uDCA1 A API local ser\xE1 executada em http://localhost:8000`);
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
// src/index.ts
|
|
641
|
+
var COMMANDS = {
|
|
642
|
+
setup: "Configura a API local no seu computador",
|
|
643
|
+
doctor: "Verifica se o sistema est\xE1 configurado corretamente",
|
|
644
|
+
"serve on": "Inicia a API local em http://localhost:8000",
|
|
645
|
+
"serve off": "Para a API local",
|
|
646
|
+
"serve status": "Mostra o status da API local e Render",
|
|
647
|
+
"serve logs": "Exibe os logs da API local",
|
|
648
|
+
open: "Abre o AgroPlan AI no navegador"
|
|
649
|
+
};
|
|
650
|
+
function showHelp() {
|
|
651
|
+
console.log("\uD83C\uDF31 AgroPlan AI - CLI Local v1.0.0");
|
|
652
|
+
console.log(` Launcher para modo local acelerado
|
|
653
|
+
`);
|
|
654
|
+
console.log("\uD83D\uDCCB Comandos dispon\xEDveis:");
|
|
655
|
+
Object.entries(COMMANDS).forEach(([cmd, desc]) => {
|
|
656
|
+
console.log(` bun run agroplan ${cmd.padEnd(12)} # ${desc}`);
|
|
657
|
+
});
|
|
658
|
+
console.log(`
|
|
659
|
+
\uD83C\uDFAF Fluxo recomendado:`);
|
|
660
|
+
console.log(" 1. agroplan setup # Configurar API local");
|
|
661
|
+
console.log(" 2. agroplan serve on # Iniciar API local");
|
|
662
|
+
console.log(" 3. agroplan open # Abrir no navegador");
|
|
663
|
+
console.log(" 4. agroplan serve off # Parar quando terminar");
|
|
664
|
+
console.log(`
|
|
665
|
+
\uD83D\uDCA1 Modo h\xEDbrido:`);
|
|
666
|
+
console.log(" \u2022 API Local: R\xE1pida, n\xE3o dorme, ideal para uso di\xE1rio");
|
|
667
|
+
console.log(" \u2022 API Render: Fallback universal, funciona em qualquer PC");
|
|
668
|
+
console.log(" \u2022 Frontend detecta automaticamente qual usar");
|
|
669
|
+
}
|
|
670
|
+
async function main() {
|
|
671
|
+
const args = process.argv.slice(2);
|
|
672
|
+
if (args.length === 0) {
|
|
673
|
+
showHelp();
|
|
674
|
+
return;
|
|
675
|
+
}
|
|
676
|
+
const command = args.join(" ");
|
|
677
|
+
try {
|
|
678
|
+
switch (command) {
|
|
679
|
+
case "setup":
|
|
680
|
+
await setupCommand();
|
|
681
|
+
break;
|
|
682
|
+
case "doctor":
|
|
683
|
+
await doctorCommand();
|
|
684
|
+
break;
|
|
685
|
+
case "serve on":
|
|
686
|
+
await serveOnCommand();
|
|
687
|
+
break;
|
|
688
|
+
case "serve off":
|
|
689
|
+
await serveOffCommand();
|
|
690
|
+
break;
|
|
691
|
+
case "serve status":
|
|
692
|
+
await serveStatusCommand();
|
|
693
|
+
break;
|
|
694
|
+
case "serve logs":
|
|
695
|
+
serveLogsCommand();
|
|
696
|
+
break;
|
|
697
|
+
case "open":
|
|
698
|
+
openCommand();
|
|
699
|
+
break;
|
|
700
|
+
case "help":
|
|
701
|
+
case "--help":
|
|
702
|
+
case "-h":
|
|
703
|
+
showHelp();
|
|
704
|
+
break;
|
|
705
|
+
default:
|
|
706
|
+
console.log(`\u274C Comando desconhecido: ${command}`);
|
|
707
|
+
console.log(`
|
|
708
|
+
\uD83D\uDCA1 Use 'bun run agroplan help' para ver comandos dispon\xEDveis`);
|
|
709
|
+
process.exit(1);
|
|
710
|
+
}
|
|
711
|
+
} catch (error) {
|
|
712
|
+
console.log(`\u274C Erro ao executar comando: ${error}`);
|
|
713
|
+
process.exit(1);
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
main().catch((error) => {
|
|
717
|
+
console.error("\u274C Erro fatal:", error);
|
|
718
|
+
process.exit(1);
|
|
719
|
+
});
|