- 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.
8.0 KiB
Natives Architecture
@oh-my-pi/pi-natives is now a two-layer package around a loader:
- CommonJS loader/package entrypoint resolves and loads the correct
.nodeaddon and patches generated enum objects onto the export object. - 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.jspackages/natives/native/index.d.tspackages/natives/native/loader-state.jspackages/natives/native/embedded-addon.jspackages/natives/scripts/build-native.tspackages/natives/scripts/embed-native.tspackages/natives/scripts/gen-enums.tspackages/natives/package.jsoncrates/pi-natives/src/lib.rs
Package entrypoint and public surface
packages/natives/package.json points directly at generated native bindings:
main:./native/index.jstypes:./native/index.d.tsexports["."].types:./native/index.d.tsexports["."].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-x64linux-arm64darwin-x64darwin-arm64win32-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.nodeor...-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, thenmachdep.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:
- Check versioned user cache path:
<getNativesDir()>/<packageVersion>/.... - Check legacy compiled-binary location:
- Windows:
%LOCALAPPDATA%/omp(fallback%USERPROFILE%/AppData/Local/omp) - non-Windows:
~/.local/bin
- Windows:
- 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:
appearanceastclipboardfdfs_cacheglobglob_utilgrephighlighthtmlimagekeyslanguagepowerprofprojfs_overlaypsptyshelltasktexttokensutils(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)
- Consumer imports from
@oh-my-pi/pi-natives. native/index.jscomputes platform/arch/variant and candidate paths.- Optional embedded binary extraction occurs for compiled distributions.
- The first
require(candidate)that succeeds becomes the exported addon object. - Generated enum objects are appended to
module.exports. - Caller invokes generated N-API functions/classes directly.
Glossary
- Native addon: A
.nodebinary loaded via Node-API (N-API). - Platform tag: Runtime tuple
platform-arch(for exampledarwin-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 viaoptionalDependencies; the package manager installs only the host-matching one (os/cpu). - Variant: x64 CPU-specific build flavor (
modernAVX2,baselinefallback). - Generated binding declaration:
native/index.d.tsemitted by napi-rs duringbuild-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.jsso compiled binaries can extract matching.nodepayloads.