LPM CLI

lpm publish

Publish a package to LPM.dev Registry, npm, GitHub Packages, or a custom registry.

lpm publish [--wait [--wait-timeout <SECONDS>]]   # alias: lpm p

Packs the current directory and uploads it to a registry. By default, the target is LPM.dev Registry (where every package is private until you flip distribution to Pool or Marketplace). Publish elsewhere with --npm, --github, --gitlab, or --publish-registry.

The final artifact for every target is scanned for hardcoded secrets. When LPM.dev Registry is one of the targets, LPM CLI also computes the local quality score and enforces --min-score. Normal npm, GitHub Packages, GitLab Packages, and custom-registry publishes do not run that quality gate.

Examples

lpm publish                                  # publish to LPM.dev Registry
lpm publish --dry-run                        # prepare and show the publish plan
lpm publish --check                          # local preparation, no registry calls
lpm publish --npm                            # publish to registry.npmjs.org
lpm publish --github                         # publish to GitHub Packages
lpm publish --publish-registry https://r.example.com   # custom npm-compatible
lpm publish --provenance                     # require Sigstore provenance (CI)
lpm publish --npm --provenance               # npm-compatible publish provenance
lpm publish --npm --provenance-file bundle.sigstore
lpm publish --no-provenance                  # override provenance config
lpm publish --min-score 80                   # gate an LPM.dev publish on local quality
lpm publish --lpm --otp 123456               # supply LPM.dev MFA without a prompt
lpm publish --wait                           # wait for LPM.dev lifecycle activation
lpm publish --wait --wait-timeout 1200       # wait for up to 20 minutes
lpm stage publish --tag next                 # stage a version on npm
lpm stage list @scope/pkg                    # list staged npm versions
lpm stage approve <stage-id> --otp 123456    # promote the staged version

What runs before upload

In order:

  1. Skills validation (LPM.dev Registry target only) — when the project ships authored files under .lpm/skills/, validate their format and run the skill security-pattern check.
  2. Pack — assemble an in-memory tarball from the package manifest. Without package.json > files, the implicit set excludes project-local .lpm state except direct publisher-authored .lpm/skills/*.md candidates. LPM.dev Registry publishes validate those candidates first; npm, GitHub Packages, GitLab Packages, and custom-registry-only publishes include them by path shape without skill-schema validation. Installed dependency skill sets remain excluded. An explicit files list controls deliberate inclusion, but cannot re-include installed dependency skills. Declared bundledDependencies (and the legacy bundleDependencies alias) are copied from verified hoisted or isolated install layouts, including required transitives. Bundled bytes are part of the same size, secret-scan, digest, and provenance boundaries. The archive limits apply before any upload.
  3. Workspace protocol rewrite — in monorepos, replace publishable workspace: and catalog: dependency specifiers inside the in-memory tarball. The source manifest stays unchanged.
  4. Provenance-file validation — when --provenance-file is used for an npm-compatible target, verify the bundle and confirm its subject and SHA-512 digest match the final target artifact.
  5. Secret scan — scan supported text files in the actual final artifact for every target, including target-specific rewrites and bundled dependency bytes. A file is not exempt because Git ignores it: if it is explicitly included in an artifact, it is scanned. Skip with --allow-secrets only when you have reviewed every target artifact carefully.
  6. Quality score (LPM.dev Registry target only) — compute the local readme/license/types/tests/maintenance signals. The local preflight score is normalized to 0–100 over checks the CLI can actually evaluate; server-only checks remain not_evaluated and do not depress the local denominator. With --min-score N, abort if the normalized local score is below the threshold.
  7. Confirm — ask before a real interactive publish. Skip with -y.
  8. Authenticate, generate requested provenance, and upload — resolve auth for each target, generate Sigstore provenance when requested, then upload sequentially.

Archive limits

LPM CLI applies these limits when it creates or rewrites a publish artifact:

  • 100,000 archive entries.
  • 100,000 GNU or PAX metadata entries.
  • 256 path components, including the top-level package/ component.
  • 32 KiB for each encoded archive path.
  • 1 MiB for each GNU or PAX metadata record.
  • 500 MiB for the uncompressed tar archive, including headers, padding, and end markers.
  • 500 MiB for the compressed tarball.

The package/ prefix uses one component. Thus, a project-relative source path can use at most 255 components.

If an artifact exceeds a limit, LPM CLI stops during local preparation and does not upload it. Target-specific name and workspace rewrites use the same limits.

LPM.dev Registry accepts uploads of at most 100 MiB. Other registries can use smaller limits.

Successful human output is a compact status stream:

✓ Secret scan passed
✓ Quality score: 64/100
› Uploading tarball to LPM.dev Registry
    target     @lpm.dev/owner.pkg@1.2.3
    visibility private
    dist-tag   latest
✓ Done · published @lpm.dev/owner.pkg@1.2.3 in 2.41s

If quality checks fail or are missing, only those rows are shown beneath the score. The full per-category breakdown lives in lpm quality.

For LPM.dev Registry publishes, the package link uses the configured registry host and port. If you publish to a local development registry, the link opens that registry.

Interrupted publishing

These recovery checks apply to publishes to LPM.dev Registry.

If an upload stops before the registry accepts it, repeat the publish command:

lpm publish

If the connection fails after upload, LPM CLI checks the stored version and artifact integrity before it retries. A matching artifact can complete successfully without another upload.

If you terminate the process after the registry accepts the version, a repeat publish reports that the version already exists. It also explains how to proceed.

Verify the existing release in the registry. To publish different contents, choose a new version. An existing version cannot be overwritten.

Publication review and --wait

An LPM.dev Registry version can enter a processing state after upload. A private version can enter a short security cooldown after a publish-source change.

Pool and Marketplace versions can require publication review before they become public.

For a pending version, LPM CLI shows these separate values:

  • The uploaded version.
  • The publication state.
  • The current latest version available under the package's current distribution rules, if one exists.

When automation must wait for the uploaded version, use --wait. The command waits until the version is available under its distribution rules:

lpm publish --wait
lpm publish --wait --wait-timeout 1200

LPM CLI polls every three seconds. The default timeout is 1200 seconds. You can set a timeout from 10 through 3600 seconds.

For multiple targets, LPM CLI completes all uploads before it starts the wait. An LPM.dev Registry target is required.

The command stops successfully when the version becomes active. It stops with a nonzero exit code for these results:

  • The wait times out.
  • A polling request fails.
  • The version requires manual review.
  • The review rejects the version.
  • The version becomes quarantined or unpublished.
  • The Registry returns an unrecognized publication state.

These results do not undo the upload. If the wait fails, do not publish the same version again.

JSON keeps the upload result in each target's success field. It reports the separate wait result in publication_wait.

--wait is not valid with --dry-run or --check. The --wait-timeout flag requires --wait.

In --json output, the quality object keeps score, max_score, and checks, and also reports the raw local denominator as earned_points and applicable_points. Every check has an evaluation value of "passed", "failed", or "not_evaluated"; the last value identifies server-only checks while preserving their existing passed: false representation.

--check is the network-free preparation mode. It resolves the target configuration and names, validates authored LPM.dev skills, assembles the tarball, validates any supplied provenance file, runs the secret scan, and computes the local quality score when LPM.dev Registry is a target. It stops before OIDC exchange, target authentication, generated provenance, confirmation, and upload.

--dry-run runs the same local preparation. For an LPM.dev Registry target, it resolves the real available authentication (including Trusted Publisher/OIDC), requires any authored-skills staleness lookup to succeed, and calls a non-mutating Registry preflight that validates the resolved publish.lpm.name, publish permission, token restrictions, owner authorization, account limits, and version availability. Any failure exits non-zero; JSON mode emits one success: false document. The preflight never uploads, reserves a version, creates package state, or claims publication succeeded. npm/GitHub/GitLab dry runs retain their existing local-only behavior. A supplied --provenance-file is still validated locally.

Both preview modes leave the working tree untouched. When authored skills exist and package.json > files does not include .lpm/skills, preparation adds that entry only to the effective manifest used in memory. A real publish may persist the entry.

For LPM.dev Registry targets, lpm.config.json > type controls Registry classification and accepts only package or source. Omitting it leaves classification to Registry inference. The file itself remains an LPM CLI lpm add contract: any valid lpm.config.json activates the configured add path, including when type is package.

Bundled dependencies

LPM CLI honors npm-compatible bundledDependencies and bundleDependencies values. Arrays bundle the named runtime dependencies; true expands to every declared dependencies entry; false bundles none. Once a bundle root is selected, its installed production and optional transitive dependencies are included when available. Ordinary node_modules content remains excluded.

Run lpm install before publishing. A requested bundle that is missing, has the wrong package identity, escapes the project/LPM store roots, or contains an internal symlink fails preparation instead of producing an incomplete or unsafe archive. Archive paths use portable node_modules/<name>/... paths and are sorted deterministically for every publish target.

npm Trusted Publishing

lpm publish --npm and lpm stage publish automatically use npm Trusted Publishing when a supported CI OIDC token is available. LPM CLI exchanges the CI identity token with npm for a short-lived registry token, then uses that token only for the publish or stage-publish upload.

Supported inputs:

CI sourceConfigure
GitHub Actionspermissions: id-token: write; LPM CLI fetches the runtime token with audience npm:registry.npmjs.org
GitLab CI / CircleCI / other supported providersSet NPM_ID_TOKEN to an OIDC ID token minted with audience npm:registry.npmjs.org

Trusted Publishing is attempted only for npm's public registry (https://registry.npmjs.org; HTTP loopback is accepted for local test registries). Custom npm-compatible registries, GitHub Packages, GitLab Packages, and --publish-registry targets keep using token auth. If npm's OIDC exchange fails and a normal npm token is available, LPM CLI falls back to the token path; otherwise the OIDC error is surfaced.

OIDC is publish-only. lpm stage list, view, download, approve, and reject still require normal npm auth because npm limits Trusted Publishing authentication to publish operations.

npm staged publish

Full command reference: lpm stage.

lpm stage publish [--tag next] [--access public] [--provenance | --provenance-file <PATH> | --no-provenance] [-y]
lpm stage list [package]
lpm stage view <stage-id>
lpm stage download <stage-id>
lpm stage approve <stage-id> [--otp 123456]
lpm stage reject <stage-id> [--otp 123456]

lpm stage wraps npm staged publishing for the current project. It is npm-only in this release: there is no LPM.dev Registry, GitHub Packages, GitLab Packages, custom publish registry, workspace-recursive, or tarball/package-spec staging mode yet.

Stage publish reuses the normal npm publish preparation path: pack, workspace dependency rewrite, secret scan, quality gate, npm name/access/tag config, optional npm-name tarball rewrite, and optional --provenance / --provenance-file over the final tarball. The upload goes to npm's staging endpoint instead of the live publish endpoint, so the version is not installable until it is approved.

Auth for lpm stage publish against the default npm registry uses npm Trusted Publishing first when available, then the normal npm token sources:

Source order
npm Trusted Publishing (NPM_ID_TOKEN or GitHub Actions OIDC, audience npm:registry.npmjs.org)
NPM_TOKEN
token stored by lpm login --npm
locked .npmrc token

Publishing to staging does not prompt for OTP. npm defers proof-of-presence to lpm stage approve and lpm stage reject, where --otp or the usual npm web-auth retry flow is used. Those approval commands are token-authenticated; NPM_ID_TOKEN alone is not enough.

lpm stage publish validates npm's version/tag safety rules before the real upload: the package must already exist on npm, an already-published version is blocked, prerelease versions require an explicit tag, and implicit latest is blocked when npm already has a higher stable version. --dry-run stays local-only and does not contact npm, so those remote version checks run only for a real stage publish.

Use --npm-registry <URL> to target a compatible npm staging registry. The global --registry flag still means LPM.dev Registry and is rejected on lpm stage commands with a message to use --npm-registry.

If lpm.json > publish.npm.registry points at a non-default staging registry, LPM CLI requires an exact registry-scoped token from lpm login --login-registry <URL> --token <T>. Generic NPM_TOKEN and tokens stored by lpm login --npm are only used for https://registry.npmjs.org.

Every lpm stage subcommand supports --json. JSON output uses LPM CLI envelopes with success, target: "npm", registry, auth on stage publish ("oidc" or "token"), stageId when a single staged package is involved, and data for the registry response.

Provenance

lpm publish --provenance
lpm publish --npm --provenance
lpm publish --npm --provenance-file ./pkg-1.2.3.sigstore
lpm publish --no-provenance

--provenance generates a Sigstore-signed attestation over the final tarball for each publish target. For LPM.dev Registry, the bundle is stored in package metadata. For npm-compatible targets (--npm, --github, --gitlab, or --publish-registry), LPM CLI also attaches npm's expected {name}-{version}.sigstore bundle in the publish payload.

Generated npm-compatible attestations use npm's current libnpmpublish SLSA statement shapes for GitHub Actions and GitLab CI, so the bundle matches the provenance contract npm-compatible registries and registry UIs expect.

Generated provenance requires an audience-sigstore OIDC token from a supported CI environment:

  • GitHub Actions — enable permissions: id-token: write on the job; the runtime mints the JWT.
  • GitLab CI — mint SIGSTORE_ID_TOKEN via the id_tokens block with aud: sigstore (the legacy LPM_GITLAB_OIDC_TOKEN env var is also accepted).

Other platforms aren't supported — Sigstore needs the OIDC issuer to sign. The flag fails loud (non-zero exit) if no usable token is found or if Sigstore signing/Rekor recording fails; it never silently falls back to publishing without provenance.

Generated npm-compatible provenance and provenance-file attachments require effective npm access to be public. LPM CLI's npm access default is public for scoped and unscoped packages. If you set publish.npm.access, publish.github.access, publish.gitlab.access, or --access restricted on stage publish, LPM CLI fails before npm auth, Sigstore signing, file validation, or upload.

--provenance-file <PATH> attaches a pre-generated Sigstore bundle to npm-compatible targets. The file is parsed, verified, and checked against the final tarball subject and SHA-512 digest before upload. It is not valid for LPM.dev Registry targets, so lpm publish --lpm --npm --provenance-file ... fails before validation or upload. --provenance, --provenance-file, and --no-provenance are mutually exclusive.

Provenance can also be enabled through npm-compatible config, in this order:

  1. CLI: --no-provenance, --provenance, --provenance-file <PATH>
  2. package.json > publishConfig.provenance
  3. Environment: NPM_CONFIG_PROVENANCE_FILE, NPM_CONFIG_PROVENANCE
  4. Project .npmrc: provenance=true or provenance-file=...
  5. Home .npmrc

provenance and provenance-file are mutually exclusive in env and .npmrc config. Use --no-provenance when a repo-level npm config enables provenance but a particular publish or staged publish should skip it.

Consumers can later verify provenance via the audit pipeline or registry UI.

Targets

lpm publish --lpm --npm                 # publish to both in one run
lpm publish --github --gitlab           # cross-publish to both git-host registries

Multiple target flags compose — lpm publish runs through them in order and reports per-target success / failure. Up to 5 target registries per invocation.

FlagRegistryRequirements
(default) / --lpmLPM.dev RegistryPackage name must be @lpm.dev/owner.pkg. Override with publish.lpm.name in lpm.json
--npmregistry.npmjs.org (or custom via publish.npm.registry)Override name with publish.npm.name; non-default registries require exact registry-scoped auth
--githubGitHub PackagesPackage name must be scoped (@owner/pkg). Falls back to publish.github.namepublish.npm.namepackage.json#name — any of them being a scoped name is enough
--gitlabGitLab Packagespublish.gitlab.projectId is required. Optional publish.gitlab.registry overrides the default https://gitlab.com instance
--publish-registry <URL>Any npm-compatible registryURL must be https://...

When no target flag is set on the CLI, the active set comes from lpm.json > publish.registries (default: ["lpm"]).

You must have auth for the chosen target — see Authentication for lpm login --npm, --github, --gitlab, or lpm login --login-registry <URL> for custom registries.

Custom publish registry URLs are HTTPS-only whether they come from --publish-registry or lpm.json > publish.registries. LPM CLI rejects http:// before dry-run planning or upload.

Repo-configured custom npm registries are routing data only. They do not redirect NPM_TOKEN, tokens stored by lpm login --npm, or locked default-npm .npmrc auth. Store a token for the exact registry URL with lpm login --login-registry <URL> --token <T>.

Authentication and OTP

If LPM.dev Registry requires MFA, an interactive publish asks for a six-digit authenticator code. Use --otp <CODE> when a terminal prompt is unavailable:

lpm publish --lpm --otp 123456 --json

The flag applies only to LPM.dev Registry uploads. LPM CLI rejects a value that is not exactly six ASCII digits.

Token resolution for npm-compatible targets:

TargetResolution order
npm (https://registry.npmjs.org)npm Trusted Publishing for publish --npm / stage publish -> NPM_TOKEN -> token stored by lpm login --npm -> locked .npmrc token
GitHub PackagesGITHUB_TOKEN → valid gh auth for github.com → LPM CLI-stored fallback token
GitLab.com PackagesGITLAB_TOKEN / CI_JOB_TOKEN → valid glab auth → LPM CLI-stored fallback token
Self-managed GitLabGITLAB_TOKEN / CI_JOB_TOKEN → LPM CLI-stored fallback token
Custom registriesExact LPM CLI-stored custom-registry token from lpm login --login-registry <URL> --token <T>

LPM CLI advertises npm web auth on npm-compatible publish requests (npm-auth-type: web, npm-command: publish). If npm returns a browser-based OTP challenge (authUrl + doneUrl), LPM CLI opens the browser flow, polls for completion, and retries with the returned npm-otp. Classic www-authenticate: OTP prompts still work in an interactive terminal; set lpm.json > publish.npm.otpRequired to prompt before the first publish attempt. Non-interactive publish should use an automation token.

Flags

FlagEffect
--dry-runPrepare and show the plan; for LPM.dev, perform non-mutating remote auth/permission/version preflight without uploading
--checkRun network-free local publish preparation and stop before upload
--waitWait until an LPM.dev Registry upload becomes active under its distribution rules
--wait-timeout <SECONDS>Set the wait limit from 10 through 3600 seconds. The default is 1200 seconds
--otp <CODE>Send a six-digit authenticator code for an MFA-protected LPM.dev publish
-y, --yesSkip interactive confirmation
--provenanceRequire Sigstore provenance (CI with OIDC only)
--no-provenanceDisable provenance even when npm config enables it
--provenance-file <PATH>Attach a pre-generated Sigstore bundle to npm-compatible targets
--min-score <N>Minimum local quality score required when LPM.dev Registry is a target
--allow-secretsSkip pre-publish secret scanning (not recommended)
--npm / --lpm / --github / --gitlabTarget a specific registry
--publish-registry <URL>Custom npm-compatible registry (https:// only)
stage --npm-registry <URL>npm staging registry for lpm stage subcommands
stage publish --provenanceGenerate npm-compatible Sigstore provenance for a staged publish
stage publish --no-provenanceDisable staged-publish provenance even when npm config enables it
stage publish --provenance-file <PATH>Attach a pre-generated Sigstore bundle to a staged publish
stage --otp <CODE>One-time password for lpm stage approve / reject

Plus the global flags.

See also

  • Authentication — login, tokens, CI setup
  • lpm stage — npm staged publish command reference
  • Save policy — how versions get saved on the consumer side
  • lpm quality — preview the quality report
  • Pool — flipping a published package to pool distribution