tools

various tools I have been using throughout the years
Log | Files | Refs | README | LICENSE

commit b31ff96374603a56ca943dbd9aec8d980b92f0bb
parent 901745c27ec568193ab0ba1fe8af9165c0850bbd
Author: mtmn <miro@haravara.org>
Date:   Fri,  8 May 2026 11:45:33 +0200

feat: add readme, move shirts, deprecate mold

Diffstat:
MMODULE.bazel | 1-
MREADME.md | 68++++++++++++++++++++++++++++++++++++--------------------------------
Abackup/README.md | 35+++++++++++++++++++++++++++++++++++
Mbazel/rust.bzl | 2--
Adiffamer/README.md | 38++++++++++++++++++++++++++++++++++++++
Alastfm_rs/README.md | 32++++++++++++++++++++++++++++++++
Ampd/README.md | 54++++++++++++++++++++++++++++++++++++++++++++++++++++++
Ants/README.md | 24++++++++++++++++++++++++
Apracomer/README.md | 33+++++++++++++++++++++++++++++++++
Dshirts/.gitignore | 4----
Dshirts/BUILD | 13-------------
Dshirts/Containerfile | 20--------------------
Dshirts/Makefile | 32--------------------------------
Dshirts/README.md | 46----------------------------------------------
Dshirts/config/config.exs | 7-------
Dshirts/config/runtime.exs | 10----------
Dshirts/flake.lock | 27---------------------------
Dshirts/flake.nix | 28----------------------------
Dshirts/lib/shortener/application.ex | 37-------------------------------------
Dshirts/lib/shortener/db.ex | 83-------------------------------------------------------------------------------
Dshirts/lib/shortener/router.ex | 111-------------------------------------------------------------------------------
Dshirts/mix.exs | 31-------------------------------
Dshirts/mix.lock | 27---------------------------
Ashuffle/README.md | 25+++++++++++++++++++++++++
Aspeediness/README.md | 25+++++++++++++++++++++++++
Atempfox/README.md | 22++++++++++++++++++++++
Awlr-river-title/README.md | 19+++++++++++++++++++
27 files changed, 343 insertions(+), 511 deletions(-)

diff --git a/MODULE.bazel b/MODULE.bazel @@ -15,7 +15,6 @@ rust.toolchain( edition = "2024", extra_rustc_flags = [ "-Ctarget-cpu=native", - "-Clink-arg=-fuse-ld=mold", ], extra_target_triples = [], versions = ["nightly/2026-04-25"], diff --git a/README.md b/README.md @@ -1,41 +1,45 @@ # tools -### bootstrap -```bash -sudo pacman -Sy bazelisk mold -bazel fetch //... -``` +Various tools I have been using throughout the years. -### query -```bash -bazel query "kind('rust_', //...) except attr('name', '.*-(debug|profiling)$', //...)" -bazel query "kind('zig_', //...)" -bazel query "kind('py_', //...)" -bazel query "kind('cc_', //...)" -bazel query "kind('sh_', //...)" -bazel query "kind('rb_', //...)" -``` +| tool | description | +|---|---| +| [aaltomuoto](aaltomuoto/) | frequency-colored audio waveform generator | +| [backup](backup/) | backup orchestrator — git, plakar, S3 | +| [bandeno](bandeno/) | Bandcamp JSON Feed proxy | +| [diffamer](diffamer/) | line-file syncer with rsync and coloured diff | +| [diggah](diggah/) | find files modified within a time range, locally or over SSH | +| [hue](hue/) | Ruby library and CLI for Philips Hue | +| [lastfm_rs](lastfm_rs/) | Last.fm scrobble exporter to CSV | +| [lazymaster](lazymaster/) | 2-pass audio loudness normalization via ffmpeg | +| [mpd](mpd/) | MPD utility tools | +| [nts](nts/) | NTS show tracklist fetcher | +| [pmn](pmn/) | password selection utility for pass (Wayland, X11, macOS) | +| [pracomer](pracomer/) | Pomodoro timer | +| [shuffle](shuffle/) | random album picker for MPD | +| [speediness](speediness/) | network speed test | +| [tempfox](tempfox/) | Firefox user.js preference overrides | +| [wlr-river-title](wlr-river-title/) | River/Wayland focused view title printer | + +## Building -### build -```bash -bazel build //magnolia +Install [Bazelisk](https://github.com/bazelbuild/bazelisk), then fetch dependencies: + +```sh +bazelisk fetch //... ``` -### deploy -> `bazel-bin` -> ``$HOME/.local/share/tools`` -```bash -sudo install -d -m 700 -o $USER $HOME/.local/share/tools -bazel run //magnolia:deploy +Build all tools: + +```sh +bazelisk build //... ``` -### misc -```bash -# updates root `Cargo.lock` (rust tools are nested in a workspace) -cargo generate-lockfile -# builds everything (including `debug` and `profiling` binaries) -bazel build //... -# updates python packages (`requirements.in` -> `requirements.out`) -bazel run //:requirements.update -# runs a `justfile` that contains all of the necessary commands -just +## Deploying + +Installs built binaries to `$HOME/.local/share/tools`: + +```sh +sudo install -d -m 700 -o $USER $HOME/.local/share/tools +bazelisk run //magnolia:deploy ``` diff --git a/backup/README.md b/backup/README.md @@ -0,0 +1,35 @@ +# backup + +A backup orchestrator that commits and pushes git repos, snapshots directories with [plakar](https://github.com/PlakarKorp/plakar), and syncs files to S3 via `aws s3 sync`. + +## Building + +```sh +cabal build +``` + +## Usage + +```sh +backup <type> [--push] +``` + +### Types + +| Type | Description | +|------|-------------| +| `push` | Commit and push all git repos | +| `git` | Commit all git repos (push with `--push`) | +| `annex` | Sync all git-annex repos | +| `local` | Snapshot `$HOME` to the local plakar store | +| `plakar` | Snapshot all configured directories to the remote plakar store | +| `mobius` | Sync all configured directories to S3 | +| `ls` | List all configured backup targets | +| `<name>` | Run backup for a specific named target | + +### Options + +| Flag | Description | +|------|-------------| +| `--push` | Push after committing (for git targets) | +| `-h`, `--help` | Show help | diff --git a/bazel/rust.bzl b/bazel/rust.bzl @@ -19,7 +19,6 @@ def rust_app( common_flags = [ "-Ctarget-cpu=native", - "-Clink-arg=-fuse-ld=mold", ] + rustc_flags rust_binary( @@ -95,7 +94,6 @@ def rust_lib( common_flags = [ "-Ctarget-cpu=native", - "-Clink-arg=-fuse-ld=mold", ] + rustc_flags rust_library( diff --git a/diffamer/README.md b/diffamer/README.md @@ -0,0 +1,38 @@ +# diffamer + +Syncs line-oriented text files between a local directory and a remote host over rsync. By default shows a coloured diff; pass `--sync` to apply changes. + +Merge strategy: union of local and remote lines, deduplicated and sorted with natural ordering. + +## Building + +```sh +cargo build --release +``` + +## Usage + +```sh +diffamer --host <host> --local <dir> (--remote <dir> | --same-as-local) [--sync] [--reverse] +``` + +### Options + +| Flag | Description | +|------|-------------| +| `--host` | SSH host alias to sync with | +| `--local` | Local directory containing the text files | +| `--remote` | Remote path to sync from/to | +| `--same-as-local` | Use the same path on the remote as `--local` | +| `--sync` | Apply changes (default: dry-run diff only) | +| `--reverse` | Merge local → remote instead of remote → local | + +### Example + +```sh +# Preview changes from remote +diffamer --host myserver --local ~/lists --same-as-local + +# Apply changes +diffamer --host myserver --local ~/lists --same-as-local --sync +``` diff --git a/lastfm_rs/README.md b/lastfm_rs/README.md @@ -0,0 +1,32 @@ +# lastfm_rs + +Exports Last.fm scrobbles to a CSV file. Output format is compatible with [Maloja](https://github.com/krateng/maloja). + +## Building + +```sh +cargo build --release +``` + +## Usage + +```sh +lastfm_rs --username <user> --api-key <key> [options] +``` + +### Options + +| Flag | Default | Description | +|------|---------|-------------| +| `-u`, `--username` | — | Last.fm username | +| `-a`, `--api-key` | — | Last.fm API key | +| `-o`, `--output` | `scrobbles.csv` | Output CSV file path | +| `--from` | — | Start time (Unix timestamp) | +| `--to` | — | End time (Unix timestamp) | +| `-l`, `--limit` | — | Max pages to fetch (200 tracks/page) | + +### Example + +```sh +lastfm_rs --username miro --api-key abc123 --output scrobbles.csv +``` diff --git a/mpd/README.md b/mpd/README.md @@ -0,0 +1,54 @@ +# mpd + +A collection of C utilities for controlling [MPD](https://www.musicpd.org/) via libmpdclient. + +## Tools + +### mpd_add_to_queue + +Adds tracks to the MPD play queue. Reads a list of paths from a file or stdin. + +```sh +mpd_add_to_queue [file] +``` + +### mpd_add_to_playlist + +Adds tracks to a named MPD playlist. Reads a list of paths from a file or stdin. + +```sh +mpd_add_to_playlist [file] +``` + +### mpd_edit_queue + +Opens the current MPD queue in `$EDITOR` as a list of file paths. Saves and replaces the queue with the edited result, preserving the currently playing track's position where possible. + +```sh +mpd_edit_queue +``` + +### mpd_update_library + +Triggers an MPD library update and waits for it to complete, retrying on connection errors with exponential backoff. + +```sh +mpd_update_library +``` + +### mpd_update_queue + +Replaces the current MPD queue with a new list of tracks read from a file or stdin. + +```sh +mpd_update_queue [file] +``` + +## Configuration + +All tools read connection settings from environment variables. + +| Variable | Default | Description | +|----------|---------|-------------| +| `MPD_HOST` | `localhost` | MPD server hostname | +| `MPD_PORT` | `6600` | MPD server port | diff --git a/nts/README.md b/nts/README.md @@ -0,0 +1,24 @@ +# nts + +Fetches the tracklist for an [NTS Radio](https://www.nts.live/) episode and saves it as a text file. + +## Usage + +```sh +nts <episode-url> +``` + +The tracklist is written to `<episode-name>.txt` in the current directory and also printed to stdout. + +### Example + +```sh +nts https://www.nts.live/shows/some-show/episodes/some-episode-01-01-2025 +# writes some-episode-01-01-2025.txt +``` + +## Options + +| Flag | Description | +|------|-------------| +| `--debug` | Print debug output to stderr | diff --git a/pracomer/README.md b/pracomer/README.md @@ -0,0 +1,33 @@ +# pracomer + +A Pomodoro timer for the terminal. Alternates between work and break intervals, printing the current status and sending desktop notifications at each transition. + +## Building + +```sh +zig build +``` + +## Usage + +```sh +pracomer [-t <minutes>] [-b <minutes>] [-s] [--file <path>] +``` + +### Options + +| Flag | Default | Description | +|------|---------|-------------| +| `-t <minutes>` | `26` | Work interval length in minutes | +| `-b <minutes>` | `6` | Break interval length in minutes | +| `-s` | — | Disable desktop notifications | +| `--file <path>` | — | Write current status to a file (useful for status bars) | +| `-h` | — | Show help | + +### Controls + +| Key | Action | +|-----|--------| +| `p` | Pause / resume | +| `r` | Reset current interval | +| `q` | Quit | diff --git a/shirts/.gitignore b/shirts/.gitignore @@ -1,4 +0,0 @@ -/.expert/ -/_build/ -/deps/ -khal_notifier diff --git a/shirts/BUILD b/shirts/BUILD @@ -1,13 +0,0 @@ -load("//bazel:local-deploy.bzl", "local_deploy") - -# you have to `mix escript.build` first and then copy the binary to /opt/tools -filegroup( - name = "binary", - srcs = ["shirt_linkener"], - visibility = ["//visibility:public"], -) - -local_deploy( - name = "deploy", - srcs = [":binary"], -) diff --git a/shirts/Containerfile b/shirts/Containerfile @@ -1,20 +0,0 @@ -FROM docker.io/elixir:1.19-slim - -ENV MIX_ENV=prod -ENV DEBIAN_FRONTEND=noninteractive - -RUN apt-get update && apt-get install -y --no-install-recommends \ - ca-certificates \ - && rm -rf /var/lib/apt/lists/ - -WORKDIR /app -COPY mix.exs mix.lock ./ - -RUN mix local.hex --force && mix local.rebar --force && MIX_ENV=prod mix deps.get --only prod - -COPY config ./config -COPY lib ./lib - -RUN MIX_ENV=prod mix compile - -CMD ["mix", "run", "--no-halt"] diff --git a/shirts/Makefile b/shirts/Makefile @@ -1,32 +0,0 @@ -.PHONY: all build format lint check clean run - -AUTH_TOKEN := demo -BASE_URL := http://localhost -PORT := 4000 -DB_PATH := shortener.db - -export AUTH_TOKEN -export BASE_URL -export PORT -export DB_PATH - -all: build - -build: - mix deps.get - mix compile - -run: - mix run --no-halt - -format: - mix format - -lint: - mix credo --strict - -check: - mix dialyzer - -clean: - mix clean diff --git a/shirts/README.md b/shirts/README.md @@ -1,46 +0,0 @@ -# shirts - -A URL shortener. Links are stored in a SQLite database and served under a configurable base URL. - -## Building - -```sh -mix deps.get -mix release -``` - -## Usage - -### Create a short link - -```sh -curl -X POST http://localhost:8721/curtail \ - -H "Authorization: Bearer <token>" \ - -H "Content-Type: application/json" \ - -d '{"url": "https://example.com/very/long/path"}' -``` - -Optionally provide a custom code: - -```sh -curl -X POST http://localhost:8721/curtail \ - -H "Authorization: Bearer <token>" \ - -H "Content-Type: application/json" \ - -d '{"url": "https://example.com/very/long/path", "code": "my-code"}' -``` - -### Follow a short link - -``` -GET /s/<code> -``` - -## Configuration - -| Variable | Description | -|------------|--------------------------------------| -| `PORT` | Port to listen on (default: `8721`) | -| `HOST` | Host to bind to (default: `127.0.0.1`) | -| `BASE_URL` | Base URL used when generating links | -| `AUTH_TOKEN` | Bearer token for the create endpoint | -| `DB_PATH` | Path to the SQLite database file | diff --git a/shirts/config/config.exs b/shirts/config/config.exs @@ -1,7 +0,0 @@ -import Config - -config :shortener, - port: System.get_env("PORT", "4000") |> String.to_integer(), - auth_token: System.get_env("AUTH_TOKEN", "changeme"), - db_path: System.get_env("DB_PATH", "data.db"), - base_url: System.get_env("BASE_URL", "http://localhost") diff --git a/shirts/config/runtime.exs b/shirts/config/runtime.exs @@ -1,10 +0,0 @@ -import Config - -if config_env() == :prod do - config :shortener, - port: System.get_env("PORT") |> then(&(if &1, do: String.to_integer(&1), else: 4000)), - host: System.get_env("HOST") || "127.0.0.1", - auth_token: System.fetch_env!("AUTH_TOKEN"), - db_path: System.get_env("DB_PATH") || "data.db", - base_url: System.get_env("BASE_URL") || "http://localhost" -end diff --git a/shirts/flake.lock b/shirts/flake.lock @@ -1,27 +0,0 @@ -{ - "nodes": { - "nixpkgs": { - "locked": { - "lastModified": 1778124196, - "narHash": "sha256-pYEytCNic/czazbV9r3tbQ6BZzqRBg/41x2dIC5ymOo=", - "owner": "NixOS", - "repo": "nixpkgs", - "rev": "68a8af93ff4297686cb68880845e61e5e2e41d92", - "type": "github" - }, - "original": { - "owner": "NixOS", - "ref": "nixpkgs-unstable", - "repo": "nixpkgs", - "type": "github" - } - }, - "root": { - "inputs": { - "nixpkgs": "nixpkgs" - } - } - }, - "root": "root", - "version": 7 -} diff --git a/shirts/flake.nix b/shirts/flake.nix @@ -1,28 +0,0 @@ -{ - inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; - - outputs = {nixpkgs, ...}: { - packages = nixpkgs.lib.genAttrs ["x86_64-linux" "aarch64-linux"] (system: let - pkgs = nixpkgs.legacyPackages.${system}; - in { - default = pkgs.beamPackages.mixRelease { - pname = "shortener"; - version = "0.0.1"; - src = ./.; - - nativeBuildInputs = [pkgs.beamPackages.elixir_1_18]; - - MIX_HOME = "nix-mix"; - HEX_HOME = "nix-hex"; - ELIXIR_MAKE_CACHE_DIR = "nix-elixir-make"; - - mixFodDeps = pkgs.beamPackages.fetchMixDeps { - pname = "shortener-deps"; - version = "0.0.1"; - src = ./.; - sha256 = "sha256-U/tNP3s7b4bBUrQx5l4VtDHpV9DsWj6zBYj8jsM4Acw="; - }; - }; - }); - }; -} diff --git a/shirts/lib/shortener/application.ex b/shirts/lib/shortener/application.ex @@ -1,37 +0,0 @@ -defmodule Shortener.Application do - @moduledoc """ - Shortener application entry point. - Starts the database and the HTTP server (Bandit). - """ - use Application - require Logger - - @impl true - def start(_type, _args) do - port = Application.fetch_env!(:shortener, :port) - host = Application.fetch_env!(:shortener, :host) - db_path = Application.fetch_env!(:shortener, :db_path) - - children = [ - {Shortener.DB, db_path}, - {Bandit, plug: Shortener.Router, port: port, ip: parse_ip(host)} - ] - - Logger.info("Starting shortener on #{host}:#{port}") - - Supervisor.start_link(children, strategy: :one_for_one, name: Shortener.Supervisor) - end - - @impl true - def stop(_state) do - Logger.info("Shortener application stopped") - :ok - end - - defp parse_ip(ip_str) do - case :inet.parse_address(to_charlist(ip_str)) do - {:ok, ip} -> ip - _ -> {127, 0, 0, 1} - end - end -end diff --git a/shirts/lib/shortener/db.ex b/shirts/lib/shortener/db.ex @@ -1,83 +0,0 @@ -defmodule Shortener.DB do - @moduledoc """ - SQLite database interface using Exqlite. - Stores and retrieves short links. - """ - - use GenServer - require Logger - - def start_link(db_path) do - GenServer.start_link(__MODULE__, db_path, name: __MODULE__) - end - - @impl true - def init(db_path) do - Process.flag(:trap_exit, true) - {:ok, conn} = Exqlite.Sqlite3.open(db_path) - - :ok = - Exqlite.Sqlite3.execute(conn, """ - CREATE TABLE IF NOT EXISTS shirts ( - code TEXT PRIMARY KEY, - url TEXT NOT NULL, - created_at TEXT NOT NULL DEFAULT (datetime('now')) - ) - """) - - Logger.info("Database ready at #{db_path}") - {:ok, conn} - end - - @doc "Insert a new short code → url mapping. Returns :ok or {:error, reason}." - def insert(code, url) do - GenServer.call(__MODULE__, {:insert, code, url}) - end - - @doc "Look up a URL by short code. Returns {:ok, url} or :not_found." - def lookup(code) do - GenServer.call(__MODULE__, {:lookup, code}) - end - - # --- server callbacks --- - - @impl true - def handle_call({:insert, code, url}, _from, conn) do - result = - with {:ok, stmt} <- - Exqlite.Sqlite3.prepare(conn, "INSERT INTO shirts (code, url) VALUES (?1, ?2)"), - :ok <- Exqlite.Sqlite3.bind(stmt, [code, url]), - :done <- Exqlite.Sqlite3.step(conn, stmt) do - _ = Exqlite.Sqlite3.release(conn, stmt) - :ok - else - {:error, reason} -> {:error, reason} - end - - {:reply, result, conn} - end - - def handle_call({:lookup, code}, _from, conn) do - result = - with {:ok, stmt} <- - Exqlite.Sqlite3.prepare(conn, "SELECT url FROM shirts WHERE code = ?1"), - :ok <- Exqlite.Sqlite3.bind(stmt, [code]), - step_result <- Exqlite.Sqlite3.step(conn, stmt) do - _ = Exqlite.Sqlite3.release(conn, stmt) - - case step_result do - {:row, [url]} -> {:ok, url} - :done -> :not_found - {:error, reason} -> {:error, reason} - end - end - - {:reply, result, conn} - end - - @impl true - def terminate(_reason, conn) do - Logger.info("Closing database connection") - Exqlite.Sqlite3.close(conn) - end -end diff --git a/shirts/lib/shortener/router.ex b/shirts/lib/shortener/router.ex @@ -1,111 +0,0 @@ -defmodule Shortener.Router do - @moduledoc """ - Plug router: - POST /curtail — create a short link (requires Bearer token) - GET /s/:code — redirect to the original URL - """ - - use Plug.Router - - plug Plug.Logger - plug :match - plug Plug.Parsers, parsers: [:json], json_decoder: Jason - plug :dispatch - - # --------------------------------------------------------------------------- - # POST /l/curtail - # Body (JSON): {"url": "https://example.com/very/long/path"} - # Optional: {"url": "...", "code": "my-custom-code"} - # Header: Authorization: Bearer <token> - # --------------------------------------------------------------------------- - post "/curtail" do - with :ok <- check_auth(conn), - {:ok, url} <- fetch_url(conn), - code <- Map.get(conn.body_params, "code") || random_code(), - :ok <- Shortener.DB.insert(code, url) do - base_url = Application.fetch_env!(:shortener, :base_url) - short_url = "#{base_url}/s/#{code}" - - conn - |> put_resp_content_type("application/json") - |> send_resp(201, Jason.encode!(%{short_url: short_url, code: code, url: url})) - else - {:error, :unauthorized} -> - json(conn, 401, %{error: "unauthorized"}) - - {:error, :missing_url} -> - json(conn, 422, %{error: "missing required field: url"}) - - {:error, :invalid_url} -> - json(conn, 422, %{error: "url must be a valid http or https URL"}) - - {:error, _reason} -> - json(conn, 409, %{error: "could not save link"}) - end - end - - # --------------------------------------------------------------------------- - # GET /s/:code — redirect - # --------------------------------------------------------------------------- - get "/s/:code" do - case Shortener.DB.lookup(code) do - {:ok, url} -> - conn - |> put_resp_header("location", url) - |> send_resp(301, "") - - :not_found -> - json(conn, 404, %{error: "not found"}) - - {:error, _} -> - json(conn, 500, %{error: "internal error"}) - end - end - - # --------------------------------------------------------------------------- - # Catch-all - # --------------------------------------------------------------------------- - match _ do - json(conn, 404, %{error: "not found"}) - end - - # --------------------------------------------------------------------------- - # Helpers - # --------------------------------------------------------------------------- - - defp check_auth(conn) do - expected = "Bearer #{Application.fetch_env!(:shortener, :auth_token)}" - - case get_req_header(conn, "authorization") do - [^expected] -> :ok - _ -> {:error, :unauthorized} - end - end - - defp fetch_url(conn) do - case conn.body_params do - %{"url" => url} when is_binary(url) and url != "" -> - uri = URI.parse(url) - - if uri.scheme in ["http", "https"] and is_binary(uri.host) and uri.host != "" do - {:ok, url} - else - {:error, :invalid_url} - end - - _ -> - {:error, :missing_url} - end - end - - defp random_code do - :crypto.strong_rand_bytes(6) - |> Base.url_encode64(padding: false) - end - - defp json(conn, status, body) do - conn - |> put_resp_content_type("application/json") - |> send_resp(status, Jason.encode!(body)) - end -end diff --git a/shirts/mix.exs b/shirts/mix.exs @@ -1,31 +0,0 @@ -defmodule Shortener.MixProject do - use Mix.Project - - def project do - [ - app: :shortener, - version: "0.0.1", - elixir: "~> 1.18", - start_permanent: Mix.env() == :prod, - deps: deps() - ] - end - - def application do - [ - extra_applications: [:logger], - mod: {Shortener.Application, []} - ] - end - - defp deps do - [ - {:credo, "~> 1.7", only: [:dev, :test], runtime: false}, - {:dialyxir, "~> 1.4", only: [:dev, :test], runtime: false}, - {:bandit, "~> 1.10"}, - {:plug, "~> 1.19"}, - {:exqlite, "~> 0.35"}, - {:jason, "~> 1.4"} - ] - end -end diff --git a/shirts/mix.lock b/shirts/mix.lock @@ -1,27 +0,0 @@ -%{ - "bandit": {:hex, :bandit, "1.10.3", "1e5d168fa79ec8de2860d1b4d878d97d4fbbe2fdbe7b0a7d9315a4359d1d4bb9", [:mix], [{:hpax, "~> 1.0", [hex: :hpax, repo: "hexpm", optional: false]}, {:plug, "~> 1.18", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:thousand_island, "~> 1.0", [hex: :thousand_island, repo: "hexpm", optional: false]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "99a52d909c48db65ca598e1962797659e3c0f1d06e825a50c3d75b74a5e2db18"}, - "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, - "burrito": {:hex, :burrito, "1.5.0", "d68ec01df2871f1d5bc603b883a78546c75761ac73c1bec1b7ae2cc74790fcd1", [:mix], [{:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:req, ">= 0.5.0", [hex: :req, repo: "hexpm", optional: false]}, {:typed_struct, "~> 0.2.0 or ~> 0.3.0", [hex: :typed_struct, repo: "hexpm", optional: false]}], "hexpm", "3861abda7bffa733862b48da3e03df0b4cd41abf6fd24b91745f5c16d971e5fa"}, - "cc_precompiler": {:hex, :cc_precompiler, "0.1.11", "8c844d0b9fb98a3edea067f94f616b3f6b29b959b6b3bf25fee94ffe34364768", [:mix], [{:elixir_make, "~> 0.7", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "3427232caf0835f94680e5bcf082408a70b48ad68a5f5c0b02a3bea9f3a075b9"}, - "credo": {:hex, :credo, "1.7.16", "a9f1389d13d19c631cb123c77a813dbf16449a2aebf602f590defa08953309d4", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "d0562af33756b21f248f066a9119e3890722031b6d199f22e3cf95550e4f1579"}, - "db_connection": {:hex, :db_connection, "2.9.0", "a6a97c5c958a2d7091a58a9be40caf41ab496b0701d21e1d1abff3fa27a7f371", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "17d502eacaf61829db98facf6f20808ed33da6ccf495354a41e64fe42f9c509c"}, - "dialyxir": {:hex, :dialyxir, "1.4.7", "dda948fcee52962e4b6c5b4b16b2d8fa7d50d8645bbae8b8685c3f9ecb7f5f4d", [:mix], [{:erlex, ">= 0.2.8", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "b34527202e6eb8cee198efec110996c25c5898f43a4094df157f8d28f27d9efe"}, - "elixir_make": {:hex, :elixir_make, "0.9.0", "6484b3cd8c0cee58f09f05ecaf1a140a8c97670671a6a0e7ab4dc326c3109726", [:mix], [], "hexpm", "db23d4fd8b757462ad02f8aa73431a426fe6671c80b200d9710caf3d1dd0ffdb"}, - "erlex": {:hex, :erlex, "0.2.8", "cd8116f20f3c0afe376d1e8d1f0ae2452337729f68be016ea544a72f767d9c12", [:mix], [], "hexpm", "9d66ff9fedf69e49dc3fd12831e12a8a37b76f8651dd21cd45fcf5561a8a7590"}, - "exqlite": {:hex, :exqlite, "0.35.0", "90741471945db42b66cd8ca3149af317f00c22c769cc6b06e8b0a08c5924aae5", [:make, :mix], [{:cc_precompiler, "~> 0.1", [hex: :cc_precompiler, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.8", [hex: :elixir_make, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "a009e303767a28443e546ac8aab2539429f605e9acdc38bd43f3b13f1568bca9"}, - "file_system": {:hex, :file_system, "1.1.1", "31864f4685b0148f25bd3fbef2b1228457c0c89024ad67f7a81a3ffbc0bbad3a", [:mix], [], "hexpm", "7a15ff97dfe526aeefb090a7a9d3d03aa907e100e262a0f8f7746b78f8f87a5d"}, - "finch": {:hex, :finch, "0.21.0", "b1c3b2d48af02d0c66d2a9ebfb5622be5c5ecd62937cf79a88a7f98d48a8290c", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.6.2 or ~> 1.7", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 1.1", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "87dc6e169794cb2570f75841a19da99cfde834249568f2a5b121b809588a4377"}, - "hpax": {:hex, :hpax, "1.0.3", "ed67ef51ad4df91e75cc6a1494f851850c0bd98ebc0be6e81b026e765ee535aa", [:mix], [], "hexpm", "8eab6e1cfa8d5918c2ce4ba43588e894af35dbd8e91e6e55c817bca5847df34a"}, - "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, - "mime": {:hex, :mime, "2.0.7", "b8d739037be7cd402aee1ba0306edfdef982687ee7e9859bee6198c1e7e2f128", [:mix], [], "hexpm", "6171188e399ee16023ffc5b76ce445eb6d9672e2e241d2df6050f3c771e80ccd"}, - "mint": {:hex, :mint, "1.7.1", "113fdb2b2f3b59e47c7955971854641c61f378549d73e829e1768de90fc1abf1", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1 or ~> 0.2.0 or ~> 1.0", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "fceba0a4d0f24301ddee3024ae116df1c3f4bb7a563a731f45fdfeb9d39a231b"}, - "nimble_options": {:hex, :nimble_options, "1.1.1", "e3a492d54d85fc3fd7c5baf411d9d2852922f66e69476317787a7b2bb000a61b", [:mix], [], "hexpm", "821b2470ca9442c4b6984882fe9bb0389371b8ddec4d45a9504f00a66f650b44"}, - "nimble_pool": {:hex, :nimble_pool, "1.1.0", "bf9c29fbdcba3564a8b800d1eeb5a3c58f36e1e11d7b7fb2e084a643f645f06b", [:mix], [], "hexpm", "af2e4e6b34197db81f7aad230c1118eac993acc0dae6bc83bac0126d4ae0813a"}, - "plug": {:hex, :plug, "1.19.1", "09bac17ae7a001a68ae393658aa23c7e38782be5c5c00c80be82901262c394c0", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "560a0017a8f6d5d30146916862aaf9300b7280063651dd7e532b8be168511e62"}, - "plug_crypto": {:hex, :plug_crypto, "2.1.1", "19bda8184399cb24afa10be734f84a16ea0a2bc65054e23a62bb10f06bc89491", [:mix], [], "hexpm", "6470bce6ffe41c8bd497612ffde1a7e4af67f36a15eea5f921af71cf3e11247c"}, - "req": {:hex, :req, "0.5.17", "0096ddd5b0ed6f576a03dde4b158a0c727215b15d2795e59e0916c6971066ede", [:mix], [{:brotli, "~> 0.3.1", [hex: :brotli, repo: "hexpm", optional: true]}, {:ezstd, "~> 1.0", [hex: :ezstd, repo: "hexpm", optional: true]}, {:finch, "~> 0.17", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mime, "~> 2.0.6 or ~> 2.1", [hex: :mime, repo: "hexpm", optional: false]}, {:nimble_csv, "~> 1.0", [hex: :nimble_csv, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "0b8bc6ffdfebbc07968e59d3ff96d52f2202d0536f10fef4dc11dc02a2a43e39"}, - "telemetry": {:hex, :telemetry, "1.3.0", "fedebbae410d715cf8e7062c96a1ef32ec22e764197f70cda73d82778d61e7a2", [:rebar3], [], "hexpm", "7015fc8919dbe63764f4b4b87a95b7c0996bd539e0d499be6ec9d7f3875b79e6"}, - "thousand_island": {:hex, :thousand_island, "1.4.3", "2158209580f633be38d43ec4e3ce0a01079592b9657afff9080d5d8ca149a3af", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "6e4ce09b0fd761a58594d02814d40f77daff460c48a7354a15ab353bb998ea0b"}, - "typed_struct": {:hex, :typed_struct, "0.3.0", "939789e3c1dca39d7170c87f729127469d1315dcf99fee8e152bb774b17e7ff7", [:mix], [], "hexpm", "c50bd5c3a61fe4e198a8504f939be3d3c85903b382bde4865579bc23111d1b6d"}, - "websock": {:hex, :websock, "0.5.3", "2f69a6ebe810328555b6fe5c831a851f485e303a7c8ce6c5f675abeb20ebdadc", [:mix], [], "hexpm", "6105453d7fac22c712ad66fab1d45abdf049868f253cf719b625151460b8b453"}, -} diff --git a/shuffle/README.md b/shuffle/README.md @@ -0,0 +1,25 @@ +# shuffle + +Picks random albums from a file of paths and adds them to the MPD queue via `mpc`. + +## Usage + +```sh +shuffle <file> [-n <number>] [-d <depth>] +``` + +`<file>` should contain one MPD path per line (e.g. output of `mpc ls`). + +### Options + +| Flag | Default | Description | +|------|---------|-------------| +| `-n`, `--number` | `5` | Number of albums to pick | +| `-d`, `--depth` | `2` | Minimum `/`-depth of path to include | + +### Example + +```sh +mpc ls > albums.txt +shuffle albums.txt -n 3 +``` diff --git a/speediness/README.md b/speediness/README.md @@ -0,0 +1,25 @@ +# speediness + +A network speed test. Downloads 100 MB from OVH and uploads 20 MB to Cloudflare, printing live throughput and a final summary for each. + +## Building + +```sh +zig build +``` + +## Usage + +```sh +speediness +``` + +Output example: + +``` + 100.0 MB 11.23 MB/s 89.8 Mbps +100.0 MB in 8.90s => 11.23 MB/s (89.8 Mbps) + + 20.0 MB 47.81 MB/s 382.5 Mbps +20.0 MB in 0.42s => 47.81 MB/s (382.5 Mbps) +``` diff --git a/tempfox/README.md b/tempfox/README.md @@ -0,0 +1,22 @@ +# tempfox + +A Firefox [`user.js`](https://kb.mozillazine.org/User.js_file) with personal preference overrides. + +## Installation + +Copy `user.js` into your Firefox profile directory: + +```sh +cp user.js ~/.mozilla/firefox/<profile>/user.js +``` + +Preferences are applied on the next Firefox launch. To make a preference permanent, it must remain in `user.js` — Firefox will not revert it automatically, but the GUI can override it until the next restart. + +## What it sets + +- Dark mode +- Disables first-run and update pages +- Disables default browser check +- Disables `about:config` warning +- Disables telemetry and data reporting notices +- Disables the sidebar and vertical tabs diff --git a/wlr-river-title/README.md b/wlr-river-title/README.md @@ -0,0 +1,19 @@ +# wlr-river-title + +Prints the title of the currently focused view in the [River](https://codeberg.org/river/river) Wayland compositor and exits. Useful for feeding into status bars. + +## Building + +```sh +make +# or +cc wlr-river-title.c river-status.c -o wlr-river-title $(pkg-config --cflags --libs wayland-client) +``` + +## Usage + +```sh +wlr-river-title +``` + +Prints one line to stdout and exits. Exits with status 1 if the Wayland display cannot be connected to or the river-status protocol is unavailable.