No project description provided
Project description
twat
Twardoch's Workstation Automation Toolkit. A minimal plugin host for Python: install small, focused packages and access them all through one namespace and one CLI.
twat does nothing on its own. Install plugins, and they snap into place.
How it works
Python has a built-in mechanism for packages to register themselves with a group name — called entry points. twat uses this to build a dynamic namespace: when you access twat.fs, it looks up the twat.plugins entry point named fs, loads the module, and hands it back. No configuration files, no registration steps beyond a normal pip install.
import twat
# twat-fs registered itself as a "twat.plugins" entry point named "fs"
files = twat.fs.list_directory(".")
# twat-cache registered as "cache"
@twat.cache.memoize()
def expensive():
...
The CLI works the same way. twat fs list /tmp finds the fs plugin, rewrites sys.argv so the plugin thinks it was called directly, and calls twat_fs.main().
Discovering commands
Every plugin ships a Fire-based CLI with two access shapes:
twat-image gray2alpha input.png output.png # subcommand form
twat-image-gray2alpha input.png output.png # dashed form (one script per leaf)
Each leaf (and each command group) is registered as a real console_scripts
entry point, so typing twat-<TAB> in your shell offers dozens of completions
once you have twat[all] installed:
twat-audio twat-image-gray2alpha twat-search-web
twat-audio-normalize twat-llm-ask twat-speech-transcribe
twat-fs-upload twat-os-clipboard twat-video-probe
... (~90 more)
To wire completion of the twat dispatcher itself:
twat --completions zsh > ~/.zfunc/_twat # zsh
twat --completions bash > ~/.local/share/bash-completion/completions/twat
twat --completions fish > ~/.config/fish/completions/twat.fish
Install
pip install twat
twat is an empty host. Install plugins separately:
pip install twat-fs twat-cache twat-os
CLI
twat --help # show host usage
twat --list # list installed plugin entry point names
twat <plugin_name> [args...]
# Examples:
twat --list
twat fs list /tmp
twat cache clear
twat --list reads only plugin entry point metadata, so it does not import
plugin modules just to show what is installed. Dispatch still works by loading
the selected plugin, rewriting sys.argv so the plugin sees twat.<plugin> as
its executable name, and calling the plugin module's callable main().
Writing a plugin
No SDK required. A plugin is a normal Python package that declares one entry point.
1. Package structure
twat-myplugin/
├── src/
│ └── twat_myplugin/
│ ├── __init__.py # Public API — everything here becomes twat.myplugin.*
│ └── __main__.py # CLI handler — must expose a main() function
├── pyproject.toml
└── README.md
2. Register the entry point (pyproject.toml)
[project.entry-points."twat.plugins"]
myplugin = "twat_myplugin"
This is the only twat-specific line in your entire package.
3. Expose your API (__init__.py)
from importlib import metadata
try:
__version__ = metadata.version(__name__)
except metadata.PackageNotFoundError:
__version__ = "0.0.0-dev"
def do_something():
return "result"
# The CLI dispatcher calls plugin.main() — expose it here
try:
from .__main__ import main
except ImportError:
def main() -> None:
pass
__all__ = ["do_something", "main", "__version__"]
4. Handle CLI calls (__main__.py)
import sys
def main() -> None:
# sys.argv[0] is already "twat.myplugin" when called via `twat myplugin`
print(f"Hello from myplugin! Args: {sys.argv[1:]}")
sys.exit(0)
if __name__ == "__main__":
main()
Naming conventions
| Thing | Convention | Example |
|---|---|---|
| pip package | twat-<name> with hyphens |
twat-myplugin |
| entry point name | lowercase, no prefix | myplugin |
| Python module | twat_<name> with underscores |
twat_myplugin |
The entry point name becomes the attribute on twat and the subcommand in the CLI.
How plugin loading works
twat.__getattr__ is called whenever you access an attribute that doesn't exist on the module. It scans the twat.plugins entry point group for a matching name, loads the module, registers it in sys.modules as twat.<name> (so subsequent accesses skip the lookup), and returns it.
If no plugin matches, a PluginError is raised with a clear message.
The CLI entry point (twat.main) intercepts sys.argv, pulls out the plugin name, rewrites sys.argv[0] to twat.<plugin> and sys.argv[1:] to the remaining arguments, then calls the plugin's main(). The plugin never knows it was dispatched through twat.
Development
git clone https://github.com/twardoch/twat
cd twat
uv venv && uv sync
pytest -xvs
License
MIT
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 twat-2.7.17.tar.gz.
File metadata
- Download URL: twat-2.7.17.tar.gz
- Upload date:
- Size: 22.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.2 {"installer":{"name":"uv","version":"0.11.2","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
779de431a74a81ebd66017c91b26f8043a543fb814f88f17f03d669bf39a14ef
|
|
| MD5 |
a80abd42f448770423d6c89c983e0f71
|
|
| BLAKE2b-256 |
1f73a3bf8f9c5fdb9b05a4c5d4ece3d7d34a658536ab17632862c062cd7e0919
|
File details
Details for the file twat-2.7.17-py3-none-any.whl.
File metadata
- Download URL: twat-2.7.17-py3-none-any.whl
- Upload date:
- Size: 19.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.2 {"installer":{"name":"uv","version":"0.11.2","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f2e364ddc0d2d1a15a9e7365ed7829c5cfb81bf691b8c9d77b143ac618f96e68
|
|
| MD5 |
72c44209504a5170e5bf94a812fa22d3
|
|
| BLAKE2b-256 |
bd9446d92842560261272bb9308404c4cd96c1aef15784f877d9f3cafb818741
|