Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ dist/
.DS_Store
package-lock.json
!nix/package-lock.json
/diagnostics/
62 changes: 31 additions & 31 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,39 +1,39 @@
# torhunt

A sleek, zero-setup torrent finder and downloader that lives right in your terminal.
A fast, distraction-free torrent search engine and downloader built for your terminal.

Finding a torrent these days sucks. One site is a minefield of fake download buttons. Another hides the real link under a popup that spawns two more tabs. And after all that, half the results are dead, zero seeders.
Modern torrent searching is broken. Most indexers are cluttered with intrusive ads, misleading download buttons, and dead magnet links.

torhunt fixes that. One search checks a short, curated list of reputable sources at once, and whatever you pick downloads straight to your computer. No browser, no ads, no nonsense. The files are yours, saved to your downloads folder.
torhunt cuts through the noise. A single search queries a curated selection of reliable sources simultaneously, streaming results straight to your terminal and downloading directly to your system. No browser tabs, no popups, no hassle.

## Get started
## Quick start

1. **Install Node** (from [nodejs.org](https://nodejs.org)), it's all torhunt needs.
1. **Install Node.js** (v22+ from [nodejs.org](https://nodejs.org)).
2. **Open your terminal.**
3. **Start it:**
3. **Launch:**

```sh
npx torhunt
```

That's it. torhunt opens straight to a search bar: search for what you want, paste in a magnet link or a bare infohash, or just press Enter on an empty box to browse the curated library. From there it's all keypresses — nothing to memorize, and `?` brings up the full list anytime.
That's all it takes. torhunt opens directly to an interactive search prompt: type your query, paste a magnet link or infohash, or press Enter on an empty query to browse curated picks. Everything is controlled via simple hotkeys — press `?` anytime for the full keymap.

## Features

- **Instant search** — type and hit Enter. Results stream in from every source, tagged with size and peer count so you can see what'll come down fast.
- **One-key downloads** — arrow to what you want and press `d` to save it, or `D` to pick a different folder for just that download.
- **Background downloads** — keep searching while downloads run. Queue up as many as you want.
- **Resume on restart** — anything interrupted picks up where it left off.
- **Seeding controls** — finished downloads seed automatically. Pause or stop anytime from the Seeding tab.
- **Completed library** — every finished download is archived in the Completed tab, grouped by date (Today, Yesterday, Older).
- **Settings panel** — change your download folder, color theme, and spinner style from within the app.
- **Global search** — press `/` from anywhere to jump straight to search.
- **Customizable themes** — multiple built-in color themes to match your terminal aesthetic.
- **Quality filters** — filter results by quality (4K, 1080p, 720p, x265, FLAC, FitGirl) with a single keypress.
- **Concurrent search** — Query multiple indexers in parallel with real-time streaming results tagged by file size and live peer counts.
- **One-key downloads** — Navigate to any item and press `d` to start downloading, or `D` to choose a custom target directory.
- **Non-blocking queue** — Continue searching and browsing while transfers run in the background.
- **State auto-resume** — Interrupted downloads pick up seamlessly where they left off after a restart.
- **Seeding manager** — Finished downloads seed automatically. Pause, resume, or stop seeding anytime from the Seeding view.
- **Completed archive** — Organized record of finished downloads grouped by date (Today, Yesterday, Older).
- **In-app settings** — Change your download directory, color theme, and spinner animation on the fly.
- **Instant navigation** — Press `/` from any view to jump straight to the search field.
- **Custom visual themes** — Hand-crafted color palettes to match your terminal environment.
- **Quality filters** — Filter results by resolution and format (4K, 1080p, 720p, x265, FLAC, FitGirl) with a single keypress.

## What it searches
## Indexer sources

A short, hand-picked list of trusted sources:
torhunt queries a curated list of trusted sources by category:

| Category | Sources |
| --- | --- |
Expand All @@ -42,24 +42,24 @@ A short, hand-picked list of trusted sources:
| TV | EZTV, The Pirate Bay, 1337x, BitTorrented |
| Anime | Nyaa, SubsPlease |

Games are the only category that can run code, so they come from FitGirl alone, a repacker with a long, trusted track record. Everything else is plain video and subtitles. If a source is down, the search carries on without it, and torhunt tells you which one is offline.
Game downloads are restricted to FitGirl due to their verified repack safety track record. Movies, TV, and anime sources cover clean video and audio streams. If any source is offline, search continues smoothly while displaying a status indicator.

## Headless mode
## Headless & server modes

torhunt also runs without the TUI, for servers and seedboxes:
torhunt includes headless background daemons for servers and seedboxes:

```
torhunt watch <dir> download anything dropped into a folder
torhunt serve take magnets over HTTP
torhunt files stream finished downloads over HTTP
torhunt attach keep the TUI alive across ssh sessions
```sh
torhunt watch <dir> download torrents or magnets dropped into a folder
torhunt serve HTTP API for remote magnet submission
torhunt files range-aware HTTP server for media streaming
torhunt attach persistent tmux session for remote SSH usage
```

Add `--daemon` to keep watch, serve, or files running after you log out. Run `torhunt --help` for the full list of modes and flags.
Append `--daemon` to run `watch`, `serve`, or `files` as background processes. Run `torhunt --help` for all commands and flags.

## Privacy
## Privacy & security

Your files stay on your disk, and nothing routes through a central server — torhunt only talks to the torrent network directly. Once a download finishes it keeps seeding by default, sharing it back so the next person can find it too. Opt out anytime from the Seeding tab.
torhunt connects directly to the BitTorrent P2P swarm. No telemetry, tracking, or proxying through third-party servers. All files land on your local storage, and seeding behavior can be toggled anytime.

## Development

Expand All @@ -70,7 +70,7 @@ npm install
npm run dev
```

`npm run dev` runs the live TUI through tsx, no build step needed. To build and run the bundled version:
`npm run dev` launches the live TUI via `tsx`. To build and execute the production bundle:

```sh
npm run build
Expand Down
4 changes: 2 additions & 2 deletions flake.nix
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
description = "Torlink is a torrent finder that lives in your terminal, with zero setup and nothing to configure.";
description = "Torhunt is a fast, distraction-free torrent search engine and downloader built for your terminal.";

inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
Expand All @@ -26,7 +26,7 @@
}
);
overlays.default = final: prev: {
torlink = final.callPackage ./nix/package.nix { };
torhunt = final.callPackage ./nix/package.nix { };
};

};
Expand Down
8 changes: 4 additions & 4 deletions nix/README.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
# Flake install
Add this repo to your ```flake.nix```. The package is built using the unstable channel. You can overwrite this by setting ```inputs.nixpkgs.follows = "nixpkgs"``` (if your default is 26.05).

**The binary is executed as ```torlnk```.**
**The binary is executed as ```torhunt```.**

```nix
inputs = {
...
torlink.url = "github:baairon/torlink";
torhunt.url = "github:pseudoshell/torhunt";
...
}
```
Expand All @@ -21,7 +21,7 @@ You can install the package in either home.nix or your configuration.nix dependi
{
home.packages = with pkgs; [
...
inputs.torlink.packages.${pkgs.system}.default
inputs.torhunt.packages.${pkgs.system}.default
...
];
}
Expand All @@ -35,7 +35,7 @@ You can install the package in either home.nix or your configuration.nix dependi
{
environment.systemPackages = with pkgs; [
...
inputs.torlink.packages.${pkgs.system}.default
inputs.torhunt.packages.${pkgs.system}.default
...
];
}
Expand Down
20 changes: 10 additions & 10 deletions nix/package.nix
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,11 @@ let
in

buildNpmPackage (finalAttrs: {
pname = "torlink";
version = "1.4.1";
pname = "torhunt";
version = "1.12.0";
src = fetchFromGitHub {
owner = "baairon";
repo = "torlink";
owner = "pseudoshell";
repo = "torhunt";
tag = "v${finalAttrs.version}";
hash = "sha256-VXfYzwjhSS+zZCnGoRUCVGgmuRaV5KeYASASM4E9Xj4=";
};
Expand All @@ -72,7 +72,7 @@ buildNpmPackage (finalAttrs: {

# build node-datachannel, and wrap clipboard
postInstall = ''
pushd $out/lib/node_modules/torlnk/node_modules/node-datachannel
pushd $out/lib/node_modules/torhunt/node_modules/node-datachannel

# link shared nixpkgs openssl
substituteInPlace CMakeLists.txt \
Expand All @@ -98,7 +98,7 @@ buildNpmPackage (finalAttrs: {
popd

# wrap clipboard
wrapProgram $out/bin/torlnk \
wrapProgram $out/bin/torhunt \
--prefix PATH : ${
lib.makeBinPath [
wl-clipboard
Expand All @@ -108,12 +108,12 @@ buildNpmPackage (finalAttrs: {
'';

meta = {
description = "Torlink is a torrent finder that lives in your terminal, with zero setup and nothing to configure.";
homepage = "https://github.com/baairon/torlink";
changelog = "https://github.com/baairon/torlink/releases/tag/${finalAttrs.src.tag}";
description = "Torhunt is a fast, distraction-free torrent search engine and downloader built for your terminal.";
homepage = "https://github.com/pseudoshell/torhunt";
changelog = "https://github.com/pseudoshell/torhunt/releases/tag/${finalAttrs.src.tag}";
license = lib.licenses.mit;
maintainers = with lib.maintainers; [ ghastrum ];
mainProgram = "torlnk";
mainProgram = "torhunt";
platforms = lib.platforms.linux;
};
})
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "torhunt",
"version": "1.9.1",
"description": "A sleek, zero-setup torrent finder and downloader that lives right in your terminal.",
"version": "2.0.3",
"description": "A fast, distraction-free torrent search engine and downloader built for your terminal.",
"type": "module",
"bin": {
"torhunt": "dist/cli.cjs"
Expand Down
12 changes: 6 additions & 6 deletions scripts/cli-entry.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
var major = parseInt(process.versions.node.split('.')[0], 10);
if (major < 22) {
process.stderr.write(
'\ntorlnk requires Node.js v22 or later.\n' +
'\ntorhunt requires Node.js v22 or later.\n' +
'You are running v' + process.versions.node + '.\n\n' +
'Upgrade: https://nodejs.org\n' +
'With nvm: nvm install 22 && nvm use 22\n\n'
Expand Down Expand Up @@ -35,22 +35,22 @@ try {
},
});
process.stderr.write(
'torlnk: WebRTC peers unavailable (native module not installed); ' +
'TCP/UDP peers still work. https://github.com/baairon/torlink/issues/60\n'
'torhunt: WebRTC peers unavailable (native module not installed); ' +
'TCP/UDP peers still work. https://github.com/pseudoshell/torhunt/issues/60\n'
);
} else {
// Node 22.0 to 22.14 has no module.registerHooks, so the eager import
// cannot be redirected; a clear explanation beats the raw module error.
process.stderr.write(
'\ntorlnk needs the WebRTC native module (node-datachannel), and it is\n' +
'not installed. Either upgrade to Node 22.15+ (torlnk then runs\n' +
'\ntorhunt needs the WebRTC native module (node-datachannel), and it is\n' +
'not installed. Either upgrade to Node 22.15+ (torhunt then runs\n' +
'without WebRTC peers), or install the build tools and reinstall:\n' +
' Fedora: sudo dnf install cmake gcc-c++ openssl-devel libstdc++-static\n' +
' Debian / Ubuntu: sudo apt install cmake g++ libssl-dev\n' +
' macOS: xcode-select --install\n' +
' Windows: install CMake and Visual Studio Build Tools\n' +
'On npm 12, also allow install scripts: npm approve-scripts\n\n' +
'https://github.com/baairon/torlink/issues/60\n\n'
'https://github.com/pseudoshell/torhunt/issues/60\n\n'
);
process.exit(1);
}
Expand Down
12 changes: 6 additions & 6 deletions scripts/ensure-webrtc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ const { execSync } = require('node:child_process');
const { existsSync } = require('node:fs');
const { resolve } = require('node:path');

// During postinstall the CWD is always torlink's root directory.
// During postinstall the CWD is always torhunt's root directory.
// Check whether the native module actually loads; if prebuild-install
// succeeded on its own (Node 18/20) there is nothing to do. require()
// resolves through any node_modules layout, so this check is layout-proof.
Expand All @@ -28,7 +28,7 @@ if (!moduleDir) {
process.exit(0); // nothing installed, nothing to build
}

console.error('\ntorlnk: building WebRTC native module from source.\n');
console.error('\ntorhunt: building WebRTC native module from source.\n');

try {
execSync('npx --yes cmake-js build', {
Expand All @@ -38,18 +38,18 @@ try {
timeout: 300000,
});
} catch {
// Warn but never fail the install: torlink works without WebRTC peers
// Warn but never fail the install: torhunt works without WebRTC peers
// (TCP/uTP swarms still connect), so a missing toolchain must not brick
// `npm install`.
console.error('');
console.error('torlnk: could not build the WebRTC native module.');
console.error('torlnk still works; WebRTC peers just stay unavailable.');
console.error('torhunt: could not build the WebRTC native module.');
console.error('torhunt still works; WebRTC peers just stay unavailable.');
console.error('To enable them, install the build tools, then reinstall:');
console.error(' Fedora: sudo dnf install cmake gcc-c++ openssl-devel libstdc++-static');
console.error(' Debian / Ubuntu: sudo apt install cmake g++ libssl-dev');
console.error(' macOS: xcode-select --install');
console.error(' Windows: install CMake and Visual Studio Build Tools');
console.error('');
console.error('https://github.com/baairon/torlink/issues/60');
console.error('https://github.com/pseudoshell/torhunt/issues/60');
}
process.exit(0);
4 changes: 3 additions & 1 deletion scripts/render-previews-impl.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,8 @@ function makeStore(
const noop = (): void => {};
return {
config: {
downloadDir: "~/Downloads/torlink",
downloadDir: "~/Downloads/torhunt",
categorySubfolders: true,
theme: "electric-cyan",
spinner: "dots",
trackers: [],
Expand Down Expand Up @@ -125,6 +126,7 @@ function makeStore(
openThemePicker: noop,
openSpinnerPicker: noop,
openFolderPicker: noop,
openQrModal: noop,
searchModeTrigger: 0,
triggerSearch: noop,
bookmarks: [],
Expand Down
6 changes: 3 additions & 3 deletions scripts/verify-seeding.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/**
* Proves torlink's seeding actually transfers bytes (not just a UI label), and
* Proves torhunt's seeding actually transfers bytes (not just a UI label), and
* that pause and resume really stop/restart it.
*
* Offline-deterministic: the leechers run with dht/tracker/lsd OFF and are
Expand Down Expand Up @@ -86,15 +86,15 @@ async function waitSeederReady(
}

async function main(): Promise<void> {
const root = await fs.mkdtemp(path.join(os.tmpdir(), "torlink-seedcheck-"));
const root = await fs.mkdtemp(path.join(os.tmpdir(), "torhunt-seedcheck-"));
const seedDir = path.join(root, "seed");
await fs.mkdir(seedDir, { recursive: true });
const filePath = path.join(seedDir, "payload.bin");
await fs.writeFile(filePath, randomBytes(FILE_BYTES));
log(`payload: ${filePath} (${FILE_BYTES} bytes)`);

// Mint the .torrent metadata for the on-disk file via a throwaway client, then
// drop it. This is what torlink now captures at download time and seeds from.
// drop it. This is what torhunt now captures at download time and seeds from.
const minter = new WebTorrent({ dht: false, tracker: false, lsd: false });
const { meta, infoHash } = await new Promise<{ meta: Uint8Array; infoHash: string }>(
(resolve) => {
Expand Down
10 changes: 5 additions & 5 deletions src/cli/args.ts
Original file line number Diff line number Diff line change
Expand Up @@ -156,23 +156,23 @@ after it finishes (e.g. 1h, 30m, 90s, 2d); files are kept by default. Add
--daemon (watch/serve/files): background the process (own session, logs to a
file), so you can log out and it keeps running. Prints the pid and log path.

torlnk attach: run the TUI inside a persistent tmux session. Detach with
tmux's ctrl-b d, log out, then torlnk attach again to reattach where you
torhunt attach: run the TUI inside a persistent tmux session. Detach with
tmux's ctrl-b d, log out, then torhunt attach again to reattach where you
left off. Downloads and seeds keep running while detached.

serve mode (no TUI): a small HTTP API for handing torlink a magnet.
serve mode (no TUI): a small HTTP API for handing torhunt a magnet.
POST /add {"magnet":"..."} queue a magnet or info hash
GET /downloads list active downloads and seeds
GET /health liveness (no auth)
flags: --port <n> (default 9161), --host <addr> (default 127.0.0.1),
--token <secret> (required to bind a public --host; or TORLINK_API_TOKEN),
--token <secret> (required to bind a public --host; or TORHUNT_API_TOKEN),
--to <dir> (where files land).

files mode (no TUI): a read-only, range-aware HTTP server over the downloads
folder, so finished files stream to a browser or media player.
GET / list the folder (JSON)
GET /<path> stream a file (supports Range for seeking/resuming)
flags: --port <n> (default 9160), --host <addr> (default 127.0.0.1),
--token <secret> (required to bind a public --host; or TORLINK_FILES_TOKEN),
--token <secret> (required to bind a public --host; or TORHUNT_FILES_TOKEN),
--dir <dir> (folder to serve; defaults to your downloads folder).
`;
Loading
Loading