-
Notifications
You must be signed in to change notification settings - Fork 157
feat(skills): add int-aws integration — SNS/SES/S3/CloudWatch #89
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,108 @@ | ||
| --- | ||
| name: int-aws | ||
| description: "Interact with AWS services — SNS (SMS), SES (email), S3, CloudWatch. Use when you need to send SMS, manage buckets, check costs, or query AWS resources. Calls AWS API directly via CLI or boto3." | ||
| metadata: | ||
| openclaw: | ||
| requires: | ||
| env: | ||
| - AWS_ACCESS_KEY_ID | ||
| - AWS_SECRET_ACCESS_KEY | ||
| bins: | ||
| - aws | ||
| - python3 | ||
| primaryEnv: AWS_ACCESS_KEY_ID | ||
| files: | ||
| - "scripts/*" | ||
| --- | ||
|
|
||
| # AWS Integration | ||
|
|
||
| Interact with AWS services: SNS, SES, S3, CloudWatch e mais. | ||
|
|
||
| ## Setup (já configurado) | ||
|
|
||
| Credenciais configuradas em `.env` (gitignored). IAM user: `evonexus`. | ||
|
|
||
| ```env | ||
| AWS_ACCESS_KEY_ID=<your_key> | ||
| AWS_SECRET_ACCESS_KEY=<your_secret> | ||
| AWS_REGION=us-east-1 | ||
| ``` | ||
|
|
||
| Permissões: SNSFullAccess + SESFullAccess. | ||
|
|
||
| --- | ||
|
|
||
| ## SNS — Envio de SMS | ||
|
|
||
| ### Enviar SMS avulso | ||
| ```bash | ||
| python3 .claude/skills/int-aws/scripts/sns_sms.py send --phone "+5511999999999" --message "Sua mensagem aqui" | ||
| ``` | ||
|
|
||
| ### Listar números verificados (sandbox) | ||
| ```bash | ||
| python3 .claude/skills/int-aws/scripts/sns_sms.py list-verified | ||
| ``` | ||
|
|
||
| ### Verificar número no sandbox | ||
| ```bash | ||
| python3 .claude/skills/int-aws/scripts/sns_sms.py verify --phone "+5511999999999" --code 123456 | ||
| ``` | ||
|
|
||
| ### Status da conta SNS (limite, tipo, sandbox) | ||
| ```bash | ||
| python3 .claude/skills/int-aws/scripts/sns_sms.py status | ||
| ``` | ||
|
|
||
| ### Envio em massa (arquivo CSV: phone,message) | ||
| ```bash | ||
| python3 .claude/skills/int-aws/scripts/sns_sms.py bulk --file /caminho/para/lista.csv | ||
| ``` | ||
|
|
||
| --- | ||
|
|
||
| ## SNS — Sandbox vs Produção | ||
|
|
||
| **Situação atual: SANDBOX** | ||
| - Só envia para números verificados | ||
| - Limite de gasto: $1/mês | ||
|
|
||
| **Para sair do sandbox (produção):** | ||
| 1. Acesse: console.aws.amazon.com → SNS → Text messaging (SMS) → Get out of SMS sandbox | ||
| 2. Preencha o formulário justificando o uso (use case: transactional) | ||
| 3. Aprovação em 24-48h | ||
|
|
||
| Após aprovação, aumentar o limite: | ||
| ```bash | ||
| # Via Service Quotas (requer permissão service-quotas:*) | ||
| aws service-quotas request-service-quota-increase \ | ||
| --service-code sns --quota-code L-D6DB7028 --desired-value 50 | ||
| ``` | ||
|
|
||
| --- | ||
|
|
||
| ## SES — Email Transacional | ||
|
|
||
| ### Enviar email | ||
| ```bash | ||
| python3 .claude/skills/int-aws/scripts/ses_email.py send \ | ||
| --to destinatario@email.com \ | ||
| --subject "Assunto" \ | ||
| --body "Corpo do email" | ||
| ``` | ||
|
|
||
| ### Status da conta SES | ||
| ```bash | ||
| python3 .claude/skills/int-aws/scripts/ses_email.py status | ||
| ``` | ||
|
|
||
| --- | ||
|
|
||
| ## Custos estimados | ||
|
|
||
| | Serviço | Preço | 3000 unidades | | ||
| |---------|-------|----------------| | ||
| | SNS SMS (BR) | $0,00645/SMS | ~$19 (~R$110) | | ||
| | SES Email | $0,0001/email | ~$0,30 | | ||
| | S3 (storage) | $0,023/GB/mês | variável | |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,146 @@ | ||
| #!/usr/bin/env python3 | ||
| """SNS SMS helper — envio, verificação e status via Amazon SNS.""" | ||
|
|
||
| import argparse | ||
| import boto3 | ||
| import csv | ||
| import os | ||
| import sys | ||
| import time | ||
|
|
||
| REGION = os.environ.get("AWS_REGION", "us-east-1") | ||
|
|
||
|
|
||
| def get_client(): | ||
| return boto3.client( | ||
| "sns", | ||
| region_name=REGION, | ||
| aws_access_key_id=os.environ.get("AWS_ACCESS_KEY_ID"), | ||
| aws_secret_access_key=os.environ.get("AWS_SECRET_ACCESS_KEY"), | ||
| ) | ||
|
|
||
|
|
||
| def format_phone(phone: str) -> str: | ||
| digits = "".join(c for c in phone if c.isdigit()) | ||
| if digits.startswith("55"): | ||
| return f"+{digits}" | ||
| if len(digits) in (10, 11): | ||
| return f"+55{digits}" | ||
| return f"+{digits}" | ||
|
|
||
|
|
||
| def send_sms(phone: str, message: str) -> dict: | ||
| client = get_client() | ||
| resp = client.publish( | ||
| PhoneNumber=format_phone(phone), | ||
| Message=message, | ||
| MessageAttributes={ | ||
| "AWS.SNS.SMS.SMSType": {"DataType": "String", "StringValue": "Transactional"}, | ||
| "AWS.SNS.SMS.SenderID": {"DataType": "String", "StringValue": "EvoNexus"}, | ||
| }, | ||
| ) | ||
| return resp | ||
|
|
||
|
|
||
| def cmd_send(args): | ||
| resp = send_sms(args.phone, args.message) | ||
| print(f"✅ Enviado! MessageId: {resp['MessageId']}") | ||
|
|
||
|
|
||
| def cmd_bulk(args): | ||
| with open(args.file, newline="", encoding="utf-8") as f: | ||
| reader = csv.DictReader(f) | ||
| rows = list(reader) | ||
|
Comment on lines
+50
to
+53
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. suggestion (performance): Avoid loading the entire CSV into memory; iterate over the reader to support large files. Converting the def cmd_bulk(args):
with open(args.file, newline="", encoding="utf-8") as f:
reader = csv.DictReader(f)
print("Enviando...")
sent, failed = 0, 0
for row in reader:
...If you need the total count, consider a Suggested implementation: def cmd_bulk(args):
sent, failed = 0, 0
with open(args.file, newline="", encoding="utf-8") as f:
reader = csv.DictReader(f)
print("Enviando...")
for row in reader:
phone = row.get("phone") or row.get("telefone") or row.get("celular", "")
message = row.get("message") or row.get("mensagem", "")
if not phone or not message:
print(f" ⚠️ Linha ignorada (phone ou message vazio): {row}")
failed += 1
continue
try:
send_sms(phone, message)
sent += 1
print(f" ✅ {phone}")
except Exception as exc:
failed += 1
print(f" ❌ {phone} - erro ao enviar: {exc}")
print(f"Resumo: {sent} enviados, {failed} falharam")If there is existing error-handling or summary logic for |
||
|
|
||
| print(f"Enviando para {len(rows)} números...") | ||
| sent, failed = 0, 0 | ||
| for row in rows: | ||
| phone = row.get("phone") or row.get("telefone") or row.get("celular", "") | ||
| message = row.get("message") or row.get("mensagem", "") | ||
| if not phone or not message: | ||
| print(f" ⚠️ Linha ignorada (phone ou message vazio): {row}") | ||
| continue | ||
| try: | ||
| send_sms(phone, message) | ||
| print(f" ✅ {phone}") | ||
| sent += 1 | ||
| except Exception as e: | ||
| print(f" ❌ {phone}: {e}") | ||
| failed += 1 | ||
| time.sleep(0.3) | ||
|
|
||
| print(f"\nResumo: {sent} enviados, {failed} falhas") | ||
|
|
||
|
|
||
| def cmd_status(args): | ||
| client = get_client() | ||
| attrs = client.get_sms_attributes()["attributes"] | ||
| print("📊 Status SNS SMS:") | ||
| for k, v in attrs.items(): | ||
| print(f" {k}: {v}") | ||
|
|
||
| try: | ||
| sandbox = client.get_sms_sandbox_account_status() | ||
| in_sandbox = sandbox.get("IsInSandbox", True) | ||
| print(f"\n Sandbox: {'⚠️ SIM (apenas números verificados)' if in_sandbox else '✅ NÃO (produção)'}") | ||
| except Exception: | ||
| pass | ||
|
|
||
|
|
||
| def cmd_list_verified(args): | ||
| client = get_client() | ||
| resp = client.list_sms_sandbox_phone_numbers() | ||
| numbers = resp.get("PhoneNumbers", []) | ||
| if not numbers: | ||
| print("Nenhum número verificado no sandbox.") | ||
| return | ||
| print("Números no sandbox:") | ||
| for n in numbers: | ||
| status = "✅ Verificado" if n["Status"] == "Verified" else "⏳ Pendente" | ||
| print(f" {n['PhoneNumber']} — {status}") | ||
|
|
||
|
|
||
| def cmd_verify(args): | ||
| client = get_client() | ||
| client.verify_sms_sandbox_phone_number( | ||
| PhoneNumber=format_phone(args.phone), | ||
| OneTimePassword=args.code, | ||
| ) | ||
| print(f"✅ Número {args.phone} verificado com sucesso!") | ||
|
|
||
|
|
||
| def main(): | ||
| parser = argparse.ArgumentParser(description="SNS SMS helper") | ||
| sub = parser.add_subparsers(dest="cmd") | ||
|
|
||
| p_send = sub.add_parser("send", help="Enviar SMS avulso") | ||
| p_send.add_argument("--phone", required=True) | ||
| p_send.add_argument("--message", required=True) | ||
|
|
||
| p_bulk = sub.add_parser("bulk", help="Envio em massa via CSV (phone,message)") | ||
| p_bulk.add_argument("--file", required=True) | ||
|
|
||
| sub.add_parser("status", help="Status da conta SNS") | ||
| sub.add_parser("list-verified", help="Listar números verificados no sandbox") | ||
|
|
||
| p_verify = sub.add_parser("verify", help="Verificar número no sandbox") | ||
| p_verify.add_argument("--phone", required=True) | ||
| p_verify.add_argument("--code", required=True) | ||
|
|
||
| args = parser.parse_args() | ||
| if not args.cmd: | ||
| parser.print_help() | ||
| sys.exit(1) | ||
|
|
||
| cmds = { | ||
| "send": cmd_send, | ||
| "bulk": cmd_bulk, | ||
| "status": cmd_status, | ||
| "list-verified": cmd_list_verified, | ||
| "verify": cmd_verify, | ||
| } | ||
| cmds[args.cmd](args) | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| main() | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
suggestion (performance): Reuse a single SNS client instead of creating a new one for each operation.
Each helper call creates a fresh SNS client, which adds unnecessary overhead and connection churn in bulk usage. Consider caching the client (e.g., module-level singleton or memoizing
get_client()) so subsequent calls reuse the same instance.