Excel AI Bridge — alpha

The Excel add-in

Download the bundle from https://bridge.ram.sh/download/<your invite>/latest.zip (the invite is the code you got with your access), unzip it and run install.cmd. It puts the window add-in into %APPDATA%\Microsoft\Excel\XLSTART, so it loads in every Excel session; uninstall.cmd removes it again.

Bind this workbook points the bridge at the active workbook. It works whether or not a bridge is running — the next one to start picks it up.

Private marks or unmarks the current selection. One press always leaves the selection wholly private; a second press leaves it wholly public.

Show private selects every private range on the sheet. Shade private shades them, per workbook, off by default.

Private ranges are stored as workbook AI_PRIVATE_* Names, so they travel with the file and survive with no bridge running. Each name is derived only from the sheet and the marked range's own address, so inserting, moving, or renaming a sheet, or marking and unmarking other ranges, never changes an existing private range's name or what it points at. The names are hidden, so they don't appear in the Name Box or the Paste Name dialog while you're building a formula.

The guard follows direct references across sheets: with Sheet1!C1 private, Sheet2!A1 = Sheet1!C1 and Sheet3!B2 = Sheet2!A1*2 are masked too, and a SUM over a range that contains C1 is masked with them. Marking still lists those cells and offers to mark them outright, so they stay covered if a formula is later pasted as a value.

Known limit: the guard reads formula text, so it does not follow INDIRECT, OFFSET arithmetic, references through a defined name, or values already copied elsewhere as constants.

What hidden and locked mean to the AI

Excel's own hiding and protection count as privacy signals, not just layout:

private to the AI, exactly like marked cells, until you press Hidden private in the ribbon's Private group, which then reads Hidden readable (private by default, saved with the workbook). The AI may say a hidden row or column exists; it never unhides one and never explains how.

sheet is as absent as a missing one, and a visible formula that reads it is masked.

Locked cells are never changed, sorted, filtered or formatted; unlocked cells can be written, without touching their formatting. A formula hidden by protection is never shown as text. The bridge never unprotects a sheet, never asks for a password and never uses one typed into the chat.

over them.

from that file; the ribbon warns once per workbook when that happens.

What the bridge reports

On start and on a heartbeat the bridge sends installation metadata to the hosted broker so staff can see which invite is running: invite, bridge version, channel, last-seen, OS, Excel version, daily tool-call counts, and per-action outcome and timing counts (how many calls ended ok, refused, busy, timed out or in error, and two duration bucket bounds). It never sends workbook content, cell values, addresses, sheet names, or file names. privacy_report lists exactly those fields. Uninstall reports uninstalled. Every host the bridge talks to, and what each receives, is listed in PRIVACY.md; what the bridge protects, what it does not, and how to report a security problem is in SECURITY.md.

What every read tells you

Every read_range, analyze_sheet and get_active_selection reply carries:

masking_note — one sentence saying so. Addresses only, never contents.

commands (instruction-like, url-or-host, command-like). They are returned as data and flagged; nothing is hidden or blocked.

Every tool call is appended to %LOCALAPPDATA%\ExcelAIBridge\audit\<workbook-key>.jsonl with the caller's identity (the Google account that signed in, token:<id> for the bearer token, or local), the sheet and range, and how many cells were masked. privacy_report() returns the current private addresses and the recent audit.

When something goes wrong the AI writes the bug report, not you: it works out what happened, says it back to you in plain words, asks whether that is right, and then files the whole technical ticket itself — you never see a number or a link. AI Bridge → My reports shows what you have reported from this computer and where each one stands; see docs/report-a-bug.md.

write_cells refuses to replace a non-empty cell unless overwrite=true, and names what is there. Private cells are refused before that check and are never quoted.

Understanding a model

sheets) and what it feeds (same sheet). Private cells appear by address with [redacted] values.

how many private cells each holds.

underline, font size, borders (edges, weight, colour), horizontal and vertical alignment, wrap text (no merge); journalled; undo_last_write restores every attribute it changed. write_cells keeps a cell's own number format unless you pass number_format.

from %LOCALAPPDATA%\ExcelAIBridge\instructions\<workbook-key>.md. Edit the file by hand; no tool writes it.

Native operations

sort_range, apply_filter / clear_filter, add_conditional_format, add_validation_list, copy_range, move_range. Each refuses a range that contains a private cell; a copy or move refuses when its source or its destination touches one. Sorts, conditional formats, validations, copies and moves are journalled and reversed by undo_last_write (a move is put back on both sides); a filter changes no cell and is removed by clear_filter. copy_range and move_range use Excel's own Copy / Cut, so relative references adjust as they do by hand, and a non-empty destination is refused unless the customer chose to replace it (overwrite=true). rename_sheet, freeze_panes, autofit_columns and set_column_width change the sheet's name or layout, never a cell; each is journalled and reversed by undo_last_write, and each refuses a protected sheet or a private cell (a width change refuses the whole column when any cell in it is private). delete_sheet needs the customer's own Yes on the question page to a question that names that sheet and says the word sheet (a "Yes, delete it" button), the way replacing a non-empty cell does; a question giving the name alone deletes nothing, because a name is no evidence of a kind; without it the call is refused and nothing is deleted, and each Yes allows one delete of the sheet it named. It refuses a sheet holding a private cell first, and cannot be undone. add_hyperlink puts a clickable link on one cell and remove_hyperlink takes it off, leaving the cell's text. The cell's own text is kept unless you pass text, so a link to the source document can be dropped onto a figure; passing text for a cell that already holds something is a replacement and needs the customer's yes (overwrite=true), as write_cells does. A private cell is refused, and so is a link whose url or display text carries a private value - the refusal says only that, and repeats no value. Both are journalled with the cell's prior text and prior link, and undo_last_write puts both back. select_range moves Excel's selection to a range and scrolls it into view so the customer sees what the assistant means; it reads and changes nothing. find_cells answers "where is the 2024 budget line?" with addresses and masked values instead of a whole sheet: look_in is values or formulas (the parameter is look_in because in is a Python keyword), match is contains or exact, both ignoring case, and without a sheet every visible sheet is searched. It changes nothing, and a private cell -- or a cell holding a value that matches a private one -- is dropped before the comparison, so a query whose only matches are private answers with an empty list and no hint that anything was withheld: what is found reveals what is hidden. find_replace replaces text inside one bounded range; it refuses the whole batch when the range holds a private cell or such a twin, and refuses a replacement that is itself a private value (a twin written in the open). Cells holding a formula are left alone and named in skipped. Each replaced cell is journalled before it changes, so undo_last_write puts them back one at a time. insert_rows / insert_columns, delete_rows / delete_columns and clear_range (contents, formats or all) change the sheet's structure. A delete refuses when a deleted cell is private or feeds a private cell, an insert when it would split a private block, a clear when the range holds a private cell. Each is journalled before the change: undo of a delete puts the rows or columns back with their values, formulas and formats, undo of an insert removes them, undo of a clear restores what was cleared. add_comment puts a note on one cell and delete_comment takes it off; both are journalled before the change, so undo_last_write puts the previous note back or removes the one that was added, and both refuse a private cell plainly. list_comments reads a sheet's notes: a note on a private cell keeps its address and loses its text ({"address": "C3", "text": null, "private": true}), the same shape a plain note has, so the reply never says more about a private cell than where it is. define_name, list_names and delete_name are the workbook's defined names, so a formula can read =SUM(Sales) rather than =SUM(B2:B40). A name is journalled before it is added or removed: undo of a define_name deletes the name, undo of a delete_name defines it again with the reference it had. define_name refuses a range holding a private cell — a private cell is never named — and the AI_PRIVATE / CLAUDE_PRIVATE names are the guard's own, so neither define_name nor delete_name will touch one. list_names never returns the guard's names or the addresses they cover, and withholds every customer name whose range holds a private cell, saying only how many were withheld and why, never which cells. add_chart draws one bar, column, line, pie or scatter chart over a bounded range, anchored by default on the cell right of the data. The chart is read back before it is called added (#2088): one whose series plot no number — a column of text, or numbers only in hidden rows — is deleted again and the call refused in plain words that name no cell value. That check is skipped over private cells, since its answer would tell what they hold: such a chart is drawn even when it plots nothing, and the customer can delete it. A range holding a private cell is still drawn — Excel plots it on the customer's screen — but the AI is never told its values, title or source: the reply says only that the chart was added and its values are hidden, and the chart is untouchable to the AI (no delete, no change of source). The chart is journalled by name, so undo_last_write and the ribbon's Undo AI change still delete exactly that chart. list_charts names every chart with its kind, sheet and anchor, withholds the source range of a chart that reads a private cell, and withholds a title Excel took from a cell (a series name, or a title linked to a cell) whenever the chart reads a private cell or that cell is private or on a hidden sheet. A chart of how many rows fall in each category needs no helper table (#2088): add_pivot with that column as a row and a count of it as the value, then add_chart over the address and on the sheet its reply gives, draws a pivot chart; the tool text and the AI's write rules name that route. delete_chart removes one chart by name and cannot be undone, so it needs the customer's own Yes on the question page to a question that names that chart and says the word chart (a "Yes, delete it" button), the way delete_sheet does; without it the call is refused and nothing is deleted, and each Yes allows one delete of the chart it named. A chart that reads a private cell is refused before consent is even looked at - "chart <name> was not deleted: it reads a private cell, and private means untouchable; while adding it is still on the undo list, undoing the AI's own changes back to it removes it" - and no Yes can spend that away. That undo of the AI's own add is by design (#2359): add_chart's note on such a chart, list_charts, restyle_chart and the WRITES instructions say it in the same words. A chart is styled as it is added and can be restyled later (#2058): add_chart also draws doughnut, area, column_3d, bar_3d and pie_3d charts, and takes a size in points (width, height), a style preset (style, Excel's chart style number), a colour palette (palette, 10 to 26) or a colour per series - per slice for a pie or doughnut - (colors), data labels on or off, the legend's place or none, axis titles and gridlines on or off. restyle_chart changes the kind, title and those same options on a chart already there, a chart over private cells included: it changes the look only, never the source, and tells the AI nothing of the chart's values. An option Excel will not apply to that kind of chart (a pie has no axes) is named back in not_applied. A restyle reads the chart back after it acts and answers per option: changed, unchanged (already so) or not_applied with a plain reason, so an option Excel took without a word and did not show is never said to be applied. A palette clears each series' own colours first, so it recolours a chart drawn with colors too. A restyle is journalled as evidence, with only the options that landed, and has no undo of its own; undoing the chart's add removes it. Every part of a chart can be designed, on add_chart and restyle_chart alike (#2117): a font (name, size, bold, italic, colour) for the title, the data labels, the legend and the axis titles (title_font, label_font, legend_font, axis_title_font); what the labels show - any of value, percent and category (label_content) - where they sit (label_position: inside, outside, best fit or center on a pie) and their number format (label_number_format); one slice of a pie or doughnut pulled out (explode: by its position, its category or largest, with a distance in percent, or none); and the chart and plot area's fill, a colour or none (chart_fill, plot_fill). Each is read back like the rest. Over private cells every one of them still applies and no reply tells a value: the bridge picks the largest slice itself and does not say which or whether there was one (largest reads back changed there whatever the cells hold), a slice is not picked there by its category's name (whether one matched would tell that the category is there), and labels that show values are drawn for the customer and never read back. add_table turns a bounded range into an Excel table (a ListObject) for filters, banded rows and structured references, remove_table turns one back into a plain range, and list_tables names every table with its sheet, header flag and style. Neither write touches a value. Both refuse a range holding a private cell — a table's banding and filter dropdowns say where the private block is, and so does its formatting on the way out — and list_tables withholds the address of such a table while still naming it. Each is journalled before the change: undo of an add_table converts the table back to a plain range, undo of a remove_table re-creates it over the same range with its name, style and header flag.

Pivot tables

list_pivots names every pivot table: its sheet, the address of its report, its source range, its row, column and value fields (each value with its aggregation), its filter fields with their current selection, and when it was last refreshed — names and addresses only, no value is read. read_pivot reads one report by name through the same guard as every read. A pivot is an aggregation over its source, so a report whose source overlaps a private cell reads as masked cells with the standard wording, and list_pivots withholds that pivot's source and address while still naming it. add_pivot builds one over a range or a table (row, column, filter and value fields with their aggregation; a numeric column is summed and any other counted when no aggregation is given) on an existing sheet or on a new sheet named after the pivot; the source is refused when it holds a private cell, and the report's ground is checked as a write is. delete_pivot takes the customer's Yes on the question page to a question that names that pivot and says the words pivot table, the way delete_chart does. Both are journalled: undo of an add_pivot removes that pivot (then its new sheet), undo of a delete_pivot rebuilds it from the layout the record kept. change_pivot moves fields (add or remove row, column and value fields, change an aggregation, set a filter) and journals the layout before the change so undo puts it back; refresh_pivot re-reads the source into the report and cannot be undone, which its reply says. Both refuse when the source holds a private cell.

Connecting claude.ai

Run venv\Scripts\python.exe src\server.py --broker and paste the connector URL it prints into claude.ai — the hosted-broker route, no tunnel and no DNS; see Route C.

The three tools

claude.ai sees three tools: excel_read (read_range, analyze_sheet, analyze_workbook, get_active_selection, trace, find_errors, list_private_cells, privacy_report, select_range, list_comments, list_names, find_cells, list_charts, list_tables), excel_write (write_cells, create_sheet, format_cells, sort_range, apply_filter, clear_filter, add_conditional_format, add_validation_list, copy_range, move_range, undo_last_write, insert_rows, insert_columns, delete_rows, delete_columns, clear_range, rename_sheet, delete_sheet, freeze_panes, autofit_columns, set_column_width, add_comment, delete_comment, define_name, delete_name, find_replace, add_hyperlink, remove_hyperlink, add_chart, delete_chart, restyle_chart, add_table, remove_table, label_rows) and excel_session (bind_workbook, list_bound_workbooks, get_instructions, report_session, ask_customer, report_bug, request_feature, collect_bug_evidence). Each call names an action and passes that action's parameters in params; a wrong action or parameter answers with the exact list. Reads run without a permission dialog; the write and session tools each ask once. label_rows answers one closed question per row with a local model on the customer's computer, writing one of the given labels into the output column and leaving unsure, private and blank rows empty; until the model is on disk, each session asks the customer before its one-time download of about 681 MB from models.ram.sh. Only these three tools are listed. A connector added before 2026-09-08 must "Refresh tools list" once in claude.ai, since it caches the tool list and the old per-action names are gone.

Using Skills with the bridge

claude.ai applies enabled Skills to any connector. Nothing to install here: enable the Skills you want in claude.ai and they apply to Excel AI Bridge conversations. Skills that suit a workbook: financial-model review, data cleaning, chart specification.

If a crash leaves a workbook encrypted

While a workbook is bound the bridge encrypts it on disk, so a copy of the file, or a program that opens the .xlsx without the password, gets nothing readable. It is not a guard against a program running as you, which can read the password from the bridge's keystore first (see SECURITY.md). An Excel 97-2003 .xls is the exception: it is left unencrypted, because a password on one makes Excel ask on every save whether to convert it to the newer format. The password comes off again when the bridge exits — but a hard kill, a crash or a power cut never gets to run that step, and the file is left as an .xlsx Excel will not open.

The next start puts it right by itself: before it binds anything, the bridge unlocks every workbook it holds a key for and finds still encrypted. Nothing is asked of you. That startup step is capped at two minutes in total, however many workbooks are waiting — anything it does not reach is left for the next start and named on screen, so recovery can never be the reason the bridge does not come up.

When the bridge will not start at all, run the same thing on its own, from the bridge's folder (%LOCALAPPDATA%\ExcelAIBridge\releases\<version>):

bridgehost.exe --recover            # fix
bridgehost.exe --recover --dry-run  # report only
Excel AI Bridge recovery
  C:\books\Model.xlsx   encrypted, key on file              -> unlocked
  C:\books\Secret.xlsx  recorded protected before the bind  -> left alone
  C:\books\Open.xlsx    open in Excel                       -> left alone, in use
  C:\books\Old.xlsx     file missing                        -> stale key kept

--dry-run asks Excel which workbooks are open before it says what it would do, so it takes a moment longer than you might expect — and it says "would unlock" only about a workbook it really would.

It only ever touches a workbook it minted the password for. A file you protected yourself and never bound has no key here, so it is not merely skipped — recovery cannot see it, and it is left exactly as it is. The Secret.xlsx row above is the other case: a workbook the bridge DID bind, whose key blob records that the file was already protected when it did, so it is listed and left alone rather than unlocked. Nor will it touch one another bridge has bound, one that is open in Excel, or a read-only file — and if it cannot tell (Excel will not answer, or the file that records which workbooks are bound is unreadable) it refuses rather than guesses, and says so.

The limitation, stated plainly. The passwords live in %LOCALAPPDATA%\ExcelAIBridge\keys\, encrypted with Windows DPAPI, which is bound to this Windows account on this machine. Copying those files to another computer, or restoring them after the profile is deleted or the OS is reinstalled, will not decrypt them — and a workbook stranded encrypted at that moment cannot be opened again. If you need a copy of a password somewhere else, take it out deliberately:

bridgehost.exe --recover --show C:\books\Model.xlsx

That is the only command that prints a password. bridgehost.exe --recover --forget <path> drops a key you no longer want; nothing deletes one on its own, because it is the only copy.