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.
72
91%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
View guide
Low
Low-risk findings worth noting
"""Read the operator's current IANA zone through the core `current-tz` reader.
`jbaruch/nanoclaw-core` hosts the one reader of the host's `tz_state` store,
`skills/current-tz/scripts/read-current-tz.py`, installed in every tier at the
runtime mount named below. This module is how a Python precheck in this plugin
asks it: spawn the script, parse its single-line JSON, hand back the zone. No
script here opens the store itself — the reader's no-guess contract (the zone
the host resolved from the operator's live location, or nothing; `home_tz`
never a fallback) is what keeps every surface agreeing on where the operator is.
Reader contract, restated only as far as this caller reads it (the docstring of
`read-current-tz.py` is authoritative): stdout is `{"available": true, "tz",
"local_now", "local_date"}` or the all-null `available: false` shape; exit 0
when the store was read, 1 when it could not be read (the unavailable shape is
still on stdout), 2 on CLI misuse.
Every unavailable outcome returns None with its own stderr line, so a caller
degrades to an explicit date or the event's own zone instead of the whole
cycle going dark: reader not installed, reader failed to run or timed out,
store unreadable, `available: false`, output this caller cannot parse. The
reader's own stderr is relayed verbatim so its diagnosis is not lost.
Consumers: flight-assist's `precheck` (the `day_before` day label, #300) and
the drive engine's `reconcile_sweep` (the meeting-drive display zone, #301).
stdlib-only per `coding-policy: dependency-management`.
Public API:
from operator_tz import CORE_READER_PATH, OperatorTz, read_operator_tz
zone = read_operator_tz() # OperatorTz | None
zone = read_operator_tz(now=some_instant) # local fields at that instant
zone.tz, zone.local_now, zone.local_date
"""
from __future__ import annotations
import json
import subprocess
import sys
from collections.abc import Callable
from dataclasses import dataclass
from datetime import date, datetime
from pathlib import Path
from zoneinfo import ZoneInfo, ZoneInfoNotFoundError
# The NanoClaw runtime mounts every `tessl__*` skill under this prefix; core's
# `current-tz` skill is installed in every tier (nanoclaw-travel 0.2.130).
CORE_READER_PATH = Path("/home/node/.claude/skills/tessl__current-tz/scripts/read-current-tz.py")
# The reader does one local sqlite read; anything slower is a hung process, and
# both consumers run under a host precheck kill budget.
READER_TIMEOUT_SECONDS = 5.0
RunFn = Callable[..., subprocess.CompletedProcess]
@dataclass(frozen=True)
class OperatorTz:
"""The reader's `available: true` payload.
`tz` is the IANA name; `local_now` / `local_date` are the queried instant
expressed in it (`YYYY-MM-DDTHH:MM:SS±HH:MM` / `YYYY-MM-DD`), so a caller
that only needs the operator's date never converts by hand.
"""
tz: str
local_now: str
local_date: str
# What to do when the reader's output does not match the contract this module
# reads: the two plugins ship through separate pipelines, so the fix is to bring
# them back in step rather than to retry.
_CONTRACT_HINT = (
"check that the installed jbaruch/nanoclaw-core current-tz reader matches the "
"contract in travel-core/operator_tz.py, and update whichever side drifted"
)
def _unavailable(reason: str) -> None:
print(f"operator_tz: {reason}; operator zone unavailable", file=sys.stderr)
return None
def _relay(stream: str | bytes | None) -> None:
"""Pass the reader's own stderr through, newline-terminated."""
if not stream:
return
text = stream.decode("utf-8", errors="replace") if isinstance(stream, bytes) else stream
sys.stderr.write(text if text.endswith("\n") else text + "\n")
def _valid_fields(tz: str, local_now: str, local_date: str) -> bool:
"""Whether the payload's three strings are what they claim to be: a zone
`ZoneInfo` resolves, a timezone-aware ISO instant, and an ISO date."""
try:
ZoneInfo(tz)
parsed_now = datetime.fromisoformat(local_now)
date.fromisoformat(local_date)
except (ZoneInfoNotFoundError, ValueError):
return False
return parsed_now.tzinfo is not None and parsed_now.utcoffset() is not None
def read_operator_tz(
*,
now: datetime | None = None,
reader: Path = CORE_READER_PATH,
run: RunFn = subprocess.run,
) -> OperatorTz | None:
"""Return the operator's current zone, or None when no usable zone came back.
`now` pins the instant `local_now` / `local_date` describe (passed to the
reader as `--now`); it must be timezone-aware, matching the reader's own
rule. `reader` and `run` are injection points for tests; production callers
take the defaults.
"""
if now is not None and (now.tzinfo is None or now.utcoffset() is None):
# `tzinfo` alone is not awareness: a tzinfo whose `utcoffset()` is None
# still renders a naive `--now`, which the reader rejects with exit 2.
# Same test as `drive-engine/flight_identity._as_utc`.
raise ValueError("read_operator_tz: `now` must be timezone-aware")
if not reader.is_file():
return _unavailable(
f"core reader not installed at {reader} — install jbaruch/nanoclaw-core "
"(skills/current-tz) in this tier"
)
argv = [sys.executable, str(reader)]
if now is not None:
argv += ["--now", now.isoformat()]
try:
proc = run(
argv,
capture_output=True,
text=True,
timeout=READER_TIMEOUT_SECONDS,
check=False,
)
except subprocess.TimeoutExpired as exc:
# Whatever the reader managed to say before the kill is the best clue.
_relay(exc.stderr)
return _unavailable(
f"core reader did not answer within {READER_TIMEOUT_SECONDS:.0f}s — check that "
"the host store mount (/workspace/store) is present and not locked, then let "
"the next cycle retry"
)
except OSError as exc:
return _unavailable(
f"core reader could not be started ({exc}) — check that {reader} is readable "
f"and that {sys.executable} can run it"
)
_relay(proc.stderr)
if proc.returncode not in (0, 1):
# Exit 2 is CLI misuse — a contract break between two plugins we both
# own, not an operational miss. Still a degrade, never a dark cycle.
return _unavailable(
f"core reader exited {proc.returncode} for {argv[2:]} — {_CONTRACT_HINT}"
)
try:
payload = json.loads(proc.stdout)
except json.JSONDecodeError:
return _unavailable(
f"core reader printed non-JSON stdout: {proc.stdout.strip()[:120]!r} — {_CONTRACT_HINT}"
)
if not isinstance(payload, dict):
return _unavailable(f"core reader stdout is not a JSON object — {_CONTRACT_HINT}")
if payload.get("available") is not True:
# The reader normally says why on stderr (no row, empty zone,
# unsupported schema, unreadable store) and that line was relayed
# above. This module's own line still fires, so the miss is visible
# even when the reader stays quiet.
return _unavailable(f"core reader reported available: false (exit {proc.returncode})")
tz, local_now, local_date = (payload.get(k) for k in ("tz", "local_now", "local_date"))
if not all(isinstance(v, str) and v for v in (tz, local_now, local_date)):
return _unavailable(
f"core reader payload is missing tz/local fields: {payload!r} — {_CONTRACT_HINT}"
)
assert isinstance(tz, str) and isinstance(local_now, str) and isinstance(local_date, str)
if not _valid_fields(tz, local_now, local_date):
return _unavailable(
f"core reader payload does not parse (tz={tz!r}, local_now={local_now!r}, "
f"local_date={local_date!r}) — {_CONTRACT_HINT}"
)
return OperatorTz(tz=tz, local_now=local_now, local_date=local_date).tessl-plugin
skills
check-travel-bookings
drive-engine
expertflyer
flight-assist
references
nightly-travel-sync
sync-tripit