A monorepo of personal Nix flakes. Each packaged CLI lives in its own
pkgs/<name>/ directory; the root flake.nix auto-discovers them, so adding a
new package is "drop a directory" — no edits to the root flake (unless the tool
needs an extra pinned input).
| Package | Tool | Directory |
|---|---|---|
aspire-cli (+ aspire-cli_staging/aspire-cli_dev) |
Aspire CLI | pkgs/aspire-cli |
cve-lite-cli |
OWASP CVE Lite CLI | pkgs/cve-lite-cli |
playwright-cli |
Playwright CLI | pkgs/playwright-cli |
sentry-cli |
Sentry CLI | pkgs/sentry-cli |
There is no default package or app — a multi-tool repo has no single
obvious default, so name the output explicitly (#aspire-cli, #sentry-cli, …).
The CLIs have no dedicated run-app: nix run .#<name> resolves the package and
runs its meta.mainProgram. aspire's non-stable channels are separate packages
(aspire-cli_staging, aspire-cli_dev); aspire-cli itself is stable.
# Run a CLI without cloning:
nix run github:kennethhoff/flakes#aspire-cli # stable; or #aspire-cli_staging / _dev
nix run github:kennethhoff/flakes#playwright-cli
nix run github:kennethhoff/flakes#sentry-cli
# Build a package:
nix build github:kennethhoff/flakes#sentry-cli
# Dev shell with every CLI on PATH:
nix develop github:kennethhoff/flakesConsume in your own flake:
{
inputs.flakes.url = "github:kennethhoff/flakes";
outputs = { self, nixpkgs, flakes, ... }: let
system = "x86_64-linux";
pkgs = nixpkgs.legacyPackages.${system};
in {
devShells.${system}.default = pkgs.mkShell {
packages = [ flakes.packages.${system}.sentry-cli ];
};
};
}Per-package usage, version-override, and platform notes live in each tool's README (linked in the table above).
-
Create
pkgs/<name>/(named after the package) and add the packaging files (package.nix,versions.nix,update.sh,README.md, optionaltests/). -
Add
pkgs/<name>/default.nixreturning the tool-module contract — every key is optional:{ inputs, system, self, lib }: let pkgs = inputs.nixpkgs.legacyPackages.${system}; # or import with overlays/config # drv = pkgs.callPackage ./package.nix { ... }; # must set meta.mainProgram in { packages = { <name> = drv; }; # globally-unique name # No run-app needed: `nix run .#<name>` resolves the package and runs its # meta.mainProgram. Declare an updater spec and the root builds the # `update-<name>` app for you (adds git + a cd into this dir): update = { runtimeInputs = [ /* curl, jq, nix, … */ ]; script = ./update.sh; }; checks = import ./tests { inherit pkgs self; }; # may be {} devShellPackages = [ drv ]; # added to the shared dev shell }
The directory name is the canonical name:
pkgs/<name>/gives package<name>, theupdate-<name>app, checks<name>-*, and the Conventional Commit scope the workflow uses — so name the dir after the package (aspire-cli,sentry-cli, …). Channels/variants are separate flat packages (aspire-cli_staging), each runnable via its own mainProgram. Avoid merging derivations (a // { b = …; }) into one package — the sibling derivations leak onto consumers' devShell PATH.
That's it. The root flake folds the new tool into packages/apps/checks/
devShells, and the weekly update workflow auto-discovers it via its
update-<name> app. The only reason to touch the root flake.nix is when a
tool needs an extra flake input (Nix requires inputs at the root) — see the
nixpkgs-openssl pin that pkgs/aspire-cli consumes.
- Dir name = package name — name
pkgs/<name>/after its package. That name is the output name, theupdate-<name>app, the<name>-*check prefix, and the Conventional Commit scope the workflow uses. meta.mainProgram— set it on each package sonix run .#<name>works without a dedicated run-app.updatespec — declare{ runtimeInputs; script; }and the root builds theupdate-<dir>app: it addsgit, cd's into the tool's dir (resolved from the repo root), then runs the script — sonix run .#update-<dir>works from anywhere.update.shjust writesversions.nixto the current directory.- Namespaced checks —
tests/default.nixreturns bare keys; the root prefixes them with<name>-.
Each tool ships an update-<name> app that bumps its versions.nix. Run it
from anywhere in the repo:
nix run .#update-sentry-cliThe .github/workflows/update.yml matrix runs every tool's updater weekly,
opening one auto-merging PR per tool when a new upstream version lands.
- One
flake.lock.nix flake updatebumpsnixpkgsfor every tool at once; a tool's nixpkgs can't be pinned independently. Fine for personal use. nixpkgs-opensslpin. Temporary OpenSSL 3.6.1 pin for the Aspire CLI; carries aTODO(~2026-07)to revisit — seepkgs/aspire-cli/default.nix.- Old per-tool repos stay live. This migration is additive; existing
consumers pinned to
github:kennethhoff/<tool>-cli-flakekeep working.