feat: externalize apps/analysis to Gitea repos, add analysis table
- Migration 007: repo_url on apps table + analysis table with FTS5 - Analysis struct, parser, CRUD, validation, hash computation - Selective purge: remote-only apps/analysis preserved across fn index - CLI: fn app list/clone/pull, fn analysis list/clone/pull - search/show/list now include analysis results - Apps removed from git tracking (content lives in Gitea repos) - .gitkeep for apps/ and analysis/ dirs - Bash functions: jupyter analysis pipeline, shell utilities - Browser domain: CDP functions moved from infra to browser Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,36 @@
|
||||
---
|
||||
name: init_uv_venv
|
||||
kind: function
|
||||
lang: bash
|
||||
domain: infra
|
||||
version: "1.0.0"
|
||||
purity: impure
|
||||
signature: "init_uv_venv([project_dir: string]) -> string"
|
||||
description: "Crea un virtualenv Python con uv en el directorio dado si no existe. Fallback a python3 -m venv. Retorna la ruta del venv."
|
||||
tags: [python, venv, uv, setup, infra]
|
||||
uses_functions: []
|
||||
uses_types: []
|
||||
returns: []
|
||||
returns_optional: false
|
||||
error_type: "error_go_core"
|
||||
imports: []
|
||||
tested: false
|
||||
tests: []
|
||||
test_file_path: ""
|
||||
file_path: "bash/functions/infra/init_uv_venv.sh"
|
||||
---
|
||||
|
||||
## Ejemplo
|
||||
|
||||
```bash
|
||||
source init_uv_venv.sh
|
||||
venv=$(init_uv_venv /home/lucas/analysis/finanzas)
|
||||
echo "Venv creado en: $venv"
|
||||
|
||||
# Idempotente — si ya existe, retorna la ruta sin recrear
|
||||
venv=$(init_uv_venv .)
|
||||
```
|
||||
|
||||
## Notas
|
||||
|
||||
Idempotente: si el venv ya existe con un python valido, retorna la ruta sin hacer nada. Prefiere uv por velocidad, usa python3 como fallback.
|
||||
@@ -0,0 +1,35 @@
|
||||
# init_uv_venv
|
||||
# -------------
|
||||
# Crea un venv con uv en el directorio especificado si no existe.
|
||||
# Fallback a python -m venv si uv no esta disponible.
|
||||
# Imprime la ruta del venv a stdout.
|
||||
#
|
||||
# USO (sourced):
|
||||
# source init_uv_venv.sh
|
||||
# venv_path=$(init_uv_venv /path/to/project)
|
||||
|
||||
init_uv_venv() {
|
||||
local project_dir="${1:-.}"
|
||||
local venv_path="${project_dir}/.venv"
|
||||
|
||||
if [ -d "$venv_path" ] && [ -f "$venv_path/bin/python" ]; then
|
||||
echo "$venv_path"
|
||||
return 0
|
||||
fi
|
||||
|
||||
if command -v uv &>/dev/null; then
|
||||
(cd "$project_dir" && uv venv) >/dev/null 2>&1
|
||||
elif command -v python3 &>/dev/null; then
|
||||
python3 -m venv "$venv_path"
|
||||
else
|
||||
echo "init_uv_venv: ni uv ni python3 disponibles" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
if [ ! -f "$venv_path/bin/python" ]; then
|
||||
echo "init_uv_venv: fallo al crear venv en $venv_path" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
echo "$venv_path"
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
---
|
||||
name: uv_add_packages
|
||||
kind: function
|
||||
lang: bash
|
||||
domain: infra
|
||||
version: "1.0.0"
|
||||
purity: impure
|
||||
signature: "uv_add_packages(project_dir: string, ...packages: string) -> void"
|
||||
description: "Instala paquetes Python en un proyecto usando uv add con fallback a pip. Inicializa pyproject.toml si no existe."
|
||||
tags: [python, uv, pip, packages, infra]
|
||||
uses_functions: []
|
||||
uses_types: []
|
||||
returns: []
|
||||
returns_optional: false
|
||||
error_type: "error_go_core"
|
||||
imports: []
|
||||
tested: false
|
||||
tests: []
|
||||
test_file_path: ""
|
||||
file_path: "bash/functions/infra/uv_add_packages.sh"
|
||||
---
|
||||
|
||||
## Ejemplo
|
||||
|
||||
```bash
|
||||
source uv_add_packages.sh
|
||||
uv_add_packages /home/lucas/analysis/finanzas jupyter jupyterlab pandas numpy
|
||||
|
||||
# Solo un paquete
|
||||
uv_add_packages . polars
|
||||
```
|
||||
|
||||
## Notas
|
||||
|
||||
Requiere que el venv ya exista (usa `init_uv_venv` antes). Prefiere uv por velocidad y reproducibilidad (lockfile). Si uv no esta disponible, usa pip del venv directamente.
|
||||
@@ -0,0 +1,35 @@
|
||||
# uv_add_packages
|
||||
# -----------------
|
||||
# Instala paquetes Python en un proyecto con uv add.
|
||||
# Inicializa pyproject.toml si no existe.
|
||||
# Fallback a pip install si uv no esta disponible.
|
||||
#
|
||||
# USO (sourced):
|
||||
# source uv_add_packages.sh
|
||||
# uv_add_packages /path/to/project jupyter jupyterlab pandas
|
||||
|
||||
uv_add_packages() {
|
||||
local project_dir="$1"
|
||||
shift
|
||||
local packages=("$@")
|
||||
|
||||
if [ ${#packages[@]} -eq 0 ]; then
|
||||
echo "uv_add_packages: se requiere al menos un paquete" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
if [ ! -d "$project_dir/.venv" ]; then
|
||||
echo "uv_add_packages: no existe .venv en $project_dir — ejecuta init_uv_venv primero" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
if command -v uv &>/dev/null; then
|
||||
# Inicializar pyproject.toml si no existe
|
||||
if [ ! -f "$project_dir/pyproject.toml" ]; then
|
||||
(cd "$project_dir" && uv init 2>/dev/null) || true
|
||||
fi
|
||||
(cd "$project_dir" && uv add "${packages[@]}" 2>&1)
|
||||
else
|
||||
"$project_dir/.venv/bin/pip" install "${packages[@]}" 2>&1
|
||||
fi
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
---
|
||||
name: write_claude_jupyter_rules
|
||||
kind: function
|
||||
lang: bash
|
||||
domain: infra
|
||||
version: "1.0.0"
|
||||
purity: impure
|
||||
signature: "write_claude_jupyter_rules([project_dir: string]) -> string"
|
||||
description: "Genera o actualiza .claude/CLAUDE.md con reglas para agentes que trabajan con Jupyter: celdas inmutables, programacion funcional, uso de MCP, acceso al fn_registry."
|
||||
tags: [claude, jupyter, rules, setup, infra]
|
||||
uses_functions: []
|
||||
uses_types: []
|
||||
returns: []
|
||||
returns_optional: false
|
||||
error_type: "error_go_core"
|
||||
imports: []
|
||||
tested: false
|
||||
tests: []
|
||||
test_file_path: ""
|
||||
file_path: "bash/functions/infra/write_claude_jupyter_rules.sh"
|
||||
---
|
||||
|
||||
## Ejemplo
|
||||
|
||||
```bash
|
||||
source write_claude_jupyter_rules.sh
|
||||
path=$(write_claude_jupyter_rules /home/lucas/analysis/finanzas)
|
||||
echo "Reglas escritas en: $path"
|
||||
```
|
||||
|
||||
## Notas
|
||||
|
||||
Idempotente: si CLAUDE.md ya contiene las reglas (detecta "JUPYTER HABILITADO"), no las duplica. Si CLAUDE.md existe sin las reglas, las prepend al contenido existente. Incluye instrucciones de acceso al fn_registry via `FN_REGISTRY_ROOT`.
|
||||
@@ -0,0 +1,74 @@
|
||||
# write_claude_jupyter_rules
|
||||
# ----------------------------
|
||||
# Genera .claude/CLAUDE.md con reglas para agentes que trabajan con Jupyter.
|
||||
# Si ya existe CLAUDE.md y no tiene las reglas, las prepend.
|
||||
#
|
||||
# USO (sourced):
|
||||
# source write_claude_jupyter_rules.sh
|
||||
# write_claude_jupyter_rules /path/to/project
|
||||
|
||||
write_claude_jupyter_rules() {
|
||||
local project_dir="${1:-.}"
|
||||
local claude_dir="${project_dir}/.claude"
|
||||
local claude_md="${claude_dir}/CLAUDE.md"
|
||||
|
||||
mkdir -p "$claude_dir"
|
||||
|
||||
# Si ya tiene las reglas, no hacer nada
|
||||
if [ -f "$claude_md" ] && grep -q "JUPYTER HABILITADO" "$claude_md" 2>/dev/null; then
|
||||
echo "$claude_md"
|
||||
return 0
|
||||
fi
|
||||
|
||||
local rules
|
||||
rules='# JUPYTER HABILITADO EN ESTE ANALISIS
|
||||
|
||||
## Reglas OBLIGATORIAS para Claude
|
||||
|
||||
### 1. CODIGO INMUTABLE — NUNCA MODIFICAR CELDAS EXISTENTES
|
||||
- **PROHIBIDO** usar NotebookEdit para reemplazar celdas existentes
|
||||
- **SIEMPRE** anadir celdas NUEVAS al final del notebook
|
||||
- Si hay un error en una celda, crear celda nueva con la correccion
|
||||
- El historial de trabajo debe quedar intacto para trazabilidad
|
||||
|
||||
### 2. PROGRAMACION FUNCIONAL OBLIGATORIA
|
||||
- **Funciones puras**: sin efectos secundarios, mismo input -> mismo output
|
||||
- **Inmutabilidad**: nunca mutar datos, crear copias transformadas
|
||||
- **Composicion**: funciones pequenas que se combinan
|
||||
- Preferir: `map`, `filter`, `reduce`, list comprehensions
|
||||
- Evitar: loops con mutacion, `global`, modificar argumentos in-place
|
||||
|
||||
### 3. SIEMPRE usar MCP jupyter para ejecutar codigo Python
|
||||
- Las ejecuciones se ven en tiempo real en Jupyter Lab del usuario
|
||||
- Compartimos variables y estado del kernel
|
||||
- **NUNCA usar bash para ejecutar Python en este analisis**
|
||||
|
||||
### 4. Verificar Jupyter activo ANTES de ejecutar
|
||||
- Si no esta activo: pedir al usuario que ejecute `./run-jupyter-lab.sh`
|
||||
|
||||
### 5. Gestion de notebooks
|
||||
- Notebooks en la carpeta `notebooks/` o subcarpetas
|
||||
- Si un notebook tiene >50 celdas, crear uno nuevo
|
||||
- Nombrar descriptivamente: `01_exploracion.ipynb`, `02_limpieza.ipynb`
|
||||
|
||||
### 6. Gestion de Python
|
||||
- **SIEMPRE usar `uv`** para gestionar dependencias
|
||||
- Anadir paquetes con `uv add nombre_paquete`
|
||||
|
||||
### 7. Acceso al fn_registry
|
||||
- `FN_REGISTRY_ROOT` apunta a la raiz del registry
|
||||
- Para importar funciones Python: `sys.path.insert(0, os.path.join(os.environ["FN_REGISTRY_ROOT"], "python", "functions"))`
|
||||
- Para consultar registry.db: `sqlite3` o `import sqlite3` con la ruta `$FN_REGISTRY_ROOT/registry.db`
|
||||
|
||||
'
|
||||
|
||||
if [ -f "$claude_md" ]; then
|
||||
local existing
|
||||
existing=$(cat "$claude_md")
|
||||
printf '%s\n%s' "$rules" "$existing" > "$claude_md"
|
||||
else
|
||||
echo "$rules" > "$claude_md"
|
||||
fi
|
||||
|
||||
echo "$claude_md"
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
---
|
||||
name: write_jupyter_launcher
|
||||
kind: function
|
||||
lang: bash
|
||||
domain: infra
|
||||
version: "1.0.0"
|
||||
purity: impure
|
||||
signature: "write_jupyter_launcher([project_dir: string]) -> string"
|
||||
description: "Genera un script run-jupyter-lab.sh que lanza Jupyter Lab en modo colaborativo con autodeteccion de puerto y token deshabilitado."
|
||||
tags: [jupyter, launcher, setup, infra]
|
||||
uses_functions: []
|
||||
uses_types: []
|
||||
returns: []
|
||||
returns_optional: false
|
||||
error_type: "error_go_core"
|
||||
imports: []
|
||||
tested: false
|
||||
tests: []
|
||||
test_file_path: ""
|
||||
file_path: "bash/functions/infra/write_jupyter_launcher.sh"
|
||||
---
|
||||
|
||||
## Ejemplo
|
||||
|
||||
```bash
|
||||
source write_jupyter_launcher.sh
|
||||
path=$(write_jupyter_launcher /home/lucas/analysis/finanzas)
|
||||
echo "Launcher creado en: $path"
|
||||
|
||||
# Luego en otra terminal:
|
||||
# ./run-jupyter-lab.sh [puerto]
|
||||
```
|
||||
|
||||
## Notas
|
||||
|
||||
El launcher generado:
|
||||
- Autodetecta un puerto libre (8888-8899)
|
||||
- Guarda el puerto en `.jupyter-port` para que otros procesos lo lean
|
||||
- Activa el venv automaticamente
|
||||
- Lanza Jupyter Lab en modo `--collaborative` (requiere jupyter-collaboration)
|
||||
- Token y password deshabilitados para acceso local
|
||||
@@ -0,0 +1,65 @@
|
||||
# write_jupyter_launcher
|
||||
# -----------------------
|
||||
# Genera un script run-jupyter-lab.sh en el directorio dado.
|
||||
# El script lanza Jupyter Lab en modo colaborativo con autodeteccion de puerto.
|
||||
#
|
||||
# USO (sourced):
|
||||
# source write_jupyter_launcher.sh
|
||||
# write_jupyter_launcher /path/to/project
|
||||
|
||||
write_jupyter_launcher() {
|
||||
local project_dir="${1:-.}"
|
||||
local launcher="${project_dir}/run-jupyter-lab.sh"
|
||||
|
||||
cat > "$launcher" << 'LAUNCHER'
|
||||
#!/bin/bash
|
||||
# Jupyter Lab — modo colaborativo con autodeteccion de puerto
|
||||
# Generado por write_jupyter_launcher (fn_registry)
|
||||
|
||||
find_free_port() {
|
||||
for port in 8888 8889 8890 8891 8892 8893 8894 8895 8896 8897 8898 8899; do
|
||||
if ! ss -tln 2>/dev/null | grep -q ":${port} " && \
|
||||
! lsof -i:"$port" >/dev/null 2>&1; then
|
||||
echo $port
|
||||
return
|
||||
fi
|
||||
done
|
||||
echo 8888
|
||||
}
|
||||
|
||||
PORT=${1:-$(find_free_port)}
|
||||
cd "$(dirname "$0")"
|
||||
|
||||
echo $PORT > .jupyter-port
|
||||
|
||||
source .venv/bin/activate 2>/dev/null || true
|
||||
|
||||
if ! python -c "import jupyter_collaboration" 2>/dev/null; then
|
||||
echo "ERROR: jupyter-collaboration no esta instalado"
|
||||
echo "Instala con: uv add jupyter-collaboration"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "════════════════════════════════════════════════"
|
||||
echo " Jupyter Lab + Colaboracion en puerto $PORT"
|
||||
echo "════════════════════════════════════════════════"
|
||||
echo ""
|
||||
echo " Abre: http://localhost:$PORT"
|
||||
echo " Ctrl+C para detener"
|
||||
echo ""
|
||||
|
||||
jupyter lab \
|
||||
--port=$PORT \
|
||||
--no-browser \
|
||||
--ServerApp.token='' \
|
||||
--ServerApp.password='' \
|
||||
--ServerApp.disable_check_xsrf=True \
|
||||
--ServerApp.allow_origin='*' \
|
||||
--ServerApp.root_dir="$(pwd)" \
|
||||
--YDocExtension.ystore_class='ypy_websocket.ystore.TempFileYStore' \
|
||||
--collaborative
|
||||
LAUNCHER
|
||||
|
||||
chmod +x "$launcher"
|
||||
echo "$launcher"
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
---
|
||||
name: write_jupyter_registry_kernel
|
||||
kind: function
|
||||
lang: bash
|
||||
domain: infra
|
||||
version: "1.0.0"
|
||||
purity: impure
|
||||
signature: "write_jupyter_registry_kernel([project_dir: string]) -> string"
|
||||
description: "Genera un script de startup de IPython que autoconfigura FN_REGISTRY_ROOT, sys.path a python/functions del registry, y helpers fn_query/fn_search/fn_code para consultar registry.db desde notebooks."
|
||||
tags: [jupyter, ipython, kernel, registry, setup, infra]
|
||||
uses_functions: []
|
||||
uses_types: []
|
||||
returns: []
|
||||
returns_optional: false
|
||||
error_type: "error_go_core"
|
||||
imports: []
|
||||
tested: false
|
||||
tests: []
|
||||
test_file_path: ""
|
||||
file_path: "bash/functions/infra/write_jupyter_registry_kernel.sh"
|
||||
---
|
||||
|
||||
## Ejemplo
|
||||
|
||||
```bash
|
||||
source write_jupyter_registry_kernel.sh
|
||||
path=$(write_jupyter_registry_kernel /home/lucas/analysis/finanzas)
|
||||
echo "Startup creado en: $path"
|
||||
```
|
||||
|
||||
Luego en cualquier notebook del proyecto:
|
||||
|
||||
```python
|
||||
# Ya disponible automaticamente al abrir el notebook:
|
||||
|
||||
# Buscar funciones
|
||||
fn_search("finance")
|
||||
|
||||
# Consultar registry.db directamente
|
||||
fn_query("SELECT id, signature FROM functions WHERE domain = ?", ("core",))
|
||||
|
||||
# Ver codigo de una funcion
|
||||
print(fn_code("filter_list_py_core"))
|
||||
|
||||
# Importar funciones Python del registry directamente
|
||||
from core import filter_list, map_list
|
||||
from finance import sma, ema, rsi
|
||||
```
|
||||
|
||||
## Notas
|
||||
|
||||
Genera `.ipython/profile_default/startup/00_fn_registry.py` que se ejecuta automaticamente al iniciar cualquier kernel IPython en el proyecto. No requiere imports manuales — las funciones `fn_query`, `fn_search` y `fn_code` estan disponibles inmediatamente en cada notebook.
|
||||
|
||||
El `sys.path` se configura para que cada dominio de `python/functions/` sea importable directamente (`from core import filter_list`).
|
||||
|
||||
Detecta `FN_REGISTRY_ROOT` buscando la raiz del repo git o subiendo directorios hasta encontrar `registry.db`.
|
||||
@@ -0,0 +1,111 @@
|
||||
# write_jupyter_registry_kernel
|
||||
# -------------------------------
|
||||
# Genera un script de startup de IPython que autoconfigura el acceso
|
||||
# al fn_registry en cada notebook: FN_REGISTRY_ROOT, sys.path a
|
||||
# python/functions, y un helper fn_query() para consultar registry.db.
|
||||
#
|
||||
# USO (sourced):
|
||||
# source write_jupyter_registry_kernel.sh
|
||||
# write_jupyter_registry_kernel /path/to/project
|
||||
|
||||
write_jupyter_registry_kernel() {
|
||||
local project_dir="${1:-.}"
|
||||
local startup_dir="${project_dir}/.ipython/profile_default/startup"
|
||||
local registry_root
|
||||
registry_root="$(cd "$project_dir" && cd "$(git -C "$project_dir" rev-parse --show-toplevel 2>/dev/null || echo "../..")" && pwd)"
|
||||
|
||||
# Fallback: si no es git, buscar registry.db subiendo directorios
|
||||
if [ ! -f "$registry_root/registry.db" ] && [ -f "$project_dir/../../registry.db" ]; then
|
||||
registry_root="$(cd "$project_dir/../.." && pwd)"
|
||||
fi
|
||||
|
||||
mkdir -p "$startup_dir"
|
||||
|
||||
cat > "${startup_dir}/00_fn_registry.py" << STARTUP
|
||||
"""
|
||||
fn_registry kernel startup
|
||||
Autoconfigura acceso al registry en cada notebook.
|
||||
Generado por write_jupyter_registry_kernel (fn_registry).
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
import sqlite3
|
||||
from pathlib import Path
|
||||
|
||||
# ── FN_REGISTRY_ROOT ────────────────────────────────────────
|
||||
FN_REGISTRY_ROOT = Path("${registry_root}")
|
||||
os.environ["FN_REGISTRY_ROOT"] = str(FN_REGISTRY_ROOT)
|
||||
|
||||
# ── sys.path: importar funciones Python del registry ────────
|
||||
_python_functions = FN_REGISTRY_ROOT / "python" / "functions"
|
||||
for _domain in sorted(_python_functions.iterdir()) if _python_functions.exists() else []:
|
||||
if _domain.is_dir() and not _domain.name.startswith("_"):
|
||||
_path = str(_domain)
|
||||
if _path not in sys.path:
|
||||
sys.path.insert(0, _path)
|
||||
|
||||
# Tambien el directorio padre para imports por dominio: from core import filter_list
|
||||
_pf = str(_python_functions)
|
||||
if _pf not in sys.path:
|
||||
sys.path.insert(0, _pf)
|
||||
|
||||
# ── fn_query: consultar registry.db desde el notebook ───────
|
||||
_REGISTRY_DB = FN_REGISTRY_ROOT / "registry.db"
|
||||
|
||||
def fn_query(sql, params=()):
|
||||
"""Ejecuta una consulta SQL sobre registry.db y retorna las filas.
|
||||
|
||||
Ejemplos:
|
||||
fn_query("SELECT id, description FROM functions WHERE domain = ?", ("finance",))
|
||||
fn_query("SELECT id FROM functions_fts WHERE functions_fts MATCH ?", ("slice*",))
|
||||
"""
|
||||
if not _REGISTRY_DB.exists():
|
||||
raise FileNotFoundError(f"registry.db no encontrado en {_REGISTRY_DB}")
|
||||
con = sqlite3.connect(str(_REGISTRY_DB))
|
||||
con.row_factory = sqlite3.Row
|
||||
try:
|
||||
rows = con.execute(sql, params).fetchall()
|
||||
return [dict(r) for r in rows]
|
||||
finally:
|
||||
con.close()
|
||||
|
||||
def fn_search(term):
|
||||
"""Busca funciones y tipos en el registry por nombre o descripcion.
|
||||
|
||||
Ejemplo:
|
||||
fn_search("slice")
|
||||
fn_search("finance")
|
||||
"""
|
||||
fts_term = f"name:{term}* OR description:{term}*"
|
||||
functions = fn_query(
|
||||
"SELECT id, kind, purity, lang, description FROM functions "
|
||||
"WHERE id IN (SELECT id FROM functions_fts WHERE functions_fts MATCH ?) "
|
||||
"ORDER BY name", (fts_term,)
|
||||
)
|
||||
types = fn_query(
|
||||
"SELECT id, algebraic, lang, description FROM types "
|
||||
"WHERE id IN (SELECT id FROM types_fts WHERE types_fts MATCH ?) "
|
||||
"ORDER BY name", (fts_term,)
|
||||
)
|
||||
return {"functions": functions, "types": types}
|
||||
|
||||
def fn_code(function_id):
|
||||
"""Retorna el codigo fuente de una funcion del registry.
|
||||
|
||||
Ejemplo:
|
||||
print(fn_code("filter_list_py_core"))
|
||||
"""
|
||||
rows = fn_query("SELECT code FROM functions WHERE id = ?", (function_id,))
|
||||
if not rows:
|
||||
raise KeyError(f"Funcion no encontrada: {function_id}")
|
||||
return rows[0]["code"]
|
||||
|
||||
# ── Mensaje de bienvenida ───────────────────────────────────
|
||||
print(f"fn_registry conectado: {FN_REGISTRY_ROOT}")
|
||||
print(f" registry.db: {'OK' if _REGISTRY_DB.exists() else 'NO ENCONTRADO'}")
|
||||
print(f" Python functions: {_pf}")
|
||||
print(f" Helpers: fn_query(), fn_search(), fn_code()")
|
||||
STARTUP
|
||||
|
||||
echo "${startup_dir}/00_fn_registry.py"
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
---
|
||||
name: write_mcp_jupyter_config
|
||||
kind: function
|
||||
lang: bash
|
||||
domain: infra
|
||||
version: "1.0.0"
|
||||
purity: impure
|
||||
signature: "write_mcp_jupyter_config([project_dir: string], [port: int]) -> string"
|
||||
description: "Genera o actualiza .mcp.json con la configuracion de jupyter-mcp-server apuntando al venv local y puerto dado. Merge con jq si ya existe."
|
||||
tags: [mcp, jupyter, config, setup, infra]
|
||||
uses_functions: []
|
||||
uses_types: []
|
||||
returns: []
|
||||
returns_optional: false
|
||||
error_type: "error_go_core"
|
||||
imports: []
|
||||
tested: false
|
||||
tests: []
|
||||
test_file_path: ""
|
||||
file_path: "bash/functions/infra/write_mcp_jupyter_config.sh"
|
||||
---
|
||||
|
||||
## Ejemplo
|
||||
|
||||
```bash
|
||||
source write_mcp_jupyter_config.sh
|
||||
path=$(write_mcp_jupyter_config /home/lucas/analysis/finanzas 8890)
|
||||
echo "Config MCP en: $path"
|
||||
```
|
||||
|
||||
## Notas
|
||||
|
||||
El MCP se invoca como modulo Python (`python -m jupyter_mcp_server`) usando el python del venv local, nunca una instalacion global. Si `.mcp.json` ya existe y jq esta disponible, hace merge conservando otros servidores MCP. Sin jq, sobrescribe el archivo.
|
||||
@@ -0,0 +1,57 @@
|
||||
# write_mcp_jupyter_config
|
||||
# -------------------------
|
||||
# Genera o actualiza .mcp.json con la configuracion de jupyter-mcp-server.
|
||||
# Usa el python del venv local con -m jupyter_mcp_server.
|
||||
# Hace merge si ya existe .mcp.json (requiere jq).
|
||||
#
|
||||
# USO (sourced):
|
||||
# source write_mcp_jupyter_config.sh
|
||||
# write_mcp_jupyter_config /path/to/project 8888
|
||||
|
||||
write_mcp_jupyter_config() {
|
||||
local project_dir="${1:-.}"
|
||||
local port="${2:-8888}"
|
||||
local mcp_file="${project_dir}/.mcp.json"
|
||||
local abs_project
|
||||
abs_project="$(cd "$project_dir" && pwd)"
|
||||
|
||||
local python_bin="${abs_project}/.venv/bin/python"
|
||||
if [ ! -f "$python_bin" ]; then
|
||||
echo "write_mcp_jupyter_config: python no encontrado en ${python_bin}" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Verificar que el modulo esta instalado
|
||||
if ! "$python_bin" -c "import jupyter_mcp_server" 2>/dev/null; then
|
||||
echo "write_mcp_jupyter_config: jupyter_mcp_server no instalado en el venv" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
local new_config
|
||||
new_config=$(cat << EOF
|
||||
{
|
||||
"mcpServers": {
|
||||
"jupyter": {
|
||||
"command": "${python_bin}",
|
||||
"args": [
|
||||
"-m", "jupyter_mcp_server",
|
||||
"--runtime-url", "http://localhost:${port}",
|
||||
"--start-new-runtime", "false"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
EOF
|
||||
)
|
||||
|
||||
if [ -f "$mcp_file" ] && command -v jq &>/dev/null; then
|
||||
# Merge conservando otros servidores MCP
|
||||
jq -s '.[0] * {mcpServers: ((.[0].mcpServers // {}) * (.[1].mcpServers // {}))}' \
|
||||
"$mcp_file" <(echo "$new_config") > "${mcp_file}.tmp"
|
||||
mv "${mcp_file}.tmp" "$mcp_file"
|
||||
else
|
||||
echo "$new_config" > "$mcp_file"
|
||||
fi
|
||||
|
||||
echo "$mcp_file"
|
||||
}
|
||||
Reference in New Issue
Block a user