{
  "name": "location",
  "title": "Location (get_location)",
  "description": "Get user location: GPS, IP geolocation, reverse-geocode coords, and stored history - all via get_location",
  "guid": "sk_plat_loc",
  "category": "Agent Tools",
  "requiredTools": [
    "get_location"
  ],
  "content": "# Location (get_location)\n\nThe `get_location` tool returns geographic data. All actions return the same unified shape so you never have to case-split on the source:\n\n```\n{\n  source: 'gps' | 'ip' | 'coords' | 'latest' | 'history',\n  city: string | null,\n  region: string | null,\n  country: string | null,\n  timezone: string | null,\n  lat: number | null,\n  lon: number | null,\n  ip: string | null,       // echoed back when you pass an IP, or the caller's IP on 'me'/'gps'\n  accuracy: number | null, // meters; populated only for GPS\n}\n```\n\nFields the action can't populate are `null`. Pick what you need; nothing is hidden behind the action name.\n\n## Actions\n\n| Action | When to use |\n|---|---|\n| `me` (default) | \"Where am I?\" - tries GPS if the user's browser advertised the capability, otherwise falls back to IP geo. The right default 95% of the time. |\n| `ip` | Look up any IP. If `ip` param is omitted, uses the caller's IP. |\n| `coords` | Reverse-geocode an arbitrary `lat`+`lng` into city/region/country. |\n| `gps` | Force a live GPS round-trip to the user's browser. Fails clearly if no socket session or permission denied. |\n| `latest` | Most recent stored location for this user (from the `user_locations` table). |\n| `history` | Past stored locations - paginate with `count` (default 10, max 1000) or `hours`. |\n\n## Examples\n\n- \"Where is the user right now?\" → `action: 'me'`\n- \"What city is IP 8.8.8.8 in?\" → `action: 'ip', ip: '8.8.8.8'`\n- \"Reverse-geocode 37.77, -122.41\" → `action: 'coords', lat: 37.77, lng: -122.41`\n- \"Force a fresh GPS fix for navigation\" → `action: 'gps', reason: 'to show nearby coffee shops'`\n- \"When was the user last in a different city?\" → `action: 'history', hours: 168`\n\n## GPS specifics\n\nGPS only works when:\n1. The user is in a **live browser session** (socket-connected - not REST-only, not a workflow).\n2. Their browser has the Geolocation API (`navigator.geolocation`) - essentially all modern browsers.\n3. They haven't previously denied permission (the client advertises `gpsPermission` on connect via the Permissions API).\n\nIf any of those fails for `action: 'me'`, the tool silently falls back to IP geo so you still get an answer. For `action: 'gps'`, you get a clear error instead of a fallback - use it when GPS specifically is required (navigation, high-accuracy fitness tracking, etc.).\n\nAlways pass a `reason` when calling GPS-capable actions - some clients show it to the user in the permission prompt.\n\n## IP geo specifics\n\nIP lookup uses the platform's local Geo DB (no network latency, no rate limits). Private IPs (`10.*`, `192.168.*`, `172.16-31.*`, `127.0.0.1`) return an error - there's no useful answer for those.\n\n## User-side usage (outside the agent)\n\nEnd users query their own location directly without going through the agent:\n\n- **Web CLI**: `/location` (me), `/location 8.8.8.8` (IP), `/location 37.77 -122.41` (reverse geocode), `/location latest`, `/location history [count]`\n- **Local CLI**: `gipity location [...]` - same auto-detect pattern, `--json` for structured output\n- **REST (user-scoped)**: `GET /location/me`, `POST /location/ip`, `POST /location/coords`, `GET /location/latest`, `GET /location/history` - all return the unified shape. Auth: user JWT (`Authorization: Bearer <token>`).\n\n---\n\n## Calling location from a deployed app\n\nEverything above is the agent-side `get_location` tool. A **deployed app** resolves a user's location through the app-scoped HTTP service instead - see [app-location](app-location.md) for the `/services/location/*` endpoints, the frontend and serverless-function patterns, and the X-Forwarded-For gotcha.\n"
}
