Zero-dependency Python CLI for sharing local directories over HTTP with authentication, file browsing, and ZIP downloads
Project description
neev
Zero-dependency Python CLI for sharing local directories over HTTP — with auth, file browsing, ZIP downloads, and uploads.
Table of Contents
- Why neev?
- Install
- Quick Start
- CLI Reference
- Configuration File (
neev.toml) - Environment Variables
- Configuration Precedence
- Features
- HTTP API
- Security Model
- Recipes
- Architecture
- Development
- Troubleshooting
- FAQ
- Contributing
- License
Why neev?
python -m http.server is great, but it has no authentication, no way to download a folder, no uploads, and exposes dotfiles by default. neev is the drop-in replacement with the things you actually need:
- HTTP Basic Auth — constant-time credential comparison
- ZIP folder downloads — streamed on the fly, no temp files
- File uploads — opt-in, with size limits and path-traversal protection
- Clean browser UI — dark/light theme, Markdown preview, syntax-aware file icons
- HTTP Range support — resumable downloads, video/audio seeking
- Concurrent requests — threaded server, not single-request-at-a-time
- Secure defaults — localhost-only, no writes, no hidden files
- Zero dependencies — pure Python stdlib, Python 3.11+
Built for developers sharing build artifacts, teams exchanging files on a LAN, and anyone who wants http.server but grown up.
Install
From PyPI (recommended)
# with uv (fastest)
uv tool install neev
# or pipx
pipx install neev
# or plain pip
pip install neev
Run without installing
uvx neev --dir ./public
From source
git clone https://github.com/prabhuakshay/neev
cd neev
uv sync
uv run neev --help
Requires Python 3.11 or newer. Works on Linux, macOS, and Windows.
Quick Start
# Serve the current directory on http://127.0.0.1:8000
neev
# Serve a specific directory
neev ./public
# With auth, on all interfaces, custom port
neev ./share --host 0.0.0.0 --port 8080 --auth alice:s3cret
# Full-featured: auth + uploads + zip downloads
neev ./share --auth alice:s3cret --enable-upload --enable-zip-download
Open the URL printed on startup. You'll see a file browser. If auth is enabled, your browser will prompt for credentials.
CLI Reference
neev [DIRECTORY] [OPTIONS]
Positional arguments
| Argument | Default | Description |
|---|---|---|
directory |
. |
Directory to serve. Must exist. Resolved to its real path (symlinks followed). |
Options
| Flag | Default | Description |
|---|---|---|
--host HOST |
127.0.0.1 |
Address to bind. Use 0.0.0.0 to expose on LAN. |
--port PORT, -p PORT |
8000 |
TCP port (1–65535). |
--auth USER:PASS |
(none) | Enable HTTP Basic Auth. Equivalent to NEEV_AUTH env var. |
--show-hidden / --no-show-hidden |
off | Show dotfiles and neev.toml in listings. |
--enable-zip-download / --no-enable-zip-download |
off | Allow folders to be downloaded as streamed ZIP. |
--max-zip-size MB |
100 |
Maximum ZIP archive size in MB. Rejected if exceeded. |
--enable-upload / --no-enable-upload |
off | Allow multipart file uploads from the browser. |
--read-only / --no-read-only |
off | Force-disable uploads (overrides --enable-upload). |
--banner TEXT |
(none) | Message displayed at the top of directory listings. |
-h, --help |
— | Show help and exit. |
All boolean flags use argparse.BooleanOptionalAction, so --no-<flag> works too — useful for overriding neev.toml from the CLI.
Examples
# Read-only share even if TOML enables uploads
neev ./build --read-only
# Serve on LAN with auth, show dotfiles, custom banner
neev ./code --host 0.0.0.0 --auth me:pw --show-hidden --banner "Internal only"
# Raise ZIP size cap to 500 MB
neev ./data --enable-zip-download --max-zip-size 500
Configuration File (neev.toml)
neev looks for a neev.toml file in the served directory and merges it with CLI args. CLI flags always win.
Example
# ./neev.toml
host = "0.0.0.0"
port = 9000
show-hidden = false
enable-zip-download = true
max-zip-size = 250
enable-upload = false
read-only = false
banner = "Build artifacts — ask #devops for access"
Recognized keys
| TOML key | Type | Notes |
|---|---|---|
host |
string | Same as --host. |
port |
integer | Same as --port. |
auth |
string | "user:pass". Same as --auth. |
show-hidden |
bool | Same as --show-hidden. |
enable-zip-download |
bool | Same as --enable-zip-download. |
max-zip-size |
integer | MB. Same as --max-zip-size. |
enable-upload |
bool | Same as --enable-upload. |
read-only |
bool | Same as --read-only. |
banner |
string | Same as --banner. |
Denied keys: directory is never read from TOML (the served directory is always set by CLI, to avoid surprise path changes).
Unknown keys are logged at WARN level and ignored. Malformed TOML is logged and the file is skipped — neev keeps running with CLI defaults.
The neev.toml file itself is hidden from listings unless --show-hidden is set.
Environment Variables
| Variable | Purpose |
|---|---|
NEEV_AUTH |
Credentials as user:pass. Alternative to --auth. |
--auth beats NEEV_AUTH when both are set.
Configuration Precedence
From highest to lowest:
- CLI flags (explicit
--foo bar) NEEV_AUTHenv var (auth only)neev.tomlin the served directory- Built-in defaults (see CLI reference table)
Features
File browser
- Directory listing with sortable columns (name, size, modified).
- Per-file icons by extension/type.
- Breadcrumb navigation.
- In-browser preview for text files, Markdown (rendered), images, and PDFs.
- Correct MIME types and charset detection per file.
ZIP folder downloads
- Triggered by a "Download as ZIP" button on any folder (if
--enable-zip-download). - Streamed — no temp files, constant memory usage.
- Capped at
--max-zip-sizeMB; oversized archives return413 Payload Too Large. - Hidden files excluded unless
--show-hidden.
Uploads
- Opt-in via
--enable-upload. Disabled under--read-only. - Multipart upload form on each directory page.
- Filename sanitization — path traversal, null bytes, and absolute paths rejected.
- Writes land in the directory currently being viewed.
- Authentication (if configured) is required.
HTTP Range requests
Partial content (206) supported for all file downloads — resumable downloads, video/audio seeking, curl -C -.
Concurrency
Threaded HTTPServer — multiple clients can browse, download, and upload simultaneously.
Authentication
- HTTP Basic Auth.
- Credentials compared with
hmac.compare_digest(constant-time). - Failed auth returns
401with aWWW-Authenticate: Basicchallenge.
HTTP API
neev is primarily browser-driven, but every interaction is a plain HTTP request — scriptable with curl, wget, httpie, etc.
Listing / serving files
GET /path/to/dir/ → HTML directory listing
GET /path/to/file.txt → file contents (with Range support)
Trailing slash → directory; no slash → file. Requests to a path without a trailing slash that resolves to a directory are redirected with 301 to the canonical slashed URL.
ZIP download
GET /path/to/dir/?zip=1
Returns application/zip stream. Filename is <dirname>.zip. Disabled unless --enable-zip-download.
File preview (rendered)
GET /path/to/file.md?preview=1
Returns HTML with Markdown rendered server-side. Works for text and Markdown files.
Upload
POST /path/to/dir/
Content-Type: multipart/form-data; boundary=...
<file field named "file">
Returns 303 See Other redirect to the directory on success, 4xx on validation failure. Disabled unless --enable-upload and not --read-only.
Example with curl:
curl -u "$USER_PASS" -F "file=@./report.pdf" http://localhost:8000/uploads/
Authentication
Every endpoint honors Basic Auth when enabled:
curl -u "$USER_PASS" http://localhost:8000/
# or (equivalent) — build the header yourself
curl -H "Authorization: Basic $(printf '%s' "$USER_PASS" | base64)" http://localhost:8000/
Status codes
| Code | When |
|---|---|
200 OK |
Successful GET of file/listing. |
206 Partial Content |
Successful Range request. |
301 Moved Permanently |
Directory URL missing trailing slash. |
303 See Other |
Successful upload. |
400 Bad Request |
Malformed request / upload. |
401 Unauthorized |
Auth required or invalid credentials. |
403 Forbidden |
Path traversal, access denied, or writes on read-only. |
404 Not Found |
Path doesn't exist or hidden without --show-hidden. |
405 Method Not Allowed |
e.g. POST when uploads disabled. |
413 Payload Too Large |
ZIP exceeds --max-zip-size. |
416 Range Not Satisfiable |
Invalid Range header. |
500 Internal Server Error |
Unexpected server fault. |
Security Model
What neev protects against:
- Path traversal — every request resolves
os.path.realpath()and verifies the result is a descendant of the served directory.../, symlink escapes, and absolute paths are rejected with403. - Credential leaks in timing —
hmac.compare_digestfor both username and password. - Upload filename injection — rejects traversal, null bytes, and absolute paths.
- Accidental exposure — defaults are localhost-only, no writes, no dotfiles, no ZIPs.
- Oversized ZIP abuse — streaming ZIPs are bounded by
--max-zip-size.
What neev does NOT do:
- No HTTPS/TLS. Run behind a reverse proxy (nginx, Caddy) for internet-facing deployments.
- No rate limiting. Use a reverse proxy or firewall.
- No user management — single Basic Auth pair, one shared credential.
- No virus scanning on uploads.
- No audit log beyond stdout request logging.
If exposing to a network or internet:
- Always set
--auth. - Use a strong password (treat as a bearer token).
- Put it behind a TLS-terminating proxy.
- Prefer
--read-onlyunless uploads are required. - Keep
--show-hiddenoff.
Recipes
Share a build artifact with a coworker
neev ./dist --host 0.0.0.0 --auth reviewer:pleasecheck
# share: http://<your-ip>:8000/
Drop box — let someone upload files to you
mkdir -p ~/inbox
neev ~/inbox --host 0.0.0.0 --auth sender:pw --enable-upload
Static preview site with Markdown rendering
neev ./docs --enable-zip-download --banner "Project docs"
Behind Caddy with TLS
share.example.com {
reverse_proxy localhost:8000
}
neev /srv/share --auth alice:s3cret --enable-zip-download
Systemd service
# /etc/systemd/system/neev.service
[Unit]
Description=neev file server
After=network.target
[Service]
ExecStart=/usr/local/bin/neev /srv/share --host 127.0.0.1 --port 8000 --enable-zip-download
Environment=NEEV_AUTH=alice:s3cret
Restart=on-failure
User=neev
[Install]
WantedBy=multi-user.target
Docker one-liner
docker run --rm -p 8000:8000 -v "$PWD:/srv:ro" python:3.13-slim \
sh -c "pip install neev && neev /srv --host 0.0.0.0 --read-only"
Architecture
src/neev/
├── cli.py # argparse, validation, entry point
├── config.py # frozen Config dataclass
├── toml_config.py # neev.toml loader + CLI merge
├── server.py # HTTPServer wiring (threaded)
├── server_core.py # main request dispatcher
├── server_auth.py # HTTP Basic Auth
├── server_preview.py # file/Markdown preview
├── server_upload.py # multipart upload handler
├── server_zip.py # streaming ZIP response
├── server_assets.py # bundled static assets (CSS, JS)
├── server_utils.py # Range, redirects, MIME helpers
├── fs.py # path-safe filesystem ops
├── auth.py # constant-time credential check
├── upload.py # upload validation
├── upload_multipart.py # multipart body parsing
├── zip.py # streaming zip generator
├── url_utils.py # URL encoding/decoding
├── log.py # logging helpers + ANSI styling
├── html*.py # HTML templating (no jinja — pure stdlib)
└── static/ # compiled CSS bundled with package
Design pillars:
- Stdlib only. Every feature is built on
http.server,zipfile,argparse,html,tomllib,base64,hmac. - Defense-in-depth on paths. Every filesystem op goes through
fs.pywhich resolves and verifies containment. - Frozen config.
Configis built once at startup; request handlers never mutate configuration state. - No dynamic imports, no plugin system. Small, auditable surface area.
- Files under 300 lines. Enforced by convention — keeps modules focused.
Development
Uses uv for everything.
Setup
git clone https://github.com/prabhuakshay/neev
cd neev
uv sync
Run
uv run neev --help
uv run neev ./tests/fixtures --enable-zip-download
Test
uv run pytest # full suite with coverage (≥95% enforced)
uv run pytest -k upload # filter
uv run pytest --no-cov -x # fast iteration
Lint & type check
uv run ruff check .
uv run ruff format .
uv run ty check
Pre-commit hooks (prek)
uv run prek install
uv run prek run --all-files
The pre-commit config also recompiles the Tailwind CSS bundle when any html_*.py changes.
Build & publish
uv build # produces sdist + wheel in dist/
# Publishing is automated: create a GitHub Release → PyPI workflow uploads.
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
Address already in use |
Port taken | Pick a different --port or kill the prior process. |
| Browser keeps re-prompting for password | Wrong credentials or auth off on restart | Verify --auth / NEEV_AUTH, clear browser cache. |
403 Forbidden on a file that exists |
Path-traversal guard / symlink escape | The file isn't inside the served directory's real path. |
ZIP download returns 413 |
Folder exceeds --max-zip-size |
Raise the cap or download individual files. |
Uploads return 405 |
--enable-upload not set, or --read-only on |
Enable uploads and disable read-only. |
| Dotfiles don't appear | Hidden by default | Use --show-hidden. |
neev.toml takes effect unexpectedly |
Merged from served directory | Check startup banner; override with CLI flags. |
FAQ
Does neev support HTTPS? No. Use a reverse proxy (Caddy, nginx, Traefik) for TLS termination.
Can I serve multiple directories?
Not in one process. Run multiple neev instances on different ports.
Is it safe to expose to the internet?
Only behind HTTPS + auth + ideally --read-only. For anything mission-critical, reach for a proper server.
Why "neev"? "Neev" (नींव) means foundation in Hindi — a simple base to build file sharing on.
Does it work on Windows? Yes. Python 3.11+ required.
What about WebDAV / S3 / FTP? Out of scope. neev is HTTP + browser UI only.
Contributing
Issues and PRs welcome at github.com/prabhuakshay/neev.
Before submitting:
uv run ruff check . && uv run ruff format --check .
uv run ty check
uv run pytest
See CLAUDE.md for the full development philosophy (stability > features, stdlib only, files < 300 lines, etc.).
License
MIT © Akshay Prabhu
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 neev-0.1.0.tar.gz.
File metadata
- Download URL: neev-0.1.0.tar.gz
- Upload date:
- Size: 77.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
73a6d463a5da0cd09bcf1f7a6992517a28b44e62663dc5aa26c5e5b7b039e0f3
|
|
| MD5 |
6cf6f8ddc7fce7b27d0ce2ed87bcfac2
|
|
| BLAKE2b-256 |
3da7924508587052c09fe9aa7c09b8e689aa30f7246baa475208ff5dc4b239bf
|
Provenance
The following attestation bundles were made for neev-0.1.0.tar.gz:
Publisher:
publish.yml on prabhuakshay/neev
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
neev-0.1.0.tar.gz -
Subject digest:
73a6d463a5da0cd09bcf1f7a6992517a28b44e62663dc5aa26c5e5b7b039e0f3 - Sigstore transparency entry: 1280530888
- Sigstore integration time:
-
Permalink:
prabhuakshay/neev@6c80459f9a571770e243053e518c725b322ecaa7 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/prabhuakshay
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@6c80459f9a571770e243053e518c725b322ecaa7 -
Trigger Event:
release
-
Statement type:
File details
Details for the file neev-0.1.0-py3-none-any.whl.
File metadata
- Download URL: neev-0.1.0-py3-none-any.whl
- Upload date:
- Size: 91.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
dced5403bbb6e5264ea6d735b191cbd05cdbd55bb7125ff01ee6b52152990fe2
|
|
| MD5 |
2f1e32beedb97edbd2eed73f597ef782
|
|
| BLAKE2b-256 |
4a1de5f0b59d822f1617468a1acddbf8540b4169642bbd4ffd2a6e6c1ff20a86
|
Provenance
The following attestation bundles were made for neev-0.1.0-py3-none-any.whl:
Publisher:
publish.yml on prabhuakshay/neev
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
neev-0.1.0-py3-none-any.whl -
Subject digest:
dced5403bbb6e5264ea6d735b191cbd05cdbd55bb7125ff01ee6b52152990fe2 - Sigstore transparency entry: 1280530889
- Sigstore integration time:
-
Permalink:
prabhuakshay/neev@6c80459f9a571770e243053e518c725b322ecaa7 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/prabhuakshay
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@6c80459f9a571770e243053e518c725b322ecaa7 -
Trigger Event:
release
-
Statement type: