Admin CLI (reference)
shepard-admin reference
shepard-admin is the operator command-line tool that wraps the
/v2/admin/... and operator-only /shepard/api/temp/migrations/...
endpoints. L1 Phase 1 ships three read-only commands (features /
health / migrations); N1c then grafts the first mutation
subcommand on — semantic refresh-ontologies — for refreshing
the bundled ontologies. RO-Crate import/export and the first-run
TUI wizard land in later phases (see aidocs/22).
Install
The CLI is shipped as a shaded uber-jar — one file, runs on any host with Java 21. Until the JBang catalogue + GHCR container image ship in a follow-up phase, the canonical install path is a local Maven build:
git clone https://github.com/noheton/shepard.git
cd shepard/cli
mvn package -DskipTests
# Produces cli/target/shepard-admin-<version>.jar
java -jar target/shepard-admin-*.jar --version
A future minor release will publish to a JBang catalogue and a GHCR container image; this page will update with the published commands when they land.
Configure
The CLI reads its target shepard URL and the API key it presents in
the X-API-KEY header from layered sources (highest priority first):
- CLI flags:
--url <base-url>and--api-key <key>. - Environment variables:
SHEPARD_ADMIN_URLandSHEPARD_ADMIN_API_KEY. - Config file:
~/.shepard/admin.toml— keysurl = "..."andapiKey = "...". - Built-in defaults — URL falls back to
http://localhost:8080; API key has no default (commands needing auth report a clear error).
The API key must carry the instance-admin role (per A0; see
aidocs/51). Mint it with the role explicitly set:
# Create an admin-role-bearing key via the existing API key endpoints
# (the minter must themselves be an instance-admin).
curl -X POST \
-H "X-API-KEY: $YOUR_ADMIN_KEY" \
-H "Content-Type: application/json" \
-d '{"name":"ops-cli","roles":["instance-admin"]}' \
https://shepard.example.com/shepard/api/users/$ME/apikeys
Export the key + URL once per shell:
export SHEPARD_ADMIN_URL=https://shepard.example.com
export SHEPARD_ADMIN_API_KEY=<key-from-above>
After that, every CLI invocation runs against your shepard instance with zero per-command flags.
Commands
shepard-admin features list
Lists the runtime feature toggles known to the backend. Calls
GET /v2/admin/features.
$ shepard-admin features list
NAME ENABLED DESCRIPTION
---------- ------- ---------------------------------------------
spatial true Enable spatial payloads.
versioning false Enable versioned writes.
hdf false Enable HDF5/HSDS payloads.
JSON output for scripting:
$ shepard-admin features list --output json
[
{"name":"spatial","enabled":true,"description":"Enable spatial payloads."},
{"name":"versioning","enabled":false,"description":"Enable versioned writes."}
]
shepard-admin health
Probes the backend’s Quarkus SmallRye-Health endpoints
(/shepard/api/healthz/ready and /shepard/api/healthz/live) and
prints the per-check breakdown. Exit code maps to health state —
0 when both readiness and liveness are UP, 1 when either reports
DOWN. Drop this into a shell pipeline as a kubelet-style probe.
$ shepard-admin health
READINESS — overall UP
CHECK STATUS DATA
------ ------ -----------
neo4j UP latency-ms=4
mongo UP
LIVENESS — overall UP
CHECK STATUS DATA
----- ------ ----
alive UP
overall: readiness=UP liveness=UP
$ echo $?
0
When something is wrong:
$ shepard-admin health
READINESS — overall DOWN
CHECK STATUS DATA
------ ------ --------------------------
neo4j DOWN error=connection refused
mongo UP
LIVENESS — overall UP
CHECK STATUS DATA
----- ------ ----
alive UP
overall: readiness=DOWN liveness=UP
$ echo $?
1
shepard-admin migrations status [containerId]
Lists migration progress. Without a positional argument, fetches
GET /shepard/api/temp/migrations/state and prints one row per
container. With an explicit containerId, fetches
GET /shepard/api/temp/migrations/{containerId} and prints a
single-row table.
$ shepard-admin migrations status
CONTAINER STATUS MIGRATED TOTAL FAILED BATCH STARTED UPDATED ETA(s)
--------- --------- -------- ----- ------ ----- --------------------- --------------------- ------
42 RUNNING 750 1000 0 7 2026-05-12T10:00:00Z 2026-05-12T10:05:30Z 90
99 COMPLETED 50 50 0 1 2026-05-11T09:00:00Z 2026-05-11T09:00:10Z -
For one container (handy under watch for a live progress bar):
$ shepard-admin migrations status 42
CONTAINER STATUS MIGRATED TOTAL FAILED BATCH STARTED UPDATED ETA(s)
--------- ------- -------- ----- ------ ----- --------------------- --------------------- ------
42 RUNNING 750 1000 0 7 2026-05-12T10:00:00Z 2026-05-12T10:05:30Z 90
Failed containers surface their errors field underneath the table:
$ shepard-admin migrations status
CONTAINER STATUS MIGRATED TOTAL FAILED BATCH STARTED UPDATED ETA(s)
--------- ------ -------- ----- ------ ----- --------------------- --------------------- ------
13 FAILED 100 500 7 2 2026-05-12T08:00:00Z 2026-05-12T08:01:00Z -
container 13 errors: schema column missing on batch 3
JSON output is symmetric — a list when no containerId is given, a
single object when one is.
shepard-admin semantic refresh-ontologies
Calls POST /v2/admin/semantic/refresh-ontologies on the backend.
Walks the bundled-ontology manifest (per N1b / ADR-0019), fetches
each bundle’s pinned canonicalUrl, recomputes its SHA-256, and
re-imports into the internal n10s repository when the hash
differs from the bundled stub.
The shipped ontology bundles are minimum-viable Turtle stubs
(per-bundle 1-2 KB, ~16 KB total) — enough for the casual
annotation flow on a fresh install. refresh-ontologies is the
operator’s escape hatch for landing the full canonical Turtle
(~13 MB) without waiting for the next shepard release.
$ shepard-admin semantic refresh-ontologies
Refresh complete — requested=9 refreshed=7 alreadyCurrent=2 errors=0
Scope to specific bundles (use the manifest’s id field —
prov-o, dublin-core, schema-org, foaf, qudt, om-2,
time, geosparql, obo-relations):
$ shepard-admin semantic refresh-ontologies --bundles=qudt,prov-o
Refresh complete — requested=2 refreshed=2 alreadyCurrent=0 errors=0
Force a re-import even when the canonical Turtle hash matches the bundled stub (useful after an n10s data reset):
$ shepard-admin semantic refresh-ontologies --force
Refresh complete — requested=9 refreshed=9 alreadyCurrent=0 errors=0
Per-bundle failures surface under the summary line (and set the exit code to 1 for shell-pipeline integration):
$ shepard-admin semantic refresh-ontologies
Refresh complete — requested=9 refreshed=7 alreadyCurrent=1 errors=1
BUNDLE REASON
------ -------------------------------------------------------------
qudt Could not fetch http://qudt.org/2.1/vocab/unit.ttl: timeout
$ echo $?
1
JSON output mirrors the wire shape:
$ shepard-admin semantic refresh-ontologies --output json
{
"requested" : 9,
"refreshed" : 7,
"alreadyCurrent" : 2,
"errors" : [ ]
}
The command is operator-only by design — there’s no frontend exposure. Refresh is a controlled-cadence action; canonical ontology hosts are often rate-limited and re-importing under routine clicks would be hostile.
shepard-admin semantic ontologies (N1c2)
The N1c2 sub-group surfaces the runtime bundle-management knobs
described under
semantic repositories — admin-configurable preseed.
Every subcommand is instance-admin-gated and supports the shared
--output={human,json} / --url / --api-key / --verbose
flags.
shepard-admin semantic ontologies list
Calls GET /v2/admin/semantic/ontologies and renders a table of
every pre-seeded + operator-uploaded bundle. Built-ins first
(manifest order), then user uploads (id ASC).
$ shepard-admin semantic ontologies list
ID SOURCE ENABLED REQ LICENSE IRI PREFIX
------------- ------- ------- ---- ---------------------- ---------------------------------
prov-o builtin true yes W3C Document License http://www.w3.org/ns/prov#
dublin-core builtin true - CC BY 4.0 http://purl.org/dc/terms/
qudt builtin false - CC BY 4.0 http://qudt.org/vocab/unit/
obo-relations builtin true yes CC0 1.0 http://purl.obolibrary.org/obo/RO_
acme-lab user true - CC BY 4.0 https://acme.example.org/vocab/
ENABLED reflects the effective state (required wins over any
disable list). REQ=yes rows can’t be runtime-disabled.
shepard-admin semantic ontologies enable <id>
Flips a previously-disabled bundle back on:
$ shepard-admin semantic ontologies enable qudt
Bundle 'qudt' is now enabled=true (source=builtin).
No-op if the id is not currently disabled. The bundle (re-)joins the seed loop on the next restart.
shepard-admin semantic ontologies disable <id>
Flips a bundle off:
$ shepard-admin semantic ontologies disable qudt
Bundle 'qudt' is now enabled=false (source=builtin).
Required bundles refuse the operation with a 409:
$ shepard-admin semantic ontologies disable prov-o
error: Unexpected HTTP 409 from http://localhost:8080/v2/admin/semantic/ontologies/prov-o/disable: …
$ echo $?
1
shepard-admin semantic ontologies upload
Multipart upload of a custom Turtle file:
$ shepard-admin semantic ontologies upload \
--file=lab-vocab.ttl \
--id=acme-lab \
--name="ACME Lab vocab" \
--iri-prefix=https://acme.example.org/vocab/ \
--canonical-url=https://acme.example.org/vocab/v1.ttl \
--license="CC BY 4.0"
Uploaded bundle 'acme-lab' (source=user, 12345 bytes, sha256=ab12cd…). It will join the seed loop on the next restart; or run `shepard-admin semantic refresh-ontologies --bundles=acme-lab` for an immediate import.
Required fields: --file, --id, --iri-prefix, --license.
Optional: --name (defaults to id), --canonical-url (omitted
for local-only bundles that never get refreshed).
Validation refusals:
| Stderr | Cause |
|---|---|
409 ... |
Id collides with a built-in or another user bundle (semantic.bundle.duplicate-id). |
400 ... |
Payload > 10 MB (semantic.bundle.too-large), does not look like Turtle (semantic.bundle.invalid-ttl), or is missing required metadata (semantic.bundle.bad-metadata). |
error: Could not read /path/to/file.ttl: … |
Local-file IO failure (no shepard call made). |
shepard-admin semantic ontologies remove <id>
Drops an operator-uploaded bundle (catalogue row + on-disk file):
$ shepard-admin semantic ontologies remove acme-lab
Bundle 'acme-lab' removed.
Built-in bundles refuse removal with a 409 semantic.bundle.builtin-not-removable
— they ship in the JAR and update via release upgrades.
Exit codes
| Code | Meaning |
|---|---|
| 0 | Success (including the “no migrations” and “nothing to list” cases). |
| 1 | Operator-readable error — connect failure, 401/403/404, malformed body, or health reported anything DOWN. The message on stderr is meant to be the only thing you need; --verbose adds the stack. |
| 2 | Unexpected runtime exception. |
Common errors
| Stderr message | Cause | Fix |
|---|---|---|
error: Cannot connect to shepard at <url> — is the backend reachable? |
TCP-level refusal — wrong host/port, or shepard down. | Verify $SHEPARD_ADMIN_URL and that the backend pod is Ready. |
error: 401 Unauthorized from <url> — set SHEPARD_ADMIN_API_KEY or pass --api-key. |
API key missing or wrong. | Set the env var or pass --api-key. |
error: 403 Forbidden from <url> — the API key lacks the instance-admin role. |
The key is valid but role-less. | Rotate to a key whose JWT carries roles: ["instance-admin"]. The minting user must themselves hold the role (no privilege escalation). |
error: 404 Not Found from <url> — endpoint missing (backend may be older than this CLI expects). |
CLI expects /v2/admin/features (or another endpoint) that doesn’t exist on the running backend version. |
Upgrade the backend to a build that includes A3b / A0 / P3c, or pin the CLI to a matching version. |
What’s next
Phase 2 will add shepard-admin cleanup deleted-entities --dry-run
(aidocs/22 §4.1). Phase 3 adds RO-Crate import/export
(aidocs/22 §4.7). Phase 4 lands the init TUI wizard, JBang
distribution, and the universal-TUI patterns from aidocs/22 §4.x.
semantic refresh-ontologies (N1c) is the first mutation
subcommand on top of L1 Phase 1; later N1-series work (LUMEN seed
integration, frontend annotation picker) lives on the data /
frontend side rather than the CLI.