zerochat 7.4.0__tar.gz → 7.5.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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: zerochat
3
- Version: 7.4.0
3
+ Version: 7.5.0
4
4
  Summary: Private local web client for AI models, tools and MCP services
5
5
  Author: Alberto Balday
6
6
  License-Expression: MIT
@@ -41,13 +41,15 @@ Funciona de forma estática en el navegador (servido por GitHub Pages o directam
41
41
  ```bash
42
42
  python3 zerochat.py
43
43
  ```
44
- Inicia el asistente local, crea su entorno virtual de forma desatendida y abre la interfaz web en tu navegador.
44
+ Inicia el asistente local, crea `./zerochat/.venv` para los MCP y abre la interfaz web en tu navegador.
45
45
  - **Instalación con PyPI**:
46
46
  ```bash
47
47
  pip install zerochat
48
48
  zerochat
49
49
  ```
50
- Es la vía recomendada para instalar el servidor local desde PyPI. La interfaz se carga desde GitHub Pages, igual que al descargar y ejecutar `zerochat.py` directamente.
50
+ Es la vía recomendada para instalar el servidor local desde PyPI. Igual que con la descarga directa, crea `./zerochat/` para el estado y los MCP; la interfaz se carga desde GitHub Pages.
51
+
52
+ En ambos modos, `./zerochat/` contiene configuración, token, servicios MCP y sus dependencias Python en `.venv/`. Borrar ese directorio elimina el estado local y los MCP, sin desinstalar el ejecutable de PyPI ni el archivo descargado.
51
53
 
52
54
  Las publicaciones se realizan desde el repositorio oficial mediante GitHub Actions y PyPI Trusted Publishing.
53
55
 
@@ -19,13 +19,15 @@ Funciona de forma estática en el navegador (servido por GitHub Pages o directam
19
19
  ```bash
20
20
  python3 zerochat.py
21
21
  ```
22
- Inicia el asistente local, crea su entorno virtual de forma desatendida y abre la interfaz web en tu navegador.
22
+ Inicia el asistente local, crea `./zerochat/.venv` para los MCP y abre la interfaz web en tu navegador.
23
23
  - **Instalación con PyPI**:
24
24
  ```bash
25
25
  pip install zerochat
26
26
  zerochat
27
27
  ```
28
- Es la vía recomendada para instalar el servidor local desde PyPI. La interfaz se carga desde GitHub Pages, igual que al descargar y ejecutar `zerochat.py` directamente.
28
+ Es la vía recomendada para instalar el servidor local desde PyPI. Igual que con la descarga directa, crea `./zerochat/` para el estado y los MCP; la interfaz se carga desde GitHub Pages.
29
+
30
+ En ambos modos, `./zerochat/` contiene configuración, token, servicios MCP y sus dependencias Python en `.venv/`. Borrar ese directorio elimina el estado local y los MCP, sin desinstalar el ejecutable de PyPI ni el archivo descargado.
29
31
 
30
32
  Las publicaciones se realizan desde el repositorio oficial mediante GitHub Actions y PyPI Trusted Publishing.
31
33
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "zerochat"
7
- version = "7.4.0"
7
+ version = "7.5.0"
8
8
  description = "Private local web client for AI models, tools and MCP services"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: zerochat
3
- Version: 7.4.0
3
+ Version: 7.5.0
4
4
  Summary: Private local web client for AI models, tools and MCP services
5
5
  Author: Alberto Balday
6
6
  License-Expression: MIT
@@ -41,13 +41,15 @@ Funciona de forma estática en el navegador (servido por GitHub Pages o directam
41
41
  ```bash
42
42
  python3 zerochat.py
43
43
  ```
44
- Inicia el asistente local, crea su entorno virtual de forma desatendida y abre la interfaz web en tu navegador.
44
+ Inicia el asistente local, crea `./zerochat/.venv` para los MCP y abre la interfaz web en tu navegador.
45
45
  - **Instalación con PyPI**:
46
46
  ```bash
47
47
  pip install zerochat
48
48
  zerochat
49
49
  ```
50
- Es la vía recomendada para instalar el servidor local desde PyPI. La interfaz se carga desde GitHub Pages, igual que al descargar y ejecutar `zerochat.py` directamente.
50
+ Es la vía recomendada para instalar el servidor local desde PyPI. Igual que con la descarga directa, crea `./zerochat/` para el estado y los MCP; la interfaz se carga desde GitHub Pages.
51
+
52
+ En ambos modos, `./zerochat/` contiene configuración, token, servicios MCP y sus dependencias Python en `.venv/`. Borrar ese directorio elimina el estado local y los MCP, sin desinstalar el ejecutable de PyPI ni el archivo descargado.
51
53
 
52
54
  Las publicaciones se realizan desde el repositorio oficial mediante GitHub Actions y PyPI Trusted Publishing.
53
55
 
@@ -37,6 +37,8 @@ import webbrowser
37
37
  from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
38
38
  from pathlib import Path
39
39
 
40
+ SOURCE_BACKEND_VERSION = "7.5.0"
41
+
40
42
  def _read_source_version(filename: str) -> str | None:
41
43
  """Lee la versión de un archivo del repositorio cuando se ejecuta desde fuentes."""
42
44
  try:
@@ -53,10 +55,13 @@ def _read_source_version(filename: str) -> str | None:
53
55
 
54
56
  def _read_backend_version() -> str:
55
57
  """Devuelve la versión publicada del backend, sin depender de package.json instalado."""
58
+ source_version = _read_source_version("pyproject.toml")
59
+ if source_version:
60
+ return source_version
56
61
  try:
57
62
  return importlib.metadata.version("zerochat")
58
63
  except importlib.metadata.PackageNotFoundError:
59
- return _read_source_version("pyproject.toml") or "7.3.0"
64
+ return SOURCE_BACKEND_VERSION
60
65
 
61
66
 
62
67
  def _read_ui_version() -> str:
@@ -84,6 +89,7 @@ DEFAULT_PORT = 6388
84
89
  DEFAULT_HOST = "127.0.0.1"
85
90
  DEFAULT_UI_URL = "https://albalday.github.io/zerochat/zerochat.html"
86
91
  REMOTE_VERSION_URL = "https://raw.githubusercontent.com/albalday/zerochat/master/package.json"
92
+ PYPI_VERSION_URL = "https://pypi.org/pypi/zerochat/json"
87
93
  REMOTE_SCRIPT_URL = "https://raw.githubusercontent.com/albalday/zerochat/master/zerochat.py"
88
94
  CONSOLE_STATUS_IDLE_SECONDS = 8.0
89
95
  CONSOLE_CONTROL = None
@@ -256,26 +262,22 @@ def is_installed_runtime() -> bool:
256
262
  return False
257
263
 
258
264
 
259
- def get_venv_dir() -> Path:
260
- """Devuelve el estado persistente; PyPI nunca crea un venv en el proyecto del usuario."""
261
- if get_dev_root() is not None:
262
- return (Path.cwd() / "zerochat").resolve()
263
-
265
+ def get_data_dir() -> Path:
266
+ """Devuelve el directorio local que concentra el estado y los MCP de ZeroChat."""
264
267
  configured = os.environ.get("ZEROCHAT_DATA_DIR", "").strip()
265
268
  if configured:
266
269
  return Path(configured).expanduser().resolve()
267
- if sys.platform.startswith("win"):
268
- base = Path(os.environ.get("APPDATA", Path.home()))
269
- elif sys.platform == "darwin":
270
- base = Path.home() / "Library" / "Application Support"
271
- else:
272
- base = Path(os.environ.get("XDG_STATE_HOME", Path.home() / ".local" / "state"))
273
- return (base / "zerochat").resolve()
270
+ return (Path.cwd() / "zerochat").resolve()
271
+
272
+
273
+ def get_venv_dir() -> Path:
274
+ """Devuelve el entorno aislado usado exclusivamente por los MCP Python."""
275
+ return get_data_dir() / ".venv"
274
276
 
275
277
 
276
278
  def get_daily_token() -> str:
277
279
  """Devuelve un token de sesión diario persistido en ./zerochat/config/token.json."""
278
- config_dir = get_venv_dir() / "config"
280
+ config_dir = get_data_dir() / "config"
279
281
  config_dir.mkdir(parents=True, exist_ok=True)
280
282
  token_file = config_dir / "token.json"
281
283
  today = datetime.date.today().isoformat()
@@ -313,12 +315,10 @@ def get_venv_python(venv_dir: Path) -> Path:
313
315
 
314
316
  def ensure_virtual_environment():
315
317
  """
316
- Comprueba si existe el entorno virtual en ./zerochat. Si no existe, lo crea.
317
- Si el proceso actual no se está ejecutando bajo dicho entorno, se re-ejecuta.
318
+ Crea ./zerochat/.venv para dependencias MCP en ambos modos de distribución.
319
+ El servidor conserva el intérprete con el que fue iniciado; el entorno se usa
320
+ exclusivamente al lanzar procesos MCP Python.
318
321
  """
319
- if is_installed_runtime():
320
- return
321
-
322
322
  venv_dir = get_venv_dir()
323
323
  venv_py = get_venv_python(venv_dir)
324
324
 
@@ -332,19 +332,6 @@ def ensure_virtual_environment():
332
332
  console_log(f"[{time.strftime('%H:%M:%S')}] [zerochat] Advertencia al crear venv: {err}. Continuando con intérprete actual.", flush=True)
333
333
  return
334
334
 
335
- # 2. Comprobar si ya estamos ejecutándonos dentro del venv
336
- try:
337
- current_py = Path(sys.executable).resolve()
338
- target_py = venv_py.resolve()
339
- if current_py != target_py and target_py.exists():
340
- console_log(f"[{time.strftime('%H:%M:%S')}] [zerochat] Re-ejecutando bajo {venv_py}...", flush=True)
341
- # Re-ejecutar con los mismos argumentos
342
- args = [str(target_py), str(Path(__file__).resolve())] + sys.argv[1:]
343
- os.execv(str(target_py), args)
344
- except Exception as err:
345
- console_log(f"[{time.strftime('%H:%M:%S')}] [zerochat] Error en re-ejecución: {err}. Continuando.", flush=True)
346
-
347
-
348
335
  def parse_version(ver: str) -> tuple[int, ...]:
349
336
  """Convierte una cadena de versión semántica en tupla de enteros para comparación."""
350
337
  parts = []
@@ -355,29 +342,43 @@ def parse_version(ver: str) -> tuple[int, ...]:
355
342
  return tuple(parts)
356
343
 
357
344
 
345
+ def _read_remote_version(url: str) -> str | None:
346
+ """Obtiene una versión publicada desde package.json o la API JSON de PyPI."""
347
+ req = urllib.request.Request(url, headers={"User-Agent": f"ZeroChat/{VERSION}"})
348
+ with urllib.request.urlopen(req, timeout=3) as resp:
349
+ content = resp.read(4096).decode("utf-8", errors="ignore")
350
+ try:
351
+ data = json.loads(content)
352
+ if isinstance(data, dict):
353
+ candidate = data.get("version")
354
+ if not isinstance(candidate, str) and isinstance(data.get("info"), dict):
355
+ candidate = data["info"].get("version")
356
+ if isinstance(candidate, str):
357
+ return candidate.strip()
358
+ except (json.JSONDecodeError, TypeError):
359
+ pass
360
+ match = re.search(r'["\']?version["\']?\s*[:=]\s*["\'](\d+\.\d+\.\d+)["\']', content)
361
+ return match.group(1) if match else None
362
+
363
+
364
+ def has_new_backend_version(remote_version: str, local_version: str = VERSION) -> bool:
365
+ """Compara solo major.minor: los parches pertenecen a la interfaz web."""
366
+ return parse_version(compatibility_version(remote_version)) > parse_version(compatibility_version(local_version))
367
+
368
+
358
369
  def check_version():
359
- """Comprueba si hay una nueva versión de zerochat.py en el repositorio remoto (solo en modo producción/standalone)."""
360
- if get_dev_root() is not None or is_installed_runtime():
370
+ """Informa de actualizaciones del backend, sin avisar por parches web."""
371
+ if get_dev_root() is not None:
361
372
  return
362
373
  try:
363
- req = urllib.request.Request(REMOTE_VERSION_URL, headers={"User-Agent": f"ZeroChat/{VERSION}"})
364
- with urllib.request.urlopen(req, timeout=3) as resp:
365
- content = resp.read(2048).decode("utf-8", errors="ignore")
366
- remote_ver = None
367
- try:
368
- data = json.loads(content)
369
- if isinstance(data, dict) and "version" in data and isinstance(data["version"], str):
370
- remote_ver = data["version"].strip()
371
- except Exception:
372
- pass
373
- if not remote_ver:
374
- match = re.search(r'["\']?version["\']?\s*[:=]\s*["\'](\d+\.\d+\.\d+)["\']', content)
375
- if match:
376
- remote_ver = match.group(1)
377
- if remote_ver and re.match(r"^\d+(\.\d+)+", remote_ver):
378
- if parse_version(remote_ver) > parse_version(VERSION):
379
- console_log(f"[{time.strftime('%H:%M:%S')}] [zerochat] ¡Nueva versión disponible! (Local: {VERSION}, Remota: {remote_ver})", flush=True)
380
- console_log(f"[{time.strftime('%H:%M:%S')}] [zerochat] Actualiza con: curl -sSL {REMOTE_SCRIPT_URL} -o zerochat.py", flush=True)
374
+ installed = is_installed_runtime()
375
+ remote_ver = _read_remote_version(PYPI_VERSION_URL if installed else REMOTE_VERSION_URL)
376
+ if remote_ver and re.match(r"^\d+(\.\d+)+", remote_ver) and has_new_backend_version(remote_ver):
377
+ console_log(f"[{time.strftime('%H:%M:%S')}] [zerochat] Nueva versión del servidor disponible (Local: {VERSION}, Remota: {compatibility_version(remote_ver)})", flush=True)
378
+ if installed:
379
+ console_log(f"[{time.strftime('%H:%M:%S')}] [zerochat] Actualiza cuando quieras con: {sys.executable} -m pip install --upgrade zerochat", flush=True)
380
+ else:
381
+ console_log(f"[{time.strftime('%H:%M:%S')}] [zerochat] Actualiza con: curl -sSL {REMOTE_SCRIPT_URL} -o zerochat.py", flush=True)
381
382
  except Exception:
382
383
  # Modo offline o timeout ignorado de forma segura
383
384
  pass
@@ -784,11 +785,11 @@ class McpServiceManager:
784
785
  else:
785
786
  candidates = [
786
787
  Path.cwd() / "services",
787
- get_venv_dir() / "services"
788
+ get_data_dir() / "services"
788
789
  ]
789
790
  self.services_root = candidates[0] if get_dev_root() is not None and candidates[0].is_dir() else candidates[1]
790
791
  self.services_root.mkdir(parents=True, exist_ok=True)
791
- self.config_file = get_venv_dir() / "config" / "services.json"
792
+ self.config_file = get_data_dir() / "config" / "services.json"
792
793
  self.config_file.parent.mkdir(parents=True, exist_ok=True)
793
794
  self.clients: dict[str, StdioMcpClient] = {}
794
795
  self.states: dict[str, str] = {}
@@ -1110,7 +1111,7 @@ for raw in sys.stdin:
1110
1111
 
1111
1112
  return {
1112
1113
  "serviceDir": str(service_dir),
1113
- "pythonExecutable": sys.executable,
1114
+ "pythonExecutable": str(get_venv_python(get_venv_dir())) if get_venv_python(get_venv_dir()).is_file() else sys.executable,
1114
1115
  "nodeExecutable": node
1115
1116
  }
1116
1117
 
@@ -1862,7 +1863,7 @@ def main():
1862
1863
  parser.add_argument("--ui-url", default=None, help="URL de la interfaz web a abrir (por defecto: interfaz local en desarrollo o GitHub Pages)")
1863
1864
  parser.add_argument("--no-browser", action="store_true", help="No abrir automáticamente el navegador")
1864
1865
  parser.add_argument("--no-exit-on-close", action="store_true", help="No detener el servidor automáticamente al cerrar el navegador")
1865
- parser.add_argument("--no-venv", action="store_true", help="Omitir la comprobación/creación del venv ./zerochat")
1866
+ parser.add_argument("--no-venv", action="store_true", help="Omitir la comprobación/creación de ./zerochat/.venv")
1866
1867
  parser.add_argument("--test", action="store_true", help="Ejecutar autocomprobación interna de herramientas")
1867
1868
  parser.add_argument("--version", action="version", version=f"ZeroChat {VERSION}")
1868
1869
  args = parser.parse_args()
@@ -1874,7 +1875,7 @@ def main():
1874
1875
  print(f"[{time.strftime('%H:%M:%S')}] TEST all local tools ready.")
1875
1876
  return
1876
1877
 
1877
- # 1. Asegurar entorno virtual ./zerochat solo cuando se ejecuta desde fuentes.
1878
+ # 1. Asegurar el entorno MCP aislado en ambos modos de distribución.
1878
1879
  if not args.no_venv:
1879
1880
  ensure_virtual_environment()
1880
1881
 
@@ -1911,7 +1912,8 @@ def main():
1911
1912
  print("=" * 64)
1912
1913
  print(f" ZeroChat Local Server v{VERSION} (UI {UI_VERSION})")
1913
1914
  print(f" Directorio de trabajo : {Path.cwd()}")
1914
- print(f" Entorno virtual : {get_venv_dir()}")
1915
+ print(f" Datos y MCP : {get_data_dir()}")
1916
+ print(f" Entorno MCP : {get_venv_dir()}")
1915
1917
  if is_dev:
1916
1918
  print(f" Modo de ejecución : Desarrollo local ({dev_root})")
1917
1919
  elif is_installed:
File without changes
File without changes