Skip to main content

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:

  1. 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.
  2. 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 doctor lists 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. Run ch list --json to see what is available, or ch find <what you want to do> --json to search by intent. Run one with ch <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)

Source distribution for clihub-cli 1.0.2
File Size Uploaded
clihub_cli-1.0.2.tar.gz 118.9 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for clihub-cli 1.0.2
File Interpreter ABI Platform
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 log

Release 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

Release history Release notifications | RSS feed

1.1.0

2 release files

1.0.4

2 release files

1.0.3

2 release files

This release

1.0.2 This release

2 release files

1.0.1

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page