Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,20 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
# Build the React console so the wheel can bundle it as vouch/web/console
# (hatch_build.py force-includes webapp/dist when present).
- uses: actions/setup-node@v4
with:
node-version: "20"
- run: npm --prefix webapp ci && npm --prefix webapp run build
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- run: python -m pip install --upgrade build
- run: python -m build
# sdist is source-only; the wheel is built from the working tree (not the
# sdist) so the freshly-built, gitignored webapp/dist rides inside it.
- run: python -m build --sdist
- run: python -m build --wheel
- uses: actions/upload-artifact@v7
with:
name: dist
Expand Down
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ All notable changes to vouch are documented here. Format follows
## [Unreleased]

### Added
- `vouch console`: serve the vendored React web console straight from the
installed package — a same-origin `/proxy` bridge (loopback-guarded) to
`vouch serve --transport http` backends, reimplementing the vite dev-proxy
in python. the built SPA is bundled into the wheel as `vouch/web/console`
(conditionally, via a hatch build hook), so `pip install 'vouch-kb[web]'`
then `vouch console` needs no node and no repo clone.
- `kb.activity` read method (+ `vouch activity` CLI mirror): audit-log
activity buckets for dashboards — per-day counts with proposal/decision
breakdowns, an hour-of-week matrix, and actor/event histograms. windowed
Expand Down
23 changes: 20 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,16 @@ docker run --rm -p 127.0.0.1:5173:5173 -v vouch-demo-data:/data ghcr.io/plind-ju

Pre-seeded KB + full webapp console, zero setup. Pass `-e ANTHROPIC_API_KEY=sk-ant-...` to enable LLM features.

**For the full UI without Docker** — Python only, no clone, no node:

```bash
pipx install 'vouch-kb[web]' # the browser console ships inside the wheel
vouch serve --transport http --port 8731 & # a backend for the current .vouch/
vouch console # console at http://localhost:5173 — connect it to :8731
```

`vouch console` serves the same React console as the Docker demo, straight from the installed package.

**For CLI + Claude Code integration** (most common ongoing workflow):

```bash
Expand Down Expand Up @@ -93,9 +103,10 @@ compile:
vouch review # walk pending proposals one at a time
```

**Want a browser UI for reviewing and proposing?** The video shows the **vouch webapp** — chat, review queue, claims, and stats. You have three options:
**Want a browser UI for reviewing and proposing?** The video shows the **vouch webapp** — chat, review queue, claims, and stats. You have four options:

- **No setup**: Use the Docker demo (recommended)
- **pip, no clone**: `pipx install 'vouch-kb[web]'` then `vouch console` — serves the same React console from the installed package (Python only, no Docker, no node), open http://localhost:5173
- **Local development**: Clone the repo, run `make console`, open http://localhost:5173
- **CLI-only**: Use `vouch review`, `vouch show <id>`, `vouch approve <id>` commands instead

Expand All @@ -113,7 +124,13 @@ docker run --rm -p 127.0.0.1:5173:5173 \
# then open http://localhost:5173
```

Or to skip Docker entirely and use the CLI tools:
Or serve that same console with no Docker — `vouch console` in place of Terminal 2 (needs the `[web]` extra), then add the `:8731` backend in the connect dialog:

```bash
vouch console # http://localhost:5173, proxying to the server above
```

Or to skip the browser entirely and use the CLI tools:

```bash
vouch review # walk pending proposals
Expand All @@ -122,7 +139,7 @@ vouch approve <id> # approve a proposal
vouch reject <id> --reason "…" # reject with feedback
```

Lighter alternatives ship with vouch itself: `vouch review-ui` (a built-in browser queue; `pipx install 'vouch-kb[web]'` for the extra), or piecemeal `vouch pending`, `vouch show <id>`, `vouch approve <id>`, `vouch reject <id> --reason "…"`.
Both browser UIs ship with vouch under the `[web]` extra (`pipx install 'vouch-kb[web]'`): `vouch console` is the full React console shown in the video; `vouch review-ui` is a lighter built-in review queue. Or go piecemeal: `vouch pending`, `vouch show <id>`, `vouch approve <id>`, `vouch reject <id> --reason "…"`.

**5. Compile the wiki.**

Expand Down
27 changes: 27 additions & 0 deletions hatch_build.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
"""Bundle the built React console (webapp/dist) into the wheel.

When webapp/dist has been built, ship it as ``vouch/web/console`` so
``pip install 'vouch-kb[web]'`` + ``vouch console`` serves it with no node.
When it has NOT been built (a fresh checkout, or the sdist -> wheel path where
the gitignored dist isn't present), skip it silently: the wheel still builds,
and ``vouch console`` reports the missing console cleanly. This is why the
include is a hook rather than a static ``force-include``, which hard-errors on
a missing source path.
"""

from __future__ import annotations

import os
from typing import Any

from hatchling.builders.hooks.plugin.interface import BuildHookInterface


class ConsoleBundleHook(BuildHookInterface):
PLUGIN_NAME = "custom"

def initialize(self, version: str, build_data: dict[str, Any]) -> None:
dist = os.path.join(self.root, "webapp", "dist")
if not os.path.isfile(os.path.join(dist, "index.html")):
return # not built — degrade gracefully rather than fail the build
build_data.setdefault("force_include", {})[dist] = "vouch/web/console"
2 changes: 1 addition & 1 deletion openclaw.plugin.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"id": "vouch",
"name": "Vouch",
"version": "1.2.2",
"version": "1.3.0",
"description": "Git-native, review-gated knowledge base. Registers vouch's context engine (cited retrieval + salience reflex + hot memory) and the vouch skills. The kb.* MCP server is deployment config: `openclaw mcp add vouch -- vouch serve`.",
"kind": "context-engine",
"skills": [
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "vouch",
"version": "1.2.2",
"version": "1.3.0",
"private": true,
"description": "OpenClaw plugin packaging for vouch. The Python package lives in pyproject.toml; this file only tells OpenClaw's plugin loader which entry module to import and which plugin API range the plugin supports.",
"openclaw": {
Expand Down
10 changes: 9 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "vouch-kb"
version = "1.2.2"
version = "1.3.0"
description = "Git-native, review-gated knowledge base for LLM agents. MCP server + CLI."
readme = "README.md"
requires-python = ">=3.11"
Expand Down Expand Up @@ -82,6 +82,14 @@ packages = ["src/vouch"]
[tool.hatch.build.targets.wheel.force-include]
"adapters" = "vouch/adapters"

# The built React console (webapp/dist) rides inside the wheel as
# vouch/web/console so `pip install 'vouch-kb[web]'` + `vouch console` serves it
# with no node. It is force-included *conditionally* by hatch_build.py — a plain
# force-include hard-errors when webapp/dist isn't built (fresh checkout, sdist
# → wheel), whereas the hook skips it and `vouch console` reports it cleanly.
[tool.hatch.build.targets.wheel.hooks.custom]
path = "hatch_build.py"

[tool.ruff]
line-length = 100
target-version = "py311"
Expand Down
2 changes: 1 addition & 1 deletion src/vouch/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@
# One of FOUR version sites kept in lockstep (with pyproject.toml,
# openclaw.plugin.json, package.json) — enforced by
# tests/test_openclaw_plugin_manifest.py::test_manifest_versions_in_step.
__version__ = "1.2.2"
__version__ = "1.3.0"
65 changes: 65 additions & 0 deletions src/vouch/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -3804,6 +3804,71 @@ def _resolve_auth_token(auth: str | None) -> str | None:
return auth


@cli.command(name="console")
@click.option(
"--bind",
"bind",
default="127.0.0.1:5173",
show_default=True,
help="host:port to bind. A non-loopback host (e.g. 0.0.0.0) also "
"requires --allow-remote so the proxy bridge isn't exposed openly.",
)
@click.option(
"--allow-remote",
is_flag=True,
help="Drop the loopback guard on the /proxy bridge. Only for a deployment "
"behind its own auth — a same-origin page could otherwise drive a "
"local reviewer's backends.",
)
@click.option(
"--open-browser/--no-open-browser",
default=True,
show_default=True,
help="Open the browser to the console on startup.",
)
def console(bind: str, allow_remote: bool, open_browser: bool) -> None:
"""Serve the vouch web console (the React review UI) locally.

Ships the built SPA and a same-origin /proxy bridge to your
`vouch serve --transport http` backends — one `pip install 'vouch-kb[web]'`,
no node. Add a backend from the connect dialog in the UI.
"""
from .web import _require_console_deps

try:
_require_console_deps()
except ImportError as exc:
raise click.ClickException(str(exc)) from exc

from .web.console import ConsoleError, resolve_console_dir, serve_console

host, sep, port_raw = bind.partition(":")
if not sep:
raise click.ClickException(f"invalid --bind {bind!r}; expected host:port")
try:
port = int(port_raw)
except ValueError as exc:
raise click.ClickException(f"invalid port in --bind {bind!r}") from exc
host = host or "127.0.0.1"
if host not in ("127.0.0.1", "::1", "localhost") and not allow_remote:
raise click.ClickException(
f"--bind {bind} is non-loopback; pass --allow-remote to expose the "
"proxy bridge (only behind your own auth)."
)

url = f"http://{host}:{port}/"
if open_browser and resolve_console_dir() is not None:
import threading
import webbrowser

threading.Timer(0.6, lambda: webbrowser.open(url)).start()
click.echo(f"vouch console → {url}")
try:
serve_console(host=host, port=port, allow_remote=allow_remote)
except ConsoleError as exc:
raise click.ClickException(str(exc)) from exc


@cli.command(name="review-ui")
@click.option(
"--bind",
Expand Down
20 changes: 20 additions & 0 deletions src/vouch/web/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,26 @@ def _require_web_extra() -> None:
)


def _require_console_deps() -> None:
"""Fail with a clean message if the console's serve deps aren't installed.

The React console needs only starlette (the app) + uvicorn (the server) —
a subset of the [web] extra; jinja2 is not required.
"""
missing: list[str] = []
for name in ("starlette", "uvicorn"):
try:
__import__(name)
except ImportError:
missing.append(name)
if missing:
raise ImportError(
"vouch console needs the [web] extra. "
"Install with: pip install 'vouch-kb[web]' "
f"(missing: {', '.join(missing)})"
)


def create_app( # type: ignore[no-untyped-def]
kb_root: str | None = None,
*,
Expand Down
Loading
Loading