@umbral/cli 0.0.4 → 0.0.6
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/dist/index.js +153 -67
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -854,7 +854,10 @@ function getTemplate(detection) {
|
|
|
854
854
|
}
|
|
855
855
|
|
|
856
856
|
// src/claude-generate.ts
|
|
857
|
-
import { spawnSync } from "child_process";
|
|
857
|
+
import { spawn, spawnSync } from "child_process";
|
|
858
|
+
import { writeFileSync, unlinkSync } from "fs";
|
|
859
|
+
import { join as join12 } from "path";
|
|
860
|
+
import { tmpdir } from "os";
|
|
858
861
|
function isClaudeAvailable() {
|
|
859
862
|
try {
|
|
860
863
|
const r = spawnSync("claude", ["--version"], {
|
|
@@ -970,35 +973,121 @@ function normalizeEde(raw, index) {
|
|
|
970
973
|
}
|
|
971
974
|
};
|
|
972
975
|
}
|
|
976
|
+
function parseEdes(stdout2) {
|
|
977
|
+
const jsonStr = extractJson(stdout2);
|
|
978
|
+
const parsed = JSON.parse(jsonStr);
|
|
979
|
+
if (!Array.isArray(parsed) || parsed.length === 0) return null;
|
|
980
|
+
const edes = [];
|
|
981
|
+
for (let i = 0; i < parsed.length; i++) {
|
|
982
|
+
try {
|
|
983
|
+
const ede = normalizeEde(parsed[i], i);
|
|
984
|
+
if (ede.whatAndHow.decision && ede.why.rationale) {
|
|
985
|
+
edes.push(ede);
|
|
986
|
+
}
|
|
987
|
+
} catch {
|
|
988
|
+
}
|
|
989
|
+
}
|
|
990
|
+
return edes.length > 0 ? edes : null;
|
|
991
|
+
}
|
|
973
992
|
function generateWithClaude(detections) {
|
|
974
|
-
if (detections.length === 0)
|
|
993
|
+
if (detections.length === 0)
|
|
994
|
+
return Promise.resolve({ edes: null, error: "Sin detecciones" });
|
|
975
995
|
const prompt = buildPrompt(detections);
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
996
|
+
const tmpFile = join12(tmpdir(), `umbral-prompt-${Date.now()}.txt`);
|
|
997
|
+
writeFileSync(tmpFile, prompt, "utf-8");
|
|
998
|
+
const readCmd = process.platform === "win32" ? `type "${tmpFile}"` : `cat "${tmpFile}"`;
|
|
999
|
+
return new Promise((resolve) => {
|
|
1000
|
+
const child = spawn(
|
|
1001
|
+
process.platform === "win32" ? "cmd.exe" : "/bin/sh",
|
|
1002
|
+
process.platform === "win32" ? ["/c", `${readCmd} | claude --print`] : ["-c", `${readCmd} | claude --print`],
|
|
1003
|
+
{ stdio: ["ignore", "pipe", "pipe"] }
|
|
1004
|
+
);
|
|
1005
|
+
let stdout2 = "";
|
|
1006
|
+
let stderr = "";
|
|
1007
|
+
let timedOut = false;
|
|
1008
|
+
const timeout = setTimeout(() => {
|
|
1009
|
+
timedOut = true;
|
|
1010
|
+
child.kill();
|
|
1011
|
+
}, 12e4);
|
|
1012
|
+
child.stdout.on("data", (chunk) => {
|
|
1013
|
+
stdout2 += chunk.toString();
|
|
983
1014
|
});
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
for (let i = 0; i < parsed.length; i++) {
|
|
1015
|
+
child.stderr.on("data", (chunk) => {
|
|
1016
|
+
stderr += chunk.toString();
|
|
1017
|
+
});
|
|
1018
|
+
child.on("close", (code) => {
|
|
1019
|
+
clearTimeout(timeout);
|
|
990
1020
|
try {
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
1021
|
+
unlinkSync(tmpFile);
|
|
1022
|
+
} catch {
|
|
1023
|
+
}
|
|
1024
|
+
if (timedOut) {
|
|
1025
|
+
resolve({ edes: null, error: "Timeout (>2min)" });
|
|
1026
|
+
return;
|
|
1027
|
+
}
|
|
1028
|
+
if (code !== 0 || !stdout2.trim()) {
|
|
1029
|
+
const reason = stderr.trim().split("\n")[0] || `exit code ${code}`;
|
|
1030
|
+
resolve({ edes: null, error: reason });
|
|
1031
|
+
return;
|
|
1032
|
+
}
|
|
1033
|
+
try {
|
|
1034
|
+
const edes = parseEdes(stdout2);
|
|
1035
|
+
if (!edes) {
|
|
1036
|
+
resolve({
|
|
1037
|
+
edes: null,
|
|
1038
|
+
error: "Claude respondio pero el JSON no es valido"
|
|
1039
|
+
});
|
|
1040
|
+
return;
|
|
994
1041
|
}
|
|
1042
|
+
resolve({ edes });
|
|
1043
|
+
} catch (e) {
|
|
1044
|
+
resolve({ edes: null, error: `Error parseando JSON: ${e.message}` });
|
|
1045
|
+
}
|
|
1046
|
+
});
|
|
1047
|
+
child.on("error", (err) => {
|
|
1048
|
+
clearTimeout(timeout);
|
|
1049
|
+
try {
|
|
1050
|
+
unlinkSync(tmpFile);
|
|
995
1051
|
} catch {
|
|
996
1052
|
}
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
}
|
|
1000
|
-
|
|
1053
|
+
resolve({ edes: null, error: err.message });
|
|
1054
|
+
});
|
|
1055
|
+
});
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1058
|
+
// src/spinner.ts
|
|
1059
|
+
var FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
1060
|
+
function createSpinner(text) {
|
|
1061
|
+
let i = 0;
|
|
1062
|
+
const start = Date.now();
|
|
1063
|
+
const w = process.stdout;
|
|
1064
|
+
function elapsed() {
|
|
1065
|
+
const s = Math.floor((Date.now() - start) / 1e3);
|
|
1066
|
+
return s < 60 ? `${s}s` : `${Math.floor(s / 60)}m ${s % 60}s`;
|
|
1067
|
+
}
|
|
1068
|
+
function render() {
|
|
1069
|
+
w.write(`\r ${FRAMES[i % FRAMES.length]} ${text} (${elapsed()})`);
|
|
1070
|
+
i++;
|
|
1071
|
+
}
|
|
1072
|
+
render();
|
|
1073
|
+
const interval = setInterval(render, 80);
|
|
1074
|
+
function clear(symbol, msg) {
|
|
1075
|
+
clearInterval(interval);
|
|
1076
|
+
const line = ` ${symbol} ${msg}`;
|
|
1077
|
+
w.write(`\r${line}${"".padEnd(40)}
|
|
1078
|
+
`);
|
|
1001
1079
|
}
|
|
1080
|
+
return {
|
|
1081
|
+
update(newText) {
|
|
1082
|
+
text = newText;
|
|
1083
|
+
},
|
|
1084
|
+
succeed(msg) {
|
|
1085
|
+
clear("\u2713", msg);
|
|
1086
|
+
},
|
|
1087
|
+
fail(msg) {
|
|
1088
|
+
clear("\u2717", msg);
|
|
1089
|
+
}
|
|
1090
|
+
};
|
|
1002
1091
|
}
|
|
1003
1092
|
|
|
1004
1093
|
// src/generate.ts
|
|
@@ -1054,14 +1143,22 @@ function generateFromTemplates(detections) {
|
|
|
1054
1143
|
}
|
|
1055
1144
|
return proposals;
|
|
1056
1145
|
}
|
|
1057
|
-
function generateProposals(detections
|
|
1146
|
+
async function generateProposals(detections) {
|
|
1147
|
+
const w = (s) => process.stdout.write(s);
|
|
1058
1148
|
if (isClaudeAvailable()) {
|
|
1059
|
-
|
|
1060
|
-
const
|
|
1061
|
-
if (
|
|
1062
|
-
|
|
1149
|
+
const spinner = createSpinner("Generando EDEs con Claude Code...");
|
|
1150
|
+
const result = await generateWithClaude(detections);
|
|
1151
|
+
if (result.edes && result.edes.length > 0) {
|
|
1152
|
+
spinner.succeed(
|
|
1153
|
+
`${result.edes.length} EDEs generadas con Claude Code (IA)`
|
|
1154
|
+
);
|
|
1155
|
+
return { proposals: result.edes, source: "claude" };
|
|
1063
1156
|
}
|
|
1064
|
-
|
|
1157
|
+
spinner.fail(
|
|
1158
|
+
`Claude fallo: ${result.error ?? "respuesta vacia"} \u2014 usando templates`
|
|
1159
|
+
);
|
|
1160
|
+
} else {
|
|
1161
|
+
w(" \u21B3 Claude Code no disponible \u2014 usando templates locales\n");
|
|
1065
1162
|
}
|
|
1066
1163
|
return { proposals: generateFromTemplates(detections), source: "templates" };
|
|
1067
1164
|
}
|
|
@@ -1319,12 +1416,12 @@ function setupDatabase(edes) {
|
|
|
1319
1416
|
}
|
|
1320
1417
|
|
|
1321
1418
|
// src/setup/hooks.ts
|
|
1322
|
-
import { existsSync as existsSync10, readFileSync as readFileSync10, writeFileSync, mkdirSync as mkdirSync2 } from "fs";
|
|
1323
|
-
import { join as
|
|
1419
|
+
import { existsSync as existsSync10, readFileSync as readFileSync10, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2 } from "fs";
|
|
1420
|
+
import { join as join13 } from "path";
|
|
1324
1421
|
function setupHooks(projectPath) {
|
|
1325
|
-
const claudeDir =
|
|
1422
|
+
const claudeDir = join13(projectPath, ".claude");
|
|
1326
1423
|
mkdirSync2(claudeDir, { recursive: true });
|
|
1327
|
-
const settingsPath =
|
|
1424
|
+
const settingsPath = join13(claudeDir, "settings.json");
|
|
1328
1425
|
let existing = {};
|
|
1329
1426
|
if (existsSync10(settingsPath)) {
|
|
1330
1427
|
try {
|
|
@@ -1370,12 +1467,12 @@ function setupHooks(projectPath) {
|
|
|
1370
1467
|
}
|
|
1371
1468
|
}
|
|
1372
1469
|
};
|
|
1373
|
-
|
|
1470
|
+
writeFileSync2(settingsPath, JSON.stringify(settings, null, 2) + "\n", "utf-8");
|
|
1374
1471
|
}
|
|
1375
1472
|
|
|
1376
1473
|
// src/setup/context.ts
|
|
1377
|
-
import { writeFileSync as
|
|
1378
|
-
import { join as
|
|
1474
|
+
import { writeFileSync as writeFileSync3, mkdirSync as mkdirSync3 } from "fs";
|
|
1475
|
+
import { join as join14 } from "path";
|
|
1379
1476
|
|
|
1380
1477
|
// ../orchestrator/src/claude-context.ts
|
|
1381
1478
|
function assembleClaudeContext(edes) {
|
|
@@ -1478,9 +1575,9 @@ function setupContext(projectPath) {
|
|
|
1478
1575
|
const edes = createEdeStore(db).getAll();
|
|
1479
1576
|
db.close();
|
|
1480
1577
|
const content = assembleClaudeContext(edes);
|
|
1481
|
-
const claudeDir =
|
|
1578
|
+
const claudeDir = join14(projectPath, ".claude");
|
|
1482
1579
|
mkdirSync3(claudeDir, { recursive: true });
|
|
1483
|
-
|
|
1580
|
+
writeFileSync3(join14(claudeDir, "CLAUDE.md"), content, "utf-8");
|
|
1484
1581
|
}
|
|
1485
1582
|
|
|
1486
1583
|
// src/commands/init.ts
|
|
@@ -1500,18 +1597,7 @@ async function initCommand(options) {
|
|
|
1500
1597
|
}
|
|
1501
1598
|
}
|
|
1502
1599
|
w("\n");
|
|
1503
|
-
const { proposals, source } = generateProposals(
|
|
1504
|
-
analysis.detections,
|
|
1505
|
-
(msg) => w(` ${msg}
|
|
1506
|
-
`)
|
|
1507
|
-
);
|
|
1508
|
-
if (source === "claude") {
|
|
1509
|
-
w(` \u2713 ${proposals.length} EDEs generadas con Claude Code (IA)
|
|
1510
|
-
`);
|
|
1511
|
-
} else {
|
|
1512
|
-
w(` \u21B3 EDEs generadas desde templates locales
|
|
1513
|
-
`);
|
|
1514
|
-
}
|
|
1600
|
+
const { proposals, source } = await generateProposals(analysis.detections);
|
|
1515
1601
|
if (proposals.length === 0) {
|
|
1516
1602
|
w("\n No hay propuestas de EDEs para generar.\n");
|
|
1517
1603
|
w(" Configurando infraestructura base...\n\n");
|
|
@@ -1633,28 +1719,28 @@ async function hookCommand(_event) {
|
|
|
1633
1719
|
|
|
1634
1720
|
// src/commands/mcp.ts
|
|
1635
1721
|
import { execFileSync } from "child_process";
|
|
1636
|
-
import { join as
|
|
1722
|
+
import { join as join15, dirname as dirname2 } from "path";
|
|
1637
1723
|
import { fileURLToPath } from "url";
|
|
1638
1724
|
async function mcpCommand() {
|
|
1639
1725
|
const __dirname = dirname2(fileURLToPath(import.meta.url));
|
|
1640
|
-
const mcpEntry =
|
|
1726
|
+
const mcpEntry = join15(__dirname, "mcp-entry.js");
|
|
1641
1727
|
execFileSync(process.execPath, [mcpEntry], {
|
|
1642
1728
|
stdio: "inherit"
|
|
1643
1729
|
});
|
|
1644
1730
|
}
|
|
1645
1731
|
|
|
1646
1732
|
// src/commands/start.ts
|
|
1647
|
-
import { execSync, spawn } from "child_process";
|
|
1648
|
-
import { writeFileSync as
|
|
1649
|
-
import { join as
|
|
1733
|
+
import { execSync as execSync2, spawn as spawn2 } from "child_process";
|
|
1734
|
+
import { writeFileSync as writeFileSync4, mkdirSync as mkdirSync4 } from "fs";
|
|
1735
|
+
import { join as join16 } from "path";
|
|
1650
1736
|
import { homedir as homedir3 } from "os";
|
|
1651
1737
|
import { createServer } from "net";
|
|
1652
|
-
var UMBRAL_DIR =
|
|
1653
|
-
var COMPOSE_PATH =
|
|
1738
|
+
var UMBRAL_DIR = join16(homedir3(), ".umbral");
|
|
1739
|
+
var COMPOSE_PATH = join16(UMBRAL_DIR, "docker-compose.yml");
|
|
1654
1740
|
var DOCKER_IMAGE = process.env.UMBRAL_IMAGE ?? "ghcr.io/josephrobles23/umbral-web:latest";
|
|
1655
1741
|
function dockerInstalled() {
|
|
1656
1742
|
try {
|
|
1657
|
-
|
|
1743
|
+
execSync2("docker --version", { stdio: "ignore" });
|
|
1658
1744
|
return true;
|
|
1659
1745
|
} catch {
|
|
1660
1746
|
return false;
|
|
@@ -1662,7 +1748,7 @@ function dockerInstalled() {
|
|
|
1662
1748
|
}
|
|
1663
1749
|
function composeInstalled() {
|
|
1664
1750
|
try {
|
|
1665
|
-
|
|
1751
|
+
execSync2("docker compose version", { stdio: "ignore" });
|
|
1666
1752
|
return true;
|
|
1667
1753
|
} catch {
|
|
1668
1754
|
return false;
|
|
@@ -1683,7 +1769,7 @@ function findFreePort(start) {
|
|
|
1683
1769
|
});
|
|
1684
1770
|
}
|
|
1685
1771
|
function generateCompose(webPort, wsPort) {
|
|
1686
|
-
const dbPath =
|
|
1772
|
+
const dbPath = join16(UMBRAL_DIR, "umbral.db").replace(/\\/g, "/");
|
|
1687
1773
|
const dbDir = UMBRAL_DIR.replace(/\\/g, "/");
|
|
1688
1774
|
return `# Auto-generated by Umbral CLI \u2014 do not edit manually
|
|
1689
1775
|
name: umbral
|
|
@@ -1743,18 +1829,18 @@ async function startCommand(options) {
|
|
|
1743
1829
|
const wsPort = await findFreePort(webPort + 99);
|
|
1744
1830
|
mkdirSync4(UMBRAL_DIR, { recursive: true });
|
|
1745
1831
|
const compose = generateCompose(webPort, wsPort);
|
|
1746
|
-
|
|
1832
|
+
writeFileSync4(COMPOSE_PATH, compose, "utf-8");
|
|
1747
1833
|
w(` \u2713 docker-compose.yml generado
|
|
1748
1834
|
`);
|
|
1749
1835
|
w(" \u27F3 Descargando imagenes y levantando servicios...\n\n");
|
|
1750
1836
|
const detach = options.detach !== false;
|
|
1751
1837
|
try {
|
|
1752
1838
|
if (detach) {
|
|
1753
|
-
|
|
1839
|
+
execSync2(`docker compose -f "${COMPOSE_PATH}" up -d`, {
|
|
1754
1840
|
stdio: "inherit"
|
|
1755
1841
|
});
|
|
1756
1842
|
} else {
|
|
1757
|
-
const child =
|
|
1843
|
+
const child = spawn2("docker", ["compose", "-f", COMPOSE_PATH, "up"], {
|
|
1758
1844
|
stdio: "inherit"
|
|
1759
1845
|
});
|
|
1760
1846
|
child.on("close", (code) => process.exit(code ?? 0));
|
|
@@ -1781,11 +1867,11 @@ async function startCommand(options) {
|
|
|
1781
1867
|
}
|
|
1782
1868
|
|
|
1783
1869
|
// src/commands/stop.ts
|
|
1784
|
-
import { execSync as
|
|
1870
|
+
import { execSync as execSync3 } from "child_process";
|
|
1785
1871
|
import { existsSync as existsSync12 } from "fs";
|
|
1786
|
-
import { join as
|
|
1872
|
+
import { join as join17 } from "path";
|
|
1787
1873
|
import { homedir as homedir4 } from "os";
|
|
1788
|
-
var COMPOSE_PATH2 =
|
|
1874
|
+
var COMPOSE_PATH2 = join17(homedir4(), ".umbral", "docker-compose.yml");
|
|
1789
1875
|
function stopCommand() {
|
|
1790
1876
|
const w = (s) => process.stdout.write(s);
|
|
1791
1877
|
w("\n Umbral \u2014 Deteniendo plataforma\n\n");
|
|
@@ -1795,7 +1881,7 @@ function stopCommand() {
|
|
|
1795
1881
|
process.exit(1);
|
|
1796
1882
|
}
|
|
1797
1883
|
try {
|
|
1798
|
-
|
|
1884
|
+
execSync3(`docker compose -f "${COMPOSE_PATH2}" down`, {
|
|
1799
1885
|
stdio: "inherit"
|
|
1800
1886
|
});
|
|
1801
1887
|
w("\n \u2713 Servicios detenidos.\n\n");
|
|
@@ -1807,7 +1893,7 @@ function stopCommand() {
|
|
|
1807
1893
|
|
|
1808
1894
|
// src/index.ts
|
|
1809
1895
|
var program = new Command();
|
|
1810
|
-
program.name("umbral").description("Umbral \u2014 Framework de gobernanza para proyectos con Claude Code").version("0.0.
|
|
1896
|
+
program.name("umbral").description("Umbral \u2014 Framework de gobernanza para proyectos con Claude Code").version("0.0.6");
|
|
1811
1897
|
program.command("init").description("Inicializar Umbral en el proyecto actual").option("--yes", "Aceptar todas las propuestas sin preguntar").option("--path <path>", "Ruta al proyecto (default: directorio actual)").action(initCommand);
|
|
1812
1898
|
program.command("start").description("Levantar la plataforma Umbral (Neo4j + Dashboard web)").option("--port <port>", "Puerto para el dashboard (default: auto)").option("--no-detach", "Correr en primer plano (sin -d)").action(startCommand);
|
|
1813
1899
|
program.command("stop").description("Detener la plataforma Umbral").action(stopCommand);
|