AWSTemplateFormatVersion: '2010-09-09'
Description: 'Project 8: AWS CLI Helper - Natural language to AWS CLI via Amazon Bedrock'
Parameters:
ModelId:
Type: String
Default: 'amazon.nova-micro-v1:0'
Description: Bedrock model ID
BedrockRegion:
Type: String
Default: 'us-east-1'
Description: Region where Bedrock model is enabled
Resources:
# IAM Role for Lambda
LambdaExecutionRole:
Type: AWS::IAM::Role
Properties:
RoleName: 'project-8-cli-copilot-lambda-role'
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service: lambda.amazonaws.com
Action: sts:AssumeRole
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
Policies:
- PolicyName: BedrockAccess
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- bedrock:InvokeModel
- bedrock:Converse
- bedrock:InvokeModelWithResponseStream
Resource: '*'
# Lambda Function with embedded Python code
CliAgentFunction:
Type: AWS::Lambda::Function
Properties:
FunctionName: 'project-8-cli-copilot'
Runtime: python3.12
Handler: index.lambda_handler
Role: !GetAtt LambdaExecutionRole.Arn
Timeout: 20
MemorySize: 512
Environment:
Variables:
MODEL_ID: !Ref ModelId
BEDROCK_REGION: !Ref BedrockRegion
# API Gateway HTTP API
ApiGateway:
Type: AWS::ApiGatewayV2::Api
Properties:
Name: 'project-8-cli-copilot-api'
ProtocolType: HTTP
CorsConfiguration:
AllowMethods: [OPTIONS, POST]
AllowOrigins: ['*']
AllowHeaders: [Content-Type]
Outputs:
ApiUrl:
Description: 'API Gateway URL for CLI Helper'
Value: !Sub 'https://${ApiGateway}.execute-api.${AWS::Region}.amazonaws.com/prod'
import os
import json
import boto3
import logging
from botocore.config import Config
logger = logging.getLogger()
logger.setLevel(logging.INFO)
SYSTEM_PROMPT = """You are an expert AWS CLI command generator.
Given a user's natural-language goal, respond with a single JSON object with these fields:
- command: string (the best single command; multiline only if unavoidable)
- explanation: string (why this command, notable flags, alternatives)
- prerequisites: string[] (IAM permissions, resources, assumptions)
- safety: string[] (non-destructive variants such as --dry-run; caution about side effects)
Rules:
- Prefer safe commands and idempotent operations.
- If destructive, include warnings in safety.
- Do NOT include markdown fences or any text outside the JSON.
- Do not hallucinate account-specific values.
"""
MODEL_ID = os.environ.get("MODEL_ID", "amazon.nova-micro-v1:0")
BEDROCK_REGION = os.environ.get("BEDROCK_REGION", "us-east-1")
br_cfg = Config(retries={"max_attempts": 3, "mode": "standard"})
bedrock = boto3.client("bedrock-runtime", region_name=BEDROCK_REGION, config=br_cfg)
def _converse(goal: str) -> str:
"""Call Bedrock Converse API to generate CLI command"""
resp = bedrock.converse(
modelId=MODEL_ID,
system=[{"text": SYSTEM_PROMPT}],
messages=[{"role": "user", "content": [{"text": goal}]}],
inferenceConfig={"maxTokens": 600, "temperature": 0.2, "topP": 0.9},
)
content_blocks = resp.get("output", {}).get("message", {}).get("content", [])
texts = [b.get("text") for b in content_blocks if "text" in b]
return "".join(texts) if texts else ""
def _safe_json_parse(s: str):
"""Safely parse JSON response from AI model"""
try:
return json.loads(s)
except Exception:
start = s.find("{")
end = s.rfind("}")
if start != -1 and end != -1 and end > start:
try:
return json.loads(s[start:end+1])
except Exception:
pass
return {
"command": "",
"explanation": "The model returned an unexpected format. Please refine your request.",
"prerequisites": [],
"safety": ["Re-run with a more specific resource name or region."]
}
def lambda_handler(event, context):
"""Main Lambda handler function"""
logger.info("Event: %s", json.dumps(event))
try:
body = json.loads(event.get("body", "{}"))
except Exception:
body = {}
goal = (body.get("goal") or "").strip()
if not goal:
return {
"statusCode": 400,
"headers": {"Content-Type": "application/json", "Access-Control-Allow-Origin": "*"},
"body": json.dumps({"error": "Missing 'goal' in request body."})
}
try:
text = _converse(goal)
result = _safe_json_parse(text)
# Ensure all required fields exist
for key in ("command", "explanation", "prerequisites", "safety"):
result.setdefault(key, "" if key in ("command", "explanation") else [])
return {
"statusCode": 200,
"headers": {
"Content-Type": "application/json",
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Headers": "Content-Type",
"Access-Control-Allow-Methods": "OPTIONS,POST"
},
"body": json.dumps(result)
}
except Exception as e:
logger.error("Error: %s", str(e))
return {
"statusCode": 500,
"headers": {"Content-Type": "application/json", "Access-Control-Allow-Origin": "*"},
"body": json.dumps({"error": str(e)})
}
AWS CLI Helper (Bedrock)
AWS CLI Helper
β οΈ Always review commands before running. Add --dry-run to test safely without making changes (when supported).
# Deploy the serverless backend
aws cloudformation deploy \
--template-file cloudformation-template.yaml \
--stack-name project-8-cli-copilot \
--capabilities CAPABILITY_NAMED_IAM \
--parameter-overrides ModelId=amazon.nova-micro-v1:0 \
--region us-east-1
# Create S3 bucket for frontend
aws s3 mb s3://project-8-cli-copilot --region us-east-1
# Configure static website hosting
aws s3 website s3://project-8-cli-copilot \
--index-document index.html \
--region us-east-1
# Upload web interface
aws s3 cp index.html s3://project-8-cli-copilot/ --region us-east-1
# Configure public access
aws s3api put-public-access-block \
--bucket project-8-cli-copilot \
--public-access-block-configuration \
"BlockPublicAcls=false,IgnorePublicAcls=false,BlockPublicPolicy=false,RestrictPublicBuckets=false"
# Apply bucket policy for public read
aws s3api put-bucket-policy \
--bucket project-8-cli-copilot \
--policy file://bucket-policy.json
echo "β
Project 8 CLI Helper deployed successfully!"