hsx CLI
Setup
Before using hsx, check it's available. If not, install the package and its system dependencies (Cairo/Pango for canvas):
which hsx 2>/dev/null || npm install -g @hewliyang/headless-spreadjs
Commands
All operations are bash calls. Formulas recalculate instantly (no sync step needed).
Global options:
--timeout <seconds>(default:30)--no-daemon(run command directly bypassing daemon)
hsx create file.xlsx # new workbook
hsx info file.xlsx # workbook metadata
hsx get file.xlsx "Sheet1!A1:C10" # read cells → JSON
hsx csv file.xlsx "Sheet1!A1:C10" [--mode value|formula|both] [--formulas]
hsx set file.xlsx "Sheet1!A1:C3" '<json>' # write cells
hsx clear file.xlsx "A1:C10" [--type all|styles] # clear values (default), all, or styles
hsx search file.xlsx "term" [--sheet S] [--regex] # find values
hsx copy file.xlsx "A1:C1" "A10:C10" # copy range (formulas + styles)
hsx diff left.xlsx right.xlsx [--inline-limit N] [--preview-limit N] # compare value + formula changes
hsx deps file.xlsx "Sheet1!A1" [--depth N|--recursive] # precedents (upstream)
hsx refs file.xlsx "Sheet1!A1" [--depth N|--recursive] [--max-formulas N] # dependents (downstream)
hsx sheet file.xlsx list|create|delete|rename # manage sheets
hsx rc file.xlsx insert|delete|hide|freeze rows|columns [--ref R] [--count N]
hsx resize file.xlsx [--columns A:D] [--width N] [--rows 1:10] [--height N]
hsx objects file.xlsx [--sheet Name] # list charts, tables, pivots
hsx screenshot file.xlsx [ref] [-o out.png] # screenshot workbook or range as PNG
hsx eval file.xlsx "code" # arbitrary JS
hsx daemon start|stop|status|flush # daemon lifecycle
References use A1 notation: Sheet1!A1:C10, A1:C10 (active sheet), or A1 (single cell).
Dependency tracing defaults to one-hop (--depth 1). Use --depth N for bounded multi-hop traversal or --recursive (shorthand for --depth 50).
Write
hsx set file.xlsx "A1:B3" '[
[{"value":"Name","style":{"fontStyle":{"bold":true}}}, {"value":"Qty","style":{"fontStyle":{"bold":true}}}],
[{"value":"Alice"}, {"value":4}],
[{"value":"Bob"}, {"formula":"=B2+1"}]
]'
Each cell: {"value": ...}, {"formula": "=..."}, optional "style": {...}.
style (SpreadJS-style):
fontStyle:{ bold?: boolean, italic?: boolean, underline?: boolean }fontSize: CSS size string (e.g."12px")fontFamilyforeColorbackColorhAlign: SpreadJSHorizontalAlignenum valueformatter
Read
hsx get file.xlsx "A1:B3"
# → {"cells":{"A1":{"value":"Name","style":{"fontStyle":{"bold":true}}},"B3":{"value":5,"formula":"B2+1"}}, ...}
hsx csv file.xlsx "A1:B3"
# → Name,Qty
# Alice,4
# Bob,5
hsx csv file.xlsx "A1:B3" --mode formula
hsx csv file.xlsx "A1:B3" --mode both
Screenshot
hsx screenshot file.xlsx
hsx screenshot file.xlsx "Sheet1!A1:F20"
hsx screenshot file.xlsx "Summary!A1:H30" -o summary.png
Screenshots render the full workbook (default) or a specific range to PNG.
Search
hsx search file.xlsx "Alice" # case-insensitive across all sheets
hsx search file.xlsx "^Q[1-4]$" --regex # regex
hsx search file.xlsx "Revenue" --sheet Summary # single sheet
Copy / Diff
hsx copy file.xlsx "A1:C1" "A1:C10" # repeat header pattern down
hsx copy file.xlsx "Sheet1!A1:B5" "Sheet2!A1:B5" # cross-sheet
hsx diff old.xlsx new.xlsx
hsx diff old.xlsx new.xlsx --inline-limit 1000 --preview-limit 50
hsx diff output is JSON with a top-level summary string plus counts (changedCells, comparedCells).
outputMode: "inline"→ full diff rows indiffsoutputMode: "tmpfile"→ preview rows indiffs, full NDJSON atdiffFile(grep-friendly)
# inspect spilled diff
jq -r '.diffFile' diff-output.json
rg '"sheet":"Sheet1"' "$(jq -r '.diffFile' diff-output.json)"
Dependency tracing (deps / refs)
# Direct precedents (what Sheet3!A1 reads)
hsx deps model.xlsx "Sheet3!A1"
# Multi-hop precedents
hsx deps model.xlsx "Sheet3!A1" --depth 2
# Direct dependents (what reads Sheet1!A1)
hsx refs model.xlsx "Sheet1!A1"
# Recursive dependents with a safety cap for large files
hsx refs model.xlsx "Sheet1!A1" --recursive --max-formulas 300000
Output is JSON and includes hop counts plus stats. This is best-effort for dynamic formulas (e.g. INDIRECT, OFFSET).
Sheets
hsx sheet file.xlsx list
hsx sheet file.xlsx create "Revenue"
hsx sheet file.xlsx rename "Sheet1" "Data"
hsx sheet file.xlsx delete "OldSheet"
Rows & Columns
hsx rc file.xlsx insert rows --ref 5 --count 3 # insert 3 rows at row 5
hsx rc file.xlsx delete columns --ref B --count 2 # delete columns B-C
hsx rc file.xlsx hide rows --ref 10 # hide row 10
hsx rc file.xlsx freeze rows --ref 1 # freeze top row
hsx rc file.xlsx unfreeze rows # unfreeze
Resize
hsx resize file.xlsx --columns A:D --width 120
hsx resize file.xlsx --rows 1:1 --height 30
hsx resize file.xlsx --columns A --width 200 --sheet Revenue
Eval (escape hatch)
For charts, sparklines, pivot tables, conditional formatting — anything the structured commands don't cover.
hsx eval file.xlsx "
// Globals: workbook, sheet (active), GC, file, range(ref)
sheet.charts.add('Revenue', GC.Spread.Sheets.Charts.ChartType.line,
0, 120, 500, 300, 'A1:E6');
"
# A1 notation helper: range(ref) returns native SpreadJS Range
hsx eval file.xlsx "
range('B2').value(96); // active sheet
range("'Data Sheet'!C5").formula('B2*2'); // quoted sheet names supported
"
hsx eval file.xlsx "
const data = workbook.getSheetFromName('Data');
data.tables.add('SalesTable', 0, 0, 10, 4,
GC.Spread.Sheets.Tables.TableThemes.medium2);
"
# Read values back
hsx eval file.xlsx "
console.log('Sheets:', workbook.getSheetCount());
return sheet.getValue(0, 0);
"
Stdin also works: echo "return sheet.getValue(0,0)" | hsx eval file.xlsx
SpreadJS API Reference
When using hsx eval, you have access to the full SpreadJS API via the GC namespace. If you need to look up a specific API (enum values, method signatures, class properties), grep the type definitions:
# Find an enum or class
grep -n "enum ChartType" ./spreadjs.d.ts
grep -n "class Worksheet" ./spreadjs.d.ts
# Find methods on a class (read nearby lines for signatures)
grep -n "addRows\|deleteRows\|addColumns" ./spreadjs.d.ts
# Find available style properties
grep -n "fontWeight:\|foreColor:\|backColor:\|formatter:" ./spreadjs.d.ts
# Find conditional formatting APIs
grep -n "conditionalFormats\|ConditionalFormatting" ./spreadjs.d.ts
The file is at ./spreadjs.d.ts. Use grep -n to find what you need, then read with offset to see full signatures and JSDoc examples.
Best Practices
- Use formulas, not hardcoded computed values:
{"formula":"=SUM(A1:A9)"}not{"value":45} - Keep each
setcall focused; build incrementally across multiple calls - Use
hsx getorhsx csvto verify after writes - Use
hsx deps/hsx refsfor repeatable lineage checks; useevalfor custom one-offs - Prefer uniform column widths; use empty columns for indentation
- Always specify units in headers:
Revenue ($mm),Growth (%)
Modern Excel Formula Guidelines (365/2022+)
Prefer dynamic arrays & spill formulas over helper columns and copy-down formulas.
| Instead of... | Use... |
|---|---|
| INDEX/MATCH | XLOOKUP or XMATCH |
| MOD + MATCH wrapping logic | VSTACK + DROP + TAKE to rearrange arrays |
| Copying formulas down rows | A single spill formula (FILTER, SORT, UNIQUE, SEQUENCE) |
| Nested IF chains | IFS, SWITCH, or FILTER |
| Helper columns for intermediate values | LET to name intermediate steps |
| IFERROR(VLOOKUP(...)) | XLOOKUP (has built-in if_not_found arg) |
| SUMPRODUCT hacks for conditional logic | FILTER + SUM, or BYROW/BYCOL |
| VBA / repeated formulas for transforms | MAP, REDUCE, LAMBDA |
| Manual array reshaping | WRAPCOLS, WRAPROWS, TOCOL, TOROW |
CONCATENATE or & in loops | TEXTJOIN or TEXTSPLIT |
| INDEX(range, MATCH(...), MATCH(...)) | CHOOSECOLS / CHOOSEROWS |
Rules:
- Always use LET when a sub-expression appears more than once.
- Prefer one spilling formula over N copied formulas.
- Use LAMBDA + names for reusable logic instead of complex nested formulas.
- Use VSTACK/HSTACK to combine arrays instead of building results cell-by-cell.
Your formulas must be human-readable.