CtrlK
BlogDocsLog inGet started
Tessl Logo

jbaruch/nanoclaw-flight-assist

Flight notifications via byAir: delay, gate, connection risk, inbound aircraft delay, time-to-leave, arrival logistics. NanoClaw per-chat overlay tile.

69

Quality

87%

Does it follow best practices?

Impact

No eval scenarios have been run

SecuritybySnyk

Passed

No known issues

Overview
Quality
Evals
Security
Files

update-travel-booking-state.pyskills/check-travel-bookings/scripts/

#!/usr/bin/env python3
"""
Snooze / resolve a trip's booking-gap state under
`/workspace/group/travel-booking-state.json`.

Per `coding-policy: script-delegation`, deterministic JSON mutation
lives in scripts. The agent picks the slug + action (the fuzzy-match
from natural-language stays in the LLM's hands per the same rule);
this script handles read, mutate, and atomic write.

State file owner: `check-travel-bookings` (see sibling `state-schema.md`).
Every entry the script writes carries `schema_version: 1`.

Usage:
    update-travel-booking-state.py --slug <slug> --action snooze --until YYYY-MM-DD
    update-travel-booking-state.py --slug <slug> --action resolve

Output: single-line JSON to stdout `{"action": "...", "slug": "...",
"state": {<post-update state map>}}`. Errors go to stderr with non-
zero exit per `rules/file-hygiene.md` I/O conventions.
"""

import argparse
import json
import os
import sys
from datetime import date

STATE_PATH = "/workspace/group/travel-booking-state.json"
SCHEMA_VERSION = 1


def _read_state(path: str) -> dict:
    """Read the current state. Missing / unreadable / non-dict roots
    return an empty state — the file's contract is purely advisory
    snooze data, so absent/corrupt = no snoozes active.
    """
    try:
        with open(path, encoding="utf-8") as f:
            data = json.load(f)
    except (OSError, UnicodeDecodeError, json.JSONDecodeError):
        return {}
    if not isinstance(data, dict):
        return {}
    return data


def _atomic_write(path: str, payload: dict) -> None:
    """Write payload to `path` atomically. Same-dir `.tmp` + `os.replace`
    matches the pattern in `skills/check-travel-bookings/scripts/build-
    travel-db.py` and `skills/flight-assist/state.py::_atomic_write_json`;
    file mode inherits process umask so cross-tile readers (the same
    group-volume readers `build-travel-db.py` accommodates) keep their
    read access."""
    parent = os.path.dirname(path) or "."
    os.makedirs(parent, exist_ok=True)
    tmp = path + ".tmp"
    with open(tmp, "w", encoding="utf-8") as f:
        json.dump(payload, f, indent=2, ensure_ascii=False, sort_keys=True)
    os.replace(tmp, path)


def main(argv: list[str] | None = None) -> int:
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument("--slug", required=True, help="Trip slug, e.g. madrid-2026-06")
    parser.add_argument("--action", required=True, choices=("snooze", "resolve"))
    parser.add_argument("--until", help="ISO date for snooze (required for --action snooze)")
    parser.add_argument(
        "--state-path",
        default=STATE_PATH,
        help="Override state file path. Tests inject a tmp path; the "
        "production default is the module-level constant.",
    )
    args = parser.parse_args(argv)

    if args.action == "snooze":
        if not args.until:
            print(
                "update-travel-booking-state: --until is required for --action snooze",
                file=sys.stderr,
            )
            return 1
        try:
            date.fromisoformat(args.until)
        except ValueError:
            print(
                f"update-travel-booking-state: --until {args.until!r} is not a valid "
                "ISO date (YYYY-MM-DD)",
                file=sys.stderr,
            )
            return 1

    state = _read_state(args.state_path)

    if args.action == "snooze":
        state[args.slug] = {
            "schema_version": SCHEMA_VERSION,
            "snooze_until": args.until,
        }
    else:  # resolve
        state.pop(args.slug, None)

    try:
        _atomic_write(args.state_path, state)
    except OSError as exc:
        # PermissionError, ENOSPC, cross-device EXDEV, etc. surface
        # as a clean stderr diagnostic + non-zero exit instead of an
        # uncaught traceback. The mutation didn't land — `os.replace`
        # is atomic, so partial state is impossible.
        print(
            f"update-travel-booking-state: failed to write {args.state_path}: "
            f"{type(exc).__name__}: {exc}",
            file=sys.stderr,
        )
        return 1

    print(
        json.dumps(
            {"action": args.action, "slug": args.slug, "state": state},
            ensure_ascii=False,
        )
    )
    return 0


if __name__ == "__main__":
    sys.exit(main())

README.md

tile.json