Skip to content
havl-codePublic

About

lx turns plain-English task descriptions into ready-to-use Linux commands, powered entirely by a local LLM.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

39 Commits

Folders and files

Repository files navigation

lx

Tests Python Licence

lx turns plain-English task descriptions into ready-to-use Linux commands, powered by a local LLM (via Ollama) by default, so it's private, free, and works offline. If you'd rather use a cloud model, OpenAI, Google Gemini, Anthropic Claude, Groq, OpenRouter, or any OpenAI-compatible API can optionally be enabled with an API key. It never runs anything automatically: you always stay in control.

What would you like to do? find every pdf larger than 50mb
⠋ Thinking...

Command:     find . -type f -name "*.pdf" -size +50M
Explanation: This command recursively searches the current directory and its
             subdirectories for files that match the PDF extension and are
             larger than 50 megabytes.
Risk:        LOW

Copy command to clipboard? [y/N]

Why

I don't want to memorise every Linux command flag, and I don't want an AI agent auto-executing commands on my machine without me reviewing them first. lx is a small, focused tool built around one idea: an LLM is great at generating a command from a description, but a human should always be the one who decides whether to run it.

Design decisions

  • Local by default, cloud optional. Out of the box everything runs through Ollama on your own machine, so nothing is sent anywhere and there's no API cost. Cloud providers are strictly opt-in: they only appear once you add an API key, and lx tells you which provider your task is being sent to before you type it.
  • One client for every cloud provider. Instead of a separate SDK per vendor, lx talks to each provider's OpenAI-compatible /chat/completions endpoint using the same requests library it already uses for Ollama, so no extra dependencies are needed and any compatible service can be added with just a base URL.
  • Never auto-executes. lx only displays a command, an explanation, and a risk level. Copying it to your clipboard is the only optional action it takes, and only with explicit confirmation.
  • Structured, reliable output. The LLM is prompted to return strict JSON (command, explanation, risk), with a worked example included in the prompt (few-shot prompting) to improve reliability on trickier tasks. Since small local models don't always follow formatting instructions perfectly, lx also includes a JSON-repair step (for common escaping mistakes), Ollama's format: json constrained decoding, and automatic retry logic if parsing still fails.
  • A syntax-check safety net. Before displaying a generated command, lx runs a dry-run bash parse (bash -n, nothing is executed) and shows a warning if the command fails basic syntax validation.
  • Risk labelling. Every generated command is labelled low, medium, or high risk, and colour-coded in the terminal (green/yellow/red), so dangerous commands are visually distinct before you ever consider running them.
  • Streamed under the hood. Responses are read from Ollama as a stream of chunks rather than one blocking call, with a live animated status indicator while the model works. The final result is still shown all at once (not word-by-word), since a partially-generated command or JSON fragment isn't meaningful or safe to display mid-stream.
  • Model choice, your call. On startup, lx lists every model you've pulled locally via Ollama and lets you pick one interactively. If cloud API keys are configured, you first choose a provider, then one of that provider's models, with type-to-search for long lists. Set LX_PROVIDER and/or LX_MODEL to skip the pickers.
  • Remembers your last choice. lx saves your most recently used provider and model (per provider) under ~/.config/lx/, and offers them as defaults (press Enter to accept) the next time you run it, without removing your ability to pick a different one.
  • Keeps a local history. Every completed request (task, provider, model, command, risk, and syntax-check result) is appended to ~/.config/lx/history.log, one JSON object per line, purely for your own reference.
  • Tested and CI-checked. Core logic (JSON parsing/repair, input validation, display rendering, clipboard behaviour) has unit tests, run automatically via GitHub Actions on every push.

Requirements

  • Linux (developed and tested on CachyOS/Arch)
  • Python 3.11+
  • uv (curl -LsSf https://astral.sh/uv/install.sh | sh, or sudo pacman -S uv on Arch)
  • Ollama installed and running, with at least one model pulled (e.g. ollama pull gemma4:e4b)
  • Optional: an API key for a cloud provider (see Using cloud providers)
  • For clipboard support: a clipboard tool available to pyperclip: on Wayland, wl-clipboard (provides wl-copy); on X11, xclip or xsel

Setup

git clone https://github.com/havl-code/lx.git
cd lx
ollama pull gemma4:e4b   # or any model you prefer

Then pick one of:

uv tool install --editable .   # recommended: installs `lx` as a command available in any terminal
uv run lx                      # or: run straight from the repo, no install step

uv tool install puts lx in its own isolated environment (like pipx, but faster). Thanks to --editable, changes to the source take effect immediately without reinstalling. uv run creates a local .venv on first use, installs the exact versions pinned in uv.lock, and always runs the current code, so it's handy while developing.

To uninstall: uv tool uninstall lx.

Usage

lx          # if installed with `uv tool install`
uv run lx   # or, from the repo folder

You'll be shown a list of your locally available Ollama models to choose from, with your last-used model marked and offered as the default (press Enter to accept it), then prompted for a task in plain English. lx will show the generated command, an explanation, and a risk level, then ask if you'd like to copy the command to your clipboard. It will never run the command for you.

To skip the model picker and always use a specific model:

LX_MODEL=gemma4:e4b lx

Using cloud providers (optional)

lx reads API keys from a .env file. Start from the provided example, in either location:

# Option A: in the repo folder (simplest while running from source)
cp .env.example .env
chmod 600 .env   # it holds secrets, keep it private

# Option B: in your config folder (works for any kind of install)
mkdir -p ~/.config/lx
cp .env.example ~/.config/lx/.env
chmod 600 ~/.config/lx/.env

The repo-folder .env is found no matter which directory you run lx from, as long as lx runs from this source checkout (uv run lx or uv tool install --editable .). For a regular non-editable install, use ~/.config/lx/.env. If both exist, the order of priority is: variables set in your shell, then the repo .env, then ~/.config/lx/.env.

Then uncomment the keys you want. Supported out of the box:

Provider Variable
OpenAI OPENAI_API_KEY
Google Gemini GEMINI_API_KEY
Anthropic Claude ANTHROPIC_API_KEY
Groq GROQ_API_KEY
OpenRouter OPENROUTER_API_KEY
Any OpenAI-compatible API (LM Studio, vLLM, DeepSeek, Mistral, …) LX_CUSTOM_BASE_URL (+ optional LX_CUSTOM_API_KEY)

With at least one key set, lx asks for a provider first (Ollama stays the first option), then lists that provider's chat models in columns, newest first where the provider reports release dates. Pick one by number, or type part of a name (e.g. mini) to narrow the list; a single match is picked automatically. Models lx can't use (embeddings, audio, image, and models that only work through other APIs, such as OpenAI's -pro models) are hidden. To skip both pickers:

LX_PROVIDER=openai LX_MODEL=gpt-5-mini lx

LX_MODEL on its own still means an Ollama model, exactly as before. If no keys are configured, lx behaves exactly as it always has.

To try a provider once without touching the .env file:

GEMINI_API_KEY=your-key uv run lx

Other flags:

lx --version   # show the installed version
lx --help      # show usage information

Development

Dependencies are managed with uv: pyproject.toml declares them and uv.lock pins exact versions. The dev tools (pytest and matplotlib) live in the dev dependency group, which uv run and uv sync install automatically (uv tool install leaves them out).

Run the test suite:

uv run pytest

Add or update a dependency (updates both pyproject.toml and uv.lock):

uv add <package>          # runtime dependency
uv add --dev <package>    # dev-only dependency
uv lock --upgrade         # bump everything to the latest allowed versions

Tests cover JSON parsing/repair logic, CLI input validation, display rendering (including a regression test for a real markup-escaping bug and the syntax-check warning), clipboard behaviour, the last-used-model and .env config, provider selection, and the cloud client (with mocked HTTP, so no API keys are needed), and run automatically on every push via GitHub Actions (see the badge above).

Benchmarking models

scripts/benchmark.py times a fixed set of tasks against every locally pulled Ollama model, checks JSON-parsing reliability and basic command syntax validity, and generates a comparison chart. See BENCHMARKS.md for results and findings from testing on this project's development hardware, including a follow-up investigation into few-shot prompting and syntax checking as reliability improvements.

uv run scripts/benchmark.py

Known limitations

  • Small local models (3B to 8B class) don't always produce perfectly formed JSON. lx mitigates this with a repair step, retry logic, few-shot prompting, and Ollama's format: json constraint, but it isn't foolproof: occasional failures are still possible, especially for tasks requiring nested shell quoting (e.g. a command that itself needs both single and double quotes). See BENCHMARKS.md for documented examples.
  • Valid JSON doesn't guarantee a correct or safe shell command; benchmarking surfaced real cases of syntactically broken or semantically incorrect commands, in some cases labelled "low risk". Always read the command and explanation yourself before running anything.
  • Risk classification is entirely the LLM's judgement based on prompt guidance, and can vary between runs for similar commands. It's a helpful signal, not a guarantee.
  • lx is only available in terminals where it's been installed with uv tool install (or, from the repo folder, via uv run lx), see Setup above.
  • Generated commands are checked for basic bash syntax validity (via bash -n) before being shown, and a warning is displayed if a command fails this check. This catches bash grammar errors (e.g. unbalanced quotes) but not command-specific argument errors (e.g. a missing required argument to find -exec), see BENCHMARKS.md for a real example of each.
  • lx generates commands assumed to run in bash, and its syntax check (bash -n) validates against bash specifically. Since bash and zsh differ in some edge cases (notably unquoted glob expansion), commands run in a different shell (e.g. zsh, the author's default) could theoretically behave differently. In practice, commands generated so far have consistently used quoted patterns, which avoids the most common divergence, but this hasn't been exhaustively tested across shells.
  • When a cloud provider is selected, your plain-English task is sent to that provider and is subject to its data policies. Ollama remains fully local.
  • .env files store API keys in plain text; keep them at chmod 600 and never commit them (.env is already in .gitignore).
  • ~/.config/lx/last_model and ~/.config/lx/history.log are plain local files with no encryption; history includes the plain-English tasks you've typed and the commands generated, treat this the same as shell history.

Roadmap

  • Package for proper installation (pip install -e ., lx as a console command)
  • Add automated tests for core logic
  • Add CI (GitHub Actions) to run tests on every push
  • Stream responses from Ollama instead of waiting for the full response
  • Interactive model selection, with an LX_MODEL override
  • Benchmark alternative models for speed/accuracy tradeoffs on CPU-only hardware
  • Installable system-wide via pipx, without needing manual venv activation
  • Switch to uv for installs, dependency locking (uv.lock), and CI
  • Investigate whether smaller models can be made more reliable for nested-quoting tasks (few-shot prompting, plus a bash syntax-check safety net)
  • lx --version / lx --help
  • Remember and default to the last-used model
  • Local command history log
  • Optional cloud AI providers (OpenAI, Gemini, Claude, Groq, OpenRouter, any OpenAI-compatible API)

Acknowledgments

Built with guidance from Claude (Anthropic), used as a mentor throughout development: explaining concepts, reviewing code, and helping diagnose real bugs along the way.

Licence

Released under the MIT Licence, see LICENSE.

About

lx turns plain-English task descriptions into ready-to-use Linux commands, powered entirely by a local LLM.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Contributors

Languages