Metadata-Version: 2.5
Name: lex-the-hacker
Version: 0.2.0
Summary: An AI CLI that writes shell commands for you — and never runs them.
Requires-Python: >=3.10
Requires-Dist: click>=8.5.0
Requires-Dist: pydantic-ai-slim[openai]>=2.35.0
Requires-Dist: pyperclip>=1.11.0
Requires-Dist: questionary>=2.1.1
Requires-Dist: rich>=15.0.0
Requires-Dist: shellingham>=1.5.4
Description-Content-Type: text/markdown

# lex the hacker

> *I prefer to be called a hacker.*

![It's a UNIX system! I know this!](docs/images/lex.jpeg)

Describe what you want in plain English, and `lex` writes the shell command for you.

```
$ lex "search the dir for 'xyz'"
```

```
$ grep -rn 'xyz' .
```

While `lex` thinks, you get a spinner. Then it shows the command with a short
explanation of how it works, and offers it to you — pick it (just press Enter)
to copy it to your clipboard:

```
╭──────────────────────────────────────────────────╮
│ grep -rn 'xyz' .                                 │
│                                                  │
│ Searches every file under the current directory  │
│ for the text "xyz", printing each match with its │
│ file name and line number.                       │
╰──────────────────────────────────────────────────╯
? copy to clipboard? (Use arrow keys)
 » grep -rn 'xyz' .
   skip

✓ copied to clipboard
```

Either way, the command also lands on your command line, ready to run.

It **never executes anything itself**. You always read the command and press
Enter (or edit it, or throw it away). Nothing runs without you.

> **On typing.** Putting text on your prompt uses the `TIOCSTI` ioctl, which
> current Linux kernels disable by default (`dev.tty.legacy_tiocsti = 0`)
> because it was a privilege-escalation vector. When it's unavailable — a
> hardened kernel, no terminal, a pipe — or when the command spans more than
> one line, the command simply doesn't land on your prompt. That's a normal
> outcome, not an error: it's still right there in the panel, and on your
> clipboard. Either way, `lex` never presses Enter for you.

## Install

```
pip install lex-the-hacker
```

This gives you the `lex` command.

## Configuration

`lex` talks to any OpenAI-compatible chat API — hosted OpenAI, or a local
[Ollama](https://ollama.com) server. Point it at one with three environment
variables:

| Variable | Required | What it is |
| --- | --- | --- |
| `LEX_MODEL` | yes | The model name to request, e.g. `gpt-4o-mini` or `llama3.1`. |
| `LEX_API_KEY` | yes | The API key. Ollama ignores it, but the client still requires a non-empty value — `ollama` works fine. |
| `LEX_BASE_URL` | no | Base URL of the endpoint. Defaults to OpenAI's hosted API. |

Hosted OpenAI:

```
export LEX_MODEL=gpt-4o-mini
export LEX_API_KEY=sk-...
```

A local Ollama server:

```
export LEX_MODEL=llama3.1
export LEX_API_KEY=ollama
export LEX_BASE_URL=http://localhost:11434/v1
```

`lex` also tells the model which shell you're in, so the command it writes
matches your shell's syntax. Run `lex shell` to see what it detected.

> **Clipboard on Linux.** Copying first tries your system clipboard via
> [pyperclip](https://pypi.org/project/pyperclip/), which on Linux needs one of
> `xclip`, `xsel`, or `wl-clipboard` installed. Without one, `lex` falls back
> to OSC 52 — asking your terminal itself to set the clipboard, no install
> needed — which modern terminals
> (VS Code, kitty, alacritty, WezTerm, iTerm2, foot, …) support, even over
> SSH. If neither route works, the copy is skipped quietly.

## Usage

Call `lex` with a description of what you want to do, in quotes:

```
$ lex "find every python file changed in the last week"
```

`lex` puts its best guess on your prompt:

```
$ find . -name '*.py' -mtime -7
```

From there it's your command like any other — run it, tweak it, or clear the line and try a different description.

More examples:

```
$ lex "undo my last git commit but keep the changes"
$ git reset --soft HEAD~1

$ lex "compress this folder into a tar.gz"
$ tar -czf folder.tar.gz folder/

$ lex "show what's listening on port 8080"
$ lsof -i :8080
```

### Aliases and scripts

`lex alias <name> <description>` gives you the command *and* a one-liner that
writes the alias to your shell's config file — `~/.zshrc` for zsh, `~/.bashrc`
for bash, and so on — so it survives beyond the current session:

```
$ lex alias ll "list files recursively, long format"
$ echo "alias ll='ls -laR'" >> ~/.zshrc
```

(New shells pick it up automatically; `source ~/.zshrc` brings it into the
current one. If `lex` doesn't know your shell's config file, it offers the
bare `alias` definition instead.)

`lex script <name> <description>` gives you the command *and* a one-liner that
writes it to an executable script, with the extension and shebang for your
shell:

```
$ lex script convert "convert every png in this folder to jpg"
$ cat > convert.sh <<'LEX_EOF'
#!/bin/bash
for f in *.png; do magick "$f" "${f%.png}.jpg"; done
LEX_EOF
chmod +x convert.sh
```

The alias or script command is the one you're offered to copy — the panel shows
the underlying command above it, so you can see what you're about to wrap. The
script one spans several lines, which is the "more than one line" case in the
note on typing above: it goes to your clipboard rather than your prompt.

As always, `lex` only writes the command — it doesn't touch your config file
or create the script itself. Nothing changes until you run it (`lex shell`
shows which shell and config file it detected).

## Why you're always safe

`lex` only ever *writes* a command to your shell. It doesn't run it, and it can't run it — executing is up to you. So if a suggestion looks wrong, you'll see it before anything happens.
