This is a submission for the OpenClaw Writing Challenge
When I first installed OpenClaw, I was blown away by the built-in capabilities — email, calendar, web browsing, shell access. But the real magic is the Skills system: the ability to extend OpenClaw with custom plugins so it can do literally anything your machine can do.
I built CloudClaw — a skill that lets me control AWS EC2 VMs from WhatsApp. In this tutorial, I'll walk you through exactly how to build a custom OpenClaw skill from scratch, using CloudClaw as the working example. By the end, you'll be able to connect OpenClaw to any API or tool you want.
What you'll learn:
- How OpenClaw Skills work (the SKILL.md system)
- How to write Python scripts that OpenClaw can call via exec
- How to connect WhatsApp to OpenClaw in 2 minutes
- The full pattern for any API integration
Let's build.
Prerequisites
- macOS (or Linux) with Node.js 22.14+ installed (Node 24 recommended)
- Python 3.9+ installed
- An AWS account (free tier is enough to follow along)
- An API key from a model provider (Anthropic, OpenAI, Google, etc.)
Step 1 — Install OpenClaw
OpenClaw runs as a local background daemon on your machine. Install it with the official one-liner:
# macOS / Linux
curl -fsSL https://openclaw.ai/install.sh | bash
Then run the onboarding wizard — it sets up everything interactively:
openclaw onboard --install-daemon
The wizard guides you through:
- Choosing your LLM (Gemini, Claude, GPT-4, local Ollama — your choice)
- Entering your API key
- Installing the gateway daemon (runs OpenClaw 24/7 in the background)
- Naming your assistant
After onboarding, verify the gateway is running:
openclaw gateway status
Step 2 — Understand the Skills System
An OpenClaw Skill is just a folder with a SKILL.md file inside it, placed in your OpenClaw skills directory:
~/.openclaw/skills/
└── your-skill-name/
├── SKILL.md ← Required: defines the skill
└── scripts/ ← Optional: your supporting code
└── your_script.py
The SKILL.md file has two parts:
Part 1: YAML Frontmatter (metadata)
---
name: cloud-manager
description: Manages AWS and GCP cloud instances via natural language commands.
user-invocable: true
triggers:
- start vm
- stop vm
- list vms
metadata:
openclaw:
requires:
bins: ["python3"]
---
Key fields:
- name — identifier for the skill
- description — used by OpenClaw to decide when to activate this skill
- user-invocable: true — the user can activate it directly by sending a message
- triggers — hint phrases for when the skill should fire
- metadata.openclaw.requires.bins — binaries the skill needs (e.g. python3, aws)
Part 2: Markdown Instructions (the brain)
After the frontmatter, you write plain Markdown telling OpenClaw's LLM:
- What the skill does
- When to use which action
- How to call your scripts
- How to format the response
This is the real magic. You're writing a structured prompt that becomes the AI's "operating manual" for this skill. The more clearly you write it, the better it performs — no code needed for the intent layer.
Step 3 — Write the SKILL.md
Create the skill directory:
mkdir -p ~/.openclaw/skills/cloud-manager/scripts
Create ~/.openclaw/skills/cloud-manager/SKILL.md with this content:
---
name: cloud-manager
description: >
Manages AWS EC2 instances and GCP Compute Engine instances via natural language.
Can start, stop, restart, list, and monitor cloud virtual machines.
user-invocable: true
triggers:
- start vm
- stop vm
- restart vm
- list vms
- check cpu
- check memory
- cloud status
metadata:
openclaw:
requires:
bins: ["python3"]
---
# Cloud Manager Skill
You are a cloud infrastructure assistant. When the user asks to perform any
cloud operation, use the exec tool to run the appropriate Python script.
## Routing Commands
For AWS operations, run:
exec python3 ~/.openclaw/skills/cloud-manager/scripts/aws_manager.py --action <ACTION> [--name <name>]
For GCP operations, run:
exec python3 ~/.openclaw/skills/cloud-manager/scripts/gcp_manager.py --action <ACTION> [--name <name>]
## Intent Mapping
| User says | --action value |
|---------------------------------|----------------|
| start, launch, boot, power on | start |
| stop, halt, shut down, kill | stop |
| restart, reboot, reset, bounce | restart |
| list, show, what servers | list |
| cpu, processor usage | cpu |
| memory, ram | memory |
| status, health | status |
## Cloud Routing
- User says "AWS" or "Amazon" → only run aws_manager.py
- User says "GCP" or "Google" → only run gcp_manager.py
- No cloud specified → run BOTH and combine results
## Response Format
Format all responses for WhatsApp (plain text + emoji):
- Use 🟢 for running, 🔴 for stopped, 🟡 for pending
- Keep it concise — WhatsApp isn't a dashboard
- Always confirm the action completed (don't just say "sent command")
That's the whole skill definition. OpenClaw reads this file and now understands how to handle cloud messages.
Step 4 — Write the Python Script
This is where your actual API logic lives. OpenClaw's exec tool calls this script and passes the output back to the LLM.
Install the dependency:
pip3 install boto3
aws configure # enter your AWS Access Key, Secret, region
Create ~/.openclaw/skills/cloud-manager/scripts/aws_manager.py:
#!/usr/bin/env python3
import argparse, boto3, time
def get_client(region="us-east-1"):
return boto3.client("ec2", region_name=region)
def state_icon(state):
return {"running": "🟢", "stopped": "🔴", "pending": "🟡",
"stopping": "🟡", "rebooting": "🔄"}.get(state, "⚪")
def action_list(ec2):
resp = ec2.describe_instances()
instances = []
for r in resp["Reservations"]:
for inst in r["Instances"]:
tags = {t["Key"]: t["Value"] for t in inst.get("Tags", [])}
instances.append({
"name": tags.get("Name", inst["InstanceId"]),
"id": inst["InstanceId"],
"state": inst["State"]["Name"],
"type": inst["InstanceType"],
"ip": inst.get("PublicIpAddress", "—"),
})
if not instances:
print("☁️ No EC2 instances found.")
return
print("☁️ AWS EC2 Instances:")
print("━" * 40)
for i in instances:
print(f"{state_icon(i['state'])} {i['name']} | {i['type']} | IP: {i['ip']}")
print("━" * 40)
running = sum(1 for i in instances if i["state"] == "running")
print(f"Total: {len(instances)} | 🟢 Running: {running} | 🔴 Stopped: {len(instances)-running}")
def action_start(ec2, name=None):
filters = [{"Name": "tag:Name", "Values": [f"*{name}*"]}] if name else []
resp = ec2.describe_instances(Filters=filters)
for r in resp["Reservations"]:
for inst in r["Instances"]:
iid = inst["InstanceId"]
tags = {t["Key"]: t["Value"] for t in inst.get("Tags", [])}
iname = tags.get("Name", iid)
print(f"🚀 Starting {iname} ({iid})...")
ec2.start_instances(InstanceIds=[iid])
for _ in range(18):
time.sleep(5)
s = ec2.describe_instances(InstanceIds=[iid])
state = s["Reservations"][0]["Instances"][0]["State"]["Name"]
ip = s["Reservations"][0]["Instances"][0].get("PublicIpAddress", "—")
if state == "running":
print(f"✅ {iname} is now RUNNING!")
print(f" Public IP: {ip}")
return
print("⏳ Still starting — check AWS Console.")
def main():
parser = argparse.ArgumentParser()
# This example shows "list" and "start" — see GitHub repo for stop/restart/cpu
parser.add_argument("--action", required=True, choices=["list", "start"])
parser.add_argument("--name", help="Instance name tag (for start)")
args = parser.parse_args()
ec2 = get_client()
if args.action == "list": action_list(ec2)
elif args.action == "start": action_start(ec2, args.name)
if __name__ == "__main__":
main()
Always test your script in isolation first:
python3 ~/.openclaw/skills/cloud-manager/scripts/aws_manager.py --action list
If this works standalone, OpenClaw will work. Half of all skill debugging is just the underlying script misbehaving.
Step 5 — Load the Skill
Restart the gateway to pick up the new skill:
openclaw gateway restart
Verify it's registered:
openclaw skills list
You should see cloud-manager in the output.
Step 6 — Connect WhatsApp (2 minutes, one-time)
This is where it all comes together. OpenClaw supports WhatsApp natively via the Linked Devices protocol — the exact same way WhatsApp Web works in your browser. No Meta developer account, no webhooks, no ngrok, no server.
openclaw channels add
Follow the prompt:
- Select WhatsApp (QR link)
- Open WhatsApp on your phone → Settings → Linked Devices → Link a Device
- Scan the QR code shown in your terminal
Done. The link is permanent — you only do this once. Every time you restart the gateway, the WhatsApp session reconnects automatically.
Step 7 — Test End-to-End
With the gateway running, send yourself these messages on WhatsApp:
list vms
→ ☁️ AWS EC2 Instances (us-east-1):
🔴 aws-web-vm | t3.micro | us-east-1c | STOPPED
start vm
→ 🚀 Starting aws-web-vm... ✅ RUNNING! Public IP: 54.x.x.x
check cpu
→ 📊 CPU — aws-web-vm: Last 5 min: 4.2% [░░░░░░░░░░] ✅
The whole round trip — WhatsApp → OpenClaw → Python script → AWS API → reply — takes 8–15 seconds.
How It Works Under the Hood
When you send "start vm":
- WhatsApp → OpenClaw via local Linked Devices session (no public server needed)
- OpenClaw LLM receives your message + all SKILL.md files as context
- Intent match — LLM reads the cloud-manager skill and decides to call aws_manager.py with --action start
- exec tool runs the Python script on your machine with your local AWS credentials
- stdout captured — script output returned to the LLM
- LLM formats a clean WhatsApp reply from the script output
- Reply sent back to WhatsApp
Your AWS credentials never leave your machine. No middleware, no SaaS layer.
5 Tips for Building Great OpenClaw Skills
After building CloudClaw, here's what I'd tell anyone building their first skill:
1. Be specific in your SKILL.md
The LLM reads your Markdown literally. Write it like documentation for a junior engineer who needs to know exactly what to do. Vague instructions = vague behavior.
2. One script with --action flags beats six separate scripts
Easier to maintain, easier to describe in SKILL.md, easier to test.
3. Design output for the channel
WhatsApp ≠ terminal. No ANSI colors, no wide tables, no JSON blobs. Use emoji, keep lines short, always end with a human-readable status summary.
4. Test your script standalone before connecting OpenClaw
Run python3 your_script.py --action list first. Always. Half of debugging is the script, not OpenClaw.
5. Write actionable error messages
"❌ AWS credentials expired. Run: aws configure" beats a stack trace landing in someone's WhatsApp chat every single time.
Why This Pattern Works for Any API
The CloudClaw pattern — SKILL.md for intent routing, Python script for API logic, exec to wire them together — works for literally any integration:
| You want to... | Skill triggers | Script calls |
|---|---|---|
| Control cloud VMs | "start vm", "list servers" | boto3, GCP SDK |
| Query a database | "show users", "run report" | psycopg2, pymongo |
| Post to social media | "tweet this", "post update" | tweepy, requests |
| Control home devices | "turn on lights", "set temp" | Home Assistant API |
| Query internal tools | "show tickets", "what's open" | Jira/Linear API |
The pattern is always the same. The only thing that changes is the API you're wrapping.
Full Source Code
Everything is open source — including the complete aws_manager.py, gcp_manager.py (for GCP support), SKILL.md, one-command setup.sh, and a live real-time dashboard that syncs with real AWS status every 10 seconds:
🔗 GitHub: github.com/parulmalhotraiitk/clawcloud
ClawCon Michigan
Did not attend, but the ClawCon community was a huge inspiration — seeing what people were building with OpenClaw in-person was incredible to read about!
Built CloudClaw in 3 days using OpenClaw + boto3 + google-cloud-compute. The lobster runs the cloud now. 🦞☁️
Top comments (0)