Agent skill

Wasm Interop

by andymai in andymai/brepjs

This skill should be used when working across the JS/WASM boundary in brepjs — writing or debugging code in src/kernel/occt, src/kernel/occtWasm, or src/kernel/brepkit, or diagnosing symptoms like…

Apache-2.0Auto-check passedDevOps & Cloud

Install Wasm Interop

skills CLI
$ npx skills add andymai/brepjs --skill wasm-interop -a claude-code

Project install by default; add -g for ~/.claude/skills/.

GitHub CLI
$ gh skill install andymai/brepjs wasm-interop --agent claude-code

Project scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).

Manual copy
$ git clone --depth 1 https://github.com/andymai/brepjs.git skills-src && mkdir -p .claude/skills && cp -r skills-src/.claude/skills/wasm-interop .claude/skills/wasm-interop && rm -rf skills-src

Use ~/.claude/skills/ instead of .claude/skills for a personal install. The folder must contain SKILL.md.

Claude Code skills documentation · loads skills from .claude/skills/

Facts

Skill name
wasm-interop
GitHub stars
115
Token cost
~3k tokens
SKILL.md length
1,225 words
Files
2 (incl. references)
Skills in repo
21
Repo updated
First seen
Licence
Apache-2.0

At a glance

This skill should be used when working across the JS/WASM boundary in brepjs — writing or debugging code in src/kernel/occt, src/kernel/occtWasm, or src/kernel/brepkit, or diagnosing symptoms like…

  • DevOps & Cloud work in your project
  • SKILL.md covers When to use, Enums across the boundary, Typed arrays and the heap and Initialization, plus 3 more sections
  • Calls bash

What it does

Wasm Interop is an agent skill from andymai/brepjs. This skill should be used when working across the JS/WASM boundary in brepjs — writing or debugging code in src/kernel/occt, src/kernel/occtWasm, or src/kernel/brepkit, or diagnosing symptoms like "enum comparison is always false", "GetType returned an object not a number", "mesh vertices are garbage or zeros", "detached ArrayBuffer", ".map on a Uint32Array produces wrong values", "brepjs kernel not initialized", "brepjssingle.js is missing", "init() falls back to the wrong kernel", "dynamic import of occt-wasm…

Its SKILL.md is about 3k tokens, which your agent loads only when the skill is triggered. The skill folder holds 2 other files, including reference files (for example `references/opencascade-build.md`).

It sits in DevOps & Cloud. It works with WebAssembly, Docker and Vite. The repository describes itself as: Web CAD library with exact B-Rep geometry. The licence is Apache-2.0.

When your agent uses it

  • DevOps & Cloud work in your project

Example prompts

  • “enum comparison is always false”
  • “GetType returned an object not a number”
  • “mesh vertices are garbage or zeros”
  • “/wasm-interop”

Requirements

  • Docker

What it can do on your machine

Read from SKILL.md and the folder at commit 6e20740. It shows what the files ask for, not the result of running them.

  • Tool permissions

    Pre-approves nothing: there is no allowed-tools line, so your agent's usual permission prompts apply.

    From allowed-tools in the SKILL.md frontmatter.

  • Runs code

    Shell commands in SKILL.md call:

    • bash

    From the folder's file list and the shell code blocks in SKILL.md.

  • Network

    No URLs in SKILL.md.

    From URLs in SKILL.md, links to its own repository left out.

  • Credentials

    Names no API keys, tokens, secrets or passwords.

    From names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.

Context cost

Wasm Interop loads about 3k tokens when it runs, and up to ~5.5k if it reads all its reference files. Until then it costs about 207 tokens; SKILL.md has 1,225 words of instructions outside code blocks.

Always · name and description, kept in context so the agent knows when to use it
~207
When it runs · the whole SKILL.md, loaded when a task matches
~3k
With references · SKILL.md plus every file in references/, read only if the agent opens them
~5.5k

Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.

Safety

Auto-check passed

The automated check found no risky patterns in SKILL.md.

Automated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); files beside SKILL.md are not scanned.

SKILL.md

The full file from andymai/brepjs at commit 6e20740, republished under its Apache-2.0 licence (© andymai). 1,225 words, ~3,032 tokens.

Download SKILL.mdSave it as .claude/skills/wasm-interop/SKILL.md (or your agent's skills folder). This skill also uses 1 other file; get the full folder from GitHub.
name
wasm-interop
description
This skill should be used when working across the JS/WASM boundary in brepjs — writing or debugging code in src/kernel/occt, src/kernel/occtWasm, or src/kernel/brepkit, or diagnosing symptoms like "enum comparison is always false", "GetType returned an object not a number", "mesh vertices are garbage or zeros", "detached ArrayBuffer", ".map on a Uint32Array produces wrong values", "brepjs kernel not initialized", "brepjs_single.js is missing", "init() falls back to the wrong kernel", "dynamic import of occt-wasm breaks the Vite build", "SetRunParallel has no effect", or deciding whether a kernel bug needs a Docker WASM rebuild. This skill owns the raw JS↔WASM mechanics (Emscripten enums, heap/typed-array reads, threading); adapter, registry, and capability *design* belong to the kernel-abstraction skill.

WASM and OCCT interop gotchas

Cover the mechanics of the JS↔WASM boundary in the kernel adapters: how Emscripten enums cross, how to read the WASM heap without corrupting it, how initialization and the bundler-safe fallback chain work, and why every shipped build is single-threaded. Adapter/registry/capability design lives in the kernel-abstraction skill — this skill stays on the raw Emscripten mechanics.

When to use

  • Editing or debugging any file under src/kernel/occt/ (brepjs-opencascade facade), src/kernel/occtWasm/ (occt-wasm, the default kernel), or src/kernel/brepkit/.
  • A value works in one kernel but is garbage in another, or an enum comparison is silently always false.
  • Mesh/curve extraction returns zeros, wrong numbers, or a detached-buffer error.
  • Init fails, falls back to the wrong kernel, or a dynamic import breaks a consumer's build.

Enums across the boundary

OCCT is bound with embind, which surfaces C++ enums as objects, not numbers.

Values coming OUT — never compare a raw enum result to an integer. Extract with the canonical idiom (src/kernel/occt/geometryQueryOps.ts):

ts
const typeVal = adaptor.GetType();
// OCCT Emscripten returns enum objects with a .value property
const idx = typeof typeVal === 'number' ? typeVal : Number(typeVal?.value ?? typeVal);

Then map the number through a Record<number, string> (geometryQueryOps.ts). The same pattern recurs in nurbsQueryOps.ts and manifold/repairOps.ts.

Values going IN — pass the enum object straight from the oc instance, not an integer. Constructors and comparisons both accept the object form (src/kernel/occt/topologyOps.ts):

ts
const ta = oc.TopAbs_ShapeEnum;
new oc.TopExp_Explorer_2(shape, ta.TopAbs_FACE, oc.TopAbs_ShapeEnum.TopAbs_SHAPE);

Identity comparison against the object works and is preferred over .value on the IN side (geometryQueryOps.ts):

ts
const orient = shape.Orientation_1();
if (orient === oc.TopAbs_Orientation.TopAbs_FORWARD) return 'forward';

Enum-object maps are cached per oc instance in a WeakMap (topologyOps.ts) — build once, reuse; do not rebuild them per call.

Overloaded constructors get numeric suffixes. Embind renames overloads _1, _2, … — TopExp_Explorer_2, BRepAdaptor_Curve_2, Orientation_1. A "not a constructor" or wrong-arity error usually means the wrong suffix; grep a working call site (geometryQueryOps.ts) for the right one.

SymptomCauseFix
Enum comparison always falseCompared object to an intExtract .value, or compare against oc.<Enum>.<MEMBER>
GetType() returns [object]Embind enum objectUse the .value extraction idiom
X_2 is not a constructorWrong embind overload suffixMatch the _N at a known-good call site

Typed arrays and the heap

Read heap pointers, slice before the next WASM call. The brepjs-opencascade facade returns raw pointers + sizes; copy into an owned TypedArray before any other WASM call could grow or relocate the heap — the build sets ALLOW_MEMORY_GROWTH=1, so a stale heap view becomes a detached/garbage buffer (src/kernel/occt/meshOps.ts). Divide byte pointers by 4 for the 32-bit heap index, slice, then free the C++ side with raw.delete() (meshOps.ts):

ts
const offset = ptr / 4; // byte ptr → HEAPF32 index
return heap.slice(offset, offset + size); // copy now, before any other WASM call

The occt-wasm adapter reads element-by-element with a ?? 0 fallback because noUncheckedIndexedAccess is on (src/kernel/occtWasm/meshOps.ts); it uses >> 2 for the same byte→index divide. For a structurally-guaranteed index (WASM ABI fixed arrays, post-bounds-check loops) use the sanctioned escape hatch wasmIndex<T>(arr, i) in src/utils/vec3.ts instead of a bare !.

Convert Uint32Array to number[] only for JS array methods. .map/.filter/.flatMap on a Uint32Array coerce results back to u32 (and cannot produce objects), so convert first with toArray(ids) = Array.from(ids) (src/kernel/brepkit/helpers.ts). This is not a rule about passing arrays into the kernel — brepkit methods accept Uint32Array | number[], and booleanOps.ts passes new Uint32Array(...) straight into bk.compoundFuse(...). (CLAUDE.md's blanket "always convert before passing to kernel methods" overstates it; the real reason is the map/filter coercion.)

No zero-copy between separate WASM linear memories — each kernel instance owns its own heap, so copy bytes across with copyWasmBytes(bytes) (helpers.ts) or re-serialize via a BREP string. See docs/decisions/0013-voxel-domain.md.

SymptomCauseFix
Mesh vertices are garbage / zeros after a later opHeap view read after a WASM call grew itSlice into an owned array immediately
Detached ArrayBuffer errorHeld a HEAP view across an allocationCopy first; never store a raw heap subarray
.map on IDs yields wrong valuesu32 coercion of typed-array mapArray.from(ids) / toArray first
Off-by-4 / nonsense offsetsUsed byte pointer as element indexDivide by 4 (ptr / 4 or >> 2)

Handle .delete() on occt-wasm and brepkit handles is a no-op — those adapters use an arena/id model, not per-handle embind objects (occtWasm/occtWasmTypes.ts, brepkit/helpers.ts). Free with the adapter's dispose/release, not by chasing .delete(). Disposal semantics belong to the memory-and-disposal skill.

Show full SKILL.md (581 more words)Show less

Initialization

init() (src/kernel/index.ts) is idempotent (returns the current kernel id immediately) and tries, in order: occt-wasm → brepjs-opencascade (initFromOC, returns 'occt') → brepkit-wasm, throwing with install instructions if none load. brepjs/quick (src/quick.ts) does the same as a top-level await but with the brepjs-opencascade fallback only (no brepkit). All three kernel packages are optional peerDependencies (package.json).

Every optional backend loads through importOptionalBackend(specifier) (src/kernel/optionalBackend.ts). The specifier is a variable so no bundler (esbuild, Rollup, Vite import-analysis) can statically resolve it — an uninstalled peer stays a runtime import instead of hard-failing the build. A string literal with only a @vite-ignore comment regressed when Vite reflowed the comment (#1726). When adding a new optional backend, route it through this function; never write a literal import('occt-wasm').

initFromOC(oc) (index.ts) resets seven feature-detection caches (measure, transform, boolean/loft/extrude/shell/fillet batch), registers DefaultAdapter as 'occt', and forces it default. Call it when hand-wiring the brepjs-opencascade instance; skipping the cache reset leaves stale capability flags.

prewarm() (index.ts) builds and disposes a 1×1×1 box to pay OCCT's ~400-900 ms first-call JIT cost off the critical path. Fire-and-forget after init() resolves.

getKernel() throws brepjs kernel not initialized. Call initFromOC() or registerKernel() when nothing is registered — that message means init was skipped, not that WASM is broken.

Missing WASM artifacts. packages/brepjs-opencascade/src/*.js and *.wasm are gitignored (.gitignore:31-36); only .d.ts files are tracked. A brepjs_single.js is missing error means restore them from the published tarball with bash scripts/ensure-wasm.sh (version-stamped via src/.wasm-version; CI runs it) — not a Docker build.

Tests. tests/setup.ts re-exports initOC (alias of initOCCT) from tests/setup-kernel.ts; TEST_KERNEL selects occt | brepkit | occt-wasm | manifold, defaulting to occt-wasm. Under vitest, brepkit-wasm is aliased to its Node CJS entry because the ESM entry uses the unsupported WASM-ESM-integration proposal (vitest.config.ts). See the writing-tests skill for the multi-kernel test setup.

Threading reality

Every shipped kernel build is single-threaded. The brepjs-opencascade build compiles without -pthread (packages/brepjs-opencascade/build-config/brepjs.yml), and occt-wasm's README states "Single WASM thread — each kernel instance is single-threaded." Consequences:

  • op.SetRunParallel(true) (src/kernel/occt/booleanOps.ts) and the facade's SetRunParallel(Standard_True) degrade to sequential in a threadless build — effectively no-ops. Do not expect a speedup from them; the meshing path passes isInParallel = Standard_False deliberately.
  • Off-main-thread work uses message passing, not shared handles: brepjs's own src/worker/ exchanges BREP strings across the boundary (workerHandler.ts calls initFn(msg.wasmUrl) on init), and occt-wasm ships an occt-wasm/worker export (OcctWorker.spawn, Comlink) whose handles are worker-local. A handle from one instance is meaningless in another.

Vitest test-runner config (pool, workers, memory cap, timeout) is owned by the writing-tests skill; the WASM-specific reason those knobs stay conservative is that OCCT WASM linear memory grows monotonically across a fork's files, so over-committing workers trips timeouts (#1102).

Kernel-issue debugging discipline

Reproduce a suspected kernel bug in JS/TS against the installed WASM first. A Docker rebuild of the OpenCascade WASM (ghcr.io/andymai/opencascade.js:v8, via the brepjs-opencascade buildWasm/buildSingle scripts, then wasm-opt) takes on the order of hours — treat it as the last resort. Complete all C++ facade edits before starting a build. The C++ binding surface (facade classes like MeshExtractor, BooleanBatch, BooleanPipeline, EvolutionExtractor, TopoDS_Cast, manual Bnd_Box bindings) and the emcc flags live in build-config/brepjs.yml; see references/opencascade-build.md for the inventory and flag list. Note docs/compatibility.md still says "WASM SIMD ❌ Not used" — that line is stale; the build passes -msimd128 -mrelaxed-simd. Trust the yml.

Additional resources

  • references/opencascade-build.md — brepjs.yml C++ facade-class inventory + emcc flags.
  • docs/kernel-swap.md — full init/registration guide for all three kernels.
  • docs/compatibility.md — bundler externalization, WASM variants/sizes, threading (SIMD line is stale).
  • docs/memory-management.md — using/Symbol.dispose, DisposalScope, manual delete().
  • kernel-abstraction skill — KernelAdapter, capabilities, withKernel/quality-tier semantics.
  • memory-and-disposal skill — handle lifecycle and disposal ordering.
  • writing-tests skill — vitest runner config and multi-kernel test setup.

© andymai, Apache-2.0. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file

Files

SKILL.md and 1 other file (references) in .claude/skills/wasm-interop of andymai/brepjs.

  • SKILL.md
  • references/opencascade-build.md

Open the folder on GitHubat commit 6e20740

Compare with similar skills

Wasm Interop next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.

Wasm Interop compared with similar skills
SkillStarsUsed inTokensAuto-checkLicenceRepo updated
Wasm Interop this skillandymai/brepjs115—~3kAutomated safety check: PassApache-2.0
Reflexo ReleaseMyriad-Dreamin/typst.ts1.2k—~1.5kAutomated safety check: PassApache-2.0
Compile Php WasmWordPress/wordpress-playground2k—~2.8kAutomated safety check: PassGPL-2.0
Fungienbop/fungi132—~2.9kAutomated safety check: PassApache-2.0
Devinggo Generatorhuagelong/devinggo122—~3.3kAutomated safety check: PassApache-2.0
Pwa ReleaseAHS12/thoth-blueprint625—~505Automated safety check: PassGPL-3.0

Similar skills

  • Reflexo Release

    Myriad-Dreamin/typst.ts

    Guide Reflexo/typst.ts release preparation and operator handoffs.

    1.2k GitHub stars~1.5k tokensUpdated yesterday
    DevOps & CloudAuto-check passed
  • Compile Php Wasm

    WordPress/wordpress-playground

    Compile PHP.wasm main modules and side modules (dynamic extensions) for Node.js and web platforms.

    2k GitHub stars~2.8k tokensUpdated today
    DevOps & CloudAuto-check passed
  • Fungi

    enbop/fungi

    Install, configure, and operate the Fungi CLI across devices.

    132 GitHub stars~2.9k tokensUpdated 4 days ago
    DevOps & CloudAuto-check passed
  • Devinggo Generator

    huagelong/devinggo

    DevingGo 代码生成器 CLI 工具集。当用户需要生成 CRUD 代码、创建模块、管理 Worker 任务、 导入导出模块、或需要快速搭建后端/前端代码时使用。适用于任何涉及代码生成、模块管理、 数据库表对应的前后端代码生成的场景。

    122 GitHub stars~3.3k tokensUpdated 3 mo ago
    DevOps & CloudAuto-check passed
  • Pwa Release

    AHS12/thoth-blueprint

    Safely change Vite, service-worker, offline fallback, cache, Docker, Vercel, or release-distribution behavior.

    625 GitHub stars~505 tokensUpdated 1 mo ago
    DevOps & CloudAuto-check passed
  • Env Configuration

    latitude-dev/latitude-llm

    Adding or reading env vars, updating .env.example, or validating config at startup with parseEnv / parseEnvOptional.

    4.7k GitHub stars~911 tokensUpdated today
    DevOps & CloudAuto-check: notes

More from andymai/brepjs

All 21 skills in this repo
  • Implement

    andymai/brepjs

    A skill your agent uses when authoring or editing a brepjs .brep.ts part — writing the geometry with the functional API (box, cylinder, fuse, cut, fillet, sketch→extrude…), declaring an expected…

    115 GitHub stars~3.4k tokensUpdated today
    Auto-check passed
  • Memory And Disposal

    andymai/brepjs

    This skill should be used when managing WASM handle lifetimes or hunting memory leaks in brepjs — when a task mentions "createHandle() without using keyword risks WASM memory leak"…

    115 GitHub stars~3.1k tokensUpdated today
    Auto-check passed
  • Polish

    andymai/brepjs

    A skill your agent uses when a valid brepjs part should look designed rather than glued-from-primitives (products, toys, mechanisms, anything a human eyeballs), and when exporting/handing off the…

    115 GitHub stars~588 tokensUpdated today
    Auto-check passed
  • Writing Tests

    andymai/brepjs

    This skill should be used when writing, running, or fixing tests in the brepjs repository — when a task says "add a test", "write a regression test", "tests are failing", "test timed out", "coverage…

    115 GitHub stars~4.3k tokensUpdated today
    Auto-check passed
  • Adding Operations

    andymai/brepjs

    This skill should be used when adding or extending a geometric shape operation in brepjs — the end-to-end recipe once the target module is chosen (which is decided by architecture-navigation) — when…

    115 GitHub stars~4.4k tokensUpdated today
    Auto-check passed
  • This skill should be used when deciding which layer or module a new file, function, or directory belongs in, or when a layer-boundary check fails.

    115 GitHub stars~3.4k tokensUpdated today
    Auto-check passed

Questions about Wasm Interop

What does Wasm Interop do?

This skill should be used when working across the JS/WASM boundary in brepjs — writing or debugging code in src/kernel/occt, src/kernel/occtWasm, or src/kernel/brepkit, or diagnosing symptoms like…. Wasm Interop is an agent skill from andymai/brepjs.

When should I use Wasm Interop?

Wasm Interop fits situations like: devOps & Cloud work in your project.

How do I install Wasm Interop in Claude Code?

Run `npx skills add andymai/brepjs --skill wasm-interop -a claude-code`. Or copy the skill folder (.claude/skills/wasm-interop in andymai/brepjs) into .claude/skills/wasm-interop in your project. Claude Code loads it when a task matches its description.

How do I install Wasm Interop in Codex?

Run `npx skills add andymai/brepjs --skill wasm-interop -a codex`. Or copy the skill folder (.claude/skills/wasm-interop in andymai/brepjs) into .agents/skills/wasm-interop in your project. Codex loads it when a task matches its description.

Can I use Wasm Interop in Cursor, Gemini CLI or GitHub Copilot?

Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add andymai/brepjs --skill wasm-interop -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/wasm-interop, .gemini/skills/wasm-interop, .github/skills/wasm-interop and .opencode/skills/wasm-interop in your project.

What does Wasm Interop need to run?

Going by SKILL.md and its folder, Wasm Interop needs the command-line tools its instructions call (bash). Our summary lists: Docker.

Does Wasm Interop access the network?

SKILL.md contains no URLs. Any network use would come from the scripts or tools the agent runs. This is read from the text; nothing was executed.

Is Wasm Interop safe to install?

Our automated static check of SKILL.md found no risky patterns, such as piping downloads into a shell, reading credential files or hidden Unicode. It is not a guarantee. Review the folder before installing.

What licence does Wasm Interop use?

Wasm Interop is published under the Apache-2.0 licence (the repository's licence). It allows redistribution, so the full SKILL.md is shown on this page.

How many tokens does Wasm Interop use?

About 3k tokens (SKILL.md is roughly 12k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full. Its references folder adds about 2.5k tokens, read only when the agent opens those files.

What are the alternatives to Wasm Interop?

Skills that share tags, products or a category with Wasm Interop: Reflexo Release (Myriad-Dreamin/typst.ts, 1.2k stars), Compile Php Wasm (WordPress/wordpress-playground, 2k stars), Fungi (enbop/fungi, 132 stars) and Devinggo Generator (huagelong/devinggo, 122 stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.

Who maintains Wasm Interop?

andymai (a GitHub user) maintains it in andymai/brepjs, which has 115 GitHub stars. The repository holds 21 skills in this directory. The repository was last updated on October 8, 2026.

Source: andymai/brepjs on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.