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.
Files changed (2) hide show
  1. package/index.js +341 -18
  2. 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
- printDockerHelp(os);
701
- process.exit(1);
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.red}✗ Docker Compose nao encontrado${c.reset}\n`);
708
- process.exit(1);
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.red}✗ Docker nao esta rodando${c.reset}`);
715
- if (os.platform === "win32" || os.platform === "darwin") {
716
- console.log(` Abra o Docker Desktop e tente novamente.\n`);
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
- cmdLogs(args[1]);
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
- cmdStop();
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
- cmdRestart();
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
- cmdUpdate();
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":
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pipely-ai",
3
- "version": "1.0.0",
3
+ "version": "1.2.0",
4
4
  "description": "Pipely AI — Instale, gerencie e atualize com um unico comando",
5
5
  "type": "module",
6
6
  "bin": {