stackhelx 1.0.0__tar.gz → 1.0.1__tar.gz

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 (63) hide show
  1. {stackhelx-1.0.0 → stackhelx-1.0.1}/CHANGELOG.md +10 -0
  2. {stackhelx-1.0.0 → stackhelx-1.0.1}/PKG-INFO +1 -1
  3. {stackhelx-1.0.0 → stackhelx-1.0.1}/docs/plan-expansion.md +29 -29
  4. {stackhelx-1.0.0 → stackhelx-1.0.1}/docs/plan-lenguajes.md +3 -3
  5. {stackhelx-1.0.0 → stackhelx-1.0.1}/stack.example.yaml +3 -3
  6. stackhelx-1.0.1/stackhelx/__init__.py +2 -0
  7. {stackhelx-1.0.0 → stackhelx-1.0.1}/stackhelx/ports.py +4 -4
  8. {stackhelx-1.0.0 → stackhelx-1.0.1}/stackhelx/registry.py +2 -2
  9. {stackhelx-1.0.0 → stackhelx-1.0.1}/stackhelx/runner.py +6 -4
  10. {stackhelx-1.0.0 → stackhelx-1.0.1}/stackhelx/scripts.py +1 -1
  11. {stackhelx-1.0.0 → stackhelx-1.0.1}/stackhelx/server.py +5 -5
  12. {stackhelx-1.0.0 → stackhelx-1.0.1}/tests/test_cli.py +3 -3
  13. {stackhelx-1.0.0 → stackhelx-1.0.1}/tests/test_detect.py +2 -2
  14. {stackhelx-1.0.0 → stackhelx-1.0.1}/tests/test_mcp.py +3 -3
  15. {stackhelx-1.0.0 → stackhelx-1.0.1}/tests/test_runner.py +41 -3
  16. {stackhelx-1.0.0 → stackhelx-1.0.1}/tests/test_server.py +4 -4
  17. {stackhelx-1.0.0 → stackhelx-1.0.1}/tests/test_tunnel.py +4 -4
  18. stackhelx-1.0.0/stackhelx/__init__.py +0 -2
  19. {stackhelx-1.0.0 → stackhelx-1.0.1}/.github/workflows/ci.yml +0 -0
  20. {stackhelx-1.0.0 → stackhelx-1.0.1}/.github/workflows/release.yml +0 -0
  21. {stackhelx-1.0.0 → stackhelx-1.0.1}/.gitignore +0 -0
  22. {stackhelx-1.0.0 → stackhelx-1.0.1}/AGENTS.md +0 -0
  23. {stackhelx-1.0.0 → stackhelx-1.0.1}/CLAUDE.md +0 -0
  24. {stackhelx-1.0.0 → stackhelx-1.0.1}/LICENSE +0 -0
  25. {stackhelx-1.0.0 → stackhelx-1.0.1}/README.md +0 -0
  26. {stackhelx-1.0.0 → stackhelx-1.0.1}/docs/comandos.md +0 -0
  27. {stackhelx-1.0.0 → stackhelx-1.0.1}/docs/deteccion.md +0 -0
  28. {stackhelx-1.0.0 → stackhelx-1.0.1}/docs/interfaz.md +0 -0
  29. {stackhelx-1.0.0 → stackhelx-1.0.1}/docs/pendientes.md +0 -0
  30. {stackhelx-1.0.0 → stackhelx-1.0.1}/docs/plan-siguiente.md +0 -0
  31. {stackhelx-1.0.0 → stackhelx-1.0.1}/docs/stack-yaml.md +0 -0
  32. {stackhelx-1.0.0 → stackhelx-1.0.1}/docs/superpowers/plans/2026-08-08-partir-readme.md +0 -0
  33. {stackhelx-1.0.0 → stackhelx-1.0.1}/docs/superpowers/plans/2026-08-08-release-1-0-1.md +0 -0
  34. {stackhelx-1.0.0 → stackhelx-1.0.1}/docs/superpowers/plans/2026-08-18-campo-url.md +0 -0
  35. {stackhelx-1.0.0 → stackhelx-1.0.1}/docs/superpowers/specs/2026-07-29-autodeteccion-design.md +0 -0
  36. {stackhelx-1.0.0 → stackhelx-1.0.1}/pyproject.toml +0 -0
  37. {stackhelx-1.0.0 → stackhelx-1.0.1}/scripts/check_docs.py +0 -0
  38. {stackhelx-1.0.0 → stackhelx-1.0.1}/scripts/check_metadata.py +0 -0
  39. {stackhelx-1.0.0 → stackhelx-1.0.1}/stackhelx/__main__.py +0 -0
  40. {stackhelx-1.0.0 → stackhelx-1.0.1}/stackhelx/browse.py +0 -0
  41. {stackhelx-1.0.0 → stackhelx-1.0.1}/stackhelx/cli.py +0 -0
  42. {stackhelx-1.0.0 → stackhelx-1.0.1}/stackhelx/config.py +0 -0
  43. {stackhelx-1.0.0 → stackhelx-1.0.1}/stackhelx/detect.py +0 -0
  44. {stackhelx-1.0.0 → stackhelx-1.0.1}/stackhelx/docker.py +0 -0
  45. {stackhelx-1.0.0 → stackhelx-1.0.1}/stackhelx/doctor.py +0 -0
  46. {stackhelx-1.0.0 → stackhelx-1.0.1}/stackhelx/guardrails.py +0 -0
  47. {stackhelx-1.0.0 → stackhelx-1.0.1}/stackhelx/history.py +0 -0
  48. {stackhelx-1.0.0 → stackhelx-1.0.1}/stackhelx/mcp.py +0 -0
  49. {stackhelx-1.0.0 → stackhelx-1.0.1}/stackhelx/tunnel.py +0 -0
  50. {stackhelx-1.0.0 → stackhelx-1.0.1}/stackhelx/web/app.css +0 -0
  51. {stackhelx-1.0.0 → stackhelx-1.0.1}/stackhelx/web/app.js +0 -0
  52. {stackhelx-1.0.0 → stackhelx-1.0.1}/stackhelx/web/index.html +0 -0
  53. {stackhelx-1.0.0 → stackhelx-1.0.1}/stackhelx/web/tokens.css +0 -0
  54. {stackhelx-1.0.0 → stackhelx-1.0.1}/tests/conftest.py +0 -0
  55. {stackhelx-1.0.0 → stackhelx-1.0.1}/tests/test_browse.py +0 -0
  56. {stackhelx-1.0.0 → stackhelx-1.0.1}/tests/test_config.py +0 -0
  57. {stackhelx-1.0.0 → stackhelx-1.0.1}/tests/test_conftest.py +0 -0
  58. {stackhelx-1.0.0 → stackhelx-1.0.1}/tests/test_guardrails.py +0 -0
  59. {stackhelx-1.0.0 → stackhelx-1.0.1}/tests/test_history.py +0 -0
  60. {stackhelx-1.0.0 → stackhelx-1.0.1}/tests/test_ports.py +0 -0
  61. {stackhelx-1.0.0 → stackhelx-1.0.1}/tests/test_registry.py +0 -0
  62. {stackhelx-1.0.0 → stackhelx-1.0.1}/tests/test_scripts.py +0 -0
  63. {stackhelx-1.0.0 → stackhelx-1.0.1}/uv.lock +0 -0
@@ -4,6 +4,16 @@ Formato de [Keep a Changelog](https://keepachangelog.com/es/1.1.0/).
4
4
  Versionado semántico: la superficie pública son los comandos del CLI, el
5
5
  esquema de `stack.yaml` y las rutas de la API local.
6
6
 
7
+ ## [1.0.1] - 2026-09-13
8
+
9
+ ### Seguridad y Limpieza
10
+
11
+ - **Purga de mensajes de seguridad y excepciones (`ports.py`):** Los mensajes de `KillRefused` ante intento de cerrar el PID propio o el de la terminal padre ahora identifican explícitamente a `StackHelx`.
12
+ - **Precedencia de entorno global (`runner.py`):** `build_env()` lee `~/.stackhelx/env.global` y mantiene fallback transparente a `~/.portmaster/env.global` si existe, con pruebas de precedencia añadidas a la suite.
13
+ - **Títulos y logs de servidor (`server.py`):** Título de FastAPI fijado a `StackHelx`, logger migrado a `stackhelx.server`, y mensajes de guarda en `/api/share` actualizados para rechazar túneles contra la consola de `StackHelx`.
14
+ - **Plantilla de configuración (`stack.example.yaml`):** Actualizada para documentar `stackhelx init` y la nueva identidad de StackHelx.
15
+ - **Suite de pruebas:** 474 pruebas en verde (+1 test de precedencia de entorno global).
16
+
7
17
  ## [1.0.0] - 2026-09-13
8
18
 
9
19
  ### Rebrand y Nuevo Comienzo
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: stackhelx
3
- Version: 1.0.0
3
+ Version: 1.0.1
4
4
  Summary: Orquestador de entornos de desarrollo locales: puertos, Docker, backend y frontend con un solo comando
5
5
  Project-URL: Homepage, https://github.com/TicoraX/StackHelx
6
6
  Project-URL: Repository, https://github.com/TicoraX/StackHelx
@@ -1,9 +1,9 @@
1
- # Plan de Expansión Estratégica: PortMaster 2.0 (Ecosistema Completo de Desarrollo)
1
+ # Plan de Expansión Estratégica: StackHelx (Ecosistema Completo de Desarrollo)
2
2
 
3
3
  Fecha: 14 de agosto de 2026.
4
4
  Rama: `feature/expansion-plan`
5
5
 
6
- Este documento define la evolución de PortMaster desde un orquestador de *stacks* locales hacia una **plataforma integral de productividad, diagnóstico, automatización y control del entorno de desarrollo local**.
6
+ Este documento define la evolución de StackHelx desde un orquestador de *stacks* locales hacia una **plataforma integral de productividad, diagnóstico, automatización y control del entorno de desarrollo local**.
7
7
 
8
8
  ---
9
9
 
@@ -11,17 +11,17 @@ Este documento define la evolución de PortMaster desde un orquestador de *stack
11
11
 
12
12
  ```
13
13
  ┌─────────────────────────────────────────────────────────────────────────────┐
14
- │ PORTMASTER ECOSYSTEM │
14
+ │ STACKHELX ECOSYSTEM │
15
15
  ├───────────────────────────────┬───────────────────────────────┬─────────────┤
16
16
  │ 1. ORQUESTACIÓN DE STACK │ 2. RUNNER DE TAREAS │ 3. TÚNELES │
17
17
  │ - Topological sort & health │ - Scripts y pipelines locales │ - ngrok │
18
18
  │ - env_file & pre_start hooks │ - Inyección de variables │ - cloudflare│
19
- │ - Monorrepos (pnpm/turbo/uv) │ - portmaster run <tarea> │ - share │
19
+ │ - Monorrepos (pnpm/turbo/uv) │ - stackhelx run <tarea> │ - share │
20
20
  ├───────────────────────────────┼───────────────────────────────┼─────────────┤
21
21
  │ 4. MULTI-PROYECTO COMPUESTO │ 5. HIGIENE Y MANTENIMIENTO │ 6. MCP & AI │
22
22
  │ - Inclusión inter-repositorios│ - Docker prune inteligente │ - Tools MCP │
23
23
  │ - Grupos de servicios │ - Detección de zombies │ - AI Agent │
24
- │ - Matriz de colisiones │ - portmaster clean │ Diagnosis │
24
+ │ - Matriz de colisiones │ - stackhelx clean │ Diagnosis │
25
25
  └───────────────────────────────┴───────────────────────────────┴─────────────┘
26
26
  ```
27
27
 
@@ -30,15 +30,15 @@ Este documento define la evolución de PortMaster desde un orquestador de *stack
30
30
  ## 2. Especificación de Fases y Módulos
31
31
 
32
32
  ### Fase 1: Variables de Entorno y Hooks del Stack
33
- * **Módulo**: [`portmaster/config.py`](../portmaster/config.py), [`portmaster/runner.py`](../portmaster/runner.py)
33
+ * **Módulo**: [`stackhelx/config.py`](../stackhelx/config.py), [`stackhelx/runner.py`](../stackhelx/runner.py)
34
34
  * **`env_file`**: Carga de `.env`, `.env.local` o lista ordenada sin dependencias externas.
35
35
  * **Hooks de Ciclo de Vida**:
36
36
  * `pre_start`: Ejecución síncrona preparatoria (ej. migraciones, build).
37
37
  * `post_start`: Ejecución tras confirmación de salud (ej. `seed`, notificación).
38
- * **Bóveda Global (`~/.portmaster/env.global`)**: Herencia automática de variables comunes entre proyectos.
38
+ * **Bóveda Global (`~/.stackhelx/env.global`)**: Herencia automática de variables comunes entre proyectos.
39
39
 
40
- ### Fase 2: Runner de Tareas y Scripts de Proyecto (`portmaster run`)
41
- * **Módulo**: [`portmaster/scripts.py`](../portmaster/scripts.py), [`portmaster/cli.py`](../portmaster/cli.py)
40
+ ### Fase 2: Runner de Tareas y Scripts de Proyecto (`stackhelx run`)
41
+ * **Módulo**: [`stackhelx/scripts.py`](../stackhelx/scripts.py), [`stackhelx/cli.py`](../stackhelx/cli.py)
42
42
  * **Declaración en `stack.yaml`**:
43
43
  ```yaml
44
44
  scripts:
@@ -47,15 +47,15 @@ Este documento define la evolución de PortMaster desde un orquestador de *stack
47
47
  migrate: alembic upgrade head
48
48
  check: [lint, test] # Pipeline secuencial
49
49
  ```
50
- * **Comando CLI**: `portmaster run <script>` ejecuta en la raíz del proyecto, con el contexto de variables del stack.
50
+ * **Comando CLI**: `stackhelx run <script>` ejecuta en la raíz del proyecto, con el contexto de variables del stack.
51
51
 
52
- ### Fase 3: Exposición Segura y Compartir (`portmaster share`)
53
- * **Módulo**: [`portmaster/tunnel.py`](../portmaster/tunnel.py), [`portmaster/cli.py`](../portmaster/cli.py)
52
+ ### Fase 3: Exposición Segura y Compartir (`stackhelx share`)
53
+ * **Módulo**: [`stackhelx/tunnel.py`](../stackhelx/tunnel.py), [`stackhelx/cli.py`](../stackhelx/cli.py)
54
54
  * **Integración de Túneles**: Detección y manejo de binarios locales (`cloudflared`, `ngrok`, `tailscale`).
55
- * **Comando CLI & Web**: `portmaster share web` genera URL pública temporal, QR en consola y botón directo en la UI web.
55
+ * **Comando CLI & Web**: `stackhelx share web` genera URL pública temporal, QR en consola y botón directo en la UI web.
56
56
 
57
57
  ### Fase 4: Orquestación Multi-Proyecto y Dependencias Inter-Repositorios
58
- * **Módulo**: [`portmaster/registry.py`](../portmaster/registry.py), [`portmaster/runner.py`](../portmaster/runner.py)
58
+ * **Módulo**: [`stackhelx/registry.py`](../stackhelx/registry.py), [`stackhelx/runner.py`](../stackhelx/runner.py)
59
59
  * **Proyectos Compuestos (`includes`)**:
60
60
  ```yaml
61
61
  # frontend/stack.yaml
@@ -63,28 +63,28 @@ Este documento define la evolución de PortMaster desde un orquestador de *stack
63
63
  includes:
64
64
  - ../backend-api # Levanta backend automáticamente si no está corriendo
65
65
  ```
66
- * **Grupos de Proyectos**: `portmaster group up <nombre_grupo>` y matriz preventiva de colisión de puertos.
66
+ * **Grupos de Proyectos**: `stackhelx group up <nombre_grupo>` y matriz preventiva de colisión de puertos.
67
67
 
68
- ### Fase 5: Higiene del Sistema y Docker (`portmaster clean`)
69
- * **Módulo**: [`portmaster/docker.py`](../portmaster/docker.py), [`portmaster/cli.py`](../portmaster/cli.py)
68
+ ### Fase 5: Higiene del Sistema y Docker (`stackhelx clean`)
69
+ * **Módulo**: [`stackhelx/docker.py`](../stackhelx/docker.py), [`stackhelx/cli.py`](../stackhelx/cli.py)
70
70
  * **Limpieza Asistida**:
71
- * `portmaster clean` (con `--volumes` opcional): Remueve contenedores parados, imágenes huérfanas, build-cache y redes no usadas.
72
- * `portmaster doctor`: Diagnóstico integral del entorno local y detección de problemas.
71
+ * `stackhelx clean` (con `--volumes` opcional): Remueve contenedores parados, imágenes huérfanas, build-cache y redes no usadas.
72
+ * `stackhelx doctor`: Diagnóstico integral del entorno local y detección de problemas.
73
73
 
74
74
  ### Fase 6: Servidor MCP e Integración con Agentes de IA
75
- * **Módulo**: [`portmaster/mcp.py`](../portmaster/mcp.py)
75
+ * **Módulo**: [`stackhelx/mcp.py`](../stackhelx/mcp.py)
76
76
  * **Herramientas MCP Expuestas**:
77
- * `portmaster_status`: Estado de salud, puertos y procesos.
78
- * `portmaster_restart`: Reinicio atómico de un servicio específico.
79
- * `portmaster_logs`: Consulta de logs con filtrado para depuración autónoma.
80
- * `portmaster_free_port`: Liberación de puertos conflictivos.
81
- * `portmaster_clean`: Limpieza segura de recursos Docker.
82
- * `portmaster_doctor`: Diagnóstico integral del sistema.
83
- * `portmaster_history`: Historial de arranques de proyectos.
84
- * `portmaster_init`: Inicialización asistida de `stack.yaml`.
77
+ * `stackhelx_status`: Estado de salud, puertos y procesos.
78
+ * `stackhelx_restart`: Reinicio atómico de un servicio específico.
79
+ * `stackhelx_logs`: Consulta de logs con filtrado para depuración autónoma.
80
+ * `stackhelx_free_port`: Liberación de puertos conflictivos.
81
+ * `stackhelx_clean`: Limpieza segura de recursos Docker.
82
+ * `stackhelx_doctor`: Diagnóstico integral del sistema.
83
+ * `stackhelx_history`: Historial de arranques de proyectos.
84
+ * `stackhelx_init`: Inicialización asistida de `stack.yaml`.
85
85
 
86
86
  ### Fase 7: Detección Avanzada (Monorrepos, `uv`, Frameworks) y UI Web Polish
87
- * **Módulo**: [`portmaster/detect.py`](../portmaster/detect.py), [`portmaster/web/`](../portmaster/web)
87
+ * **Módulo**: [`stackhelx/detect.py`](../stackhelx/detect.py), [`stackhelx/web/`](../stackhelx/web)
88
88
  * Detección de `pnpm-workspace.yaml`, `turbo.json`, `uv.lock`, Astro (`4321`), Vite (`5173`).
89
89
  * UI Web: Pausa y búsqueda en streaming de logs, editor de `stack.yaml` y disparador de tareas `run`.
90
90
 
@@ -15,7 +15,7 @@
15
15
 
16
16
  Fecha: 7 de septiembre de 2026. Rama `main`, 432 tests en verde.
17
17
 
18
- PortMaster detecta hoy compose, Python, Node, Deno, Go, Rust, Ruby, PHP y .NET.
18
+ StackHelx detecta hoy compose, Python, Node, Deno, Go, Rust, Ruby, PHP y .NET.
19
19
  Una carpeta con un proyecto de otro lenguaje no devuelve nada y obliga a escribir
20
20
  el `stack.yaml` a mano. Este plan cierra los tres huecos que tienen usuarios
21
21
  reales de desarrollo local multi-servicio: la JVM (Java y Kotlin), Elixir y Bun
@@ -298,8 +298,8 @@ primero). Y se verifica revirtiendo, como todo lo demás acá.
298
298
 
299
299
  Cada PR toca:
300
300
 
301
- - `portmaster/detect.py`: las dos funciones, sus constantes, y el nombre en la tupla de `detect()`.
302
- - `portmaster/browse.py`: el marcador nuevo en `MARKERS` (`pom.xml`, `build.gradle`, `mix.exs`, `bunfig.toml`), que es lo que pinta el badge en el explorador de carpetas.
301
+ - `stackhelx/detect.py`: las dos funciones, sus constantes, y el nombre en la tupla de `detect()`.
302
+ - `stackhelx/browse.py`: el marcador nuevo en `MARKERS` (`pom.xml`, `build.gradle`, `mix.exs`, `bunfig.toml`), que es lo que pinta el badge en el explorador de carpetas.
303
303
  - `tests/test_detect.py`: los casos de arriba, negativo incluido.
304
304
  - `docs/deteccion.md`, `README.md` y `CHANGELOG.md`. De `CLAUDE.md`: lo que se escribe en la doc se comprueba corriendo el comando, no recordándolo.
305
305
 
@@ -1,7 +1,7 @@
1
- # Esquema de configuracion de PortMaster.
1
+ # Esquema de configuracion de StackHelx.
2
2
  #
3
- # El archivo es opcional: sin uno, PortMaster detecta los servicios del proyecto.
4
- # `portmaster init` escribe lo detectado aca para editarlo a mano.
3
+ # El archivo es opcional: sin uno, StackHelx detecta los servicios del proyecto.
4
+ # `stackhelx init` (o `shx init`) escribe lo detectado aca para editarlo a mano.
5
5
 
6
6
  name: mi-proyecto
7
7
 
@@ -0,0 +1,2 @@
1
+ __version__ = "1.0.1"
2
+
@@ -39,7 +39,7 @@ TERMINATE_TIMEOUT = 5
39
39
 
40
40
 
41
41
  class KillRefused(Exception):
42
- """El proceso existe pero PortMaster se niega a matarlo."""
42
+ """El proceso existe pero StackHelx se niega a matarlo."""
43
43
 
44
44
 
45
45
  @dataclass(frozen=True)
@@ -356,7 +356,7 @@ def kill(
356
356
  no existe, y psutil.AccessDenied si faltan permisos.
357
357
  """
358
358
  # psutil.Process(None) es el proceso actual: sin esto, un scan que no vio al
359
- # dueno del puerto termina en PortMaster matandose a si mismo.
359
+ # dueno del puerto termina en StackHelx matandose a si mismo.
360
360
  if pid is None:
361
361
  raise KillRefused("no hay PID que cerrar")
362
362
  if pid in PROTECTED_PIDS:
@@ -364,9 +364,9 @@ def kill(
364
364
 
365
365
  me = psutil.Process()
366
366
  if pid == me.pid:
367
- raise KillRefused("ese PID es PortMaster")
367
+ raise KillRefused("ese PID es StackHelx")
368
368
  if pid in {ancestor.pid for ancestor in me.parents()}:
369
- raise KillRefused(f"PID {pid} es un proceso padre de PortMaster (tu terminal)")
369
+ raise KillRefused(f"PID {pid} es un proceso padre de StackHelx (tu terminal)")
370
370
 
371
371
  proc = psutil.Process(pid)
372
372
  if create_time is not None and proc.create_time() != create_time:
@@ -116,7 +116,7 @@ _docker_cache: tuple[float, bool] | None = None
116
116
  def declared_ports(max_age: float = 0.0) -> dict[int, list[Path]]:
117
117
  """Puerto declarado -> proyectos registrados que lo piden.
118
118
 
119
- Es el unico dato que PortMaster tiene y las herramientas de un proyecto solo
119
+ Es el unico dato que StackHelx tiene y las herramientas de un proyecto solo
120
120
  no pueden tener: cada compose se conoce a si mismo y ninguno sabe del de al
121
121
  lado. Sin esto, que dos proyectos peleen por el 3000 se descubre cuando el
122
122
  segundo no arranca.
@@ -251,7 +251,7 @@ def _save(items: list[Path]) -> None:
251
251
  def token() -> str:
252
252
  """Token de la API local.
253
253
 
254
- Prioriza PORTMASTER_TOKEN. Si no esta, usa uno generado en el directorio del
254
+ Prioriza STACKHELX_TOKEN (con fallback a PORTMASTER_TOKEN). Si no esta, usa uno generado en el directorio del
255
255
  usuario. Ver la desviacion documentada en CLAUDE.md: una herramienta que se
256
256
  instala con pipx no puede traer un .env, y un token generado con permisos
257
257
  0600 fuera del repo es mas seguro que uno que el usuario copia a mano.
@@ -32,13 +32,15 @@ from .config import Service, Stack
32
32
  def build_env(service: Service, extra_env: dict[str, str] | None = None) -> dict[str, str]:
33
33
  """Construye el entorno de ejecucion con precedencia clara:
34
34
  1. os.environ
35
- 2. ~/.portmaster/env.global (si existe)
35
+ 2. ~/.stackhelx/env.global (o legacy ~/.portmaster/env.global si existe)
36
36
  3. service.env_file (en orden)
37
37
  4. service.env (declarado explicito)
38
38
  5. extra_env (e.g. desde --env-file en CLI)
39
39
  """
40
40
  env = dict(os.environ)
41
- global_env = Path.home() / ".portmaster" / "env.global"
41
+ global_env = Path.home() / ".stackhelx" / "env.global"
42
+ if not global_env.is_file():
43
+ global_env = Path.home() / ".portmaster" / "env.global"
42
44
  if global_env.is_file():
43
45
  env.update(config.parse_env_file(global_env))
44
46
  for env_path in service.env_file:
@@ -692,7 +694,7 @@ def dependency_graph(stack: Stack, profile: str | None = None) -> dict:
692
694
  def run_stop(service: Service, extra_env: dict[str, str] | None = None) -> subprocess.CompletedProcess | None:
693
695
  """Corre el `stop:` de un servicio. None si no termino a tiempo.
694
696
 
695
- Vive afuera del `Runner` porque `portmaster down` tiene que poder apagar un
697
+ Vive afuera del `Runner` porque `stackhelx down` tiene que poder apagar un
696
698
  stack que arranco otro proceso: los contenedores de un `docker compose up -d`
697
699
  sobreviven a la terminal que los levanto, y ahi no hay ningun `Proc` vivo del
698
700
  que colgarse.
@@ -755,7 +757,7 @@ def _port_hint(proc: Proc) -> str:
755
757
  quien = dueno.name or f"pid {dueno.pid}"
756
758
  return (
757
759
  f"; el puerto {declarado} ya lo tenia {quien} (pid {dueno.pid}):"
758
- f" liberalo con `portmaster free {declarado}` y reintenta"
760
+ f" liberalo con `stackhelx free {declarado}` y reintenta"
759
761
  )
760
762
  return ""
761
763
 
@@ -33,7 +33,7 @@ def _entrecomillar(args: Sequence[str]) -> str:
33
33
 
34
34
  Iban con un `" ".join`, y estos comandos corren con `shell=True`: un
35
35
  argumento con un separador ejecutaba lo que viniera despues. Desde el CLI lo
36
- escribe el usuario, pero `portmaster_run` los recibe de un agente de IA.
36
+ escribe el usuario, pero `stackhelx_run` (o `portmaster_run`) los recibe de un agente de IA.
37
37
 
38
38
  Por plataforma, porque el shell no es el mismo. `shlex.join` entrecomilla al
39
39
  estilo POSIX y `cmd.exe` no entiende las comillas simples: un `&` adentro le
@@ -54,7 +54,7 @@ from . import (
54
54
  # `browse_module` porque el endpoint de /api/browse ya se llama browse.
55
55
  from . import browse as browse_module
56
56
 
57
- log = logging.getLogger("portmaster.server")
57
+ log = logging.getLogger("stackhelx.server")
58
58
 
59
59
  WEB = Path(__file__).parent / "web"
60
60
  LOG_LINES = 500
@@ -265,7 +265,7 @@ class Session:
265
265
  self.stopped_by_user = False
266
266
  self.state = "starting"
267
267
  self.sink.write(
268
- f"\n[portmaster] Conmutando al perfil '{new_profile or 'default'}'...\n"
268
+ f"\n[stackhelx] Conmutando al perfil '{new_profile or 'default'}'...\n"
269
269
  )
270
270
  console = Console(
271
271
  file=self.sink, force_terminal=False, no_color=True, width=160, soft_wrap=True
@@ -707,7 +707,7 @@ def create_app(token: str | None = None) -> FastAPI:
707
707
  _load_sessions_state()
708
708
  token = token or registry.token()
709
709
  app = FastAPI(
710
- title="PortMaster",
710
+ title="StackHelx",
711
711
  docs_url=None,
712
712
  redoc_url=None,
713
713
  openapi_url=None,
@@ -1520,7 +1520,7 @@ def create_app(token: str | None = None) -> FastAPI:
1520
1520
  log.info("puerto rechazado: %s", exc)
1521
1521
  raise HTTPException(400, str(exc))
1522
1522
 
1523
- # PortMaster no se publica a si mismo. Detras de este puerto esta la API
1523
+ # StackHelx no se publica a si mismo. Detras de este puerto esta la API
1524
1524
  # que arranca stack.yaml, o sea ejecucion de comandos: exponerla deja al
1525
1525
  # token como unica puerta contra internet entero. La validacion de Host
1526
1526
  # del middleware ya rechaza al cliente de tuneles, asi que hoy el efecto
@@ -1534,7 +1534,7 @@ def create_app(token: str | None = None) -> FastAPI:
1534
1534
  if propio is not None and port == propio:
1535
1535
  raise HTTPException(
1536
1536
  400,
1537
- f"el puerto {port} es el de PortMaster. Publicarlo expone la API "
1537
+ f"el puerto {port} es el de StackHelx. Publicarlo expone la API "
1538
1538
  "que ejecuta los comandos de tu stack.yaml, no tu proyecto.",
1539
1539
  )
1540
1540
 
@@ -29,7 +29,7 @@ def sin_color(texto: str) -> str:
29
29
  Rich pinta los numeros, asi que `f"El puerto {port} ya esta ocupado"` llega
30
30
  partido por un `\x1b[1;36m` en el medio y un `in` plano no lo encuentra. Solo
31
31
  aparece cuando el entorno trae `FORCE_COLOR` (la terminal del desarrollador,
32
- o un `portmaster up` que se lo pone a sus hijos): sin eso Rich ve que no hay
32
+ o un `stackhelx up` que se lo pone a sus hijos): sin eso Rich ve que no hay
33
33
  tty y no pinta nada, que es por lo que en CI el test daba verde.
34
34
 
35
35
  Afirmar sobre el texto pelado, y no sobre como se ve, es lo correcto igual:
@@ -245,7 +245,7 @@ def _proyecto_con_puerto(tmp_path, nombre, port):
245
245
 
246
246
 
247
247
  def test_doctor_avisa_si_otro_proyecto_declara_el_mismo_puerto(tmp_path, monkeypatch, free_ports):
248
- """El dato que solo PortMaster tiene: ningun compose sabe del de al lado."""
248
+ """El dato que solo StackHelx tiene: ningun compose sabe del de al lado."""
249
249
  (port,) = free_ports(1)
250
250
  _proyecto_con_puerto(tmp_path, "blog", port)
251
251
  fitness = _proyecto_con_puerto(tmp_path, "fitness", port)
@@ -819,7 +819,7 @@ def test_open_sin_puerto_no_tapa_al_servicio_que_si_contesta(
819
819
  def test_share_rechaza_un_puerto_fuera_de_rango(tmp_path, monkeypatch):
820
820
  """`target` es texto porque tambien acepta el nombre de un servicio, asi que
821
821
  se pierde el min/max que traen los demas comandos. Sin la validacion a mano,
822
- `portmaster share 0` levantaba el cliente de tuneles contra 127.0.0.1:0.
822
+ `stackhelx share 0` levantaba el cliente de tuneles contra 127.0.0.1:0.
823
823
  """
824
824
  monkeypatch.chdir(tmp_path)
825
825
  for target in ("0", "70000", "99999"):
@@ -471,7 +471,7 @@ COMPOSE_CON_PERFILES = """
471
471
 
472
472
 
473
473
  def test_un_contenedor_con_perfil_no_arranca_por_defecto(tmp_path):
474
- """En compose, `profiles:` excluye. En PortMaster un perfil es una lista de
474
+ """En compose, `profiles:` excluye. En StackHelx un perfil es una lista de
475
475
  lo que se arranca. Traducirlos al reves arranca lo que compose apaga."""
476
476
  write(tmp_path, "compose.yaml", COMPOSE_CON_PERFILES)
477
477
  stack = detect.detect(tmp_path)
@@ -541,7 +541,7 @@ def test_un_compose_sin_perfiles_arranca_todo(tmp_path):
541
541
 
542
542
 
543
543
  def test_congelar_un_compose_con_perfiles_no_cambia_lo_que_arranca(tmp_path):
544
- """`portmaster init` tiene que ser fiel: el archivo congelado arranca lo
544
+ """`stackhelx init` tiene que ser fiel: el archivo congelado arranca lo
545
545
  mismo que la deteccion, ni un contenedor mas."""
546
546
  write(tmp_path, "compose.yaml", COMPOSE_CON_PERFILES)
547
547
  detectado = detect.detect(tmp_path)
@@ -318,7 +318,7 @@ def test_mcp_una_peticion_con_id_si_se_contesta():
318
318
 
319
319
 
320
320
  def test_mcp_tool_call_doctor(tmp_path):
321
- """Prueba que portmaster_doctor ejecuta los checks sin crash."""
321
+ """Prueba que stackhelx_doctor ejecuta los checks sin crash."""
322
322
  req = {
323
323
  "jsonrpc": "2.0",
324
324
  "id": 30,
@@ -332,7 +332,7 @@ def test_mcp_tool_call_doctor(tmp_path):
332
332
 
333
333
 
334
334
  def test_mcp_tool_call_history(tmp_path, monkeypatch):
335
- """Prueba que portmaster_history lee la telemetria."""
335
+ """Prueba que stackhelx_history lee la telemetria."""
336
336
  monkeypatch.setattr(mcp.registry, "HOME", tmp_path)
337
337
  pid = mcp.registry.project_id(tmp_path)
338
338
  mcp.history.append(pid, {"event": "test", "duration_s": 1.5})
@@ -352,7 +352,7 @@ def test_mcp_tool_call_history(tmp_path, monkeypatch):
352
352
 
353
353
 
354
354
  def test_mcp_tool_call_ports(free_ports):
355
- """Prueba que portmaster_ports retorna lista de puertos formateada."""
355
+ """Prueba que stackhelx_ports retorna lista de puertos formateada."""
356
356
  (port,) = free_ports(1)
357
357
  req = {
358
358
  "jsonrpc": "2.0",
@@ -657,7 +657,7 @@ def test_niveles_de_una_cadena_lineal():
657
657
 
658
658
  def test_env_file_inyecta_variables(tmp_path, free_ports):
659
659
  (port,) = free_ports(1)
660
- (tmp_path / ".env").write_text("CUSTOM_VAR=portmaster_rocks\n", encoding="utf-8")
660
+ (tmp_path / ".env").write_text("CUSTOM_VAR=stackhelx_rocks\n", encoding="utf-8")
661
661
  flag = tmp_path / "env_result.txt"
662
662
 
663
663
  stack = stack_from(
@@ -674,7 +674,7 @@ def test_env_file_inyecta_variables(tmp_path, free_ports):
674
674
  try:
675
675
  engine.up()
676
676
  assert flag.exists()
677
- assert flag.read_text(encoding="utf-8") == "portmaster_rocks"
677
+ assert flag.read_text(encoding="utf-8") == "stackhelx_rocks"
678
678
  finally:
679
679
  engine.down()
680
680
 
@@ -726,7 +726,7 @@ def test_pre_start_fallo_aborta_arranque(tmp_path, free_ports):
726
726
 
727
727
  @pytest.fixture
728
728
  def sin_env_global(tmp_path, monkeypatch):
729
- """`build_env` lee `~/.portmaster/env.global`, o sea el disco de quien corre.
729
+ """`build_env` lee `~/.stackhelx/env.global` (o `~/.portmaster/env.global`), o sea el disco de quien corre.
730
730
 
731
731
  Las reglas de precedencia dejan las aserciones de abajo a salvo hoy, pero un
732
732
  env.global que defina una de las variables que estos tests dan por ausentes
@@ -1271,3 +1271,41 @@ def test_bun_detectado_arranca_y_abre_el_puerto(tmp_path, free_ports):
1271
1271
  while time.time() < deadline and not ports.is_free(port):
1272
1272
  time.sleep(0.1)
1273
1273
  assert ports.is_free(port), "el puerto quedo tomado despues de down()"
1274
+
1275
+
1276
+ def test_build_env_compatibilidad_stackhelx_y_portmaster(tmp_path, monkeypatch):
1277
+ """build_env lee ~/.stackhelx/env.global y usa ~/.portmaster/env.global como fallback."""
1278
+ hogar = tmp_path / "home"
1279
+ hogar.mkdir()
1280
+ monkeypatch.setattr(Path, "home", classmethod(lambda cls: hogar))
1281
+
1282
+ srv = config.Service(
1283
+ name="srv",
1284
+ command="echo ok",
1285
+ cwd=tmp_path,
1286
+ port=8080,
1287
+ ready="port",
1288
+ needs=(),
1289
+ env={},
1290
+ detached=False,
1291
+ )
1292
+
1293
+ # 1. Sin archivo
1294
+ env = runner.build_env(srv)
1295
+ assert "MI_GLOBAL" not in env
1296
+
1297
+ # 2. Solo portmaster legacy
1298
+ dir_portmaster = hogar / ".portmaster"
1299
+ dir_portmaster.mkdir()
1300
+ (dir_portmaster / "env.global").write_text("MI_GLOBAL=desde_portmaster\nOTRA_VAR=1\n", encoding="utf-8")
1301
+ env = runner.build_env(srv)
1302
+ assert env.get("MI_GLOBAL") == "desde_portmaster"
1303
+ assert env.get("OTRA_VAR") == "1"
1304
+
1305
+ # 3. StackHelx sobrescribe portmaster legacy
1306
+ dir_stackhelx = hogar / ".stackhelx"
1307
+ dir_stackhelx.mkdir()
1308
+ (dir_stackhelx / "env.global").write_text("MI_GLOBAL=desde_stackhelx\n", encoding="utf-8")
1309
+ env = runner.build_env(srv)
1310
+ assert env.get("MI_GLOBAL") == "desde_stackhelx"
1311
+
@@ -1024,7 +1024,7 @@ def test_el_chequeo_de_docker_no_corre_en_cada_request(client, tmp_path, monkeyp
1024
1024
 
1025
1025
 
1026
1026
  def _sesion_recuperada(tmp_path, port):
1027
- """Simula un reinicio de `portmaster serve` con el proceso todavia vivo."""
1027
+ """Simula un reinicio de `stackhelx serve` con el proceso todavia vivo."""
1028
1028
  import json
1029
1029
 
1030
1030
  root = tmp_path / "sobreviviente"
@@ -1279,7 +1279,7 @@ def _tunel_falso(monkeypatch, proc):
1279
1279
 
1280
1280
 
1281
1281
  def test_los_tuneles_se_cierran_al_apagar_el_servidor(monkeypatch, free_ports):
1282
- """`portmaster serve` terminaba y el cliente de tuneles seguia vivo.
1282
+ """`stackhelx serve` terminaba y el cliente de tuneles seguia vivo.
1283
1283
 
1284
1284
  El puerto quedaba expuesto a internet, sin nada en pantalla que lo dijera y
1285
1285
  sin forma de cerrarlo salvo matar el proceso a mano.
@@ -2088,7 +2088,7 @@ def test_browse_frecuentes(client, tmp_path, monkeypatch):
2088
2088
 
2089
2089
 
2090
2090
  def test_share_rechaza_el_puerto_propio(client, monkeypatch):
2091
- """PortMaster no se publica a si mismo.
2091
+ """StackHelx no se publica a si mismo.
2092
2092
 
2093
2093
  Detras de ese puerto esta la API que corre los comandos de stack.yaml, o
2094
2094
  sea ejecucion arbitraria: el token pasa a ser lo unico entre internet y tu
@@ -2107,7 +2107,7 @@ def test_share_rechaza_el_puerto_propio(client, monkeypatch):
2107
2107
 
2108
2108
  res = client.post("/api/share?port=80")
2109
2109
  assert res.status_code == 400
2110
- assert llamadas == [], "se abrio un tunel hacia el puerto de PortMaster"
2110
+ assert llamadas == [], "se abrio un tunel hacia el puerto de StackHelx"
2111
2111
 
2112
2112
 
2113
2113
  def test_share_no_confia_en_el_header_host(client, monkeypatch):
@@ -275,12 +275,12 @@ def test_el_mcp_rechaza_un_puerto_fuera_de_rango():
275
275
  mcp._execute_tool("stackhelx_share", {"port": 0})
276
276
 
277
277
 
278
- def test_no_se_publica_un_portmaster_serve(monkeypatch):
278
+ def test_no_se_publica_un_stackhelx_serve(monkeypatch):
279
279
  """El unico choke point de los tuneles se niega a exponer la propia API.
280
280
 
281
281
  Detras de ese puerto esta lo que corre los comandos de stack.yaml. Va aca y
282
282
  no en el comando porque por `start_tunnel` pasan los tres caminos: el boton
283
- de la interfaz, `portmaster share` y el CLI.
283
+ de la interfaz, `stackhelx share` y el CLI.
284
284
 
285
285
  Se afirma el efecto: que no se lance ningun cliente de tuneles.
286
286
  """
@@ -290,7 +290,7 @@ def test_no_se_publica_un_portmaster_serve(monkeypatch):
290
290
  tunnel.ports,
291
291
  "scan",
292
292
  lambda port: tunnel.ports.PortStatus(
293
- port=port, free=False, pid=123, cmdline="python -m portmaster serve --port 7666"
293
+ port=port, free=False, pid=123, cmdline="python -m stackhelx serve --port 7666"
294
294
  ),
295
295
  )
296
296
 
@@ -299,7 +299,7 @@ def test_no_se_publica_un_portmaster_serve(monkeypatch):
299
299
  assert lanzados == [], "se levanto un cliente de tuneles contra la propia API"
300
300
 
301
301
 
302
- def test_un_puerto_ajeno_no_se_confunde_con_portmaster(monkeypatch):
302
+ def test_un_puerto_ajeno_no_se_confunde_con_stackhelx(monkeypatch):
303
303
  """La otra mitad: la guarda no puede frenar un tunel legitimo.
304
304
 
305
305
  Sin esto, un `return True` constante pasaria el test de arriba.
@@ -1,2 +0,0 @@
1
- __version__ = "1.0.0"
2
-
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes