diff --git a/pyproject.toml b/pyproject.toml index 00301e63b..47ada1bad 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -30,7 +30,7 @@ dependencies = [ "pyyaml>=6.0.2", "rich>=13.9.4", "scikit-learn>=1.6.0", - "typer>=0.15.1", + "typer>=0.15.3", "websockets>=12.0", ] @@ -77,7 +77,8 @@ cli = [ "hvac>=1.1.1", "httpx>=0.28.1", "pyjwt>=2.10.1", - "typer[all]>=0.15.1", + "typer[all]>=0.15.3", + "watchdog>=6.0.0" ] [build-system] diff --git a/src/mcp_agent/cli/commands/build.py b/src/mcp_agent/cli/commands/build.py index 1779bf8bd..7e4b5ea26 100644 --- a/src/mcp_agent/cli/commands/build.py +++ b/src/mcp_agent/cli/commands/build.py @@ -378,6 +378,9 @@ def build( console.print(server_table) console.print() + else: + console.print("[yellow]No MCP servers found in configuration[/yellow]") + console.print() # Show warnings if report["warnings"]: diff --git a/src/mcp_agent/cli/commands/dev.py b/src/mcp_agent/cli/commands/dev.py index 1234d3da9..d598893b5 100644 --- a/src/mcp_agent/cli/commands/dev.py +++ b/src/mcp_agent/cli/commands/dev.py @@ -6,15 +6,14 @@ from __future__ import annotations -import asyncio +import subprocess +import sys from pathlib import Path import shutil -import time import typer from rich.console import Console -from mcp_agent.cli.core.utils import load_user_app from mcp_agent.config import get_settings @@ -39,16 +38,16 @@ def _preflight_ok() -> bool: ok = False return ok - async def _run_once(): - app_obj = load_user_app(script) - async with app_obj.run(): - console.print(f"Running {script}") - # Sleep until cancelled - try: - while True: - await asyncio.sleep(1) - except asyncio.CancelledError: - pass + def _run_script() -> subprocess.Popen: + """Run the script as a subprocess.""" + console.print(f"Running {script}") + # Run the script with the same Python interpreter + return subprocess.Popen( + [sys.executable, str(script)], + stdout=None, # Inherit stdout + stderr=None, # Inherit stderr + stdin=None, # Inherit stdin + ) # Simple preflight _ = _preflight_ok() @@ -57,50 +56,77 @@ async def _run_once(): try: from watchdog.observers import Observer # type: ignore from watchdog.events import FileSystemEventHandler # type: ignore + import time class _Handler(FileSystemEventHandler): def __init__(self): self.touched = False def on_modified(self, event): # type: ignore - self.touched = True + if not event.is_directory: + self.touched = True def on_created(self, event): # type: ignore - self.touched = True - - loop = asyncio.get_event_loop() - task = loop.create_task(_run_once()) + if not event.is_directory: + self.touched = True handler = _Handler() observer = Observer() observer.schedule(handler, path=str(script.parent), recursive=True) observer.start() console.print("Live reload enabled (watchdog)") + + # Start the script + process = _run_script() + try: while True: time.sleep(0.5) + + # Check if process died + if process.poll() is not None: + console.print( + f"[red]Process exited with code {process.returncode}[/red]" + ) + break + + # Check for file changes if handler.touched: handler.touched = False console.print("Change detected. Restarting...") - task.cancel() + process.terminate() try: - loop.run_until_complete(task) - except Exception: - pass - task = loop.create_task(_run_once()) + process.wait(timeout=5) + except subprocess.TimeoutExpired: + process.kill() + process.wait() + process = _run_script() + except KeyboardInterrupt: - pass + console.print("\n[yellow]Stopping...[/yellow]") + process.terminate() + try: + process.wait(timeout=5) + except subprocess.TimeoutExpired: + process.kill() + process.wait() finally: observer.stop() observer.join() - task.cancel() - try: - loop.run_until_complete(task) - except Exception: - pass - except Exception: - # Fallback: run once + + except ImportError: + # Fallback: run once without watchdog + console.print( + "[yellow]Watchdog not installed. Running without live reload.[/yellow]" + ) + process = _run_script() try: - asyncio.run(_run_once()) + process.wait() except KeyboardInterrupt: - pass + console.print("\n[yellow]Stopping...[/yellow]") + process.terminate() + try: + process.wait(timeout=5) + except subprocess.TimeoutExpired: + process.kill() + process.wait() diff --git a/src/mcp_agent/cli/commands/invoke.py b/src/mcp_agent/cli/commands/invoke.py index a30ac9b09..38434f7ab 100644 --- a/src/mcp_agent/cli/commands/invoke.py +++ b/src/mcp_agent/cli/commands/invoke.py @@ -16,7 +16,7 @@ app = typer.Typer(help="Invoke an agent or workflow programmatically") -console = Console() +console = Console(color_system=None) @app.callback(invoke_without_command=True) @@ -27,6 +27,9 @@ def invoke( vars: Optional[str] = typer.Option(None, "--vars", help="JSON structured inputs"), script: Optional[str] = typer.Option(None, "--script"), model: Optional[str] = typer.Option(None, "--model"), + servers: Optional[str] = typer.Option( + None, "--servers", help="Comma-separated list of MCP server names" + ), ) -> None: """Run either an agent (LLM) or a workflow from the user's app script.""" if not agent and not workflow: @@ -50,16 +53,18 @@ async def _run(): async with app_obj.run(): if agent: # Run via LLM + server_list = servers.split(",") if servers else [] + server_list = [s.strip() for s in server_list if s.strip()] llm = create_llm( agent_name=agent, - server_names=[], + server_names=server_list, provider=None, model=model, context=app_obj.context, ) if message: res = await llm.generate_str(message) - console.print(res) + console.print(res, end="\n\n\n") return if payload: # If structured vars contain messages, prefer that key; else stringify @@ -69,7 +74,7 @@ async def _run(): or json.dumps(payload) ) res = await llm.generate_str(msg) - console.print(res) + console.print(res, end="\n\n\n") return typer.secho("No input provided", err=True, fg=typer.colors.YELLOW) return @@ -98,7 +103,7 @@ async def _run(): val = getattr(result, "value", result) except Exception: val = result - console.print(val) + console.print(val, end="\n\n\n") from pathlib import Path diff --git a/src/mcp_agent/cli/commands/serve.py b/src/mcp_agent/cli/commands/serve.py index 6de14ec19..fbc2de633 100644 --- a/src/mcp_agent/cli/commands/serve.py +++ b/src/mcp_agent/cli/commands/serve.py @@ -191,6 +191,8 @@ async def _run(): info_table.add_row("Address", f"http://{address}") if transport == "sse": info_table.add_row("SSE Endpoint", f"http://{address}/sse") + elif transport == "http": + info_table.add_row("HTTP Endpoint", f"http://{address}/mcp") # Show registered components if hasattr(app_obj, "workflows") and app_obj.workflows: @@ -251,6 +253,7 @@ async def _run(): def signal_handler(sig, frame): console.print("\n[yellow]Shutting down server...[/yellow]") shutdown_event.set() + os._exit(0) signal.signal(signal.SIGINT, signal_handler) signal.signal(signal.SIGTERM, signal_handler) @@ -284,7 +287,7 @@ def signal_handler(sig, frame): # Configure uvicorn uvicorn_config = uvicorn.Config( - mcp.app, + mcp.streamable_http_app if transport == "http" else mcp.sse_app, host=host, port=port or 8000, log_level="debug" if debug else "info", @@ -307,6 +310,10 @@ def signal_handler(sig, frame): if transport == "sse": console.print(f"[bold]SSE:[/bold] http://{host}:{port or 8000}/sse") + elif transport == "http": + console.print( + f"[bold]HTTP:[/bold] http://{host}:{port or 8000}/mcp" + ) console.print("\n[dim]Press Ctrl+C to stop the server[/dim]\n") diff --git a/src/mcp_agent/cli/commands/server.py b/src/mcp_agent/cli/commands/server.py index f72347354..b6ca03d54 100644 --- a/src/mcp_agent/cli/commands/server.py +++ b/src/mcp_agent/cli/commands/server.py @@ -12,8 +12,9 @@ from rich.table import Table from rich.prompt import Confirm -from mcp_agent.config import Settings, MCPServerSettings, MCPSettings +from mcp_agent.config import Settings, MCPServerSettings, MCPSettings, get_settings from mcp_agent.cli.utils.importers import import_servers_from_mcp_json +from mcp_agent.core.context import cleanup_context app = typer.Typer(help="Local server helpers") @@ -338,7 +339,7 @@ def list_servers( ), ) -> None: """List configured servers.""" - settings = Settings() + settings = get_settings() servers = (settings.mcp.servers if settings.mcp else {}) or {} if not servers: @@ -443,7 +444,7 @@ def add( ), ) -> None: """Add a server to configuration.""" - settings = Settings() + settings = get_settings() if settings.mcp is None: settings.mcp = MCPSettings() servers = settings.mcp.servers or {} @@ -759,7 +760,8 @@ async def _probe(): pass # Resources might not be supported console.print( - f"\n[green bold]✅ Server '{name}' is working correctly![/green bold]" + f"\n[green bold]✅ Server '{name}' is working correctly![/green bold]", + end="\n\n", ) except asyncio.TimeoutError: @@ -773,6 +775,9 @@ async def _probe(): console.print(f"[dim]{traceback.format_exc()}[/dim]") raise typer.Exit(1) + # Force complete shutdown of logging infrastructure for CLI commands + await cleanup_context(shutdown_logger=True) + try: asyncio.run(asyncio.wait_for(_probe(), timeout=timeout)) except asyncio.TimeoutError: diff --git a/src/mcp_agent/cli/main.py b/src/mcp_agent/cli/main.py index fe14a50f8..5b6dd359a 100644 --- a/src/mcp_agent/cli/main.py +++ b/src/mcp_agent/cli/main.py @@ -27,6 +27,15 @@ from mcp_agent.cli.cloud.commands import deploy_config, login, logout, whoami from mcp_agent.cli.commands import ( check as check_cmd, + chat as chat_cmd, + dev as dev_cmd, + invoke as invoke_cmd, + serve as serve_cmd, + server as server_cmd, + build as build_cmd, + logs as logs_cmd, + doctor as doctor_cmd, + configure as configure_cmd, ) from mcp_agent.cli.commands import ( config as config_cmd, @@ -50,8 +59,8 @@ app = typer.Typer( help="mcp-agent CLI", - add_completion=False, - no_args_is_help=False, + add_completion=True, + no_args_is_help=True, context_settings={"help_option_names": ["-h", "--help"]}, cls=HelpfulTyperGroup, ) @@ -123,20 +132,19 @@ def main( app.add_typer(keys_cmd.app, name="keys", help="Manage provider API keys") app.add_typer(models_cmd.app, name="models", help="List and manage models") -# TODO: Uncomment after testing - Local Development and beyond -# app.add_typer(chat_cmd.app, name="chat", help="Ephemeral REPL for quick iteration") -# app.add_typer(dev_cmd.app, name="dev", help="Run app locally with live reload") -# app.add_typer( -# invoke_cmd.app, name="invoke", help="Invoke agent/workflow programmatically" -# ) -# app.add_typer(serve_cmd.app, name="serve", help="Serve app as an MCP server") -# app.add_typer(server_cmd.app, name="server", help="Local server helpers") -# app.add_typer( -# build_cmd.app, name="build", help="Preflight and bundle prep for deployment" -# ) -# app.add_typer(logs_cmd.app, name="logs", help="Tail local logs") -# app.add_typer(doctor_cmd.app, name="doctor", help="Comprehensive diagnostics") -# app.add_typer(configure_cmd.app, name="configure", help="Client integration helpers") +app.add_typer(chat_cmd.app, name="chat", help="Ephemeral REPL for quick iteration") +app.add_typer(dev_cmd.app, name="dev", help="Run app locally with live reload") +app.add_typer( + invoke_cmd.app, name="invoke", help="Invoke agent/workflow programmatically" +) +app.add_typer(serve_cmd.app, name="serve", help="Serve app as an MCP server") +app.add_typer(server_cmd.app, name="server", help="Local server helpers") +app.add_typer( + build_cmd.app, name="build", help="Preflight and bundle prep for deployment" +) +app.add_typer(logs_cmd.app, name="logs", help="Tail local logs") +app.add_typer(doctor_cmd.app, name="doctor", help="Comprehensive diagnostics") +app.add_typer(configure_cmd.app, name="configure", help="Client integration helpers") # Mount cloud commands app.add_typer(cloud_app, name="cloud", help="MCP Agent Cloud commands") diff --git a/uv.lock b/uv.lock index 51a77d543..5f475ce9b 100644 --- a/uv.lock +++ b/uv.lock @@ -1,5 +1,5 @@ version = 1 -revision = 3 +revision = 2 requires-python = ">=3.10" resolution-markers = [ "python_full_version >= '3.13'", @@ -2087,6 +2087,7 @@ cli = [ { name = "hvac" }, { name = "pyjwt" }, { name = "typer" }, + { name = "watchdog" }, ] cohere = [ { name = "cohere" }, @@ -2156,8 +2157,9 @@ requires-dist = [ { name = "rich", specifier = ">=13.9.4" }, { name = "scikit-learn", specifier = ">=1.6.0" }, { name = "temporalio", extras = ["opentelemetry"], marker = "extra == 'temporal'", specifier = ">=1.10.0" }, - { name = "typer", specifier = ">=0.15.1" }, + { name = "typer", specifier = ">=0.15.3" }, { name = "typer", extras = ["all"], marker = "extra == 'cli'", specifier = ">=0.15.1" }, + { name = "watchdog", marker = "extra == 'cli'", specifier = ">=6.0.0" }, { name = "websockets", specifier = ">=12.0" }, ] provides-extras = ["temporal", "anthropic", "anthropic-bedrock", "anthropic-vertex", "bedrock", "openai", "azure", "google", "cohere", "langchain", "crewai", "cli"] @@ -4575,6 +4577,38 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f3/40/b1c265d4b2b62b58576588510fc4d1fe60a86319c8de99fd8e9fec617d2c/virtualenv-20.31.2-py3-none-any.whl", hash = "sha256:36efd0d9650ee985f0cad72065001e66d49a6f24eb44d98980f630686243cf11", size = 6057982, upload-time = "2025-05-08T17:58:21.15Z" }, ] +[[package]] +name = "watchdog" +version = "6.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/db/7d/7f3d619e951c88ed75c6037b246ddcf2d322812ee8ea189be89511721d54/watchdog-6.0.0.tar.gz", hash = "sha256:9ddf7c82fda3ae8e24decda1338ede66e1c99883db93711d8fb941eaa2d8c282", size = 131220, upload-time = "2024-11-01T14:07:13.037Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0c/56/90994d789c61df619bfc5ce2ecdabd5eeff564e1eb47512bd01b5e019569/watchdog-6.0.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d1cdb490583ebd691c012b3d6dae011000fe42edb7a82ece80965b42abd61f26", size = 96390, upload-time = "2024-11-01T14:06:24.793Z" }, + { url = "https://files.pythonhosted.org/packages/55/46/9a67ee697342ddf3c6daa97e3a587a56d6c4052f881ed926a849fcf7371c/watchdog-6.0.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bc64ab3bdb6a04d69d4023b29422170b74681784ffb9463ed4870cf2f3e66112", size = 88389, upload-time = "2024-11-01T14:06:27.112Z" }, + { url = "https://files.pythonhosted.org/packages/44/65/91b0985747c52064d8701e1075eb96f8c40a79df889e59a399453adfb882/watchdog-6.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c897ac1b55c5a1461e16dae288d22bb2e412ba9807df8397a635d88f671d36c3", size = 89020, upload-time = "2024-11-01T14:06:29.876Z" }, + { url = "https://files.pythonhosted.org/packages/e0/24/d9be5cd6642a6aa68352ded4b4b10fb0d7889cb7f45814fb92cecd35f101/watchdog-6.0.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6eb11feb5a0d452ee41f824e271ca311a09e250441c262ca2fd7ebcf2461a06c", size = 96393, upload-time = "2024-11-01T14:06:31.756Z" }, + { url = "https://files.pythonhosted.org/packages/63/7a/6013b0d8dbc56adca7fdd4f0beed381c59f6752341b12fa0886fa7afc78b/watchdog-6.0.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ef810fbf7b781a5a593894e4f439773830bdecb885e6880d957d5b9382a960d2", size = 88392, upload-time = "2024-11-01T14:06:32.99Z" }, + { url = "https://files.pythonhosted.org/packages/d1/40/b75381494851556de56281e053700e46bff5b37bf4c7267e858640af5a7f/watchdog-6.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:afd0fe1b2270917c5e23c2a65ce50c2a4abb63daafb0d419fde368e272a76b7c", size = 89019, upload-time = "2024-11-01T14:06:34.963Z" }, + { url = "https://files.pythonhosted.org/packages/39/ea/3930d07dafc9e286ed356a679aa02d777c06e9bfd1164fa7c19c288a5483/watchdog-6.0.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:bdd4e6f14b8b18c334febb9c4425a878a2ac20efd1e0b231978e7b150f92a948", size = 96471, upload-time = "2024-11-01T14:06:37.745Z" }, + { url = "https://files.pythonhosted.org/packages/12/87/48361531f70b1f87928b045df868a9fd4e253d9ae087fa4cf3f7113be363/watchdog-6.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c7c15dda13c4eb00d6fb6fc508b3c0ed88b9d5d374056b239c4ad1611125c860", size = 88449, upload-time = "2024-11-01T14:06:39.748Z" }, + { url = "https://files.pythonhosted.org/packages/5b/7e/8f322f5e600812e6f9a31b75d242631068ca8f4ef0582dd3ae6e72daecc8/watchdog-6.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6f10cb2d5902447c7d0da897e2c6768bca89174d0c6e1e30abec5421af97a5b0", size = 89054, upload-time = "2024-11-01T14:06:41.009Z" }, + { url = "https://files.pythonhosted.org/packages/68/98/b0345cabdce2041a01293ba483333582891a3bd5769b08eceb0d406056ef/watchdog-6.0.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:490ab2ef84f11129844c23fb14ecf30ef3d8a6abafd3754a6f75ca1e6654136c", size = 96480, upload-time = "2024-11-01T14:06:42.952Z" }, + { url = "https://files.pythonhosted.org/packages/85/83/cdf13902c626b28eedef7ec4f10745c52aad8a8fe7eb04ed7b1f111ca20e/watchdog-6.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:76aae96b00ae814b181bb25b1b98076d5fc84e8a53cd8885a318b42b6d3a5134", size = 88451, upload-time = "2024-11-01T14:06:45.084Z" }, + { url = "https://files.pythonhosted.org/packages/fe/c4/225c87bae08c8b9ec99030cd48ae9c4eca050a59bf5c2255853e18c87b50/watchdog-6.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a175f755fc2279e0b7312c0035d52e27211a5bc39719dd529625b1930917345b", size = 89057, upload-time = "2024-11-01T14:06:47.324Z" }, + { url = "https://files.pythonhosted.org/packages/30/ad/d17b5d42e28a8b91f8ed01cb949da092827afb9995d4559fd448d0472763/watchdog-6.0.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:c7ac31a19f4545dd92fc25d200694098f42c9a8e391bc00bdd362c5736dbf881", size = 87902, upload-time = "2024-11-01T14:06:53.119Z" }, + { url = "https://files.pythonhosted.org/packages/5c/ca/c3649991d140ff6ab67bfc85ab42b165ead119c9e12211e08089d763ece5/watchdog-6.0.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:9513f27a1a582d9808cf21a07dae516f0fab1cf2d7683a742c498b93eedabb11", size = 88380, upload-time = "2024-11-01T14:06:55.19Z" }, + { url = "https://files.pythonhosted.org/packages/a9/c7/ca4bf3e518cb57a686b2feb4f55a1892fd9a3dd13f470fca14e00f80ea36/watchdog-6.0.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:7607498efa04a3542ae3e05e64da8202e58159aa1fa4acddf7678d34a35d4f13", size = 79079, upload-time = "2024-11-01T14:06:59.472Z" }, + { url = "https://files.pythonhosted.org/packages/5c/51/d46dc9332f9a647593c947b4b88e2381c8dfc0942d15b8edc0310fa4abb1/watchdog-6.0.0-py3-none-manylinux2014_armv7l.whl", hash = "sha256:9041567ee8953024c83343288ccc458fd0a2d811d6a0fd68c4c22609e3490379", size = 79078, upload-time = "2024-11-01T14:07:01.431Z" }, + { url = "https://files.pythonhosted.org/packages/d4/57/04edbf5e169cd318d5f07b4766fee38e825d64b6913ca157ca32d1a42267/watchdog-6.0.0-py3-none-manylinux2014_i686.whl", hash = "sha256:82dc3e3143c7e38ec49d61af98d6558288c415eac98486a5c581726e0737c00e", size = 79076, upload-time = "2024-11-01T14:07:02.568Z" }, + { url = "https://files.pythonhosted.org/packages/ab/cc/da8422b300e13cb187d2203f20b9253e91058aaf7db65b74142013478e66/watchdog-6.0.0-py3-none-manylinux2014_ppc64.whl", hash = "sha256:212ac9b8bf1161dc91bd09c048048a95ca3a4c4f5e5d4a7d1b1a7d5752a7f96f", size = 79077, upload-time = "2024-11-01T14:07:03.893Z" }, + { url = "https://files.pythonhosted.org/packages/2c/3b/b8964e04ae1a025c44ba8e4291f86e97fac443bca31de8bd98d3263d2fcf/watchdog-6.0.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:e3df4cbb9a450c6d49318f6d14f4bbc80d763fa587ba46ec86f99f9e6876bb26", size = 79078, upload-time = "2024-11-01T14:07:05.189Z" }, + { url = "https://files.pythonhosted.org/packages/62/ae/a696eb424bedff7407801c257d4b1afda455fe40821a2be430e173660e81/watchdog-6.0.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:2cce7cfc2008eb51feb6aab51251fd79b85d9894e98ba847408f662b3395ca3c", size = 79077, upload-time = "2024-11-01T14:07:06.376Z" }, + { url = "https://files.pythonhosted.org/packages/b5/e8/dbf020b4d98251a9860752a094d09a65e1b436ad181faf929983f697048f/watchdog-6.0.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:20ffe5b202af80ab4266dcd3e91aae72bf2da48c0d33bdb15c66658e685e94e2", size = 79078, upload-time = "2024-11-01T14:07:07.547Z" }, + { url = "https://files.pythonhosted.org/packages/07/f6/d0e5b343768e8bcb4cda79f0f2f55051bf26177ecd5651f84c07567461cf/watchdog-6.0.0-py3-none-win32.whl", hash = "sha256:07df1fdd701c5d4c8e55ef6cf55b8f0120fe1aef7ef39a1c6fc6bc2e606d517a", size = 79065, upload-time = "2024-11-01T14:07:09.525Z" }, + { url = "https://files.pythonhosted.org/packages/db/d9/c495884c6e548fce18a8f40568ff120bc3a4b7b99813081c8ac0c936fa64/watchdog-6.0.0-py3-none-win_amd64.whl", hash = "sha256:cbafb470cf848d93b5d013e2ecb245d4aa1c8fd0504e863ccefa32445359d680", size = 79070, upload-time = "2024-11-01T14:07:10.686Z" }, + { url = "https://files.pythonhosted.org/packages/33/e8/e40370e6d74ddba47f002a32919d91310d6074130fe4e17dabcafc15cbf1/watchdog-6.0.0-py3-none-win_ia64.whl", hash = "sha256:a1914259fa9e1454315171103c6a30961236f508b9b623eae470268bbcc6a22f", size = 79067, upload-time = "2024-11-01T14:07:11.845Z" }, +] + [[package]] name = "watchfiles" version = "1.0.5"