npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@emgeebee/airfreyr

v1.0.23

Published

Music downloader with an HTTP queue server for remote track requests

Readme

@emgeebee/airfreyr

Download music from YouTube. Fork of freyr-js with an HTTP queue server for remote track requests.

Requirements

  • Node.js >= 16
  • Python >= 3.2 (for youtube-dl-exec)
  • AtomicParsley on your PATH

Installation

npm install -g @emgeebee/airfreyr
# or
npx @emgeebee/airfreyr <command>

From source:

git clone https://github.com/emgeebee/air-freyr.git && cd air-freyr
npm install && npm link

Queue server

Run an HTTP server that appends tracks to queue files and triggers downloads automatically.

airfreyr serve

Configuration

Precedence: CLI flags → environment variables → conf.json → defaults.

| Flag | Env | conf.json | Purpose | | --- | --- | --- | --- | | -q, --queue-dir <DIR> | AIRFREYR_QUEUE_DIR | serve.queueDir | Queue .json files directory | | -D, --output-dir <DIR> | AIRFREYR_OUTPUT_DIR | dirs.output | Download output directory | | -p, --port <PORT> | AIRFREYR_PORT | serve.port | Listen port (default: 3797) | | -H, --hostname <HOST> | AIRFREYR_HOSTNAME | serve.hostname | Bind address |

"serve": {
  "hostname": "localhost",
  "port": 3797,
  "queueDir": "."
},
"dirs": {
  "output": "./0"
}

Downloads use dirs.output unless overridden by -D or AIRFREYR_OUTPUT_DIR.

AIRFREYR_QUEUE_DIR=./queues AIRFREYR_OUTPUT_DIR=./music airfreyr serve

API

All responses include "version" (the running @emgeebee/airfreyr package version).

POST /add — append a track and start downloading the queue file.

{
  "file": "kids.json",
  "genre": "Kids",
  "artist": "Moana",
  "title": "You're Welcome",
  "path": "https://www.youtube.com/watch?v=G8QjumNNNBY"
}
  • title is optional
  • path can also be sent as url
  • Existing tracks in the file are skipped; only new lines are downloaded
  • If a download is already running for that file, another run is queued when it finishes

GET /status?file=kids.json — check whether a download is in progress (includes lastError if the last run failed)

curl 'http://<nas-ip>:3797/status?file=kids.json'

Example when a download failed:

{
  "version": "1.0.1",
  "ok": true,
  "file": "kids.json",
  "download": {
    "running": false,
    "pending": false,
    "lastError": "airfreyr exited with code 2",
    "lastExitCode": 2,
    "lastStartedAt": "2026-06-22T20:15:00.000Z",
    "lastFinishedAt": "2026-06-22T20:15:42.000Z"
  }
}

If lastError is set, check container logs for detail: sudo docker logs --tail 100 airfreyr

GET /health — server status and configured directories

curl -X POST http://localhost:3797/add \
  -H 'Content-Type: application/json' \
  -d '{"file":"kids.json","genre":"Kids","artist":"Moana","title":"You'\''re Welcome","path":"https://www.youtube.com/watch?v=G8QjumNNNBY"}'

Batch downloads

Download tracks listed in a queue file:

airfreyr -i kids.json
airfreyr -i kids.json -d ./music   # custom output directory

Queue file format

Queue files are JSON with an entries array. Each entry has artist, title, url, and optional disabled, note, and genre (when different from the list name).

{
  "entries": [
    {
      "artist": "Moana",
      "title": "You're Welcome",
      "url": "https://www.youtube.com/watch?v=G8QjumNNNBY"
    },
    {
      "artist": "Peppa Pig",
      "title": "Jumping in Muddy Puddles",
      "url": "https://www.youtube.com/watch?v=t7dTdE8Aqtw",
      "disabled": true,
      "note": "already have this"
    }
  ]
}

Genre defaults from the filename: kids.json → Kids, folk rock.json → Folk Rock.

The queue UI and API still accept CSV lines for bulk paste (artist,title,url). Legacy .txt CSV files are migrated to .json automatically when the server starts.

Files are organised as <output>/<genre>/youtube/<artist> - <title>.<format>.

CLI

airfreyr <url-or-uri>              # download a single track
airfreyr -i queues/kids.json         # batch download from file
airfreyr serve                     # start the queue server
airfreyr urify <url>               # convert URLs to service URIs
airfreyr --help                    # full options

Common flags:

| Flag | Purpose | | --- | --- | | -d, --directory <DIR> | Output directory | | -f, --force | Overwrite existing files | | -b, --bitrate <N> | Audio bitrate (default: 320k) | | -x, --format <FORMAT> | Output format (default: mp3) | | --no-logo / --no-header | Quieter output |

Configuration

On first run, airfreyr creates a user config file with service credentials and defaults:

  • Linux: ~/.config/AirFreyr/d3fault.x4p
  • macOS: ~/Library/Preferences/AirFreyr/d3fault.x4p

Project defaults live in conf.json. Use -o, --config <FILE> to point at an alternative.

Docker (queue server)

A minimal node:20-alpine image runs npx @emgeebee/airfreyr@latest serve and restarts every 3 hours to pull the latest npm publish.

Quick start (build once)

mkdir -p docker/queues docker/music docker/config
cp docker/conf.json.example docker/config/conf.json

cd docker
docker compose up --build

No build — plain node:alpine

If you only want the stock Node image plus a mounted entrypoint script:

cd docker
docker compose -f compose.alpine.yml up -d

This uses node:20-alpine directly. Python is installed on first start (apk add python3 bash), then the entrypoint runs npx.

Queue files go in docker/queues/ (e.g. kids.json). Downloads land in docker/music/.

docker run (without compose)

docker build -f docker/Dockerfile -t airfreyr-serve .

docker run -d --name airfreyr \
  --restart unless-stopped \
  -p 3797:3797 \
  -e AIRFREYR_HOSTNAME=0.0.0.0 \
  -e AIRFREYR_REFRESH_HOURS=3 \
  -v "$PWD/queues:/data/queues" \
  -v "$PWD/music:/data/music" \
  -v "$PWD/config:/data/config" \
  airfreyr-serve

Put conf.json at config/conf.json inside the mounted config volume.

Synology Container Manager

Use docker/synology-compose.yml and docker/conf.json.example.

1. Create folders on the NAS

In File Station, create:

/volume1/docker/airfreyr/queues    ← queue .json files (kids.json, pop.json, …)
/volume1/docker/airfreyr/music     ← downloaded tracks
/volume1/docker/airfreyr/config    ← conf.json

Change volume1 if your shared folder lives on a different volume.

2. Copy config and compose onto the NAS

| NAS path | Source in repo | | --- | --- | | /volume1/docker/airfreyr/config/conf.json | docker/conf.json.example | | /volume1/docker/airfreyr/docker-compose.yml | docker/synology-compose.yml |

Edit paths in docker-compose.yml if not using volume1. Put queue files in queues/.

3. Create the container project

Container Manager → Project → Create

  • Project name: airfreyr
  • Path: /volume1/docker/airfreyr/docker-compose.yml
  • Start the project (pulls node:20-alpine, no build step)

Directory mappings

| NAS folder | Container path | Purpose | | --- | --- | --- | | /volume1/docker/airfreyr/queues | /data/queues | Queue .json files | | /volume1/docker/airfreyr/music | /data/music | Downloaded music | | /volume1/docker/airfreyr/config | /data/config | conf.json (+ saved auth after first run) |

Port

Map host port 3797 → container 3797. Then from your LAN:

curl http://<nas-ip>:3797/health

Manual container (UI fields)

If you prefer Container → Create instead of a Project:

| Setting | Value | | --- | --- | | Image | node:20-alpine | | Command | apk add --no-cache python3 bash && exec /entrypoint.sh | | Entrypoint | mount serve-entrypoint.sh/entrypoint.sh | | Port | 3797:3797 | | AIRFREYR_HOSTNAME | 0.0.0.0 | | AIRFREYR_QUEUE_DIR | /data/queues | | AIRFREYR_OUTPUT_DIR | /data/music | | AIRFREYR_CONFIG | /data/config/conf.json | | Volume | /volume1/docker/airfreyr/queues/data/queues | | Volume | /volume1/docker/airfreyr/music/data/music | | Volume | /volume1/docker/airfreyr/config/data/config | | Restart policy | Unless stopped |

The container runs npx @emgeebee/airfreyr@latest serve and refreshes every 3 hours.

"Build project failed" (no useful logs)

Synology often shows only Build project '…' failed with no detail. The real error is elsewhere.

1. Check the folders exist first (most common cause):

ls -la /volume1/docker/airfreyr/queues
ls -la /volume1/docker/airfreyr/music
ls -la /volume1/docker/airfreyr/config/conf.json

Synology will fail if any mapped folder or conf.json is missing.

2. Get the real error via SSH (Control Panel → Terminal & SNMP → Enable SSH):

cd /volume1/docker/airfreyr
sudo docker compose config          # validate compose syntax
sudo docker compose up              # run in foreground — errors print here
# or after a failed project start:
sudo docker compose logs --tail 50
sudo docker logs airfreyr 2>&1

3. In Container Manager UI

  • Container → select airfreyrDetailsLog tab (runtime logs, after container starts)
  • Project → your project → Action → delete and recreate after fixing paths
  • Log Center → search for docker / container around the failure time

4. Common fixes

| Problem | Fix | | --- | --- | | Volume path wrong | Use absolute paths; edit volume1 in docker-compose.yml | | conf.json missing | Copy docker/conf.json.example to config/conf.json | | Old project stuck | Delete project + container, recreate | | Still using build: | Use docker/synology-compose.yml — it has no build step | | Port in use | Change 3797:3797 to e.g. 3798:3797 |

Environment

| Variable | Default | Purpose | | --- | --- | --- | | AIRFREYR_HOSTNAME | 0.0.0.0 | Bind address (use 0.0.0.0 in Docker) | | AIRFREYR_PORT | 3797 | HTTP port | | AIRFREYR_QUEUE_DIR | /data/queues | Queue .json directory | | AIRFREYR_OUTPUT_DIR | /data/music | Download output directory | | AIRFREYR_CONFIG | /data/config/conf.json | Config file for download runs | | AIRFREYR_REFRESH_HOURS | 3 | Restart interval to pull latest from npm | | AIRFREYR_REFRESH_SECONDS | — | Override refresh interval in seconds |

On each restart the entrypoint clears the npx cache and runs npx --yes @emgeebee/airfreyr@latest serve.

Publishing to npm

Pushes to main or master run .github/workflows/publish.yml, matching the simple pipeline in phone_cli:

  1. npm ci
  2. npm publish --access public

Uses npm trusted publishing (OIDC, no NPM_TOKEN secret). On npmjs.com, link the GitHub repo to the @emgeebee scope before the first publish.

You can also trigger manually: Actions → publish → Run workflow.

Test the API

curl http://localhost:3797/health

curl -X POST http://localhost:3797/add \
  -H 'Content-Type: application/json' \
  -d '{"file":"kids.json","genre":"Kids","artist":"Moana","title":"You'\''re Welcome","path":"https://www.youtube.com/watch?v=G8QjumNNNBY"}'

License

Apache-2.0. Based on freyr-js by Miraculous Owonubi.