The command line
Everything the console does can also be done from a terminal with jenrepo, the command-line client. It is
a client of the server's API: each command is a call to the same API the console and every script use, so a
command reaches exactly what the console reaches and needs no JSON written by hand. It suits an operator at a
terminal, a CI job, and a program driving the repository with no browser at all.
Installing it
jenrepo is installed on the machine you work from, not on the server - the server image does not carry it. It
runs on Java 25 or newer, and a package manager installs it as a command of its own:
brew install jenesis/tap/jenrepo # Homebrew
scoop bucket add jenesis https://github.com/jenesis/scoop-bucket && scoop install jenrepo # Scoop
mise use packslip:jenesis/jenesis-repository/jenrepo # mise
jenrepo help
Each release of Jenesis Repository also carries the command as a single launcher jar, jenrepo-<version>.,
which runs anywhere Java 25 does with java -jar.
Signing in
A session is the server's address and a key, stored once and used by every later command:
jenrepo login https://repo.example.com --key "$KEY"
jenrepo whoami
jenrepo logout
Without --key, login asks for the key without echoing it; left blank, the session is anonymous and reaches
what the deployment serves without a key. The session is kept under ~/, or under the folder
JENREPO_ names. The key is an ordinary one, issued under Access → Credentials, and the commands it
may run are the ones its grants allow - a deploy key publishes and reads, and managing the deployment takes a
key of the operator tenant with the manage rights, as Access describes.
A command that names a repository names one of the key's tenant; another tenant's is written <tenant>/.
Finding a command
jenrepo help lists every command under the same headings the console uses, in the console's order, so a page you
know in the console has its command under the same heading:
| Heading | Among its commands |
|---|---|
| Session | login, logout, whoami |
| Contents | browse, search, assets, deploy, import, staging, index |
| Review | quarantine |
| Risk | vulnerabilities, findings, health |
| Provenance | signers, signature, origin |
| Lifecycle | retention, cleanup, pins, lifecycle, webhook, export, forget-ecosystem |
| Build cache | projects |
| Access | credentials, members, groups, roles, trusts, policy, audit, keylogin |
| Operations | metrics, posture, caches, walks, consistency, logs |
| Settings | settings, setup, tenants, repos, upstreams, limits, capabilities, spi, config, purge |
jenrepo help <command> shows every form a command takes, and jenrepo capabilities says what this deployment
carries - which formats, modules and features - so a command for something it lacks is known in advance. A command
with no action reads, and changing something takes an action word:
jenrepo repos create libraries maven "Internal libraries" --set keep-last=20
jenrepo quarantine libraries # the review queue, each version with its held paths
jenrepo quarantine release libraries <path>... # release a version, by the paths the queue lists
jenrepo repos settings libraries set routing "writable fallback https://repo1.maven.org/maven2/"
jenrepo projects create my_project gradle "Main branch builds" --set project-size=10737418240
jenrepo walks run
jenrepo skill prints a briefing for a program that has not used the tool before: what it is, how a session
works, what the exit codes mean, and which commands delete what cannot be recovered.
Output for scripts
Without options, a command prints one record per line in columns, with - where a value is absent. With
--json, what it prints on standard output is exactly one JSON value - the server's own answer, with every field
the API returns:
- a command that makes several calls prints an array of their answers;
- an answer that is not JSON, such as a PEM key or a CSV file, arrives as
{"contentType":…,"body":…}; - a change the server answers with no body prints
{"ok":true}; - an error is JSON too,
{"error":…,"remedy":…,"exit":N}, and goes to standard error, as every message does.
Watching long work
A command that starts work in the background - an import, an export, a rescan, a walk of the store - returns as
soon as the server has accepted it, and never holds the connection open. Add --refresh to watch it instead: the
command prints the state again on an interval until the work finishes, then exits with the code that outcome
deserves. The interval suits what is watched, or is given outright as --refresh=:
jenrepo export libraries --url https://other.example.com/repository/releases/libraries/ --token "$TOKEN"
jenrepo export status libraries d4e5f6… --refresh
Under --json, a watching command still prints one value - the last state.
Exit codes
| Code | Means |
|---|---|
0 |
It worked. |
1 |
The server refused, or could not be reached - the message says which. |
2 |
The command line was wrong: an unknown command or a missing argument. |
3 |
This deployment does not serve what was asked: the server has no route for it. |
Code 3 is worth handling on its own. A 404 means either a feature this deployment does not carry or a thing
that does not exist, and only the server knows which: a request it has no route for is answered with the header
Jenesis-Installed: false, and jenrepo turns that into code 3. Retrying does not change it; jenrepo capabilities shows what the deployment does carry.
A setting the server reads only when it starts - a storage backend, turning a vulnerability feed on - is saved at
once but takes effect on the next restart. jenrepo settings set says so when the server does, and the settings
screens mark such a setting ↻ restart.
quarantine discard, which drops a held artifact,
and purge <module> --delete, which reclaims the stored data of a removed module. The deletions
that repos delete, projects delete and tenants delete make ask you to type
the name first, unless --yes is given. purge without --delete and
cleanup plan only report what would happen.