Skip to content

Repository files navigation

npm-trust

Bulk-configure npm OIDC Trusted Publishing for every package in your npm scope.

Note: this CLI is npm-trust (hyphenated). It wraps the underlying npm trust github subcommand (with a space) — they are different things.

Built with AI license npm version npm downloads node version


Built for LLM consumption. Every entry point is shaped for an agent to drive end to end:

  • Filesystem auto-detection (--auto) that removes the "what packages live here?" guesswork — works for pnpm/npm/yarn workspaces and single-package repos, picks up scope from package names automatically.
  • --only-new for incremental setup so the agent doesn't waste calls re-checking packages that are already trust-configured.
  • --doctor for a structured environment + per-package health report (JSON via --json); a Claude skill or other tooling can branch on the report's issue codes.
  • A typed programmatic API alongside the CLI (discoverFromCwd, checkPackageStatuses, findUnconfiguredPackages, configureTrust, …) so an agent can choose between spawning the binary or importing the library — same primitives, same data shapes.
  • Deterministic output: every package status comes back as one of configured | already | not_published | auth_failed | error, so an agent can branch on the result without parsing prose.
  • For an interactive guided wizard with AskUserQuestion gates, install the gagle/solo-npm marketplace plugin and invoke /solo-npm:trust — that skill orchestrates this CLI.

The problem

npm OIDC Trusted Publishing lets GitHub Actions publish packages without secrets or expiring tokens. But it requires per-package configuration on npmjs.com. If you maintain an npm org with 10, 50, or 100+ packages, setting up each one manually through the web UI is tedious and error-prone.

The solution

npm-trust bulk-configures OIDC Trusted Publishing for every package in your npm scope from a single command. It auto-discovers all published packages in your org, handles npm 2FA authentication once, and configures the rest automatically.

Use cases

Pick the section that matches your situation. Each one shows the command to run and what to expect.

1. First-time setup for an org

You've published a batch of packages under your npm scope and need to enable OIDC trust for all of them in one go.

npx npm-trust --scope @myorg --repo myorg/release-pipeline --workflow release.yml

The CLI auto-discovers every published package in the scope, configures each one, and reports a summary at the end. The first package triggers a browser auth flow; on the npm site, choose "skip 2FA for the next 5 minutes" so the rest finish without further prompts.

2. Adding new packages to an existing trusted setup

You already configured OIDC for your org's packages and just published one or more new ones. Combine --scope with --only-new to filter automatically — the CLI runs npm trust list and npm view per package and configures only the ones missing trust or not yet published.

npx npm-trust --scope @myorg --repo myorg/release-pipeline --workflow release.yml --only-new

--only-new works with any source (--scope, --packages, or --auto).

3. A single-package project

You maintain a standalone npm package (no monorepo, no scope-wide setup). Run from the repo root and let --auto read ./package.json:

cd ~/projects/my-package
npx npm-trust --auto --repo me/my-repo --workflow release.yml

If you'd rather be explicit:

npx npm-trust --packages my-package --repo me/my-repo --workflow release.yml

4. A monorepo (pnpm / npm / yarn workspaces, with or without NX)

You maintain a monorepo with multiple publishable packages — for example packages/foo, packages/bar, apps/something. Run from the repo root with --auto:

cd ~/projects/my-monorepo
npx npm-trust --auto --repo myorg/repo --workflow release.yml

Detection priority: pnpm-workspace.yamlpackage.json#workspaces → single ./package.json. Packages marked private: true are skipped.

If every published package shares the same scope, --scope @myorg is also a one-liner. For ad-hoc lists, --packages still works.

5. Auditing — checking what's already trusted

To inspect current trust status without making changes:

npx npm-trust --scope @myorg --list

To preview what configure would do:

npx npm-trust --scope @myorg --repo myorg/repo --workflow release.yml --dry-run

What happens during execution

  1. The CLI discovers all packages in your npm scope (or uses the list you provide).
  2. For each package, it runs npm trust github to configure OIDC trust.
  3. If npm requires 2FA, the CLI pauses and opens a browser-based authentication prompt. You authenticate once — npm caches the session for ~5 minutes, long enough to configure the remaining packages.
  4. Packages already configured are silently skipped. Unpublished packages are reported so you know to publish them first.
  5. At the end, a summary shows how many were configured, already set, or failed.

Requirements

  • Node.js >= 24.0.0
  • npm >= 11.5.1 (for npm trust support)
  • 2FA enabled on your npm account
  • Write access to the packages you're configuring

Options

Flag Description
--scope <scope> npm org scope (e.g. @myorg) — auto-discovers all published packages
--packages <pkg...> explicit package names (alternative to --scope)
--auto detect packages from the current directory (workspaces or single package.json)
--repo <owner/repo> GitHub repository
--workflow <file> GitHub Actions workflow filename (e.g. release.yml)
--list list current trust status instead of configuring
--only-new filter the resolved package list to those without OIDC trust yet, or not yet published
--doctor print a structured health report (environment, repo, packages); exit 1 on fail issues
--validate-only fast read-only pre-flight (workflow + repo + auth, no per-package npm calls)
--verify-provenance bulk-query provenance attestations for the discovered/named packages
--emit-workflow print the canonical OIDC release.yml template to stdout
--with-prepare-dist modifier for --emit-workflow; emits the variant that wires in gagle/prepare-dist@v1
--capabilities emit a CapabilitiesReport JSON describing the CLI surface (for tool-discovery)
--json emit machine-readable JSON (works with --doctor, --list, --validate-only, --verify-provenance, --capabilities, and configure)
--dry-run show what would be done without making changes
--help show help message

Downstream consumers should pin to the public CLI contract documented in docs/cli-api.md — exit codes, JSON schemas, and migration notes.

Example output

Discovering packages in scope @myorg...
Found 12 packages

Configuring OIDC trusted publishing for 12 packages in @myorg
Repo: owner/repo | Workflow: release.yml

@myorg/core                    ✓ configured
@myorg/cli                     ✓ configured
@myorg/utils                   ✓ already configured
@myorg/new-pkg                 ✗ not published yet

Done: 2 configured, 9 already set, 1 failed

Failed packages (publish first, then re-run):
  - @myorg/new-pkg

Programmatic usage

npm-trust is published as a dual CLI + library. The same package exposes a typed public API for use inside other tools, scripts, or CIs.

import {
  checkPackageStatuses,
  configureTrust,
  discoverFromCwd,
  discoverPackages,
  findUnconfiguredPackages,
  listTrust,
  runCli,
} from "npm-trust";

discoverPackages(scope)

Discovers all published packages in an npm scope by paginating the public registry search API.

Parameter Type Description
scope string The npm scope (with or without leading @, e.g. @myorg or myorg).

Returns: Promise<Array<string>> — sorted package names in the scope.

Throws if the registry response is malformed, the registry URL is invalid, or the request times out (15 s).

const packages = await discoverPackages("@myorg");

discoverFromCwd(cwd)

Detects packages from a directory's filesystem layout: pnpm-workspace.yamlpackage.json#workspaces → single ./package.json. Packages marked private: true are skipped.

Parameter Type Description
cwd string Absolute path to the directory to inspect (often process.cwd()).

Returns: Promise<DiscoveredWorkspace | null>

interface DiscoveredWorkspace {
  readonly source: "pnpm-workspace" | "npm-workspace" | "single-package";
  readonly packages: ReadonlyArray<string>;
}
const detected = await discoverFromCwd(process.cwd());
if (detected !== null) {
  console.log(`Found ${detected.packages.length} packages in ${detected.source}`);
}

checkPackageStatuses(packages) and findUnconfiguredPackages(packages)

Both call three npm commands per package — npm trust list <pkg> (existing trust record), npm view <pkg> name (publication), and npm view <pkg> dist.attestations.url (SLSA provenance attestation).

checkPackageStatuses returns the full status of every package — useful for orchestration code (e.g. a wizard) that needs to differentiate "unpublished" from "missing trust" from "trusted via the npm web UI":

interface PackageStatus {
  readonly pkg: string;
  readonly trustConfigured: boolean;
  readonly published: boolean;
  readonly hasProvenance: boolean;
}

hasProvenance cross-checks the registry for a SLSA provenance attestation on the latest version. It catches the common case where Trusted Publishing was set up via npm's web UI rather than the CLI — npm trust list returns empty in that scenario, but the package's publishes are still going through OIDC.

findUnconfiguredPackages is a convenience filter over the same data. A package is kept (i.e. flagged as needing work) only when it lacks both an explicit trust record and a provenance attestation, or isn't yet published. Concretely: kept if !((trustConfigured || hasProvenance) && published).

const statuses = checkPackageStatuses(["@myorg/foo", "@myorg/new"]);
const unconfigured = findUnconfiguredPackages(["@myorg/foo", "@myorg/new"]);

configureTrust(options)

Runs npm trust github <pkg> --repo <r> --file <w> --yes for every package and aggregates the results.

Option Type Default Description
packages ReadonlyArray<string> (required) Package names to configure.
repo string (required) GitHub owner/repo.
workflow string (required) GitHub Actions workflow file (*.yml / *.yaml).
dryRun boolean false Print what would happen without invoking npm.
logger Logger console { log, error } — supply a capturing logger to suppress stdout.

2FA: npm trust uses web-based 2FA only — there is no OTP/TOTP flag to pass programmatically. The first call opens a browser auth flow; on the npm site, enable the "skip 2FA for the next 5 minutes" option to let bulk calls proceed without re-authenticating. configureTrust falls back to an interactive prompt automatically when run from a TTY; in non-TTY contexts (CI without an OIDC issuer) it returns auth_failed immediately.

Returns: TrustSummary

interface TrustSummary {
  readonly configured: number;
  readonly already: number;
  readonly failed: number;
  readonly failedPackages: ReadonlyArray<string>;
}
const summary = configureTrust({
  packages: ["@myorg/foo", "@myorg/bar"],
  repo: "owner/repo",
  workflow: "release.yml",
});

if (summary.failed > 0) {
  console.error("Failed:", summary.failedPackages);
  process.exit(1);
}

listTrust(options)

Runs npm trust list <pkg> for each package and prints the current trust configuration.

Option Type Default Description
packages ReadonlyArray<string> (required) Package names to query.
logger Logger console { log, error } — destination for output.

Returns: void.

listTrust({ packages: await discoverPackages("@myorg") });

runCli(argv, logger?)

The same entry point used by the bin script. Useful for embedding the full CLI behaviour inside larger tools without spawning a child process. Returns the exit code instead of calling process.exit.

Parameter Type Default Description
argv ReadonlyArray<string> (required) The argument list, equivalent to process.argv.slice(2).
logger Logger console { log, error } — supply a capturing logger to suppress output.

Returns: Promise<number> — process exit code.

const code = await runCli(["--scope", "@myorg", "--list"]);
process.exit(code);

Health check (--doctor)

--doctor produces a single structured report covering everything an agent or human needs before running a trust setup: Node + npm versions, npm auth, the detected workspace, the GitHub remote, candidate workflow files, and per- package trust + provenance state. Plus a list of issues with stable codes.

npx npm-trust --doctor                # human-readable, color when stdout is a TTY
npx npm-trust --doctor --json         # machine-parseable JSON for agents and CI

Exit code is 0 when no fail-severity issues exist, 1 otherwise. Warnings don't fail the gate. Stable issue codes (for agents to branch on):

Code When
NODE_TOO_OLD (fail) Node < 24
NPM_TOO_OLD / NPM_UNREACHABLE npm < 11.5.1 or not on PATH
AUTH_NOT_LOGGED_IN npm whoami failed
AUTH_REGISTRY_UNUSUAL registry isn't https://registry.npmjs.org
WORKSPACE_NOT_DETECTED / WORKSPACE_EMPTY no signals or all packages private
REPO_NO_REMOTE / REPO_REMOTE_NOT_GITHUB no origin or non-GitHub origin
WORKFLOWS_NONE / WORKFLOWS_AMBIGUOUS / WORKFLOW_NOT_FOUND publish workflow problems
PACKAGE_NOT_PUBLISHED package not yet on the registry
PACKAGE_TRUST_DISCREPANCY npm trust list empty but registry has SLSA provenance for the package
REGISTRY_UNREACHABLE sentinel registry call failed
DOCTOR_FLAG_IGNORED --auto / --scope / --packages was supplied alongside --doctor and ignored

The JSON shape is DoctorReport from the public types. schemaVersion is versioned; consumers should switch on it before reading other fields.

Use from a Claude Code agent

The CLI is callable directly from any agent that can spawn shell commands — npx npm-trust --auto --repo <owner/repo> --workflow release.yml, etc. For an interactive guided wizard with AskUserQuestion gates, install the gagle/solo-npm marketplace plugin:

/plugin marketplace add gagle/solo-npm
/plugin install solo-npm@gllamas-skills

Then invoke /solo-npm:trust for the OIDC trust wizard (which calls this CLI under the hood). Or /solo-npm:init for the full bootstrap umbrella that scaffolds release.yml + publishConfig + .nvmrc + consumer wrappers and then chains into trust.

Release workflow for solo AI devs

npm-trust is the OIDC-trust primitive in an opinionated release workflow built for solo developers (or small groups of LLM agents) shipping AI-generated code. The full lifecycle — bootstrap, release, verify, status, audit, deps — lives in the gagle/solo-npm marketplace plugin as seven Claude Code skills. solo-npm's /solo-npm:trust skill is what calls this CLI.

/plugin marketplace add gagle/solo-npm
/plugin install solo-npm@gllamas-skills

First publish (chicken-and-egg)

OIDC trusted publishing requires the package to already exist on the npm registry before trust can be configured (npm needs the package record to attach trust to). That creates a chicken-and-egg: you can't publish via OIDC + provenance until trust is set up, but trust can't be set up until the package exists.

The bootstrap path:

  1. Develop the MVP locally; bump package.json#version to the initial release (e.g., 0.1.0).
  2. Classic publish, locally, ONE time:
    npm login                           # web 2FA, browser flow
    npm publish --access public         # without --provenance
    The package now exists on npm.
  3. Configure OIDC trust via this CLI:
    pnpm exec npm-trust --auto --repo <owner/repo> --workflow release.yml
    Or invoke /solo-npm:trust in Claude Code for the full guided flow (auth gate, dry-run, configure, verify).
  4. Set package.json#publishConfig:
    "publishConfig": {
      "access": "public",
      "provenance": true
    }
  5. From here, every subsequent release uses the tag-triggered CI workflow — no more classic publishes. CI handles the publish via OIDC + provenance.

Step 2 is the only place a human is at the keyboard for the bootstrap beyond the single AskUserQuestion approval in /solo-npm:release. Everything else is agent-driven once trust is configured.

Environment variables

Variable Default Description
NPM_TRUST_NPM <dirname(process.execPath)>/npm Override the path to the npm binary. Used in tests; rarely needed in production.
NPM_TRUST_REGISTRY https://registry.npmjs.org Override the registry used for package discovery. Must be https://..., or http://localhost / http://127.0.0.1 for local mirrors and tests.

About

Bulk-configure npm OIDC Trusted Publishing for all packages in a scope

Resources

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages