From 49c6b62843c13499fafc8ec0b29af113bee69b6c Mon Sep 17 00:00:00 2001 From: AK CHAVAN <90214281+iamakchavan@users.noreply.github.com> Date: Mon, 10 Aug 2026 19:40:37 +0530 Subject: [PATCH 01/24] fix: restore bin path prefix for npm --- package.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 53dda28..f3c1f4c 100644 --- a/package.json +++ b/package.json @@ -48,11 +48,11 @@ "license": "MIT", "repository": { "type": "git", - "url": "git+https://github.com/baairon/torlink.git" + "url": "git+https://github.com/pseudoshell/torhunt.git" }, - "homepage": "https://torlink.bairon.dev", + "homepage": "https://github.com/pseudoshell/torhunt", "bugs": { - "url": "https://github.com/baairon/torlink/issues" + "url": "https://github.com/pseudoshell/torhunt/issues" }, "publishConfig": { "access": "public" From 74176b01d44c994db01ab232b3c1c31f0feafc3b Mon Sep 17 00:00:00 2001 From: AK CHAVAN <90214281+iamakchavan@users.noreply.github.com> Date: Mon, 10 Aug 2026 19:41:36 +0530 Subject: [PATCH 02/24] 1.6.1 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index b95d189..5d625f0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "torlnk", - "version": "1.6.0", + "version": "1.6.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "torlnk", - "version": "1.6.0", + "version": "1.6.1", "hasInstallScript": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index f3c1f4c..aea04ff 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "torhunt", - "version": "1.6.0", + "version": "1.6.1", "description": "A sleek, zero-setup torrent finder and downloader that lives right in your terminal.", "type": "module", "bin": { From 7177725453d52aeeb2bd08664fb36706f3fecedf Mon Sep 17 00:00:00 2001 From: AK CHAVAN <90214281+iamakchavan@users.noreply.github.com> Date: Mon, 10 Aug 2026 20:10:15 +0530 Subject: [PATCH 03/24] feat: add Bookmarks feature with sidebar panel, persistence, and b hotkey --- scripts/render-previews-impl.tsx | 4 + src/config/paths.ts | 2 + src/download/bookmarks.test.ts | 38 ++++++ src/download/bookmarks.ts | 52 ++++++++ src/ui/App.tsx | 88 ++++++++++++-- src/ui/components/BookmarksView.test.tsx | 49 ++++++++ src/ui/components/BookmarksView.tsx | 148 +++++++++++++++++++++++ src/ui/components/Results.tsx | 14 +++ src/ui/components/Sidebar.tsx | 6 +- src/ui/helpLayout.test.ts | 2 +- src/ui/keymap.ts | 21 ++++ src/ui/store.ts | 8 +- src/ui/testHarness.ts | 4 + 13 files changed, 422 insertions(+), 14 deletions(-) create mode 100644 src/download/bookmarks.test.ts create mode 100644 src/download/bookmarks.ts create mode 100644 src/ui/components/BookmarksView.test.tsx create mode 100644 src/ui/components/BookmarksView.tsx diff --git a/scripts/render-previews-impl.tsx b/scripts/render-previews-impl.tsx index 7f96ab0..3a8244a 100644 --- a/scripts/render-previews-impl.tsx +++ b/scripts/render-previews-impl.tsx @@ -124,6 +124,10 @@ function makeStore( openFolderPicker: noop, searchModeTrigger: 0, triggerSearch: noop, + bookmarks: [], + addBookmark: noop, + removeBookmark: noop, + clearBookmarks: noop, quitAll: noop, listRows: 14, compact: false, diff --git a/src/config/paths.ts b/src/config/paths.ts index 13c2bc0..c6727bf 100644 --- a/src/config/paths.ts +++ b/src/config/paths.ts @@ -21,6 +21,8 @@ export const queueFile = path.join(dataDir, "queue.json"); export const historyFile = path.join(dataDir, "history.json"); +export const bookmarksFile = path.join(dataDir, "bookmarks.json"); + export const seedsFile = path.join(dataDir, "seeds.json"); // Per-torrent .torrent metadata, captured during download so a re-seed can diff --git a/src/download/bookmarks.test.ts b/src/download/bookmarks.test.ts new file mode 100644 index 0000000..7559781 --- /dev/null +++ b/src/download/bookmarks.test.ts @@ -0,0 +1,38 @@ +import { promises as fs } from "node:fs"; +import { afterEach, beforeEach, describe, expect, it } from "vitest"; +import { loadBookmarks, saveBookmarks, type BookmarkItem } from "./bookmarks"; +import { bookmarksFile } from "../config/paths"; + +describe("bookmarks persistence", () => { + beforeEach(async () => { + await fs.rm(bookmarksFile, { force: true }).catch(() => {}); + }); + + afterEach(async () => { + await fs.rm(bookmarksFile, { force: true }).catch(() => {}); + }); + + it("saves and loads bookmark items", async () => { + const items: BookmarkItem[] = [ + { + id: "bm-1", + name: "Test Movie 1080p", + magnet: "magnet:?xt=urn:btih:1234567890abcdef", + source: "yts", + sizeBytes: 1500000000, + bookmarkedAt: Date.now(), + }, + ]; + + await saveBookmarks(items); + const loaded = await loadBookmarks(); + expect(loaded).toHaveLength(1); + expect(loaded[0]?.id).toBe("bm-1"); + expect(loaded[0]?.name).toBe("Test Movie 1080p"); + }); + + it("returns empty array when file does not exist", async () => { + const loaded = await loadBookmarks(); + expect(loaded).toEqual([]); + }); +}); diff --git a/src/download/bookmarks.ts b/src/download/bookmarks.ts new file mode 100644 index 0000000..0a83920 --- /dev/null +++ b/src/download/bookmarks.ts @@ -0,0 +1,52 @@ +import { promises as fs, mkdirSync, writeFileSync, renameSync } from "node:fs"; +import path from "node:path"; +import { bookmarksFile } from "../config/paths"; +import { serializeWrites, writeJsonAtomic } from "../util/atomic"; +import type { SourceId } from "../sources/types"; + +export const BOOKMARKS_CAP = 200; + +export interface BookmarkItem { + id: string; + name: string; + source?: SourceId; + magnet: string; + sizeBytes: number; + bookmarkedAt: number; +} + +const write = serializeWrites(); + +export function saveBookmarks(items: BookmarkItem[]): Promise { + return write(() => writeJsonAtomic(bookmarksFile, items.slice(0, BOOKMARKS_CAP))); +} + +export function saveBookmarksSync(items: BookmarkItem[]): void { + try { + mkdirSync(path.dirname(bookmarksFile), { recursive: true }); + const tmp = `${bookmarksFile}.sync.tmp`; + writeFileSync(tmp, JSON.stringify(items.slice(0, BOOKMARKS_CAP), null, 2), "utf8"); + renameSync(tmp, bookmarksFile); + } catch {} +} + +function isBookmarkItem(v: unknown): v is BookmarkItem { + if (!v || typeof v !== "object") return false; + const r = v as Record; + return typeof r.id === "string" && typeof r.name === "string" && typeof r.magnet === "string"; +} + +export async function loadBookmarks(): Promise { + let raw: string; + try { + raw = await fs.readFile(bookmarksFile, "utf8"); + } catch { + return []; + } + try { + const parsed = JSON.parse(raw) as unknown; + return Array.isArray(parsed) ? parsed.filter(isBookmarkItem).slice(0, BOOKMARKS_CAP) : []; + } catch { + return []; + } +} diff --git a/src/ui/App.tsx b/src/ui/App.tsx index bc20a7f..766b9da 100644 --- a/src/ui/App.tsx +++ b/src/ui/App.tsx @@ -6,6 +6,7 @@ import { normalizeDownloadDir } from "../config/folder"; import { DownloadQueue } from "../download/queue"; import { loadQueue, loadSeeds } from "../download/persist"; import { loadHistory } from "../download/history"; +import { loadBookmarks, saveBookmarks, type BookmarkItem } from "../download/bookmarks"; import { reconcileQueue } from "../download/reconcile"; import { BOOT_SETTLE_MS, @@ -39,6 +40,7 @@ import { HelpOverlay } from "./components/HelpOverlay"; import { Results } from "./components/Results"; import { Downloads } from "./components/Downloads"; import { Seeding } from "./components/Seeding"; +import { BookmarksView } from "./components/BookmarksView"; import { CompletedView } from "./components/CompletedView"; import { SettingsView } from "./components/SettingsView"; import { Spinner } from "./components/Spinner"; @@ -108,10 +110,17 @@ export function App({ const [editingSpinner, setEditingSpinner] = useState(false); const [previewSpinnerId, setPreviewSpinnerId] = useState(null); const [searchModeTrigger, setSearchModeTrigger] = useState(0); + const [bookmarks, setBookmarks] = useState([]); const triggerSearch = useCallback(() => { setShowHelp(false); - if (section === "downloads" || section === "seeding" || section === "completed" || section === "settings") { + if ( + section === "downloads" || + section === "seeding" || + section === "bookmarks" || + section === "completed" || + section === "settings" + ) { setSection("all"); } setRegion("content"); @@ -155,6 +164,8 @@ export function App({ q.restore(reconcileQueue(await loadQueue()), { safe: safeBoot }); q.restoreHistory(await loadHistory()); q.restoreSeeds(await loadSeeds(), { safe: safeBoot }); + const loadedBookmarks = await loadBookmarks(); + if (alive) setBookmarks(loadedBookmarks); } catch (e) { logCrash("boot-restore", e); } @@ -442,6 +453,53 @@ export function App({ [queue, config], ); + const addBookmark = useCallback( + (input: { + id: string; + name: string; + magnet: string; + source?: SourceId; + sizeBytes?: number; + }) => { + setBookmarks((prev) => { + if (prev.some((b) => b.id === input.id)) { + setNotice(`Already bookmarked: ${truncate(cleanText(input.name), 40)}`); + return prev; + } + const next: BookmarkItem[] = [ + { + id: input.id, + name: input.name, + magnet: input.magnet, + source: input.source, + sizeBytes: input.sizeBytes ?? 0, + bookmarkedAt: Date.now(), + }, + ...prev, + ]; + void saveBookmarks(next); + setNotice(`★ Bookmarked: ${truncate(cleanText(input.name), 40)}`); + return next; + }); + }, + [], + ); + + const removeBookmark = useCallback((id: string) => { + setBookmarks((prev) => { + const next = prev.filter((b) => b.id !== id); + void saveBookmarks(next); + setNotice("Bookmark removed"); + return next; + }); + }, []); + + const clearBookmarks = useCallback(() => { + setBookmarks([]); + void saveBookmarks([]); + setNotice("All bookmarks cleared"); + }, []); + const submitQuery = useCallback( (raw: string) => { const q = raw.trim(); @@ -548,6 +606,10 @@ export function App({ openFolderPicker: () => setEditingFolder(true), searchModeTrigger, triggerSearch, + bookmarks, + addBookmark, + removeBookmark, + clearBookmarks, quitAll, listRows, compact, @@ -565,15 +627,8 @@ export function App({ setSpinnerId, view, query, - submitQuery, section, region, - showHelp, - editingFolder, - editingTrackers, - editingTheme, - editingSpinner, - pendingDownload, captureMode, downloadFocus, seedFocus, @@ -585,13 +640,24 @@ export function App({ exportTorrent, fetchAndExportTorrent, notice, + showHelp, + editingFolder, + editingTrackers, + editingTheme, + editingSpinner, + pendingDownload, + searchModeTrigger, + triggerSearch, + bookmarks, + addBookmark, + removeBookmark, + clearBookmarks, + quitAll, listRows, compact, contentWidth, cols, rows, - setConfig, - quitAll, ]); useInput( @@ -800,6 +866,8 @@ export function App({ ) : section === "seeding" ? ( + ) : section === "bookmarks" ? ( + ) : section === "completed" ? ( ) : section === "settings" ? ( diff --git a/src/ui/components/BookmarksView.test.tsx b/src/ui/components/BookmarksView.test.tsx new file mode 100644 index 0000000..455f739 --- /dev/null +++ b/src/ui/components/BookmarksView.test.tsx @@ -0,0 +1,49 @@ +import React from "react"; +import { describe, expect, it } from "vitest"; +import { StoreContext } from "../store"; +import { BookmarksView } from "./BookmarksView"; +import { makeTestStore, renderUI, stripAnsi } from "../testHarness"; +import type { BookmarkItem } from "../../download/bookmarks"; + +describe("BookmarksView", () => { + it("renders list of bookmarked items", () => { + const now = Date.now(); + const bookmarks: BookmarkItem[] = [ + { id: "a", name: "Alpha", sizeBytes: 1e9, magnet: "m:a", bookmarkedAt: now }, + { id: "b", name: "Beta", sizeBytes: 2e9, magnet: "m:b", bookmarkedAt: now - 86400 * 1000 * 3 }, + ]; + + const store = makeTestStore({ + section: "bookmarks", + bookmarks, + }); + + const { frame, unmount } = renderUI( + + + , + ); + + const f = frame(); + expect(f).toContain("Alpha"); + expect(f).toContain("Beta"); + unmount(); + }); + + it("shows empty state message when there are no bookmarks", () => { + const store = makeTestStore({ + section: "bookmarks", + bookmarks: [], + }); + + const { frame, unmount } = renderUI( + + + , + ); + + const f = frame(); + expect(f).toContain("No bookmarks yet"); + unmount(); + }); +}); diff --git a/src/ui/components/BookmarksView.tsx b/src/ui/components/BookmarksView.tsx new file mode 100644 index 0000000..ef37cf2 --- /dev/null +++ b/src/ui/components/BookmarksView.tsx @@ -0,0 +1,148 @@ +import React, { useState } from "react"; +import { Box, Text, useInput } from "ink"; +import { useStore } from "../store"; +import { Panel } from "./Panel"; +import { wrapStep, windowStart } from "../move"; +import { GUTTER, ICON, sourceStyle } from "../theme"; +import { cleanText, formatBytes, formatRelative } from "../../util/format"; + +const MARK = 2; +const SIZE_W = 10; +const DATE_W = 14; +const SRC_W = 4; + +export function BookmarksView() { + const { + bookmarks, + removeBookmark, + clearBookmarks, + region, + contentWidth, + listRows, + startDownload, + requestDownloadTo, + copyMagnet, + theme, + } = useStore(); + const focused = region === "content"; + + const total = bookmarks.length; + const [cursor, setCursor] = useState(0); + const clamped = Math.min(cursor, Math.max(0, total - 1)); + + useInput( + (input, key) => { + if (key.upArrow || input === "k") setCursor(wrapStep(clamped, -1, total)); + else if (key.downArrow || input === "j") setCursor(wrapStep(clamped, 1, total)); + else if (input === "d") { + const b = bookmarks[clamped]; + if (b) { + startDownload({ + id: b.id, + name: b.name, + magnet: b.magnet, + source: b.source, + sizeBytes: b.sizeBytes, + }); + } + } else if (input === "D") { + const b = bookmarks[clamped]; + if (b) { + requestDownloadTo({ + id: b.id, + name: b.name, + magnet: b.magnet, + source: b.source, + sizeBytes: b.sizeBytes, + }); + } + } else if (input === "b" || input === "c") { + const b = bookmarks[clamped]; + if (b) removeBookmark(b.id); + } else if (input === "y") { + const b = bookmarks[clamped]; + if (b) copyMagnet({ name: b.name, magnet: b.magnet }); + } else if (input === "C") { + clearBookmarks(); + } + }, + { isActive: focused && total > 0 }, + ); + + const panelH = Math.max(5, listRows - 1); + + if (total === 0) { + return ( + + No bookmarks yet. Press b on any search result to save it for later. + + ); + } + + const selectedItem = bookmarks[clamped]; + + const maxVisibleRows = Math.max(1, panelH - 3); + const startLine = windowStart(clamped, total, maxVisibleRows); + const visibleLines = bookmarks.slice(startLine, startLine + maxVisibleRows); + + return ( + + + + ★ {total} {total === 1 ? "file" : "files"} bookmarked + + + + + {visibleLines.map((b, i) => { + const actualIdx = startLine + i; + const here = actualIdx === clamped && focused; + const ss = sourceStyle(b.source, theme); + return ( + + + + {here ? ICON.pointer : ""} + + + + + + + + {cleanText(b.name)} + + + + + {b.sizeBytes > 0 ? formatBytes(b.sizeBytes) : "-"} + + + + + {formatRelative(b.bookmarkedAt / 1000) || "─"} + + + + + {b.source ? ss.tag : "mag"} + + + + ); + })} + + + ); +} diff --git a/src/ui/components/Results.tsx b/src/ui/components/Results.tsx index d983847..0400a3f 100644 --- a/src/ui/components/Results.tsx +++ b/src/ui/components/Results.tsx @@ -134,6 +134,7 @@ export function Results() { listRows, theme, searchModeTrigger, + addBookmark, } = useStore(); const search = useConcurrentSearch(query); @@ -306,6 +307,11 @@ export function Results() { if (results[clamped]) copyResultMagnet(results[clamped]!); return; } + if (input === "b") { + const r = results[clamped]; + if (r) addBookmark({ id: r.infoHash, name: r.name, magnet: r.magnet, source: r.source, sizeBytes: r.sizeBytes }); + return; + } if (input === "S") { if (results[clamped]) { const r = results[clamped]!; @@ -329,6 +335,14 @@ export function Results() { } else if (input === "d" && detail) openDownload(detail); else if (input === "D" && detail) openDownloadTo(detail); else if (input === "y" && detail) copyResultMagnet(detail); + else if (input === "b" && detail) + addBookmark({ + id: detail.infoHash, + name: detail.name, + magnet: detail.magnet, + source: detail.source, + sizeBytes: detail.sizeBytes, + }); else if (input === "e" && detail) fetchAndExportTorrent({ id: detail.infoHash, name: detail.name, magnet: detail.magnet }); }, diff --git a/src/ui/components/Sidebar.tsx b/src/ui/components/Sidebar.tsx index a8d4585..db5156a 100644 --- a/src/ui/components/Sidebar.tsx +++ b/src/ui/components/Sidebar.tsx @@ -27,6 +27,7 @@ const GROUPS: NavGroup[] = [ { title: "TRANSFERS", items: [ + { key: "bookmarks", label: "Bookmarks" }, { key: "downloads", label: "Downloads" }, { key: "seeding", label: "Seeding" }, { key: "completed", label: "Completed" }, @@ -45,12 +46,13 @@ const NAV: NavItem[] = GROUPS.flatMap((g) => g.items); export const RAIL_WIDTH = 15; export function Sidebar() { - const { section, setSection, region, setRegion, queue, theme } = useStore(); + const { section, setSection, region, setRegion, queue, bookmarks, theme } = useStore(); const focused = region === "sidebar"; const idx = Math.max(0, NAV.findIndex((n) => n.key === section)); useQueueItems(queue); const active = queue.activeCount; const seeding = queue.seedingCount; + const bookmarkCount = bookmarks.length; useInput( (input, key) => { @@ -74,7 +76,7 @@ export function Sidebar() { ) : null} {group.items.map((item) => { const selected = item.key === section; - const count = item.key === "downloads" ? active : item.key === "seeding" ? seeding : 0; + const count = item.key === "downloads" ? active : item.key === "seeding" ? seeding : item.key === "bookmarks" ? bookmarkCount : 0; return ( diff --git a/src/ui/helpLayout.test.ts b/src/ui/helpLayout.test.ts index 7b8a889..9982049 100644 --- a/src/ui/helpLayout.test.ts +++ b/src/ui/helpLayout.test.ts @@ -4,7 +4,7 @@ import { MEASURED, pickLayout } from "./helpLayout"; describe("help layout measurement", () => { it("derives packing widths and grid heights from HELP_GROUPS", () => { expect(MEASURED.map((m) => m.width)).toEqual([140, 114, 77, 41]); - expect(MEASURED.map((m) => m.gridH)).toEqual([11, 16, 22, 35]); + expect(MEASURED.map((m) => m.gridH)).toEqual([12, 17, 23, 36]); }); it("picks the widest packing that fits inside cols - 2", () => { diff --git a/src/ui/keymap.ts b/src/ui/keymap.ts index e2da4d0..e2b13c2 100644 --- a/src/ui/keymap.ts +++ b/src/ui/keymap.ts @@ -32,6 +32,7 @@ export const HELP_GROUPS: HelpGroup[] = [ { keys: "q / 1-7", label: "Quality filter (1-7)" }, { keys: "f", label: "Filter list" }, { keys: "d", label: "Download (shift+d: folder)" }, + { keys: "b", label: "Bookmark for later" }, { keys: "s", label: "Sort results" }, { keys: "z", label: "Hide dead torrents" }, { keys: "y", label: "Copy magnet" }, @@ -59,6 +60,15 @@ export const HELP_GROUPS: HelpGroup[] = [ { keys: "e", label: "Open folder" }, ], }, + { + title: "Bookmarks", + hints: [ + { keys: "d", label: "Download (shift+d: folder)" }, + { keys: "b / c", label: "Remove bookmark" }, + { keys: "y", label: "Copy magnet" }, + { keys: "C", label: "Clear all" }, + ], + }, ]; const NAVIGATE: Hint = { keys: "↑↓←→", label: "Move" }; @@ -104,6 +114,16 @@ export function footerHints( seedFocus === "seeding" ? "Pause" : seedFocus === "missing" ? "Retry" : "Resume"; return [{ keys: "p", label }, { keys: "c", label: "Remove from list" }, FOLDER, SWITCH, ALWAYS]; } + if (section === "bookmarks") { + return [ + NAVIGATE, + { keys: "d", label: "Download" }, + { keys: "b", label: "Remove" }, + { keys: "y", label: "Copy" }, + SWITCH, + ALWAYS, + ]; + } if (section === "completed") { return [ NAVIGATE, @@ -136,6 +156,7 @@ export function footerHints( return [ NAVIGATE, { keys: "d", label: "Download" }, + { keys: "b", label: "Bookmark" }, { keys: "q", label: "Quality" }, resultFocus === "detail" ? EXPORT : { keys: "s", label: "Sort" }, { keys: "/", label: "Search" }, diff --git a/src/ui/store.ts b/src/ui/store.ts index d002bed..823934e 100644 --- a/src/ui/store.ts +++ b/src/ui/store.ts @@ -2,6 +2,7 @@ import { createContext, useContext, useEffect, useState } from "react"; import type { Config } from "../config/config"; import type { DownloadQueue } from "../download/queue"; import type { HistoryItem } from "../download/history"; +import type { BookmarkItem } from "../download/bookmarks"; import type { QueueItem, SeedItem } from "../download/types"; import type { SourceGroup, SourceId } from "../sources/types"; @@ -9,7 +10,7 @@ export type View = "splash" | "browser"; export type Category = "all" | "games" | "movies" | "tv" | "anime"; -export type Section = Category | "downloads" | "seeding" | "completed" | "settings"; +export type Section = Category | "downloads" | "seeding" | "bookmarks" | "completed" | "settings"; export const CATEGORIES: { key: Category; label: string; group?: SourceGroup }[] = [ { key: "all", label: "All" }, @@ -95,6 +96,11 @@ export interface Store { searchModeTrigger: number; triggerSearch: () => void; + bookmarks: BookmarkItem[]; + addBookmark: (input: { id: string; name: string; magnet: string; source?: SourceId; sizeBytes?: number }) => void; + removeBookmark: (id: string) => void; + clearBookmarks: () => void; + quitAll: () => void; listRows: number; diff --git a/src/ui/testHarness.ts b/src/ui/testHarness.ts index 5fede33..5cfccc0 100644 --- a/src/ui/testHarness.ts +++ b/src/ui/testHarness.ts @@ -185,6 +185,10 @@ export function makeTestStore(overrides: Partial = {}): Store { openFolderPicker: noop, searchModeTrigger: 0, triggerSearch: noop, + bookmarks: [], + addBookmark: noop, + removeBookmark: noop, + clearBookmarks: noop, quitAll: noop, listRows: 14, compact: false, From 3cfa36679916a40435ec74b37c7f54e856663746 Mon Sep 17 00:00:00 2001 From: AK CHAVAN <90214281+iamakchavan@users.noreply.github.com> Date: Mon, 10 Aug 2026 20:16:59 +0530 Subject: [PATCH 04/24] 1.6.2 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 5d625f0..ae57f47 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "torlnk", - "version": "1.6.1", + "version": "1.6.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "torlnk", - "version": "1.6.1", + "version": "1.6.2", "hasInstallScript": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index aea04ff..852aebb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "torhunt", - "version": "1.6.1", + "version": "1.6.2", "description": "A sleek, zero-setup torrent finder and downloader that lives right in your terminal.", "type": "module", "bin": { From 67677a918f8347b398319f98d19dedf1fbc6aa4d Mon Sep 17 00:00:00 2001 From: AK CHAVAN <90214281+iamakchavan@users.noreply.github.com> Date: Mon, 10 Aug 2026 20:27:50 +0530 Subject: [PATCH 05/24] fix(update): update branding to torhunt for update command and version outputs --- src/update/run.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/update/run.ts b/src/update/run.ts index b77bc16..8af7db7 100644 --- a/src/update/run.ts +++ b/src/update/run.ts @@ -75,7 +75,7 @@ async function restartDaemons(): Promise { const res = await restartDaemon(d); console.log( res.stillRunning - ? "still shutting down; skipped (stop it, then rerun torlnk update --force)." + ? "still shutting down; skipped (stop it, then rerun torhunt update --force)." : res.newPid ? `now pid ${res.newPid}.` : "it had already stopped.", @@ -84,7 +84,7 @@ async function restartDaemons(): Promise { } export async function runUpdate(opts: { force?: boolean } = {}): Promise { - console.log(`torlink v${VERSION}`); + console.log(`torhunt v${VERSION}`); const manifest = readManifest(); if (!manifest) { From 1dbb99754b814761e4121249a8c510ec32ada31b Mon Sep 17 00:00:00 2001 From: AK CHAVAN <90214281+iamakchavan@users.noreply.github.com> Date: Mon, 10 Aug 2026 20:32:35 +0530 Subject: [PATCH 06/24] style: remove theme name badge from top-right header bar --- src/ui/components/HeaderBar.tsx | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/ui/components/HeaderBar.tsx b/src/ui/components/HeaderBar.tsx index 3c3ffec..1bdb8d7 100644 --- a/src/ui/components/HeaderBar.tsx +++ b/src/ui/components/HeaderBar.tsx @@ -19,7 +19,6 @@ export function HeaderBar({ width }: { width: number }) { .reduce((acc, s) => acc + s.uploadSpeed, 0); const showStats = width >= 75; - const showThemeBadge = width >= 90; return ( ) : null} - - {showThemeBadge ? ( - - - {`[${theme.name}]`} - - - ) : null} ); From ddd629f2ad67e144be792338018d0cfe4e645580 Mon Sep 17 00:00:00 2001 From: AK CHAVAN <90214281+iamakchavan@users.noreply.github.com> Date: Mon, 10 Aug 2026 20:39:04 +0530 Subject: [PATCH 07/24] feat(settings): display torhunt package version in settings panel footer and fix settings footer hints --- src/ui/components/SettingsView.test.tsx | 1 + src/ui/components/SettingsView.tsx | 16 ++++++++++++---- src/ui/keymap.ts | 8 ++++++++ 3 files changed, 21 insertions(+), 4 deletions(-) diff --git a/src/ui/components/SettingsView.test.tsx b/src/ui/components/SettingsView.test.tsx index 588de4d..78dc412 100644 --- a/src/ui/components/SettingsView.test.tsx +++ b/src/ui/components/SettingsView.test.tsx @@ -18,6 +18,7 @@ describe("SettingsView", () => { expect(ui.frame()).toContain("Download Folder:"); expect(ui.frame()).toContain("Color Theme:"); expect(ui.frame()).toContain("Spinner Style:"); + expect(ui.frame()).toContain("torhunt v"); ui.unmount(); }); }); diff --git a/src/ui/components/SettingsView.tsx b/src/ui/components/SettingsView.tsx index 31a86ce..c7ea1a6 100644 --- a/src/ui/components/SettingsView.tsx +++ b/src/ui/components/SettingsView.tsx @@ -7,6 +7,7 @@ import { THEMES } from "../theme"; import { SPINNERS } from "../spinnerPresets"; import { saveConfig } from "../../config/config"; import { normalizeDownloadDir } from "../../config/folder"; +import { VERSION } from "../../version"; export function SettingsView() { const { @@ -133,10 +134,17 @@ export function SettingsView() { - - - Press ↵ on Color Theme or Spinner Style to open full interactive list. Press ↵ on Download Folder to edit path. - + + + + Press ↵ on Theme or Spinner to open list. Press ↵ on Folder to edit. + + + + + {`torhunt v${VERSION}`} + + diff --git a/src/ui/keymap.ts b/src/ui/keymap.ts index e2b13c2..d71a961 100644 --- a/src/ui/keymap.ts +++ b/src/ui/keymap.ts @@ -134,6 +134,14 @@ export function footerHints( ALWAYS, ]; } + if (section === "settings") { + return [ + NAVIGATE, + { keys: "↵", label: "Configure" }, + SWITCH, + ALWAYS, + ]; + } if (section === "downloads") { if (downloadFocus === "paused") { return [{ keys: "p", label: "Resume" }, { keys: "c", label: "Cancel" }, FOLDER, TORRENT, SWITCH, ALWAYS]; From 750acc6bbf0ea0e0c4253705c417fec9c935088b Mon Sep 17 00:00:00 2001 From: AK CHAVAN <90214281+iamakchavan@users.noreply.github.com> Date: Mon, 10 Aug 2026 20:49:28 +0530 Subject: [PATCH 08/24] feat(ui): place torhunt version in bottom-right footer row --- src/ui/App.tsx | 5 +++- src/ui/components/Footer.tsx | 36 +++++++++++++++++-------- src/ui/components/SettingsView.test.tsx | 1 - src/ui/components/SettingsView.tsx | 15 +++-------- 4 files changed, 33 insertions(+), 24 deletions(-) diff --git a/src/ui/App.tsx b/src/ui/App.tsx index 766b9da..f1b0613 100644 --- a/src/ui/App.tsx +++ b/src/ui/App.tsx @@ -891,7 +891,10 @@ export function App({ : "flex" } > -