Electron Native Lib Extraction logo

Electron Native Lib Extraction

Community
Innei
electron-native-lib-extraction

Use when extracting an in-app Electron native-module integration (N-API addon + build scripts + packaging config + release CI) into a standalone, source-distributed npm library — or when authoring such a library from scratch. Covers the ABI/install-script traps, node-gyp rebuild semantics, macOS rpath depth, electron-builder packaging excludes, composite GitHub Action hygiene, and the dogfood migration back into the source app.

Overview

PublisherInnei
RepositorySKILL
Skill nameelectron-native-lib-extraction
Stars
81
Forks
2
Bundled files
Instructions only
Links
  • Markdown instructions

    A SKILL.md file the model loads on demand, so it only costs tokens when a request actually matches.

  • Works with any LLM

    AI skills are plain Markdown, not provider-specific code, so this works with GPT, Claude, Gemini, Grok, or a local model.

  • Self-contained

    Everything the model needs lives in the instructions — no extra files to sync.

  • Open source

    Published by Innei on GitHub. Read the source before you install it.

Installation

Install the Electron Native Lib Extraction AI skill in TypingMind to use it with any LLM, or drop it into another agent that reads SKILL.md.

1

Install in TypingMind

TypingMind installs a skill straight from its GitHub folder — it reads SKILL.md, bundles the resource files, and stores the result locally.

  1. Open the app and go to Plugins → Skills.
  2. Choose "Install from GitHub".
  3. Paste the skill folder URL below and confirm.
  4. Enable the skill in any chat where you want it available.
Plugins → Skills → Add skill → From GitHub URL, then paste the folder URL and press Continue.
2

Install in another agent

Any agent that reads the Agent Skills format can use this skill — copy the folder into that agent's skills directory.

Claude Code — .claude/skills
git clone --depth 1 https://github.com/Innei/SKILL.git /tmp/SKILL
mkdir -p .claude/skills
cp -r /tmp/SKILL/skills/infrastructure/electron-native-lib-extraction .claude/skills/electron-native-lib-extraction
Restart Claude Code after copying so it picks up the new skill.

Use it in TypingMind

Enable Electron Native Lib Extraction in any TypingMind chat and the model takes it from there. Its name and description sit in the system prompt, and the moment a request matches, the model loads the full instructions itself — you never invoke it by hand, and it costs no tokens until it is actually used.

The model loads Electron Native Lib Extraction on its own as soon as a request matches it.

Works with any AI model

AI skills are plain Markdown instructions rather than provider-specific code, so Electron Native Lib Extraction is not tied to the model it was written for. Install it once in TypingMind and use it with GPT-5, Claude, Gemini, Grok, DeepSeek, Mistral, Llama, or a local model you run yourself — all on your own API keys.

  • Loaded only when it is needed

    The system prompt carries just the name and description. The instructions are fetched on the first matching request, so an idle skill costs nothing.

  • Switch models mid-chat

    Because the skill is instructions rather than code, changing model does not break it — the next model reads the same SKILL.md.

Skill instructions

This is the SKILL.md content the model loads. Read it before installing — a skill is instructions your model will follow.

electron-native-lib-extraction

An Electron app that grew a native module (N-API addon linking a system framework, e.g. Sparkle) accumulates four coupled layers: the addon source, the Electron-ABI build orchestration, the electron-builder packaging config, and the release CI. Extracting them into a reusable npm package is mostly mechanical — but most of the failure modes are invisible until a consumer installs the package or CI runs a real release. This skill encodes the extraction order and the traps. Reference implementation: https://github.com/Innei/electron-sparkle-updater (Sparkle.framework bridge + appcast release toolchain, extracted from the Kansoku desktop app).

When to use

  • In scope: source-distributed native npm packages for Electron (consumer compiles at build time), macOS framework linking, electron-builder integration fragments, composite GitHub Actions for release pipelines, migrating the source app onto the extracted library.
  • Out of scope: prebuilt binary distribution (prebuildify/node-gyp-build), Windows native bridges, ASAR internals beyond asarUnpack.

Inputs: the app's working native integration; a free npm name (npm view <name> → 404 = free); the consumer's Electron version (drives ABI flags).

Workflow

text
[1] Carve the package        addon + scripts verbatim, params for app values
[2] Neutralize npm install   no-op install script, real build = explicit CLI
[3] Runtime loader           null-degrading, path contract for dev/packaged
[4] Rebuild CLI              node-gyp resolved from OWN deps, never npx
[5] Packager fragments       asarUnpack narrow, vendor excluded, sign hook
[6] Release CI as an Action  env-routed inputs, EXIT-trap secrets
[7] Dogfood migration        app consumes the lib; packaged-layout assertion

[1] Carve

Copy the addon source and fetch scripts verbatim (keep their load-bearing comments — they move with the code). Everything app-specific (repo slug, feed URL, tag prefix, arch) becomes a parameter. Remove hardcoded ARCHS from binding.gyp; let node-gyp's --arch drive it. Parameterize in a separate, reviewable step after the verbatim copy.

[2] Neutralize npm install

npm auto-runs node-gyp rebuild for ANY package containing a binding.gyp, against the host Node ABI — the wrong target for Electron, and a build the consumer never asked for. Ship "install": "node -e \"\"" and put the real build behind an explicit CLI command. Do not set gypfile: true at the package root. Document why, prominently — the no-op looks like a bug.

[3] Runtime loader

The loader never throws: non-target-platform → null before any path work; load/shape failure → null + log. Two path contracts, both derived from the package's own import.meta.url:

  • dev: <packageRoot>/native/build/Release/<addon>.node
  • packaged: <resourcesPath>/app.asar.unpacked/node_modules/<pkg>/native/build/Release/<addon>.node

Provide an addonPath override as the escape hatch for exotic layouts. Import electron only lazily inside the one convenience wrapper, so the core stays loadable (and testable) in plain Node.

[4] Rebuild CLI

<pkg> rebuild --electron-version X --arch arm64|x64|universal:

  • node-gyp must be a runtime dependency, and the CLI must spawn its bin script resolved via createRequire(import.meta.url) from the package's own install — npx node-gyp silently network-fetches an unpinned version when the package sits in a consumer's node_modules.
  • node-gyp rebuild = clean → configure → build, and clean is rm -rf build/. Any intermediate stashed for a later step (universal lipo) must live OUTSIDE build/ or the second arch pass destroys it.
  • Electron ABI: --dist-url=https://electronjs.org/headers.
  • Auto-detect the consumer's Electron version from their node_modules when the flag is omitted.

[5] Packager fragments

Export a config-fragment function, not documentation alone. The traps:

  • electron-builder packs the ENTIRE production dependency. The vendored framework (fetched by your build) gets a useless second copy inside app.asar unless excluded: files: ["!**/node_modules/<pkg>/native/vendor/**"].
  • asarUnpack must be narrow: **/node_modules/<pkg>/native/build/Release/*.node, not native/build/** (which unpacks Makefiles and object files).
  • macOS rpath depth is layout-relative. An @loader_path/../../…/Frameworks entry copied from the app counts from the addon's OLD location; inside node_modules/<pkg>/native/build/Release the addon sits deeper — recount to Contents/Frameworks (8 ups for this layout) and verify with otool -l <addon>.node | grep -A2 LC_RPATH. Electron's own @executable_path/../Frameworks LC_RPATH can mask a dead entry — the framework loading is not proof your rpath is right.
  • Ship the afterPack ad-hoc signing hook as an export; consumers wrap it in a 3-line file (electron-builder takes a file path). Sign once before dmg and zip are packaged so both embed a valid CodeDirectory.
  • prepublishOnly: builddist/ is gitignored; without the guard a clean checkout publishes a tarball with no JS and npm omits the dir silently.
  • require() of your ESM package from a consumer's .cjs hook needs Node ≥ 20.19 / ≥ 22.12 — set engines to match what your samples assume.

[6] Release CI as a composite Action

  • Composite inputs.*.default cannot reference other inputs — derive composed defaults inside the step body with ${VAR:-default}.
  • Never interpolate ${{ inputs.* }} inside run: bodies (script-injection surface). Route every input through the step's env: block, reference as "$VAR".
  • Secrets that touch disk (signing keys): RAM disk + trap cleanup EXIT with || true guards INSIDE the trap only — set -e skips inline cleanup when the signing tool fails mid-step.
  • An action step that runs npx <pkg> executes at the caller's workspace ROOT. In a pnpm workspace the bin only exists in the subpackage's node_modules/.bin, so npx silently falls back to a registry fetch of an unpinned version. Caller-side fix: add the package to ROOT devDependencies.
  • Pin external tool downloads by version + sha256, verified before extract.

[7] Dogfood migration

Switch the source app to the library and delete the local copy. The one assertion that cannot be skipped: run the app's REAL packaging and inspect the artifact — pnpm's symlinked node_modules is a layout the loader contract never saw in the lib's own repo:

  1. addon at app.asar.unpacked/node_modules/<pkg>/native/build/Release/
  2. framework at Contents/Frameworks/, a real directory
  3. codesign --verify --deep --strict passes
  4. npx asar list app.asar | grep vendor comes back empty

If 1 fails, the addonPath override is the fix — record the observed layout. Keep behavior parity explicit: tag prefixes, throttle values, notification text, and placeholder-injection anchors must survive the migration byte-for-byte (grep the old constants against the new wiring).

Common pitfalls

SymptomCauseFix
Install builds the addon against the wrong ABI, wastes minutesnpm auto-runs node-gyp for any package with binding.gypno-op install script; explicit rebuild CLI owns the real build
Consumer's rebuild fetches node-gyp from the registry, or fails offlinenode-gyp in devDependencies + npx node-gypmove to dependencies; spawn its bin via createRequire resolution
--arch universal always fails at lipointermediate stored under build/; second arch pass's clean removes itstash intermediates outside build/
Framework loads in the packaged app but the rpath entry is deadups-count copied from the app's shallower addon location; Electron's own LC_RPATH masks itrecount ups for the node_modules layout; verify with otool -l
Consumer's app.asar carries a full copy of the vendored frameworkelectron-builder packs the whole dependencyfiles exclusion for native/vendor/**
Published tarball has no dist/dist gitignored; npm omits missing dirs silentlyprepublishOnly build script
afterPack wrapper throws ERR_REQUIRE_ESM on some Node 20.xrequire(esm) unflagged only ≥ 20.19engines.node >= 20.19, noted beside the sample
Action step executes an unpinned registry version of your CLInpx at workspace root can't see the subpackage binpackage in caller's ROOT devDependencies
Signing key survives on the runner after a failed releaseset -e aborts before inline scrubtrap cleanup EXIT, || true only inside the trap
Old delta-base archives re-uploaded as the new release's assetsglobbing the archive dir after delta bases were fetched into itrecord own artifact names BEFORE fetching bases; publish only those
Version comparison treats every release as newer after migrationtag prefix not threaded into the generalized compareprefix is a required parameter; grep every call site
Appcast items 404 after a delta rebuildgenerator stamps ALL items with the current run's download URLre-point old enclosures to their own tags (URLs are not signed; safe)

Rules

  • Extraction is verbatim-first: byte-identical copies, then parameterize in a separate reviewable step.
  • The library never throws where the app can degrade — null contracts on every load path.
  • One source of truth per pinned value (framework version, placeholder string): shell script or exported constant, never both.
  • Real-artifact verification beats unit tests for packaging claims: otool, codesign, asar list, file-exists on the actual .app.

Verification

  • npm pack --dry-run from a CLEAN clone lists dist/, bin/, native sources, scripts — nothing more, nothing missing.
  • Rebuild CLI produces the addon end-to-end on the target Electron version; otool -l shows the recounted rpath entry.
  • Consumer-side packaging assertions 1–4 (step [7]) all pass on the real .app artifact.
  • Action YAML: no ${{ inputs.* }} inside any run: body; secret cleanup runs on the failure path.
  • Behavior-parity greps: old constants (URLs, prefixes, notification strings) resolve to identical values in the new wiring.

Frequently asked questions

What does the Electron Native Lib Extraction AI skill do?

Use when extracting an in-app Electron native-module integration (N-API addon + build scripts + packaging config + release CI) into a standalone, source-distributed npm library — or when authoring such a library from scratch. Covers the ABI/install-script traps, node-gyp rebuild semantics, macOS rpath depth, electron-builder packaging excludes, composite GitHub Action hygiene, and the dogfood migration back into the source app.

Why use Electron Native Lib Extraction on TypingMind?

Because you install it once and use it with any model. Electron Native Lib Extraction is plain Markdown rather than provider-specific code, so the same skill runs on GPT-5, Claude, Gemini, Grok, or a local model — and you can switch model mid-chat without it breaking. TypingMind runs on your own API keys, so you pay providers directly instead of a per-seat subscription, and your skills and chats stay in your own storage.

How do I install Electron Native Lib Extraction in TypingMind?

Open Plugins → Skills → Install from GitHub in TypingMind and paste https://github.com/Innei/SKILL/tree/main/skills/infrastructure/electron-native-lib-extraction. TypingMind reads its SKILL.md and installs it as a skill you can enable per chat.

Which AI models can use Electron Native Lib Extraction?

Any model you connect in TypingMind. AI skills are plain Markdown instructions rather than provider-specific code, so GPT, Claude, Gemini, Grok, and local models can all load this skill when a request matches it.

How many AI models can I use with Electron Native Lib Extraction?

As many as you like. As long as a model supports skills, you can use Electron Native Lib Extraction with it — GPT, Claude, Gemini, Grok, DeepSeek, Mistral, Llama and more — all on TypingMind with your own API keys.

Is the Electron Native Lib Extraction AI skill free?

It is published on GitHub by Innei. Check the repository for licensing terms. You only pay your own AI provider for the tokens you use.

What are AI skills?

An AI skill is a reusable instruction bundle that teaches an AI model how to do one specific task. It follows the open Agent Skills format: a SKILL.md file with a name and description, plus any scripts, templates or reference files the model may need. The model reads the instructions only when your request matches the skill, so an installed skill costs nothing until it is used.

How are AI skills different from plugins or MCP servers?

A plugin or MCP server gives a model new tools to call — code that runs somewhere and returns a result. An AI skill gives the model knowledge and process instead: how to approach a task, which steps to follow, what good output looks like. Skills are plain Markdown, so they need no server, no API key and no runtime, and they work with any model.

View all

Set up your own AI workspace now

Get notified about new features and future giveaways by subscribing to our newsletter 👇