Files
oh-my-pi/docs/natives-architecture.md
T
can1357 1c968a7bfc feat(scripts): added native leaf-package publish checks to install smoke tests
- Added repeatable `--tag` argument parsing in `gen-npm-packages.ts` and threaded parsed tags into `generateNpmPackages` for targeted leaf publishing.
- Exported `prepareNativeCorePackage` and expanded package manifest typing to support scripted manifest rewrites used by release workflows.
- Reworked install-test smoke logic to pack the host leaf package, pack the rewritten natives core, and assert the platform leaf package resolves from the core optional dependency.
2026-05-30 19:52:45 +02:00

8.0 KiB

Natives Architecture

@oh-my-pi/pi-natives is now a two-layer package around a loader:

  1. CommonJS loader/package entrypoint resolves and loads the correct .node addon and patches generated enum objects onto the export object.
  2. Rust N-API module layer implements the exported functions/classes and emits the generated TypeScript declarations.

This document is the foundation for deeper module-level docs.

Implementation files

  • packages/natives/native/index.js
  • packages/natives/native/index.d.ts
  • packages/natives/native/loader-state.js
  • packages/natives/native/embedded-addon.js
  • packages/natives/scripts/build-native.ts
  • packages/natives/scripts/embed-native.ts
  • packages/natives/scripts/gen-enums.ts
  • packages/natives/package.json
  • crates/pi-natives/src/lib.rs

Package entrypoint and public surface

packages/natives/package.json points directly at generated native bindings:

  • main: ./native/index.js
  • types: ./native/index.d.ts
  • exports["."].types: ./native/index.d.ts
  • exports["."].import: ./native/index.js

There is no current packages/natives/src TypeScript wrapper layer. Consumers import functions/classes/enums directly from @oh-my-pi/pi-natives; the type contract is the generated native/index.d.ts plus enum exports appended by scripts/gen-enums.ts.

Current capability groups in the generated API include:

  • Search/text/code primitives: grep, search, hasMatch, fuzzyFind, glob, astGrep, astEdit, text width/slicing/wrapping/sanitization, syntax highlighting, token counting.
  • Execution/process/terminal primitives: executeShell, Shell, PtySession, process-tree helpers, key parsing.
  • System/media/conversion primitives: clipboard, image resize/encode/SIXEL, HTML-to-Markdown, macOS appearance/power helpers, work profiling, Windows ProjFS overlay helpers.

Loader layer

packages/natives/native/index.js owns runtime addon selection and optional embedded extraction.

Candidate resolution model

  • Platform tag is ${process.platform}-${process.arch}.
  • Supported tags are currently:
    • linux-x64
    • linux-arm64
    • darwin-x64
    • darwin-arm64
    • win32-x64
  • x64 can use CPU variants:
    • modern (AVX2-capable)
    • baseline (fallback)
  • Non-x64 uses the default filename without a variant suffix.

Filename strategy:

  • Default: pi_natives.<platform>-<arch>.node
  • x64 variant: pi_natives.<platform>-<arch>-modern.node or ...-baseline.node
  • x64 runtime fallback includes the unsuffixed default filename after variant candidates.

Platform-specific variant detection

For x64, variant selection uses:

  • Linux: /proc/cpuinfo
  • macOS: sysctl -n machdep.cpu.leaf7_features, then machdep.cpu.features
  • Windows: PowerShell check for System.Runtime.Intrinsics.X86.Avx2

PI_NATIVE_VARIANT can force modern or baseline; invalid values are ignored.

Binary distribution and extraction model

The published @oh-my-pi/pi-natives package ships only the loader layer in native/: the CommonJS loader (index.js), generated declarations (index.d.ts), the loader-state.js/.d.ts helpers, and the embedded-addon manifest stub (embedded-addon.js). It carries no .node binaries.

Each platform's prebuilt .node is published as a separate optional-dependency leaf package — @oh-my-pi/pi-natives-<platform>-<arch>, one per supported tag — which the core lists in optionalDependencies at the lockstep version. npm/bun install only the leaf whose os/cpu match the host. The working-tree package keeps built .node files under native/ for local dev; the release-publish rewrite (prepareNativeCorePackage in scripts/ci-release-publish.ts) strips them from the core tarball, and the leaves are generated by packages/natives/scripts/gen-npm-packages.ts (LEAF_TARGETS). Adding a build target therefore requires a matching LEAF_TARGETS entry, or the binary never reaches npm users.

For compiled binaries, loader behavior is:

  1. Check versioned user cache path: <getNativesDir()>/<packageVersion>/....
  2. Check legacy compiled-binary location:
    • Windows: %LOCALAPPDATA%/omp (fallback %USERPROFILE%/AppData/Local/omp)
    • non-Windows: ~/.local/bin
  3. Fall back to packaged native/ and executable directory candidates.

getNativesDir() uses $XDG_DATA_HOME/omp/natives when $XDG_DATA_HOME/omp exists; otherwise it uses ~/.omp/natives.

If a populated embedded addon manifest is present, it is also treated as a compiled-binary signal. The loader can extract the matching embedded .node into the versioned cache directory before candidate probing.

For npm/bun installs (non-compiled), loader-state.js resolves the platform leaf directory via require.resolve("@oh-my-pi/pi-natives-<tag>/package.json") and probes its .node before the core package's native/ directory and the executable directory. The optional-dependency binary is therefore preferred over any .node left in the core (e.g. a stale local-dev build).

Failure modes

Loader failures are explicit:

  • Unsupported platform tag: after failed probing, throws with supported platform list.
  • No loadable candidate: throws with all attempted paths and remediation hints.
  • Embedded extraction errors: directory/write failures are recorded and included in final load diagnostics if no candidate loads.

The current loader does not perform a separate post-require export validation pass.

Rust N-API module layer

crates/pi-natives/src/lib.rs declares exported module ownership:

  • appearance
  • ast
  • clipboard
  • fd
  • fs_cache
  • glob
  • glob_util
  • grep
  • highlight
  • html
  • image
  • keys
  • language
  • power
  • prof
  • projfs_overlay
  • ps
  • pty
  • shell
  • task
  • text
  • tokens
  • utils (crate-private helpers)

N-API exports are generated from Rust #[napi] functions/classes/objects/enums. Snake_case Rust names are exposed as camelCase JavaScript names unless explicitly configured by napi-rs.

Ownership boundaries

  • Loader/package ownership (packages/natives/native, packages/natives/scripts)
    • runtime binary selection
    • CPU variant selection and override handling
    • compiled-binary embedded extraction
    • generated TypeScript declarations and enum export patching
  • Rust ownership (crates/pi-natives/src)
    • algorithmic and system-level implementation
    • platform-native behavior and performance-sensitive logic
    • N-API symbol implementation consumed directly by package callers
  • Consumer ownership (packages/coding-agent, packages/tui)
    • user-facing policy and fallbacks that are not built into the native API
    • higher-level rendering, artifact, shell-session, and command behavior

Runtime flow (high level)

  1. Consumer imports from @oh-my-pi/pi-natives.
  2. native/index.js computes platform/arch/variant and candidate paths.
  3. Optional embedded binary extraction occurs for compiled distributions.
  4. The first require(candidate) that succeeds becomes the exported addon object.
  5. Generated enum objects are appended to module.exports.
  6. Caller invokes generated N-API functions/classes directly.

Glossary

  • Native addon: A .node binary loaded via Node-API (N-API).
  • Platform tag: Runtime tuple platform-arch (for example darwin-arm64).
  • Platform leaf package: Per-platform npm package @oh-my-pi/pi-natives-<tag> that carries one platform's prebuilt .node. The core depends on every leaf via optionalDependencies; the package manager installs only the host-matching one (os/cpu).
  • Variant: x64 CPU-specific build flavor (modern AVX2, baseline fallback).
  • Generated binding declaration: native/index.d.ts emitted by napi-rs during build-native.ts.
  • Compiled binary mode: Runtime mode where the CLI is bundled and native addons are resolved from embedded/cache paths before package-local paths.
  • Embedded addon: Build artifact metadata and file references generated into native/embedded-addon.js so compiled binaries can extract matching .node payloads.