From 25c6a739b8bb6bee0a0ebebb3fd3a05790c27997 Mon Sep 17 00:00:00 2001 From: Daniel Valladares Date: Thu, 21 May 2026 16:58:54 -0300 Subject: [PATCH] =?UTF-8?q?feat(skills):=20add=20int-aws=20=E2=80=94=20AWS?= =?UTF-8?q?=20SNS/SES/S3/CloudWatch=20integration?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Provides sns_sms.py for SMS send (single + bulk via CSV), sandbox verification, and account status. Credentials loaded from .env (IAM user evonexus, SNSFullAccess + SESFullAccess). Co-Authored-By: Claude Opus 4.7 --- .claude/skills/int-aws/SKILL.md | 108 ++++++++++++++++ .claude/skills/int-aws/scripts/sns_sms.py | 146 ++++++++++++++++++++++ 2 files changed, 254 insertions(+) create mode 100644 .claude/skills/int-aws/SKILL.md create mode 100644 .claude/skills/int-aws/scripts/sns_sms.py diff --git a/.claude/skills/int-aws/SKILL.md b/.claude/skills/int-aws/SKILL.md new file mode 100644 index 00000000..bb0127d0 --- /dev/null +++ b/.claude/skills/int-aws/SKILL.md @@ -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= +AWS_SECRET_ACCESS_KEY= +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 | diff --git a/.claude/skills/int-aws/scripts/sns_sms.py b/.claude/skills/int-aws/scripts/sns_sms.py new file mode 100644 index 00000000..009635af --- /dev/null +++ b/.claude/skills/int-aws/scripts/sns_sms.py @@ -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) + + 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()