- new PromptHints action row shared by the folder and trackers prompts
- download-to prompt shows the torrent name and size, enter reads "download"
- export torrent rebound from T to s and centralized in the store
- footer trimmed to essentials, d/D and z detail live in the ? sheet
- z keeps FitGirl and SubsPlease rows: their feeds carry no seeder data,
a new per-source reportsHealth flag marks seeders: 0 as unknown, not dead
- version 1.3.0
The o key sets one global download folder, so routing a single result
somewhere else (a movie to the NAS, a game to the big drive) meant
changing the setting and changing it back. D on a result now opens a
"download to" prompt for just that item; d stays a one-keypress
download to the default.
The queue already stores a dir per item, so resume, re-seed and
download-again honor the choice with no new plumbing. The prompt
reuses FolderPrompt (new title prop), pre-fills with the last D folder
this session so batches cost one typed path, and nothing new is
persisted. The ? sheet gains d/D entries (d was missing entirely) and
the README now mentions both o and D, which it never documented.
One behavior fix rides along because D exposed it: queue.add() used to
ignore the requested dir when re-adding a failed item, so redirecting
a failed download (the disk-full case D exists for) silently retried
into the old folder. add() now adopts the requested dir on failed
retry, dropping resume progress only when the folder actually changed;
covered in queue.add.test.ts with the engine mocked. The f retry key
goes through queue.retry() and keeps the item's own folder, unchanged.
D on an already-active item says "Already in queue" instead of
claiming a folder that wouldn't be used.
The detail pane renders `infoHash` and `magnet` verbatim, whereas the
result name is passed through `cleanText()`. A malicious or hijacked
indexer can embed control bytes — e.g. an OSC-52 clipboard-write
sequence — in a magnet or info hash; those bytes are then written to the
terminal when the user opens that result's detail pane.
Add `stripControl()` (removes C0/C1 controls and DEL) and apply it to
both fields at render. Unlike `cleanText()`, it preserves the exact
remaining characters — no whitespace folding, NFC, or "Untitled"
fallback — which is correct for identifiers and URLs.
macOS mDNSResponder permanently occupies UDP port 5350, which is the
NAT-PMP client port that webtorrent binds when initializing its NAT
traversal. The bind fails asynchronously with EADDRINUSE, and since
the PMP client is a raw EventEmitter with no error listener, it
surfaces as an uncaughtException that kills the app the moment a
download starts.
NAT-PMP can never succeed on macOS because the port is permanently
taken, so disable it on darwin only and let UPnP handle NAT traversal
instead. Other platforms are unaffected.
Closes#22
Move the results panel height into a tested resultsPanelOuter helper so the view stays one row short of the overflow:hidden body, matching Downloads and Seeding. Fixes#21.
The postbuild step was using cp and chmod, which only work on Unix. On Windows npm run build would run tsup successfully but then fail at the cp command, leaving dist/cli.cjs missing. Since the package.json bin field points there, npx torlnk would just say the command isn't recognized.I replaced the shell one-liner with scripts/postbuild.mjs
Small Node script that copies cli-entry.cjs to dist/cli.cjs and marks it executable. chmod is a no-op on Windows but still does the right thing for the published package, so the output is identical across Windows, macOS, and Linux.
Parse the "Date uploaded" field from 1337x detail pages into TorrentResult.added, matching the other sources. Reuses the detail-page fetch already done for the magnet, so no extra requests are made.
Restore the seeders-desc default ordering that the new sort feature dropped,
so the healthiest torrents stay on top until `s` is pressed. Trim the
contextual footer (terse labels, folder key in the `?` overlay only) so the
added sort and folder keys never wrap it past one line. Gate the
download-folder editor on its own `editingFolder` flag instead of the shared
capture state, fixing the leak where `?`, `q`, and `m` fired through while
typing a path.
Add an `o` key that opens an inline prompt to set the download folder,
shown in the `?` cheatsheet and the sidebar footer. Reuses Panel +
TextField and the captureMode/effective-region mechanism the help
overlay already uses, so section keymaps stay quiet while typing a path.
No new Store field (editingFolder is local App state, like showHelp).
expandHome resolves a leading ~/ and ~\ so a path pasted from another
OS still works; the folder is created with fs.mkdir(recursive) and the
change fails soft, keeping the old folder if it cannot be created. The
setting only feeds new downloads (queue.add reads it at add-time), so
in-progress downloads and existing seeds keep their own dir and are
never interrupted or relocated.
The npm name and command become torlnk (npx torlnk) to clear npm's
"too similar to comlink" guard, mirroring the sndcli/soundcli split. torlink
stays the brand everywhere it matters: the logo wordmark, app and terminal
title, version string, the data and downloads folder (APP_NAME), and the repo.
Updates the cli usage lines, readme install commands, and the user-agent
package url to match.
Reword the c help labels (Downloads "Cancel or remove from list", Seeding
"Remove from list", dropping the Downloads cross-reference) and add a help
note that downloaded files always stay on disk, so cancel and remove never
read as deleting your media. Also trims the over-specific c sentence from the
readme and refreshes the npm description and keywords.
torlink is a TUI, so Get started now keeps just the single npx torlink launch
and frames searching, pasting a magnet, browsing, and the ? cheatsheet as
in-app keypresses, instead of opening with magnet/torrent args and a global
install.
Finished downloads now keep seeding automatically and you opt out per item with
p, reusing the live torrent instead of tearing it down. Resume and restore seed
from the .torrent metadata captured at download time, so they verify the local
file immediately rather than depending on re-fetching metadata from the swarm.
Quitting now flushes queue, history, and seeds synchronously and exits
immediately, fixing the hang when quitting while seeding or with a paused seed.
Paused seeds persist their state and stay paused across restarts. Hide the
hardware cursor on the splash, and clarify that c removes a finished item the
same way in Downloads and Seeding.
Tests are isolated from the real data dir via a TORLINK_STATE_DIR override plus
vitest config, so runs never touch user data. Adds scripts/verify-seeding.ts
(npm run verify:seeding), an offline-deterministic harness proving seed, pause,
and resume transfer real bytes.
Curated, multi-source torrent search and download from the terminal:
concurrent search across built-in sources with streamed, source-tagged
results; a download-only webtorrent engine with true pause/resume and
restart recovery; magnet paste; and a calm Ink + React TUI. No indexer
server, no setup.