Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
108 changes: 108 additions & 0 deletions .claude/skills/int-aws/SKILL.md
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 |
146 changes: 146 additions & 0 deletions .claude/skills/int-aws/scripts/sns_sms.py
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"),
)
Comment on lines +14 to +20

Copy link
Copy Markdown

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.



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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The 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 DictReader to a list loads the whole file into memory, which can be a problem for large bulk sends. Instead, iterate over reader directly:

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 --dry-run mode to count first, or otherwise avoid materializing all rows just to print the total up front.

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 cmd_bulk further down in the file (beyond the snippet provided), ensure it is removed or adjusted to avoid duplicate logging or conflicting counters, since this implementation now handles per-row errors and prints a final summary.


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()