
Tailscale
- 9 installs
- 638 repo stars
- Updated March 7, 2026
- sundial-org/awesome-openclaw-skills
Helps with ai & agent building tasks during AI-assisted development.
About
tailscale is a Claude Code skill for ai & agent building. It helps developers move faster with AI-assisted coding.
- tailscale
- AI & Agent Building
- AI-coding skill
Tailscale by the numbers
- 9 all-time installs (skills.sh)
- Ranked #12,152 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/sundial-org/awesome-openclaw-skills --skill tailscaleAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 9 |
|---|---|
| repo stars | ★ 638 |
| Last updated | March 7, 2026 |
| Repository | sundial-org/awesome-openclaw-skills ↗ |
What it does
Helps with ai & agent building tasks during AI-assisted development.
Files
Tailscale Skill
Hybrid skill using CLI for local operations and API for tailnet-wide management.
Setup
API config (optional, for tailnet-wide operations): ~/.clawdbot/credentials/tailscale/config.json
{
"apiKey": "tskey-api-k...",
"tailnet": "-"
}Get your API key from: Tailscale Admin Console → Settings → Keys → Generate API Key
The tailnet can be - (auto-detect), your org name, or email domain.
---
Local Operations (CLI)
These work on the current machine only.
Status & Diagnostics
# Current status (peers, connection state)
tailscale status
tailscale status --json | jq '.Peer | to_entries[] | {name: .value.HostName, ip: .value.TailscaleIPs[0], online: .value.Online}'
# Network diagnostics (NAT type, DERP, UDP)
tailscale netcheck
tailscale netcheck --format=json
# Get this machine's Tailscale IP
tailscale ip -4
# Identify a Tailscale IP
tailscale whois 100.x.x.xConnectivity
# Ping a peer (shows direct vs relay)
tailscale ping <hostname-or-ip>
# Connect/disconnect
tailscale up
tailscale down
# Use an exit node
tailscale up --exit-node=<node-name>
tailscale exit-node list
tailscale exit-node suggestFile Transfer (Taildrop)
# Send files to a device
tailscale file cp myfile.txt <device-name>:
# Receive files (moves from inbox to directory)
tailscale file get ~/Downloads
tailscale file get --wait ~/Downloads # blocks until file arrivesExpose Services
# Share locally within tailnet (private)
tailscale serve 3000
tailscale serve https://localhost:8080
# Share publicly to internet
tailscale funnel 8080
# Check what's being served
tailscale serve status
tailscale funnel statusSSH
# SSH via Tailscale (uses MagicDNS)
tailscale ssh user@hostname
# Enable SSH server on this machine
tailscale up --ssh---
Tailnet-Wide Operations (API)
These manage your entire tailnet. Requires API key.
List All Devices
./scripts/ts-api.sh devices
# With details
./scripts/ts-api.sh devices --verboseDevice Details
./scripts/ts-api.sh device <device-id-or-name>Check Online Status
# Quick online check for all devices
./scripts/ts-api.sh onlineAuthorize/Delete Device
./scripts/ts-api.sh authorize <device-id>
./scripts/ts-api.sh delete <device-id>Device Tags & Routes
./scripts/ts-api.sh tags <device-id> tag:server,tag:prod
./scripts/ts-api.sh routes <device-id>Auth Keys
# Create a reusable auth key
./scripts/ts-api.sh create-key --reusable --tags tag:server
# Create ephemeral key (device auto-removes when offline)
./scripts/ts-api.sh create-key --ephemeral
# List keys
./scripts/ts-api.sh keysDNS Management
./scripts/ts-api.sh dns # Show DNS config
./scripts/ts-api.sh dns-nameservers # List nameservers
./scripts/ts-api.sh magic-dns on|off # Toggle MagicDNSACLs
./scripts/ts-api.sh acl # Get current ACL
./scripts/ts-api.sh acl-validate <file> # Validate ACL file---
Common Use Cases
"Who's online right now?"
./scripts/ts-api.sh online"Send this file to my phone"
tailscale file cp document.pdf my-phone:"Expose my dev server publicly"
tailscale funnel 3000"Create a key for a new server"
./scripts/ts-api.sh create-key --reusable --tags tag:server --expiry 7d"Is the connection direct or relayed?"
tailscale ping my-serverTailscale Skill
Manage your Tailscale tailnet from Clawdbot.
What It Does
CLI (local operations):
- Status — check connection status, peers, NAT type
- Ping — test connectivity to peers (direct vs relay)
- File transfer — send/receive files via Taildrop
- Serve/Funnel — expose local services privately or publicly
- SSH — connect via Tailscale SSH
API (tailnet-wide):
- Devices — list all devices, authorize/delete, set tags
- Auth keys — create reusable/ephemeral keys for new devices
- DNS — manage nameservers, toggle MagicDNS
- ACLs — view and validate access control policies
Setup
CLI Only (No Config Needed)
The tailscale CLI works out of the box for local operations:
tailscale status
tailscale ping my-server
tailscale file cp document.pdf my-phone:API Access (for Tailnet-wide Operations)
1. Create an API Key
1. Go to Tailscale Admin Console 2. Click Generate API Key 3. Copy the key (starts with tskey-api-)
2. Create Credentials File
mkdir -p ~/.clawdbot/credentials/tailscale
cp config.json.example ~/.clawdbot/credentials/tailscale/config.json
# Edit with your actual API keyOr create manually:
{
"apiKey": "tskey-api-your-key-here",
"tailnet": "-"
}The tailnet can be:
-(auto-detect from API key)- Your organization name
- Your email domain
3. Test It
./scripts/ts-api.sh devicesUsage Examples
Local CLI operations
# Status and diagnostics
tailscale status
tailscale netcheck
# Ping a peer
tailscale ping my-server
# Send a file
tailscale file cp myfile.txt my-phone:
# Expose a local service
tailscale serve 3000 # Private (tailnet only)
tailscale funnel 8080 # Public (internet)API operations
# List all devices
ts-api.sh devices
ts-api.sh devices --verbose
# Check who's online
ts-api.sh online
# Device details
ts-api.sh device my-server
# Create auth key
ts-api.sh create-key --reusable --tags tag:server --expiry 7d
# List auth keys
ts-api.sh keys
# Authorize/delete device
ts-api.sh authorize <device-id>
ts-api.sh delete <device-id>
# DNS management
ts-api.sh dns
ts-api.sh magic-dns onEnvironment Variables (Alternative)
export TS_API_KEY="tskey-api-..."
export TS_TAILNET="-"Troubleshooting
"No API key configured" → Create config file at ~/.clawdbot/credentials/tailscale/config.json or set TS_API_KEY
401 Unauthorized → API key is invalid or expired — generate a new one
"tailscale: command not found" → Install Tailscale: https://tailscale.com/download
Device not found by name → The script searches by hostname. Use the full device ID if name lookup fails.
License
MIT
#!/bin/bash
# Tailscale API helper script
# Usage: ts-api.sh <command> [args...]
set -euo pipefail
CONFIG_FILE="${TS_CONFIG:-$HOME/.clawdbot/credentials/tailscale/config.json}"
API_BASE="https://api.tailscale.com/api/v2"
# Load config
if [[ -f "$CONFIG_FILE" ]]; then
TS_API_KEY=$(jq -r '.apiKey // empty' "$CONFIG_FILE")
TS_TAILNET=$(jq -r '.tailnet // "-"' "$CONFIG_FILE")
else
TS_API_KEY="${TS_API_KEY:-}"
TS_TAILNET="${TS_TAILNET:--}"
fi
if [[ -z "$TS_API_KEY" ]]; then
echo '{"error": "No API key configured. Set TS_API_KEY or create '"$CONFIG_FILE"'"}' >&2
exit 1
fi
# API call helper (Basic Auth with API key as username)
api() {
local method="$1"
local endpoint="$2"
shift 2
curl -sS -X "$method" \
-u "${TS_API_KEY}:" \
-H "Content-Type: application/json" \
"$@" \
"${API_BASE}${endpoint}"
}
usage() {
cat <<EOF
Tailscale API CLI
Usage: $(basename "$0") <command> [options]
Device Commands:
devices [--verbose] List all devices in tailnet
device <id|name> Get device details
online Show online status of all devices
authorize <id> Authorize a device
delete <id> Delete a device from tailnet
tags <id> <tags> Set device tags (comma-separated)
routes <id> Get device routes
Key Management:
keys List auth keys
create-key [options] Create new auth key
--reusable Key can be used multiple times
--ephemeral Devices auto-remove when offline
--tags <tags> Comma-separated tags (e.g., tag:server)
--expiry <duration> Expiry (e.g., 1h, 7d, 90d)
delete-key <id> Delete an auth key
DNS Commands:
dns Show DNS configuration
dns-nameservers List nameservers
magic-dns <on|off> Toggle MagicDNS
ACL Commands:
acl Get current ACL policy
acl-validate <file> Validate an ACL file
Examples:
$(basename "$0") devices
$(basename "$0") online
$(basename "$0") create-key --reusable --tags tag:ci --expiry 7d
$(basename "$0") tags nodekey:abc123 tag:server,tag:prod
EOF
}
# Find device ID by name or return as-is if already an ID
resolve_device() {
local input="$1"
# If it looks like a device ID, return as-is
if [[ "$input" =~ ^[0-9]+$ ]] || [[ "$input" =~ ^nodekey: ]]; then
echo "$input"
return
fi
# Search by hostname
local devices
devices=$(api GET "/tailnet/${TS_TAILNET}/devices")
local id
id=$(echo "$devices" | jq -r --arg name "$input" '.devices[] | select(.hostname == $name or .name == $name or (.name | split(".")[0]) == $name) | .id' | head -1)
if [[ -n "$id" && "$id" != "null" ]]; then
echo "$id"
else
echo "$input" # Return as-is, let API error if invalid
fi
}
cmd_devices() {
local verbose=false
while [[ $# -gt 0 ]]; do
case "$1" in
--verbose|-v) verbose=true; shift ;;
*) shift ;;
esac
done
local result
result=$(api GET "/tailnet/${TS_TAILNET}/devices")
if [[ "$verbose" == "true" ]]; then
echo "$result" | jq '.devices'
else
echo "$result" | jq '[.devices[] | {
id: .id,
name: .hostname,
ip: .addresses[0],
os: .os,
online: (.lastSeen | if . then (now - (. | fromdateiso8601) < 300) else false end),
lastSeen: .lastSeen
}]'
fi
}
cmd_device() {
local id
id=$(resolve_device "$1")
api GET "/device/${id}"
}
cmd_online() {
local result
result=$(api GET "/tailnet/${TS_TAILNET}/devices")
echo "$result" | jq '[.devices[] | {
name: .hostname,
ip: .addresses[0],
online: (.lastSeen | if . then (now - (. | fromdateiso8601) < 300) else false end),
lastSeen: (if .lastSeen then .lastSeen else "never" end)
}] | sort_by(.online) | reverse'
}
cmd_authorize() {
local id
id=$(resolve_device "$1")
api POST "/device/${id}/authorized" -d '{"authorized": true}'
echo '{"status": "ok", "device": "'"$id"'", "authorized": true}'
}
cmd_delete() {
local id
id=$(resolve_device "$1")
api DELETE "/device/${id}"
echo '{"status": "ok", "device": "'"$id"'", "deleted": true}'
}
cmd_tags() {
local id
id=$(resolve_device "$1")
local tags="$2"
# Convert comma-separated to JSON array
local tags_json
tags_json=$(echo "$tags" | jq -R 'split(",") | map(select(length > 0))')
api POST "/device/${id}/tags" -d "{\"tags\": ${tags_json}}"
echo '{"status": "ok", "device": "'"$id"'", "tags": '"$tags_json"'}'
}
cmd_routes() {
local id
id=$(resolve_device "$1")
api GET "/device/${id}/routes"
}
cmd_keys() {
api GET "/tailnet/${TS_TAILNET}/keys" | jq '.keys'
}
cmd_create_key() {
local reusable=false
local ephemeral=false
local tags=""
local expiry=86400 # Default 1 day in seconds
while [[ $# -gt 0 ]]; do
case "$1" in
--reusable) reusable=true; shift ;;
--ephemeral) ephemeral=true; shift ;;
--tags) tags="$2"; shift 2 ;;
--expiry)
# Convert human-readable to seconds
local val="$2"
if [[ "$val" =~ ^([0-9]+)d$ ]]; then
expiry=$((${BASH_REMATCH[1]} * 86400))
elif [[ "$val" =~ ^([0-9]+)h$ ]]; then
expiry=$((${BASH_REMATCH[1]} * 3600))
else
expiry="$val"
fi
shift 2
;;
*) shift ;;
esac
done
local tags_json="[]"
if [[ -n "$tags" ]]; then
tags_json=$(echo "$tags" | jq -R 'split(",") | map(select(length > 0))')
fi
local body
body=$(jq -n \
--argjson reusable "$reusable" \
--argjson ephemeral "$ephemeral" \
--argjson tags "$tags_json" \
--argjson expiry "$expiry" \
'{
capabilities: {
devices: {
create: {
reusable: $reusable,
ephemeral: $ephemeral,
tags: $tags
}
}
},
expirySeconds: $expiry
}')
api POST "/tailnet/${TS_TAILNET}/keys" -d "$body"
}
cmd_delete_key() {
local id="$1"
api DELETE "/tailnet/${TS_TAILNET}/keys/${id}"
echo '{"status": "ok", "key": "'"$id"'", "deleted": true}'
}
cmd_dns() {
echo "{"
echo ' "nameservers": '
api GET "/tailnet/${TS_TAILNET}/dns/nameservers"
echo ','
echo ' "searchPaths": '
api GET "/tailnet/${TS_TAILNET}/dns/searchpaths"
echo ','
echo ' "preferences": '
api GET "/tailnet/${TS_TAILNET}/dns/preferences"
echo "}"
}
cmd_dns_nameservers() {
api GET "/tailnet/${TS_TAILNET}/dns/nameservers"
}
cmd_magic_dns() {
local state="$1"
local enabled=false
[[ "$state" == "on" || "$state" == "true" || "$state" == "1" ]] && enabled=true
api POST "/tailnet/${TS_TAILNET}/dns/preferences" -d "{\"magicDNS\": $enabled}"
}
cmd_acl() {
api GET "/tailnet/${TS_TAILNET}/acl" -H "Accept: application/hujson"
}
cmd_acl_validate() {
local file="$1"
if [[ ! -f "$file" ]]; then
echo '{"error": "File not found: '"$file"'"}' >&2
exit 1
fi
api POST "/tailnet/${TS_TAILNET}/acl/validate" -d @"$file"
}
# Main dispatch
case "${1:-}" in
devices) shift; cmd_devices "$@" ;;
device) shift; cmd_device "$@" ;;
online) shift; cmd_online "$@" ;;
authorize) shift; cmd_authorize "$@" ;;
delete) shift; cmd_delete "$@" ;;
tags) shift; cmd_tags "$@" ;;
routes) shift; cmd_routes "$@" ;;
keys) shift; cmd_keys "$@" ;;
create-key) shift; cmd_create_key "$@" ;;
delete-key) shift; cmd_delete_key "$@" ;;
dns) shift; cmd_dns "$@" ;;
dns-nameservers) shift; cmd_dns_nameservers "$@" ;;
magic-dns) shift; cmd_magic_dns "$@" ;;
acl) shift; cmd_acl "$@" ;;
acl-validate) shift; cmd_acl_validate "$@" ;;
-h|--help|help|"") usage ;;
*) echo "Unknown command: $1" >&2; usage; exit 1 ;;
esac