# Jupyter Notebook Tools

Cinco funciones Python para operar notebooks Jupyter programaticamente via API REST y WebSocket colaborativo (CRDT/Y.js). Reemplazan al MCP jupyter y funcionan desde cualquier directorio.

## Flujo tipico

```
discover → read → write/exec
```

1. **Descubrir** que Jupyter esta corriendo y en que puerto
2. **Leer** celdas del notebook (estado en memoria, no disco)
3. **Escribir** celdas nuevas o **ejecutar** codigo

```bash
PYTHON="python/.venv/bin/python3"

# 1. Descubrir instancias activas
$PYTHON python/functions/notebook/jupyter_discover.py --json

# 2. Leer notebook
$PYTHON python/functions/notebook/jupyter_read.py notebooks/01.ipynb --json

# 3. Crear notebook nuevo (si no existe)
$PYTHON python/functions/notebook/jupyter_write.py create notebooks/02.ipynb

# 4. Escribir celdas en batch
$PYTHON python/functions/notebook/jupyter_write.py batch notebooks/02.ipynb --from cells.json

# 5. Ejecutar celda
$PYTHON python/functions/notebook/jupyter_exec.py cell notebooks/02.ipynb 0
```

## Funciones

### jupyter_discover

Descubre instancias de Jupyter Lab activas. Escanea `.jupyter-port` en `analysis/` y puertos 8888-8892. Detecta el `root_dir` real parseando `/proc/{pid}/cmdline`.

| Subcomando | Descripcion |
|---|---|
| *(sin subcomando)* | Lista instancias con puerto, analysis, root_dir, kernels, sesiones |

```bash
$PYTHON jupyter_discover.py --json
$PYTHON jupyter_discover.py --port 8888 --port 8889
```

Retorna por instancia: `url`, `port`, `analysis`, `root_dir`, `collaborative`, `kernels`, `sessions`.

### jupyter_read

Lee celdas de un notebook via protocolo CRDT (estado en memoria, incluye cambios no guardados).

| Subcomando | Descripcion |
|---|---|
| *(sin subcomando)* | Lee todas las celdas (formato legible) |
| `--cell N` | Lee solo la celda N |
| `--info` | Solo metadata (total celdas, conteo por tipo) |
| `--json` | Salida JSON |

```bash
$PYTHON jupyter_read.py notebooks/01.ipynb --json
$PYTHON jupyter_read.py notebooks/01.ipynb --cell 3
$PYTHON jupyter_read.py notebooks/01.ipynb --info
```

### jupyter_write

Operaciones de escritura sobre celdas via WebSocket colaborativo. NO ejecuta celdas.

| Subcomando | Descripcion |
|---|---|
| `create` | Crea un notebook .ipynb nuevo (API REST PUT) |
| `append-code` | Anade celda de codigo al final |
| `append-markdown` | Anade celda markdown al final |
| `insert` | Inserta celda en posicion especifica |
| `edit` | Sobrescribe contenido de celda existente |
| `delete` | Elimina una celda |
| `batch` | Escribe multiples celdas en una sola conexion WebSocket |

```bash
# Crear notebook
$PYTHON jupyter_write.py create notebooks/02.ipynb
$PYTHON jupyter_write.py create notebooks/02.ipynb --kernel python3 --force

# Celdas individuales
$PYTHON jupyter_write.py append-code notebooks/02.ipynb "import pandas as pd"
$PYTHON jupyter_write.py append-markdown notebooks/02.ipynb "## Titulo"
$PYTHON jupyter_write.py insert notebooks/02.ipynb 0 "x = 42" --type code
$PYTHON jupyter_write.py edit notebooks/02.ipynb 0 "# Titulo actualizado"
$PYTHON jupyter_write.py delete notebooks/02.ipynb 3

# Batch (una sola conexion para N celdas)
$PYTHON jupyter_write.py batch notebooks/02.ipynb --from cells.json
cat cells.json | $PYTHON jupyter_write.py batch notebooks/02.ipynb --from -
```

Formato `cells.json`:
```json
[
  {"type": "markdown", "source": "# Titulo"},
  {"type": "code", "source": "import pandas as pd"},
  {"type": "code", "source": "df = pd.read_csv('data.csv')"}
]
```

### jupyter_exec

Ejecuta codigo en kernels de Jupyter via WebSocket.

| Subcomando | Descripcion |
|---|---|
| `append` | Anade celda al notebook y la ejecuta |
| `cell` | Ejecuta celda existente por indice |
| `kernel` | Ejecuta en el kernel sin tocar notebook |

```bash
$PYTHON jupyter_exec.py append notebooks/01.ipynb "df.describe()"
$PYTHON jupyter_exec.py cell notebooks/01.ipynb 3
$PYTHON jupyter_exec.py kernel "print(df.shape)"
```

Normaliza automaticamente celdas sin `outputs` o `execution_count` (comun en notebooks creados programaticamente).

### jupyter_kernel

CRUD de kernels Jupyter via API REST.

| Subcomando | Descripcion |
|---|---|
| `list` | Lista kernels activos |
| `start` | Inicia kernel nuevo |
| `restart` | Reinicia kernel |
| `interrupt` | Interrumpe ejecucion |
| `shutdown` | Apaga y elimina kernel |
| `sessions` | Lista sesiones (mapeo notebook-kernel) |

```bash
$PYTHON jupyter_kernel.py list
$PYTHON jupyter_kernel.py start --name python3
$PYTHON jupyter_kernel.py restart <kernel_id>
$PYTHON jupyter_kernel.py shutdown <kernel_id>
$PYTHON jupyter_kernel.py sessions
```

## Troubleshooting

### WebSocket 4404 — notebook no existe

El servidor no puede abrir un documento CRDT para un archivo que no existe en disco.

**Solucion:** Crear el notebook primero con `jupyter_write.py create`.

```bash
$PYTHON jupyter_write.py create notebooks/nuevo.ipynb
$PYTHON jupyter_write.py append-code notebooks/nuevo.ipynb "print('hola')"
```

### KeyError 'outputs' — celda sin estructura completa

Notebooks creados manualmente (no via Jupyter UI) pueden tener celdas de codigo sin los campos `outputs` y `execution_count` que el protocolo CRDT requiere.

**Solucion:** `jupyter_exec.py` normaliza automaticamente estas celdas antes de ejecutar. Si el problema persiste, recrear la celda con `jupyter_write.py edit`.

### "Kernel does not exist" — sesion stale

El kernel referenciado ya no existe (fue apagado o el servidor se reinicio).

**Solucion:**
```bash
# Ver kernels activos
$PYTHON jupyter_kernel.py list
# Ver sesiones (mapeo notebook-kernel)
$PYTHON jupyter_kernel.py sessions
# Reiniciar kernel si necesario
$PYTHON jupyter_kernel.py restart <kernel_id>
```

### "Document not yet synced" — timing de colaboracion

El cliente WebSocket no pudo sincronizar el documento CRDT a tiempo.

**Solucion:** Reintentar. Si persiste, verificar que el servidor tiene `jupyter-collaboration` activo:
```bash
$PYTHON jupyter_discover.py --json | python3 -c "import sys,json; [print(i['collaborative']) for i in json.load(sys.stdin)]"
```

### Discover muestra analysis incorrecto

Versiones anteriores podian confundir instancias. La deteccion actual parsea `--ServerApp.root_dir` del cmdline del proceso via `/proc/{pid}/cmdline`.

**Solucion:** Actualizar a la version actual de `jupyter_discover.py`.

## Parametros comunes

Todos los subcomandos aceptan:

| Flag | Default | Descripcion |
|---|---|---|
| `--server` | `http://localhost:8888` | URL del servidor Jupyter |
| `--token` | `""` (vacio) | Token de autenticacion |

Los paths de notebooks son siempre **relativos a la raiz del servidor Jupyter** (normalmente `analysis/{tema}/`).

## Dependencias

| Paquete | Usado por | Para que |
|---|---|---|
| `jupyter_nbmodel_client` | write, exec, read | WebSocket colaborativo (CRDT/Y.js) |
| `jupyter_kernel_client` | exec | Ejecucion de codigo en kernels |
| stdlib (`urllib`, `json`) | discover, kernel, write (create) | API REST |

## Diferencias con MCP jupyter

| Aspecto | MCP jupyter | Estas funciones |
|---|---|---|
| Requiere registro | Si (.mcp.json) | No |
| Funciona desde cualquier dir | No (solo desde el dir del MCP) | Si |
| Protocolo | MCP sobre stdio | HTTP REST + WebSocket directo |
| Crear notebooks | No | Si (`write create`) |
| Batch de celdas | No | Si (`write batch`) |
| Multi-instancia | No | Si (discover detecta todas) |

## Limitaciones

- Requiere Jupyter Lab >= 4 con `jupyter-collaboration` para las funciones que usan WebSocket (write, exec, read)
- Las funciones REST (discover, kernel) funcionan con cualquier Jupyter Lab 3.x/4.x y Notebook 6.x/7.x
- La deteccion de root_dir via `/proc` solo funciona en Linux
- No soportan autenticacion por cookie, solo por token
- `jupyter_write batch` requiere JSON como entrada (no YAML)
