sheet service (host half of Spreadsheets)
svc_sheet.py registers service sheet in the hub (one line in hostd.py build_hub). It drives GridCraft (gridcraft-cli mcp,
MIT OR Apache-2.0, alpha, third party) as one long-lived process. The workbook lives in that process; the guest only paints and types.
Wire is the hub's: REQ id sheet token op arg... -> RES id OK + records (TAB fields, \t \n \\ escaped, ASCII only).
Ops and replies (args are strings)#
| op | args | reply records |
|---|---|---|
open |
file in workspace, or budget/sales/grades |
OPEN src title nsheets active dirty, then SH idx name used r0 c0 nrows ncols per sheet |
new |
[sample] |
same as open |
sheets / activate |
[name or index] |
SH records |
read |
range [sheet] (empty range = used range) |
RNG sheet range r0 c0 nrows ncols truncated, R rownum cell..., then ST records |
cell |
A1 [sheet] |
CELL ref input shown type flags bold italic (input is the formula text) |
eval |
formula, = optional (needs no open sheet) |
EVAL type text (errors: type e, text #DIV/0!; a bad formula is ERR) |
set |
cell text (text starting = is a formula) |
SET ref shown nsheets capped, then CH ref shown for every cell whose display changed (dependents included, max 200) |
format |
range bold=1 italic=0 fill=RRGGBB\|none align=left\|center\|right |
FMT range options |
save / export_csv |
path.xlsx / path.csv\|tsv [sheet] |
SAVED relpath bytes; first try returns APPROVE fs:write:<abs path> |
close, status |
CLOSED; STAT binary_found running pid rss_kb open title workspace |
Read layout. A window is at most 100 rows x 26 columns. Row record R n f1 f2 ...: field j is column c0+j; an empty cell is an
empty field and trailing empty fields are cut. A field is one type char then the shown text (30 chars max, ASCII): s text,
n number, c currency, p percent, d date/time, b bool, e error. Numbers are not re-sent as raw values; use cell for the
formula/input. ST dcol drow flags align fill is sent only for cells that differ from the default (flags bit1 bold, bit2 italic;
align l c r, default r for numbers else l; fill RRGGBB or -); dcol/drow are offsets in the window. Styles come from the
engine's edit.copy html; if the window has merged cells the ST records are omitted.
Config (config.json, git-ignored; see config.example.json)#
gridcraft_cli absolute path (else env GRIDCRAFT_CLI, else PATH; the service answers ERR "gridcraft-cli was not found" without it),
sheet_workspace (default ~/.cache/kosmos-hostd/sheets, mode 0700), sheet_timeout_s (default 10, per engine call).
Security#
- Engine always under
sandbox-exec -p <profile>(profile built in memory bymake_profile, not a file):deny network*, writes only in the workspace, no reads under /Users or $HOME except the workspace and the binary's folder;env -i, HOME and TMPDIR in<workspace>/.sandbox/, cwd there. Tests prove a rawopen_workbook ~/.zshrc, a write to~, andmcp --connect <local port>fail. - GridCraft MCP has no root confinement (docs/mcp.md), so the service checks every path first: no
.., no hidden component, no symlink at any level, must be inside the workspace, extension xlsx/xlsm/csv/tsv/json (save: xlsx, export: csv/tsv), 20 MB read limit. It exposes fixed ops only, neverexecute_command. - File writes ask the guest (APPROVE naming the full path); the approval is used up by that one write.
set/formatneed none. - Limits: arg 1000 chars, window 100x26, engine line 4 MB, call timeout 10 s (the engine is killed, next call restarts it), at most
5 starts a minute. Supervision: started on first call; a dead or timed-out engine is replaced and the guest is told once that
unsaved edits were lost;
close()and atexit kill only the Popen pid.sys hellocloses the workbook.
Measured (M-series Mac, gridcraft-cli 0.12.1, tests/bench_sheet.py)#
First call (start + new + eval) 50 ms; open sample 8 ms; read 20x12 2.4 ms; set with changed cells 0.8 ms; save 5 ms; reopen 2 ms. RSS 4.5 MB at start, 15 MB after the bench. Reply for a dense 20x12 window (240 cells, bold and fill rows): 2595 bytes; the budget sample: 923 bytes. Compare the hub's own cost: about 0.6 s for 20 KB on the serial link.
What the CLI cannot do (or we do not use)#
No bulk style read (we parse edit.copy html; fonts, borders, number-format strings are not sent). No headless PNG; charts are not
rendered (the sheet reports them in inspect_workbook only). Save writes xlsx only; xlsb/ods/xls open via gridcraft-cli convert,
not this service yet. Sheet-qualified style reads work on the active sheet only. Alpha: function coverage is self-reported.
Tests: python3 -I tests/test_sheet.py (fake engine always, real with GRIDCRAFT_CLI=...).