Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 0 additions & 3 deletions .bandit.yaml

This file was deleted.

19 changes: 9 additions & 10 deletions .github/actions/codestyle/action.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name: "codestyle"
description: "Runs python code style checklist (black, flake8, mypy and bandit)"
description: "Runs Python code style checks via ruff (format + lint) and ty (type-check)"
inputs:
workdir:
description: "working directory"
Expand All @@ -16,15 +16,14 @@ runs:
run: |
cd ${{ inputs.workdir }}
uv sync
- name: "Bandit"
- name: "Ruff format (check)"
shell: bash
run: uv run bandit -c .bandit.yaml -r ${{ inputs.workdir }}
- name: "Mypy"
run: uv run ruff format --check ${{ inputs.workdir }}
- name: "Ruff lint"
shell: bash
run: uv run mypy --config-file .mypy.ini ${{ inputs.workdir }}
- name: "Black"
run: uv run ruff check ${{ inputs.workdir }}
- name: "ty (type check)"
shell: bash
run: uv run black --exclude ".venv" --check ${{ inputs.workdir }}
- name: "Flake8"
shell: bash
run: uv run flake8 ${{ inputs.workdir }}
# ty does whole-project inference; the explicit roots match
# `[tool.ty.src]` in pyproject.toml.
run: uv run ty check rogue sdks/python hatch_build.py
24 changes: 24 additions & 0 deletions .github/actions/web-build/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
name: "web-build"
description: "Builds the Vite SPA into packages/web/dist for inclusion in the rogue-ai wheel"
runs:
using: "composite"
steps:
- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
version: 10
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: "20"
cache: "pnpm"
cache-dependency-path: packages/web/pnpm-lock.yaml
- name: Install dependencies
shell: bash
run: cd packages/web && pnpm install --frozen-lockfile
- name: Build SPA
shell: bash
run: cd packages/web && pnpm build
- name: Verify dist exists
shell: bash
run: test -f packages/web/dist/index.html
31 changes: 31 additions & 0 deletions .github/actions/web-codestyle/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
name: "web-codestyle"
description: "Runs prettier --check, eslint, and tsc --noEmit on packages/web"
runs:
using: "composite"
steps:
- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
version: 10
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: "20"
cache: "pnpm"
cache-dependency-path: packages/web/pnpm-lock.yaml
- name: Install dependencies
shell: bash
run: cd packages/web && pnpm install --frozen-lockfile
- name: Prettier
shell: bash
run: cd packages/web && pnpm exec prettier --check .
- name: ESLint
shell: bash
run: cd packages/web && pnpm exec eslint .
- name: TypeScript
shell: bash
# `pnpm typecheck` is `pnpm routes && tsc --noEmit`. The route-tree
# generator (`tsr generate`) is required because src/routeTree.gen.ts
# is gitignored — without it tsc can't resolve `./routeTree.gen` and
# every `createFileRoute(...)` call collapses to `(arg: undefined)`.
run: cd packages/web && pnpm typecheck
7 changes: 3 additions & 4 deletions .github/pull_request_template.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,9 @@

- [ ] I have read the [CONTRIBUTING.md](../CONTRIBUTING.md) guide
- [ ] My code follows the code style of this project (PEP 8, type hints, docstrings)
- [ ] I have run `uv run black .` to format my code
- [ ] I have run `uv run flake8 .` and fixed all issues
- [ ] I have run `uv run mypy --config-file .mypy.ini .` and addressed type checking issues
- [ ] I have run `uv run bandit -c .bandit.yaml -r .` for security checks
- [ ] I have run `uv run ruff format .` to format my code
- [ ] I have run `uv run ruff check --fix .` and fixed all issues (replaces flake8 + isort + bandit)
- [ ] I have run `uv run ty check rogue sdks/python hatch_build.py` and addressed type-check errors
- [ ] I have added tests that prove my fix is effective or that my feature works
- [ ] I have run `uv run pytest` and all tests pass
- [ ] I have manually tested my changes
Expand Down
5 changes: 5 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,13 @@ jobs:
with:
python-version-file: ".python-version"

- name: Build web SPA
uses: ./.github/actions/web-build

- name: Run uv build - main package
uses: ./.github/actions/uv-build
env:
ROGUE_SKIP_WEB_BUILD: "1"

- name: Run uv build - sdk package
uses: ./.github/actions/uv-build
Expand Down
3 changes: 3 additions & 0 deletions .github/workflows/pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,6 @@ jobs:

- name: Go tui codestyle
uses: ./.github/actions/tui-codestyle

- name: Web codestyle
uses: ./.github/actions/web-codestyle
5 changes: 5 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,15 @@ jobs:
with:
python-version-file: ".python-version"

- name: Build web SPA
uses: ./.github/actions/web-build

- name: Run uv build - main package
uses: ./.github/actions/uv-build
with:
dist_dir: "${{ runner.temp }}/dist/rogue-ai"
env:
ROGUE_SKIP_WEB_BUILD: "1"

- name: Run uv build - sdk package
uses: ./.github/actions/uv-build
Expand Down
9 changes: 9 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -93,3 +93,12 @@ bin/

# Go TUI
packages/tui/cmd/rogue/__debug*

# Web UI (Vite SPA)
packages/web/node_modules/
packages/web/dist/
packages/web/.vite/
packages/web/src/api/schema.d.ts
packages/web/src/routeTree.gen.ts
rogue/web_dist/

3 changes: 0 additions & 3 deletions .mypy.ini

This file was deleted.

2 changes: 1 addition & 1 deletion examples/mcp/tshirt_store_langgraph_mcp/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

@click.command()
@click.option("--host", "host", default="127.0.0.1", help="Host to run the server on")
@click.option("--port", "port", default=10001, help="Port to run the server on")
@click.option("--port", "port", default=10002, help="Port to run the server on")
@click.option(
"--transport",
"transport",
Expand Down
2 changes: 1 addition & 1 deletion examples/tshirt_store_agent/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ def print_json_response(response: Any, description: str) -> None:
if hasattr(response, "root"):
print(f"{response.root.model_dump_json(exclude_none=True)}\n")
else:
print(f'{response.model_dump(mode="json", exclude_none=True)}\n')
print(f"{response.model_dump(mode='json', exclude_none=True)}\n")


def build_message(
Expand Down
3 changes: 1 addition & 2 deletions examples/tshirt_store_langgraph_agent/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,2 @@
from . import shirtify_langgraph_agent, shirtify_langgraph_agent_executor
from .shirtify_langgraph_agent import root_agent
from . import shirtify_langgraph_agent
from . import shirtify_langgraph_agent_executor
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from typing import Dict, Any, AsyncIterable, Literal
from typing import Any, AsyncIterable, Dict, Literal

from langchain_core.messages import AIMessage, ToolMessage
from langchain_core.runnables import RunnableConfig
Expand Down
94 changes: 94 additions & 0 deletions hatch_build.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
"""Hatch build hook that ensures the web SPA is built before the wheel ships.

The hook runs ``pnpm install --frozen-lockfile && pnpm build`` inside
``packages/web`` only when the dist is missing or stale. If pnpm is not
installed, the hook degrades gracefully: it warns and continues, so the wheel
build still succeeds for contributors who don't need the web UI. CI is
expected to run ``pnpm --filter web build`` explicitly before ``uv build`` so
the published wheel always carries ``dist/``.
"""

from __future__ import annotations

import os
import shutil
import subprocess # noqa: S404
import sys
from pathlib import Path
from typing import Any

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


class WebBuildHook(BuildHookInterface):
PLUGIN_NAME = "custom"

def initialize(self, version: str, build_data: dict[str, Any]) -> None:
repo_root = Path(self.root)
web_dir = repo_root / "packages" / "web"
dist_dir = web_dir / "dist"
dist_index = dist_dir / "index.html"
src_dir = web_dir / "src"

skip_build = bool(os.environ.get("ROGUE_SKIP_WEB_BUILD"))
if skip_build:
self.app.display_info("ROGUE_SKIP_WEB_BUILD set; skipping web build.")

if not skip_build and web_dir.is_dir():
needs_build = not dist_index.is_file() or _is_stale(dist_index, src_dir)
if needs_build:
pnpm = shutil.which("pnpm")
if pnpm is None:
if dist_index.is_file():
self.app.display_warning(
"pnpm not found; reusing existing packages/web/dist "
"(may be stale).",
)
else:
self.app.display_warning(
"pnpm not found and packages/web/dist is missing. "
"The wheel will not include the web UI. "
"Install Node + pnpm and rebuild, or run "
"`cd packages/web && pnpm install && pnpm build` manually.",
)
else:
self.app.display_info("Building web SPA via pnpm...")
try:
subprocess.run( # noqa: S603
[pnpm, "install", "--frozen-lockfile"],
cwd=str(web_dir),
check=True,
)
subprocess.run( # noqa: S603
[pnpm, "run", "build"],
cwd=str(web_dir),
check=True,
)
except subprocess.CalledProcessError as e:
self.app.display_error(f"pnpm build failed: {e}")
sys.exit(1)

# Force-include the web dist only when it actually exists, so installs
# in environments without pnpm (and without a prebuilt dist) don't fail
# on a missing forced-include path. This replaces the static
# `[tool.hatch.build.targets.*.force-include]` tables in pyproject.toml.
if dist_dir.is_dir():
target = self.target_name
if target == "wheel":
build_data.setdefault("force_include", {})[str(dist_dir)] = (
"rogue/web_dist"
)
elif target == "sdist":
build_data.setdefault("force_include", {})[str(dist_dir)] = (
"packages/web/dist"
)


def _is_stale(dist_index: Path, src_dir: Path) -> bool:
if not src_dir.is_dir():
return False
dist_mtime = dist_index.stat().st_mtime
for path in src_dir.rglob("*"):
if path.is_file() and path.stat().st_mtime > dist_mtime:
return True
return False
49 changes: 30 additions & 19 deletions lefthook.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -23,31 +23,25 @@ pre-commit:
python -c "import yaml; yaml.safe_load(open('$file'))" || exit 1
done

# Python
- name: add-trailing-comma
glob: "*.py"
run: add-trailing-comma {staged_files}

- name: isort
glob: "*.py"
run: isort --profile black {staged_files}

- name: black
# Python — ruff replaces black + flake8 + isort + bandit + add-trailing-comma;
# ty replaces mypy. Both are Astral Rust binaries, an order of magnitude
# faster than the toolchain they superseded.
- name: ruff-format
glob: "*.py"
exclude: ".venv/"
run: black {staged_files}

- name: flake8
glob: "*.py"
run: flake8 {staged_files}
run: uv run ruff format {staged_files}

- name: mypy
- name: ruff-check
glob: "*.py"
run: mypy --config-file .mypy.ini {staged_files}
exclude: ".venv/"
run: uv run ruff check --fix {staged_files}

- name: bandit
- name: ty
glob: "*.py"
run: bandit -c .bandit.yaml -r {staged_files}
exclude: ".venv/"
# ty does whole-project inference; running on the full source roots is
# both faster and more accurate than a per-file invocation.
run: uv run ty check rogue sdks/python hatch_build.py

- name: uv lock check
glob: "pyproject.toml"
Expand All @@ -65,3 +59,20 @@ pre-commit:
- name: go vet
glob: "*.go"
run: (cd packages/tui && go vet ./...)

# Web (TypeScript / React)
# `root:` rebases {staged_files} so they're relative to packages/web,
# which is what prettier/eslint expect when their config lives there.
- name: prettier-web
root: "packages/web/"
glob: "*.{ts,tsx,js,jsx,json,css,md}"
run: pnpm exec prettier --write {staged_files}

- name: eslint-web
root: "packages/web/"
glob: "*.{ts,tsx,js,jsx}"
run: pnpm exec eslint --fix {staged_files}

- name: tsc-web
glob: "packages/web/**/*.{ts,tsx}"
run: (cd packages/web && pnpm exec tsc --noEmit)
Loading