Serverless Registry

sreg tool

Command line tool for publishing packages to Serverless Registry and writing package manager credentials.

sreg is the only way to publish a version to Serverless Registry. It packages the project, uploads the files straight to R2 through presigned URLs and commits the version through the registry's API. Published versions are immutable. It also downloads generic files and writes credentials for package managers, including GitHub Actions OIDC tokens. Administration (credentials, yank, delete) is done in the registry's web panel.

GitHub RepositoryReleases

Setup

Release binaries are published for macOS (arm64, x86_64), Linux (x86_64 glibc and musl, arm64) and Windows (x86_64).

curl --proto '=https' --tlsv1.2 -LsSf https://github.com/kduma-OSS/serverless-registry-cli/releases/latest/download/sreg-cli-installer.sh | sh

Building from source needs Rust 1.88 or newer. In GitHub Actions use the actions instead - they install sreg with checksum verification.

Configuration

There is no configuration file, only flags and environment variables. They are accepted by every command (global options):

FlagEnvironment variableDescription
--registry <URL>REGISTRY_URLBase URL of the registry, e.g. https://packages.example.com. It may carry the token (see below).
--token <TOKEN>REGISTRY_TOKENToken in the form [<source>.]<project>:[<type>.]<secret>.
--jsonPrint machine-readable JSON only on stdout.
-v, --verboseLogs of sreg itself to stderr (-v debug, -vv trace). Tokens and presigned URLs are never logged.
--no-browserSREG_NO_BROWSERWith --login: print the approval page instead of opening it in a browser.

In CI pass both the URL and the token as environment variables from repository secrets.

Credentials in the registry URL

As with Composer, the registry URL may carry the token instead of --token / REGISTRY_TOKEN:

export REGISTRY_URL='https://shop-frontend:k7Qx2mN9pLw4@packages.example.com/'
sreg whoami
  • both a login and a password are required; percent-encode @, :, /, ?, # and % in them,
  • credentials are accepted only over https (plain http only for localhost and 127.0.0.1),
  • sreg strips the credentials before using the URL: requests, output, logs and --json only show https://packages.example.com/,
  • --oidc wins over everything; otherwise an explicit token and the URL's credentials must agree when both are given.

Treat such a URL as a secret (in GitHub Actions store it in secrets.).

Where the token comes from

A static token (--token / REGISTRY_TOKEN, or the credentials of the registry URL), or one of two options that replace it: --oidc (GitHub Actions OIDC) and --login (a short-lived token approved in the browser, see sreg login). --login and --oidc cannot be combined.

Commands

CommandWhat it does
sreg composer publishArchive a Composer package with git archive and publish it.
sreg npm publishPack with npm pack and publish an npm package.
sreg cargo publishPackage with cargo package and publish a crate.
sreg nuget publishPack with dotnet pack and publish a NuGet package.
sreg swift publishArchive a Swift package and publish it.
sreg maven publishPublish Maven / Gradle artifacts from a local repository.
sreg generic publishPublish arbitrary files as a version of a generic package.
sreg generic getDownload and verify files of a generic package version.
sreg authWrite registry credentials for a package manager, or print them.
sreg loginSign in through the browser and print a short-lived token to export.
sreg whoamiShow the identity and grants of the current token.
sreg versionShow the version of sreg and of the registry.

Common behaviour of publish commands

Every publish command works the same way:

  1. packages the project (see the table below),
  2. computes the SHA-256 digest and size of every file and calls init - the registry checks the token's publish grant for the package and version and returns one presigned upload URL per file,
  3. uploads every file straight to R2 with PUT (3 attempts with backoff; a new init when the URLs expire),
  4. calls commit - the registry verifies the uploaded files, writes the immutable version manifest and rebuilds the index.

sreg never runs the native tools' publish, only their packing commands. The flags it sets itself are reserved and rejected after --; everything else after -- goes verbatim to the tool (sreg cargo publish --version 1.2.0 -- --features ssl --locked).

EcosystemCommandFlags set by sregExamples of flags to pass after --
composergit archive --format=zip HEAD:<path> (fallback: a zip of the directory without vendor/ and .git/)--
npmnpm pack--json, --pack-destination <tmp>, -w <pkg> from --workspace--ignore-scripts
cargocargo package--no-verify, --allow-dirty, -p <crate> from --package, --target-dir <tmp>--features, --locked, --manifest-path
nugetdotnet pack <project>-o <tmp>, -p:PackageVersion=<v> from --version-c Release, --no-build, --include-symbols
swiftswift package dump-package, then swift package archive-source-o <tmp>/<name>-<version>.zip--verbose
mavennone - the build deploys into --from-dir first--
genericnone - the files are ready--

Options shared by the publish commands

Accepted by composer, npm, cargo, nuget, swift and maven publish (generic publish has its own set, see below).

OptionDescription
--version <VERSION>Version to publish. Composer and Swift: instead of the release tag on HEAD (v1.2.3 becomes 1.2.3). npm, cargo and NuGet: written into the manifest (or passed to dotnet pack) before packaging; sreg says so on stderr and leaves the change in the working tree. Maven: selects the version directory.
--tag-prefix <PREFIX>Take the version from the tag on HEAD that starts with the prefix, e.g. sdk/v for sdk/v1.2.3. Cannot be combined with --version. Without both, only v<semver> tags are considered and more than one on HEAD is an error. For monorepos.
--dry-runStop after the registry accepted the upload declaration (init): everything is validated, nothing is uploaded.
--skip-existingWhen the version is already published, print version already published, skipping and exit 0 instead of 2. Makes re-running a workflow safe.
--oidcAuthenticate with a GitHub Actions OIDC token instead of REGISTRY_TOKEN (the job needs permissions: id-token: write).
--oidc-project <PROJECT>With --oidc: the project the token is issued for. Without it the registry finds the project from the repository the token was issued to.
--loginSign in through the browser and approve this publish: you get a 15-minute token for exactly the version being published (see sreg login). Cannot be combined with --oidc.

With --json, a successful publish prints { "eco", "name", "version", "url", "files": [{ "name", "url" }] }; a skipped version prints { "skipped": true, "eco", "name", "version" }.

Publishing prebuilt archives

composer, npm, cargo and nuget publish accept --archive <FILE> (.zip, .tgz, .crate, .nupkg) to publish an archive built earlier, byte for byte. Build, test and sign the archive in one job and publish exactly those bytes from another, or migrate historical releases without changing their checksums.

sreg npm publish      --archive package.tgz
sreg composer publish --archive package.zip
sreg cargo publish    --archive package.crate
sreg nuget publish    --archive Package.1.2.3.nupkg
  • The file is uploaded as it is: the SHA-256 the registry records is the SHA-256 of the input file. Only the name it is stored under follows the registry's conventions.
  • Identity, version and dependencies come from the manifest inside the archive (package/package.json, composer.json, Cargo.toml, the .nuspec). The archive is validated completely before the first request.
  • No git checkout and no native tool is needed, and tags in the current directory are ignored.
  • --version is only a consistency check - it must equal the version in the archive (a Composer archive whose composer.json has no version is the exception: there --version is the version to publish).
  • --tag-prefix, --path (composer), --workspace (npm), --package (cargo), --project and --no-symbols (nuget) and arguments after -- are rejected together with --archive. --dry-run, --skip-existing, --json, authentication and exit codes work as before; npm publish --archive still takes --tag. A NuGet symbols package is not published in this mode.
$ sreg npm publish --archive acme-ui-2.2.0.tgz --dry-run
Dry run: the registry accepted npm @acme/ui 2.2.0; nothing was uploaded.
FILE               SIZE  SHA256                                                            LABELS
acme-ui-2.2.0.tgz  225   d4ea7dc95643a011146180e0d224fb91dfe3efbc544d3841e955d82c9586183c

$ sreg npm publish --archive acme-ui-2.2.0.tgz --version 9.9.9
error: failed to read the npm archive acme-ui-2.2.0.tgz: invalid input: the tarball contains @acme/ui@2.2.0, but --version 9.9.9 was given

Requires sreg 0.1.3 or newer.

Migrating Cargo crates from another registry

A crate built for a retired registry names that registry's index in its Cargo.toml. To publish it here without touching the archive, name the old index:

sreg cargo publish \
  --archive licensing-kit-example-1.2.3.crate \
  --source-registry-index sparse+https://old.example.com/cargo/index/

Dependencies that refer to that index are described in this registry's sparse index as dependencies from the same registry (registry: null), so cargo resolves them here and the old host is never contacted. The archive and its checksum stay unchanged; dependencies from other registries keep their identity.

sreg composer publish

sreg composer publish [--path <DIR> | --archive <FILE>] [--version <V> | --tag-prefix <P>] [--dry-run] [--skip-existing] [--oidc [--oidc-project <P>] | --login]

Archives a Composer package and publishes it. Composer has no packing tool, so sreg packs by itself.

OptionDefaultDescription
--path <DIR>.Directory of the package to publish (monorepos: --path packages/sdk).

Plus --archive <FILE> (prebuilt archives) and the shared options.

How it works:

  • Name: name in composer.json is required and must be a valid Composer name (vendor/package, lower case).
  • Version: from --version, or the release tag pointing at HEAD (v1.2.3 becomes 1.2.3; with --tag-prefix sdk/v, sdk/v1.2.3; --tag-prefix "" for bare 1.2.3 tags). Versions are MAJOR.MINOR.PATCH, optionally with -alpha, -beta or -rc and a number (1.0.0-rc1, 1.0.0-beta.2); dev-* branches, other prerelease labels and build metadata are rejected. If composer.json declares a version, it must equal the published one - best leave it out.
  • Archive: in a git repository git archive HEAD:<path>: the package directory becomes the archive root, only committed files are published and paths marked export-ignore are left out. Put the rules into the package directory's own .gitattributes. composer.json must be committed and equal to the one on disk. Outside a git repository the directory is zipped without vendor/ and .git.
  • Upload: the archive as <package>-<version>.zip (acme/sdk 1.2.3 becomes sdk-1.2.3.zip); composer.json is the version's manifest.
git tag v1.2.3
sreg composer publish                                    # acme/sdk 1.2.3 from the tag
sreg composer publish --version 1.2.4                    # explicit version
sreg composer publish --dry-run                          # validate only
sreg composer publish --path packages/sdk --tag-prefix sdk/v   # monorepo
$ sreg composer publish --version 1.0.0
Published composer acme/sdk 1.0.0
https://packages.example.com/composer/dist/acme/sdk/1.0.0.zip

$ sreg composer publish --version 1.0.0
error: failed to publish composer acme/sdk 1.0.0: this version already exists in the registry
hint: published versions are immutable; bump the version to publish again

(the second call exits with 2; with --skip-existing it exits with 0).

sreg npm publish

sreg npm publish [--version <V> | --tag-prefix <P>] [--tag <TAG>] [--workspace <W> | --archive <FILE>] [shared options] [-- <npm pack args>]

Packs the project in the current directory with npm pack and publishes the tarball.

OptionDefaultDescription
--tag <TAG>latest (next for prereleases)Distribution tag of this version.
--workspace <W>-Publish this npm workspace (name or path) of the project in the current directory: npm pack -w <W>.
--archive <FILE>-Publish this .tgz as it is instead of running npm pack (details).
-- <npm pack args>-Extra arguments passed verbatim to npm pack, e.g. --ignore-scripts.

How it works:

  • Only scoped packages (@scope/name) are served by the registry; the version must be semver.
  • With a version requested, sreg first writes it into package.json (npm pkg set version=<v>, with -w for workspaces) and says so on stderr. Without --version and --tag-prefix it uses the v<semver> tag on HEAD, falling back to the version in package.json.
  • Runs npm pack --json --pack-destination <tmp> and reads package/package.json from the tarball (without fields starting with _) as the manifest.
  • Uploads the tarball under the name npm pack gave it (@acme/sdk 1.2.3 becomes acme-sdk-1.2.3.tgz).
sreg npm publish --version 1.2.3
sreg npm publish --tag beta -- --ignore-scripts
sreg npm publish --workspace packages/sdk --tag-prefix sdk/v   # npm workspaces
$ sreg npm publish --version 2.1.0
note: set the version in ./package.json to 2.1.0 (npm pkg set)
Published npm @acme/ui 2.1.0
https://packages.example.com/npm/@acme/ui/-/ui-2.1.0.tgz

sreg cargo publish

sreg cargo publish [--version <V> | --tag-prefix <P>] [--package <CRATE> | --archive <FILE> [--source-registry-index <URL>]] [shared options] [-- <cargo package args>]

Packages a crate with cargo package and publishes the .crate.

OptionDefaultDescription
--package <CRATE>the crate of the current directory, or the only crate of the workspacePublish this workspace member (cargo package -p <CRATE>).
--archive <FILE>-Publish this .crate as it is instead of running cargo package (details).
--source-registry-index <URL>-With --archive: index URL of the retired registry the crate was built for (migrating crates).
-- <cargo package args>-Extra arguments passed verbatim to cargo package, e.g. --locked, --features ssl, --manifest-path.

How it works:

  • cargo metadata --no-deps finds the crate. With a requested version, sreg writes it into the crate's Cargo.toml (formatting preserved); a crate with version.workspace = true cannot get its own --version - set it in [workspace.package] instead. Without --version it uses the v<semver> tag on HEAD, falling back to Cargo.toml.
  • Runs cargo package --no-verify --allow-dirty -p <crate> --target-dir <tmp>.
  • Uploads the .crate as <name>-<version>.crate. The manifest is the crate's line of the cargo sparse index, built from cargo metadata: dependencies from crates.io point at the crates.io index, dependencies from another registry (declared with registry = "<name>") are taken to come from this registry. Path-only dev-dependencies are left out, as cargo publish does.
  • Make the registry known to cargo for consumers with sreg auth cargo.
sreg cargo publish --version 1.4.2
sreg cargo publish --package acme-core --tag-prefix core/v -- --locked
$ sreg cargo publish --version 0.3.0
note: set the version in /work/Cargo.toml to 0.3.0 (was 0.1.0)
Published cargo acme-core 0.3.0
https://packages.example.com/cargo/dl/acme-core/0.3.0

sreg nuget publish

sreg nuget publish [--version <V> | --tag-prefix <P>] [--project <PROJECT>] [--no-symbols] [--archive <FILE>] [shared options] [-- <dotnet pack args>]

Packs a project with dotnet pack and publishes the .nupkg (and symbols).

OptionDefaultDescription
--project <PROJECT>the single .csproj in the current directoryProject file to pack. Solutions (.sln) are refused because they produce several packages.
--no-symbolsDo not publish the .snupkg symbols package that dotnet pack produced.
--archive <FILE>-Publish this .nupkg as it is instead of running dotnet pack (details).
-- <dotnet pack args>-Extra arguments for dotnet pack, e.g. -c Release, --no-build, --include-symbols.

How it works:

  • Runs dotnet pack <project> -o <tmp> with the requested version passed as -p:PackageVersion=<v>. Without --version and --tag-prefix the v<semver> tag on HEAD is used, falling back to the project's own version.
  • Uploads the .nupkg as <id>.<version>.nupkg (lower case) and, when dotnet pack produced one (e.g. with -p:IncludeSymbols=true -p:SymbolPackageFormat=snupkg) and unless --no-symbols, the .snupkg. Legacy .symbols.nupkg packages are ignored.
  • The manifest holds the id, version, description, authors, project URL, license expression, tags and dependency groups from the .nuspec, plus the .nuspec itself.
sreg nuget publish --version 1.2.0 -- -c Release
sreg nuget publish --project src/Acme.Sdk/Acme.Sdk.csproj --tag-prefix sdk/v --no-symbols

sreg swift publish

sreg swift publish --scope <SCOPE> [--path <DIR>] [--repository-url <URL>] [--version <V> | --tag-prefix <P>] [shared options] [-- <archive-source args>]

Archives a Swift package and publishes it as <scope>.<name>.

OptionDefaultDescription
--scope <SCOPE>requiredRegistry scope to publish under (1-39 characters of A-Z, a-z, 0-9 and -). Swift packages have no scope of their own.
--path <DIR>.Directory of the package (with Package.swift).
--repository-url <URL>the URL of the origin git remote, if anySource repository URL recorded with the version. Credentials in the URL are stripped.
-- <archive-source args>-Extra arguments for swift package archive-source.

How it works:

  • Name: name in Package.swift as reported by swift package dump-package (1-100 characters of A-Z, a-z, 0-9, _ and -); published as <scope>.<name>.
  • Version: Package.swift has no version, so it comes from --version or the release tag on HEAD (v1.2.0, or <prefix>1.2.0); without either, the command fails. Versions are semver 2 without a leading v.
  • Files: the zip written by swift package archive-source (it archives the committed state of a git repository) as <name>-<version>.zip; Package.swift and every Package@swift-<version>.swift next to it as manifest files.
  • Manifest: the tools version, the repository URL and the complete output of swift package dump-package.
  • Consumers point SwiftPM at <registry>/swift with swift package-registry set <registry>/swift --scope acme and log in with sreg auth swift.
git tag v1.2.0
sreg swift publish --scope acme                              # acme.DemoKit 1.2.0 from the tag
sreg swift publish --scope acme --version 1.3.0-beta.1
sreg swift publish --scope acme --path packages/Kit --tag-prefix kit/v

sreg maven publish

sreg maven publish --from-dir <DIR> [--coords <GROUP:ARTIFACT>] [--version <V> | --tag-prefix <P>] [shared options]

sreg does not run Maven or Gradle. Let the build deploy into a local directory (a Maven-2 file repository) and publish one artifact version from it.

OptionDescription
--from-dir <DIR>Required. The local repository the build deployed to.
--coords <GROUP:ARTIFACT>The artifact to publish when the repository holds several (multi-module builds; publish each module with its own call).

How it works:

  • Artifact: looks for <group/as/path>/<artifactId>/<version>/<artifactId>-<version>.pom under --from-dir. The package name in the registry is <groupId>:<artifactId>.
  • Version: --version or the release tag on HEAD selects the version directory; without either the directory must hold exactly one version. -SNAPSHOT versions are rejected. sreg does not change the build: the version the build used must be the one being published.
  • POM: must be an effective POM - a <parent> or a ${...} property in coordinates, packaging or dependencies is rejected (Gradle writes resolved POMs; with Maven deploy a flattened POM, e.g. with flatten-maven-plugin). Its groupId, artifactId and version must match its location.
  • Files: every file of the version directory except checksums (the registry serves its own) and maven-metadata*.xml, each with a role: pom, archive (the packaging's extension), sources, javadoc, Gradle module metadata, or extra (classified jars, distributions, .asc signatures).
  • Manifest: groupId, artifactId, version, packaging, the POM itself and the dependencies with their scope and optional flag.
mvn -B deploy -DaltDeploymentRepository=local::file:./out
sreg maven publish --from-dir out --coords com.acme:sdk

sreg generic publish

sreg generic publish <NAME> <VERSION> [FILE]... [--label <K=V>]... [--meta <K=V>]... [--meta-file <FILE>] [--spec <FILE>] [--no-append] [--dry-run] [--skip-existing] [--oidc [--oidc-project <P>] | --login]

Publishes files as one version of a generic package (binaries, installers, anything).

Argument / optionDescription
<NAME>Package name [<group>/]<name>, lower case, e.g. tools/acme-tool.
<VERSION>Semver, e.g. 1.2.0 or 1.3.0-rc.1. There is no version autodetection.
[FILE]...Files to publish; each is published under its file name.
--label <K=V>Label applied to every file of the call (repeatable), e.g. os=linux. Consumers filter by labels. sreg adds no default labels.
--meta <K=V>Metadata entry of the version, as a string (repeatable).
--meta-file <FILE>JSON object with the metadata of the version; --meta entries override its keys.
--spec <FILE>JSON file listing files with their own labels: [{"path": "...", "labels": {"os": "linux"}}, ...] - instead of FILE arguments.
--no-appendRequire a new version instead of adding the files to an already published one.
--dry-run, --skip-existing, --oidc, --oidc-project, --loginAs in the shared options. Here --skip-existing also applies to a file being appended that already exists.

How it works:

  • By default files are appended to an already published version, as long as the registry's append window (APPEND_WINDOW_HOURS, 24 h by default) has not passed and the file names are new. This lets a CI matrix publish one file per job (Linux, macOS, Windows builds) into the same version. A committed file is immutable.
  • Arguments after -- are rejected.
sreg generic publish tools/acme-tool 1.4.0 acme-linux.tar.gz   --label os=linux   --label arch=x86_64
sreg generic publish tools/acme-tool 1.4.0 acme-macos.tar.gz   --label os=macos   --label arch=arm64
sreg generic publish tools/acme-tool 1.4.0 acme-windows.zip    --label os=windows --label arch=x86_64 --json
$ sreg generic publish tools/acme-tool 1.4.0 acme-linux.tar.gz --label os=linux
Published generic tools/acme-tool 1.4.0
https://packages.example.com/generic/tools%2Facme-tool/1.4.0

Publishing the same file again exits with 2 (a file with this name already exists in the published version).

sreg generic get

sreg generic get <NAME> [VERSION] [--label <K=V>]... [--sole] [--out <PATH>] [--prerelease] [--oidc [--oidc-project <P>] | --login]

Downloads the files of a generic package version after verifying their size and SHA-256. Needs a read token with a grant for the package.

Argument / optionDefaultDescription
<NAME>Package name [<group>/]<name>.
[VERSION]latestAn exact version, a constraint resolved by the registry (npm syntax: ^1.2, ">=1 <2", 1.x) or latest. The resolved version is printed on stderr.
--label <K=V>Only files with this label (repeatable; all must match).
--soleFail unless the label filter selects exactly one file, and download that file. When several match, the candidates are listed and sreg exits with 1.
--out <PATH>.Directory to write the files to; with --sole also the path of the file (an existing directory or a path ending in / stays a directory).
--prereleaseWith latest or a constraint, consider prerelease versions too.
--oidc, --oidc-projectAuthenticate with a GitHub Actions OIDC token.
--loginSign in through the browser and approve this download (sreg login); the token is kept in memory only.

The paths of the downloaded files are printed on stdout. With --json sreg prints the version's feed entry (name, version, published, yanked, meta and files with size, sha256, labels and url) without downloading.

sreg generic get tools/acme-tool '^1.0' --label os=linux --sole --out acme.tar.gz
sreg generic get tools/acme-tool --label os=macos --out dist/
sreg generic get tools/acme-tool latest --json
$ sreg generic get tools/acme-tool '^1.0' --label os=linux --sole --out acme.tar.gz
Resolved tools/acme-tool ^1.0 to 1.4.0 acme-linux.tar.gz
/work/acme.tar.gz

$ sreg generic get tools/acme-tool --sole
error: the labels select more than one file: 3 files of tools/acme-tool 1.4.0 match; add labels to select one.
hint: add --label filters to select one of these files of 1.4.0:
  acme-linux.tar.gz
  acme-macos.tar.gz
  acme-windows.zip

sreg auth

sreg auth <composer|npm|cargo|nuget|swift|maven> [--oidc [--project <PROJECT>] | --login] [--write <FILE>] [--registry-name <NAME>]

Hands the registry token to a package manager so that installing packages works. It either writes the token into the package manager's credentials file (--write) or prints what puts it in place. --registry / REGISTRY_URL is always required (the host and path determine where the token is stored); no request is made to the registry.

OptionDefaultDescription
--oidcBuild the token from a GitHub Actions OIDC token (<PROJECT>:gh.<jwt>, or _:gh.<jwt>) instead of REGISTRY_TOKEN. Needs id-token: write; the audience is the registry URL, which must equal the Worker's PUBLIC_URL.
--loginSign in through the browser and get a short-lived pull token (one hour, longer with "extended validity") instead of REGISTRY_TOKEN - see sreg login. Cannot be combined with --oidc.
--project <PROJECT>the registry finds it from the repositoryWith --oidc: the project the token is issued for; its credential must have a matching gh secret. Only accepted together with --oidc. (Publish commands call it --oidc-project.)
--write <FILE>print insteadWrite to this file; an existing file is updated in place, other content is kept, a new file gets mode 0600.
--registry-name <NAME>sregcargo: registry name; NuGet: source name; Maven: server <id>.

GitHub OIDC tokens are valid for minutes only, so run sreg auth --oidc right before the install step.

Without --write it prints a line or a command (treat the output as a secret):

EcosystemPrinted
composerexport COMPOSER_AUTH='{"bearer":{"<host>":"<token>"}}' (use with eval "$(sreg auth composer)")
npmnpm_config_//<host>/npm/:_authToken=<token>, plain NAME=value for "$GITHUB_ENV" (sreg auth npm >> "$GITHUB_ENV")
cargoexport CARGO_REGISTRIES_<NAME>_TOKEN='<token>'
nugetdotnet nuget add source '<registry>/nuget/index.json' -n '<name>' -u '<login>' -p '<password>' --store-password-in-clear-text
swiftswift package-registry login '<registry>/swift' --username '<login>' --password '<password>'
mavena settings.xml <server> snippet and a Gradle credentials snippet

With --write <FILE>:

EcosystemTypical fileEntry written
composerauth.json"bearer": { "<host>": "<token>" }
npm.npmrc//<host>/npm/:_authToken=<token> (the scope line @scope:registry=... is not written - add it yourself)
cargo~/.cargo/credentials.toml[registries.<name>] with token = "<token>" (the registry itself is declared in .cargo/config.toml with index = "sparse+<registry>/cargo/index/")
nugetnuget.configsource <name> and its clear-text credentials
swift~/.netrcmachine <hostname> login <login> password <password>
maven~/.m2/settings.xml<server> with <id>, username and password

With --json it prints { "eco", "file", "key" } (the token is not printed) when writing, or { "eco", "name", "value" } (richer objects for NuGet, Swift and Maven) otherwise.

# Local development: store a read token for Composer
sreg auth composer --write auth.json

# GitHub Actions: npm packages with an OIDC token of the project shop-frontend
sreg auth npm --oidc --project shop-frontend >> "$GITHUB_ENV"

# cargo with a custom registry name
eval "$(sreg auth cargo --registry-name acme)"

# Local development: sign in through the browser, no token to copy around
sreg auth npm --login --write .npmrc

# NuGet / Swift / Maven in CI
sreg auth nuget --oidc --project shop-frontend --write nuget.config
sreg auth swift --oidc --write ~/.netrc
sreg auth maven --oidc --write ~/.m2/settings.xml
$ sreg auth composer --write auth.json
Wrote the token of `shop-frontend` for bearer.packages.example.com to auth.json.

Details of every file format: docs/cli-auth.md.

sreg login

sreg login [--publish <ECO>:<PACKAGE>@<VERSION>]... [--no-browser] [--json]

Signs in through the browser and prints a short-lived token to export, for several commands in a row (eval "$(sreg login)"). It needs a registry credential with a login secret (a password and an optional one-time code), see logins for people. Requires sreg 0.1.4 or newer.

OptionDescription
--publish <ECO:PACKAGE@VERSION>Ask for a publish token for this package version (repeatable), e.g. composer:acme/sdk@1.4.0 or npm:@acme/sdk@2.0.0. Without it the token is for downloading.
--no-browserPrint the approval page instead of opening it (also SREG_NO_BROWSER).

How it works:

  1. sreg asks the registry for a login session (no credential is sent), prints the page to open and the operation it requests, and opens the page unless --no-browser is given.
  2. On the page you sign in with the project name and password of a login secret (and a one-time code when it has one) or, if the browser is already signed in, only approve. The password and the page belong to the registry - sreg never sees them.
  3. sreg polls until you approve or refuse and receives the token. A refusal, an expired session or a timeout fails the command with exit code 1.

The token is short-lived: a pull token (needs a read credential) lasts one hour, or longer when you tick "extended validity" (24 hours by default); a publish token lasts 15 minutes and works only for the listed versions - it is approved only when a publish credential's grants allow every one of them. The ecosystem in --publish ends at the first : (a Maven package is group:artifact) and the version starts after the last @ (an npm scope starts with one).

The same sign-in is available as --login on sreg auth, sreg generic get and every publish command, where the token is used directly:

eval "$(sreg login)"                                          # a pull token in $REGISTRY_TOKEN
eval "$(sreg login --publish composer:acme/sdk@1.4.0 --publish npm:@acme/sdk@2.0.0)"
sreg composer publish --login                                 # approve the publish of this one version
$ sreg composer publish --login --no-browser --version 1.3.0
Requesting a token to publish:
  composer acme/sdk 1.3.0
Open this page, sign in and approve:
  https://packages.example.com/authorize/<session>
Waiting for the approval (Ctrl-C to cancel)...
Approved: signed in as ci-acme-sdk (publish); the token is valid until 2026-10-28T20:43:51.000Z.
Published composer acme/sdk 1.3.0
https://packages.example.com/composer/dist/acme/sdk/1.3.0.zip

The approval page looks like this (see the Worker):

Protocol details: docs/cli-auth.md.

sreg whoami

sreg whoami

Shows the identity of the current token: the project, its kind and its grants (GET /api/v1/whoami). Useful to check that a token works and what it may do.

$ sreg whoami
Project: shop-frontend
Kind:    read
Grants:
  ECO       PACKAGE  VERSIONS  TRANSITIVE
  composer  acme/*   ^1.0      yes
  npm       @acme/*  *         yes

With --json: { "project", "kind", "grants": [{ "eco", "package", "versions", "transitive" }] } (transitive only for read credentials).

sreg version

sreg version
sreg --version

Prints the version of sreg; with --registry (or REGISTRY_URL) also the registry's version and the minimum client version it accepts. This endpoint is anonymous - no token is sent.

$ sreg version
sreg 0.1.5
registry 0.1.7 (schema 3, minimum client 0.1.0)

With --json: { "clientVersion", "registry": { "version", "minClientVersion", "schemaVersion" }, "compatible" }.

Exit codes

CodeMeaning
0Success.
1Generic error.
2The version already exists (409).
3sreg is too old for the registry (426) - update it.

Documentation

Copyright © 2026