Write professional, persuasive complaint letters to US airlines emphasizing loyalty status, DOT regulations, and airline commitments.
—
—
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
This skill is an action router — pick the step that matches the user's intent and execute only that step. Do not run other steps; do not parallelize. Explicit chains:
0 continues to Step 3
and from there to the step that needed itunconsumable count is
zero. Records the reader cannot consume mean every later command returns a
subset, so it continues to Step 11 and stops insteadThis skill is the owner of the inventory artifact. Shape changes to the store belong to it and to no other skill, and Step 3 is the only place a migration runs. The record shape, the writer/reader contract, and the migration policy are documented beside it:
skills/using-travel-credits/state-schema.mdNever hand-edit inventory.md. Every write goes through the script.
Run the script as python3 <path>. From a consumer repo:
.tessl/plugins/jbaruch/frequent-flyer-advocate/skills/frequent-flyer-advocate/scripts/credits-tracker.pyAlways pass --json. Every subcommand then emits exactly one object on stdout
and nothing else — bootstrap narration and warnings go to stderr. Read fields
off that object; never scrape the prose rendering, which exists for the
interactive human path. A failure emits an object too, carrying an error code,
so branch on that field rather than on stderr text.
Argument contract, filter flags, accepted types, and the full output shape are
the script's — --help on the script and on each subcommand.
~/.claude/travel-credits/ must exist as a directory, or a symlink to one. The
script refuses to run rather than create a second inventory beside the real one.
python3 .tessl/plugins/jbaruch/frequent-flyer-advocate/skills/frequent-flyer-advocate/scripts/credits-tracker.py status --jsonBranch on the exit code, never on the printed wording:
0 — store ready. Invoked directly, report that and finish here. Reached as a
prerequisite, continue to Step 3 and from there to the step that needed it3 — no store. Continue to Step 11, then return here4 — store unusable. Continue to Step 11; do not bootstrap over itReached from Step 11 after an exit 3, with the user's choice already made.
Never pick the choice for them.
python3 .tessl/plugins/jbaruch/frequent-flyer-advocate/skills/frequent-flyer-advocate/scripts/credits-tracker.py link --json --path "<dir>"link adopts an inventory that already exists, including one in cloud storage.
init --default creates a fresh store at the default path. init --path "<dir>"
creates one elsewhere and symlinks it back.
Re-run Step 1 once, and route on the original request. Exit 0 reports the
store ready and finishes when readiness itself is what the user asked about;
otherwise it continues to Step 3 and from there to the step that needed the
store. Anything else continues to Step 11 and does not return — a bootstrap that
did not take is reported, never retried.
Needs a ready store: run Step 1 first if readiness is unknown.
python3 .tessl/plugins/jbaruch/frequent-flyer-advocate/skills/frequent-flyer-advocate/scripts/credits-tracker.py migrate --jsonThis skill owns the record shape, so it upgrades records before consuming them rather than reading them at a version it has moved past. That is why Steps 4-10 run this first — the owner migrates what it is about to read or write.
Other skills write to this store through the same script, and their writes deliberately leave records they did not author untouched. A non-owner must not migrate, so records they wrote at an older version stay as they are until this runs.
deposits_relocated and archived_deposits_relocated list the grants moved into
Compensation History from Active and from the archive respectively. Report the two
lists separately. An archive entry may carry dropped_used_state, the used fields
removed on the way out. Surface those values.
Idempotent: a store already current reports changed: false and is not
rewritten, so running it ahead of every read costs one call.
Then gate on unconsumable. It is the count of records in the store that the
reader cannot consume after migration, measured by asking the parser rather than
by tallying what migration did — so it holds whatever the cause. skipped_newer
and unreadable explain it: records written by a newer version of the plugin,
and records whose version line does not parse. Migration leaves both alone by
design, and every later command omits them.
unconsumable zero — the store is wholly readable. Reached from Steps 4-10,
continue to the step that called it. Invoked directly, report the counts and
finish hereunconsumable above zero — continue to Step 11 and stop. Do not fall through to
the calling step: list, expiring, and check would return a subset while
reading as the whole store, and add would write against an inventory it
cannot fully seeRun Step 3 first — the owner migrates the store before it reads or writes it.
python3 .tessl/plugins/jbaruch/frequent-flyer-advocate/skills/frequent-flyer-advocate/scripts/credits-tracker.py list --jsonLists active credits only. Narrow with --passenger, --airline, or --brand.
Report the credits entries as given, and say which filters were applied — the
payload echoes them, so a narrowed list is never presented as the whole store.
Finish here.
Run Step 3 first — the owner migrates the store before it reads or writes it.
python3 .tessl/plugins/jbaruch/frequent-flyer-advocate/skills/frequent-flyer-advocate/scripts/credits-tracker.py expiring --jsonThe window is the script's default, echoed back as window_days. Pass --days
only when the user names a different horizon.
Run without a passenger filter before a flight search. Surface each expiring
entry with its deadline. The no_expiry entries are a separate list and are not
a deadline — never fold them into the urgent set. Finish here.
Run Step 3 first — the owner migrates the store before it reads or writes it.
python3 .tessl/plugins/jbaruch/frequent-flyer-advocate/skills/frequent-flyer-advocate/scripts/credits-tracker.py check --json --scenario "<itinerary description>" --passengers "Name One,Name Two"--passengers is one comma-separated string, not repeated flags and not
space-separated.
Describe the itinerary in the scenario string — airlines, routing, cabin, hotel
brand. Which credits match, and why, is the script's judgment: quote each
match's reasons rather than re-deriving them. other_passenger_matches holds
credits belonging to family members not on the trip — present it as its own
callout, never merged into matches. Finish here.
Run Step 3 first — the owner migrates the store before it reads or writes it.
python3 .tessl/plugins/jbaruch/frequent-flyer-advocate/skills/frequent-flyer-advocate/scripts/credits-tracker.py history --jsonDeposited compensation: miles and points an airline or hotel granted for a
service failure. Narrow with --airline, --brand, or --passenger.
Report the deposits entries with their issuer, date, and amount. Present them
as history — prior compensation already received, not credits on hand. They do
not appear in Steps 4-6 and have no used state; state-schema.md Versioning
covers why.
Finish here.
Run Step 3 first — the owner migrates the store before it reads or writes it.
--type, --description, and --value are the required flags:
python3 .tessl/plugins/jbaruch/frequent-flyer-advocate/skills/frequent-flyer-advocate/scripts/credits-tracker.py add --json --type <TYPE> --description "..." --value <amount>Everything else is optional — add --passenger, --expiry, --airline,
--brand, --restrictions, --confirmation when the fact is known. Never
block on a missing optional; a transferable gift card has no passenger and a
goodwill deposit has no expiry.
--airline and --brand are independent dimensions, not alternatives. A
co-branded credit carries both, and each matches its own scenario type in
Step 6.
Confirm the type against --help before writing. The type is recorded verbatim
and drives Step 6's matching; never infer one from an abbreviation's plain
reading.
An airline's offer is not a credit until it exists. Add it when the user confirms they hold it, never from an alert or a promise. Finish here.
Run Step 3 first — the owner migrates the store before it reads or writes it.
python3 .tessl/plugins/jbaruch/frequent-flyer-advocate/skills/frequent-flyer-advocate/scripts/credits-tracker.py update --json --id <N> --expiry <date> --confirmation "..."For details that arrive after the credit was logged — an expiry, a voucher number, a case reference, restrictions. Pass only the fields that changed; the rest are left as they are.
Success returns updated (id, type, description, section) and fields_changed.
Report what changed from that, not from what you passed.
Every failure exits non-zero and writes nothing:
no_fields_given — the call named no field. updatable lists what can be set.not_found — no record with that id. Re-run Step 4 for a current one.record_is_archived — the credit was marked used. It keeps the outcome use
recorded and is not editable; report that rather than retrying.invalid_expiry — re-ask for the date in YYYY-MM-DD rather than retrying.expiry_not_valid_for_deposit — the record is a deposit and has no expiry of its
own. Re-run without it.multiline_value — a value carried a line break. fields names which. Record
fields are one line each; re-ask for a single-line value.Finish here.
Run Step 3 first — the owner migrates the store before it reads or writes it.
python3 .tessl/plugins/jbaruch/frequent-flyer-advocate/skills/frequent-flyer-advocate/scripts/credits-tracker.py use --json --id <N> --note "<what it was applied to>"Marking used moves the row to the archive with a used date. The record survives in full — description, case number, issuer, amount — and stays readable as prior-compensation history.
There is no reverse subcommand. Confirm with the user that the credit was
actually applied before writing; a wrong use is undone by hand-editing the
store, which this skill forbids. Finish here.
3, no store at the path. Ask the user which bootstrap they want, then
continue to Step 2 to run it. Never create one unasked: a store may exist
unlinked in cloud storage, and init beside it forks the data.4, store unusable. The path is a plain file, or a dangling symlink
whose target is not mounted. A dangling symlink is never recreated
automatically. Report it and stop — remounting or re-linking the real store
is the user's action, not this skill's.skipped_newer above zero. Records were written by a newer
version of this plugin than the one installed. Report the count, say the
plugin needs updating to read them, and stop. Step 3 does not fix this and
neither does re-running it.unreadable above zero. A record's version line does not
parse as an integer, which means the store was hand-edited or truncated.
Report the count and stop. Repairing it is the user's action — this skill
will not guess at the intended version.unconsumable above zero with both other counts at zero.
Records exist that the reader drops for a reason migration did not classify.
Report the count and stop; do not present the partial inventory as the store.--id on Step 10. The id is not in the active section. The usual cause
is a credit already marked used, which now sits in the archive under that same
id — ids are stable and a record keeps its own through archiving. Report that
and stop; listing the active credits is Step 4, on the user's next ask.--type on Step 8. Read --help for the accepted set. Never guess
from an abbreviation's plain-English reading.Report the reason in one line. Every branch except exit 3 finishes here.