| name | location |
| description | Get the user's current GPS location. Auto-detects the best backend
for the host OS:
macOS → corelocationcli (CoreLocation — cell / WiFi / GPS)
Linux → gdbus + geoclue2 (kernel-driver / WiFi / Mozilla MLS)
fallback → IP geolocation (ipapi.co — city-level only)
Returns coordinates / address / city / full details — request what
you need with the `format` arg.
When to use this vs the {{location}} system prompt context:
- {{location}} is set from $KINCLAW_LOCATION env var — "where the
user generally is" (home / office). Static, free, always available.
- This skill reads the OS location service in real time — "where the
user is right NOW". Use when user might be traveling / driving /
out, or when meter-level precision matters.
First invocation prompts for permission (macOS Location Services /
Linux Geoclue per-app authorization through xdg-desktop-portal).
Grant it for whichever process kinclaw runs in.
First-time setup:
macOS: brew install corelocationcli
Linux: apt install geoclue-2.0 jq curl # or pacman -S / dnf install
(gdbus ships with glib2 — already present on every desktop Linux)
|
| command | ["sh","-c","FORMAT=\"$1\"\n[ \"$FORMAT\" = \"\" ] && FORMAT=\"coords\"\n\n# ──── Backend detection ────────────────────────────────────────\n# Prefer corelocationcli on macOS (CoreLocation is the most accurate\n# and lowest-latency option there). Linux uses geoclue2 via gdbus\n# (universal — ships with glib2). IP fallback is last resort for\n# both, useful for servers / CI / Pi without GPS hardware.\n\nOS=$(uname -s)\nBACKEND=\"\"\nif [ \"$OS\" = \"Darwin\" ] && command -v corelocationcli >/dev/null 2>&1; then\n BACKEND=\"corelocationcli\"\nelif [ \"$OS\" = \"Linux\" ] && command -v gdbus >/dev/null 2>&1; then\n BACKEND=\"geoclue\"\nelif command -v curl >/dev/null 2>&1; then\n BACKEND=\"ip\"\nelse\n echo \"no location backend available\" >&2\n case \"$OS\" in\n Darwin) echo \" install: brew install corelocationcli\" >&2 ;;\n Linux) echo \" install: apt install geoclue-2.0 jq curl\" >&2 ;;\n esac\n exit 1\nfi\n\n# ──── Backend: corelocationcli (macOS) ─────────────────────────\nif [ \"$BACKEND\" = \"corelocationcli\" ]; then\n case \"$FORMAT\" in\n coords)\n LL=$(corelocationcli -once -format \"%latitude,%longitude\")\n printf 'GPS coordinates (lat,lon): %s\\n' \"$LL\"\n ;;\n address)\n ADDR=$(corelocationcli -once -format \"%address\")\n printf 'GPS address: %s\\n' \"$ADDR\"\n ;;\n city)\n CITY=$(corelocationcli -once -format \"%locality\")\n printf 'GPS city: %s\\n' \"$CITY\"\n ;;\n full)\n printf 'GPS (full reading from CoreLocation):\\n'\n corelocationcli -once\n ;;\n *)\n echo \"unknown format: $FORMAT (expected: coords | address | city | full)\" >&2\n exit 1\n ;;\n esac\n exit 0\nfi\n\n# ──── Backend: geoclue2 via gdbus (Linux) ──────────────────────\n# Geoclue2 D-Bus dance:\n# 1) Manager.GetClient → client object path\n# 2) Set DesktopId + RequestedAccuracyLevel on the client\n# 3) Client.Start → triggers actual fix\n# 4) Read Location property → returns location object path\n# 5) Read Latitude / Longitude / Description from location object\n# 6) Client.Stop → release the resource\nif [ \"$BACKEND\" = \"geoclue\" ]; then\n gdbus_call() {\n gdbus call --system --dest org.freedesktop.GeoClue2 \"$@\" 2>/dev/null\n }\n\n # 1) Create a Client\n CLIENT_OUT=$(gdbus_call --object-path /org/freedesktop/GeoClue2/Manager \\\n --method org.freedesktop.GeoClue2.Manager.GetClient)\n if [ -z \"$CLIENT_OUT\" ]; then\n echo \"Geoclue2 not running. Start with: systemctl --user start geoclue.service\" >&2\n echo \"Falling back to IP geolocation...\" >&2\n BACKEND=\"ip\"\n else\n CLIENT_PATH=$(printf '%s' \"$CLIENT_OUT\" | sed -n \"s|.*'\\\\(/org/freedesktop/GeoClue2/Client/[^']*\\\\)'.*|\\\\1|p\")\n if [ -z \"$CLIENT_PATH\" ]; then\n echo \"Geoclue2: couldn't parse client path from: $CLIENT_OUT\" >&2\n BACKEND=\"ip\"\n fi\n fi\n\n if [ \"$BACKEND\" = \"geoclue\" ]; then\n # 2) Set DesktopId (required for newer geoclue) + accuracy 8 = exact\n gdbus_call --object-path \"$CLIENT_PATH\" \\\n --method org.freedesktop.DBus.Properties.Set \\\n org.freedesktop.GeoClue2.Client DesktopId '<\"kinclaw\">' >/dev/null\n\n gdbus_call --object-path \"$CLIENT_PATH\" \\\n --method org.freedesktop.DBus.Properties.Set \\\n org.freedesktop.GeoClue2.Client RequestedAccuracyLevel '<uint32 8>' >/dev/null\n\n # 3) Start\n gdbus_call --object-path \"$CLIENT_PATH\" \\\n --method org.freedesktop.GeoClue2.Client.Start >/dev/null\n\n # 4) Poll for Location property (up to ~10s)\n LOC_PATH=\"\"\n for i in 1 2 3 4 5 6 7 8 9 10; do\n LP_OUT=$(gdbus_call --object-path \"$CLIENT_PATH\" \\\n --method org.freedesktop.DBus.Properties.Get \\\n org.freedesktop.GeoClue2.Client Location)\n LOC_PATH=$(printf '%s' \"$LP_OUT\" | sed -n \"s|.*'\\\\(/org/freedesktop/GeoClue2/Client/[^']*Location[^']*\\\\)'.*|\\\\1|p\")\n [ -n \"$LOC_PATH\" ] && [ \"$LOC_PATH\" != \"/\" ] && break\n sleep 1\n done\n\n if [ -z \"$LOC_PATH\" ] || [ \"$LOC_PATH\" = \"/\" ]; then\n # 6) Cleanup\n gdbus_call --object-path \"$CLIENT_PATH\" \\\n --method org.freedesktop.GeoClue2.Client.Stop >/dev/null\n echo \"Geoclue2 didn't return a fix within 10s — agent may need to grant permission via xdg-desktop-portal dialog.\" >&2\n echo \"Falling back to IP geolocation...\" >&2\n BACKEND=\"ip\"\n else\n # 5) Read Latitude / Longitude / Description\n read_prop() {\n local prop=\"$1\"\n local out\n out=$(gdbus_call --object-path \"$LOC_PATH\" \\\n --method org.freedesktop.DBus.Properties.Get \\\n org.freedesktop.GeoClue2.Location \"$prop\")\n # Strip the variant wrapper: \"(<value>,)\" → \"value\"\n printf '%s' \"$out\" | sed -E \"s/^\\\\(<(.*)>,?\\\\)\\$/\\\\1/\" | sed -E \"s/^'(.*)'\\$/\\\\1/\"\n }\n LAT=$(read_prop Latitude)\n LON=$(read_prop Longitude)\n ALT=$(read_prop Altitude)\n ACC=$(read_prop Accuracy)\n DESCR=$(read_prop Description)\n\n # Cleanup\n gdbus_call --object-path \"$CLIENT_PATH\" \\\n --method org.freedesktop.GeoClue2.Client.Stop >/dev/null\n\n case \"$FORMAT\" in\n coords)\n printf 'GPS coordinates (lat,lon): %s,%s\\n' \"$LAT\" \"$LON\"\n ;;\n address|city)\n # Geoclue doesn't reverse-geocode; query Nominatim (OpenStreetMap).\n if command -v curl >/dev/null 2>&1; then\n NOM=$(curl -sS --max-time 5 \\\n -H 'User-Agent: KinClaw/1.0 (https://localkin.dev)' \\\n \"https://nominatim.openstreetmap.org/reverse?lat=$LAT&lon=$LON&format=json\")\n if [ \"$FORMAT\" = \"address\" ]; then\n ADDR=$(printf '%s' \"$NOM\" | sed -nE 's/.*\"display_name\":\"([^\"]+)\".*/\\1/p')\n printf 'GPS address: %s\\n' \"${ADDR:-(reverse geocode failed)}\"\n else\n CITY=$(printf '%s' \"$NOM\" | sed -nE 's/.*\"city\":\"([^\"]+)\".*/\\1/p')\n [ -z \"$CITY\" ] && CITY=$(printf '%s' \"$NOM\" | sed -nE 's/.*\"town\":\"([^\"]+)\".*/\\1/p')\n [ -z \"$CITY\" ] && CITY=$(printf '%s' \"$NOM\" | sed -nE 's/.*\"village\":\"([^\"]+)\".*/\\1/p')\n printf 'GPS city: %s\\n' \"${CITY:-(reverse geocode failed)}\"\n fi\n else\n echo \"curl not installed — can't reverse-geocode. Coords: $LAT,$LON\" >&2\n exit 1\n fi\n ;;\n full)\n printf 'GPS (Geoclue2):\\n'\n printf ' latitude: %s\\n' \"$LAT\"\n printf ' longitude: %s\\n' \"$LON\"\n printf ' altitude: %s\\n' \"$ALT\"\n printf ' accuracy_m: %s\\n' \"$ACC\"\n printf ' description: %s\\n' \"$DESCR\"\n ;;\n *)\n echo \"unknown format: $FORMAT (expected: coords | address | city | full)\" >&2\n exit 1\n ;;\n esac\n exit 0\n fi\n fi\nfi\n\n# ──── Backend: IP geolocation (last resort) ────────────────────\n# Useful for servers / CI / Pi without GPS. Accuracy: city-level only.\nif [ \"$BACKEND\" = \"ip\" ]; then\n JSON=$(curl -sS --max-time 5 https://ipapi.co/json)\n if [ -z \"$JSON\" ]; then\n echo \"IP geolocation failed (no network?)\" >&2\n exit 1\n fi\n LAT=$(printf '%s' \"$JSON\" | sed -nE 's/.*\"latitude\": *([^,]+).*/\\1/p')\n LON=$(printf '%s' \"$JSON\" | sed -nE 's/.*\"longitude\": *([^,]+).*/\\1/p')\n CITY=$(printf '%s' \"$JSON\" | sed -nE 's/.*\"city\": *\"([^\"]+)\".*/\\1/p')\n REGION=$(printf '%s' \"$JSON\" | sed -nE 's/.*\"region\": *\"([^\"]+)\".*/\\1/p')\n COUNTRY=$(printf '%s' \"$JSON\" | sed -nE 's/.*\"country_name\": *\"([^\"]+)\".*/\\1/p')\n\n case \"$FORMAT\" in\n coords)\n printf 'GPS coordinates (lat,lon, via IP geolocation — city-level accuracy): %s,%s\\n' \"$LAT\" \"$LON\"\n ;;\n address)\n printf 'GPS address (via IP — city-level only): %s, %s, %s\\n' \"$CITY\" \"$REGION\" \"$COUNTRY\"\n ;;\n city)\n printf 'GPS city (via IP): %s\\n' \"$CITY\"\n ;;\n full)\n printf 'GPS (full reading via IP geolocation — city-level accuracy):\\n'\n printf ' latitude: %s\\n' \"$LAT\"\n printf ' longitude: %s\\n' \"$LON\"\n printf ' city: %s\\n' \"$CITY\"\n printf ' region: %s\\n' \"$REGION\"\n printf ' country: %s\\n' \"$COUNTRY\"\n ;;\n *)\n echo \"unknown format: $FORMAT (expected: coords | address | city | full)\" >&2\n exit 1\n ;;\n esac\n exit 0\nfi\n","_"] |
| args | ["{{format}}"] |
| schema | {"format":{"type":"string","description":"Output shape:\n coords - \"37.7749,-122.4194\" (default; lat,lon)\n address - reverse-geocoded street address\n city - locality name only\n full - all available fields (lat / lon / altitude / accuracy / address / time)\n"}} |
| timeout | 20 |
location — real-time GPS, cross-platform
Auto-detected backend per OS:
| OS | Backend | Accuracy | Permission |
|---|
| macOS | corelocationcli (CoreLocation) | cell / WiFi / GPS (meter-level) | System Settings → Privacy & Security → Location |
| Linux | gdbus + Geoclue2 | WiFi / Mozilla MLS / kernel-DRM | xdg-desktop-portal dialog (per-app) |
| any | curl ipapi.co fallback | city-level only | none (uses your IP) |
Two-tier location story for KinClaw:
| Layer | Where | When to use |
|---|
{{location}} substitution | Soul prompt, from $KINCLAW_LOCATION env | Persistent context — "user lives in Beijing" |
location skill (this) | Tool call to OS location service | Real-time GPS — "user is at $current_lat,$lon" |
The skill is opt-in: not installed → clear error message + install hint.
Already installed → uses the OS's actual location services.
Privacy
- macOS: triggers Apple's standard Location Services permission flow.
macOS asks ONCE per process; subsequent calls reuse the grant. Revoke
via System Settings → Privacy & Security → Location Services.
- Linux: Geoclue2 uses
xdg-desktop-portal for permission. On GNOME /
KDE it prompts via the portal dialog; on Sway/i3 the dialog may not
appear (in which case grant via gnome-control-center privacy once).
- IP fallback: no permission needed because no GPS hardware is queried —
ipapi.co geolocates your public IP. City-level only.
Usage
location → "37.7749,-122.4194"
location format=address → "1 Apple Park Way, Cupertino, CA"
location format=city → "Cupertino"
location format=full → multi-line: lat/lon/alt/accuracy/...
The backend is auto-selected at invocation time; the agent doesn't need
to know whether it's running on macOS or Linux.
Linux setup (one-time)
sudo apt install geoclue-2.0 curl
sudo dnf install geoclue2 curl
sudo pacman -S geoclue curl
systemctl --user status geoclue.service
If Geoclue2 doesn't return a fix within 10 seconds (often the case on
servers / Pi without WiFi positioning), the skill auto-falls-back to IP
geolocation. Forge a derivative skill if you want different fallback
behavior (e.g. cache last good fix, exponential retry).