clihub
One entry point for the tools you already have.
$ ch find reshape json
jq filter and reshape JSON text
$ echo '{"name":"clihub"}' | ch jq -r .name
clihub
ch does not wrap your tools or reimplement them. It keeps a registry of names you
chose and what each one runs, so a tool you set up months ago stays reachable by
description instead of by memory.
Install
pip install -e . # creates `ch`, inside that virtualenv only
ch init # symlinks it into ~/.local/bin, and writes ~/.clihub/config.toml
Both steps matter. pip install gives you the command but leaves it in the
virtualenv's bin/, so ch only resolves while that virtualenv is active.
ch init is what makes it work from anywhere; it also writes config.toml with
every setting present and commented out, which is where the tunables are
documented. It tells you if ~/.local/bin is not on your PATH.
Add --completions to install shell completion at the same time, or run
ch tools completion later.
Python 3.12+, no dependencies. It also runs straight from a checkout:
PYTHONPATH=src python3 -m clihub list
Commands
ch <name> [args...] # run a registered command
ch <group>.<name> [args...] # one inside a group
ch list [<group>] # everything registered
ch find <what you want> # by description, not by name
ch help <name> # that tool's own --help
ch doctor # is any of this going to work
ch --version
Editing the registry:
ch registry add jq --describe "filter and reshape JSON text" -- /usr/bin/jq
ch rg add llm.llama -- ~/bin/llmctl llama # a tool inside a group
ch rg edit jq --describe "..." # change one field
ch rg remove jq
ch rg {show,validate,export,import}
Everything after -- is the command and its fixed arguments. Anything you type at
run time is appended to that, untouched.
The registry
~/.clihub/registry.toml. A table is a group; a table inside it is a command in
that group. Hand-editing is expected — comments you write survive clihub rewriting
the file.
[jq]
description = "filter and reshape JSON text"
command = "/usr/bin/jq"
[llm]
description = "local model servers"
[llm.llama]
description = "start, stop and inspect the local model servers"
command = "/Users/me/bin/llmctl llama"
How a target is stored
Three ways to name one, each meaning something different:
| you write | stored as | resolved |
|---|---|---|
jq |
jq, as written |
at run time, by PATH — picks up a project's venv |
/usr/bin/jq |
as written | that exact file |
./bin/jq |
absolutised when added | — you will not be in that directory later |
A relative path is the only one rewritten, and add says so when it does.
add refuses only what can never come right — a command the shell cannot parse.
A target that is simply not here is reported and still recorded: a bare name
resolves at dispatch, and a file can be installed after the fact. ch doctor
counts that as broken and exits non-zero, so it is not lost.
Fixed arguments containing spaces are quoted for you.
Where your arguments go
command is a shell command line, run as sh -ec, so pipes, &&, redirects and
VAR=x cmd mean what they say. Your arguments bind to "$@" and are never
re-parsed — a pipe in the registry is syntax, a pipe in an argument is data.
Multi-line commands work, and are written by hand rather than through add
(argv cannot carry a script). Two rules:
- Contains
$@anywhere → used exactly as written. This is how you put arguments in the middle, or into a loop, or into one stage of a pipeline. - Otherwise
"$@"is appended — but only to a single simple command. For anything else (a pipeline,&&, a loop, several lines, a trailing comment, a here-doc) the end of the text is not an argument position, so nothing is appended. Such a command runs fine with no arguments; give it some and it stops with exit 126 rather than dropping them.ch doctorlists it as takes no arguments beforehand.
[stack.up]
description = "bring the local model stack up"
command = """
export LLM_PROFILE=dev
ch llm.vm start
ch llm.llama serve --port 8080 "$@"
"""
A trailing \ inside a """ block is TOML's own line continuation: it joins the
lines before clihub sees them, so splitting one long command across lines for
readability is still a single-line command and none of the above applies.
Configuration
~/.clihub/config.toml, written by ch init with every key present, commented
out, with its default and what it does. Uncomment what you want. Anything not in
that file is structural, not tunable.
A config that will not parse is ignored rather than fatal — dispatch carries on
with the shipped defaults, and ch doctor tells you the whole file is inert.
CLIHUB_HOME moves the directory; it is the only path variable clihub reads.
For agents
An agent has been trained on git and docker. It has not been trained on the
tools you wrote, and cannot guess they exist. That is what the registry is for, and
the integration is one paragraph — put this in your AGENTS.md or CLAUDE.md:
Local tools are registered with
ch. Runch list --jsonto see what is available, orch find <what you want to do> --jsonto search by intent. Run one withch <name> [args...]; arguments are passed through untouched. Check this before concluding a capability is missing.
ch list, ch find and ch tools stats take --json; ch rg show <name> emits
TOML. There is no MCP server and no per-agent install step: an agent already has a
shell, and ch is a command.
When something is wrong
ch doctor checks the registry (parsing, commands, syntax, quoting, targets,
permissions, shadowing, duplicates, portability, names) and clihub's own setup
(ch on PATH, config readable, completion current).
It never runs your tools. The one command it spawns is sh -n, which parses a
stored command and exits without executing it — that is the syntax check, and
it runs on the line clihub would actually run, "$@" included.
It exits non-zero only when something is broken — an entry that cannot run, a registry that will not parse, a config discarded whole. Advisories exit 0.
| exit | meaning |
|---|---|
| child's own | dispatch propagated it verbatim |
| 1 | a builtin failed, or (under bash as sh) a path target the shell could not run |
| 2 | usage error, or a syntax error in a stored command |
| 126 | the entry cannot take your arguments — empty, not text, or it does not say where they go |
| 127 | unknown name, or a bare target the shell could not find |
| 130 | interrupted |
Codes above 0 come from the shell once dispatch starts, and the two common sh
implementations differ for a target written as a path. Under bash as sh (macOS)
-e collapses both "missing" and "not executable" to 1; under dash (most Linux)
they stay 127 and 126. Bare names give 127 either way. clihub passes through
whatever it gets, and ch doctor names both conditions before you ever run them.
Development
python3 -m unittest discover -s tests -t . # no dependencies needed
python3 -m pytest tests/ # if you have it
AGENTS.md is how to work in this repo. docs/design.md is the specification,
docs/findings.md what testing showed about descriptions and discovery,
docs/backlog.md the open questions.
License
MIT.
Release files for clihub-cli 1.0.2
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| clihub_cli-1.0.2.tar.gz | 118.9 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| clihub_cli-1.0.2-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 223.0 kB
Release files / clihub_cli-1.0.2.tar.gz
| Download URL | clihub_cli-1.0.2.tar.gz |
|---|---|
| Size | 118.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
8d68b02e869990e62c412635b7c67f192e94202381ec16e6537dba4a0cc87b66
|
|
BLAKE2b-256 checksum How to use checksums |
a97a0e0f8bdd6641f2fba66b2a27f5bfd3fc876f9f673f2f6edd22c715cd5251
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
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 Sep 16, 2026.
Transparency logRelease files / clihub_cli-1.0.2-py3-none-any.whl
| Download URL | clihub_cli-1.0.2-py3-none-any.whl |
|---|---|
| Size | 104.1 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
66b77e02059d7f60544e03522ea99f4ab133bdc824e740c7b6583191c8bcfb54
|
|
BLAKE2b-256 checksum How to use checksums |
542a1c089ae43587cf9cb24a9d370fe22ef645a9e44c1a52c324c62a2ee65c91
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
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 Sep 16, 2026.
Transparency log