ndl 0.1.10

A minimal TUI client for Threads
ndl-0.1.10 is not a library.

ndl - needle

A minimal TUI client for Threads - stay aware of notifications without the distractions of a full social media interface.

ndl screenshot

Why needle?

Social media notifications can pull you out of flow state. needle lets you:

  • Monitor your feed in a lightweight terminal interface
  • Quickly check and respond without opening a browser
  • Keep your focus while staying connected

Features

  • Vim-style navigation - h, j, k, l for intuitive movement
  • Two-panel layout - Threads list on left, detail view on right (swappable)
  • Thread feed - View your threads with auto-refresh every 15 seconds
  • Nested replies - See replies to threads, including replies-to-replies (2 levels deep)
  • Quick replies - Respond to threads without leaving the terminal
  • Post new threads - Create new posts directly from the terminal
  • Media type indicators - Reposts, images, videos, and carousels clearly labeled
  • Minimal footprint - Runs in a terminal, no Electron bloat

Project Structure

This is a Cargo workspace with two binaries and a shared library:

  • ndl - The TUI client
  • ndld - OAuth server for hosted authentication (keeps client_secret secure on server)
  • ndl-core - Shared library for OAuth types and token exchange

Installation

cargo install ndl

From source

git clone https://github.com/pgray/ndl
cd ndl
cargo build --release --workspace

On Linux, the build uses wild linker for faster builds. Install with cargo install wild-linker and ensure clang is available.

Configuration

needle requires a Threads API access token. See OAUTH.md for detailed setup instructions.

Default: Hosted Auth

By default, ndl uses the hosted auth server at ndl.pgray.dev - no setup required:

ndl login

Custom Auth Server

To use a different auth server:

# Via environment variable
export NDL_OAUTH_ENDPOINT=https://your-ndld-server.com
ndl login

# Or add to ~/.config/ndl/config.toml:
# auth_server = "https://your-ndld-server.com"

Local OAuth

If you have your own Threads API credentials and want to run OAuth locally:

# Set empty endpoint to disable hosted auth
export NDL_OAUTH_ENDPOINT=""
export NDL_CLIENT_ID=your_client_id
export NDL_CLIENT_SECRET=your_client_secret
ndl login

Logout

ndl logout

Version

ndl --version

Config is stored at ~/.config/ndl/config.toml.

Running the Auth Server (ndld)

If you want to host your own OAuth server:

export NDL_CLIENT_ID=your_client_id
export NDL_CLIENT_SECRET=your_client_secret
export NDLD_PUBLIC_URL=https://your-domain.com  # Must match Threads app redirect URI
export NDLD_PORT=8080  # Optional, defaults to 8080

cargo run -p ndld

With Let's Encrypt (ACME)

Automatic TLS certificates via Let's Encrypt:

export NDLD_ACME_DOMAIN=ndl.example.com
export NDLD_ACME_EMAIL=admin@example.com
export NDLD_ACME_DIR=/var/lib/ndld/acme  # Optional, for cert persistence
export NDLD_PORT=443
cargo run -p ndld

Set NDLD_ACME_STAGING=1 to use Let's Encrypt staging environment for testing.

With Manual TLS

export NDLD_TLS_CERT=/path/to/cert.pem
export NDLD_TLS_KEY=/path/to/key.pem
cargo run -p ndld

Docker Compose (Recommended)

cp .env.example .env
# Edit .env with your credentials

# Create data directory with correct ownership (ndld runs as UID 10001)
sudo mkdir -p /ndld-data
sudo chown 10001:10001 /ndld-data

docker compose up -d

Docker

docker build -f ndld/Dockerfile -t ndld .
docker run -p 8080:8080 \
  -e NDL_CLIENT_ID=your_client_id \
  -e NDL_CLIENT_SECRET=your_client_secret \
  -e NDLD_PUBLIC_URL=https://your-domain.com \
  ndld

For Let's Encrypt in Docker:

# Create data directory with correct ownership (ndld runs as UID 10001)
sudo mkdir -p /var/lib/ndld
sudo chown 10001:10001 /var/lib/ndld

docker run -p 443:443 \
  -e NDL_CLIENT_ID=your_client_id \
  -e NDL_CLIENT_SECRET=your_client_secret \
  -e NDLD_PUBLIC_URL=https://your-domain.com \
  -e NDLD_PORT=443 \
  -e NDLD_ACME_DOMAIN=your-domain.com \
  -e NDLD_ACME_EMAIL=admin@your-domain.com \
  -v /var/lib/ndld:/var/lib/ndld \
  ndld

For manual TLS in Docker:

docker run -p 443:443 \
  -e NDL_CLIENT_ID=your_client_id \
  -e NDL_CLIENT_SECRET=your_client_secret \
  -e NDLD_PUBLIC_URL=https://your-domain.com \
  -e NDLD_PORT=443 \
  -e NDLD_TLS_CERT=/certs/cert.pem \
  -e NDLD_TLS_KEY=/certs/key.pem \
  -v /path/to/certs:/certs:ro \
  ndld

The server exposes:

  • GET / - Landing page with project info
  • GET /privacy-policy - Privacy policy
  • GET /tos - Terms of service
  • POST /auth/start - Start OAuth session
  • GET /auth/callback - OAuth callback (configure in Threads app)
  • GET /auth/poll/{session_id} - Poll for auth completion
  • GET /health - Health check

Usage

ndl

Keybindings

Key Action
j/Down Move down
k/Up Move up
h/Left Focus threads panel
l/Right Focus detail panel
t Swap panel positions
p Post new thread
r Reply to selected thread
R Refresh feed
Enter Select / focus detail
Esc Back / cancel
? Toggle help
q Quit

Roadmap

  • OAuth login with auto-generated localhost certs
  • Hosted OAuth server (ndld) for secure credential management
  • View threads feed
  • View thread details with nested replies
  • Reply to threads
  • Post new threads
  • Auto-refresh (15s)
  • Like/repost actions
  • Media preview (images)

Privacy

ndl and ndld do not track, collect, or store any personal information. See PRIVACY.md for details.

License

MIT

References