The xl CLI provides a command-line interface for Excel operations, designed for LLM agents and automation.
Design Philosophy:
- Stateless — Each command is self-contained
- Explicit cell refs — Always use
A1,B5:D10notation - Global flags —
-ffor file,-sfor sheet,-ofor output - LLM-optimized output — Markdown tables, token-efficient
git clone https://github.com/TJC-LP/xl.git
cd xl
make installThis builds a fat JAR and installs xl to ~/.local/bin/. Ensure it's in your PATH:
export PATH="$HOME/.local/bin:$PATH"Uninstall: make uninstall
Update: After git pull, run make build — the wrapper auto-picks up the new JAR.
# Global flags (used with all commands)
-f, --file <path> # Input file (required)
-s, --sheet <name> # Sheet to operate on (optional, defaults to first)
-o, --output <path> # Output file for mutations (required for put/putf)
# Read-only operations
xl -f model.xlsx sheets # List all sheets
xl -f model.xlsx bounds # Show used range
xl -f model.xlsx -s "P&L" view A1:D20 # View range as markdown
xl -f model.xlsx cell B5 # Get single cell details
xl -f model.xlsx search "Revenue" # Find cells by content
xl -f model.xlsx eval "=SUM(B1:B10)" # Evaluate formula (what-if)
# Mutations (require -o)
xl -f model.xlsx -o output.xlsx put B5 1000000 # Write value
xl -f model.xlsx -o output.xlsx putf C5 "=B5*1.1" # Write formula
# What-if analysis with overrides
xl -f model.xlsx eval "=B1*1.1" --with "B1=100" # Evaluate with temporary values| Category | Commands | Purpose |
|---|---|---|
| Navigate | sheets, bounds |
Find your way around |
| Explore | view, cell, search |
Read data incrementally |
| Analyze | eval |
What-if formula evaluation |
| Mutate | put, putf, style, row, col, fill, clear |
Make changes (requires -o) |
| Command | Arguments | Description |
|---|---|---|
sheets |
List all sheets with stats | |
bounds |
Show used range of current sheet | |
view |
<range> |
Render range as markdown |
cell |
<ref> |
Get single cell details |
search |
<pattern> |
Find cells matching pattern |
eval |
<formula> [--with overrides] |
Evaluate formula without modifying |
put |
<ref> <value> |
Write value to cell (requires -o) |
putf |
<ref> <formula> |
Write formula to cell (requires -o) |
style |
<range> [options] |
Apply styling (requires -o) |
row |
<n> [options] |
Set row height, hide/show (requires -o) |
col |
<letter> [options] |
Set column width, hide/show, auto-fit (requires -o) |
fill |
<source> <target> [--right] |
Fill cells with source value/formula (requires -o) |
clear |
<range> [--all|--styles|--comments] |
Clear cell contents/styles/comments (requires -o) |
batch |
<file|-> |
Apply multiple operations from JSON (requires -o) |
List all sheets as a markdown table.
Output:
| # | Name | Range | Cells | Formulas |
|---|-------------|----------|-------|----------|
| 1 | Assumptions | A1:F50 | 234 | 12 |
| 2 | Revenue | A1:M100 | 892 | 156 |
| 3 | P&L | A1:N120 | 978 | 76 |Show the used range (bounding box of non-empty cells).
Arguments:
| Arg | Type | Required | Default | Description |
|---|---|---|---|---|
sheet |
string | No | active | Sheet name |
Output:
Sheet: Revenue
Used range: A1:M100
Rows: 1-100 (100 total)
Columns: A-M (13 total)
Non-empty: 892 cells
View a rectangular range as a markdown table with row/column headers.
Arguments:
| Arg | Type | Required | Default | Description |
|---|---|---|---|---|
range |
string | Yes | — | Cell range (e.g., "A1:D20") |
--formulas |
flag | No | false | Show formulas instead of values |
--eval |
flag | No | false | Evaluate formulas (compute live values) |
--limit |
int | No | 50 | Max rows to display |
Output:
| | A | B | C | D |
|---|-------------|---------|------------|---------|
| 1 | Revenue | | $1,000,000 | |
| 2 | COGS | | $400,000 | |
| 4 | Gross Profit| | =C1-C2 | |Get complete information about a single cell.
Arguments:
| Arg | Type | Required | Description |
|---|---|---|---|
ref |
string | Yes | Cell reference (e.g., "A1", "B5") |
Output (formula cell):
Cell: C4
Type: Formula
Formula: =C1-C2
Cached Value: 600000
Formatted: $600,000
Output (value cell):
Cell: A1
Type: Text
Value: Revenue
Find cells containing text matching pattern.
Arguments:
| Arg | Type | Required | Default | Description |
|---|---|---|---|---|
pattern |
string | Yes | — | Search pattern (supports regex) |
--in |
string | No | entire sheet | Limit search to range |
--limit |
int | No | 20 | Max results |
Output:
Found 5 matches for "Revenue":
| Ref | Value | Context (row) |
|-----|-----------------|----------------------------|
| A1 | Revenue | Revenue | | $1,000,000 |
| A10 | Revenue Growth | Revenue Growth | | 5% |Evaluate a formula without modifying the file (what-if analysis).
Arguments:
| Arg | Type | Required | Description |
|---|---|---|---|
formula |
string | Yes | Formula to evaluate |
--with |
string | No | Temporary cell overrides (e.g., "B1=100") |
Examples:
xl -f model.xlsx eval "=SUM(B1:B10)"
xl -f model.xlsx eval "=B1*1.1" --with "B1=100"Write a value to a cell.
Arguments:
| Arg | Type | Required | Description |
|---|---|---|---|
ref |
string | Yes | Cell reference |
value |
string | Yes | Value to write |
Type Inference:
- Numbers:
1000,1,234.56,$100,50% - Dates:
2024-01-15,01/15/2024 - Booleans:
true,false - Text: Everything else
Example:
xl -f input.xlsx -o output.xlsx put B5 1000000Write a formula to a cell.
Arguments:
| Arg | Type | Required | Description |
|---|---|---|---|
ref |
string | Yes | Cell reference or range |
formula |
string | Yes | Formula (with or without =) |
--from |
string | No | Source cell for relative reference adjustment |
Examples:
xl -f input.xlsx -o output.xlsx putf C5 "=B5*1.1"
xl -f input.xlsx -o output.xlsx putf B2:B10 "=SUM(\$A\$1:A2)" --from B2Apply styling to cells.
Arguments:
| Arg | Type | Description |
|---|---|---|
range |
string | Cell/range reference |
--bold |
flag | Bold text |
--italic |
flag | Italic text |
--bg |
string | Background color (name or hex) |
--fg |
string | Text color |
--align |
string | Alignment: left, center, right |
--format |
string | Number format: currency, percent, date |
--border |
string | Border style: none, thin, medium, thick |
--replace |
flag | Replace entire style instead of merging |
Example:
xl -f input.xlsx -o output.xlsx style A1:D1 --bold --bg yellow --align centerSet row properties (height, hide/show).
Arguments:
| Arg | Type | Required | Default | Description |
|---|---|---|---|---|
n |
int | Yes | — | Row number (1-based) |
--height |
double | No | — | Row height in points |
--hide |
flag | No | false | Hide the row |
--show |
flag | No | false | Show (unhide) the row |
Examples:
xl -f input.xlsx -o output.xlsx row 5 --height 30
xl -f input.xlsx -o output.xlsx row 10 --hide
xl -f input.xlsx -o output.xlsx row 10 --showSet column properties (width, hide/show, auto-fit).
Arguments:
| Arg | Type | Required | Default | Description |
|---|---|---|---|---|
letter |
string | Yes | — | Column letter (e.g., "A", "B", "AA") |
--width |
double | No | — | Column width in character units (~8.43 default) |
--auto-fit |
flag | No | false | Auto-fit width based on cell content |
--hide |
flag | No | false | Hide the column |
--show |
flag | No | false | Show (unhide) the column |
Behavior:
--auto-fitcalculates optimal width based on longest cell content in the column- Adds 2 characters of padding to the calculated width
- Minimum width is 8.43 (Excel default)
- If both
--widthand--auto-fitare specified,--auto-fittakes precedence
Examples:
# Set explicit width
xl -f input.xlsx -o output.xlsx col B --width 20
# Auto-fit column width based on content
xl -f input.xlsx -o output.xlsx col A --auto-fit
# Hide/show columns
xl -f input.xlsx -o output.xlsx col C --hide
xl -f input.xlsx -o output.xlsx col C --showClear cell contents, styles, or comments from a range.
Arguments:
| Arg | Type | Required | Default | Description |
|---|---|---|---|---|
range |
string | Yes | — | Cell or range reference (e.g., "A1", "A1:D10") |
--all |
flag | No | false | Clear contents, styles, and comments |
--styles |
flag | No | false | Clear styles only (reset to default) |
--comments |
flag | No | false | Clear comments only |
Behavior:
- Default (no flags): Clears cell contents only
--all: Clears everything (contents, styles, comments)--styles: Clears formatting but keeps contents and comments--comments: Clears comments but keeps contents and styles- Flags can be combined:
--styles --commentsclears both - Merged regions overlapping the cleared range are automatically unmerged
Examples:
# Clear contents (default)
xl -f input.xlsx -o output.xlsx clear A1:D10
# Clear everything
xl -f input.xlsx -o output.xlsx clear A1:D10 --all
# Clear styles only (keep data)
xl -f input.xlsx -o output.xlsx clear A1:D10 --styles
# Clear comments only
xl -f input.xlsx -o output.xlsx clear B5 --comments
# Clear styles and comments, keep contents
xl -f input.xlsx -o output.xlsx clear A1:D10 --styles --commentsFill cells with source value/formula (Excel Ctrl+D/Ctrl+R equivalent).
Arguments:
| Arg | Type | Required | Default | Description |
|---|---|---|---|---|
source |
string | Yes | — | Source cell or range (e.g., "A1", "A1:C1") |
target |
string | Yes | — | Target range to fill (e.g., "A1:A10", "A1:C10") |
--right |
flag | No | false | Fill rightward instead of downward |
Behavior:
- Fill Down (default): Source row(s) are repeated down through target range
- Columns must match between source and target
- Example:
fill A1 A1:A10copies A1 to A2:A10 - Example:
fill A1:C1 A1:C10copies row 1 to rows 2-10
- Fill Right (
--right): Source column(s) are repeated right through target range- Rows must match between source and target
- Example:
fill A1 A1:E1 --rightcopies A1 to B1:E1 - Example:
fill A1:A5 A1:E5 --rightcopies column A to columns B-E
- Formulas are shifted using Excel anchor rules (
$anchors are preserved)
Examples:
# Fill value down a column (Ctrl+D equivalent)
xl -f input.xlsx -o output.xlsx fill A1 A1:A100
# Fill multiple columns down together
xl -f input.xlsx -o output.xlsx fill A1:E1 A1:E100
# Fill value right across a row (Ctrl+R equivalent)
xl -f input.xlsx -o output.xlsx fill A1 A1:J1 --right
# Fill multiple rows right together
xl -f input.xlsx -o output.xlsx fill A1:A5 A1:J5 --right
# Formula shifting example: =A1*2 in B1 fills to =A2*2, =A3*2, etc.
xl -f input.xlsx -o output.xlsx fill B1 B1:B10Apply multiple operations atomically from JSON input.
Arguments:
| Arg | Type | Required | Description |
|---|---|---|---|
file |
string | Yes | JSON file path or - for stdin |
JSON Schema:
[
{"op": "put", "ref": "A1", "value": "Hello"},
{"op": "putf", "ref": "B1", "value": "=A1*2"},
{"op": "style", "range": "A1:B1", "bold": true},
{"op": "merge", "range": "A1:D1"},
{"op": "colwidth", "col": "A", "width": 15.5},
{"op": "rowheight", "row": 1, "height": 30},
{"op": "comment", "ref": "A1", "text": "Revenue figure", "author": "Analyst"},
{"op": "autofit", "columns": "A:D"},
{"op": "add-sheet", "name": "Summary", "after": "Sheet1"}
]Supported Operations:
| Operation | Required Fields | Optional Fields | Description |
|---|---|---|---|
put |
ref, value |
format, values, detect |
Write value to cell |
putf |
ref, value |
from, values |
Write formula(s) to cell(s) |
style |
range |
styling options | Apply cell styling |
merge |
range |
Merge cells | |
unmerge |
range |
Unmerge cells | |
colwidth |
col, width |
Set column width | |
rowheight |
row, height |
Set row height | |
comment |
ref, text |
author |
Add cell comment |
remove-comment |
ref |
Remove cell comment | |
clear |
range |
all, styles, comments |
Clear cell contents/styles/comments |
col-hide |
col |
Hide column | |
col-show |
col |
Show column | |
row-hide |
row |
Hide row | |
row-show |
row |
Show row | |
autofit |
columns |
Auto-fit column widths | |
add-sheet |
name |
after |
Add new sheet |
rename-sheet |
from, to |
Rename sheet |
Native JSON Types (recommended):
// Numbers are stored as numeric values (not text)
{"op": "put", "ref": "A1", "value": 99.0}
// Booleans
{"op": "put", "ref": "A2", "value": true}
// With explicit format
{"op": "put", "ref": "A3", "value": 99.0, "format": "currency"}
{"op": "put", "ref": "A4", "value": 0.594, "format": "percent"}Format Options:
| Format Name | Description | Example Output |
|---|---|---|
general |
Default format | 1234.5 |
integer |
Whole numbers | 1235 |
decimal |
Two decimal places | 1234.50 |
currency |
Currency with symbol | $1,234.50 |
percent |
Percentage | 59% |
percent_decimal |
Percentage with decimals | 59.4% |
date |
Date format | 11/10/25 |
datetime |
Date and time | 11/10/25 14:30 |
time |
Time only | 14:30:00 |
text |
Text format | 1234.5 |
| custom | Any Excel format code | See below |
Custom Format Codes:
// MOIC/Multiple format (3.5x)
{"op": "put", "ref": "A1", "value": 3.5, "format": "0.0x"}
// Accounting format with negatives in parentheses
{"op": "put", "ref": "A2", "value": -1234, "format": "$#,##0;($#,##0)"}
// Basis points
{"op": "put", "ref": "A3", "value": 50, "format": "0 \"bps\""}
// Custom date format
{"op": "put", "ref": "A4", "value": "2025-11-10", "format": "yyyy-mm-dd"}Smart String Detection (enabled by default):
Strings are automatically detected and formatted:
// Currency detected from $ prefix
{"op": "put", "ref": "A1", "value": "$99.00"} // → Number(99.0), Currency
// Percent detected from % suffix
{"op": "put", "ref": "A2", "value": "59.4%"} // → Number(0.594), Percent
// ISO date detected
{"op": "put", "ref": "A3", "value": "2025-11-10"} // → DateTime, Date format
// Plain text (no detection pattern)
{"op": "put", "ref": "A4", "value": "Hello"} // → TextDisable Detection: Set "detect": false to treat strings as plain text:
{"op": "put", "ref": "A1", "value": "$99.00", "detect": false} // → Text "$99.00"
{"op": "put", "ref": "A2", "value": "59.4%", "detect": false} // → Text "59.4%"Formula Dragging (putf with range):
// Single formula dragged across range (uses Excel $ anchoring)
{"op": "putf", "ref": "B2:B10", "value": "=SUM($A$1:A2)", "from": "B2"}
// Explicit formulas for each cell (no dragging)
{"op": "putf", "ref": "B2:B4", "values": ["=A2*2", "=A3*2", "=A4*2"]}Style Options:
| Option | Type | Description |
|---|---|---|
bold |
boolean | Bold text |
italic |
boolean | Italic text |
underline |
boolean | Underlined text |
bg |
string | Background color (hex: #FF0000) |
fg |
string | Font color (hex: #0000FF) |
fontSize |
number | Font size in points |
fontName |
string | Font family name |
align |
string | Horizontal alignment: left, center, right, justify |
valign |
string | Vertical alignment: top, middle, bottom |
wrap |
boolean | Enable text wrapping |
numFormat |
string | Number format (see Format Options above) |
border |
string | All borders: none, thin, medium, thick |
borderTop |
string | Top border style |
borderRight |
string | Right border style |
borderBottom |
string | Bottom border style |
borderLeft |
string | Left border style |
borderColor |
string | Border color (hex) |
replace |
boolean | Replace style instead of merge (default: false) |
Note: Use align for horizontal alignment, not halign. Unknown properties are ignored with a warning.
Examples:
# From file
xl -f input.xlsx -o output.xlsx batch operations.json
# From stdin (pipe)
echo '[{"op": "put", "ref": "A1", "value": 100, "format": "currency"}]' | \
xl -f input.xlsx -s Sheet1 -o output.xlsx batch -
# Complex workflow
cat <<'EOF' | xl -f input.xlsx -s Sheet1 -o output.xlsx batch -
[
{"op": "put", "ref": "A1", "value": "Revenue", "format": "text"},
{"op": "put", "ref": "B1", "value": 1000000, "format": "currency"},
{"op": "style", "range": "A1:B1", "bold": true, "bg": "#FFFF00"},
{"op": "merge", "range": "A1:B1"},
{"op": "colwidth", "col": "A", "width": 20}
]
EOF| Scenario | Behavior | Workaround |
|---|---|---|
Leading zeros: "00123" |
Smart detection converts to number 123 |
Use "detect": false to preserve as text |
Mixed patterns: "50 (50%)" |
First pattern wins (treated as text) | Use explicit "format" field |
values array length mismatch |
Error raised if array length ≠ range cell count | Ensure exact match |
| Percent as decimal | "59.4%" stored as 0.594 |
Excel displays correctly with percent format |
| Invalid custom formats | Accepted but may render incorrectly in Excel | Test format codes in Excel first |
--stream mode |
Supports formula dragging but not formula evaluation | Use non-streaming for --eval |
Always include explicit references in output:
# Good - References visible
| | A | B |
|---|----------|---------|
| 1 | Revenue | $1M |
| 2 | COGS | $400K |Error: <ErrorType>
Location: <Context>
Details: <Human-readable explanation>
Suggestion: <How to fix>
Example:
Error: CircularReference
Location: B10
Details: Formula =A10+B10 creates cycle: B10 → A10 → B10
Suggestion: Use a different cell reference to break the cycle
- Quick Start Guide — Library usage
- Performance Guide — Streaming for large files
- GitHub Issues — Feature requests and bug reports