diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..efc0df1 --- /dev/null +++ b/.env.example @@ -0,0 +1,2 @@ +ANGEL_LOG_LEVEL=INFO +ANGEL_STORAGE_PATH= diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml new file mode 100644 index 0000000..45dbb2e --- /dev/null +++ b/.github/workflows/cd.yml @@ -0,0 +1,20 @@ +name: CD + +on: + push: + tags: + - 'v*' + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: '3.10' + - name: Build + run: | + python -m pip install --upgrade pip + pip install build + python -m build diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..7e9bf99 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,27 @@ +name: CI + +on: + pull_request: + push: + branches: [main, develop] + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: '3.10' + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + pip install -r requirements-dev.txt + pip install -e . + - name: Lint + run: ruff check . + - name: Format + run: black --check . + - name: Tests + run: pytest diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 0000000..548fe36 --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,20 @@ +name: Docs + +on: + push: + branches: [main] + +jobs: + docs: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: '3.10' + - name: Install mkdocs + run: | + python -m pip install --upgrade pip + pip install mkdocs-material + - name: Build docs + run: mkdocs build diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..92212b3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,32 @@ +# Python +__pycache__/ +*.py[cod] +*.pyo +*.pyd +*.egg-info/ +.pytest_cache/ +.mypy_cache/ +.ruff_cache/ + +# Virtualenv +.venv/ +venv/ + +# Coverage +.coverage +htmlcov/ + +# IDE +.vscode/ +.idea/ + +# OS +.DS_Store +Thumbs.db + +# Logs +*.log + +# Build +build/ +dist/ diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..f57eb08 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,13 @@ +repos: + - repo: https://github.com/psf/black + rev: 24.2.0 + hooks: + - id: black + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.2.2 + hooks: + - id: ruff + - repo: https://github.com/pre-commit/mirrors-mypy + rev: v1.8.0 + hooks: + - id: mypy diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..fc48691 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 ANGEL Contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..cbb3a43 --- /dev/null +++ b/README.md @@ -0,0 +1,32 @@ +# ANGEL + +ANGEL (Automated Navigator for Gathering and Executing Local tools) is a cross-platform CLI + web console for storing and managing your personal toolkit and repositories. + +## Features +- CLI for tool and repository management +- Local FastAPI web console +- SQLite-backed metadata storage +- Configurable storage paths and settings + +## Quick Start +```bash +python -m venv .venv +source .venv/bin/activate # Windows: .\.venv\Scripts\Activate.ps1 +pip install -r requirements.txt +pip install -e . +angel init +angel web +``` + +## Configuration +Configuration is stored in `~/.config/angel/config.toml` (Linux/macOS) or `%APPDATA%\angel\config.toml` (Windows). Use `angel config` to manage settings. + +## Development +```bash +pip install -r requirements-dev.txt +pre-commit install +pytest +``` + +## License +MIT diff --git a/alembic.ini b/alembic.ini new file mode 100644 index 0000000..3c6e98c --- /dev/null +++ b/alembic.ini @@ -0,0 +1,35 @@ +[alembic] +script_location = migrations +sqlalchemy.url = sqlite:///angel.db + +[loggers] +keys = root,sqlalchemy,alembic + +[handlers] +keys = console + +[formatters] +keys = generic + +[logger_root] +level = WARN +handlers = console + +[logger_sqlalchemy] +level = WARN +handlers = console +qualname = sqlalchemy.engine + +[logger_alembic] +level = INFO +handlers = console +qualname = alembic + +[handler_console] +class = StreamHandler +args = (sys.stderr,) +level = NOTSET +formatter = generic + +[formatter_generic] +format = %(levelname)-5.5s [%(name)s] %(message)s diff --git a/angel_cli/__init__.py b/angel_cli/__init__.py new file mode 100644 index 0000000..7177ab4 --- /dev/null +++ b/angel_cli/__init__.py @@ -0,0 +1,5 @@ +"""ANGEL CLI package.""" + +__all__ = ["__version__"] + +__version__ = "0.1.0" diff --git a/angel_cli/__main__.py b/angel_cli/__main__.py new file mode 100644 index 0000000..26e2254 --- /dev/null +++ b/angel_cli/__main__.py @@ -0,0 +1,5 @@ +"""Entry point for python -m angel_cli.""" +from angel_cli.cli import main + +if __name__ == "__main__": + main() diff --git a/angel_cli/cli.py b/angel_cli/cli.py new file mode 100644 index 0000000..9ff9e22 --- /dev/null +++ b/angel_cli/cli.py @@ -0,0 +1,25 @@ +"""Main CLI entry point for ANGEL.""" +from __future__ import annotations + +import click +from rich.console import Console + +from angel_cli.commands import auth, config, maintenance, repos, tools + +console = Console() + + +@click.group(help="ANGEL CLI - manage tools and repositories.") +@click.option("--config-path", type=click.Path(), help="Custom config path.") +@click.pass_context +def main(ctx: click.Context, config_path: str | None) -> None: + """Root command group.""" + ctx.ensure_object(dict) + ctx.obj["config_path"] = config_path + + +main.add_command(auth.group) +main.add_command(tools.group) +main.add_command(repos.group) +main.add_command(maintenance.group) +main.add_command(config.group) diff --git a/angel_cli/commands/__init__.py b/angel_cli/commands/__init__.py new file mode 100644 index 0000000..9937ab9 --- /dev/null +++ b/angel_cli/commands/__init__.py @@ -0,0 +1 @@ +"""CLI command modules.""" diff --git a/angel_cli/commands/auth.py b/angel_cli/commands/auth.py new file mode 100644 index 0000000..56092c9 --- /dev/null +++ b/angel_cli/commands/auth.py @@ -0,0 +1,28 @@ +"""Authentication commands.""" +import click +from rich.console import Console + +console = Console() + + +@click.group(name="auth", help="Authentication commands.") +def group() -> None: + """Auth command group.""" + + +@group.command("login") +def login() -> None: + """Login to the local instance.""" + console.print("[green]Login flow not yet implemented.[/green]") + + +@group.command("logout") +def logout() -> None: + """Logout from the local instance.""" + console.print("[green]Logout flow not yet implemented.[/green]") + + +@group.command("change-password") +def change_password() -> None: + """Change the current user's password.""" + console.print("[yellow]Password change not yet implemented.[/yellow]") diff --git a/angel_cli/commands/config.py b/angel_cli/commands/config.py new file mode 100644 index 0000000..4f66c82 --- /dev/null +++ b/angel_cli/commands/config.py @@ -0,0 +1,16 @@ +"""Configuration commands.""" +import click +from rich.console import Console + +console = Console() + + +@click.group(name="config", help="Configuration management.") +def group() -> None: + """Config command group.""" + + +@group.command("show") +def show_config() -> None: + """Show current configuration.""" + console.print("[green]Config display not yet implemented.[/green]") diff --git a/angel_cli/commands/maintenance.py b/angel_cli/commands/maintenance.py new file mode 100644 index 0000000..8ae1303 --- /dev/null +++ b/angel_cli/commands/maintenance.py @@ -0,0 +1,16 @@ +"""Maintenance commands.""" +import click +from rich.console import Console + +console = Console() + + +@click.group(name="maintenance", help="Maintenance tasks.") +def group() -> None: + """Maintenance command group.""" + + +@group.command("doctor") +def doctor() -> None: + """Run a health check.""" + console.print("[green]All checks passed (stub).[/green]") diff --git a/angel_cli/commands/repos.py b/angel_cli/commands/repos.py new file mode 100644 index 0000000..d625c7a --- /dev/null +++ b/angel_cli/commands/repos.py @@ -0,0 +1,16 @@ +"""Repository commands.""" +import click +from rich.console import Console + +console = Console() + + +@click.group(name="repos", help="Manage repositories.") +def group() -> None: + """Repos command group.""" + + +@group.command("list") +def list_repos() -> None: + """List tracked repositories.""" + console.print("[green]No repositories available (stub).[/green]") diff --git a/angel_cli/commands/tools.py b/angel_cli/commands/tools.py new file mode 100644 index 0000000..0a1931d --- /dev/null +++ b/angel_cli/commands/tools.py @@ -0,0 +1,23 @@ +"""Tool management commands.""" +import click +from rich.console import Console + +console = Console() + + +@click.group(name="tools", help="Manage tools.") +def group() -> None: + """Tools command group.""" + + +@group.command("list") +def list_tools() -> None: + """List stored tools.""" + console.print("[green]No tools available (stub).[/green]") + + +@group.command("add") +@click.argument("path", required=False) +def add_tool(path: str | None) -> None: + """Add a tool from a file path.""" + console.print(f"[green]Add tool stub: {path}[/green]") diff --git a/angel_cli/core/__init__.py b/angel_cli/core/__init__.py new file mode 100644 index 0000000..b0b929e --- /dev/null +++ b/angel_cli/core/__init__.py @@ -0,0 +1 @@ +"""Core utilities for ANGEL.""" diff --git a/angel_cli/core/auth.py b/angel_cli/core/auth.py new file mode 100644 index 0000000..3f00a49 --- /dev/null +++ b/angel_cli/core/auth.py @@ -0,0 +1,14 @@ +"""Authentication utilities.""" +from passlib.context import CryptContext + +pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto") + + +def hash_password(password: str) -> str: + """Hash a password using bcrypt.""" + return pwd_context.hash(password) + + +def verify_password(password: str, hashed: str) -> bool: + """Verify a password against a hash.""" + return pwd_context.verify(password, hashed) diff --git a/angel_cli/core/config.py b/angel_cli/core/config.py new file mode 100644 index 0000000..304e916 --- /dev/null +++ b/angel_cli/core/config.py @@ -0,0 +1,33 @@ +"""Configuration management for ANGEL.""" +from __future__ import annotations + +from dataclasses import dataclass +from pathlib import Path +import os + + +@dataclass(frozen=True) +class AngelConfig: + """Runtime configuration settings.""" + + storage_path: Path + database_path: Path + log_level: str = "INFO" + + +def default_config_path() -> Path: + """Return the default config path for the current platform.""" + if os.name == "nt": + base = Path(os.environ.get("APPDATA", Path.home() / "AppData" / "Roaming")) + else: + base = Path(os.environ.get("XDG_CONFIG_HOME", Path.home() / ".config")) + return base / "angel" / "config.toml" + + +def load_config(config_path: Path | None = None) -> AngelConfig: + """Load configuration with defaults.""" + if config_path is None: + config_path = default_config_path() + storage_path = Path.home() / ".angel" / "storage" + database_path = storage_path / "angel.db" + return AngelConfig(storage_path=storage_path, database_path=database_path) diff --git a/angel_cli/core/database.py b/angel_cli/core/database.py new file mode 100644 index 0000000..47103c9 --- /dev/null +++ b/angel_cli/core/database.py @@ -0,0 +1,13 @@ +"""Database session management.""" +from __future__ import annotations + +from sqlalchemy import create_engine +from sqlalchemy.orm import sessionmaker + +from angel_cli.core.config import AngelConfig + + +def create_session_factory(config: AngelConfig) -> sessionmaker: + """Create a SQLAlchemy session factory.""" + engine = create_engine(f"sqlite:///{config.database_path}") + return sessionmaker(bind=engine) diff --git a/angel_cli/core/storage.py b/angel_cli/core/storage.py new file mode 100644 index 0000000..0f9d6b0 --- /dev/null +++ b/angel_cli/core/storage.py @@ -0,0 +1,14 @@ +"""File storage utilities.""" +from __future__ import annotations + +from pathlib import Path +import hashlib + + +def calculate_sha256(file_path: Path) -> str: + """Calculate SHA-256 hash for a file.""" + hash_obj = hashlib.sha256() + with file_path.open("rb") as handle: + for chunk in iter(lambda: handle.read(8192), b""): + hash_obj.update(chunk) + return hash_obj.hexdigest() diff --git a/angel_cli/core/utils.py b/angel_cli/core/utils.py new file mode 100644 index 0000000..72668e5 --- /dev/null +++ b/angel_cli/core/utils.py @@ -0,0 +1,9 @@ +"""Utility functions.""" +from __future__ import annotations + +from pathlib import Path + + +def ensure_directory(path: Path) -> None: + """Ensure a directory exists.""" + path.mkdir(parents=True, exist_ok=True) diff --git a/angel_cli/models/__init__.py b/angel_cli/models/__init__.py new file mode 100644 index 0000000..ff9c3ed --- /dev/null +++ b/angel_cli/models/__init__.py @@ -0,0 +1 @@ +"""Database models for ANGEL.""" diff --git a/angel_cli/models/audit.py b/angel_cli/models/audit.py new file mode 100644 index 0000000..c1bb570 --- /dev/null +++ b/angel_cli/models/audit.py @@ -0,0 +1,19 @@ +"""Audit log model.""" +from __future__ import annotations + +from datetime import datetime +from sqlalchemy import DateTime, String +from sqlalchemy.orm import Mapped, mapped_column + +from angel_cli.models.base import Base + + +class AuditLog(Base): + """Audit log entries.""" + + __tablename__ = "audit_logs" + + id: Mapped[str] = mapped_column(String, primary_key=True) + action: Mapped[str] = mapped_column(String) + resource_type: Mapped[str] = mapped_column(String) + created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow) diff --git a/angel_cli/models/base.py b/angel_cli/models/base.py new file mode 100644 index 0000000..f455b41 --- /dev/null +++ b/angel_cli/models/base.py @@ -0,0 +1,6 @@ +"""Base model definitions.""" +from sqlalchemy.orm import DeclarativeBase + + +class Base(DeclarativeBase): + """Base class for all models.""" diff --git a/angel_cli/models/repository.py b/angel_cli/models/repository.py new file mode 100644 index 0000000..d22c3a5 --- /dev/null +++ b/angel_cli/models/repository.py @@ -0,0 +1,19 @@ +"""Repository model.""" +from __future__ import annotations + +from datetime import datetime +from sqlalchemy import DateTime, String +from sqlalchemy.orm import Mapped, mapped_column + +from angel_cli.models.base import Base + + +class Repository(Base): + """Tracked repository metadata.""" + + __tablename__ = "repositories" + + id: Mapped[str] = mapped_column(String, primary_key=True) + name: Mapped[str] = mapped_column(String) + url: Mapped[str] = mapped_column(String, unique=True) + created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow) diff --git a/angel_cli/models/tool.py b/angel_cli/models/tool.py new file mode 100644 index 0000000..62bc8b9 --- /dev/null +++ b/angel_cli/models/tool.py @@ -0,0 +1,22 @@ +"""Tool model.""" +from __future__ import annotations + +from datetime import datetime +from sqlalchemy import DateTime, Integer, String +from sqlalchemy.orm import Mapped, mapped_column + +from angel_cli.models.base import Base + + +class Tool(Base): + """Stored tool metadata.""" + + __tablename__ = "tools" + + id: Mapped[str] = mapped_column(String, primary_key=True) + name: Mapped[str] = mapped_column(String, index=True) + version: Mapped[str] = mapped_column(String) + file_path: Mapped[str] = mapped_column(String) + file_size: Mapped[int] = mapped_column(Integer) + file_hash: Mapped[str] = mapped_column(String) + created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow) diff --git a/angel_cli/models/user.py b/angel_cli/models/user.py new file mode 100644 index 0000000..ebb3415 --- /dev/null +++ b/angel_cli/models/user.py @@ -0,0 +1,20 @@ +"""User model.""" +from __future__ import annotations + +from datetime import datetime +from sqlalchemy import Boolean, DateTime, String +from sqlalchemy.orm import Mapped, mapped_column + +from angel_cli.models.base import Base + + +class User(Base): + """User accounts.""" + + __tablename__ = "users" + + id: Mapped[str] = mapped_column(String, primary_key=True) + username: Mapped[str] = mapped_column(String, unique=True, index=True) + password_hash: Mapped[str] = mapped_column(String) + is_admin: Mapped[bool] = mapped_column(Boolean, default=False) + created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow) diff --git a/angel_cli/services/__init__.py b/angel_cli/services/__init__.py new file mode 100644 index 0000000..ba9e100 --- /dev/null +++ b/angel_cli/services/__init__.py @@ -0,0 +1 @@ +"""Service layer for ANGEL.""" diff --git a/angel_cli/services/backup_service.py b/angel_cli/services/backup_service.py new file mode 100644 index 0000000..db7785c --- /dev/null +++ b/angel_cli/services/backup_service.py @@ -0,0 +1,12 @@ +"""Backup service layer.""" +from __future__ import annotations + +from pathlib import Path + + +def create_backup(destination: Path) -> Path: + """Create a backup archive (stub).""" + destination.mkdir(parents=True, exist_ok=True) + backup_file = destination / "backup.zip" + backup_file.write_text("backup placeholder") + return backup_file diff --git a/angel_cli/services/repo_service.py b/angel_cli/services/repo_service.py new file mode 100644 index 0000000..cf7fade --- /dev/null +++ b/angel_cli/services/repo_service.py @@ -0,0 +1,12 @@ +"""Repository service layer.""" +from __future__ import annotations + +from dataclasses import dataclass + + +@dataclass +class RepoMetadata: + """Repository metadata container.""" + + name: str + url: str diff --git a/angel_cli/services/search_service.py b/angel_cli/services/search_service.py new file mode 100644 index 0000000..78a47ad --- /dev/null +++ b/angel_cli/services/search_service.py @@ -0,0 +1,7 @@ +"""Search service layer.""" +from __future__ import annotations + + +def search(query: str) -> list[str]: + """Perform a search (stub).""" + return [query] diff --git a/angel_cli/services/tool_service.py b/angel_cli/services/tool_service.py new file mode 100644 index 0000000..4ab251e --- /dev/null +++ b/angel_cli/services/tool_service.py @@ -0,0 +1,27 @@ +"""Tool service layer.""" +from __future__ import annotations + +from dataclasses import dataclass +from pathlib import Path + +from angel_cli.core.storage import calculate_sha256 + + +@dataclass +class ToolMetadata: + """Simple tool metadata container.""" + + name: str + version: str + file_path: Path + file_hash: str + + +def build_metadata(path: Path, name: str, version: str) -> ToolMetadata: + """Build metadata for a tool file.""" + return ToolMetadata( + name=name, + version=version, + file_path=path, + file_hash=calculate_sha256(path), + ) diff --git a/angel_web/__init__.py b/angel_web/__init__.py new file mode 100644 index 0000000..3dd7ca5 --- /dev/null +++ b/angel_web/__init__.py @@ -0,0 +1 @@ +"""ANGEL web package.""" diff --git a/angel_web/api/__init__.py b/angel_web/api/__init__.py new file mode 100644 index 0000000..dff53e5 --- /dev/null +++ b/angel_web/api/__init__.py @@ -0,0 +1 @@ +"""API package.""" diff --git a/angel_web/api/routes/__init__.py b/angel_web/api/routes/__init__.py new file mode 100644 index 0000000..1ce04c3 --- /dev/null +++ b/angel_web/api/routes/__init__.py @@ -0,0 +1 @@ +"""API routes.""" diff --git a/angel_web/api/routes/auth.py b/angel_web/api/routes/auth.py new file mode 100644 index 0000000..ce290a0 --- /dev/null +++ b/angel_web/api/routes/auth.py @@ -0,0 +1,10 @@ +"""Auth API routes.""" +from fastapi import APIRouter + +router = APIRouter() + + +@router.post("/login") +def login() -> dict[str, str]: + """Login endpoint stub.""" + return {"status": "ok"} diff --git a/angel_web/api/routes/repos.py b/angel_web/api/routes/repos.py new file mode 100644 index 0000000..ec5db03 --- /dev/null +++ b/angel_web/api/routes/repos.py @@ -0,0 +1,10 @@ +"""Repository API routes.""" +from fastapi import APIRouter + +router = APIRouter() + + +@router.get("") +def list_repos() -> list[dict[str, str]]: + """List repositories stub.""" + return [] diff --git a/angel_web/api/routes/search.py b/angel_web/api/routes/search.py new file mode 100644 index 0000000..c5c95dc --- /dev/null +++ b/angel_web/api/routes/search.py @@ -0,0 +1,10 @@ +"""Search API routes.""" +from fastapi import APIRouter + +router = APIRouter() + + +@router.get("") +def search(q: str) -> dict[str, str]: + """Search stub.""" + return {"query": q} diff --git a/angel_web/api/routes/system.py b/angel_web/api/routes/system.py new file mode 100644 index 0000000..d89a14e --- /dev/null +++ b/angel_web/api/routes/system.py @@ -0,0 +1,10 @@ +"""System API routes.""" +from fastapi import APIRouter + +router = APIRouter() + + +@router.get("/health") +def health() -> dict[str, str]: + """Health check stub.""" + return {"status": "ok"} diff --git a/angel_web/api/routes/tools.py b/angel_web/api/routes/tools.py new file mode 100644 index 0000000..8d03e28 --- /dev/null +++ b/angel_web/api/routes/tools.py @@ -0,0 +1,10 @@ +"""Tool API routes.""" +from fastapi import APIRouter + +router = APIRouter() + + +@router.get("") +def list_tools() -> list[dict[str, str]]: + """List tools stub.""" + return [] diff --git a/angel_web/api/schemas/__init__.py b/angel_web/api/schemas/__init__.py new file mode 100644 index 0000000..34faa18 --- /dev/null +++ b/angel_web/api/schemas/__init__.py @@ -0,0 +1 @@ +"""API schemas.""" diff --git a/angel_web/api/schemas/repository.py b/angel_web/api/schemas/repository.py new file mode 100644 index 0000000..9e976b2 --- /dev/null +++ b/angel_web/api/schemas/repository.py @@ -0,0 +1,10 @@ +"""Repository schemas.""" +from pydantic import BaseModel + + +class RepositoryOut(BaseModel): + """Repository output schema.""" + + id: str + name: str + url: str diff --git a/angel_web/api/schemas/tool.py b/angel_web/api/schemas/tool.py new file mode 100644 index 0000000..7fd0b6b --- /dev/null +++ b/angel_web/api/schemas/tool.py @@ -0,0 +1,10 @@ +"""Tool schemas.""" +from pydantic import BaseModel + + +class ToolOut(BaseModel): + """Tool output schema.""" + + id: str + name: str + version: str diff --git a/angel_web/api/schemas/user.py b/angel_web/api/schemas/user.py new file mode 100644 index 0000000..a232ecd --- /dev/null +++ b/angel_web/api/schemas/user.py @@ -0,0 +1,9 @@ +"""User schemas.""" +from pydantic import BaseModel + + +class UserOut(BaseModel): + """User output schema.""" + + id: str + username: str diff --git a/angel_web/app.py b/angel_web/app.py new file mode 100644 index 0000000..3f384e2 --- /dev/null +++ b/angel_web/app.py @@ -0,0 +1,32 @@ +"""FastAPI application factory.""" +from __future__ import annotations + +from fastapi import FastAPI +from fastapi.responses import HTMLResponse +from fastapi.staticfiles import StaticFiles +from fastapi.templating import Jinja2Templates +from starlette.requests import Request + +from angel_web.api.routes import auth, repos, search, system, tools + + +def create_app() -> FastAPI: + """Create and configure the FastAPI application.""" + app = FastAPI(title="ANGEL", version="0.1.0") + app.mount("/static", StaticFiles(directory="angel_web/static"), name="static") + templates = Jinja2Templates(directory="angel_web/templates") + + @app.get("/", response_class=HTMLResponse) + def dashboard(request: Request) -> HTMLResponse: + return templates.TemplateResponse("dashboard.html", {"request": request}) + + app.include_router(auth.router, prefix="/api/auth") + app.include_router(tools.router, prefix="/api/tools") + app.include_router(repos.router, prefix="/api/repos") + app.include_router(search.router, prefix="/api/search") + app.include_router(system.router, prefix="/api/system") + + return app + + +app = create_app() diff --git a/angel_web/static/css/custom.css b/angel_web/static/css/custom.css new file mode 100644 index 0000000..65597b6 --- /dev/null +++ b/angel_web/static/css/custom.css @@ -0,0 +1 @@ +/* Custom styles placeholder */ diff --git a/angel_web/static/js/alpine-components.js b/angel_web/static/js/alpine-components.js new file mode 100644 index 0000000..f7f089d --- /dev/null +++ b/angel_web/static/js/alpine-components.js @@ -0,0 +1 @@ +"""Alpine.js components placeholder.""" diff --git a/angel_web/static/js/app.js b/angel_web/static/js/app.js new file mode 100644 index 0000000..7c43339 --- /dev/null +++ b/angel_web/static/js/app.js @@ -0,0 +1,2 @@ +"""Frontend JavaScript placeholder.""" +console.log("ANGEL web console loaded"); diff --git a/angel_web/templates/base.html b/angel_web/templates/base.html new file mode 100644 index 0000000..239bb4f --- /dev/null +++ b/angel_web/templates/base.html @@ -0,0 +1,24 @@ + + +
+ + +Total Tools
+0
+Repositories
+0
+Storage Used
+0 GB
+