Button for direct launch from nixos packages site

Sometimes I spend a lot of time with searching / testing apps, and I fell into thought about optimizations of it.

My base flow is:
search on net → search on NixOS Search → copy-paste install command → run app
So I tried to remove / optimize copy-paste and run steps.

I add button “Launch” to every package in results list. On button click selected package starts.

Here is the example.

2026-07-14-19-36-43

I use Tampermonkey browser extension to inject my custom script on webpage.
Script checks if specific elements are appeared on page and inject button (technically, <a> tag).
Buttons have href built with template nixpkgs://<install_name>&<run_name>.
nixpkgs - custom mime type, tell about it later.
Here is the script

// ==UserScript==
// @name         Inject Button to search.nixos.org/packages site
// @namespace    http://tampermonkey.net/
// @version      2026-07-14
// @description  Inject button to package element to launch package with nix-shell
// @author       skilletfun
// @match        https://search.nixos.org/packages*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=nixos.org
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    const proto = "nixpkgs";

    function injectLink(element) {
        const ul = element.querySelector('ul');
        if (!ul) return;

        const lis = ul.querySelectorAll('li');
        if (lis.length < 2) return;

        const installPkg = element.querySelector('span').textContent;
        const runPkg = element.querySelector('code').textContent;

        const newLi = document.createElement('li');
        const newCode = document.createElement('code');
        const link = document.createElement('a');
        link.textContent = 'Launch';
        link.href = `${proto}://${installPkg}&${runPkg}`;

        newCode.appendChild(link);
        newLi.appendChild(newCode);

        ul.insertBefore(newLi, lis[2]);
    }

    const observer = new MutationObserver((mutations) => {
        mutations.forEach((mutation) => {
            mutation.addedNodes.forEach((node) => {
                // If it's an element node
                if (node.nodeType === 1) {
                    if (node.classList && node.classList.contains('package')) {
                        injectLink(node);
                    }
                }

                if (node.querySelectorAll) {
                    node.querySelectorAll('.package').forEach((el) => {
                        injectLink(el);
                    });
                }
            });
        });
    });

    observer.observe(document.body, {
        childList: true,
        subtree: true
    });
})();

Browser cannot directly exec bash scripts by obvious reasons, so script wrapped with .desktop file (generated with home-manager). And I define custom mime type, named nixpkgs, so now any urls started with nixpkgs:// like nixpkgs://foo?bar=baz are handled and passed to my script.

Here is xdg.nix file imported in home-manager config

{ config, pkgs, ... }:

{
  xdg.desktopEntries.pkgQuickLaunch = {
    name = "Nixpkgs";
    comment = "Launch provided package with nix-shell";
    terminal = false;
    exec = "script.sh %u";
    type = "Application";
    mimeType = [ "x-scheme-handler/nixpkgs" ];
    noDisplay = true;
  };

  xdg.mimeApps = {
    enable = true;
    defaultApplications = {
      "x-scheme-handler/nixpkgs" = [ "pkgQuickLaunch.desktop" ];
    };
  };

  xdg.configFile."mimeapps.list".force = true;
}

Here is bash script. It gets data from browser, extract package names, install and run it.

#!/bin/sh
input=$1

fixed="${input/nixpkgs:\/\/}"
installPkg="$(echo "$fixed" | cut -d'&' -f1)"
runPkg="$(echo "$fixed" | cut -d'&' -f2)"

kitty nix-shell -p "$installPkg" --run "$runPkg"

I make it more for fun / learning, not as kinda serious project.
Hope it will be interesting for someone :slight_smile:

9 Likes

I don’t have this particular need, but I always love seeing people doing creative things with browser scripts. Own your web browsing experience!

I should warn you and anyone else considering doing this exact thing that you are making your computer vulnerable to targeted attacks. It is very easy for someone to craft a JS snippet that creates a nixpkgs://bash&arbitrary_code_injection_here link and clicks it for you. As soon as you visit a page with such a JS snippet, your box is owned.

At the very least, use a slightly more obscure URL scheme (something like randomcharacters.nixpkgs:// would work just as well) and don’t publish it on the internet!

Better would be to gate the kitty nix-shell command with zenity --question --text "Do you want to execute '$runPkg' from '$installPkg'?" &&, though that does introduce one more click into your workflow.

10 Likes

I definitely need to do more with them.
You can also install userscripts declaratively with Nix (only with Firefox-based browser that support xpinstall.signatures.required=false) via my Violentmonkey Declarative fork (part of my bigger Firefox Extensions Declarative project, and packaged with Nix via a Flake).

Or you could instead put an in-terminal prompt with gum confirm so you just need to press y in the terminal it spawns.

I want to agree with @rhendric that this is both cool and fun. An alternative workflow that doesn’t involve the brower at all is using comma and if you combine with with nix-index-database then you have quite a comfortable way to insta launch anything in nixpkgs from the comfort of your terminal.

2 Likes

That’s really slick!

As a hilarious follow-on to that: try something similar for services; e.g., you click a service, it gives a little modal with all the options and lets you configure it, and then launches it in a VM on your machine.

2 Likes

Why launch a full VM when you can launch a MicroVM? :grin:

Seriously, this custom URL scheme approach for letting userscripts interact with the host is REALLY cool.

1 Like

Microvms are neat indeed!

And yeah, it’s neat that nixos makes it semi-trivial to treat entire services/programs/configs as documents that can be opened locally.

Walking the garden path there, you can extend that to a sort of odd kirby-like thin-client:

You suck down a config, it includes disko or similar for mounting a remote object store or NFS or something, provisions that development or working environment, and then you’re all good to go to work on that project or company business or whatever, and then you throw it away afterwards. Smear some PKCS or whatever on it and you can verify that you’re supposed to be cleared to work with the thing.

There’s probably something there for somebody with the cycles (or tokens) to FAFO.

1 Like