# soundcli puts your YouTube and Spotify libraries on disk and plays them from an Ink terminal UI

> An npm package called sndcli that downloads tracks from YouTube, SoundCloud and Spotify into a folder inside your Music directory and plays them from the keyboard. Installation is one npx command, but the pieces that do the actual work are yt-dlp and mpv, which the app fetches for itself.

**baairon/soundcli** — Own your music. Download your YouTube, SoundCloud, and Spotify libraries to your computer and play them offline, all from your terminal.

- Repository: https://github.com/baairon/soundcli
- Website: https://www.npmjs.com/package/sndcli
- Stars: 513 · Forks: 46
- Language: TypeScript
- License: MIT
- Published: 2026-09-20 · Updated: 2026-09-20 · Language: en
- Canonical page: https://hysenlabs.com/projects/baairon-soundcli

## npx sndcli is the install, and it fetches its own tools

There is no global install step and no configuration file to write. Install Node.js, open a terminal, paste one line:

```sh
npx sndcli
```

From there the application sets itself up, downloading the few tools it needs on first run. That detail matters more than it sounds, because the components doing the real work are not npm packages at all. The project is TypeScript with an Ink terminal user interface, riding on yt-dlp and mpv, so the fetching and playback come from two external binaries the app manages for you. The package itself is published to npm as `sndcli` rather than `soundcli`, with the binary mapped to `./dist/index.js`, and it declares `node >=22` in its engines field. Only the `dist` and `preview` directories are shipped in the tarball, so the repository's `src/`, `test/` and `scripts/` folders never reach your machine.

## The first run picks a folder, then asks where the music comes from

The opening screen answers the question you would otherwise have to guess at, which is where the files land. You are shown a dedicated folder inside your computer's own Music directory, so the location is visible from the first launch rather than buried in a config file. After that it asks for the source, and the choices are YouTube, SoundCloud or Spotify. You supply either a username or a pasted link, and the link can point at a playlist, an album, or a single track. Downloading starts immediately rather than after a confirmation pass over the whole library, so you can start listening while the rest of your collection is still arriving. That single behaviour decision is the difference between a tool you try once and one you keep, since a first run over a large library would otherwise appear to hang.

## Public Spotify playlists work with no sign-in at all

The accepted inputs are wider than the three-way source choice suggests. Beyond playlists and albums, you can paste an artist profile, your likes, or one song, and each is treated as a starting point for a download. The one authentication note is specific: public Spotify playlists and albums work without signing in, which removes the account step for the most common case. For the other sources you identify yourself by username or link rather than by logging in anywhere, and the privacy section is emphatic that there are no accounts and no logins at all. Tracks arrive in their original quality with album artwork and artist details attached, and they are sorted into folders automatically, so the result is a usable library tree rather than a flat directory of files with opaque names.

## It resumes, it never re-downloads, and you can rename in place

Three behaviours do the heavy lifting for daily use. The first is deduplication: the same song is never downloaded twice, which matters when your starting points overlap, as playlists and artist profiles usually do. The second is resumption. If you close the app part way through, the next run picks up where it left off instead of starting the batch again, so a large library can be fetched in stages without babysitting. The third is that once a track is saved it stays, with no expiry check and no revalidation against the source. Tidy up happens in the interface rather than in a file manager, since tracks and playlists can both be renamed from within the app, which keeps whatever folder scheme the tool chose internally intact.

## One contextual footer and a ? cheatsheet, never a wall of commands

Playback is keyboard driven throughout, and the interface makes a deliberate choice about how much of the keymap to show. A bar along the bottom of the screen lists only the keys that matter for the current context, so there is nothing to memorise for ordinary use. Pressing `?` brings up the full list whenever you want to see everything. The same restraint is written into the contribution rules as a hard requirement rather than a preference: keep the UI surface minimal, one contextual footer plus the `?` cheatsheet, never a wall of commands. Anyone adding a feature is expected to respect that. The rest of the pre-pull-request checklist is concrete: run `npm test`, run `npm run typecheck`, and write commits in Conventional Commits style with the `fix:`, `feat:`, `docs:`, `chore:` and `refactor:` prefixes, then open the pull request against `main`.

## Five runtime dependencies, and the tools are not among them

The dependency list explains the shape of the project. Runtime packages number five: `ink` for the terminal UI, `react` at version 19 as the rendering layer, `execa` for spawning the external tools, `env-paths` for locating per-OS config and cache directories, and `string-width` for laying out the footer correctly in a terminal. Notably absent is anything to do with audio decoding or with talking to YouTube, SoundCloud or Spotify directly, because yt-dlp and mpv do both jobs outside Node. Development is where the rest of the tooling sits: `tsup` for bundling, `vitest` with `ink-testing-library` for the interface tests, `tsx` for running TypeScript directly during development, and `typescript` at version 6. Running from source locally means `npm install` then `npm run dev`, which is `tsx src/index.tsx`, while the bundled path is `npm run build` followed by `npm start`.

## Three network reasons, and one of them is tool upkeep

soundcli runs on your computer and nowhere else, with no accounts, no logins, and nothing tracking what you play. The claim is specific rather than absolute, because the app does connect to the internet, for three stated reasons: to download the music you ask for, to set itself up the first time, and to keep its own tools current so downloads keep working. Everything else stays local. That third reason deserves a second look, since maintaining the yt-dlp and mpv binaries over the network is a condition of the app continuing to work at all, which is a supply chain surface even though it is not a tracking surface. Filesystem side, per-OS directories are resolved through `env-paths`, so the downloaded library and any cached state land in each platform's conventional locations rather than inside the project folder, and the first run shows you the Music folder path explicitly. The licence is MIT, and the repository itself carries only a `LICENSE`, a `.gitignore`, a `package-lock.json` and the source directories, with no documentation site and no changelog file at the root.

## prepublishOnly checks dist imports before anything reaches npm

The `package.json` scripts are arranged so that the wrong thing cannot ship easily. `prepublishOnly` runs the build and then a separate script, `check-dist-imports.ts`, so a bad import in the compiled output is caught before publication rather than after. `previews` renders assets through `scripts/render-previews.ts`, which explains the `preview/` directory in the shipped files list, and `clean` is absent while `build` and `start` are the only runtime paths. Repository structure is correspondingly lean: `src/`, `test/`, `scripts/`, `preview/`, a `tsconfig.json`, `tsup.config.ts` and `vitest.config.ts`, with no CI configuration beyond a `.github/` directory and no documentation site. Version history is short and recent: v1.4.0 and v1.4.1 both on 2026-07-15, then v1.5.0 on 2026-08-19, which is also the last push to `main`. The package is MIT licensed, published with public access, and authored as `bairon`.

## Conclusion

soundcli fits a specific person: someone who wants their own files rather than another streaming subscription, is comfortable in a terminal, and would rather press a key than reach for a mouse. The resume behaviour, the never-download-twice guarantee and the auto-sorted folders are what make it usable day to day, and a five dependency runtime keeps the install light. It is the wrong tool if you want a graphical library browser or continuous playlist sync, and the documentation says nothing about the terms of the services it pulls from, which is the first thing to check before you point it at a library. Before installing, confirm Node 22 or newer, expect the first run to download yt-dlp and mpv on your behalf, and note that the newest tag is v1.5.0 from 2026-08-19 with no release since.

## FAQ

### How do I install and start soundcli?

Install Node.js, open a terminal and run npx sndcli. On first launch the app downloads the tools it needs and sets itself up. The package on npm is named sndcli, its binary maps to ./dist/index.js, and it declares node >=22 as its engine requirement.

### Does soundcli need a SoundCloud, YouTube or Spotify login?

No accounts or logins are used. Public Spotify playlists and albums work without signing in, and for other sources you provide a username or paste a link. The privacy section states that soundcli connects to the internet for three reasons only: downloading the music you ask for, first-time setup, and keeping its own tools current.

### What happens if soundcli is closed in the middle of a download?

The next run picks up where it left off instead of starting over, and the same song is never downloaded twice, which matters when playlists and artist profiles overlap. Once a track is saved it stays, with album artwork and artist details, and tracks and playlists can be renamed from the interface.

### Which external tools does soundcli depend on?

yt-dlp for fetching and mpv for playback. Both sit outside npm: the project is TypeScript with an Ink terminal UI and only five runtime dependencies, namely ink, react, execa, env-paths and string-width. The first run downloads those tools automatically.

## Sources

- [baairon/soundcli on GitHub](https://github.com/baairon/soundcli)
- [License: MIT](https://github.com/baairon/soundcli/blob/main/LICENSE)
- [Project website](https://www.npmjs.com/package/sndcli)
- [README](https://github.com/baairon/soundcli/blob/main/README.md)
- [Releases](https://github.com/baairon/soundcli/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/baairon-soundcli
