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.
- package/index.js +347 -19
- 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
|
-
|
|
702
|
-
|
|
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.
|
|
709
|
-
|
|
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.
|
|
716
|
-
|
|
717
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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":
|