appgenerator-cli 1.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. appgenerator_cli-1.0.0.dist-info/METADATA +213 -0
  2. appgenerator_cli-1.0.0.dist-info/RECORD +48 -0
  3. appgenerator_cli-1.0.0.dist-info/WHEEL +4 -0
  4. appgenerator_cli-1.0.0.dist-info/entry_points.txt +4 -0
  5. pyforge/__init__.py +10 -0
  6. pyforge/commands/__init__.py +1 -0
  7. pyforge/commands/create.py +60 -0
  8. pyforge/generator.py +248 -0
  9. pyforge/main.py +32 -0
  10. pyforge/templates/ai/.env.example +29 -0
  11. pyforge/templates/ai/.gitignore +47 -0
  12. pyforge/templates/ai/Dockerfile +22 -0
  13. pyforge/templates/ai/README.md +97 -0
  14. pyforge/templates/ai/app/__init__.py +1 -0
  15. pyforge/templates/ai/app/agents/__init__.py +1 -0
  16. pyforge/templates/ai/app/agents/assistant.py +100 -0
  17. pyforge/templates/ai/app/chains/__init__.py +1 -0
  18. pyforge/templates/ai/app/chains/rag.py +50 -0
  19. pyforge/templates/ai/app/config.py +47 -0
  20. pyforge/templates/ai/app/tools/__init__.py +1 -0
  21. pyforge/templates/ai/app/tools/registry.py +19 -0
  22. pyforge/templates/ai/app/tools/search.py +34 -0
  23. pyforge/templates/ai/docker-compose.yml +39 -0
  24. pyforge/templates/ai/main.py +40 -0
  25. pyforge/templates/ai/pyproject.toml +28 -0
  26. pyforge/templates/ai/tests/__init__.py +1 -0
  27. pyforge/templates/ai/tests/conftest.py +21 -0
  28. pyforge/templates/ai/tests/test_agent.py +53 -0
  29. pyforge/templates/fastapi/.env.example +17 -0
  30. pyforge/templates/fastapi/.gitignore +42 -0
  31. pyforge/templates/fastapi/Dockerfile +27 -0
  32. pyforge/templates/fastapi/README.md +68 -0
  33. pyforge/templates/fastapi/app/__init__.py +1 -0
  34. pyforge/templates/fastapi/app/api/__init__.py +1 -0
  35. pyforge/templates/fastapi/app/api/v1/__init__.py +1 -0
  36. pyforge/templates/fastapi/app/api/v1/health.py +25 -0
  37. pyforge/templates/fastapi/app/config.py +45 -0
  38. pyforge/templates/fastapi/app/db/__init__.py +1 -0
  39. pyforge/templates/fastapi/app/db/session.py +35 -0
  40. pyforge/templates/fastapi/app/dependencies.py +12 -0
  41. pyforge/templates/fastapi/app/main.py +58 -0
  42. pyforge/templates/fastapi/app/models/__init__.py +4 -0
  43. pyforge/templates/fastapi/app/models/base.py +23 -0
  44. pyforge/templates/fastapi/docker-compose.yml +39 -0
  45. pyforge/templates/fastapi/pyproject.toml +29 -0
  46. pyforge/templates/fastapi/tests/__init__.py +1 -0
  47. pyforge/templates/fastapi/tests/conftest.py +46 -0
  48. pyforge/templates/fastapi/tests/test_health.py +13 -0
@@ -0,0 +1,213 @@
1
+ Metadata-Version: 2.3
2
+ Name: appgenerator-cli
3
+ Version: 1.0.0
4
+ Summary: A developer-friendly scaffolding CLI for FastAPI and LangChain/LangGraph projects
5
+ Keywords: cli,scaffolding,fastapi,langchain,langgraph,uv
6
+ Author: Rajendra Kumar Yadav
7
+ Author-email: Rajendra Kumar Yadav <yadavrajendrakumar@outlook.com>
8
+ License: MIT
9
+ Classifier: Development Status :: 3 - Alpha
10
+ Classifier: Environment :: Console
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.10
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Topic :: Software Development :: Code Generators
18
+ Requires-Dist: typer>=0.12.0
19
+ Requires-Dist: rich>=13.0.0
20
+ Requires-Dist: jinja2>=3.1.0
21
+ Requires-Python: >=3.10
22
+ Project-URL: Homepage, https://github.com/rajendrakumaryadav/pyforge
23
+ Project-URL: Repository, https://github.com/rajendrakumaryadav/pyforge
24
+ Project-URL: Issues, https://github.com/rajendrakumaryadav/pyforge/issues
25
+ Description-Content-Type: text/markdown
26
+
27
+ # ⚒ AppGenerator CLI
28
+
29
+ > Scaffold production-ready Python projects in seconds — powered by `uv`.
30
+
31
+ ```
32
+ $ appgenerator create fastapi my_api
33
+ $ appgenerator create ai my_ai_app --docker --postgres
34
+ ```
35
+
36
+ ---
37
+
38
+ ## Installation
39
+
40
+ ```bash
41
+ pip install appgenerator-cli
42
+ # or, with uv (recommended):
43
+ uv tool install appgenerator-cli
44
+ ```
45
+
46
+ Verify:
47
+ ```bash
48
+ appgenerator --version
49
+ ```
50
+
51
+ ---
52
+
53
+ ## Commands
54
+
55
+ ### `appgenerator create fastapi <name>`
56
+
57
+ Scaffold a **FastAPI** backend.
58
+
59
+ | Flag | Description |
60
+ |------|-------------|
61
+ | `--docker` | Add `Dockerfile` + `docker-compose.yml` |
62
+ | `--postgres` | Use PostgreSQL (`asyncpg`) instead of SQLite |
63
+ | `--redis` | Add Redis client (`redis`) |
64
+ | `--output PATH` | Create the project in a custom directory |
65
+
66
+ **Example:**
67
+ ```bash
68
+ appgenerator create fastapi my_api --docker --postgres --redis
69
+ cd my_api
70
+ cp .env.example .env
71
+ uv run uvicorn app.main:app --reload
72
+ ```
73
+
74
+ ### `appgenerator create ai <name>`
75
+
76
+ Scaffold a **LangChain / LangGraph** AI application.
77
+
78
+ | Flag | Description |
79
+ |------|-------------|
80
+ | `--docker` | Add `Dockerfile` + `docker-compose.yml` |
81
+ | `--postgres` | Add pgvector PostgreSQL support |
82
+ | `--redis` | Add Redis semantic cache |
83
+ | `--output PATH` | Create the project in a custom directory |
84
+
85
+ **Example:**
86
+ ```bash
87
+ appgenerator create ai my_assistant --docker
88
+ cd my_assistant
89
+ cp .env.example .env # add your OPENAI_API_KEY
90
+ uv run python main.py
91
+ ```
92
+
93
+ ---
94
+
95
+ ## Generated Project Structure
96
+
97
+ ### FastAPI
98
+
99
+ ```
100
+ my_api/
101
+ ├── app/
102
+ │ ├── main.py # Application factory
103
+ │ ├── config.py # Pydantic Settings
104
+ │ ├── dependencies.py # FastAPI deps (DB session, etc.)
105
+ │ ├── api/v1/
106
+ │ │ └── health.py # Health-check endpoint
107
+ │ ├── models/
108
+ │ │ └── base.py # SQLModel base with timestamps
109
+ │ └── db/
110
+ │ └── session.py # Async session factory
111
+ ├── tests/
112
+ │ ├── conftest.py # Async test client + DB fixtures
113
+ │ └── test_health.py
114
+ ├── .env.example
115
+ ├── .gitignore
116
+ ├── pyproject.toml
117
+ ├── Dockerfile # (--docker)
118
+ └── docker-compose.yml # (--docker)
119
+ ```
120
+
121
+ ### AI (LangChain / LangGraph)
122
+
123
+ ```
124
+ my_assistant/
125
+ ├── main.py # Interactive REPL
126
+ ├── app/
127
+ │ ├── config.py # Pydantic Settings
128
+ │ ├── agents/
129
+ │ │ └── assistant.py # LangGraph ReAct agent
130
+ │ ├── chains/
131
+ │ │ └── rag.py # RAG chain example
132
+ │ └── tools/
133
+ │ ├── registry.py # Tool registry
134
+ │ └── search.py # Web search tool stub
135
+ ├── tests/
136
+ │ └── test_agent.py
137
+ ├── .env.example
138
+ ├── .gitignore
139
+ ├── pyproject.toml
140
+ ├── Dockerfile # (--docker)
141
+ └── docker-compose.yml # (--docker)
142
+ ```
143
+
144
+ ---
145
+
146
+ ## Packaging & Publishing to PyPI
147
+
148
+ ### 1. Build
149
+
150
+ ```bash
151
+ # Install build tools
152
+ pip install build twine
153
+
154
+ # Build wheel + sdist
155
+ python -m build
156
+ # Outputs: dist/appgenerator_cli-0.1.0-py3-none-any.whl
157
+ # dist/appgenerator_cli-0.1.0.tar.gz
158
+ ```
159
+
160
+ ### 2. Test on TestPyPI
161
+
162
+ ```bash
163
+ twine upload --repository testpypi dist/*
164
+ pip install --index-url https://test.pypi.org/simple/ appgenerator-cli
165
+ ```
166
+
167
+ ### 3. Publish to PyPI
168
+
169
+ ```bash
170
+ twine upload dist/*
171
+ ```
172
+
173
+ Or with uv:
174
+
175
+ ```bash
176
+ uv build
177
+ uv publish
178
+ ```
179
+
180
+ ---
181
+
182
+ ## Development Setup
183
+
184
+ ```bash
185
+ git clone https://github.com/yourname/pyforge-cli
186
+ cd pyforge-cli
187
+
188
+ uv venv
189
+ uv sync --dev
190
+
191
+ # Run locally
192
+ uv run appgenerator --help
193
+
194
+ # Tests
195
+ uv run pytest
196
+
197
+ # Lint
198
+ uv run ruff check .
199
+ uv run ruff format .
200
+ ```
201
+
202
+ ---
203
+
204
+ ## Requirements
205
+
206
+ - Python ≥ 3.10
207
+ - [`uv`](https://docs.astral.sh/uv/) installed on the system (for generated project env management)
208
+
209
+ ---
210
+
211
+ ## License
212
+
213
+ MIT
@@ -0,0 +1,48 @@
1
+ pyforge/__init__.py,sha256=8izMloJRT3IJTWLHt4DPnRnF_HBWNa4kCeptw8ixZ4g,229
2
+ pyforge/commands/__init__.py,sha256=gQ6tnU0Rvm0-ESWFUBU-KDl5dpNOpUTG509hXOQQjwY,27
3
+ pyforge/commands/create.py,sha256=0uLonhz2kJ5f33_kC9Tf8ogsVMCn41gopRxWOS4SSFc,2233
4
+ pyforge/generator.py,sha256=vXlZqNfU6ar-9t_C0ip-9NODP39Y0FDbVl34ilFh0UM,8620
5
+ pyforge/main.py,sha256=6pU8tYJayiVnMBTE8yvwoln2AxhIN5R3glq2YyG1AhY,875
6
+ pyforge/templates/ai/.env.example,sha256=dhiwWIbwCpXRetrAHBswJZchhlW0c6Lm8FBPv-5iEpk,1332
7
+ pyforge/templates/ai/.gitignore,sha256=d37AN_cpkr6IYCoQhvWkP1g5-352Z5FxT0qsQVn_sro,399
8
+ pyforge/templates/ai/Dockerfile,sha256=X5_IlCm9oP9XrqCOD69WkXdx6LiDu-Itls17Im6uDP0,786
9
+ pyforge/templates/ai/README.md,sha256=6c39K6kt_2hbCBespsRLHqkUVSrn9kPC_j1u4wSBYVQ,2748
10
+ pyforge/templates/ai/app/__init__.py,sha256=NhaPqUY6LTjOiB6li75lrY01m3Maf77e-TrB610URA4,49
11
+ pyforge/templates/ai/app/agents/__init__.py,sha256=K3TLCPOIwxTcWpYR-rmP-QPJWdb5_wnMW5B1Wr68trs,25
12
+ pyforge/templates/ai/app/agents/assistant.py,sha256=Wi_X_CifyGtgJaAZ8ZqgOqEbDt7fQKUwcu0hDcdKHtQ,3655
13
+ pyforge/templates/ai/app/chains/__init__.py,sha256=gPLZRZ2MLXBiafF_riw6Wgo07yn_rjugUK1Bq8eZyqQ,44
14
+ pyforge/templates/ai/app/chains/rag.py,sha256=RrLT2DTNfSuP5tqnOSBlC3-33oZY6sdb8aKLprPrDxU,1383
15
+ pyforge/templates/ai/app/config.py,sha256=Co6_eaaWpaUq4siqm6LoETX5Np4tGrI2FAYuHHvbDzk,1088
16
+ pyforge/templates/ai/app/tools/__init__.py,sha256=ffZt2FB3NFrHjbvROCGyorUYdJHu-yzmi17E0sacXtQ,30
17
+ pyforge/templates/ai/app/tools/registry.py,sha256=iMYxWm2DNk7NnhV0Ng0ACVs4pyf6iIiLvxdQmorqMww,455
18
+ pyforge/templates/ai/app/tools/search.py,sha256=q4nGmVIR3dlVGxnASHx6YHJZZ2WduyCAm5aNjt6gvyE,1327
19
+ pyforge/templates/ai/docker-compose.yml,sha256=zAp94_ssKr21Ckm5Ai6-JcOhWZBg0Zi2vF-IES403Do,776
20
+ pyforge/templates/ai/main.py,sha256=3JtvWH3p8ZjfduMf5WED2L5jR7416bch0z8h3-sRG9s,948
21
+ pyforge/templates/ai/pyproject.toml,sha256=cGIXwjkq4W-ov22DzbM6LldP6KC-lg2nLo2OkGd05xI,543
22
+ pyforge/templates/ai/tests/__init__.py,sha256=j-x4uousrt8rA7mrJ8pNKAc_38kKUMrU8I7gvI1ylG0,41
23
+ pyforge/templates/ai/tests/conftest.py,sha256=YNMpVpX3-kolFtxcpW-1wep2ehtj6FXp5DS1hjSIS2k,625
24
+ pyforge/templates/ai/tests/test_agent.py,sha256=INDRb-XwMWSZOzK0ej3i2KwkDGIr5yZzD1ty565tMec,1762
25
+ pyforge/templates/fastapi/.env.example,sha256=6Iv7Opys57RpWdeYuZQOnleXG3n-MaY79gAON7o8Vjs,1092
26
+ pyforge/templates/fastapi/.gitignore,sha256=WWUEaHQnwHKCefTYooPgdDGJScKWIxlqFvBOVrvPdbE,307
27
+ pyforge/templates/fastapi/Dockerfile,sha256=4gk2LYeRHaU9LUL7RbOpbIYv2RiHPih7xYjuUZB-70c,923
28
+ pyforge/templates/fastapi/README.md,sha256=24i60XTJQ86edN6Ey_QfHueWiaW2FHjmOEjuqsyzVeg,1931
29
+ pyforge/templates/fastapi/app/__init__.py,sha256=WwDGHlbBHaUL99XtoQhbvGhV_O7EaCE6nCikhkk9cvs,46
30
+ pyforge/templates/fastapi/app/api/__init__.py,sha256=d7XMEse6wwtLORqrQmnhaKMI8KM3lJSzRVE7HVskq1w,19
31
+ pyforge/templates/fastapi/app/api/v1/__init__.py,sha256=TSD_55lofQDrklwHqqm_zlDAL695k0RTThXsgIDDqDY,22
32
+ pyforge/templates/fastapi/app/api/v1/health.py,sha256=WFkYMNXEs-RL76gF1j7tT8PQyzDRCLQn7b8tOTdCxsg,575
33
+ pyforge/templates/fastapi/app/config.py,sha256=nAGQZG5C9pKl8OO1UJ054oE1MwHNgJCd1_fkQxluUNc,1120
34
+ pyforge/templates/fastapi/app/db/__init__.py,sha256=-izZUgEShtb52VcDe78h2-xPG0Im1WDkKVHy8dMfg78,24
35
+ pyforge/templates/fastapi/app/db/session.py,sha256=XK8ekpRmF_S46Dgj2SK2gkYSyt9ibNu_8Nkee_CRoEU,968
36
+ pyforge/templates/fastapi/app/dependencies.py,sha256=U_wGGNnyYqbdBUevC9CHATHxMtJ6oIqxyRZyk54KlNg,362
37
+ pyforge/templates/fastapi/app/main.py,sha256=dfLQSZriNVao5w1L__VtlsWlznnj5Mts7tH1VnuAsBs,1378
38
+ pyforge/templates/fastapi/app/models/__init__.py,sha256=YNPfDtvd2_5O_4RfPf67FGHivfhpASepWeC9W-jut3M,127
39
+ pyforge/templates/fastapi/app/models/base.py,sha256=R6_YyZXhSMKCpum5Ago2pHduIJB6vQhiHLtRH8TU8AQ,637
40
+ pyforge/templates/fastapi/docker-compose.yml,sha256=fujLVY_QxzEV_ZHEpu5GAurO9SyIl0UfDORDtdn5rMQ,713
41
+ pyforge/templates/fastapi/pyproject.toml,sha256=sNce628pZlgSTuTCRHDcUAFqwCMG6T_xcDKgpNSLlC4,531
42
+ pyforge/templates/fastapi/tests/__init__.py,sha256=TiS6C1IzwAXmNa8u36Y2xzL1CTTZm2PwzAtmZgoqepE,18
43
+ pyforge/templates/fastapi/tests/conftest.py,sha256=8I69eLCzAtA5tlZ4m94ktxcUOnCs7uehBnd2odWXe98,1312
44
+ pyforge/templates/fastapi/tests/test_health.py,sha256=EEw9GEVlCoHv7BVTx2dZMOfot_73g_5orD8cHUjJL54,375
45
+ appgenerator_cli-1.0.0.dist-info/WHEEL,sha256=M4DeIjVCA49okfALADZoWX5JOGwnmHb-JOpQHtI-1c0,80
46
+ appgenerator_cli-1.0.0.dist-info/entry_points.txt,sha256=5bQ5Wcg5NqyyvTzJ0fZy8zO41FCKvABQ72ObGrSIW-o,78
47
+ appgenerator_cli-1.0.0.dist-info/METADATA,sha256=mZx_QnK8ZWjGnjRSzfmHPfEJhXWV57aP7R9dlHsyBwc,4882
48
+ appgenerator_cli-1.0.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: uv 0.11.2
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,4 @@
1
+ [console_scripts]
2
+ appgenerator = pyforge.main:app
3
+ pyforge = pyforge.main:app
4
+
pyforge/__init__.py ADDED
@@ -0,0 +1,10 @@
1
+ """PyForge CLI package."""
2
+
3
+ from importlib.metadata import metadata
4
+
5
+ try:
6
+ _meta = metadata("appgenerator-cli")
7
+ except Exception:
8
+ _meta = metadata("pyforge-cli")
9
+ __version__ = _meta["Version"]
10
+ __author__ = _meta["Author-email"]
@@ -0,0 +1 @@
1
+ """CLI command modules."""
@@ -0,0 +1,60 @@
1
+ """
2
+ `appgenerator create` subcommand group.
3
+ Delegates to template-specific generators.
4
+ """
5
+ from __future__ import annotations
6
+
7
+ from pathlib import Path
8
+ from typing import Optional
9
+
10
+ import typer
11
+ from rich.console import Console
12
+
13
+ from pyforge.generator import ProjectGenerator
14
+
15
+ create_app = typer.Typer(no_args_is_help=True, rich_markup_mode="rich")
16
+ console = Console()
17
+
18
+
19
+ def _run_generator(
20
+ template: str,
21
+ project_name: str,
22
+ output_dir: Optional[Path],
23
+ docker: bool,
24
+ postgres: bool,
25
+ redis: bool,
26
+ ) -> None:
27
+ target = (output_dir or Path.cwd()) / project_name
28
+ if target.exists():
29
+ console.print(f"[bold red]✗[/] Directory [bold]{target}[/] already exists.")
30
+ raise typer.Exit(code=1)
31
+
32
+ generator = ProjectGenerator(
33
+ template=template,
34
+ project_name=project_name,
35
+ target_dir=target,
36
+ options={"docker": docker, "postgres": postgres, "redis": redis},
37
+ )
38
+ generator.run()
39
+
40
+
41
+ @create_app.command("fastapi", help="Scaffold a [bold]FastAPI[/] backend project.")
42
+ def create_fastapi(
43
+ project_name: str = typer.Argument(..., help="Name of the new project."),
44
+ output_dir: Optional[Path] = typer.Option(None, "--output", "-o", help="Parent directory."),
45
+ docker: bool = typer.Option(False, "--docker", help="Add Dockerfile & docker-compose.yml."),
46
+ postgres: bool = typer.Option(False, "--postgres", help="Add PostgreSQL support."),
47
+ redis: bool = typer.Option(False, "--redis", help="Add Redis support."),
48
+ ) -> None:
49
+ _run_generator("fastapi", project_name, output_dir, docker, postgres, redis)
50
+
51
+
52
+ @create_app.command("ai", help="Scaffold a [bold]LangChain / LangGraph[/] AI project.")
53
+ def create_ai(
54
+ project_name: str = typer.Argument(..., help="Name of the new project."),
55
+ output_dir: Optional[Path] = typer.Option(None, "--output", "-o", help="Parent directory."),
56
+ docker: bool = typer.Option(False, "--docker", help="Add Dockerfile & docker-compose.yml."),
57
+ postgres: bool = typer.Option(False, "--postgres", help="Add PostgreSQL vector DB support."),
58
+ redis: bool = typer.Option(False, "--redis", help="Add Redis cache support."),
59
+ ) -> None:
60
+ _run_generator("ai", project_name, output_dir, docker, postgres, redis)
pyforge/generator.py ADDED
@@ -0,0 +1,248 @@
1
+ """
2
+ Core project generation engine.
3
+ Handles file rendering, uv init, and dependency installation.
4
+ """
5
+ from __future__ import annotations
6
+
7
+ import shutil
8
+ import subprocess
9
+ import sys
10
+ from pathlib import Path
11
+ from typing import Any
12
+
13
+ from jinja2 import Environment, FileSystemLoader
14
+ from rich.console import Console
15
+ from rich.panel import Panel
16
+ from rich.progress import Progress, SpinnerColumn, TextColumn
17
+ from rich.table import Table
18
+
19
+ console = Console()
20
+
21
+ # Package root — templates live here
22
+ TEMPLATES_DIR = Path(__file__).parent / "templates"
23
+
24
+
25
+ class ProjectGenerator:
26
+ """Renders a project template and initialises it with uv."""
27
+
28
+ FASTAPI_DEPS = [
29
+ "fastapi",
30
+ "uvicorn[standard]",
31
+ "sqlmodel",
32
+ "aiosqlite",
33
+ "pydantic",
34
+ "pydantic-settings",
35
+ "python-dotenv",
36
+ "alembic",
37
+ "httpx",
38
+ ]
39
+ FASTAPI_DEV_DEPS = ["pytest", "pytest-asyncio", "httpx", "ruff", "mypy"]
40
+
41
+ AI_DEPS = [
42
+ "langchain",
43
+ "langgraph",
44
+ "langchain-community",
45
+ "langchain-openai",
46
+ "langchain-ollama",
47
+ "langchain-chroma",
48
+ "chromadb",
49
+ "openai",
50
+ "tiktoken",
51
+ "python-dotenv",
52
+ "pydantic",
53
+ "pydantic-settings",
54
+ "httpx",
55
+ ]
56
+ AI_DEV_DEPS = ["pytest", "pytest-asyncio", "ruff", "mypy"]
57
+
58
+ OPTIONAL_DEPS: dict[str, list[str]] = {
59
+ "postgres": ["asyncpg", "psycopg2-binary"],
60
+ "redis": ["redis", "hiredis"],
61
+ }
62
+
63
+ def __init__(
64
+ self,
65
+ template: str,
66
+ project_name: str,
67
+ target_dir: Path,
68
+ options: dict[str, Any],
69
+ ) -> None:
70
+ self.template = template # "fastapi" | "ai"
71
+ self.project_name = project_name
72
+ self.target_dir = target_dir
73
+ self.options = options
74
+ self.template_dir = TEMPLATES_DIR / template
75
+ self.env = Environment(
76
+ loader=FileSystemLoader(str(self.template_dir)),
77
+ keep_trailing_newline=True,
78
+ )
79
+
80
+ # ------------------------------------------------------------------ #
81
+ # Public API
82
+ # ------------------------------------------------------------------ #
83
+
84
+ def run(self) -> None:
85
+ console.print()
86
+ console.print(
87
+ Panel.fit(
88
+ f"[bold cyan]⚒ AppGenerator[/] · Creating [bold]{self.project_name}[/] "
89
+ f"([italic]{self.template}[/] template)",
90
+ border_style="cyan",
91
+ )
92
+ )
93
+ console.print()
94
+
95
+ with Progress(
96
+ SpinnerColumn(),
97
+ TextColumn("[progress.description]{task.description}"),
98
+ console=console,
99
+ transient=True,
100
+ ) as progress:
101
+ t = progress.add_task("Rendering template files …", total=None)
102
+ self._render_template()
103
+ progress.update(t, description="[green]✓[/] Template files written")
104
+
105
+ progress.update(t, description="Initialising uv project …", completed=None)
106
+ self._uv_init()
107
+ progress.update(t, description="[green]✓[/] uv project initialised")
108
+
109
+ progress.update(t, description="Installing dependencies …", completed=None)
110
+ self._install_deps()
111
+ progress.update(t, description="[green]✓[/] Dependencies installed")
112
+
113
+ self._print_success()
114
+
115
+ # ------------------------------------------------------------------ #
116
+ # Steps
117
+ # ------------------------------------------------------------------ #
118
+
119
+ def _render_template(self) -> None:
120
+ """Walk the template directory, render Jinja files, copy static files."""
121
+ ctx = self._build_context()
122
+
123
+ for src in self.template_dir.rglob("*"):
124
+ if src.is_dir():
125
+ continue
126
+
127
+ rel = src.relative_to(self.template_dir)
128
+ dest = self.target_dir / rel
129
+
130
+ # Skip optional files based on flags
131
+ if self._should_skip(rel):
132
+ continue
133
+
134
+ dest.parent.mkdir(parents=True, exist_ok=True)
135
+
136
+ if src.suffix in {".py", ".toml", ".env", ".yml", ".yaml", ".md", ".txt", ".cfg", ".ini", ".dockerfile", ""} or src.name == ".env.example":
137
+ try:
138
+ template = self.env.get_template(str(rel).replace("\\", "/"))
139
+ dest.write_text(template.render(**ctx), encoding="utf-8")
140
+ except Exception:
141
+ # Binary or unparseable — just copy
142
+ shutil.copy2(src, dest)
143
+ else:
144
+ shutil.copy2(src, dest)
145
+
146
+ def _uv_init(self) -> None:
147
+ """Run `uv init` inside the target directory (no-op if uv not found)."""
148
+ if not shutil.which("uv"):
149
+ console.print(
150
+ "[yellow]⚠[/] [bold]uv[/] not found — skipping venv initialisation. "
151
+ "Install uv: [link=https://docs.astral.sh/uv/]https://docs.astral.sh/uv/[/link]"
152
+ )
153
+ return
154
+
155
+ # uv init creates a pyproject.toml — we already created ours, so just create the venv
156
+ self._run(["uv", "venv"], cwd=self.target_dir)
157
+
158
+ def _install_deps(self) -> None:
159
+ """Write dependencies into pyproject.toml and sync the venv.
160
+
161
+ Uses --no-sync on `uv add` so uv only updates pyproject.toml + uv.lock
162
+ without trying to build/install the generated project itself as an
163
+ editable package.
164
+
165
+ The actual installation is done by a follow-up
166
+ `uv sync --no-install-project`.
167
+ """
168
+ if not shutil.which("uv"):
169
+ return
170
+
171
+ deps = self.FASTAPI_DEPS if self.template == "fastapi" else self.AI_DEPS
172
+ dev_deps = self.FASTAPI_DEV_DEPS if self.template == "fastapi" else self.AI_DEV_DEPS
173
+
174
+ for flag, pkgs in self.OPTIONAL_DEPS.items():
175
+ if self.options.get(flag):
176
+ deps = deps + pkgs
177
+
178
+ # --no-sync: only pin versions into pyproject.toml / uv.lock, don't build project
179
+ self._run(["uv", "add", "--no-sync"] + deps, cwd=self.target_dir)
180
+ self._run(["uv", "add", "--no-sync", "--dev"] + dev_deps, cwd=self.target_dir)
181
+
182
+ # Install all pinned deps into .venv, skipping the project root package
183
+ self._run(["uv", "sync", "--no-install-project"], cwd=self.target_dir)
184
+
185
+ # ------------------------------------------------------------------ #
186
+ # Helpers
187
+ # ------------------------------------------------------------------ #
188
+
189
+ def _build_context(self) -> dict[str, Any]:
190
+ pkg = self.project_name.lower().replace("-", "_").replace(" ", "_")
191
+ return {
192
+ "project_name": self.project_name,
193
+ "package_name": pkg,
194
+ "template": self.template,
195
+ "docker": self.options.get("docker", False),
196
+ "postgres": self.options.get("postgres", False),
197
+ "redis": self.options.get("redis", False),
198
+ "fastapi_deps": self.FASTAPI_DEPS,
199
+ "ai_deps": self.AI_DEPS,
200
+ }
201
+
202
+ def _should_skip(self, rel: Path) -> bool:
203
+ name = rel.name
204
+ parts = set(rel.parts)
205
+ if name in {"Dockerfile", "docker-compose.yml"} and not self.options.get("docker"):
206
+ return True
207
+ if "postgres" in parts and not self.options.get("postgres"):
208
+ return True
209
+ if "redis" in parts and not self.options.get("redis"):
210
+ return True
211
+ return False
212
+
213
+ def _run(self, cmd: list[str], cwd: Path) -> None:
214
+ result = subprocess.run(
215
+ cmd,
216
+ cwd=cwd,
217
+ capture_output=True,
218
+ text=True,
219
+ )
220
+ if result.returncode != 0:
221
+ console.print(f"[red]Command failed:[/] {' '.join(cmd)}")
222
+ console.print(result.stderr)
223
+ sys.exit(result.returncode)
224
+
225
+ def _print_success(self) -> None:
226
+ table = Table.grid(padding=(0, 2))
227
+ table.add_column(style="bold green")
228
+ table.add_column()
229
+
230
+ rel = self.target_dir.resolve()
231
+ run_cmd = (
232
+ "uvicorn app.main:app --reload"
233
+ if self.template == "fastapi"
234
+ else "python main.py"
235
+ )
236
+
237
+ table.add_row("Project:", str(rel))
238
+ table.add_row("Template:", self.template)
239
+ table.add_row("venv:", str(rel / ".venv"))
240
+ table.add_row("", "")
241
+ table.add_row("Next steps:", f"cd {self.project_name}")
242
+ table.add_row("", "cp .env.example .env # fill in your secrets")
243
+ table.add_row("", f"uv run {run_cmd}")
244
+
245
+ console.print(
246
+ Panel(table, title="[bold green]✓ Project created[/]", border_style="green")
247
+ )
248
+ console.print()
pyforge/main.py ADDED
@@ -0,0 +1,32 @@
1
+ """
2
+ AppGenerator CLI — A scaffolding tool for FastAPI and LangChain/LangGraph projects.
3
+ """
4
+ import typer
5
+ from rich.console import Console
6
+
7
+ from pyforge.commands.create import create_app
8
+
9
+ app = typer.Typer(
10
+ name="appgenerator",
11
+ help="⚒️ AppGenerator — Scaffold production-ready Python projects instantly.",
12
+ no_args_is_help=True,
13
+ rich_markup_mode="rich",
14
+ )
15
+ console = Console()
16
+
17
+ app.add_typer(create_app, name="create", help="Create a new project from a template.")
18
+
19
+
20
+ @app.callback(invoke_without_command=True)
21
+ def main(
22
+ ctx: typer.Context,
23
+ version: bool = typer.Option(False, "--version", "-v", help="Show version and exit."),
24
+ ) -> None:
25
+ if version:
26
+ from pyforge import __version__
27
+ console.print(f"[bold cyan]AppGenerator[/] version [bold]{__version__}[/]")
28
+ raise typer.Exit()
29
+
30
+
31
+ if __name__ == "__main__":
32
+ app()
@@ -0,0 +1,29 @@
1
+ # ── LLM Providers ────────────────────────────────────────────
2
+ OPENAI_API_KEY=sk-...
3
+ OPENAI_MODEL=gpt-4o-mini
4
+
5
+ # Ollama (local models) — optional
6
+ OLLAMA_BASE_URL=http://localhost:11434
7
+ OLLAMA_MODEL=llama3.2
8
+
9
+ # ── Application ──────────────────────────────────────────────
10
+ APP_NAME="{{ project_name }}"
11
+ APP_ENV=development
12
+ DEBUG=true
13
+
14
+ # ── Vector Store ──────────────────────────────────────────────
15
+ CHROMA_PERSIST_DIR=./chroma_db
16
+ {% if postgres %}
17
+ # pgvector (PostgreSQL) — enable with --postgres
18
+ PGVECTOR_URL=postgresql://postgres:postgres@localhost:5432/{{ package_name }}_vectors
19
+ {% endif %}
20
+
21
+ {% if redis %}
22
+ # ── Redis Cache ───────────────────────────────────────────────
23
+ REDIS_URL=redis://localhost:6379/0
24
+ {% endif %}
25
+
26
+ # ── LangSmith (optional tracing) ─────────────────────────────
27
+ # LANGCHAIN_TRACING_V2=true
28
+ # LANGCHAIN_API_KEY=ls__...
29
+ # LANGCHAIN_PROJECT={{ project_name }}