lambda-forge-cli-aws 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 (80) hide show
  1. lambda_forge_cli_aws-1.0.0.dist-info/METADATA +352 -0
  2. lambda_forge_cli_aws-1.0.0.dist-info/RECORD +80 -0
  3. lambda_forge_cli_aws-1.0.0.dist-info/WHEEL +4 -0
  4. lambda_forge_cli_aws-1.0.0.dist-info/entry_points.txt +2 -0
  5. lambda_forge_cli_aws-1.0.0.dist-info/licenses/LICENSE +189 -0
  6. lambdaforge/__init__.py +10 -0
  7. lambdaforge/bedrock_client.py +109 -0
  8. lambdaforge/cli.py +428 -0
  9. lambdaforge/deploy.py +308 -0
  10. lambdaforge/logs.py +185 -0
  11. lambdaforge/renderer.py +72 -0
  12. lambdaforge/spec_generator.py +171 -0
  13. lambdaforge/status.py +111 -0
  14. lambdaforge/templates.py +106 -0
  15. lambdaforge/upgrade.py +231 -0
  16. templates/python-api/cookiecutter.json +12 -0
  17. templates/python-api/{{cookiecutter.project_slug}}/.github/workflows/dependabot.yml +22 -0
  18. templates/python-api/{{cookiecutter.project_slug}}/.github/workflows/deploy.yml +90 -0
  19. templates/python-api/{{cookiecutter.project_slug}}/.gitignore +65 -0
  20. templates/python-api/{{cookiecutter.project_slug}}/Dockerfile +11 -0
  21. templates/python-api/{{cookiecutter.project_slug}}/README.md +130 -0
  22. templates/python-api/{{cookiecutter.project_slug}}/infrastructure/app.js +26 -0
  23. templates/python-api/{{cookiecutter.project_slug}}/infrastructure/cdk.json +20 -0
  24. templates/python-api/{{cookiecutter.project_slug}}/infrastructure/lib/stack.js +155 -0
  25. templates/python-api/{{cookiecutter.project_slug}}/infrastructure/package.json +17 -0
  26. templates/python-api/{{cookiecutter.project_slug}}/pyproject.toml +66 -0
  27. templates/python-api/{{cookiecutter.project_slug}}/requirements-dev.txt +8 -0
  28. templates/python-api/{{cookiecutter.project_slug}}/requirements.txt +3 -0
  29. templates/python-api/{{cookiecutter.project_slug}}/src/handler.py +93 -0
  30. templates/python-api/{{cookiecutter.project_slug}}/src/utils/logger.py +39 -0
  31. templates/python-api/{{cookiecutter.project_slug}}/tests/conftest.py +22 -0
  32. templates/python-api/{{cookiecutter.project_slug}}/tests/test_handler.py +68 -0
  33. templates/python-cron/cookiecutter.json +13 -0
  34. templates/python-cron/{{cookiecutter.project_slug}}/.gitignore +65 -0
  35. templates/python-cron/{{cookiecutter.project_slug}}/Dockerfile +11 -0
  36. templates/python-cron/{{cookiecutter.project_slug}}/infrastructure/app.js +25 -0
  37. templates/python-cron/{{cookiecutter.project_slug}}/infrastructure/cdk.json +3 -0
  38. templates/python-cron/{{cookiecutter.project_slug}}/infrastructure/lib/stack.js +55 -0
  39. templates/python-cron/{{cookiecutter.project_slug}}/infrastructure/package.json +17 -0
  40. templates/python-cron/{{cookiecutter.project_slug}}/pyproject.toml +56 -0
  41. templates/python-cron/{{cookiecutter.project_slug}}/requirements.txt +2 -0
  42. templates/python-cron/{{cookiecutter.project_slug}}/src/handler.py +53 -0
  43. templates/python-cron/{{cookiecutter.project_slug}}/tests/conftest.py +22 -0
  44. templates/python-cron/{{cookiecutter.project_slug}}/tests/test_handler.py +48 -0
  45. templates/python-queue/cookiecutter.json +12 -0
  46. templates/python-queue/{{cookiecutter.project_slug}}/.gitignore +65 -0
  47. templates/python-queue/{{cookiecutter.project_slug}}/Dockerfile +11 -0
  48. templates/python-queue/{{cookiecutter.project_slug}}/infrastructure/app.js +25 -0
  49. templates/python-queue/{{cookiecutter.project_slug}}/infrastructure/cdk.json +3 -0
  50. templates/python-queue/{{cookiecutter.project_slug}}/infrastructure/lib/stack.js +79 -0
  51. templates/python-queue/{{cookiecutter.project_slug}}/infrastructure/package.json +17 -0
  52. templates/python-queue/{{cookiecutter.project_slug}}/pyproject.toml +56 -0
  53. templates/python-queue/{{cookiecutter.project_slug}}/requirements.txt +2 -0
  54. templates/python-queue/{{cookiecutter.project_slug}}/src/handler.py +64 -0
  55. templates/python-queue/{{cookiecutter.project_slug}}/tests/conftest.py +22 -0
  56. templates/python-queue/{{cookiecutter.project_slug}}/tests/test_handler.py +60 -0
  57. templates/python-s3/cookiecutter.json +13 -0
  58. templates/python-s3/{{cookiecutter.project_slug}}/.gitignore +65 -0
  59. templates/python-s3/{{cookiecutter.project_slug}}/Dockerfile +11 -0
  60. templates/python-s3/{{cookiecutter.project_slug}}/infrastructure/app.js +25 -0
  61. templates/python-s3/{{cookiecutter.project_slug}}/infrastructure/cdk.json +3 -0
  62. templates/python-s3/{{cookiecutter.project_slug}}/infrastructure/lib/stack.js +89 -0
  63. templates/python-s3/{{cookiecutter.project_slug}}/infrastructure/package.json +17 -0
  64. templates/python-s3/{{cookiecutter.project_slug}}/pyproject.toml +56 -0
  65. templates/python-s3/{{cookiecutter.project_slug}}/requirements.txt +2 -0
  66. templates/python-s3/{{cookiecutter.project_slug}}/src/handler.py +86 -0
  67. templates/python-s3/{{cookiecutter.project_slug}}/tests/conftest.py +22 -0
  68. templates/python-s3/{{cookiecutter.project_slug}}/tests/test_handler.py +71 -0
  69. templates/python-stream/cookiecutter.json +12 -0
  70. templates/python-stream/{{cookiecutter.project_slug}}/.gitignore +65 -0
  71. templates/python-stream/{{cookiecutter.project_slug}}/Dockerfile +11 -0
  72. templates/python-stream/{{cookiecutter.project_slug}}/infrastructure/app.js +25 -0
  73. templates/python-stream/{{cookiecutter.project_slug}}/infrastructure/cdk.json +3 -0
  74. templates/python-stream/{{cookiecutter.project_slug}}/infrastructure/lib/stack.js +81 -0
  75. templates/python-stream/{{cookiecutter.project_slug}}/infrastructure/package.json +17 -0
  76. templates/python-stream/{{cookiecutter.project_slug}}/pyproject.toml +56 -0
  77. templates/python-stream/{{cookiecutter.project_slug}}/requirements.txt +2 -0
  78. templates/python-stream/{{cookiecutter.project_slug}}/src/handler.py +69 -0
  79. templates/python-stream/{{cookiecutter.project_slug}}/tests/conftest.py +22 -0
  80. templates/python-stream/{{cookiecutter.project_slug}}/tests/test_handler.py +64 -0
@@ -0,0 +1,109 @@
1
+ """Bedrock client — wrapper for Amazon Bedrock with retry and streaming."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ from typing import Any
7
+
8
+ import boto3
9
+
10
+
11
+ class BedrockClient:
12
+ """Wrapper for Amazon Bedrock with built-in retry and error handling."""
13
+
14
+ DEFAULT_MODEL = "us.anthropic.claude-sonnet-4-5-20250929-v1:0"
15
+ REGION = "us-east-1"
16
+
17
+ def __init__(self, region: str = REGION, model_id: str = DEFAULT_MODEL) -> None:
18
+ self.client = boto3.client("bedrock-runtime", region_name=region)
19
+ self.region = region
20
+ self.model_id = model_id
21
+
22
+ def generate(
23
+ self,
24
+ system_prompt: str,
25
+ user_message: str,
26
+ max_tokens: int = 2048,
27
+ temperature: float = 0.3,
28
+ ) -> str:
29
+ """Generate text with Claude on Bedrock.
30
+
31
+ Args:
32
+ system_prompt: System instructions for Claude
33
+ user_message: The user's request
34
+ max_tokens: Maximum tokens in response
35
+ temperature: Sampling temperature (0.0-1.0)
36
+
37
+ Returns:
38
+ Generated text from Claude
39
+
40
+ Raises:
41
+ Exception: If the Bedrock call fails
42
+ """
43
+ try:
44
+ response = self.client.converse(
45
+ modelId=self.model_id,
46
+ messages=[
47
+ {
48
+ "role": "user",
49
+ "content": [{"text": user_message}],
50
+ }
51
+ ],
52
+ system=[{"text": system_prompt}],
53
+ inferenceConfig={
54
+ "maxTokens": max_tokens,
55
+ "temperature": temperature,
56
+ },
57
+ )
58
+ return response["output"]["message"]["content"][0]["text"]
59
+
60
+ except Exception as e:
61
+ raise BedrockError(f"Bedrock call failed: {e}") from e
62
+
63
+ def generate_json(
64
+ self,
65
+ system_prompt: str,
66
+ user_message: str,
67
+ max_tokens: int = 2048,
68
+ ) -> dict[str, Any]:
69
+ """Generate JSON with Claude. Strips markdown fences if present."""
70
+ text = self.generate(
71
+ system_prompt=system_prompt,
72
+ user_message=user_message,
73
+ max_tokens=max_tokens,
74
+ temperature=0.2,
75
+ )
76
+ return self._extract_json(text)
77
+
78
+ def _extract_json(self, text: str) -> dict[str, Any]:
79
+ """Extract JSON from text, handling markdown code fences."""
80
+ text = text.strip()
81
+
82
+ # Strip markdown code fences
83
+ if text.startswith("```"):
84
+ lines = text.split("\n")
85
+ if lines[-1].startswith("```"):
86
+ text = "\n".join(lines[1:-1])
87
+ else:
88
+ text = "\n".join(lines[1:])
89
+
90
+ return json.loads(text)
91
+
92
+ def estimate_cost(
93
+ self,
94
+ input_tokens: int,
95
+ output_tokens: int,
96
+ ) -> float:
97
+ """Estimate cost in USD."""
98
+ # Approximate pricing for Claude 3.5 Sonnet
99
+ PRICES = {
100
+ "input": 0.003 / 1000,
101
+ "output": 0.015 / 1000,
102
+ }
103
+ return input_tokens * PRICES["input"] + output_tokens * PRICES["output"]
104
+
105
+
106
+ class BedrockError(Exception):
107
+ """Raised when a Bedrock operation fails."""
108
+
109
+ pass
lambdaforge/cli.py ADDED
@@ -0,0 +1,428 @@
1
+ """LambdaForge CLI — main entry point."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import sys
6
+ from pathlib import Path
7
+
8
+ import click
9
+ from rich.console import Console
10
+ from rich.panel import Panel
11
+ from rich.table import Table
12
+
13
+ from lambdaforge import __version__
14
+ from lambdaforge.deploy import bootstrap_account, deploy_lambda, destroy_stack
15
+ from lambdaforge.logs import show_logs
16
+ from lambdaforge.renderer import TemplateRenderer
17
+ from lambdaforge.spec_generator import SpecGenerator
18
+ from lambdaforge.status import show_status
19
+ from lambdaforge.templates import TEMPLATES
20
+ from lambdaforge.upgrade import upgrade_lambda
21
+
22
+ console = Console()
23
+
24
+
25
+ @click.group()
26
+ @click.version_option(version=__version__, prog_name="lambdaforge")
27
+ @click.option("--verbose", "-v", is_flag=True, help="Show detailed output")
28
+ @click.option("--quiet", "-q", is_flag=True, help="Suppress non-essential output")
29
+ @click.pass_context
30
+ def cli(ctx: click.Context, verbose: bool, quiet: bool) -> None:
31
+ """LambdaForge — Lambda production-ready en 60 segundos."""
32
+ ctx.ensure_object(dict)
33
+ ctx.obj["verbose"] = verbose
34
+ ctx.obj["quiet"] = quiet
35
+ if quiet:
36
+ console.quiet = True
37
+
38
+
39
+ @cli.command()
40
+ @click.argument("name", required=False, default=None)
41
+ @click.option(
42
+ "--template",
43
+ "-t",
44
+ default=None,
45
+ type=click.Choice(list(TEMPLATES.keys())),
46
+ help="Template to use",
47
+ )
48
+ @click.option("--description", default=None, help="Project description")
49
+ @click.option("--author", default=None, help="Author name")
50
+ @click.option("--email", default=None, help="Author email")
51
+ @click.option("--runtime", default="python3.12", help="Lambda runtime")
52
+ @click.option("--trigger", default="api-gateway", help="Trigger type")
53
+ @click.option("--database", default=None, help="Database (dynamodb)")
54
+ @click.option("--auth", default=None, help="Auth method (cognito, iam)")
55
+ @click.option(
56
+ "--observability",
57
+ "-o",
58
+ default="xray",
59
+ help="Observability (xray, basic)",
60
+ )
61
+ @click.option("--region", default="us-east-1", help="AWS region")
62
+ @click.option("--memory", default=512, help="Memory in MB")
63
+ @click.option("--timeout", default=30, help="Timeout in seconds")
64
+ @click.option(
65
+ "--no-specs",
66
+ is_flag=True,
67
+ help="Skip generating .kiro/specs/ (saves Bedrock cost)",
68
+ )
69
+ @click.option(
70
+ "--budget",
71
+ default=0.50,
72
+ type=float,
73
+ help="Maximum USD for Bedrock specs generation",
74
+ )
75
+ @click.option(
76
+ "--model",
77
+ default=None,
78
+ help="Bedrock model ID (default: Claude Sonnet 4.5)",
79
+ )
80
+ @click.option(
81
+ "--output",
82
+ "-d",
83
+ default=".",
84
+ type=click.Path(),
85
+ help="Output directory",
86
+ )
87
+ @click.option("--git/--no-git", default=True, help="Initialize git repo")
88
+ def new(
89
+ name: str | None,
90
+ template: str | None,
91
+ description: str | None,
92
+ author: str | None,
93
+ email: str | None,
94
+ runtime: str,
95
+ trigger: str,
96
+ database: str | None,
97
+ auth: str | None,
98
+ observability: str,
99
+ region: str,
100
+ memory: int,
101
+ timeout: int,
102
+ no_specs: bool,
103
+ budget: float,
104
+ model: str | None,
105
+ output: str,
106
+ git: bool,
107
+ ) -> None:
108
+ """Create a new Lambda project.
109
+
110
+ Run without arguments for interactive mode:
111
+
112
+ lambdaforge new
113
+
114
+ Or pass arguments directly:
115
+
116
+ lambdaforge new payment-processor
117
+
118
+ lambdaforge new image-resizer --template python-s3
119
+
120
+ lambdaforge new webhooker --template python-queue --no-specs
121
+ """
122
+ # Interactive mode when no name is provided
123
+ if name is None:
124
+ import questionary
125
+
126
+ name = questionary.text(
127
+ "Project name:",
128
+ validate=lambda v: len(v) > 0 or "Project name is required",
129
+ ).ask()
130
+ if not name:
131
+ console.print("[yellow]Cancelled.[/yellow]")
132
+ sys.exit(0)
133
+
134
+ if template is None:
135
+ template = questionary.select(
136
+ "Template:",
137
+ choices=[
138
+ questionary.Choice(
139
+ title=f"{k} — {v['description']}",
140
+ value=k,
141
+ )
142
+ for k, v in TEMPLATES.items()
143
+ ],
144
+ ).ask()
145
+
146
+ if description is None:
147
+ description = questionary.text(
148
+ "Description:",
149
+ default=f"Serverless {name} built with AWS Lambda",
150
+ ).ask()
151
+
152
+ if author is None:
153
+ author = questionary.text("Author name:", default="LambdaForge User").ask()
154
+
155
+ if email is None:
156
+ email = questionary.text("Author email:", default="user@example.com").ask()
157
+
158
+ region = questionary.text("AWS region:", default=region).ask() or region
159
+
160
+ # Defaults for non-interactive mode
161
+ if template is None:
162
+ template = "python-api"
163
+ if description is None:
164
+ description = f"Serverless {name} built with AWS Lambda"
165
+ if author is None:
166
+ author = "LambdaForge User"
167
+ if email is None:
168
+ email = "user@example.com"
169
+ console.print(
170
+ Panel(
171
+ f"[bold cyan]LambdaForge v{__version__}[/bold cyan]\n"
172
+ f"Creating: [yellow]{name}[/yellow]\n"
173
+ f"Template: [yellow]{template}[/yellow]\n"
174
+ f"Region: [yellow]{region}[/yellow]",
175
+ title="[bold]🔥 LambdaForge[/bold]",
176
+ )
177
+ )
178
+
179
+ output_path = Path(output) / name
180
+ if output_path.exists():
181
+ if not click.confirm(f"Directory {output_path} exists. Continue?", default=False):
182
+ console.print("[yellow]Cancelled.[/yellow]")
183
+ sys.exit(0)
184
+
185
+ # Build context for template rendering
186
+ context = {
187
+ "project_name": name,
188
+ "project_name_snake": name.replace("-", "_"),
189
+ "module_name": name.replace("-", "_"),
190
+ "description": description,
191
+ "author_name": author,
192
+ "author_email": email,
193
+ "runtime": runtime,
194
+ "trigger": trigger,
195
+ "database": database or "none",
196
+ "auth": auth or "none",
197
+ "observability": observability,
198
+ "aws_region": region,
199
+ "memory_mb": memory,
200
+ "timeout_seconds": timeout,
201
+ }
202
+
203
+ # 1. Render template
204
+ console.print("[dim]1/4 Rendering template...[/dim]")
205
+ renderer = TemplateRenderer()
206
+ try:
207
+ project_path = renderer.render(
208
+ template_name=template,
209
+ project_name=name,
210
+ context=context,
211
+ output_dir=output,
212
+ )
213
+ except FileNotFoundError:
214
+ console.print(f"[red]✗ Template '{template}' not found.[/red]")
215
+ console.print("[yellow]Run `lambdaforge templates` to see available options.[/yellow]")
216
+ sys.exit(1)
217
+ console.print(f"[green]✓ Template rendered: {project_path}[/green]")
218
+
219
+ # 2. Generate .kiro/specs/ (if not skipped)
220
+ if not no_specs:
221
+ console.print("[dim]2/4 Generating .kiro/specs/ via Bedrock...[/dim]")
222
+ try:
223
+ spec_gen = SpecGenerator(budget_usd=budget, model_id=model)
224
+ spec_gen.generate_all(project_path, template, context)
225
+ console.print(f"[green]✓ Specs generated[/green] (cost: ${spec_gen.total_cost:.3f})")
226
+ except Exception as e:
227
+ console.print(f"[yellow]⚠ Specs generation failed: {e}[/yellow]")
228
+ console.print("[yellow]Continuing without specs...[/yellow]")
229
+ else:
230
+ console.print("[dim]2/4 Skipping specs (--no-specs)[/dim]")
231
+
232
+ # 3. Initialize git
233
+ if git:
234
+ console.print("[dim]3/4 Initializing git...[/dim]")
235
+ try:
236
+ import git as gitpython
237
+
238
+ repo = gitpython.Repo.init(project_path)
239
+ repo.index.add(
240
+ [str(p.relative_to(project_path)) for p in project_path.rglob("*") if p.is_file()]
241
+ )
242
+ repo.index.commit("Initial Lambda scaffold from LambdaForge")
243
+ console.print("[green]✓ Git initialized[/green]")
244
+ except Exception as e:
245
+ console.print(f"[yellow]⚠ Git init failed: {e}[/yellow]")
246
+ else:
247
+ console.print("[dim]3/4 Skipping git (--no-git)[/dim]")
248
+
249
+ # 4. Done
250
+ console.print(
251
+ Panel(
252
+ f"[bold green]✓ Lambda '{name}' created in {project_path}[/bold green]\n\n"
253
+ f"Next steps:\n"
254
+ f" 1. cd {name}\n"
255
+ f" 2. pip install -r requirements.txt\n"
256
+ f" 3. pytest\n"
257
+ f" 4. cdk deploy",
258
+ title="[bold]Done[/bold]",
259
+ border_style="green",
260
+ )
261
+ )
262
+
263
+
264
+ @cli.command()
265
+ @click.argument("path", type=click.Path(exists=True), default=".")
266
+ def upgrade(path: str) -> None:
267
+ """Upgrade an existing Lambda with LambdaForge best practices.
268
+
269
+ PATH defaults to the current directory if not specified.
270
+
271
+ Examples:
272
+
273
+ lambdaforge upgrade
274
+
275
+ lambdaforge upgrade ./my-existing-lambda
276
+ """
277
+ upgrade_lambda(Path(path))
278
+
279
+
280
+ @cli.command()
281
+ def templates() -> None:
282
+ """List available templates."""
283
+ table = Table(title="Available Lambda Templates", show_header=True, header_style="bold cyan")
284
+ table.add_column("Template", style="yellow")
285
+ table.add_column("Description")
286
+ table.add_column("Runtime", style="green")
287
+ table.add_column("Triggers")
288
+
289
+ for name, info in TEMPLATES.items():
290
+ table.add_row(
291
+ name,
292
+ info["description"],
293
+ info.get("runtime", "any"),
294
+ ", ".join(info.get("triggers", [])),
295
+ )
296
+
297
+ console.print(table)
298
+
299
+
300
+ @cli.command()
301
+ @click.option("--stack", "-s", default=None, help="CDK stack name to deploy")
302
+ @click.option(
303
+ "--env",
304
+ "-e",
305
+ "environment",
306
+ default="dev",
307
+ type=click.Choice(["dev", "staging", "prod"]),
308
+ help="Target environment",
309
+ )
310
+ def deploy(stack: str | None, environment: str) -> None:
311
+ """Deploy the current Lambda to AWS.
312
+
313
+ Bundles dependencies, installs CDK packages, and runs cdk deploy.
314
+ Use --env to target different environments with different configs.
315
+
316
+ Examples:
317
+
318
+ lambdaforge deploy
319
+
320
+ lambdaforge deploy --env staging
321
+
322
+ lambdaforge deploy --env prod --stack my-api-prod
323
+ """
324
+ deploy_lambda(Path.cwd(), stack_name=stack, env=environment)
325
+
326
+
327
+ @cli.command()
328
+ @click.option("--region", "-r", default="us-east-1", help="AWS region to bootstrap")
329
+ def bootstrap(region: str) -> None:
330
+ """Bootstrap CDK in your AWS account (one-time setup).
331
+
332
+ Creates the S3 bucket and IAM roles that CDK needs to deploy stacks.
333
+ Only needs to run once per account/region.
334
+
335
+ Examples:
336
+
337
+ lambdaforge bootstrap
338
+
339
+ lambdaforge bootstrap --region us-west-2
340
+ """
341
+ bootstrap_account(region=region)
342
+
343
+
344
+ @cli.command()
345
+ @click.option("--stack", "-s", default=None, help="CDK stack name to destroy")
346
+ @click.option(
347
+ "--env",
348
+ "-e",
349
+ "environment",
350
+ default="dev",
351
+ type=click.Choice(["dev", "staging", "prod"]),
352
+ help="Target environment to destroy",
353
+ )
354
+ def destroy(stack: str | None, environment: str) -> None:
355
+ """Destroy the deployed Lambda stack from AWS.
356
+
357
+ Removes all resources created by `lambdaforge deploy`.
358
+
359
+ Examples:
360
+
361
+ lambdaforge destroy
362
+
363
+ lambdaforge destroy --env staging
364
+
365
+ lambdaforge destroy --env prod
366
+ """
367
+ destroy_stack(Path.cwd(), stack_name=stack, env=environment)
368
+
369
+
370
+ @cli.command()
371
+ def version() -> None:
372
+ """Show LambdaForge version."""
373
+ console.print(f"lambdaforge {__version__}")
374
+
375
+
376
+ @cli.command()
377
+ @click.option(
378
+ "--env",
379
+ "-e",
380
+ "environment",
381
+ default="dev",
382
+ type=click.Choice(["dev", "staging", "prod"]),
383
+ help="Environment to check",
384
+ )
385
+ def status(environment: str) -> None:
386
+ """Show deployment status of the current Lambda project.
387
+
388
+ Queries CloudFormation to display stack status, outputs, and tags.
389
+
390
+ Examples:
391
+
392
+ lambdaforge status
393
+
394
+ lambdaforge status --env prod
395
+ """
396
+ show_status(Path.cwd(), env=environment)
397
+
398
+
399
+ @cli.command()
400
+ @click.option(
401
+ "--env",
402
+ "-e",
403
+ "environment",
404
+ default="dev",
405
+ type=click.Choice(["dev", "staging", "prod"]),
406
+ help="Environment to fetch logs from",
407
+ )
408
+ @click.option("--lines", "-n", default=50, help="Number of log lines to show")
409
+ @click.option("--errors", is_flag=True, help="Show only error logs")
410
+ def logs(environment: str, lines: int, errors: bool) -> None:
411
+ """Show CloudWatch logs for the deployed Lambda.
412
+
413
+ Fetches recent log events and formats them with color coding.
414
+ Supports structured JSON logs from aws-lambda-powertools.
415
+
416
+ Examples:
417
+
418
+ lambdaforge logs
419
+
420
+ lambdaforge logs --env prod --lines 100
421
+
422
+ lambdaforge logs --errors
423
+ """
424
+ show_logs(Path.cwd(), env=environment, lines=lines, errors_only=errors)
425
+
426
+
427
+ if __name__ == "__main__":
428
+ cli()