pipely-ai 1.1.0 → 1.3.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 +347 -19
  2. package/package.json +1 -1
package/index.js CHANGED
@@ -446,6 +446,274 @@ function printDockerHelp(os) {
446
446
  }
447
447
  }
448
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
+
449
717
  // ── Banner ──────────────────────────────────────────
450
718
 
451
719
  function printBanner() {
@@ -695,30 +963,34 @@ async function install() {
695
963
  const os = detectOS();
696
964
  console.log(` Sistema: ${c.bold}${os.label}${c.reset} (${os.arch})\n`);
697
965
 
966
+ if (forceLocal) {
967
+ console.log(` Modo: ${c.cyan}Local (--local)${c.reset}\n`);
968
+ return installLocal();
969
+ }
970
+
698
971
  process.stdout.write(` Verificando Docker... `);
699
972
  const docker = checkDocker();
973
+
700
974
  if (!docker.ok) {
701
- printDockerHelp(os);
702
- process.exit(1);
975
+ console.log(`${c.yellow}✗ Nao encontrado${c.reset}`);
976
+ console.log(`\n Docker nao detectado. Iniciando em ${c.cyan}modo local${c.reset} (SQLite, sem containers).\n`);
977
+ return installLocal();
703
978
  }
704
979
  console.log(`${c.green}✓${c.reset} v${docker.version}`);
705
980
 
706
981
  const composeCmd = getComposeCmd();
707
982
  if (!composeCmd) {
708
- console.log(`\n ${c.red}✗ Docker Compose nao encontrado${c.reset}\n`);
709
- process.exit(1);
983
+ console.log(`\n ${c.yellow}✗ Docker Compose nao encontrado${c.reset}`);
984
+ console.log(` Iniciando em ${c.cyan}modo local${c.reset}.\n`);
985
+ return installLocal();
710
986
  }
711
987
 
712
988
  try {
713
989
  execSync("docker info", { stdio: "pipe" });
714
990
  } catch {
715
- console.log(`\n ${c.red}✗ Docker nao esta rodando${c.reset}`);
716
- if (os.platform === "win32" || os.platform === "darwin") {
717
- console.log(` Abra o Docker Desktop e tente novamente.\n`);
718
- } else {
719
- console.log(` Execute: sudo systemctl start docker\n`);
720
- }
721
- process.exit(1);
991
+ console.log(`\n ${c.yellow}✗ Docker nao esta rodando${c.reset}`);
992
+ console.log(` Iniciando em ${c.cyan}modo local${c.reset}.\n`);
993
+ return installLocal();
722
994
  }
723
995
 
724
996
  console.log("");
@@ -878,30 +1150,86 @@ async function install() {
878
1150
  // ── ROUTER ───────────────────────────────────────────
879
1151
  // ══════════════════════════════════════════════════════
880
1152
 
1153
+ // ── Local-mode commands ─────────────────────────────
1154
+
1155
+ function cmdLocalKeys() {
1156
+ if (!isLocalInstalled()) {
1157
+ console.log(`\n ${c.red}✗ Pipely AI nao instalado localmente${c.reset}\n`);
1158
+ process.exit(1);
1159
+ }
1160
+ const env = readEnvFile(PIPELY_DIR);
1161
+ console.log("");
1162
+ console.log(` ${c.bold}PIPELY AI — Chaves (Local)${c.reset}\n`);
1163
+ if (env.OWNER_SETUP_KEY) {
1164
+ console.log(` Setup Key: ${c.yellow}${env.OWNER_SETUP_KEY}${c.reset}`);
1165
+ }
1166
+ if (env.JWT_SECRET) {
1167
+ console.log(` JWT Secret: ${c.yellow}${env.JWT_SECRET.slice(0, 16)}...${c.reset}`);
1168
+ }
1169
+ console.log(`\n Diretorio: ${c.dim}${PIPELY_DIR}${c.reset}`);
1170
+ console.log("");
1171
+ }
1172
+
1173
+ function cmdLocalStart() {
1174
+ if (!isLocalInstalled()) {
1175
+ console.log(`\n ${c.red}✗ Pipely AI nao instalado. Execute: npx pipely-ai${c.reset}\n`);
1176
+ process.exit(1);
1177
+ }
1178
+ runLocal().catch((err) => {
1179
+ console.error(`\n ${c.red}Erro: ${err.message}${c.reset}\n`);
1180
+ process.exit(1);
1181
+ });
1182
+ }
1183
+
1184
+ function isLocalMode() {
1185
+ // Local mode if: local install exists AND no Docker project found
1186
+ return isLocalInstalled() && !findProjectDir();
1187
+ }
1188
+
1189
+ // ══════════════════════════════════════════════════════
1190
+ // ── ROUTER ───────────────────────────────────────────
1191
+ // ══════════════════════════════════════════════════════
1192
+
881
1193
  const args = process.argv.slice(2);
882
- const command = args[0];
1194
+ const forceLocal = args.includes("--local");
1195
+ const command = args.filter((a) => a !== "--local")[0];
1196
+ const local = forceLocal || isLocalMode();
883
1197
 
884
1198
  switch (command) {
885
1199
  case "status":
886
- cmdStatus();
1200
+ if (local) { cmdLocalKeys(); } else { cmdStatus(); }
887
1201
  break;
888
1202
  case "keys":
889
- cmdKeys();
1203
+ if (local) { cmdLocalKeys(); } else { cmdKeys(); }
890
1204
  break;
891
1205
  case "logs":
892
- cmdLogs(args[1]);
1206
+ if (local) { console.log(`\n ${c.dim}Logs aparecem no terminal ao iniciar com: npx pipely-ai start${c.reset}\n`); }
1207
+ else { cmdLogs(args[1]); }
893
1208
  break;
894
1209
  case "stop":
895
- cmdStop();
1210
+ if (local) { console.log(`\n ${c.dim}Pressione Ctrl+C no terminal onde esta rodando${c.reset}\n`); }
1211
+ else { cmdStop(); }
896
1212
  break;
897
1213
  case "start":
898
- cmdStart();
1214
+ if (local) { cmdLocalStart(); } else { cmdStart(); }
899
1215
  break;
900
1216
  case "restart":
901
- cmdRestart();
1217
+ if (local) { console.log(`\n ${c.dim}Pressione Ctrl+C e rode novamente: npx pipely-ai start${c.reset}\n`); }
1218
+ else { cmdRestart(); }
902
1219
  break;
903
1220
  case "update":
904
- cmdUpdate();
1221
+ if (local) {
1222
+ // Delete and re-download bundle
1223
+ const { rmSync: rm } = require("node:fs");
1224
+ try { rm(PIPELY_DIR, { recursive: true }); } catch {}
1225
+ console.log(`\n ${c.dim}Reinstalando...${c.reset}\n`);
1226
+ installLocal().catch((err) => {
1227
+ console.error(`\n ${c.red}Erro: ${err.message}${c.reset}\n`);
1228
+ process.exit(1);
1229
+ });
1230
+ } else {
1231
+ cmdUpdate();
1232
+ }
905
1233
  break;
906
1234
  case "help":
907
1235
  case "--help":
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pipely-ai",
3
- "version": "1.1.0",
3
+ "version": "1.3.0",
4
4
  "description": "Pipely AI — Instale, gerencie e atualize com um unico comando",
5
5
  "type": "module",
6
6
  "bin": {