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
8 changes: 6 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,16 @@ on:
jobs:
test:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
python-version: ['3.10', '3.11', '3.12']
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.11'
python-version: ${{ matrix.python-version }}
- run: python -m pip install --upgrade pip
- run: python -m pip install -e '.[dev]'
- run: ruff check .
- run: pytest
- run: pytest -q
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,6 @@ backup-*.tar.gz

# Local configuration
.env

# Generated release screenshots (avoid binary files in Codex diffs)
docs/screenshots/*-release.png
1 change: 1 addition & 0 deletions .python-version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
3.12
10 changes: 10 additions & 0 deletions .vercelignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
.git
.github
.pytest_cache
.ruff_cache
.venv
tests
docs
generated
backup-*.tar.gz
docs/screenshots/*-release.png
36 changes: 32 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,17 @@
# Universal Project Compiler Agent

<p align="center">
<a href="https://github.com/Huynhthuongg/AGENTS.md/actions/workflows/ci.yml"><img alt="CI" src="https://img.shields.io/github/actions/workflow/status/Huynhthuongg/AGENTS.md/ci.yml?branch=main&style=for-the-badge&logo=github&label=CI"></a>
<img alt="Python" src="https://img.shields.io/badge/Python-3.10%20%7C%203.11%20%7C%203.12-3776AB?style=for-the-badge&logo=python&logoColor=white">
<img alt="FastAPI" src="https://img.shields.io/badge/FastAPI-ready-009688?style=for-the-badge&logo=fastapi&logoColor=white">
<img alt="Vercel" src="https://img.shields.io/badge/Vercel-deployable-000000?style=for-the-badge&logo=vercel&logoColor=white">
<img alt="Version" src="https://img.shields.io/badge/release-0.1.2-8B5CF6?style=for-the-badge">
<img alt="License" src="https://img.shields.io/badge/license-AGPL--3.0--only-blue?style=for-the-badge">
</p>

Android-first, Termux-first development agent that transforms documents, specifications, repositories, OCR text, Markdown, or natural language requests into complete, runnable, maintainable software project scaffolds.

The original product specification is preserved in [docs/SPECIFICATION.md](docs/SPECIFICATION.md).
The original product specification is preserved in [docs/SPECIFICATION.md](docs/SPECIFICATION.md). The project now ships with a polished dashboard, live API docs, GitHub badges, CI checks, and Vercel deployment wiring.

## What is included

Expand All @@ -12,7 +21,7 @@ The original product specification is preserved in [docs/SPECIFICATION.md](docs/
- A safe compiler that generates runnable Python project scaffolds with docs, tests, and scripts.
- Secret redaction, safe slug generation, path traversal protection, and HTTP security headers.
- Termux-friendly setup, start, update, and backup scripts.
- CI, tests, and architecture documentation.
- CI, tests, architecture documentation, Vercel configuration, and GitHub project badges.

## Quick start

Expand All @@ -38,6 +47,18 @@ pkg install python git

The default architecture avoids Docker, Kubernetes, and heavy services so it can run on low-memory Android devices.

## Deploy to Vercel

This repo includes a Vercel ASGI entrypoint and `vercel.json`, so the FastAPI dashboard and API can run as a Python Function.

```bash
npm i -g vercel
vercel login
vercel deploy --prod
```

Vercel routes every request to `api/index.py`, while generated compile output is redirected to `/tmp/upca` through `UPCA_OUTPUT_BASE` for serverless-safe writes.

## API examples

```bash
Expand All @@ -62,10 +83,17 @@ tests/ Unit tests

```bash
python -m pip install -e '.[dev]'
ruff check .
pytest
./scripts/check.sh
```

`./scripts/check.sh` runs Ruff and the full pytest suite so release checks match CI.

## Current release

- Version: 0.1.2
- Release date: 2026-06-03
- Release focus: redesigned project landing page, GitHub badges, Vercel deployment wiring, and serverless-safe compile output paths.

## Security model

- Never hardcode secrets in generated output.
Expand Down
Empty file added api/__init__.py
Empty file.
12 changes: 12 additions & 0 deletions api/index.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
"""Vercel ASGI entrypoint for Universal Project Compiler Agent."""

from __future__ import annotations

import sys
from pathlib import Path

APP_DIR = Path(__file__).resolve().parents[1] / "app"
if str(APP_DIR) not in sys.path:
sys.path.insert(0, str(APP_DIR))

from universal_compiler_agent.server import app # noqa: E402,F401
2 changes: 1 addition & 1 deletion app/universal_compiler_agent/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""Universal Project Compiler Agent package."""

__all__ = ["__version__"]
__version__ = "0.1.0"
__version__ = "0.1.2"
22 changes: 19 additions & 3 deletions app/universal_compiler_agent/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,14 @@ def _read_input(args: argparse.Namespace) -> str:
return "Universal Project Compiler Agent"


def _safe_output_dir(value: str) -> Path:
output_dir = Path(value)
if output_dir.is_absolute() or ".." in output_dir.parts:
msg = "output_dir must be a safe relative path"
raise argparse.ArgumentTypeError(msg)
return output_dir


def build_parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(
description="Compile requirements into a runnable project scaffold."
Expand All @@ -37,7 +45,15 @@ def build_parser() -> argparse.ArgumentParser:
compile_cmd.add_argument("--text", help="Inline requirements text.")
compile_cmd.add_argument("--name", help="Override generated project name.")
compile_cmd.add_argument(
"--output-dir", default="generated", help="Directory that will receive output."
"--dry-run",
action="store_true",
help="Print the generated plan as JSON without writing files.",
)
compile_cmd.add_argument(
"--output-dir",
default=Path("generated"),
type=_safe_output_dir,
help="Safe relative directory that will receive output.",
)
return parser

Expand All @@ -47,12 +63,12 @@ def main(argv: list[str] | None = None) -> int:
args = parser.parse_args(argv)
requirements = _read_input(args)

if args.command == "plan":
if args.command == "plan" or args.dry_run:
plan = build_plan(requirements, args.name)
print(json.dumps(asdict(plan), indent=2))
return 0

result = compile_project(requirements, Path(args.output_dir), args.name)
result = compile_project(requirements, args.output_dir, args.name)
print(f"Generated {result.file_count} files in {result.root}")
return 0

Expand Down
59 changes: 22 additions & 37 deletions app/universal_compiler_agent/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from __future__ import annotations

import os
from dataclasses import asdict
from pathlib import Path

Expand All @@ -11,6 +12,9 @@

from .compiler import compile_project
from .planner import build_plan
from .templates import INDEX_HTML

APP_VERSION = "0.1.2"


class PlanRequest(BaseModel):
Expand All @@ -22,8 +26,19 @@ class CompileRequest(PlanRequest):
output_dir: str = Field(default="generated", max_length=240)


def _safe_output_dir(value: str) -> Path:
output_dir = Path(value)
if output_dir.is_absolute() or ".." in output_dir.parts:
raise HTTPException(status_code=400, detail="output_dir must be a safe relative path")
return output_dir


def _output_base() -> Path:
return Path(os.environ.get("UPCA_OUTPUT_BASE", "."))


def create_app() -> FastAPI:
app = FastAPI(title="Universal Project Compiler Agent", version="0.1.0")
app = FastAPI(title="Universal Project Compiler Agent", version=APP_VERSION)

@app.middleware("http")
async def security_headers(request: Request, call_next): # type: ignore[no-untyped-def]
Expand All @@ -36,38 +51,7 @@ async def security_headers(request: Request, call_next): # type: ignore[no-unty

@app.get("/", response_class=HTMLResponse)
def index() -> str:
return """
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Universal Project Compiler Agent</title>
<script defer src="https://cdn.vercel-insights.com/v1/script.js"></script>
<style>
:root { color-scheme: light dark; --bg:#0b1020; --card:#11172b; --text:#eef2ff; --muted:#aab4d4; --accent:#7c3aed; }
* { box-sizing: border-box; }
body { margin:0; min-height:100vh; font-family: Inter, ui-sans-serif, system-ui, sans-serif; background: radial-gradient(circle at top, #1d2b64, var(--bg)); color:var(--text); }
main { width:min(1040px, 100%); margin:auto; padding: clamp(24px, 5vw, 72px); }
.hero { display:grid; gap:24px; }
.badge { width:max-content; border:1px solid #ffffff22; border-radius:999px; padding:8px 12px; color:var(--muted); background:#ffffff0d; }
h1 { font-size:clamp(42px, 8vw, 82px); line-height:.95; letter-spacing:-0.06em; margin:0; }
p { color:var(--muted); font-size:clamp(16px, 2vw, 20px); max-width:760px; }
.grid { display:grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap:16px; margin-top:36px; }
.card { background: color-mix(in srgb, var(--card) 88%, white 12%); border:1px solid #ffffff14; border-radius:24px; padding:22px; box-shadow:0 20px 60px #0006; }
code { color:#c4b5fd; }
</style>
</head>
<body><main><section class="hero">
<div class="badge">Android-first • Termux-first • Production-ready</div>
<h1>Compile requirements into runnable software projects.</h1>
<p>Use <code>POST /plan</code> to analyze requirements or <code>POST /compile</code> to emit a secure, maintainable scaffold with docs, tests, and scripts.</p>
</section><section class="grid">
<div class="card"><h2>CLI</h2><p><code>upca compile --input-file spec.md</code></p></div>
<div class="card"><h2>API</h2><p>JSON endpoints for automation and future UI integrations.</p></div>
<div class="card"><h2>Security</h2><p>Path safety, secret redaction, and hardened HTTP headers.</p></div>
</section></main></body></html>
"""
return INDEX_HTML

@app.get("/health")
def health() -> dict[str, str]:
Expand All @@ -80,10 +64,11 @@ def plan(request: PlanRequest) -> JSONResponse:

@app.post("/compile")
def compile_endpoint(request: CompileRequest) -> dict[str, object]:
output_dir = Path(request.output_dir)
if output_dir.is_absolute() or ".." in output_dir.parts:
raise HTTPException(status_code=400, detail="output_dir must be a safe relative path")
result = compile_project(request.requirements, output_dir, request.project_name)
result = compile_project(
request.requirements,
_output_base() / _safe_output_dir(request.output_dir),
request.project_name,
)
return {"root": str(result.root), "file_count": result.file_count, "slug": result.plan.slug}

return app
Expand Down
Loading
Loading