Travel assistant for NanoClaw: byAir flight notifications (delay, gate, connection risk, inbound aircraft delay, time-to-leave, arrival logistics), traffic-aware drive planning for in-person meetings (auto drive blocks + leave-by traffic rechecks), travel-booking gap checks, and nightly TripIt sync. Per-chat overlay plugin.
—
—
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
View guide
High
Do not use without reviewing
Reference for the data.events payload the precheck script emits on wake_agent: true. Each event is {"flight_id": int, "event": {...}}. Consumed by SKILL.md Step 3 to compose user-facing notifications.
wake_rules.py)cancelled{"reason": "cancelled"}Fires on computed_status transition into cancelled, OR on first cycle if the flight is already cancelled.
diverted{"reason": "diverted"}Same trigger as cancelled but for diverted status.
gate_change{"reason": "gate_change", "side": "dep" | "arr", "from": "B25", "to": "B7"}to may be null (gate removed). from is always non-null (first publication of a gate doesn't fire).
delay{"reason": "delay", "delay_minutes": 22, "new_dep_time": "2026-05-17T13:00:00-07:00"}
{"reason": "delay", "delay_minutes": 31, "new_dep_time": "2026-06-04T21:11:00+02:00", "schedule_slip": true}delay_minutes is the size of the delay in minutes (positive = later than before, negative = moved earlier); new_dep_time is the departure to show. The optional schedule_slip: true flag marks a delay already present on the first poll; compose it identically to a plain delay. Firing thresholds and the slip-vs-schedule rule live in wake_rules.py (detect_wake_events; constants in the module docstring).
inbound_delay_predicted{"reason": "inbound_delay_predicted", "delay_minutes": 35, "predicted_time": "2026-05-17T13:00:00-07:00"}delay_minutes is the predicted inbound-aircraft delay; predicted_time is the new estimated departure. Firing threshold and dedupe live in wake_rules.py (detect_wake_events).
inbound_delay_retracted{"reason": "inbound_delay_retracted", "prev_delay_minutes": 95, "new_delay_minutes": null}prev_delay_minutes is the delay last surfaced to the user; new_delay_minutes is the current prediction (null when retracted entirely). Send the all-clear even if an earlier surface escalated to "rebook now" — without it, silence reads as still-delayed. Firing conditions live in wake_rules.py (detect_wake_events).
boarding_started{"reason": "boarding_started"}Fires on transition into actual boarding, gated on the real-boarding signal rather than the computed_status label alone — byAir labels the phase boarding before boarding starts. First-cycle "already boarding" does not fire. The phase_markers.boarding_fired flag is reserved for a future boarding-prep notification and does not gate this event in v0.1. Firing conditions and the real-boarding predicate live in wake_rules.py (detect_wake_events, is_real_boarding).
carousel_revealed{"reason": "carousel_revealed", "baggage": "CLM1"}Fires when baggage transitions null → populated.
connection_risk.py)connection_at_risk{
"reason": "connection_at_risk",
"leg1_code": "AA100",
"leg2_code": "AA200",
"leg1_flight_id": 12345,
"connecting_airport_id": 28,
"transfer_minutes_remaining": 32,
"missed_connection": false,
"scheduled_layover_minutes": 70,
"min_transfer_minutes": 45,
"projected_leg1_arr_time": "2026-05-17T13:28:00-07:00",
"scheduled_leg2_dep_time": "2026-05-17T14:00:00-07:00"
}Fires when the projected transfer window between leg-1 arrival and leg-2 departure falls below min_transfer_minutes (configurable via config.json:min_transfer_minutes, default 45). The event is keyed to leg-2's flight_id (the at-risk downstream leg) so the once-per-flight phase marker (connection_at_risk_fired) survives leg-1 landing.
missed_connection is true when transfer_minutes_remaining is at or below zero — i.e., leg-1 is projected to arrive at or after leg-2 has already departed, so the connection is structurally lost. SKILL.md branches on the flag to render a "rebook required" message instead of a sub-zero "boards in N min" string.
Suppression rules:
landed (outcome observable), cancelled, or diverted (a more specific alert path fires)connection_at_risk_fired is already true on the leg-2 record (once-per-flight)phase_markers.py)day_before{"reason": "day_before", "scheduled_dep_time": "2026-05-18T17:00:00+00:00", "hours_until_dep": 24}Fires once per flight at T - 24h.
time_to_leave{"reason": "time_to_leave", "leave_by": "2026-05-18T16:15:00+00:00", "travel_time_minutes": 30, "scheduled_dep_time": "2026-05-18T17:00:00+00:00"}Fires when now + travel_time + 15min buffer ≥ scheduled_dep_time. travel_time_minutes is the in-traffic estimate from Google Maps Distance Matrix. Suppressed once the snapshot shows the flight really boarding or already gone — departed, en_route, landed, cancelled, or diverted (phase_markers.is_boarding_or_gone) — the leave-by alert is moot by then (#102).
gate_assignment{"reason": "gate_assignment", "dep_gate": "E16", "dep_terminal": "2"}One-time departure gate + terminal readout, fired the first cycle a gate exists inside the pre-boarding window. The window bound lives in phase_markers.check_gate_assignment (GATE_ASSIGNMENT_WINDOW_LEAD_MINUTES). dep_terminal is null when byAir hasn't published a terminal. Gate info before the window is recorded to state silently; gate moves after this readout surface as gate_change (#103).
arrival_logistics{"reason": "arrival_logistics", "scheduled_arr_time": "2026-05-18T20:00:00+00:00", "minutes_until_arr": 15}Fires once per flight at T-arr − 15min.
removed_upstream{"reason": "removed_upstream"}Fires when byAir returns 404 for a previously-tracked flight. The agent should surface this to the user (don't auto-delete state — let the user confirm).
sync_tripit.py)The daily sync script also emits via the same data.events shape so SKILL.md's composition table has a single contract.
tracked_flight_added{
"reason": "tracked_flight_added",
"code": "XX123",
"scheduled_dep_time": "2026-05-18T17:00:00+00:00",
"scheduled_arr_time": "2026-05-18T20:00:00+00:00"
}Fires when a flight appeared in byair_list_trips that wasn't on the active-flights index. The sync script writes the initial state record; the event carries the flight code + scheduled times so the agent has notification context without re-reading state.
tracked_flight_removed{
"reason": "tracked_flight_removed",
"code": "XX123",
"scheduled_dep_time": "2026-05-18T17:00:00+00:00",
"scheduled_arr_time": "2026-05-18T20:00:00+00:00"
}Fires when a previously-tracked flight is no longer in byair_list_trips (e.g., the trip expired or the user untracked it upstream). The sync script deletes the per-flight state file — the event payload captures the prior-state metadata BEFORE the delete so the agent's composition template has code to render. Reading state by flight_id after this event arrives returns None (state already deleted).
flight_id arrive in one cycle (then merge per the ordering rule in SKILL.md Step 3).reason. Render <missing> or skip the substring rather than inventing a value..tessl-plugin
skills
check-travel-bookings
drive-engine
flight-assist
references
nightly-travel-sync
sync-tripit
travel-core