@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.
Files changed (2) hide show
  1. package/dist/index.js +153 -67
  2. 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) return null;
993
+ if (detections.length === 0)
994
+ return Promise.resolve({ edes: null, error: "Sin detecciones" });
975
995
  const prompt = buildPrompt(detections);
976
- try {
977
- const result = spawnSync("claude", ["--print"], {
978
- input: prompt,
979
- encoding: "utf-8",
980
- timeout: 12e4,
981
- shell: true,
982
- maxBuffer: 5 * 1024 * 1024
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
- if (result.status !== 0 || !result.stdout) return null;
985
- const jsonStr = extractJson(result.stdout);
986
- const parsed = JSON.parse(jsonStr);
987
- if (!Array.isArray(parsed) || parsed.length === 0) return null;
988
- const edes = [];
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
- const ede = normalizeEde(parsed[i], i);
992
- if (ede.whatAndHow.decision && ede.why.rationale) {
993
- edes.push(ede);
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
- return edes.length > 0 ? edes : null;
999
- } catch {
1000
- return null;
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, onStatus) {
1146
+ async function generateProposals(detections) {
1147
+ const w = (s) => process.stdout.write(s);
1058
1148
  if (isClaudeAvailable()) {
1059
- onStatus?.("Generando EDEs con Claude Code...");
1060
- const claudeEdes = generateWithClaude(detections);
1061
- if (claudeEdes && claudeEdes.length > 0) {
1062
- return { proposals: claudeEdes, source: "claude" };
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
- onStatus?.("Claude no pudo generar EDEs, usando templates...");
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 join12 } from "path";
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 = join12(projectPath, ".claude");
1422
+ const claudeDir = join13(projectPath, ".claude");
1326
1423
  mkdirSync2(claudeDir, { recursive: true });
1327
- const settingsPath = join12(claudeDir, "settings.json");
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
- writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n", "utf-8");
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 writeFileSync2, mkdirSync as mkdirSync3 } from "fs";
1378
- import { join as join13 } from "path";
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 = join13(projectPath, ".claude");
1578
+ const claudeDir = join14(projectPath, ".claude");
1482
1579
  mkdirSync3(claudeDir, { recursive: true });
1483
- writeFileSync2(join13(claudeDir, "CLAUDE.md"), content, "utf-8");
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 join14, dirname as dirname2 } from "path";
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 = join14(__dirname, "mcp-entry.js");
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 writeFileSync3, mkdirSync as mkdirSync4 } from "fs";
1649
- import { join as join15 } from "path";
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 = join15(homedir3(), ".umbral");
1653
- var COMPOSE_PATH = join15(UMBRAL_DIR, "docker-compose.yml");
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
- execSync("docker --version", { stdio: "ignore" });
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
- execSync("docker compose version", { stdio: "ignore" });
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 = join15(UMBRAL_DIR, "umbral.db").replace(/\\/g, "/");
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
- writeFileSync3(COMPOSE_PATH, compose, "utf-8");
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
- execSync(`docker compose -f "${COMPOSE_PATH}" up -d`, {
1839
+ execSync2(`docker compose -f "${COMPOSE_PATH}" up -d`, {
1754
1840
  stdio: "inherit"
1755
1841
  });
1756
1842
  } else {
1757
- const child = spawn("docker", ["compose", "-f", COMPOSE_PATH, "up"], {
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 execSync2 } from "child_process";
1870
+ import { execSync as execSync3 } from "child_process";
1785
1871
  import { existsSync as existsSync12 } from "fs";
1786
- import { join as join16 } from "path";
1872
+ import { join as join17 } from "path";
1787
1873
  import { homedir as homedir4 } from "os";
1788
- var COMPOSE_PATH2 = join16(homedir4(), ".umbral", "docker-compose.yml");
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
- execSync2(`docker compose -f "${COMPOSE_PATH2}" down`, {
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.4");
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);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umbral/cli",
3
- "version": "0.0.4",
3
+ "version": "0.0.6",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "bin": {