Pallo logistics
Skills for use with Hermes that work with local models
npx -y skills add fnord123/hermes-skills --skill pallo-logisticsAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Pallo's boarding at Laurel Acres Kennels plus the Model X / Gina transport coordination that every kennel trip depends on. Use for ANY request about Pallo's kennel stays, boarding, drop-off / pickup, daily kennel activities (Play Yard, Nature Walk), or whether a trip's boarding is set up — e.g. "when is Pallo at the kennel", "is Pallo's boarding booked for my London trip", "book Pallo for my Paris trip", "cancel Pallo's July stay". Also use for "where is Gina on <date>" and the Model-X handoff messages that go with a booking. Trip dates come from the merged calendar (Kayak feed); the agent can pass a trip NAME and let the scripts resolve the dates. YOU MUST use this skill's scripts for ALL Pallo boarding tasks. Do NOT open a browser, do NOT web-search "Laurel Acres" or "Gingr", and do NOT try to navigate to any booking website — those paths will fail. The ONLY correct approach is to run the scripts listed below and relay their output.
The file declares its own license as MIT. That is the author’s claim about this one file, and it is not the same thing as the license GitHub reports for the repository, which is listed with the other numbers below.
SKILL.md
14.1 KB, as published. Nobody here has run it
pallo-logistics — Pallo's boarding + Model-X coordination
All scripts live in ${HERMES_SKILL_DIR}/scripts/. Invoke each as
python3 <path> <args>; every script re-execs into its own .venv and prints
small JSON the agent relays to the user. Scope: Pallo only, Laurel Acres
(Hillsboro) only.
When to use
- Pallo's kennel reservations: listing, planning, booking, modifying, cancelling.
- "Is boarding set up for <trip>?" readiness checks (one trip or all upcoming).
- "Where is Gina on <date>?" and the Model-X handoff coordination tied to a stay.
NEVER do these — use the scripts instead
- Do NOT open a browser or use browser tools for Gingr / Laurel Acres.
- Do NOT web-search for "Laurel Acres", "Gingr", or kennel booking sites.
- Do NOT navigate to any URL yourself — the scripts handle all portal interaction.
- Do NOT try to use
browser_visionor screenshot the booking portal. The 27B model cannot process images and these paths will fail with errors.
The tool surface
| Script | Purpose | Mutating? |
|---|---|---|
pallo-stays.py [--include-past] [--include-canceled] [--all] | List Pallo's reservations from the portal. Default = upcoming + in-progress. | No |
pallo-trip-plan.py --trip-name <name> or --trip-start <ISO> --trip-end <ISO> | Build the boarding window + activity slate + proposed Gina messages for a trip. Adds one day of buffer each end (drop-off = day before trip, pickup = day after). Returns an opaque plan_json. | No |
pallo-trip-plan.py --drop-date <ISO> --pickup-date <ISO> [--drop-time <t>] [--pickup-time <t>] | Same as above but uses the given dates directly as boarding dates — no buffer added. Use this when the user specifies explicit drop-off and pickup dates (e.g. "drop Pallo on Sep 30, pick up Oct 4") rather than travel dates. Pass --drop-time/--pickup-time when the user gives clock times; they're carried in the plan_json so the booking uses them. | No |
pallo-trip-status.py [--trip-name <name>] [--trip-start/--trip-end] [--horizon-days N] | Is a trip's boarding set up? No trip identifier = sweep ALL upcoming trips. | No |
gina-where.py --date <ISO> [--who Gina|Sky] | Where is Gina (residency) on a date, from the merged 2Houses feed. | No |
pallo-book-trip.py --plan '<plan_json>' --confirm-drop-date <ISO> --confirm-pickup-date <ISO> [--drop-time <t>] [--pickup-time <t>] [--dry-run] [--simple-slate] | Make the boarding reservation + activity slate, then send the plan's Gina messages. Drop/pickup times come from the plan; --drop-time/--pickup-time here override them. SLOW: drives the whole Playwright wizard — 1–4 minutes. ALWAYS call the terminal tool with timeout=600 (or run it in the background); the default 60–120s timeout WILL kill it mid-booking. --simple-slate is much faster (~1 min) and reliable; the full slate is slower. | Yes — real, paid |
pallo-cancel.py --stay-id <id> --confirm-drop-date <ISO> --confirm-pickup-date <ISO> [--dry-run] | Cancel a stay (located by its dates; re-verifies the displayed dates before cancelling). | Yes |
pallo-modify-stay.py --stay-id <id> --new-drop-date <ISO> --new-pickup-date <ISO> [--simple-slate] [--dry-run] | Change a stay's dates/activities. The portal has no in-place edit, so this books the new stay, then cancels the old. | Yes |
pallo-trip-prep.py --trip-name <name> [--commit --confirm-drop-date <ISO> --confirm-pickup-date <ISO>] | One-call trip prep: readiness → plan → (with --commit) book + notify Gina. No --commit = read-only preview. | Yes (with --commit) |
gina-notify.py --topic <t> --body <b> [--handoff-date <ISO>] [--trip-name <n>] [--dry-run] | Post a Model-X coordination message to the shared Discord channel (mentions Gina + the user). Normally fired automatically by a booking; direct call is an escape hatch. | Yes — sends a message |
gina-pending.py [--resolve <id>] | List outstanding Gina-coordination asks; --resolve clears one. | Read / small write |
pallo-calendar-invite.py --plan '<plan_json>' [--events pickup,dropoff] [--to <emails>] [--dry-run] or --drop-date/--drop-time/--pickup-date/--pickup-time | Email Google-Calendar invites (iMIP .ics) for the drop-off and/or pickup to you + Gina via AgentMail — they land in Google Calendar with RSVP. Fired automatically after a booking; call directly to (re)send for an existing stay. --dry-run prints the .ics without sending. | Yes — sends email invites |
gingr-login.py [--show-head] | Capture / refresh the saved portal session. Run only when a script reports session_expired or not_logged_in. | No (writes session file) |
Safety — read-only by default
Read-only scripts are safe any time. Every mutating action — pallo-book-trip.py,
pallo-cancel.py, pallo-modify-stay.py, pallo-trip-prep.py --commit, and
gina-notify.py — requires an explicit "yes" from the user in the same turn
when run for real (without --dry-run / without --commit). First echo what will
change (dates, activity slate, estimated price, or which stay gets cancelled), get
the clean yes, THEN run the real action.
Every mutating booking/cancel script takes --confirm-drop-date /
--confirm-pickup-date (or --stay-id + confirms) — pass the exact dates the
user agreed to. They refuse (confirm_mismatch) on a mismatch; pallo-book-trip.py
also refuses (conflict) if Pallo already has an overlapping reservation.
Always preview first:
pallo-book-trip.py --dry-runfills the whole request and stops at the Review screen (estimated total + screenshot) without submitting.pallo-cancel.py --dry-runopens the booking and verifies its dates without cancelling.pallo-trip-prep.py(no--commit) returns the plan-of-plans (window + slate + the Gina messages it would send).
Opaque handles
stay_id (from pallo-stays.py) and plan_json (from pallo-trip-plan.py)
are black boxes. Pass them through verbatim; never construct, edit, or decode
them.
Daily activity slate
Every booking gets, per §5: drop-off day = 1 Play Yard; each full day = 2 Play
Yard + 1 Nature Walk; pickup day = 1 Nature Walk. pallo-book-trip.py applies
this automatically via three frequency rules — the second daily Play Yard is
just the Play Yard activity added again at a DIFFERENT time (07:30 AM + 03:30 PM),
which is how Gingr allows two same-day sessions. --simple-slate drops the
second daily Play Yard (1 Play Yard/day; faster, reasonable for long stays).
The result reports:
activity_rules: how many frequency rules attached per activity — the real slate signal. Play Yard should be 2 (both times) unless--simple-slate; Nature Walk 1.estimate_days: the Review estimate's per-activity count. Note this is UNIQUE DAYS, not sessions, so it shows e.g. Play Yard "5" for a 5-day stay even with both rules attached — that's expected, not a shortfall.slate_warning: only appears if a rule failed to attach (e.g. Play Yard shows 1 rule instead of 2). Relay it to the user and offer to re-run; a clean booking has noslate_warning.
Gina coordination
When the user's request involves a stay whose drop-off or pickup falls on a day
Gina is at her mom's, the Model X needs a handoff. pallo-trip-plan.py proposes
those messages; pallo-book-trip.py sends them after a successful booking.
When you see a message FROM Gina in the coordination channel, call
gina-pending.py FIRST to get the outstanding asks her reply is answering;
after you've acted on her answer, call gina-pending.py --resolve <id>.
Calendar invites (drop-off + pickup)
After a real booking, pallo-book-trip.py automatically emails Google-Calendar
invites for BOTH handoffs (drop-off and pickup) to you and Gina via AgentMail —
they arrive as normal calendar invites and land on both Google Calendars with
RSVP + reminders (1 day and 2 hours before). The booking result includes a
calendar_invites field (ok / partial / an error status); a send failure
never undoes the booking. Pass --no-calendar to skip.
To (re)send for an EXISTING stay, or send just one handoff, call
pallo-calendar-invite.py directly (e.g. --events pickup). Re-sending uses a
stable per-stay UID, so it UPDATES the same calendar event rather than
duplicating it. Attendee emails come from USER_EMAIL / GINA_EMAIL in
secrets.env (override with --to); the AgentMail key/inbox are reused from
Hermes' config. If a stay's time changes, bump --sequence so the update
supersedes the prior invite.
Trip dates vs. boarding dates
--trip-start / --trip-end are TRAVEL dates. The script automatically
adds a buffer: drop-off = day before trip starts, pickup = day after trip ends.
Use these when the user says "I'm flying to London July 12–14" or gives you
trip/travel dates.
--drop-date / --pickup-date are the BOARDING dates themselves — no
buffer is added. Use these when the user says things like "drop Pallo off Sep 30,
pick up Oct 4" or gives you specific kennel times ("Sep 30 3pm to Oct 4 11am").
If in doubt about which kind of dates the user means, ask — getting this wrong
adds an unwanted extra day on each end.
Drop-off / pickup times
When the user gives clock times ("3pm", "11am", "Sep 30 3pm to Oct 4 11am"),
pass them as --drop-time / --pickup-time. The scripts accept loose forms —
3pm, 3:00 PM, 15:00 all work; you do NOT need to reformat them. Without
these flags the booking defaults to 08:00 AM drop / 09:00 AM pickup, so if
the user stated times and you omit the flags, the reservation gets the wrong
times. Set them on pallo-trip-plan.py (they travel in the plan_json) or
directly on pallo-book-trip.py. A dates_invalid status with a time reason
means the clock value couldn't be parsed — re-read what the user said.
Example for "book Pallo Sep 30 3pm to Oct 4 11am":
pallo-trip-plan.py --drop-date 2026-09-30 --pickup-date 2026-10-04 \
--drop-time 3pm --pickup-time 11am
# then, after the user confirms:
pallo-book-trip.py --plan '<plan_json>' \
--confirm-drop-date 2026-09-30 --confirm-pickup-date 2026-10-04
Session expiry
Run gingr-login.py only when a script's JSON explicitly returns
session_expired or not_logged_in. Do NOT run it because a booking timed out,
errored, or "felt stuck" — a timeout means the booking is slow (re-run with
timeout=600), not that the session died. Running login needlessly wastes a
minute and can trip the portal's rate limiting.
If gingr-login.py returns login_failed, stop and tell the user — do not
retry in a loop. The Gingr portal is a React-Native-Web app whose login form
resists headless automation (the LOGIN press fires no auth request), so an
automated refresh may simply not be possible from here; the user likely needs to
refresh the saved session another way or verify the portal credentials. The
existing saved session usually keeps working for reads and bookings until it
truly expires, so a login_failed does not necessarily block the current task.
Files this skill must NEVER read
| Path | Reason |
|---|---|
~/.config/pallo-logistics/secrets.env | Portal password + Discord webhook. Scripts read it; the agent must not. |
~/.config/pallo-logistics/gingr-storage-state.json | Saved login session (auth cookies). |
~/.config/pallo-logistics/pending-coordination.json | Read it via gina-pending.py, not directly. |
Common flows
- "When is Pallo next at the kennel?" →
pallo-stays.py. - "Is Pallo's boarding set for my London trip?" →
pallo-trip-status.py --trip-name London. - "Is boarding arranged for all my trips?" →
pallo-trip-status.py(no args, sweep), then offer to book each gap one at a time. - "What's the plan for Pallo for Paris?" →
pallo-trip-plan.py --trip-name Paris; relay the window + slate + any Gina messages. - "Yes, book it." → after the user confirms the dates/price you showed (from a
--dry-run):pallo-book-trip.py --plan '<plan_json>' --confirm-drop-date <ISO> --confirm-pickup-date <ISO>. Relay the confirmation and whether the Gina messages went out. - "Set everything up for my Paris trip." →
pallo-trip-prep.py --trip-name Paris(preview); show the window + slate + Gina messages; on "yes":pallo-trip-prep.py --trip-name Paris --commit --confirm-drop-date <ISO> --confirm-pickup-date <ISO>. - "Cancel Pallo's July stay." →
pallo-stays.pyto get thestay_id; confirm which one with the user;pallo-cancel.py --stay-id <id> --confirm-drop-date <ISO> --confirm-pickup-date <ISO> --dry-run; on "yes" re-run without--dry-run. - "Move Pallo's stay to the 22nd" / "give him the simple activity slate" →
pallo-stays.pyfor thestay_id;pallo-modify-stay.py --stay-id <id> --new-drop-date <ISO> --new-pickup-date <ISO> [--simple-slate] --dry-run; on "yes" re-run without--dry-run. (For an activity-only change, pass the SAME dates.) It books the new stay then cancels the old; if it returnsmodified_old_not_cancelled, tell the user to cancel the old stay manually. - "Where's Gina on the 21st?" →
gina-where.py --date 2026-07-21.