# Bilig llms-full

> Full agent context for Bilig, a formula WorkPaper runtime for Node services, MCP clients, and coding-agent workbook tools.

Repository: https://github.com/proompteng/bilig
Site: https://proompteng.github.io/bilig/
npm: https://www.npmjs.com/package/@bilig/workpaper
Agent instructions: https://proompteng.github.io/bilig/AGENTS.md
Skill manifest: https://proompteng.github.io/bilig/skill.txt
Compact index: https://proompteng.github.io/bilig/llms.txt

## Generated Agent Instructions
# Bilig Agent Instructions

Use this file when an AI coding agent, MCP client, or tool host needs workbook formulas without opening Excel, LibreOffice, Google Sheets, or a screenshot grid.

## Discovery Order

1. Read `https://proompteng.github.io/bilig/llms.txt` for the compact map.
2. Read `https://proompteng.github.io/bilig/llms-full.txt` when you need enough context to implement a workflow without searching the whole site.
3. Read `https://proompteng.github.io/bilig/skill.txt` when your agent supports skill manifests.
4. Start the MCP server or import `@bilig/workpaper` directly.

Remote MCP clients that support Streamable HTTP can smoke-test the stateless
demo endpoint at `https://bilig.proompteng.ai/mcp`. Directory scanners that inspect the
hosted endpoint origin can read the same-origin server card at
`https://bilig.proompteng.ai/.well-known/mcp/server-card.json`. Local agents that need writable project files should
still prefer the file-backed stdio command below.

## Default Decision

Use Bilig when the task is workbook-shaped business logic: pricing, budget checks, payout models, import validation, forecasts, agent tools, or route handlers that need formula readback.

Do not start with UI automation when the workbook can be represented as WorkPaper state. Screenshots are useful for human review; they are not formula truth.

## Required Proof

Before reporting success, return a compact proof object with:

- `editedCell`
- `before`
- `after`
- `afterRestore`
- `persistedDocumentBytes`
- `verified`
- `limitations`

Do not claim success from a write call alone. The proof is computed readback plus persisted state.

## Fast Commands

```sh
npm exec --package @bilig/workpaper@0.40.43 -- bilig-agent-challenge
npm exec --package @bilig/workpaper@0.40.43 -- bilig-mcp-challenge
npm exec --package @bilig/workpaper@0.40.43 -- bilig-workpaper-mcp --workpaper ./pricing.workpaper.json --init-demo-workpaper --writable
npm exec --package @bilig/workpaper@0.40.43 -- bilig-formula-clinic ./reduced.xlsx --cells "Summary!B7,Inputs!B2"
```

Claude Desktop users can install the released MCPB bundle from:

- https://github.com/proompteng/bilig/releases/download/libraries-v0.40.43/bilig-workpaper.mcpb
- https://github.com/proompteng/bilig/releases/download/libraries-v0.40.43/bilig-workpaper.mcpb.sha256

## Direct TypeScript

Use `WorkPaper.buildFromSheets()` for hand-authored models, `setCellContents()` for edits, `getCellDisplayValue()` for readback, and `exportWorkPaperDocument()` plus `serializeWorkPaperDocument()` for persistence proof.

## Boundaries

Keep Excel, LibreOffice, Microsoft Graph, or an oracle harness in the loop when the workbook depends on macros, pivots, charts, external links, unsupported functions, locale-specific Excel behavior, or exact desktop UI behavior.

## Generated Skill Manifest
---
name: bilig-workpaper
version: 0.1.0
description: Use @bilig/workpaper WorkPaper state for workbook formulas, agent spreadsheet tools, MCP file-backed or remote demo editing, and XLSX formula bug reports without driving spreadsheet UI.
tags:
  - ai-agents
  - spreadsheet-automation
  - formulas
  - xlsx
  - mcp
  - typescript
---

# Bilig WorkPaper Agent Skill

Use this skill when an agent needs spreadsheet-style formulas but the work should run through files, terminal commands, TypeScript, HTTP routes, or MCP tools instead of Excel UI automation.

## When To Trigger

Trigger this skill for tasks involving:

- workbook-shaped business logic in Node.js services;
- formula readback after writing cells;
- quote, budget, payout, pricing, import-validation, or forecast models;
- agent spreadsheet tools that need deterministic cell addresses;
- MCP clients that can run a stdio server or call a Streamable HTTP endpoint;
- reduced XLSX formula bugs that need a paste-ready report.

Do not trigger it for manual spreadsheet editing, Office macros, VBA, pivots, charts, COM automation, or exact Excel desktop behavior unless the user explicitly asks to compare Bilig against an Excel oracle.

## Command Safety

Do not build shell commands by concatenating user text. Treat the commands below as literal templates, validate workbook paths before use, and reject values containing newlines, backticks, `$(`, `;`, `&`, `|`, `<`, or `>`. Prefer MCP client `command` plus `args` arrays or direct TypeScript calls when inserting user-provided paths or cell references.

## First Choice: MCP

Use MCP when the host can run a stdio server or call a Streamable HTTP server.
Configure stdio as an argument array, not a shell-concatenated string:

Before wiring a client, an agent can prove the direct WorkPaper loop with:

```json
{
  "command": "npm",
  "args": ["exec", "--package", "@bilig/workpaper@0.40.43", "--", "bilig-agent-challenge"]
}
```

For the actual file-backed MCP path, run the package-owned challenge first:

```json
{
  "command": "npm",
  "args": ["exec", "--package", "@bilig/workpaper@0.40.43", "--", "bilig-mcp-challenge"]
}
```

```json
{
  "command": "npm",
  "args": [
    "exec",
    "--package",
    "@bilig/workpaper@0.40.43",
    "--",
    "bilig-workpaper-mcp",
    "--workpaper",
    "./pricing.workpaper.json",
    "--init-demo-workpaper",
    "--writable"
  ]
}
```

The useful file-backed tools are:

- `list_sheets`
- `read_range`
- `read_cell`
- `set_cell_contents`
- `get_cell_display_value`
- `export_workpaper_document`
- `validate_formula`

After a write, always read the dependent output cell and export the WorkPaper document.

For remote MCP clients, use the stateless demo endpoint when the client supports
Streamable HTTP:

```text
https://bilig.proompteng.ai/mcp
https://bilig.proompteng.ai/mcp/workpaper
```

The remote endpoint is request-local and does not write user files. Use it for
connector smoke tests, tool discovery, and agent onboarding; use the file-backed
stdio command when the workflow must persist a project WorkPaper JSON file.

## Second Choice: Direct TypeScript

Use `@bilig/workpaper` directly when workbook logic belongs in a service, queue worker, test, or route:

```ts
import { WorkPaper, exportWorkPaperDocument, serializeWorkPaperDocument } from '@bilig/workpaper'

const workbook = WorkPaper.buildFromSheets({
  Inputs: [
    ['Metric', 'Value'],
    ['Customers', 20],
    ['Average revenue', 1200],
  ],
  Summary: [
    ['Metric', 'Value'],
    ['Revenue', '=Inputs!B2*Inputs!B3'],
  ],
})

const inputs = workbook.getSheetId('Inputs')
const summary = workbook.getSheetId('Summary')
if (inputs === undefined || summary === undefined) {
  throw new Error('Workbook is missing required sheets')
}

workbook.setCellContents({ sheet: inputs, row: 1, col: 1 }, 32)
const revenue = workbook.getCellDisplayValue({ sheet: summary, row: 1, col: 1 })
const saved = serializeWorkPaperDocument(exportWorkPaperDocument(workbook, { includeConfig: true }))

console.log({ revenue, savedBytes: saved.length })
```

## XLSX Formula Clinic

When the user has a reduced XLSX formula/import bug, generate a local report through an argument array:

```json
{
  "command": "npm",
  "args": [
    "exec",
    "--package",
    "@bilig/workpaper@0.40.43",
    "--",
    "bilig-formula-clinic",
    "./reduced.xlsx",
    "--cells",
    "Summary!B7,Inputs!B2"
  ]
}
```

The report is local. It does not upload workbook contents. Ask for a reduced public fixture rather than private customer spreadsheets.

## Required Verification

Return proof, not vibes. A successful agent response should include:

- the exact edited sheet and A1 cell;
- before values for relevant inputs and dependent outputs;
- after values read from the recalculated workbook;
- persistence evidence from serialized or exported WorkPaper state;
- restore or reimport proof when file boundaries matter;
- limitations for unsupported formulas or Excel-only features.

If any proof step fails, report the blocker instead of claiming the workbook was updated.

## Reference URLs

- Compact docs map: https://proompteng.github.io/bilig/llms.txt
- Full agent context: https://proompteng.github.io/bilig/llms-full.txt
- Agent handbook: https://proompteng.github.io/bilig/headless-workpaper-agent-handbook.html
- Agent workbook challenge: https://proompteng.github.io/bilig/agent-workbook-challenge.html
- MCP server guide: https://proompteng.github.io/bilig/mcp-workpaper-tool-server.html
- XLSX formula clinic: https://proompteng.github.io/bilig/formula-bug-clinic.html
- Compatibility limits: https://proompteng.github.io/bilig/where-bilig-is-not-excel-compatible-yet.html
- Repository: https://github.com/proompteng/bilig

---

## Repository README

Source: https://github.com/proompteng/bilig/blob/main/README.md

# bilig

[![CI](https://github.com/proompteng/bilig/actions/workflows/ci.yml/badge.svg)](https://github.com/proompteng/bilig/actions/workflows/ci.yml)
[![GitHub Repo stars](https://img.shields.io/github/stars/proompteng/bilig?style=social)](https://github.com/proompteng/bilig/stargazers)
[![npm: @bilig/workpaper](https://img.shields.io/npm/v/@bilig/workpaper?label=%40bilig%2Fworkpaper)](https://www.npmjs.com/package/@bilig/workpaper)
[![npm: @bilig/xlsx-formula-recalc](https://img.shields.io/npm/v/@bilig/xlsx-formula-recalc?label=%40bilig%2Fxlsx-formula-recalc)](https://www.npmjs.com/package/@bilig/xlsx-formula-recalc)
[![npm weekly downloads](https://img.shields.io/npm/dw/@bilig/headless?label=%40bilig%2Fheadless%20downloads)](https://www.npmjs.com/package/@bilig/headless)
[![CodeQL](https://github.com/proompteng/bilig/actions/workflows/codeql.yml/badge.svg)](https://github.com/proompteng/bilig/actions/workflows/codeql.yml)
[![OpenSSF Scorecard](https://api.scorecard.dev/projects/github.com/proompteng/bilig/badge)](https://scorecard.dev/viewer/?uri=github.com/proompteng/bilig)
[![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](LICENSE)

**Formula workbooks for Node services and agent tools.**

Use [`@bilig/workpaper`](https://www.npmjs.com/package/@bilig/workpaper) when a
calculation is easiest to review as cells and formulas, but it has to run in a
Node service, queue worker, serverless route, test, or coding-agent tool. Use
[`@bilig/xlsx-formula-recalc`](https://www.npmjs.com/package/@bilig/xlsx-formula-recalc)
when the immediate problem is "I changed XLSX inputs in Node and need the
formula results now," including SheetJS / `xlsx` pipelines that already produce
XLSX bytes. Use
[`@bilig/exceljs-formula-recalc`](https://www.npmjs.com/package/@bilig/exceljs-formula-recalc)
when the workbook is already moving through ExcelJS.

[`@bilig/headless`](https://www.npmjs.com/package/@bilig/headless) remains the
full lower-level runtime package with bundled agent metadata. The unscoped
packages remain published as compatibility and search aliases, but scoped
`@bilig/*` packages are the canonical install path.

It gives you a `WorkPaper`: build sheets, write inputs, recalculate, read the
cell value, and save the workbook as JSON. No browser grid is involved.
The published package also carries `AGENTS.md` and `SKILL.md` so coding agents
inspecting `node_modules/@bilig/workpaper` can find the write/read/persist loop
locally. The public docs expose the same path through
[`AGENTS.md`](docs/AGENTS.md), [`skill.md`](docs/skill.md),
[`docs/.well-known/agent.json`](docs/.well-known/agent.json),
[`AI spreadsheet agent tool`](docs/ai-agent-spreadsheet-tool-node.md), and
[`llms-full.txt`](docs/llms-full.txt).

Good fits: pricing rules, budget checks, payout models, import validation, and
agent tools that need read-after-write proof. Bad fits: manual spreadsheet
editing, Office macros, desktop Excel automation, or one-off arithmetic where a
workbook would be ceremony.

Project site: <https://proompteng.github.io/bilig/>

## n8n Formula Readback

Need an n8n workflow to write workbook inputs, recalculate formulas, and verify
the computed value before the workflow continues? Start with the no-install
proof workflow:

```text
examples/n8n-workpaper-formula-readback/bilig-workpaper-formula-readback.n8n.json
```

Or hit the hosted proof route directly:

```sh
curl -sS -X POST https://bilig.proompteng.ai/api/workpaper/n8n/forecast \
  -H 'content-type: application/json' \
  --data '{"sheetName":"Inputs","address":"B3","value":0.4}'
```

Use this when n8n owns the workbook/calculation state and needs formula-backed
readback without opening Excel, LibreOffice, Google Sheets, or a browser UI. Do
not use it as a patch for Microsoft Excel 365 / Graph append-row behavior or
for making n8n's built-in XLSX writer reinterpret text as formulas.

See [n8n WorkPaper formula readback](docs/n8n-workpaper-formula-readback.md)
for the proof shape, import steps, and limits.

## Which Package Should I Install?

| Problem you have right now                                                                      | Install                                      | First proof                                                                                  |
| ----------------------------------------------------------------------------------------------- | -------------------------------------------- | -------------------------------------------------------------------------------------------- |
| Formula workbook state inside a Node service or agent tool                                      | `npm install @bilig/workpaper`                       | [90-second Node quickstart](docs/try-bilig-headless-in-node.md)                        |
| AI agent needs to edit workbook inputs and verify formula readback                              | `npm create @bilig/workpaper@latest pricing-agent -- --agent` | [AI spreadsheet agent tool](docs/ai-agent-spreadsheet-tool-node.md)    |
| SheetJS / `xlsx` pipeline returns stale formula values after input edits                        | `npm install @bilig/sheetjs-formula-recalc`          | [SheetJS formula result not updating](docs/sheetjs-formula-result-not-updating-node.md) |
| Generic XLSX bytes changed in Node; formula outputs must refresh before returning               | `npm install @bilig/xlsx-formula-recalc`             | [XLSX formula recalculation in Node.js](docs/xlsx-formula-recalculation-node.md)       |
| Existing ExcelJS workflow needs recalculated values, not stale cached results                   | `npm install exceljs @bilig/exceljs-formula-recalc`  | [ExcelJS formula recalculation in Node.js](docs/exceljs-formula-recalculation-node.md) |
| Full runtime package with agent metadata, MCP binary, provenance docs, and lower-level subpaths | `npm install @bilig/headless`                        | [npm provenance and package trust](docs/npm-provenance-package-trust.md)               |

### Stale XLSX Formula Values? Run This First

If a Node job already has an XLSX file and only needs fresh formula values
before returning, use the file-level recalculation package before evaluating
the broader WorkPaper runtime:

```sh
npx --package @bilig/sheetjs-formula-recalc sheetjs-recalc --demo --json

npx --package @bilig/xlsx-formula-recalc xlsx-recalc --demo --json

npx --package @bilig/xlsx-formula-recalc xlsx-recalc quote.xlsx \
  --set Inputs!B2=42 \
  --read Summary!B7 \
  --out quote.recalculated.xlsx \
  --json
```

If the workbook is already in ExcelJS, keep that boundary and add
`@bilig/exceljs-formula-recalc`:

```sh
npm install exceljs @bilig/exceljs-formula-recalc
npx --package @bilig/exceljs-formula-recalc exceljs-recalc --demo --json
```

For one checkout proof across SheetJS/`xlsx`, `xlsx-populate`, and ExcelJS:

```sh
npm --prefix examples/recalc-bridge-workflows install
npm --prefix examples/recalc-bridge-workflows run smoke
```

## Choose An Evaluation Path

| If you are evaluating...      | Start here                                                                                                                                                                                                                | What should be true before you star, watch, or adopt                                                   |
| ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------ |
| Basic fit                     | [Why use Bilig?](docs/why-use-bilig.md)                                                                                                                                                                                   | The problem is workbook-shaped business logic that needs API readback and persistence.                 |
| Published npm package         | [90-second Node quickstart](docs/try-bilig-headless-in-node.md)                                                                                                                                                           | `@bilig/workpaper` edits one input, recalculates, persists JSON, restores, and prints `verified: true`. |
| XLSX or ExcelJS recalculation | [XLSX formula recalculation](docs/xlsx-formula-recalculation-node.md) and [ExcelJS formula recalculation](docs/exceljs-formula-recalculation-node.md)                                                                     | The package updates inputs, reads recalculated values, and exports or mutates the workbook boundary.   |
| Backend service shape         | [Quote approval WorkPaper API](docs/quote-approval-workpaper-api.md)                                                                                                                                                      | A realistic route-style workflow returns formula readback and `restoredMatchesAfter: true`.            |
| Agent or MCP tools            | [Headless WorkPaper agent handbook](docs/headless-workpaper-agent-handbook.md), [MCP spreadsheet tool server](docs/mcp-workpaper-tool-server.md), and [Claude Desktop MCPB bundle](docs/claude-desktop-mcpb-workpaper.md) | The agent installs a tool path, gets a copy-paste handoff prompt, then proves write/readback/persist.  |
| Agent-owned XLSX files        | [Agent XLSX recalculation without LibreOffice](docs/agent-xlsx-formula-recalculation-without-libreoffice.md)                                                                                                              | A tool can edit XLSX inputs, recalculate, export, reimport, and return `verified: true`.               |
| Public technical review       | [Show HN maintainer note](docs/show-hn-formula-workbooks-node-services.md)                                                                                                                                                | One shareable page has the npm check, benchmark caveat, known limits, and feedback ask.                |
| Trust and performance         | [npm provenance](docs/npm-provenance-package-trust.md) and [benchmark evidence](docs/what-workpaper-benchmark-proves.md)                                                                                                  | npm shows SLSA provenance, and benchmark claims match the checked artifact.                            |
| Almost a fit                  | [adoption blocker form](https://github.com/proompteng/bilig/discussions/new?category=general)                                                                                                                             | Name the formula, import/export, persistence, framework, MCP, package, or benchmark gap.               |
| Formula or XLSX bug           | [formula bug clinic](docs/formula-bug-clinic.md)                                                                                                                                                                          | Share a reduced public case that can become a test, example, corpus fixture, or docs proof.            |
| Real workbook blocked         | [submit a workbook fixture](docs/submit-workbook-fixture.md)                                                                                                                                                              | Use the structured form when a reduced workbook is ready.                                              |

Reduced workbook already in hand? Generate the paste-ready fixture report in
one command:

```sh
npm exec --package @bilig/headless@0.40.43 -- bilig-formula-clinic ./reduced.xlsx --cells "Summary!B7,Inputs!B2"
```

Handing a spreadsheet task to another coding agent? Start with the
[agent handoff prompt](docs/headless-workpaper-agent-handbook.md#copy-paste-prompt-for-another-agent)
before opening Excel, LibreOffice, Google Sheets, or a screenshot UI.
To prove the package-owned agent loop without cloning the repo or downloading a
TypeScript file:

```sh
npm exec --package @bilig/headless@0.40.43 -- bilig-agent-challenge
npm exec --package @bilig/headless@0.40.43 -- bilig-mcp-challenge
```

Agent tools that support skill manifests can start from
[`skill.md`](docs/skill.md) or the well-known index at
[`docs/.well-known/agent-skills/index.json`](docs/.well-known/agent-skills/index.json).
Claude Desktop users can also install the released MCPB bundle directly:
<https://github.com/proompteng/bilig/releases/download/libraries-v0.40.43/bilig-workpaper.mcpb>.
If you need a copy-paste eval for another tool host, use the
[agent workbook challenge](docs/agent-workbook-challenge.md): one input edit,
one dependent formula readback, one serialized restore, and a `verified: true`
proof object.

<p align="center">
  <img src="docs/assets/github-social-preview.png" alt="bilig headless workbook runtime for formulas in TypeScript" />
</p>

## Try It In 90 Seconds

This uses the published npm package. It builds a workbook, changes one input,
reads the calculated value, saves JSON, restores the workbook, and prints the
same value again.

```sh
mkdir bilig-headless-eval
cd bilig-headless-eval
npm init -y
npm pkg set type=module
npm install @bilig/workpaper
npm install -D tsx typescript @types/node
curl -fsSLo quickstart.ts https://proompteng.github.io/bilig/npm-eval.ts
npx tsx quickstart.ts
```

Expected output:

```json
{
  "before": 24000,
  "after": 38400,
  "afterRestore": 38400,
  "sheets": ["Inputs", "Summary"],
  "bytes": 999,
  "verified": true,
  "nextStep": "If this proof matches your service or agent workflow, star or bookmark Bilig: https://github.com/proompteng/bilig/stargazers"
}
```

The TypeScript file is maintained in
[`examples/headless-workpaper/npm-eval.ts`](examples/headless-workpaper/npm-eval.ts).
The exact byte count can change between package versions; `verified: true` and
matching `after`/`afterRestore` values are the check.

For a route-shaped quote approval API today, run the maintained example:

```sh
git clone --depth 1 https://github.com/proompteng/bilig.git
cd bilig
pnpm --dir examples/serverless-workpaper-api install --ignore-workspace
pnpm --dir examples/serverless-workpaper-api run smoke
```

For a generated project from a blank directory, run
`npm create @bilig/workpaper@latest pricing-workpaper` through the
`@bilig/create-workpaper` package. The package source lives in
[`packages/create-workpaper`](packages/create-workpaper), and the publish gate
is documented in [create a Bilig WorkPaper starter](docs/create-bilig-workpaper.md).
For an agent-ready project with `AGENTS.md`, MCP client configs, and an
`agent:verify` script, run
`npm create @bilig/workpaper@latest pricing-agent -- --agent`.

If that proof matches a service or agent workflow you maintain, the useful next
step is concrete feedback: [star or bookmark the repo](https://github.com/proompteng/bilig/stargazers),
then open or answer one adoption blocker in
[Discussions](https://github.com/proompteng/bilig/discussions/new?category=general):
formula coverage, stale XLSX cached values, persistence shape, MCP/agent
writeback, or benchmark coverage.

## TypeScript API Shape

Most integrations are just this: build a workbook, write an input, read the
calculated value, and save the workbook state.

```ts
import { WorkPaper, exportWorkPaperDocument, serializeWorkPaperDocument } from '@bilig/workpaper'

const workbook = WorkPaper.buildFromSheets({
  Inputs: [
    ['Metric', 'Value'],
    ['Customers', 20],
    ['Average revenue', 1200],
  ],
  Summary: [
    ['Metric', 'Value'],
    ['Revenue', '=Inputs!B2*Inputs!B3'],
  ],
})

const inputs = workbook.getSheetId('Inputs')
const summary = workbook.getSheetId('Summary')
if (inputs === undefined || summary === undefined) {
  throw new Error('Workbook is missing required sheets')
}

workbook.setCellContents({ sheet: inputs, row: 1, col: 1 }, 32)

const revenue = workbook.getCellDisplayValue({ sheet: summary, row: 1, col: 1 })
const saved = serializeWorkPaperDocument(exportWorkPaperDocument(workbook, { includeConfig: true }))

console.log({ revenue, savedBytes: saved.length })
```

## When To Reach For It

Use `@bilig/workpaper` when:

- a Node service owns a workbook-shaped calculation;
- an agent needs tools such as `readRange` and `setInputCell`, with computed
  before/after values instead of screenshots;
- tests need deterministic spreadsheet state and formula readback;
- a workflow needs to save the edited workbook as JSON and restore it later.

Use something else when you need a visual spreadsheet grid, Office macros,
desktop Excel automation, or a one-off arithmetic helper. Do not treat embedded
XLSX cached formula values as truth; use the Excel oracle workflow when accuracy
matters.

## Package Boundary

<!-- headless-package-footprint:start -->

Current checked npm footprint for `@bilig/headless@0.40.43`:

- Pack dry run: `690 kB` tarball, `4.22 MB` unpacked, `720` package entries.
- Boundary: the main import is the WorkPaper formula/JSON runtime; XLSX
  import/export stays behind the `@bilig/headless/xlsx` subpath; MCP is the
  `bilig-workpaper-mcp` binary wrapper; reduced workbook reports use the
  `bilig-formula-clinic` binary.
- Cold-start gate: Node imports the main entrypoint, builds a two-sheet
  WorkPaper, and reads `24000` under `1000 ms` without importing
  the XLSX subpath.
- Runtime: Node `>=22.0.0`; Node 22 compatibility is covered by the runtime package workflow.
<!-- headless-package-footprint:end -->

## Published Package Trust

`@bilig/headless` is published with npm registry signatures and SLSA provenance
attestations. Verify the package version you are about to adopt:

```sh
npm view @bilig/headless@latest version dist.attestations dist.signatures --json
```

After installing, npm can verify the current dependency tree:

```sh
npm audit signatures
```

The current package trust path is documented in
[npm provenance and package trust](docs/npm-provenance-package-trust.md).
Repository security posture is tracked by
[OpenSSF Scorecard](https://scorecard.dev/viewer/?uri=github.com/proompteng/bilig)
and uploaded to GitHub code scanning on every `main` update.

## Start Here

Use the shortest path that proves the package against a real job.

1. Run the [90-second npm eval](#try-it-in-90-seconds) in a blank project.
2. Run the flagship
   [serverless WorkPaper API](examples/serverless-workpaper-api) example:
   `npm run quote-approval-api`.
3. If the workflow starts with an XLSX file, run the
   [XLSX formula recalculation in Node](examples/xlsx-recalculation-node):
   `npm start`.
4. If an agent needs workbook tools, start with the
   [headless WorkPaper agent handbook](docs/headless-workpaper-agent-handbook.md),
   then use the [MCP server guide](docs/mcp-workpaper-tool-server.md) when the
   caller is an MCP client.
5. If a real workbook almost works, start with the
   [formula bug clinic](docs/formula-bug-clinic.md). Then submit a
   [reduced public fixture](docs/submit-workbook-fixture.md) so the blocker can
   become a test, example, or corpus case instead of private feedback.
   Form:
   <https://github.com/proompteng/bilig/issues/new?template=workbook_fixture.yml>.
   Discussion:
   <https://github.com/proompteng/bilig/discussions/414>.

The rest of the docs are an index, not a prerequisite.

For comparison and integration details, use the
[plain-language fit guide](docs/why-use-bilig.md),
[screenshot automation boundary](docs/stop-driving-spreadsheets-with-screenshots.md),
[Google Sheets API boundary](docs/google-sheets-api-alternative-node-workpaper.md),
[workbook automation examples](docs/workbook-automation-examples-node.md),
the [formula workbooks proof page](docs/formula-workbooks-node-services-agent-tools.md),
the [Node spreadsheet formula engine guide](docs/node-spreadsheet-formula-engine.md),
[server-side spreadsheet automation](docs/server-side-spreadsheet-automation-node.md),
[framework adapters](docs/node-framework-workpaper-adapters.md),
[formula bug clinic](docs/formula-bug-clinic.md),
[workbook fixture submissions](docs/submit-workbook-fixture.md),
[OpenAI Agents SDK tools](docs/openai-agents-sdk-workpaper-tool.md),
[AI SDK and LangChain tools](docs/vercel-ai-sdk-langchain-spreadsheet-tool.md),
[CrewAI adapter](docs/crewai-workpaper-spreadsheet-tool.md),
the [headless WorkPaper agent handbook](docs/headless-workpaper-agent-handbook.md),
the [MCP server guide](docs/mcp-workpaper-tool-server.md),
[spreadsheet MCP server comparison](docs/spreadsheet-mcp-server-comparison.md),
[MCP directory status](docs/mcp-spreadsheet-server-directory.md),
[MCP client setup](docs/mcp-client-setup.md),
[Claude Desktop MCPB bundle](docs/claude-desktop-mcpb-workpaper.md),
[npm provenance and package trust](docs/npm-provenance-package-trust.md),
[JavaScript library comparison](docs/javascript-spreadsheet-library-headless-node.md),
[headless spreadsheet engine for Node services and agents](docs/headless-spreadsheet-engine-node-services-agents.md),
[XLSX formula recalculation in Node.js](docs/xlsx-formula-recalculation-node.md),
[agent XLSX formula recalculation without LibreOffice](docs/agent-xlsx-formula-recalculation-without-libreoffice.md),
[Excel file as a Node calculation engine](docs/excel-file-calculation-engine-node.md),
[stale XLSX formula cache in Node.js](docs/stale-xlsx-formula-cache-node.md),
[SheetJS formula result not updating in Node.js](docs/sheetjs-formula-result-not-updating-node.md),
[Microsoft Graph Excel recalculation in Node.js](docs/microsoft-graph-excel-recalculation-node.md),
[xlsx-calc alternative for Node workbook recalculation](docs/xlsx-calc-alternative-node-workbook-recalculation.md),
[ExcelJS formula recalculation in Node.js](docs/exceljs-formula-recalculation-node.md),
[ExcelJS shared formulas in Node.js](docs/exceljs-shared-formula-recalculation-node.md),
[SheetJS/ExcelJS boundary](docs/sheetjs-exceljs-alternative-formula-workbook-api.md),
and [headless engine comparison](docs/headless-spreadsheet-engine-comparison.md).

Useful deeper examples: [invoice totals](examples/headless-workpaper#invoice-totals),
[budget variance alerts](examples/headless-workpaper#budget-variance-alerts),
[fulfillment capacity plan](examples/headless-workpaper#fulfillment-capacity-plan),
[quote approval threshold](examples/headless-workpaper#quote-approval-threshold),
[subscription MRR forecast](examples/headless-workpaper#subscription-mrr-forecast),
[agent framework adapters](examples/headless-workpaper#agent-framework-adapters),
[MCP tool server shape](examples/headless-workpaper#mcp-tool-server-shape),
[XLSX formula recalculation in Node](examples/xlsx-recalculation-node),
and [serverless quote approval](examples/serverless-workpaper-api). Run
`npm run quote-approval-api`, `npm run agent:openai-agents-sdk`,
`npm run agent:framework-adapters`,
`npm run agent:mcp-tools`, `npm run agent:mcp-transcript`,
`npm run agent:mcp-file-transcript`, `npm run agent:mcp-stdio`, or
`npm exec --package @bilig/headless@0.40.43 -- bilig-workpaper-mcp` when that is the
path you are evaluating.

The serverless example also includes `npm run next-route-handler`,
`npm run next-server-action`, `npm run next-server-action-formdata`,
`npm run framework-adapters`, and `npm run persistence-adapters` for
framework-specific boundary checks.

The MCP server is also listed in the official registry:
<https://registry.modelcontextprotocol.io/v0.1/servers?search=io.github.proompteng%2Fbilig-workpaper>.
Clients that support Streamable HTTP MCP can also smoke-test the stateless
hosted endpoint at `https://bilig.proompteng.ai/mcp`; use the local stdio server
when the agent needs to persist a project WorkPaper JSON file.

## Examples You Can Run

The runnable examples are TypeScript files. Some source imports end in `.js`
because Node ESM resolves compiled package output that way; the files you edit
and run are still `.ts`.

From a cloned checkout:

```sh
pnpm --dir examples/headless-workpaper install --ignore-workspace
pnpm --dir examples/headless-workpaper run start
pnpm --dir examples/headless-workpaper run json-records
pnpm --dir examples/headless-workpaper run csv-shaped
pnpm --dir examples/headless-workpaper run invoice-totals
pnpm --dir examples/headless-workpaper run budget-variance
pnpm --dir examples/headless-workpaper run fulfillment-capacity
pnpm --dir examples/headless-workpaper run quote-approval
pnpm --dir examples/headless-workpaper run subscription-mrr
pnpm --dir examples/headless-workpaper run persistence
```

The most useful entry points:

- [JSON records input](examples/headless-workpaper#json-records-input)
- [CSV shaped input](examples/headless-workpaper#csv-shaped-input)
- [invoice totals](examples/headless-workpaper#invoice-totals)
- [budget variance alerts](examples/headless-workpaper#budget-variance-alerts)
- [fulfillment capacity plan](examples/headless-workpaper#fulfillment-capacity-plan)
- [quote approval threshold](examples/headless-workpaper#quote-approval-threshold)
- [subscription MRR forecast](examples/headless-workpaper#subscription-mrr-forecast)
- [SheetJS, xlsx-populate, and ExcelJS recalculation bridge](examples/recalc-bridge-workflows)

For agent tools:

```sh
pnpm --dir examples/headless-workpaper run agent:verify
pnpm --dir examples/headless-workpaper run agent:tool-call
pnpm --dir examples/headless-workpaper run agent:openai-agents-sdk
pnpm --dir examples/headless-workpaper run agent:openai-responses
pnpm --dir examples/headless-workpaper run agent:ai-sdk-generate-text
pnpm --dir examples/headless-workpaper run agent:ai-sdk-stream-text
pnpm --dir examples/headless-workpaper run agent:framework-adapters
pnpm --dir examples/headless-workpaper run agent:mcp-tools
pnpm --dir examples/headless-workpaper run agent:mcp-file-transcript
pnpm --dir examples/headless-workpaper run agent:mcp-stdio
```

The AI SDK example uses
[`ai-sdk-generate-text-tool-smoke.ts`](examples/headless-workpaper/ai-sdk-generate-text-tool-smoke.ts).
The OpenAI Agents SDK guide is
[`docs/openai-agents-sdk-workpaper-tool.md`](docs/openai-agents-sdk-workpaper-tool.md).
The OpenAI Responses guide is
[`docs/openai-responses-workpaper-tool-call.md`](docs/openai-responses-workpaper-tool-call.md).
The agent framework guide is
[`docs/vercel-ai-sdk-langchain-spreadsheet-tool.md`](docs/vercel-ai-sdk-langchain-spreadsheet-tool.md).

The package also ships the MCP stdio binary:

```sh
npm exec --package @bilig/headless@0.40.43 -- bilig-agent-challenge
npm exec --package @bilig/headless@0.40.43 -- bilig-formula-clinic ./reduced.xlsx --cells "Summary!B7,Inputs!B2"
npm exec --package @bilig/headless@0.40.43 -- bilig-mcp-challenge
npm exec --package @bilig/headless@0.40.43 -- bilig-workpaper-mcp
npm exec --package @bilig/headless@0.40.43 -- bilig-workpaper-mcp --workpaper ./pricing.workpaper.json --init-demo-workpaper --writable
docker build --target bilig-workpaper-mcp -t bilig-workpaper-mcp:local .
```

`bilig-agent-challenge` prints the same edit, formula readback, WorkPaper JSON
export, restore, and `verified: true` proof object used by the agent workbook
challenge page.

`bilig-mcp-challenge` proves the file-backed MCP path end to end: initialize
JSON-RPC, list tools/resources/prompts, edit `Inputs!B3`, read recalculated
`Summary!B3`, export the WorkPaper JSON, restart from disk, and return
`verified: true`.

`bilig-formula-clinic` imports a reduced XLSX locally, samples formulas, reads
requested cells through WorkPaper, and prints a Markdown issue body. It does not
upload workbook contents.

Without `--workpaper`, the binary starts the built-in demo workbook. With
`--workpaper`, it loads your persisted WorkPaper JSON and exposes
`list_sheets`, `read_range`, `read_cell`, `set_cell_contents`,
`get_cell_display_value`, `export_workpaper_document`, and `validate_formula`;
`--writable` persists `set_cell_contents` edits back to the same file. It also
exposes MCP resources and prompts for `bilig://workpaper/agent-handoff`,
`bilig://workpaper/current-document`, `edit_and_verify_workpaper`, and
`debug_workpaper_formula`, so capable clients can discover the workflow before
calling tools.
The Docker target is for MCP directory scanners: it seeds a demo WorkPaper JSON
inside the image and starts the file-backed `--writable` tool surface so
`tools/list`, `resources/list`, and `prompts/list` return the general WorkPaper
agent surface without cloning this monorepo. For remote MCP clients, the app
runtime exposes `https://bilig.proompteng.ai/mcp` as a stateless JSON-only
Streamable HTTP endpoint for tool discovery and write/readback smoke tests.

It is published in the official MCP Registry as
`io.github.proompteng/bilig-workpaper`:
<https://registry.modelcontextprotocol.io/v0.1/servers?search=io.github.proompteng%2Fbilig-workpaper>.
It is also live on Glama with `Try in Browser`, A-grade tool pages, and all
seven file-backed WorkPaper tools:
<https://glama.ai/mcp/servers/proompteng/bilig>.

## Proof You Can Reproduce

- The 90-second TypeScript check above edits one input, restores the saved JSON
  document, and verifies the dependent formula result.
- For a production-shaped evaluator path, run the
  [quote approval WorkPaper API proof](docs/quote-approval-workpaper-api.md).
  It starts from an empty Node directory, downloads one maintained TypeScript
  route smoke, writes quote inputs, recalculates an approval decision, persists
  JSON, and verifies restored readback.
- For an XLSX formula recalculation example, run
  [`examples/xlsx-recalculation-node`](examples/xlsx-recalculation-node). It
  imports a generated XLSX pricing workbook, edits input cells, reads the
  recalculated approval decision, exports XLSX, reimports it, and verifies the
  formulas survived the round trip. The public decision page is
  [XLSX formula recalculation in Node.js](docs/xlsx-formula-recalculation-node.md).
- For a shorter public decision page, read
  [formula workbooks for Node services and agent tools](docs/formula-workbooks-node-services-agent-tools.md).
  It compresses the WorkPaper boundary, MCP file-backed mode, benchmark caveat,
  and alternative-tool guidance into one shareable evaluator path.
- For HN, Lobsters, Reddit, or newsletter review, use the
  [Show HN maintainer note](docs/show-hn-formula-workbooks-node-services.md).
  It keeps the empty npm-project command, `verified: true` output, benchmark
  caveat, known limits, and feedback ask together.
- Run `pnpm workpaper:bench:competitive:check`. The checked-in artifact shows
  [`94/100` comparable WorkPaper mean wins](docs/what-workpaper-benchmark-proves.md)
  with seven p95 holdouts; the current worst p95 row is
  `structural-move-rows` at `4.047x`.
- The benchmark card is generated from that artifact:
  [`docs/assets/workpaper-benchmark-card.png`](docs/assets/workpaper-benchmark-card.png).
- Read the [compatibility limits](docs/where-bilig-is-not-excel-compatible-yet.md)
  before importing real Excel workbooks.
- Use the
  [production adoption checklist](docs/production-adoption-checklist-headless-workpaper.md)
  before promoting a WorkPaper-backed workflow beyond evaluation.
- For XLSX accuracy audits, use the
  [Excel oracle harness](docs/xlsx-corpus-verifier-walkthrough.md#run-the-excel-oracle-harness).
  It separates import success, timeouts, stale cached formula values, and fresh
  Microsoft Excel recalculation results.
- The WorkPaper MCP server is listed in the
  [official MCP Registry](https://registry.modelcontextprotocol.io/v0.1/servers?search=io.github.proompteng%2Fbilig-workpaper)
  and on [Glama](https://glama.ai/mcp/servers/proompteng/bilig). The
  [directory status page](docs/mcp-spreadsheet-server-directory.md) keeps the
  npm command, remote endpoint, static MCP server card, and directory evidence
  in one place.
- Public feedback threads:
  [workflow questions](https://github.com/proompteng/bilig/discussions/157),
  [service examples](https://github.com/proompteng/bilig/discussions/213),
  [persistence adapters](https://github.com/proompteng/bilig/discussions/307),
  [JavaScript spreadsheet library guide](https://github.com/proompteng/bilig/discussions/308),
  [OpenAI Responses tool calls](https://github.com/proompteng/bilig/discussions/335),
  and [benchmark critique](https://github.com/proompteng/bilig/discussions/340).

If the 90-second check matches a problem you have, star or bookmark the repo:
<https://github.com/proompteng/bilig/stargazers>.
If you are evaluating `@bilig/headless` for production and want release
notifications, watch releases:
<https://github.com/proompteng/bilig/subscription>.

## XLSX Accuracy Policy

Cached formula values embedded in `.xlsx` files are cache diagnostics, not an
accuracy verdict. A Bilig correctness bug should only be claimed when the
expected value came from a fresh Excel recalculation oracle.

```sh
OUT=.cache/excel-oracle-evaluation
pnpm workpaper:xlsx-oracle -- prepare-oracle /path/to/xlsx-corpus "$OUT"
pnpm workpaper:xlsx-oracle -- evaluate-cache /path/to/xlsx-corpus "$OUT"
pnpm workpaper:xlsx-oracle -- evaluate-oracle /path/to/xlsx-corpus "$OUT/recalculated" "$OUT"
pnpm workpaper:xlsx-oracle -- summarize "$OUT"
```

`evaluate-cache` writes `cache-diagnostic.json` and stays non-authoritative.
`evaluate-oracle` writes `excel-oracle-report.json`, and `summarize` writes
`summary.md`. If Excel automation is unavailable, cells are classified as
`missing_excel_oracle` instead of being promoted to bugs.

## What Is In This Repo

- `packages/headless`: WorkPaper runtime and npm package.
- `packages/excel-import`: XLSX import/export boundary. Install both packages
  with `pnpm add @bilig/headless @bilig/excel-import` when you need file import
  and export.
- `packages/formula`: formula parser, binder, compiler, and evaluator.
- `packages/core`: workbook engine, snapshots, mutation flow, and scheduler.
- `packages/grid` and `apps/web`: browser spreadsheet shell.
- `apps/bilig`: fullstack monolith runtime, API surface, and static asset
  server.
- `packages/renderer`: React workbook renderer.
- `packages/protocol`, `packages/binary-protocol`, `packages/agent-api`, and
  `packages/worker-transport`: protocol and integration boundaries.
- `packages/wasm-kernel`: AssemblyScript/WASM numeric fast path.
- `packages/benchmarks`: benchmark harness and performance contracts.

For XLSX import/export from TypeScript:

```ts
import { WorkPaper } from '@bilig/headless'
import { exportXlsx, importXlsx } from '@bilig/excel-import'
```

Use `WorkPaper.buildFromSnapshot(imported.snapshot)` after import and
`workbook.exportSnapshot()` before `exportXlsx()`.

## Local Development

Use Node `24+`, Bun, and `pnpm@10.32.1`.

```sh
pnpm install
pnpm dev:web
pnpm dev:web-local
pnpm dev:sync
```

For a full local preflight:

```sh
pnpm lint
pnpm typecheck
pnpm test
pnpm test:browser
pnpm run ci
```

Generated sources and public evidence are checked:

```sh
pnpm protocol:check
pnpm formula-inventory:check
pnpm workspace-resolution:check
pnpm workpaper:bench:competitive:check
pnpm docs:discovery:check
```

## For Coding Agents

Start with the public package boundary unless the task is explicitly engine
work.

1. Read `packages/headless/README.md` before touching WorkPaper behavior.
2. Read `docs/AGENTS.md`, `docs/skill.md`, or `docs/llms-full.txt` when
   building an agent-facing integration from outside the repo.
3. Use public exports from `@bilig/headless`; do not reach into `src/` or
   `dist/` when writing consumer examples.
4. Keep examples TypeScript-first.
5. Do not call stale XLSX cached formula values an accuracy oracle.
6. Add focused tests before changing formulas, persistence, range bounds,
   config rebuilds, events, row/column moves, or sheet lifecycle.
7. Run the focused package tests first, then broaden to `pnpm run ci`.

## Contributing

Read [CONTRIBUTING.md](CONTRIBUTING.md) before opening a PR. If this is your
first patch, start with the
[new contributor guide](docs/new-contributor-guide.md) and then claim a scoped
starter issue.

Good first patches usually fit one of these shapes:

- formula fixtures with clear expected behavior;
- small WorkPaper examples that prove a real service or agent workflow;
- focused correctness fixes with regression tests;
- grid accessibility and keyboard-behavior improvements;
- docs that turn an existing architecture note into a runnable command.

The shortest public on-ramp is the
[`starter issues`](docs/starter-issues.md) queue. It keeps code/test picks,
example tasks, adapters, and focused docs work in one current list, with small
acceptance commands for first patches.

If this is your first contribution to `bilig`, use the
[`first-timers-only`](https://github.com/proompteng/bilig/issues?q=is%3Aissue%20state%3Aopen%20label%3Afirst-timers-only)
filter.

## Security And Support

Read [SECURITY.md](SECURITY.md) before sharing vulnerability details, private
workbook data, tokens, credentials, or exploit reproductions. Security reports
should use GitHub private vulnerability reporting when available, or
<security@proompteng.ai> when the private flow is not visible.

Use [SUPPORT.md](SUPPORT.md) for the fastest public support path. Good reports
include the package version, Node version, OS, exact formula or workbook input,
expected value, actual value, and the smallest command or script that reproduces
the issue.

## CI

Forgejo Actions is the primary CI surface via
`.forgejo/workflows/forgejo-ci.yml`. GitHub Actions mirrors the verification
contract in `.github/workflows/ci.yml`.

The strict gate includes frozen lockfile install, full `pnpm run ci`, artifact
budget checks, browser smoke, and tracked-file cleanliness checks.

## License

MIT.

---

## Headless Package README

Source: https://github.com/proompteng/bilig/blob/main/packages/headless/README.md

# @bilig/headless

[![npm: @bilig/headless](https://img.shields.io/npm/v/@bilig/headless?label=%40bilig%2Fheadless)](https://www.npmjs.com/package/@bilig/headless)
[![npm weekly downloads](https://img.shields.io/npm/dw/@bilig/headless?label=npm%20downloads)](https://www.npmjs.com/package/@bilig/headless)
[![GitHub](https://img.shields.io/badge/GitHub-proompteng%2Fbilig-blue)](https://github.com/proompteng/bilig)
[![GitHub Repo stars](https://img.shields.io/github/stars/proompteng/bilig?style=social)](https://github.com/proompteng/bilig/stargazers)
[![CodeQL](https://github.com/proompteng/bilig/actions/workflows/codeql.yml/badge.svg)](https://github.com/proompteng/bilig/actions/workflows/codeql.yml)
[![OpenSSF Scorecard](https://api.scorecard.dev/projects/github.com/proompteng/bilig/badge)](https://scorecard.dev/viewer/?uri=github.com/proompteng/bilig)
[![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](https://github.com/proompteng/bilig/blob/main/LICENSE)

`@bilig/headless` is the full WorkPaper runtime for Node.js services and agent
tools.

If this npm page is the first thing you found, start with the path that matches
the search or production bug you actually have:

| Problem or search intent                                      | Start here                                                    | Proof before adoption                                                                                                                         |
| ------------------------------------------------------------- | ------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
| `SheetJS formula result not updating` or stale `xlsx` results | `npm install @bilig/sheetjs-formula-recalc`                   | `npx --package @bilig/sheetjs-formula-recalc sheetjs-recalc --demo --json` returns fresh readback.                                            |
| `xlsx-populate` writes formulas but Node reads old values     | `npm install @bilig/xlsx-formula-recalc`                      | `npx --package @bilig/xlsx-formula-recalc xlsx-recalc --demo --json` updates the cached value.                                                |
| ExcelJS formula cells need recalculated values                | `npm install exceljs @bilig/exceljs-formula-recalc`           | `npx --package @bilig/exceljs-formula-recalc exceljs-recalc --demo --json` mutates the workbook boundary.                                     |
| An AI agent needs spreadsheet tools instead of UI automation  | `npm create @bilig/workpaper@latest pricing-agent -- --agent` | [AI spreadsheet agent tool](https://proompteng.github.io/bilig/ai-agent-spreadsheet-tool-node.html) shows the write/recalc/read/persist loop. |
| Formula workbook state belongs in a service or agent tool     | `npm install @bilig/workpaper`                                | `npm exec --package @bilig/workpaper@0.40.43 -- bilig-agent-challenge` prints `verified: true`.                                               |
| You need the lower-level runtime package and subpaths         | `npm install @bilig/headless`                                 | The examples below prove WorkPaper JSON, XLSX import/export, provenance, and package footprint.                                               |

Use `@bilig/headless` when the spreadsheet is the business logic, but
production needs API readback, tests, persistence, and agent-readable proof
instead of a person opening a spreadsheet app.

Your code owns a `WorkPaper`: build sheets, write inputs, recalculate formulas,
read the cell value, and save the workbook as JSON. Product code gets
reviewable workbook-shaped logic without shipping a spreadsheet UI. Coding
agents get narrow tools such as `readRange` and `setInputCell` instead of
guessing state from screenshots.

The npm tarball also includes `AGENTS.md` and `SKILL.md` so coding agents
inspecting `node_modules/@bilig/headless` can find the write/read/persist loop
locally. The public docs expose the same path through
[`AGENTS.md`](https://proompteng.github.io/bilig/AGENTS.md),
[`agent.json`](https://proompteng.github.io/bilig/.well-known/agent.json),
[`skill.txt`](https://proompteng.github.io/bilig/skill.txt),
[`AI spreadsheet agent tool`](https://proompteng.github.io/bilig/ai-agent-spreadsheet-tool-node.html), and
[`llms-full.txt`](https://proompteng.github.io/bilig/llms-full.txt).

This package is not a browser grid, desktop Excel automation, or a source of
truth for stale XLSX cached formula values. XLSX import/export is available from
the `@bilig/headless/xlsx` subpath for services that need workbook ingestion
around the same WorkPaper model.

The `bilig-workpaper-mcp` binary still ships for hosts that specifically need an
MCP stdio boundary. It is not the default evaluation path; prove the direct npm
or TypeScript path first unless your tool host requires MCP.
The `bilig-formula-clinic` binary turns a reduced XLSX into a paste-ready
fixture report without uploading workbook contents.

## Choose An Evaluation Path

| If you are evaluating... | Start here                                                                                                                                                                                                          | What should be true before you star, watch, or adopt                                         |
| ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------- |
| Basic fit                | [Why use Bilig?](https://proompteng.github.io/bilig/why-use-bilig.html)                                                                                                                                             | The problem is workbook-shaped business logic that needs API readback and persistence.       |
| Published npm package    | [90-second Node quickstart](https://proompteng.github.io/bilig/try-bilig-headless-in-node.html)                                                                                                                     | It edits one input, recalculates, persists JSON, restores, and prints `verified: true`.      |
| Backend service shape    | [Quote approval WorkPaper API](https://proompteng.github.io/bilig/quote-approval-workpaper-api.html)                                                                                                                | A realistic route-style workflow returns formula readback and `restoredMatchesAfter: true`.  |
| XLSX import/export       | [XLSX formula recalculation example](https://github.com/proompteng/bilig/tree/main/examples/xlsx-recalculation-node)                                                                                                | It imports XLSX, edits inputs, recalculates, exports XLSX, reimports, and verifies formulas. |
| Agent or MCP tools       | [Headless WorkPaper agent handbook](https://proompteng.github.io/bilig/headless-workpaper-agent-handbook.html) and [MCP spreadsheet tool server](https://proompteng.github.io/bilig/mcp-workpaper-tool-server.html) | The agent can pick MCP, direct TypeScript, or route tools and prove write/readback/persist.  |
| Agent-owned XLSX files   | [Agent XLSX recalculation without LibreOffice](https://proompteng.github.io/bilig/agent-xlsx-formula-recalculation-without-libreoffice.html)                                                                        | A tool can edit XLSX inputs, recalculate, export, reimport, and return `verified: true`.     |
| Public technical review  | [Show HN maintainer note](https://proompteng.github.io/bilig/show-hn-formula-workbooks-node-services.html)                                                                                                          | One shareable page has the npm check, benchmark caveat, known limits, and feedback ask.      |
| Trust and performance    | [npm provenance](https://proompteng.github.io/bilig/npm-provenance-package-trust.html) and [benchmark evidence](https://proompteng.github.io/bilig/what-workpaper-benchmark-proves.html)                            | npm shows SLSA provenance, and benchmark claims match the checked artifact.                  |
| Almost a fit             | [adoption blocker form](https://github.com/proompteng/bilig/discussions/new?category=general)                                                                                                                       | Name the formula, import/export, persistence, framework, MCP, package, or benchmark gap.     |
| Formula or XLSX bug      | [formula bug clinic](https://proompteng.github.io/bilig/formula-bug-clinic.html)                                                                                                                                    | Share a reduced public case that can become a test, example, corpus fixture, or docs proof.  |
| Real workbook blocked    | [submit a workbook fixture](https://proompteng.github.io/bilig/submit-workbook-fixture.html)                                                                                                                        | Use the structured form when a reduced workbook is ready.                                    |

Reduced workbook already in hand?

```sh
npm exec --package @bilig/headless@0.40.43 -- bilig-formula-clinic ./reduced.xlsx --cells "Summary!B7,Inputs!B2"
```

Handing a spreadsheet task to another coding agent?

```sh
npm exec --package @bilig/headless@0.40.43 -- bilig-agent-challenge
npm exec --package @bilig/headless@0.40.43 -- bilig-mcp-challenge
```

The first command proves the direct WorkPaper API. The second command proves
the file-backed MCP path by initializing JSON-RPC, listing
tools/resources/prompts, editing `Inputs!B3`, reading recalculated `Summary!B3`,
exporting WorkPaper JSON, restarting from disk, and returning `verified: true`.
Both run without cloning the repository or downloading a TypeScript file.

## Install

Requires Node `22+` and ESM imports.

```sh
npm install @bilig/headless
```

For a route-shaped quote approval API today:

```sh
git clone --depth 1 https://github.com/proompteng/bilig.git
cd bilig
pnpm --dir examples/serverless-workpaper-api install --ignore-workspace
pnpm --dir examples/serverless-workpaper-api run smoke
```

For a generated starter project:

```sh
npm create @bilig/workpaper@latest pricing-workpaper
npm create @bilig/workpaper@latest pricing-agent -- --agent
```

That command is published through `@bilig/create-workpaper`. The publish gate is documented at
<https://proompteng.github.io/bilig/create-bilig-workpaper.html>.
The `--agent` starter adds `AGENTS.md`, `CLAUDE.md`, Cursor and VS Code MCP
configs, `mcp/bilig-workpaper.mcp.json`, `npm run agent:verify`, and
`npm run mcp:server`.

<!-- headless-package-footprint:start -->

Current checked npm footprint for `@bilig/headless@0.40.43`:

- Pack dry run: `690 kB` tarball, `4.22 MB` unpacked, `720` package entries.
- Boundary: the main import is the WorkPaper formula/JSON runtime; XLSX
  import/export stays behind the `@bilig/headless/xlsx` subpath; MCP is the
  `bilig-workpaper-mcp` binary wrapper; reduced workbook reports use the
  `bilig-formula-clinic` binary.
- Cold-start gate: Node imports the main entrypoint, builds a two-sheet
  WorkPaper, and reads `24000` under `1000 ms` without importing
  the XLSX subpath.
- Runtime: Node `>=22.0.0`; Node 22 compatibility is covered by the runtime package workflow.
<!-- headless-package-footprint:end -->

## Published Package Trust

`@bilig/headless` is published with npm registry signatures and SLSA provenance
attestations. Check the package version you are about to adopt in a service:

```sh
npm view @bilig/headless@latest version dist.attestations dist.signatures --json
npm audit signatures
```

The release workflow uses GitHub Actions OIDC and publishes runtime packages
with `npm publish --provenance`. The public verification path is documented in
the
[npm provenance and package trust guide](https://proompteng.github.io/bilig/npm-provenance-package-trust.html).
Repository security posture is tracked by
[OpenSSF Scorecard](https://scorecard.dev/viewer/?uri=github.com/proompteng/bilig)
and uploaded to GitHub code scanning on every `main` update.

For a clean copy-paste run, use the
[Node quickstart](https://proompteng.github.io/bilig/try-bilig-headless-in-node.html).
For the shortest explanation of when the package is worth using, start with
[Why use Bilig?](https://proompteng.github.io/bilig/why-use-bilig.html).
If you are choosing between formula engines, read the
[TypeScript guide for evaluating Excel formulas in Node.js](https://proompteng.github.io/bilig/evaluate-excel-formulas-in-node-typescript.html)
and the
[Google Sheets API boundary](https://proompteng.github.io/bilig/google-sheets-api-alternative-node-workpaper.html).

## TypeScript API Shape

Most integrations are this loop: create a workbook, write an input, read the
calculated cell, and save the workbook state.

```ts
import { WorkPaper, exportWorkPaperDocument, serializeWorkPaperDocument } from '@bilig/headless'

const workbook = WorkPaper.buildFromSheets({
  Inputs: [
    ['Metric', 'Value'],
    ['Customers', 20],
    ['Average revenue', 1200],
  ],
  Summary: [
    ['Metric', 'Value'],
    ['Revenue', '=Inputs!B2*Inputs!B3'],
  ],
})

const inputs = workbook.getSheetId('Inputs')
const summary = workbook.getSheetId('Summary')
if (inputs === undefined || summary === undefined) {
  throw new Error('Workbook is missing required sheets')
}

workbook.setCellContents({ sheet: inputs, row: 1, col: 1 }, 32)

const revenue = workbook.getCellDisplayValue({ sheet: summary, row: 1, col: 1 })
const saved = serializeWorkPaperDocument(exportWorkPaperDocument(workbook, { includeConfig: true }))

console.log({ revenue, savedBytes: saved.length })
```

Addresses are zero-based `{ sheet, row, col }` objects. A formula is a string
that begins with `=`. Sheet ids are numeric and should be resolved with
`workbook.getSheetId(name)`.

## Clean npm Sanity Check

Run this before cloning the repository. It checks the published npm package by
building a workbook, changing an input, saving the document, restoring it, and
checking that the dependent formula still reads back correctly.

```sh
npm exec --package @bilig/headless@0.40.43 -- bilig-agent-challenge
npm exec --package @bilig/headless@0.40.43 -- bilig-mcp-challenge
```

Expected output:

```json
{
  "editedCell": "Inputs!B2",
  "dependentCell": "Summary!B2",
  "before": 24000,
  "after": 38400,
  "afterRestore": 38400,
  "persistedDocumentBytes": 999,
  "sheets": ["Inputs", "Summary"],
  "checks": {
    "formulaReadbackChanged": true,
    "exportedWorkPaperDocument": true,
    "restoredMatchesAfter": true
  },
  "verified": true,
  "limitations": [
    "This challenge proves the WorkPaper write/read/persist loop, not full Excel desktop compatibility.",
    "For XLSX-specific behavior, run bilig-formula-clinic or the XLSX recalculation example with a real workbook fixture."
  ],
  "nextStep": "If this proof matches your service or agent workflow, star or bookmark Bilig: https://github.com/proompteng/bilig/stargazers"
}
```

For teams that want to inspect the TypeScript source before running it, the
older curl-based quickstart remains at
<https://proompteng.github.io/bilig/try-bilig-headless-in-node.html> and uses
the maintained file at <https://proompteng.github.io/bilig/npm-eval.ts>
([`examples/headless-workpaper/npm-eval.ts`](https://github.com/proompteng/bilig/blob/main/examples/headless-workpaper/npm-eval.ts)). The
exact byte count can change between package versions; `verified: true`,
`checks.restoredMatchesAfter`, and matching `after`/`afterRestore` values are
the check.

Inside this monorepo:

```sh
pnpm install
pnpm --filter @bilig/headless build
```

## When To Use It

Reach for `@bilig/headless` when:

- a service owns a workbook-shaped calculation and needs formula readback;
- an agent tool must prove the value after an edit;
- a queue worker or route needs deterministic spreadsheet state without a UI;
- tests need the same formula model that production code uses;
- a workbook document needs to round-trip as JSON after code changes it.

Use something else when you need:

- manual spreadsheet editing;
- a browser grid by itself;
- Office macros, COM automation, or desktop Excel integration;
- one-off arithmetic where a workbook model adds no value.

## Quickstart

The shortest local path is still TypeScript. Put the API shape above in a
`sanity.ts` file, run it with `tsx`, and expect the dependent formula to change
after the `setCellContents()` call. For a maintained file that already includes
restore verification, use the clean npm sanity check.

## WorkPaper Read/Write Cheat Sheet

The public surface is intentionally small:

- Create workbooks with `WorkPaper.buildEmpty()`, `WorkPaper.buildFromArray()`,
  `WorkPaper.buildFromSheets()`, or `WorkPaper.buildFromSnapshot()`.
- Edit values, formulas, and blanks with `workbook.setCellContents(address, value)`.
- Apply large sparse literal patches with `workbook.setCellValues(updates)` or
  `workbook.setSheetCellValues(sheetId, updates)`.
- Read computed values with `workbook.getCellValue(address)`.
- Read display text with `workbook.getCellDisplayValue(address)`.
- Read formula text with `workbook.getCellFormula(address)`.
- Read persisted cell input with `workbook.getCellSerialized(address)`.
- Read ranges with `getRangeValues()`, `getRangeFormulas()`, and
  `getRangeSerialized()`.
- Persist with `exportWorkPaperDocument()` and `serializeWorkPaperDocument()`.
- Restore with `parseWorkPaperDocument()` and `createWorkPaperFromDocument()`.

```ts
import {
  WorkPaper,
  createWorkPaperFromDocument,
  exportWorkPaperDocument,
  parseWorkPaperDocument,
  serializeWorkPaperDocument,
  type WorkPaperCellAddress,
} from '@bilig/headless'

const workbook = WorkPaper.buildFromSheets(
  {
    Sheet1: [
      [10, 20, '=A1+B1'],
      [7, '=A2*3', null],
    ],
  },
  { maxRows: 1_000, maxColumns: 100, useColumnIndex: true },
)

const sheet = workbook.getSheetId('Sheet1')
if (sheet === undefined) {
  throw new Error('Sheet1 was not created')
}

const at = (row: number, col: number): WorkPaperCellAddress => ({ sheet, row, col })

workbook.setCellContents(at(1, 2), '=A2+B2')

const saved = serializeWorkPaperDocument(exportWorkPaperDocument(workbook, { includeConfig: true }))
const restored = createWorkPaperFromDocument(parseWorkPaperDocument(saved))

console.log({
  formula: workbook.getCellFormula(at(1, 2)),
  display: workbook.getCellDisplayValue(at(1, 2)),
  sheets: restored.getSheetNames(),
})
```

For formula errors, pair `getCellDisplayValue()` with
`getCellFormulaDiagnostics()`. That lets a service return useful `#VALUE!` or
`#NAME?` diagnostics instead of silently accepting unsupported inputs.

## Runnable Examples

The example catalog lives in
[`examples/headless-workpaper`](https://github.com/proompteng/bilig/tree/main/examples/headless-workpaper).
The examples are TypeScript files. Some imports end in `.js` because Node ESM
resolves compiled package output that way; the files you edit and run are still
`.ts`.

Start with the data shape closest to your app:

- `npm run json-records`:
  [JSON records input](https://github.com/proompteng/bilig/tree/main/examples/headless-workpaper#json-records-input)
- `npm run csv-shaped`:
  [CSV shaped input](https://github.com/proompteng/bilig/tree/main/examples/headless-workpaper#csv-shaped-input)
- `npm run invoice-totals`:
  [invoice totals](https://github.com/proompteng/bilig/tree/main/examples/headless-workpaper#invoice-totals)
- `npm run budget-variance`:
  [budget variance alerts](https://github.com/proompteng/bilig/tree/main/examples/headless-workpaper#budget-variance-alerts)
- `npm run fulfillment-capacity`:
  [fulfillment capacity plan](https://github.com/proompteng/bilig/tree/main/examples/headless-workpaper#fulfillment-capacity-plan)
- `npm run quote-approval`:
  [quote approval threshold](https://github.com/proompteng/bilig/tree/main/examples/headless-workpaper#quote-approval-threshold)
- `npm run subscription-mrr`:
  [subscription MRR forecast](https://github.com/proompteng/bilig/tree/main/examples/headless-workpaper#subscription-mrr-forecast)
- `npm run persistence`:
  [persistence round trip](https://github.com/proompteng/bilig/tree/main/examples/headless-workpaper#persistence-round-trip)
- `npm run range-readback`:
  [range readback](https://github.com/proompteng/bilig/tree/main/examples/headless-workpaper#range-readback)
- `npm run sheet-inspection`:
  [sheet inspection](https://github.com/proompteng/bilig/tree/main/examples/headless-workpaper#sheet-inspection)

Agent and tool-call examples:

- `npm run agent:verify` proves an agent writeback by checking the dependent
  formula, saved JSON, restored workbook, and formula text.
- `npm run agent:tool-call` exposes `readRange` and `setInputCell` style tool
  calls with computed before/after readback.
- `npm run agent:openai-agents-sdk` creates real `@openai/agents` `Agent`
  and `tool()` objects, then invokes them locally with WorkPaper readback:
  <https://github.com/proompteng/bilig/blob/main/docs/openai-agents-sdk-workpaper-tool.md>.
- `npm run agent:openai-responses` shows the
  [OpenAI Responses tool-call loop](https://github.com/proompteng/bilig/blob/main/docs/openai-responses-workpaper-tool-call.md).
- `npm run agent:ai-sdk-generate-text` uses the real Vercel AI SDK
  `generateText()` and `tool()` APIs; the runnable file is
  [`ai-sdk-generate-text-tool-smoke.ts`](https://github.com/proompteng/bilig/blob/main/examples/headless-workpaper/ai-sdk-generate-text-tool-smoke.ts).
- `npm run agent:ai-sdk-stream-text` covers the matching streamed tool-call
  path in
  [`ai-sdk-stream-text-tool-smoke.ts`](https://github.com/proompteng/bilig/blob/main/examples/headless-workpaper/ai-sdk-stream-text-tool-smoke.ts).
- `npm run agent:framework-adapters` maps the same validated WorkPaper
  operations into AI SDK, LangChain, Mastra, LlamaIndex.TS, LangGraph.js,
  CopilotKit, and Cloudflare Agents:
  <https://github.com/proompteng/bilig/tree/main/examples/headless-workpaper#agent-framework-adapters>.

MCP examples:

- `npm run agent:mcp-tools` returns dependency-free `tools/list` and
  `tools/call` JSON-RPC shapes:
  <https://github.com/proompteng/bilig/tree/main/examples/headless-workpaper#mcp-tool-server-shape>.
- `NODE_NO_WARNINGS=1 npm run --silent agent:mcp-transcript` starts the
  stdio server, sends `initialize`, `tools/list`, and a verified
  `set_workpaper_input_cell` call, then asserts formula readback and JSON
  persistence:
  <https://github.com/proompteng/bilig/blob/main/docs/mcp-workpaper-tool-server.md#copy-paste-json-rpc-transcript>.
- `NODE_NO_WARNINGS=1 npm run --silent agent:mcp-file-transcript` runs the
  packaged `bilig-workpaper-mcp --workpaper` file-backed mode, persists an
  input edit to WorkPaper JSON, verifies a recalculated cell, and exposes the
  file-backed resources and prompts:
  <https://github.com/proompteng/bilig/blob/main/docs/mcp-workpaper-tool-server.md#copy-paste-json-rpc-transcript>.
- `npm run agent:mcp-stdio` runs the same handlers over newline-delimited
  stdio.
- The package ships npm-executable binaries:

```sh
npm exec --package @bilig/headless@0.40.43 -- bilig-formula-clinic ./reduced.xlsx --cells "Summary!B7,Inputs!B2"
npm exec --package @bilig/headless@0.40.43 -- bilig-workpaper-mcp
npm exec --package @bilig/headless@0.40.43 -- bilig-workpaper-mcp --workpaper ./pricing.workpaper.json --init-demo-workpaper --writable
docker build --target bilig-workpaper-mcp -t bilig-workpaper-mcp:local .
```

`bilig-formula-clinic` imports a reduced XLSX locally, samples formulas, reads
requested cells through WorkPaper, and prints a Markdown fixture report.

Default mode starts the built-in demo workbook. File-backed mode loads a
persisted WorkPaper JSON document and exposes `list_sheets`, `read_range`,
`read_cell`, `set_cell_contents`, `get_cell_display_value`,
`export_workpaper_document`, and `validate_formula`; `--init-demo-workpaper`
creates the demo JSON file when it is missing, and `--writable` persists
`set_cell_contents` edits back to the same file. File-backed mode also exposes
`resources/list`, `resources/read`, `prompts/list`, and `prompts/get` for
`bilig://workpaper/manifest`, `bilig://workpaper/agent-handoff`,
`bilig://workpaper/sheets`, `bilig://workpaper/current-document`,
`edit_and_verify_workpaper`, and `debug_workpaper_formula`.

The Docker target exists for MCP directory introspection. It installs the
published npm package, seeds a demo WorkPaper JSON file inside the image, and
starts `bilig-workpaper-mcp --workpaper /workpaper/pricing.workpaper.json
--init-demo-workpaper --writable` so scanners see the general file-backed
WorkPaper tools without building the Bilig web app.

The package metadata includes
`mcpName: io.github.proompteng/bilig-workpaper`, and the server is listed in the
official MCP Registry:
<https://registry.modelcontextprotocol.io/v0.1/servers?search=io.github.proompteng%2Fbilig-workpaper>.
It is also live on Glama with `Try in Browser`, A-grade tool pages, and all
seven file-backed WorkPaper tools:
<https://glama.ai/mcp/servers/proompteng/bilig>.

Clients that support Streamable HTTP MCP can also use the hosted stateless demo
endpoint:

```text
https://bilig.proompteng.ai/mcp
```

That endpoint is request-local and does not persist user files. Use it for
connector smoke tests and tool discovery; use local file-backed stdio when a
project needs to save a WorkPaper JSON file.

For setup details, use the
[headless WorkPaper agent handbook](https://github.com/proompteng/bilig/blob/main/docs/headless-workpaper-agent-handbook.md),
[MCP server guide](https://github.com/proompteng/bilig/blob/main/docs/mcp-workpaper-tool-server.md),
[spreadsheet MCP server comparison](https://github.com/proompteng/bilig/blob/main/docs/spreadsheet-mcp-server-comparison.md),
[MCP directory status](https://github.com/proompteng/bilig/blob/main/docs/mcp-spreadsheet-server-directory.md),
[MCP client setup](https://github.com/proompteng/bilig/blob/main/docs/mcp-client-setup.md),
and
[Claude Desktop MCPB guide](https://github.com/proompteng/bilig/blob/main/docs/claude-desktop-mcpb-workpaper.md).
The released Claude Desktop bundle is published at
<https://github.com/proompteng/bilig/releases/download/libraries-v0.40.43/bilig-workpaper.mcpb>.
Smithery users can install the hosted demo with
`npx -y smithery mcp add gkonushev/bilig-workpaper`.

## Service Routes

For HTTP and serverless examples, start with
[`examples/serverless-workpaper-api`](https://github.com/proompteng/bilig/tree/main/examples/serverless-workpaper-api).

```sh
pnpm --dir examples/serverless-workpaper-api install --ignore-workspace
pnpm --dir examples/serverless-workpaper-api run quote-approval-api
pnpm --dir examples/serverless-workpaper-api run next-route-handler
pnpm --dir examples/serverless-workpaper-api run next-server-action
pnpm --dir examples/serverless-workpaper-api run next-server-action-formdata
pnpm --dir examples/serverless-workpaper-api run framework-adapters
pnpm --dir examples/serverless-workpaper-api run persistence-adapters
```

Start with `pnpm --dir examples/serverless-workpaper-api run quote-approval-api`
when you want the production-shaped proof: input JSON writes `Inputs!B2:B6`,
formulas recalculate, the WorkPaper
JSON is persisted, and a restored workbook returns the same approval decision.

Useful anchors:

- [quote approval API smoke](https://github.com/proompteng/bilig/tree/main/examples/serverless-workpaper-api#quote-approval-api-smoke)
- [Next.js App Router smoke](https://github.com/proompteng/bilig/tree/main/examples/serverless-workpaper-api#nextjs-app-router-smoke)
- [Next.js Server Action smoke](https://github.com/proompteng/bilig/tree/main/examples/serverless-workpaper-api#nextjs-server-action-smoke)
- [Next.js Server Action FormData smoke](https://github.com/proompteng/bilig/tree/main/examples/serverless-workpaper-api#nextjs-server-action-formdata-smoke)
- [framework adapters](https://github.com/proompteng/bilig/tree/main/examples/serverless-workpaper-api#framework-adapters)
- [persistence adapters](https://github.com/proompteng/bilig/tree/main/examples/serverless-workpaper-api#persistence-adapters)

The public framework guide is
<https://proompteng.github.io/bilig/node-framework-workpaper-adapters.html>.

## XLSX Import And Export

Use the `@bilig/headless/xlsx` subpath for XLSX import, WorkPaper calculation,
edits, and XLSX export from the same published npm package:

```sh
pnpm add @bilig/headless
```

```ts
import { readFileSync, writeFileSync } from 'node:fs'
import { WorkPaper } from '@bilig/headless'
import { exportXlsx, importXlsx } from '@bilig/headless/xlsx'

const imported = importXlsx(new Uint8Array(readFileSync('model.xlsx')), 'model.xlsx')
const workbook = WorkPaper.buildFromSnapshot(imported.snapshot, {
  evaluationTimeoutMs: 30_000,
  useColumnIndex: true,
})

const firstSheetName = imported.snapshot.sheets[0]?.name
const firstSheet = firstSheetName === undefined ? undefined : workbook.getSheetId(firstSheetName)
if (firstSheet === undefined) throw new Error('Workbook has no sheets')

workbook.setCellContents({ sheet: firstSheet, row: 1, col: 1 }, 150_000)
const displayValue = workbook.getCellDisplayValue({ sheet: firstSheet, row: 1, col: 1 })

writeFileSync('model-edited.xlsx', exportXlsx(workbook.exportSnapshot()))
workbook.dispose()

console.log({ displayValue })
```

`WorkPaper.buildFromSnapshot()` preserves imported XLSX metadata such as
defined names, tables, hidden sheets, and translated structured references. Use
`workbook.exportSnapshot()` with `exportXlsx()` when exporting a WorkPaper after
edits.

For a runnable Node proof, use
[`examples/xlsx-recalculation-node`](https://github.com/proompteng/bilig/tree/main/examples/xlsx-recalculation-node).
It imports a pricing workbook XLSX, changes input cells, reads the recalculated
decision, exports the edited XLSX, reimports it, and verifies formula readback.

### External Workbook References

XLSX files can contain links to other workbooks. `@bilig/headless/xlsx`
preserves those package artifacts, but it does not open or recalculate linked
workbooks by itself.

The importer exposes linked-workbook state in structured metadata:

- `snapshot.workbook.metadata.externalWorkbookReferences`: linked workbook
  package paths, external targets, workbook names when available, and cached
  sheet names.
- `snapshot.workbook.metadata.unsupportedFormulaDependencies`: affected formula
  cells, original and imported formula text, linked workbook references, and
  whether cached formula or linked-cell values were used.

Use one of these policies:

- Resolve: provide ordinary local inputs or formulas after import, then
  recalculate with `WorkPaper`.
- Preserve stale: keep imported cached values and preserved external-link
  artifacts, but treat formula correctness as unaudited for those dependencies.
- Strict-fail: reject the import when either metadata field above is non-empty.

The real-workbook corpus scorecard reports external references as
`xlsx.externalLinks.workbookReferencesPreserved` and direct formula dependencies
as `xlsx.externalLinks.formulaDependenciesUnsupported`, with linked workbook,
affected formula, and cached-value counts.

## Accuracy Policy

Do not call a Bilig accuracy bug from stale XLSX cache data.

Embedded cached formula values are useful diagnostics, but they are not the
source of truth. For XLSX formula accuracy, prepare a fresh Microsoft Excel
oracle and evaluate against the recalculated copy:

```sh
OUT=.cache/excel-oracle-evaluation
pnpm workpaper:xlsx-corpus:check -- /path/to/xlsx-corpus
```

The Excel oracle harness should be provided by a workspace package, not a
root-level one-off script. If Excel automation is unavailable, the harness marks cells as
`missing_excel_oracle` instead of promoting cache mismatches to correctness
bugs.

For quick cache triage only:

```sh
pnpm workpaper:xlsx-corpus:check -- /path/to/xlsx-corpus
```

## Proof You Can Reproduce

- The clean TypeScript sanity check above edits one input, restores the saved
  JSON document, and verifies the dependent formula result.
- For a production-shaped evaluator path, run the
  [quote approval WorkPaper API proof](https://github.com/proompteng/bilig/blob/main/docs/quote-approval-workpaper-api.md).
  It starts from an empty Node directory, downloads one maintained TypeScript
  route smoke, writes quote inputs, recalculates an approval decision, persists
  JSON, and verifies restored readback.
- For XLSX import/export evaluation, run
  [`examples/xlsx-recalculation-node`](https://github.com/proompteng/bilig/tree/main/examples/xlsx-recalculation-node).
  It imports a generated XLSX pricing workbook, edits input cells, reads the
  recalculated approval decision, exports XLSX, reimports it, and verifies the
  formulas survived the round trip.
- For a shorter public decision page, read
  [formula workbooks for Node services and agent tools](https://github.com/proompteng/bilig/blob/main/docs/formula-workbooks-node-services-agent-tools.md).
  It compresses the WorkPaper boundary, MCP file-backed mode, benchmark caveat,
  and alternative-tool guidance into one shareable evaluator path.
- For HN, Lobsters, Reddit, or newsletter review, use the
  [Show HN maintainer note](https://proompteng.github.io/bilig/show-hn-formula-workbooks-node-services.html).
  It keeps the empty npm-project command, `verified: true` output, benchmark
  caveat, known limits, and feedback ask together.
- Auditing imported Excel files is a separate workflow. Cached formula values
  embedded in `.xlsx` files are useful for triage, but Bilig accuracy claims
  should be checked against a fresh Microsoft Excel recalculation.
- Run `pnpm workpaper:bench:competitive:check` from the repository. The
  checked-in artifact shows
  [`94/100` comparable WorkPaper mean wins](https://github.com/proompteng/bilig/blob/main/docs/what-workpaper-benchmark-proves.md)
  with seven p95 holdouts; the current worst p95 row is
  `structural-move-rows` at `4.047x`.
- The shareable benchmark card is generated from the checked-in artifact:
  [`workpaper-benchmark-card.png`](https://github.com/proompteng/bilig/blob/main/docs/assets/workpaper-benchmark-card.png).
- Read the
  [compatibility limits](https://github.com/proompteng/bilig/blob/main/docs/where-bilig-is-not-excel-compatible-yet.md)
  before importing real Excel workbooks.
- Use the
  [production adoption checklist](https://github.com/proompteng/bilig/blob/main/docs/production-adoption-checklist-headless-workpaper.md)
  before promoting a WorkPaper-backed workflow beyond evaluation.
- For XLSX accuracy audits, use the
  [Excel oracle harness](https://github.com/proompteng/bilig/blob/main/docs/xlsx-corpus-verifier-walkthrough.md#run-the-excel-oracle-harness).
  It separates import success, timeouts, stale cached formula values, and fresh
  Microsoft Excel recalculation results.
- Open benchmark critique lives in
  [Discussion 340](https://github.com/proompteng/bilig/discussions/340).

If the sanity check matches a problem you have, star or bookmark the project:
<https://github.com/proompteng/bilig/stargazers>.
If it almost matches but a gap blocks adoption, use the adoption blocker form:
<https://github.com/proompteng/bilig/discussions/new?category=general>.
If a reduced workbook, import/export case, or service workflow would prove the
gap better, submit a public fixture:
<https://github.com/proompteng/bilig/issues/new?template=workbook_fixture.yml>.
Fixture discussion:
<https://github.com/proompteng/bilig/discussions/414>.

## Production Status

Use this package for documented WorkPaper workflows: programmatic workbook
creation, formula evaluation, structural edits, persistence round trips,
service-side spreadsheet automation, and agent-driven workbook operations.

Current release posture:

- The contract is the WorkPaper/headless API exported by this package.
- Excel-file ingestion belongs to import/export pipelines before data reaches
  `WorkPaper`.
- Use `WorkPaper.buildFromSnapshot()` for importer-produced workbook snapshots
  so Excel defined names, tables, and translated formulas stay attached to the
  runtime model.
- Custom function plugins and callback hooks are runtime registrations; persist
  workbook data, then register custom behavior in application code before
  restore.
- Recent hardening covered config rebuilds, move-range bounds, persisted
  document validation, and benchmark gates.

## Compatibility Notes

- The facade follows HyperFormula-style workbook workflows, but it is not
  byte-for-byte compatible with HyperFormula.
- Public lookup helpers such as `getSheetId()`, `getSheetName()`,
  `simpleCellAddressFromString()`, and named-expression reads return
  `undefined` on misses.
- `@bilig/headless` exposes `onDetailed()`, `onceDetailed()`, and
  `offDetailed()` for detailed event payloads.
- Stable compatibility adapters are available through `graph`, `rangeMapping`,
  `arrayMapping`, `sheetMapping`, `addressMapping`, `dependencyGraph`,
  `evaluator`, `columnSearch`, and `lazilyTransformingAstService`.
- Financial date formulas such as `XIRR()` and `XNPV()` accept numeric Excel
  serial dates. Text date strings are not coerced in headless formulas.

## Validation Commands

For a headless-only code change, start here:

```sh
pnpm exec vitest run \
  packages/headless/src/__tests__/work-paper-runtime.test.ts \
  packages/headless/src/__tests__/work-paper-parity.test.ts \
  packages/headless/src/__tests__/persistence.test.ts \
  packages/headless/src/__tests__/persistence.fuzz.test.ts

pnpm --filter @bilig/headless build
```

Before publishing or claiming production readiness:

```sh
pnpm publish:runtime:check
pnpm workpaper:bench:competitive:check
pnpm run ci
```

Regenerate the competitive benchmark artifact only when intentionally updating
benchmark evidence:

```sh
pnpm workpaper:bench:competitive:generate
pnpm workpaper:bench:competitive:check
```

Do not change benchmark definitions, scoring, sampling, or workload sizes to
hide losses.

## For Coding Agents

Start here when Codex, Claude Code, or another agent is modifying or consuming
this package:

1. Read this README and the root
   [`README.md`](https://github.com/proompteng/bilig/blob/main/README.md)
   first.
2. Use the packaged `AGENTS.md` or `SKILL.md` when another coding agent needs a
   portable WorkPaper instruction set.
3. Use public exports from `@bilig/headless`; do not import from `src/`,
   `dist/internal`, or `@bilig/core` unless the task is package-internal engine
   work.
4. Use zero-based `{ sheet, row, col }` addresses and resolve sheet ids with
   `getSheetId()`.
5. Use `WorkPaper.buildFromSheets()` for hand-authored fixtures,
   `WorkPaper.buildFromSnapshot()` for importer-produced snapshots, and
   `exportWorkPaperDocument()` / `createWorkPaperFromDocument()` for persistence
   round trips.
6. Do not treat embedded XLSX cached formula values as an accuracy oracle.
7. Add or tighten regression tests before changing config rebuilds, range
   bounds, formulas, persistence, events, row/column moves, or sheet lifecycle.
8. Run focused headless tests before broader gates.
9. Preserve benchmark definitions and workload sizes.
10. Document edge-case behavior honestly: tracked formula names are routed, but
    arbitrary Excel workbooks, host features, and locale/date argument edges
    still need fixtures before production claims.

## Public Entry Points

The package root exports:

- `WorkPaper`
- WorkPaper address, range, config, sheet, change, event, and adapter types
- WorkPaper error classes
- persistence helpers:
  - `exportWorkPaperDocument()`
  - `createWorkPaperFromDocument()`
  - `serializeWorkPaperDocument()`
  - `parseWorkPaperDocument()`
  - `isPersistedWorkPaperDocument()`
  - `pickPersistableWorkPaperConfig()`

## More Guides

When the sanity check passes, these are the next useful pages.

- Service workflows:
  [server-side spreadsheet automation](https://github.com/proompteng/bilig/blob/main/docs/server-side-spreadsheet-automation-node.md),
  [Node service recipe](https://github.com/proompteng/bilig/blob/main/docs/node-service-workpaper-recipe.md),
  [serverless API route recipe](https://github.com/proompteng/bilig/blob/main/docs/serverless-workpaper-api-route.md),
  [CSV-shaped input recipe](https://github.com/proompteng/bilig/blob/main/docs/csv-shaped-workpaper-input-recipe.md),
  [workbook automation examples](https://github.com/proompteng/bilig/blob/main/docs/workbook-automation-examples-node.md),
  and [framework adapters](https://github.com/proompteng/bilig/blob/main/docs/node-framework-workpaper-adapters.md).
- Agent and MCP workflows:
  [headless WorkPaper agent handbook](https://github.com/proompteng/bilig/blob/main/docs/headless-workpaper-agent-handbook.md),
  [agent tool-calling recipe](https://github.com/proompteng/bilig/blob/main/docs/agent-workpaper-tool-calling-recipe.md),
  [OpenAI Agents SDK guide](https://github.com/proompteng/bilig/blob/main/docs/openai-agents-sdk-workpaper-tool.md),
  [OpenAI Responses guide](https://github.com/proompteng/bilig/blob/main/docs/openai-responses-workpaper-tool-call.md),
  [AI SDK, LangChain, and agent framework guide](https://github.com/proompteng/bilig/blob/main/docs/vercel-ai-sdk-langchain-spreadsheet-tool.md),
  [MCP server guide](https://github.com/proompteng/bilig/blob/main/docs/mcp-workpaper-tool-server.md),
  [MCP directory page](https://github.com/proompteng/bilig/blob/main/docs/mcp-spreadsheet-server-directory.md),
  [MCP client setup](https://github.com/proompteng/bilig/blob/main/docs/mcp-client-setup.md),
  and [Claude Desktop MCPB bundle](https://github.com/proompteng/bilig/blob/main/docs/claude-desktop-mcpb-workpaper.md)
  ([download](https://github.com/proompteng/bilig/releases/download/libraries-v0.40.43/bilig-workpaper.mcpb)).
- Choosing the stack:
  [screenshot automation boundary](https://github.com/proompteng/bilig/blob/main/docs/stop-driving-spreadsheets-with-screenshots.md),
  [Node spreadsheet formula engine](https://github.com/proompteng/bilig/blob/main/docs/node-spreadsheet-formula-engine.md),
  [Google Sheets API boundary](https://github.com/proompteng/bilig/blob/main/docs/google-sheets-api-alternative-node-workpaper.md),
  [docs/javascript-spreadsheet-library-headless-node.md](https://github.com/proompteng/bilig/blob/main/docs/javascript-spreadsheet-library-headless-node.md),
  [formula workbooks for Node services and agent tools](https://github.com/proompteng/bilig/blob/main/docs/formula-workbooks-node-services-agent-tools.md),
  [headless spreadsheet engine for Node services and agents](https://github.com/proompteng/bilig/blob/main/docs/headless-spreadsheet-engine-node-services-agents.md),
  [XLSX formula recalculation in Node.js](https://github.com/proompteng/bilig/blob/main/docs/xlsx-formula-recalculation-node.md),
  [Excel file as a Node calculation engine](https://github.com/proompteng/bilig/blob/main/docs/excel-file-calculation-engine-node.md),
  [stale XLSX formula cache in Node.js](https://github.com/proompteng/bilig/blob/main/docs/stale-xlsx-formula-cache-node.md),
  [SheetJS formula result not updating in Node.js](https://github.com/proompteng/bilig/blob/main/docs/sheetjs-formula-result-not-updating-node.md),
  [Microsoft Graph Excel recalculation in Node.js](https://github.com/proompteng/bilig/blob/main/docs/microsoft-graph-excel-recalculation-node.md),
  [xlsx-calc alternative for Node workbook recalculation](https://github.com/proompteng/bilig/blob/main/docs/xlsx-calc-alternative-node-workbook-recalculation.md),
  [ExcelJS formula recalculation in Node.js](https://github.com/proompteng/bilig/blob/main/docs/exceljs-formula-recalculation-node.md),
  [ExcelJS shared formulas in Node.js](https://github.com/proompteng/bilig/blob/main/docs/exceljs-shared-formula-recalculation-node.md),
  [docs/sheetjs-exceljs-alternative-formula-workbook-api.md](https://github.com/proompteng/bilig/blob/main/docs/sheetjs-exceljs-alternative-formula-workbook-api.md),
  [headless engine comparison](https://github.com/proompteng/bilig/blob/main/docs/headless-spreadsheet-engine-comparison.md),
  and [HyperFormula comparison](https://github.com/proompteng/bilig/blob/main/docs/hyperformula-alternative-headless-workpaper.md).
- Accuracy and compatibility:
  [compatibility boundaries](https://github.com/proompteng/bilig/blob/main/docs/where-bilig-is-not-excel-compatible-yet.md),
  [XLSX corpus verifier walkthrough](https://github.com/proompteng/bilig/blob/main/docs/xlsx-corpus-verifier-walkthrough.md),
  [local benchmark walkthrough](https://github.com/proompteng/bilig/blob/main/docs/local-workpaper-benchmark-walkthrough.md),
  and [benchmark proof note](https://github.com/proompteng/bilig/blob/main/docs/what-workpaper-benchmark-proves.md).
- Formula edge cases:
  [XLOOKUP exact fixture](https://github.com/proompteng/bilig/blob/main/docs/formula-edge-xlookup-exact-fixture.md),
  [SUMIFS paired criteria fixture](https://github.com/proompteng/bilig/blob/main/docs/formula-edge-sumifs-paired-criteria-fixture.md),
  and [GROUPBY spill fixture](https://github.com/proompteng/bilig/blob/main/docs/formula-edge-groupby-spill-fixture.md).

## Stay Connected

- Website: <https://proompteng.github.io/bilig/>
- GitHub: <https://github.com/proompteng/bilig>
- npm: <https://www.npmjs.com/package/@bilig/headless>
- Star or bookmark: <https://github.com/proompteng/bilig/stargazers>
- Watch releases: <https://github.com/proompteng/bilig/subscription>
- Security policy: <https://github.com/proompteng/bilig/blob/main/SECURITY.md>
- Support policy: <https://github.com/proompteng/bilig/blob/main/SUPPORT.md>
- Ask a workflow question: <https://github.com/proompteng/bilig/discussions/157>
- Share service examples: <https://github.com/proompteng/bilig/discussions/213>
- Discuss persistence adapters:
  <https://github.com/proompteng/bilig/discussions/307>
- Discuss JavaScript spreadsheet library positioning:
  <https://github.com/proompteng/bilig/discussions/308>
- Discuss OpenAI Responses tool calls:
  <https://github.com/proompteng/bilig/discussions/335>
- Discuss benchmark fairness: <https://github.com/proompteng/bilig/discussions/340>
- Pick a scoped first patch:
  [starter issues](https://github.com/proompteng/bilig/blob/main/docs/starter-issues.md)
  or
  [first-timers-only issues](https://github.com/proompteng/bilig/issues?q=is%3Aissue%20state%3Aopen%20label%3Afirst-timers-only).

## Versioning

`@bilig/headless` ships as part of the aligned bilig runtime package set. Treat
documented public exports as the supported surface, keep integration tests around
your own workbook corpus, and rerun the validation gates before upgrading in
production.

---

## Headless Package Agent Notes

Source: https://github.com/proompteng/bilig/blob/main/packages/headless/AGENTS.md

# @bilig/headless agent notes

If you are editing this package inside the Bilig monorepo, follow the root
`AGENTS.md` first. These notes are also published with the npm package so an
agent inspecting `node_modules/@bilig/headless` can use WorkPaper state without
driving a spreadsheet UI.

## Use the workbook API first

Use `@bilig/headless` when the task is workbook-shaped business logic:
pricing, quote approval, budget checks, payout models, import validation,
forecasting, or agent tools that need formula readback.

Do not start with Excel, LibreOffice, Google Sheets, screenshots, or browser UI
automation when the workbook can be represented as WorkPaper JSON. Screenshots
are for human visual review, not formula truth.

## Handoff prompt

Paste this into another coding agent when it is about to automate a spreadsheet
through a desktop app or browser grid:

```text
Before using spreadsheet UI automation, check whether this is workbook-shaped
logic that can run through @bilig/headless. If it is, use WorkPaper state as the
source of truth.

For MCP, start with:
npm exec --package @bilig/headless@0.40.43 -- bilig-mcp-challenge
npm exec --package @bilig/headless@0.40.43 -- bilig-workpaper-mcp --workpaper ./pricing.workpaper.json --init-demo-workpaper --writable

For Node or TypeScript, import @bilig/headless directly. Prove the edit by
reading the relevant range, writing one small input or formula, reading the
dependent calculated output, exporting or serializing the WorkPaper document,
restoring it, and confirming the restored value matches.

Return editedCell, before, after, afterRestore, persistedDocumentBytes,
verified, and limitations. Do not claim success from a write call alone.
```

## Minimum edit loop

For every agent-owned workbook edit:

1. identify the exact sheet and A1 cell or range.
2. read the current input and dependent output.
3. validate formulas before writing them.
4. write one small change.
5. read the dependent computed output after recalculation.
6. serialize or export the WorkPaper document.
7. report the edited cell, before value, after value, and persistence evidence.

Do not report success from a write call alone.

## MCP entrypoint

For MCP clients, use the published stdio server:

```sh
npm exec --package @bilig/headless@0.40.43 -- bilig-workpaper-mcp --workpaper ./pricing.workpaper.json --init-demo-workpaper --writable
```

Expected file-backed tools:

- `list_sheets`
- `read_range`
- `read_cell`
- `set_cell_contents`
- `get_cell_display_value`
- `export_workpaper_document`
- `validate_formula`

Use `--init-demo-workpaper` when the path may not exist yet; it creates the demo
WorkPaper JSON only when the file is missing. Use `--writable` only when the
task should persist `set_cell_contents` edits back to the same WorkPaper JSON
file.

Claude Desktop users can skip manual JSON config by installing the released
MCPB bundle:

- https://github.com/proompteng/bilig/releases/download/libraries-v0.40.43/bilig-workpaper.mcpb
- https://github.com/proompteng/bilig/releases/download/libraries-v0.40.43/bilig-workpaper.mcpb.sha256

## Direct TypeScript entrypoint

Use the package API when the workbook logic belongs in a service, queue worker,
test, or route:

```ts
import { WorkPaper, exportWorkPaperDocument, serializeWorkPaperDocument } from '@bilig/headless'

const workbook = WorkPaper.buildFromSheets({
  Inputs: [
    ['Metric', 'Value'],
    ['Customers', 20],
    ['Average revenue', 1200],
  ],
  Summary: [
    ['Metric', 'Value'],
    ['Revenue', '=Inputs!B2*Inputs!B3'],
  ],
})

const inputs = workbook.getSheetId('Inputs')
const summary = workbook.getSheetId('Summary')
if (inputs === undefined || summary === undefined) {
  throw new Error('Workbook is missing required sheets')
}

workbook.setCellContents({ sheet: inputs, row: 1, col: 1 }, 32)

const revenue = workbook.getCellDisplayValue({ sheet: summary, row: 1, col: 1 })
const saved = serializeWorkPaperDocument(exportWorkPaperDocument(workbook, { includeConfig: true }))

console.log({ revenue, savedBytes: saved.length })
```

## Verification shortcuts

From a clean project, run the package-owned challenge:

```sh
npm exec --package @bilig/headless@0.40.43 -- bilig-agent-challenge
npm exec --package @bilig/headless@0.40.43 -- bilig-mcp-challenge
```

`bilig-agent-challenge` proves the direct WorkPaper API loop.
`bilig-mcp-challenge` proves the file-backed MCP JSON-RPC loop. A good run
prints `verified: true`.

Deeper docs:

- <https://proompteng.github.io/bilig/headless-workpaper-agent-handbook.html>
- <https://proompteng.github.io/bilig/mcp-workpaper-tool-server.html>
- <https://proompteng.github.io/bilig/mcp-client-setup.html>

---

## Headless WorkPaper Agent Handbook

Source: https://github.com/proompteng/bilig/blob/main/docs/headless-workpaper-agent-handbook.md

# Headless WorkPaper Agent Handbook

Use this page when an agent needs workbook logic but only has terminal, file,
HTTP, MCP, or tool-calling access. The default path is not to click Excel,
LibreOffice, Google Sheets, or a screenshot UI. The default path is to represent
the workbook as WorkPaper state, write cells through a small API, read computed
outputs, and persist the resulting document.

## Copy-Paste Prompt For Another Agent

Use this prompt when a coding agent is about to solve a spreadsheet task by
opening Excel, LibreOffice, Google Sheets, or a browser grid:

```text
Before using spreadsheet UI automation, check whether this is workbook-shaped
logic that can run through @bilig/headless. If it is, use WorkPaper state as the
source of truth.

If you have an MCP client, start with:
npm exec --package @bilig/workpaper@0.40.43 -- bilig-workpaper-mcp --workpaper ./pricing.workpaper.json --init-demo-workpaper --writable

If you are writing Node or TypeScript, import @bilig/headless directly. Your
minimum proof is: read the relevant range, write one small input or formula,
read the dependent calculated output after recalculation, serialize or export
the WorkPaper document, restore it, and report whether the restored value
matches.

Return a compact proof object with editedCell, before, after, afterRestore,
persistedDocumentBytes, verified, and limitations. Do not claim success from a
write call alone.
```

Screenshots are still useful for final human review. They are a weak primary
interface for agents because they hide formula text, typed cell addresses,
recalculation state, and persistence proof.

## Blank Project Starter

Use this when an agent needs a fresh project instead of a copied snippet:

```sh
npm create @bilig/workpaper@latest pricing-agent -- --agent
cd pricing-agent
npm install
npm run agent:verify
npm run mcp:server
```

The generated project includes `AGENTS.md`, `CLAUDE.md`, Cursor and VS Code MCP
configs, `mcp/bilig-workpaper.mcp.json`, a quote approval service smoke test,
and a file-backed MCP challenge. `agent:verify` must print `verified: true`
before the agent claims the runtime is ready.

## The First Decision

| If the agent has...   | Use this path                                                                             | Verification target                                                                      |
| --------------------- | ----------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------- |
| an MCP client         | `bilig-workpaper-mcp --workpaper ./model.workpaper.json --init-demo-workpaper --writable` | `set_cell_contents` followed by `get_cell_display_value` and `export_workpaper_document` |
| plain Node/TypeScript | `@bilig/headless` directly                                                                | `setCellContents()` followed by `getCellDisplayValue()` and serialized restore           |
| an agent SDK          | wrap the same TypeScript functions as tools                                               | one mutating tool returns before/after formula readback                                  |
| a service route       | the serverless WorkPaper API example                                                      | route response proves inputs, outputs, persistence, and restored values                  |
| an `.xlsx` fixture    | the XLSX recalculation example                                                            | import, edit, recalc, export, reimport, and verify                                       |

Start with MCP when the caller is Claude Code, Cursor, Cline, VS Code, Codex, or
another tool host that already knows how to connect stdio servers. Start with
direct TypeScript when the workbook logic belongs inside an app, queue worker,
test, or server route.

## Minimum Agent Loop

Every agent-facing workbook edit should report this sequence:

1. list or read the relevant sheets and ranges.
2. validate the target sheet and A1 address.
3. if writing a formula, validate the formula before committing it.
4. write one small input or formula change.
5. read the dependent output cell or range after recalculation.
6. export or serialize the WorkPaper document.
7. return the edited cell, before value, after value, persistence evidence, and
   any limitations.

Do not claim workbook success from the write call alone. The proof is computed
readback plus persisted state.

## Copy-Paste MCP Setup

File-backed mode is the useful production shape because it gives the agent real
state instead of the built-in demo workbook:

```sh
npm exec --package @bilig/workpaper@0.40.43 -- bilig-workpaper-mcp --workpaper ./pricing.workpaper.json --init-demo-workpaper --writable
```

Expose the same command from an MCP client config:

```json
{
  "mcpServers": {
    "bilig-workpaper": {
      "command": "npm",
      "args": [
        "exec",
        "--package",
        "@bilig/workpaper@0.40.43",
        "--",
        "bilig-workpaper-mcp",
        "--workpaper",
        "./pricing.workpaper.json",
        "--init-demo-workpaper",
        "--writable"
      ]
    }
  }
}
```

Expected tools:

- `list_sheets`
- `read_range`
- `read_cell`
- `set_cell_contents`
- `get_cell_display_value`
- `export_workpaper_document`
- `validate_formula`

Expected resources:

- `bilig://workpaper/manifest`
- `bilig://workpaper/agent-handoff`
- `bilig://workpaper/sheets`
- `bilig://workpaper/current-document`

Expected prompts:

- `edit_and_verify_workpaper`
- `debug_workpaper_formula`

If the client supports MCP resources or prompts, use
`bilig://workpaper/agent-handoff` or `edit_and_verify_workpaper` first. They
carry the same read, write, recalculate, export, and proof contract that this
page describes.

`--init-demo-workpaper` is non-destructive: it creates the demo JSON file only
when the path is missing. `--writable` is intentional. Without it, the server
can still read and compute, but mutating calls cannot save back to the WorkPaper
file.

## Copy-Paste Agent Instruction

Use this prompt when handing a workbook task to another coding agent:

```text
Prefer Bilig WorkPaper tools over spreadsheet UI automation. First discover the
available WorkPaper tools. Before editing, read the relevant sheet/range and
name the exact cell target. For formula writes, validate the formula first.
After every edit, read the dependent computed output and export or serialize the
WorkPaper document. Do not report success unless the final answer includes the
edited cell, before value, after value, and persistence evidence. Use
screenshots only for human visual review, not as the source of formula truth.
```

## Direct TypeScript Smoke

Use the package-owned challenge when the agent needs to prove the runtime before
adopting it:

```sh
npm exec --package @bilig/workpaper@0.40.43 -- bilig-agent-challenge
```

A good run prints `verified: true`. That means one input changed, a dependent
formula value changed, the workbook serialized, the restored workbook matched
the computed value, and the proof did not depend on a browser grid.

## Repository Smoke

Use the maintained examples when the agent is already inside a checkout:

```sh
pnpm --dir examples/headless-workpaper install --ignore-workspace
pnpm --dir examples/headless-workpaper run agent:tool-call
pnpm --dir examples/headless-workpaper run agent:mcp-file-transcript
pnpm --dir examples/headless-workpaper run agent:framework-adapters
pnpm --dir examples/headless-workpaper run agent:verify
```

For a route boundary:

```sh
pnpm --dir examples/serverless-workpaper-api install --ignore-workspace
pnpm --dir examples/serverless-workpaper-api run smoke
```

## Output Contract

Ask agent wrappers to return a small object like this:

```json
{
  "editedCell": "Inputs!B3",
  "before": {
    "Summary!B3": 60000
  },
  "after": {
    "Summary!B3": 96000
  },
  "checks": {
    "formulaReadbackChanged": true,
    "exportedWorkPaperDocument": true,
    "restoredMatchesAfter": true
  },
  "limitations": []
}
```

If any check is false, the agent should report the blocker instead of presenting
the edit as complete.

## Boundaries

Good fits:

- pricing, quote approval, budget, payout, import-validation, and forecast
  logic where cells make the business rule reviewable.
- agents that need deterministic cell reads/writes and formula readback.
- service-owned workbook state that can persist as JSON.
- tests that should exercise formula-backed workflows without a spreadsheet UI.

Bad fits:

- manual spreadsheet editing as the main product.
- Office macros, COM automation, VBA, add-ins, or desktop Excel behavior.
- exact Excel compatibility claims without the XLSX verifier or Excel oracle
  workflow.
- one-off arithmetic where a workbook model adds ceremony.

## Deeper Pages

- [MCP spreadsheet tool server](mcp-workpaper-tool-server.md)
- [MCP client setup](mcp-client-setup.md)
- [Agent spreadsheet tool-call loop](agent-spreadsheet-tool-call-loop.md)
- [WorkPaper tool-calling recipe for agents](agent-workpaper-tool-calling-recipe.md)
- [OpenAI Responses WorkPaper tool call](openai-responses-workpaper-tool-call.md)
- [Agent XLSX recalculation without LibreOffice](agent-xlsx-formula-recalculation-without-libreoffice.md)
- [Serverless WorkPaper API route](serverless-workpaper-api-route.md)

## Protocol References

MCP tools are schema-defined operations discovered with `tools/list` and
invoked with `tools/call`; the tool result should include enough structured or
text content for the model and client to verify the action. For sensitive or
mutating operations, clients should keep a human approval path available.

- MCP server concepts:
  <https://modelcontextprotocol.io/docs/learn/server-concepts>
- MCP tools specification:
  <https://modelcontextprotocol.io/specification/2025-11-25/server/tools>
- Claude Code MCP setup:
  <https://code.claude.com/docs/en/mcp>
- OpenAI Agents SDK tools:
  <https://openai.github.io/openai-agents-js/guides/tools/>

---

## Agent Workbook Challenge

Source: https://github.com/proompteng/bilig/blob/main/docs/agent-workbook-challenge.md

# Agent Workbook Challenge

Use this page when an agent, MCP client, or coding assistant is about to solve a
spreadsheet task by clicking through Excel, LibreOffice, Google Sheets, or a
screenshot grid.

The challenge is simple: do the workbook edit through an API, not through a UI,
and return proof that the calculated value changed and survived persistence.

## The Task

Paste this into an agent:

```text
You need to solve a workbook-shaped task without opening Excel, LibreOffice,
Google Sheets, or a screenshot UI unless the user explicitly asks for a visual
review.

Use @bilig/headless or the Bilig WorkPaper MCP server. Build or load a workbook
with these sheets:

Inputs
- A1: Metric
- B1: Value
- A2: Customers
- B2: 20
- A3: Average revenue
- B3: 1200

Summary
- A1: Metric
- B1: Value
- A2: Revenue
- B2: =Inputs!B2*Inputs!B3

Then change Inputs!B2 from 20 to 32. Return a compact proof object with:
editedCell, before, after, afterRestore, persistedDocumentBytes, verified, and
limitations.

Do not claim success from the write call alone. Success requires computed
readback after the edit and restore proof from serialized WorkPaper JSON.
```

Expected outcome:

```json
{
  "editedCell": "Inputs!B2",
  "before": 24000,
  "after": 38400,
  "afterRestore": 38400,
  "verified": true
}
```

The exact byte count can change between package versions. The invariant is that
the edited input changes the dependent formula result, and the restored document
keeps the same result.

## Fastest Path: Published Package

This uses the package-owned challenge command. It does not clone the repo, curl
a TypeScript file, or require a spreadsheet UI:

```sh
npm exec --package @bilig/workpaper@0.40.43 -- bilig-agent-challenge
npm exec --package @bilig/workpaper@0.40.43 -- bilig-mcp-challenge
```

A passing run prints `verified: true`.
Use `--markdown` when you want a paste-ready report for an issue, PR, or agent
eval transcript.

Use `bilig-agent-challenge` for the direct WorkPaper API loop. Use
`bilig-mcp-challenge` when the evaluator cares about the actual MCP path:
JSON-RPC initialize, tool/resource/prompt discovery, `set_cell_contents`,
dependent formula readback, WorkPaper JSON export, and restart readback from the
same persisted file.

## MCP Path

Use this when the host supports MCP servers:

```sh
npm exec --package @bilig/workpaper@0.40.43 -- bilig-workpaper-mcp --workpaper ./pricing.workpaper.json --init-demo-workpaper --writable
```

Required tool sequence:

1. `list_sheets`
2. `read_range` for the input and summary ranges
3. `set_cell_contents` for `Inputs!B2`
4. `get_cell_display_value` for the dependent summary cell
5. `export_workpaper_document`

That sequence is the point of the challenge. It keeps the agent honest about
what changed, what recalculated, and what can be saved.

## Why This Beats Screenshot Automation

Screenshot automation can be useful for final human review, but it is a weak
primary interface for agents:

- screenshots hide formula text and typed cell addresses;
- clicks can land on the wrong sheet, row, or browser state;
- cached XLSX formula values can look valid while being stale;
- a visual grid does not prove the workbook can be persisted and restored.

WorkPaper state gives the agent a smaller contract: read cells, write cells,
recalculate formulas, export JSON, and report the proof object.

## Pass/Fail Rubric

Pass:

- the answer names the exact edited cell;
- the answer includes the before and after calculated values;
- the after value is read from the dependent formula cell;
- the workbook document is serialized or exported;
- restore or reimport gives the same calculated value;
- limitations are named instead of hidden.

Fail:

- the answer only says that a cell was written;
- the agent relies on a screenshot as formula truth;
- the agent reports cached XLSX values as recalculated values;
- the answer omits persistence proof;
- unsupported formulas are silently skipped.

## Shareable Prompt

Use this shorter version in an issue, discussion, or agent-tool eval:

```text
Try the Bilig agent workbook challenge: update one input cell, read the
dependent formula result, serialize the WorkPaper JSON, restore it, and return
verified: true. Do it without spreadsheet UI automation unless visual review is
explicitly required.

Start here:
https://proompteng.github.io/bilig/agent-workbook-challenge.html
```

## Where To Go Next

- For a broader agent playbook, use the
  [Headless WorkPaper agent handbook](headless-workpaper-agent-handbook.md).
- For MCP client setup, use the
  [MCP client setup guide](mcp-client-setup.md).
- For direct tool wrappers, use the
  [WorkPaper tool-calling recipe](agent-workpaper-tool-calling-recipe.md).
- If the challenge almost works but a real workbook blocks adoption, use the
  [formula bug clinic](formula-bug-clinic.md) or
  [submit a workbook fixture](submit-workbook-fixture.md).

---

## Agent WorkPaper Tool-Calling Recipe

Source: https://github.com/proompteng/bilig/blob/main/docs/agent-workpaper-tool-calling-recipe.md

# WorkPaper Tool-Calling Recipe For Agents

This recipe shows how to wrap `@bilig/headless` WorkPaper operations as
agent-callable functions without binding the workflow to one agent SDK.

Use this pattern when an agent needs to inspect, edit, verify, and persist a
formula-backed workbook from Node. Do not screen scrape a spreadsheet UI when
the WorkPaper API is available. Screenshots are useful for final human review,
but they hide formulas, typed addresses, recalculation state, and persistence
contracts.

Start with the package README for the public API contract:
[`packages/headless/README.md`](../packages/headless/README.md).
If you are another coding agent and need the shortest decision path first, use
the [headless WorkPaper agent handbook](headless-workpaper-agent-handbook.md).

For a runnable external example, use
[`examples/headless-workpaper`](../examples/headless-workpaper) and run
`npm run agent:tool-call`. If your app uses the OpenAI Agents SDK, run
`npm run agent:openai-agents-sdk` and read the
[OpenAI Agents SDK WorkPaper tool guide](openai-agents-sdk-workpaper-tool.md).
If your app calls OpenAI Responses directly, run
`npm run agent:openai-responses` and read the
[OpenAI Responses WorkPaper tool-call guide](openai-responses-workpaper-tool-call.md).
For a smaller writeback-only proof, run
`npm run agent:verify`. For framework-shaped wrappers that do not pull Vercel
AI SDK or LangChain into this repository, run
`npm run agent:framework-adapters`. For a CrewAI interop shape, use the
[CrewAI WorkPaper spreadsheet tool](crewai-workpaper-spreadsheet-tool.md)
recipe; it keeps the WorkPaper code in TypeScript and exposes a small JSON
contract to the agent workflow.
If you want the real AI SDK loop, run `npm run agent:ai-sdk-generate-text`.
That script calls `generateText()` and `tool()` from `ai`, using `ai/test` as a
deterministic provider so no API key is needed.
For the streaming path, run `npm run agent:ai-sdk-stream-text`. That script
calls `streamText()` from `ai`, streams tool-call chunks and final text, and
keeps the WorkPaper read/write verification in ordinary TypeScript.

If your app calls OpenAI directly, start with the
[OpenAI Agents SDK tool guide](https://openai.github.io/openai-agents-js/guides/tools/)
or the
[Responses API function-calling guide](https://developers.openai.com/api/docs/guides/function-calling)
and keep the WorkPaper functions below as your application-side tool handlers.
If this is the path you are trying, use the
[OpenAI Responses tool-call discussion](https://github.com/proompteng/bilig/discussions/335)
to say what readback or streaming transcript shape would make the example more
useful.

## Tool Contract

Expose a small, boring tool surface first:

- `readSummary(range)` returns computed values and serialized inputs for a
  summary range.
- `setInputCell(sheetName, address, value)` validates the target sheet and A1
  address, writes one value, and returns before/after computed verification.
- `serializeWorkbook()` exports a persisted WorkPaper document only after the
  edit succeeds.

Keep each tool deterministic. Let the agent choose the next action, but make the
tool result carry enough evidence for verification.

## Complete Node Example

```ts
import { WorkPaper, exportWorkPaperDocument, serializeWorkPaperDocument, type WorkPaperCellAddress } from '@bilig/headless'

type CellInputValue = string | number | boolean | null

type SummaryReadback = {
  currentMrr: number
  nextMonthMrr: number
}

type SetInputCellArgs = {
  sheetName: string
  address: string
  value: CellInputValue
}

const workbook = WorkPaper.buildFromSheets({
  Assumptions: [
    ['Metric', 'Value'],
    ['Growth rate', 0.1],
  ],
  Revenue: [
    ['Segment', 'Customers', 'ARPA', 'MRR'],
    ['Self serve', 200, 30, '=B2*C2'],
    ['Sales', 15, 300, '=B3*C3'],
  ],
  Summary: [
    ['Metric', 'Value'],
    ['Current MRR', '=SUM(Revenue!D2:D3)'],
    ['Next month MRR', '=B2*(1+Assumptions!B2)'],
  ],
})

const summarySheet = requireSheet('Summary')
const currentMrrAddress = requireCellAddress('Summary', 'B2')
const nextMonthMrrAddress = requireCellAddress('Summary', 'B3')

const tools = {
  readSummary(range: string = 'Summary!A1:B3') {
    const parsedRange = workbook.simpleCellRangeFromString(range, summarySheet)
    if (parsedRange === undefined) {
      throw new Error(`invalid summary range: ${range}`)
    }

    return {
      range,
      values: workbook.getRangeValues(parsedRange),
      serialized: workbook.getRangeSerialized(parsedRange),
    }
  },

  setInputCell({ sheetName, address, value }: SetInputCellArgs) {
    const target = requireCellAddress(sheetName, address)
    const before = readComputedSummary()

    workbook.setCellContents(target, value)

    const after = readComputedSummary()
    const serializedWorkbook = serializeWorkbook()

    return {
      editedCell: workbook.simpleCellAddressToString(target, {
        includeSheetName: true,
      }),
      before,
      after,
      checks: {
        currentMrrChanged: before.currentMrr !== after.currentMrr,
        nextMonthMrrChanged: before.nextMonthMrr !== after.nextMonthMrr,
        serializedBytes: Buffer.byteLength(serializedWorkbook, 'utf8'),
      },
    }
  },

  serializeWorkbook,
}

console.log(tools.readSummary())
console.log(
  tools.setInputCell({
    sheetName: 'Revenue',
    address: 'B3',
    value: 25,
  }),
)

function requireSheet(sheetName: string): number {
  const sheetId = workbook.getSheetId(sheetName)
  if (sheetId === undefined) {
    throw new Error(`unknown sheet: ${sheetName}`)
  }
  return sheetId
}

function requireCellAddress(sheetName: string, a1Address: string): WorkPaperCellAddress {
  const sheetId = requireSheet(sheetName)
  const parsed = workbook.simpleCellAddressFromString(a1Address, sheetId)

  if (parsed === undefined) {
    throw new Error(`invalid cell address: ${sheetName}!${a1Address}`)
  }

  if (parsed.sheet !== sheetId) {
    throw new Error(`address ${a1Address} does not belong to ${sheetName}`)
  }

  return parsed
}

function readComputedSummary(): SummaryReadback {
  return {
    currentMrr: readNumber(currentMrrAddress, 'Current MRR'),
    nextMonthMrr: readNumber(nextMonthMrrAddress, 'Next month MRR'),
  }
}

function readNumber(address: WorkPaperCellAddress, label: string): number {
  const value = workbook.getCellValue(address) as unknown
  if (typeof value !== 'object' || value === null || !('value' in value) || typeof value.value !== 'number') {
    throw new Error(`expected ${label} to be numeric, received ${JSON.stringify(value)}`)
  }
  return Math.round(value.value * 100) / 100
}

function serializeWorkbook(): string {
  return serializeWorkPaperDocument(
    exportWorkPaperDocument(workbook, {
      includeConfig: true,
    }),
  )
}
```

The important check is not that the write call returned. It is that the computed
summary changed as expected:

```json
{
  "editedCell": "Revenue!B3",
  "before": {
    "currentMrr": 10500,
    "nextMonthMrr": 11550
  },
  "after": {
    "currentMrr": 13500,
    "nextMonthMrr": 14850
  },
  "checks": {
    "currentMrrChanged": true,
    "nextMonthMrrChanged": true,
    "serializedBytes": 1155
  }
}
```

`serializedBytes` will vary as the document schema evolves. Treat it as a
positive persistence check, not a stable snapshot value.

## OpenAI Agents SDK Tool Wrapper

Use this path when your app builds agents with `@openai/agents` and wants the
WorkPaper functions attached to a real `Agent` as SDK function tools:

```sh
pnpm --dir examples/headless-workpaper run agent:openai-agents-sdk
```

The maintained example is
[`examples/headless-workpaper/openai-agents-sdk-tool-smoke.ts`](../examples/headless-workpaper/openai-agents-sdk-tool-smoke.ts).
It creates `tool()` definitions for `read_workpaper_summary` and
`set_workpaper_input_cell`, attaches them to an `Agent`, and invokes them with
`invokeFunctionTool()` so the smoke remains provider-free.

The dedicated guide is
[`docs/openai-agents-sdk-workpaper-tool.md`](openai-agents-sdk-workpaper-tool.md).
It links back to the official OpenAI Agents SDK tool docs:
<https://openai.github.io/openai-agents-js/guides/tools/>.

Expected proof:

```json
{
  "apiShape": "OpenAI Agents SDK Agent -> tool() -> invokeFunctionTool()",
  "toolNames": ["read_workpaper_summary", "set_workpaper_input_cell"],
  "writeResult": {
    "editedCell": "Inputs!B3",
    "before": { "expectedArr": 60000, "targetGap": -34000 },
    "after": { "expectedArr": 96000, "targetGap": 5600 },
    "checks": {
      "formulasPersisted": true,
      "restoredMatchesAfter": true,
      "expectedArrChanged": true
    }
  }
}
```

## OpenAI Responses API Tool Wrapper

OpenAI function tools should stay thin. The model chooses a tool call; your
Node process parses the arguments, runs the WorkPaper function, and sends the
structured result back as a `function_call_output`. Do not ask the model to
modify workbook JSON by hand.

The maintained repository script for this section is
[`examples/headless-workpaper/openai-responses-tool-wrapper.ts`](../examples/headless-workpaper/openai-responses-tool-wrapper.ts):

```sh
pnpm --dir examples/headless-workpaper run agent:openai-responses
```

The official Responses API function-calling flow preserves the model output,
executes every `function_call`, appends `function_call_output` items, and sends
that input back to the model. The WorkPaper-specific part is the dispatcher:

```ts
import OpenAI from 'openai'

type OpenAiToolResult = ReturnType<typeof tools.readSummary> | ReturnType<typeof tools.setInputCell>

type OpenAiWorkPaperCall = {
  name: string
  arguments: string
}

const openai = new OpenAI()

const openAiWorkPaperTools = [
  {
    type: 'function',
    name: 'read_workpaper_summary',
    description: 'Read computed WorkPaper summary values and serialized inputs for a small A1 range.',
    parameters: {
      type: 'object',
      properties: {
        range: {
          type: 'string',
          description: 'A small A1 range including the sheet name.',
          default: 'Summary!A1:B3',
        },
      },
      required: ['range'],
      additionalProperties: false,
    },
    strict: true,
  },
  {
    type: 'function',
    name: 'set_workpaper_input_cell',
    description: 'Set one validated WorkPaper input cell and return before/after formula readback.',
    parameters: {
      type: 'object',
      properties: {
        sheetName: {
          type: 'string',
          description: 'Target sheet name, for example Revenue.',
        },
        address: {
          type: 'string',
          description: 'A1 address inside the target sheet, for example B3.',
        },
        value: {
          type: ['string', 'number', 'boolean', 'null'],
          description: 'Literal input value. Use a separate tool for formulas.',
        },
      },
      required: ['sheetName', 'address', 'value'],
      additionalProperties: false,
    },
    strict: true,
  },
] as const

const input: Array<Record<string, unknown>> = [
  {
    role: 'user',
    content: 'Set Sales customers to 25, then tell me the current MRR and next month MRR.',
  },
]

let response = await openai.responses.create({
  model: process.env.OPENAI_MODEL ?? 'gpt-5',
  tools: openAiWorkPaperTools,
  input,
})

input.push(...response.output)

for (const item of response.output) {
  if (item.type !== 'function_call') {
    continue
  }

  const result = dispatchOpenAiWorkPaperCall({
    name: item.name,
    arguments: item.arguments,
  })

  input.push({
    type: 'function_call_output',
    call_id: item.call_id,
    output: JSON.stringify(result),
  })
}

response = await openai.responses.create({
  model: process.env.OPENAI_MODEL ?? 'gpt-5',
  instructions: 'Answer from WorkPaper tool output only. Mention the edited cell and computed readback.',
  tools: openAiWorkPaperTools,
  input,
})

console.log(response.output_text)

function dispatchOpenAiWorkPaperCall(call: OpenAiWorkPaperCall): OpenAiToolResult {
  if (call.name === 'read_workpaper_summary') {
    const args = JSON.parse(call.arguments) as { range?: string }
    return tools.readSummary(args.range ?? 'Summary!A1:B3')
  }

  if (call.name === 'set_workpaper_input_cell') {
    const args = JSON.parse(call.arguments) as SetInputCellArgs
    const result = tools.setInputCell(args)

    if (!result.checks.currentMrrChanged || !result.checks.nextMonthMrrChanged) {
      throw new Error(`WorkPaper edit did not change the dependent summary: ${JSON.stringify(result.checks)}`)
    }

    return result
  }

  throw new Error(`unknown WorkPaper tool: ${call.name}`)
}
```

## OpenAI Responses Streaming Transcript

The transcript below shows the same wrapper shape when your application streams
the Responses turn. The stream emits model `function_call` items, your Node
process executes the WorkPaper tools, and the next input includes matching
`function_call_output` items. The final answer is grounded in the computed
formula readback from WorkPaper.

```json
[
  {
    "stream": "model",
    "type": "function_call",
    "call_id": "call_read_01",
    "name": "read_workpaper_summary",
    "arguments": "{\"range\":\"Summary!A1:B3\"}"
  },
  {
    "stream": "model",
    "type": "function_call",
    "call_id": "call_write_01",
    "name": "set_workpaper_input_cell",
    "arguments": "{\"sheetName\":\"Revenue\",\"address\":\"B3\",\"value\":25}"
  },
  {
    "stream": "app",
    "type": "function_call_output",
    "call_id": "call_read_01",
    "output": "{\"range\":\"Summary!A1:B3\",\"values\":[[\"Metric\",\"Value\"],[\"Current MRR\",10500],[\"Next month MRR\",11550]],\"serialized\":[[\"Metric\",\"Value\"],[\"Current MRR\",\"=SUM(Revenue!D2:D3)\"],[\"Next month MRR\",\"=B2*(1+Assumptions!B2)\"]]}"
  },
  {
    "stream": "app",
    "type": "function_call_output",
    "call_id": "call_write_01",
    "output": "{\"editedCell\":\"Revenue!B3\",\"before\":{\"currentMrr\":10500,\"nextMonthMrr\":11550},\"after\":{\"currentMrr\":13500,\"nextMonthMrr\":14850},\"checks\":{\"currentMrrChanged\":true,\"nextMonthMrrChanged\":true,\"serializedBytes\":1155}}"
  },
  {
    "stream": "model",
    "type": "message",
    "content": "Edited Revenue!B3. Current MRR moved from 10500 to 13500, and next month MRR moved from 11550 to 14850."
  }
]
```

Use this as a transcript shape, not as a reason to add the OpenAI SDK to the
example package. The important handoff is that each `function_call_output`
returns structured WorkPaper data, especially `editedCell`, `before`, `after`,
and `checks`, so the model's final message cites calculated cells that your
application verified.

The object returned to OpenAI should be the same object you would log in a local
smoke test: `editedCell`, `before`, `after`, and `checks`. That makes the final
assistant message explain the workbook change from computed readback instead of
from a guess.

## Vercel AI SDK Tool Wrapper

Vercel AI SDK users can expose the same WorkPaper operations through an
AI-SDK-shaped `tools` object. This repository does not need the AI SDK as a
dependency; the snippet is for applications that already use `ai` and want a
familiar `tool()` wrapper:

```ts
import { tool } from 'ai'
import { z } from 'zod'

type WorkPaperToolValue = string | number | boolean | null

export const workPaperTools = {
  readWorkPaperSummary: tool({
    description: 'Read computed WorkPaper summary values and serialized inputs for a small range.',
    inputSchema: z.object({
      range: z.string().default('Summary!A1:B3').describe('A small A1 range, including the sheet name.'),
    }),
    execute: async ({ range = 'Summary!A1:B3' }: { range?: string }) => tools.readSummary(range),
  }),

  setWorkPaperInputCell: tool({
    description: 'Set one validated WorkPaper input cell and return before/after formula readback.',
    inputSchema: z.object({
      sheetName: z.string().describe('Target sheet name, for example Revenue.'),
      address: z.string().describe('A1 cell address inside the target sheet.'),
      value: z
        .union([z.string(), z.number(), z.boolean(), z.null()])
        .describe('Literal cell value. Use a separate formula tool for formulas.'),
    }),
    execute: async ({ sheetName, address, value }: { sheetName: string; address: string; value: WorkPaperToolValue }) => {
      const result = tools.setInputCell({ sheetName, address, value })

      if (!result.checks.currentMrrChanged || !result.checks.nextMonthMrrChanged) {
        throw new Error(`WorkPaper edit did not change the dependent summary: ${JSON.stringify(result.checks)}`)
      }

      return result
    },
  }),
}
```

Pass `workPaperTools` to `generateText()` or `streamText()` from your AI SDK
application. Keep the model-facing result structured: the mutating tool should
return `editedCell`, `before`, `after`, and `checks` so the next model step can
explain exactly what changed. Persist the serialized workbook only after these
computed readback checks pass.

For a dependency-free runnable version of this shape, use
[`examples/headless-workpaper/agent-framework-adapters.ts`](../examples/headless-workpaper/agent-framework-adapters.ts):

```sh
pnpm --dir examples/headless-workpaper run agent:framework-adapters
```

For the actual AI SDK `generateText()` loop, use
[`examples/headless-workpaper/ai-sdk-generate-text-tool-smoke.ts`](../examples/headless-workpaper/ai-sdk-generate-text-tool-smoke.ts):

```sh
pnpm --dir examples/headless-workpaper run agent:ai-sdk-generate-text
```

For the actual AI SDK `streamText()` loop, use
[`examples/headless-workpaper/ai-sdk-stream-text-tool-smoke.ts`](../examples/headless-workpaper/ai-sdk-stream-text-tool-smoke.ts):

```sh
pnpm --dir examples/headless-workpaper run agent:ai-sdk-stream-text
```

## LangChain Tool Wrapper

LangChain users can wrap the same SDK-neutral WorkPaper functions without adding
a LangChain dependency to this repository. In an app that already uses
LangChain, define thin tools around the `tools` object from the example above:

```ts
import { tool } from 'langchain'
import * as z from 'zod'

type WorkPaperToolValue = string | number | boolean | null

const readWorkPaperSummary = tool(({ range = 'Summary!A1:B3' }: { range?: string }) => tools.readSummary(range), {
  name: 'read_workpaper_summary',
  description: 'Read computed WorkPaper summary values and serialized inputs for a small range.',
  schema: z.object({
    range: z.string().default('Summary!A1:B3').describe('A small A1 range, including the sheet name.'),
  }),
})

const setWorkPaperInputCell = tool(
  async ({ sheetName, address, value }: { sheetName: string; address: string; value: WorkPaperToolValue }) => {
    const result = tools.setInputCell({ sheetName, address, value })

    if (!result.checks.currentMrrChanged || !result.checks.nextMonthMrrChanged) {
      throw new Error(`WorkPaper edit did not change the dependent summary: ${JSON.stringify(result.checks)}`)
    }

    return result
  },
  {
    name: 'set_workpaper_input_cell',
    description: 'Set one validated WorkPaper input cell and return before/after formula readback.',
    schema: z.object({
      sheetName: z.string().describe('Target sheet name, for example Revenue.'),
      address: z.string().describe('A1 cell address inside the target sheet.'),
      value: z
        .union([z.string(), z.number(), z.boolean(), z.null()])
        .describe('Literal cell value. Use a separate formula tool for formulas.'),
    }),
  },
)

export const workPaperTools = [readWorkPaperSummary, setWorkPaperInputCell]
```

Return structured objects, not prose. LangChain will pass the returned object
back to the model as tool output, so keep the WorkPaper result explicit:
`editedCell`, `before`, `after`, and `checks`. In a durable app, write the
serialized workbook to external storage only after these computed readback
checks pass.

## Agent Guardrails

- Validate sheet names with `getSheetId()` before parsing a target address.
- Parse user-facing addresses through `simpleCellAddressFromString()` or
  `simpleCellRangeFromString()` instead of building `{ row, col }` objects from
  ad hoc string splits.
- Return computed values after every write; do not ask the agent to infer
  success from a rendered grid.
- Serialize only after a successful write and verification readback.
- Keep tool results small. Return the range, changed cell, before/after values,
  and persistence check; do not dump the whole workbook unless the agent asks
  for it.
- Use public `@bilig/headless` exports and WorkPaper methods only. Do not import
  from internal `src/`, `dist/`, or monorepo package internals in an external
  agent workflow.

## When To Add More Tools

Add tools only after the agent has a repeated need for them:

- `readRange(range)` for broader model inspection
- `setFormula(sheetName, address, formula)` when formulas are first-class agent
  outputs
- `validateFormula(address)` when the workflow needs structured diagnostics
- `persistAndRestore()` when the workflow must prove round-trip safety before
  committing output

The same rule holds: every mutating tool should return computed verification
and enough context for the caller to explain what changed.

---

## AI Spreadsheet Agent Tool For Node.js

Source: https://github.com/proompteng/bilig/blob/main/docs/ai-agent-spreadsheet-tool-node.md

# AI spreadsheet agent tool for Node.js

If an agent needs to change workbook inputs and trust the formula output, do
not start with screenshots. Give it a small tool surface that can write cells,
recalculate, read the dependent formula values, and save a proof object.

Bilig has two entry points for that:

- `@bilig/workpaper` or `@bilig/workpaper` when the workbook can live as
  WorkPaper JSON inside the service or agent tool.
- `@bilig/xlsx-formula-recalc` or `@bilig/exceljs-formula-recalc` when the user already has
  an `.xlsx` pipeline and the immediate bug is stale formula results after
  editing inputs in Node.

## Run the agent starter first

From an empty directory:

```sh
npm create @bilig/workpaper@latest pricing-agent -- --agent
cd pricing-agent
npm install
npm run agent:verify
```

The starter builds a quote-approval workbook, writes request inputs, reads the
recalculated decision cells, persists JSON, restores the workbook, and prints a
compact `verified: true` proof. It also includes `AGENTS.md`, `CLAUDE.md`,
Cursor and VS Code MCP configs, and a generic MCP config under `mcp/`.

Use this when the agent owns the model and you want reviewable business logic,
not a hidden spreadsheet process.

## Prove the direct package path

If you do not want a generated project yet:

```sh
npm exec --package @bilig/workpaper@latest -- bilig-agent-challenge
```

That command is intentionally small. It proves the minimum loop an agent needs:

1. build or load a workbook;
2. read a formula-backed output;
3. edit an input cell;
4. read the dependent formula output again;
5. persist and restore state;
6. return `verified: true` only after readback matches.

## Tool contract

Keep the agent tool API boring. The useful surface is:

```ts
type SpreadsheetAgentTools = {
  listSheets(): Promise<string[]>
  readRange(input: { sheet: string; range: string }): Promise<unknown[][]>
  setCellContents(input: { sheet: string; cell: string; value: unknown }): Promise<{ changed: boolean }>
  getCellDisplayValue(input: { sheet: string; cell: string }): Promise<string>
  exportWorkpaperDocument(): Promise<{ json: string; bytes: number }>
}
```

The agent should not report success from `setCellContents` alone. The return
path should include the edited cell, formula readback before and after the
edit, persisted document size, and known limitations.

## Existing Excel or XLSX files

When the product already uses ExcelJS, SheetJS, `xlsx-populate`, or a template
library, keep that file-writing layer. Add a recalculation step before reading
formula outputs or sending the workbook.

For raw XLSX bytes:

```sh
npm install @bilig/xlsx-formula-recalc
npx --package @bilig/xlsx-formula-recalc xlsx-recalc quote.xlsx \
  --set Inputs!B2=48 \
  --read Summary!B7 \
  --out quote.recalculated.xlsx \
  --json
```

For ExcelJS:

```sh
npm install exceljs @bilig/exceljs-formula-recalc
npx --package @bilig/exceljs-formula-recalc exceljs-recalc --demo --json
```

Use this path for the common support-ticket shape: "my Node service changed
inputs in an XLSX file, but the formula value I read is still the old cached
value."

## Framework adapters

The same tool contract works in the usual agent stacks:

- OpenAI Agents SDK function tools;
- OpenAI Responses API function calling;
- Vercel AI SDK tools;
- LangChain.js tools and LangGraph.js `ToolNode`;
- LlamaIndex.TS tools;
- CrewAI or other Python agents through a small Node worker or MCP bridge.

The important part is not the framework. The important part is making the
spreadsheet state explicit: write input cells, recalculate, read formula
outputs, and persist the model.

## When not to use this

Keep Excel, LibreOffice, Microsoft Graph, or a human review step in the loop
when the workbook depends on macros, pivots, charts, external links, desktop
Excel add-ins, unsupported functions, or exact visual layout behavior.

Bilig is for workbook-shaped logic that can be represented as cells and
formulas. It is not a replacement for every Excel feature.

## Links

- [Agent WorkPaper tool-calling recipe](agent-workpaper-tool-calling-recipe.md)
- [Headless WorkPaper agent handbook](headless-workpaper-agent-handbook.md)
- [OpenAI Agents SDK WorkPaper tool](openai-agents-sdk-workpaper-tool.md)
- [OpenAI Responses WorkPaper tool call](openai-responses-workpaper-tool-call.md)
- [Vercel AI SDK and LangChain spreadsheet tools](vercel-ai-sdk-langchain-spreadsheet-tool.md)
- [XLSX formula recalculation in Node.js](xlsx-formula-recalculation-node.md)
- [ExcelJS formula recalculation in Node.js](exceljs-formula-recalculation-node.md)
- [GitHub repo](https://github.com/proompteng/bilig)
- [Star or bookmark Bilig](https://github.com/proompteng/bilig/stargazers)

---

## OpenAI Agents SDK WorkPaper Tool

Source: https://github.com/proompteng/bilig/blob/main/docs/openai-agents-sdk-workpaper-tool.md

# OpenAI Agents SDK WorkPaper Tools

Use this path when an OpenAI Agents SDK app needs a workbook tool it can call
from Node without opening Excel, LibreOffice, Google Sheets, or a screenshot UI.
The agent gets two ordinary function tools:

- `read_workpaper_summary` reads computed WorkPaper values and serialized cells.
- `set_workpaper_input_cell` writes one validated input cell and returns
  before/after readback, formula persistence checks, and restored JSON proof.

The maintained smoke script is provider-free by default. It imports
`Agent`, `tool()`, `RunContext`, and `invokeFunctionTool()` from
`@openai/agents`, creates a real SDK agent and function tools, then invokes the
tools locally so the read/write contract can run in CI without an API key:

```sh
pnpm --dir examples/headless-workpaper run agent:openai-agents-sdk
```

The OpenAI Agents SDK documents function tools as local functions wrapped with a
schema through `tool()`, and the same tools can be attached to an `Agent`:
<https://openai.github.io/openai-agents-js/guides/tools/>.

## Minimal Tool Shape

```ts
import { Agent, RunContext, invokeFunctionTool, tool } from '@openai/agents'
import { z } from 'zod'
import { WorkPaper } from '@bilig/headless'

const workbook = WorkPaper.buildFromSheets({
  Inputs: [
    ['Metric', 'Value'],
    ['Qualified opportunities', 20],
    ['Win rate', 0.25],
    ['Average ARR', 12000],
  ],
  Summary: [
    ['Metric', 'Value'],
    ['Expected ARR', '=Inputs!B2*Inputs!B3*Inputs!B4'],
  ],
})

const setWorkPaperInputCell = tool({
  name: 'set_workpaper_input_cell',
  description: 'Set one validated WorkPaper input cell and return formula readback.',
  parameters: z.object({
    sheetName: z.literal('Inputs'),
    address: z.string().regex(/^[A-Z]+[1-9][0-9]*$/),
    value: z.union([z.string(), z.number(), z.boolean(), z.null()]),
  }),
  execute: async ({ sheetName, address, value }) => {
    const sheet = workbook.getSheetId(sheetName)
    const summarySheet = workbook.getSheetId('Summary')
    if (sheet === undefined) {
      throw new Error(`Unknown sheet: ${sheetName}`)
    }
    if (summarySheet === undefined) {
      throw new Error('Summary sheet is missing')
    }
    const cell = workbook.simpleCellAddressFromString(address, sheet)
    const summaryRange = workbook.simpleCellRangeFromString('Summary!A1:B2', summarySheet)
    if (cell === undefined) {
      throw new Error(`Invalid cell: ${sheetName}!${address}`)
    }
    if (summaryRange === undefined) {
      throw new Error('Summary range is invalid')
    }

    const before = workbook.getRangeValues(summaryRange)
    workbook.setCellContents(cell, value)

    return {
      editedCell: `${sheetName}!${address}`,
      before,
      after: workbook.getRangeValues(summaryRange),
    }
  },
})

const agent = new Agent({
  name: 'WorkPaper verification agent',
  instructions: 'Use WorkPaper tools and answer only from computed readback.',
  tools: [setWorkPaperInputCell],
})

const result = await invokeFunctionTool({
  tool: setWorkPaperInputCell,
  runContext: new RunContext(),
  input: JSON.stringify({
    sheetName: 'Inputs',
    address: 'B3',
    value: 0.4,
  }),
})

console.log(agent.name, result)
```

For a production adapter, use the full example instead of this short snippet:
[`examples/headless-workpaper/openai-agents-sdk-tool-smoke.ts`](../examples/headless-workpaper/openai-agents-sdk-tool-smoke.ts).
It also verifies persisted formulas by exporting a WorkPaper document, restoring
it, and comparing the computed readback after restore.

## Expected Proof

The smoke output includes this shape:

```json
{
  "apiShape": "OpenAI Agents SDK Agent -> tool() -> invokeFunctionTool()",
  "package": "@openai/agents",
  "agentName": "WorkPaper verification agent",
  "toolNames": ["read_workpaper_summary", "set_workpaper_input_cell"],
  "writeResult": {
    "editedCell": "Inputs!B3",
    "before": { "expectedArr": 60000, "targetGap": -34000 },
    "after": { "expectedArr": 96000, "targetGap": 5600 },
    "checks": {
      "formulasPersisted": true,
      "restoredMatchesAfter": true,
      "expectedArrChanged": true
    }
  }
}
```

Keep the workbook mutation closed-world: validate sheet names and A1 addresses,
write one input at a time, recalculate through WorkPaper, return computed
readback, and persist only after the verification passes.

---

## MCP WorkPaper Tool Server

Source: https://github.com/proompteng/bilig/blob/main/docs/mcp-workpaper-tool-server.md

# MCP Spreadsheet Tool Server For WorkPaper Agents

This page is for agent builders who want workbook formulas behind a Model
Context Protocol surface. The useful boundary is small: list the tools, read
the workbook context resources, invoke a reusable workflow prompt, call one
tool, return exact cell readback, and include enough structured output for the
agent to verify the edit.

`@bilig/workpaper` is the public agent-facing package for WorkPaper MCP. MCP
stays as the transport and discovery layer around ordinary Node functions; the
lower-level runtime implementation still lives in `@bilig/headless`.

If you need the short agent decision path before the protocol details, start
with the [headless WorkPaper agent handbook](headless-workpaper-agent-handbook.md).

## Runnable MCP-Style Example

Run the dependency-free example from a clean checkout:

```sh
git clone https://github.com/proompteng/bilig.git
cd bilig
pnpm --dir examples/headless-workpaper install --ignore-workspace
pnpm --dir examples/headless-workpaper run agent:mcp-tools
```

For a local stdio transport, pipe newline-delimited JSON-RPC requests into the
stdio entrypoint:

```sh
printf '%s\n' \
  '{"jsonrpc":"2.0","id":1,"method":"initialize"}' \
  '{"jsonrpc":"2.0","method":"notifications/initialized"}' \
  '{"jsonrpc":"2.0","id":2,"method":"tools/list"}' |
  npm run --silent agent:mcp-stdio
```

## Copy-Paste JSON-RPC Transcript

Use the maintained transcript smoke when reviewing the server from an MCP
client, directory submission, or HN-style launch thread:

```sh
pnpm --dir examples/headless-workpaper install --ignore-workspace
pnpm --dir examples/headless-workpaper run agent:mcp-transcript
```

The script starts the stdio server, sends `initialize`, `tools/list`, and
`tools/call`, parses the JSON-RPC responses, asserts the formula readback, and
prints a compact transcript summary. The important response is the `tools/call`
result. A passing run returns structured content like this:

```json
{
  "jsonrpc": "2.0",
  "id": 3,
  "result": {
    "structuredContent": {
      "editedCell": "Inputs!B3",
      "before": {
        "expectedCustomers": 5,
        "expectedArr": 60000,
        "expansionArr": 66000,
        "targetGap": -34000
      },
      "after": {
        "expectedCustomers": 8,
        "expectedArr": 96000,
        "expansionArr": 105600,
        "targetGap": 5600
      },
      "restored": {
        "expectedCustomers": 8,
        "expectedArr": 96000,
        "expansionArr": 105600,
        "targetGap": 5600
      },
      "formulaContracts": {
        "expectedCustomers": "=Inputs!B2*Inputs!B3",
        "expectedArr": "=B2*Inputs!B4",
        "expansionArr": "=B3*Inputs!B5",
        "targetGap": "=B4-100000"
      },
      "checks": {
        "previousValue": 0.25,
        "newValue": 0.4,
        "formulasPersisted": true,
        "restoredMatchesAfter": true,
        "expectedArrChanged": true,
        "serializedBytes": 1163
      }
    },
    "isError": false
  }
}
```

That single response proves the tool changed one input cell, recalculated
dependent formulas, preserved the formulas through WorkPaper JSON
serialization, restored the document, and returned machine-checkable readback.

If you want the raw newline-delimited JSON-RPC request stream instead of the
maintained transcript wrapper, use:

```sh
printf '%s\n' \
  '{"jsonrpc":"2.0","id":1,"method":"initialize"}' \
  '{"jsonrpc":"2.0","method":"notifications/initialized"}' \
  '{"jsonrpc":"2.0","id":2,"method":"tools/list"}' \
  '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"set_workpaper_input_cell","arguments":{"sheetName":"Inputs","address":"B3","value":0.4}}}' |
  NODE_NO_WARNINGS=1 npm run --silent agent:mcp-stdio
```

The npm package exposes the demo server as `bilig-workpaper-mcp` by default:

```sh
npm exec --package @bilig/workpaper@0.40.43 -- bilig-workpaper-mcp
```

## Remote Stateless Endpoint

The hosted app runtime also exposes a JSON-only Streamable HTTP MCP endpoint for
clients that cannot launch a local stdio process:

```text
https://bilig.proompteng.ai/mcp
```

There is also a compatibility alias:

```text
https://bilig.proompteng.ai/mcp/workpaper
```

The endpoint is stateless and request-local. It loads the packaged demo
WorkPaper for each JSON-RPC request, exposes the same file-backed tool catalog,
resources, and prompts, and returns write/readback proof without writing user
files or issuing an MCP session id. Use it for Claude custom connector smoke
tests, directory probes, and agent onboarding. Use local file-backed stdio when
an agent needs to persist a real project WorkPaper JSON file.

Protocol smoke:

```sh
curl -fsS https://bilig.proompteng.ai/mcp \
  -H 'content-type: application/json' \
  -H 'accept: application/json, text/event-stream' \
  -H 'mcp-protocol-version: 2025-11-25' \
  --data '{"jsonrpc":"2.0","id":1,"method":"tools/list"}' | jq '.result.tools[].name'
```

For server-to-server clients, omit `Origin`. Browser-based clients must send an
allowed `Origin`; Claude origins are allowed by default.

For a real agent workflow, point the same binary at a persisted WorkPaper JSON
document:

```sh
npm exec --package @bilig/workpaper@0.40.43 -- bilig-mcp-challenge
npm exec --package @bilig/workpaper@0.40.43 -- bilig-workpaper-mcp --workpaper ./pricing.workpaper.json --init-demo-workpaper --writable
```

`bilig-mcp-challenge` is the one-command evaluator path. It initializes the
file-backed MCP server, lists tools/resources/prompts, edits `Inputs!B3`, reads
recalculated `Summary!B3`, exports WorkPaper JSON, restarts from disk, and
prints `verified: true`.

File-backed mode loads `./pricing.workpaper.json`, exposes `list_sheets`,
`read_range`, `read_cell`, `set_cell_contents`, `get_cell_display_value`,
`export_workpaper_document`, and `validate_formula`, then writes the updated
WorkPaper JSON back to the same file after `set_cell_contents` when `--writable`
is present. It also exposes `resources/list`, `resources/read`,
`prompts/list`, and `prompts/get` so clients can discover the live workbook
manifest, agent handoff instructions, current document JSON, and reusable edit
or formula-debug prompts. Omit `--writable` for read-only inspection.

The high-signal runtime resources are:

- `bilig://workpaper/manifest`
- `bilig://workpaper/agent-handoff`
- `bilig://workpaper/sheets`
- `bilig://workpaper/current-document`

The reusable prompts are:

- `edit_and_verify_workpaper`
- `debug_workpaper_formula`

Every file-backed tool includes an MCP `outputSchema`, parameter descriptions,
and safety annotations (`readOnlyHint`, `destructiveHint`, `idempotentHint`,
and `openWorldHint`). That is deliberate: directory scanners and coding agents
should be able to pick the workbook read, write, display, export, or formula
validation tool without treating the description as a vague demo.

Use the maintained file-backed transcript when a directory reviewer or agent
builder needs proof that the packaged binary mutates a real WorkPaper JSON file:

```sh
pnpm --dir examples/headless-workpaper install --ignore-workspace
pnpm --dir examples/headless-workpaper run agent:mcp-file-transcript
```

A passing run starts `npm exec --package @bilig/workpaper@latest --
bilig-workpaper-mcp --workpaper pricing.workpaper.json --init-demo-workpaper --writable`, lists the
file-backed tool surface, writes `Inputs!B3`, persists the JSON file, reads
`Summary!B3`, and asserts that the recalculated value is `96000`.

## Docker Target For Directory Introspection

MCP directories such as Glama need to start the server and run `tools/list`
without cloning the monorepo or building the web app. The root Dockerfile keeps
the production web image as `--target bilig-runtime` and adds a separate MCP
target for directory scanners:

```sh
docker build --target bilig-workpaper-mcp -t bilig-workpaper-mcp:local .
printf '%s\n' \
  '{"jsonrpc":"2.0","id":1,"method":"initialize"}' \
  '{"jsonrpc":"2.0","method":"notifications/initialized"}' \
  '{"jsonrpc":"2.0","id":2,"method":"tools/list"}' |
  docker run --rm -i bilig-workpaper-mcp:local
```

The target installs `@bilig/workpaper` from npm, seeds
`/workpaper/pricing.workpaper.json`, and starts
`bilig-workpaper-mcp --workpaper /workpaper/pricing.workpaper.json --init-demo-workpaper --writable`
over stdio. That makes directory introspection see the general WorkPaper tools:
`list_sheets`, `read_range`, `read_cell`, `set_cell_contents`,
`get_cell_display_value`, `export_workpaper_document`, and `validate_formula`.
It also carries the OCI label
`io.modelcontextprotocol.server.name=io.github.proompteng/bilig-workpaper`, so
registry and directory tooling can match the container target to the official
MCP Registry name.

For crawlers that cannot run Docker or stdio, the docs site also publishes a
static MCP server card at
`https://proompteng.github.io/bilig/.well-known/mcp/server-card.json`. The card
lists the same `list_sheets`, `read_range`, `read_cell`, `set_cell_contents`,
`get_cell_display_value`, `export_workpaper_document`, and `validate_formula`
tools, plus the WorkPaper resources and prompts, without requiring account auth
or a live server connection.

The hosted endpoint origin serves the same crawler-friendly card at
`https://bilig.proompteng.ai/.well-known/mcp/server-card.json`, with
`streamable-http` transport metadata for `https://bilig.proompteng.ai/mcp`.
That gives Smithery-style scanners a same-origin metadata path when they start
from the remote MCP URL rather than the documentation site.

The `@bilig/workpaper` package carries
`mcpName: io.github.proompteng/bilig-workpaper` and a matching `server.json`.
It is the canonical package metadata for the official MCP Registry entry
`io.github.proompteng/bilig-workpaper`:
<https://registry.modelcontextprotocol.io/v0.1/servers?search=io.github.proompteng%2Fbilig-workpaper>.

If you already know which client you want to use, start with the
[MCP client setup guide](mcp-client-setup.md) for Claude, Cursor, VS Code, and
Codex config snippets.

If you are checking a directory listing or preparing one, use the
[MCP spreadsheet server directory status page](mcp-spreadsheet-server-directory.md)
for the canonical npm command, official Registry proof, Glama listing, and
pending directory-review status.

Before submitting the server to an MCP registry, verify this repo-specific
readiness checklist:

- `packages/workpaper/server.json` exists and describes the packaged stdio
  server.
- `packages/workpaper/package.json` exposes `bilig-workpaper-mcp` in `bin`.
- `packages/workpaper/package.json` includes
  `mcpName: io.github.proompteng/bilig-workpaper`.
- `pnpm publish:runtime:check` passes against the runtime packages.
- `pnpm workpaper:smoke:external` passes against packed local runtime packages.

Passing the checklist means the repository metadata and smoke checks are ready
for registry submission; it does not mean the package has already been
published.

## Vercel AI SDK MCP Client Recipe

If your agent loop already uses the Vercel AI SDK, keep the MCP client thin and
let the WorkPaper server own the spreadsheet reads and writes:

```ts
import { createMCPClient } from '@ai-sdk/mcp'
import { Experimental_StdioMCPTransport } from '@ai-sdk/mcp/mcp-stdio'
import { generateText } from 'ai'

const client = await createMCPClient({
  transport: new Experimental_StdioMCPTransport({
    command: 'npm',
    args: [
      'exec',
      '--package',
      '@bilig/workpaper@0.40.43',
      '--',
      'bilig-workpaper-mcp',
      '--workpaper',
      './pricing.workpaper.json',
      '--init-demo-workpaper',
      '--writable',
    ],
  }),
})

try {
  const tools = await client.tools()
  const { text } = await generateText({
    model: 'your-model',
    tools,
    prompt: [
      'Read Summary!A1:B5 with read_range.',
      'Then set Inputs!B3 to 0.4 with set_cell_contents.',
      'Read Summary!B3 with read_cell and export the document.',
      'Return editedCell, before, after, persisted, and restoredMatchesAfter.',
    ].join('\n'),
  })

  console.log(text)
} finally {
  await client.close()
}
```

The server command is `bilig-workpaper-mcp`; the `npm exec --package
@bilig/workpaper -- bilig-workpaper-mcp` wrapper only resolves the published npm
package for a clean checkout. The stdio transport receives `npm` as the command
and the rest as `args`, so shell parsing does not sit between the AI SDK client
and the MCP server. The two tool calls prove the useful workflow: read a
formula-backed summary, set one input cell, and return computed before/after
readback.

Verify the docs links and discovery metadata after editing this page:

```sh
pnpm docs:discovery:check
```

The script implements the JSON-RPC methods needed for the file-backed WorkPaper
agent surface:

- `tools/list` returns `read_workpaper_summary` and
  `set_workpaper_input_cell` with JSON Schema inputs and MCP tool annotations.
- `tools/call` invokes the requested WorkPaper tool and returns text content
  plus structured formula readback.
- `resources/list` and `resources/read` expose the live WorkPaper manifest,
  sheet summary, current document JSON, and compact agent handoff.
- `prompts/list` and `prompts/get` expose the edit-and-verify and formula-debug
  workflows as reusable client prompts.

The packaged binary has two tool sets:

- default demo mode: `read_workpaper_summary` and `set_workpaper_input_cell`
- file-backed mode: `list_sheets`, `read_range`, `read_cell`,
  `set_cell_contents`, `get_cell_display_value`, `export_workpaper_document`,
  and `validate_formula`

The annotations are explicit for directory reviewers and cautious MCP clients:
`read_workpaper_summary` is read-only, idempotent, and closed-world.
`set_workpaper_input_cell` mutates the local WorkPaper state, is idempotent for
the same cell/value arguments, and is closed-world rather than a network or
filesystem tool.
In file-backed mode, `set_cell_contents` is annotated as destructive only when
the server starts with `--writable`.

### MCP Stdio Troubleshooting

| Symptom                        | What to check                                                                                   |
| ------------------------------ | ----------------------------------------------------------------------------------------------- |
| `Parse error` response         | Make sure each stdin line is valid JSON before it reaches the server.                           |
| No response appears            | End each JSON-RPC message with a newline; the server waits for newline-delimited input.         |
| Notification has no output     | `notifications/initialized` is intentionally one-way and does not produce a JSON-RPC response.  |
| `Invalid params` or tool error | Check that `tools/call` includes a supported `name` and the required `arguments` for that tool. |

The example deliberately avoids an MCP SDK dependency so the workbook contract
is visible. Put the same handlers behind stdio, HTTP, or your MCP SDK adapter
when you wire it into a production agent host.

## What A Passing Run Proves

The write tool edits `Inputs!B3`, recalculates dependent formulas, serializes
the WorkPaper document, restores it, and checks that formulas and computed
values survived the round trip:

```json
{
  "editedCell": "Inputs!B3",
  "before": {
    "expectedCustomers": 5,
    "expectedArr": 60000,
    "expansionArr": 66000,
    "targetGap": -34000
  },
  "after": {
    "expectedCustomers": 8,
    "expectedArr": 96000,
    "expansionArr": 105600,
    "targetGap": 5600
  },
  "checks": {
    "previousValue": 0.25,
    "newValue": 0.4,
    "formulasPersisted": true,
    "restoredMatchesAfter": true,
    "expectedArrChanged": true
  }
}
```

That is the part spreadsheet agents need. A tool that only says "updated" is
not enough. Return the edited address, previous value, new value, before/after
computed values, formula contracts, and persistence proof.

## Tool Boundary

Expose only the minimum useful surface first:

1. `read_workpaper_summary` reads a bounded range and returns computed values
   plus serialized cell contents.
2. `set_workpaper_input_cell` validates the sheet and A1 address before a
   write, then returns formula readback and persistence checks.
3. Everything outside that boundary stays in your MCP host: auth, transport,
   rate limits, logging, and user approval policy.

The official MCP specification describes tool discovery through `tools/list`,
tool invocation through `tools/call`, input schemas, and tool annotations:
<https://modelcontextprotocol.io/specification/2025-11-25/server/tools>.
It also defines server resources through `resources/list` and
`resources/read`, and reusable prompt templates through `prompts/list` and
`prompts/get`:
<https://modelcontextprotocol.io/specification/2025-11-25/server/resources>
and
<https://modelcontextprotocol.io/specification/2025-11-25/server/prompts>.

## Files To Inspect

- MCP-style adapter script:
  [`examples/headless-workpaper/mcp-tool-server.ts`](https://github.com/proompteng/bilig/blob/main/examples/headless-workpaper/mcp-tool-server.ts)
- stdio adapter script:
  [`examples/headless-workpaper/mcp-stdio-server.ts`](https://github.com/proompteng/bilig/blob/main/examples/headless-workpaper/mcp-stdio-server.ts)
- official MCP Registry entry:
  [`io.github.proompteng/bilig-workpaper`](https://registry.modelcontextprotocol.io/v0.1/servers?search=io.github.proompteng%2Fbilig-workpaper)
- example README:
  [`examples/headless-workpaper/README.md#mcp-tool-server-shape`](https://github.com/proompteng/bilig/tree/main/examples/headless-workpaper#mcp-tool-server-shape)
- SDK-neutral tool-calling recipe:
  [`docs/agent-workpaper-tool-calling-recipe.md`](agent-workpaper-tool-calling-recipe.md)
- Vercel AI SDK and LangChain wrappers:
  [`docs/vercel-ai-sdk-langchain-spreadsheet-tool.md`](vercel-ai-sdk-langchain-spreadsheet-tool.md)

## Feedback Thread

Use the
[MCP spreadsheet tool server discussion](https://github.com/proompteng/bilig/discussions/230)
for adapter feedback. The open questions are deliberately concrete: stdio,
HTTP/SSE, or SDK adapter next; which spreadsheet workflow should be proven
next; and which structured fields every write tool should return.

## When This Is A Good Fit

Use this pattern when an agent needs to edit a forecast, pricing workbook,
quote approval rule, budget check, or service-side spreadsheet model and prove
the formulas reacted. Keep the MCP layer thin, keep the workbook logic
testable, and make every write return structured verification.

Start with the adapter command above. If it saves you a spreadsheet-tooling
spike, star the repository so the next person searching for MCP spreadsheet
tools can find it:
<https://github.com/proompteng/bilig/stargazers>.

If it almost matches but a gap blocks adoption, use the adoption blocker form:
<https://github.com/proompteng/bilig/discussions/new?category=general>.

---

## Agent XLSX Formula Recalculation Without LibreOffice

Source: https://github.com/proompteng/bilig/blob/main/docs/agent-xlsx-formula-recalculation-without-libreoffice.md

# Agent XLSX formula recalculation without LibreOffice

If an agent edits an `.xlsx` file and then acts on a formula result, it needs a
fresh value before the next tool call. Returning the old cached value is worse
than an error because the agent thinks the workbook agreed with it.

Many spreadsheet-agent recipes solve this by running Excel, LibreOffice,
Microsoft Graph, or a Python recalculation helper after every file write. That
is a reasonable choice when exact Excel behavior matters. It is also a heavy
boundary for a Node agent tool that only needs a supported formula workbook,
verified readback, and an exported `.xlsx` at the edge.

Bilig's narrower path is:

1. import the `.xlsx` into a WorkPaper;
2. write the agent's input cells;
3. recalculate in the Node process;
4. read the output cells;
5. export the edited `.xlsx`;
6. reimport it in a smoke test to prove the boundary still works.

## Run the proof

This is the smallest useful check. It starts from a blank directory, downloads
one TypeScript file, creates an XLSX quote workbook, edits inputs, reads the
calculated approval result, exports the edited XLSX, and reimports it.

```sh
mkdir bilig-agent-xlsx-proof
cd bilig-agent-xlsx-proof
npm init -y >/dev/null
npm pkg set type=module
npm install @bilig/headless tsx
curl -fsSLO https://proompteng.github.io/bilig/xlsx-recalculation-proof.ts
npx tsx xlsx-recalculation-proof.ts
```

The run is useful only if it ends with:

```json
{
  "checks": {
    "decisionChanged": true,
    "recalculatedMargin": true,
    "exportedReimportMatchesAfter": true,
    "formulasSurvivedXlsxRoundTrip": true,
    "verified": true
  }
}
```

## Tool contract

For an agent, keep the tool surface boring:

```ts
type WorkbookEditRequest = {
  file: string
  writes: Array<{ sheet: string; cell: string; value: string | number | boolean }>
  reads: Array<{ sheet: string; cell: string }>
}

type WorkbookEditResult = {
  values: Array<{ sheet: string; cell: string; value: unknown }>
  exportedFile: string
  verified: true
}
```

The tool should refuse to return `verified: true` unless all of these happened:

- the target sheets and cells existed;
- every requested write was applied;
- formula output cells were read after the writes;
- the edited workbook was exported;
- the exported workbook could be imported again;
- the reimported values matched the values returned to the agent.

That contract is more important than the model prompt. The agent needs a
tool-shaped invariant it cannot hand-wave past.

## When not to use this

Keep Excel, LibreOffice, or Microsoft Graph in the loop when the workbook
depends on macros, pivots, charts, external links, unsupported functions, or
exact Excel UI behavior.

Use Bilig when the formulas are in the supported runtime surface and the job is
a backend or agent workflow: pricing checks, payout approvals, import
validation, budget gates, quote models, or fixture-driven workbook tests.

## Where this fits

This page exists for the same class of problem documented by spreadsheet-agent
tooling that shells out to a recalculation step after writing formulas. If your
agent already has LibreOffice available and the latency is acceptable, keep it.
If you want a TypeScript runtime that can be tested inside the agent tool loop,
run the proof above and inspect the emitted XLSX files.

Related:

- [curlable XLSX recalculation proof](xlsx-recalculation-proof.md)
- [XLSX formula recalculation in Node.js](xlsx-formula-recalculation-node.md)
- [stale XLSX formula cache in Node.js](stale-xlsx-formula-cache-node.md)
- [agent spreadsheet tool-call loop](agent-spreadsheet-tool-call-loop.md)
- [MCP spreadsheet tool server](mcp-workpaper-tool-server.md)
- [compatibility limits](where-bilig-is-not-excel-compatible-yet.md)

If this is the exact agent spreadsheet loop you are trying to avoid rebuilding,
star or bookmark Bilig so the next developer can find it:
<https://github.com/proompteng/bilig/stargazers>.

---

## Formula Bug Clinic

Source: https://github.com/proompteng/bilig/blob/main/docs/formula-bug-clinic.md

# Bilig formula bug clinic

If a workbook formula bug is blocking your Node service, send the smallest
public case that proves it. The goal is not to collect private spreadsheets.
The goal is to turn real failures into public fixtures that future evaluators
can run.

Good cases:

- an ExcelJS workflow writes inputs but formula readback is stale;
- an XLSX uses shared formulas and the imported formula text is wrong;
- a workbook works in Excel but fails in a local Node formula runtime;
- a WorkPaper JSON restore changes a calculated value;
- an agent or MCP tool writes a cell but cannot prove the recalculated output;
- a service route needs one missing formula family, import detail, or example.

Open the fixture form:
<https://github.com/proompteng/bilig/issues/new?template=workbook_fixture.yml>.

Discuss the shape first:
<https://github.com/proompteng/bilig/discussions/414>.

## Generate a local report

If the workbook is already reduced, run the clinic reporter locally and paste
the Markdown output into the fixture form. It reads the file on your machine and
does not upload workbook contents.

```sh
npm exec --package @bilig/headless@0.40.43 -- bilig-formula-clinic ./reduced.xlsx \
  --cells "Summary!B7,Inputs!B2"
```

That is the lowest-friction path for package users. It imports the workbook,
samples formulas, reads the requested cells through WorkPaper, and prints a
paste-ready Markdown report.

If you want to pin or edit the reporter script directly:

```sh
mkdir bilig-formula-clinic
cd bilig-formula-clinic
npm init -y
npm pkg set type=module
npm install @bilig/headless
npm install --save-dev tsx typescript @types/node
curl -fsSLo formula-clinic-report.ts \
  https://proompteng.github.io/bilig/formula-clinic-report.ts
npx tsx formula-clinic-report.ts ./reduced.xlsx \
  --cells "Summary!B7,Inputs!B2"
```

Use `--cells` for the output cells that prove the bug. The report includes
import warnings, formula samples, requested readback, and a paste-ready fixture
checklist.

## What to send

Send one reduced public fixture, not the whole production workbook.

Include:

- package version or commit tested;
- sheet names and exact cells or ranges;
- formulas involved;
- input values before and after the edit;
- expected output from Excel, LibreOffice, Graph, an existing service, or a
  manual check;
- actual Bilig output, import error, stale cached value, or missing API;
- the shortest command or script that maintainers can run.

Do not attach confidential workbooks, customer data, financial models, or files
that cannot be redistributed in a public test corpus. Replace names and numbers
with neutral values while keeping the same formula shape.

## Why this helps

Stars usually follow evidence, not claims. A reduced workbook fixture is better
than a marketing post because it gives maintainers something concrete to merge:

- a regression test;
- an XLSX import/export corpus case;
- a formula compatibility note;
- a WorkPaper JSON persistence fixture;
- a service-route example;
- an MCP or agent-tool transcript.

When a case lands, the issue can point to the commit, release, and docs page
that fixed it. That is the evidence a skeptical backend developer can inspect
before adopting the package.

## Fast local check

For stale cached XLSX values, first verify whether the backend is reading an old
stored value instead of a fresh calculation:

```sh
git clone --depth 1 https://github.com/proompteng/bilig.git
cd bilig/examples/xlsx-recalculation-node
pnpm install
pnpm run smoke
```

For a pure WorkPaper case, reduce it to a script:

```sh
mkdir bilig-fixture-check
cd bilig-fixture-check
npm init -y
npm pkg set type=module
npm install @bilig/headless
npm install --save-dev tsx typescript @types/node
```

If the script is short enough to paste into an issue, it is probably a good
fixture.

## Useful references

- [Submit a workbook fixture](submit-workbook-fixture.md)
- [ExcelJS shared formulas and Node.js recalculation](exceljs-shared-formula-recalculation-node.md)
- [Fix stale XLSX formula values in Node.js](stale-xlsx-formula-cache-node.md)
- [XLSX formula recalculation in Node.js](xlsx-formula-recalculation-node.md)
- [Where Bilig is not Excel-compatible yet](where-bilig-is-not-excel-compatible-yet.md)

If this helped you reduce a workbook bug, star or bookmark the repository so
the next backend developer can find the fixture path:
<https://github.com/proompteng/bilig/stargazers>.

---

## Try Bilig Headless In Node

Source: https://github.com/proompteng/bilig/blob/main/docs/try-bilig-headless-in-node.md

# Try Bilig WorkPaper in Node

This page is for people who want to try the package before reading the whole
repo. It starts from an empty directory, installs the published npm package,
builds a tiny WorkPaper, edits an input cell, reads the recalculated formula
result, serializes the document, restores it, and reads the result again.

No browser UI, account, server, or clone is required.

## Quickstart

```sh
mkdir bilig-headless-eval
cd bilig-headless-eval
npm init -y
npm pkg set type=module
npm install @bilig/workpaper
npm install -D tsx typescript @types/node
curl -fsSLo quickstart.ts https://proompteng.github.io/bilig/npm-eval.ts
npx tsx quickstart.ts
```

Expected output:

```json
{
  "before": 24000,
  "after": 38400,
  "afterRestore": 38400,
  "sheets": ["Inputs", "Summary"],
  "bytes": 999,
  "verified": true,
  "nextStep": "If this proof matches your service or agent workflow, star or bookmark Bilig: https://github.com/proompteng/bilig/stargazers"
}
```

The exact byte count can change between package versions. The important part is
that `verified` is `true` and `afterRestore` matches `after`.

The downloaded file is the maintained TypeScript example at
[`examples/headless-workpaper/npm-eval.ts`](https://github.com/proompteng/bilig/blob/main/examples/headless-workpaper/npm-eval.ts).

## Try it in Docker (optional)

> **Note:** pnpm is the primary recommended path. This section is for
> evaluators who prefer not to change their local Node version.

After completing the **Quickstart** step above you will have a `quickstart.ts` file
inside `bilig-headless-eval/`. Mount that directory into an official Node 24
container and run the same script:

```sh
docker run --rm \
  -v "$(pwd)":/eval \
  -w /eval \
  node:24-slim \
  bash -c "npm install --silent && npx tsx quickstart.ts"
```

Expected output (same as above; `verified` must be `true`):

```json
{
  "before": 24000,
  "after": 38400,
  "afterRestore": 38400,
  "sheets": ["Inputs", "Summary"],
  "bytes": 999,
  "verified": true,
  "nextStep": "If this proof matches your service or agent workflow, star or bookmark Bilig: https://github.com/proompteng/bilig/stargazers"
}
```

No repo clone is needed. The container installs dependencies from npm and exits
cleanly after printing the result.

## What this proves

- multi-sheet workbook creation from plain arrays
- formula evaluation without a browser grid
- input edits through the workbook API
- computed value readback after the edit
- JSON document export, parse, restore, and readback

This is the core shape behind the larger examples for service routes, MCP tools,
agent writeback, and workbook automation.

## What this does not prove

`bilig` is not a finished Excel clone. It is useful when a TypeScript service or
agent needs a formula-backed workbook object it can mutate and persist. For full
Excel compatibility or XLSX layout fidelity, check the comparison and
compatibility pages before adopting it.

## Next paths

- [GitHub repository](https://github.com/proompteng/bilig)
- [@bilig/workpaper npm package](https://www.npmjs.com/package/@bilig/workpaper)
- [@bilig/headless npm package](https://www.npmjs.com/package/@bilig/headless)
- [Five Node.js workbook automation examples](workbook-automation-examples-node.md)
- [Node.js spreadsheet formula engine guide](node-spreadsheet-formula-engine.md)
- [WorkPaper service recipe](node-service-workpaper-recipe.md)
- [MCP spreadsheet tool server](mcp-workpaper-tool-server.md)
- [What the WorkPaper benchmark proves](what-workpaper-benchmark-proves.md)
- [Where bilig is not Excel-compatible yet](where-bilig-is-not-excel-compatible-yet.md)

If the quickstart matches a backend or agent workflow you are building, star the
repo so the package is easier to find later:
<https://github.com/proompteng/bilig/stargazers>.

If it almost matches but a gap blocks adoption, use the adoption blocker form:
<https://github.com/proompteng/bilig/discussions/new?category=general>.

---

## Quote Approval WorkPaper API

Source: https://github.com/proompteng/bilig/blob/main/docs/quote-approval-workpaper-api.md

# Quote approval WorkPaper API in Node

Use this page when you want a production-shaped `@bilig/headless` proof instead
of a toy arithmetic workbook.

The smoke runs a quote approval workflow:

1. Build a two-sheet WorkPaper with `Inputs` and `Summary`.
2. Write quote input cells: units, list price, discount, unit cost, and minimum
   margin.
3. Recalculate formulas for net revenue, gross margin, and approval decision.
4. Serialize the WorkPaper document as JSON.
5. Restore that JSON and verify the restored workbook still matches the
   recalculated result.

No browser grid, spreadsheet account, OAuth setup, or repo clone is required.

## Run It From An Empty Directory

```sh
mkdir bilig-quote-approval
cd bilig-quote-approval
npm init -y
npm pkg set type=module
npm install @bilig/headless
npm install -D tsx typescript @types/node
curl -fsSLo quote-approval-api.ts \
  https://raw.githubusercontent.com/proompteng/bilig/main/examples/serverless-workpaper-api/quote-approval-api.ts
npx tsx quote-approval-api.ts
```

Expected shape:

```json
{
  "route": "Quote approval WorkPaper API",
  "inputCells": {
    "units": "Inputs!B2",
    "listPrice": "Inputs!B3",
    "discount": "Inputs!B4",
    "unitCost": "Inputs!B5",
    "minimumMargin": "Inputs!B6"
  },
  "before": {
    "netRevenue": 43200,
    "grossMargin": 0.2963,
    "decision": "review"
  },
  "edit": {
    "input": {
      "units": 40,
      "listPrice": 1200,
      "discount": 0.05,
      "unitCost": 760,
      "minimumMargin": 0.3
    },
    "after": {
      "netRevenue": 45600,
      "grossMargin": 0.3333,
      "decision": "approved"
    },
    "checks": {
      "decisionChanged": true,
      "formulasPersisted": true,
      "inputPersisted": true,
      "restoredMatchesAfter": true
    }
  },
  "verified": true,
  "nextStep": "If this proof matches your service or agent workflow, star or bookmark Bilig: https://github.com/proompteng/bilig/stargazers"
}
```

The exact serialized byte count can move between releases. The important parts
are:

- `decisionChanged: true`
- `formulasPersisted: true`
- `inputPersisted: true`
- `restoredMatchesAfter: true`
- `verified: true`

## What This Proves

This is the service boundary that matters for backend adoption:

- input JSON maps to known workbook cells
- formulas recalculate after the write
- the returned values come from formula readback, not a screenshot
- the persisted JSON still contains formulas
- a restored WorkPaper returns the same decision

That is the shape behind pricing rules, discount approval, payout checks,
budget guardrails, import validation, and agent tools that need exact readback.

## What This Does Not Prove

It does not prove full Excel compatibility. It does not prove formatting,
charts, collaboration, or broad XLSX file fidelity. For those boundaries, read
the [compatibility limits](where-bilig-is-not-excel-compatible-yet.md) and the
[production adoption checklist](production-adoption-checklist-headless-workpaper.md).

It also does not prove every formula family you need is implemented. If this API
shape is right but a formula, persistence shape, or framework boundary blocks a
trial, open a concrete adoption note in the
[workflow feedback discussion](https://github.com/proompteng/bilig/discussions/157).

## Use It In A Service

The full example is
[`examples/serverless-workpaper-api`](https://github.com/proompteng/bilig/tree/main/examples/serverless-workpaper-api).
It includes:

- a web-standard `Request` / `Response` route handler
- a quote approval route
- a Vercel Function smoke
- a Next.js App Router smoke
- framework adapters
- persistence adapter examples

Run the wider proof from a repo checkout:

```sh
pnpm --dir examples/serverless-workpaper-api install --ignore-workspace
pnpm --dir examples/serverless-workpaper-api run test
pnpm --dir examples/serverless-workpaper-api run framework-adapters
pnpm --dir examples/serverless-workpaper-api run persistence-adapters
```

## Next Pages

- [Try `@bilig/headless` in Node](try-bilig-headless-in-node.md)
- [Serverless WorkPaper API route](serverless-workpaper-api-route.md)
- [Node service WorkPaper recipe](node-service-workpaper-recipe.md)
- [Five Node.js workbook automation examples](workbook-automation-examples-node.md)
- [What the WorkPaper benchmark proves](what-workpaper-benchmark-proves.md)
- [Where bilig is not Excel-compatible yet](where-bilig-is-not-excel-compatible-yet.md)

If this matches a workflow you are evaluating, star the repo as a bookmark:
<https://github.com/proompteng/bilig/stargazers>.

If it almost matches but a gap blocks adoption, use the adoption blocker form:
<https://github.com/proompteng/bilig/discussions/new?category=general>.

---

## Compatibility Limits

Source: https://github.com/proompteng/bilig/blob/main/docs/where-bilig-is-not-excel-compatible-yet.md

# Where bilig Is Not Excel-Compatible Yet

Status: public compatibility boundary for `@bilig/headless`

`bilig` is not a complete Excel clone. The current adoption wedge is narrower:
`@bilig/headless` gives Node services and agents a workbook API with formulas,
structural edits, persistence, validation, and auditable benchmark artifacts.

This page names the main compatibility boundaries so people can evaluate the
project without reading a pile of benchmark JSON first.

## Current Evidence Snapshot

The repository keeps compatibility and performance claims tied to checked-in
artifacts:

- formula inventory breadth is `100%` for the current office-listed and tracked
  formula inventory in
  [`packages/benchmarks/baselines/bilig-dominance-scorecard.json`](../packages/benchmarks/baselines/bilig-dominance-scorecard.json)
- formula semantics coverage has `300` canonical fixtures and `10` workbook
  semantics fixtures, with no missing committed fixture ids in
  [`packages/benchmarks/baselines/calculation-semantics-scorecard.json`](../packages/benchmarks/baselines/calculation-semantics-scorecard.json)
- import/export fidelity passes required CSV/XLSX cases, reports no unsupported
  import/export features, and explicitly declines native macro execution in
  [`packages/benchmarks/baselines/import-export-fidelity-scorecard.json`](../packages/benchmarks/baselines/import-export-fidelity-scorecard.json)
- the headless benchmark claim is `94/100` mean wins against the current
  HyperFormula-style comparable workload scorecard, with the worst p95 row kept
  visible in
  [`docs/what-workpaper-benchmark-proves.md`](what-workpaper-benchmark-proves.md)

Those artifacts are useful evidence. They are not a blanket promise that every
Excel workbook, every formula argument shape, every UI interaction, or every
third-party file behaves exactly like desktop Excel.

## The Biggest Non-Goals

### Native macro execution

`bilig` does not execute VBA or spreadsheet macro code.

The XLSM path detects macro-enabled workbooks, preserves safe workbook cells,
preserves the original VBA payload and code names for round trips, and records a
non-execution warning. Native macro execution remains a deliberately declined
runtime feature: `xlsx.macros.execution`.

That boundary is security posture, not a missing convenience feature.

### Full Excel application parity

`@bilig/headless` is a workbook engine package, not a replacement for the full
Excel desktop application.

It does not claim complete parity for:

- ribbon behavior, dialog behavior, add-ins, and desktop automation surfaces
- arbitrary interactive chart editing
- arbitrary interactive PivotTable refresh behavior
- Excel's full UI collaboration surface
- every file produced by every Excel-compatible application

The current XLSX scorecard proves round trips for values, formulas, formats,
defined names, comments, styles, conditional formats, dimensions, merges,
freeze panes, filters, sorts, sheet protection, protected ranges, data
validations, tables, charts, pivots, multi-sheet workbooks, and macro payload
preservation. It does not turn charts and pivots into a promise of full desktop
Excel interactivity.

### Universal formula-behavior parity

The formula registry and fixture suite are broad, and the current tracked
Office formula inventory is production-routed. The formula-behavior claim is
still evidence-scoped.

The current formula semantics artifact proves the committed canonical fixtures
and workbook semantics fixtures. It should not be read as "every Excel formula
argument combination and locale/date edge case is already proven." New edge
cases should become fixtures, and unsupported deterministic formulas in an XLSX
corpus should show up as mismatches rather than being silently accepted.

### Cached XLSX result parity for arbitrary corpora

Cached-result parity is a corpus property, not a universal package guarantee.

Use:

```sh
pnpm workpaper:xlsx-corpus:check -- /path/to/xlsx-corpus
```

The verifier reads `.xlsx`, `.xlsm`, and `.xls` files and compares formula
cells against cached workbook results where that comparison is meaningful.
Missing cached results and volatile or environment-dependent formulas such as
`NOW()` and `CELL()` are counted as skipped, not as proof of parity.

For a concrete report walkthrough, see
[`docs/xlsx-corpus-verifier-walkthrough.md`](xlsx-corpus-verifier-walkthrough.md).

### UI dominance claims

The local browser grid and WorkPaper headless engine are different surfaces.

The live browser scorecard currently covers public unauthenticated browser load
and viewport scroll timing for Google Sheets and Microsoft Excel Web. Its own
limitations say it does not cover authenticated edit latency, equivalent
tenants, every browser-cache condition, or every real user workflow.

Do not use the headless WorkPaper benchmark to claim the browser grid is faster
than every spreadsheet UI. Keep those claims separated.

## When bilig Is A Good Fit Today

`@bilig/headless` is a good fit when you need:

- a Node workbook engine for formula-backed business workflows
- agent-controlled workbook edits with explicit readback
- structural edits without driving a browser UI
- JSON persistence and restore for workbook state
- benchmark artifacts you can inspect and rerun
- import/export paths that surface compatibility warnings instead of hiding
  them

Start with:

- [`docs/why-agents-need-workbook-apis.md`](why-agents-need-workbook-apis.md)
- [`docs/building-a-revenue-model-with-headless-workpaper.md`](building-a-revenue-model-with-headless-workpaper.md)
- [`examples/headless-workpaper`](../examples/headless-workpaper)

## How To Improve Compatibility

The right contribution is usually not a vague "support Excel better" issue.
Use one of these shapes:

- add a minimal workbook fixture that exposes a real mismatch
- add a canonical formula fixture for a missing semantic edge
- add an XLSX round-trip case with a specific expected metadata surface
- extend the corpus verifier report when a skipped or mismatched case needs a
  clearer explanation
- add a focused public example that shows a supported workflow end to end

Small, reproducible compatibility reports are much more useful than screenshots
or broad parity claims.

---

## npm Provenance And Package Trust

Source: https://github.com/proompteng/bilig/blob/main/docs/npm-provenance-package-trust.md

# Verify npm Provenance For `@bilig/headless`

Production adoption starts before the first import. For a service runtime or
agent tool, the package needs to be traceable to source, release CI, and a
specific GitHub commit.

`@bilig/headless` is published with npm registry signatures and SLSA provenance
attestations. npm reports this for the latest published package:

```sh
npm view @bilig/headless@latest version dist.attestations dist.signatures --json
```

The important signal is that `dist.attestations.provenance.predicateType` is
`https://slsa.dev/provenance/v1` and that `dist.signatures` is non-empty.

## Verify After Install

From a clean project:

```sh
mkdir bilig-package-trust
cd bilig-package-trust
npm init -y
npm install @bilig/headless
npm audit signatures
```

Expected result for the current dependency tree:

```text
audited 31 packages in 0s

31 packages have verified registry signatures

10 packages have verified attestations
```

Use this as a package-integrity check, not as an application-security claim.
You still need workflow fixtures, rollback, and formula compatibility gates for
your own WorkPaper-backed service.

## Release Path

Runtime packages are released by `.github/workflows/headless-package.yml`.
The workflow:

- verifies the runtime package chain;
- checks publishable package metadata with `pnpm publish:runtime:check`;
- requires Forgejo and GitHub `main` to agree before publishing;
- uses `id-token: write` for GitHub Actions OIDC;
- publishes through `scripts/publish-runtime-package-set.ts` with
  `npm publish ... --provenance`.

npm documents trusted publishing as an OIDC flow that avoids long-lived npm
tokens and can automatically generate provenance for public packages published
from public repositories:

- <https://docs.npmjs.com/trusted-publishers/>
- <https://docs.npmjs.com/viewing-package-provenance/>

OpenSSF Scorecard is another useful consumer-side signal for evaluating
dependency risk:

- <https://scorecard.dev/>

This repository runs the official OpenSSF Scorecard action on every `main`
update and on a weekly schedule. Results are published to the public Scorecard
API, exposed through the README badge, and uploaded as SARIF to GitHub code
scanning so dependency evaluators can inspect repository posture separately
from npm package provenance.

The GitHub trust surface also includes CodeQL analysis for the
JavaScript/TypeScript codebase and Dependabot version updates for npm, GitHub
Actions, and the root Dockerfile. Those checks do not replace package
provenance, but they make vulnerability discovery and dependency drift visible
before a production adopter has to ask for it.

## What This Does Not Prove

Package provenance does not prove that a workbook workflow is correct, complete,
or safe for every production domain.

Before adopting `@bilig/headless` for customer-critical work, also run:

- the [90-second npm eval](try-bilig-headless-in-node.md);
- the [quote approval WorkPaper API proof](quote-approval-workpaper-api.md);
- the [production adoption checklist](production-adoption-checklist-headless-workpaper.md);
- the [compatibility limits](where-bilig-is-not-excel-compatible-yet.md).

The package-trust question is: "Did this package come from the expected source
and release path?" The production-readiness question is: "Does this exact
workflow have fixtures, rollback, and compatibility evidence?"
