Files
u-claw/portable/skills/en/sg-transport/SKILL.md
zheng dd8e0f55c3 fix(portable): Gemini keys, Telegram pairing UI, and config durability
Improve first-run and channel setup for non-technical users: detect newer
Gemini key formats, pin Node 22.22.3, add Control Panel Telegram approve
flow, and keep channels/models when config is rewritten. Persist uclaw
wizard state via uclaw-meta.json so restarts skip language/persona prompts.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-18 18:45:03 +08:00

83 lines
3.0 KiB
Markdown

---
name: sg-transport
description: "Singapore transport - bus arrival times, MRT service alerts, carpark availability, traffic. Uses LTA DataMall."
metadata: { "openclaw": { "emoji": "🚇" } }
---
# Singapore Transport
Live bus, MRT and traffic data from LTA DataMall.
## Before anything works: a key
Unlike the weather API, LTA needs one. It is free.
1. Register at https://datamall.lta.gov.sg/content/datamall/en/request-for-api.html
2. The AccountKey arrives by email
3. Store it as `LTA_ACCOUNT_KEY` — never paste it into a message or a filename
Every request sends it as the `AccountKey` header.
## What it does
- **Bus arrivals** — next three buses at a stop, with crowding
- **Train alerts** — live disruptions and advisories
- **Carpark availability** — free lots, useful before driving to a mall
- **Travel times** — expressway estimates
## How to run it
```bash
# Next buses at a stop. Bus stop codes are the 5 digits on the pole.
curl -s -H "AccountKey: $LTA_ACCOUNT_KEY" \
"https://datamall2.mytransport.sg/ltaodataservice/v3/BusArrival?BusStopCode=83139" \
| python3 -c "
import json,sys,datetime
d=json.load(sys.stdin)
now=datetime.datetime.now(datetime.timezone.utc)
for svc in d.get('Services',[]):
mins=[]
for k in ('NextBus','NextBus2','NextBus3'):
t=svc.get(k,{}).get('EstimatedArrival')
if not t: continue
eta=datetime.datetime.fromisoformat(t)
mins.append(max(0,round((eta-now).total_seconds()/60)))
load={'SEA':'seats','SDA':'standing','LSD':'packed'}.get(svc['NextBus'].get('Load'),'')
print(f\"Bus {svc['ServiceNo']:5} {', '.join(str(m)+' min' for m in mins) or 'no data':30} {load}\")
"
# Train disruptions right now
curl -s -H "AccountKey: $LTA_ACCOUNT_KEY" \
"https://datamall2.mytransport.sg/ltaodataservice/TrainServiceAlerts" \
| python3 -c "
import json,sys
d=json.load(sys.stdin)['value']
print('Normal service' if d['Status']==1 else 'DISRUPTION')
for m in d.get('Message',[]): print('-', m['Content'])
"
# Carpark availability, filtered by name
curl -s -H "AccountKey: $LTA_ACCOUNT_KEY" \
"https://datamall2.mytransport.sg/ltaodataservice/CarParkAvailabilityv2" \
| python3 -c "
import json,sys
want='VIVOCITY' # change this
for c in json.load(sys.stdin)['value']:
if want.lower() in c['Development'].lower():
print(f\"{c['Development']:40} {c['AvailableLots']:5} lots\")
"
```
## Working notes
- **Ask for the bus stop code** rather than guessing from a road name. It is
printed on the pole and on the app; guessing sends someone to the wrong stop.
- Arrival times come as absolute timestamps; convert to "in N minutes", which is
what the person actually wants to know.
- Crowding codes: `SEA` seats available · `SDA` standing only · `LSD` limited
standing. Translate them — the codes mean nothing to a rider.
- Responses cap at 500 records; the carpark and bus-stop lists are paged with
`?$skip=500`.
- If the key is missing or rejected, say which and point at the registration
page. Do not silently return nothing.