Docs / All tools
All tools.
Every tool the GridPath MCP server exposes, generated from the server's own tool definitions for gridpath 0.1.9. The descriptions are the ones the agent sees. 33 tools.
Every tool takes path: the .xlsx or .xlsm to operate on, absolute or ~/…. It must sit inside an allowed folder (see CLI options). Cells and ranges are A1 notation; sheet names are case-sensitive.
Write tools never touch the file. They accumulate as pending batches, each write returns a readback of what landed plus a review_url, and save_workbook is the only call that writes to disk. See Review before save.
Reading
Read-only. Hosts can run these without an approval prompt.
describe_workbook | Get a structural map of the ACTIVE workbook without reading cell data. |
find_rows | Search the ACTIVE workbook's row labels and column headers by name and get exact positions back. |
query_rows | Filter, project and aggregate the rows of ONE sheet of the ACTIVE workbook by column values — served by the calculation engine in milliseconds on any sheet size. |
read_range | Read evaluated cell values from a specific A1 range WITHOUT modifying anything. |
Writing
Each call lands as one pending batch and returns a readback of what was written.
set_cell | Set the value or formula of a single cell. |
set_range | Set a rectangular block of values starting at top_left (A1 address). |
copy_range | Copy a rectangular range to another location with Excel copy/paste semantics, executed against the LIVE grid: relative formula references shift by the paste offset exactly like Excel ($ anchors are respected — $B$5 stays put, B5 shifts), and cell formatting (number format, font, fill, alignment) travels with the cells. |
clear_range | Empty the values AND formulas of cells in range. |
run_script | Execute a short JavaScript program against the workbook and land ALL its writes as one reviewable batch. |
Formatting and layout
set_format | Apply formatting to one OR MANY cell ranges in a single call. |
set_column_width | Set the pixel width of one or more columns. |
set_row_height | Set the pixel height of one or more rows. |
merge_cells | Merge a rectangular range into a single visible cell. |
unmerge_cells | Unmerge a previously merged range. |
freeze_panes | Freeze the top rows rows and left cols columns of a sheet so they stay visible while scrolling. |
unfreeze_panes | Remove any freeze panes from a sheet. |
hide_rows | Hide one or more rows. |
show_rows | Unhide one or more rows. |
hide_columns | Hide one or more columns. |
show_columns | Unhide one or more columns. |
set_note | Attach a note (Excel-style cell comment) to a single cell — a small indicator that shows the given text on hover. |
delete_note | Remove a cell's note, if it has one. |
define_name | Create (or replace) a workbook-scoped named range so formulas can reference a cell/range by a readable name instead of a raw address. |
Rows, columns and sheets
Structural edits. Formula references shift the way they do in Excel.
insert_rows | Insert count empty rows starting at (and shifting down from) row before (1-indexed Excel row number). |
delete_rows | Delete count rows starting at row start (1-indexed). |
insert_columns | Insert count empty columns starting at (and shifting right from) column before (column letter like "C"). |
delete_columns | Delete count columns starting at column start (column letter like "C"). |
create_sheet | Create a new sheet (worksheet tab) in the workbook. |
rename_sheet | Rename a sheet. |
delete_sheet | Remove a sheet from the workbook. |
Batches and saving
Nothing touches the file until save_workbook.
list_batches | List this workbook's pending (unsaved) change batches with their mutation counts, and the review link. |
reject_batch | Drop one pending batch. |
save_workbook | Write every pending batch into the .xlsx on disk through the surgical patcher: only the parts an edit touched are rewritten, everything else stays byte-identical. |
Reference
describe_workbook read-only
Get a structural map of the ACTIVE workbook without reading cell data. Per sheet: the used range, the detected header row with its period/column headers mapped to column letters (e.g. {"BU": "FY2026E"}), and a table of contents of sections — contiguous row bands with their titles, like {"rows": "5-42", "title": "Revenue build"}. Also lists workbook defined names. Served from an in-memory index, so it's near-instant and returns in ONE call what would take many read_range probes. Call it once to orient on any sheet whose preview is truncated. Optional sheet filters to a single sheet.
| Parameter | Type | Description |
|---|---|---|
sheet | string | Optional: limit the map to this sheet (case-sensitive). |
find_rows read-only
Search the ACTIVE workbook's row labels and column headers by name and get exact positions back — e.g. query "total revenue" returns each matching row's sheet, 1-indexed row number, containing section, and one evaluated sample cell ("BT209 = =BT194 → 39001.1") read live from the grid. Matching is case-insensitive substring plus word-prefix ("tot rev" matches "Total revenue"). Near-instant. Use this — NOT exploratory read_range probes — whenever you need to LOCATE a row or header ("where is Subscriber ARPU?", "which column is FY2027E?"); then read_range the exact range you need. Searches only the active workbook, not reference workbooks.
| Parameter | Type | Description |
|---|---|---|
query required | string | Label text to find, e.g. "total revenue". |
max_results | integer | Max matches to return (default 20). |
sheet | string | Optional: search only this sheet (case-sensitive). |
query_rows read-only
Filter, project and aggregate the rows of ONE sheet of the ACTIVE workbook by column values — served by the calculation engine in milliseconds on any sheet size. This is the tool for "which rows have AWS in the Owner column", "sites with capacity >= 100 MW online after 2026-01-01", or "total capacity by market": one call, instead of scanning with read_range or writing a script. Columns are column letters ("DB") or header texts from the sheet's header row ("Capacity (MW)", case-insensitive, unique substrings accepted); the header row is auto-detected (override with header_row). where lists predicates that are ANDed: op is one of eq, ne, contains, not_contains, starts_with, ends_with, gt, gte, lt, lte, between (value = [low, high]), in / not_in (value = list), empty, not_empty. Text compares case-insensitively and trimmed, numbers numerically, dates as ISO strings ("2026-01-01") against date-formatted cells. Returns the matching rows — 1-indexed row plus the requested columns keyed by column letter (date-formatted numbers come back as ISO dates) — up to limit (default 50, max 500), with matched = the total count and truncated. With group_by, returns one entry per distinct value with count and each requested aggregate (sum / count / min / max / avg) instead of rows. Read-only, evaluated values (set include_formulas for formulas too). Row numbers in the reply are exact — use them directly in formulas or read_range.
| Parameter | Type | Description |
|---|---|---|
sheet required | string | Sheet name (case-sensitive). |
aggregate | array of object | With group_by: aggregates to compute per group (e.g. sum of Capacity). Groups are ordered by the first aggregate, descending. |
aggregate[].column required | string | |
aggregate[].fn required | string: "sum" | "count" | "min" | "max" | "avg" | |
columns | array of string | Columns to return (letters or header texts). Default: the header-row columns, up to 40. |
end_row | integer | Last data row to scan (default: the sheet's last row). |
group_by | string | Group matching rows by this column's value and return one entry per value instead of rows. |
header_row | integer | 1-indexed header row, when auto-detection picks the wrong one. |
include_formulas | boolean | Return {v, f} for formula cells instead of the bare value. |
limit | integer | Max rows (or groups) to return. Default 50. |
order_by | object | Sort matches by a column before applying limit (numbers before text, blanks last). |
order_by.column required | string | |
order_by.desc | boolean | |
start_row | integer | First data row to scan (default: the row after the header). |
where | array of object | Predicates, all of which must hold. Omit for every row. |
where[].column required | string | Column letter or header text. |
where[].op required | string: "eq" | "ne" | "contains" | "not_contains" | "starts_with" | "ends_with" | "gt" | "gte" | "lt" | "lte" | "between" | "in" | "not_in" | "empty" | "not_empty" | |
where[].value | any | Comparison value: string, number, ISO date, or a list for between / in / not_in. Omit for empty / not_empty. |
read_range read-only
Read evaluated cell values from a specific A1 range WITHOUT modifying anything. Use this to verify your own work, confirm where the assumption block actually landed before composing dependent formulas, or pull current values you need to reference. Returns each non-empty cell's value AND formula (if any) in A1 = value form. Costs one tool turn but is the cheapest way to prevent the formula-pointing-at-wrong-row class of bug on long sheets. Range limited to 500 cells per call. To LOCATE a row/section/header by NAME, don't probe with this — use find_rows or describe_workbook first, then read the exact range.
| Parameter | Type | Description |
|---|---|---|
range required | string | A1 range, e.g. "G45:K45", "A1:Z10", or a single cell "B7". |
sheet required | string | Sheet name (case-sensitive). |
set_cell pending batch
Set the value or formula of a single cell. cell is an Excel A1-style address (case-insensitive, e.g. "A1", "B17", "AA42"). For formulas, pass them in formula starting with '=' (e.g. "=SUM(A1:A10)") — do NOT put formulas in value.
| Parameter | Type | Description |
|---|---|---|
cell required | string | A1 address. Examples: "A1", "B17", "AA42". |
sheet required | string | Sheet name (case-sensitive). |
formula | string | null | Excel-style formula starting with '='. References inside use A1 notation. Example: "=SUM(A1:A10)". |
value | any | Literal value (string, number, boolean, or null). Leave null when using formula. |
set_range pending batch
Set a rectangular block of values starting at top_left (A1 address). values is a 2D array — outer rows, inner cells. Strings starting with '=' inside values are treated as formulas; formula references must use A1 notation pointing at the FINAL placement of cells (e.g. if top_left is "A9" and the value at row index 2 / col 0 should reference the data row just above your block at A8, write "=A8" — not "=A1" or "=A11"). **Off-by-one guard: if formulas will reference rows created by THIS call, split the work — write labels + hardcoded values first, read the row_map in this call's result, then write formulas in a second set_range against those verified row numbers.** Composing formulas in the same call that lays out their target rows is the top source of off-by-one references. Use this for bulk writes; prefer it over many set_cell calls. **null and empty string "" inside values are treated as PRESERVE — the cell is left untouched, NOT cleared.** This means you can ship [["Gross Profit", "", "", ""]] to set only the label in column A without wiping the data in B–D. To actually clear cells, use clear_range. **Writing to a sheet name that doesn't exist CREATES that sheet automatically** (near-miss spellings of an existing sheet are rejected as probable typos) — so organize multi-section builds across purposefully named sheets (e.g. Data / Assumptions / Model / Notes) instead of piling everything onto one long sheet; a new tab costs nothing.
| Parameter | Type | Description |
|---|---|---|
sheet required | string | |
top_left required | string | A1 address of the top-left cell, e.g. "A9". |
values required | array of array | 2D array — outer = rows, inner = cells. Strings beginning with '=' are formulas. |
copy_range pending batch
Copy a rectangular range to another location with Excel copy/paste semantics, executed against the LIVE grid: relative formula references shift by the paste offset exactly like Excel ($ anchors are respected — $B$5 stays put, B5 shifts), and cell formatting (number format, font, fill, alignment) travels with the cells. STRONGLY PREFER this over reading a range and re-typing shifted formulas yourself — it cannot make off-by-one mistakes and costs a fraction of the tokens. Ideal for: extending a projection by one more period (copy the last full year column block to the next column), duplicating a section as a template, or seeding a new sheet from an existing block. Blank source cells leave the destination untouched (they do NOT clear it). Result reports the evaluated destination values so you can verify. Limited to 5000 cells per call.
| Parameter | Type | Description |
|---|---|---|
dest required | string | A1 address of the destination TOP-LEFT cell, e.g. "BU5". The copy lands in a rectangle of the same size starting here. |
sheet required | string | Source sheet name (case-sensitive). |
source required | string | A1 range to copy, e.g. "BT5:BT120" or a single cell "C7". |
dest_sheet | string | null | Destination sheet if different from sheet. Formula references shift the same way; sheet-qualified refs keep pointing at their original sheets. |
mode | string: "all" | "values" | "formats" | "all" (default): shifted formulas + values + formats. "values": paste evaluated values only — use to freeze a calculation as hardcodes. "formats": formatting only, contents untouched. |
clear_range pending batch
Empty the values AND formulas of cells in range. Use this when you want to delete content (not just overwrite with new values). range is an A1 range like "A1", "A1:F1", or "B2:D10". Cell formatting is preserved.
| Parameter | Type | Description |
|---|---|---|
range required | string | |
sheet required | string |
run_script pending batch
Execute a short JavaScript program against the workbook and land ALL its writes as one reviewable batch. STRONGLY PREFER this over long set_range payloads or many tool turns whenever the edit is loop-shaped: per-row conditional transforms ("for every row where margin < 0 …"), repeating formula patterns across many rows/columns, address arithmetic, or edits that depend on reading existing cells first. One run_script call replaces dozens of tool turns and cannot drift out of alignment mid-payload, because it computes addresses programmatically.
API (plain synchronous JavaScript, strict mode — no async/await, no imports, no network, no DOM):
sheets() -> array of sheet names
sheet(name) -> handle for one sheet (name must match exactly; the name arg may be omitted ONLY if the workbook has a single sheet). Handle methods:
.get("B7") -> {value, formula} — evaluated value as of BEFORE this script, overlaid with your own literal writes
.values("A2:C50") -> 2D array of values (rows × cols)
.set("B7", x) — write a value; strings starting with '=' are formulas; null clears the cell's content
.setValues("A9", [[...],[...]]) — bulk write with set_range semantics: null / "" preserve the existing cell
.format("A1:F1", {bold: true, number_format: "0.0%", background_color: "#1F4E79", font_color: "#FFFFFF"}) — same keys as set_format
.clear("B2:D10") — empty values + formulas, formatting preserved
.usedRange() -> "A1:Q48" or null
log(...) — up to 100 entries of up to 4,000 characters each (64 KB in total), returned to you in the result as script_logs. A cut entry ends with … [+N chars]. Logs are for debugging and small extracts; for bulk reads use query_rows / read_range, not a script that logs rows.
Rules: formulas you write are NOT evaluated during the script — .get on a cell you just wrote a formula into returns {value: null, formula}; the automatic readback after the script (cells + row_map) carries the evaluated results, so verify there. Follow the same conventions as set_range: write raw numbers (never pre-formatted strings), write formulas for derived values, compose against row_map. Limits: 5-second CPU cap, and at most 20,000 WRITTEN cells per script (set/setValues/format/clear combined). Reads are not budgeted: .values() over a whole region (even 7,000 rows × 100 columns) is the right way to scan, far cheaper than many read_range turns — do not narrow reads to save budget. Workbook size is not a limit either: the script is fed per sheet from the calc engine, so name every sheet you READ as a string literal (sheet("NA Data Center Supply")) — only named sheets are loaded; reading one you did not name errors; calling sheets() loads all of them. sheet("NewName") on a name that doesn't exist AUTO-CREATES that sheet when the script's writes land — use it to organize builds across purposeful tabs. Other structural ops (insert/delete rows or columns, sheet delete/rename, merges, freeze) are NOT in the script API — use the dedicated tools for those. The script's writes flow into the same pending batch as every other tool: the user reviews and can reject them.
| Parameter | Type | Description |
|---|---|---|
script required | string | JavaScript source, executed as a function body. Example: const s = sheet("Model"); const rows = s.values("A2:C50"); rows.forEach((r, i) => { if (r[2] !== null && r[2] < 0) s.set("D" + (i + 2), "=C" + (i + 2) + "*-1"); }); |
set_format pending batch
Apply formatting to one OR MANY cell ranges in a single call. STRONGLY PREFER the bulk operations form when you have more than one format to apply — it's one tool call instead of N, dramatically faster. Each operation's format is a partial object; only specified properties are applied, others are left as-is. Use this for: bold headers, currency / percent / number formatting on data columns, italic notes, alignment, font color/size/family, **text wrap via wrap_text** (long methodology notes / descriptions in a fixed-width column — wrap beats restructuring the layout around overflow), and **cell fill via background_color** (e.g. dark-blue header bars, yellow assumption highlights — see the financial-model color conventions). v1 limitation: cell borders are not yet supported.
Two equivalent shapes:
• Single range: { sheet, range, format }
• Bulk: { sheet, operations: [ { range, format }, ... ] }
| Parameter | Type | Description |
|---|---|---|
sheet required | string | |
format | object | Partial format (only when using single-range form). |
format.background_color | string | null | Cell fill color, e.g. "#1F4E79" for a dark-blue header bar or "#FFFF00" for a yellow assumption highlight. Pass null to clear. **Always pair light/white font_color with a contrasting background_color — white-on-white reads as invisible text.** |
format.bold | boolean | |
format.font_color | string | CSS color, e.g. "#000000" or "red". |
format.font_family | string | Font family name, e.g. "Calibri", "Arial", "Aptos Narrow". |
format.font_size | number | |
format.horizontal_align | string: "left" | "center" | "right" | |
format.italic | boolean | |
format.number_format | string | Excel-style number format. Common: "$#,##0.00" currency, "0.0%" or "0.00%" percent, "#,##0" integers with commas, "#,##0.00" decimals, "@" plain text. |
format.strike | boolean | Strikethrough. |
format.underline | boolean | |
format.vertical_align | string: "top" | "middle" | "bottom" | |
format.wrap_text | boolean | Wrap long text within the cell instead of overflowing. Pair with a set_column_width + vertical_align "top" for description/notes columns. |
operations | array of object | Bulk form — array of {range, format} pairs. Use this whenever you have ≥2 formats to apply. Example: [{"range":"A1","format":{"bold":true,"font_size":16}},{"range":"B8:F8","format":{"number_format":"#,##0"}},{"range":"B9:F9","format":{"number_format":"0.0%"}}]. |
operations[].format required | object | |
operations[].range required | string | |
range | string | A1 range (only when using single-range form). Examples: "A1", "B2:B10", "A1:F1". |
set_column_width pending batch
Set the pixel width of one or more columns. **Always prefer the bulk operations form when you need multiple widths** (e.g. label column A wide, data columns narrower) — one call instead of N. The flat single-width form is kept for trivial cases. Default Excel column is ~64px; a wide column for labels is typically 180–220.
| Parameter | Type | Description |
|---|---|---|
sheet required | string | |
columns | string | Single-op form (omit operations). Comma-separated column letters. |
operations | array of object | Bulk form. Each entry sets a width on a group of columns. Use this when you need different widths for different column groups. |
operations[].columns required | string | Comma-separated column letters, e.g. "A" or "B,C,D,E,F". |
operations[].width required | number | Pixel width. |
width | number | Single-op form pixel width. |
set_row_height pending batch
Set the pixel height of one or more rows. **Always prefer the bulk operations form when you need multiple heights** (e.g. title row tall, body rows short) — one call instead of N. The flat single-height form is kept for trivial cases. Default row is ~24.
| Parameter | Type | Description |
|---|---|---|
sheet required | string | |
height | number | Single-op form pixel height. |
operations | array of object | Bulk form. Each entry sets a height on a group of rows. |
operations[].height required | number | Pixel height. |
operations[].rows required | string | Comma-separated 1-indexed row numbers, e.g. "1" or "4,5,6,7,8". |
rows | string | Single-op form (omit operations). Comma-separated 1-indexed row numbers. |
merge_cells pending batch
Merge a rectangular range into a single visible cell (e.g. for section headers). range is an A1 range like "A1:F1".
| Parameter | Type | Description |
|---|---|---|
range required | string | |
sheet required | string |
unmerge_cells pending batch
Unmerge a previously merged range.
| Parameter | Type | Description |
|---|---|---|
range required | string | |
sheet required | string |
freeze_panes pending batch
Freeze the top rows rows and left cols columns of a sheet so they stay visible while scrolling. Classic financial-model setup is freeze_rows: 1, freeze_cols: 1 (lock the header row and label column). Pass 0 to disable that axis.
| Parameter | Type | Description |
|---|---|---|
freeze_cols required | integer | Number of left columns to freeze. |
freeze_rows required | integer | Number of top rows to freeze. |
sheet required | string |
unfreeze_panes pending batch
Remove any freeze panes from a sheet.
| Parameter | Type | Description |
|---|---|---|
sheet required | string |
hide_rows pending batch
Hide one or more rows. rows is a comma-separated list of 1-indexed row numbers (e.g. "3" or "3,5,12"). Hidden rows still hold data but don't display in the workbook.
| Parameter | Type | Description |
|---|---|---|
rows required | string | Comma-separated 1-indexed row numbers. |
sheet required | string |
show_rows pending batch
Unhide one or more rows. Same rows format as hide_rows.
| Parameter | Type | Description |
|---|---|---|
rows required | string | |
sheet required | string |
hide_columns pending batch
Hide one or more columns. columns is a comma-separated list of column letters (e.g. "D" or "D,F,H").
| Parameter | Type | Description |
|---|---|---|
columns required | string | Comma-separated column letters. |
sheet required | string |
show_columns pending batch
Unhide one or more columns. Same columns format as hide_columns.
| Parameter | Type | Description |
|---|---|---|
columns required | string | |
sheet required | string |
set_note pending batch
Attach a note (Excel-style cell comment) to a single cell — a small indicator that shows the given text on hover. Use this for methodology callouts, source citations, or caveats that would clutter the cell's own value. Overwrites any existing note on that cell.
| Parameter | Type | Description |
|---|---|---|
cell required | string | A1 address, e.g. "B7". |
sheet required | string | |
text required | string |
delete_note pending batch
Remove a cell's note, if it has one. No-op if the cell has no note.
| Parameter | Type | Description |
|---|---|---|
cell required | string | A1 address, e.g. "B7". |
sheet required | string |
define_name pending batch
Create (or replace) a workbook-scoped named range so formulas can reference a cell/range by a readable name instead of a raw address — e.g. name GrowthRate for the assumption cell, then write =Revenue*(1+GrowthRate). The name persists in the saved .xlsx. name must be a valid Excel name (letters, digits, underscores; can't look like a cell reference such as "A1"). ref must be sheet-qualified, e.g. "Model!B22" or "Model!B5:B12" (absolute $ signs optional — they're added automatically). Calling again with the same name replaces its target.
| Parameter | Type | Description |
|---|---|---|
name required | string | Defined name, e.g. "GrowthRate" or "Revenue". Must not look like a cell address. |
ref required | string | Sheet-qualified target, e.g. "Model!B22" or "Assumptions!B5:B12". |
sheet | string | Optional. Only needed if ref is a bare range without a sheet prefix; supplies the sheet for it. |
insert_rows pending batch
Insert count empty rows starting at (and shifting down from) row before (1-indexed Excel row number). Existing content at row before and below shifts down. Use this to make space in the middle of a model. Charts and conditional-formatting references in unrelated areas of the workbook may not auto-adjust to the new layout.
| Parameter | Type | Description |
|---|---|---|
before required | integer | 1-indexed row number to insert before. |
sheet required | string | |
count | integer |
delete_rows pending batch
Delete count rows starting at row start (1-indexed). Content below shifts up. Permanent — the removed rows are gone.
| Parameter | Type | Description |
|---|---|---|
sheet required | string | |
start required | integer | |
count | integer |
insert_columns pending batch
Insert count empty columns starting at (and shifting right from) column before (column letter like "C"). Existing content shifts right.
| Parameter | Type | Description |
|---|---|---|
before required | string | Column letter to insert before, e.g. "C". |
sheet required | string | |
count | integer |
delete_columns pending batch
Delete count columns starting at column start (column letter like "C"). Content to the right shifts left. Permanent.
| Parameter | Type | Description |
|---|---|---|
sheet required | string | |
start required | string | |
count | integer |
create_sheet pending batch
Create a new sheet (worksheet tab) in the workbook. Use a clear human-readable name like "Assumptions", "Q4 2024", "Inputs". The new sheet starts empty — write cells with set_cell / set_range afterwards. NOTE: writes to a not-yet-existing sheet name auto-create it, so an explicit create_sheet is only needed when you want to set a tab_color or create an empty tab ahead of time.
| Parameter | Type | Description |
|---|---|---|
name required | string | Sheet name (must be unique in the workbook). |
tab_color | string | null | Optional CSS color for the tab, e.g. "#22c55e". |
rename_sheet pending batch
Rename a sheet. Cross-sheet formula references in other sheets will be updated automatically by the workbook engine.
| Parameter | Type | Description |
|---|---|---|
new_name required | string | |
old_name required | string |
delete_sheet pending batch
Remove a sheet from the workbook. Permanent — every cell on this sheet is lost. Cross-sheet references in other sheets will become #REF!.
| Parameter | Type | Description |
|---|---|---|
name required | string |
list_batches read-only
List this workbook's pending (unsaved) change batches with their mutation counts, and the review link.
No parameters beyond path.
reject_batch pending batch
Drop one pending batch. The remaining batches are replayed onto a fresh model, so later edits that depended on it may change.
| Parameter | Type | Description |
|---|---|---|
batch_id required | string |
save_workbook writes the file
Write every pending batch into the .xlsx on disk through the surgical patcher: only the parts an edit touched are rewritten, everything else stays byte-identical. This is the ONLY tool that modifies the file. When the server runs with --review-required, this returns the review link instead and the user saves from there.
| Parameter | Type | Description |
|---|---|---|
as | string | Optional: write the result to this path instead (a copy), leaving the original untouched. Pending batches stay pending against the original. |
Found a mistake? Open an issue or email support@gridpath.dev.