stackhelx 1.0.0__tar.gz → 1.1.0__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.
- {stackhelx-1.0.0 → stackhelx-1.1.0}/CHANGELOG.md +24 -5
- {stackhelx-1.0.0 → stackhelx-1.1.0}/PKG-INFO +13 -6
- {stackhelx-1.0.0 → stackhelx-1.1.0}/README.md +12 -5
- {stackhelx-1.0.0 → stackhelx-1.1.0}/docs/comandos.md +5 -2
- {stackhelx-1.0.0 → stackhelx-1.1.0}/docs/plan-expansion.md +29 -29
- {stackhelx-1.0.0 → stackhelx-1.1.0}/docs/plan-lenguajes.md +3 -3
- {stackhelx-1.0.0 → stackhelx-1.1.0}/stack.example.yaml +3 -3
- stackhelx-1.1.0/stackhelx/__init__.py +2 -0
- {stackhelx-1.0.0 → stackhelx-1.1.0}/stackhelx/cli.py +15 -1
- {stackhelx-1.0.0 → stackhelx-1.1.0}/stackhelx/mcp.py +1 -1
- {stackhelx-1.0.0 → stackhelx-1.1.0}/stackhelx/ports.py +4 -4
- {stackhelx-1.0.0 → stackhelx-1.1.0}/stackhelx/registry.py +2 -2
- {stackhelx-1.0.0 → stackhelx-1.1.0}/stackhelx/runner.py +7 -5
- {stackhelx-1.0.0 → stackhelx-1.1.0}/stackhelx/scripts.py +1 -1
- {stackhelx-1.0.0 → stackhelx-1.1.0}/stackhelx/server.py +6 -6
- {stackhelx-1.0.0 → stackhelx-1.1.0}/stackhelx/tunnel.py +1 -1
- {stackhelx-1.0.0 → stackhelx-1.1.0}/stackhelx/web/app.css +102 -0
- {stackhelx-1.0.0 → stackhelx-1.1.0}/stackhelx/web/app.js +101 -1
- {stackhelx-1.0.0 → stackhelx-1.1.0}/stackhelx/web/index.html +30 -1
- {stackhelx-1.0.0 → stackhelx-1.1.0}/tests/test_cli.py +37 -3
- {stackhelx-1.0.0 → stackhelx-1.1.0}/tests/test_detect.py +2 -2
- {stackhelx-1.0.0 → stackhelx-1.1.0}/tests/test_mcp.py +3 -3
- {stackhelx-1.0.0 → stackhelx-1.1.0}/tests/test_runner.py +41 -3
- {stackhelx-1.0.0 → stackhelx-1.1.0}/tests/test_scripts.py +1 -1
- {stackhelx-1.0.0 → stackhelx-1.1.0}/tests/test_server.py +37 -4
- {stackhelx-1.0.0 → stackhelx-1.1.0}/tests/test_tunnel.py +7 -7
- stackhelx-1.0.0/stackhelx/__init__.py +0 -2
- {stackhelx-1.0.0 → stackhelx-1.1.0}/.github/workflows/ci.yml +0 -0
- {stackhelx-1.0.0 → stackhelx-1.1.0}/.github/workflows/release.yml +0 -0
- {stackhelx-1.0.0 → stackhelx-1.1.0}/.gitignore +0 -0
- {stackhelx-1.0.0 → stackhelx-1.1.0}/AGENTS.md +0 -0
- {stackhelx-1.0.0 → stackhelx-1.1.0}/CLAUDE.md +0 -0
- {stackhelx-1.0.0 → stackhelx-1.1.0}/LICENSE +0 -0
- {stackhelx-1.0.0 → stackhelx-1.1.0}/docs/deteccion.md +0 -0
- {stackhelx-1.0.0 → stackhelx-1.1.0}/docs/interfaz.md +0 -0
- {stackhelx-1.0.0 → stackhelx-1.1.0}/docs/pendientes.md +0 -0
- {stackhelx-1.0.0 → stackhelx-1.1.0}/docs/plan-siguiente.md +0 -0
- {stackhelx-1.0.0 → stackhelx-1.1.0}/docs/stack-yaml.md +0 -0
- {stackhelx-1.0.0 → stackhelx-1.1.0}/docs/superpowers/plans/2026-08-08-partir-readme.md +0 -0
- {stackhelx-1.0.0 → stackhelx-1.1.0}/docs/superpowers/plans/2026-08-08-release-1-0-1.md +0 -0
- {stackhelx-1.0.0 → stackhelx-1.1.0}/docs/superpowers/plans/2026-08-18-campo-url.md +0 -0
- {stackhelx-1.0.0 → stackhelx-1.1.0}/docs/superpowers/specs/2026-07-29-autodeteccion-design.md +0 -0
- {stackhelx-1.0.0 → stackhelx-1.1.0}/pyproject.toml +0 -0
- {stackhelx-1.0.0 → stackhelx-1.1.0}/scripts/check_docs.py +0 -0
- {stackhelx-1.0.0 → stackhelx-1.1.0}/scripts/check_metadata.py +0 -0
- {stackhelx-1.0.0 → stackhelx-1.1.0}/stackhelx/__main__.py +0 -0
- {stackhelx-1.0.0 → stackhelx-1.1.0}/stackhelx/browse.py +0 -0
- {stackhelx-1.0.0 → stackhelx-1.1.0}/stackhelx/config.py +0 -0
- {stackhelx-1.0.0 → stackhelx-1.1.0}/stackhelx/detect.py +0 -0
- {stackhelx-1.0.0 → stackhelx-1.1.0}/stackhelx/docker.py +0 -0
- {stackhelx-1.0.0 → stackhelx-1.1.0}/stackhelx/doctor.py +0 -0
- {stackhelx-1.0.0 → stackhelx-1.1.0}/stackhelx/guardrails.py +0 -0
- {stackhelx-1.0.0 → stackhelx-1.1.0}/stackhelx/history.py +0 -0
- {stackhelx-1.0.0 → stackhelx-1.1.0}/stackhelx/web/tokens.css +0 -0
- {stackhelx-1.0.0 → stackhelx-1.1.0}/tests/conftest.py +0 -0
- {stackhelx-1.0.0 → stackhelx-1.1.0}/tests/test_browse.py +0 -0
- {stackhelx-1.0.0 → stackhelx-1.1.0}/tests/test_config.py +0 -0
- {stackhelx-1.0.0 → stackhelx-1.1.0}/tests/test_conftest.py +0 -0
- {stackhelx-1.0.0 → stackhelx-1.1.0}/tests/test_guardrails.py +0 -0
- {stackhelx-1.0.0 → stackhelx-1.1.0}/tests/test_history.py +0 -0
- {stackhelx-1.0.0 → stackhelx-1.1.0}/tests/test_ports.py +0 -0
- {stackhelx-1.0.0 → stackhelx-1.1.0}/tests/test_registry.py +0 -0
- {stackhelx-1.0.0 → stackhelx-1.1.0}/uv.lock +0 -0
|
@@ -4,6 +4,26 @@ 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.1.0] - 2026-09-18
|
|
8
|
+
|
|
9
|
+
### Añadido y Mejorado
|
|
10
|
+
|
|
11
|
+
- **Onboarding de Agentes MCP:** Modal web interactivo con panel desplegable colapsable para configurar clientes de IA (Claude Desktop, Cursor, Antigravity, VS Code), con generación de snippets JSON (`shx` / `uvx`), descarga directa de `stackhelx-mcp.json` y copia de System Prompt para agentes.
|
|
12
|
+
- **Banderas CLI para MCP:** Se incorporan las opciones `shx mcp --config` (imprime bloque JSON de configuración) y `shx mcp --prompt` (imprime instrucciones recomendadas para agentes).
|
|
13
|
+
- **Telemetría y Métricas en Tiempo Real:** Tooltip explicativo (`title` y `aria-label`) con cursor `help` sobre el badge de métricas de servicio (`.service__metrics`). Desglosa el uso de CPU multi-core (equivalencia de núcleos activos) y aclara que la memoria es física residente (RSS).
|
|
14
|
+
- **Documentación del alias `shx`:** `README.md` y `docs/comandos.md` documentan formalmente la instalación y uso del comando abreviado `shx` 1:1 junto con `stackhelx`.
|
|
15
|
+
- **Diseño sin emojis:** Estilo visual puro y tipográfico, conforme a los principios de diseño sobrio y accesible.
|
|
16
|
+
|
|
17
|
+
## [1.0.1] - 2026-09-13
|
|
18
|
+
|
|
19
|
+
### Seguridad y Limpieza
|
|
20
|
+
|
|
21
|
+
- **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`.
|
|
22
|
+
- **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.
|
|
23
|
+
- **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`.
|
|
24
|
+
- **Plantilla de configuración (`stack.example.yaml`):** Actualizada para documentar `stackhelx init` y la nueva identidad de StackHelx.
|
|
25
|
+
- **Suite de pruebas:** 474 pruebas en verde (+1 test de precedencia de entorno global).
|
|
26
|
+
|
|
7
27
|
## [1.0.0] - 2026-09-13
|
|
8
28
|
|
|
9
29
|
### Rebrand y Nuevo Comienzo
|
|
@@ -548,8 +568,7 @@ Primera versión publicada. Lo que sigue es el alcance completo, no un diff.
|
|
|
548
568
|
[1.3.0]: https://github.com/TicoraX/PortMaster/releases/tag/v1.3.0
|
|
549
569
|
[1.2.0]: https://github.com/TicoraX/PortMaster/releases/tag/v1.2.0
|
|
550
570
|
[1.1.1]: https://github.com/TicoraX/PortMaster/releases/tag/v1.1.1
|
|
551
|
-
[1.1.0]: https://github.com/TicoraX/
|
|
552
|
-
[1.0.
|
|
553
|
-
[1.0.
|
|
554
|
-
|
|
555
|
-
[1.0.0]: https://github.com/TicoraX/PortMaster/releases/tag/v1.0.0
|
|
571
|
+
[1.1.0]: https://github.com/TicoraX/StackHelx/releases/tag/v1.1.0
|
|
572
|
+
[1.0.1]: https://github.com/TicoraX/StackHelx/releases/tag/v1.0.1
|
|
573
|
+
[1.0.0]: https://github.com/TicoraX/StackHelx/releases/tag/v1.0.0
|
|
574
|
+
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: stackhelx
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.1.0
|
|
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
|
|
@@ -59,7 +59,7 @@ Description-Content-Type: text/markdown
|
|
|
59
59
|
|
|
60
60
|
# StackHelx
|
|
61
61
|
|
|
62
|
-
[](https://pypi.org/project/stackhelx/)
|
|
62
|
+
[](https://pypi.org/project/stackhelx/)
|
|
63
63
|
[](https://github.com/TicoraX/StackHelx/actions/workflows/ci.yml)
|
|
64
64
|
[](LICENSE)
|
|
65
65
|
[](pyproject.toml)
|
|
@@ -76,11 +76,15 @@ uv tool install stackhelx
|
|
|
76
76
|
pipx install stackhelx
|
|
77
77
|
```
|
|
78
78
|
|
|
79
|
+
La instalación registra dos ejecutables idénticos en el sistema: el comando principal **`stackhelx`** y su versión abreviada **`shx`**.
|
|
80
|
+
|
|
79
81
|
Requiere Python 3.10 o superior. Funciona en Windows, macOS y Linux.
|
|
80
82
|
|
|
81
83
|
## Comandos
|
|
82
84
|
|
|
83
|
-
|
|
85
|
+
Todos los comandos pueden ejecutarse como `stackhelx <comando>` o de forma abreviada con su alias oficial **`shx <comando>`** (ej. `shx up`, `shx down`, `shx doctor`, `shx ports`):
|
|
86
|
+
|
|
87
|
+
| Comando (`stackhelx` / `shx`) | Qué hace |
|
|
84
88
|
|---|---|
|
|
85
89
|
| `stackhelx up` | Levanta el stack entero: libera puertos, arranca en orden y sigue los logs |
|
|
86
90
|
| `stackhelx down` | Baja lo que sobrevive a la terminal, o sea contenedores |
|
|
@@ -113,10 +117,13 @@ Cada uno con `--help`.
|
|
|
113
117
|
## Arrancar un stack
|
|
114
118
|
|
|
115
119
|
```bash
|
|
120
|
+
shx up
|
|
121
|
+
# o
|
|
116
122
|
stackhelx up
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
123
|
+
|
|
124
|
+
shx up --profile backend # solo un subconjunto
|
|
125
|
+
shx up --no-free # no tocar los puertos ocupados
|
|
126
|
+
shx up --env-file .env.qa # carga ese .env antes de arrancar
|
|
120
127
|
```
|
|
121
128
|
|
|
122
129
|
`--env-file` no reemplaza al `env_file:` de `stack.yaml`, se suma: carga el
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# StackHelx
|
|
2
2
|
|
|
3
|
-
[](https://pypi.org/project/stackhelx/)
|
|
3
|
+
[](https://pypi.org/project/stackhelx/)
|
|
4
4
|
[](https://github.com/TicoraX/StackHelx/actions/workflows/ci.yml)
|
|
5
5
|
[](LICENSE)
|
|
6
6
|
[](pyproject.toml)
|
|
@@ -17,11 +17,15 @@ uv tool install stackhelx
|
|
|
17
17
|
pipx install stackhelx
|
|
18
18
|
```
|
|
19
19
|
|
|
20
|
+
La instalación registra dos ejecutables idénticos en el sistema: el comando principal **`stackhelx`** y su versión abreviada **`shx`**.
|
|
21
|
+
|
|
20
22
|
Requiere Python 3.10 o superior. Funciona en Windows, macOS y Linux.
|
|
21
23
|
|
|
22
24
|
## Comandos
|
|
23
25
|
|
|
24
|
-
|
|
26
|
+
Todos los comandos pueden ejecutarse como `stackhelx <comando>` o de forma abreviada con su alias oficial **`shx <comando>`** (ej. `shx up`, `shx down`, `shx doctor`, `shx ports`):
|
|
27
|
+
|
|
28
|
+
| Comando (`stackhelx` / `shx`) | Qué hace |
|
|
25
29
|
|---|---|
|
|
26
30
|
| `stackhelx up` | Levanta el stack entero: libera puertos, arranca en orden y sigue los logs |
|
|
27
31
|
| `stackhelx down` | Baja lo que sobrevive a la terminal, o sea contenedores |
|
|
@@ -54,10 +58,13 @@ Cada uno con `--help`.
|
|
|
54
58
|
## Arrancar un stack
|
|
55
59
|
|
|
56
60
|
```bash
|
|
61
|
+
shx up
|
|
62
|
+
# o
|
|
57
63
|
stackhelx up
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
64
|
+
|
|
65
|
+
shx up --profile backend # solo un subconjunto
|
|
66
|
+
shx up --no-free # no tocar los puertos ocupados
|
|
67
|
+
shx up --env-file .env.qa # carga ese .env antes de arrancar
|
|
61
68
|
```
|
|
62
69
|
|
|
63
70
|
`--env-file` no reemplaza al `env_file:` de `stack.yaml`, se suma: carga el
|
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
# Otros comandos
|
|
2
2
|
|
|
3
|
+
> **Nota:** Todos los comandos del sistema pueden ejecutarse indistintamente con `stackhelx <cmd>` o mediante el alias corto oficial **`shx <cmd>`** (por ejemplo: `shx down`, `shx doctor`, `shx switch`, `shx run`, `shx share`).
|
|
4
|
+
|
|
3
5
|
`up`, `serve`, `ports` y `free` están en el [README](../README.md#comandos).
|
|
4
6
|
Acá están los cuatro que quedan, con qué revisa cada uno y por qué.
|
|
5
7
|
|
|
6
8
|
## Bajar lo que sobrevive a la terminal
|
|
7
9
|
|
|
8
10
|
```bash
|
|
9
|
-
|
|
10
|
-
stackhelx down
|
|
11
|
+
shx down
|
|
12
|
+
# o stackhelx down
|
|
13
|
+
shx down --profile backend
|
|
11
14
|
```
|
|
12
15
|
|
|
13
16
|
`Ctrl-C` sobre un `stackhelx up` apaga a sus hijos, pero un
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
# Plan de Expansión Estratégica:
|
|
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
|
|
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
|
-
│
|
|
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) │ -
|
|
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 │ -
|
|
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**: [`
|
|
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 (`~/.
|
|
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 (`
|
|
41
|
-
* **Módulo**: [`
|
|
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**: `
|
|
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 (`
|
|
53
|
-
* **Módulo**: [`
|
|
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**: `
|
|
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**: [`
|
|
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**: `
|
|
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 (`
|
|
69
|
-
* **Módulo**: [`
|
|
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
|
-
* `
|
|
72
|
-
* `
|
|
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**: [`
|
|
75
|
+
* **Módulo**: [`stackhelx/mcp.py`](../stackhelx/mcp.py)
|
|
76
76
|
* **Herramientas MCP Expuestas**:
|
|
77
|
-
* `
|
|
78
|
-
* `
|
|
79
|
-
* `
|
|
80
|
-
* `
|
|
81
|
-
* `
|
|
82
|
-
* `
|
|
83
|
-
* `
|
|
84
|
-
* `
|
|
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**: [`
|
|
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
|
-
|
|
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
|
-
- `
|
|
302
|
-
- `
|
|
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
|
|
1
|
+
# Esquema de configuracion de StackHelx.
|
|
2
2
|
#
|
|
3
|
-
# El archivo es opcional: sin uno,
|
|
4
|
-
# `
|
|
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
|
|
|
@@ -892,19 +892,33 @@ def mcp_cmd(
|
|
|
892
892
|
"-c",
|
|
893
893
|
help="Muestra el bloque de configuracion JSON para Claude Desktop, Cursor o Antigravity.",
|
|
894
894
|
),
|
|
895
|
+
show_prompt: bool = typer.Option(
|
|
896
|
+
False,
|
|
897
|
+
"--prompt",
|
|
898
|
+
"-p",
|
|
899
|
+
help="Muestra las instrucciones recomendadas para el agente de IA.",
|
|
900
|
+
),
|
|
895
901
|
) -> None:
|
|
896
902
|
"""Inicia el servidor Model Context Protocol (MCP) sobre stdio para agentes de IA."""
|
|
897
903
|
if show_config:
|
|
898
904
|
cfg = {
|
|
899
905
|
"mcpServers": {
|
|
900
906
|
"stackhelx": {
|
|
901
|
-
"command": "
|
|
907
|
+
"command": "shx",
|
|
902
908
|
"args": ["mcp"],
|
|
903
909
|
}
|
|
904
910
|
}
|
|
905
911
|
}
|
|
906
912
|
console.print(json.dumps(cfg, indent=2))
|
|
907
913
|
return
|
|
914
|
+
if show_prompt:
|
|
915
|
+
console.print(
|
|
916
|
+
"Tienes a tu disposición las herramientas MCP de StackHelx (`stackhelx_*`). "
|
|
917
|
+
"Úsalas para inspeccionar el estado de puertos (`stackhelx_ports`), diagnosticar problemas de entorno (`stackhelx_doctor`), "
|
|
918
|
+
"liberar puertos en conflicto (`stackhelx_free`), levantar el stack del proyecto (`stackhelx_up`), "
|
|
919
|
+
"apagarlo (`stackhelx_down`), ejecutar scripts declarados (`stackhelx_run`) y compartir servicios vía túneles HTTPS (`stackhelx_share`)."
|
|
920
|
+
)
|
|
921
|
+
return
|
|
908
922
|
mcp.serve_stdio()
|
|
909
923
|
|
|
910
924
|
|
|
@@ -420,7 +420,7 @@ def _reservar_stdout():
|
|
|
420
420
|
"""Deja el descriptor 1 solo para el protocolo y manda lo demas a stderr.
|
|
421
421
|
|
|
422
422
|
Sobre stdio el JSON-RPC comparte el descriptor 1 con todo lo que imprima el
|
|
423
|
-
proceso. `portmaster_run` lanza los comandos del usuario heredando ese
|
|
423
|
+
proceso. `stackhelx_run` (o `portmaster_run`) lanza los comandos del usuario heredando ese
|
|
424
424
|
descriptor, asi que un `echo` adentro de un script se metia entre dos
|
|
425
425
|
respuestas y el cliente perdia la sesion.
|
|
426
426
|
|
|
@@ -39,7 +39,7 @@ TERMINATE_TIMEOUT = 5
|
|
|
39
39
|
|
|
40
40
|
|
|
41
41
|
class KillRefused(Exception):
|
|
42
|
-
"""El proceso existe pero
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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() / ".
|
|
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:
|
|
@@ -346,7 +348,7 @@ class Runner:
|
|
|
346
348
|
if estrenados:
|
|
347
349
|
# Un solo respiro para todo el lote, no uno por proceso: sin esto,
|
|
348
350
|
# la primera lectura de un servicio recien arrancado seria 0.0 y
|
|
349
|
-
# `
|
|
351
|
+
# `stackhelx stats`, que hace una sola consulta, nunca mediria nada.
|
|
350
352
|
time.sleep(CPU_MUESTRA)
|
|
351
353
|
|
|
352
354
|
vivos = {proc.pid for arbol in arboles.values() for proc in arbol}
|
|
@@ -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 `
|
|
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 `
|
|
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("
|
|
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[
|
|
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
|
|
@@ -695,7 +695,7 @@ def _cerrar_tuneles() -> None:
|
|
|
695
695
|
async def _ciclo_de_vida(app: FastAPI):
|
|
696
696
|
"""Al apagar, cierra los tuneles.
|
|
697
697
|
|
|
698
|
-
Sin esto `
|
|
698
|
+
Sin esto `stackhelx serve` terminaba y el cliente de tuneles seguia vivo,
|
|
699
699
|
con el puerto expuesto a internet, sin nada en pantalla que lo dijera y sin
|
|
700
700
|
forma de cerrarlo que no fuera matarlo a mano.
|
|
701
701
|
"""
|
|
@@ -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="
|
|
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
|
-
#
|
|
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
|
|
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
|
|
|
@@ -146,7 +146,7 @@ def start_tunnel(
|
|
|
146
146
|
|
|
147
147
|
# Mirando tambien si el proceso se murio, y no solo el reloj. Un cliente que
|
|
148
148
|
# falla al arrancar (`ngrok` sin autenticar) se va en menos de un segundo, y
|
|
149
|
-
# esperarle el plazo entero dejaba a `
|
|
149
|
+
# esperarle el plazo entero dejaba a `stackhelx share` pareciendo colgado
|
|
150
150
|
# antes de dar un error que ya se sabia.
|
|
151
151
|
limite = time.monotonic() + timeout
|
|
152
152
|
while not ready_event.wait(0.1):
|
|
@@ -635,6 +635,7 @@ body {
|
|
|
635
635
|
border-radius: var(--radius-sm);
|
|
636
636
|
white-space: nowrap;
|
|
637
637
|
align-self: center;
|
|
638
|
+
cursor: help;
|
|
638
639
|
}
|
|
639
640
|
|
|
640
641
|
.service__act {
|
|
@@ -1653,6 +1654,107 @@ body {
|
|
|
1653
1654
|
margin-top: var(--space-sm);
|
|
1654
1655
|
}
|
|
1655
1656
|
|
|
1657
|
+
.mcp__setup {
|
|
1658
|
+
border: var(--rule) solid var(--color-rule);
|
|
1659
|
+
border-radius: var(--radius);
|
|
1660
|
+
background: var(--color-paper-2);
|
|
1661
|
+
overflow: hidden;
|
|
1662
|
+
}
|
|
1663
|
+
|
|
1664
|
+
.mcp__setup-summary {
|
|
1665
|
+
display: flex;
|
|
1666
|
+
align-items: center;
|
|
1667
|
+
justify-content: space-between;
|
|
1668
|
+
padding: var(--space-xs) var(--space-sm);
|
|
1669
|
+
font-size: var(--text-sm);
|
|
1670
|
+
font-weight: 500;
|
|
1671
|
+
color: var(--color-ink-2);
|
|
1672
|
+
cursor: pointer;
|
|
1673
|
+
user-select: none;
|
|
1674
|
+
list-style: none;
|
|
1675
|
+
}
|
|
1676
|
+
|
|
1677
|
+
.mcp__setup-summary::-webkit-details-marker {
|
|
1678
|
+
display: none;
|
|
1679
|
+
}
|
|
1680
|
+
|
|
1681
|
+
.mcp__setup-summary:hover {
|
|
1682
|
+
color: var(--color-ink);
|
|
1683
|
+
background: var(--color-paper-3);
|
|
1684
|
+
}
|
|
1685
|
+
|
|
1686
|
+
.mcp__setup-summary:focus-visible {
|
|
1687
|
+
outline: 2px solid var(--color-focus);
|
|
1688
|
+
outline-offset: -2px;
|
|
1689
|
+
}
|
|
1690
|
+
|
|
1691
|
+
.mcp__setup-icon {
|
|
1692
|
+
font-size: var(--text-xs);
|
|
1693
|
+
transition: transform 0.15s ease;
|
|
1694
|
+
}
|
|
1695
|
+
|
|
1696
|
+
.mcp__setup[open] .mcp__setup-icon {
|
|
1697
|
+
transform: rotate(180deg);
|
|
1698
|
+
}
|
|
1699
|
+
|
|
1700
|
+
.mcp__setup-body {
|
|
1701
|
+
padding: var(--space-sm) var(--space-md) var(--space-md);
|
|
1702
|
+
border-top: var(--rule) solid var(--color-rule);
|
|
1703
|
+
display: flex;
|
|
1704
|
+
flex-direction: column;
|
|
1705
|
+
gap: var(--space-xs);
|
|
1706
|
+
}
|
|
1707
|
+
|
|
1708
|
+
.mcp__setup-tabs {
|
|
1709
|
+
display: flex;
|
|
1710
|
+
gap: var(--space-md);
|
|
1711
|
+
font-size: var(--text-xs);
|
|
1712
|
+
color: var(--color-ink-2);
|
|
1713
|
+
}
|
|
1714
|
+
|
|
1715
|
+
.mcp__setup-radio {
|
|
1716
|
+
display: inline-flex;
|
|
1717
|
+
align-items: center;
|
|
1718
|
+
gap: 0.35rem;
|
|
1719
|
+
cursor: pointer;
|
|
1720
|
+
}
|
|
1721
|
+
|
|
1722
|
+
.mcp__code {
|
|
1723
|
+
font-family: var(--font-mono);
|
|
1724
|
+
font-size: var(--text-xs);
|
|
1725
|
+
background: var(--color-paper);
|
|
1726
|
+
border: var(--rule) solid var(--color-rule);
|
|
1727
|
+
border-radius: var(--radius-sm);
|
|
1728
|
+
padding: var(--space-xs) var(--space-sm);
|
|
1729
|
+
margin: 0;
|
|
1730
|
+
white-space: pre;
|
|
1731
|
+
overflow-x: auto;
|
|
1732
|
+
color: var(--color-ink);
|
|
1733
|
+
}
|
|
1734
|
+
|
|
1735
|
+
.mcp__setup-actions {
|
|
1736
|
+
display: flex;
|
|
1737
|
+
flex-wrap: wrap;
|
|
1738
|
+
gap: var(--space-xs);
|
|
1739
|
+
margin-top: 0.25rem;
|
|
1740
|
+
}
|
|
1741
|
+
|
|
1742
|
+
.mcp__setup-hint {
|
|
1743
|
+
font-size: var(--text-xs);
|
|
1744
|
+
color: var(--color-ink-3);
|
|
1745
|
+
margin: 0.25rem 0 0;
|
|
1746
|
+
line-height: 1.4;
|
|
1747
|
+
}
|
|
1748
|
+
|
|
1749
|
+
.mcp__setup-hint code {
|
|
1750
|
+
font-family: var(--font-mono);
|
|
1751
|
+
color: var(--color-ink-2);
|
|
1752
|
+
background: var(--color-paper);
|
|
1753
|
+
padding: 0.1rem 0.3rem;
|
|
1754
|
+
border-radius: 3px;
|
|
1755
|
+
border: var(--rule) solid var(--color-rule);
|
|
1756
|
+
}
|
|
1757
|
+
|
|
1656
1758
|
.mcp__stats {
|
|
1657
1759
|
display: flex;
|
|
1658
1760
|
flex-wrap: wrap;
|