pipely-ai 1.0.0 → 1.2.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/index.js +341 -18
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -271,6 +271,7 @@ services:
|
|
|
271
271
|
- POLL_INTERVAL_MS=\${POLL_INTERVAL_MS:-60000}
|
|
272
272
|
- EVOLUTION_SERVER_URL=http://evolution:8080
|
|
273
273
|
- EVOLUTION_API_KEY=\${EVOLUTION_API_KEY}
|
|
274
|
+
- EVOLUTION_PORT=\${EVOLUTION_PORT:-${ports.evolution}}
|
|
274
275
|
- NODE_ENV=production
|
|
275
276
|
ports:
|
|
276
277
|
- "\${FRONTEND_PORT:-${ports.frontend}}:80"
|
|
@@ -445,6 +446,274 @@ function printDockerHelp(os) {
|
|
|
445
446
|
}
|
|
446
447
|
}
|
|
447
448
|
|
|
449
|
+
// ── Local Mode ──────────────────────────────────────
|
|
450
|
+
|
|
451
|
+
const PIPELY_DIR = join(process.env.HOME || process.env.USERPROFILE || ".", ".pipely");
|
|
452
|
+
const BUNDLE_REPO = "Pedro-Furtado/pipely-ai";
|
|
453
|
+
const BUNDLE_NAME = "pipely-local.tar.gz";
|
|
454
|
+
|
|
455
|
+
async function getLatestReleaseUrl() {
|
|
456
|
+
return new Promise((resolve) => {
|
|
457
|
+
const url = `https://api.github.com/repos/${BUNDLE_REPO}/releases/latest`;
|
|
458
|
+
const https = require("node:https");
|
|
459
|
+
https.get(url, { headers: { "User-Agent": "pipely-cli" } }, (res) => {
|
|
460
|
+
let body = "";
|
|
461
|
+
res.on("data", (chunk) => (body += chunk));
|
|
462
|
+
res.on("end", () => {
|
|
463
|
+
try {
|
|
464
|
+
const release = JSON.parse(body);
|
|
465
|
+
const asset = (release.assets || []).find((a) => a.name === BUNDLE_NAME);
|
|
466
|
+
resolve(asset?.browser_download_url || null);
|
|
467
|
+
} catch {
|
|
468
|
+
resolve(null);
|
|
469
|
+
}
|
|
470
|
+
});
|
|
471
|
+
}).on("error", () => resolve(null));
|
|
472
|
+
});
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
async function downloadFile(url, dest) {
|
|
476
|
+
return new Promise((resolve, reject) => {
|
|
477
|
+
const https = require("node:https");
|
|
478
|
+
const fs = require("node:fs");
|
|
479
|
+
|
|
480
|
+
function follow(url) {
|
|
481
|
+
https.get(url, { headers: { "User-Agent": "pipely-cli" } }, (res) => {
|
|
482
|
+
if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
|
|
483
|
+
follow(res.headers.location);
|
|
484
|
+
return;
|
|
485
|
+
}
|
|
486
|
+
if (res.statusCode !== 200) {
|
|
487
|
+
reject(new Error(`HTTP ${res.statusCode}`));
|
|
488
|
+
return;
|
|
489
|
+
}
|
|
490
|
+
const file = fs.createWriteStream(dest);
|
|
491
|
+
res.pipe(file);
|
|
492
|
+
file.on("finish", () => { file.close(); resolve(); });
|
|
493
|
+
}).on("error", reject);
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
follow(url);
|
|
497
|
+
});
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
function extractTarGz(file, dest) {
|
|
501
|
+
const { mkdirSync } = require("node:fs");
|
|
502
|
+
mkdirSync(dest, { recursive: true });
|
|
503
|
+
|
|
504
|
+
const os = detectOS();
|
|
505
|
+
if (os.platform === "win32") {
|
|
506
|
+
// Windows: use tar (available since Windows 10)
|
|
507
|
+
execSync(`tar -xzf "${file}" -C "${dest}"`, { stdio: "pipe" });
|
|
508
|
+
} else {
|
|
509
|
+
execSync(`tar -xzf "${file}" -C "${dest}"`, { stdio: "pipe" });
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
function getLocalEnvPath() {
|
|
514
|
+
return join(PIPELY_DIR, ".env");
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
function isLocalInstalled() {
|
|
518
|
+
return existsSync(join(PIPELY_DIR, "start.mjs")) && existsSync(join(PIPELY_DIR, "package.json"));
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
function generateLocalEnv() {
|
|
522
|
+
const jwtSecret = generateKey(64);
|
|
523
|
+
const setupKey = require("node:crypto").randomUUID();
|
|
524
|
+
|
|
525
|
+
const env = `# Pipely AI — Local Mode
|
|
526
|
+
DATABASE_URL=file:./data/pipely.db
|
|
527
|
+
JWT_SECRET=${jwtSecret}
|
|
528
|
+
OWNER_SETUP_KEY=${setupKey}
|
|
529
|
+
FRONTEND_URL=http://localhost:3000
|
|
530
|
+
BACKEND_URL=http://localhost:3333
|
|
531
|
+
POLL_INTERVAL_MS=60000
|
|
532
|
+
PORT=3333
|
|
533
|
+
VITE_API_URL=http://localhost:3333
|
|
534
|
+
`;
|
|
535
|
+
return { env, setupKey };
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
async function installLocal() {
|
|
539
|
+
printBanner();
|
|
540
|
+
|
|
541
|
+
const os = detectOS();
|
|
542
|
+
console.log(` Sistema: ${c.bold}${os.label}${c.reset} (${os.arch})`);
|
|
543
|
+
console.log(` Modo: ${c.cyan}Local (sem Docker)${c.reset}\n`);
|
|
544
|
+
|
|
545
|
+
// Check if already installed
|
|
546
|
+
if (isLocalInstalled()) {
|
|
547
|
+
console.log(` ${c.green}✓${c.reset} Pipely AI ja instalado em ${c.dim}${PIPELY_DIR}${c.reset}\n`);
|
|
548
|
+
console.log(` Iniciando...\n`);
|
|
549
|
+
return runLocal();
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
// Download bundle
|
|
553
|
+
console.log(` ${c.magenta}── Baixando Pipely AI ─────────────────────${c.reset}\n`);
|
|
554
|
+
|
|
555
|
+
process.stdout.write(` Buscando ultima versao... `);
|
|
556
|
+
const bundleUrl = await getLatestReleaseUrl();
|
|
557
|
+
|
|
558
|
+
if (!bundleUrl) {
|
|
559
|
+
console.log(`${c.red}✗${c.reset}`);
|
|
560
|
+
console.log(`\n ${c.red}Bundle nao encontrado no GitHub Releases.${c.reset}`);
|
|
561
|
+
console.log(` ${c.dim}Verifique: https://github.com/${BUNDLE_REPO}/releases${c.reset}\n`);
|
|
562
|
+
process.exit(1);
|
|
563
|
+
}
|
|
564
|
+
console.log(`${c.green}✓${c.reset}`);
|
|
565
|
+
|
|
566
|
+
const tmpFile = join(PIPELY_DIR, BUNDLE_NAME);
|
|
567
|
+
const { mkdirSync: mk } = require("node:fs");
|
|
568
|
+
mk(PIPELY_DIR, { recursive: true });
|
|
569
|
+
|
|
570
|
+
process.stdout.write(` Baixando bundle... `);
|
|
571
|
+
try {
|
|
572
|
+
await downloadFile(bundleUrl, tmpFile);
|
|
573
|
+
console.log(`${c.green}✓${c.reset}`);
|
|
574
|
+
} catch (err) {
|
|
575
|
+
console.log(`${c.red}✗${c.reset}`);
|
|
576
|
+
console.log(` ${c.red}Erro: ${err.message}${c.reset}\n`);
|
|
577
|
+
process.exit(1);
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
// Extract
|
|
581
|
+
process.stdout.write(` Extraindo... `);
|
|
582
|
+
try {
|
|
583
|
+
extractTarGz(tmpFile, PIPELY_DIR);
|
|
584
|
+
console.log(`${c.green}✓${c.reset}`);
|
|
585
|
+
} catch (err) {
|
|
586
|
+
console.log(`${c.red}✗${c.reset}`);
|
|
587
|
+
console.log(` ${c.red}Erro: ${err.message}${c.reset}\n`);
|
|
588
|
+
process.exit(1);
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
// Clean up tar
|
|
592
|
+
try { require("node:fs").unlinkSync(tmpFile); } catch {}
|
|
593
|
+
|
|
594
|
+
// Install deps
|
|
595
|
+
console.log(`\n ${c.magenta}── Instalando dependencias ────────────────${c.reset}\n`);
|
|
596
|
+
try {
|
|
597
|
+
execSync("npm install --production --no-fund --no-audit", {
|
|
598
|
+
cwd: PIPELY_DIR,
|
|
599
|
+
stdio: "inherit",
|
|
600
|
+
});
|
|
601
|
+
console.log(`\n ${c.green}✓${c.reset} Dependencias instaladas`);
|
|
602
|
+
} catch {
|
|
603
|
+
console.log(`\n ${c.red}✗ Erro ao instalar dependencias${c.reset}\n`);
|
|
604
|
+
process.exit(1);
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
// Generate .env
|
|
608
|
+
console.log(`\n ${c.magenta}── Configurando ───────────────────────────${c.reset}\n`);
|
|
609
|
+
const { env: envContent, setupKey } = generateLocalEnv();
|
|
610
|
+
writeFileSync(getLocalEnvPath(), envContent);
|
|
611
|
+
console.log(` ${c.green}✓${c.reset} .env gerado`);
|
|
612
|
+
|
|
613
|
+
// Create data directory
|
|
614
|
+
mk(join(PIPELY_DIR, "data"), { recursive: true });
|
|
615
|
+
|
|
616
|
+
// Setup database
|
|
617
|
+
process.stdout.write(` Criando banco de dados... `);
|
|
618
|
+
try {
|
|
619
|
+
execSync("npx prisma db push --skip-generate", {
|
|
620
|
+
cwd: join(PIPELY_DIR, "server"),
|
|
621
|
+
stdio: "pipe",
|
|
622
|
+
env: { ...process.env, DATABASE_URL: `file:${join(PIPELY_DIR, "data/pipely.db")}` },
|
|
623
|
+
});
|
|
624
|
+
console.log(`${c.green}✓${c.reset}`);
|
|
625
|
+
} catch (err) {
|
|
626
|
+
console.log(`${c.red}✗${c.reset}`);
|
|
627
|
+
console.log(` ${c.dim}${err.message}${c.reset}`);
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
console.log(`\n ${c.green}✓${c.reset} Instalacao concluida\n`);
|
|
631
|
+
|
|
632
|
+
// Print summary and run
|
|
633
|
+
const line = "═".repeat(56);
|
|
634
|
+
console.log(` ${c.green}${line}${c.reset}`);
|
|
635
|
+
console.log(` ${c.green}${c.bold} PIPELY AI — LOCAL MODE${c.reset}`);
|
|
636
|
+
console.log(` ${c.green}${line}${c.reset}`);
|
|
637
|
+
console.log("");
|
|
638
|
+
console.log(` ${c.bold}Setup Key:${c.reset} ${c.yellow}${setupKey}${c.reset}`);
|
|
639
|
+
console.log("");
|
|
640
|
+
console.log(` ${c.bold}Diretorio:${c.reset} ${c.dim}${PIPELY_DIR}${c.reset}`);
|
|
641
|
+
console.log(` ${c.bold}Banco de dados:${c.reset} ${c.dim}${join(PIPELY_DIR, "data/pipely.db")}${c.reset}`);
|
|
642
|
+
console.log("");
|
|
643
|
+
|
|
644
|
+
return runLocal();
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
async function runLocal() {
|
|
648
|
+
if (!isLocalInstalled()) {
|
|
649
|
+
console.log(` ${c.red}✗ Pipely AI nao instalado. Execute: npx pipely-ai${c.reset}\n`);
|
|
650
|
+
process.exit(1);
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
console.log(` ${c.magenta}── Iniciando Pipely AI ────────────────────${c.reset}\n`);
|
|
654
|
+
|
|
655
|
+
const { fork } = require("node:child_process");
|
|
656
|
+
const envPath = getLocalEnvPath();
|
|
657
|
+
const envContent = existsSync(envPath) ? readFileSync(envPath, "utf-8") : "";
|
|
658
|
+
const envVars = {};
|
|
659
|
+
for (const line of envContent.split("\n")) {
|
|
660
|
+
const match = line.match(/^([A-Z_]+)=(.*)$/);
|
|
661
|
+
if (match) envVars[match[1]] = match[2];
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
const childEnv = { ...process.env, ...envVars };
|
|
665
|
+
|
|
666
|
+
// Start server
|
|
667
|
+
const server = fork(join(PIPELY_DIR, "server/dist/index.js"), [], {
|
|
668
|
+
cwd: join(PIPELY_DIR, "server"),
|
|
669
|
+
env: { ...childEnv, PORT: "3333" },
|
|
670
|
+
stdio: "inherit",
|
|
671
|
+
});
|
|
672
|
+
|
|
673
|
+
// Start agent
|
|
674
|
+
const agent = fork(join(PIPELY_DIR, "agent/dist/index.js"), [], {
|
|
675
|
+
cwd: join(PIPELY_DIR, "agent"),
|
|
676
|
+
env: { ...childEnv, PORT: "3335" },
|
|
677
|
+
stdio: "inherit",
|
|
678
|
+
});
|
|
679
|
+
|
|
680
|
+
// Serve frontend
|
|
681
|
+
const frontendDir = join(PIPELY_DIR, "frontend");
|
|
682
|
+
if (existsSync(frontendDir)) {
|
|
683
|
+
const express = await import("express").catch(() => null);
|
|
684
|
+
if (express) {
|
|
685
|
+
const app = express.default();
|
|
686
|
+
app.use(express.default.static(frontendDir));
|
|
687
|
+
app.get(/^\/(?!api|health).*/, (_req, res) => {
|
|
688
|
+
res.sendFile(join(frontendDir, "index.html"));
|
|
689
|
+
});
|
|
690
|
+
const fPort = envVars.FRONTEND_PORT || 3000;
|
|
691
|
+
app.listen(fPort, () => {
|
|
692
|
+
console.log(` ${c.cyan}Frontend:${c.reset} http://localhost:${fPort}`);
|
|
693
|
+
console.log(` ${c.cyan}Backend:${c.reset} http://localhost:3333`);
|
|
694
|
+
console.log(` ${c.cyan}Agent:${c.reset} http://localhost:3335`);
|
|
695
|
+
console.log("");
|
|
696
|
+
console.log(` Pressione ${c.bold}Ctrl+C${c.reset} para parar.\n`);
|
|
697
|
+
});
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
function shutdown() {
|
|
702
|
+
console.log(`\n Parando...\n`);
|
|
703
|
+
server.kill();
|
|
704
|
+
agent.kill();
|
|
705
|
+
process.exit(0);
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
process.on("SIGINT", shutdown);
|
|
709
|
+
process.on("SIGTERM", shutdown);
|
|
710
|
+
server.on("exit", (code) => { if (code) { agent.kill(); process.exit(1); } });
|
|
711
|
+
agent.on("exit", (code) => { if (code) { server.kill(); process.exit(1); } });
|
|
712
|
+
|
|
713
|
+
// Keep process alive
|
|
714
|
+
await new Promise(() => {});
|
|
715
|
+
}
|
|
716
|
+
|
|
448
717
|
// ── Banner ──────────────────────────────────────────
|
|
449
718
|
|
|
450
719
|
function printBanner() {
|
|
@@ -696,28 +965,27 @@ async function install() {
|
|
|
696
965
|
|
|
697
966
|
process.stdout.write(` Verificando Docker... `);
|
|
698
967
|
const docker = checkDocker();
|
|
968
|
+
|
|
699
969
|
if (!docker.ok) {
|
|
700
|
-
|
|
701
|
-
|
|
970
|
+
console.log(`${c.yellow}✗ Nao encontrado${c.reset}`);
|
|
971
|
+
console.log(`\n Docker nao detectado. Iniciando em ${c.cyan}modo local${c.reset} (SQLite, sem containers).\n`);
|
|
972
|
+
return installLocal();
|
|
702
973
|
}
|
|
703
974
|
console.log(`${c.green}✓${c.reset} v${docker.version}`);
|
|
704
975
|
|
|
705
976
|
const composeCmd = getComposeCmd();
|
|
706
977
|
if (!composeCmd) {
|
|
707
|
-
console.log(`\n ${c.
|
|
708
|
-
|
|
978
|
+
console.log(`\n ${c.yellow}✗ Docker Compose nao encontrado${c.reset}`);
|
|
979
|
+
console.log(` Iniciando em ${c.cyan}modo local${c.reset}.\n`);
|
|
980
|
+
return installLocal();
|
|
709
981
|
}
|
|
710
982
|
|
|
711
983
|
try {
|
|
712
984
|
execSync("docker info", { stdio: "pipe" });
|
|
713
985
|
} catch {
|
|
714
|
-
console.log(`\n ${c.
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
} else {
|
|
718
|
-
console.log(` Execute: sudo systemctl start docker\n`);
|
|
719
|
-
}
|
|
720
|
-
process.exit(1);
|
|
986
|
+
console.log(`\n ${c.yellow}✗ Docker nao esta rodando${c.reset}`);
|
|
987
|
+
console.log(` Iniciando em ${c.cyan}modo local${c.reset}.\n`);
|
|
988
|
+
return installLocal();
|
|
721
989
|
}
|
|
722
990
|
|
|
723
991
|
console.log("");
|
|
@@ -877,30 +1145,85 @@ async function install() {
|
|
|
877
1145
|
// ── ROUTER ───────────────────────────────────────────
|
|
878
1146
|
// ══════════════════════════════════════════════════════
|
|
879
1147
|
|
|
1148
|
+
// ── Local-mode commands ─────────────────────────────
|
|
1149
|
+
|
|
1150
|
+
function cmdLocalKeys() {
|
|
1151
|
+
if (!isLocalInstalled()) {
|
|
1152
|
+
console.log(`\n ${c.red}✗ Pipely AI nao instalado localmente${c.reset}\n`);
|
|
1153
|
+
process.exit(1);
|
|
1154
|
+
}
|
|
1155
|
+
const env = readEnvFile(PIPELY_DIR);
|
|
1156
|
+
console.log("");
|
|
1157
|
+
console.log(` ${c.bold}PIPELY AI — Chaves (Local)${c.reset}\n`);
|
|
1158
|
+
if (env.OWNER_SETUP_KEY) {
|
|
1159
|
+
console.log(` Setup Key: ${c.yellow}${env.OWNER_SETUP_KEY}${c.reset}`);
|
|
1160
|
+
}
|
|
1161
|
+
if (env.JWT_SECRET) {
|
|
1162
|
+
console.log(` JWT Secret: ${c.yellow}${env.JWT_SECRET.slice(0, 16)}...${c.reset}`);
|
|
1163
|
+
}
|
|
1164
|
+
console.log(`\n Diretorio: ${c.dim}${PIPELY_DIR}${c.reset}`);
|
|
1165
|
+
console.log("");
|
|
1166
|
+
}
|
|
1167
|
+
|
|
1168
|
+
function cmdLocalStart() {
|
|
1169
|
+
if (!isLocalInstalled()) {
|
|
1170
|
+
console.log(`\n ${c.red}✗ Pipely AI nao instalado. Execute: npx pipely-ai${c.reset}\n`);
|
|
1171
|
+
process.exit(1);
|
|
1172
|
+
}
|
|
1173
|
+
runLocal().catch((err) => {
|
|
1174
|
+
console.error(`\n ${c.red}Erro: ${err.message}${c.reset}\n`);
|
|
1175
|
+
process.exit(1);
|
|
1176
|
+
});
|
|
1177
|
+
}
|
|
1178
|
+
|
|
1179
|
+
function isLocalMode() {
|
|
1180
|
+
// Local mode if: local install exists AND no Docker project found
|
|
1181
|
+
return isLocalInstalled() && !findProjectDir();
|
|
1182
|
+
}
|
|
1183
|
+
|
|
1184
|
+
// ══════════════════════════════════════════════════════
|
|
1185
|
+
// ── ROUTER ───────────────────────────────────────────
|
|
1186
|
+
// ══════════════════════════════════════════════════════
|
|
1187
|
+
|
|
880
1188
|
const args = process.argv.slice(2);
|
|
881
1189
|
const command = args[0];
|
|
1190
|
+
const local = isLocalMode();
|
|
882
1191
|
|
|
883
1192
|
switch (command) {
|
|
884
1193
|
case "status":
|
|
885
|
-
cmdStatus();
|
|
1194
|
+
if (local) { cmdLocalKeys(); } else { cmdStatus(); }
|
|
886
1195
|
break;
|
|
887
1196
|
case "keys":
|
|
888
|
-
cmdKeys();
|
|
1197
|
+
if (local) { cmdLocalKeys(); } else { cmdKeys(); }
|
|
889
1198
|
break;
|
|
890
1199
|
case "logs":
|
|
891
|
-
|
|
1200
|
+
if (local) { console.log(`\n ${c.dim}Logs aparecem no terminal ao iniciar com: npx pipely-ai start${c.reset}\n`); }
|
|
1201
|
+
else { cmdLogs(args[1]); }
|
|
892
1202
|
break;
|
|
893
1203
|
case "stop":
|
|
894
|
-
|
|
1204
|
+
if (local) { console.log(`\n ${c.dim}Pressione Ctrl+C no terminal onde esta rodando${c.reset}\n`); }
|
|
1205
|
+
else { cmdStop(); }
|
|
895
1206
|
break;
|
|
896
1207
|
case "start":
|
|
897
|
-
cmdStart();
|
|
1208
|
+
if (local) { cmdLocalStart(); } else { cmdStart(); }
|
|
898
1209
|
break;
|
|
899
1210
|
case "restart":
|
|
900
|
-
|
|
1211
|
+
if (local) { console.log(`\n ${c.dim}Pressione Ctrl+C e rode novamente: npx pipely-ai start${c.reset}\n`); }
|
|
1212
|
+
else { cmdRestart(); }
|
|
901
1213
|
break;
|
|
902
1214
|
case "update":
|
|
903
|
-
|
|
1215
|
+
if (local) {
|
|
1216
|
+
// Delete and re-download bundle
|
|
1217
|
+
const { rmSync: rm } = require("node:fs");
|
|
1218
|
+
try { rm(PIPELY_DIR, { recursive: true }); } catch {}
|
|
1219
|
+
console.log(`\n ${c.dim}Reinstalando...${c.reset}\n`);
|
|
1220
|
+
installLocal().catch((err) => {
|
|
1221
|
+
console.error(`\n ${c.red}Erro: ${err.message}${c.reset}\n`);
|
|
1222
|
+
process.exit(1);
|
|
1223
|
+
});
|
|
1224
|
+
} else {
|
|
1225
|
+
cmdUpdate();
|
|
1226
|
+
}
|
|
904
1227
|
break;
|
|
905
1228
|
case "help":
|
|
906
1229
|
case "--help":
|