How to Add a New Plugin
Relevant source files
The following files were used as context for generating this wiki page:
Adding a new plugin to the numan ecosystem involves a multi-step process of promotion from a backlog, registration in the build manifest, and execution of a CI-driven build and release pipeline. This project acts as a binary-delivery feeder for the official registry, cross-compiling source-only plugins into signed, hash-pinned artifacts.
Sources: README.md:3-9, README.md:46-51
1. Defining the Plugin in Manifest
The first step is to promote a plugin from the demand-ranked backlog (docs/backlog.json) to the active[] list in manifest.json. Each entry must pin an immutable upstream commit to ensure provenance.
Sources: manifest.json:151-152, README.md:46-51
Manifest Entry Schema
A valid plugin entry requires the following fields:
| Field | Type | Description |
|---|---|---|
repo | String | GitHub repository in owner/name format. |
name | String | Unique name of the plugin for the registry. |
owner | String | The primary maintainer/owner of the plugin. |
plugin_bin | String | Name of the resulting binary file (e.g., nu_plugin_regex). |
tag | String | Human-facing version tag from the upstream repository. |
source_commit | String | Immutable 40-character SHA of the upstream source. |
version | String | Semantic version of the plugin. |
nu_version | String | Semver range of compatible Nushell versions (e.g., >=0.113.0). |
verified_with | Array | Specific Nushell versions tested with this plugin build. |
description | String | Technical description of plugin functionality. |
tags | Array | List of category tags (e.g., ["network", "dns"]). |
Sources: manifest.json:18-31, scripts/validate_manifest.py:16-29
Target Matrix and Exclusions
By default, plugins are built for five targets: x86_64-unknown-linux-gnu, aarch64-unknown-linux-gnu, x86_64-apple-darwin, aarch64-apple-darwin, and x86_64-pc-windows-msvc. If a plugin fails to build on a specific platform, it must be added to exclude_targets with a descriptive exclude_reason.
Sources: manifest.json:8-14, manifest.json:32-33, scripts/validate_manifest.py:53-70
2. Validation and CI Safety
Before a plugin can be built, the manifest must pass automated validation and safety checks.
This diagram shows the validation logic used to ensure that human-provided tags resolve to the exact pinned immutable commits.
Sources: scripts/validate_manifest.py:100-149, REVIEW.md:25-30
Key Invariants
- Immutable Pins:
source_commitmust be 40 lowercase hex characters. Sources: scripts/validate_manifest.py:101-102 - Tag Verification: The
resolve_tagfunction usesgit ls-remoteto ensure the upstream tag matches the declared SHA. Sources: scripts/validate_manifest.py:129-149 - Manual Trigger: Publishing is restricted to
workflow_dispatchwith a non-emptyonly=list. Sources: REVIEW.md:35, README.md:67-72
3. Build and Packaging Pipeline
The build process cross-compiles the plugin and normalizes the resulting binaries into deterministic archives.
Binary Normalization
The package_plugin.py script ensures that rebuilds produce identical hashes by fixing metadata:
- MTIME: Fixed to
1980-01-01 UTC. - Mode: Set to
0o755(executable). - Format:
.tar.gzfor Unix,.zipfor Windows.
Sources: scripts/package_plugin.py:28-31, scripts/package_plugin.py:41-49
Deterministic Archive Creation
Archives are generated to ensure a stable sha256 for hash-pinned registry entries.
Sources: scripts/package_plugin.py:41-69
4. Release Transaction Management
To prevent "half-published" releases, the project uses a transactional approach for GitHub Releases.
- Claim: Verify the tag/release does not exist, then create a "Run-owned" draft release and claim the tag. Sources: scripts/ensure_release_absent.py:47-66, scripts/release_transaction.py:65-95
- Upload: Upload normalized assets to the specific draft by ID to avoid race conditions. Sources: scripts/release_transaction.py:118-142
- Finalize: Re-verify that the tag still points to the correct commit and that all uploaded asset hashes match local builds before setting
draft: false. Sources: scripts/release_transaction.py:157-185
Sources: scripts/release_transaction.py:301-325, README.md:26-30
5. Registry Intake Generation
The final step in this repository is generating a kind: binary specification for the numan-registry.
The gen_spec.py script consumes the PACKAGED output from build jobs and the manifest.json entry to produce a JSON spec. This spec intentionally omits the sha256 field, as the registry's intake script (add-package.py) is responsible for downloading and hashing assets independently to maintain the trust boundary.
Sources: scripts/gen_spec.py:9-15, scripts/gen_spec.py:91-125, README.md:19-25
Spec Component Table
| Component | Source | Description |
|---|---|---|
source.rev | manifest.json | Pinned immutable upstream commit. |
artifact.targets | package_plugin.py | Map of target triples to download URLs and internal binary paths. |
nu_version | manifest.json | Nushell compatibility constraints. |
Sources: scripts/gen_spec.py:113-125, REVIEW.md:31-32
Summary
To add a plugin:
- Promote from
backlog.jsontoactive[]inmanifest.json. - Run
scripts/validate_manifest.py --verify-upstreamlocally. - Trigger the
.github/workflows/build.ymlworkflow manually via GitHub Actions. - Use the generated
spec-<name>.jsonto update thenuman-registry.
Sources: README.md:67-78