pipely-ai 1.1.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 +340 -18
- 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() {
|
|
@@ -697,28 +965,27 @@ async function install() {
|
|
|
697
965
|
|
|
698
966
|
process.stdout.write(` Verificando Docker... `);
|
|
699
967
|
const docker = checkDocker();
|
|
968
|
+
|
|
700
969
|
if (!docker.ok) {
|
|
701
|
-
|
|
702
|
-
|
|
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();
|
|
703
973
|
}
|
|
704
974
|
console.log(`${c.green}✓${c.reset} v${docker.version}`);
|
|
705
975
|
|
|
706
976
|
const composeCmd = getComposeCmd();
|
|
707
977
|
if (!composeCmd) {
|
|
708
|
-
console.log(`\n ${c.
|
|
709
|
-
|
|
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();
|
|
710
981
|
}
|
|
711
982
|
|
|
712
983
|
try {
|
|
713
984
|
execSync("docker info", { stdio: "pipe" });
|
|
714
985
|
} catch {
|
|
715
|
-
console.log(`\n ${c.
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
} else {
|
|
719
|
-
console.log(` Execute: sudo systemctl start docker\n`);
|
|
720
|
-
}
|
|
721
|
-
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();
|
|
722
989
|
}
|
|
723
990
|
|
|
724
991
|
console.log("");
|
|
@@ -878,30 +1145,85 @@ async function install() {
|
|
|
878
1145
|
// ── ROUTER ───────────────────────────────────────────
|
|
879
1146
|
// ══════════════════════════════════════════════════════
|
|
880
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
|
+
|
|
881
1188
|
const args = process.argv.slice(2);
|
|
882
1189
|
const command = args[0];
|
|
1190
|
+
const local = isLocalMode();
|
|
883
1191
|
|
|
884
1192
|
switch (command) {
|
|
885
1193
|
case "status":
|
|
886
|
-
cmdStatus();
|
|
1194
|
+
if (local) { cmdLocalKeys(); } else { cmdStatus(); }
|
|
887
1195
|
break;
|
|
888
1196
|
case "keys":
|
|
889
|
-
cmdKeys();
|
|
1197
|
+
if (local) { cmdLocalKeys(); } else { cmdKeys(); }
|
|
890
1198
|
break;
|
|
891
1199
|
case "logs":
|
|
892
|
-
|
|
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]); }
|
|
893
1202
|
break;
|
|
894
1203
|
case "stop":
|
|
895
|
-
|
|
1204
|
+
if (local) { console.log(`\n ${c.dim}Pressione Ctrl+C no terminal onde esta rodando${c.reset}\n`); }
|
|
1205
|
+
else { cmdStop(); }
|
|
896
1206
|
break;
|
|
897
1207
|
case "start":
|
|
898
|
-
cmdStart();
|
|
1208
|
+
if (local) { cmdLocalStart(); } else { cmdStart(); }
|
|
899
1209
|
break;
|
|
900
1210
|
case "restart":
|
|
901
|
-
|
|
1211
|
+
if (local) { console.log(`\n ${c.dim}Pressione Ctrl+C e rode novamente: npx pipely-ai start${c.reset}\n`); }
|
|
1212
|
+
else { cmdRestart(); }
|
|
902
1213
|
break;
|
|
903
1214
|
case "update":
|
|
904
|
-
|
|
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
|
+
}
|
|
905
1227
|
break;
|
|
906
1228
|
case "help":
|
|
907
1229
|
case "--help":
|