diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index b6172a29e..67d95e1ca 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -49,7 +49,7 @@ body: attributes: label: nanobot Version description: Run `nanobot --version` or `pip show nanobot-ai` - placeholder: e.g., 0.1.5 + placeholder: e.g., 0.2.0 validations: required: true diff --git a/README.md b/README.md index ccc854fa6..7efdcf886 100644 --- a/README.md +++ b/README.md @@ -214,10 +214,9 @@ nanobot agent - Want to run nanobot in chat apps like Telegram, Discord, WeChat or Feishu? See [Chat Apps](./docs/chat-apps.md) - Want Docker or Linux service deployment? See [Deployment](./docs/deployment.md) -## ๐งช WebUI (Development) +## ๐ WebUI -> [!NOTE] -> The WebUI development workflow currently requires a source checkout and is not yet shipped together with the official packaged release. See [WebUI Document](./webui/README.md) for full WebUI development docs and build steps. +The WebUI ships **inside the published wheel** โ no extra build step. Just enable the WebSocket channel and open it in your browser.
@@ -235,13 +234,12 @@ nanobot agent
nanobot gateway
```
-**3. Start the webui dev server**
+**3. Open the WebUI**
-```bash
-cd webui
-bun install
-bun run dev
-```
+Visit [`http://127.0.0.1:8765`](http://127.0.0.1:8765) in your browser. To open it from another device on your LAN, see [WebUI docs โ LAN access](./webui/README.md#access-from-another-device-lan).
+
+> [!TIP]
+> Working on the WebUI itself? Check out [`webui/README.md`](./webui/README.md) for the Vite dev server (HMR) workflow.
## ๐๏ธ Architecture
diff --git a/docs/README.md b/docs/README.md
index 56b8dab2f..7ac873bd1 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -15,6 +15,7 @@ Start here for setup, everyday usage, and deployment.
| Agent social network | [`agent-social-network.md`](./agent-social-network.md) | Join external agent communities from nanobot |
| Configuration | [`configuration.md`](./configuration.md) | Providers, tools, channels, MCP, and runtime settings |
| Image generation | [`image-generation.md`](./image-generation.md) | Configure image providers, WebUI image mode, and generated artifacts |
+| WebUI | [`../webui/README.md`](../webui/README.md) | Open the bundled browser UI; LAN access; Vite dev server for contributors |
| Multiple instances | [`multiple-instances.md`](./multiple-instances.md) | Run isolated bots with separate configs and workspaces |
| CLI reference | [`cli-reference.md`](./cli-reference.md) | Core CLI commands and common entrypoints |
| In-chat commands | [`chat-commands.md`](./chat-commands.md) | Slash commands and periodic task behavior |
diff --git a/hatch_build.py b/hatch_build.py
new file mode 100644
index 000000000..28dbcd09a
--- /dev/null
+++ b/hatch_build.py
@@ -0,0 +1,101 @@
+"""Hatch build hook that bundles the webui (Vite) into nanobot/web/dist.
+
+Triggered automatically by `python -m build` (and any other hatch-driven build)
+so published wheels and sdists ship a fresh webui without requiring developers
+to remember `cd webui && bun run build` beforehand.
+
+Behaviour:
+
+- Skips for editable installs (`pip install -e .`). Editable mode is for Python
+ development; webui contributors use `cd webui && bun run dev` (Vite HMR) and
+ do not need a packaged `dist/`.
+- No-op when `webui/package.json` is absent (e.g. installing from an sdist that
+ already contains a prebuilt `nanobot/web/dist/`).
+- Skips when `NANOBOT_SKIP_WEBUI_BUILD=1` is set.
+- Skips when `nanobot/web/dist/index.html` already exists, unless
+ `NANOBOT_FORCE_WEBUI_BUILD=1` is set.
+- Uses `bun` when available, otherwise falls back to `npm`. The chosen tool
+ performs `install` followed by `run build`.
+"""
+
+from __future__ import annotations
+
+import os
+import shutil
+import subprocess
+from pathlib import Path
+
+from hatchling.builders.hooks.plugin.interface import BuildHookInterface
+
+
+class WebUIBuildHook(BuildHookInterface):
+ PLUGIN_NAME = "webui-build"
+
+ def initialize(self, version: str, build_data: dict) -> None: # noqa: D401
+ root = Path(self.root)
+ webui_dir = root / "webui"
+ package_json = webui_dir / "package.json"
+ dist_dir = root / "nanobot" / "web" / "dist"
+ index_html = dist_dir / "index.html"
+
+ # `pip install -e .` builds an editable wheel; skip the (slow) webui
+ # bundle since editable installs target Python development and webui
+ # work uses `bun run dev` instead.
+ if self.target_name == "wheel" and version == "editable":
+ self.app.display_info(
+ "[webui-build] skipped for editable install "
+ "(use `cd webui && bun run build` to bundle webui manually)"
+ )
+ return
+
+ if os.environ.get("NANOBOT_SKIP_WEBUI_BUILD") == "1":
+ self.app.display_info("[webui-build] skipped via NANOBOT_SKIP_WEBUI_BUILD=1")
+ return
+
+ if not package_json.is_file():
+ self.app.display_info(
+ "[webui-build] no webui/ source tree, assuming prebuilt nanobot/web/dist/"
+ )
+ return
+
+ force = os.environ.get("NANOBOT_FORCE_WEBUI_BUILD") == "1"
+ if index_html.is_file() and not force:
+ self.app.display_info(
+ f"[webui-build] reusing existing build at {dist_dir} "
+ "(set NANOBOT_FORCE_WEBUI_BUILD=1 to rebuild)"
+ )
+ return
+
+ runner = self._pick_runner()
+ if runner is None:
+ raise RuntimeError(
+ "[webui-build] neither `bun` nor `npm` is available on PATH; "
+ "install one or set NANOBOT_SKIP_WEBUI_BUILD=1 to bypass."
+ )
+
+ self.app.display_info(f"[webui-build] using {runner} to build webui")
+ self._run([runner, "install"], cwd=webui_dir)
+ self._run([runner, "run", "build"], cwd=webui_dir)
+
+ if not index_html.is_file():
+ raise RuntimeError(
+ f"[webui-build] build finished but {index_html} is missing; "
+ "check webui/vite.config.ts outDir."
+ )
+ self.app.display_info(f"[webui-build] webui ready at {dist_dir}")
+
+ @staticmethod
+ def _pick_runner() -> str | None:
+ for candidate in ("bun", "npm"):
+ if shutil.which(candidate):
+ return candidate
+ return None
+
+ def _run(self, cmd: list[str], *, cwd: Path) -> None:
+ self.app.display_info(f"[webui-build] $ {' '.join(cmd)} (cwd={cwd})")
+ try:
+ subprocess.run(cmd, cwd=cwd, check=True)
+ except subprocess.CalledProcessError as exc:
+ raise RuntimeError(
+ f"[webui-build] command failed ({exc.returncode}): {' '.join(cmd)}"
+ ) from exc
diff --git a/nanobot/__init__.py b/nanobot/__init__.py
index e6fdbf0ba..8ab213a33 100644
--- a/nanobot/__init__.py
+++ b/nanobot/__init__.py
@@ -21,7 +21,7 @@ def _resolve_version() -> str:
return _pkg_version("nanobot-ai")
except PackageNotFoundError:
# Source checkouts often import nanobot without installed dist-info.
- return _read_pyproject_version() or "0.1.5.post3"
+ return _read_pyproject_version() or "0.2.0"
__version__ = _resolve_version()
diff --git a/nanobot/web/__init__.py b/nanobot/web/__init__.py
index 7a08932f6..36ee3e934 100644
--- a/nanobot/web/__init__.py
+++ b/nanobot/web/__init__.py
@@ -1,6 +1,8 @@
"""Embedded web UI assets.
-The ``dist/`` subdirectory is populated by ``cd webui && bun run build`` and
-is shipped in the wheel; it stays empty in source checkouts until that command
-has been run.
+The ``dist/`` subdirectory holds the production WebUI bundle served by the
+gateway. It is shipped inside the published wheel and is rebuilt automatically
+by the ``webui-build`` Hatch hook during ``python -m build``. In an editable
+source checkout it stays empty until you run ``cd webui && bun run build``
+(or use the Vite dev server at ``cd webui && bun run dev``).
"""
diff --git a/pyproject.toml b/pyproject.toml
index 16ed57dd2..eaf57a2ad 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
[project]
name = "nanobot-ai"
-version = "0.1.5.post3"
+version = "0.2.0"
description = "A lightweight personal AI assistant framework"
readme = { file = "README.md", content-type = "text/markdown" }
requires-python = ">=3.11"
@@ -121,12 +121,22 @@ build-backend = "hatchling.build"
[tool.hatch.metadata]
allow-direct-references = true
+[tool.hatch.build.hooks.custom]
+# Implementation lives in the conventional `hatch_build.py` at the repo root.
+
[tool.hatch.build]
include = [
"nanobot/**/*.py",
"nanobot/templates/**/*.md",
"nanobot/skills/**/*.md",
"nanobot/skills/**/*.sh",
+ "nanobot/web/dist/**/*",
+]
+# nanobot/web/dist/ is produced by `cd webui && bun run build` and is
+# git-ignored. List it as an artifact so hatch ships it in both wheel and
+# sdist even though VCS does not track it.
+artifacts = [
+ "nanobot/web/dist/**/*",
]
[tool.hatch.build.targets.wheel]
@@ -141,7 +151,9 @@ packages = ["nanobot"]
[tool.hatch.build.targets.sdist]
include = [
"nanobot/",
+ "nanobot/web/dist/",
"bridge/",
+ "hatch_build.py",
"README.md",
"LICENSE",
"THIRD_PARTY_NOTICES.md",
diff --git a/webui/README.md b/webui/README.md
index b99874ba0..8538bc1ed 100644
--- a/webui/README.md
+++ b/webui/README.md
@@ -8,15 +8,11 @@ on the same port.
For the project overview, install guide, and general docs map, see the root
[`README.md`](../README.md).
-## Current status
+## Just want to use the WebUI?
-> [!NOTE]
-> The standalone WebUI development workflow currently requires a source
-> checkout.
->
-> WebUI changes in the GitHub repository may land before they are included in
-> the next packaged release, so source installs and published package versions
-> are not yet guaranteed to move in lockstep.
+If you installed nanobot via `pip install nanobot-ai`, the WebUI is **already bundled** in the wheel. Enable the WebSocket channel in `~/.nanobot/config.json` and run `nanobot gateway` โ see the root [`README.md`](../README.md#-webui) for the 3-step setup. You do **not** need anything in this directory.
+
+This `webui/` tree is for people **hacking on the WebUI itself** (UI changes, new components, styling, etc.).
## Layout
@@ -25,7 +21,7 @@ webui/ source tree (this directory)
nanobot/web/dist/ build output served by the gateway
```
-## Develop from source
+## Develop the WebUI (Vite HMR)
### 1. Install nanobot from source
@@ -35,6 +31,8 @@ From the repository root:
pip install -e .
```
+> Editable installs intentionally **skip** the WebUI bundle step โ Vite HMR is faster than rebuilding `dist/` on every change.
+
### 2. Enable the WebSocket channel
In `~/.nanobot/config.json`:
@@ -63,8 +61,7 @@ bun run dev
Then open `http://127.0.0.1:5173`.
-By default, the dev server proxies `/api`, `/webui`, `/auth`, and WebSocket
-traffic to `http://127.0.0.1:8765`.
+By default the dev server proxies `/api`, `/webui`, `/auth`, and WebSocket traffic to `http://127.0.0.1:8765`.
If your gateway listens on a non-default port, point the dev server at it:
@@ -74,7 +71,7 @@ NANOBOT_API_URL=http://127.0.0.1:9000 bun run dev
### Access from another device (LAN)
-To use the webui from another device on the same network, set `host` to `"0.0.0.0"` and configure a `token` or `tokenIssueSecret` in `~/.nanobot/config.json`:
+To use the WebUI from another device on the same network, set `host` to `"0.0.0.0"` and configure a `token` or `tokenIssueSecret` in `~/.nanobot/config.json`:
```json
{
@@ -91,20 +88,20 @@ To use the webui from another device on the same network, set `host` to `"0.0.0.
The gateway will refuse to start if `host` is `"0.0.0.0"` and neither `token` nor `tokenIssueSecret` is set.
-Then open `http://