pehloo-shell 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pehloo_shell-0.1.0/.env.example +8 -0
- pehloo_shell-0.1.0/.github/workflows/publish.yml +60 -0
- pehloo_shell-0.1.0/.gitignore +20 -0
- pehloo_shell-0.1.0/AGENTS.md +148 -0
- pehloo_shell-0.1.0/LICENSE +21 -0
- pehloo_shell-0.1.0/PKG-INFO +126 -0
- pehloo_shell-0.1.0/README.md +111 -0
- pehloo_shell-0.1.0/install.sh +175 -0
- pehloo_shell-0.1.0/pyproject.toml +43 -0
- pehloo_shell-0.1.0/src/pehloo_shell/__init__.py +4 -0
- pehloo_shell-0.1.0/src/pehloo_shell/cli.py +348 -0
- pehloo_shell-0.1.0/src/pehloo_shell/onboarding.py +160 -0
- pehloo_shell-0.1.0/src/pehloo_shell/providers.py +158 -0
- pehloo_shell-0.1.0/tests/test_cli.py +226 -0
- pehloo_shell-0.1.0/tests/test_onboarding.py +66 -0
- pehloo_shell-0.1.0/tests/test_providers.py +45 -0
- pehloo_shell-0.1.0/uv.lock +28 -0
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# Cline-pass judge model (OpenAI-compatible API) that grades semantic correctness.
|
|
2
|
+
CLINE_API_KEY=your_api_key_here
|
|
3
|
+
CLINE_BASE_URL=https://api.cline.bot/api/v1
|
|
4
|
+
CLINE_JUDGE_MODEL=cline-pass/deepseek-v4-flash
|
|
5
|
+
|
|
6
|
+
# The evaluated local ggufs are served by llama-server on :8080. Inspect's
|
|
7
|
+
# "llamacpp" provider still expects an API key; llama-server ignores it.
|
|
8
|
+
LLAMACPP_API_KEY=unused
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
name: publish
|
|
2
|
+
|
|
3
|
+
# Publishes to PyPI with trusted publishing (OIDC) — no API token is stored
|
|
4
|
+
# anywhere. The PyPI publisher form must match this repository row for row:
|
|
5
|
+
#
|
|
6
|
+
# PyPI Project Name: pehloo-shell
|
|
7
|
+
# Owner: ribhu97
|
|
8
|
+
# Repository name: pehloo-shell
|
|
9
|
+
# Workflow name: publish.yml
|
|
10
|
+
# Environment name: pypi
|
|
11
|
+
#
|
|
12
|
+
# If you leave "Environment name" blank on PyPI, delete `environment: pypi`
|
|
13
|
+
# below as well: the OIDC token carries the environment, and a mismatch is
|
|
14
|
+
# rejected. A release publishes whatever version `pyproject.toml` declares, so
|
|
15
|
+
# bump it first, then tag `v<version>`.
|
|
16
|
+
|
|
17
|
+
on:
|
|
18
|
+
release:
|
|
19
|
+
types: [published]
|
|
20
|
+
workflow_dispatch:
|
|
21
|
+
|
|
22
|
+
permissions:
|
|
23
|
+
contents: read
|
|
24
|
+
|
|
25
|
+
jobs:
|
|
26
|
+
build:
|
|
27
|
+
name: build sdist and wheel
|
|
28
|
+
runs-on: ubuntu-latest
|
|
29
|
+
steps:
|
|
30
|
+
- uses: actions/checkout@v7
|
|
31
|
+
- uses: astral-sh/setup-uv@v10
|
|
32
|
+
with:
|
|
33
|
+
python-version: "3.13"
|
|
34
|
+
- name: Build
|
|
35
|
+
run: uv build
|
|
36
|
+
- name: Check the artifact metadata before anything is uploaded
|
|
37
|
+
run: uv publish --dry-run dist/*
|
|
38
|
+
- uses: actions/upload-artifact@v7
|
|
39
|
+
with:
|
|
40
|
+
name: dist
|
|
41
|
+
path: dist/
|
|
42
|
+
|
|
43
|
+
publish:
|
|
44
|
+
name: publish to PyPI
|
|
45
|
+
needs: build
|
|
46
|
+
runs-on: ubuntu-latest
|
|
47
|
+
# Must match the Environment name configured on PyPI (see the header).
|
|
48
|
+
environment: pypi
|
|
49
|
+
permissions:
|
|
50
|
+
id-token: write # the OIDC token that trusted publishing exchanges for upload rights
|
|
51
|
+
steps:
|
|
52
|
+
- uses: actions/download-artifact@v8
|
|
53
|
+
with:
|
|
54
|
+
name: dist
|
|
55
|
+
path: dist/
|
|
56
|
+
- uses: astral-sh/setup-uv@v10
|
|
57
|
+
- name: Publish
|
|
58
|
+
# `--check-url` skips files PyPI already has, so re-running a release is
|
|
59
|
+
# a no-op instead of an error.
|
|
60
|
+
run: uv publish --trusted-publishing always --check-url https://pypi.org/simple/ dist/*
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
.venv/
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
.env
|
|
5
|
+
inspect-logs/
|
|
6
|
+
logs/
|
|
7
|
+
dist/
|
|
8
|
+
build/
|
|
9
|
+
*.egg-info/
|
|
10
|
+
.DS_Store
|
|
11
|
+
|
|
12
|
+
# Private for now — kept out of the public repo and the PyPI sdist.
|
|
13
|
+
benchmark/
|
|
14
|
+
|
|
15
|
+
# Editor/agent configuration.
|
|
16
|
+
.serena/
|
|
17
|
+
.claude/
|
|
18
|
+
.mcp.json
|
|
19
|
+
*.log
|
|
20
|
+
.ruff_cache/
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
# AGENTS.md
|
|
2
|
+
|
|
3
|
+
Guidance for humans and AI agents working in this repository.
|
|
4
|
+
|
|
5
|
+
## Project in one line
|
|
6
|
+
|
|
7
|
+
`pehloo-shell` (CLI: `pls`) turns a natural-language request into a shell command
|
|
8
|
+
via an OpenAI-compatible model server (local llama.cpp by default, or Cerebras,
|
|
9
|
+
Groq, OpenRouter). It prints the command, then asks whether to run, reject or
|
|
10
|
+
change it — nothing executes without an explicit `y`. From Pehloo
|
|
11
|
+
(<https://pehloo.xyz>).
|
|
12
|
+
|
|
13
|
+
## Layout
|
|
14
|
+
|
|
15
|
+
- `src/pehloo_shell/cli.py` — the `pls` CLI: settings resolution, the model call,
|
|
16
|
+
and the run/reject/change confirmation loop.
|
|
17
|
+
- `src/pehloo_shell/providers.py` — the backend registry (local + hosted), URL
|
|
18
|
+
derivation, and `/models` discovery. Add a backend here and nowhere else.
|
|
19
|
+
- `src/pehloo_shell/onboarding.py` — the first-run setup wizard and the config
|
|
20
|
+
file writer. Also reachable with `pls --setup`.
|
|
21
|
+
- `install.sh` — the `curl … | sh` installer (uv → pipx → pip, user-level only).
|
|
22
|
+
- `tests/test_cli.py`, `tests/test_providers.py`, `tests/test_onboarding.py` —
|
|
23
|
+
unit tests (mock the HTTP layer and `input`).
|
|
24
|
+
- `benchmark/` is private and gitignored (kept out of the public repo and the
|
|
25
|
+
PyPI sdist "for now"); its tooling and run instructions live in
|
|
26
|
+
`benchmark/AGENTS.md`. The same applies to `.serena/`, `.claude/` and
|
|
27
|
+
`.mcp.json`.
|
|
28
|
+
|
|
29
|
+
## Environment
|
|
30
|
+
|
|
31
|
+
- The project uses a `uv`-managed venv (`.venv`):
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
uv venv .venv
|
|
35
|
+
uv sync # installs the `pls` CLI and its one dependency
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
`uv.lock` is committed; keep it in sync when you touch `pyproject.toml`.
|
|
39
|
+
Benchmark tooling (`inspect-ai`, `openai`, `matplotlib`) is not part of the
|
|
40
|
+
package — install it as a local overlay when you work in `benchmark/`:
|
|
41
|
+
`uv pip install --python .venv/bin/python inspect-ai openai matplotlib`
|
|
42
|
+
(`uv sync` removes the overlay again).
|
|
43
|
+
|
|
44
|
+
- `.env` holds secrets and local defaults (cline-pass judge, local llama-server).
|
|
45
|
+
Never commit a real API key — `.env` is gitignored; put placeholders in
|
|
46
|
+
`.env.example` instead.
|
|
47
|
+
|
|
48
|
+
- End users install with `install.sh` (`sh install.sh` from a checkout, or
|
|
49
|
+
`curl -fsSL <url> | sh`): uv → pipx → pip, user-level only, no sudo. It
|
|
50
|
+
installs the `pehloo-shell` package from PyPI unless `--source` /
|
|
51
|
+
`$PEHLOO_SHELL_SOURCE` says otherwise.
|
|
52
|
+
|
|
53
|
+
## Branding & voice
|
|
54
|
+
|
|
55
|
+
Public surfaces match <https://pehloo.xyz>:
|
|
56
|
+
|
|
57
|
+
- The ANSI-shadow `PEHLOO` wordmark (top of `README.md`, header of
|
|
58
|
+
`install.sh`) — the exact 6 lines are on the site's landing page.
|
|
59
|
+
- Status tags look like `[ PLS_INSTALL ]` and `[ COMING_SOON ]` — square
|
|
60
|
+
brackets, words_joined_by_underscores, sometimes boxed with `╔═╗`.
|
|
61
|
+
- Progress and pointer lines start with `▸` (the wizard, the installer).
|
|
62
|
+
- Terse terminal voice: "Natural language in, shell command out." — no
|
|
63
|
+
marketing, no filler. Fonts on the site are Share Tech Mono / Space Mono /
|
|
64
|
+
VT323; accent colour is brand-amber `#d4a017` on cream `#f5f2eb`.
|
|
65
|
+
- `pls` itself prints the command then `[y] run [n] reject [c] change >`.
|
|
66
|
+
|
|
67
|
+
## How `pls` behaves (keep these contracts)
|
|
68
|
+
|
|
69
|
+
- **Nothing executes without an explicit `y`** at the `[y] run [n] reject
|
|
70
|
+
[c] change` prompt. Enter and `n` reject; unknown keys ask again.
|
|
71
|
+
- **Non-interactive runs never execute**: with stdin/stdout not a terminal,
|
|
72
|
+
`pls` prints the command and stops, so pipes and scripts stay safe.
|
|
73
|
+
- `y` propagates the command's exit status as `pls`'s own; Ctrl-C/Ctrl-D at a
|
|
74
|
+
prompt is 130; a missing request is 2.
|
|
75
|
+
- `c` sends the revision as a follow-up turn (`previous_command` + `change` in
|
|
76
|
+
`generate_command`), so revisions keep the original request as context.
|
|
77
|
+
- The setup wizard runs only when there is no config file and the session is
|
|
78
|
+
interactive, or when `--setup` is passed.
|
|
79
|
+
- New backends go in `providers.py`; nothing else should know provider URLs or
|
|
80
|
+
API-key environment variables.
|
|
81
|
+
|
|
82
|
+
## Releasing (PyPI)
|
|
83
|
+
|
|
84
|
+
`install.sh` defaults to the `pehloo-shell` package on PyPI, so publishing is what
|
|
85
|
+
makes `pipx install pehloo-shell` work for everyone else.
|
|
86
|
+
|
|
87
|
+
Releases go out from `.github/workflows/publish.yml` using trusted publishing —
|
|
88
|
+
no API token is stored in the repo or in GitHub secrets. PyPI's publisher form
|
|
89
|
+
("pending publisher" for a project that does not exist yet) must match that file
|
|
90
|
+
exactly:
|
|
91
|
+
|
|
92
|
+
| PyPI field | Value |
|
|
93
|
+
| --- | --- |
|
|
94
|
+
| PyPI Project Name | `pehloo-shell` |
|
|
95
|
+
| Owner | `ribhu97` |
|
|
96
|
+
| Repository name | `pehloo-shell` |
|
|
97
|
+
| Workflow name | `publish.yml` |
|
|
98
|
+
| Environment name | `pypi` — the workflow sets `environment: pypi`; leaving this blank on PyPI means deleting that line too |
|
|
99
|
+
|
|
100
|
+
Then: bump `version` in `pyproject.toml`, commit, tag `v<version>`, publish a
|
|
101
|
+
GitHub release. The workflow builds the artifacts, validates their metadata with
|
|
102
|
+
`uv publish --dry-run`, and uploads them with OIDC. `workflow_dispatch` re-runs it
|
|
103
|
+
on demand; `--check-url` makes that a no-op for files PyPI already has.
|
|
104
|
+
|
|
105
|
+
Publishing by hand, with a token, still works:
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
uv build # dist/*.whl + dist/*.tar.gz
|
|
109
|
+
uv publish --dry-run dist/* # validates the metadata, stops at credentials
|
|
110
|
+
|
|
111
|
+
# optional rehearsal on TestPyPI, then install from there
|
|
112
|
+
uv publish --publish-url https://test.pypi.org/legacy/ --token "$TESTPYPI_TOKEN" dist/*
|
|
113
|
+
pipx install --index-url https://test.pypi.org/simple/ \
|
|
114
|
+
--pip-args="--extra-index-url https://pypi.org/simple/" pehloo-shell
|
|
115
|
+
|
|
116
|
+
uv publish --token "$PYPI_TOKEN" dist/* # the real thing
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
- Credentials for the manual path come from `UV_PUBLISH_TOKEN` / `--token`, or a
|
|
120
|
+
token from <https://pypi.org/manage/account/token/>; never put one in the repo.
|
|
121
|
+
- Bump `version` in `pyproject.toml` for every upload — PyPI rejects a version it
|
|
122
|
+
already has, so a fix means a new version, not a re-upload.
|
|
123
|
+
- The sdist ships the package, tests and docs only; `.env`, `logs/`,
|
|
124
|
+
`benchmark/`, `.serena/`, `.claude/` and `.mcp.json` are excluded (see
|
|
125
|
+
`[tool.hatch.build.targets.sdist]` in `pyproject.toml`). After touching
|
|
126
|
+
packaging, re-check with `tar tzf dist/*.tar.gz` — the sdist must never contain
|
|
127
|
+
`.env`.
|
|
128
|
+
- Published installs are copies: `pipx install pehloo-shell` and
|
|
129
|
+
`uv tool install pehloo-shell` need `--force` (or `pipx upgrade` /
|
|
130
|
+
`uv tool upgrade`) to pick up a release.
|
|
131
|
+
|
|
132
|
+
## Working agreements
|
|
133
|
+
|
|
134
|
+
1. **Keep code human readable.**
|
|
135
|
+
- No unnecessary abstractions. Plain functions and simple data flow beat clever
|
|
136
|
+
design patterns.
|
|
137
|
+
- No god-functions. If a function does more than one clearly named thing, split it.
|
|
138
|
+
- Inline comments and docstrings explain *why*, not just *what*. A stranger should
|
|
139
|
+
be able to read the code top-to-bottom without spelunking.
|
|
140
|
+
|
|
141
|
+
## Before committing
|
|
142
|
+
|
|
143
|
+
- Unit tests should pass: `uv run python -m unittest discover tests`
|
|
144
|
+
- CLI still runs: `uv run pls "list files by size"` (interactive), and
|
|
145
|
+
`echo "list files by size" | uv run pls` must print the command and run nothing.
|
|
146
|
+
- Installer still parses and installs: `sh -n install.sh`, then
|
|
147
|
+
`uv tool install --force .` (or `sh install.sh`).
|
|
148
|
+
- `.env` must not contain real secrets (only placeholders).
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Ribhu
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: pehloo-shell
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Natural language in, shell command out — review it, then press y to run. A Pehloo tool.
|
|
5
|
+
Project-URL: Homepage, https://pehloo.xyz
|
|
6
|
+
Author: Ribhu
|
|
7
|
+
License: MIT
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Classifier: Environment :: Console
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
12
|
+
Requires-Python: >=3.10
|
|
13
|
+
Requires-Dist: python-dotenv>=1
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
|
|
16
|
+
```
|
|
17
|
+
██████╗ ███████╗██╗ ██╗██╗ ██████╗ ██████╗
|
|
18
|
+
██╔══██╗██╔════╝██║ ██║██║ ██╔═══██╗██╔═══██╗
|
|
19
|
+
██████╔╝█████╗ ███████║██║ ██║ ██║██║ ██║
|
|
20
|
+
██╔═══╝ ██╔══╝ ██╔══██║██║ ██║ ██║██║ ██║
|
|
21
|
+
██║ ███████╗██║ ██║███████╗╚██████╔╝╚██████╔╝
|
|
22
|
+
╚═╝ ╚══════╝╚═╝ ╚═╝╚══════╝ ╚═════╝ ╚═════╝
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
# pls
|
|
26
|
+
|
|
27
|
+
**Natural language in, shell command out. Review it, then press `y`.**
|
|
28
|
+
|
|
29
|
+
`pls` is the Pehloo shell assistant — from [Pehloo](https://pehloo.xyz), AI
|
|
30
|
+
tools for builders who ship. It asks an OpenAI-compatible model to turn your
|
|
31
|
+
request into one shell command, shows it to you, and waits:
|
|
32
|
+
|
|
33
|
+
```console
|
|
34
|
+
$ pls "list the 10 largest directories in the current folder"
|
|
35
|
+
du -h --max-depth=1 | sort -hr | head -n 10
|
|
36
|
+
[y] run [n] reject [c] change >
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
- `y` runs the command in your shell; `pls` exits with its status.
|
|
40
|
+
- `n` or Enter rejects it — nothing runs.
|
|
41
|
+
- `c` asks the model to change it (e.g. type `sort by size instead`) and shows
|
|
42
|
+
the revision, with the previous command as context.
|
|
43
|
+
|
|
44
|
+
## Install
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
curl -fsSL https://raw.githubusercontent.com/ribhu97/pehloo-shell/main/install.sh | sh
|
|
48
|
+
sh install.sh # from a checkout
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
The installer works at user level — no sudo, no system Python — using uv, pipx
|
|
52
|
+
or pip, whichever you have, and prints the installed `pls` path plus the
|
|
53
|
+
`export PATH=…` line if you need one. It installs the `pehloo-shell` package
|
|
54
|
+
from PyPI, falling back to this repository while the package is unpublished.
|
|
55
|
+
Point it elsewhere with `--source` / `$PEHLOO_SHELL_SOURCE`:
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
sh install.sh --source "git+https://github.com/ribhu97/pehloo-shell"
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Already have a Python tool runner? Skip the script:
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
uv tool install pehloo-shell # or: pipx install pehloo-shell
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
For development see [AGENTS.md](AGENTS.md) (`uv sync`, then `.venv/bin/pls`).
|
|
68
|
+
Note that `pipx install .` and `uv tool install .` copy the code — rerun them
|
|
69
|
+
after changing the CLI.
|
|
70
|
+
|
|
71
|
+
## Setup
|
|
72
|
+
|
|
73
|
+
The first run with no config asks which backend to use:
|
|
74
|
+
|
|
75
|
+
```console
|
|
76
|
+
╔══════════════════════════════╗
|
|
77
|
+
║ PLS_SETUP: MODEL_BACKEND ║
|
|
78
|
+
╚══════════════════════════════╝
|
|
79
|
+
▸ pehloo-shell · a Pehloo tool (pehloo.xyz)
|
|
80
|
+
▸ your answers are saved to ~/.pehloo/shell-config.json (re-run any time with `pls --setup`)
|
|
81
|
+
|
|
82
|
+
Use a local model server? [Y/n]: y
|
|
83
|
+
|
|
84
|
+
Local server: llama.cpp, Ollama, LM Studio or vLLM — any OpenAI-compatible server.
|
|
85
|
+
Server URL [http://127.0.0.1:8080/v1]:
|
|
86
|
+
API key (leave blank if the server has none):
|
|
87
|
+
1. LFM2.5-1.2B-Instruct-Q8_0.gguf (default)
|
|
88
|
+
2. MiniCPM5-2B
|
|
89
|
+
Model number or name [1]:
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
- **Local** (`y`): any OpenAI-compatible server — llama.cpp, Ollama, LM Studio,
|
|
93
|
+
vLLM. `pls` reads its `/v1/models` list so you can pick a model instead of
|
|
94
|
+
guessing a name.
|
|
95
|
+
- **Hosted** (`n`): Cerebras, Groq or OpenRouter. The wizard tells you where to
|
|
96
|
+
create a key and detects the provider's models with it. A key already in your
|
|
97
|
+
environment is used without being copied into the config file.
|
|
98
|
+
|
|
99
|
+
Re-run setup any time with `pls --setup`, or edit `~/.pehloo/shell-config.json`
|
|
100
|
+
by hand.
|
|
101
|
+
|
|
102
|
+
## Non-interactive
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
echo "show files changed today" | pls # prints the command, runs nothing
|
|
106
|
+
pls --markdown "show my current directory"
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
When stdin is not a terminal — pipes, scripts, CI — `pls` prints the command and
|
|
110
|
+
stops. Nothing executes without an explicit `y` at a terminal.
|
|
111
|
+
|
|
112
|
+
## Environment variables
|
|
113
|
+
|
|
114
|
+
Handy for CI, or to keep the API key out of the config file. They override the
|
|
115
|
+
config file; command-line flags override both.
|
|
116
|
+
|
|
117
|
+
| Variable | Meaning |
|
|
118
|
+
| --- | --- |
|
|
119
|
+
| `PEHLOO_SHELL_CONFIG` | where the config file lives (default `~/.pehloo/shell-config.json`) |
|
|
120
|
+
| `PEHLOO_SHELL_PROVIDER` | `llama.cpp`, `cerebras`, `groq` or `openrouter` |
|
|
121
|
+
| `PEHLOO_SHELL_API_KEY` | API key for any provider |
|
|
122
|
+
| `CEREBRAS_API_KEY`, `GROQ_API_KEY`, `OPENROUTER_API_KEY` | provider-specific keys |
|
|
123
|
+
| `LFM_SHELL_URL` | chat-completions URL of any OpenAI-compatible server |
|
|
124
|
+
| `LFM_SHELL_MODEL` | model name to send |
|
|
125
|
+
|
|
126
|
+
Read the command before you press `y`: model output can be destructive.
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
```
|
|
2
|
+
██████╗ ███████╗██╗ ██╗██╗ ██████╗ ██████╗
|
|
3
|
+
██╔══██╗██╔════╝██║ ██║██║ ██╔═══██╗██╔═══██╗
|
|
4
|
+
██████╔╝█████╗ ███████║██║ ██║ ██║██║ ██║
|
|
5
|
+
██╔═══╝ ██╔══╝ ██╔══██║██║ ██║ ██║██║ ██║
|
|
6
|
+
██║ ███████╗██║ ██║███████╗╚██████╔╝╚██████╔╝
|
|
7
|
+
╚═╝ ╚══════╝╚═╝ ╚═╝╚══════╝ ╚═════╝ ╚═════╝
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
# pls
|
|
11
|
+
|
|
12
|
+
**Natural language in, shell command out. Review it, then press `y`.**
|
|
13
|
+
|
|
14
|
+
`pls` is the Pehloo shell assistant — from [Pehloo](https://pehloo.xyz), AI
|
|
15
|
+
tools for builders who ship. It asks an OpenAI-compatible model to turn your
|
|
16
|
+
request into one shell command, shows it to you, and waits:
|
|
17
|
+
|
|
18
|
+
```console
|
|
19
|
+
$ pls "list the 10 largest directories in the current folder"
|
|
20
|
+
du -h --max-depth=1 | sort -hr | head -n 10
|
|
21
|
+
[y] run [n] reject [c] change >
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
- `y` runs the command in your shell; `pls` exits with its status.
|
|
25
|
+
- `n` or Enter rejects it — nothing runs.
|
|
26
|
+
- `c` asks the model to change it (e.g. type `sort by size instead`) and shows
|
|
27
|
+
the revision, with the previous command as context.
|
|
28
|
+
|
|
29
|
+
## Install
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
curl -fsSL https://raw.githubusercontent.com/ribhu97/pehloo-shell/main/install.sh | sh
|
|
33
|
+
sh install.sh # from a checkout
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
The installer works at user level — no sudo, no system Python — using uv, pipx
|
|
37
|
+
or pip, whichever you have, and prints the installed `pls` path plus the
|
|
38
|
+
`export PATH=…` line if you need one. It installs the `pehloo-shell` package
|
|
39
|
+
from PyPI, falling back to this repository while the package is unpublished.
|
|
40
|
+
Point it elsewhere with `--source` / `$PEHLOO_SHELL_SOURCE`:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
sh install.sh --source "git+https://github.com/ribhu97/pehloo-shell"
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Already have a Python tool runner? Skip the script:
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
uv tool install pehloo-shell # or: pipx install pehloo-shell
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
For development see [AGENTS.md](AGENTS.md) (`uv sync`, then `.venv/bin/pls`).
|
|
53
|
+
Note that `pipx install .` and `uv tool install .` copy the code — rerun them
|
|
54
|
+
after changing the CLI.
|
|
55
|
+
|
|
56
|
+
## Setup
|
|
57
|
+
|
|
58
|
+
The first run with no config asks which backend to use:
|
|
59
|
+
|
|
60
|
+
```console
|
|
61
|
+
╔══════════════════════════════╗
|
|
62
|
+
║ PLS_SETUP: MODEL_BACKEND ║
|
|
63
|
+
╚══════════════════════════════╝
|
|
64
|
+
▸ pehloo-shell · a Pehloo tool (pehloo.xyz)
|
|
65
|
+
▸ your answers are saved to ~/.pehloo/shell-config.json (re-run any time with `pls --setup`)
|
|
66
|
+
|
|
67
|
+
Use a local model server? [Y/n]: y
|
|
68
|
+
|
|
69
|
+
Local server: llama.cpp, Ollama, LM Studio or vLLM — any OpenAI-compatible server.
|
|
70
|
+
Server URL [http://127.0.0.1:8080/v1]:
|
|
71
|
+
API key (leave blank if the server has none):
|
|
72
|
+
1. LFM2.5-1.2B-Instruct-Q8_0.gguf (default)
|
|
73
|
+
2. MiniCPM5-2B
|
|
74
|
+
Model number or name [1]:
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
- **Local** (`y`): any OpenAI-compatible server — llama.cpp, Ollama, LM Studio,
|
|
78
|
+
vLLM. `pls` reads its `/v1/models` list so you can pick a model instead of
|
|
79
|
+
guessing a name.
|
|
80
|
+
- **Hosted** (`n`): Cerebras, Groq or OpenRouter. The wizard tells you where to
|
|
81
|
+
create a key and detects the provider's models with it. A key already in your
|
|
82
|
+
environment is used without being copied into the config file.
|
|
83
|
+
|
|
84
|
+
Re-run setup any time with `pls --setup`, or edit `~/.pehloo/shell-config.json`
|
|
85
|
+
by hand.
|
|
86
|
+
|
|
87
|
+
## Non-interactive
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
echo "show files changed today" | pls # prints the command, runs nothing
|
|
91
|
+
pls --markdown "show my current directory"
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
When stdin is not a terminal — pipes, scripts, CI — `pls` prints the command and
|
|
95
|
+
stops. Nothing executes without an explicit `y` at a terminal.
|
|
96
|
+
|
|
97
|
+
## Environment variables
|
|
98
|
+
|
|
99
|
+
Handy for CI, or to keep the API key out of the config file. They override the
|
|
100
|
+
config file; command-line flags override both.
|
|
101
|
+
|
|
102
|
+
| Variable | Meaning |
|
|
103
|
+
| --- | --- |
|
|
104
|
+
| `PEHLOO_SHELL_CONFIG` | where the config file lives (default `~/.pehloo/shell-config.json`) |
|
|
105
|
+
| `PEHLOO_SHELL_PROVIDER` | `llama.cpp`, `cerebras`, `groq` or `openrouter` |
|
|
106
|
+
| `PEHLOO_SHELL_API_KEY` | API key for any provider |
|
|
107
|
+
| `CEREBRAS_API_KEY`, `GROQ_API_KEY`, `OPENROUTER_API_KEY` | provider-specific keys |
|
|
108
|
+
| `LFM_SHELL_URL` | chat-completions URL of any OpenAI-compatible server |
|
|
109
|
+
| `LFM_SHELL_MODEL` | model name to send |
|
|
110
|
+
|
|
111
|
+
Read the command before you press `y`: model output can be destructive.
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
# Install the `pls` CLI from pehloo-shell.
|
|
3
|
+
#
|
|
4
|
+
# curl -fsSL https://example.com/install.sh | sh
|
|
5
|
+
#
|
|
6
|
+
# Options:
|
|
7
|
+
# --source SPEC what to install (default: the `pehloo-shell` package)
|
|
8
|
+
# examples: --source .
|
|
9
|
+
# --source "git+https://github.com/you/pehloo-shell"
|
|
10
|
+
#
|
|
11
|
+
# Installs into your user account only: no sudo, no system Python, no files
|
|
12
|
+
# outside your home directory. Run it again to upgrade.
|
|
13
|
+
set -eu
|
|
14
|
+
|
|
15
|
+
SOURCE="${PEHLOO_SHELL_SOURCE:-pehloo-shell}"
|
|
16
|
+
# Fallback used when the PyPI package is not available (see the install step).
|
|
17
|
+
REPO_SOURCE="git+https://github.com/ribhu97/pehloo-shell"
|
|
18
|
+
MIN_PYTHON="3.10"
|
|
19
|
+
|
|
20
|
+
usage() {
|
|
21
|
+
cat <<'EOF'
|
|
22
|
+
Install the pls CLI.
|
|
23
|
+
|
|
24
|
+
Usage: install.sh [--source SPEC]
|
|
25
|
+
|
|
26
|
+
--source SPEC pip requirement to install (default: the pehloo-shell package
|
|
27
|
+
on PyPI, falling back to the GitHub repository when the
|
|
28
|
+
package is not published). Also read from $PEHLOO_SHELL_SOURCE.
|
|
29
|
+
--help show this message.
|
|
30
|
+
|
|
31
|
+
Installing from a checkout of this repository installs that checkout.
|
|
32
|
+
EOF
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
while [ $# -gt 0 ]; do
|
|
36
|
+
case "$1" in
|
|
37
|
+
--source|-s)
|
|
38
|
+
[ $# -ge 2 ] || { echo "install.sh: --source needs a value" >&2; exit 2; }
|
|
39
|
+
SOURCE="$2"
|
|
40
|
+
shift 2
|
|
41
|
+
;;
|
|
42
|
+
--help|-h)
|
|
43
|
+
usage
|
|
44
|
+
exit 0
|
|
45
|
+
;;
|
|
46
|
+
*)
|
|
47
|
+
echo "install.sh: unknown option '$1'" >&2
|
|
48
|
+
usage >&2
|
|
49
|
+
exit 2
|
|
50
|
+
;;
|
|
51
|
+
esac
|
|
52
|
+
done
|
|
53
|
+
|
|
54
|
+
# --- Banner -----------------------------------------------------------------
|
|
55
|
+
|
|
56
|
+
cat <<'BANNER'
|
|
57
|
+
|
|
58
|
+
██████╗ ███████╗██╗ ██╗██╗ ██████╗ ██████╗
|
|
59
|
+
██╔══██╗██╔════╝██║ ██║██║ ██╔═══██╗██╔═══██╗
|
|
60
|
+
██████╔╝█████╗ ███████║██║ ██║ ██║██║ ██║
|
|
61
|
+
██╔═══╝ ██╔══╝ ██╔══██║██║ ██║ ██║██║ ██║
|
|
62
|
+
██║ ███████╗██║ ██║███████╗╚██████╔╝╚██████╔╝
|
|
63
|
+
╚═╝ ╚══════╝╚═╝ ╚═╝╚══════╝ ╚═════╝ ╚═════╝
|
|
64
|
+
[ PLS_INSTALL ] pehloo-shell · a Pehloo tool (pehloo.xyz)
|
|
65
|
+
|
|
66
|
+
BANNER
|
|
67
|
+
|
|
68
|
+
# Running inside the repository? Install that, so `sh install.sh` in a checkout
|
|
69
|
+
# does the obvious thing.
|
|
70
|
+
if [ "$SOURCE" = "pehloo-shell" ] && [ -f pyproject.toml ] && [ -f src/pehloo_shell/cli.py ]; then
|
|
71
|
+
SOURCE="."
|
|
72
|
+
echo "▸ installing from this checkout"
|
|
73
|
+
fi
|
|
74
|
+
|
|
75
|
+
# --- Python -----------------------------------------------------------------
|
|
76
|
+
|
|
77
|
+
PY=""
|
|
78
|
+
for candidate in python3 python; do
|
|
79
|
+
if command -v "$candidate" >/dev/null 2>&1; then
|
|
80
|
+
PY="$(command -v "$candidate")"
|
|
81
|
+
break
|
|
82
|
+
fi
|
|
83
|
+
done
|
|
84
|
+
|
|
85
|
+
if [ -z "$PY" ]; then
|
|
86
|
+
echo "install.sh: python3 not found." >&2
|
|
87
|
+
echo " macOS: brew install python" >&2
|
|
88
|
+
echo " Debian: sudo apt install python3 python3-pip" >&2
|
|
89
|
+
exit 1
|
|
90
|
+
fi
|
|
91
|
+
|
|
92
|
+
if ! "$PY" -c "import sys; raise SystemExit(0 if sys.version_info >= tuple(int(p) for p in '$MIN_PYTHON'.split('.')) else 1)"; then
|
|
93
|
+
echo "install.sh: python $MIN_PYTHON or newer is required, found $("$PY" -V 2>&1)." >&2
|
|
94
|
+
exit 1
|
|
95
|
+
fi
|
|
96
|
+
|
|
97
|
+
# --- Install ----------------------------------------------------------------
|
|
98
|
+
|
|
99
|
+
installer=""
|
|
100
|
+
|
|
101
|
+
# Install one pip requirement with whichever tool this machine has.
|
|
102
|
+
try_install() {
|
|
103
|
+
spec="$1"
|
|
104
|
+
if [ -z "$installer" ]; then
|
|
105
|
+
if command -v uv >/dev/null 2>&1; then
|
|
106
|
+
installer="uv"
|
|
107
|
+
elif command -v pipx >/dev/null 2>&1; then
|
|
108
|
+
installer="pipx"
|
|
109
|
+
else
|
|
110
|
+
installer="pip"
|
|
111
|
+
fi
|
|
112
|
+
fi
|
|
113
|
+
case "$installer" in
|
|
114
|
+
uv) echo "▸ uv tool install $spec"; uv tool install --force "$spec" ;;
|
|
115
|
+
pipx) echo "▸ pipx install $spec"; pipx install --force "$spec" ;;
|
|
116
|
+
*) echo "▸ $PY -m pip install --user --upgrade $spec"; "$PY" -m pip install --user --upgrade "$spec" ;;
|
|
117
|
+
esac
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if ! try_install "$SOURCE"; then
|
|
121
|
+
if [ "$SOURCE" = "pehloo-shell" ]; then
|
|
122
|
+
# Nothing published on PyPI yet (or PyPI is down): install straight from
|
|
123
|
+
# the repository so the advertised `curl … | sh` always works.
|
|
124
|
+
echo "▸ pehloo-shell is not on PyPI yet — installing from $REPO_SOURCE"
|
|
125
|
+
if ! try_install "$REPO_SOURCE"; then
|
|
126
|
+
echo "install.sh: could not install $REPO_SOURCE." >&2
|
|
127
|
+
exit 1
|
|
128
|
+
fi
|
|
129
|
+
else
|
|
130
|
+
echo "install.sh: could not install $SOURCE." >&2
|
|
131
|
+
if [ "$installer" = "pip" ]; then
|
|
132
|
+
echo " This is often an externally-managed environment (PEP 668). Install uv or pipx and rerun:" >&2
|
|
133
|
+
echo " curl -LsSf https://astral.sh/uv/install.sh | sh" >&2
|
|
134
|
+
fi
|
|
135
|
+
exit 1
|
|
136
|
+
fi
|
|
137
|
+
fi
|
|
138
|
+
|
|
139
|
+
# --- Report -----------------------------------------------------------------
|
|
140
|
+
|
|
141
|
+
case "$installer" in
|
|
142
|
+
uv) bin_dir="${UV_TOOL_BIN_DIR:-$HOME/.local/bin}" ;;
|
|
143
|
+
pipx) bin_dir="${PIPX_BIN_DIR:-$HOME/.local/bin}" ;;
|
|
144
|
+
*) bin_dir="$("$PY" -c 'import sysconfig; print(sysconfig.get_path("scripts", scheme="posix_user"))')" ;;
|
|
145
|
+
esac
|
|
146
|
+
installed="$bin_dir/pls"
|
|
147
|
+
|
|
148
|
+
if [ ! -x "$installed" ]; then
|
|
149
|
+
echo "install.sh: $installer finished but $installed is missing." >&2
|
|
150
|
+
echo " Look for the tool in $bin_dir and report this at the project's issue tracker." >&2
|
|
151
|
+
exit 1
|
|
152
|
+
fi
|
|
153
|
+
echo "▸ installed: $installed"
|
|
154
|
+
|
|
155
|
+
if ! command -v pls >/dev/null 2>&1; then
|
|
156
|
+
echo "▸ $bin_dir is not on your PATH. Add it:"
|
|
157
|
+
echo " export PATH=\"$bin_dir:\$PATH\""
|
|
158
|
+
elif [ "$(command -v pls)" != "$installed" ]; then
|
|
159
|
+
# A different pls earlier on PATH would silently win over this install.
|
|
160
|
+
echo "▸ note: the 'pls' on your PATH is $(command -v pls), not this one."
|
|
161
|
+
fi
|
|
162
|
+
|
|
163
|
+
case "$installer" in
|
|
164
|
+
uv) echo "▸ upgrade: uv tool upgrade pehloo-shell" ;;
|
|
165
|
+
pipx) echo "▸ upgrade: pipx upgrade pehloo-shell" ;;
|
|
166
|
+
*) echo "▸ upgrade: $PY -m pip install --user --upgrade pehloo-shell" ;;
|
|
167
|
+
esac
|
|
168
|
+
|
|
169
|
+
cat <<'EOF'
|
|
170
|
+
|
|
171
|
+
▸ next: pls "list files by size" — the first run sets up your model backend
|
|
172
|
+
▸ local: llama.cpp, Ollama, LM Studio or vLLM (any OpenAI-compatible server)
|
|
173
|
+
▸ hosted: Cerebras · Groq · OpenRouter
|
|
174
|
+
▸ config: ~/.pehloo/shell-config.json · re-run setup with `pls --setup`
|
|
175
|
+
EOF
|