stup 0.1.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.
- app/__init__.py +3 -0
- app/cli.py +173 -0
- app/commands/__init__.py +1 -0
- app/commands/activate.py +33 -0
- app/commands/add.py +35 -0
- app/commands/api.py +178 -0
- app/commands/cli_cmd.py +137 -0
- app/commands/django_cmd.py +153 -0
- app/commands/docs.py +162 -0
- app/commands/lang_agent.py +147 -0
- app/commands/mean.py +130 -0
- app/commands/mern.py +134 -0
- app/commands/ml.py +119 -0
- app/commands/next_django.py +147 -0
- app/commands/notebook.py +116 -0
- app/commands/openai_agent.py +112 -0
- app/commands/react_fastapi.py +139 -0
- app/commands/saas.py +249 -0
- app/commands/scraper.py +163 -0
- app/commands/test_cmd.py +117 -0
- app/commands/uv.py +31 -0
- app/utils.py +166 -0
- stup-0.1.0.dist-info/METADATA +176 -0
- stup-0.1.0.dist-info/RECORD +27 -0
- stup-0.1.0.dist-info/WHEEL +4 -0
- stup-0.1.0.dist-info/entry_points.txt +2 -0
- stup-0.1.0.dist-info/licenses/LICENSE +21 -0
app/__init__.py
ADDED
app/cli.py
ADDED
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
"""stup CLI — Main entry point."""
|
|
2
|
+
|
|
3
|
+
import typer
|
|
4
|
+
from rich.console import Console
|
|
5
|
+
|
|
6
|
+
from app import __version__
|
|
7
|
+
from app.commands import (
|
|
8
|
+
activate,
|
|
9
|
+
add,
|
|
10
|
+
api,
|
|
11
|
+
cli_cmd,
|
|
12
|
+
django_cmd,
|
|
13
|
+
docs,
|
|
14
|
+
lang_agent,
|
|
15
|
+
mean,
|
|
16
|
+
mern,
|
|
17
|
+
ml,
|
|
18
|
+
next_django,
|
|
19
|
+
notebook,
|
|
20
|
+
openai_agent,
|
|
21
|
+
react_fastapi,
|
|
22
|
+
saas,
|
|
23
|
+
scraper,
|
|
24
|
+
test_cmd,
|
|
25
|
+
uv,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
console = Console()
|
|
29
|
+
|
|
30
|
+
app = typer.Typer(
|
|
31
|
+
name="stup",
|
|
32
|
+
help="Scaffold production-ready project structures in seconds.",
|
|
33
|
+
add_completion=False,
|
|
34
|
+
rich_markup_mode="rich",
|
|
35
|
+
no_args_is_help=True,
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _version_callback(value: bool) -> None:
|
|
40
|
+
if value:
|
|
41
|
+
console.print(f"[bold cyan]stup[/bold cyan] v{__version__}")
|
|
42
|
+
raise typer.Exit()
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@app.callback()
|
|
46
|
+
def main(
|
|
47
|
+
version: bool = typer.Option(
|
|
48
|
+
False, "--version", "-v", help="Show version and exit.", callback=_version_callback, is_eager=True
|
|
49
|
+
),
|
|
50
|
+
) -> None:
|
|
51
|
+
"""stup — Install once, scaffold forever."""
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
# ── Foundation commands ──────────────────────────────────────────────
|
|
55
|
+
|
|
56
|
+
@app.command()
|
|
57
|
+
def uv_cmd() -> None:
|
|
58
|
+
"""[bold cyan]Foundation[/bold cyan] · Bootstrap a complete uv Python project."""
|
|
59
|
+
uv.run_command()
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
# Register as "uv" not "uv-cmd"
|
|
63
|
+
uv_cmd.__name__ = "uv" # type: ignore[attr-defined]
|
|
64
|
+
uv_cmd = app.registered_commands[-1]
|
|
65
|
+
uv_cmd.name = "uv"
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
@app.command(name="activate")
|
|
69
|
+
def activate_cmd() -> None:
|
|
70
|
+
"""[bold cyan]Foundation[/bold cyan] · Smart cross-platform venv activator."""
|
|
71
|
+
activate.run_command()
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
@app.command(name="add")
|
|
75
|
+
def add_cmd(packages: list[str]) -> None:
|
|
76
|
+
"""[bold cyan]Foundation[/bold cyan] · Add packages and sync requirements.txt."""
|
|
77
|
+
add.run_command(packages)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
# ── Template commands ────────────────────────────────────────────────
|
|
81
|
+
|
|
82
|
+
@app.command(name="react-fastapi")
|
|
83
|
+
def react_fastapi_cmd() -> None:
|
|
84
|
+
"""[bold magenta]Full-stack[/bold magenta] · React + FastAPI with Docker Compose & CORS."""
|
|
85
|
+
react_fastapi.run_command()
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
@app.command(name="notebook")
|
|
89
|
+
def notebook_cmd() -> None:
|
|
90
|
+
"""[bold green]Data Science[/bold green] · Jupyter notebooks with uv-managed deps."""
|
|
91
|
+
notebook.run_command()
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
@app.command(name="mern")
|
|
95
|
+
def mern_cmd() -> None:
|
|
96
|
+
"""[bold magenta]Full-stack[/bold magenta] · MongoDB + Express + React + Node."""
|
|
97
|
+
mern.run_command()
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
@app.command(name="mean")
|
|
101
|
+
def mean_cmd() -> None:
|
|
102
|
+
"""[bold magenta]Full-stack[/bold magenta] · MongoDB + Express + Angular + Node."""
|
|
103
|
+
mean.run_command()
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
@app.command(name="django")
|
|
107
|
+
def django_cmd_fn() -> None:
|
|
108
|
+
"""[bold blue]Backend[/bold blue] · Django + DRF + Celery + Redis + PostgreSQL."""
|
|
109
|
+
django_cmd.run_command()
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
@app.command(name="next-django")
|
|
113
|
+
def next_django_cmd() -> None:
|
|
114
|
+
"""[bold magenta]Full-stack[/bold magenta] · Next.js 14 + Django REST + JWT auth."""
|
|
115
|
+
next_django.run_command()
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
@app.command(name="ml")
|
|
119
|
+
def ml_cmd() -> None:
|
|
120
|
+
"""[bold green]Data Science[/bold green] · ML project with experiment tracking & MLflow."""
|
|
121
|
+
ml.run_command()
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
@app.command(name="lang-agent")
|
|
125
|
+
def lang_agent_cmd() -> None:
|
|
126
|
+
"""[bold yellow]AI[/bold yellow] · LangGraph agent with tool stubs & Ollama config."""
|
|
127
|
+
lang_agent.run_command()
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
@app.command(name="openai-agent")
|
|
131
|
+
def openai_agent_cmd() -> None:
|
|
132
|
+
"""[bold yellow]AI[/bold yellow] · Modern OpenAI agent with function calling."""
|
|
133
|
+
openai_agent.run_command()
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
@app.command(name="scraper")
|
|
137
|
+
def scraper_cmd() -> None:
|
|
138
|
+
"""[bold yellow]Automation[/bold yellow] · Playwright + BeautifulSoup scraping pipeline."""
|
|
139
|
+
scraper.run_command()
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
@app.command(name="cli")
|
|
143
|
+
def cli_cmd_fn() -> None:
|
|
144
|
+
"""[bold blue]Package[/bold blue] · PyPI-ready CLI package with Typer + Rich."""
|
|
145
|
+
cli_cmd.run_command()
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
@app.command(name="api")
|
|
149
|
+
def api_cmd() -> None:
|
|
150
|
+
"""[bold blue]Backend[/bold blue] · Minimal FastAPI microservice with auth middleware."""
|
|
151
|
+
api.run_command()
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
@app.command(name="saas")
|
|
155
|
+
def saas_cmd() -> None:
|
|
156
|
+
"""[bold magenta]Full-stack[/bold magenta] · Full SaaS boilerplate with Stripe & Docker."""
|
|
157
|
+
saas.run_command()
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
@app.command(name="docs")
|
|
161
|
+
def docs_cmd() -> None:
|
|
162
|
+
"""[bold dim]Addon[/bold dim] · Add MkDocs + Material theme to any project."""
|
|
163
|
+
docs.run_command()
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
@app.command(name="test")
|
|
167
|
+
def test_cmd_fn() -> None:
|
|
168
|
+
"""[bold dim]Addon[/bold dim] · Scaffold a full pytest suite with coverage."""
|
|
169
|
+
test_cmd.run_command()
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
if __name__ == "__main__":
|
|
173
|
+
app()
|
app/commands/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""stup.commands — All CLI command implementations."""
|
app/commands/activate.py
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"""stup activate — Smart cross-platform venv activator."""
|
|
2
|
+
|
|
3
|
+
from app.utils import (
|
|
4
|
+
console,
|
|
5
|
+
ensure_venv_exists,
|
|
6
|
+
get_activate_command,
|
|
7
|
+
is_windows,
|
|
8
|
+
print_banner,
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def run_command() -> None:
|
|
13
|
+
"""Print the correct venv activation command for the current platform."""
|
|
14
|
+
print_banner("activate", "Smart cross-platform venv activator")
|
|
15
|
+
|
|
16
|
+
ensure_venv_exists()
|
|
17
|
+
|
|
18
|
+
cmd = get_activate_command()
|
|
19
|
+
|
|
20
|
+
console.print()
|
|
21
|
+
if is_windows():
|
|
22
|
+
console.print(" [yellow]→[/yellow] Run this in PowerShell:")
|
|
23
|
+
console.print(f" [bold white]{cmd}[/bold white]")
|
|
24
|
+
console.print()
|
|
25
|
+
console.print(" [dim]Or in CMD:[/dim]")
|
|
26
|
+
console.print(r" [dim].venv\Scripts\activate.bat[/dim]")
|
|
27
|
+
else:
|
|
28
|
+
console.print(" [yellow]→[/yellow] Run this in your terminal:")
|
|
29
|
+
console.print(f" [bold white]{cmd}[/bold white]")
|
|
30
|
+
console.print()
|
|
31
|
+
console.print(" [dim]Or use eval:[/dim]")
|
|
32
|
+
console.print(" [dim]eval $(stup activate)[/dim]")
|
|
33
|
+
console.print()
|
app/commands/add.py
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"""stup add — Thin wrapper over uv add that maintains requirements.txt."""
|
|
2
|
+
|
|
3
|
+
import typer
|
|
4
|
+
from app.utils import (
|
|
5
|
+
check_uv,
|
|
6
|
+
ensure_venv_exists,
|
|
7
|
+
print_banner,
|
|
8
|
+
print_done,
|
|
9
|
+
run,
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
def run_command(packages: list[str]) -> None:
|
|
13
|
+
"""Add packages using uv and update requirements.txt."""
|
|
14
|
+
print_banner("add", f"Adding packages: {', '.join(packages)}")
|
|
15
|
+
|
|
16
|
+
check_uv()
|
|
17
|
+
ensure_venv_exists()
|
|
18
|
+
|
|
19
|
+
# Add packages
|
|
20
|
+
pkg_str = " ".join(packages)
|
|
21
|
+
run(f"uv add {pkg_str}")
|
|
22
|
+
|
|
23
|
+
# Update requirements.txt for compatibility (clean format)
|
|
24
|
+
run("uv export --format requirements-txt --no-hashes --no-emit-project --output-file requirements.txt --quiet")
|
|
25
|
+
|
|
26
|
+
# Post-process to remove all 'via' comments for a super clean look
|
|
27
|
+
req_file = "requirements.txt"
|
|
28
|
+
with open(req_file, "r") as f:
|
|
29
|
+
lines = f.readlines()
|
|
30
|
+
with open(req_file, "w") as f:
|
|
31
|
+
for line in lines:
|
|
32
|
+
if not line.strip().startswith("#"):
|
|
33
|
+
f.write(line)
|
|
34
|
+
|
|
35
|
+
print_done()
|
app/commands/api.py
ADDED
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
"""stup api — Minimal FastAPI microservice scaffold."""
|
|
2
|
+
|
|
3
|
+
from app.utils import (
|
|
4
|
+
check_uv,
|
|
5
|
+
create_dirs,
|
|
6
|
+
ensure_venv_exists,
|
|
7
|
+
print_banner,
|
|
8
|
+
print_done,
|
|
9
|
+
run,
|
|
10
|
+
write_file,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
# ── Templates ────────────────────────────────────────────────────────
|
|
14
|
+
|
|
15
|
+
MAIN_PY = '''\
|
|
16
|
+
"""FastAPI microservice entry point."""
|
|
17
|
+
|
|
18
|
+
from fastapi import FastAPI
|
|
19
|
+
from fastapi.middleware.cors import CORSMiddleware
|
|
20
|
+
from routers import health, v1
|
|
21
|
+
from middleware.auth import AuthMiddleware
|
|
22
|
+
|
|
23
|
+
app = FastAPI(
|
|
24
|
+
title="My API",
|
|
25
|
+
version="0.1.0",
|
|
26
|
+
docs_url="/docs",
|
|
27
|
+
redoc_url="/redoc",
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
# CORS
|
|
31
|
+
app.add_middleware(
|
|
32
|
+
CORSMiddleware,
|
|
33
|
+
allow_origins=["*"],
|
|
34
|
+
allow_credentials=True,
|
|
35
|
+
allow_methods=["*"],
|
|
36
|
+
allow_headers=["*"],
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
# Custom auth middleware (optional — uncomment to enable)
|
|
40
|
+
# app.add_middleware(AuthMiddleware)
|
|
41
|
+
|
|
42
|
+
# Routers
|
|
43
|
+
app.include_router(health.router, prefix="/health", tags=["Health"])
|
|
44
|
+
app.include_router(v1.router, prefix="/api/v1", tags=["v1"])
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@app.get("/")
|
|
48
|
+
async def root():
|
|
49
|
+
return {"message": "Welcome to the API. Visit /docs for documentation."}
|
|
50
|
+
'''
|
|
51
|
+
|
|
52
|
+
HEALTH_ROUTER = '''\
|
|
53
|
+
"""Health check router."""
|
|
54
|
+
|
|
55
|
+
from fastapi import APIRouter
|
|
56
|
+
|
|
57
|
+
router = APIRouter()
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@router.get("/")
|
|
61
|
+
async def health_check():
|
|
62
|
+
return {"status": "ok"}
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
@router.get("/ready")
|
|
66
|
+
async def readiness():
|
|
67
|
+
return {"status": "ready"}
|
|
68
|
+
'''
|
|
69
|
+
|
|
70
|
+
V1_ROUTER = '''\
|
|
71
|
+
"""API v1 router."""
|
|
72
|
+
|
|
73
|
+
from fastapi import APIRouter
|
|
74
|
+
from pydantic import BaseModel
|
|
75
|
+
|
|
76
|
+
router = APIRouter()
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
class Item(BaseModel):
|
|
80
|
+
name: str
|
|
81
|
+
description: str = ""
|
|
82
|
+
price: float
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
# In-memory store (replace with database)
|
|
86
|
+
items: list[Item] = []
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
@router.get("/items")
|
|
90
|
+
async def list_items():
|
|
91
|
+
return {"items": items}
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
@router.post("/items")
|
|
95
|
+
async def create_item(item: Item):
|
|
96
|
+
items.append(item)
|
|
97
|
+
return {"message": "Item created", "item": item}
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
@router.get("/items/{item_id}")
|
|
101
|
+
async def get_item(item_id: int):
|
|
102
|
+
if 0 <= item_id < len(items):
|
|
103
|
+
return {"item": items[item_id]}
|
|
104
|
+
return {"error": "Item not found"}
|
|
105
|
+
'''
|
|
106
|
+
|
|
107
|
+
MODELS_INIT = '''\
|
|
108
|
+
"""Pydantic models / schemas."""
|
|
109
|
+
|
|
110
|
+
from pydantic import BaseModel
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
class HealthResponse(BaseModel):
|
|
114
|
+
status: str
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
class ErrorResponse(BaseModel):
|
|
118
|
+
detail: str
|
|
119
|
+
'''
|
|
120
|
+
|
|
121
|
+
AUTH_MIDDLEWARE = '''\
|
|
122
|
+
"""Authentication middleware stub."""
|
|
123
|
+
|
|
124
|
+
from starlette.middleware.base import BaseHTTPMiddleware
|
|
125
|
+
from starlette.requests import Request
|
|
126
|
+
from starlette.responses import JSONResponse
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
class AuthMiddleware(BaseHTTPMiddleware):
|
|
130
|
+
"""Simple API key authentication middleware.
|
|
131
|
+
|
|
132
|
+
TODO: Replace with your preferred auth strategy (JWT, OAuth, etc.)
|
|
133
|
+
"""
|
|
134
|
+
|
|
135
|
+
SKIP_PATHS = {"/", "/docs", "/redoc", "/openapi.json", "/health", "/health/ready"}
|
|
136
|
+
|
|
137
|
+
async def dispatch(self, request: Request, call_next):
|
|
138
|
+
if request.url.path in self.SKIP_PATHS:
|
|
139
|
+
return await call_next(request)
|
|
140
|
+
|
|
141
|
+
api_key = request.headers.get("X-API-Key")
|
|
142
|
+
if not api_key:
|
|
143
|
+
return JSONResponse(
|
|
144
|
+
status_code=401,
|
|
145
|
+
content={"detail": "Missing X-API-Key header"},
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
# TODO: Validate api_key against your store
|
|
149
|
+
# if not is_valid_key(api_key):
|
|
150
|
+
# return JSONResponse(status_code=403, content={"detail": "Invalid API key"})
|
|
151
|
+
|
|
152
|
+
return await call_next(request)
|
|
153
|
+
'''
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def run_command() -> None:
|
|
157
|
+
"""Scaffold a minimal FastAPI microservice."""
|
|
158
|
+
print_banner("api", "Minimal FastAPI microservice with auth middleware")
|
|
159
|
+
|
|
160
|
+
check_uv()
|
|
161
|
+
ensure_venv_exists()
|
|
162
|
+
|
|
163
|
+
# Install dependencies
|
|
164
|
+
run("uv add fastapi uvicorn pydantic")
|
|
165
|
+
|
|
166
|
+
# Create directory structure
|
|
167
|
+
create_dirs("routers/v1", "routers/health", "models", "middleware")
|
|
168
|
+
|
|
169
|
+
# Create files
|
|
170
|
+
write_file("main.py", MAIN_PY)
|
|
171
|
+
write_file("routers/__init__.py", "")
|
|
172
|
+
write_file("routers/health/__init__.py", HEALTH_ROUTER)
|
|
173
|
+
write_file("routers/v1/__init__.py", V1_ROUTER)
|
|
174
|
+
write_file("models/__init__.py", MODELS_INIT)
|
|
175
|
+
write_file("middleware/__init__.py", "")
|
|
176
|
+
write_file("middleware/auth.py", AUTH_MIDDLEWARE)
|
|
177
|
+
|
|
178
|
+
print_done()
|
app/commands/cli_cmd.py
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
"""stup cli — PyPI-ready CLI package with Typer + Rich."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
|
|
5
|
+
from app.utils import (
|
|
6
|
+
check_uv,
|
|
7
|
+
create_dirs,
|
|
8
|
+
ensure_venv_exists,
|
|
9
|
+
print_banner,
|
|
10
|
+
print_done,
|
|
11
|
+
run,
|
|
12
|
+
write_file,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
# ── Templates ────────────────────────────────────────────────────────
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _get_templates(project_name: str) -> dict[str, str]:
|
|
19
|
+
"""Return template files with project name substituted."""
|
|
20
|
+
return {
|
|
21
|
+
"init": f'''\
|
|
22
|
+
"""{project_name} — A CLI tool built with Typer + Rich."""
|
|
23
|
+
|
|
24
|
+
__version__ = "0.1.0"
|
|
25
|
+
''',
|
|
26
|
+
"main": f'''\
|
|
27
|
+
"""Entry point for {project_name} CLI."""
|
|
28
|
+
|
|
29
|
+
import typer
|
|
30
|
+
from rich.console import Console
|
|
31
|
+
from {project_name} import __version__
|
|
32
|
+
from {project_name}.commands import hello
|
|
33
|
+
|
|
34
|
+
console = Console()
|
|
35
|
+
|
|
36
|
+
app = typer.Typer(
|
|
37
|
+
name="{project_name}",
|
|
38
|
+
help="🚀 {project_name} CLI",
|
|
39
|
+
add_completion=False,
|
|
40
|
+
rich_markup_mode="rich",
|
|
41
|
+
no_args_is_help=True,
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _version_callback(value: bool) -> None:
|
|
46
|
+
if value:
|
|
47
|
+
console.print(f"[bold cyan]{project_name}[/bold cyan] v{{__version__}}")
|
|
48
|
+
raise typer.Exit()
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@app.callback()
|
|
52
|
+
def main(
|
|
53
|
+
version: bool = typer.Option(
|
|
54
|
+
False, "--version", "-v", help="Show version.", callback=_version_callback, is_eager=True
|
|
55
|
+
),
|
|
56
|
+
) -> None:
|
|
57
|
+
pass
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@app.command()
|
|
61
|
+
def greet(name: str = typer.Argument("World", help="Name to greet.")) -> None:
|
|
62
|
+
"""Say hello to someone."""
|
|
63
|
+
hello.greet(name)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
if __name__ == "__main__":
|
|
67
|
+
app()
|
|
68
|
+
''',
|
|
69
|
+
"hello": '''\
|
|
70
|
+
"""Hello command implementation."""
|
|
71
|
+
|
|
72
|
+
from rich.console import Console
|
|
73
|
+
|
|
74
|
+
console = Console()
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def greet(name: str) -> None:
|
|
78
|
+
"""Print a greeting."""
|
|
79
|
+
console.print(f"[bold green]Hello, {name}![/bold green] 👋")
|
|
80
|
+
''',
|
|
81
|
+
"commands_init": '''\
|
|
82
|
+
"""CLI commands package."""
|
|
83
|
+
''',
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def run_command() -> None:
|
|
88
|
+
"""Scaffold a PyPI-ready CLI package."""
|
|
89
|
+
print_banner("cli", "PyPI-ready CLI package with Typer + Rich")
|
|
90
|
+
|
|
91
|
+
check_uv()
|
|
92
|
+
ensure_venv_exists()
|
|
93
|
+
|
|
94
|
+
# Use current directory name as project name
|
|
95
|
+
project_name = os.path.basename(os.getcwd()).replace("-", "_").replace(" ", "_").lower()
|
|
96
|
+
|
|
97
|
+
# Install CLI dependencies
|
|
98
|
+
run("uv add typer rich")
|
|
99
|
+
|
|
100
|
+
templates = _get_templates(project_name)
|
|
101
|
+
|
|
102
|
+
# Create package structure
|
|
103
|
+
create_dirs(f"src/{project_name}/commands")
|
|
104
|
+
|
|
105
|
+
write_file(f"src/{project_name}/__init__.py", templates["init"])
|
|
106
|
+
write_file(f"src/{project_name}/__main__.py", templates["main"])
|
|
107
|
+
write_file(f"src/{project_name}/commands/__init__.py", templates["commands_init"])
|
|
108
|
+
write_file(f"src/{project_name}/commands/hello.py", templates["hello"])
|
|
109
|
+
|
|
110
|
+
# Create README
|
|
111
|
+
readme = f"""\
|
|
112
|
+
# {project_name}
|
|
113
|
+
|
|
114
|
+
A CLI tool built with [Typer](https://typer.tiangolo.com/) + [Rich](https://rich.readthedocs.io/).
|
|
115
|
+
|
|
116
|
+
## Installation
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
pip install {project_name}
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
## Usage
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
{project_name} --help
|
|
126
|
+
{project_name} greet "World"
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
## Development
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
uv pip install -e .
|
|
133
|
+
```
|
|
134
|
+
"""
|
|
135
|
+
write_file("README.md", readme)
|
|
136
|
+
|
|
137
|
+
print_done()
|