keywharf
keywharf is a Python 3.11+ CLI for selecting SSH hosts from a host repo into a local desired state, then materializing only manager-owned SSH artifacts.
It manages only:
- one explicit local state file
- one managed SSH config fragment
- one managed key directory
It does not take over the user's whole ~/.ssh/config. Only install-include may minimally append one Include block to the main SSH config.
Recommended Workflow
Create one named workspace:
keywharf init demo --directory ~
If you already have a host repo remote URL:
# edit ~/demo/config.json and set host_repo_remote_url
keywharf --workspace ~/demo repo sync
keywharf --workspace ~/demo repo host list
keywharf --workspace ~/demo repo host show demo
repo sync uses GitPython as an object interface to the required system Git executable. The configured origin URL
must exactly match host_repo_remote_url; synchronization clones or pulls fast-forward-only and never commits, pushes,
stashes, resets, cleans, or rewrites history. System credential helpers, the SSH agent, SSH configuration, and
known_hosts remain authoritative.
High-frequency read-only convenience views are also available:
keywharf --workspace ~/demo list repo
keywharf --workspace ~/demo show repo demo
keywharf --workspace ~/demo list local
keywharf --workspace ~/demo show local demo
If you are starting from scratch:
keywharf --workspace ~/demo repo init
keywharf --workspace ~/demo repo host add demo --comment "demo host"
keywharf --workspace ~/demo repo host endpoint add demo public --hostname demo.example.com --comment "public endpoint"
keywharf --workspace ~/demo repo host auth add demo home --user fox --identity-file keys/id_demo --comment "home key"
keywharf init creates ~/demo/repo as the workspace's one host repo directory, but it starts empty. repo init writes the host repo skeleton there. repo host add creates a host shell only; endpoint and authentication options are added separately by stable name. If you want ~/demo/repo to become a real git repository, run git init, git remote add, and git push there yourself.
Then continue with normal selection and apply flow:
keywharf --workspace ~/demo select demo
# or select explicitly by stable name:
# keywharf --workspace ~/demo select demo --endpoint public --auth home
keywharf --workspace ~/demo validate
keywharf --workspace ~/demo render
keywharf --workspace ~/demo apply
keywharf --workspace ~/demo install-include
select still accepts explicit --endpoint and --auth stable names. If one side has a single candidate, it is selected automatically. If one side has multiple candidates, select prompts in an interactive terminal and fails fast in noninteractive environments until you pass the stable name explicitly. Local state keeps name-based selectors; singleton selections may leave endpoint_name or authentication_name as null.
If the manager config lives outside the default workspace root, use --config <path> instead of --workspace.
Canonical Identity and SSH Names
One ServerName identifies a connection definition. Optional shared Aliases declare additional
literal SSH names; each client explicitly selects a non-empty subset with repeated --name:
keywharf repo host add demo-node-01 --alias dev --alias work
# Add the endpoint and authentication separately.
keywharf select demo-node-01 --endpoint direct --auth developer --name demo-node-01 --name dev
keywharf select demo-node-01 --endpoint direct --auth developer --name dev
All enabled names share one connection block and canonical key copy. select <query> finds a host
by server name, alias, or endpoint hostname, including partial matches. Ambiguity requires a choice;
see the matching rules. Other management
commands use ServerName. Discovery does not enable names or choose an endpoint. Without --name,
first selection defaults to canonical-only and reselection preserves names. A terminal offers a
numbered multi-selection with that default when aliases exist.
Adding an alias never enables it automatically. Removing an enabled alias requires explicit
replacement; renaming a canonical identifier requires new client selections even if the old name
remains an alias.
State is now v2. V1 loads in memory with canonical-only names; only successful select/deselect
writes upgrade retained entries. apply does not rewrite state. Upgrade every client/editor before
introducing shared aliases or private renames. Downgrade needs coordinated original-file restoration,
not a version-number edit. See configuration
and CLI details.
Ownership Boundary
keywharf manages:
state_pathmanaged_config_pathmanaged_keys_dir
keywharf does not manage:
- unrelated
Hostentries in the main SSH config Matchblocks- other
Includelines - user comments and ordering in the main SSH config
Workspace Discovery
Workspace resolution is explicit and predictable:
--workspaceKEYWHARF_WORKSPACE- auto-search
pwd, each ancestor, then~ - for each base directory: scan one level of child directories first, then the base directory itself
- the first directory containing
KEYWHARF_WORKSPACEwins - fail fast with the checked candidate paths listed
keywharf init <workspace_name> creates the marker, config.json, state/state.json, an empty repo/ directory, and small workspace text files from package resources. It does not touch ~/.ssh and does not initialize a git repo for you.
Formal Config And Templates
Manager config is a formal runtime config:
- defaults come from
pkg://keywharf/config_defaults/manager.json - file or mapping input is override only
- defaults and overrides are deep-merged before Pydantic v2 validation
- runtime path resolution is separate from raw config loading
Resource roles are intentionally split:
config_defaults/*.json: formal defaults for manager configtemplates/*.json: structured starter data such as the empty state filetemplates/*.j2: human-facing text templates such as workspaceREADME.md, workspace.gitignore, and the include block text
Host Repo CRUD
repo init bootstraps a local-first host repo skeleton:
- empty
config.json keys/.gitignore
It writes those files into the workspace's one host repo directory, %{WORKSPACE}/repo. It does not run git init or create .git.
repo host edits only the host repo config.json:
repo host listrepo host showrepo host addrepo host updaterepo host removerepo host endpoint listrepo host endpoint showrepo host endpoint addrepo host endpoint updaterepo host endpoint removerepo host auth listrepo host auth showrepo host auth addrepo host auth updaterepo host auth remove
These commands do not commit, push, run git init, or mutate git metadata. They perform structured JSON reads/writes, preserve array order, and revalidate the resulting host set before writing.
Design rules:
repo host addcreates a host shell and may set its comment and aliasesrepo host endpoint ...manages named endpoint options withHostName, optionalPort, and optional commentrepo host auth ...manages named authentication options with optionalUser, optionalIdentityFile, and optional commentselectwrites name-based endpoint/authentication selectors into local state; singleton selections may leave those fieldsnullvalidatescans the whole host repo and reports every host shell that is missing endpoint options, authentication options, or bothrenderandapplyonly require the hosts selected in local state to be complete
ExtraConfig is preserved and rendered, but it is not exposed as a CLI editor yet.
Read-only convenience facade (canonical paths stay primary):
list repo->repo host listshow repo <server>->repo host show <server>list local->local listshow local <server>->local show <server>
Scope boundary:
- convenience targets are only
repo(host definitions) andlocal - convenience commands are read-only only
- there is no convenience facade for
repo host endpoint ...,repo host auth ..., or any write operations
--sudo
Mutating commands support --sudo:
initrepo initrepo syncselectdeselectapplyinstall-includerepo host addrepo host updaterepo host removerepo host endpoint addrepo host endpoint updaterepo host endpoint removerepo host auth addrepo host auth updaterepo host auth remove
Privilege handling is centralized:
- normal writable paths run without sudo
- unwritable paths fail fast with concrete path-based reasons
--sudore-execs the full command throughsudo
Installation
python3.11 -m venv .venv
. .venv/bin/activate
python -m pip install -e '.[dev]'
pytest
Runtime requirements:
- Python 3.11+
- system
git - GitPython 3.1.59 or newer within the supported 3.x series (installed with Keywharf)
Documentation
Metadata
Release files for keywharf 1.1.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| keywharf-1.1.1.tar.gz | 83.8 kB | With provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| keywharf-1.1.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 167.5 kB
Release files / keywharf-1.1.1.tar.gz
| Download URL | keywharf-1.1.1.tar.gz |
|---|---|
| Size | 83.8 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
594466df0635ba67578a6af553393db0cde192b54791af970f95df0edb8aa41c
|
|
BLAKE2b-256 checksum How to use checksums |
ba9fca765b1ba7319adf7c9e494c1f51308b8ef6c3d2db8700c5b1e1b255fd85
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 25, 2026.
Transparency logRelease files / keywharf-1.1.1-py3-none-any.whl
| Download URL | keywharf-1.1.1-py3-none-any.whl |
|---|---|
| Size | 83.7 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
2687bd7b17a383d28b412f319abde27248182dd12f561da7d3267bcbb2107a05
|
|
BLAKE2b-256 checksum How to use checksums |
3ef55b14568145f021ebcd0b6169385494b4a6a42d82aaa418036b32b0917d2e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 25, 2026.
Transparency logSecurity for keywharf 1.1.1
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for keywharf-1.1.1.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 25, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.