@umbral/cli 0.0.2 → 0.0.3

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 +444 -22
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -3,6 +3,10 @@
3
3
  // src/index.ts
4
4
  import { Command } from "commander";
5
5
 
6
+ // src/analyze.ts
7
+ import { readdirSync } from "fs";
8
+ import { join as join11 } from "path";
9
+
6
10
  // src/detect/node.ts
7
11
  import { existsSync, readFileSync } from "fs";
8
12
  import { join } from "path";
@@ -258,17 +262,250 @@ var StylingDetector = class {
258
262
  }
259
263
  };
260
264
 
265
+ // src/detect/python.ts
266
+ import { existsSync as existsSync6, readFileSync as readFileSync7 } from "fs";
267
+ import { join as join7 } from "path";
268
+ function readTextFile(path) {
269
+ try {
270
+ return readFileSync7(path, "utf-8");
271
+ } catch {
272
+ return "";
273
+ }
274
+ }
275
+ function extractPythonDeps(projectPath) {
276
+ const deps = [];
277
+ const reqPath = join7(projectPath, "requirements.txt");
278
+ if (existsSync6(reqPath)) {
279
+ const content = readTextFile(reqPath);
280
+ for (const line of content.split("\n")) {
281
+ const trimmed = line.trim();
282
+ if (trimmed && !trimmed.startsWith("#") && !trimmed.startsWith("-")) {
283
+ const name = trimmed.split(/[>=<!\[;]/)[0].trim().toLowerCase();
284
+ if (name) deps.push(name);
285
+ }
286
+ }
287
+ }
288
+ const pyprojectPath = join7(projectPath, "pyproject.toml");
289
+ if (existsSync6(pyprojectPath)) {
290
+ const content = readTextFile(pyprojectPath);
291
+ const depMatches = content.match(/["']([a-zA-Z0-9_-]+)(?:\[.*?\])?(?:[>=<~!].*?)?["']/g);
292
+ if (depMatches) {
293
+ for (const m of depMatches) {
294
+ const name = m.replace(/["']/g, "").split(/[>=<~!\[]/)[0].trim().toLowerCase();
295
+ if (name && name.length > 1) deps.push(name);
296
+ }
297
+ }
298
+ }
299
+ return [...new Set(deps)];
300
+ }
301
+ var PythonDetector = class {
302
+ detect(projectPath) {
303
+ const hasPyproject = existsSync6(join7(projectPath, "pyproject.toml"));
304
+ const hasRequirements = existsSync6(join7(projectPath, "requirements.txt"));
305
+ const hasSetupPy = existsSync6(join7(projectPath, "setup.py"));
306
+ const hasPipfile = existsSync6(join7(projectPath, "Pipfile"));
307
+ const hasPoetryLock = existsSync6(join7(projectPath, "poetry.lock"));
308
+ const hasManagePy = existsSync6(join7(projectPath, "manage.py"));
309
+ if (!hasPyproject && !hasRequirements && !hasSetupPy && !hasPipfile && !hasManagePy) return [];
310
+ const results = [];
311
+ const evidence = [];
312
+ if (hasPyproject) evidence.push("pyproject.toml");
313
+ if (hasRequirements) evidence.push("requirements.txt");
314
+ if (hasSetupPy) evidence.push("setup.py");
315
+ if (hasPipfile) evidence.push("Pipfile");
316
+ let pythonVersion = "";
317
+ if (existsSync6(join7(projectPath, ".python-version"))) {
318
+ pythonVersion = readTextFile(join7(projectPath, ".python-version")).trim();
319
+ }
320
+ results.push({
321
+ category: "runtime",
322
+ name: `Python${pythonVersion ? ` ${pythonVersion}` : ""}`,
323
+ slug: "python",
324
+ confidence: 1,
325
+ evidence,
326
+ metadata: { pythonVersion }
327
+ });
328
+ if (hasPoetryLock) {
329
+ results.push({ category: "package-manager", name: "Poetry", slug: "poetry", confidence: 1, evidence: ["poetry.lock"], metadata: {} });
330
+ } else if (hasPipfile) {
331
+ results.push({ category: "package-manager", name: "Pipenv", slug: "pipenv", confidence: 1, evidence: ["Pipfile"], metadata: {} });
332
+ } else if (hasPyproject) {
333
+ const content = readTextFile(join7(projectPath, "pyproject.toml"));
334
+ if (content.includes("uv")) {
335
+ results.push({ category: "package-manager", name: "uv", slug: "uv", confidence: 0.7, evidence: ["pyproject.toml referencia uv"], metadata: {} });
336
+ }
337
+ }
338
+ const deps = extractPythonDeps(projectPath);
339
+ if (deps.includes("fastapi") || deps.includes("fastapi[standard]")) {
340
+ results.push({ category: "framework", name: "FastAPI", slug: "fastapi", confidence: 1, evidence: ["fastapi en dependencies"], metadata: {} });
341
+ } else if (hasManagePy || deps.includes("django")) {
342
+ results.push({ category: "framework", name: "Django", slug: "django", confidence: 1, evidence: [hasManagePy ? "manage.py" : "django en dependencies"], metadata: {} });
343
+ } else if (deps.includes("flask")) {
344
+ results.push({ category: "framework", name: "Flask", slug: "flask", confidence: 1, evidence: ["flask en dependencies"], metadata: {} });
345
+ } else if (deps.includes("streamlit")) {
346
+ results.push({ category: "framework", name: "Streamlit", slug: "streamlit", confidence: 1, evidence: ["streamlit en dependencies"], metadata: {} });
347
+ }
348
+ if (deps.includes("sqlalchemy")) {
349
+ results.push({ category: "database", name: "SQLAlchemy", slug: "sqlalchemy", confidence: 1, evidence: ["sqlalchemy en dependencies"], metadata: {} });
350
+ } else if (deps.includes("supabase")) {
351
+ results.push({ category: "database", name: "Supabase (PostgreSQL)", slug: "supabase", confidence: 1, evidence: ["supabase en dependencies"], metadata: {} });
352
+ } else if (deps.includes("psycopg2") || deps.includes("psycopg2-binary") || deps.includes("asyncpg")) {
353
+ results.push({ category: "database", name: "PostgreSQL", slug: "postgresql-python", confidence: 0.9, evidence: ["driver PostgreSQL en dependencies"], metadata: {} });
354
+ }
355
+ if (deps.includes("pytest")) {
356
+ results.push({ category: "testing", name: "pytest", slug: "pytest", confidence: 1, evidence: ["pytest en dependencies"], metadata: {} });
357
+ } else if (deps.includes("unittest")) {
358
+ results.push({ category: "testing", name: "unittest", slug: "unittest", confidence: 0.8, evidence: ["unittest en dependencies"], metadata: {} });
359
+ }
360
+ if (deps.includes("langchain") || deps.includes("langchain-core") || deps.includes("langgraph")) {
361
+ const parts = [];
362
+ if (deps.includes("langchain") || deps.includes("langchain-core")) parts.push("LangChain");
363
+ if (deps.includes("langgraph")) parts.push("LangGraph");
364
+ results.push({ category: "framework", name: parts.join(" + "), slug: "langchain", confidence: 1, evidence: parts.map((p) => `${p.toLowerCase()} en dependencies`), metadata: {} });
365
+ }
366
+ return results;
367
+ }
368
+ };
369
+
370
+ // src/detect/go.ts
371
+ import { existsSync as existsSync7, readFileSync as readFileSync8 } from "fs";
372
+ import { join as join8 } from "path";
373
+ var GoDetector = class {
374
+ detect(projectPath) {
375
+ const goModPath = join8(projectPath, "go.mod");
376
+ if (!existsSync7(goModPath)) return [];
377
+ const results = [];
378
+ let moduleName = "";
379
+ let goVersion = "";
380
+ try {
381
+ const content = readFileSync8(goModPath, "utf-8");
382
+ const moduleMatch = content.match(/^module\s+(\S+)/m);
383
+ const versionMatch = content.match(/^go\s+(\S+)/m);
384
+ if (moduleMatch) moduleName = moduleMatch[1];
385
+ if (versionMatch) goVersion = versionMatch[1];
386
+ } catch {
387
+ }
388
+ results.push({
389
+ category: "runtime",
390
+ name: `Go${goVersion ? ` ${goVersion}` : ""}`,
391
+ slug: "golang",
392
+ confidence: 1,
393
+ evidence: ["go.mod"],
394
+ metadata: { moduleName, goVersion }
395
+ });
396
+ return results;
397
+ }
398
+ };
399
+
400
+ // src/detect/rust.ts
401
+ import { existsSync as existsSync8, readFileSync as readFileSync9 } from "fs";
402
+ import { join as join9 } from "path";
403
+ var RustDetector = class {
404
+ detect(projectPath) {
405
+ const cargoPath = join9(projectPath, "Cargo.toml");
406
+ if (!existsSync8(cargoPath)) return [];
407
+ const results = [];
408
+ let edition = "";
409
+ try {
410
+ const content = readFileSync9(cargoPath, "utf-8");
411
+ const editionMatch = content.match(/edition\s*=\s*"(\d+)"/);
412
+ if (editionMatch) edition = editionMatch[1];
413
+ } catch {
414
+ }
415
+ results.push({
416
+ category: "runtime",
417
+ name: `Rust${edition ? ` (edition ${edition})` : ""}`,
418
+ slug: "rust",
419
+ confidence: 1,
420
+ evidence: ["Cargo.toml"],
421
+ metadata: { edition }
422
+ });
423
+ return results;
424
+ }
425
+ };
426
+
427
+ // src/detect/docker.ts
428
+ import { existsSync as existsSync9 } from "fs";
429
+ import { join as join10 } from "path";
430
+ var DockerDetector = class {
431
+ detect(projectPath) {
432
+ const results = [];
433
+ if (existsSync9(join10(projectPath, "Dockerfile"))) {
434
+ results.push({
435
+ category: "infra",
436
+ name: "Docker",
437
+ slug: "docker",
438
+ confidence: 1,
439
+ evidence: ["Dockerfile"],
440
+ metadata: {}
441
+ });
442
+ }
443
+ const composeNames = ["docker-compose.yml", "docker-compose.yaml", "compose.yml", "compose.yaml"];
444
+ const found = composeNames.find((n) => existsSync9(join10(projectPath, n)));
445
+ if (found) {
446
+ results.push({
447
+ category: "infra",
448
+ name: "Docker Compose",
449
+ slug: "docker-compose",
450
+ confidence: 1,
451
+ evidence: [found],
452
+ metadata: {}
453
+ });
454
+ }
455
+ return results;
456
+ }
457
+ };
458
+
261
459
  // src/analyze.ts
262
460
  var DETECTORS = [
263
461
  new NodeDetector(),
462
+ new PythonDetector(),
463
+ new GoDetector(),
464
+ new RustDetector(),
264
465
  new FrameworkDetector(),
265
466
  new DatabaseDetector(),
266
467
  new TestingDetector(),
267
468
  new BuildDetector(),
268
- new StylingDetector()
469
+ new StylingDetector(),
470
+ new DockerDetector()
269
471
  ];
472
+ var SKIP_DIRS = /* @__PURE__ */ new Set([
473
+ "node_modules",
474
+ ".git",
475
+ ".next",
476
+ "dist",
477
+ "build",
478
+ "__pycache__",
479
+ ".venv",
480
+ "venv",
481
+ "env",
482
+ ".env",
483
+ ".tox",
484
+ "coverage",
485
+ ".turbo",
486
+ ".cache",
487
+ "target",
488
+ "vendor"
489
+ ]);
490
+ function runDetectors(dirPath) {
491
+ return DETECTORS.flatMap((d) => d.detect(dirPath));
492
+ }
270
493
  function analyzeProject(projectPath) {
271
- const detections = DETECTORS.flatMap((d) => d.detect(projectPath));
494
+ const detections = runDetectors(projectPath);
495
+ try {
496
+ const entries = readdirSync(projectPath, { withFileTypes: true });
497
+ for (const entry of entries) {
498
+ if (!entry.isDirectory() || SKIP_DIRS.has(entry.name) || entry.name.startsWith(".")) continue;
499
+ const subPath = join11(projectPath, entry.name);
500
+ const subDetections = runDetectors(subPath);
501
+ for (const d of subDetections) {
502
+ d.subdir = entry.name;
503
+ d.name = `${d.name} (${entry.name}/)`;
504
+ detections.push(d);
505
+ }
506
+ }
507
+ } catch {
508
+ }
272
509
  detections.sort((a, b) => b.confidence - a.confidence);
273
510
  return { projectPath, detections };
274
511
  }
@@ -425,6 +662,191 @@ var TEMPLATES = [
425
662
  "No saltarse el pipeline de Turbo ejecutando scripts directamente en paquetes interdependientes.",
426
663
  "No ignorar los outputs de cache en turbo.json."
427
664
  ]
665
+ },
666
+ {
667
+ slugs: ["fastapi"],
668
+ cognitiveLevel: "navigator",
669
+ complexityTier: 2,
670
+ title: () => "FastAPI como framework backend",
671
+ decision: () => "El backend utiliza FastAPI para servir la API HTTP con validacion automatica y documentacion OpenAPI.",
672
+ mechanism: () => "Endpoints async definidos con decoradores. Pydantic para validacion de request/response. Uvicorn como servidor ASGI.",
673
+ rationale: () => "FastAPI combina rendimiento async con type-safety via Pydantic y genera documentacion OpenAPI automaticamente.",
674
+ alternatives: () => [
675
+ { option: "Django REST Framework", rejectedBecause: "Mayor overhead para APIs puras sin necesidad de ORM integrado." },
676
+ { option: "Flask", rejectedBecause: "Sin validacion nativa ni soporte async." }
677
+ ],
678
+ antiPatterns: () => [
679
+ "No usar funciones sincronas bloqueantes en endpoints async.",
680
+ "No omitir modelos Pydantic para validacion de entrada.",
681
+ "No exponer excepciones internas sin un exception handler."
682
+ ]
683
+ },
684
+ {
685
+ slugs: ["django"],
686
+ cognitiveLevel: "navigator",
687
+ complexityTier: 2,
688
+ title: () => "Django como framework backend",
689
+ decision: () => "El backend utiliza Django como framework web full-stack.",
690
+ mechanism: () => "Models definen el esquema de datos. Views procesan requests. URLs mapean rutas. Migraciones via manage.py migrate.",
691
+ rationale: () => "Django provee ORM, auth, admin y migraciones out-of-the-box. Ideal para aplicaciones con modelo de datos relacional complejo.",
692
+ alternatives: () => [
693
+ { option: "FastAPI", rejectedBecause: "No incluye ORM ni admin panel integrados." }
694
+ ],
695
+ antiPatterns: () => [
696
+ "No ejecutar queries N+1 en views sin select_related/prefetch_related.",
697
+ "No modificar modelos sin crear migraciones."
698
+ ]
699
+ },
700
+ {
701
+ slugs: ["flask"],
702
+ cognitiveLevel: "explorer",
703
+ complexityTier: 1,
704
+ title: () => "Flask como framework backend",
705
+ decision: () => "El backend utiliza Flask como micro-framework HTTP.",
706
+ mechanism: () => "Rutas definidas con decoradores. Blueprints para modularizar. Extensions para funcionalidad adicional.",
707
+ rationale: () => "Flask es minimalista y flexible. Permite elegir cada componente (ORM, auth, etc.) independientemente.",
708
+ alternatives: () => [
709
+ { option: "FastAPI", rejectedBecause: "Mayor complejidad inicial para APIs simples." }
710
+ ],
711
+ antiPatterns: () => [
712
+ "No almacenar estado mutable en variables globales del modulo.",
713
+ "No omitir manejo de errores en endpoints."
714
+ ]
715
+ },
716
+ {
717
+ slugs: ["langchain"],
718
+ cognitiveLevel: "anchor",
719
+ complexityTier: 3,
720
+ title: () => "LangChain/LangGraph como framework de agentes AI",
721
+ decision: () => "La orquestacion de agentes AI utiliza LangChain y/o LangGraph para flujos multi-paso con LLMs.",
722
+ mechanism: () => "LangGraph define grafos de estado con nodos y edges. Cada nodo ejecuta una accion (tool call, LLM call). El estado se propaga entre nodos.",
723
+ rationale: () => "LangGraph permite flujos de agentes complejos con ciclos, branching y estado persistente. LangChain provee abstracciones para tools, memory y prompts.",
724
+ alternatives: () => [
725
+ { option: "Llamadas directas a la API del LLM", rejectedBecause: "Sin soporte para grafos de estado, reintentos ni tool calling estandarizado." },
726
+ { option: "CrewAI", rejectedBecause: "Menor control sobre el flujo de ejecucion." }
727
+ ],
728
+ antiPatterns: () => [
729
+ "No hardcodear prompts sin versionarlos o parametrizarlos.",
730
+ "No omitir manejo de errores en tool calls (timeouts, rate limits).",
731
+ "No ignorar el costo de tokens en flujos con multiples LLM calls."
732
+ ]
733
+ },
734
+ {
735
+ slugs: ["sqlalchemy"],
736
+ cognitiveLevel: "anchor",
737
+ complexityTier: 2,
738
+ title: () => "SQLAlchemy como ORM Python",
739
+ decision: () => "La persistencia utiliza SQLAlchemy para modelado relacional con type-safety.",
740
+ mechanism: () => "Modelos declarativos con Column definitions. Sessions para transacciones. Alembic para migraciones.",
741
+ rationale: () => "SQLAlchemy es el ORM mas maduro de Python. Soporta multiples backends SQL y ofrece tanto ORM como Core para queries.",
742
+ alternatives: () => [
743
+ { option: "Django ORM", rejectedBecause: "Acoplado al framework Django." }
744
+ ],
745
+ antiPatterns: () => [
746
+ "No crear sesiones sin cerrarlas (usar context manager).",
747
+ "No ejecutar queries en loops sin batch/bulk operations."
748
+ ]
749
+ },
750
+ {
751
+ slugs: ["supabase"],
752
+ cognitiveLevel: "anchor",
753
+ complexityTier: 2,
754
+ title: () => "Supabase como backend-as-a-service",
755
+ decision: () => "La persistencia y autenticacion utilizan Supabase (PostgreSQL + Auth + Storage + Realtime).",
756
+ mechanism: () => "Cliente Supabase conecta a PostgreSQL via REST/Realtime. Auth con JWT. Storage para archivos. Row Level Security para autorizacion.",
757
+ rationale: () => "Supabase provee PostgreSQL managed con auth, storage y realtime integrados. Reduce la necesidad de infraestructura propia.",
758
+ alternatives: () => [
759
+ { option: "Firebase", rejectedBecause: "NoSQL (Firestore) vs SQL (PostgreSQL); vendor lock-in mas fuerte." },
760
+ { option: "PostgreSQL autohosteado", rejectedBecause: "Requiere mantener infraestructura de auth, storage y realtime por separado." }
761
+ ],
762
+ antiPatterns: () => [
763
+ "No omitir Row Level Security en tablas con datos de usuario.",
764
+ "No exponer la service_role key en el frontend.",
765
+ "No hacer queries complejas sin indices en las columnas filtradas."
766
+ ]
767
+ },
768
+ {
769
+ slugs: ["pytest"],
770
+ cognitiveLevel: "explorer",
771
+ complexityTier: 1,
772
+ title: () => "pytest como framework de testing",
773
+ decision: () => "Los tests del proyecto Python utilizan pytest.",
774
+ mechanism: () => "Tests como funciones con prefijo test_. Fixtures para setup/teardown. Markers para categorizar. conftest.py para configuracion compartida.",
775
+ rationale: () => "pytest es el standard de facto en Python. Fixtures, parametrize y plugins lo hacen extensible sin boilerplate.",
776
+ alternatives: () => [
777
+ { option: "unittest", rejectedBecause: "Requiere clases y mas boilerplate." }
778
+ ],
779
+ antiPatterns: () => [
780
+ "No compartir estado mutable entre tests sin fixtures con scope adecuado.",
781
+ "No omitir fixtures de limpieza para recursos externos (DB, files)."
782
+ ]
783
+ },
784
+ {
785
+ slugs: ["python"],
786
+ cognitiveLevel: "explorer",
787
+ complexityTier: 1,
788
+ title: () => "Python como runtime del backend",
789
+ decision: () => "El proyecto utiliza Python como lenguaje principal del backend.",
790
+ mechanism: () => "Entorno virtual para aislamiento de dependencias. pip/poetry/uv para gestion de paquetes.",
791
+ rationale: () => "Python tiene el ecosistema mas amplio para AI/ML, data processing y APIs web.",
792
+ alternatives: () => [
793
+ { option: "Node.js", rejectedBecause: "Menor ecosistema de librerias AI/ML nativas." },
794
+ { option: "Go", rejectedBecause: "Menor productividad para prototipado rapido." }
795
+ ],
796
+ antiPatterns: () => [
797
+ "No instalar paquetes fuera del entorno virtual.",
798
+ "No commitear el entorno virtual (venv/) al repositorio."
799
+ ]
800
+ },
801
+ {
802
+ slugs: ["golang"],
803
+ cognitiveLevel: "explorer",
804
+ complexityTier: 1,
805
+ title: () => "Go como runtime principal",
806
+ decision: () => "El proyecto utiliza Go como lenguaje de desarrollo.",
807
+ mechanism: () => "Go modules para gestion de dependencias. go build para compilacion. go test para testing.",
808
+ rationale: () => "Go ofrece compilacion rapida, binarios estaticos y concurrencia nativa con goroutines.",
809
+ alternatives: () => [
810
+ { option: "Rust", rejectedBecause: "Curva de aprendizaje mas pronunciada." },
811
+ { option: "Node.js", rejectedBecause: "Menor rendimiento en workloads CPU-bound." }
812
+ ],
813
+ antiPatterns: () => [
814
+ "No ignorar errores retornados (no usar _ sin justificacion).",
815
+ "No compartir estado mutable entre goroutines sin sincronizacion."
816
+ ]
817
+ },
818
+ {
819
+ slugs: ["rust"],
820
+ cognitiveLevel: "navigator",
821
+ complexityTier: 2,
822
+ title: () => "Rust como runtime principal",
823
+ decision: () => "El proyecto utiliza Rust para rendimiento y seguridad de memoria.",
824
+ mechanism: () => "Cargo para build y dependencias. Ownership system para seguridad de memoria sin GC. Crates.io como registry.",
825
+ rationale: () => "Rust garantiza seguridad de memoria en compile-time sin garbage collector. Ideal para sistemas de alto rendimiento.",
826
+ alternatives: () => [
827
+ { option: "C++", rejectedBecause: "Sin garantias de seguridad de memoria en compile-time." },
828
+ { option: "Go", rejectedBecause: "GC introduce latencia impredecible." }
829
+ ],
830
+ antiPatterns: () => [
831
+ "No usar unsafe sin documentar la invariante de seguridad.",
832
+ "No ignorar warnings del compilador."
833
+ ]
834
+ },
835
+ {
836
+ slugs: ["docker"],
837
+ cognitiveLevel: "explorer",
838
+ complexityTier: 1,
839
+ title: () => "Docker como entorno de containerizacion",
840
+ decision: () => "El proyecto utiliza Docker para empaquetar y desplegar la aplicacion.",
841
+ mechanism: () => "Dockerfile define la imagen. Multi-stage builds para optimizar tamano. .dockerignore para excluir archivos innecesarios.",
842
+ rationale: () => "Docker garantiza reproducibilidad del entorno entre desarrollo y produccion.",
843
+ alternatives: () => [
844
+ { option: "Despliegue directo", rejectedBecause: "Sin aislamiento ni reproducibilidad del entorno." }
845
+ ],
846
+ antiPatterns: () => [
847
+ "No instalar dependencias de desarrollo en la imagen de produccion.",
848
+ "No correr el proceso como root en el contenedor."
849
+ ]
428
850
  }
429
851
  ];
430
852
  function getTemplate(detection) {
@@ -738,16 +1160,16 @@ function setupDatabase(edes) {
738
1160
  }
739
1161
 
740
1162
  // src/setup/hooks.ts
741
- import { existsSync as existsSync6, readFileSync as readFileSync7, writeFileSync, mkdirSync as mkdirSync2 } from "fs";
742
- import { join as join7 } from "path";
1163
+ import { existsSync as existsSync10, readFileSync as readFileSync10, writeFileSync, mkdirSync as mkdirSync2 } from "fs";
1164
+ import { join as join12 } from "path";
743
1165
  function setupHooks(projectPath) {
744
- const claudeDir = join7(projectPath, ".claude");
1166
+ const claudeDir = join12(projectPath, ".claude");
745
1167
  mkdirSync2(claudeDir, { recursive: true });
746
- const settingsPath = join7(claudeDir, "settings.json");
1168
+ const settingsPath = join12(claudeDir, "settings.json");
747
1169
  let existing = {};
748
- if (existsSync6(settingsPath)) {
1170
+ if (existsSync10(settingsPath)) {
749
1171
  try {
750
- existing = JSON.parse(readFileSync7(settingsPath, "utf-8"));
1172
+ existing = JSON.parse(readFileSync10(settingsPath, "utf-8"));
751
1173
  } catch {
752
1174
  }
753
1175
  }
@@ -794,7 +1216,7 @@ function setupHooks(projectPath) {
794
1216
 
795
1217
  // src/setup/context.ts
796
1218
  import { writeFileSync as writeFileSync2, mkdirSync as mkdirSync3 } from "fs";
797
- import { join as join8 } from "path";
1219
+ import { join as join13 } from "path";
798
1220
 
799
1221
  // ../orchestrator/src/claude-context.ts
800
1222
  function assembleClaudeContext(edes) {
@@ -897,9 +1319,9 @@ function setupContext(projectPath) {
897
1319
  const edes = createEdeStore(db).getAll();
898
1320
  db.close();
899
1321
  const content = assembleClaudeContext(edes);
900
- const claudeDir = join8(projectPath, ".claude");
1322
+ const claudeDir = join13(projectPath, ".claude");
901
1323
  mkdirSync3(claudeDir, { recursive: true });
902
- writeFileSync2(join8(claudeDir, "CLAUDE.md"), content, "utf-8");
1324
+ writeFileSync2(join13(claudeDir, "CLAUDE.md"), content, "utf-8");
903
1325
  }
904
1326
 
905
1327
  // src/commands/init.ts
@@ -1040,11 +1462,11 @@ async function hookCommand(_event) {
1040
1462
 
1041
1463
  // src/commands/mcp.ts
1042
1464
  import { execFileSync } from "child_process";
1043
- import { join as join9, dirname as dirname2 } from "path";
1465
+ import { join as join14, dirname as dirname2 } from "path";
1044
1466
  import { fileURLToPath } from "url";
1045
1467
  async function mcpCommand() {
1046
1468
  const __dirname = dirname2(fileURLToPath(import.meta.url));
1047
- const mcpEntry = join9(__dirname, "mcp-entry.js");
1469
+ const mcpEntry = join14(__dirname, "mcp-entry.js");
1048
1470
  execFileSync(process.execPath, [mcpEntry], {
1049
1471
  stdio: "inherit"
1050
1472
  });
@@ -1053,11 +1475,11 @@ async function mcpCommand() {
1053
1475
  // src/commands/start.ts
1054
1476
  import { execSync, spawn } from "child_process";
1055
1477
  import { writeFileSync as writeFileSync3, mkdirSync as mkdirSync4 } from "fs";
1056
- import { join as join10 } from "path";
1478
+ import { join as join15 } from "path";
1057
1479
  import { homedir as homedir3 } from "os";
1058
1480
  import { createServer } from "net";
1059
- var UMBRAL_DIR = join10(homedir3(), ".umbral");
1060
- var COMPOSE_PATH = join10(UMBRAL_DIR, "docker-compose.yml");
1481
+ var UMBRAL_DIR = join15(homedir3(), ".umbral");
1482
+ var COMPOSE_PATH = join15(UMBRAL_DIR, "docker-compose.yml");
1061
1483
  var DOCKER_IMAGE = process.env.UMBRAL_IMAGE ?? "ghcr.io/josephrobles23/umbral-web:latest";
1062
1484
  function dockerInstalled() {
1063
1485
  try {
@@ -1090,7 +1512,7 @@ function findFreePort(start) {
1090
1512
  });
1091
1513
  }
1092
1514
  function generateCompose(webPort, wsPort) {
1093
- const dbPath = join10(UMBRAL_DIR, "umbral.db").replace(/\\/g, "/");
1515
+ const dbPath = join15(UMBRAL_DIR, "umbral.db").replace(/\\/g, "/");
1094
1516
  const dbDir = UMBRAL_DIR.replace(/\\/g, "/");
1095
1517
  return `# Auto-generated by Umbral CLI \u2014 do not edit manually
1096
1518
  name: umbral
@@ -1189,14 +1611,14 @@ async function startCommand(options) {
1189
1611
 
1190
1612
  // src/commands/stop.ts
1191
1613
  import { execSync as execSync2 } from "child_process";
1192
- import { existsSync as existsSync8 } from "fs";
1193
- import { join as join11 } from "path";
1614
+ import { existsSync as existsSync12 } from "fs";
1615
+ import { join as join16 } from "path";
1194
1616
  import { homedir as homedir4 } from "os";
1195
- var COMPOSE_PATH2 = join11(homedir4(), ".umbral", "docker-compose.yml");
1617
+ var COMPOSE_PATH2 = join16(homedir4(), ".umbral", "docker-compose.yml");
1196
1618
  function stopCommand() {
1197
1619
  const w = (s) => process.stdout.write(s);
1198
1620
  w("\n Umbral \u2014 Deteniendo plataforma\n\n");
1199
- if (!existsSync8(COMPOSE_PATH2)) {
1621
+ if (!existsSync12(COMPOSE_PATH2)) {
1200
1622
  w(" \u2717 No se encontro docker-compose.yml en ~/.umbral/\n");
1201
1623
  w(" Ejecuta 'umbral start' primero.\n\n");
1202
1624
  process.exit(1);
@@ -1214,7 +1636,7 @@ function stopCommand() {
1214
1636
 
1215
1637
  // src/index.ts
1216
1638
  var program = new Command();
1217
- program.name("umbral").description("Umbral \u2014 Framework de gobernanza para proyectos con Claude Code").version("0.0.2");
1639
+ program.name("umbral").description("Umbral \u2014 Framework de gobernanza para proyectos con Claude Code").version("0.0.3");
1218
1640
  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);
1219
1641
  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);
1220
1642
  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.2",
3
+ "version": "0.0.3",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "bin": {