{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Getting Started With mdfs Over HTTP\n", "\n", "This notebook starts a local `mdfs-server`, writes markdown files, reads them back, searches the workspace, and commits the result.\n", "\n", "Run it from the repository root. The notebook uses a throwaway data directory under `.demo/notebook-http`." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 1. Start A Local Server\n", "\n", "The HTTP server is the safest integration point for notebooks and agents because it acts as the single writer to the mdfs state file." ] }, { "cell_type": "code", "metadata": {}, "source": [ "from pathlib import Path\n", "import atexit\n", "import json\n", "import os\n", "import shutil\n", "import subprocess\n", "import time\n", "import urllib.error\n", "import urllib.parse\n", "import urllib.request\n", "\n", "\n", "def find_repo_root(start=None):\n", " \"\"\"Find the mdfs repo even when Jupyter starts in examples/notebooks.\"\"\"\n", " env_root = os.environ.get(\"MDFS_REPO_ROOT\") or os.environ.get(\"MARKDOWNFS_REPO_ROOT\")\n", " candidates = []\n", " if env_root:\n", " candidates.append(Path(env_root).expanduser())\n", "\n", " start_path = Path(start or Path.cwd()).resolve()\n", " candidates.extend([start_path, *start_path.parents])\n", "\n", " for candidate in candidates:\n", " if (candidate / \"Cargo.toml\").exists() and (candidate / \"src\").is_dir():\n", " return candidate\n", "\n", " raise RuntimeError(\n", " \"Could not find the mdfs repository root. \"\n", " \"Run the notebook from inside the repo or set MDFS_REPO_ROOT.\"\n", " )\n", "\n", "\n", "def find_cargo():\n", " \"\"\"Find Cargo even when the Jupyter kernel has a minimal PATH.\"\"\"\n", " candidates = []\n", " env_cargo = os.environ.get(\"CARGO\")\n", " if env_cargo:\n", " candidates.append(Path(env_cargo).expanduser())\n", "\n", " path_cargo = shutil.which(\"cargo\")\n", " if path_cargo:\n", " candidates.append(Path(path_cargo))\n", "\n", " candidates.extend([\n", " Path.home() / \".cargo\" / \"bin\" / \"cargo\",\n", " Path(\"/opt/homebrew/bin/cargo\"),\n", " Path(\"/usr/local/bin/cargo\"),\n", " ])\n", "\n", " for candidate in candidates:\n", " if candidate.exists() and candidate.is_file():\n", " return str(candidate)\n", "\n", " raise RuntimeError(\n", " \"Could not find Cargo. Install Rust from https://rustup.rs, \"\n", " \"or set CARGO=/absolute/path/to/cargo before running this notebook.\"\n", " )\n", "\n", "\n", "ROOT = find_repo_root()\n", "CARGO = find_cargo()\n", "DATA_DIR = ROOT / \".demo\" / \"notebook-http\"\n", "BASE_URL = \"http://127.0.0.1:3000\"\n", "print(f\"Using mdfs repo at {ROOT}\")\n", "print(f\"Using cargo at {CARGO}\")\n", "\n", "if DATA_DIR.exists():\n", " shutil.rmtree(DATA_DIR)\n", "DATA_DIR.mkdir(parents=True)\n", "\n", "env = os.environ.copy()\n", "env.update({\n", " \"MARKDOWNFS_DATA_DIR\": str(DATA_DIR),\n", " \"MARKDOWNFS_LISTEN\": \"127.0.0.1:3000\",\n", "})\n", "env[\"PATH\"] = os.pathsep.join([str(Path(CARGO).parent), env.get(\"PATH\", \"\")])\n", "\n", "server = subprocess.Popen(\n", " [CARGO, \"run\", \"--quiet\", \"--bin\", \"mdfs-server\"],\n", " cwd=ROOT,\n", " env=env,\n", " stdout=subprocess.PIPE,\n", " stderr=subprocess.STDOUT,\n", " text=True,\n", ")\n", "\n", "def stop_server():\n", " if server.poll() is None:\n", " server.terminate()\n", " try:\n", " server.wait(timeout=5)\n", " except subprocess.TimeoutExpired:\n", " server.kill()\n", "\n", "atexit.register(stop_server)\n", "\n", "def request(method, path, body=None, headers=None):\n", " url = f\"{BASE_URL}{path}\"\n", " data = body.encode(\"utf-8\") if isinstance(body, str) else body\n", " req = urllib.request.Request(url, data=data, method=method, headers=headers or {})\n", " try:\n", " with urllib.request.urlopen(req, timeout=5) as response:\n", " raw = response.read().decode(\"utf-8\")\n", " content_type = response.headers.get(\"content-type\", \"\")\n", " if \"application/json\" in content_type:\n", " return json.loads(raw)\n", " return raw\n", " except urllib.error.HTTPError as exc:\n", " message = exc.read().decode(\"utf-8\")\n", " raise RuntimeError(f\"{method} {path} failed: {exc.code} {message}\") from exc\n", "\n", "for _ in range(60):\n", " try:\n", " print(request(\"GET\", \"/health\"))\n", " break\n", " except Exception:\n", " if server.poll() is not None:\n", " output = server.stdout.read() if server.stdout else \"\"\n", " raise RuntimeError(f\"mdfs-server exited early:\\n{output}\")\n", " time.sleep(0.25)\n", "else:\n", " raise TimeoutError(\"mdfs-server did not become ready\")" ], "execution_count": null, "outputs": [], "id": "92ffc9f6" }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 2. Write Markdown Files\n", "\n", "Write markdown files directly. The HTTP API creates missing parent directories for new files." ], "id": "e16a5812" }, { "cell_type": "code", "metadata": {}, "source": [ "readme = \"\"\"# Notebook Demo\n", "\n", "This file was written through the mdfs HTTP API.\n", "\n", "TODO: delegate the next draft to an agent.\n", "\"\"\"\n", "\n", "plan = \"\"\"# Agent Plan\n", "\n", "- Inspect the workspace before writing.\n", "- Save every meaningful result as markdown.\n", "- Commit the state after each handoff.\n", "\"\"\"\n", "\n", "print(request(\"PUT\", \"/fs/notebook-demo/readme.md\", readme))\n", "print(request(\"PUT\", \"/fs/notebook-demo/agent-plan.md\", plan))" ], "execution_count": null, "outputs": [], "id": "55dd6495" }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 3. Read, List, Search" ], "id": "36d31719" }, { "cell_type": "code", "metadata": {}, "source": [ "print(request(\"GET\", \"/fs/notebook-demo/readme.md\"))\n", "print(json.dumps(request(\"GET\", \"/fs/notebook-demo/\"), indent=2))\n", "\n", "query = urllib.parse.urlencode({\"pattern\": \"TODO|agent\", \"path\": \"notebook-demo\", \"recursive\": \"true\"})\n", "print(json.dumps(request(\"GET\", f\"/search/grep?{query}\"), indent=2))" ], "execution_count": null, "outputs": [], "id": "902143d9" }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 4. Commit And Inspect History" ], "id": "b9ab61a5" }, { "cell_type": "code", "metadata": {}, "source": [ "commit = request(\n", " \"POST\",\n", " \"/vcs/commit\",\n", " json.dumps({\"message\": \"notebook getting started demo\"}),\n", " {\"Content-Type\": \"application/json\"},\n", ")\n", "print(json.dumps(commit, indent=2))\n", "print(request(\"GET\", \"/vcs/status\"))\n", "print(json.dumps(request(\"GET\", \"/vcs/log\"), indent=2))" ], "execution_count": null, "outputs": [], "id": "1a2f578e" }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 5. Clean Up" ], "id": "11dbd153" }, { "cell_type": "code", "metadata": {}, "source": [ "stop_server()\n", "print(\"server stopped\")" ], "execution_count": null, "outputs": [], "id": "0d14d3ee" } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.9.6" } }, "nbformat": 4, "nbformat_minor": 5 }