zugzbot 1.0.30 → 1.0.31
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.opencode/agents/sdd-deployer.md +4 -4
- package/.opencode/plugins/plugin_tui.tsx +1 -1
- package/.opencode/templates/fastapi-sdd/Dockerfile +4 -3
- package/.opencode/templates/fastapi-sdd/README.md +15 -16
- package/.opencode/templates/fastapi-sdd/docker-compose.yml +1 -1
- package/.opencode/templates/fastapi-sdd/pyproject.toml +2 -2
- package/.opencode/templates/fastapi-sdd/src/{app/main.py → main.py} +1 -1
- package/.opencode/templates/fastapi-sdd/src/tests/conftest.py +1 -1
- package/.opencode/templates/fastapi-sdd/src/tests/test_main.py +1 -1
- package/.opencode/tools/sdd_bootstrap.ts +8 -8
- package/.opencode/tools/sdd_docker.ts +55 -7
- package/.utils/export_opencode_session.py +1 -1
- package/bin/init.js +1 -1
- package/package.json +1 -1
- /package/.opencode/templates/fastapi-sdd/src/{app/__init__.py → __init__.py} +0 -0
- /package/.opencode/templates/fastapi-sdd/src/{app/core → core}/__init__.py +0 -0
- /package/.opencode/templates/fastapi-sdd/src/{app/core → core}/config.py +0 -0
- /package/.opencode/templates/fastapi-sdd/src/{app/routers → routers}/__init__.py +0 -0
- /package/.opencode/templates/fastapi-sdd/src/{app/schemas → schemas}/__init__.py +0 -0
|
@@ -17,16 +17,16 @@ Eres el Desplegador de Software (sdd-deployer) del flujo SDD. Tu único trabajo
|
|
|
17
17
|
</identity>
|
|
18
18
|
|
|
19
19
|
<constraints>
|
|
20
|
-
- **
|
|
20
|
+
- **Lienzo en Blanco Obligatorio**: Es un requerimiento crítico del sistema limpiar completamente el entorno de Docker (todos los contenedores e imágenes, sin importar el proyecto o contenedor que sea) antes de iniciar un despliegue para evitar cualquier colisión o fallo por recursos heredados.
|
|
21
21
|
- **Modo Detach Obligatorio**: Tienes prohibido ejecutar contenedores en primer plano. Ejecútalos siempre en segundo plano (`-d`) para no bloquear la terminal.
|
|
22
22
|
</constraints>
|
|
23
23
|
|
|
24
24
|
<deployment>
|
|
25
|
-
- **Chequeo de Docker**:
|
|
25
|
+
- **Chequeo de Docker**: El daemon de Docker debe estar activo. La herramienta de limpieza `sdd_clean_docker_environment` se encargará de verificar su estado y levantarlo automáticamente si es necesario (ej. `open -a Docker` en macOS).
|
|
26
26
|
- **Limpieza y Liberación (OBLIGATORIO)**:
|
|
27
27
|
1. Ejecuta la herramienta `sdd_free_port` con el puerto objetivo (ej. 3000) para terminar de forma proactiva cualquier proceso que esté ocupándolo.
|
|
28
|
-
2. Ejecuta la herramienta `sdd_clean_docker_environment` para
|
|
29
|
-
3. Detiene y limpia el entorno anterior ejecutando `docker compose down -v --remove-orphans
|
|
28
|
+
2. Ejecuta la herramienta `sdd_clean_docker_environment` para detener y eliminar TODOS los contenedores existentes, y remover TODAS las imágenes, volúmenes y redes del sistema, garantizando un lienzo en blanco.
|
|
29
|
+
3. Detiene y limpia el entorno anterior ejecutando `docker compose down -v --remove-orphans` (si existe un archivo docker-compose previo en el proyecto).
|
|
30
30
|
- **Generar Docker artifacts (RECOMENDADO)**: Usa la tool `sdd_generate_dockerfile({ stack: "nextjs", port: 3000 })` para crear `Dockerfile` + `.dockerignore` + `docker-compose.yml` en **una sola llamada** (detecta automáticamente npm/pnpm/yarn).
|
|
31
31
|
- **Plantillas Docker (FALLBACK)**: Si la tool no está disponible, carga la skill `docker-templates` para obtener configuraciones optimizadas de acuerdo a tu stack.
|
|
32
32
|
- **Dockerignore**: Asegúrate de que existe un `.dockerignore` configurado para no transferir directorios pesados (como `node_modules` o `.next`) al contexto del build.
|
|
@@ -12,7 +12,7 @@ const PluginTuiSidebar: TuiPlugin = async (api) => {
|
|
|
12
12
|
const [sessionIds, setSessionIds] = createSignal<string[]>([props.session_id])
|
|
13
13
|
|
|
14
14
|
const getZugzbotVersion = (): string => {
|
|
15
|
-
const fallback = "1.0.
|
|
15
|
+
const fallback = "1.0.31"
|
|
16
16
|
try {
|
|
17
17
|
// 1. Try reading the dynamic version.json written during install
|
|
18
18
|
const versionJsonPath = path.join(process.cwd(), ".opencode/version.json")
|
|
@@ -13,7 +13,8 @@ WORKDIR /app
|
|
|
13
13
|
|
|
14
14
|
ENV PYTHONDONTWRITEBYTECODE=1 \
|
|
15
15
|
PYTHONUNBUFFERED=1 \
|
|
16
|
-
ENV=production
|
|
16
|
+
ENV=production \
|
|
17
|
+
PYTHONPATH=/app
|
|
17
18
|
|
|
18
19
|
RUN addgroup --system --gid 1001 app \
|
|
19
20
|
&& adduser --system --uid 1001 appuser
|
|
@@ -26,6 +27,6 @@ USER appuser
|
|
|
26
27
|
EXPOSE 8000
|
|
27
28
|
|
|
28
29
|
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
|
|
29
|
-
CMD python -c "import
|
|
30
|
+
CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:8000/health', timeout=5).read()" || exit 1
|
|
30
31
|
|
|
31
|
-
CMD ["uvicorn", "
|
|
32
|
+
CMD ["uvicorn", "src.main:app", "--host", "0.0.0.0", "--port", "8000"]
|
|
@@ -5,19 +5,19 @@ Backend FastAPI bootstrapped por el arnés SDD.
|
|
|
5
5
|
## Quickstart
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
|
-
# Con
|
|
9
|
-
uv sync
|
|
10
|
-
uv run uvicorn app.main:app --reload --port 8000
|
|
11
|
-
|
|
12
|
-
# O con pip
|
|
8
|
+
# Con pip (con o sin venv activo)
|
|
13
9
|
pip install -e ".[dev]"
|
|
14
|
-
uvicorn
|
|
10
|
+
uvicorn src.main:app --reload --port 8000
|
|
11
|
+
|
|
12
|
+
# Con uv (si está disponible)
|
|
13
|
+
uv sync
|
|
14
|
+
uv run uvicorn src.main:app --reload --port 8000
|
|
15
15
|
```
|
|
16
16
|
|
|
17
17
|
## Tests
|
|
18
18
|
|
|
19
19
|
```bash
|
|
20
|
-
|
|
20
|
+
python -m pytest
|
|
21
21
|
# o
|
|
22
22
|
pytest
|
|
23
23
|
```
|
|
@@ -25,8 +25,8 @@ pytest
|
|
|
25
25
|
## Lint + format
|
|
26
26
|
|
|
27
27
|
```bash
|
|
28
|
-
|
|
29
|
-
|
|
28
|
+
ruff check .
|
|
29
|
+
ruff format .
|
|
30
30
|
```
|
|
31
31
|
|
|
32
32
|
## Docker
|
|
@@ -40,12 +40,11 @@ curl http://localhost:8000/health
|
|
|
40
40
|
|
|
41
41
|
```
|
|
42
42
|
src/
|
|
43
|
-
├── app
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
└── tests/ # Pytest suite
|
|
43
|
+
├── main.py # FastAPI app
|
|
44
|
+
├── core/config.py # Settings (pydantic-settings)
|
|
45
|
+
├── routers/ # Endpoints (vacío por defecto)
|
|
46
|
+
├── schemas/ # Pydantic models (vacío por defecto)
|
|
47
|
+
└── tests/ # Pytest suite
|
|
49
48
|
```
|
|
50
49
|
|
|
51
50
|
## Stack
|
|
@@ -57,4 +56,4 @@ src/
|
|
|
57
56
|
- Pytest 8 + pytest-asyncio
|
|
58
57
|
- Ruff 0.8+ (lint + format)
|
|
59
58
|
|
|
60
|
-
Ver
|
|
59
|
+
Ver `pyproject.toml` para versiones pinneadas.
|
|
@@ -9,7 +9,7 @@ services:
|
|
|
9
9
|
- ENV=production
|
|
10
10
|
restart: unless-stopped
|
|
11
11
|
healthcheck:
|
|
12
|
-
test: ["CMD", "python", "-c", "import
|
|
12
|
+
test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:8000/health', timeout=5).read()"]
|
|
13
13
|
interval: 30s
|
|
14
14
|
timeout: 5s
|
|
15
15
|
retries: 3
|
|
@@ -23,7 +23,7 @@ requires = ["hatchling"]
|
|
|
23
23
|
build-backend = "hatchling.build"
|
|
24
24
|
|
|
25
25
|
[tool.hatch.build.targets.wheel]
|
|
26
|
-
packages = ["src
|
|
26
|
+
packages = ["src"]
|
|
27
27
|
|
|
28
28
|
[tool.ruff]
|
|
29
29
|
line-length = 100
|
|
@@ -36,4 +36,4 @@ ignore = ["E501"]
|
|
|
36
36
|
[tool.pytest.ini_options]
|
|
37
37
|
testpaths = ["src/tests", "tests"]
|
|
38
38
|
asyncio_mode = "auto"
|
|
39
|
-
pythonpath = ["src"]
|
|
39
|
+
pythonpath = [".", "src"]
|
|
@@ -375,7 +375,7 @@ export const bootstrap_fastapi = tool({
|
|
|
375
375
|
}
|
|
376
376
|
|
|
377
377
|
const isInitialized =
|
|
378
|
-
fs.existsSync(path.resolve(root, "src/
|
|
378
|
+
fs.existsSync(path.resolve(root, "src/main.py")) &&
|
|
379
379
|
fs.existsSync(path.resolve(root, "pyproject.toml"))
|
|
380
380
|
|
|
381
381
|
if (isInitialized && !args.force) {
|
|
@@ -430,12 +430,12 @@ export const bootstrap_fastapi = tool({
|
|
|
430
430
|
".dockerignore",
|
|
431
431
|
"docker-compose.yml",
|
|
432
432
|
"README.md",
|
|
433
|
-
"src/
|
|
434
|
-
"src/
|
|
435
|
-
"src/
|
|
436
|
-
"src/
|
|
437
|
-
"src/
|
|
438
|
-
"src/
|
|
433
|
+
"src/__init__.py",
|
|
434
|
+
"src/main.py",
|
|
435
|
+
"src/core/__init__.py",
|
|
436
|
+
"src/core/config.py",
|
|
437
|
+
"src/routers/__init__.py",
|
|
438
|
+
"src/schemas/__init__.py",
|
|
439
439
|
"src/tests/__init__.py",
|
|
440
440
|
"src/tests/conftest.py",
|
|
441
441
|
"src/tests/test_main.py",
|
|
@@ -522,7 +522,7 @@ export const bootstrap_fastapi = tool({
|
|
|
522
522
|
installDuration,
|
|
523
523
|
extrasInstalled,
|
|
524
524
|
finalPyprojectToml,
|
|
525
|
-
nextSteps: "Run `
|
|
525
|
+
nextSteps: "Run `uvicorn src.main:app --reload --port 8000` (o `python -m uvicorn src.main:app --reload --port 8000`) para arrancar el dev server.",
|
|
526
526
|
_bootstrapRecord: bootstrapRecord,
|
|
527
527
|
}, null, 2)
|
|
528
528
|
}
|
|
@@ -13,31 +13,79 @@ const getRoot = (context: any) => {
|
|
|
13
13
|
|
|
14
14
|
// Tool: sdd_clean_docker_environment
|
|
15
15
|
export const clean_docker_environment = tool({
|
|
16
|
-
description: "
|
|
16
|
+
description: "Asegura que Docker esté abierto y realiza una limpieza total y agresiva: detiene y elimina TODOS los contenedores (activos o inactivos), remueve TODAS las imágenes, volúmenes y redes para garantizar un lienzo en blanco absoluto antes de desplegar.",
|
|
17
17
|
args: {},
|
|
18
18
|
async execute(args, context) {
|
|
19
19
|
const results: Record<string, string> = {}
|
|
20
|
+
const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms))
|
|
21
|
+
|
|
22
|
+
// 1. Asegurar que Docker daemon esté listo
|
|
23
|
+
let dockerReady = false
|
|
24
|
+
for (let i = 0; i < 12; i++) {
|
|
25
|
+
try {
|
|
26
|
+
execSync("docker info", { stdio: "ignore", timeout: 3000 })
|
|
27
|
+
dockerReady = true
|
|
28
|
+
break
|
|
29
|
+
} catch (e) {
|
|
30
|
+
if (i === 0) {
|
|
31
|
+
try { execSync("open -a Docker", { stdio: "ignore" }) } catch (err) {}
|
|
32
|
+
}
|
|
33
|
+
await sleep(5000)
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (!dockerReady) {
|
|
38
|
+
return JSON.stringify({
|
|
39
|
+
status: "ERROR",
|
|
40
|
+
message: "No se pudo iniciar o conectar al demonio de Docker. Por favor, asegúrese de que Docker Desktop esté corriendo."
|
|
41
|
+
}, null, 2)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// 2. Detener y eliminar todos los contenedores de forma agresiva
|
|
20
45
|
try {
|
|
21
|
-
|
|
46
|
+
const containerIds = execSync("docker ps -aq", { encoding: "utf8" }).toString().trim()
|
|
47
|
+
if (containerIds) {
|
|
48
|
+
const ids = containerIds.split(/\s+/).join(" ")
|
|
49
|
+
execSync(`docker rm -f ${ids}`, { encoding: "utf8" })
|
|
50
|
+
results.containers = `Todos los contenedores detenidos y eliminados con éxito: ${ids}`
|
|
51
|
+
} else {
|
|
52
|
+
results.containers = "No se encontraron contenedores para eliminar."
|
|
53
|
+
}
|
|
54
|
+
} catch (e: any) {
|
|
55
|
+
results.containers = `Error eliminando contenedores: ${e.message}`
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// 3. Eliminar todas las imágenes de forma agresiva
|
|
59
|
+
try {
|
|
60
|
+
const imageIds = execSync("docker images -aq", { encoding: "utf8" }).toString().trim()
|
|
61
|
+
if (imageIds) {
|
|
62
|
+
const ids = imageIds.split(/\s+/).join(" ")
|
|
63
|
+
execSync(`docker rmi -f ${ids}`, { encoding: "utf8" })
|
|
64
|
+
results.images = `Todas las imágenes eliminadas con éxito: ${ids}`
|
|
65
|
+
} else {
|
|
66
|
+
results.images = "No se encontraron imágenes para eliminar."
|
|
67
|
+
}
|
|
22
68
|
} catch (e: any) {
|
|
23
|
-
results.
|
|
69
|
+
results.images = `Error eliminando imágenes: ${e.message}`
|
|
24
70
|
}
|
|
25
71
|
|
|
72
|
+
// 4. Eliminar volúmenes huérfanos
|
|
26
73
|
try {
|
|
27
|
-
results.
|
|
74
|
+
results.volumes = execSync("docker volume prune -af", { encoding: "utf8" }).toString().trim()
|
|
28
75
|
} catch (e: any) {
|
|
29
|
-
results.
|
|
76
|
+
results.volumes = `Error podando volúmenes: ${e.message}`
|
|
30
77
|
}
|
|
31
78
|
|
|
79
|
+
// 5. Eliminar redes inactivas
|
|
32
80
|
try {
|
|
33
81
|
results.networks = execSync("docker network prune -f", { encoding: "utf8" }).toString().trim()
|
|
34
82
|
} catch (e: any) {
|
|
35
|
-
results.networks = `Error: ${e.message}`
|
|
83
|
+
results.networks = `Error podando redes: ${e.message}`
|
|
36
84
|
}
|
|
37
85
|
|
|
38
86
|
return JSON.stringify({
|
|
39
87
|
status: "SUCCESS",
|
|
40
|
-
message: "
|
|
88
|
+
message: "Lienzo en blanco garantizado: Docker limpio al 100%.",
|
|
41
89
|
details: results
|
|
42
90
|
}, null, 2)
|
|
43
91
|
}
|
|
@@ -9,7 +9,7 @@ DB_PATH = "/Users/wavesbyte/.local/share/opencode/opencode.db"
|
|
|
9
9
|
|
|
10
10
|
# Escribe aquí el ID de la sesión que deseas exportar (ejemplo: "ses_1234...")
|
|
11
11
|
# Si se deja vacío, el script requerirá el ID como argumento al ejecutarlo
|
|
12
|
-
TARGET_SESSION_ID = "
|
|
12
|
+
TARGET_SESSION_ID = "ses_11e1"
|
|
13
13
|
|
|
14
14
|
|
|
15
15
|
def format_timestamp(ts):
|
package/bin/init.js
CHANGED
|
@@ -20,7 +20,7 @@ const pkgRoot = join(__dirname, '..');
|
|
|
20
20
|
const targetDir = process.cwd();
|
|
21
21
|
|
|
22
22
|
// Load dynamic version from package.json
|
|
23
|
-
let pkgVersion = '1.0.
|
|
23
|
+
let pkgVersion = '1.0.31';
|
|
24
24
|
try {
|
|
25
25
|
const pkgJsonPath = join(pkgRoot, 'package.json');
|
|
26
26
|
if (fs.existsSync(pkgJsonPath)) {
|
package/package.json
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|