From 1f1eb691cd16179bfac3b784d9f258e056df28f7 Mon Sep 17 00:00:00 2001 From: Nipun Darshana Date: Thu, 29 Jan 2026 09:30:13 +0530 Subject: [PATCH 1/3] chore: drop rich dependency --- .env.example | 2 + .github/workflows/cd.yml | 20 +++++ .github/workflows/ci.yml | 27 +++++++ .github/workflows/docs.yml | 20 +++++ .gitignore | 32 ++++++++ .pre-commit-config.yaml | 13 ++++ LICENSE | 21 ++++++ README.md | 32 ++++++++ alembic.ini | 35 +++++++++ angel_cli/__init__.py | 5 ++ angel_cli/__main__.py | 5 ++ angel_cli/cli.py | 21 ++++++ angel_cli/commands/__init__.py | 1 + angel_cli/commands/auth.py | 25 ++++++ angel_cli/commands/config.py | 13 ++++ angel_cli/commands/maintenance.py | 13 ++++ angel_cli/commands/repos.py | 13 ++++ angel_cli/commands/tools.py | 20 +++++ angel_cli/core/__init__.py | 1 + angel_cli/core/auth.py | 14 ++++ angel_cli/core/config.py | 33 ++++++++ angel_cli/core/database.py | 13 ++++ angel_cli/core/storage.py | 14 ++++ angel_cli/core/utils.py | 9 +++ angel_cli/models/__init__.py | 1 + angel_cli/models/audit.py | 19 +++++ angel_cli/models/base.py | 6 ++ angel_cli/models/repository.py | 19 +++++ angel_cli/models/tool.py | 22 ++++++ angel_cli/models/user.py | 20 +++++ angel_cli/services/__init__.py | 1 + angel_cli/services/backup_service.py | 12 +++ angel_cli/services/repo_service.py | 12 +++ angel_cli/services/search_service.py | 7 ++ angel_cli/services/tool_service.py | 27 +++++++ angel_web/__init__.py | 1 + angel_web/api/__init__.py | 1 + angel_web/api/routes/__init__.py | 1 + angel_web/api/routes/auth.py | 10 +++ angel_web/api/routes/repos.py | 10 +++ angel_web/api/routes/search.py | 10 +++ angel_web/api/routes/system.py | 10 +++ angel_web/api/routes/tools.py | 10 +++ angel_web/api/schemas/__init__.py | 1 + angel_web/api/schemas/repository.py | 10 +++ angel_web/api/schemas/tool.py | 10 +++ angel_web/api/schemas/user.py | 9 +++ angel_web/app.py | 32 ++++++++ angel_web/static/css/custom.css | 1 + angel_web/static/js/alpine-components.js | 1 + angel_web/static/js/app.js | 2 + angel_web/templates/base.html | 24 ++++++ angel_web/templates/dashboard.html | 25 ++++++ docs/api-reference.md | 3 + docs/cli-reference.md | 3 + docs/configuration.md | 3 + docs/index.md | 3 + docs/installation.md | 7 ++ docs/quickstart.md | 6 ++ migrations/env.py | 41 ++++++++++ migrations/versions/001_initial_schema.py | 57 ++++++++++++++ mkdocs.yml | 12 +++ pyproject.toml | 62 +++++++++++++++ requirements-dev.txt | 8 ++ requirements.txt | 11 +++ scripts/build_docs.sh | 3 + scripts/dev_setup.sh | 7 ++ scripts/run_tests.sh | 3 + setup-angel.ps1 | 92 +++++++++++++++++++++++ setup-angel.sh | 29 +++++++ tests/conftest.py | 1 + tests/integration/test_health.py | 12 +++ tests/unit/test_auth.py | 8 ++ 73 files changed, 1087 insertions(+) create mode 100644 .env.example create mode 100644 .github/workflows/cd.yml create mode 100644 .github/workflows/ci.yml create mode 100644 .github/workflows/docs.yml create mode 100644 .gitignore create mode 100644 .pre-commit-config.yaml create mode 100644 LICENSE create mode 100644 README.md create mode 100644 alembic.ini create mode 100644 angel_cli/__init__.py create mode 100644 angel_cli/__main__.py create mode 100644 angel_cli/cli.py create mode 100644 angel_cli/commands/__init__.py create mode 100644 angel_cli/commands/auth.py create mode 100644 angel_cli/commands/config.py create mode 100644 angel_cli/commands/maintenance.py create mode 100644 angel_cli/commands/repos.py create mode 100644 angel_cli/commands/tools.py create mode 100644 angel_cli/core/__init__.py create mode 100644 angel_cli/core/auth.py create mode 100644 angel_cli/core/config.py create mode 100644 angel_cli/core/database.py create mode 100644 angel_cli/core/storage.py create mode 100644 angel_cli/core/utils.py create mode 100644 angel_cli/models/__init__.py create mode 100644 angel_cli/models/audit.py create mode 100644 angel_cli/models/base.py create mode 100644 angel_cli/models/repository.py create mode 100644 angel_cli/models/tool.py create mode 100644 angel_cli/models/user.py create mode 100644 angel_cli/services/__init__.py create mode 100644 angel_cli/services/backup_service.py create mode 100644 angel_cli/services/repo_service.py create mode 100644 angel_cli/services/search_service.py create mode 100644 angel_cli/services/tool_service.py create mode 100644 angel_web/__init__.py create mode 100644 angel_web/api/__init__.py create mode 100644 angel_web/api/routes/__init__.py create mode 100644 angel_web/api/routes/auth.py create mode 100644 angel_web/api/routes/repos.py create mode 100644 angel_web/api/routes/search.py create mode 100644 angel_web/api/routes/system.py create mode 100644 angel_web/api/routes/tools.py create mode 100644 angel_web/api/schemas/__init__.py create mode 100644 angel_web/api/schemas/repository.py create mode 100644 angel_web/api/schemas/tool.py create mode 100644 angel_web/api/schemas/user.py create mode 100644 angel_web/app.py create mode 100644 angel_web/static/css/custom.css create mode 100644 angel_web/static/js/alpine-components.js create mode 100644 angel_web/static/js/app.js create mode 100644 angel_web/templates/base.html create mode 100644 angel_web/templates/dashboard.html create mode 100644 docs/api-reference.md create mode 100644 docs/cli-reference.md create mode 100644 docs/configuration.md create mode 100644 docs/index.md create mode 100644 docs/installation.md create mode 100644 docs/quickstart.md create mode 100644 migrations/env.py create mode 100644 migrations/versions/001_initial_schema.py create mode 100644 mkdocs.yml create mode 100644 pyproject.toml create mode 100644 requirements-dev.txt create mode 100644 requirements.txt create mode 100755 scripts/build_docs.sh create mode 100755 scripts/dev_setup.sh create mode 100755 scripts/run_tests.sh create mode 100644 setup-angel.ps1 create mode 100755 setup-angel.sh create mode 100644 tests/conftest.py create mode 100644 tests/integration/test_health.py create mode 100644 tests/unit/test_auth.py 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..238aed2 --- /dev/null +++ b/angel_cli/cli.py @@ -0,0 +1,21 @@ +"""Main CLI entry point for ANGEL.""" +from __future__ import annotations + +import click + +from angel_cli.commands import auth, config, maintenance, repos, tools + +@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..a1be8dc --- /dev/null +++ b/angel_cli/commands/auth.py @@ -0,0 +1,25 @@ +"""Authentication commands.""" +import click + + +@click.group(name="auth", help="Authentication commands.") +def group() -> None: + """Auth command group.""" + + +@group.command("login") +def login() -> None: + """Login to the local instance.""" + click.echo("Login flow not yet implemented.") + + +@group.command("logout") +def logout() -> None: + """Logout from the local instance.""" + click.echo("Logout flow not yet implemented.") + + +@group.command("change-password") +def change_password() -> None: + """Change the current user's password.""" + click.echo("Password change not yet implemented.") diff --git a/angel_cli/commands/config.py b/angel_cli/commands/config.py new file mode 100644 index 0000000..fbc94b9 --- /dev/null +++ b/angel_cli/commands/config.py @@ -0,0 +1,13 @@ +"""Configuration commands.""" +import click + + +@click.group(name="config", help="Configuration management.") +def group() -> None: + """Config command group.""" + + +@group.command("show") +def show_config() -> None: + """Show current configuration.""" + click.echo("Config display not yet implemented.") diff --git a/angel_cli/commands/maintenance.py b/angel_cli/commands/maintenance.py new file mode 100644 index 0000000..3f3fe5d --- /dev/null +++ b/angel_cli/commands/maintenance.py @@ -0,0 +1,13 @@ +"""Maintenance commands.""" +import click + + +@click.group(name="maintenance", help="Maintenance tasks.") +def group() -> None: + """Maintenance command group.""" + + +@group.command("doctor") +def doctor() -> None: + """Run a health check.""" + click.echo("All checks passed (stub).") diff --git a/angel_cli/commands/repos.py b/angel_cli/commands/repos.py new file mode 100644 index 0000000..3209030 --- /dev/null +++ b/angel_cli/commands/repos.py @@ -0,0 +1,13 @@ +"""Repository commands.""" +import click + + +@click.group(name="repos", help="Manage repositories.") +def group() -> None: + """Repos command group.""" + + +@group.command("list") +def list_repos() -> None: + """List tracked repositories.""" + click.echo("No repositories available (stub).") diff --git a/angel_cli/commands/tools.py b/angel_cli/commands/tools.py new file mode 100644 index 0000000..0d4163c --- /dev/null +++ b/angel_cli/commands/tools.py @@ -0,0 +1,20 @@ +"""Tool management commands.""" +import click + + +@click.group(name="tools", help="Manage tools.") +def group() -> None: + """Tools command group.""" + + +@group.command("list") +def list_tools() -> None: + """List stored tools.""" + click.echo("No tools available (stub).") + + +@group.command("add") +@click.argument("path", required=False) +def add_tool(path: str | None) -> None: + """Add a tool from a file path.""" + click.echo(f"Add tool stub: {path}") 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 @@ + + + + + + ANGEL + + + +
+
+
+

ANGEL Console

+ +
+
+
+ {% block content %}{% endblock %} +
+
+ + diff --git a/angel_web/templates/dashboard.html b/angel_web/templates/dashboard.html new file mode 100644 index 0000000..3c3dd07 --- /dev/null +++ b/angel_web/templates/dashboard.html @@ -0,0 +1,25 @@ +{% extends "base.html" %} +{% block content %} +
+
+

Total Tools

+

0

+
+
+

Repositories

+

0

+
+
+

Storage Used

+

0 GB

+
+
+ +
+

Quick Actions

+
+ + +
+
+{% endblock %} diff --git a/docs/api-reference.md b/docs/api-reference.md new file mode 100644 index 0000000..204d5f7 --- /dev/null +++ b/docs/api-reference.md @@ -0,0 +1,3 @@ +# API Reference + +Open `/docs` in the web console for Swagger UI. diff --git a/docs/cli-reference.md b/docs/cli-reference.md new file mode 100644 index 0000000..dd152c1 --- /dev/null +++ b/docs/cli-reference.md @@ -0,0 +1,3 @@ +# CLI Reference + +Use `angel --help` for full CLI usage. diff --git a/docs/configuration.md b/docs/configuration.md new file mode 100644 index 0000000..68a9815 --- /dev/null +++ b/docs/configuration.md @@ -0,0 +1,3 @@ +# Configuration + +Configuration is stored in the platform config directory. diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000..481825b --- /dev/null +++ b/docs/index.md @@ -0,0 +1,3 @@ +# ANGEL Documentation + +Welcome to ANGEL, the Automated Navigator for Gathering and Executing Local tools. diff --git a/docs/installation.md b/docs/installation.md new file mode 100644 index 0000000..e0e301d --- /dev/null +++ b/docs/installation.md @@ -0,0 +1,7 @@ +# Installation + +Install with pip: + +```bash +pip install -e . +``` diff --git a/docs/quickstart.md b/docs/quickstart.md new file mode 100644 index 0000000..9f20044 --- /dev/null +++ b/docs/quickstart.md @@ -0,0 +1,6 @@ +# Quickstart + +```bash +angel init +angel web +``` diff --git a/migrations/env.py b/migrations/env.py new file mode 100644 index 0000000..c00a790 --- /dev/null +++ b/migrations/env.py @@ -0,0 +1,41 @@ +"""Alembic environment configuration.""" +from __future__ import annotations + +from logging.config import fileConfig + +from sqlalchemy import engine_from_config, pool +from alembic import context + +from angel_cli.models.base import Base + +config = context.config + +if config.config_file_name is not None: + fileConfig(config.config_file_name) + +target_metadata = Base.metadata + + +def run_migrations_offline() -> None: + url = config.get_main_option("sqlalchemy.url") + context.configure(url=url, target_metadata=target_metadata, literal_binds=True) + with context.begin_transaction(): + context.run_migrations() + + +def run_migrations_online() -> None: + connectable = engine_from_config( + config.get_section(config.config_ini_section), + prefix="sqlalchemy.", + poolclass=pool.NullPool, + ) + with connectable.connect() as connection: + context.configure(connection=connection, target_metadata=target_metadata) + with context.begin_transaction(): + context.run_migrations() + + +if context.is_offline_mode(): + run_migrations_offline() +else: + run_migrations_online() diff --git a/migrations/versions/001_initial_schema.py b/migrations/versions/001_initial_schema.py new file mode 100644 index 0000000..7ca2cc0 --- /dev/null +++ b/migrations/versions/001_initial_schema.py @@ -0,0 +1,57 @@ +"""Initial schema. + +Revision ID: 001 +Revises: +Create Date: 2024-01-01 00:00:00.000000 +""" +from __future__ import annotations + +from alembic import op +import sqlalchemy as sa + +revision = "001" +down_revision = None +branch_labels = None +depends_on = None + + +def upgrade() -> None: + op.create_table( + "users", + sa.Column("id", sa.String(), primary_key=True), + sa.Column("username", sa.String(), nullable=False), + sa.Column("password_hash", sa.String(), nullable=False), + sa.Column("is_admin", sa.Boolean(), default=False), + sa.Column("created_at", sa.DateTime(), nullable=False), + ) + op.create_table( + "tools", + sa.Column("id", sa.String(), primary_key=True), + sa.Column("name", sa.String(), nullable=False), + sa.Column("version", sa.String(), nullable=False), + sa.Column("file_path", sa.String(), nullable=False), + sa.Column("file_size", sa.Integer(), nullable=False), + sa.Column("file_hash", sa.String(), nullable=False), + sa.Column("created_at", sa.DateTime(), nullable=False), + ) + op.create_table( + "repositories", + sa.Column("id", sa.String(), primary_key=True), + sa.Column("name", sa.String(), nullable=False), + sa.Column("url", sa.String(), nullable=False), + sa.Column("created_at", sa.DateTime(), nullable=False), + ) + op.create_table( + "audit_logs", + sa.Column("id", sa.String(), primary_key=True), + sa.Column("action", sa.String(), nullable=False), + sa.Column("resource_type", sa.String(), nullable=False), + sa.Column("created_at", sa.DateTime(), nullable=False), + ) + + +def downgrade() -> None: + op.drop_table("audit_logs") + op.drop_table("repositories") + op.drop_table("tools") + op.drop_table("users") diff --git a/mkdocs.yml b/mkdocs.yml new file mode 100644 index 0000000..8537484 --- /dev/null +++ b/mkdocs.yml @@ -0,0 +1,12 @@ +site_name: ANGEL Documentation +site_description: Documentation for ANGEL Toolkit Manager +repo_url: https://github.com/nipudarsh/Angel +nav: + - Home: index.md + - Installation: installation.md + - Quickstart: quickstart.md + - CLI Reference: cli-reference.md + - API Reference: api-reference.md + - Configuration: configuration.md +theme: + name: material diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..0d988db --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,62 @@ +[build-system] +requires = ["hatchling>=1.24.2"] +build-backend = "hatchling.build" + +[project] +name = "angel-toolkit" +version = "0.1.0" +description = "ANGEL: Automated Navigator for Gathering and Executing Local tools" +readme = "README.md" +requires-python = ">=3.10" +license = { text = "MIT" } +authors = [{ name = "ANGEL Contributors" }] +keywords = ["cli", "tooling", "fastapi", "tool-manager"] +classifiers = [ + "Development Status :: 3 - Alpha", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.10", + "License :: OSI Approved :: MIT License", +] +dependencies = [ + "click>=8.1.7", + "fastapi>=0.110.0", + "uvicorn>=0.27.1", + "jinja2>=3.1.3", + "sqlalchemy>=2.0.25", + "pydantic>=2.6.1", + "passlib[bcrypt]>=1.7.4", + "itsdangerous>=2.1.2", + "pyjwt>=2.8.0", + "aiofiles>=23.2.1", + "gitpython>=3.1.41", +] + +[project.optional-dependencies] +dev = [ + "black>=24.2.0", + "ruff>=0.2.2", + "mypy>=1.8.0", + "pytest>=8.0.0", + "pytest-asyncio>=0.23.5", + "pytest-cov>=4.1.0", + "httpx>=0.26.0", + "pre-commit>=3.6.0", +] + +[project.scripts] +angel = "angel_cli.cli:main" + +[tool.black] +line-length = 88 + +[tool.ruff] +line-length = 88 +select = ["E", "F", "I", "B"] + +[tool.mypy] +python_version = "3.10" +strict = true + +[tool.pytest.ini_options] +addopts = "-ra --cov=angel_cli --cov=angel_web" +asyncio_mode = "auto" diff --git a/requirements-dev.txt b/requirements-dev.txt new file mode 100644 index 0000000..f14983f --- /dev/null +++ b/requirements-dev.txt @@ -0,0 +1,8 @@ +black>=24.2.0 +ruff>=0.2.2 +mypy>=1.8.0 +pytest>=8.0.0 +pytest-asyncio>=0.23.5 +pytest-cov>=4.1.0 +httpx>=0.26.0 +pre-commit>=3.6.0 diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..45cc30b --- /dev/null +++ b/requirements.txt @@ -0,0 +1,11 @@ +click>=8.1.7 +fastapi>=0.110.0 +uvicorn>=0.27.1 +jinja2>=3.1.3 +sqlalchemy>=2.0.25 +pydantic>=2.6.1 +passlib[bcrypt]>=1.7.4 +itsdangerous>=2.1.2 +pyjwt>=2.8.0 +aiofiles>=23.2.1 +gitpython>=3.1.41 diff --git a/scripts/build_docs.sh b/scripts/build_docs.sh new file mode 100755 index 0000000..a3b5531 --- /dev/null +++ b/scripts/build_docs.sh @@ -0,0 +1,3 @@ +#!/usr/bin/env bash +set -euo pipefail +mkdocs build diff --git a/scripts/dev_setup.sh b/scripts/dev_setup.sh new file mode 100755 index 0000000..7bcf6ff --- /dev/null +++ b/scripts/dev_setup.sh @@ -0,0 +1,7 @@ +#!/usr/bin/env bash +set -euo pipefail +python -m venv .venv +source .venv/bin/activate +pip install --upgrade pip +pip install -r requirements.txt -r requirements-dev.txt +pip install -e . diff --git a/scripts/run_tests.sh b/scripts/run_tests.sh new file mode 100755 index 0000000..1923407 --- /dev/null +++ b/scripts/run_tests.sh @@ -0,0 +1,3 @@ +#!/usr/bin/env bash +set -euo pipefail +pytest diff --git a/setup-angel.ps1 b/setup-angel.ps1 new file mode 100644 index 0000000..12eb72f --- /dev/null +++ b/setup-angel.ps1 @@ -0,0 +1,92 @@ +<#[ +.SYNOPSIS + Setup script for ANGEL. +.DESCRIPTION + Creates project structure, installs dependencies, and initializes git. +#> +[CmdletBinding()] +param( + [Parameter(Mandatory=$true)] + [string]$ProjectPath, + + [Parameter(Mandatory=$true)] + [string]$RepoUrl, + + [Parameter(Mandatory=$false)] + [string]$StoragePath, + + [Parameter(Mandatory=$false)] + [string]$AdminUsername, + + [Parameter(Mandatory=$false)] + [string]$AdminPassword, + + [switch]$SkipGitHub, + [switch]$SkipTests, + [switch]$DevMode, + [switch]$VerboseOutput +) + +$ErrorActionPreference = "Stop" +$logFile = Join-Path $ProjectPath "setup.log" + +function Write-Log { + param([string]$Message) + $timestamp = Get-Date -Format "u" + $line = "$timestamp $Message" + $line | Tee-Object -FilePath $logFile -Append +} + +function Ensure-Tool { + param([string]$Name, [string]$Command) + if (-not (Get-Command $Command -ErrorAction SilentlyContinue)) { + throw "$Name is required but not found in PATH." + } +} + +try { + Write-Log "Starting ANGEL setup." + Ensure-Tool -Name "Python" -Command "python" + Ensure-Tool -Name "Git" -Command "git" + + if (-not (Test-Path $ProjectPath)) { + New-Item -ItemType Directory -Path $ProjectPath | Out-Null + } + + Push-Location $ProjectPath + + if (-not (Test-Path ".git")) { + git init | Out-Null + } + + if (-not (Test-Path ".venv")) { + python -m venv .venv + } + + $venvPython = Join-Path $ProjectPath ".venv\Scripts\python.exe" + if (-not (Test-Path $venvPython)) { + throw "Virtual environment not created." + } + + & $venvPython -m pip install --upgrade pip + & $venvPython -m pip install -r requirements.txt + if ($DevMode) { + & $venvPython -m pip install -r requirements-dev.txt + } + & $venvPython -m pip install -e . + + if (-not $SkipGitHub) { + git remote add origin $RepoUrl 2>$null + } + + if (-not $SkipTests) { + & $venvPython -m pytest + } + + Write-Log "Setup complete." +} catch { + Write-Log "Error: $($_.Exception.Message)" + throw +} finally { + Pop-Location +} diff --git a/setup-angel.sh b/setup-angel.sh new file mode 100755 index 0000000..45f57a8 --- /dev/null +++ b/setup-angel.sh @@ -0,0 +1,29 @@ +#!/usr/bin/env bash +set -euo pipefail + +PROJECT_PATH="$1" +REPO_URL="$2" + +if [[ -z "$PROJECT_PATH" || -z "$REPO_URL" ]]; then + echo "Usage: ./setup-angel.sh " + exit 1 +fi + +mkdir -p "$PROJECT_PATH" +cd "$PROJECT_PATH" + +if [[ ! -d .git ]]; then + git init +fi + +python -m venv .venv +source .venv/bin/activate +pip install --upgrade pip +pip install -r requirements.txt +pip install -e . + +if ! git remote | grep -q origin; then + git remote add origin "$REPO_URL" +fi + +echo "Setup complete." diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..0cbf2c2 --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1 @@ +"""Pytest fixtures.""" diff --git a/tests/integration/test_health.py b/tests/integration/test_health.py new file mode 100644 index 0000000..21613d0 --- /dev/null +++ b/tests/integration/test_health.py @@ -0,0 +1,12 @@ +"""Integration tests for the health endpoint.""" +from fastapi.testclient import TestClient + +from angel_web.app import create_app + + +def test_health_endpoint() -> None: + app = create_app() + client = TestClient(app) + response = client.get("/api/system/health") + assert response.status_code == 200 + assert response.json() == {"status": "ok"} diff --git a/tests/unit/test_auth.py b/tests/unit/test_auth.py new file mode 100644 index 0000000..892a3de --- /dev/null +++ b/tests/unit/test_auth.py @@ -0,0 +1,8 @@ +"""Auth unit tests.""" +from angel_cli.core.auth import hash_password, verify_password + + +def test_hash_and_verify() -> None: + password = "test-password" + hashed = hash_password(password) + assert verify_password(password, hashed) From 6e709dad66eabbc51df393dad89ccc125d5c690f Mon Sep 17 00:00:00 2001 From: Nipun Darshana Date: Thu, 29 Jan 2026 09:37:39 +0530 Subject: [PATCH 2/3] chore: prefer binary installs for dependencies --- .github/workflows/ci.yml | 5 +++-- scripts/dev_setup.sh | 4 ++-- setup-angel.ps1 | 6 +++--- setup-angel.sh | 4 ++-- 4 files changed, 10 insertions(+), 9 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7e9bf99..2c2586d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,8 +16,9 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip - pip install -r requirements.txt - pip install -r requirements-dev.txt + pip install --upgrade setuptools wheel + pip install --prefer-binary -r requirements.txt + pip install --prefer-binary -r requirements-dev.txt pip install -e . - name: Lint run: ruff check . diff --git a/scripts/dev_setup.sh b/scripts/dev_setup.sh index 7bcf6ff..442b09d 100755 --- a/scripts/dev_setup.sh +++ b/scripts/dev_setup.sh @@ -2,6 +2,6 @@ set -euo pipefail python -m venv .venv source .venv/bin/activate -pip install --upgrade pip -pip install -r requirements.txt -r requirements-dev.txt +pip install --upgrade pip setuptools wheel +pip install --prefer-binary -r requirements.txt -r requirements-dev.txt pip install -e . diff --git a/setup-angel.ps1 b/setup-angel.ps1 index 12eb72f..c492ddc 100644 --- a/setup-angel.ps1 +++ b/setup-angel.ps1 @@ -68,10 +68,10 @@ try { throw "Virtual environment not created." } - & $venvPython -m pip install --upgrade pip - & $venvPython -m pip install -r requirements.txt + & $venvPython -m pip install --upgrade pip setuptools wheel + & $venvPython -m pip install --prefer-binary -r requirements.txt if ($DevMode) { - & $venvPython -m pip install -r requirements-dev.txt + & $venvPython -m pip install --prefer-binary -r requirements-dev.txt } & $venvPython -m pip install -e . diff --git a/setup-angel.sh b/setup-angel.sh index 45f57a8..9a53125 100755 --- a/setup-angel.sh +++ b/setup-angel.sh @@ -18,8 +18,8 @@ fi python -m venv .venv source .venv/bin/activate -pip install --upgrade pip -pip install -r requirements.txt +pip install --upgrade pip setuptools wheel +pip install --prefer-binary -r requirements.txt pip install -e . if ! git remote | grep -q origin; then From 255c543fc635aca1f46f7fc312d8d77f2e1cf5bf Mon Sep 17 00:00:00 2001 From: Nipun Darshana Date: Thu, 29 Jan 2026 10:16:38 +0530 Subject: [PATCH 3/3] docs: prefer binary installs in README --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index cbb3a43..8c067ad 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ ANGEL (Automated Navigator for Gathering and Executing Local tools) is a cross-p ```bash python -m venv .venv source .venv/bin/activate # Windows: .\.venv\Scripts\Activate.ps1 -pip install -r requirements.txt +pip install --prefer-binary -r requirements.txt pip install -e . angel init angel web @@ -23,7 +23,7 @@ Configuration is stored in `~/.config/angel/config.toml` (Linux/macOS) or `%APPD ## Development ```bash -pip install -r requirements-dev.txt +pip install --prefer-binary -r requirements-dev.txt pre-commit install pytest ```