# Vehicle History

> A one-paste skill for your AI: it keeps a private log of your Tesla's battery, distance and charging on your side, and builds a dashboard from it. mytesla.io stores none of it.

<!-- Skill file. Tool names/params must match src/mcp/agent.ts exactly.
     If a tool is renamed or its parameters change, update this file. The
     column table below is checked against the status payload shape in
     test/skill_docs.test.ts. -->

**If you're an AI assistant reading this:** the user wants a private history
of their Tesla, kept on their side, and a dashboard you build from it. Follow
the steps below using the mytesla.io MCP tools (`https://mcp.mytesla.io/mcp`).

## What this does

mytesla.io answers every question with a live reading and deliberately keeps
no history of the car. This skill puts the history where it belongs: with you
and the user. You take small readings of the car, append them to a log you
keep for the user, and turn the log into answers and a dashboard:

- **Battery health:** the car's rated range at a full charge, over time.
- **Distance:** by day, week and month.
- **Charging:** the sessions you saw, the energy added, and the cost at the
  user's own price.
- **Efficiency and parked drain:** battery used per distance driven, and
  battery lost while parked.
- **Tires and software:** pressures over time, and when each update arrived.

Because you keep the log, what gets recorded and how the dashboard looks is
up to you and the user, not a fixed product. Ask what they care about and
record for that.

History starts the day you set this up. None of the mytesla.io tools can read
the car's past, so there is nothing to backfill. Say so up front.

## Where the data lives, and who sees it

mytesla.io never receives, copies or stores this history. It sees only what it
sees about any status read: that the read happened and what it cost. The log
is a file, note or sheet that you keep wherever the user's AI host lets you
keep one, and the dashboard is built from it on their side.

Say that plainly, and say the rest of it too: the company behind your
assistant sees these readings in the conversation, the same as everything else
the user discusses with it. This is private from mytesla.io, not from the
assistant. Do not describe it as more private than that.

Two rules that do not bend:

- **No coordinates.** The status reading contains the car's position, heading
  and route. Never write any of it into the log. If the user wants something
  place-based, such as home versus away, record a label you worked out
  (`home`, `away`), never the position, and tell them that is what you are
  storing.
- **Nothing leaves their side.** Do not upload the log, send it to a
  third-party service, or paste it into a tool that is not the user's own,
  unless they ask. The dashboard makes no network requests (see below).

## What it needs

- **A place to keep a file between conversations.** In order of preference: a
  file on the user's computer, if your host can write files there; a
  document, note or sheet in an app the user has connected, if you can write
  to it and not just read it; a file in a project or workspace you are allowed
  to edit. Check what you can really write, write a test row, read it back,
  and tell the user exactly where the log lives. Do not use your memory
  feature as the log: it is for short notes, not tables. If you cannot keep
  anything between conversations, say so and stop. A log that disappears is
  credits spent for nothing.
- **A schedule, for scheduled readings only.** See the next section.

## Does this repeat on its own?

There are two kinds of history here, and the user should know which one they
have.

**Passive history needs no schedule and costs nothing extra.** Whenever you
read the car's status for any reason (the user asked how the car is, a
morning routine, a weekly digest), that reading is already paid for. Append it
to the log. This works anywhere you can keep the log, and the standing note
below is what makes you do it.

**Scheduled history takes extra readings and spends credits.** It only happens
if your host can run a task on a schedule (several can; check yours rather
than assume), and only after the user has agreed to the cost below. If your
host cannot, passive history still works. Say so, and never imply a schedule
exists when it does not.

## What it costs

Passive history costs nothing. A scheduled reading costs one status read,
**whether or not the car answers**.

The price of one status read is in the `get_vehicle_status` tool description.
Do not quote a figure from memory, and do not write one into the log, the
standing note or the dashboard: prices change, and those places are never
refreshed. If a description ever stops stating it, `get_credit_balance` before
and after a reading shows what that reading cost. Work it out each time you set
this up or change the cadence:

```
credits per month = readings per month x the price of one status read
```

Tell the user the result, set it against their allowance (`get_credit_balance`
shows the balance and the reset date, and is free), and get a clear yes before
the first reading and before anything is scheduled. If the result is a large
share of what they have, say so and suggest a lighter cadence.

Some readings will find the car asleep or out of signal. Those cost the same as
a good reading and record only "asleep". That is the price of never waking the
car, and it is the right trade, but it is why the times you choose matter
(below).

## What this does NOT do

- **It does not wake the car.** A wake costs many times what a reading does
  and keeps the car from settling into sleep, which costs range. History is
  never worth that. A car that is asleep at reading time is logged as asleep
  and left alone.
- **It does not watch the car continuously.** Readings are snapshots. A short
  drive or a short charge that began and ended between two readings can be
  missed. Say so on the dashboard.
- **It is not a lab test.** The battery line is a trend from the car's own
  rated range, not a measurement of the pack.

## Ask the user once, not every run

Ask these together, in one message. A rough answer is enough.

1. **What do you want to learn?** Battery health, distance, charging cost,
   efficiency, parked drain, tire pressures: any of them. Record for what they
   pick (see "What to record").
2. **How often?** Offer these and recommend the second:
   - *Light:* one reading a day, about thirty a month.
   - *Standard:* two a day, about sixty a month.
   - *Detailed:* every few hours, never more often than hourly. It costs the
     most, and more of its readings will find the car asleep.
   - *Passive only:* no schedule; log the readings that already happen.

   Pick times when the car is usually awake and parked: shortly after they
   normally get home, shortly before they normally leave. A reading at 3am
   mostly finds a sleeping car and pays for nothing.
3. **What do you pay per kWh to charge at home, and in what currency?**
   Optional. With it you can show cost; without it you still show energy. If
   they also want DC fast charging priced, ask for that price too.
4. **How many credits should always stay untouched for commands?** Before each
   scheduled reading you will check the balance and skip the reading when it is
   below this reserve. Suggest a figure based on their allowance, and say it is
   easy to change.
5. **Which car?** Only if `get_vehicles` shows more than one. Each car you log
   costs its own readings.

## Before you run: refresh, then remember

**Refresh, at most once a day.** This file changes. Costs, tool names and
steps all get revised, and a stale copy can tell you to spend credits you do
not need to spend or to call a tool that no longer exists. Before running,
check whether you have re-read `https://mytesla.io/skill-vehicle-history.md`
in the last 24 hours. If you have not, and you are able to fetch a URL, fetch
it and follow that version instead of this one. Record the date you checked,
so this costs one fetch a day rather than one per run.

If you cannot fetch URLs, say so once and treat this copy as possibly out of
date, particularly on anything it claims about credits.

**Then remember it.** Do not treat this as a one-time paste. Write a short line
into your own standing memory or instructions, wherever you keep the user's
other preferences, so history keeps building without the user having to
re-paste anything. Something like:

> mytesla.io vehicle history: the log lives at [where the log lives]. Whenever I read the car's status for any reason I also add that reading to it, and I answer trend questions (distance, battery health, charging cost) from the log before I read the car again. Scheduled readings: [what the user chose, or none]. How this works and what it costs: follow https://mytesla.io/skill-vehicle-history.md and the tool descriptions, never this note. Re-read that file if I have not in 24h. Last read: [date].

If you have no persistent memory to write to, say so plainly rather than
letting the user believe history will keep building by itself.

## Set up (once)

1. Call `get_vehicles` once and note each car's VIN. This is the only use of
   `get_vehicles` in this skill. It is free to the user, but it is a live
   request to Tesla, so never call it again as a heartbeat before a reading.
   The stored VIN is all you need.
2. Decide where the log lives (see "What it needs") and create two things
   there: the log, `readings.csv`, with the header row below, and a small
   settings file, `settings.json`.
3. Take the first reading now (see "Take a reading"). It is the first row of
   the log and proves the whole path works. If the car is asleep, record the
   `asleep` row, tell the user, and let the schedule or the next conversation
   catch it awake. Do not wake it.
4. Read the files back. Tell the user, in plain language: where the log is,
   what you record, how often you will read, what that costs per month, and
   how to stop.
5. If they agreed to scheduled readings and your host can run them, create the
   schedule at the times they chose. Then write the standing note.

Settings file:

```json
{
  "v": 1,
  "cars": [{ "vin": "<full VIN>", "label": "<last six characters>" }],
  "units": { "distance": "mi", "temperature": "F" },
  "cadence": { "mode": "standard", "times_local": ["07:30", "18:30"] },
  "reserve_credits": null,
  "home_price_per_kwh": null,
  "dc_price_per_kwh": null,
  "currency": null,
  "interests": ["battery", "distance", "charging"],
  "extra_columns": [],
  "skill_checked": "<date>"
}
```

## Take a reading

This is the whole routine, whether it was scheduled or you are logging a
reading you took for the user anyway.

1. **Scheduled readings only: check the budget.** Call `get_credit_balance`
   (free, never touches the car). If the balance is below the user's reserve,
   skip this reading, tell the user once, and keep skipping until the balance
   recovers. Do not retry.
2. **Scheduled readings only: skip if the log is fresh.** If the log already
   has an `ok` row for this car from the last 30 minutes, a second reading buys
   nothing. Skip it.
3. **Call `get_vehicle_status` with the stored VIN.** One call. Do not check
   `get_vehicles` first, and never call `wake_vehicle`.
   - Got a reading? Go to step 4.
   - Timed out (a 408: the car is asleep or out of signal)? Append an `asleep`
     row and stop. Do not retry in this run.
   - Any other failure: see "If something goes wrong".
4. **Append one row** (see "What to record"). Never rewrite or delete an
   earlier row, and do not append a reading that repeats the previous row
   within five minutes.
5. **Say nothing unless there is something to say.** A scheduled run that went
   fine ends with no message, or one line.

A reading taken during a conversation is logged the same way, with `source`
set to `conversation` and without the budget check or the freshness skip: the
user asked for it, and it is already paid for.

## What to record

One row per reading. Odometer, ranges and pressures come from the API in
miles, degrees Celsius and bar, whatever the car displays. Log them in those
units and convert when you show them (the car's display units are in
`gui_settings`).

Header row, in this order:

```csv
v,time_utc,car,source,status,odometer_mi,battery_pct,usable_pct,rated_range_mi,est_range_mi,charge_limit_pct,charging_state,charge_added_kwh,charger_kw,dc_fast,outside_c,sw_version,tire_fl_bar,tire_fr_bar,tire_rl_bar,tire_rr_bar
```

| Column | Where it comes from | Notes |
|---|---|---|
| `v` | - | Schema version of the row. Currently `1`. |
| `time_utc` | `charge_state.timestamp` | ISO 8601 in UTC. Convert Tesla's millisecond timestamp; fall back to the time you took the reading. |
| `car` | the VIN | Last six characters only. |
| `source` | - | `scheduled` or `conversation`. |
| `status` | - | `ok`, `asleep` or `error`. |
| `odometer_mi` | `vehicle_state.odometer` | |
| `battery_pct` | `charge_state.battery_level` | |
| `usable_pct` | `charge_state.usable_battery_level` | |
| `rated_range_mi` | `charge_state.battery_range` | The car's rated range. |
| `est_range_mi` | `charge_state.est_battery_range` | Range at the user's recent driving efficiency. |
| `charge_limit_pct` | `charge_state.charge_limit_soc` | |
| `charging_state` | `charge_state.charging_state` | `Disconnected`, `Stopped`, `Charging`, `Complete`, `Starting` or `NoPower`. |
| `charge_added_kwh` | `charge_state.charge_energy_added` | Energy added in the current or most recent session. |
| `charger_kw` | `charge_state.charger_power` | Blank when the car does not report it. |
| `dc_fast` | `charge_state.fast_charger_present` | `true` for DC fast charging. |
| `outside_c` | `climate_state.outside_temp` | |
| `sw_version` | `vehicle_state.car_version` | The version number only, the part before the first space. |
| `tire_fl_bar` | `vehicle_state.tpms_pressure_fl` | Blank when the car does not report it. |
| `tire_fr_bar` | `vehicle_state.tpms_pressure_fr` | Same. |
| `tire_rl_bar` | `vehicle_state.tpms_pressure_rl` | Same. |
| `tire_rr_bar` | `vehicle_state.tpms_pressure_rr` | Same. |

An `asleep` or `error` row fills `v`, `time_utc`, `car`, `source` and `status`
and leaves the rest blank.

**Extras, only if the user wants them:** inside temperature, charger voltage
and current, whether the climate is on, Sentry state, software update status,
or anything else in the status reading they ask about. Add each as a new
column at the END of the header, leave earlier rows blank for it, and record
it in `extra_columns`. Never reorder or remove a column, and never add a
coordinate.

If this skill's schema version is ever higher than the log's, add the new
columns the same way and start writing the new version. Old rows stay as they
were.

## Reading the log honestly

Use these definitions, so the same log gives the same answer every time. Use
`ok` rows for every number; `asleep` rows only feed the coverage line.

- **Coverage.** Always show the date of the first reading, how many readings,
  how many found the car asleep, and the longest gap. Every figure below is
  only as good as this.
- **Time.** The log is in UTC. Group days and weeks in the user's local time.
- **Distance.** The odometer difference between consecutive `ok` rows,
  credited to the day of the later one. If the odometer goes down, drop that
  interval and mention it once.
- **Battery health.** For `ok` rows with `battery_pct` of 50 or more,
  `rated_range_mi / (battery_pct / 100)` is the range at a full charge. Take
  the median per calendar week. Show it also as a percentage of the first
  week's median ("retained since logging began"). It moves with software
  updates and recalibration, so compare weeks, not single readings, and call
  the first weeks a baseline.
- **Efficiency.** For consecutive `ok` rows where the odometer rose by at
  least five miles, the battery fell, and neither row is `Charging`: battery
  points used per 100 miles. Do not convert to kWh unless the user tells you
  their pack's usable capacity, and then say it is an estimate.
  `est_range_mi / rated_range_mi` is a free single-reading indicator of how
  the car rates recent driving against its rating.
- **Parked drain.** For consecutive `ok` rows with an unchanged odometer and no
  rise in battery: battery points lost per 24 hours, as a median. It reflects
  Sentry, climate and how often the car woke. Readings can keep an awake car
  awake, which is why this skill never reads more often than hourly.
- **Charging.** A charge happened between two `ok` rows if the battery rose by
  three points or more with an unchanged odometer, or either row shows
  `Charging`.
  - *Energy:* take `charge_added_kwh` from the later row when it is above zero
    and `charging_state` is `Charging`, `Complete` or `Stopped`. It is the
    session total so far. Otherwise count the session without an energy
    figure.
  - *Kind:* `dc_fast` true is DC fast charging, otherwise AC.
  - *Cost:* energy x the home price, for AC sessions only, and only if the user
    gave a price. List DC sessions with energy and no cost unless they gave a
    DC price. Never invent a price per kWh.
  - *Coverage:* a session that started and ended between two readings can be
    missed, or only partly seen. Say "at least".
- **Tires.** Show the latest four. Flag a steady fall over several weeks that
  outside temperature does not explain; compare readings taken at similar
  outside temperatures. Never raise an alarm on a single low reading.
- **Software.** List each `sw_version` change with the first date it was seen.

## The dashboard

When the user asks to see their history, or after the first week of readings,
build a dashboard from the log. Where:

- If your host has a canvas or artifact feature, build it there, so they see
  it in the conversation.
- If you can also write files, save the same dashboard as one HTML file next
  to the log (`dashboard.html`), so it opens from disk.
- If you have neither, answer in the conversation with a short table and say
  there is no dashboard.

Panels, in this order: a header strip (latest reading, battery, odometer,
software version, coverage); battery health by week; distance by week;
charging sessions with monthly energy and cost; efficiency; parked drain; tires
(only if recorded); software timeline.

Rules for it:

- **One self-contained file.** Inline CSS, JavaScript and SVG, with the log
  embedded as data. No network requests of any kind: no CDN, no web fonts, no
  analytics, no external images. It must work offline.
- **Honest.** Show the coverage line, label estimates, and put "a trend from
  the car's own rated range, not a lab test" under the battery chart.
- **Readable.** Works in light and dark, on a phone, with a text alternative or
  a table behind each chart. Show distances and temperatures in the user's
  units.
- **Dated.** Show when it was built, and say so if the log is more than a week
  ahead of it.

## Answering from the log

When the user asks a trend question (how far did I drive this month, is the
battery ageing, what did charging cost), answer from the log first. It costs
nothing. Take a new reading only if the log is stale and they want the current
state, and then log that reading too.

## Stopping

If the user asks to stop: cancel the schedule, remove the history line from
your standing note, and ask whether to keep or delete the log. Tell them where
it is. Never delete it unasked.

## If a tool asks for approval

Nothing in this skill changes anything about the car, so approval prompts are
unlikely. If a call does come back waiting for approval, stop and tell the
user which tool is waiting. Do not retry: nothing about a second identical call
makes an approval appear. A scheduled run cannot answer a prompt, so say that
approving it once will let the schedule resume.

## If something goes wrong

- **You cannot write the log.** Stop scheduled readings and tell the user. A
  reading you cannot record is credits spent for nothing.
- **The car is not connected, or the connection needs refreshing.** The error
  carries a link to fix it; relay it, stop the schedule, and do not retry.
- **No subscription, or not enough credits.** Stop the schedule and say so.
  Do not retry; the user decides what to do.
- **The car keeps being asleep.** If more than half of the last ten scheduled
  readings found it asleep, tell the user once and offer to move the times or
  lower the cadence. Those readings cost credits and record nothing.
- **Any other error.** Append an `error` row and stop. After three in a row,
  stop the schedule and tell the user.
- **A log already exists** from an earlier setup. Reuse it; do not start over.

## Tools used

`get_vehicles` (once, at setup, free), `get_credit_balance` (free, no call to
the car), `get_vehicle_status` (the reading). Never `wake_vehicle`.

## Don't have mytesla.io yet?

This skill needs a mytesla.io account with a connected Tesla. Setup takes
about three minutes: [get started](https://mcp.mytesla.io/signup?src=skill-history).
