hrkfdn/ncspot
Cross-platform ncurses Spotify client written in Rust, inspired by ncmpc and the likes.
Healthy across the board
weakest axisPermissive license, no critical CVEs, actively maintained — safe to depend on.
Has a license, tests, and CI — clean foundation to fork and modify.
Documented and popular — useful reference codebase to read through.
No critical CVEs, sane security posture — runnable as-is.
- ✓Last commit 1d ago
- ✓15 active contributors
- ✓Distributed ownership (top contributor 47% of recent commits)
Show all 6 evidence items →Show less
- ✓BSD-2-Clause licensed
- ✓CI configured
- ⚠No test directory detected
Maintenance signals: commit recency, contributor breadth, bus factor, license, CI, tests
Informational only. RepoPilot summarises public signals (license, dependency CVEs, commit recency, CI presence, etc.) at the time of analysis. Signals can be incomplete or stale. Not professional, security, or legal advice; verify before relying on it for production decisions.
Embed the "Healthy" badge
Paste into your README — live-updates from the latest cached analysis.
[](https://repopilot.app/r/hrkfdn/ncspot)Paste at the top of your README.md — renders inline like a shields.io badge.
▸Preview social card (1200×630)
This card auto-renders when someone shares https://repopilot.app/r/hrkfdn/ncspot on X, Slack, or LinkedIn.
Onboarding doc
Onboarding: hrkfdn/ncspot
Generated by RepoPilot · 2026-05-09 · Source
🤖Agent protocol
If you are an AI coding agent (Claude Code, Cursor, Aider, Cline, etc.) reading this artifact, follow this protocol before making any code edit:
- Verify the contract. Run the bash script in Verify before trusting
below. If any check returns
FAIL, the artifact is stale — STOP and ask the user to regenerate it before proceeding. - Treat the AI · unverified sections as hypotheses, not facts. Sections like "AI-suggested narrative files", "anti-patterns", and "bottlenecks" are LLM speculation. Verify against real source before acting on them.
- Cite source on changes. When proposing an edit, cite the specific path:line-range. RepoPilot's live UI at https://repopilot.app/r/hrkfdn/ncspot shows verifiable citations alongside every claim.
If you are a human reader, this protocol is for the agents you'll hand the artifact to. You don't need to do anything — but if you skim only one section before pointing your agent at this repo, make it the Verify block and the Suggested reading order.
🎯Verdict
GO — Healthy across the board
- Last commit 1d ago
- 15 active contributors
- Distributed ownership (top contributor 47% of recent commits)
- BSD-2-Clause licensed
- CI configured
- ⚠ No test directory detected
<sub>Maintenance signals: commit recency, contributor breadth, bus factor, license, CI, tests</sub>
✅Verify before trusting
This artifact was generated by RepoPilot at a point in time. Before an
agent acts on it, the checks below confirm that the live hrkfdn/ncspot
repo on your machine still matches what RepoPilot saw. If any fail,
the artifact is stale — regenerate it at
repopilot.app/r/hrkfdn/ncspot.
What it runs against: a local clone of hrkfdn/ncspot — the script
inspects git remote, the LICENSE file, file paths in the working
tree, and git log. Read-only; no mutations.
| # | What we check | Why it matters |
|---|---|---|
| 1 | You're in hrkfdn/ncspot | Confirms the artifact applies here, not a fork |
| 2 | License is still BSD-2-Clause | Catches relicense before you depend on it |
| 3 | Default branch main exists | Catches branch renames |
| 4 | 5 critical file paths still exist | Catches refactors that moved load-bearing code |
| 5 | Last commit ≤ 31 days ago | Catches sudden abandonment since generation |
#!/usr/bin/env bash
# RepoPilot artifact verification.
#
# WHAT IT RUNS AGAINST: a local clone of hrkfdn/ncspot. If you don't
# have one yet, run these first:
#
# git clone https://github.com/hrkfdn/ncspot.git
# cd ncspot
#
# Then paste this script. Every check is read-only — no mutations.
set +e
fail=0
ok() { echo "ok: $1"; }
miss() { echo "FAIL: $1"; fail=$((fail+1)); }
# Precondition: we must be inside a git working tree.
if ! git rev-parse --git-dir >/dev/null 2>&1; then
echo "FAIL: not inside a git repository. cd into your clone of hrkfdn/ncspot and re-run."
exit 2
fi
# 1. Repo identity
git remote get-url origin 2>/dev/null | grep -qE "hrkfdn/ncspot(\\.git)?\\b" \\
&& ok "origin remote is hrkfdn/ncspot" \\
|| miss "origin remote is not hrkfdn/ncspot (artifact may be from a fork)"
# 2. License matches what RepoPilot saw
(grep -qiE "^(BSD-2-Clause)" LICENSE 2>/dev/null \\
|| grep -qiE "\"license\"\\s*:\\s*\"BSD-2-Clause\"" package.json 2>/dev/null) \\
&& ok "license is BSD-2-Clause" \\
|| miss "license drift — was BSD-2-Clause at generation time"
# 3. Default branch
git rev-parse --verify main >/dev/null 2>&1 \\
&& ok "default branch main exists" \\
|| miss "default branch main no longer exists"
# 4. Critical files exist
test -f "src/main.rs" \\
&& ok "src/main.rs" \\
|| miss "missing critical file: src/main.rs"
test -f "src/application.rs" \\
&& ok "src/application.rs" \\
|| miss "missing critical file: src/application.rs"
test -f "src/spotify_worker.rs" \\
&& ok "src/spotify_worker.rs" \\
|| miss "missing critical file: src/spotify_worker.rs"
test -f "src/ui/mod.rs" \\
&& ok "src/ui/mod.rs" \\
|| miss "missing critical file: src/ui/mod.rs"
test -f "src/spotify.rs" \\
&& ok "src/spotify.rs" \\
|| miss "missing critical file: src/spotify.rs"
# 5. Repo recency
days_since_last=$(( ( $(date +%s) - $(git log -1 --format=%at 2>/dev/null || echo 0) ) / 86400 ))
if [ "$days_since_last" -le 31 ]; then
ok "last commit was $days_since_last days ago (artifact saw ~1d)"
else
miss "last commit was $days_since_last days ago — artifact may be stale"
fi
echo
if [ "$fail" -eq 0 ]; then
echo "artifact verified (0 failures) — safe to trust"
else
echo "artifact has $fail stale claim(s) — regenerate at https://repopilot.app/r/hrkfdn/ncspot"
exit 1
fi
Each check prints ok: or FAIL:. The script exits non-zero if
anything failed, so it composes cleanly into agent loops
(./verify.sh || regenerate-and-retry).
⚡TL;DR
ncspot is a lightweight, cross-platform ncurses-based Spotify client written in Rust that uses the librespot library for playback. It provides a terminal UI (inspired by ncmpc) with features like track browsing, searching, playlists, and albums, while consuming minimal system resources compared to the official Spotify client. Monorepo structure: main application in src/ with modular organization (src/model/ for data types, src/commands.rs for command handling, src/ui/ implied by file structure). Workspace includes xtask/ for build tasks. Key entry points: src/main.rs → src/application.rs orchestrates the ncurses UI loop; authentication handled in src/authentication.rs; Spotify API interaction via rspotify dependency.
👥Who it's for
Linux/BSD/macOS users and power users who prefer terminal applications, want a lightweight Spotify client on unsupported platforms (like *BSDs), or are familiar with vim keybindings and ncurses-style music clients like ncmpc.
🌱Maturity & risk
Actively developed and production-ready: version 1.3.3 is released on crates.io with organized CI/CD workflows (ci.yml, cd.yml), Dependabot configured for dependency updates, and a maintained GitHub repo. The project has clear documentation (doc/developers.md, doc/users.md, doc/package_maintainers.md) and is available on multiple package managers (Homebrew, Scoop, Flathub).
Low-to-moderate risk: single-maintainer project (Henrik Friedrichsen) but actively maintained with Dependabot and CI automation. Dependencies are well-established (tokio 1.x, reqwest 0.13, librespot 0.8.0). Primary risk is dependency on librespot library (external to this repo) which could face breaking changes; requires Spotify Premium account (not free tier compatible).
Active areas of work
Active maintenance with recent Rust toolchain updates (update-rust-toolchain.yml workflow), Dependabot managing dependencies, and CI passing on pull requests. The project uses edition 2024 and aggressive optimization profiles (LTO enabled in release builds).
🚀Get running
git clone https://github.com/hrkfdn/ncspot.git
cd ncspot
rustup install
cargo build --release
./target/release/ncspot
Requirements: Rust toolchain and Python 3 (per README). See doc/developers.md for detailed build instructions.
Daily commands:
cargo build
cargo run --release
Optional: use profile.optimized for faster iteration (codegen-units 16 vs 1). Config file location: ~/.config/ncspot (TOML format per doc/users.md).
🗺️Map of the codebase
src/main.rs— Application entry point; initializes ncspot, sets up event loop, and orchestrates the main UI lifecycle.src/application.rs— Core application state machine; manages UI navigation, event handling, and all user interactions.src/spotify_worker.rs— Async worker thread managing Spotify API communication and librespot authentication; decouples network I/O from UI.src/ui/mod.rs— Central UI module router; coordinates rendering of all screen components (tabs, views, modals).src/spotify.rs— Spotify client wrapper around librespot; abstracts playback control and session management.Cargo.toml— Workspace configuration with librespot, ncurses, and tokio dependencies; defines release optimization profile.
🛠️How to make changes
Add a new UI tab
- Create a new module under src/ui/mynew_tab.rs with a struct implementing the DrawableComponent trait. (
src/ui/mynew_tab.rs) - Add a variant to the DrawableComponent union in src/ui/mod.rs and route its draw() call. (
src/ui/mod.rs) - Register the new tab in the TabbedView in src/ui/tabbedview.rs by adding it to the tabs vector. (
src/ui/tabbedview.rs) - Add keybinds for tab navigation (e.g., 't' for new tab) in src/commands.rs. (
src/commands.rs) - Update the help text in src/ui/help.rs to document the new tab. (
src/ui/help.rs)
Add a new Spotify API endpoint call
- Define a new async method in src/spotify_api.rs that uses reqwest to hit the Spotify Web API. (
src/spotify_api.rs) - Create or extend a model in src/model/*.rs to deserialize the response JSON. (
src/model/mod.rs) - Wrap the API call in src/spotify_worker.rs as a non-blocking tokio task and emit a WorkerEvent. (
src/spotify_worker.rs) - Handle the WorkerEvent in src/application.rs and update UI state. (
src/application.rs)
Add a new user command (e.g., favorite track)
- Add a new Command variant in src/command.rs. (
src/command.rs) - Map a keybind to the command in src/config.rs or the default keybinds section. (
src/config.rs) - Implement the action in src/commands.rs as a match arm dispatching to spotify_worker. (
src/commands.rs) - Add the Spotify API call in src/spotify_api.rs (e.g., PUT /v1/me/tracks). (
src/spotify_api.rs)
Add a new configuration option
- Add a new field to the Config struct in src/config.rs with a default value. (
src/config.rs) - Update the TOML schema documentation in doc/users.md. (
doc/users.md) - Use the config value in src/theme.rs or src/application.rs to alter behavior. (
src/application.rs)
🔧Why these technologies
- Rust + tokio — Memory-safe systems language with async runtime for non-blocking I/O; enables lightweight terminal client suitable for embedded/BSD platforms.
- ncurses — Terminal UI standard; provides cross-platform portable rendering without heavy dependencies (vs Electron).
- librespot — Open-source Spotify client library; provides native playback and session management without depending on proprietary Spotify desktop app.
- serde + reqwest — serde handles JSON deserialization from Spotify Web API; reqwest provides ergonomic async HTTP client.
- MPRIS D-Bus (Linux) — Allows desktop media key integration and external player control on Linux/X11 systems.
⚖️Trade-offs already made
-
Spotify Premium only
- Why: librespot playback requires active subscription; avoids complexity of fallback to free-tier limitations.
- Consequence: Non-premium users cannot use ncspot for playback; must use official client or web player.
-
Async worker thread (spotify_worker.rs) instead of blocking UI
- Why: Keeps UI responsive during network calls and authentication.
- Consequence: Added complexity of event-driven architecture and message passing between threads; requires careful state synchronization.
-
NCurses over TUI/Ratatui
- Why: Direct C binding provides stable, proven rendering on all POSIX systems (BSD compatibility).
- Consequence: Manual terminal state management; less ergonomic than higher-level Rust TUI frameworks; harder to theme dynamically.
-
Single-instance enforcement via IPC (src/ipc.rs)
- Why: Prevents multiple ncspot processes fighting over audio device.
- Consequence: Users cannot run multiple ncspot sessions; adds Unix socket boilerplate.
🪤Traps & gotchas
Spotify Premium account required (free accounts will not work with librespot). Build requires Python 3 in addition to Rust (likely for build.rs script). Unix-specific signal handling (signal-hook) only on Unix targets. IPC socket communication in src/ipc.rs may require understanding of tokio codecs and serialization. Authentication via OAuth requires internet connectivity; librespot protocol may break on Spotify API changes. Config directory respects platform-dirs conventions, not always ~/.config/ncspot on all systems.
🏗️Architecture
💡Concepts to learn
- ncurses/pancurses — ncspot's entire UI layer depends on pancurses (Rust binding to ncurses); understanding terminal control codes and window management is essential for UI modifications
- OAuth 2.0 Authorization Code Flow — src/authentication.rs implements Spotify OAuth for user login; critical for understanding how credentials are obtained and sessions are maintained
- librespot Protocol — ncspot delegates all Spotify streaming to librespot, which implements Spotify's proprietary protocol; breaking changes in Spotify's backend can affect playback
- Tokio Async Runtime — Core concurrency model: src/application.rs uses tokio::main macro and manages async tasks for network I/O, playback, and UI events without blocking
- IPC (Inter-Process Communication) Sockets — src/ipc.rs enables remote control of ncspot from other processes via Unix sockets; critical for understanding the remote control feature
- CBOR Serialization — serde_cbor dependency used for compact binary serialization of library cache and state; smaller footprint than JSON for resource-constrained environments
- Vim Keybindings in Terminal UIs — ncspot defaults to vim-style key mappings (hjkl navigation, etc.); understanding how these are mapped in src/commands.rs is important for UI/UX contributions
🔗Related repos
librespot-org/librespot— Core Spotify playback library dependency; ncspot is a UI wrapper around librespot's streaming capabilitiesrspotify-rs/rspotify— Rust Spotify Web API client that ncspot uses for metadata and search queriessputnik22/spotify-tui— Alternative Rust-based Spotify TUI client; similar problem domain but different architectural approachmusicpd/ncmpc— Original ncurses MPD client that inspired ncspot's UI design and keybindings philosophyaome510/spotify-player— Another Rust Spotify terminal player offering comparable feature set with different UI framework choices
🪄PR ideas
To work on one of these in Claude Code or Cursor, paste:
Implement the "<title>" PR idea from CLAUDE.md, working through the checklist as the task list.
Add integration tests for Spotify API authentication flow in src/spotify_api.rs
The repo has authentication logic spread across src/authentication.rs and src/spotify_api.rs, but there are no visible test files for the OAuth flow or token refresh mechanisms. Given that librespot-oauth is a core dependency and authentication failures are common user issues, adding integration tests would prevent regressions and help new contributors understand the auth flow.
- [ ] Create tests/spotify_auth_integration_tests.rs for OAuth token acquisition
- [ ] Add tests for token refresh logic in src/spotify_api.rs
- [ ] Test authentication error handling and edge cases (expired tokens, network failures)
- [ ] Document test setup requirements in doc/developers.md
Add platform-specific CI workflow for Windows/macOS builds in .github/workflows/
The repo claims to be 'cross-platform' and supports Unix systems (signal-hook is Unix-only), but ci.yml and cd.yml likely only test Linux. Windows and macOS builds are critical for a TUI client, especially since pancurses behavior differs per platform. Adding platform-specific workflows would catch platform-specific bugs early.
- [ ] Examine existing .github/workflows/ci.yml for current test matrix
- [ ] Create separate workflow jobs for Windows (MSVC) and macOS (Clang) builds
- [ ] Test pancurses functionality on each platform (cursors, key handling)
- [ ] Document platform-specific setup in doc/developers.md if needed
Refactor src/ui/ directory: Extract contextmenu and cover rendering into separate modules with unit tests
The src/ui/ directory has multiple .rs files (album.rs, artist.rs, browse.rs, contextmenu.rs, cover.rs) that likely contain rendering logic, but there are no visible unit tests for UI components. The contextmenu.rs and cover.rs modules appear self-contained and would benefit from isolated unit tests covering rendering, input handling, and state transitions.
- [ ] Create tests/ui_contextmenu_tests.rs with tests for menu item selection, navigation, and rendering
- [ ] Create tests/ui_cover_tests.rs with tests for image fallbacks and display edge cases
- [ ] Add helper utilities in src/ui/mod.rs for testable UI component abstractions
- [ ] Document UI testing patterns in doc/developers.md
🌿Good first issues
- Add integration tests for src/model/ types (album.rs, artist.rs, episode.rs, playable.rs) to verify serialization/deserialization from Spotify API responses; currently no test coverage visible in provided file list
- Document the IPC socket protocol and add examples in doc/users.md#ipc-commands showing remote control usage via src/ipc.rs
- Expand src/cli.rs argument parsing coverage: add missing --help details and validation examples for configuration options, cross-referencing doc/users.md#configuration
⭐Top contributors
Click to expand
Top contributors
- @dependabot[bot] — 47 commits
- @hrkfdn — 28 commits
- @github-actions[bot] — 9 commits
- @ThomasFrans — 4 commits
- @AnAngryRaven — 2 commits
📝Recent commits
Click to expand
Recent commits
c083f6b— chore(deps): bump rustls-webpki from 0.103.10 to 0.103.12 (dependabot[bot])15eb83a— chore(deps): bump rand from 0.10.0 to 0.10.1 (dependabot[bot])5f57f51— chore(deps): bump softprops/action-gh-release (dependabot[bot])4c2ea92— chore(deps): bump rustls-webpki from 0.103.8 to 0.103.10 (dependabot[bot])1de5ec1— chore(deps): bump the cargo group with 3 updates (dependabot[bot])8ec225f— [create-pull-request] automated change (github-actions[bot])89d0421— chore(deps): bump quinn-proto from 0.11.13 to 0.11.14 (dependabot[bot])09c9092— chore(deps): bump the cargo group with 3 updates (dependabot[bot])9c9573e— chore(deps): bump chrono from 0.4.43 to 0.4.44 in the cargo group (dependabot[bot])ad4efc0— test: add queue unit tests, fix shuffle append bug (hrkfdn)
🔒Security observations
The ncspot codebase has a critical build configuration issue (invalid Rust edition 2024) that would prevent compilation and hide security warnings. The dependency tree is generally well-maintained with Rust/librespot ecosystem libraries. Primary concerns are: (1) the invalid edition declaration breaking compiler safety checks, (2) incomplete rspotify feature configuration, (3) lack of explicit security documentation for API interactions and token handling, and (4) wide version constraints on low-level dependencies like libc. The codebase uses tokio for async operations and interacts with the Spotify API, requiring careful attention to input validation and credential handling. No hardcoded secrets, SQL injection risks, or obvious XSS vectors were identified in the visible file structure, which is appropriate for a TUI application.
- High · Outdated Rust Edition Declaration —
Cargo.toml - [workspace.package]. The workspace declares edition = '2024' which does not exist. Valid Rust editions are 2015, 2018, and 2021. This will cause compilation failures and may hide security-related compiler warnings and checks that would normally be enforced in valid editions. Fix: Change 'edition = "2024"' to 'edition = "2021"' to use the latest stable Rust edition with security improvements and modern safety features. - Medium · Incomplete rspotify Dependency Configuration —
Cargo.toml - [dependencies.rspotify]. The rspotify dependency is declared with default-features disabled and an empty features list. This could result in missing security-relevant features or functionality, and the incomplete configuration suggests the build may not function as intended. Fix: Explicitly specify required features for rspotify (e.g., 'client', 'oauth', etc.) or document why features are intentionally empty. Verify this doesn't disable important security features. - Medium · Libc Dependency Without Pinned Version —
Cargo.toml - libc dependency. The libc crate (version 0.2.183) is a fundamental low-level dependency that interfaces with the operating system. Using a major version constraint allows for automatic updates to new minor/patch versions which could introduce security regressions or breaking changes affecting memory safety. Fix: Consider using a more specific version constraint like '=0.2.183' or regularly audit libc updates. Monitor libc security advisories closely given its critical nature. - Medium · No TOML Version Pinning for Parse_Duration —
Cargo.toml - parse_duration dependency. The parse_duration dependency (2.1.1) uses major version constraint allowing breaking changes that could affect parsing security or correctness of duration inputs from user configuration. Fix: Test and validate parse_duration updates thoroughly. Consider pinning to a specific patch version '=2.1.1' if the dependency has had security issues. - Low · Missing Security Headers and Input Validation Documentation —
Documentation - developers.md, users.md. While this is a TUI application not a web service, the codebase uses reqwest for HTTP requests and interacts with Spotify API. There is no visible security guidance documentation in the codebase regarding input validation, API security, or token handling. Fix: Add security documentation covering: API token handling best practices, input validation approaches, secure configuration storage, and security vulnerability reporting procedures. - Low · Signal Handler Setup Without Visible Synchronization Primitives —
src/ - signal-hook integration point. The codebase uses signal-hook for Unix signal handling but the synchronization approach between signal handlers and the async tokio runtime is not immediately evident from file structure. Fix: Review signal-hook usage in main.rs and application.rs to ensure proper synchronization with the tokio runtime. Use signal-hook's crossbeam-channel integration to safely communicate signals.
LLM-derived; treat as a starting point, not a security audit.
👉Where to read next
- Open issues — current backlog
- Recent PRs — what's actively shipping
- Source on GitHub
Generated by RepoPilot. Verdict based on maintenance signals — see the live page for receipts. Re-run on a new commit to refresh.