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.
90%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
Low
Low-risk findings worth noting
This skill is an action router — pick the step that matches the situation and execute only that step. Do not run other steps; do not parallelize.
The precheck (reconcile_sweep.py) plans and applies every Drive: block change — airport, meeting, and lodging drives — every ~30 minutes, diffing against the calendar and touching only its own blocks. It leaves legacy drive-planner / flight-assist blocks alone (you clean those up). Its contract — inputs, apply counts, the fail-closed no-wake payload on error, and the wake gating — lives in reconcile_sweep.py (module docstring, build_sweep_payload) and calendar_apply.apply_plan. Do not restate its logic here.
The cadence sweep activates no step. Its precheck renders the operator notice and returns wake_agent: false. The host delivers data.message verbatim. No step below relays it. The steps below are the operator's replies to that notice. They wake normally.
Run this when the operator replies to a drive notification to skip one — "skip", "skip 1", "skip 2 and 3", "skip the Massage drive". Map each local index to the meeting NAME from the message you sent (index 1 = the first meeting listed); a bare "skip" refers to the single meeting just offered. Never surface an internal id — the operator only ever named the drive by its position or name. For each named meeting, invoke:
python3 /home/node/.claude/skills/tessl__drive-engine/skip_drive.py '{"summary": "<meeting name>"}'The script deletes that meeting's drive blocks and records a skip so no future sweep recreates them. It always prints a JSON result on stdout; read it (do not treat a non-zero exit as "no result"):
{"skipped": true, "meeting": ...} (exit 0) — confirm what you skipped.{"skipped": false, "unmatched": ...} (exit 0) — the name wasn't found; say so.{"skipped": false, "ambiguous": ..., "candidates": [...]} (exit 0) — several same-named meetings; ask the operator which when they mean, then re-invoke.{"skipped": false, "error": ...} (exit 1) — an operational failure (an unauthenticated gateway, transport error); tell the operator the skip couldn't be recorded and to retry. Exit 2 is a usage/JSON error in how it was invoked — fix the call.Reply in one message. Finish here.
Run this when the operator answers a drive-or-fly question the sweep asked about a trip with no flight booked — "drive", "fly", "we're driving", "I'll fly that one". Map the answer to the trip NAME from the message you sent; a bare "drive" or "fly" refers to the single trip just asked about. Never surface an internal key — the operator only ever saw the trip name. Invoke:
python3 /home/node/.claude/skills/tessl__drive-engine/answer_drive_or_fly.py '{"trip": "<trip name>", "answer": "drive"}'answer is drive or fly, nothing else. The script records the answer so it outranks the drive time from then on and the question is not repeated. It always prints a JSON result on stdout; read it (do not treat a non-zero exit as "no result"):
{"recorded": true, "trip": ..., "answer": ...} (exit 0) — confirm what you recorded. On drive, the next sweep adds the drives; on fly, the booking check reports the missing flight.{"recorded": false, "unmatched": ...} (exit 0) — the trip name wasn't found; say so.{"recorded": false, "ambiguous": ..., "candidates": [...]} (exit 0) — several same-named trips; ask the operator which when they mean, then re-invoke.{"recorded": false, "error": ...} (exit 1) — an operational failure; tell the operator the answer couldn't be recorded and to retry. Exit 2 is a usage error in how it was invoked — fix the call.Reply in one message. Finish here.
Run this when a block looks like an engine bug — a drive for a meeting you are travelling away from, a wrong-timezone block, a missing drive for a real trip. Send one message via mcp__nanoclaw__send_message naming the block's summary and leg identity (e.g. meeting_outbound mtg123 or airport_departure BNA-STN-...) and what looks wrong, so it can be fixed in code. Never edit the calendar by hand to compensate. Finish here.
Run this when the owner confirms attendance or the existing travel intake has actual owner/trip/calendar evidence for the conference days and venue labels. Read attendance-contract.md in full. Use its three-choice owner intake for each occurrence and retain actual cited schedule references or owner-confirmed hours, together with a grounded drivable venue address and exact Calendar labels. Invoke its owner intake only for those grounded values. Missing windows stay ordinary meetings; existing legacy rows keep their meaning until explicitly re-recorded. Do not call a provider or change calendar events in this step. Finish here.
Read the complete contract:
skills/drive-engine/ev-planning-contract.mdResolve only owner-confirmed vehicle/driver selectors and charging evidence. Invoke its intake with one complete literal preference object:
python3 /home/node/.claude/skills/tessl__drive-engine/configure_ev.py '<complete schema-1 preference JSON from the contract>'Exit 0 returns {"schema_version":1,"recorded":true}. Exit 1 returns recorded:false and a fixed stderr diagnostic; preserve the existing config and report the hold. Exit 2 has empty stdout for usage errors; correct the invocation. The intake records only planner preferences and preserves other config fields. It calls no provider, vehicle or calendar and grants no enrollment. Report its result. Finish here.
.tessl-plugin
rules
skills
check-travel-bookings
drive-engine
expertflyer
flight-assist
references
nightly-travel-sync
sync-tripit