Command-line tool to interact with eXist-db via REST
Project description
exsh — eXist-db Shell
A command-line tool to interact with an eXist-db server via its REST API. Designed for shell scripting and pipe-friendly workflows.
Requirements
- Python 3.11+
- uv
Installation
Install system-wide with uv tool:
uv tool install exist-shell
Or with pipx:
pipx install exist-shell
This places exsh on your PATH. Verify with:
exsh --version
To uninstall:
uv tool uninstall exist-shell
Run without installing
uvx --from exist-shell exsh --version
uvx fetches the package into a temporary, cached environment and runs it — handy for one-off use or trying out a new release.
Install from git
To track an unreleased commit instead of a PyPI release:
uv tool install git+https://github.com/ambs/exist-shell
Configuration
Add a server
exsh server add localhost --port 8080 --user admin
A nickname is derived from the hostname by default (e.g. localhost). Override with --nick.
Add an existing collection
exsh collection add mydata@localhost
This registers the /db/mydata collection on the localhost server under the nick mydata.
Create and register a new collection
exsh collection new mydata@localhost
Creates /db/mydata on the server and registers it in one step. If the collection already exists, it prints a message and exits without modifying the config. Use --nick to register it under a different name:
exsh collection new mydata@localhost --nick md
List configured servers and collections:
exsh server ls
exsh collection ls
Configuration is stored at ~/.config/exsh/config.toml.
Commands
| Command | Description |
|---|---|
exsh ls <nick>[:<path>] |
List subcollections and documents at a path |
exsh cat <nick>:<path> |
Print a document to stdout |
exsh put <file> <nick>:<path> |
Upload a local file to a collection |
exsh cp <src> <dst> |
Copy a document (local ↔ remote or remote ↔ remote) |
exsh edit <nick>:<path> |
Open a document in $EDITOR, re-upload if changed |
exsh rm <nick>:<path>... |
Delete one or more documents |
exsh mkdir <nick>:<path> |
Create a collection |
exsh sync <local> <nick>[:<path>] |
Push a local folder to a remote collection |
exsh sync <nick>[:<path>] <local> |
Pull a remote collection to a local folder |
exsh exec <nick>[:<path>] |
Execute an XQuery script on a server |
exsh server add <host> |
Register a server |
exsh server ls |
List registered servers |
exsh server rm <nick> |
Remove a server (and its collections) |
exsh server rename <old> <new> |
Rename a server nick (updates collection references) |
exsh collection add <name>[@<server>] |
Register an existing collection |
exsh collection new <name>[@<server>] |
Create a collection on the server and register it |
exsh collection ls |
List registered collections |
exsh collection rm <nick> |
Remove a collection from the config |
exsh user ls [@server] |
List user accounts and their groups |
exsh user add <user[@server]> |
Create a user account (prompts for password) |
exsh user rm <user[@server]> |
Remove a user account |
exsh user info <user[@server]> |
Show user account details |
exsh group ls [@server] |
List groups and their members |
exsh group add <group[@server]> |
Create a group |
exsh group rm <group[@server]> |
Remove a group |
exsh chown <spec> <nick>:<path> |
Change owner and/or group of a document or collection |
exsh chmod <mode> <nick>:<path> |
Change POSIX permissions of a document or collection |
Examples
# List the root of a collection
exsh ls mydata
# List a subdirectory
exsh ls mydata:reports/2025
# Print a document
exsh cat mydata:reports/2025/summary.xml
# Upload a file
exsh put report.xml mydata:reports/2025/report.xml
# Copy from remote to local
exsh cp mydata:reports/2025/report.xml ./report.xml
# Edit in place
exsh edit mydata:reports/2025/report.xml
# Delete a document
exsh rm mydata:reports/2025/old.xml
# Delete multiple documents
exsh rm mydata:reports/2025/a.xml mydata:reports/2025/b.xml
# Create a subcollection
exsh mkdir mydata:reports/2026
# Execute an XQuery script from a file
exsh exec mydata:/ -f query.xq
# Execute an XQuery script from stdin
echo 'count(collection("/db/mydata"))' | exsh exec mydata:/
# Execute without local preprocessing
exsh exec mydata:/ --no-fix -f query.xq
# List locally available XQuery validators
exsh exec --list-validators
# Push a local folder to the server (only transfers changed files)
exsh sync ./reports mydata:reports
# Pull a remote collection to a local folder
exsh sync mydata:reports ./reports
# Preview what would be transferred without doing it
exsh sync --dry-run ./reports mydata:reports
# Push and remove files on the server that no longer exist locally
exsh sync --delete ./reports mydata:reports
# Change owner and group of a document
exsh chown alice:editors mydata:reports/annual.xml
# Recursively reassign a collection tree
exsh chown -R alice mydata:reports
# Set permissions with octal mode
exsh chmod 0644 mydata:reports/annual.xml
# Add execute permission for the owner
exsh chmod u+x mydata:scripts/run.xq
# Recursively set permissions for a collection
exsh chmod -R 0644 mydata:data
Sync
exsh sync transfers only files that have changed, using a local manifest stored at ~/.cache/exsh/sync/. Direction is inferred from the argument order: local-first means push, remote-first means pull.
Change detection:
- Push: SHA-256 hash of the local file is compared against the manifest. Same-size edits are caught.
- Pull:
last_modifiedtimestamp from the eXist listing is compared against the manifest.
Conflicts (both sides changed since last sync) are reported and skipped — use --force to override.
Options:
| Flag | Effect |
|---|---|
--force / -f |
Transfer all files, bypassing change detection |
--dry-run / -n |
Show what would happen without transferring |
--delete |
Remove files and empty folders on the destination that no longer exist on the source |
--verbose / -v |
Also print unchanged (skipped) files |
--checkpoint-every N |
Flush the manifest every N files (default: 100); allows interrupted syncs to resume near the point of failure |
Shell completion
Generate and install tab-completion for your shell:
# bash
exsh --install-completion bash
# zsh
exsh --install-completion zsh
# fish
exsh --install-completion fish
Development
git clone https://github.com/ambs/exist-shell
cd exist-shell
uv sync
exsh --help
Run checks:
make checks # lint, type-check, and tests
make test # tests only
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file exist_shell-0.1.2.tar.gz.
File metadata
- Download URL: exist_shell-0.1.2.tar.gz
- Upload date:
- Size: 138.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
cb79f0aa05705930cb526cc9568766ee040feec1bf892b342533476b28b549ca
|
|
| MD5 |
e25629ba8ec90952f88c44270f3ce63f
|
|
| BLAKE2b-256 |
69887af0ef258e74798e182f9dbd4089e3d3252cac0b38fa2c24639745c0a488
|
Provenance
The following attestation bundles were made for exist_shell-0.1.2.tar.gz:
Publisher:
release.yml on ambs/exist-shell
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
exist_shell-0.1.2.tar.gz -
Subject digest:
cb79f0aa05705930cb526cc9568766ee040feec1bf892b342533476b28b549ca - Sigstore transparency entry: 1927572912
- Sigstore integration time:
-
Permalink:
ambs/exist-shell@7314026e3fc7ba432e581f04782842b64622b378 -
Branch / Tag:
refs/tags/v0.1.2 - Owner: https://github.com/ambs
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@7314026e3fc7ba432e581f04782842b64622b378 -
Trigger Event:
push
-
Statement type:
File details
Details for the file exist_shell-0.1.2-py3-none-any.whl.
File metadata
- Download URL: exist_shell-0.1.2-py3-none-any.whl
- Upload date:
- Size: 57.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
cb649a4caf798aaf9419733084c38b4d16e33915d5d1887bca795b8bf1dc9196
|
|
| MD5 |
028fd80d7be66f9a7d6c36734868b66f
|
|
| BLAKE2b-256 |
ea110d1f4bbed6806f4adcaa381666740e23ff813b5ca747cd66a7ef89d1cd33
|
Provenance
The following attestation bundles were made for exist_shell-0.1.2-py3-none-any.whl:
Publisher:
release.yml on ambs/exist-shell
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
exist_shell-0.1.2-py3-none-any.whl -
Subject digest:
cb649a4caf798aaf9419733084c38b4d16e33915d5d1887bca795b8bf1dc9196 - Sigstore transparency entry: 1927573016
- Sigstore integration time:
-
Permalink:
ambs/exist-shell@7314026e3fc7ba432e581f04782842b64622b378 -
Branch / Tag:
refs/tags/v0.1.2 - Owner: https://github.com/ambs
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@7314026e3fc7ba432e581f04782842b64622b378 -
Trigger Event:
push
-
Statement type: