AGENT SKILL

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
所属仓库
LocalKinAI/kinclaw
最近更新
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-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)

# 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

LocalKinAI
LocalKinAI/kinclaw

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

文档处理测试与质量
★ 9
LocalKinAI
LocalKinAI/kinclaw

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.

★ 9
LocalKinAI
LocalKinAI/kinclaw

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.

图像与多媒体Skill与智能体开发
★ 9
LocalKinAI
LocalKinAI/kinclaw

translate

Translate text between languages using the system's translate command

★ 9
LocalKinAI
LocalKinAI/kinclaw

summarize

Summarize a file's contents into key bullet points

资料检索与研究
★ 9
LocalKinAI
LocalKinAI/kinclaw

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.

图像与多媒体
★ 9