simonmysun/ell: a Bash CLI for LLMs that pipes your terminal into the prompt
A command-line interface for LLMs written in Bash.
At a glance
- What is it?
- ell is a shell script wrapper around OpenAI and Gemini style APIs, with record mode, templates and plugins. It is small, dependency-light and honest about where it breaks on Windows.
- Who is it for?
- Adopt ell if you already live in Bash and want prompts, piped files and recorded terminal sessions to reach an LLM without leaving the shell. Do not adopt it if you need a supported Python or Node SDK surface, streaming UI, or a hardened secret-handling path on Git Bash, where the README states the config permission check and the chmod 600 on the temporary auth-header file cannot be enforced.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 51 days ago.
- What is it written in?
- Mainly Shell, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 15, 2026, and from our analysis. They are not legal advice.
DEEP OPEN-SOURCE ANALYSIS
The problem ell solves, and who it is actually for
Most LLM clients assume you are willing to leave the shell. They ship as Python packages, Node CLIs or desktop apps, and they want you to paste context into them. ell takes the opposite position: the context is already in your terminal, and the tool should come to it.
The README lists the intended capabilities plainly: ask LLMs from your terminal, pipe into them, bring your terminal context along, chat interactively, and support function calling through templates. That list describes a specific user. Someone debugging a stack trace, reading a config file, or working through a capture the flag challenge, who wants to type a question and have the preceding terminal output included automatically.
The project is written in Bash and depends on bash-4.1 or later, coreutils or OS X utilities, awk, sed and curl. That dependency set is the whole pitch. If curl exists on the machine, ell can run. No virtualenv, no node_modules, no compiled binary. The README also notes util-linux is only needed for record mode, because it relies on the script command to capture terminal input and output. Everything else works without it.
This is not a general-purpose LLM framework. It has no SDK, no library API, no server component. It is a launcher script plus helpers, templates, plugins and backend definitions, and the repository layout reflects that: ell and ell.sh at the top level, with helpers/, llm_backends/, plugins/ and templates/ as directories alongside them.
Request pipeline, backends and the four hook stages
The architecture documentation describes the startup sequence, configuration precedence, the request pipeline and its four hook stages, backends, and record mode. The repository makes the backend concept concrete: llm_backends/ holds the adapters, and a backend is selected with --api-style or the ELL_API_STYLE variable. OpenAI and Gemini are supported out of the box, and the README states you can add your own.
What separates a backend from a template matters here, because the naming is easy to confuse. A backend adapts ell to an API style. A template shapes the request itself, and the README is explicit that provider plugin support in ell is implemented through templates, not through the plugin directory. The Plugins documentation defines a plugin as a script ell can call, used to extend ell's functionality, and states that provider-side plugin support is not covered by that term.
The record mode data flow is the part worth understanding before you rely on it. Running ell -r starts capturing terminal input and output, and subsequent invocations use that capture as context. Interactive mode turns record mode on automatically, which is why ell -i lets you chat with context without a separate flag. The README's own example shows the shape: run ell -r, do random things, then ask what an error code means and how to fix it.
The four hook stages named in the architecture documentation are not enumerated in the README, so anyone planning to hook into the pipeline should read docs/Architecture.md rather than infer the stages from the top-level file.
Installing ell and asking your first question
Installation is a clone into an XDG data directory plus a PATH entry. The README gives this exact pair of commands, and the second appends the export to your ~/.bashrc:
git clone --depth 1 https://github.com/simonmysun/ell.git \
"${XDG_DATA_HOME:-$HOME/.local/share}/ell"
echo 'export PATH="${XDG_DATA_HOME:-$HOME/.local/share}/ell:$PATH"' >> ~/.bashrcIf ~/.local/bin is already on your PATH, the README offers a symlink instead. The instruction to link the ell launcher rather than ell.sh is deliberate: the launcher resolves the symlink back to the clone so bundled helpers, templates and plugins are found.
mkdir -p ~/.local/bin
ln -s "${XDG_DATA_HOME:-$HOME/.local/share}/ell/ell" ~/.local/bin/ellConfiguration lives in ${XDG_CONFIG_HOME:-$HOME/.config}/ell/config, and the legacy ~/.ellrc is still read. The README's Gemini example sets five variables:
ELL_API_STYLE=gemini
ELL_LLM_MODEL=gemini-1.5-flash
ELL_TEMPLATE=default-gemini
ELL_API_KEY=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
ELL_API_URL=https://generativelanguage.googleapis.com/v1beta/models/The OpenAI equivalent swaps the style, model, template and URL. Once configured, the first real use is a single quoted argument, and the README's example is:
ell "What is the capital of France?"Piping works through -f with a dash, so cat somecode.py | ell -f - sends the file as input. You can also append a prompt inline by grouping commands, which is the pattern the README shows for asking about code without editing a template.
Where ell breaks: Git Bash permissions, record mode and stale installs
The Windows section of the README is unusually candid, and it is the clearest limitation in the project. Git Bash over NTFS cannot create genuinely group or world-writable files, which means the config permission check in load_config, described as refusing to source a world-writable .ellrc, cannot be enforced. The chmod 600 applied to the temporary auth-header file cannot be enforced either. The README labels these security limitations on a multi-user machine and points to MSYS2 or WSL as safer alternatives.
Git Bash also lacks script(1), so record mode is unavailable there by default, and real symlinks are absent unless the environment is configured otherwise. WSL behaves like Linux and the README says everything works. MSYS2 and Cygwin provide a fairly complete POSIX layer, including ACL-backed permissions, real symlinks and script(1).
A second failure mode is version drift in the install layout. The README notes that the previous layout, cloning into ~/.ellrc.d with configuration in ~/.ellrc, still works, and that no migration is required. That is convenient, but it means two configuration locations are live at once. If a machine has both an old ~/.ellrc and a new config file, the precedence rules in the architecture documentation are what determine which one wins. The README does not spell that out.
Record mode is also the feature most likely to surprise you operationally. It captures terminal input and output, which is exactly what makes it useful and exactly why it deserves a look at docs/Risk_Consideration.md before you run it against a session containing credentials. The README lists sensitive information redaction as a feature and links to issue #14, which suggests the mechanism is tracked rather than fully settled.
ell against a Python or Node LLM SDK
The obvious alternative is not another shell script. It is a language SDK, such as the official OpenAI or Google client libraries, or a CLI built on top of one. The difference in approach is structural rather than cosmetic.
An SDK gives you a typed request and response object, retry policy, streaming callbacks and a documented error hierarchy. You can unit test against a mock transport and pin a version in a lockfile. ell gives you none of that. It gives you a script that assembles a request, hands it to curl, and prints what comes back. Its extension points are templates and plugins, which are shell scripts, and its configuration is a sourced file of environment variables.
The trade runs the other way too. An SDK cannot read the last two hundred lines of your terminal. It cannot be invoked from a shell function in the middle of a pipeline without a subprocess and an argument-passing layer. With ell, cat somecode.py | ell -f - is the entire integration. There is no runtime to install on a jump host, no interpreter version to reconcile.
If your work is building an application, use an SDK. If your work is operating systems and you want a question answered from the machine you are already logged into, the shell script is the shorter path, and the cost is that you own the request format yourself through templates.
Maintenance, licence and what upgrading costs
The repository is not archived, and the last push was on 2026-07-26. That same date carries the v0.2.0 release, which followed v0.1.1 and v0.1.0, both from 2024-08-02. The gap between those dates is the honest picture: a burst of initial releases, a long quiet period, then a version bump roughly two years later. Anyone adopting ell should read the CHANGELOG.md for what v0.2.0 changed rather than assume continuous churn.
Upgrade cost is low by design. The install is a git clone, so updating means pulling the clone rather than reinstalling a package. The README explicitly states that the older layout still works and no migration is required, which means the upgrade path from v0.1.x to v0.2.0 does not force a config rewrite. The risk sits in the opposite direction: if you cloned with --depth 1 as the README instructs, you have a shallow clone, and pulling new history from a shallow clone can require extra git arguments. The README does not discuss updating.
Licensing is MIT, per the LICENSE file at the repository root. That is permissive: it allows use, modification and redistribution with the licence and copyright notice retained. It does not grant trademark rights, and it provides no warranty. This is a description of the licence text, not legal advice, and anyone embedding ell in a distributed product should read LICENSE directly.
Editorial conclusion
Adopt ell if you already live in Bash and want prompts, piped files and recorded terminal sessions to reach an LLM without leaving the shell. Do not adopt it if you need a supported Python or Node SDK surface, streaming UI, or a hardened secret-handling path on Git Bash, where the README states the config permission check and the chmod 600 on the temporary auth-header file cannot be enforced. Before committing, verify three things: that your Bash is 4.1 or later, that your chosen backend template name (default-openai or default-gemini) actually exists under templates/, and that your API key file sits at ${XDG_CONFIG_HOME:-$HOME/.config}/ell/config with the permissions your platform can enforce.
Frequently asked questions
Which LLM providers does ell support?
The README states that OpenAI and Gemini are supported out of the box, selected through the backend setting --api-style or ELL_API_STYLE. The Backends documentation notes you can add your own backend to adapt ell to another API style.
Where does ell keep its configuration file?
The README gives ${XDG_CONFIG_HOME:-$HOME/.config}/ell/config as the config path, and notes that the legacy ~/.ellrc is still read. The example configs set ELL_API_STYLE, ELL_LLM_MODEL, ELL_TEMPLATE, ELL_API_KEY and ELL_API_URL.
Why does record mode not work in Git Bash?
Record mode depends on the script command from util-linux, and the README states that Git Bash lacks script(1). The README recommends WSL or MSYS2 on Windows for the most complete experience.
Community notes