@malamute/ai-rules 1.0.0 → 1.2.0

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 (133) hide show
  1. package/README.md +270 -121
  2. package/bin/cli.js +5 -2
  3. package/configs/_shared/.claude/rules/conventions/documentation.md +324 -0
  4. package/configs/_shared/.claude/rules/conventions/git.md +265 -0
  5. package/configs/_shared/.claude/rules/{performance.md → conventions/performance.md} +1 -1
  6. package/configs/_shared/.claude/rules/conventions/principles.md +334 -0
  7. package/configs/_shared/.claude/rules/devops/ci-cd.md +262 -0
  8. package/configs/_shared/.claude/rules/devops/docker.md +275 -0
  9. package/configs/_shared/.claude/rules/devops/nx.md +194 -0
  10. package/configs/_shared/.claude/rules/domain/backend/api-design.md +203 -0
  11. package/configs/_shared/.claude/rules/lang/csharp/async.md +220 -0
  12. package/configs/_shared/.claude/rules/lang/csharp/csharp.md +314 -0
  13. package/configs/_shared/.claude/rules/lang/csharp/linq.md +210 -0
  14. package/configs/_shared/.claude/rules/lang/python/async.md +337 -0
  15. package/configs/_shared/.claude/rules/lang/python/celery.md +476 -0
  16. package/configs/_shared/.claude/rules/lang/python/config.md +339 -0
  17. package/configs/{python/.claude/rules → _shared/.claude/rules/lang/python}/database/sqlalchemy.md +6 -1
  18. package/configs/_shared/.claude/rules/lang/python/deployment.md +523 -0
  19. package/configs/_shared/.claude/rules/lang/python/error-handling.md +330 -0
  20. package/configs/_shared/.claude/rules/lang/python/migrations.md +421 -0
  21. package/configs/_shared/.claude/rules/lang/python/python.md +172 -0
  22. package/configs/_shared/.claude/rules/lang/python/repository.md +383 -0
  23. package/configs/{python/.claude/rules → _shared/.claude/rules/lang/python}/testing.md +2 -69
  24. package/configs/_shared/.claude/rules/lang/typescript/async.md +447 -0
  25. package/configs/_shared/.claude/rules/lang/typescript/generics.md +356 -0
  26. package/configs/_shared/.claude/rules/lang/typescript/typescript.md +212 -0
  27. package/configs/_shared/.claude/rules/quality/error-handling.md +48 -0
  28. package/configs/_shared/.claude/rules/quality/logging.md +45 -0
  29. package/configs/_shared/.claude/rules/quality/observability.md +240 -0
  30. package/configs/_shared/.claude/rules/quality/testing-patterns.md +65 -0
  31. package/configs/_shared/.claude/rules/security/secrets-management.md +222 -0
  32. package/configs/_shared/.claude/skills/analysis/explore/SKILL.md +257 -0
  33. package/configs/_shared/.claude/skills/analysis/security-audit/SKILL.md +184 -0
  34. package/configs/_shared/.claude/skills/dev/api-endpoint/SKILL.md +126 -0
  35. package/configs/_shared/.claude/{commands/generate-tests.md → skills/dev/generate-tests/SKILL.md} +6 -0
  36. package/configs/_shared/.claude/{commands/fix-issue.md → skills/git/fix-issue/SKILL.md} +6 -0
  37. package/configs/_shared/.claude/{commands/review-pr.md → skills/git/review-pr/SKILL.md} +6 -0
  38. package/configs/_shared/.claude/skills/infra/deploy/SKILL.md +139 -0
  39. package/configs/_shared/.claude/skills/infra/docker/SKILL.md +95 -0
  40. package/configs/_shared/.claude/skills/infra/migration/SKILL.md +158 -0
  41. package/configs/_shared/.claude/skills/nx/nx-affected/SKILL.md +72 -0
  42. package/configs/_shared/.claude/skills/nx/nx-lib/SKILL.md +375 -0
  43. package/configs/_shared/CLAUDE.md +52 -149
  44. package/configs/angular/.claude/rules/{components.md → core/components.md} +69 -15
  45. package/configs/angular/.claude/rules/core/resource.md +285 -0
  46. package/configs/angular/.claude/rules/core/signals.md +323 -0
  47. package/configs/angular/.claude/rules/http.md +338 -0
  48. package/configs/angular/.claude/rules/routing.md +291 -0
  49. package/configs/angular/.claude/rules/ssr.md +312 -0
  50. package/configs/angular/.claude/rules/state/signal-store.md +408 -0
  51. package/configs/angular/.claude/rules/{state.md → state/state.md} +2 -2
  52. package/configs/angular/.claude/rules/testing.md +7 -7
  53. package/configs/angular/.claude/rules/ui/aria.md +422 -0
  54. package/configs/angular/.claude/rules/ui/forms.md +424 -0
  55. package/configs/angular/.claude/rules/ui/pipes-directives.md +335 -0
  56. package/configs/angular/.claude/settings.json +1 -0
  57. package/configs/angular/.claude/skills/ngrx-slice/SKILL.md +362 -0
  58. package/configs/angular/.claude/skills/signal-store/SKILL.md +445 -0
  59. package/configs/angular/CLAUDE.md +24 -216
  60. package/configs/dotnet/.claude/rules/background-services.md +552 -0
  61. package/configs/dotnet/.claude/rules/configuration.md +426 -0
  62. package/configs/dotnet/.claude/rules/ddd.md +447 -0
  63. package/configs/dotnet/.claude/rules/dependency-injection.md +343 -0
  64. package/configs/dotnet/.claude/rules/mediatr.md +320 -0
  65. package/configs/dotnet/.claude/rules/middleware.md +489 -0
  66. package/configs/dotnet/.claude/rules/result-pattern.md +363 -0
  67. package/configs/dotnet/.claude/rules/validation.md +388 -0
  68. package/configs/dotnet/.claude/settings.json +21 -3
  69. package/configs/dotnet/CLAUDE.md +53 -286
  70. package/configs/fastapi/.claude/rules/background-tasks.md +254 -0
  71. package/configs/fastapi/.claude/rules/dependencies.md +170 -0
  72. package/configs/{python → fastapi}/.claude/rules/fastapi.md +61 -1
  73. package/configs/fastapi/.claude/rules/lifespan.md +274 -0
  74. package/configs/fastapi/.claude/rules/middleware.md +229 -0
  75. package/configs/fastapi/.claude/rules/pydantic.md +433 -0
  76. package/configs/fastapi/.claude/rules/responses.md +251 -0
  77. package/configs/fastapi/.claude/rules/routers.md +202 -0
  78. package/configs/fastapi/.claude/rules/security.md +222 -0
  79. package/configs/fastapi/.claude/rules/testing.md +251 -0
  80. package/configs/fastapi/.claude/rules/websockets.md +298 -0
  81. package/configs/fastapi/.claude/settings.json +33 -0
  82. package/configs/fastapi/CLAUDE.md +144 -0
  83. package/configs/flask/.claude/rules/blueprints.md +208 -0
  84. package/configs/flask/.claude/rules/cli.md +285 -0
  85. package/configs/flask/.claude/rules/configuration.md +281 -0
  86. package/configs/flask/.claude/rules/context.md +238 -0
  87. package/configs/flask/.claude/rules/error-handlers.md +278 -0
  88. package/configs/flask/.claude/rules/extensions.md +278 -0
  89. package/configs/flask/.claude/rules/flask.md +171 -0
  90. package/configs/flask/.claude/rules/marshmallow.md +206 -0
  91. package/configs/flask/.claude/rules/security.md +267 -0
  92. package/configs/flask/.claude/rules/testing.md +284 -0
  93. package/configs/flask/.claude/settings.json +33 -0
  94. package/configs/flask/CLAUDE.md +166 -0
  95. package/configs/nestjs/.claude/rules/common-patterns.md +300 -0
  96. package/configs/nestjs/.claude/rules/filters.md +376 -0
  97. package/configs/nestjs/.claude/rules/interceptors.md +317 -0
  98. package/configs/nestjs/.claude/rules/middleware.md +321 -0
  99. package/configs/nestjs/.claude/rules/modules.md +26 -0
  100. package/configs/nestjs/.claude/rules/pipes.md +351 -0
  101. package/configs/nestjs/.claude/rules/websockets.md +451 -0
  102. package/configs/nestjs/.claude/settings.json +16 -2
  103. package/configs/nestjs/CLAUDE.md +57 -215
  104. package/configs/nextjs/.claude/rules/api-routes.md +358 -0
  105. package/configs/nextjs/.claude/rules/authentication.md +355 -0
  106. package/configs/nextjs/.claude/rules/components.md +52 -0
  107. package/configs/nextjs/.claude/rules/data-fetching.md +249 -0
  108. package/configs/nextjs/.claude/rules/database.md +400 -0
  109. package/configs/nextjs/.claude/rules/middleware.md +303 -0
  110. package/configs/nextjs/.claude/rules/routing.md +324 -0
  111. package/configs/nextjs/.claude/rules/seo.md +350 -0
  112. package/configs/nextjs/.claude/rules/server-actions.md +353 -0
  113. package/configs/nextjs/.claude/rules/state/zustand.md +6 -6
  114. package/configs/nextjs/.claude/settings.json +5 -0
  115. package/configs/nextjs/CLAUDE.md +69 -331
  116. package/package.json +23 -9
  117. package/src/cli.js +220 -0
  118. package/src/config.js +29 -0
  119. package/src/index.js +13 -0
  120. package/src/installer.js +361 -0
  121. package/src/merge.js +116 -0
  122. package/src/tech-config.json +29 -0
  123. package/src/utils.js +96 -0
  124. package/configs/python/.claude/rules/flask.md +0 -332
  125. package/configs/python/.claude/settings.json +0 -18
  126. package/configs/python/CLAUDE.md +0 -273
  127. package/src/install.js +0 -315
  128. /package/configs/_shared/.claude/rules/{accessibility.md → domain/frontend/accessibility.md} +0 -0
  129. /package/configs/_shared/.claude/rules/{security.md → security/security.md} +0 -0
  130. /package/configs/_shared/.claude/skills/{debug → dev/debug}/SKILL.md +0 -0
  131. /package/configs/_shared/.claude/skills/{learning → dev/learning}/SKILL.md +0 -0
  132. /package/configs/_shared/.claude/skills/{spec → dev/spec}/SKILL.md +0 -0
  133. /package/configs/_shared/.claude/skills/{review → git/review}/SKILL.md +0 -0
@@ -0,0 +1,144 @@
1
+ # FastAPI Project Guidelines
2
+
3
+ @../_shared/CLAUDE.md
4
+
5
+ ## Stack
6
+
7
+ - Python 3.12+
8
+ - FastAPI 0.115+ (Pydantic v2 by default)
9
+ - SQLAlchemy 2.0+ (async support)
10
+ - Pydantic v2 for validation
11
+ - pytest + httpx for testing
12
+ - uv or poetry for dependencies
13
+
14
+ ## Architecture
15
+
16
+ ```
17
+ src/app/
18
+ ├── main.py # Entry point
19
+ ├── config.py # Settings (pydantic-settings)
20
+ ├── database.py # DB session, engine
21
+ ├── [domain]/ # Feature modules
22
+ │ ├── router.py # API endpoints
23
+ │ ├── schemas.py # Pydantic models (request/response)
24
+ │ ├── models.py # SQLAlchemy models
25
+ │ ├── service.py # Business logic
26
+ │ ├── repository.py # Data access
27
+ │ └── dependencies.py # Route dependencies
28
+ ├── core/ # Shared utilities
29
+ │ ├── exceptions.py
30
+ │ └── security.py
31
+ └── common/
32
+ ├── models.py # Base models
33
+ └── schemas.py # Shared schemas
34
+ ```
35
+
36
+ ## FastAPI Patterns
37
+
38
+ ### Dependency Injection
39
+
40
+ ```python
41
+ from typing import Annotated
42
+ from fastapi import Depends
43
+
44
+ CurrentUser = Annotated[User, Depends(get_current_user)]
45
+ DbSession = Annotated[AsyncSession, Depends(get_db)]
46
+
47
+ @router.get("/users/me")
48
+ async def get_me(user: CurrentUser, db: DbSession) -> UserResponse:
49
+ return await user_service.get_profile(db, user.id)
50
+ ```
51
+
52
+ ### Lifespan (not `on_event`)
53
+
54
+ ```python
55
+ from contextlib import asynccontextmanager
56
+
57
+ @asynccontextmanager
58
+ async def lifespan(app: FastAPI):
59
+ # Startup
60
+ app.state.db = await create_engine()
61
+ app.state.redis = await aioredis.from_url(settings.redis_url)
62
+ yield
63
+ # Shutdown
64
+ await app.state.db.dispose()
65
+ await app.state.redis.close()
66
+
67
+ app = FastAPI(lifespan=lifespan)
68
+ ```
69
+
70
+ ### Response Models
71
+
72
+ ```python
73
+ @router.get(
74
+ "/users/{user_id}",
75
+ response_model=UserResponse,
76
+ status_code=status.HTTP_200_OK,
77
+ )
78
+ async def get_user(user_id: int, db: DbSession) -> User:
79
+ user = await user_repo.get(db, user_id)
80
+ if not user:
81
+ raise HTTPException(status_code=404, detail="User not found")
82
+ return user
83
+ ```
84
+
85
+ ## Pydantic v2
86
+
87
+ ```python
88
+ from pydantic import BaseModel, ConfigDict, Field, EmailStr
89
+
90
+ class UserBase(BaseModel):
91
+ email: EmailStr
92
+ name: str = Field(min_length=1, max_length=100)
93
+
94
+ class UserCreate(UserBase):
95
+ password: str = Field(min_length=8)
96
+
97
+ class UserResponse(UserBase):
98
+ id: int
99
+ model_config = ConfigDict(from_attributes=True)
100
+ ```
101
+
102
+ ## SQLAlchemy 2.0
103
+
104
+ ```python
105
+ from sqlalchemy.orm import Mapped, mapped_column, DeclarativeBase
106
+
107
+ class Base(DeclarativeBase):
108
+ pass
109
+
110
+ class User(Base):
111
+ __tablename__ = "users"
112
+
113
+ id: Mapped[int] = mapped_column(primary_key=True)
114
+ email: Mapped[str] = mapped_column(unique=True, index=True)
115
+ hashed_password: Mapped[str]
116
+ is_active: Mapped[bool] = mapped_column(default=True)
117
+ ```
118
+
119
+ ## Error Handling
120
+
121
+ ```python
122
+ class AppException(Exception):
123
+ def __init__(self, status_code: int, detail: str):
124
+ self.status_code = status_code
125
+ self.detail = detail
126
+
127
+ @app.exception_handler(AppException)
128
+ async def app_exception_handler(request: Request, exc: AppException):
129
+ return JSONResponse(
130
+ status_code=exc.status_code,
131
+ content={"error": exc.detail},
132
+ )
133
+ ```
134
+
135
+ ## Commands
136
+
137
+ ```bash
138
+ uvicorn app.main:app --reload # Dev server
139
+ pytest # Run tests
140
+ pytest --cov=app # Coverage
141
+ ruff check . && ruff format . # Lint + format
142
+ alembic upgrade head # Run migrations
143
+ alembic revision --autogenerate # Generate migration
144
+ ```
@@ -0,0 +1,208 @@
1
+ ---
2
+ paths:
3
+ - "**/*.py"
4
+ ---
5
+
6
+ # Flask Blueprint Patterns
7
+
8
+ ## Basic Blueprint
9
+
10
+ ```python
11
+ # GOOD - organized blueprint
12
+ from flask import Blueprint, request, jsonify
13
+
14
+ users_bp = Blueprint("users", __name__)
15
+
16
+ @users_bp.route("/", methods=["GET"])
17
+ def list_users():
18
+ """List all users."""
19
+ users = UserService.get_all()
20
+ return jsonify(UserSchema(many=True).dump(users))
21
+
22
+ @users_bp.route("/", methods=["POST"])
23
+ def create_user():
24
+ """Create a new user."""
25
+ schema = UserCreateSchema()
26
+ data = schema.load(request.get_json())
27
+ user = UserService.create(data)
28
+ return jsonify(UserSchema().dump(user)), 201
29
+
30
+ @users_bp.route("/<int:user_id>")
31
+ def get_user(user_id: int):
32
+ """Get user by ID."""
33
+ user = UserService.get_by_id(user_id)
34
+ if not user:
35
+ return jsonify({"error": "User not found"}), 404
36
+ return jsonify(UserSchema().dump(user))
37
+ ```
38
+
39
+ ## Blueprint Registration
40
+
41
+ ```python
42
+ # app/__init__.py
43
+ from flask import Flask
44
+
45
+ def create_app(config_name: str = "development") -> Flask:
46
+ app = Flask(__name__)
47
+ app.config.from_object(config[config_name])
48
+
49
+ # Register blueprints
50
+ from app.users import users_bp
51
+ from app.auth import auth_bp
52
+ from app.products import products_bp
53
+
54
+ app.register_blueprint(users_bp, url_prefix="/api/v1/users")
55
+ app.register_blueprint(auth_bp, url_prefix="/api/v1/auth")
56
+ app.register_blueprint(products_bp, url_prefix="/api/v1/products")
57
+
58
+ return app
59
+ ```
60
+
61
+ ## Blueprint with Resources
62
+
63
+ ```python
64
+ # app/users/__init__.py
65
+ from flask import Blueprint
66
+
67
+ users_bp = Blueprint("users", __name__)
68
+
69
+ # Import routes after blueprint creation to avoid circular imports
70
+ from app.users import routes # noqa: E402, F401
71
+ ```
72
+
73
+ ```python
74
+ # app/users/routes.py
75
+ from app.users import users_bp
76
+ from app.users.schemas import UserSchema, UserCreateSchema
77
+ from app.users.services import UserService
78
+
79
+ @users_bp.route("/", methods=["GET"])
80
+ def list_users():
81
+ ...
82
+ ```
83
+
84
+ ## Nested Blueprints
85
+
86
+ ```python
87
+ # API versioning with nested blueprints
88
+ api_v1 = Blueprint("api_v1", __name__, url_prefix="/api/v1")
89
+ api_v2 = Blueprint("api_v2", __name__, url_prefix="/api/v2")
90
+
91
+ # Register child blueprints
92
+ api_v1.register_blueprint(users_bp, url_prefix="/users")
93
+ api_v1.register_blueprint(products_bp, url_prefix="/products")
94
+
95
+ api_v2.register_blueprint(users_v2_bp, url_prefix="/users")
96
+
97
+ # Register with app
98
+ app.register_blueprint(api_v1)
99
+ app.register_blueprint(api_v2)
100
+ ```
101
+
102
+ ## Blueprint-Specific Error Handlers
103
+
104
+ ```python
105
+ users_bp = Blueprint("users", __name__)
106
+
107
+ @users_bp.errorhandler(404)
108
+ def user_not_found(error):
109
+ return jsonify({"error": "User not found"}), 404
110
+
111
+ @users_bp.errorhandler(ValidationError)
112
+ def validation_error(error):
113
+ return jsonify({"error": "Validation failed", "details": error.messages}), 400
114
+ ```
115
+
116
+ ## Blueprint Hooks
117
+
118
+ ```python
119
+ users_bp = Blueprint("users", __name__)
120
+
121
+ @users_bp.before_request
122
+ def before_user_request():
123
+ """Run before every request to this blueprint."""
124
+ # Verify API key, log request, etc.
125
+ if not verify_api_key(request.headers.get("X-API-Key")):
126
+ return jsonify({"error": "Invalid API key"}), 401
127
+
128
+ @users_bp.after_request
129
+ def after_user_request(response):
130
+ """Run after every request to this blueprint."""
131
+ response.headers["X-Blueprint"] = "users"
132
+ return response
133
+ ```
134
+
135
+ ## Blueprint URL Builders
136
+
137
+ ```python
138
+ from flask import url_for
139
+
140
+ # Build URL for blueprint route
141
+ url = url_for("users.get_user", user_id=1) # /api/v1/users/1
142
+
143
+ # With external URL
144
+ url = url_for("users.get_user", user_id=1, _external=True)
145
+ # https://example.com/api/v1/users/1
146
+ ```
147
+
148
+ ## Blueprint Templates
149
+
150
+ ```python
151
+ # Blueprint with templates
152
+ users_bp = Blueprint(
153
+ "users",
154
+ __name__,
155
+ template_folder="templates",
156
+ static_folder="static",
157
+ )
158
+
159
+ @users_bp.route("/profile")
160
+ def profile():
161
+ return render_template("users/profile.html") # From blueprint's templates/
162
+ ```
163
+
164
+ ## Blueprint Context Processor
165
+
166
+ ```python
167
+ @users_bp.context_processor
168
+ def user_context():
169
+ """Add variables to all templates rendered by this blueprint."""
170
+ return {
171
+ "current_user": get_current_user(),
172
+ "user_count": User.query.count(),
173
+ }
174
+ ```
175
+
176
+ ## Class-Based Views with Blueprints
177
+
178
+ ```python
179
+ from flask.views import MethodView
180
+
181
+ class UserAPI(MethodView):
182
+ def get(self, user_id: int | None = None):
183
+ if user_id is None:
184
+ return jsonify(UserSchema(many=True).dump(User.query.all()))
185
+ user = User.query.get_or_404(user_id)
186
+ return jsonify(UserSchema().dump(user))
187
+
188
+ def post(self):
189
+ data = UserCreateSchema().load(request.get_json())
190
+ user = UserService.create(data)
191
+ return jsonify(UserSchema().dump(user)), 201
192
+
193
+ def put(self, user_id: int):
194
+ user = User.query.get_or_404(user_id)
195
+ data = UserUpdateSchema().load(request.get_json())
196
+ user = UserService.update(user, data)
197
+ return jsonify(UserSchema().dump(user))
198
+
199
+ def delete(self, user_id: int):
200
+ user = User.query.get_or_404(user_id)
201
+ UserService.delete(user)
202
+ return "", 204
203
+
204
+ # Register view
205
+ user_view = UserAPI.as_view("user_api")
206
+ users_bp.add_url_rule("/", view_func=user_view, methods=["GET", "POST"])
207
+ users_bp.add_url_rule("/<int:user_id>", view_func=user_view, methods=["GET", "PUT", "DELETE"])
208
+ ```
@@ -0,0 +1,285 @@
1
+ ---
2
+ paths:
3
+ - "**/*.py"
4
+ ---
5
+
6
+ # Flask CLI Commands
7
+
8
+ ## Basic Commands
9
+
10
+ ```python
11
+ import click
12
+ from flask import Flask
13
+ from flask.cli import with_appcontext
14
+
15
+ app = Flask(__name__)
16
+
17
+ @app.cli.command("init-db")
18
+ @with_appcontext
19
+ def init_db_command():
20
+ """Initialize the database."""
21
+ db.create_all()
22
+ click.echo("Database initialized.")
23
+
24
+ @app.cli.command("seed")
25
+ @with_appcontext
26
+ def seed_command():
27
+ """Seed the database with sample data."""
28
+ from app.seeds import seed_all
29
+ seed_all()
30
+ click.echo("Database seeded.")
31
+
32
+ # Usage:
33
+ # flask init-db
34
+ # flask seed
35
+ ```
36
+
37
+ ## Commands with Arguments
38
+
39
+ ```python
40
+ @app.cli.command("create-user")
41
+ @click.argument("email")
42
+ @click.argument("name")
43
+ @click.option("--admin", is_flag=True, help="Make user an admin")
44
+ @with_appcontext
45
+ def create_user_command(email: str, name: str, admin: bool):
46
+ """Create a new user."""
47
+ user = User(email=email, name=name, is_admin=admin)
48
+ db.session.add(user)
49
+ db.session.commit()
50
+ click.echo(f"Created user: {user.email} (admin={admin})")
51
+
52
+ # Usage:
53
+ # flask create-user john@example.com "John Doe"
54
+ # flask create-user admin@example.com "Admin" --admin
55
+ ```
56
+
57
+ ## Commands with Options
58
+
59
+ ```python
60
+ @app.cli.command("export-users")
61
+ @click.option("--format", type=click.Choice(["json", "csv"]), default="json")
62
+ @click.option("--output", "-o", type=click.Path(), default="users.json")
63
+ @click.option("--active-only", is_flag=True, help="Export only active users")
64
+ @with_appcontext
65
+ def export_users_command(format: str, output: str, active_only: bool):
66
+ """Export users to a file."""
67
+ query = User.query
68
+ if active_only:
69
+ query = query.filter(User.is_active == True)
70
+
71
+ users = query.all()
72
+
73
+ if format == "json":
74
+ data = UserSchema(many=True).dump(users)
75
+ with open(output, "w") as f:
76
+ json.dump(data, f, indent=2)
77
+ elif format == "csv":
78
+ # CSV export logic
79
+ pass
80
+
81
+ click.echo(f"Exported {len(users)} users to {output}")
82
+
83
+ # Usage:
84
+ # flask export-users --format csv -o users.csv --active-only
85
+ ```
86
+
87
+ ## Command Groups
88
+
89
+ ```python
90
+ @app.cli.group()
91
+ def user():
92
+ """User management commands."""
93
+ pass
94
+
95
+ @user.command("create")
96
+ @click.argument("email")
97
+ @click.argument("name")
98
+ @with_appcontext
99
+ def user_create(email: str, name: str):
100
+ """Create a new user."""
101
+ user = User(email=email, name=name)
102
+ db.session.add(user)
103
+ db.session.commit()
104
+ click.echo(f"Created: {user.email}")
105
+
106
+ @user.command("delete")
107
+ @click.argument("email")
108
+ @click.confirmation_option(prompt="Are you sure?")
109
+ @with_appcontext
110
+ def user_delete(email: str):
111
+ """Delete a user."""
112
+ user = User.query.filter_by(email=email).first()
113
+ if not user:
114
+ click.echo(f"User not found: {email}", err=True)
115
+ return
116
+
117
+ db.session.delete(user)
118
+ db.session.commit()
119
+ click.echo(f"Deleted: {email}")
120
+
121
+ @user.command("list")
122
+ @click.option("--limit", default=10)
123
+ @with_appcontext
124
+ def user_list(limit: int):
125
+ """List users."""
126
+ users = User.query.limit(limit).all()
127
+ for user in users:
128
+ click.echo(f"{user.id}: {user.email} ({user.name})")
129
+
130
+ # Usage:
131
+ # flask user create john@example.com "John"
132
+ # flask user delete john@example.com
133
+ # flask user list --limit 20
134
+ ```
135
+
136
+ ## Blueprint Commands
137
+
138
+ ```python
139
+ # In blueprint
140
+ from flask import Blueprint
141
+
142
+ users_bp = Blueprint("users", __name__, cli_group="users")
143
+
144
+ @users_bp.cli.command("sync")
145
+ @with_appcontext
146
+ def sync_users():
147
+ """Sync users from external source."""
148
+ # Sync logic
149
+ click.echo("Users synced")
150
+
151
+ # Usage:
152
+ # flask users sync
153
+ ```
154
+
155
+ ## Progress Bars
156
+
157
+ ```python
158
+ @app.cli.command("migrate-data")
159
+ @with_appcontext
160
+ def migrate_data_command():
161
+ """Migrate data with progress bar."""
162
+ records = OldModel.query.all()
163
+
164
+ with click.progressbar(records, label="Migrating") as bar:
165
+ for record in bar:
166
+ new_record = migrate_record(record)
167
+ db.session.add(new_record)
168
+
169
+ db.session.commit()
170
+ click.echo("Migration complete!")
171
+ ```
172
+
173
+ ## Colored Output
174
+
175
+ ```python
176
+ @app.cli.command("check")
177
+ @with_appcontext
178
+ def check_command():
179
+ """Check application health."""
180
+ # Database
181
+ try:
182
+ db.session.execute("SELECT 1")
183
+ click.secho("✓ Database: OK", fg="green")
184
+ except Exception as e:
185
+ click.secho(f"✗ Database: {e}", fg="red")
186
+
187
+ # Redis
188
+ try:
189
+ redis_client.ping()
190
+ click.secho("✓ Redis: OK", fg="green")
191
+ except Exception as e:
192
+ click.secho(f"✗ Redis: {e}", fg="red")
193
+
194
+ # External API
195
+ try:
196
+ response = requests.get(f"{API_URL}/health", timeout=5)
197
+ response.raise_for_status()
198
+ click.secho("✓ External API: OK", fg="green")
199
+ except Exception as e:
200
+ click.secho(f"✗ External API: {e}", fg="yellow")
201
+ ```
202
+
203
+ ## Interactive Prompts
204
+
205
+ ```python
206
+ @app.cli.command("setup")
207
+ def setup_command():
208
+ """Interactive setup wizard."""
209
+ click.echo("Welcome to the setup wizard!")
210
+
211
+ # Text input
212
+ app_name = click.prompt("Application name", default="My App")
213
+
214
+ # Password input
215
+ secret = click.prompt("Secret key", hide_input=True)
216
+
217
+ # Choice
218
+ env = click.prompt(
219
+ "Environment",
220
+ type=click.Choice(["development", "staging", "production"]),
221
+ default="development",
222
+ )
223
+
224
+ # Confirmation
225
+ if click.confirm("Save configuration?"):
226
+ save_config(app_name=app_name, secret=secret, env=env)
227
+ click.echo("Configuration saved!")
228
+ else:
229
+ click.echo("Aborted.")
230
+ ```
231
+
232
+ ## Async Commands (Flask 2.0+)
233
+
234
+ ```python
235
+ import asyncio
236
+
237
+ @app.cli.command("async-task")
238
+ @with_appcontext
239
+ def async_task_command():
240
+ """Run async task."""
241
+ asyncio.run(run_async_task())
242
+
243
+ async def run_async_task():
244
+ async with aiohttp.ClientSession() as session:
245
+ async with session.get("https://api.example.com/data") as response:
246
+ data = await response.json()
247
+ click.echo(f"Fetched {len(data)} records")
248
+ ```
249
+
250
+ ## Custom CLI Script
251
+
252
+ ```python
253
+ # manage.py
254
+ import click
255
+ from app import create_app, db
256
+
257
+ app = create_app()
258
+
259
+ @click.group()
260
+ def cli():
261
+ """Management script."""
262
+ pass
263
+
264
+ @cli.command()
265
+ def runserver():
266
+ """Run development server."""
267
+ app.run(debug=True)
268
+
269
+ @cli.command()
270
+ @click.option("--drop", is_flag=True, help="Drop tables first")
271
+ def initdb(drop: bool):
272
+ """Initialize database."""
273
+ with app.app_context():
274
+ if drop:
275
+ db.drop_all()
276
+ db.create_all()
277
+ click.echo("Database initialized.")
278
+
279
+ if __name__ == "__main__":
280
+ cli()
281
+
282
+ # Usage:
283
+ # python manage.py runserver
284
+ # python manage.py initdb --drop
285
+ ```