MusePi

Natives Binding Contract (JavaScript/TypeScript Side)

This document defines the JS/TS contract between @musepi/pi-natives callers and the loaded N-API addon.

Current package shape is direct-to-native: there is no packages/natives/src/<module> TypeScript wrapper layer. The public API is the generated packages/natives/native/index.d.ts declaration file, the ESM loader/export wrapper in packages/natives/native/index.js, and the Rust #[napi] exports in crates/pi-natives/src.

Implementation files

Contract model

The contract has three parts:

  1. ESM runtime loader/export wrapper (native/index.js)
    • calls loadNative() from loader-state.js, which require(...)s the .node addon;
    • binds generated classes/functions as explicit named ESM exports;
    • emits enum runtime objects generated by scripts/gen-enums.ts.
  2. Generated TypeScript declarations (native/index.d.ts)
    • generated by napi-rs during scripts/build-native.ts;
    • declares exported functions, classes, object interfaces, and native enums;
    • is the package types entry.
  3. Rust N-API exports (crates/pi-natives/src)
    • #[napi] functions/classes/objects/enums are the source of generated declarations and runtime symbols;
    • snake_case Rust names become camelCase JavaScript names by napi-rs convention.

There is no current NativeBindings declaration-merging lifecycle and no full required-export list in the loader. Install/compiled loads do validate the package-version sentinel export; workspace-dev loads skip that check.

Public export surface organization

packages/natives/package.json exposes the package root only:

{
  "main": "./native/index.js",
  "types": "./native/index.d.ts",
  "exports": {
    ".": {
      "types": "./native/index.d.ts",
      "import": "./native/index.js"
    }
  }
}

Consumers in packages/coding-agent and packages/tui import directly from @musepi/pi-natives.

JS API ↔ native export mapping (representative)

Category Public JS API Rust source Return style
Grep grep(options, onMatch?) grep.rs Promise<GrepResult>
Grep search(content, options) grep.rs SearchResult
Grep hasMatch(content, pattern, ignoreCase?, multiline?) grep.rs boolean
Fuzzy path search fuzzyFind(options) fd.rs Promise<FuzzyFindResult>
Glob/workspace glob(options, onMatch?), listWorkspace(options) glob.rs, workspace.rs Promise<...>
Glob cache invalidateFsScanCache(path?) fs_cache.rs void
AST/block/summary astGrep(options), astMatch(options), astEdit(options), blockRangeAt(options), enclosingBlockBoundaries(options), summarizeCode(options) ast.rs, block.rs, summary.rs mixed
Shell executeShell(options, onChunk?) shell.rs Promise<ShellRunResult>
Shell new Shell(options?), shell.run(...), shell.abort() shell.rs class / promises
Shell applyBashFixups(command) shell.rs BashFixupResult
PTY new PtySession(), start/write/resize/kill pty.rs class / promises
Process Process.fromPid/fromPath, status/children/killTree/terminate/waitForExit ps.rs class / mixed
Keys parseKey, matchesKey, Kitty/legacy helpers keys.rs sync
Text wrapTextWithAnsi, truncateToWidth, sliceWithWidth, extractSegments, visibleWidth text.rs sync
Highlight highlightCode, supportsLanguage, getSupportedLanguages highlight.rs sync
HTML htmlToMarkdown(html, options?) html.rs Promise<string>
SIXEL encodeSixel sixel.rs sync
Snapcompact renderSnapcompactPng(text, options) snapcompact.rs sync
Clipboard copyToClipboard, readImageFromClipboard clipboard.rs sync / promise
Tokens countTokens(input, encoding?) tokens.rs sync
System/isolation detectMacOSAppearance, MacAppearanceObserver, MacOSPowerAssertion, getWorkProfile, iso* helpers appearance.rs, power.rs, prof.rs, iso.rs mixed

Sync vs async contract differences

The contract preserves Rust/N-API call style:

Changing sync ↔ async for an existing export is a breaking public API change because consumers call these exports directly.

Object and enum typing patterns

Object patterns

#[napi(object)] Rust structs become TS interfaces, for example:

Runtime shape correctness is owned by napi-rs and the Rust implementation.

Enum patterns

Native enums are represented in generated declarations and also emitted as runtime objects by scripts/gen-enums.ts, because napi-rs string enums are TS-only without explicit JS exports. Current enum objects include:

Error behavior and caveats

Maintainer checklist for binding changes

When adding/changing an export, update all of:

  1. Rust #[napi] implementation in the owning crates/pi-natives/src/<module>.rs.
  2. crates/pi-natives/src/lib.rs if a new module is added.
  3. Any consumer imports/callsites in packages/coding-agent or packages/tui.
  4. Build output by running the natives build so native/index.d.ts and native/index.js stay in sync.
  5. scripts/gen-enums.ts if enum runtime export patching needs to change.

Do not add a parallel TS wrapper convention unless the package design intentionally moves back to wrappers; current consumers depend on the direct generated API.