Convert Mathematica .nb or .m files to Wolfbook .wb format so they open and run in VS Code. Use when bringing existing .nb/.m files into Wolfbook, or to make an existing .wb bridge-safe.
Installs into .claude/skills of the current project.
Are you the author of Nb To Wolfbook?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/mexregkan-nb-to-wolfbook)
---
name: nb-to-wolfbook
description: "Convert Mathematica .nb or .m files to Wolfbook .wb format so they open and run in VS Code. Use when bringing existing .nb/.m files into Wolfbook, or to make an existing .wb bridge-safe."
---
# nb-to-wolfbook
Converts Mathematica files (.nb / .m) to Wolfbook `.wb` (a VS Code Notebook JSON),
and — critically — makes the resulting cells **bridge-safe** so they evaluate
identically through the Wolfbook MCP (`runCell` / `evaluateExpression`) as they do
in the Mathematica front end.
## When to invoke
`/nb-to-wolfbook <file-or-directory>` — convert .nb/.m to .wb (bridge-safe + PUA→ASCII).
`/nb-to-wolfbook --fix-wb <file.wb>` — normalize an EXISTING .wb in place (bridge-safe + PUA→ASCII), backup written.
`/nb-to-wolfbook --puafix <file.wb>` — de-rectangle ONLY (PUA→ASCII, no newline reflow): the smallest diff that removes the rectangle glyphs from an already-bridge-safe .wb.
## ⚠️ The bug this skill exists to prevent (READ THIS)
A Wolfram cell can wrap **one statement over several physical lines**:
```
Eeqv[{j1_},{k1_}] := (-1)^(j1) j1!/(k1-1) Coefficient[
GenSer, ep[j1,k1]];
MMVbar[j_,k_] := (-1)^(Total[j]+Length[j])
MMV[j,k];
```
The front end parses each cell as one unit, so this is fine *interactively*. But an
evaluator that **splits a cell on newlines** (the Wolfbook MCP `runCell`, a headless
`Get` of re-serialised text, naive `ToExpression`) sees broken fragments and either:
- throws `Syntax::sntxi: Incomplete expression` (the `Sum[...,⏎{...}]` case), or
- treats the newline as **implicit `Times`**: `(-1)^k ⏎ MMV[...]` silently drops to a
product / loses the `MMV` factor, and `def1;⏎def2;` becomes `Times[def1;, def2;]`
instead of two definitions.
This corrupts the kernel **silently** — definitions look present but are wrong — and is
exactly what makes a cell-by-cell rebuild "unfaithful." The fix: put **each statement on
one physical line**, collapsing only the *intra-statement* newlines (insignificant
whitespace) and keeping a newline only after a top-level `;`. Semantics are unchanged,
comments are preserved, and the cell is now safe for any evaluator.
### The subtle case that slipped through once (the MMVbar regression)
`MMVbar[...] := (-1)^(Total[j]+Length[j]) MMV[...]` is **one** RowBox (the space is
implicit `Times`), merely *soft-wrapped* for display in the `.nb`. The FE's `"InputText"`
export **ignores PageWidth** (verified: Cell option, ExportPacket option, wrapping-Notebook
option — all still wrap) and turns the wrap into a hard newline **with a continuation
indent**: `... Length[j])\n MMV[...]`. The line ends in `)` — a *complete value* — so the
old "does the line end in an operator?" heuristic said "statement boundary, keep the
newline," and the `MMV` factor was dropped (MMVbar became a bare sign). This sat latent in
the committed `.wb`/`.nb` for months.
The fix (in `wl_normalize.normalize`): a depth-0 newline **followed by a continuation
indent** (spaces/tabs then more code, not a blank line) is a wrap → collapse to a space;
genuine statement breaks export at column 0, so they are still kept. Backstop:
`find_split_hazards` (CLI `--check`) flags any definition whose RHS is split across a
top-level newline.
## ⚠️ The OTHER bug: PUA operators render as empty rectangles
Mathematica stores `==`, `->`, `:>` (and the constants `I`, `E`) as characters in the
Unicode **Private Use Area** (`\[Equal]`=U+F431, `\[Rule]`=U+F522, `\[RuleDelayed]`=U+F51F,
`\[ImaginaryI]`=U+F74E, `\[ExponentialE]`=U+F74D, `\[LongEqual]`=U+F7D9). The kernel parses
them IDENTICALLY to the ASCII forms, and Mathematica's own fonts have glyphs for them — but
the front-end `"InputText"` export keeps the literal PUA chars, so the `.wb` carries them too.
A code font without the Wolfram PUA (VS Code's default, most monospace fonts) then draws each
as an **empty rectangle (tofu)** — e.g. a cell's `If[w == 1, If[k == 2, …]]` showed as
`If[w ▯ 1, If[k ▯ 2, …]]`. Nothing is broken; it is purely a display problem, but a confusing one.
The fix (in `wl_normalize.pua_to_ascii`, called first inside `normalize`): replace each PUA
operator/constant with its ASCII equivalent (`\[Equal]`→`==`, `\[Rule]`→`->`, `\[RuleDelayed]`→`:>`,
`\[LongEqual]`→`==`, `\[ImaginaryI]`→`I`, `\[ExponentialE]`→`E`). Kernel-identical, displays in
any font. **String-aware:** PUA inside `"..."` string literals is left untouched (that is data,
not code); PUA inside `(* comments *)` IS converted (display text, safe). **Word-boundary-safe:**
the letter constants `I`/`E` get spaces inserted only when an adjacent alphanumeric would otherwise
glue them into a different symbol (`2\[ImaginaryI]`→`2 I`, never `2I`-as-something-else).
Standard-Unicode operators that are NOT PUA (`\[And]`∧, `\[Or]`∨, `\[LessEqual]`≤, `\[Element]`∈,
Greek letters ω/α/…) render fine and are deliberately left alone — converting them would be scope
creep. Any PUA char with no ASCII mapping is reported (still a rectangle; decide by hand).
⚠️ **Editing a `.wb` that is OPEN in VS Code:** the live editor buffer overwrites on-disk edits,
so a disk-level fix gets silently clobbered when the buffer next saves. Close the notebook tab (or
edit via the Wolfbook MCP), THEN run the fix, THEN reopen.
## Helper scripts (committed alongside this skill)
- `wl_normalize.py` — the normalizer. `normalize(code)` first runs `pua_to_ascii` (PUA
operators → ASCII; see the rectangle bug above) then collapses intra-statement newlines
(incl. FE soft-wraps via the continuation-indent signal); string/comment/bracket-aware.
CLIs: `python3 wl_normalize.py --wb <file.wb>` rewrites every wolfram code cell in place
(newline reflow + PUA→ASCII; writes `<file>.wb.bak`; warns on residual hazards/PUA);
`python3 wl_normalize.py --puafix <file.wb>` does PUA→ASCII only (no reflow, minimal diff);
`python3 wl_normalize.py --check <file.wb>` reports split-statement hazards AND any
rectangle-rendering PUA chars (exit 1 if any). Or pipe: `... | python3 wl_normalize.py`.
- `nb2wb_extract.wls` — wolframscript extractor: reads a `.nb` and emits every cell in
order as **faithful InputText** via the front end (`FrontEnd`ExportPacket[..., "InputText"]`),
preserving comments and special characters. Avoids the old lossy "Package export + split
on blank lines," which destroyed cell boundaries.
- `nb2wb.py` — the driver: runs the extractor, normalizes code cells, writes the `.wb`.
## Steps for .nb files (preferred path — needs wolframscript)
```bash
python3 .claude/skills/nb-to-wolfbook/nb2wb.py "<file.nb>" ["<out.wb>"]
```
This produces a faithful, bridge-safe `.wb`. Report the printed summary line.
If `wolframscript` is unavailable, fall back to: open the notebook in Mathematica,
File → Save As → Package (.m), then use the `.m` steps below — but STILL run the
normalizer on the result (see below), or the multi-line bug persists.
## Steps for .m files
1. Read the file.
2. Split into cells: runs of ≥2 blank lines are cell boundaries; a block that is only a
`(* === Title === *)`-style comment becomes a markdown cell, others are code cells.
3. **Normalize each code cell** with `wl_normalize.normalize` (bridge-safety — non-negotiable).
4. Write the `.wb` with `json.dumps(notebook, indent=1, ensure_ascii=False)`.
## .wb format reminders
- `kind: 2` = wolfram code cell (set `"languageId": "wolfram"`), `kind: 1` = markdown.
- 1-space indent; `value` a valid JSON string; `outputs: []`; UTF-8.
- Always build JSON with Python's `json` module, never by hand.
## Making an existing .wb safe (no reconversion)
```bash
python3 .claude/skills/nb-to-wolfbook/wl_normalize.py --wb "numerics/your-notebook.wb"
```
Normalizes every code cell in place (backup `.wb.bak`). Use this when a `.wb` already
exists but mis-evaluates through the bridge.
## Verify after conversion
Run the checker (catches the `)`-suffix / implicit-`Times` split that a naive operator-suffix
test misses — the MMVbar regression — AND reports any rectangle-rendering PUA chars left in
code cells):
```bash
python3 .claude/skills/nb-to-wolfbook/wl_normalize.py --check "<file.wb>"
# expect: "OK: no split-statement hazards ..." and "OK: no PUA (rectangle) chars ..." (exit 0)
```
The driver also prints any hazard inline. Output format:
`Converted: <src> -> <dst> (N code cells, M markdown cells, bridge-safe).`
If any file could not be converted, say which and why. A flagged hazard is usually a
genuine split to fix; an occasional false positive is a real two-statement cell that
lacks a `;` separator — add the `;` (or split into two cells) to clear it.