location
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)
- License 许可
- Apache-2.0
- 最近更新
- 2026-11-01
- Stars
- ★ 9
- Forks
- 3
01 前言 Front Matter
- args
- [ "{{format}}" ]
- name
- location
- 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" } }
- 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) ─────────────────────────\n# NOTE: do NOT pass `-once`. It is not a valid flag in v4.x, and the\n# tool silently swallows unknown args — including the `-format` that\n# follows — then exits 0 printing the default \"lat lon\". Every branch\n# below would return bare coords for every requested format. It prints\n# once anyway unless `--watch` is given. (Verified against v4.0.7.)\nif [ \"$BACKEND\" = \"corelocationcli\" ]; then\n case \"$FORMAT\" in\n coords)\n LL=$(corelocationcli --format \"%latitude,%longitude\")\n printf 'GPS coordinates (lat,lon): %s\\n' \"$LL\"\n ;;\n address)\n ADDR=$(corelocationcli --format \"%address\")\n printf 'GPS address: %s\\n' \"$ADDR\"\n ;;\n city)\n CITY=$(corelocationcli --format \"%locality\")\n printf 'GPS city: %s\\n' \"$CITY\"\n ;;\n full)\n printf 'GPS (full reading from CoreLocation):\\n'\n # One line with '|' between fields, made lines here: written as lines inside the\n # format, the fields fell outside this YAML block and the whole skill stopped loading.\n corelocationcli --format \"latitude: %latitude|longitude: %longitude|altitude: %altitude|accuracy_m: %h_accuracy|address: %address|time: %time\" \\\n | tr '|' '\\n' | sed 's/^/ /'\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", "_" ]
- timeout
- 20
- 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)
03 SKILL.md 正文
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-portalfor permission. On GNOME / KDE it prompts via the portal dialog; on Sway/i3 the dialog may not appear (in which case grant viagnome-control-center privacyonce). - 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)
# Debian / Ubuntu
sudo apt install geoclue-2.0 curl
# Fedora
sudo dnf install geoclue2 curl
# Arch
sudo pacman -S geoclue curl
# Verify geoclue is running
systemctl --user status geoclue.service # may be system-wide, try without --user too
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).
04 同仓库更多 Skills
web
Universal web tool — fetches and interacts with web pages through real Chromium (Playwright). Renders JavaScript, follows redirects, handles cookies, bypasses many anti-scrape stubs that block plain `web_fetch`. Each call launches a fresh browser, executes the requested flow, closes. ~2-3s cold start; no persistent state. Use INSTEAD of `web_fetch` when: - Target is a JS-heavy SPA (prices via XHR, lazy content) - `web_fetch` returned an anti-scrape placeholder (Zhihu's "click here" stub, JD's chrome-only HTML) - You need to interact (click, fill input) before extracting Common patterns: fetch rendered text: url=X wait for content then read: url=X wait_for=".price" extract specific element: url=X selector=".product-list" click and read result: url=X click=".search-button" fill form, then read: url=X click="input[name=q]" type_text="kinclaw" fill + Enter (React forms): url=X click="input" type_text="hi" press_enter=true wait_for=".reply" screenshot the page: url=X screenshot=true (returns image:// marker) run JS and get result: url=X js="document.title" First-time setup (one-time per machine, ~500MB Chromium download): pip install playwright playwright install chromium
weather
Current conditions plus a three-day forecast (today / tomorrow / day after) for a place, in metric, from wttr.in. No API key. Use this for any weather question — including "明天" and "后天" — before reaching for web search.
tts
Synthesize speech from text and play it through the macOS speakers. Talks to a local LocalKin Service Audio Server (default :8001 / Kokoro). When `record` is running with audio=true, the spoken audio is captured into the recording — use this in place of `shell say` for high-quality multilingual narration in demo videos. Set the TTS_ENDPOINT env var to point at a different server.
translate
Translate text between languages using the system's translate command
summarize
Summarize a file's contents into key bullet points
stt
Transcribe an audio file (wav/mp3/mp4/m4a) into text via a local LocalKin Service Audio Server (default :8000 / SenseVoice). Pair with `record action=start mic=true` to turn a recorded mic track into text. Set STT_ENDPOINT env var to point at a different server.