# OrbTorrent: the docs in full > OrbTorrent is a free, open-source torrent app for the Mac (macOS 26 or later, Apple silicon and Intel). It's a native Swift and SwiftUI app that runs qBittorrent's engine, lives in the menu bar, can download only through a VPN, and holds fake downloads before they arrive. Extensions, written in TypeScript with @orbtorrent/api, check downloads before they start and after they finish. GPL-licensed, with no ads, no account and no tracking: only anonymous counts, without an ID, with its update check. This website and its docs are open source too. Search engines and AI tools are welcome to use everything on it, including to answer questions and to train on. --- # OrbTorrent Docs > How to use OrbTorrent, the free, open-source torrent app made for the Mac, and how to build extensions for it in TypeScript. ## Use OrbTorrent #### Get started [Install OrbTorrent](https://orbtorrent.com/docs/get-started/install), [add your first download](https://orbtorrent.com/docs/get-started/first-download), and [bring your downloads over](https://orbtorrent.com/docs/get-started/switch) from Transmission or qBittorrent. #### Everyday use [The menu bar](https://orbtorrent.com/docs/guides/menu-bar), [the library window](https://orbtorrent.com/docs/guides/library), [each download's details](https://orbtorrent.com/docs/guides/download-details), and [choosing files](https://orbtorrent.com/docs/guides/files) and the order they download in. #### Network and privacy [Download only through a VPN](https://orbtorrent.com/docs/guides/vpn), keep your VPN's [forwarded port](https://orbtorrent.com/docs/guides/port-forwarding) in step, and [find more peers](https://orbtorrent.com/docs/guides/peers) for slow downloads. #### Help [Troubleshooting](https://orbtorrent.com/docs/help/troubleshooting) for the usual problems, every [setting](https://orbtorrent.com/docs/reference/settings) and [shortcut](https://orbtorrent.com/docs/reference/shortcuts), and how to [report a problem](https://orbtorrent.com/docs/help/report-a-problem). ## Build extensions Extensions are TypeScript. They check a download before it starts, and can hold it until you decide, or act on it once it finishes. They run in a sandbox, and reach only the files and sites they list. - [Your first extension](https://orbtorrent.com/docs/extensions/getting-started): Make one from a template, test it, and run it on one of your downloads in about ten minutes. - [Build with a coding agent](https://orbtorrent.com/docs/extensions/coding-agents): Say what it should do, and Claude Code, Codex, Cursor or Gemini CLI writes it, tests it and tries it. - [API reference](https://orbtorrent.com/docs/extensions/reference/api): fetch, files, storage, progress, settings and everything else @orbtorrent/api provides. - [The orb command](https://orbtorrent.com/docs/extensions/reference/cli): Every orb command and option, its JSON output, and what it needs from OrbTorrent. ## For language models Every page here is also plain Markdown, at its address plus `.md`, and has a **Copy page** button under its title. [llms.txt](https://orbtorrent.com/llms.txt) lists the whole site, and [llms-full.txt](https://orbtorrent.com/llms-full.txt) holds all of these docs in one file. Coding agents building an extension can read just [the extension docs](https://orbtorrent.com/docs/extensions/llms-full.txt). --- # Install OrbTorrent > Download OrbTorrent for macOS 26 or later, install it from its disk image, and set it up the first time it opens. OrbTorrent is one universal app for Macs with Apple silicon or Intel. It's free, with no account and no ads. ## What it needs | | | |---|---| | macOS | 26 (Tahoe) or later | | Mac | Apple silicon or Intel | | Price | Free and open source, under the GNU GPL | OrbTorrent is built with the SwiftUI and Liquid Glass that came with macOS 26, so it can't run on earlier versions of macOS. It runs on macOS 26 and every version since. ## Install it 1. Download the disk image from [orbtorrent.com/download](https://orbtorrent.com/download). It's called `OrbTorrent--universal.dmg`. Every version is also on [GitHub Releases](https://github.com/orbtorrent/OrbTorrent/releases), with its release notes. 2. Open the disk image, and drag **OrbTorrent** into the **Applications** folder. 3. Open OrbTorrent from your Applications folder. The first time, macOS asks whether you're sure you want to open an app from the internet, and says Apple checked it for malicious software. Click **Open**. Every release is notarized by Apple, and every update is signed. If macOS won't open OrbTorrent, see [Troubleshooting](https://orbtorrent.com/docs/help/troubleshooting#macos-wont-open-orbtorrent). ## With Homebrew If you use [Homebrew](https://brew.sh), install OrbTorrent from its tap instead: ```sh brew install orbtorrent/tap/orbtorrent ``` OrbTorrent still updates itself, so `brew upgrade` leaves it alone unless you add `--greedy`. To uninstall it, `brew uninstall orbtorrent`, and [Uninstall OrbTorrent](https://orbtorrent.com/docs/help/uninstall#with-homebrew) has the rest. ## The first time it opens A short welcome walks you through setting up: 1. **Bring your downloads over.** If OrbTorrent finds Transmission or qBittorrent on your Mac, it offers to bring their downloads over, and the settings of the one you used most recently. You see what would come over before anything changes. [Switch from another app](https://orbtorrent.com/docs/get-started/switch) has the details. 2. **Open torrents with OrbTorrent.** Click **Make Default**, and magnet links in your browser and `.torrent` files in Finder open in OrbTorrent. 3. **Start at login.** OrbTorrent waits quietly in the menu bar, so your downloads carry on after you restart your Mac. You can change all of these later in [Settings](https://orbtorrent.com/docs/reference/settings). If you skip making OrbTorrent the default, a banner at the top of its window offers again, until you click **Not Now**. ## Where it lives OrbTorrent lives in your **menu bar**, as a small orb that fills up as your downloads progress. Click it for your downloads and their speeds. [The menu bar](https://orbtorrent.com/docs/guides/menu-bar) explains what it shows. The **OrbTorrent window** has your whole library, with each download's files, peers and trackers. Open it with **Open App** in the menu bar panel, or by opening OrbTorrent again from your Applications folder. While the window is open, OrbTorrent shows in the Dock. Downloads keep going when the window is closed. To stop them all, quit OrbTorrent: click **More (…)** at the bottom of the menu bar panel, then **Quit OrbTorrent**. ## What it sets up On its first launch, OrbTorrent sets its download engine up for a Mac: - New downloads are saved to your **Downloads** folder. - Uploads are limited to 5,000 KB/s, so sharing doesn't slow down the rest of your internet use. Downloads aren't limited. - Your router's port is opened automatically, with UPnP or NAT-PMP, so other peers can reach you. - OrbTorrent asks before each download, so you can choose its files and folder first. - Deleted downloads' files go to the Trash. - It finds more peers for public torrents, from a public list of trackers that's tested every day. Every one of these is a setting. [Settings](https://orbtorrent.com/docs/reference/settings) lists them all. ## Updates OrbTorrent checks for a new version once a day, and the update is signed, so your Mac knows it came from OrbTorrent. After an update, **What's New** shows what changed. [Updates](https://orbtorrent.com/docs/guides/updates) explains the settings. ## Next - [Your first download](https://orbtorrent.com/docs/get-started/first-download) - [Switch from Transmission or qBittorrent](https://orbtorrent.com/docs/get-started/switch) - [Download only through a VPN](https://orbtorrent.com/docs/guides/vpn) --- # Your first download > Add a magnet link or a .torrent file to OrbTorrent, choose its files and folder, and find it once it's finished. To try OrbTorrent, download something you're allowed to share, such as a Linux image: Ubuntu, Fedora and Debian all offer torrents on their download pages. ## Add a download OrbTorrent takes magnet links, `.torrent` files, and web links to `.torrent` files. Use whichever is closest: - **Click a magnet link in your browser**, or open a `.torrent` file in Finder. This works once OrbTorrent is your Mac's [default torrent app](https://orbtorrent.com/docs/reference/settings#general). - **Drag a `.torrent` file** onto the OrbTorrent window. - **Paste** a magnet link or a link to a `.torrent` file into the OrbTorrent window, with ⌘V. - **Copy a magnet link**, then open the menu bar panel: it offers **Add “…” from Clipboard**. - Choose **File › Add Magnet Link…** (⌘U) or **File › Open Torrent File…** (⌘O), or click **Add Link** in the menu bar panel. If the download is already in your library, OrbTorrent says so rather than adding it twice. ## Choose what to download OrbTorrent asks before downloading, so you can check what's coming first. Links from your browser and files from Finder are asked about in the **menu bar panel**; downloads you add in the window are asked about there. *Screenshot: The menu bar panel asking about a download: its three files, each with its priority circle, one marked by a check that found a problem, the folder it saves to and the free space on the disk* - **Files.** Untick any you don't want, and click a file's circle to give it a [priority](https://orbtorrent.com/docs/guides/files#before-it-starts). **Select All** and **Select None** are above the list. A download with one file has no list. - **Checks.** If your [extensions](https://orbtorrent.com/docs/guides/checks) check downloads before they start, their results show here. If one finds a problem, the button becomes **Download Anyway**. - **Save to.** Where the files go. Click **Change…** to choose another folder for this download. - **Free space.** How much room the disk has. If the download won't fit, OrbTorrent says so before anything downloads. Click **Download** to start. Adding in the window also offers **Start right away**, and **Download in order, to watch while it downloads**. > **Magnet links** > > A magnet link doesn't carry a download's file list. OrbTorrent asks peers for it, and the list appears once one answers, usually within seconds. If you [download only through a VPN](https://orbtorrent.com/docs/guides/vpn) and it isn't connected, the list waits for the VPN. If you'd rather downloads start without asking, choose **Start downloading right away** in **Settings › General**. Extensions still check each download, and hold one that looks wrong. ## Watch it download - The **menu bar orb** fills with blue light as your downloads progress. Click it for each download's speed and time left. - The **library window** shows every download, and the inspector shows one in detail: its files, peers and trackers. - When the download finishes, a notification says so, with **Open** and **Show in Finder**. ## When it's done A finished download keeps **seeding**: sharing what you downloaded with other people, the way BitTorrent works. You can [limit how much it shares](https://orbtorrent.com/docs/guides/speed-and-sharing), or pause it. To open it, click the arrow next to it in the menu bar panel, or double-click it in the library. **Show in Finder** (⌘R) shows its files. To remove it, select it and press ⌘⌫. You can remove it from OrbTorrent and keep its files, or remove its files too. Unless you've [changed it](https://orbtorrent.com/docs/reference/settings#deleting-downloads), removed files go to the Trash, so you can put them back. ## Next - [The menu bar](https://orbtorrent.com/docs/guides/menu-bar) - [The library window](https://orbtorrent.com/docs/guides/library) - [Choose files and their order](https://orbtorrent.com/docs/guides/files) --- # Magnet links and .torrent files > What a magnet link is, how to open one on a Mac, how to choose the app that opens them, and what to do when your browser won't. A magnet link starts a download from a web page. It isn't a file: it's a short address, beginning `magnet:`, that names the download by a fingerprint of its contents. Your torrent app uses it to find people who have the download, and gets its list of files from them. A magnet link looks like this, usually much longer: ```txt magnet:?xt=urn:btih:0123456789abcdef0123456789abcdef01234567&dn=Big+Buck+Bunny&tr=udp%3A%2F%2Ftracker.example.org%3A1337 ``` - **`xt`** is the fingerprint, called the info hash. It's the only part a magnet link needs. - **`dn`** is the download's name, to show while its file list arrives. - **`tr`** is a tracker, a server that introduces people sharing the download. There can be several, or none. ## Open one Click the link in your browser. If OrbTorrent is your Mac's [default torrent app](#choose-the-app-that-opens-them), it opens in OrbTorrent, which asks which files you want and where to save them. Your browser checks with you first: - **Safari** asks whether to allow the page to open OrbTorrent, every time. Click **Allow**. - **Chrome** asks whether to open OrbTorrent. Tick **Always allow** to stop it asking on that site, then click **Open OrbTorrent**. - **Firefox** asks whether the site may open the link. Choose OrbTorrent, and tick that it should always do so. You can also add a magnet link without clicking it: - **Copy it,** then open OrbTorrent's menu bar panel: it offers **Add “…” from Clipboard**. - **Paste it** into the OrbTorrent window, with ⌘V. - **Choose File › Add Magnet Link…** (⌘U), and paste it there. A magnet link carries no file list, so OrbTorrent shows **Fetching Info** until someone sharing the download sends it, usually within seconds. [Your first download](https://orbtorrent.com/docs/get-started/first-download) explains the rest. ## Choose the app that opens them macOS has no setting of its own for magnet links: each torrent app has a switch to make itself the default, and whichever app last used it opens them. To make it OrbTorrent, open **Settings › General** and click **Make OrbTorrent the Default**. From then on, magnet links in your browser and `.torrent` files in Finder open in OrbTorrent. OrbTorrent also offers this when you first open it. ## If nothing happens - **No app is set to open them.** Make OrbTorrent the default, as above, then click the link again. - **Another app opens them,** often after you've installed or updated another torrent app. Make OrbTorrent the default again. - **Your browser remembers saying no.** Firefox keeps your choice in **Settings › General › Applications**, next to **magnet**: set it to OrbTorrent, or **Always ask**. - **The link doesn't start with `magnet:?`.** You may have copied only part of it. Copy the whole link again. Whatever the browser does, you can always copy the link and add it from OrbTorrent instead. ## .torrent files A `.torrent` file holds what a magnet link does, plus the download's file list, so it can start straight away. Some sites offer one instead of, or as well as, a magnet link. - **Double-click it** in Finder, and it opens in your default torrent app. - **To open it in OrbTorrent this once,** Control-click it and choose **Open With › OrbTorrent**. - **To have every `.torrent` file open in OrbTorrent,** select one, choose **File › Get Info**, choose OrbTorrent under **Open with**, and click **Change All**. You can also drag a `.torrent` file onto OrbTorrent's window, or choose **File › Open Torrent File…** (⌘O). Downloading `.torrent` files saves them to your Downloads folder, where you can delete them once the download has started. ## Are they safe? A magnet link or a `.torrent` file only describes a download. Opening one can't run anything on your Mac. What it downloads can still be a fake, such as a "film" that's really a program. OrbTorrent's Fake Download Check looks at a download's files as soon as their list arrives, before any of them do, and holds one that looks fake until you decide. [Checks and held downloads](https://orbtorrent.com/docs/guides/checks) explains. --- # Switch from Transmission or qBittorrent > Bring your downloads and settings over from Transmission or the qBittorrent app in one step, with their folders, file choices, labels and progress. OrbTorrent brings your downloads over from **Transmission** or the **qBittorrent** app in one step, with the settings you choose. Your files stay where they are: nothing is copied or moved. ## When to import - **The first time OrbTorrent opens,** it looks for both apps on your Mac and offers to bring their downloads over, with the settings of the one you used most recently. - **Any time after,** choose **File › Import From › Transmission…** or **qBittorrent…**, or click **Import…** in **Settings › Engine**. An app shows as **Not on this Mac** if OrbTorrent can't find its downloads or settings. ## What comes over | | | |---|---| | **Where each one is saved** | Files stay where they are. Nothing is copied or moved. | | **The files you chose** | Files you skipped stay skipped. | | **Names and labels** | Renamed downloads keep their names, and labels come too. | | **Limits and history** | Speed limits, when each was added, and how much it's downloaded and shared, so ratios carry on. | | **Progress** | qBittorrent's comes over as it is. Transmission's downloads have their files checked first, then carry on. Downloads that were stopped stay stopped. | Downloads OrbTorrent already has are left out, so importing again only brings over what's new. ## Settings, if you want them Before anything changes, you see each of the other app's settings that's different from OrbTorrent's, and choose which ones come over. Anything you leave keeps OrbTorrent's own. These settings can come over: - Save downloads to - Download and upload speed limits - Ratio limit, and what happens when it's reached - Listening port, and opening it on your router automatically - Connect using (TCP and µTP) - Find peers through DHT, peer exchange (PeX) and on the local network - Encryption - Maximum connections, and connections per download While you [download through a VPN](https://orbtorrent.com/docs/guides/vpn) that forwards a port, the listening port stays the VPN's, so it isn't offered. ## How it goes 1. **Choose what comes over.** The import sheet lists the downloads that would come over and the settings that would change. Untick any setting you'd rather keep. 2. **Import.** The other app quits while it happens, so the two don't use the same files. The button says **Quit Transmission and Import** or **Quit qBittorrent and Import** while it's open. 3. **Carry on.** Your downloads carry on in OrbTorrent. Transmission's are checked first, and each goes on once its files have been checked. The other app keeps its own list. You can open it again afterwards, though only one app should download a torrent at a time. ## Where OrbTorrent looks | App | Downloads | Settings | |---|---|---| | Transmission | `~/Library/Application Support/Transmission` | Transmission's preferences | | qBittorrent | `~/Library/Application Support/qBittorrent/BT_backup` | `~/.config/qBittorrent/qBittorrent.ini` | ## If something doesn't come over - **"There's nothing new to bring over."** OrbTorrent already has that app's downloads, and its settings match. - **A download is missing its files, or starts again from the beginning.** OrbTorrent looks for its files where the other app saved them. If you've moved them since, move them back, then select the download and choose **More › Verify Local Data**, which [checks what's on disk](https://orbtorrent.com/docs/guides/download-details#actions). ## From µTorrent or another app OrbTorrent can't import from other apps, such as µTorrent. Open each download's magnet link or `.torrent` file in OrbTorrent instead, and choose the folder the other app saved it in. OrbTorrent checks the files already there, and downloads only what's missing. To see how OrbTorrent compares first, there are pages for [µTorrent](https://orbtorrent.com/utorrent-for-mac), [qBittorrent](https://orbtorrent.com/qbittorrent-for-mac) and [Transmission](https://orbtorrent.com/transmission-for-mac). --- # The menu bar > What OrbTorrent's menu bar orb shows, and everything its panel does, from pausing a download to choosing its files. OrbTorrent lives in your menu bar, so your downloads are a click away without a window open. ## The orb The orb in the menu bar is all your downloads at a glance. It fills up as they progress, and its colour says how they're doing: | Colour | Means | |---|---| | **Blue** | Downloading. The faster it goes, the livelier the light moves. | | **Amber** | Waiting: for peers, for a magnet link's file list, for files being checked or moved, or for your VPN. | | **Grey** | Paused. | | **Green** | Finished, or seeding. | | **Red** | Something needs attention, such as a download with an error, or the download engine stopped. | When some downloads are moving, the orb shows those. Otherwise it shows the one that matters most: an error first, then seeding, then waiting, then paused. ## The panel Click the orb to open the panel. *Screenshot: The menu bar panel, with live speeds at the top, five downloads with their progress, and Open App, Pause All and Add Link at the bottom* - **At the top,** your total download and upload speeds. - **Your downloads,** busiest first: running ones fastest first, then paused ones, then finished ones. Each shows its progress and what it's doing, such as "Downloading · 2.9 GB of 5.07 GB · 1 hr, 44 min left". With more than six, the list scrolls. - **At the bottom,** **Open App** opens the OrbTorrent window, **Pause All** or **Resume All** does as it says, and **Add Link** adds a magnet link. **More (…)** has the rest. A strip at the top of the list tells you when something needs a look: - **"… is held by a check."** An extension held a download before it started. Click **Review** to see why, and decide. [Checks and held downloads](https://orbtorrent.com/docs/guides/checks) explains. - **"OrbTorrent … is available."** A new version was found while OrbTorrent was in the background. Click **Update** to see what's new and install it. If you've copied a magnet link, the panel offers to add it: **Add “…” from Clipboard**. ## A download in the panel Each download has a button at its end: **Pause** or **Resume** while it's downloading, and **Open** once it's finished. **Click a download** to see its files right in the panel. You can open a finished file, choose which files to download, and set their priority, just as in the window. [Choose files and their order](https://orbtorrent.com/docs/guides/files) explains how. **Back** returns to the list. **Right-click a download** for more: - **Open** and **Show in Finder** - **Pause** or **Resume** - **Copy Name**, its full name, and **Copy Magnet Link** - **Show in OrbTorrent**, which opens it in the window - **Delete…** **Point at a download** and a **Delete** button appears. Deleting from the panel removes the download and its files, which go to the Trash, or are erased if you've [changed that](https://orbtorrent.com/docs/reference/settings#deleting-downloads). It asks first, saying how much will go. ## The More menu **More (…)** at the bottom of the panel has: - **About OrbTorrent** - **Check for Updates…** - **Settings…** (⌘,) - **Open Torrent File…** - **Report a Problem…** - **Quit OrbTorrent** (⌘Q) ## Asking before a download When you click a magnet link in your browser or open a `.torrent` file in Finder, OrbTorrent asks about it in the panel: which files, where to save them, and whether they fit. If several arrive at once, it asks about each in turn, showing "1 of 3". [Your first download](https://orbtorrent.com/docs/get-started/first-download#choose-what-to-download) walks through it. ## If the engine stops OrbTorrent downloads with qBittorrent's engine, which runs in the background. If it stops, the orb turns red and the panel says **Download engine stopped**. Click **Restart**. If it keeps stopping, see [Troubleshooting](https://orbtorrent.com/docs/help/troubleshooting#the-download-engine-stopped). --- # The library window > Find, sort and manage every download in OrbTorrent's window, with its sidebar, search, toolbar and right-click menu. The OrbTorrent window has every download, and everything you can do with them. Open it with **Open App** in the menu bar panel, or by opening OrbTorrent from your Applications folder. *Screenshot: The OrbTorrent window: the sidebar with All Downloads, Downloading, Seeding, Completed and Paused, the list of downloads with their progress, and the inspector showing one download's files* ## The sidebar The sidebar sorts your downloads into sections, each with how many it holds: | Section | Shows | |---|---| | **All Downloads** | Everything in your library. | | **Downloading** | Unfinished downloads that are running. | | **Seeding** | Finished downloads you're sharing. | | **Completed** | Everything that's finished, seeding or not. | | **Paused** | Downloads you've paused, and those held by a check. | | **Needs Attention** | Downloads with an error. It only shows when there are some. | At the bottom are **Extensions**, which opens [the Extensions page](https://orbtorrent.com/docs/guides/checks), and your total speeds. When something's wrong, the speeds make way for what it is, such as **Waiting for VPN** or **Disk almost full**. Point at it for more. While you [download through a VPN](https://orbtorrent.com/docs/guides/vpn), it says which. ## The list Each download shows its name, progress, speed, and a line saying what it's doing: | Status | Means | |---|---| | **Downloading** | Receiving data, with how much is done and the time left. | | **Waiting for Peers** | Running, but no one is sending it anything right now. | | **Fetching Info** | A magnet link waiting for its file list from peers. | | **Checking** | Checking the files on disk against the torrent. | | **Moving** | Moving its files to another folder. | | **Seeding** | Finished, and sharing with other people. | | **Completed** | Finished, and not sharing. | | **Paused** | Stopped until you resume it. | | **Files Missing** | Its files were moved or deleted outside OrbTorrent. | | **Error** | Something went wrong, such as a disk that's full or no longer connected. | ### Tidy names Downloads named the way releases usually are show as what they are: `Orbit.Station.S02.2160p.HDR…` as **Orbit Station · Season 2**, and `Sintel.2010.1080p.x264` as **Sintel (2010)**. A badge after the name shows the picture's quality when the name gives it: **4K**, **1080p** or **720p**, and **HDR** for HDR10, HDR10+ and Dolby Vision, such as **4K HDR**. A website's name in front, such as `www.example.org - `, is left out. In a download's files, each episode shows as its number, such as **S02E01**, with its title if the file has one, and three or more files in a folder whose names start with the same words, such as a course's lectures, leave those words out. Hold the pointer over a name to see it in full. It's also on the download's **Details** tab, and **Copy Name** copies it. OrbTorrent reads names on your Mac, and only changes how they look: the files keep their names. A name it can't read clearly shows as it is, such as `ubuntu-26.04-desktop-amd64.iso`. Two copies of a film in different qualities are told apart by their badges, and two that would look exactly the same show their names in full. To see every name in full, turn off **Show tidy download names** in [Settings › General](https://orbtorrent.com/docs/reference/settings#appearance). **Double-click** a download to open it. Select several with ⌘-click or ⇧-click, or all of them with ⌘A. ## The toolbar - **Add (+)** adds a magnet link. Hold it, or click its arrow, for **Open Torrent File…**. - **Pause** or **Resume**, and **Remove**, act on the selected downloads. - **Sort and Group** orders the list by **Date Added**, **Name** (as it's shown), **Progress**, **Size** or **Download Speed**, and splits it into groups by **Date Added** or **Status**, as Finder does. - **Inspector** shows or hides [a download's details](https://orbtorrent.com/docs/guides/download-details) on the right (⌘I). - **Search Downloads** finds downloads by name, as it's shown or in full, or by quality, such as **4K**. ## The right-click menu Right-click a download, or several, for: - **Open** and **Show in Finder** - **Pause** or **Resume** - **Download Sequentially**, which downloads its pieces in order, to watch a video while it downloads - **Copy Name**, its full name, and **Copy Magnet Link** - **More › Ask Trackers for More Peers**, **Verify Local Data** and **Move To…** - **Remove…** [A download's details](https://orbtorrent.com/docs/guides/download-details#actions) explains what each of the **More** actions does. ## Add downloads here Drag a `.torrent` file onto the window, or paste a magnet link or a link to a `.torrent` file with ⌘V. OrbTorrent asks about it in the window, where you can also choose to start it straight away or download it in order. ## Remove downloads Select one or more downloads, then press ⌘⌫, or click **Remove** in the toolbar. You choose: - **Remove from OrbTorrent**, which keeps the downloaded files where they are. - **Remove and Move Files to Trash**, which you can undo by putting them back from the Trash. If you've set OrbTorrent to [delete files permanently](https://orbtorrent.com/docs/reference/settings#deleting-downloads), the second choice is **Remove and Delete Files Permanently** instead, and the files can't be put back. ## Banners A banner at the top of the window appears when: - **OrbTorrent isn't your default torrent app.** Click **Make Default**, or **Not Now** to stop it asking. - **A new version is available.** See what's new, and install it when you're ready. --- # A download's details > The inspector shows one download in full, with its files, transfer details, peers, trackers and checks, and the actions you can take on it. Select a download in the library to see it in the **inspector**, on the right of the window. Show or hide it with the **Inspector** button in the toolbar, or ⌘I. At the top are the download's name and status, and four buttons: **Pause** or **Resume**, **Open**, **Finder** and **Remove**. Below them are its tabs. Select several downloads and the inspector shows how many, their total size and how much is left, with **Remove…** for all of them. ## Files Every file in the download, with its size and state. [Choose files and their order](https://orbtorrent.com/docs/guides/files) explains everything you can do here: skipping files, setting priorities and downloading in order. A magnet link has no files to show until peers send its details: "The file list appears once peers send the torrent's details." ## Details **Transfer** | | | |---|---| | **Size** | The size of the files you're downloading, and of the whole torrent if you've skipped some. | | **Downloaded** and **Uploaded** | How much this download has received and sent, in all. | | **Ratio** | Uploaded divided by downloaded. 1.00 means you've shared as much as you took. | | **Seeds** and **Peers** | How many you're connected to, of how many the trackers know about. | | **Availability** | How many full copies of the download the peers you're connected to have between them. Below 1, no one has all of it right now. | | **Time Active** | How long it's been running. | | **Average Speed** | Its download speed, on average, while active. | | **Wasted** | Data that arrived damaged, or twice, and was thrown away. | **Options** - **Download pieces in order.** Downloads the start of the files first, so you can watch a video while it downloads. - **Get first and last pieces first.** Lets a media player read a file's header and index early. **Location** shows the folder its files are saved in, with **Show** to open it in Finder. **Torrent** has its full **Name**, when it was added and completed, when the torrent was made and by what, its pieces, whether it's **Private** (from a private tracker), its comment, and its **Info Hash**, the fingerprint that identifies it. ## Peers The people you're connected to for this download: each one's country flag, address, how much of the download they have, the app they use, whether you're connected over TCP or µTP, and how fast data goes each way. A lock means the connection is encrypted. Above the list, three counts: - **Seeding:** peers that have the whole download. - **µTP:** peers connected over µTP, BitTorrent's protocol that eases off when your connection is busy. - **Incoming:** peers that connected to you. Behind a closed port, these arrive through µTP hole punching. [Port forwarding](https://orbtorrent.com/docs/guides/port-forwarding) brings more. Peers' countries come from a database of IP addresses that OrbTorrent downloads once a month and looks up on your Mac. Nothing about your peers is sent anywhere. ## Trackers The servers that introduce you to other peers, and how each one is doing: **Working**, **Updating**, **Not Contacted Yet**, **Not Working**, **Tracker Error** or **Unreachable**. Point at one to see its full address and its last message. Under **Peer Discovery** are the other ways OrbTorrent finds peers: **DHT**, **PeX** (peer exchange) and **LSD** (local peers). [Find more peers](https://orbtorrent.com/docs/guides/peers) explains each. **Ask for More Peers** asks every tracker for more peers now, rather than at its next scheduled time. ## Checks What your extensions found when they checked this download, before it started and after it finished. The tab shows while extensions are on. [Checks and held downloads](https://orbtorrent.com/docs/guides/checks) explains. ## Actions These are in the **More** submenu when you right-click a download, and some are on the **Details** tab too: - **Ask Trackers for More Peers.** Asks every tracker for peers straight away. - **Verify Local Data.** Checks every piece already on disk against the torrent, and downloads again any that don't match. Use it after moving files outside OrbTorrent, or if you think something's damaged. - **Move To….** Moves the download's files to another folder, and carries on from there. Use this rather than moving them in Finder, so OrbTorrent keeps track of them. - **Copy Name.** Copies the download's full name, even when it [shows a tidy one](https://orbtorrent.com/docs/guides/library#tidy-names). - **Copy Magnet Link.** Copies a link anyone can use to download the same thing. --- # Choose files and their order > Download only the files you want, give some priority, and download a torrent's files one after another, or a video in order to watch while it downloads. A torrent can hold many files. You don't have to download them all, and you can choose which come first. ## Before it starts When OrbTorrent asks about a new download, untick the files you don't want. **Select All** and **Select None** are above the list, and the total updates as you go: "3 files · 1.2 GB of 4.5 GB selected". Click the circle at the end of a file to give it a priority, as you can [while it downloads](#while-it-downloads). Choosing a priority for a file you unticked ticks it again. [Your first download](https://orbtorrent.com/docs/get-started/first-download#choose-what-to-download) shows the whole confirmation. ## While it downloads The **Files** tab in the inspector, and the panel you see when you click a download in the menu bar, list every file. Each has a checkbox, and a circle at its end that shows its state: | Circle | Means | |---|---| | Green tick | Finished. Its menu has **Open** and **Show in Finder**. | | Arrow | Downloading at **Normal** priority. | | Chevron | **High** priority, in orange. | | Filled chevron | **Maximum** priority, in orange. | | Cross | **Don't Download**: skipped. | Click the circle to change a file's priority. Higher priority files get their pieces first; files at the same priority share the connection. **Untick a file** to skip it. A file you skip after part of it has downloaded keeps what it has, and picks up from there if you tick it again. To change several files at once, select them, then right-click: - **Open** and **Show in Finder** - **Download 3 Files** or **Skip 3 Files** - **Priority** - **Download All Files** or **Skip All Files** ## One after another To download a torrent's files one at a time, such as the episodes of a series in order, turn on **Download Files in Order** above the file list. 1. Drag the files into the order you want. The top one downloads first. 2. OrbTorrent gives the first unfinished file **Maximum** priority, the next **High**, and the rest **Normal**. As each finishes, the next moves up. 3. To jump a file to the front, choose **Download This Next** from its circle or its right-click menu. Point at a file's circle to see its place: "Downloading first", "Downloading next", or "Number 4 in the download order". Turn the switch off to go back to priorities you set yourself. ## Watch while it downloads A torrent normally arrives in pieces from all over its files, whichever peers have. To play a video before it's finished, download its pieces **in order** instead: - **When adding it** in the window, tick **Download in order, to watch while it downloads**. - **Afterwards,** right-click it and choose **Download Sequentially**, or turn on **Download pieces in order** in the **Details** tab. Also turn on **Get first and last pieces first**, in the **Details** tab, so your media player can read the file's header and index straight away. > **Note** > > Downloading in order can be slower, because the pieces you need next may be ones few peers have. Turn it off once you've watched what you wanted. ## Open part of a download You can open any file that has started arriving, from its circle, its right-click menu, or by clicking it in the menu bar panel. Most players can play a video that's downloading in order. Other files may not open properly until they've finished. --- # Checks and held downloads > How extensions check your downloads, what a held download means, how to set up VirusTotal Scan, and how to install, turn off or remove extensions. Extensions check your downloads at two moments: **before a download starts**, when they can hold it until you decide, and **after it finishes**. Their results show in each download's **Checks** tab. OrbTorrent comes with two: - **Fake Download Check** runs before a download starts, and is on from the start. It holds a download that looks fake, such as a film that contains a program. - **VirusTotal Scan** runs once a download finishes. It looks up each file on VirusTotal, which checks it against about 70 antivirus engines. It's off until you add a free VirusTotal key. ## The Extensions page Click **Extensions** at the bottom of the window's sidebar. *Screenshot: The Extensions page: the switch to check downloads with extensions, a timeline showing Fake Download Check before a download starts and VirusTotal Scan after it finishes, and the two official extensions* It has three tabs: - **Installed** shows which extensions check downloads at each point, with a switch for each one. Click an extension for its page: what it does, what it can reach, its settings, its recent checks and its log. - **Activity** lists every check on every download, newest first. Show **Problems** only, or one extension's checks. Right-click a check to see the download's checks, run it again, or copy the result. - **Develop** is for [building your own](https://orbtorrent.com/docs/extensions). ## What a check can say | Result | Means | |---|---| | **Passed** | Nothing wrong. | | **Warning** | Worth a look. The download carries on, with a badge. | | **Failed** | Something's wrong. Before a download starts, this **holds** it. After it finishes, you get a notification. | | **Skipped** | The check didn't apply, or couldn't run, such as with no internet connection. | | **Didn't finish** | The extension failed, or took too long. A check that doesn't finish never stops a download. | After a download finishes, a warning sends a notification too, so you don't miss it. A check can also tell you about something it did, such as adding subtitles. If you download only some of a download's files and choose more later, its checks run again once those finish. They tell you only about what's new, not what they found before. ## Which file When a check finds something wrong with a particular file, OrbTorrent marks it, in red or yellow, beside the file: in the download's **Files** tab and the menu bar panel, with what the check found, and before anything downloads, in the confirmation's list of files. The check lists those files too, each with **Show in Finder** and, from VirusTotal Scan, its report. ## Reports on the checks Some extensions don't check downloads, but pass on what the checks found, such as in a message to your phone. They run once a download's checks are done, and when a check holds a download. They show under **After its checks** in the Checks tab, apart from the checks, and an extension's page says when it can see what other extensions found. ## Files a check added Some extensions add files next to a finished download's, such as subtitles in your language or a film's poster. A check lists any it added, with **Show in Finder**. They belong with the download. **Move To…** takes them along, and removing the download with its files removes them too. An extension only ever adds subtitles, text, images or checksums, never a program, and never replaces a file it didn't add. ## A held download When a check fails before a download starts, OrbTorrent holds it: nothing downloads until you decide. - **If OrbTorrent asked about the download,** the check's result shows in the confirmation, and the button becomes **Download Anyway**. *Screenshot: OrbTorrent asking about a download whose video file is really a Windows program, marked with a red cross: the Fake Download Check found a problem, and the button says Download Anyway* - **If the download started on its own,** OrbTorrent stops it the moment its file list arrives, holds it, and sends a **Download Held** notification. The menu bar panel shows it too: **"… is held by a check."** To decide, open the download's **Checks** tab, from the notification or **Review** in the menu bar panel. It says what the check found. Then: - **Download Anyway** starts it as it is. - **Remove…** removes it. If you didn't expect what the check found, this is the one to choose. ## Fake Download Check Before any of a download arrives, the Fake Download Check looks at the **names and sizes** of its files for the signs of a fake. It sends nothing anywhere. **It holds a download when:** - a file has a double extension that ends in a program or shortcut, such as `Film.mp4.exe`, `Atomic Habits.epub.exe` or `Sunset.jpg.app`. Spaces, invisible characters and right-to-left characters that make a name read backwards don't hide it. - a film, episode, album or book includes a program: a Windows program, script or shortcut, or a Mac app, installer, AppleScript or screensaver. A film never needs one. - a film or episode is an archive with a file about a password. The video is locked, and the password is sold or hidden elsewhere. **It warns, and lets the download start, when:** - a film is far too small for the quality its name claims: under 150 MB at 720p, 300 MB at 1080p or 1 GB at 2160p. - a film is from a year that hasn't come yet. - the only video is `.wmv` or `.asf`, the classic "codec required" fake. - link files say to download or watch elsewhere. - a film-like name has no video, or its video is inside an archive. - a course includes a program. Courses often include the software they teach, so it warns rather than holds. Software, games and courses carry programs for good reason, so programs only count in films, episodes, music and books. Names and sizes can't show a fake whose names and sizes look right, such as a "film" that's really a different video. VirusTotal Scan checks files once they arrive. ## VirusTotal Scan When a download finishes, VirusTotal Scan looks up each file's fingerprint (its SHA-256) on [VirusTotal](https://www.virustotal.com). Only the fingerprint is sent, not the file. | Result | When | |---|---| | **Failed** | 3 or more engines call a file malicious. | | **Warning** | 1 or 2 engines do, or any call it suspicious. Single-engine false alarms are common. | | **Passed** | No engine flags any file VirusTotal knows. Files it has never seen are listed in the details: new releases often haven't been seen yet. | | **Skipped** | VirusTotal hasn't seen any of the files, or the key wasn't accepted, or the daily limit was reached before every file was checked. Run it again later. | ### Set it up 1. Sign up or sign in at [virustotal.com](https://www.virustotal.com). 2. Open your profile menu and choose **API key**, then copy the key. 3. In OrbTorrent, open **Extensions**, then **VirusTotal Scan**. Paste the key into **API key** and click **Verify**. 4. Turn it on with the switch on its page. The key is kept in your Mac's Keychain. The free key allows 4 lookups a minute and 500 a day, so VirusTotal Scan waits between lookups, and a download with many files takes a few minutes. It remembers answers for a week, so running it again doesn't use up your limit. VirusTotal's terms allow the free key for personal use, not in commercial products or services. ### Its settings - **Files to check.** **Programs, archives and documents**, checked in that order, or **All files**, which includes videos and music. Each lookup counts towards your daily limit. - **Files VirusTotal hasn't seen.** Off unless you turn it on: **Upload them to be scanned**, up to 32 MB each. Uploaded files are shared with VirusTotal's paying customers, so leave this off for anything private. ## Install an extension Extensions come as `.orbext` files. To install one, open it in Finder, or choose **File › Install Extension…**, or click **Install Extension…** on the Extensions page. OrbTorrent shows what the extension does before you install it: who made it, when it runs, and what it **can** do, such as read the contents of your downloads, add files such as subtitles next to them, or connect to a site. When you update an extension, anything new it asks for is marked **New**. > **Only install extensions from people you trust** > > Extensions from anyone other than OrbTorrent are marked **Not reviewed by OrbTorrent**. OrbTorrent enforces what an extension says it can do, so it can't read other files or reach other sites, but nobody has reviewed its code. [Security model](https://orbtorrent.com/docs/extensions/security) explains the limits. ## Turn extensions off - **One extension:** turn off its switch on the **Installed** tab or its page. Its settings are kept. - **All of them:** turn off **Check downloads with extensions** at the top of the **Installed** tab. OrbTorrent then works as a plain torrent client: nothing is checked, and checks don't show. Your extensions and their settings are kept for when you turn them back on. - **Remove one:** click **Uninstall…** at the bottom of its page. Its settings, including any API key, are deleted too. The official extensions can be turned off, but not removed. ## Something to check? If a check found something you didn't expect, or an extension keeps saying **Didn't finish**, open its page: its **Logs** show what it did. [Troubleshooting](https://orbtorrent.com/docs/help/troubleshooting#an-extension-didnt-finish) has more. --- # Notifications > The notifications OrbTorrent sends, what each one's buttons do, and how to turn them on or off. OrbTorrent sends a notification when something needs you, or when a download is done. macOS asks the first time whether to allow them. ## What it sends | Notification | When | Its buttons | |---|---|---| | **Download Finished** | A download has every file you wanted. | **Open** and **Show in Finder** | | **Download Held** | A check held a download before it started. It says which check, and what it found. | **Download Anyway** and **Remove…** | | A check's name, such as **VirusTotal Scan** | A check on a finished download failed, or warned. Or a check has news, such as subtitles it added. | Click it for the download's checks. | | **Download Paused** | Downloads paused because their disk is almost full. | They resume by themselves once there's room. | | **OrbTorrent … Is Available** | A new version was found while OrbTorrent was in the background. | Click it to see what's new and install it. | Clicking a notification about a download opens it in OrbTorrent. One about a check opens on the download's **Checks** tab. ## Turn them on or off - **Download Finished** follows **Notify me when a download finishes** in **Settings › General**. It's on unless you turn it off. - The others are always sent, because each one needs you. To silence them, or change how they look, go to **System Settings › Notifications › OrbTorrent**. If no notifications appear at all, check **System Settings › Notifications › OrbTorrent**, and that **Allow notifications** is on. ## Related - [Checks and held downloads](https://orbtorrent.com/docs/guides/checks) - [Disk space](https://orbtorrent.com/docs/guides/disk-space) - [Updates](https://orbtorrent.com/docs/guides/updates) --- # Run a program when a download finishes > Have OrbTorrent run a script or program of your own when each download finishes, such as one that unpacks it or copies it to another drive. OrbTorrent can run a program or script of your own each time a download finishes: to unpack it, copy it to another drive, tell your media server, or anything else you can write a script for. It's for what [extensions](https://orbtorrent.com/docs/extensions) can't do, such as reaching your local network or changing files. ## Set it up 1. Open **Settings › Downloads**. 2. Under **When a Download Finishes**, click **Choose…** next to **Run**, and choose your script or program. A script needs a first line that says what runs it, such as `#!/bin/zsh`, and must be allowed to run. If OrbTorrent says it can't be run, open Terminal and enter `chmod +x` and the script's path. An app works too, such as one made with Automator. To stop, click **Stop Running It**. ## What it gets The download's file or folder is the first argument, `$1`, and its details are in the environment: | Variable | What it holds | Such as | |---|---|---| | `ORB_PATH` | The download's file or folder, the same as `$1` | `/Users/you/Downloads/Film (2024)` | | `ORB_FOLDER` | The folder it's saved in | `/Users/you/Downloads` | | `ORB_NAME` | Its name | `Film (2024)` | | `ORB_HASH` | Its info hash | `0123456789abcdef0123456789abcdef01234567` | | `ORB_SIZE` | The size of the files you chose, in bytes | `4200000000` | | `ORB_PRIVATE` | Whether it's from a private tracker | `true` or `false` | It starts in the folder the download is saved in. Homebrew's folders, `/opt/homebrew/bin` and `/usr/local/bin`, are on its `PATH`, so the command-line tools you've installed are found. ## When it runs - **Once a download finishes** while OrbTorrent is open, and after the download's [checks](https://orbtorrent.com/docs/guides/checks) that run once it finishes, so it sees any subtitles an extension added. - **Not if one of those checks failed,** such as VirusTotal finding malware. The log says which check, and why. - **One at a time,** in the order downloads finish. - **For up to an hour.** A program still running then is stopped. ## What it printed Everything it prints, and when each run started and how it ended, goes in a log: click **Show Log** in **Settings › Downloads**, or open `~/Library/Application Support/OrbTorrent/Logs/When a Download Finishes.log`. It keeps 1 MB, and the 1 MB before that. ## Examples ### Unpack archives Releases that come as RAR archives, unpacked next to them with [unar](https://theunarchiver.com/command-line), which you can install with `brew install unar`: ```zsh title="unpack.sh" #!/bin/zsh # Unpacks the RAR archives in a finished download, next to each one. # A download that's a single file has nothing to unpack. [[ -d "$ORB_PATH" ]] || exit 0 cd "$ORB_PATH" for archive in **/*.rar(N); do # A set of parts unpacks from its first. [[ "$archive" == *.part<2->.rar ]] && continue unar -quiet -force-skip -output-directory "${archive:h}" "$archive" done ``` The archives stay where they are, so OrbTorrent can go on sharing them. ### Copy it to another drive ```zsh title="copy.sh" #!/bin/zsh # Copies each finished download to a media drive, if it's connected. destination="/Volumes/Media/Downloads" [[ -d "$destination" ]] || exit 0 rsync -a "$ORB_PATH" "$destination/" ``` Copy rather than move: OrbTorrent shares a download from where it saved it. To move a download, use **Move To…** in OrbTorrent, which takes its files and anything extensions added along. ### Tell Plex ```zsh title="plex.sh" #!/bin/zsh # Asks Plex, on this Mac, to scan its library for what just arrived. # The library's number is in its address in Plex Web, after "source=". curl -fsS "http://127.0.0.1:32400/library/sections/1/refresh?X-Plex-Token=YOUR_TOKEN" ``` Plex's help explains [finding your token](https://support.plex.tv/articles/204059436-finding-an-authentication-token-x-plex-token/). ## Keep it safe - **It runs as you,** with access to your files, like any program you open. Only run a script you've written or read. - **Only you can choose it,** here in Settings. Extensions, the `orb` tool and the download engine can't set it or change it. The engine's own way of running programs is always off. - **A download's name can't run a command.** OrbTorrent starts your program directly, not through a shell, so a name with quotes or `;` in it is only text. Keep it that way in your script: put variables in double quotes, such as `"$ORB_PATH"`. - **macOS may ask** whether OrbTorrent can use a folder your program uses, such as **Documents** or an external drive, since it runs on OrbTorrent's behalf. --- # Updates > How OrbTorrent keeps itself up to date with signed updates, installed while you aren't using it, the settings for checking and installing, and what's new. OrbTorrent updates itself. Each update is signed, so your Mac only installs one that came from OrbTorrent. ## How it works - **Once a day,** OrbTorrent checks for a new version, while **Check for updates automatically** is on. - **A new version downloads in the background,** and OrbTorrent restarts to install it once you're not using it: once it's not in front, or no one has touched the Mac for a couple of minutes, and nothing would be cut short, such as files being moved or checked. It comes back in the menu bar, and your downloads carry on where they left off. - **If you're using OrbTorrent when it's ready,** the menu bar panel and the window say so, with **Restart to Update** to install it then. - **After an update,** **What's New** shows what changed in the version you just installed, and in any you skipped, the next time you open the window. With **Download and install updates automatically** off, a window offers each update instead, with its release notes: install it now, be reminded later, or skip that version. One found while OrbTorrent is in the background waits quietly, in the menu bar panel, at the top of the window and in a notification, until you look at it. A fix for a version with a serious problem is shown straight away, and can't be skipped. To check now, choose **OrbTorrent › Check for Updates…**, or **More (…) › Check for Updates…** in the menu bar panel, or click **Check Now** in **Settings › General**. ## Settings In **Settings › General › Updates**: | Setting | | |---|---| | **Check for updates automatically** | Checks once a day. | | **Download and install updates automatically** | Downloads each update as it's found, and restarts OrbTorrent to install it while you're not using it. On unless you turn it off. | The section also shows your version, and when OrbTorrent last checked. ## What's new Choose **Help › What's New in OrbTorrent** to see the notes for the version you have, with **View on GitHub** for the full release. Every release, with its notes and its disk image, is on [GitHub Releases](https://github.com/orbtorrent/OrbTorrent/releases). ## What a check sends A check for updates goes to `updates.orbtorrent.com`. It says which version of OrbTorrent it is, with a few anonymous counts: your version of macOS, the kind of Mac, the week OrbTorrent was installed and how often it quit unexpectedly. None of them is an ID, or says how you use OrbTorrent. **Settings › General › See What's Sent** shows exactly what the next check sends, and [Privacy](https://orbtorrent.com/docs/guides/privacy#what-orbtorrent-counts) explains each. The address a check comes from isn't kept. An update you install is downloaded from GitHub. Turn off **Check for updates automatically** and OrbTorrent never checks unless you ask. [Privacy](https://orbtorrent.com/docs/guides/privacy) lists everything OrbTorrent connects to. --- # Download only through a VPN > A kill switch for your downloads. Tie OrbTorrent to your VPN, so downloading and sharing stop if the VPN drops instead of carrying on without it. Peers and trackers can see the IP address you download from. A VPN gives them its address instead of yours, but if the VPN drops, most torrent apps carry on over your normal connection without saying so. **Only download through a VPN** ties OrbTorrent to your VPN's connection. If the VPN drops, downloading and sharing stop, and they continue once it's back. ## Turn it on 1. Open **Settings › Network**. 2. Turn on **Only download through a VPN**. 3. Choose your VPN under **VPN**, or leave it on **Any VPN**. **Any VPN** means any VPN that carries all your internet traffic. A VPN that only reaches some addresses, such as one for your office or your own devices, doesn't count. If you use more than one, choose the one to download through. Turning it on while downloads are running drops their connections, and they reconnect through the VPN. ## What you'll see - **In Settings › Network,** the VPN's status: **Connected through …** in green, or **Not connected, so nothing is downloading** in orange. - **In the menu bar panel and the window's sidebar,** "Through …" with a green shield, while downloads go through the VPN. - **When the VPN isn't connected,** the sidebar says **Waiting for …**, the orb turns amber, and nothing downloads or shares over your normal connection. New magnet links wait too: their file lists arrive once the VPN connects. ## Which VPNs work OrbTorrent finds VPNs in two ways, so most work with nothing to set up: - **VPN apps** that add their VPN to your Mac's network settings, which is how most VPN apps on the Mac work. - **Tunnels that route all your traffic** without being in your network settings, such as **Tunnelblick**, WireGuard's **`wg-quick`**, and **Private Internet Access** with OpenVPN. OrbTorrent finds these from your Mac's routes. It notices within moments when a VPN connects or drops, including after your Mac sleeps, or when Wi-Fi drops and comes back. ## A kill switch, just for downloads Many VPN apps have a kill switch, which cuts off your whole Mac's internet if the VPN drops. **Only download through a VPN** is one for OrbTorrent alone: - **It works with any VPN,** including ones whose app has no kill switch, or one you've turned off. - **The rest of your Mac stays online** while the VPN reconnects. Only downloading and sharing wait. - **It follows the VPN** when it reconnects or your Mac wakes, with nothing to choose again. You can use both: a VPN app's kill switch doesn't get in OrbTorrent's way. ## Faster downloads through a VPN Through a VPN, other peers can only connect to you if the VPN **forwards a port** to you. Without one, you can still connect to them, and downloads work, but you reach fewer peers. OrbTorrent picks up a forwarded port by itself from many VPNs, and keeps it in step as it changes. [Port forwarding](https://orbtorrent.com/docs/guides/port-forwarding) explains. ## How it works OrbTorrent's download engine is bound to the VPN's network connection, so it can't use any other. If the VPN goes away, the engine has nothing to connect through, rather than falling back to your normal connection. The engine starts already bound to the VPN, so it never sends anything first over another connection. The rest of OrbTorrent, such as checking for updates, isn't tied to the VPN, and uses whichever connection your Mac is using. [Privacy](https://orbtorrent.com/docs/guides/privacy) lists what it connects to. ## If it says it isn't connected - **Check the VPN is connected,** in its app or in **System Settings › VPN**. - **If you chose a VPN,** make sure it's the one that's connected, or choose **Any VPN**. - **If it's a tunnel that only routes some addresses,** such as a split tunnel, **Any VPN** won't use it. Route all traffic through it, or choose it by name if it's in your network settings. [Troubleshooting](https://orbtorrent.com/docs/help/troubleshooting#it-says-waiting-for-vpn) has more. --- # Port forwarding > Let other peers connect to you, for faster downloads, through your router or your VPN, and check that it's working. A torrent downloads faster with more peers. You can always connect to other peers, but they can only connect to you if your **port** is open to them. With an open port you reach peers that are themselves behind closed ports, which matters most for downloads with few people sharing them. ## At home: your router OrbTorrent opens its port on your router by itself, with UPnP or NAT-PMP, which most home routers support. In **Settings › Network › Connection**: | | | |---|---| | **Status** | **Reachable by peers** when your port is open, **Incoming connections blocked** when it isn't, or **Offline**. | | **Listening port** | The port OrbTorrent listens on. **Random** picks a new one. | | **Open the port on my router automatically (UPnP / NAT-PMP)** | On unless you turn it off. | If it says **Incoming connections blocked**: - Make sure **UPnP** or **NAT-PMP** is turned on in your router's settings, or forward the listening port to your Mac by hand there. - Your internet provider may use **carrier-grade NAT**, which shares one public address between many customers, so no port can be opened. Ask them for a public IP address, or use a VPN that forwards ports. Downloads still work while incoming connections are blocked. Some peers still reach you through µTP hole punching, and the **Peers** tab counts them as **incoming**. ## Through a VPN Your router's port is no use through a VPN: the VPN has to forward a port to you. While you [download only through a VPN](https://orbtorrent.com/docs/guides/vpn), OrbTorrent gets the VPN's forwarded port, listens on it, and keeps it in step as it changes. It looks, in order: 1. **A port you've entered.** Some VPNs, such as **AirVPN** and **Windscribe**, have you reserve a port in your account. Enter it once in **Forwarded port**, in **Settings › Network › VPN**. 2. **Your VPN's gateway, over NAT-PMP.** Many VPNs forward a port to anyone who asks this way. Some only do once you turn port forwarding on in their app, or connect to a server that allows peer-to-peer traffic, which can need a paid plan. 3. **Private Internet Access's app.** Turn on port forwarding in its settings, and OrbTorrent reads the port from it. While a port is forwarded, your own listening port and the router setting are set aside, and the **Connection** section shows which port is in use and who forwards it. They come back when you stop downloading through the VPN. ### Check it's working In **Settings › Network › VPN**, **Incoming connections** says: | | | |---|---| | **Port 51234** | "Forwarded by …, so peers can connect to you." It's working. | | **Looking for a forwarded port…** | OrbTorrent is asking. It takes a few seconds after the VPN connects. | | **No port forwarded** | "Only peers with open ports can connect to you." The VPN didn't forward one. | A forwarded port lasts a minute at a time, and OrbTorrent renews it every 45 seconds. If your VPN gives you a new port, after reconnecting or changing server, OrbTorrent moves to it by itself. ### If no port is forwarded - **Check your VPN forwards ports at all.** Not every VPN does, and some only on certain plans or servers. - **Turn port forwarding on in your VPN's app,** if it has the setting, then reconnect. - **If your VPN reserves a port in your account,** enter it in **Forwarded port**. - **Leave Forwarded port empty** for VPNs that hand out ports themselves: it says **Automatic**. Downloads still work without a forwarded port. They just reach fewer peers. --- # Find more peers > How OrbTorrent finds people to download from, how its public tracker list speeds up slow downloads, and what to try when a download is slow. A download comes from **peers**: other people with the same torrent. The more peers you can reach, and the more of them have the whole download, the faster it goes. ## How OrbTorrent finds peers - **Trackers.** Servers listed in the torrent that introduce its peers to each other. - **DHT.** A network shared by every BitTorrent app, which finds peers without a tracker. Trackerless magnet links rely on it. - **Peer exchange (PeX).** Peers you're connected to tell you about the peers they know. - **Local peer discovery (LSD).** Finds peers on your own network, such as another Mac at home. All four are on unless you turn them off, in **Settings › Network › Peers**. A download's **Trackers** tab shows how each is doing. Downloads from private trackers only use their own trackers, as private trackers ask. ## Find more peers for public torrents Many public torrents list trackers that have stopped working, so they find few peers. **Find more peers for public torrents** adds trackers from a public list that's tested every day, [ngosang/trackerslist](https://github.com/ngosang/trackerslist): the most popular public trackers that answered that day's test. - It's on unless you turn it off, in **Settings › Network**. The setting says how many trackers the list has, and when it was updated. - The trackers are added after a torrent's own, so its own are asked first. - **Torrents from private trackers are never changed.** OrbTorrent only adds the list once a torrent is known to be public. A magnet link counts as public straight away only if none of its trackers carries a personal key, as a private tracker's address does. If a magnet link turns out to be private, the trackers come off again. - OrbTorrent downloads the list once a day while the setting is on, and keeps the last good copy. It comes with a copy, for its first launch and for when the list can't be reached. - Turn the setting off and every tracker it added comes off again. A tracker that drops off the list comes off too. ## Ask for more peers now Trackers are asked for peers every so often. To ask straight away, right-click a download and choose **More › Ask Trackers for More Peers**, or click **Ask for More Peers** on its **Trackers** tab. ## If a download is slow Work through these, roughly in order: 1. **Look at its peers.** In the **Details** tab, **Seeds** says how many peers have the whole download, and **Availability** how many full copies the peers you're connected to have between them. Below 1, no one you're connected to has all of it, and it may wait for someone who does. An old or unpopular torrent may simply have few people sharing it. 2. **Open your port.** Peers can only connect to you if your port is open. Check that **Settings › Network** says **Reachable by peers**. [Port forwarding](https://orbtorrent.com/docs/guides/port-forwarding) explains, at home and through a VPN. 3. **Check your speed limits.** In **Settings › Speed**, make sure **Limit download speed** is off, or high enough. Uploading near your connection's maximum can also slow downloads, which is why OrbTorrent limits uploads to 5,000 KB/s at first. 4. **Turn on Find more peers for public torrents,** if you've turned it off. 5. **Ask trackers for more peers.** 6. **Through a VPN,** try a server closer to you, or one that forwards ports. 7. **Downloading in order** is slower, because the pieces you need next may be ones few peers have. [Turn it off](https://orbtorrent.com/docs/guides/files#watch-while-it-downloads) once you've watched what you wanted. ## Connection settings In **Settings › Network**: | Setting | | |---|---| | **Connect using** | **TCP and µTP**, **TCP Only** or **µTP Only**. µTP eases off when your connection is busy, so other apps stay quick. | | **Encryption** | **Prefer Encryption**, **Require Encryption** or **Disable Encryption**. Requiring it hides what you're downloading from your internet provider, but rules out peers that don't encrypt. | | **Maximum connections** | Peers connected at once, across every download. | | **Per download** | Peers connected at once, for each download. | Leave these as they are unless you have a reason: they suit most connections. --- # Speed limits and sharing > Limit OrbTorrent's download and upload speeds, and stop sharing a finished download once it reaches a ratio. BitTorrent works because everyone shares what they download. OrbTorrent shares, or **seeds**, finished downloads until you pause them, remove them, or set a limit. ## Speed limits In **Settings › Speed**: - **Limit download speed**, with **Maximum download speed** in KB/s. Off unless you turn it on. - **Limit upload speed**, with **Maximum upload speed** in KB/s. OrbTorrent starts with uploads limited to 5,000 KB/s. Keeping uploads below your connection's maximum stops them slowing down your downloads and the rest of your internet use. If you don't know your connection's upload speed, run a speed test, and set the limit to about three-quarters of it. The limits apply to every download together. 1,000 KB/s is about 1 MB/s. ## Stop sharing at a ratio A download's **ratio** is how much you've uploaded divided by how much you've downloaded: 1.00 means you've given back as much as you took. In **Settings › Downloads › Seeding**, turn on **Stop sharing when the ratio reaches a limit**, then set: - **Ratio limit**, such as 2. - **Then**: **Stop Seeding**, which keeps the download and its files, or **Remove Torrent**, which removes it from OrbTorrent and keeps its files. The limit applies to every download. A download's ratio is on its **Details** tab, and in its status line once it's finished: "Seeding · 6.48 GB · Ratio 1.20". ## Pause and resume - **One download:** its **Pause** button, in the menu bar panel or the inspector. - **Several:** select them in the window, then **Pause** in the toolbar. - **Everything:** **Pause All** in the menu bar panel, or **Downloads › Pause All** (⌥⌘.). **Resume All** (⌥⌘R) starts them again. A paused download keeps what it has, and picks up where it stopped. ## Private trackers Private trackers usually ask you to keep up a ratio, and count what you upload. If you use one, set any ratio limit above what it asks, or leave the limit off. OrbTorrent never adds trackers to their torrents, and they don't use DHT or peer exchange. --- # Disk space > How OrbTorrent warns you when a download won't fit, pauses downloads before a disk fills up, and resumes them once there's room. A torrent app that fills your disk can leave your Mac struggling, and a download that fails halfway. OrbTorrent looks after the space on every disk it saves to. ## Before a download starts When OrbTorrent asks about a download, it shows how much room the disk has: "57.66 GB free on “Macintosh HD”". If the download won't fit, it says **Not enough space**, and how it would go: - **"It would pause before finishing, to keep 5 GB free for your Mac,"** when it would fit, but only by leaving less than 5 GB free. - **"It would run out of space before finishing,"** when it can't fit at all. The space counted is what's free less what your unfinished downloads to that disk still need, so several large downloads can't each assume they'll get the same space. Free space is counted as Finder counts it, including space macOS can clear by itself. Choose another folder with **Change…**, untick some files, or make room, then download. ## While downloads run With **Pause downloads when the disk is almost full** on, in **Settings › Downloads**: 1. When a disk gets down to 5 GB free, OrbTorrent pauses the downloads saving to it, and sends a **Download Paused** notification. 2. The window's sidebar says **Disk almost full**. Point at it to see which disk. 3. Once the disk has 7 GB free, the downloads resume by themselves. The gap stops them pausing and starting as space comes and goes. Downloads you paused yourself stay paused. The setting is on unless you turn it off, and turning it off resumes anything it paused. ## Make room - Empty the Trash. Unless you've [changed it](https://orbtorrent.com/docs/reference/settings#deleting-downloads), downloads you remove put their files there, and they take up space until it's emptied. - Remove finished downloads you've watched or installed. - Move large downloads to another disk with **More › Move To…**. - See what's using your disk in **System Settings › General › Storage**. ## Downloads on external disks You can save downloads to any disk, including external ones. If a disk isn't connected, its downloads stop with an error until you connect it again, and OrbTorrent can't count its space until then. --- # Privacy > What other people can see when you download, everything OrbTorrent connects to, and what stays on your Mac. OrbTorrent knows nothing about you. There's no account, no ID and no crash reporting, and your library, settings and extension passwords stay on your Mac. Its daily update check carries a few anonymous counts. [The privacy page](https://orbtorrent.com/privacy) is the full statement, for the app and this website. ## What peers can see BitTorrent is public by design. Everyone downloading a torrent can see the IP address of everyone else downloading it, and so can its trackers. That's true of every torrent app. - **To hide your address,** download through a VPN, and turn on [Only download through a VPN](https://orbtorrent.com/docs/guides/vpn) so nothing goes over your normal connection if the VPN drops. - **To hide what you download from your internet provider,** set **Encryption** to **Require Encryption** in **Settings › Network**. Peers and trackers still see your address. ## What OrbTorrent connects to | | Why | |---|---| | **Peers and trackers** | For the downloads you add, as every torrent app does. | | **raw.githubusercontent.com** | Once a day, for a public list of trackers, while **Find more peers for public torrents** is on. | | **Web addresses you add torrents from** | To fetch the `.torrent` file. | | **The sites an extension lists** | While it's on. Each extension's page shows them, and it can't reach any others. | | **updates.orbtorrent.com** | Once a day, to check for a new version, while **Check for updates automatically** is on. It says which version of OrbTorrent it is, with [anonymous counts](#what-orbtorrent-counts). The address a check comes from isn't kept. | | **github.com** | To download an update you install. | | **download.db-ip.com** | Once a month, for a database of which country each IP address is in, to show peers' flags. Peers are looked up in it on your Mac, and never sent anywhere. | That's everything. The same list is in the app, under **About OrbTorrent › Privacy**. ## What OrbTorrent counts The update check adds a few anonymous counts, so we know how many Macs use OrbTorrent and whether a version has a problem: your version of macOS, whether your Mac has Apple silicon or Intel, the week OrbTorrent was installed, whether this is its first check this week or month, and how often it quit unexpectedly. None of them is an ID, and none is about your downloads or how you use OrbTorrent. **Settings › General › See What's Sent** shows exactly what the next check sends, and **Send anonymous counts with update checks** turns them off. [The privacy page](https://orbtorrent.com/privacy#counts) lists every value. ## What stays on your Mac - **Your library:** your downloads, their settings and their history. - **Your settings,** and the download engine's. - **Extension passwords and API keys,** in your Mac's Keychain rather than in a file. - **Check results and extension logs.** Secrets are hidden in the logs. [Files on your Mac](https://orbtorrent.com/docs/reference/files) lists where each is kept. ## The download engine OrbTorrent runs qBittorrent's engine in the background, and talks to it through qBittorrent's Web API. The API only accepts connections from your own Mac, on `127.0.0.1`, and is protected by a random password OrbTorrent makes the first time it runs. Nothing on your network, or the internet, can reach it. [How OrbTorrent works](https://orbtorrent.com/docs/contribute/how-it-works) has the details. ## Extensions Each extension can only reach the sites it lists, and only read the contents of your downloads if it says so. OrbTorrent enforces both, and shows them on the extension's page and before you install it. [Security model](https://orbtorrent.com/docs/extensions/security) explains how. ## Reporting a problem **Help › Report a Problem** only opens your own mail app, with the report filled in. Nothing is sent until you send it, and you can read every word first. If you include the engine's log, IP addresses and your user name are hidden, though it can name your downloads. [Report a problem](https://orbtorrent.com/docs/help/report-a-problem) explains. --- # Settings > Every setting in OrbTorrent, what it does, and what it's set to when you first install it. Open Settings with **More (…) › Settings…** in the menu bar panel, or **OrbTorrent › Settings…** (⌘,) while the window is open. Changes take effect straight away. Extensions' own settings are on the [Extensions page](https://orbtorrent.com/docs/guides/checks), not here. ## General | Setting | What it does | At first | |---|---|---| | **Default torrent app** | **Make OrbTorrent the Default** opens magnet links and `.torrent` files in OrbTorrent. Once it is, it says so. | Asked when OrbTorrent first opens | | **Open OrbTorrent when you log in** | Starts OrbTorrent in the menu bar when you log in, so downloads carry on. If macOS needs you to allow it, **Allow in Login Items Settings…** appears. | Asked when OrbTorrent first opens | ### Updates | Setting | What it does | At first | |---|---|---| | **Check Now** | Checks for a new version now. The section shows your version and when it last checked. | | | **Check for updates automatically** | Checks once a day. | On | | **Download and install updates automatically** | Downloads updates as they're found, and restarts OrbTorrent to install them while you're not using it. Downloads carry on where they left off. | On | ### Help improve OrbTorrent | Setting | What it does | At first | |---|---|---| | **Send anonymous counts with update checks** | Adds your version of macOS, the kind of Mac, the week OrbTorrent was installed and how often it quit unexpectedly to the daily update check. Never an ID, your IP address, or anything about your downloads or how you use OrbTorrent. | On | | **See What's Sent…** | Shows exactly what the next check sends, value by value. [Privacy](https://orbtorrent.com/docs/guides/privacy#what-orbtorrent-counts) explains each. | | ### Adding torrents | Setting | What it does | At first | |---|---|---| | **When you open a torrent** | **Ask before downloading** lets you choose the files and folder first. **Start downloading right away** doesn't ask. Extensions check downloads either way. | Ask before downloading | | **Show the OrbTorrent window when a download is added** | Shows each new download in the window as it's added. | On | | **Keep the Mac awake while downloading** | Stops your Mac going to sleep by itself while anything is downloading. The display can still sleep, and closing the lid still puts the Mac to sleep. | On | ### Deleting downloads | Setting | What it does | At first | |---|---|---| | **When you delete a download** | **Move its files to the Trash**, where you can put them back, though they take up space until you empty it. Or **Delete its files permanently**: erased straight away, and they can't be put back. This applies everywhere in OrbTorrent. | Move its files to the Trash | ### Appearance | Setting | What it does | At first | |---|---|---| | **Show OrbTorrent in the Dock while its window is open** | Turn off to keep OrbTorrent in the menu bar only. | On | | **Show tidy download names** | Shows release names as what they are, such as **Orbit Station · Season 2** with a **4K HDR** badge, and episodes by their numbers. Turn off to see every name in full. [Tidy names](https://orbtorrent.com/docs/guides/library#tidy-names) explains. | On | ### Notifications | Setting | What it does | At first | |---|---|---| | **Notify me when a download finishes** | Sends **Download Finished**, with **Open** and **Show in Finder**. Other notifications, such as for held downloads, are always sent. | On | ## Downloads | Setting | What it does | At first | |---|---|---| | **Save downloads to** | Where new downloads go, unless you choose another folder when adding one. **Change…** picks a folder. | Your Downloads folder | | **Pause downloads when the disk is almost full** | Pauses downloads when their disk gets down to 5 GB free, and resumes them once it has 7 GB. [Disk space](https://orbtorrent.com/docs/guides/disk-space) explains. | On | ### Seeding | Setting | What it does | At first | |---|---|---| | **Stop sharing when the ratio reaches a limit** | Stops sharing a finished download once it's uploaded this many times what it downloaded. | Off | | **Ratio limit** | Such as 2. | | | **Then** | **Stop Seeding**, or **Remove Torrent**, which removes it from OrbTorrent and keeps its files. | | ### When a download finishes | Setting | What it does | At first | |---|---|---| | **Run** | A program or script of your own, run as you after each download's checks, unless one of them failed. It gets the download's file or folder, and its name. **Choose…** picks it, and **Stop Running It** takes it away. [Run a program when a download finishes](https://orbtorrent.com/docs/guides/run-a-program) explains. | Nothing | | **What it printed** | **Show Log** shows its log in Finder, once it's run. | | ## Speed | Setting | What it does | At first | |---|---|---| | **Limit download speed** | With **Maximum download speed**, in KB/s, for every download together. | Off | | **Limit upload speed** | With **Maximum upload speed**, in KB/s. Keeping uploads below your connection's maximum stops them slowing everything else. | On, at 5,000 KB/s | ## Network ### VPN | Setting | What it does | At first | |---|---|---| | **Only download through a VPN** | Downloads and shares only over the VPN's connection, and stops if it drops. [Download only through a VPN](https://orbtorrent.com/docs/guides/vpn) explains. | Off | | **VPN** | **Any VPN** that carries all your internet traffic, or one you choose. | Any VPN | | **Status** | **Connected through …**, or **Not connected, so nothing is downloading**. | | | **Incoming connections** | The port your VPN forwards, if any. | | | **Forwarded port** | For VPNs that reserve a port in your account, such as AirVPN or Windscribe. Leave it empty, **Automatic**, for others. | Automatic | ### Connection | Setting | What it does | At first | |---|---|---| | **Status** | **Reachable by peers**, **Incoming connections blocked**, or **Offline**. | | | **Listening port** | The port peers connect to you on. **Random** picks a new one. Set aside while a VPN forwards a port. | | | **Open the port on my router automatically (UPnP / NAT-PMP)** | Asks your router to forward the listening port. [Port forwarding](https://orbtorrent.com/docs/guides/port-forwarding) explains. | On | ### Peers | Setting | What it does | At first | |---|---|---| | **Connect using** | **TCP and µTP**, **TCP Only** or **µTP Only**. | TCP and µTP | | **Find peers through DHT** | Finds peers without a tracker. | On | | **Exchange peers with other peers (PeX)** | Peers tell each other about the peers they know. | On | | **Find peers on the local network** | Finds peers on your own network. | On | | **Encryption** | **Prefer Encryption**, **Require Encryption** or **Disable Encryption**. | Prefer Encryption | | **Find more peers for public torrents** | Adds trackers from a public list that's tested every day. Torrents from private trackers are never changed. [Find more peers](https://orbtorrent.com/docs/guides/peers) explains. | On | ### Limits | Setting | What it does | At first | |---|---|---| | **Maximum connections** | Peers connected at once, across every download. At least 10. | 500 | | **Per download** | Peers connected at once, for each download. At least 5. | 100 | ## Engine | | What it does | |---|---| | **Download Engine** | Whether qBittorrent's engine is running, and the versions of qBittorrent, libtorrent, Qt, OpenSSL and Boost it's built from. | | **Restart Engine** | Stops and starts the engine. Downloads carry on once it's back. | | **Show Engine Log** | Opens the engine's log, which can say why something went wrong. | | **Show Data Folder in Finder** | Shows the engine's folder, with its settings and your library. | | **Import** | **Import…** brings downloads and settings over from Transmission or qBittorrent. [Switch from another app](https://orbtorrent.com/docs/get-started/switch) explains. | ## About The same as **About OrbTorrent**: the version, **Acknowledgements** for the open-source projects inside OrbTorrent, **Source Code**, and **Privacy**. ## Reset OrbTorrent To start again from scratch, quit OrbTorrent, then delete its folder and its settings in Terminal: ```sh title="Terminal" rm -rf ~/Library/Application\ Support/OrbTorrent defaults delete com.orbtorrent.mac ``` This removes your library, so OrbTorrent forgets your downloads, but **your downloaded files stay where they are**. Extension passwords stay in your Keychain until you remove them in **Keychain Access**. The next time you open OrbTorrent, it starts as if new. --- # Menus and shortcuts > Every command in OrbTorrent's menus, with its keyboard shortcut, and the shortcuts in the window and the menu bar panel. OrbTorrent is a menu bar app, so its menus show in your Mac's menu bar while its window is open. ## OrbTorrent | Command | Shortcut | |---|---| | About OrbTorrent | | | Check for Updates… | | | Settings… | ⌘, | | Quit OrbTorrent | ⌘Q | ## File | Command | Shortcut | |---|---| | Open Torrent File… | ⌘O | | Add Magnet Link… | ⌘U | | Add from Clipboard | ⇧⌘V | | Import From › Transmission… or qBittorrent… | | | Install Extension… | | **Add from Clipboard** adds a magnet link, or a link to a `.torrent` file, that you've copied. **Install Extension…** shows while extensions are on. ## Downloads | Command | Shortcut | |---|---| | Pause All | ⌥⌘. | | Resume All | ⌥⌘R | | Show in Finder | ⌘R | | Remove… | ⌘⌫ | | Show Inspector or Hide Inspector | ⌘I | **Show in Finder** and **Remove…** act on the downloads selected in the library. ## Help | Command | | |---|---| | OrbTorrent Help | These docs. ⌘? | | Troubleshooting | [Fixes for the usual problems](https://orbtorrent.com/docs/help/troubleshooting). | | What's New in OrbTorrent | The release notes for the version you have. | | OrbTorrent Website | orbtorrent.com. | | Report a Problem… | A report to send from your own mail app. [Report a problem](https://orbtorrent.com/docs/help/report-a-problem) explains. | ## In the window | Do this | To | |---|---| | ⌘V | Add a magnet link, or a link to a `.torrent` file, that you've copied. | | Drag a `.torrent` file onto the window | Add it. | | Double-click a download | Open it. | | ⌘A | Select every download in the list. | | ⌫ or ⌘⌫ | Remove the selected downloads, after asking. | | Right-click a download | Its menu: Open, Show in Finder, Pause, Download Sequentially, Copy Magnet Link, More and Remove. | ## When OrbTorrent asks about a download | Key | Does | |---|---| | ↩ | **Download**, or **Download Anyway** when a check found a problem. | | esc | **Cancel**. | ## In the menu bar panel | Do this | To | |---|---| | Click a download | See its files. | | Right-click a download | Open, Show in Finder, Pause, Copy Magnet Link, Show in OrbTorrent or Delete. | | **More (…)**, then ⌘, | Open Settings. | | **More (…)**, then ⌘Q | Quit OrbTorrent. | --- # Files on your Mac > Where OrbTorrent keeps your library, its settings, the download engine's files, its logs and your extensions, and what's safe to delete. Everything OrbTorrent keeps is on your Mac. Your downloaded files are wherever you saved them, your **Downloads** folder unless you chose another. Everything else is in one folder: ``` ~/Library/Application Support/OrbTorrent/ ├── Engine/ the download engine's own folder │ └── qBittorrent/ │ ├── config/qBittorrent.ini the engine's settings │ └── data/ │ ├── BT_backup/ your library: each download's torrent and progress │ └── logs/qbittorrent.log the engine's log ├── engine-credentials.json the engine's private password, readable only by you ├── engine-console.log what the engine printed as it ran ├── trackers.txt the public tracker list, for finding more peers ├── Extensions/ │ ├── Installed/ extensions you've installed │ ├── settings.json extensions' settings, except passwords │ ├── checks.json what checks found │ ├── Data/ each extension's stored data │ └── Logs/ each extension's log ├── Logs/ │ └── When a Download Finishes.log what your own program printed, if you chose one └── dev.sock for orb, while development extensions are allowed ``` To open it, choose **Go › Go to Folder…** in Finder and enter `~/Library/Application Support/OrbTorrent`, or click **Show Data Folder in Finder** in **Settings › Engine**, which shows the `Engine` folder inside it. ## Elsewhere | | Where | |---|---| | **OrbTorrent's own settings** | `~/Library/Preferences/com.orbtorrent.mac.plist` | | **Caches, such as a downloaded update** | `~/Library/Caches/com.orbtorrent.mac` and `~/Library/HTTPStorages/com.orbtorrent.mac` | | **The sandbox extensions run in** | `~/Library/Containers/com.orbtorrent.mac.extension-host`, which macOS makes | | **Extension passwords and API keys** | Your login Keychain | | **The official extensions** | Inside OrbTorrent, in `OrbTorrent.app/Contents/Resources/Extensions` | | **Pieces of files you skipped** | A hidden `..parts` file in the download's folder | | **Files extensions added,** such as subtitles | Next to the download's own files, listed in its **Checks** tab. They're moved and removed with it. | The `.parts` file holds the pieces a skipped file shares with files you're downloading, which BitTorrent needs to check them. It goes with the download's files when you remove them too. ## Logs - **The engine's log:** **Settings › Engine › Show Engine Log**. Report a Problem can include its recent lines, with addresses hidden. - **An extension's log:** on its page, under **Logs**, with **Show Log File**. - **Your own program's log,** if you chose one to [run when a download finishes](https://orbtorrent.com/docs/guides/run-a-program): **Settings › Downloads › Show Log**. ## What's safe to delete - **Your downloaded files,** once you've removed their downloads from OrbTorrent. - **The whole OrbTorrent folder,** with OrbTorrent quit, to start again from scratch. OrbTorrent forgets every download, but their files stay where they are. [Settings](https://orbtorrent.com/docs/reference/settings#reset-orbtorrent) explains. Don't edit the engine's files while OrbTorrent is open: OrbTorrent manages them, and the engine rewrites them as it runs. ## OrbTorrent Dev If you build OrbTorrent yourself, **OrbTorrent Dev** keeps its own library and settings in `~/Library/Application Support/OrbTorrent Dev`, so it can run beside OrbTorrent. [Contribute](https://orbtorrent.com/docs/contribute) explains. --- # Troubleshooting > Fixes for the usual problems with OrbTorrent, from magnet links that don't open and slow downloads to the VPN, the download engine and extensions. Most problems have a quick fix. Find yours below, and if it isn't here, [report it](https://orbtorrent.com/docs/help/report-a-problem). ## Installing and opening ### macOS won't open OrbTorrent - **It says you can't use this version of OrbTorrent with this version of macOS.** OrbTorrent needs macOS 26 (Tahoe) or later. If your Mac can run it, update in **System Settings › General › Software Update**. - **It says OrbTorrent is damaged, or that it can't check it for malicious software.** Every copy of OrbTorrent from orbtorrent.com and GitHub is notarized by Apple, so that copy came from somewhere else, or was changed. Move it to the Trash and download OrbTorrent again from [orbtorrent.com/download](https://orbtorrent.com/download). ### Magnet links open another app, or nothing Make OrbTorrent your default torrent app: **Settings › General › Make OrbTorrent the Default**. Then click the magnet link again. Some browsers ask the first time which app to open a link with: choose OrbTorrent, and tick that it should always open them. ### A `.torrent` file opens in another app Same fix: make OrbTorrent the default. Or right-click the file in Finder and choose **Open With › OrbTorrent**. ### Nothing happens when I open OrbTorrent OrbTorrent lives in the menu bar: look for the orb near the clock. If the menu bar is full, macOS may hide it behind other icons. Opening OrbTorrent again from your Applications folder opens its window. ## Downloads ### A download is slow See [Find more peers](https://orbtorrent.com/docs/guides/peers#if-a-download-is-slow), which goes through the causes in order: few people sharing it, a closed port, speed limits, and the rest. ### It's stuck on "Fetching Info" A magnet link needs a peer to send its file list. If it stays on **Fetching Info**: - **Check you're online,** and if you [download only through a VPN](https://orbtorrent.com/docs/guides/vpn), that the VPN is connected. - **Ask for more peers:** right-click it and choose **More › Ask Trackers for More Peers**. - **Leave it a while.** An old or unpopular torrent may have nobody sharing it right now. ### It's stuck on "Waiting for Peers" It's running, but no one is sending it anything. Look at **Seeds** and **Availability** on its **Details** tab. If nobody has the whole download, it can only wait. Otherwise, work through [If a download is slow](https://orbtorrent.com/docs/guides/peers#if-a-download-is-slow). ### A download says "Files Missing" Its files were moved, renamed or deleted outside OrbTorrent, or they're on a disk that isn't connected. Connect the disk, or move the files back, then right-click the download and choose **More › Verify Local Data**. Next time, move downloads with **More › Move To…**, so OrbTorrent keeps track of them. ### A download has an error The usual causes: - **The disk is full.** Make room, then resume it. [Disk space](https://orbtorrent.com/docs/guides/disk-space) explains how OrbTorrent avoids this. - **The disk isn't connected.** Connect it, then resume the download. - **OrbTorrent isn't allowed to use the folder.** If macOS asked whether OrbTorrent could access a folder such as Downloads or Documents and you chose **Don't Allow**, turn it on in **System Settings › Privacy & Security › Files & Folders › OrbTorrent**. The download's status and the engine's log, in **Settings › Engine › Show Engine Log**, say more. ### Downloads paused by themselves Something paused them on purpose: - **The disk was almost full.** They resume by themselves once it has 7 GB free. The sidebar says **Disk almost full**. - **A check held one** before it started. The menu bar panel says so: see [Checks and held downloads](https://orbtorrent.com/docs/guides/checks#a-held-download). - **The VPN dropped,** if you download only through one. They carry on once it's back. - **A finished download reached its ratio limit,** if you've set one in **Settings › Downloads**. ### A download was held, and I'd like it Open its **Checks** tab, read what the check found, and click **Download Anyway** if you're sure. If a check keeps holding downloads you trust, turn that extension off on the [Extensions page](https://orbtorrent.com/docs/guides/checks#turn-extensions-off). ## Network and VPN ### It says "Waiting for VPN" You've turned on **Only download through a VPN**, and OrbTorrent can't find the VPN's connection, so nothing downloads. - **Connect the VPN.** Downloads carry on by themselves once it's connected. - **If it's connected,** check **Settings › Network › VPN**. If you chose a VPN by name, it must be that one. **Any VPN** only uses a VPN that carries all your internet traffic, so a split tunnel doesn't count. - **To download without the VPN,** turn off **Only download through a VPN**. ### "Incoming connections blocked" Downloads work, but only peers with open ports can reach you, which can make them slower. Your router or internet provider is blocking incoming connections. [Port forwarding](https://orbtorrent.com/docs/guides/port-forwarding) explains the fixes, at home and through a VPN. ### "No port forwarded" through my VPN Your VPN didn't forward a port. Check it offers port forwarding on your plan and server, turn it on in its app if it has a setting, or enter a port you've reserved in your VPN account. [Port forwarding](https://orbtorrent.com/docs/guides/port-forwarding#if-no-port-is-forwarded) has the details. ### It says "Offline" OrbTorrent can't reach the internet. Check your Wi-Fi or network, and your VPN if you use one. ## The app ### The download engine stopped OrbTorrent downloads with qBittorrent's engine, which runs in the background, and restarts it if it stops. If the menu bar panel says **Download engine stopped**: 1. Click **Restart**, or **Restart Engine** in **Settings › Engine**. 2. If it stops again, look at the engine's log: **Settings › Engine › Show Engine Log**. 3. [Report the problem](https://orbtorrent.com/docs/help/report-a-problem), with the log included. ### The window won't open Click the orb in the menu bar, then **Open App**. If OrbTorrent isn't responding, quit it from **Activity Monitor** and open it again: your downloads carry on where they stopped. ### No notifications Check **System Settings › Notifications › OrbTorrent**, and that **Allow notifications** is on. **Download Finished** also needs **Notify me when a download finishes** in **Settings › General**. ### My Mac doesn't sleep while downloading That's **Keep the Mac awake while downloading**, in **Settings › General**. Turn it off to let your Mac sleep, which pauses downloading until it wakes. ## Extensions ### An extension didn't finish The check failed, or took longer than it's allowed. A check that doesn't finish never stops a download. - Open the extension's page, on the [Extensions page](https://orbtorrent.com/docs/guides/checks), and read its **Logs**. - Click **Run Again** in the download's **Checks** tab. - If it keeps happening, tell whoever made it. For the official extensions, [report a problem](https://orbtorrent.com/docs/help/report-a-problem). ### An extension says "Needs setup" It needs a setting, such as an API key, before it can run. Its checks are skipped until then. Open its page, fill in the setting, and click **Verify** if there is one. ### VirusTotal Scan skipped files The free VirusTotal key allows 500 lookups a day. Once they're used up, the scan skips the rest, and **Run Again** tomorrow picks them up, reusing the answers it already has. Choose **Programs, archives and documents** in its settings to save lookups. ### An extension says it needs a newer OrbTorrent It was built for a newer extension API than your OrbTorrent has. Update OrbTorrent: **OrbTorrent › Check for Updates…**. ## Still stuck? Choose **Help › Report a Problem**, or [open an issue](https://github.com/orbtorrent/OrbTorrent/issues/new/choose) on GitHub. [Report a problem](https://orbtorrent.com/docs/help/report-a-problem) explains what helps. --- # Report a problem > Send a problem report from OrbTorrent with the details that help, open an issue on GitHub, or report a security problem privately. If something's wrong, tell us. Reports go to a person, who reads every one. ## From OrbTorrent Choose **Help › Report a Problem…**, or **More (…) › Report a Problem…** in the menu bar panel. 1. **Say what happened:** what you were doing, what you expected, and what happened instead. The first line becomes the subject. 2. **Choose what's sent with it.** The report always includes the versions of OrbTorrent, macOS and the download engine, and your Mac's model. Tick **Include the engine's recent log** to add its last 60 lines, which often show what went wrong. 3. **Open in Mail.** The report opens in your own mail app, addressed to `help@orbtorrent.com`. Read it, then send it, or don't. Nothing is sent until you send it yourself. If your mail app doesn't open, click **Copy Report** and email it to `help@orbtorrent.com` from wherever you like. > **What the log shows** > > In the engine's log, IP addresses and your user name are hidden. It can name your downloads, so read it before you send it. ## On GitHub You can also [open an issue](https://github.com/orbtorrent/OrbTorrent/issues/new/choose) with the bug report form. Copy the versions from **Report a Problem** or **About OrbTorrent**, so we know what you're running. Before you do, search the [existing issues](https://github.com/orbtorrent/OrbTorrent/issues): someone may have found it already, and you can add what you've seen. ## Suggest a feature Check the [roadmap](https://github.com/orbtorrent/OrbTorrent/blob/main/ROADMAP.md) first. It lists what's planned, what's been decided against, and why. Then open an issue with the feature request form. Many ideas fit better as an [extension](https://orbtorrent.com/docs/extensions), which anyone can build. ## Security problems Don't report security problems in a public issue. Email `help@orbtorrent.com` with "Security" in the subject, and say what the problem is, how to reproduce it, and which versions of OrbTorrent and macOS you saw it on. A fix comes through OrbTorrent's automatic updates as soon as it's ready. Tell us if you'd like to be credited in the release notes. This covers the app, its download engine, the extension host, the official extensions, the extension SDK on npm, and the update feed. Only the newest version of OrbTorrent gets security fixes. ## What helps - **Steps to make it happen again,** if you can find them. - **What you expected,** and what happened instead. - **A screenshot,** for anything you can see. Leave out other people's IP addresses, and anything you'd rather not share. - **The engine's log,** for problems with downloads. - **For an extension,** which one, and what its **Logs** say on its page. --- # Uninstall OrbTorrent > Remove OrbTorrent from your Mac, with or without its library and settings. Your downloaded files stay where they are. ## Remove the app 1. Quit OrbTorrent: click **More (…)** in the menu bar panel, then **Quit OrbTorrent**. 2. Drag **OrbTorrent** from your Applications folder to the Trash. Your downloaded files stay where they are. So do your library and settings, in case you install OrbTorrent again. ## Remove everything else too To leave nothing behind, also delete: | | Where | |---|---| | **Your library, the engine's files, logs and extensions** | `~/Library/Application Support/OrbTorrent` | | **OrbTorrent's settings** | `~/Library/Preferences/com.orbtorrent.mac.plist` | | **Caches, such as a downloaded update** | `~/Library/Caches/com.orbtorrent.mac` and `~/Library/HTTPStorages/com.orbtorrent.mac` | | **The sandbox extensions run in** | `~/Library/Containers/com.orbtorrent.mac.extension-host` and `~/Library/Application Scripts/com.orbtorrent.mac.extension-host` | | **Extension passwords and API keys** | In **Keychain Access**, search for "OrbTorrent" | To get to the folders, choose **Go › Go to Folder…** in Finder and paste the path. If you'd made OrbTorrent your default torrent app, choose another in its own settings, and remove OrbTorrent from **System Settings › General › Login Items & Extensions** if it's still listed. ## With Homebrew If you installed OrbTorrent with Homebrew, uninstall it with Homebrew too: ```sh brew uninstall orbtorrent ``` To remove everything else as well, add `--zap`: `brew uninstall --zap orbtorrent` deletes everything in the table above, except the passwords in your Keychain. ## Your downloads Uninstalling never touches your downloaded files. To remove those as well, delete them in Finder, or remove each download in OrbTorrent with **Remove and Move Files to Trash** before you uninstall. ## Coming back Reinstall OrbTorrent from [the download page](https://orbtorrent.com/download). If you left its folder, your downloads and settings are just as they were. If you removed it, OrbTorrent starts as new, and can bring your downloads back from Transmission or qBittorrent if they're still there. --- # Build extensions > Extensions are TypeScript hooks that check a download before it starts, and can hold it, or act on it once it finishes. How they work, and where to start. Extensions hook into moments in a download's life, the way checks run on a pull request. An extension can look at a download **before it starts** and hold it until the user decides, or act on it **after it finishes**. Its result shows in the download's **Checks** tab. ```ts title="src/check.ts" export default async function check(download: Download): Promise { const program = download.files.find((file) => file.path.endsWith(".exe")); if (program) { return { status: "failed", summary: `Contains a Windows program: ${program.path}` }; } return { status: "passed", summary: "No programs" }; } ``` That's a whole extension, with a `package.json` that says when it runs. OrbTorrent's own [Fake Download Check](https://orbtorrent.com/docs/guides/checks#fake-download-check) and [VirusTotal Scan](https://orbtorrent.com/docs/guides/checks#virustotal-scan) are built the same way, with the same tools. ## What you can build - **Checks before a download starts.** Hold downloads that contain certain kinds of file, are too big, or come from somewhere you don't trust. These see the names and sizes of a download's files before any of it arrives. - **Actions after a download finishes.** Scan files with a service, add subtitles or artwork next to a film, or send a notification to your phone or a chat. Extensions have settings that OrbTorrent draws for them, such as API keys kept in the Keychain, and can reach the web, but only the sites they list. ## What they can't do yet Extensions can't change, rename, move or delete files, unpack archives, change a download itself (pause it, label it, skip files), reach your Mac or your local network, such as Plex, Sonarr or a NAS, or run programs. Each would need a new permission, and they're [on the roadmap](https://github.com/orbtorrent/OrbTorrent/blob/main/ROADMAP.md#what-extensions-need-next). For anything else, OrbTorrent can [run a program of your own](https://orbtorrent.com/docs/guides/run-a-program) when a download finishes, such as a script that unpacks it or tells your media server. That's a setting only you can choose, since it runs as you, outside the sandbox. ## How it fits together | | | |---|---| | **TypeScript** | Each hook is the default export of a file in `src/`. | | **`@orbtorrent/api`** | The npm package with everything an extension can use: [`fetch`](https://orbtorrent.com/docs/extensions/network), [`files`](https://orbtorrent.com/docs/extensions/files), [`storage`](https://orbtorrent.com/docs/extensions/storage), [`progress`](https://orbtorrent.com/docs/extensions/storage#show-progress) and [settings](https://orbtorrent.com/docs/extensions/settings). | | **`orb`** | The command-line tool in the same package, which [makes, tests, builds, runs and shares](https://orbtorrent.com/docs/extensions/reference/cli) extensions. | | **`package.json`** | The [manifest](https://orbtorrent.com/docs/extensions/reference/manifest): its hooks, permissions and settings. | | **The sandbox** | Each hook runs in a fresh, sandboxed process with JavaScriptCore, not Node.js or a browser. It can only reach what its manifest lists, and OrbTorrent enforces that. [Security model](https://orbtorrent.com/docs/extensions/security) explains. | ## Start here - [Your first extension](https://orbtorrent.com/docs/extensions/getting-started): Make one from a template, test it, load it into OrbTorrent and run it on a download. - [Build with a coding agent](https://orbtorrent.com/docs/extensions/coding-agents): Describe it, and Claude Code, Codex, Cursor or Gemini CLI builds, tests and installs it with you. - [Hooks](https://orbtorrent.com/docs/extensions/hooks): When beforeStart, afterFinish and afterChecks run, what they receive, and their limits. - [Examples](https://orbtorrent.com/docs/extensions/examples): The templates, the official extensions, and ideas to build. ## In OrbTorrent Extensions have their own page in OrbTorrent's main window: **Extensions**, at the bottom of the sidebar. - **Installed** shows which extensions check downloads at each point, with each one's settings. - **Activity** lists every check on every download, and any download a check is holding. - **Develop** has the switch that `orb dev` needs, and the steps to build your own, yourself or with a coding agent. A switch at the top of **Installed** turns every extension off, for using OrbTorrent as a plain torrent client. Nothing runs and no checks show until it's back on, and each extension keeps its settings. ## Requirements - **Node.js 22.13 or later,** for `orb`. - **OrbTorrent,** to load and run what you build. - **macOS 26 or later.** `orb test` runs anywhere Node.js does, but loading an extension into OrbTorrent needs a Mac. `@orbtorrent/api` is MIT licensed, so your extension can use any licence you like. --- # Your first extension > Build an extension that holds downloads containing Windows screensavers, test it, run it on a real download in OrbTorrent, and install it. In this tutorial you'll build **No Screensavers**, an extension that holds a download if it contains a Windows screensaver (`.scr`) file. Screensavers are programs, so a film or album that contains one is almost certainly a fake. It takes about ten minutes. You need: - **Node.js 22.13 or later.** Check with `node --version`. Install it from [nodejs.org](https://nodejs.org), or with `brew install node`. - **OrbTorrent,** open on your Mac. > **Rather describe it than write it?** > > A coding agent can build this for you, tests and all. See [Build with a coding agent](https://orbtorrent.com/docs/extensions/coding-agents). ## Make the extension 1. Make a new extension from the `before-start` template, and install what it needs: ```sh npm create @orbtorrent/extension@latest no-screensavers cd no-screensavers npm install ``` 2. Look around. The new folder has: - no-screensavers/ - package.json the manifest: hooks, permissions, settings - src/ - check.ts the hook, named after it - tests/ JSON tests that run without OrbTorrent - holds-a-big-download.json - … - assets/ - icon.png - AGENTS.md everything in one file, for coding agents - CLAUDE.md - README.md - CHANGELOG.md - tsconfig.json The template already works: it holds downloads bigger than a size you choose. You'll change it into No Screensavers. ## Say what it does Open `package.json`. It's npm's manifest, with OrbTorrent's fields added. Change the description and the hook, and remove the template's setting: ```json title="package.json" ins={4,11-12} del={17-24} { "name": "no-screensavers", "title": "No Screensavers", "description": "Holds downloads that contain Windows screensaver (.scr) files, which are programs in disguise.", "author": "you", "icon": "icon.png", "version": "1.0.0", "hooks": [ { "name": "check", "title": "Look for screensavers", "description": "Holds a download that contains a .scr file until you decide.", "event": "beforeStart" } ], "permissions": {}, "preferences": [ { "name": "limit", "type": "dropdown", "title": "Hold downloads bigger than", … } ], … } ``` - **`"event": "beforeStart"`** runs the hook once a download's file list is known, before any of it downloads. That's when it can hold it. - **`"name": "check"`** means the hook is the default export of `src/check.ts`. - **`"permissions": {}`** asks for nothing beyond the names and sizes of the files, which every extension gets. Users see exactly what an extension can do before they install it, so ask only for what you need. Set `"author"` to your name. [Manifest](https://orbtorrent.com/docs/extensions/reference/manifest) has every field. ## Write the hook Replace `src/check.ts` with: ```ts title="src/check.ts" /** Runs when a download's files are known, before any of it downloads. */ export default async function check(download: Download): Promise { const screensavers = download.files.filter((file) => file.wanted && file.path.toLowerCase().endsWith(".scr")); if (screensavers.length > 0) { const names = screensavers.map((file) => file.path.split("/").pop()).join(", "); return { status: "failed", summary: `Contains a screensaver, which is a Windows program: ${names}`, details: "Windows runs a `.scr` file as a program when it's opened. Films, music and books never need one.", }; } return { status: "passed", summary: "No screensavers" }; } ``` The hook gets the [download](https://orbtorrent.com/docs/extensions/hooks#what-a-hook-receives): its name, size and files, each with a `path`, a `size` and whether the user chose to download it, `wanted`. It returns a [result](https://orbtorrent.com/docs/extensions/results): a `status`, a one-line `summary` that says which file and why, and optional `details` in Markdown. A `failed` result from a `beforeStart` hook **holds** the download until the user decides. Files the user unticked are skipped here, since they won't arrive. ## Test it Tests are JSON files in `tests/`, and run without OrbTorrent. Delete the template's tests, and add three of your own: ```json title="tests/holds-a-screensaver.json" { "name": "Holds a download with a screensaver in it", "download": { "name": "Ocean Life (2026) 1080p", "files": [ { "path": "Ocean Life (2026) 1080p/Ocean Life.mp4", "size": 2400000000 }, { "path": "Ocean Life (2026) 1080p/Play Me.scr", "size": 480000 } ] }, "expect": { "status": "failed", "summaryIncludes": "Play Me.scr" } } ``` ```json title="tests/lets-a-film-start.json" { "name": "Lets a film without one start", "download": { "name": "Big Buck Bunny", "files": [{ "path": "Big Buck Bunny/Big Buck Bunny.mp4", "size": 276000000 }] }, "expect": { "status": "passed" } } ``` ```json title="tests/ignores-a-skipped-screensaver.json" { "name": "Ignores a screensaver the user isn't downloading", "download": { "name": "Screensavers Pack", "files": [ { "path": "Screensavers Pack/Read Me.txt", "size": 1200 }, { "path": "Screensavers Pack/Aquarium.scr", "size": 900000, "wanted": false } ] }, "expect": { "status": "passed" } } ``` Test what must **pass**, not only what must fail: a false alarm that holds someone's download is worse than a missed one. Run them: ```sh npm test ``` ```txt title="Output" ✓ Holds a download with a screensaver in it tests/holds-a-screensaver.json ✓ Ignores a screensaver the user isn't downloading tests/ignores-a-skipped-screensaver.json ✓ Lets a film without one start tests/lets-a-film-start.json 3 passed ``` When a test fails, it says what it expected and what the hook returned: ```txt title="Output" ✗ Lets a film without one start tests/lets-a-film-start.json expected status “failed”, got “passed” (No screensavers) returned {"status":"passed","summary":"No screensavers"} ``` [Testing](https://orbtorrent.com/docs/extensions/testing) covers mocking the network, file fingerprints, storage and time. ## Build and lint ```sh npm run build npm run lint ``` ```txt title="Output" ✓ Built No Screensavers into dist/ (1 hook) No problems found. ``` `orb build` checks `package.json`, type-checks your code and bundles it into `dist/`, the way OrbTorrent loads it. It refuses Node.js modules such as `fs`, and says what to use instead. `orb lint` runs ESLint with OrbTorrent's rules. ## Load it into OrbTorrent 1. In OrbTorrent, click **Extensions** at the bottom of the sidebar, then the **Develop** tab, and turn on **Allow development extensions**. 2. Check everything's ready: ```sh npx orb doctor ``` ```txt title="Output" ✓ Node.js 24.11.1 ✓ No Screensavers (no-screensavers), with @orbtorrent/api 1.0.3 ✓ Built into dist/ ✓ OrbTorrent 1.0 is open and allows development extensions. ✓ OrbTorrent supports extension API 1.0, which this extension needs. · The first `orb run` or `orb dev` asks the user to allow this folder in OrbTorrent. Tell them to expect it. · Not installed yet. `npx orb install` asks the user to install it. Ready. ``` Anything marked ✗ says how to fix it. 3. Load it, and keep it up to date as you work: ```sh npm run dev ``` The first time, OrbTorrent asks whether to load the folder. Allow it. `orb dev` then rebuilds and reloads the extension every time you save, and prints its checks and logs as they happen. It shows on the **Develop** tab under **In Development**, and runs on new downloads like any installed extension. ## Try it on a download In another Terminal window, in the same folder, list your downloads, then run the hook on one: ```sh npx orb downloads npx orb run check --download "Big Buck Bunny" ``` ```txt title="Output" ✓ passed: No screensavers ``` `--download` takes a download's name or its info hash. `orb run` runs the hook inside OrbTorrent itself, on the real download, and prints what it returned. To see it hold something, add a torrent that contains a `.scr` file: OrbTorrent holds it before any of it arrives. ## Install it When it does what you want, install it for good: ```sh npx orb install ``` OrbTorrent shows what the extension does and what it can reach, and installs it when you confirm, replacing the copy loaded for development. `orb install` waits for your answer. Your extension now checks every new download, until you turn it off on the **Extensions** page. ## Next - [Share it](https://orbtorrent.com/docs/extensions/sharing) as an `.orbext` file anyone can install. - Add a [setting](https://orbtorrent.com/docs/extensions/settings), such as a list of extensions to look for. - Reach a web service with [`fetch`](https://orbtorrent.com/docs/extensions/network), or check finished files with an [`afterFinish` hook](https://orbtorrent.com/docs/extensions/hooks). - Browse the [examples](https://orbtorrent.com/docs/extensions/examples). --- # Build with a coding agent > Describe an extension, and a coding agent such as Claude Code, Codex or Cursor writes it, tests it and tries it on your downloads, with you deciding each step. You don't have to write an extension yourself. Say what it should do, and a coding agent such as **Claude Code**, **Codex**, **Cursor** or **Gemini CLI** writes it, tests it and tries it on your downloads. Nothing runs in OrbTorrent until you allow it. ## From OrbTorrent 1. Open **Extensions**, at the bottom of OrbTorrent's sidebar, then the **Develop** tab. 2. Under **Build an Extension**, choose **With AI**. 3. Say what it should do, such as "Hold downloads that contain Windows screensaver (.scr) files", or pick one of the ideas under the box. 4. Click **Copy for Your Agent**, and paste it into your coding agent, started in the folder where you keep your projects. *Screenshot: OrbTorrent's Develop tab: the switch for development extensions, a box to say what the extension should do, Copy for Your Agent, and the steps you'll be asked to take* The agent needs Node.js 22.13 or later, which it can install. It then makes the extension from a template, reads the instructions that come with it, writes the code and its tests, and loops until the tests, the build and the linter all pass. ## What only you can do OrbTorrent keeps three steps for you, so nothing reaches your torrent app without you: 1. **Allow development extensions.** The switch at the top of the **Develop** tab, so your agent can load what it builds. 2. **Allow your agent's folder.** OrbTorrent asks the first time the agent tries the extension on one of your downloads. 3. **Confirm the install.** OrbTorrent shows what the extension does and what it can reach, before you install it. Your agent asks you for each one when it gets there, and can't get around them. ## Without the app The request OrbTorrent copies is plain text, so you can write it yourself. Replace the first line with what you want: ```txt title="Request" Build an OrbTorrent extension for me. What it should do: hold downloads that contain .scr files OrbTorrent is a BitTorrent app for macOS. Its extensions are TypeScript hooks, made and tested with the `orb` tool from the @orbtorrent/api npm package. You need Node.js 22.13 or later. 1. Make it with `npm create @orbtorrent/extension@latest `. Add `-- --template after-finish` if it should act once a download finishes. Then run `npm install` in the new folder. 2. Read AGENTS.md in the new folder. If what I asked for needs something extensions can't do, tell me before you build it. Then follow its loop until `npx orb test`, `npx orb build` and `npx orb lint` all pass. 3. Run `npx orb doctor`, and ask me to do anything it says only I can do in OrbTorrent. 4. Try it on one of my downloads: `npx orb downloads`, then `npx orb run --download ""`. OrbTorrent asks me to allow the folder the first time. 5. When it works, run `npx orb install`, and I'll confirm the install in OrbTorrent. ``` ## What agents read Everything an agent needs comes with the extension, so it doesn't depend on what the agent already knows: - **`AGENTS.md`,** in every new extension: the whole API, the manifest, the hooks, the test format, the rules for good results, and the errors it'll see with their fixes. Claude Code reads it through the extension's `CLAUDE.md`, Codex and Cursor read it by themselves, and the request asks any agent to read it. - **`orb doctor`,** which checks Node.js, OrbTorrent and the extension, and says what to fix. Each problem it finds has a `fix` that says how, including when only you can do it. - **The Agent Skill,** [`build-orbtorrent-extension`](https://github.com/orbtorrent/OrbTorrent/tree/main/skills/build-orbtorrent-extension), for agents that use the Agent Skills format. It has the same instructions as `AGENTS.md`. Install it with `npx skills add orbtorrent/orbtorrent`, and agents that look for a site's skills find it in [orbtorrent.com's index](https://orbtorrent.com/.well-known/agent-skills/index.json). - **These docs,** as Markdown. [llms.txt](https://orbtorrent.com/docs/extensions/llms.txt) lists the extension docs, and [llms-full.txt](https://orbtorrent.com/docs/extensions/llms-full.txt) has all of them in one file. Every page is also Markdown at its address plus `.md`. ## Made for agents `orb` is built to be driven by a program as much as by a person: - **Every command takes `--json`,** and prints one JSON object an agent can read. - **Nothing prompts.** Commands that need you, such as installing, wait for your answer in OrbTorrent, then say what you chose. - **Every command exits with 1 when something's wrong,** with what's wrong and how to fix it. - **Tests run without OrbTorrent,** with the network mocked and time on a virtual clock, so an agent can check its work in seconds. ```json title="npx orb test --json" { "ok": true, "passed": 3, "failed": 0, "tests": [ { "file": "tests/holds-a-screensaver.json", "name": "Holds a download with a screensaver in it", "passed": true, "failures": [], "result": { "status": "failed", "summary": "Contains a screensaver, which is a Windows program: Play Me.scr" }, "logs": [], "fetched": [] } ] } ``` [The orb command](https://orbtorrent.com/docs/extensions/reference/cli) has every command's JSON. ## Tips - **Say what should pass, as well as what should be caught.** "Hold films that contain a program, but not software or games" gives the agent the tests that matter. - **Ask for the narrowest permissions.** An agent asks for what the extension needs, but it's worth checking `package.json` before you confirm the install. - **Read the install sheet.** It lists what the extension can do, and that's what OrbTorrent enforces. - **Using OrbTorrent Dev?** If you build OrbTorrent itself, tell the agent to put `ORB_APP=dev` before every `npx orb` command. The Develop tab adds this to the request for you. --- # Hooks > When beforeStart, afterFinish and afterChecks hooks run, what they receive, how they hold a download, and the limits they run within. A **hook** is a function OrbTorrent calls at a moment in a download's life. It's the default export of `src/.ts`, and receives the download: ```ts title="src/check.ts" import { Download, Result } from "@orbtorrent/api"; export default async function check(download: Download): Promise { return { status: "passed", summary: "Looks like a real release" }; } ``` Each hook is listed in `package.json`, with the moment it runs at: ```json title="package.json" { "hooks": [ { "name": "check", "title": "Check for signs of a fake", "event": "beforeStart" } ] } ``` An extension can have several hooks, each in its own file, at any of the three moments. ## The three moments | | `beforeStart` | `afterFinish` | `afterChecks` | |---|---|---|---| | **Runs** | Once the file list is known, before any data downloads | Once every wanted file has finished | Once the other checks are done: after it finishes, and when one holds it | | **Can hold the download** | Yes: `failed` holds it until the user decides | No, it reports | No, it reports on the checks | | **Sees other checks' results** | No | No | Yes, in `download.checks` | | **File contents** | No: the files don't exist yet | Yes, with `"files": "contents"` | Once it's finished, with `"files": "contents"` | | **Adding files next to them** | No | Yes, with `"files": "add"` | Once it's finished, with `"files": "add"` | | **Time limit** | 15 s, of which 10 s of CPU | 6 h, of which 2 min of CPU | 6 h, of which 2 min of CPU | | **Memory** | 512 MB | 512 MB | 512 MB | **Check as early as you can.** A download's names and sizes are known before it starts, so a check on them belongs in `beforeStart`, where it can save the user a download. Use `afterFinish` to read the files themselves, or to act once a download is done. Use `afterChecks` to pass on what the checks found, such as in a message to the user's phone. ## Before a download starts Every way of adding a download goes through the same gate: - **When OrbTorrent asks before downloading,** the checks run in the confirmation as soon as the file list is known. A failed check turns the **Download** button into **Download Anyway**. - **When downloads start right away,** OrbTorrent adds the download so that the engine stops it the moment its file list arrives. The checks run, then OrbTorrent starts it, or holds it and sends a notification with **Download Anyway** and **Remove**. - **When the user adds a download stopped,** it fetches its file list, stops, and is checked the same way. It stays stopped as the user chose, and is held only if a check fails. Every extension's `beforeStart` hooks run together, at the same time, so one slow check doesn't hold up the others. A download is held if any of them fails. A hook that doesn't finish, because it threw, crashed or went over a limit, **lets the download start**. A broken extension never blocks downloads. ## After a download finishes `afterFinish` hooks run each time a download finishes: when every file the user wanted has finished. A user who downloads only some of a download's files, and chooses more of them later, finishes it again once those arrive, and the hooks run again. - **Each run gets every finished file,** not only the new ones, and its result replaces the last one. **`isNew`** marks the files that finished since the hook last ran on the download, so a hook can skip work it has done, or a message can say "Episode 5 finished" rather than repeat itself. Every file is new the first time, and when the user runs the hook again. - **Each extension's runs go one at a time,** which suits services with rate limits. A hook for ten finished downloads checks them one after another. - **At launch,** OrbTorrent also runs them for downloads that finished while it wasn't running, if that was after the extension was turned on. - **A failed or warning result sends a notification,** so the user sees it without opening the window. A later run that finds nothing new, such as the same file again once more of the download has finished, doesn't send another. ## Once its checks are done `afterChecks` hooks report on the other checks. They run: - **After a download finishes,** once its `afterFinish` hooks are done, again each time it finishes. - **When a check holds a download before it starts.** `download.isHeld` is `true`, and none of its files are on disk. `download.checks` has every other extension's result on the download, worst first, with what each found about particular files: ```ts title="src/notify.ts" import { Download, Result, fetch } from "@orbtorrent/api"; export default async function notify(download: Download): Promise { const problems = (download.checks ?? []).filter((check) => check.status === "failed" || check.status === "warning"); const message = download.isHeld ? `Held: ${problems[0]?.summary}` : problems.length > 0 ? `Finished, but ${problems[0].extension.title} says: ${problems[0].summary}` : "Finished, and every check passed"; await fetch("https://ntfy.sh/my-downloads", { method: "POST", headers: { Title: download.name }, body: message }); return { status: "passed", summary: "Sent to your phone" }; } ``` - **Their results aren't checks.** They show under **After its checks** in the download's Checks tab, not in its ring, and a failed one doesn't hold anything. - **The extension's page says it can see what other extensions found,** before it's installed. - **Each extension's runs go one at a time,** as after a download finishes. ## Running again The user can run any hook again from a download's **Checks** tab, or from the **Activity** tab. If a held download passes when its `beforeStart` checks run again, it starts. An `afterChecks` hook can run again on a download that's finished or held. While you're developing, `npx orb run --download ""` runs a hook on one of your downloads. [Run and debug](https://orbtorrent.com/docs/extensions/debugging) explains. ## What a hook receives ```ts interface Download { id: string; // its info hash name: string; size: number; // bytes of the wanted files isPrivate: boolean; // from a private tracker files: DownloadFile[]; checks?: CheckResult[]; // afterChecks only: the other checks' results isHeld?: boolean; // afterChecks only: whether a check is holding it } interface DownloadFile { index: number; path: string; // relative to the download's folder, including its top folder size: number; // bytes wanted: boolean; // whether the user chose to download it isNew?: boolean; // once it's finished: whether it finished since the hook last ran } ``` - **Paths are relative,** such as `Big Buck Bunny/Big Buck Bunny.mp4`. Hooks never see where a download is saved. - **Files the user skipped are listed,** with `wanted: false`. Most checks should leave them out, since they won't arrive. - **`isNew` is there once the download has finished,** and is `false` for files the user skipped. [The API reference](https://orbtorrent.com/docs/extensions/reference/api#checkresult) describes `CheckResult`. - **Hooks never see a download's trackers,** which can carry a private tracker's personal key, or any other download. To read a file's contents, an `afterFinish` hook asks for `"files": "contents"` in its permissions and uses [`files`](https://orbtorrent.com/docs/extensions/files). ## What a hook returns A [result](https://orbtorrent.com/docs/extensions/results): a status, a one-line summary, and optionally details in Markdown, a link, and what it found about particular files. ```ts return { status: "failed", summary: "Contains a program disguised as a video: Film.mp4.exe", details: "A film never needs a program. `.exe` files run on Windows.", link: { title: "Read more", url: "https://example.com/fakes" }, files: [{ file: download.files[0], status: "failed", summary: "A program disguised as a video" }], }; ``` ## Each run is fresh Every run of a hook starts a new, sandboxed process, which exits when the hook returns. Nothing carries over from one run to the next, except what you keep in [`storage`](https://orbtorrent.com/docs/extensions/storage). Module-level variables start again each time. ## When hooks don't run - **The extension is off,** on the Extensions page, or extensions are off altogether. - **A required setting is empty.** The extension shows **Needs setup**, and its hooks are skipped until it's filled in. [Settings](https://orbtorrent.com/docs/extensions/settings) explains. - **It needs a newer OrbTorrent.** An extension built with a newer API than OrbTorrent supports doesn't run. [Versions](https://orbtorrent.com/docs/extensions/reference/api#versions) explains. Turning extensions off stops any checks running. A download waiting on its checks starts, and one that's held stays paused for the user to start. --- # Results > What a hook returns, how each status shows in OrbTorrent, how to mark the files it's about, and how to write results people understand. A hook returns a **result**, which OrbTorrent shows in the download's **Checks** tab, and in the confirmation before a download starts. ```ts interface Result { status: "passed" | "warning" | "failed" | "skipped"; summary: string; details?: string; link?: { title: string; url: string }; notify?: boolean; files?: FileResult[]; } ``` ## Status | Status | Means | Before it starts | After it finishes | |---|---|---|---| | `passed` | Nothing wrong. | The download starts. | Shown in the Checks tab, and as a notification with `notify: true`. | | `warning` | Worth a look. | The download starts, with a badge. | A notification, unless it's nothing new. | | `failed` | Something's wrong. | **The download is held** until the user decides. | A notification, unless it's nothing new. | | `skipped` | The check didn't apply, or couldn't run. | The download starts. | Shown in the Checks tab, and as a notification with `notify: true`. | A hook that throws, crashes or goes over a limit shows **Didn't finish**, with the reason, such as "Didn't finish: it took longer than 15 s". Before a download starts, that lets it start. "Nothing new" is a run after the user chose more of a download's files that finds what an earlier run found already: the same files, no worse. The user was told the first time. An [`afterChecks` hook's](https://orbtorrent.com/docs/extensions/hooks#once-its-checks-are-done) result reports on the checks rather than being one. It shows under **After its checks**, never holds a download, and sends a notification only with `notify: true`. ## Summary One line, shown in the list of checks, in the confirmation, and in notifications. It's cut at 300 characters, and line breaks become spaces. ## Details Optional, in Markdown, shown under the summary when the user opens the check. OrbTorrent draws: - paragraphs, with lines wrapped by hand joined up; - headings, with `#`, as bold lines; - lists, with `- ` or `* `, and numbered lists, with `1. `; - **bold**, *italic*, `code` and [links](https://orbtorrent.com) inside them. Tables, images and code blocks aren't drawn. Details are cut at 20,000 characters, and the user can select and copy them. ## Link Optional: a button under the details, such as `{ title: "Open VirusTotal Report", url: "https://…" }`. The title is cut at 60 characters, and the address must be `https://` or `http://`. ## Files Optional: what the hook found about particular files. OrbTorrent marks each problem beside its file, in the download's Files tab, in the menu bar, and before it starts in the confirmation, and lists them in the check with **Show in Finder** and each file's link. ```ts return { status: "failed", summary: "12 of 70 engines flag setup.exe", files: [ { file: setup, status: "failed", summary: "12 of 70 engines call it malicious", link: { title: "VirusTotal Report", url: report } }, { file: readme, status: "passed" }, ], }; ``` - **`file`** is a file from `download.files`, or its `index`. Naming one that isn't there makes the check **Didn't finish**, with "Its result's “files” names a file that isn't part of this download." - **`status`** is `passed`, `warning` or `failed`. Files that passed show as a count, such as "14 other files passed". - **`summary`** says why in a few words, and is cut at 300 characters. **`link`** follows the same rules as the result's. - **A result is never better than its worst file.** One that says `passed` with a `failed` file shows as failed, and the extension's log says why. - **Each result replaces the hook's last one,** so name every file you know about, not only the [new ones](https://orbtorrent.com/docs/extensions/hooks#after-a-download-finishes). Up to 1,000 files are kept, problems first. ## Notification `notify: true` also shows the summary as a notification, titled with the extension's name, with the download's name under it. Clicking it opens the download's Checks tab. ```ts { status: "passed", summary: "Added English subtitles", notify: true } ``` Use it for news the user wants without looking, such as subtitles or artwork the extension added. Some results are notifications already, so they don't need it: a warning or failure after a download finishes, and a download held before it starts. Only a warning or failure makes a sound. ## Write results people understand - **Say which file and why.** "Contains a program disguised as a video: Film.mp4.exe" tells the user what to look at. "Suspicious content detected" doesn't. Put the file in `files` too, so OrbTorrent can mark it. - **Use plain words, in sentence case, with no full stop** in the summary. - **Use `failed` only when you're sure.** A false alarm that holds a download is worse than a missed warning. Use `warning` for "worth a look". - **Before a download starts, a network error or a timeout means `skipped`, never `failed`.** The user's download shouldn't wait on your server. - **Say when you skipped, and why,** such as "VirusTotal's daily limit was reached. Run it again later". - **Don't put secrets or file contents in results.** They're shown, kept with the download, and copied with **Copy Result**. ## Examples ```ts // Held before it starts. { status: "failed", summary: "Contains a screensaver, which is a Windows program: Play Me.scr" } // Starts, with a badge. { status: "warning", summary: "Far too small for 1080p: 180 MB" } // After it finishes, with the file it found and a report. { status: "failed", summary: "12 of 70 engines flag setup.exe", link: { title: "Open VirusTotal Report", url: "https://www.virustotal.com/gui/file/…" }, files: [{ file: 0, status: "failed", summary: "12 of 70 engines call it malicious" }], } // Couldn't run. { status: "skipped", summary: "VirusTotal didn't accept the API key. Check it on OrbTorrent's Extensions page" } ``` ## Returning something else If a hook returns anything without a `status` OrbTorrent knows and a `summary`, the check shows "Didn't finish: It returned something that isn't a result: return { status, summary }." TypeScript catches this before it happens, if your hook is typed `Promise`. --- # Extension settings > Give an extension settings that OrbTorrent draws, such as an API key kept in the Keychain, read them with getPreferenceValues, and check them with verify. An extension's settings are declared in `package.json`, and OrbTorrent draws them on the extension's page. You don't build any interface. ```json title="package.json" { "preferences": [ { "name": "apiKey", "type": "password", "title": "API key", "description": "Your free VirusTotal API key.", "placeholder": "64 letters and digits", "required": true }, { "name": "scope", "type": "dropdown", "title": "Files to check", "default": "risky", "data": [ { "title": "Programs, archives and documents", "value": "risky" }, { "title": "All files", "value": "all" } ] }, { "name": "uploadUnknown", "type": "checkbox", "title": "Files VirusTotal hasn't seen", "label": "Upload them to be scanned (up to 32 MB each)", "default": false } ] } ``` ## Types | Type | Value | Draws | |---|---|---| | `textfield` | Text | A text field. | | `password` | Text | A secure field, with **Save** and **Remove**. The value is kept in the macOS Keychain, never in a file, and hidden in logs. It can't have a default. | | `checkbox` | `true` or `false` | A checkbox, with `label` beside it. | | `dropdown` | One of `data`'s values | A menu of `data`'s titles. | Every setting has a `name` (letters, digits and underscores, starting with a letter), a `type` and a `title`, and can have a `description`, a `placeholder`, a `default`, and `required`. [Manifest](https://orbtorrent.com/docs/extensions/reference/manifest#preferences) has the rules. ## Read them ```ts title="src/scan.ts" import { Download, Result, getPreferenceValues } from "@orbtorrent/api"; export default async function scan(download: Download): Promise { const { apiKey, scope, uploadUnknown } = getPreferenceValues(); // … } ``` **Every setting has a value** when a hook runs: what the user chose, or its `default`, or else `""`, `false`, or a dropdown's first option. You never need to check for `undefined`. **They're typed.** `orb build` writes `orb-env.d.ts` from `package.json`, so `getPreferenceValues()` knows each setting's name and type, and a dropdown's values: ```ts title="orb-env.d.ts" // Generated by `orb build` from package.json. Don't edit: change package.json instead. declare global { interface OrbPreferences { /** Your free VirusTotal API key. */ apiKey: string; scope: "risky" | "all"; uploadUnknown: boolean; } } export {}; ``` Leave `orb-env.d.ts` out of version control: the template's `.gitignore` already does. ## Required settings While a `required` setting is empty, the extension shows **Needs setup** on the Extensions page, and its hooks are **skipped** rather than run. Its page says which settings to add. Use `required` for anything the extension can't work without, such as an API key. ## Help the user set it up Put the steps to set your extension up, such as how to get an API key, in `help.md` next to `package.json`. OrbTorrent shows it beside the settings, under **How to Set It Up**. ```md title="help.md" ### Get a free API key 1. Sign up or sign in at [virustotal.com](https://www.virustotal.com). 2. Open your profile menu and choose **API key**. 3. Copy the key into **API key** here and press **Verify**. ``` ## Check them with verify To check settings when the user presses **Verify**, such as that an API key works, name a file in `verify`: ```json title="package.json" { "verify": "verify" } ``` Its default export gets the settings, and says whether they're right: ```ts title="src/verify.ts" import { VerifyResult, fetch } from "@orbtorrent/api"; /** The EICAR test file, which every antivirus engine knows. */ const knownFile = "275a021bbfb6489e54d471899f7db9d1663fc695ec2fe2a2c4538aabf651fd0f"; export default async function verify(preferences: OrbPreferences): Promise { const key = preferences.apiKey.trim(); if (!key) return { ok: false, message: "Enter your API key." }; const response = await fetch(`https://www.virustotal.com/api/v3/files/${knownFile}`, { headers: { "x-apikey": key, accept: "application/json" }, }); switch (response.status) { case 200: return { ok: true, message: "The key works." }; case 401: case 403: return { ok: false, message: "VirusTotal didn't accept this key. Copy it again from your VirusTotal profile." }; default: return { ok: false, message: `VirusTotal answered with status ${response.status}. Try again in a moment.` }; } } ``` This is shortened from VirusTotal Scan's own, which looks up a file every antivirus engine knows. - Return `{ ok: true }`, with an optional `message`, or `{ ok: false, message }`. The message shows under the settings. - `verify` has the same permissions as the hooks, and the same limits as a `beforeStart` hook: 15 seconds, of which 10 of CPU. - [Test it](https://orbtorrent.com/docs/extensions/testing#test-verify) with `"verify": true` in a test file. ## When settings change Settings are read fresh for every run, so a hook always sees the latest. Removing an extension deletes its settings, including passwords in the Keychain. --- # Network requests > Reach a web service from an extension with fetch, list the hosts it needs, and handle errors, rate limits and redirects. Extensions have no network of their own. They reach the web through `fetch` from `@orbtorrent/api`, and only the hosts they list. ## List the hosts Add each host the extension talks to in `package.json`: ```json title="package.json" { "permissions": { "network": ["www.virustotal.com"] } } ``` - **Exact host names,** in lowercase, without `https://` or a path. `virustotal.com` doesn't cover `www.virustotal.com`: list each one you use. There are no wildcards. - **Users see the list** on the extension's page and before they install it, as "Connect to www.virustotal.com". Ask only for what you need. ## Make a request ```ts title="src/notify.ts" import { Download, Result, fetch, getPreferenceValues } from "@orbtorrent/api"; export default async function notify(download: Download): Promise { const { webhook } = getPreferenceValues(); const response = await fetch(webhook, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ content: `Finished: ${download.name}` }), }); if (!response.ok) { return { status: "skipped", summary: `Discord didn't take the message (status ${response.status})` }; } return { status: "passed", summary: "Sent to your Discord channel" }; } ``` This needs `"network": ["discord.com"]`, and a `webhook` setting for the channel's webhook address. ### The request `fetch(url, init?)` takes an address, as a string or a `URL`, and: | | | |---|---| | `method` | `GET`, `HEAD`, `POST`, `PUT`, `PATCH` or `DELETE`. `GET` unless you say. | | `headers` | An object, such as `{ "x-apikey": key }`. OrbTorrent sets `host`, `content-length`, `connection` and `transfer-encoding` itself, and drops `cookie`. | | `body` | Text, using `JSON.stringify` for JSON, or a form from [`files.formData`](https://orbtorrent.com/docs/extensions/files#upload-a-file). | ### The response | | | |---|---| | `status` | The HTTP status, such as `200`. | | `ok` | Whether the status is 200 to 299. | | `url` | The address it came from, after any redirects. | | `headers` | `get(name)`, `has(name)` and `entries()`. Names are case-insensitive. | | `text()` | The body as text. | | `json()` | The body parsed as JSON. | | `bytes()` | The body as a `Uint8Array`, for images, zip files and anything that isn't text. | | `arrayBuffer()` | The body as an `ArrayBuffer`. | Text and JSON arrive as they are, and anything else arrives intact as bytes, so an extension can fetch an image and [save it next to a download](https://orbtorrent.com/docs/extensions/files#add-a-file): ```ts const poster = await fetch(posterAddress); await files.save(video, "-poster.jpg", await poster.bytes()); ``` ## The rules OrbTorrent checks every request before it's made: - **HTTPS only,** on the standard port, 443. - **Only listed hosts.** A request to any other is refused with an error that says which host to add. - **Never your Mac or your network.** A host that points to an address on the Mac or the local network is refused, wherever its name says it is. OrbTorrent looks the host up, checks every address, and connects to the very address it checked, so a host can't pass the check and then switch to a local address. - **Redirects only to listed hosts.** A redirect to any other host isn't followed: the hook gets the redirect response itself. - **No credentials in the address.** Put them in a header instead. - **Responses up to 10 MB,** and **30 seconds** for each request. - **No cookies and no shared cache,** so one extension can't see another's sessions. A refused request, or one that fails, throws an error. [Errors and fixes](https://orbtorrent.com/docs/extensions/reference/errors#network) lists the messages. ## Handle failure The network can always fail, so decide what that means for your check: ```ts let response; try { response = await fetch(`https://api.example.com/check/${download.id}`); } catch (error) { // Before a download starts, a network error means skipped, never failed. return { status: "skipped", summary: `Couldn't reach example.com: ${(error as Error).message}` }; } ``` **Before a download starts, a network error or a timeout means `skipped`, never `failed`.** A `beforeStart` hook has 15 seconds in all, so keep its requests few and quick. ## Respect rate limits Many free APIs allow only so many requests a minute. To stay within them: - **Wait between requests** with [`sleep`](https://orbtorrent.com/docs/extensions/reference/api#sleep), which doesn't use CPU time. An `afterFinish` hook has 6 hours, so it can pace itself. - **Show progress** on long runs with [`progress`](https://orbtorrent.com/docs/extensions/storage#show-progress), so the user sees it working. - **Keep answers** in [`storage`](https://orbtorrent.com/docs/extensions/storage), so running the hook again doesn't ask again. - **Back off on 429.** Wait, try once more, and if it's refused again, return `skipped` with what happened. Each extension's `afterFinish` and `afterChecks` runs go one at a time, so a hook never competes with itself for a rate limit. ## Test it Tests answer requests from mocks, so they run without the network. [Testing](https://orbtorrent.com/docs/extensions/testing#mock-the-network) explains. ```json title="tests/sends-the-name.json" { "preferences": { "webhook": "https://discord.com/api/webhooks/123/abc" }, "download": { "name": "Ubuntu 26.04", "files": [{ "path": "ubuntu-26.04.iso", "size": 6000000000 }] }, "mocks": { "fetch": [{ "method": "POST", "url": "https://discord.com/api/webhooks/*", "status": 204 }] }, "expect": { "status": "passed", "fetched": ["https://discord.com/api/webhooks/123/abc"] } } ``` --- # Read and add files > Fingerprint and upload a finished download's files, and add files such as subtitles next to them with files.save. Every hook gets the **names and sizes** of a download's files. To do more, an extension asks for it in `package.json`: | `"files"` | Lets it | Users see | |---|---|---| | `"contents"` | Fingerprint and upload the download's files | "Read the contents of your downloads" | | `"add"` | Add files, such as subtitles, next to them | "Add files, such as subtitles, next to your downloads" | | `["contents", "add"]` | Both | Both | ```json title="package.json" { "permissions": { "files": "contents" } } ``` Users see what it asks for on the extension's page, and before they install it. Files only exist once a download has finished, so only `afterFinish` hooks can use them, and `afterChecks` hooks once the download has finished. A `beforeStart` hook that asks gets an error: "File 0 isn't available: it isn't downloaded yet, or it isn't part of this download." Extensions never read or write files directly, and never see where they're saved. They ask OrbTorrent, which does it for them, after checking the request. ## Fingerprint a file `files.sha256(file)` returns the file's SHA-256 fingerprint, in lowercase hex: ```ts title="src/scan.ts" import { Download, Result, files } from "@orbtorrent/api"; export default async function scan(download: Download): Promise { for (const file of download.files.filter((file) => file.wanted)) { const sha256 = await files.sha256(file); // Look the fingerprint up with a service, such as VirusTotal. } return { status: "passed", summary: "…" }; } ``` OrbTorrent computes it natively, so even large files are quick, and the time doesn't count towards the hook's CPU limit. Pass a file from `download.files`, or its `index`. ## Upload a file `files.formData(fields)` makes a multipart form to send with [`fetch`](https://orbtorrent.com/docs/extensions/network). Give it text fields, and files from `download.files`: ```ts import { fetch, files } from "@orbtorrent/api"; const form = files.formData({ file: download.files[0] }); const response = await fetch("https://www.virustotal.com/api/v3/files", { method: "POST", headers: { "x-apikey": apiKey }, body: form, }); ``` OrbTorrent streams the file from disk as it uploads, so the extension never holds it in memory. To set a file's name or type in the form, build it with `append`: ```ts const form = files.formData({ comment: "From OrbTorrent" }); form.append("file", download.files[2], { filename: "sample.bin", contentType: "application/octet-stream" }); ``` > **Uploading shares the file** > > Uploading a user's file sends it to someone else. Only upload when the user has turned it on in your settings, say where it goes, and leave it off for anything private. VirusTotal Scan's upload setting is off unless the user turns it on. ## Add a file `files.save(file, suffix, contents)` adds a file next to one of the download's, in the same folder, named after it with `suffix`. Players and media servers find subtitles and artwork named this way: ```ts title="src/subtitles.ts" import { Download, Result, fetch, files } from "@orbtorrent/api"; export default async function subtitles(download: Download): Promise { const video = download.files.find((file) => file.wanted && /\.(mkv|mp4)$/i.test(file.path)); if (!video) return { status: "skipped", summary: "No video in this download" }; const response = await fetch("https://subtitles.example.com/find?name=" + encodeURIComponent(video.path)); const saved = await files.save(video, ".en.srt", await response.text()); return { status: "passed", summary: `Added English subtitles: ${saved.path}`, notify: true }; } ``` `Film (2024)/Film.2024.1080p.mkv` gets `Film (2024)/Film.2024.1080p.en.srt`. It needs `"files": "add"`, and returns the new file's `path`, relative to the download's folder like the paths in `download.files`, and its `size`. - **The suffix** starts with `.`, `-`, `_` or a space, and ends with an extension, such as `.en.srt`, `.forced.srt`, `-poster.jpg` or `.nfo`. It can't hold a `/`, so the file always lands next to the one it's for. - **The contents** are text, saved as UTF-8, or bytes as a `Uint8Array`, such as an image from [`response.bytes()`](https://orbtorrent.com/docs/extensions/network#the-response). - **Only files that can't run.** Subtitles (`.srt` `.ass` `.ssa` `.vtt` `.sub` `.idx` `.sup` `.smi` `.lrc`), text (`.txt` `.nfo` `.md` `.json` `.xml` `.csv` `.cue`), images (`.jpg` `.jpeg` `.png` `.webp` `.gif` `.heic`) and checksums (`.sfv` `.md5` `.sha1` `.sha256`). Never a program, a script or an app. - **It never replaces a file,** except one the same hook added to the same download before, such as when the user runs it again. If a file is already there, such as subtitles that came with the download, `files.save` throws "“Film.2024.1080p.en.srt” already exists. An extension can't replace a file it didn't add." Catch it, and say what was there. - **Up to 10 MB a file,** and **200 files and 50 MB a run**. OrbTorrent marks each file it adds as downloaded from the internet, as browsers do, and makes it readable, never runnable. ### What the user sees The Checks tab lists the files a check added, with **Show in Finder**. They belong with the download: moving it takes them along, and removing it with its files removes them too. Removing only the download leaves them, like its own files. Set `notify: true` in the result, as above, so the user hears about it without opening the Checks tab. [Results](https://orbtorrent.com/docs/extensions/results#notification) explains. ## Which files - **Skip files the user didn't want.** They're in `download.files` with `wanted: false`, and they weren't downloaded, so asking for one throws "File 1 isn't available". - **Files can be missing.** If the user moved or deleted a file after it finished, asking for it throws "File 3 is missing: it was moved or deleted after downloading." Catch it, and report what you could check. - **Check the riskiest first,** such as programs before videos, so a hook that's stopped early has checked what matters. ## Test it In a test, give each file a `sha256`, which `files.sha256` returns: ```json { "download": { "name": "Tool 2.0", "files": [{ "path": "Tool 2.0/setup.exe", "size": 5000000, "sha256": "aaaa…" }] } } ``` Files a hook adds aren't written anywhere in a test. `expect` checks them with `saved`, their paths, and `savedIncludes`, text in each. `download.alsoOnDisk` lists files already next to the download's, which `files.save` won't replace: ```json title="tests/skips-a-film-with-subtitles.json" { "download": { "name": "Film (2024)", "files": [{ "path": "Film (2024)/Film.2024.1080p.mkv", "size": 4000000000 }], "alsoOnDisk": ["Film (2024)/Film.2024.1080p.en.srt"] }, "expect": { "status": "skipped", "summaryIncludes": "already has" } } ``` [Testing](https://orbtorrent.com/docs/extensions/testing) has the rest. --- # Storage and progress > Keep data between runs with storage, show progress on long checks, read about the extension with environment, and wait without using CPU time. ## Keep data between runs Every run of a hook is a fresh process, so nothing carries over by itself. `storage` keeps JSON values between runs: ```ts import { storage } from "@orbtorrent/api"; const cached = await storage.get<{ malicious: number; at: number }>(sha256); if (cached && Date.now() - cached.at < 7 * 24 * 60 * 60 * 1000) { // Use the answer from the last week, rather than asking again. } await storage.set(sha256, { malicious: 0, at: Date.now() }); ``` | | | |---|---| | `storage.get(key)` | The value, or `undefined` if there's none. | | `storage.set(key, value)` | Keeps any JSON value. | | `storage.remove(key)` | Removes it. | | `storage.keys()` | Every key, as an array. | - **Up to 1 MB in all,** and keys up to 200 characters. Past the limit, `set` throws "Storage is full: an extension can keep up to 1 MB. Remove keys it no longer needs." - **Kept across runs and updates,** and deleted when the extension is removed. - **Each extension has its own.** No other extension can read it. Use it to avoid repeating work, such as lookups you've already made. VirusTotal Scan keeps each answer for a week, so running it again doesn't use up the user's daily limit. Don't keep secrets here: they belong in a [password setting](https://orbtorrent.com/docs/extensions/settings). ## Show progress On a long check, tell the user how far it's got. The check's row in the **Checks** tab shows it, and so does `orb dev`: ```ts import { progress } from "@orbtorrent/api"; for (const [index, file] of checking.entries()) { await progress({ done: index, total: checking.length, message: `Checking ${file.path}…` }); // … } ``` `done` and `total` draw a progress bar, and `message`, up to 200 characters, says what's happening, such as "Waiting a minute for VirusTotal's rate limit…". ## Wait `sleep(milliseconds)` waits without using CPU time, so it doesn't count towards a hook's CPU limit: ```ts import { sleep } from "@orbtorrent/api"; await sleep(15_500); // 4 requests a minute ``` `setTimeout` works too, but `sleep` reads better in an `async` hook. ## About the extension `environment` says how the extension is running: | | | |---|---| | `environment.extensionName` | Its `name` from `package.json`. | | `environment.isDevelopment` | Whether it's loaded from a folder with `orb dev` or `orb run`, rather than installed. | | `environment.entryPoint` | What's running: `{ type: "hook", name, event }` or `{ type: "verify", name }`. | | `environment.apiVersion` | The extension API version this OrbTorrent provides, such as `1.0.0`. | ## Logs `console.log`, `info`, `debug`, `trace`, `warn` and `error` write to the extension's log. Users see it on the extension's page, under **Logs**, and `orb dev` and `orb run` print it as it happens. Each entry is cut at 10,000 characters, and OrbTorrent hides password settings in it. Don't log file contents, or anything private. --- # Testing > Write JSON tests that run an extension's hooks without OrbTorrent, with the network mocked, file fingerprints and storage set up, and time on a virtual clock. `orb test` runs every JSON file in `tests/` against your built hooks, without OrbTorrent. Each test is a download, the network's answers, and what the hook should return. ```sh npm test ``` ```txt title="Output" ✓ Holds a download with a screensaver in it tests/holds-a-screensaver.json ✓ Ignores a screensaver the user isn't downloading tests/ignores-a-skipped-screensaver.json ✓ Lets a film without one start tests/lets-a-film-start.json 3 passed ``` To run some tests only, name them: `npx orb test tests/holds-a-screensaver.json`. ## A test ```json title="tests/fails-a-program-many-engines-flag.json" { "name": "Fails a program that 12 engines flag", "hook": "scan", "preferences": { "apiKey": "test-key" }, "download": { "name": "Tool 2.0", "files": [{ "path": "Tool 2.0/setup.exe", "size": 5000000, "sha256": "aaaa…" }] }, "mocks": { "fetch": [ { "url": "https://www.virustotal.com/api/v3/files/*", "json": { "data": { "attributes": { "last_analysis_stats": { "malicious": 12, "suspicious": 0, "undetected": 58, "harmless": 0 } } } } } ] }, "expect": { "status": "failed", "summaryIncludes": "12 of 70 engines" } } ``` [Test files](https://orbtorrent.com/docs/extensions/reference/tests) lists every field. The ones you'll use most: - **`download`**: its `name` and `files`, each with a `path`, a `size`, and `"wanted": false` for a file the user skipped. Once it's finished, `"isNew": false` marks a file a hook saw on an earlier run. - **`preferences`**: the settings. Any you leave out take their defaults. - **`expect`**: what the result should be. ## What to test - **Each thing it catches,** with the summary you expect. - **What must pass.** Releases that look like what you catch, but aren't, matter most: a false alarm that holds a download is worse than a missed warning. The Fake Download Check tests real releases that must pass, as well as each fake it holds. - **What it skips,** such as files the user didn't want, and when a setting is empty. - **Failure:** a refused API key, a rate limit, a server error. Before a download starts, each should be `skipped`. ## Mock the network Requests are answered from `mocks.fetch`, never the real network: ```json { "mocks": { "fetch": [ { "url": "https://www.virustotal.com/api/v3/files/aaaa…", "status": 429, "times": 1, "json": { "error": { "code": "QuotaExceededError" } } }, { "url": "https://www.virustotal.com/api/v3/files/aaaa…", "json": { "data": { "attributes": { "last_analysis_stats": { "malicious": 0, "suspicious": 0, "undetected": 60, "harmless": 10 } } } } } ] } } ``` - **A request takes the first mock that matches** its `method` (`GET` unless you say) and `url`. - **`*` in a `url` matches any text,** such as `https://www.virustotal.com/api/v3/files/*`. - **`times`** answers that many requests, then the next matching mock takes over: here, a rate limit, then success. - **`json`** answers with JSON, and sets `content-type: application/json`. **`text`** answers with text. **`status`** is 200 unless you say, and **`headers`** adds headers. - **A request with no mock fails the test,** and says which: "The test has no mock for GET https://…. Add one to "mocks": { "fetch": [...] }." The rules OrbTorrent applies run in tests too: a host missing from `permissions.network`, or a plain `http://` address, is refused with the same error. `"fetched"` in `expect` checks that a request was made, and `"not": { "fetched": […] }` that one wasn't. ## Files and storage - **`files.sha256`** returns each file's `sha256` from the test. A file without one fails the test, saying so. - **`storage`** sets what the extension's storage holds before the run, to test that it uses what it kept: ```json { "storage": { "lookups": { "aaaa…": { "at": 9999999999999, "stats": { "malicious": 0, "suspicious": 0, "total": 70 } } } }, "expect": { "status": "passed", "not": { "fetched": ["https://www.virustotal.com/api/v3/files/aaaa…"] } } } ``` Reading files follows OrbTorrent's rules too: without `"files": "contents"`, in a `beforeStart` hook, or for a file that isn't wanted, it's refused. - **`files.save`** follows OrbTorrent's rules and messages, and keeps what it adds instead of writing it. `expect` checks it with `saved` and `savedIncludes`, and `download.alsoOnDisk` lists files already there, which it won't replace. [Read and add files](https://orbtorrent.com/docs/extensions/files#test-it) has an example. - **Bytes:** a mock with `base64` answers with bytes, such as an image, for `response.bytes()`. ## Files a result names `expect.files` checks what the result says about each file, by path, with `*` matching any text: ```json { "expect": { "status": "failed", "files": { "Tool 2.0/setup.exe": "failed", "*.txt": "none" } } } ``` `"none"` means the result says nothing about it. A result is checked as OrbTorrent checks it: it can only name the download's files, and it counts as no better than its worst file. ## Reports on the checks An `afterChecks` hook gets other extensions' results from `download.checks`, and `"held": true` makes the download one a check is holding: ```json title="tests/says-what-virustotal-found.json" { "hook": "notify", "download": { "name": "Tool 2.0", "files": [{ "path": "Tool 2.0/setup.exe" }], "checks": [ { "extension": { "name": "virustotal", "title": "VirusTotal Scan" }, "status": "failed", "summary": "12 of 70 engines flag setup.exe", "files": [{ "path": "Tool 2.0/setup.exe", "status": "failed" }] } ] }, "mocks": { "fetch": [{ "method": "POST", "url": "https://ntfy.sh/*" }] }, "expect": { "status": "passed", "fetched": ["https://ntfy.sh/*"] } } ``` ## Time Timers run on a **virtual clock**. `setTimeout` and `sleep` fire as soon as nothing else is pending, and `Date.now()` moves on by the time waited. A hook that waits 15 seconds between requests for a rate limit is tested in moments, and a test of a minute's back-off takes no time at all. Each test can run for 10 seconds of real time, unless it sets `timeout` in milliseconds. ## Test verify To test the settings check, set `"verify": true` and the `preferences` to check, and expect `ok` and part of the `message`: ```json title="tests/verify-refuses-a-bad-topic.json" { "name": "Verify refuses a topic with spaces", "verify": true, "preferences": { "topic": "my downloads" }, "expect": { "ok": false, "messageIncludes": "letters, digits" } } ``` ## Expectations | Key | Passes when | |---|---| | `status` | The result's status matches. | | `summaryIncludes` | The summary includes the text, ignoring case. | | `summaryMatches` | The summary matches the regular expression. | | `detailsIncludes` | The details include the text. | | `fetched` | Each URL was requested. `*` matches any text. | | `files` | Each file the result names has this status, by path, or `"none"`. | | `throws` | The hook threw an error including the text. | | `ok` and `messageIncludes` | For `verify`: whether it accepted the settings, and its message. | | `not` | The expectation inside it fails. | A test with no `expect` passes if the hook returns without throwing. ## How tests run `orb test` builds the extension, then runs each hook in a Node.js `vm` context set up like OrbTorrent's extension host: no Node.js APIs, and every request, fingerprint and storage call answered by the test. The rules and error messages are OrbTorrent's own. Node's JavaScript engine isn't JavaScriptCore, which OrbTorrent uses, so rare differences are possible. To be sure, run a hook in OrbTorrent itself on a real download with [`orb run`](https://orbtorrent.com/docs/extensions/debugging#run-a-hook-on-a-download). ## For scripts and agents `npx orb test --json` prints the results as one JSON object, and exits with 1 if any test failed: ```json { "ok": false, "passed": 2, "failed": 1, "tests": [ { "file": "tests/lets-a-film-start.json", "name": "Lets a film without one start", "passed": false, "failures": ["expected status “failed”, got “passed” (No screensavers)"], "result": { "status": "passed", "summary": "No screensavers" }, "logs": [], "fetched": [] } ] } ``` ## In continuous integration Tests, the build and the linter all run without OrbTorrent or a Mac, so they suit any CI. On GitHub Actions: ```yaml title=".github/workflows/test.yml" name: Test on: [push, pull_request] jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: 22 - run: npm ci - run: npx orb test - run: npx orb lint - run: npx orb build ``` --- # Run and debug > Load an extension into OrbTorrent while you work on it, run a hook on a real download, read its logs, and fix what orb doctor finds. Tests catch most problems without OrbTorrent. To see an extension work for real, load it into OrbTorrent and run it on your downloads. ## Get OrbTorrent ready `orb dev` and `orb run` talk to OrbTorrent on your Mac. They need: 1. **OrbTorrent open.** 2. **Allow development extensions** on: **Extensions**, at the bottom of OrbTorrent's sidebar, then the **Develop** tab. Only you can turn it on. 3. **Your folder allowed.** The first time a folder loads, OrbTorrent asks whether to load it. Allow it. `npx orb doctor` checks all of this, and says what's missing: ```txt title="npx orb doctor" ✓ Node.js 24.11.1 ✓ No Screensavers (no-screensavers), with @orbtorrent/api 1.0.3 ✓ Built into dist/ ✗ OrbTorrent is open, but doesn't allow development extensions. → Ask the user to turn on “Allow development extensions” in OrbTorrent: Extensions, at the bottom of the sidebar, then the Develop tab. Only the user can. If the switch at the top of the Installed tab is off, extensions are off altogether, and that needs turning on first. 1 thing to fix. ``` A ✓ is fine, a ✗ needs fixing, and a · is information. [orb doctor](https://orbtorrent.com/docs/extensions/reference/cli#orb-doctor) lists everything it checks. ## Work with orb dev ```sh npm run dev ``` ```txt title="Output" ✓ Loaded No Screensavers into OrbTorrent. Rebuilding on every save. Press Control-C to stop; the extension stays in OrbTorrent. ``` `orb dev` loads the extension into OrbTorrent, then rebuilds and reloads it every time you save a file. It also prints what the extension does as it happens, on any download: ```txt title="Output" [check · Ocean Life (2026) 1080p] ✗ failed: Contains a screensaver, which is a Windows program: Play Me.scr ✓ Rebuilt and reloaded 14:02:31 ``` Each line says which hook and which download, then the check's result, its progress, or what it logged. A loaded extension runs on new downloads like an installed one, and shows on the **Develop** tab under **In Development**, with **Reload** and **Show in Finder**. It stays loaded after you stop `orb dev`, until you remove it from its page or install it for good. > **Developing an official extension** > > A development copy of an official extension, such as the Fake Download Check, stands in for the official one while it's loaded, marked **Development copy**. Remove it from its page, and the official one takes its place again. ## Run a hook on a download To try a hook on one of your downloads, list them, then run it: ```sh npx orb downloads npx orb run check --download "Big Buck Bunny" ``` ```txt title="npx orb downloads" finished 276.0 MB Big Buck Bunny dd8255ecdc7ca55fb0bbf81323d87062db1f6d1c 42% 6.0 GB ubuntu-26.04-desktop-amd64.iso 0d6a0b7f4b3e2e4a8b9c1d2e3f405162738495a6 Run a hook on one with `npx orb run --download ""`. An afterFinish hook needs a finished one, and an afterChecks hook one that's finished or held. ``` - **`--download`** takes part of a download's name, or its whole info hash. If it matches more than one, `orb run` lists them, so you can be more specific. - **The hook's name** can be left out when the extension has only one. - **An `afterFinish` hook needs a finished download,** since it reads files that are all there. An `afterChecks` hook needs one that's finished or held by a check, and gets the checks' results as they are. - **Run by hand, every file is new,** so `isNew` is `true` for each finished file. `orb run` builds the extension, loads it, runs the hook inside OrbTorrent, and prints what it logged and returned: ```txt title="Output" ✓ passed: No screensavers ``` The result also shows in the download's **Checks** tab, as a real check would. ## Logs Anything the extension writes with `console.log`, `console.warn`, `console.error` and the rest goes to its log: - **In OrbTorrent,** on the extension's page, under **Logs**, with **Clear** and **Show Log File**. The file is `~/Library/Application Support/OrbTorrent/Extensions/Logs/.log`. - **In your terminal,** while `orb dev` or `orb run` is running. - **In tests,** under a failing test's output, and in `orb test --json`'s `logs`. When a hook throws, its stack trace goes in the log too. OrbTorrent hides password settings in logs. ## When a check didn't finish The **Checks** tab says why, and so does `orb run`: | It says | Because | Try | |---|---|---| | Didn't finish: *the error's message* | The hook threw. | Read the stack trace in its log. | | Didn't finish: it took longer than 15 s | Over the time limit. | Fewer or faster requests before a download starts, or move the work to `afterFinish`. | | Didn't finish: it kept the processor busy for over 10 s | Over the CPU limit. | Do less work in loops. Waiting with `sleep` doesn't count. | | Didn't finish: it used more than 512 MB of memory | Over the memory limit. | Don't hold large responses or lists. | | Didn't finish: It returned something that isn't a result | It returned no `status` or `summary`. | Return `{ status, summary }`. | [Limits](https://orbtorrent.com/docs/extensions/reference/limits) has every limit, and [Errors and fixes](https://orbtorrent.com/docs/extensions/reference/errors) every error message. ## OrbTorrent Dev If you build OrbTorrent itself, put `ORB_APP=dev` before `orb` commands to reach **OrbTorrent Dev** instead: ```sh ORB_APP=dev npx orb run check --download "Big Buck Bunny" ``` --- # Share and install > Install your extension, package it as an .orbext file anyone can install, release updates, and list it or propose it as an official extension. ## Install it yourself When your extension does what you want: ```sh npx orb install ``` `orb install` builds and packages the extension, then asks OrbTorrent to install it. OrbTorrent shows the install sheet: what the extension does, who made it, when it runs, and what it can do. It installs it when you confirm, and the installed copy replaces the one loaded for development. `orb install` waits for your answer, then says whether it was installed. ## Make an .orbext file To install it on another Mac, or give it to someone: ```sh npx orb package ``` ```txt title="Output" ✓ Made no-screensavers.orbext. Open it on a Mac with OrbTorrent to install it. ``` An `.orbext` file is the built extension, zipped. To install one, open it on a Mac with OrbTorrent, or choose **Install Extension…** from OrbTorrent's **File** menu or the Extensions page. OrbTorrent shows the same install sheet: - what the extension does, and who made it; - **Not reviewed by OrbTorrent**; - when it runs, and what it can do. Installed extensions live in `~/Library/Application Support/OrbTorrent/Extensions/Installed`. ## Release an update 1. Raise `"version"` in `package.json`, such as from `1.0.0` to `1.1.0`, and add a line to `CHANGELOG.md`. 2. Run `npx orb package` again, and share the new file. Opening it shows the install sheet again, with anything new the extension can do marked **New**, such as another host it connects to. Settings and storage are kept across updates. ## Before you share - **Ask for the least you need.** People see every permission, and decide on them. - **Write a clear `description`,** one sentence on what it does, and a `title` people will recognise in the Checks tab. - **Add an icon:** a 512 × 512 PNG in `assets/`, named in `"icon"`. - **Put setup steps in `help.md`,** if it needs an API key or an account. - **Test what must pass,** not only what it catches. - **Choose a licence.** `@orbtorrent/api` is MIT, so your extension can use any licence. ## Names An extension's `name` is its id: lowercase letters, digits and hyphens, unique among the extensions someone has installed. An installed extension can't use an official extension's name. A development copy loaded with `orb dev` can, and stands in for the official one while it's loaded. ## The community list Made an extension others would like? Add it to the **Community** list in OrbTorrent's [README](https://github.com/orbtorrent/OrbTorrent#community) with a pull request that adds one line, in alphabetical order. For it to be listed: - its source is public, with a licence; - a release there has the `.orbext` file, built from that source with `npx orb package`; - its tests pass, with `npx orb test`; - the line says what it does, and what it's allowed to do: each host it connects to, and whether it reads or adds files. Listings are checked against these rules, not reviewed line by line, and the list says so. If a listed extension stops working, or does something its line doesn't say, it comes off the list. ## Propose an official extension Official extensions ship inside OrbTorrent, are under its licence (GPL-3.0-or-later), and are kept working by its maintainers. Before you build one, open an issue with the [Propose an official extension](https://github.com/orbtorrent/OrbTorrent/issues/new?template=official_extension.yml) form. An official extension: - is useful to most people who use OrbTorrent; - works without an account, or makes one optional; - has nothing to do with finding or getting copyrighted material; - asks for the least it needs. Once it's agreed, add it in the repository's `extensions/` folder, laid out like the others, with a `README.md`, a `CHANGELOG.md` and tests for every rule, and open a pull request. [CONTRIBUTING.md](https://github.com/orbtorrent/OrbTorrent/blob/main/CONTRIBUTING.md) has the steps. ## Change an official extension The official extensions, the Fake Download Check and VirusTotal Scan, live in the OrbTorrent repository's [`extensions/`](https://github.com/orbtorrent/OrbTorrent/tree/main/extensions) folder. Each is an npm workspace package, built by `make extensions` and shipped inside OrbTorrent. To change one: 1. Change its code and tests, and add a line to its `CHANGELOG.md`. 2. Run `make test-extensions`. 3. Open a pull request. CI runs every extension's tests and lint. When a pull request changes an extension's hooks or permissions, CI posts the difference as a comment, so the reviewer sees first what users will be asked to allow. Say in the pull request why it needs them. --- # Security model > How OrbTorrent runs extensions in a sandbox, what each one can and can't reach, how it keeps secrets, and how the development connection is protected. Extensions are code from someone else, running on your Mac. OrbTorrent limits what that code can do, rather than relying on anyone reviewing it. ## What an extension can do Exactly what its `package.json` says, which users see before they install it: | An extension can | When | |---|---| | See the names and sizes of the files in your downloads | Always | | Hold a download before it starts, until you decide | With a `beforeStart` hook | | See what other extensions found in your downloads | With an `afterChecks` hook | | Read the contents of your downloads | With `"files": "contents"` | | Add files, such as subtitles, next to your downloads | With `"files": "add"` | | Connect to a site | For each host in `permissions.network` | OrbTorrent enforces the list. An extension can't read other files, change or delete any, reach other sites, or see anything else. It can't run programs either: [running one of your own](https://orbtorrent.com/docs/guides/run-a-program) is a setting only you can choose. ## What it never sees - **Where your downloads are saved.** Paths are relative to each download's folder. - **Your downloads' trackers,** which can carry a private tracker's personal key. - **Your other downloads,** only the one it's checking. - **Other extensions' settings and storage,** only its own. Only an `afterChecks` hook sees their results on the download it's reporting on. - **Your IP address, your library, or OrbTorrent's settings.** ## The extension host - **A fresh process for every run.** Each hook runs in its own `orb-extension-host` process, which exits when the hook returns. Nothing carries over except through `storage`. - **A strict macOS sandbox.** The helper has the App Sandbox entitlement and nothing else. It can't read or write files, connect to the network, or even look up a host name, and any program it started would inherit the same sandbox. - **Everything goes through OrbTorrent.** Every request, file fingerprint, upload and storage write goes to OrbTorrent as a message, and OrbTorrent checks it against the extension's permissions first. - **Limits.** OrbTorrent enforces time, CPU and memory limits, and stops a helper that goes over them. A `beforeStart` check that doesn't finish lets the download start, so a broken extension never blocks downloads. - **JIT, only in the helper.** JavaScriptCore's JIT compiler is on in the sandboxed helper, not in OrbTorrent itself. ## The network - HTTPS only, on port 443, to hosts listed exactly in `permissions.network`. - Redirects are followed only to hosts on the same list. - A host that resolves to an address on the Mac or a local network is refused, so an extension can't reach your router, your NAS or OrbTorrent's own engine. OrbTorrent connects to the very address it checked, through a relay of its own, so a host can't pass the check and then switch to a local address. - There are no cookies and no shared cache. - Responses are limited to 10 MB, and each request to 30 seconds. ## Files With `"files": "contents"`, an extension can ask for a finished file's SHA-256 fingerprint, or upload it with `fetch`. OrbTorrent reads the file itself and streams it, so the extension never gets a path, and never reads anything that isn't part of the download it's checking. ## Adding files With `"files": "add"`, an extension can add a file next to one of a finished download's files. OrbTorrent writes it, after checking that: - it's next to a file of the download the extension is running on, in the same folder, and named after that file, so nothing lands anywhere else; - it's subtitles, text, an image or checksums, never a program, a script or an app; - nothing's there already, not even a link, unless the same hook added it to the same download before; - it's no more than 10 MB, and the run has added no more than 200 files and 50 MB. Each file is readable but never runnable, and marked as downloaded from the internet, as browsers do. The Checks tab lists every file an extension added, and they're moved and removed with their download. ## Settings and secrets - **Passwords go in the Keychain.** `password` settings are stored in the macOS Keychain, not in a file. - **An extension sees only its own settings.** - **Secrets are hidden in logs.** OrbTorrent replaces them wherever they'd appear. - **Removing an extension deletes its settings and storage,** passwords included. ## Installing - **The install sheet comes first.** Nothing is installed until the user has seen what the extension can do and confirmed it. - **Updates show what's new.** A new permission is marked **New** on the update's install sheet. - **Extensions from anyone but OrbTorrent are marked "Not reviewed by OrbTorrent".** The limits above apply to every extension, reviewed or not. - **Official names are reserved.** An installed extension can't take an official extension's name. ## Development `orb dev` and `orb run` reach OrbTorrent through a socket at `~/Library/Application Support/OrbTorrent/dev.sock`: - only your account can open it: it's mode 0600, and the connecting process's user is checked; - it's only there while **Allow development extensions** is on; - OrbTorrent asks the first time each folder is loaded, and remembers the folders you allow; - installing still needs you to confirm the install sheet. A coding agent working for you can build, test and run an extension, but can't turn on development extensions, allow its folder, or install anything without you. ## Turning it all off One switch, **Check downloads with extensions** on the Extensions page, turns every extension off. Nothing runs until it's back on. ## Report a security problem Please report security problems privately, by email to `help@orbtorrent.com` with "Security" in the subject, not in a public issue. [Report a problem](https://orbtorrent.com/docs/help/report-a-problem#security-problems) explains. --- # Examples > The templates and official extensions to learn from, a worked example that adds subtitles, and ideas for extensions to build. ## The templates `npm create @orbtorrent/extension@latest` makes an extension from one of two templates, each complete with tests: | Template | What it does | Shows how to | |---|---|---| | `before-start`, the default | Holds downloads bigger than a size you choose. | Hold a download, and use a dropdown setting. | | `after-finish` | Sends a notification to your phone through [ntfy](https://ntfy.sh) when a download finishes. | Call a web service, use a required setting, and check it with `verify`. | ```sh npm create @orbtorrent/extension@latest notify-me -- --template after-finish ``` The after-finish template's hook, in full: ```ts title="src/notify.ts" import { Download, Result, fetch, getPreferenceValues } from "@orbtorrent/api"; /** Runs once, when every wanted file of a download has finished. */ export default async function notify(download: Download): Promise { const { topic } = getPreferenceValues(); // Only hosts listed in permissions.network can be reached. const response = await fetch(`https://ntfy.sh/${encodeURIComponent(topic)}`, { method: "POST", headers: { Title: "Download finished" }, body: download.name, }); if (!response.ok) { return { status: "skipped", summary: `ntfy didn't take the message (status ${response.status})` }; } return { status: "passed", summary: `Sent to your ntfy topic “${topic}”` }; } ``` ## The official extensions Both are open source, in the [`extensions/`](https://github.com/orbtorrent/OrbTorrent/tree/main/extensions) folder, and built with the same SDK and tools as yours. ### Fake Download Check [Source](https://github.com/orbtorrent/OrbTorrent/tree/main/extensions/fake-download-check) · `beforeStart` · no permissions Holds downloads that look fake, such as a film that contains a program, using only the names and sizes of their files. Worth reading for: - working out what a download is, a film, an album, a book or software, from its files and name; - telling `failed` from `warning` carefully, so it holds only what's almost certainly fake; - a test for each rule, and tests of real releases that must pass. ### VirusTotal Scan [Source](https://github.com/orbtorrent/OrbTorrent/tree/main/extensions/virustotal) · `afterFinish` · `"files": "contents"`, `www.virustotal.com` Looks up each finished file's fingerprint on VirusTotal. Worth reading for: - pacing requests for a rate limit with `sleep`, and backing off on 429; - keeping answers in `storage`, so running again doesn't use up the user's daily limit; - checking the riskiest files first, and showing `progress`; - a `password` setting checked with `verify`, and an opt-in upload with `files.formData`; - tests that mock every answer VirusTotal gives, including its limits. ## A worked example: subtitles Someone's first idea for an extension was to fetch the right subtitles once a film finishes. A coding agent built it in about ten minutes, and this is a shorter version of it. It finds subtitles in your language on [OpenSubtitles](https://www.opensubtitles.com), and saves each next to its film or episode, where players such as IINA and VLC find them. It needs a free OpenSubtitles account, and an API key from its **API consumers** page. OpenSubtitles limits how many subtitles a key can download each day. ```json title="package.json (part)" { "hooks": [{ "name": "subtitles", "title": "Add subtitles", "event": "afterFinish" }], "permissions": { "files": "add", "network": ["api.opensubtitles.com", "www.opensubtitles.com"] }, "preferences": [ { "name": "apiKey", "type": "password", "title": "OpenSubtitles API key", "required": true }, { "name": "language", "type": "dropdown", "title": "Language", "default": "en", "data": [ { "title": "English", "value": "en" }, { "title": "Spanish", "value": "es" }, { "title": "French", "value": "fr" }, { "title": "German", "value": "de" }, { "title": "Malay", "value": "ms" } ] } ] } ``` ```ts title="src/subtitles.ts" import { Download, DownloadFile, Result, fetch, files, getPreferenceValues, sleep } from "@orbtorrent/api"; const api = "https://api.opensubtitles.com/api/v1"; const videos = /\.(mkv|mp4|m4v|avi|mov)$/i; interface Subtitle { attributes: { download_count: number; machine_translated: boolean; files: { file_id: number }[] }; } /** Runs once a download finishes: subtitles for each film or episode in it. */ export default async function subtitles(download: Download): Promise { const { apiKey, language } = getPreferenceValues(); // OpenSubtitles asks every app to name itself. const headers = { "Api-Key": apiKey, "User-Agent": "OrbTorrent Subtitles v1.0", "Content-Type": "application/json" }; const wanted = download.files.filter((file) => file.wanted && videos.test(file.path) && !/\bsample\b/i.test(file.path)); if (wanted.length === 0) return { status: "skipped", summary: "No films or episodes in this download" }; const added: string[] = []; const there: string[] = []; for (const video of wanted) { // Subtitles that came with the download. if (download.files.some((file) => file.path === withSuffix(video, `.${language}.srt`))) { there.push(video.path); continue; } // OpenSubtitles wants its parameters in alphabetical order, in lower case. const query = new URLSearchParams({ languages: language, query: title(video).toLowerCase() }); const search = await fetch(`${api}/subtitles?${query}`, { headers }); if (search.status === 401 || search.status === 403) { return { status: "skipped", summary: "OpenSubtitles didn't accept the API key. Check it on the Extensions page" }; } if (!search.ok) return { status: "skipped", summary: `OpenSubtitles didn't answer (status ${search.status})` }; const best = ((await search.json()).data as Subtitle[]) .filter((subtitle) => !subtitle.attributes.machine_translated && subtitle.attributes.files.length > 0) .sort((a, b) => b.attributes.download_count - a.attributes.download_count)[0]; if (!best) continue; // Each download counts towards the key's daily limit. const ticket = await fetch(`${api}/download`, { method: "POST", headers, body: JSON.stringify({ file_id: best.attributes.files[0].file_id }), }); if (!ticket.ok) { return finished(added, there, `OpenSubtitles stopped at its daily limit (status ${ticket.status})`); } const { link } = await ticket.json(); const subtitle = await fetch(link); try { const saved = await files.save(video, `.${language}.srt`, await subtitle.text()); added.push(saved.path); } catch { // Already there, such as subtitles the user added. there.push(video.path); } await sleep(1000); } return finished(added, there); } function finished(added: string[], there: string[], stopped?: string): Result { const details = [...added.map((path) => `- Added \`${path}\``), ...there.map((path) => `- Already had subtitles: \`${path}\``)].join("\n"); if (added.length === 0) { return { status: "skipped", summary: stopped ?? (there.length > 0 ? "Already has subtitles" : "OpenSubtitles had none"), details }; } const count = added.length === 1 ? "1 file" : `${added.length} files`; return { status: "passed", summary: `Added subtitles for ${count}${stopped ? `. ${stopped}` : ""}`, details, notify: true }; } /** "Film.2024.1080p.WEB-DL.mkv" → "Film 2024 1080p WEB-DL", to search for. */ function title(file: DownloadFile): string { return file.path.split("/").pop()!.replace(/\.[^.]+$/, "").replace(/[._]+/g, " ").trim(); } function withSuffix(file: DownloadFile, suffix: string): string { return file.path.replace(/\.[^./]+$/, "") + suffix; } ``` Worth noticing: - **`"files": "add"`** is all it asks for. It never reads the films, only their names. - **It checks `download.files` first,** so a download that came with subtitles costs nothing from the daily limit. - **`files.save` throws if a file is already there,** such as subtitles you added yourself. The hook catches it, and says so. - **`notify: true`** tells you it's done, without opening the Checks tab. - **Every way it can't finish is `skipped`,** with what happened, never `failed`: missing subtitles aren't something wrong with the download. A test answers each request OpenSubtitles would get, and checks what was saved: ```json title="tests/adds-english-subtitles.json" { "name": "Adds English subtitles next to a film", "download": { "name": "Film (2024)", "files": [ { "path": "Film (2024)/Film.2024.1080p.WEB-DL.mkv", "size": 4000000000 }, { "path": "Film (2024)/Sample/sample.mkv", "size": 20000000 } ] }, "preferences": { "apiKey": "test-key" }, "mocks": { "fetch": [ { "url": "https://api.opensubtitles.com/api/v1/subtitles?languages=en&query=film+2024+1080p+web-dl", "json": { "data": [ { "attributes": { "download_count": 90, "machine_translated": true, "files": [{ "file_id": 1 }] } }, { "attributes": { "download_count": 40, "machine_translated": false, "files": [{ "file_id": 2 }] } } ] } }, { "method": "POST", "url": "https://api.opensubtitles.com/api/v1/download", "json": { "link": "https://www.opensubtitles.com/download/abc/subfile/Film.srt", "remaining": 4 } }, { "url": "https://www.opensubtitles.com/download/abc/subfile/Film.srt", "text": "1\n00:00:01,000 --> 00:00:03,000\nHello.\n" } ] }, "expect": { "status": "passed", "summaryIncludes": "Added subtitles for 1 file", "saved": ["Film (2024)/Film.2024.1080p.WEB-DL.en.srt"], "savedIncludes": "Hello.", "notify": true } } ``` To go further, match subtitles to the exact release, such as the same group or source as the file's name, as the agent's version did, and add a `verify` that checks the key. ## Ideas Ideas the **Develop** tab suggests, and a few more. Each is a good first extension, by hand or [with a coding agent](https://orbtorrent.com/docs/extensions/coding-agents). | Idea | Hook | Needs | |---|---|---| | **Hold `.scr` files**, which are programs in disguise. [The tutorial](https://orbtorrent.com/docs/extensions/getting-started) builds it. | `beforeStart` | Nothing | | **Warn about tiny films,** smaller than 700 MB, since they're probably fake or poor quality. | `beforeStart` | Nothing | | **Post to Discord when a download finishes,** with what its checks found. | `afterChecks` | `discord.com`, and a webhook setting | | **Send a message to your phone** when a download finishes or is held, through ntfy.sh or Pushover. | `afterChecks` | `ntfy.sh` or `api.pushover.net`, and a topic or key setting | | **Hold downloads over a size** while you're on a metered connection. | `beforeStart` | A setting | | **Save synced lyrics** next to each song, as `.lrc` files players show line by line. | `afterFinish` | `"files": "add"`, and a lyrics service's host | | **Save a poster** next to a film, for media servers, with `response.bytes()`. | `afterFinish` | `"files": "add"`, and the host of the artwork | | **Tell your media server** to scan its library once a film finishes. | `afterFinish` | Your media server's public host, and an API key setting | | **Check a finished file's fingerprint** against a list you trust, such as a Linux image's published checksums. | `afterFinish` | `"files": "contents"`, and the host of the list | Extensions can only reach hosts on the internet, never your Mac or local network, so a media server on your own network can't be reached directly. For that, or to unpack or move what you download, OrbTorrent can [run a script of your own](https://orbtorrent.com/docs/guides/run-a-program) when a download finishes. ## Share yours Made an extension others would like? [Package it](https://orbtorrent.com/docs/extensions/sharing), and add it to the **Community** list in OrbTorrent's README with a pull request. --- # API reference > Everything @orbtorrent/api provides, with each function's signature, types, limits and errors, and the JavaScript environment extensions run in. Import everything from `@orbtorrent/api`: ```ts import { fetch, files, getPreferenceValues, progress, sleep, storage, type Download, type Result } from "@orbtorrent/api"; ``` Extensions run in OrbTorrent's sandboxed extension host, with JavaScriptCore, not Node.js or a browser. They have no file system or network of their own: everything outside the JavaScript language goes through these functions, which OrbTorrent checks against the permissions in `package.json`. The functions only work inside OrbTorrent, or in `orb test`. Called anywhere else, they throw "@orbtorrent/api only works inside OrbTorrent, or in `orb test`." ## Hooks ### `Hook` ```ts type Hook = (download: Download) => Result | Promise; ``` A hook is the default export of `src/.ts`. [Hooks](https://orbtorrent.com/docs/extensions/hooks) explains when each kind runs. ### `HookEvent` ```ts type HookEvent = "beforeStart" | "afterFinish" | "afterChecks"; ``` When a hook runs, as its `event` in `package.json` says. `afterChecks` since 1.2. ### `Download` ```ts interface Download { /** Its info hash. */ id: string; name: string; /** Bytes of the wanted files. */ size: number; /** Whether it's from a private tracker. */ isPrivate: boolean; files: DownloadFile[]; /** For an `afterChecks` hook: what the other extensions' checks found, worst first. */ checks?: CheckResult[]; /** For an `afterChecks` hook: whether a check is holding it before it starts. Otherwise it's finished. */ isHeld?: boolean; } ``` The download a hook runs on. `checks` and `isHeld` since 1.2. ### `DownloadFile` ```ts interface DownloadFile { index: number; /** Relative to the download's folder, including its top folder. Never an absolute path. */ path: string; /** In bytes. */ size: number; /** Whether the user chose to download it. */ wanted: boolean; /** * Once the download has finished: whether this file finished since the hook * last ran on it, such as one the user chose to download later. Every file * is new the first time, and when the user runs the hook again. */ isNew?: boolean; } ``` `isNew` since 1.2. ### `Result` ```ts interface Result { status: Status; /** One line, shown in the list. Say which file and why. */ summary: string; /** Markdown, shown under Details. */ details?: string; link?: { title: string; url: string }; /** * Also shows the summary as a notification, such as "Added English * subtitles". A warning or failure after a download finishes, and a * download held before it starts, are notifications already. */ notify?: boolean; /** * What it found about particular files. OrbTorrent marks each problem * beside its file, and the result counts as no better than its worst file. * Each result replaces the hook's last one, so name every file you know * about, not only the new ones. */ files?: FileResult[]; } ``` What a hook returns. [Results](https://orbtorrent.com/docs/extensions/results) explains how each part shows. `files` since 1.2. ### `FileResult` ```ts interface FileResult { /** A file from `download.files`, or its index. */ file: DownloadFile | number; status: "passed" | "warning" | "failed"; /** Why, in a few words, such as "5 of 70 engines call it malicious". */ summary?: string; link?: { title: string; url: string }; } ``` What a hook found about one of the download's files. Since 1.2. ### `CheckResult` ```ts interface CheckResult { extension: { name: string; title: string }; /** The hook's name. */ hook: string; event: "beforeStart" | "afterFinish"; /** `didNotFinish` when it threw, crashed or went over a limit. */ status: Status | "didNotFinish"; summary: string; details?: string; link?: { title: string; url: string }; /** What it found about particular files, problems first. */ files?: { index: number; path: string; status: "passed" | "warning" | "failed"; summary?: string; link?: { title: string; url: string } }[]; } ``` Another extension's result on the download, as an `afterChecks` hook sees it in `download.checks`. Results still running aren't included. Since 1.2. ### `Status` ```ts type Status = "passed" | "warning" | "failed" | "skipped"; ``` - `passed`: nothing wrong. - `warning`: worth a look; the download carries on. - `failed`: something's wrong. Before a download starts, this holds it until the user decides. - `skipped`: the check didn't apply, or couldn't run. ## Settings ### `getPreferenceValues` ```ts function getPreferenceValues(): T; ``` The extension's settings, as the user set them, with defaults filled in. Every setting has a value: what the user chose, or its default, or else `""`, `false`, or a dropdown's first option. ### `OrbPreferences` ```ts declare global { interface OrbPreferences {} } ``` The type of the extension's settings. `orb build` fills it in from `package.json`, in `orb-env.d.ts`, so `getPreferenceValues()` knows each setting's name and type. [Settings](https://orbtorrent.com/docs/extensions/settings#read-them) shows an example. ### `Verify` ```ts type Verify = (preferences: OrbPreferences) => VerifyResult | Promise; ``` The default export of the file `verify` names in `package.json`. It checks the settings when the user presses **Verify**. ### `VerifyResult` ```ts type VerifyResult = { ok: true; message?: string } | { ok: false; message: string }; ``` What `verify` returns. The message shows under the settings. ## Network ### `fetch` ```ts function fetch(url: string | URL, init?: RequestInit): Promise; ``` An HTTPS request to a host listed in `permissions.network`. It throws if the request is refused, or fails. - HTTPS on port 443 only, and never an address on the Mac or its local network. OrbTorrent connects to the very address it checked. - Redirects are followed only to listed hosts. Otherwise the redirect itself is returned. - Responses are limited to 10 MB, and each request to 30 seconds. - There's no global `fetch`: import this one. [Network requests](https://orbtorrent.com/docs/extensions/network) has examples. ### `RequestInit` ```ts interface RequestInit { method?: "GET" | "HEAD" | "POST" | "PUT" | "PATCH" | "DELETE"; headers?: Record; /** Text (use `JSON.stringify` for JSON), or a form from `files.formData`. */ body?: string | FormBody; } ``` ### `Response` ```ts interface Response { readonly status: number; /** Whether the status is 200–299. */ readonly ok: boolean; /** The address it came from, after any redirects. */ readonly url: string; readonly headers: Headers; text(): Promise; json(): Promise; /** The body's bytes, such as an image or a zip file. */ bytes(): Promise; arrayBuffer(): Promise; } ``` `text()` and `json()` read the body as UTF-8, and `bytes()` and `arrayBuffer()` as it came. Since 1.1. ### `Headers` ```ts interface Headers { get(name: string): string | null; has(name: string): boolean; entries(): [string, string][]; } ``` A response's headers. Names are case-insensitive, and `entries()` gives them in lowercase. ## Files ### `files` ```ts const files: { sha256(file: DownloadFile | number): Promise; formData(fields: Record): FormBody; save(file: DownloadFile | number, suffix: string, contents: string | Uint8Array | ArrayBuffer): Promise; }; ``` The download's files, only once it's finished: in `afterFinish` hooks, and in `afterChecks` hooks when it isn't held. Reading them needs `"files": "contents"` in `permissions`, and adding files next to them needs `"files": "add"`, or `["contents", "add"]` for both. Pass a file from `download.files`, or its `index`. - **`files.sha256(file)`**: the file's SHA-256 fingerprint in lowercase hex, computed by OrbTorrent. Large files are quick, and the time doesn't count towards CPU time. OrbTorrent remembers it while the file stays as it was, so asking again, such as once more of the download has finished, doesn't read it again. - **`files.formData(fields)`**: a multipart form to upload with `fetch`. OrbTorrent streams each file from disk. - **`files.save(file, suffix, contents)`**: adds a file next to `file`, named after it: `files.save(video, ".en.srt", text)` saves `Film.2024.en.srt` beside `Film.2024.mkv`. The suffix starts with `.`, `-`, `_` or a space, and ends with an extension. Only subtitles, text, images and checksums, up to 10 MB each and 50 MB a run. It never replaces a file, except one the same hook added to the same download before, and throws if one's there. Text is saved as UTF-8. Since 1.1. [Read and add files](https://orbtorrent.com/docs/extensions/files) has examples. ### `SavedFile` ```ts interface SavedFile { /** Relative to the download's folder, like a `DownloadFile`'s path. */ path: string; /** In bytes. */ size: number; } ``` A file `files.save` added. ### `FormBody` ```ts class FormBody { readonly parts: FormPart[]; append(name: string, value: string | DownloadFile, options?: { filename?: string; contentType?: string }): this; } ``` A multipart form, which can include the download's files. Make one with `files.formData`, and add to it with `append`. ### `FormPart` ```ts type FormPart = | { name: string; value: string } | { name: string; file: number; filename?: string; contentType?: string }; ``` One field of a `FormBody`: text, or a file by its index. ## Progress and storage ### `progress` ```ts function progress(update: { done?: number; total?: number; message?: string }): Promise; ``` Shows how far along the check is, in the Checks tab and in `orb dev`, such as "Checking 3 of 10 files…". The message is cut at 200 characters. ### `storage` ```ts const storage: { get(key: string): Promise; set(key: string, value: unknown): Promise; remove(key: string): Promise; keys(): Promise; }; ``` JSON values the extension keeps between runs, up to 1 MB in all, with keys up to 200 characters. Kept across runs and updates, and deleted with the extension. [Storage and progress](https://orbtorrent.com/docs/extensions/storage) has examples. ### `sleep` ```ts function sleep(milliseconds: number): Promise; ``` Waits, without using CPU time. ### `environment` ```ts const environment: { /** The extension's `name` from package.json. */ readonly extensionName: string; /** Whether it's loaded from a folder with `orb dev`, rather than installed. */ readonly isDevelopment: boolean; /** The hook or `verify` function running now. */ readonly entryPoint: { type: "hook" | "verify"; name: string; event?: HookEvent }; /** The API version this OrbTorrent provides, such as "1.2.0". */ readonly apiVersion: string; }; ``` About the extension, and how it's running. ## The environment Besides the JavaScript language itself, ES2023 and newer, extensions have: | | | |---|---| | `console` | `log`, `info`, `debug`, `trace`, `warn` and `error`, to the extension's log. | | Timers | `setTimeout`, `setInterval`, `clearTimeout`, `clearInterval` and `queueMicrotask`. | | `URL` and `URLSearchParams` | Including `URL.canParse`. | | `TextEncoder` and `TextDecoder` | UTF-8 only. | | `atob` and `btoa` | Base64. | | `crypto` | `randomUUID()` and `getRandomValues()`. | | `structuredClone` | | **Not available:** `fs`, `path`, `process`, `Buffer`, `require`, the global `fetch`, `window`, the DOM, and npm packages that need them. `orb build` refuses Node.js modules and says what to use instead, and `orb lint` flags the globals. npm packages that are plain JavaScript work: `orb build` bundles them in. ## Versions `@orbtorrent/api`'s major and minor version are the extension API's. OrbTorrent runs extensions built with its own major version, up to the minor version it supports: one built with 1.2 needs an OrbTorrent that supports 1.2, and OrbTorrent says so if it's too old, rather than run it. - **Patch versions,** such as 1.0.3, improve `orb` and fix bugs without changing the API. - **Minor versions,** such as 1.1, add to the API. - **Major versions,** such as 2.0, are for anything that would break an extension. `npx orb doctor` says which API the OrbTorrent on your Mac supports. The [changelog](https://orbtorrent.com/docs/extensions/reference/changelog) lists every version. --- # The orb command > Every orb command and option, what it needs, what it prints, and the JSON it prints with --json, for scripts and coding agents. `orb` comes with `@orbtorrent/api`. It makes, tests, builds, runs and shares extensions. Run it from an extension's folder with `npx orb`, or through the `npm` scripts every new extension has. ```txt title="npx orb --help" Usage: orb [options] Commands: create Make a new extension in a new folder --template before-start|after-finish (default before-start) --title "" build Check package.json, type-check and bundle into dist/ test [files…] Run the tests in tests/, without OrbTorrent lint Check the code with ESLint (--fix to fix what it can) doctor Check Node.js, OrbTorrent and this extension, and say what to fix dev Load into OrbTorrent, rebuild on every save and show logs downloads List your downloads in OrbTorrent, to run a hook on run [hook] Run a hook on one of your downloads in OrbTorrent --download "<name or info hash>" install Package it and ask OrbTorrent to install it, once you confirm package Make <name>.orbext, to install on another Mac Options: --json Print results as JSON, for scripts and coding agents --help, --version ``` ## For scripts and agents - **`--json`** prints one JSON object, with `"ok"` saying whether the command succeeded. - **Nothing prompts.** `orb install` waits for the user's answer in OrbTorrent, rather than in the terminal. - **The exit status is 1 when something's wrong,** and 0 otherwise. - **Errors say what to do.** With `--json`, a failed command prints `{ "ok": false, "error": "…", "details": ["…"] }`. ## Without OrbTorrent These work anywhere Node.js 22.13 or later does, including CI. ### `orb create` ```sh npx orb create <name> [--template before-start|after-finish] [--title "<title>"] [--dir <folder>] ``` Makes a new extension in a new folder. The name is its id: lowercase letters, digits and hyphens, such as `plex-mover`. The title, shown in OrbTorrent, comes from the name unless you give one. Usually you'd run it through `npm create`, which uses the newest `@orbtorrent/api` from npm: ```sh npm create @orbtorrent/extension@latest notify-me -- --template after-finish ``` The `--` passes the options on to `orb create`. Then `cd` into the folder and run `npm install`. ```json title="--json" { "ok": true, "folder": "/Users/you/Projects/notify-me" } ``` ### `orb build` ```sh npx orb build ``` Checks `package.json`, writes `orb-env.d.ts` with your settings' types, type-checks the code, and bundles each hook into `dist/`, the way OrbTorrent loads it: ``` dist/ ├── package.json the manifest, with the API version it was built with ├── hooks/check.js one file per hook ├── verify.js if there's a verify ├── assets/, help.md, README.md, CHANGELOG.md ``` It refuses Node.js modules, and says what to use instead. npm packages that are plain JavaScript are bundled in. It warns, rather than fails, about an icon that doesn't exist. ```json title="--json" { "ok": true, "name": "no-screensavers", "dist": "/Users/you/Projects/no-screensavers/dist", "hooks": ["check"], "warnings": [] } ``` ### `orb test` ```sh npx orb test [files…] ``` Builds, then runs every JSON test in `tests/`, or the files you name, without OrbTorrent. [Testing](https://orbtorrent.com/docs/extensions/testing) explains the format. ```json title="--json" { "ok": true, "passed": 3, "failed": 0, "tests": [ { "file": "tests/holds-a-screensaver.json", "name": "Holds a download with a screensaver in it", "passed": true, "failures": [], "result": { "status": "failed", "summary": "Contains a screensaver, which is a Windows program: Play Me.scr" }, "logs": [], "fetched": [] } ] } ``` Each test has its `failures`, what the hook returned as `result`, or the `error` it threw, what it logged, and the requests it made. ### `orb lint` ```sh npx orb lint [--fix] ``` Runs ESLint with OrbTorrent's rules: the recommended JavaScript and TypeScript rules, and errors for what doesn't exist where extensions run, such as `process`, `Buffer`, `require`, `window`, the global `fetch`, and Node.js modules. `--fix` fixes what it can. If the extension has its own `eslint.config.js`, that's used instead, and can build on OrbTorrent's with `@orbtorrent/api/eslint`. ```json title="--json" { "ok": false, "errors": 1, "warnings": 0, "problems": [ { "file": "src/check.ts", "line": 5, "column": 17, "severity": "error", "rule": "no-restricted-globals", "message": "Unexpected use of 'process'. process doesn't exist where extensions run. Use getPreferenceValues and environment from @orbtorrent/api." } ] } ``` ### `orb package` ```sh npx orb package ``` Builds the extension and zips `dist/` into `<name>.orbext`, which anyone with OrbTorrent can install by opening it. It needs macOS. ```json title="--json" { "ok": true, "file": "/Users/you/Projects/no-screensavers/no-screensavers.orbext" } ``` ## With OrbTorrent These talk to OrbTorrent on the same Mac, which must be open with **Allow development extensions** on, on the **Develop** tab of its Extensions page. The first time a folder connects, OrbTorrent asks the user whether to load it. ### `orb doctor` ```sh npx orb doctor ``` Checks everything the other commands need, and says what to fix, and who can fix it: | Check | Looks at | |---|---| | `node` | Node.js is 22.13 or later. | | `extension` | The folder is an extension, and which `@orbtorrent/api` it uses. | | `built` | It's been built into `dist/`. | | `app` | OrbTorrent is installed, open, and allows development extensions. | | `api` | OrbTorrent supports the extension API this extension was built with. | | `allowed` | The user has allowed this folder in OrbTorrent. | | `installed` | Whether it's installed, loaded for development, on or off. | ```json title="--json" { "ok": false, "checks": [ { "name": "node", "ok": true, "message": "Node.js 24.11.1" }, { "name": "app", "ok": false, "message": "OrbTorrent is open, but doesn't allow development extensions.", "fix": "Ask the user to turn on “Allow development extensions” in OrbTorrent: Extensions, at the bottom of the sidebar, then the Develop tab. Only the user can. …" } ] } ``` A check's `ok` is `true` when it's fine, `false` when it needs fixing, and `null` for information. `fix` says how, including when only the user can do it. `orb doctor` exits with 1 when any check is `false`. ### `orb dev` ```sh npx orb dev ``` Builds the extension, loads it into OrbTorrent, and rebuilds and reloads it on every save. It prints the extension's checks, progress and logs as they happen, on any download. Press Control-C to stop: the extension stays loaded in OrbTorrent. It doesn't take `--json`. [Run and debug](https://orbtorrent.com/docs/extensions/debugging#work-with-orb-dev) shows what it prints. ### `orb downloads` ```sh npx orb downloads ``` Lists the downloads in OrbTorrent, to run a hook on. ```json title="--json" { "ok": true, "downloads": [ { "id": "dd8255ecdc7ca55fb0bbf81323d87062db1f6d1c", "name": "Big Buck Bunny", "size": 276000000, "progress": 1, "finished": true } ] } ``` ### `orb run` ```sh npx orb run [hook] --download "<name or info hash>" ``` Builds and loads the extension, runs a hook in OrbTorrent on one of your downloads, and prints what it logged and returned. The hook can be left out when there's only one. `--download` takes part of a name, or a whole info hash. An `afterFinish` hook needs a finished download, and an `afterChecks` hook one that's finished or held. It prints what the hook found about each file, too. ```json title="--json" { "ok": true, "type": "result", "hook": "check", "download": { "id": "dd8255ecdc7ca55fb0bbf81323d87062db1f6d1c", "name": "Big Buck Bunny" }, "outcome": "returned", "status": "passed", "summary": "No screensavers", "message": "No screensavers", "logs": [] } ``` `outcome` is `returned`, or `didNotFinish` when the hook threw or went over a limit, with the reason in `summary`. `ok` is false, and the exit status 1, when it didn't finish. ### `orb install` ```sh npx orb install ``` Packages the extension and asks OrbTorrent to install it. OrbTorrent shows the install sheet, and installs it if the user confirms, replacing any copy loaded for development. While development extensions are allowed, `orb install` waits for the answer: ```json title="--json" { "ok": true, "installed": true, "file": "/Users/you/Projects/no-screensavers/no-screensavers.orbext", "app": "OrbTorrent" } ``` If the user cancels, it fails with "The user chose not to install it." Without development extensions allowed, it opens the file in OrbTorrent, which shows the same sheet, and returns straight away with `"installed": false`. ## The npm scripts Every new extension's `package.json` has: | Script | Runs | |---|---| | `npm run build` | `orb build` | | `npm test` | `orb test` | | `npm run lint` | `orb lint` | | `npm run dev` | `orb dev` | | `npm run package` | `orb package` | ## Environment variables | Variable | | |---|---| | `ORB_APP=dev` | Talks to **OrbTorrent Dev**, the build used while working on OrbTorrent itself, instead of OrbTorrent. | --- # Manifest > Every field of an extension's package.json, OrbTorrent's rules for each, and what users see from it. An extension's `package.json` is npm's, with OrbTorrent's fields added. `orb build` checks it against the same rules OrbTorrent does, with the same messages. ```json title="package.json" { "name": "virustotal", "title": "VirusTotal Scan", "description": "Checks finished downloads against about 70 antivirus engines, by each file's fingerprint.", "author": "orbtorrent", "icon": "icon.png", "license": "GPL-3.0-or-later", "version": "1.0.0", "hooks": [ { "name": "scan", "title": "Scan with VirusTotal", "description": "Looks up each file's fingerprint on VirusTotal, riskiest files first.", "event": "afterFinish" } ], "permissions": { "files": "contents", "network": ["www.virustotal.com"] }, "preferences": [ { "name": "apiKey", "type": "password", "title": "API key", "description": "Your free VirusTotal API key.", "required": true } ], "verify": "verify", "dependencies": { "@orbtorrent/api": "^1.0.0" } } ``` ## Fields | Field | Required | Meaning | |---|---|---| | `name` | Yes | A unique id: lowercase letters, digits and hyphens, such as `fake-download-check`, up to 64 characters. | | `title` | Yes | Shown on the Extensions page and in the Checks tab. | | `description` | Yes | One sentence on what it does. | | `author` | Yes | Your name or handle. | | `icon` | No | A 512 × 512 PNG in `assets/`. Without one, OrbTorrent shows a plain icon. | | `version` | No | Such as `1.0.0`, shown on its page. Raise it with each change you install or share. | | `license` | No | npm's, such as `MIT`. Shown on its page. | | `hooks` | Yes | At least one [hook](#hooks). | | `permissions` | No | What it can reach beyond file names and sizes. See [permissions](#permissions). | | `preferences` | No | Its [settings](#preferences). | | `verify` | No | Names `src/<verify>.ts`, which checks the settings when the user presses **Verify**. Lowercase letters, digits and hyphens. | Everything else, such as `scripts`, `dependencies` and `private`, is npm's, and OrbTorrent ignores it. ## Hooks ```json { "name": "check", "title": "Check for signs of a fake", "description": "Looks at the names and sizes of a download's files.", "event": "beforeStart" } ``` | Field | Required | Meaning | |---|---|---| | `name` | Yes | Lowercase letters, digits and hyphens. The hook is the default export of `src/<name>.ts`, or `.tsx`, `.js` or `.jsx`. Two hooks can't share a name. | | `event` | Yes | `beforeStart`, `afterFinish` or `afterChecks`. [Hooks](https://orbtorrent.com/docs/extensions/hooks) explains each. An `afterChecks` hook tells users it can "See what other extensions found in your downloads". | | `title` | No | Shown in the Checks tab and on the extension's page. The name, if you leave it out. | | `description` | No | A line on what it does, shown on the extension's page. | ## Permissions ```json { "permissions": { "files": "contents", "network": ["www.virustotal.com", "api.example.com"] } } ``` | Field | Meaning | |---|---| | `files` | `"contents"` lets the extension fingerprint and upload the download's files, and `"add"` lets it add files such as subtitles next to them, both in `afterFinish` hooks. `["contents", "add"]` for both. Without it, it sees only names and sizes. | | `network` | Exact host names it can reach over HTTPS, without `https://` or a path, such as `"www.virustotal.com"`. No wildcards. | [Network requests](https://orbtorrent.com/docs/extensions/network) and [Read and add files](https://orbtorrent.com/docs/extensions/files) explain what each allows. ## Preferences ```json { "name": "scope", "type": "dropdown", "title": "Files to check", "description": "Films and music rarely carry malware, and each lookup counts towards your daily limit.", "required": false, "default": "risky", "data": [ { "title": "Programs, archives and documents", "value": "risky" }, { "title": "All files", "value": "all" } ] } ``` | Field | Required | Meaning | |---|---|---| | `name` | Yes | Letters, digits and underscores, starting with a letter, such as `apiKey`. The key in `getPreferenceValues()`. | | `type` | Yes | `textfield`, `password`, `checkbox` or `dropdown`. | | `title` | No | The setting's label. The name, if you leave it out. | | `description` | No | A line under it. | | `required` | No | `true` if the extension can't run without it. While it's empty, the extension shows **Needs setup**, and its hooks are skipped. | | `placeholder` | No | Shown in an empty `textfield` or `password`. | | `default` | No | Its value until the user changes it. Text for a `textfield`, `true` or `false` for a `checkbox`, one of `data`'s values for a `dropdown`. A `password` can't have one. | | `label` | No | For a `checkbox`: the text beside the box. | | `data` | For a dropdown | The choices, as `{ "title", "value" }`. | [Settings](https://orbtorrent.com/docs/extensions/settings) explains how they're drawn and read. ## What users see The Extensions page and the install sheet list what each extension **can** do, from its `hooks` and `permissions`: - "See the names and sizes of the files in your downloads", always; - "Hold a download before it starts, until you decide", with a `beforeStart` hook; - "Read the contents of your downloads", with `"files": "contents"`; - "Add files, such as subtitles, next to your downloads", with `"files": "add"`; - "Connect to …", for each host in `permissions.network`. OrbTorrent enforces the list, so ask only for what the extension needs. ## Other files | File | | |---|---| | `src/<hook>.ts` | Each hook, and `src/<verify>.ts`. | | `assets/` | The icon, and anything else it shows. | | `help.md` | Setup steps, shown beside the settings under **How to Set It Up**. | | `README.md`, `CHANGELOG.md` | Copied into the built extension. | | `tests/*.json` | [Tests](https://orbtorrent.com/docs/extensions/reference/tests), which aren't shipped. | | `orb-env.d.ts` | Written by `orb build`, with the settings' types. Don't edit it. | | `AGENTS.md`, `CLAUDE.md` | Instructions for coding agents, which aren't shipped. | ## What OrbTorrent loads `orb build` writes the manifest OrbTorrent reads to `dist/package.json`: just OrbTorrent's fields, with defaults filled in, and the version of `@orbtorrent/api` it was built with: ```json title="dist/package.json" { "name": "notify-me", "title": "Notify Me", … "orbtorrent": { "api": "1.0.3" } } ``` OrbTorrent uses that version to check it supports the extension's API. [Versions](https://orbtorrent.com/docs/extensions/reference/api#versions) explains. --- # Test files > Every field of an orb test file, and every expectation it can check. A test is a JSON file in an extension's `tests/` folder. `orb test` runs them all, in name order. [Testing](https://orbtorrent.com/docs/extensions/testing) explains how to use them. ```json title="tests/stops-at-the-daily-limit.json" { "name": "Stops at the daily limit and says how far it got", "preferences": { "apiKey": "test-key" }, "download": { "name": "Pack", "files": [ { "path": "Pack/setup.exe", "size": 5000000, "sha256": "aaaa…" }, { "path": "Pack/extras.zip", "size": 9000000, "sha256": "bbbb…" } ] }, "mocks": { "fetch": [ { "url": "https://www.virustotal.com/api/v3/files/aaaa…", "json": { "data": { "attributes": { "last_analysis_stats": { "malicious": 0, "suspicious": 0, "undetected": 60, "harmless": 10 } } } } }, { "url": "https://www.virustotal.com/api/v3/files/bbbb…", "status": 429, "json": { "error": { "code": "QuotaExceededError" } } } ] }, "expect": { "status": "skipped", "summaryIncludes": "Checked 1 of 2 files, then VirusTotal's daily limit was reached" } } ``` ## Fields | Field | Default | Meaning | |---|---|---| | `name` | The file's name | What the test checks, shown when it runs. | | `hook` | The only hook | The hook to run. Needed when the extension has more than one. | | `verify` | `false` | `true` runs the settings check, `verify`, with `preferences`, instead of a hook. | | `download` | | The download the hook gets. See below. | | `preferences` | The defaults | Settings. Any left out take their defaults. | | `storage` | `{}` | What the extension's storage holds before the run. | | `mocks.fetch` | `[]` | Answers to the hook's requests. See below. | | `timeout` | `10000` | Milliseconds of real time the test may take. | | `expect` | | What the result should be. Without it, the test passes if the hook doesn't throw. | ## download | Field | Default | Meaning | |---|---|---| | `name` | `"Download"` | The download's name. | | `id` | A made-up info hash | Its info hash. | | `isPrivate` | `false` | Whether it's from a private tracker. | | `files` | `[]` | Its files, in order: each `path`, `size` (0 unless given), `wanted` (true unless given), `sha256`, which `files.sha256` returns for it, and `isNew` (true unless given, once it's finished). | | `alsoOnDisk` | `[]` | Other files already next to its files, as paths like theirs, such as subtitles the user added. `files.save` won't replace them. | | `checks` | `[]` | For an `afterChecks` hook: other extensions' results, as [`CheckResult`](https://orbtorrent.com/docs/extensions/reference/api#checkresult). `extension`, `hook` and `event` can be left out, and their files named by `path` alone. | | `held` | `false` | For an `afterChecks` hook: `true` for a download a check is holding, so none of its files are on disk. | The download's `size` is the sum of its wanted files' sizes. Each file's `index` is its place in the list, from 0. As in OrbTorrent, a hook can only use the files that are wanted, and only once the download has finished. The result is checked as OrbTorrent checks it: its `files` must name files of the download, and it counts as no better than its worst file, with the same warning in the test's logs. Files a hook adds with `files.save` aren't written anywhere: the test keeps them, for `saved` and `savedIncludes` to check. ## mocks.fetch Each mock answers requests that match it: | Field | Default | Meaning | |---|---|---| | `url` | | The address to match. `*` matches any text. | | `method` | `GET` | The method to match. | | `status` | `200` | The status to answer with. | | `json` | | A body to answer with, as JSON. Sets `content-type: application/json`. | | `text` | `""` | A body to answer with, as text. | | `base64` | | A body of bytes to answer with, in Base64, such as an image or a zip file. | | `headers` | | Headers to answer with. | | `times` | Any number | How many requests this mock answers. After that, the next matching mock takes over. | A request takes the first mock that matches. A request with no mock throws, and the test says which request needs one. ## expect | Key | Passes when | |---|---| | `status` | The result's status is this. | | `summaryIncludes` | The summary includes this text, ignoring case. | | `summaryMatches` | The summary matches this regular expression. | | `detailsIncludes` | The details include this text. | | `fetched` | Each of these URLs was requested. `*` matches any text. | | `saved` | Each of these files was added with `files.save`, by its path. `*` matches any text. | | `savedIncludes` | Each file it added includes this text. | | `notify` | Whether the result asks to be a notification, with `notify: true`. | | `files` | Each file the result names has this status, by path: `{ "Pack/setup.exe": "failed" }`. `*` matches any text, and `"none"` means the result says nothing about it. | | `throws` | The hook threw an error that includes this text. The rest of `expect` is then ignored. | | `ok` | For `verify`: whether it accepted the settings. | | `messageIncludes` | For `verify`: its message includes this text. | | `not` | The expectation inside it doesn't pass, such as `{ "not": { "fetched": ["https://…"] } }`. | --- # Limits > Every limit OrbTorrent puts on an extension, in one place, from time and memory to request sizes and result lengths. OrbTorrent enforces these limits, and stops a hook that goes over one. A hook that's stopped shows **Didn't finish**, with which limit it hit. Before a download starts, that lets the download start. ## Time, CPU and memory | | Time | CPU time | Memory | |---|---|---|---| | `beforeStart` hook | 15 s | 10 s | 512 MB | | `afterFinish` hook | 6 h | 2 min | 512 MB | | `afterChecks` hook | 6 h | 2 min | 512 MB | | `verify` | 15 s | 10 s | 512 MB | **CPU time** is time spent computing. Waiting, for a response or with `sleep`, doesn't count, and nor does `files.sha256`, which OrbTorrent computes itself. That's how an `afterFinish` hook can pace itself for a rate limit for hours with 2 minutes of CPU time. ## Running together - **Before a download starts,** every extension's `beforeStart` hooks run at the same time. - **After a download finishes,** each extension's `afterFinish` and `afterChecks` runs go one at a time, and up to 4 hooks run at once across all extensions. `afterChecks` hooks run once the `afterFinish` hooks are done. ## Network | | | |---|---| | Addresses | `https://` only, on port 443, to hosts in `permissions.network` | | Methods | `GET`, `HEAD`, `POST`, `PUT`, `PATCH` and `DELETE` | | Response size | 10 MB | | Each request | 30 s | | Redirects | Followed only to listed hosts | | Local addresses | Refused: the Mac and its local network, checked again as it connects | | Bodies | Text, JSON or bytes | ## Adding files | | | |---|---| | When | `afterFinish` hooks, and `afterChecks` hooks once the download has finished, next to a file the user chose to download | | Kinds | Subtitles, text, images and checksums, by extension | | A file | 10 MB | | A run | 200 files and 50 MB | | A suffix | 64 characters, starting with `.`, `-`, `_` or a space | | Replacing | Only a file the same hook added to the same download before | ## Storage | | | |---|---| | All of an extension's storage | 1 MB of JSON | | A key | 200 characters | ## What a hook returns | | | |---|---| | `summary` | 300 characters, on one line | | `details` | 20,000 characters | | `link.title` | 60 characters | | `link.url` | `https://` or `http://` | | `files` | 1,000 files, problems first, each `summary` 300 characters | | A `progress` message | 200 characters | | A log entry | 10,000 characters | Longer text is cut, not refused. ## The manifest | | | |---|---| | `name`, hook names, `verify` | 64 characters, of lowercase letters, digits and hyphens | | A host in `permissions.network` | 253 characters | ## In tests Each test may take 10 seconds of real time, unless it sets `timeout`. Timers run on a virtual clock, so waiting doesn't use it up. --- # Errors and fixes > The error messages orb and OrbTorrent give when building, testing, running and installing an extension, what each means, and how to fix it. Every error says what's wrong, and most say what to do. Here they all are, by where you'll meet them. ## Network Thrown by `fetch`, in OrbTorrent and in tests alike. | Message | Fix | |---|---| | Request to *host* was refused. Add "*host*" to permissions.network in package.json. | Add that exact host to `permissions.network`. `example.com` doesn't cover `www.example.com`. | | Request to *address* was refused: extensions can only use https:// addresses. | Use `https://`. | | Request to *host*:*port* was refused: extensions can only use the standard HTTPS port. | Use port 443, the default for `https://`. | | Request to *address* was refused: put credentials in a header, not the address. | Move `user:password@` into a header, such as `Authorization`. | | Request to *host* was refused: it points to *address*, which is on this Mac or your local network. | Extensions can't reach the Mac or local network. Use a service on the internet. | | *host* couldn't be found. Check the address and your connection. | Check the host name, and that the Mac is online. Before a download starts, return `skipped`. | | *host* couldn't be reached. It may point to this Mac or your local network, which extensions can't reach. | It pointed to a local address as OrbTorrent connected, or couldn't be reached. Use a service on the internet, and return `skipped` if it's down. | | The response was larger than 10 MB, the most an extension can receive. | Ask for less, such as one page of results at a time. | | The request method "*method*" isn't supported. | Use `GET`, `HEAD`, `POST`, `PUT`, `PATCH` or `DELETE`. | | A request body must be text, or a form from files.formData(). | Use `JSON.stringify` for JSON. | | fetch needs an https:// address. | Pass a full address, such as `https://api.example.com/…`. | | Each form field needs a name. | Give every field in `files.formData` a name. | ## Files Thrown by `files.sha256`, `files.formData` and `files.save`. | Message | Fix | |---|---| | Reading files isn't allowed. Add "files": "contents" to permissions in package.json. | Add `"files": "contents"` to `permissions`. | | Adding files isn't allowed. Add "files": "add" to permissions in package.json, or ["contents", "add"] to read files too. | Add `"add"` to `permissions.files`. | | File *n* isn't available: it isn't downloaded yet, or it isn't part of this download. | Use files only once the download has finished, in `afterFinish` hooks or `afterChecks` hooks when it isn't held, and only files from `download.files` the user chose to download. | | "*suffix*" can't be added to a file's name. A suffix starts with ".", "-", "_" or a space, and ends with an extension, such as ".en.srt". | Use a suffix such as `.en.srt` or `-poster.jpg`, with no `/`. | | Extensions can't add .*x* files, only subtitles, text, images and checksums: … | Add only the kinds listed. | | "*name*" already exists. An extension can't replace a file it didn't add. | Something's there already, such as subtitles that came with the download. Report it, and don't try again. | | "*name*" is larger than 10 MB, the most an extension can add in one file. | Add a smaller file. | | This run has already added 200 files, or nearly 50 MB, the most an extension can add in one run. | Add fewer files in one run. | | files.save needs the file's contents: text, or bytes as a Uint8Array. | Pass a string, a `Uint8Array` or an `ArrayBuffer`. | | File *n* is missing: it was moved or deleted after downloading. | The user moved or deleted it. Skip it, and say so in the result. | | Pass a file from download.files. | Pass a `DownloadFile`, or its `index`. | ## Storage | Message | Fix | |---|---| | Storage is full: an extension can keep up to 1 MB. Remove keys it no longer needs. | Remove old keys, or keep less, such as only recent answers. | | A storage key must be text of up to 200 characters. | Use a shorter key, such as a hash. | ## A check that didn't finish Shown in the Checks tab, and by `orb run`. | Message | Fix | |---|---| | Didn't finish: *message* | The hook threw. Its log has the stack trace. | | Didn't finish: it took longer than 15 s | Before a download starts, do less, or move the work to `afterFinish`. | | Didn't finish: it kept the processor busy for over 10 s | Do less work in loops. Waiting doesn't count. | | Didn't finish: it used more than 512 MB of memory | Hold less in memory at once. | | Didn't finish: It returned something that isn't a result: return { status, summary }. | Return a `status` and a non-empty `summary`. | | Didn't finish: Its result's "files" isn't a list. Return files: [{ file, status, summary }]. | Return `files` as a list. | | Didn't finish: Its result's "files" names a file that isn't part of this download. Pass a file from download.files, or its index. | Name only files from `download.files`. | | Didn't finish: Its result gives *name* a status that isn't "passed", "warning" or "failed". | A file can't be `skipped`: leave it out instead. | | Didn't finish: The extension crashed (signal …). | Its log may say why. If it keeps happening, [report it](https://orbtorrent.com/docs/help/report-a-problem). | | OrbTorrent doesn't know "*method*". Update OrbTorrent, or check the @orbtorrent/api version. | The extension uses a newer API than this OrbTorrent has. Update OrbTorrent. | ## orb build | Message | Fix | |---|---| | package.json isn't valid. | Each problem is listed under it, such as "“name” must be lowercase letters, digits and hyphens". [Manifest](https://orbtorrent.com/docs/extensions/reference/manifest) has the rules. | | Some entry points are missing. | The hook "*name*" needs src/*name*.ts: name the file after the hook, or rename the hook. | | "*module*" is a Node.js module, and extensions don't run in Node.js. | Use the API the message names, such as `fetch` instead of `https`, or `files.save` instead of `fs`. Extensions can't start programs: OrbTorrent can [run one of the user's own](https://orbtorrent.com/docs/guides/run-a-program) when a download finishes. | | TypeScript found problems. | Each is listed with its file, line and column. | | The extension couldn't be bundled. | Usually a missing package: run `npm install`. | | ! The icon assets/*icon* doesn't exist, so OrbTorrent shows a plain one. | A warning. Add the icon, or remove `"icon"`. | ## orb test | Message | Fix | |---|---| | The test has no mock for *method* *address*. Add one to "mocks": { "fetch": [...] }. | Add a mock that matches. `*` in its `url` matches any text. | | The test gives file *n* no "sha256". Add one to its download.files entry. | Add `sha256` to that file in the test. | | say which hook to run with "hook" | The extension has more than one hook. Name one in the test. | | the extension has no "verify" in package.json | Add `"verify"`, or remove `"verify": true` from the test. | | It didn't finish within 10 s. | The hook is waiting on something that never comes, such as a promise that never settles. Or raise `timeout`. | | There are no tests. | Add a JSON file to `tests/`. | | its result can't be shown: … | The hook returned something OrbTorrent would show as **Didn't finish**, for the reason given. | ## Connecting to OrbTorrent From `orb dev`, `orb run`, `orb downloads` and `orb install`. | Message | Fix | |---|---| | OrbTorrent isn't reachable. | Open OrbTorrent, and turn on **Allow development extensions** on the Develop tab of its Extensions page. `npx orb doctor` says what's missing. | | You chose not to load it in OrbTorrent. | OrbTorrent asked whether to load the folder, and the answer was no. Run the command again, and allow it. | | OrbTorrent couldn't load it. | The built manifest isn't valid: the problems are listed. Run `npx orb build`. | | No download matches "*name*". Your downloads: | Use part of one of the names listed, or `npx orb downloads`. | | "*name*" matches 3 downloads. Use one of these names or info hashes: | Use more of the name, or the info hash. | | "*name*" hasn't finished downloading, so its files aren't all there. Choose a finished download. | An `afterFinish` hook needs a finished download. | | "*name*" hasn't finished, and no check is holding it, so there's nothing to report yet. Choose a finished or held download. | An `afterChecks` hook needs a download that's finished, or held by a check. | | The user chose not to install it. | The install was cancelled in OrbTorrent. | | OrbTorrent closed the connection. | OrbTorrent quit, or development extensions were turned off. | ## Before it runs Shown on the Extensions page. | It says | Fix | |---|---| | Needs setup | A required setting is empty. Its hooks are skipped until it's filled in. | | Needs a newer OrbTorrent | It was built with a newer `@orbtorrent/api` than this OrbTorrent supports. Update OrbTorrent, or build with the API version `orb doctor` reports. | | "*folder*" couldn't be loaded | The extension's `package.json` isn't valid. Its page lists why. | --- # Changelog > What's changed in each version of @orbtorrent/api, the extension API and the orb command, and which OrbTorrent each needs. Each version of `@orbtorrent/api` and `@orbtorrent/create-extension`, newest first. They share a version, and are released together. The major and minor version are the extension API's: a patch version improves `orb` and fixes bugs, a minor version adds to the API, and a major version is for anything that would break an extension. [Versions](https://orbtorrent.com/docs/extensions/reference/api#versions) explains which OrbTorrent each needs. To update an extension, raise `@orbtorrent/api` in its `package.json`, run `npm install`, then test and build it again. ## 1.2.1 - **`orb` finds OrbTorrent by its new bundle ID,** `com.orbtorrent.mac` (OrbTorrent Dev: `com.orbtorrent.mac.dev`), for `orb doctor` and `orb package`. ## 1.2.0 Extension API 1.2, which needs an OrbTorrent that supports it. OrbTorrent says so if it's too old. - **`files` in a result** says what a hook found about particular files: `files: [{ file, status, summary, link }]`. OrbTorrent marks each problem beside its file, in the Files tab, the menu bar and the confirmation before a download starts, and lists them in the check with **Show in Finder**. A result counts as no better than its worst file. - **`afterFinish` hooks run each time a download finishes,** including when the user chooses more of its files later. **`isNew`** on each file says which finished since the hook last ran, and a later run that flags nothing new sends no notification. - **`afterChecks` hooks** report on the other checks, once they're done: after a download finishes, and when a check holds it. They get the results in **`download.checks`**, and **`download.isHeld`**. Their results show apart from the checks, and the install sheet says the extension can "See what other extensions found in your downloads". - **`files.sha256`** is remembered while a file stays as it was, so a download checked again isn't read again. - **`orb test`** checks results as OrbTorrent does. Tests can give `download.files[].isNew`, `download.checks` and `download.held`, and `expect` can check `files`. **`orb run`** prints each file's result. ## 1.1.0 Extension API 1.1, which needs an OrbTorrent that supports it. OrbTorrent says so if it's too old. - **`files.save(file, suffix, contents)`** adds a file next to one of a finished download's, named after it: `files.save(video, ".en.srt", text)` saves `Film.2024.en.srt` beside `Film.2024.mkv`. It needs the new permission `"files": "add"`, or `["contents", "add"]` with reading, which the install sheet shows as "Add files, such as subtitles, next to your downloads". Only subtitles, text, images and checksums, up to 10 MB each, and it never replaces a file, except one the same hook added before. OrbTorrent lists added files in the Checks tab, and moves and removes them with the download. - **Bytes over the network.** A `fetch` response has `bytes()` and `arrayBuffer()`, for images and zip files, and `files.save` takes a `Uint8Array`. - **`notify: true`** in a result also shows its summary as a notification. - **`orb test`:** mocks can answer with `base64`; `expect` can check `saved`, `savedIncludes` and `notify`; and `download.alsoOnDisk` lists files already next to the download's. A file the user chose not to download isn't available to a hook, as in OrbTorrent. - `AGENTS.md`, in new extensions and the Agent Skill, starts with what extensions can and can't do, so a coding agent can say before building when a request needs something they can't do yet. ## 1.0.4 - The docs moved to orbtorrent.com/docs/extensions. The npm pages, the README, `orb create`, `orb doctor` and new extensions' `AGENTS.md` link there, and to the docs in one file for language models, [llms-full.txt](https://orbtorrent.com/docs/extensions/llms-full.txt). - `orb create` ends with a link to the getting-started guide. ## 1.0.3 - The docs, the npm pages and `npm create`'s help say `npm create @orbtorrent/extension@latest`. Without `@latest`, npm keeps running whichever version it cached first. ## 1.0.2 - `npm create @orbtorrent/extension` always makes the extension from the newest `@orbtorrent/api`. Before, npm could reuse an older one it had kept from an earlier run, with that version's template and `AGENTS.md`. - `@orbtorrent/create-extension` now shares this package's version, and both are released together. ## 1.0.1 For building extensions with a coding agent. The extension API is unchanged. - `orb doctor` checks Node.js, OrbTorrent and the extension, and says what to fix, including the steps only the user can take. - `orb downloads` lists the downloads in OrbTorrent, to run a hook on. - `orb install` packages the extension and asks OrbTorrent to install it. OrbTorrent shows what it does and what it can reach, and `orb install` waits for the user's answer. The installed copy replaces a development copy. - `AGENTS.md` in new extensions covers them, and what only the user can do. - New extensions start with `"version": "1.0.0"`, which OrbTorrent shows on their page. ## 1.0.0 The first version on npm. Runs in OrbTorrent with extension API 1.0. - `beforeStart` and `afterFinish` hooks, and `verify` for settings. - `fetch`, `files.sha256`, `files.formData`, `progress`, `storage`, `getPreferenceValues`, `environment` and `sleep`. - `orb create`, `build`, `test`, `lint`, `dev`, `run` and `package`, each with `--json`. - The `before-start` and `after-finish` templates, with an `AGENTS.md` for coding agents. - MIT licensed, so extensions can use any licence. --- # How OrbTorrent works > How OrbTorrent is put together, from its SwiftUI app and qBittorrent's engine to the sandboxed extension host and signed updates. OrbTorrent is a native Mac app, written in Swift and SwiftUI, that runs qBittorrent's download engine in the background. Here's how the parts fit. [How OrbTorrent is built](https://orbtorrent.com/how-its-built) tells the longer story, with the reasons and excerpts of the code. ## The parts ``` OrbTorrent.app ├── Contents/MacOS/OrbTorrent the app: menu bar, window, settings ├── Contents/MacOS/qbittorrent-nox qBittorrent's engine, without its interface ├── Contents/MacOS/orb-extension-host runs extensions, sandboxed ├── Contents/Frameworks/Sparkle.framework updates └── Contents/Resources/Extensions/ the official extensions ``` Everything ships inside the one app. The engine is statically linked, so it depends only on macOS itself. ## The download engine OrbTorrent downloads with **qBittorrent's engine**, `qbittorrent-nox`, built from qBittorrent's released source with libtorrent, Qt, OpenSSL and Boost. It does the BitTorrent work: µTP, DHT, peer exchange, local peer discovery, web seeds, encryption, and UPnP and NAT-PMP. - **OrbTorrent starts it** as a background process, with its own profile in `~/Library/Application Support/OrbTorrent/Engine`, so it never touches a qBittorrent app you also have. - **It talks to it through qBittorrent's Web API,** which listens on `127.0.0.1` only, on a random free port, so nothing on your network can reach it. - **The API is protected by a random password** that OrbTorrent makes the first time it runs, stored in qBittorrent's PBKDF2 format, and readable only by you. - **When OrbTorrent quits,** it asks the engine to shut down, so each download's progress is always saved. If the engine crashes, OrbTorrent restarts it. OrbTorrent isn't affiliated with or endorsed by the qBittorrent project. The versions it's built from are on **Settings › Engine**, and in **About OrbTorrent › Acknowledgements**, with their licences. ## The app The app is SwiftUI, with Liquid Glass. It asks the engine for the state of every download once a second, and draws the menu bar orb, the panel and the window from it. What only OrbTorrent does, it does itself, outside the engine: - **Download only through a VPN.** OrbTorrent watches your Mac's network state and routes, finds the VPN, and binds the engine to its network interface before the engine starts. - **VPN port forwarding.** It asks the VPN's gateway for a port over NAT-PMP, or reads it from Private Internet Access, renews it, and sets the engine's listening port to match. - **Disk space.** It counts what each unfinished download still needs, warns before one won't fit, and pauses downloads before a disk fills. - **Finding more peers.** It downloads a public tracker list each day, and adds it to public torrents only. - **Download order.** It turns the order you drag files into into the engine's four priorities, as each file finishes. - **Importing.** It reads Transmission's and qBittorrent's files directly, and adds their downloads to the engine with their progress. ## Extensions Extensions run in **`orb-extension-host`**, a small helper with JavaScriptCore and the App Sandbox. OrbTorrent starts a fresh one for every run of a hook. The helper can't read files, reach the network or look up a host name: it asks OrbTorrent for everything, and OrbTorrent checks each request against the extension's permissions. [Security model](https://orbtorrent.com/docs/extensions/security) explains. Before a download starts, OrbTorrent adds it so that the engine stops it the moment its file list arrives. The checks run on that list, and OrbTorrent then starts the download, or holds it. ## Updates OrbTorrent updates itself with [Sparkle](https://sparkle-project.org). Once a day it reads a feed from `updates.orbtorrent.com`, a Cloudflare Worker. The feed and each update are signed, and Sparkle checks both signatures before it installs anything. Updates are downloaded from GitHub Releases. After an update, **What's New** shows the release's notes, from the same feed. ## Privacy OrbTorrent has no account, ID or crash reporting. Its update check carries a few anonymous counts, from `AnonymousCounts.swift`, which the update feed's Worker adds up. [Privacy](https://orbtorrent.com/docs/guides/privacy) lists everything it connects to and counts. ## The source OrbTorrent is open source, under the GNU GPL v3 or later. The extension SDK is MIT. The [source on GitHub](https://github.com/orbtorrent/OrbTorrent) has the app, the engine's build, the extension host, the SDK, the official extensions, the update feed and this website. [Contribute](https://orbtorrent.com/docs/contribute) explains how to build it. --- # Contribute to OrbTorrent > How to report problems, suggest features, build OrbTorrent from source, and send a change, with the project's layout and the rules for pull requests. Bug reports, fixes and extensions are all welcome. OrbTorrent is open source, under the GNU GPL v3 or later, and lives on [GitHub](https://github.com/orbtorrent/OrbTorrent). ## Ways to help - **Report a problem.** **Help › Report a Problem** in the app fills in what helps, or open an issue with the bug report form. [Report a problem](https://orbtorrent.com/docs/help/report-a-problem) explains. - **Suggest a feature.** Check the [roadmap](https://github.com/orbtorrent/OrbTorrent/blob/main/ROADMAP.md) first, then open an issue with the feature request form. OrbTorrent aims to stay small and fit in on a Mac, so not every good idea will make it in. Many fit better as an extension. - **Build an extension.** It doesn't need to be in the repository. Start with [Build extensions](https://orbtorrent.com/docs/extensions), then add yours to the [community list](https://orbtorrent.com/docs/extensions/sharing#the-community-list), or [propose it as an official extension](https://orbtorrent.com/docs/extensions/sharing#propose-an-official-extension). - **Improve these docs.** Every page has **Edit page** at the bottom. The docs are Markdown, in `website/src/content/docs/docs/`. - **Report a security problem** privately, by email, not in an issue. See [Security problems](https://orbtorrent.com/docs/help/report-a-problem#security-problems). ## Build it You need **Xcode 26** or later, **Node.js 22.13** or later, and three tools from Homebrew: ```sh brew install cmake ninja xcodegen git clone https://github.com/orbtorrent/OrbTorrent.git cd OrbTorrent make engine # build qBittorrent's engine (about 15 minutes the first time) make dev # build and open OrbTorrent Dev, which runs beside OrbTorrent make test # run the tests ``` | Command | Does | |---|---| | `make engine` | Builds `qbittorrent-nox` and everything it needs from source, each checked against a pinned checksum or signature. | | `make dev` | Builds **OrbTorrent Dev**, quits the running copy and opens the new one. | | `make run` | Builds OrbTorrent itself and opens it. | | `make test` | Runs the app's tests. | | `make test-extensions` | Runs the tests of the extension SDK, the official extensions and their scripts. | | `make dmg` | Packages a disk image. | | `make project` | Generates `OrbTorrent.xcodeproj` from `project.yml`, to work in Xcode. | ### OrbTorrent Dev `make dev` builds **OrbTorrent Dev**, so you can keep using OrbTorrent while you work on it. It has its own library and engine in `~/Library/Application Support/OrbTorrent Dev`, its own settings and Keychain items, and an amber icon. It never opens magnet links or `.torrent` files, and doesn't start at login, leaving both to OrbTorrent. Point `orb` at it with `ORB_APP=dev`. The repository's [development guide](https://github.com/orbtorrent/OrbTorrent/blob/main/docs/development.md) has the rest: the engine build, signing, the project layout, and how releases go out. ## Send a change For anything bigger than a small fix, open an issue first, so we can agree on the approach before you spend time on it. 1. Work in **OrbTorrent Dev**, so your everyday library is safe. 2. Match the code around your change: its naming, structure and amount of comments. Text people see in the app is short, plain English. 3. Add or update tests for what you change, and run them with `make test` and `make test-extensions`. 4. For a change you can see, put before and after screenshots in the pull request. 5. Write commit messages that say what changes for people using OrbTorrent, such as "Show each peer's country flag in the Peers tab". ## Use a coding agent Coding agents are welcome. The repository's [`AGENTS.md`](https://github.com/orbtorrent/OrbTorrent/blob/main/AGENTS.md) tells yours how OrbTorrent is built, tested and written, and what must stay true, such as extensions staying sandboxed. Claude Code reads it through `CLAUDE.md`. You're still the author: - read every line before you send it, and try it in OrbTorrent Dev; - say in the pull request which agent helped; - keep it to one change, and to the issue it's for. A pull request that nobody has read or tried may be closed without a review. ## Licence By sending a pull request, you agree that your contribution is under the same licence as the code it changes: the GPL for the app, MIT for the SDK. There's no contributor licence agreement to sign. ## The name and icon The name OrbTorrent and the orb icon aren't covered by the GPL. You're welcome to fork OrbTorrent, but please give your version its own name and icon, so people can tell it apart from the official app, and don't change them in a pull request. ## The layout | Folder | Holds | |---|---| | `OrbTorrent/` | The SwiftUI app | | `OrbTorrentTests/` | The app's tests | | `ExtensionHost/` | `orb-extension-host`, the sandboxed helper that runs extensions | | `extensions/` | The official extensions | | `sdk/` | `@orbtorrent/api`: the extension API, the `orb` command and the templates | | `skills/` | The Agent Skill for building extensions | | `engine/` | The engine's build script, pinned versions and patches | | `updates/` | `updates.orbtorrent.com`, the update feed | | `website/` | orbtorrent.com and these docs | | `docs/` | The development guide, and the extension system's design |