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

3.0 KiB

name, description, metadata
name description metadata
sg-transport Singapore transport - bus arrival times, MRT service alerts, carpark availability, traffic. Uses LTA DataMall.
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

# 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.