Skip to content
darkmatterPublic

About

Fancy devshells for your Nix Flakes

Resources

Stars

1 star

Watchers

0 watching

Forks

Repository files navigation

prelude
Make your devshell easy to use and nice to look at

Greets nix develop with a MOTD, command picker, docs viewer, and themed prompt. Use it as a flake-parts module or, in any other flake (blueprint, a plain outputs function), through prelude.lib.evalModule.

Prelude keeps docs next to where you run the project. docs in the devshell explains this repo; nix run github:darkmatter/prelude#docs explains whatever checkout you are in, with its own Prelude theme and pages when it has them; nix run github:org/repo#prelude-docs explains a prelude-enabled dependency, and nix run github:org/repo#prelude-menu opens its command menu. The only command to remember is nix develop.


22-motd

Quickstart (Setup Wizard)

The wizard writes prelude.nix, a sibling title.txt, and a project-root .envrc (use flake):

nix run github:darkmatter/prelude -- wizard

# or:
nix run github:darkmatter/prelude -- wizard -o nix/prelude.nix

docs/recording.gif

The wizard never overwrites an existing flake.nix; wire the sidecar in as Install shows.

The generated file lists every option as a commented default. Put clone-to-running steps on the MOTD; put the rest in the command catalogue (x) and Markdown docs.

Command picker

menu

x                 # open the interactive picker
x dev             # run a command by catalogue key
x db migrate      # …a subcommand ("db migrate" or "db/migrate" in Nix)
x d               # …or by its single-key accelerator
x --list          # print the command table

Adapt existing packages so the menu does not drift:

prelude.commands.dev = prelude.lib.fromPkg packages.dev {
  description = "start the development server";
  motd = 1;
};

Docs

docs

prelude.docs.pages = [
  { text = ./README.md; }
  { text = ./docs/getting-started.md; }
];

Each Markdown file is one page. Digits jump, Tab steps, j/k scroll, q quits.

Workspace

Opt into a managed Bash with a movable docs/menu pane. prelude.workspace.enable defaults to false; enable it in your prelude.nix sidecar with the required surfaces:

{
  prelude = {
    workspace.enable = true;
    menu.enable = true;
    motd.enable = true;
    docs.pages = [ { text = ./README.md; } ];
  };
}

Both flakeModules.default and lib.evalModule export packages.prelude-workspace and bundle the active launcher in prelude-shell when enabled. The Menu/MOTD/Docs prerequisites are asserted; prelude.prompt.enable is not required. Keep .envrc as use flake: cd + direnv provides the lightweight environment, MOTD, and theming for an already-initialized Starship prompt. Run interactive nix develop for the full footer and catalogue completion; generated project init enters the enabled workspace in the foreground in Bash with a TTY. No extra Prelude Bash rc hook is required. Automatic Starship follows prelude.prompt.enable; manual launches remain available:

x prelude:workspace             # manual launch with the fixed prelude $ prompt
prelude-workspace --starship    # manual launch with Starship, even if prelude.prompt.enable = false

Both use the current checkout's menu, with your own Docs, MOTD, theme, and completion config. The published package honors prelude.root (lib.evalModule users set it to self); PRELUDE_ROOT still overrides it.

Each init is stamped before launch, so exit or failure does not immediately reopen the workspace. Leaving and reentering the devshell, or a changed init, permits automatic entry again. Noninteractive, envrc, lorri, zsh, and non-TTY contexts never auto-launch it; PRELUDE_WORKSPACE_ACTIVE prevents child recursion. Workspace mode skips legacy BLE initialization. The public prelude hook bash remains optional for already-configured interactive handoffs after direnv, not an onboarding requirement.

Disabled consumers avoid workspace native dependencies. This repo dogfoods it enabled, so its default shell includes the launcher and native runtime, but not headers or renderer tooling. See the workspace guide for activation details, published launches, controls, and runtime limitations.

TypeScript

Bun apps get the same picker, MOTD, and docs viewer from TypeScript. Commands are functions declared next to the code they run, with typed arguments:

export const preludeCommand = Command.make({
  description: "run the dev server",
  args: [{ token: "--port", type: "number", default: 3000 }],
  run: (args) => serve(args.port), // args.port: number
});

await Prelude.make({ project: "acme", commands: { dev: preludeCommand } }).main();

Install with bun add @drkmttr/prelude. Walkthrough: examples/typescript/. API: ts/.

Install

Add the input, then put prelude-shell in your devshell. It bundles every enabled component and activates through its setup-hook. Direnv's use flake loads the environment, shows the MOTD, and themes an existing Starship prompt; interactive nix develop provides the full devshell. Loader-only MOTD includes “Run nix develop for the full devshell”; that hint is hidden in workspace or quiet mode. Both ways below read the same prelude.nix and build the same packages.

inputs.prelude.url = "github:darkmatter/prelude";

With flake-parts, import the module and the sidecar:

outputs = { prelude, flake-parts, ... }@inputs:
  flake-parts.lib.mkFlake { inherit inputs; } {
    imports = [ prelude.flakeModules.default ./prelude.nix ];
    systems = [ "x86_64-linux" "aarch64-darwin" ];

    perSystem = { pkgs, config, ... }: {
      devShells.default = pkgs.mkShell {
        packages = [ config.packages.prelude-shell ];
      };
    };
  };

Without flake-parts (blueprint, a plain outputs function), evaluate the sidecar with prelude.lib.evalModule pkgs:

# blueprint: devshell.nix
{ pkgs, inputs, ... }:
let
  prelude = inputs.prelude.lib.evalModule pkgs ./prelude.nix;
in
pkgs.mkShell {
  packages = [ prelude.packages.prelude-shell ];
}

The module receives pkgs, so package-backed commands go straight into prelude.commands; pass anything else it takes (self, inputs) through _module.args. To publish prelude, prelude-menu, or an enabled prelude-workspace bound to your repository, as flakeModules.default does, set prelude.root = self;. A complete plain flake: examples/without-flake-parts/.

A custom shellHook activates with eval "$(prelude-preflight)".

Reading docs without the devshell

nix run github:darkmatter/prelude#docs shows the docs of the repository you are in. It opens the flake's own prelude-docs package when there is one, so the project's theme, pages and hero apply; any other repository gets Prelude's defaults over its README.md and docs/. The same package serves nix run github:org/repo#prelude-docs.

With flake-parts, packages.prelude-docs exists as soon as prelude.docs.pages lists a page. Without it, expose the packages evalModule returns; legacyPackages keeps them out of nix flake check:

legacyPackages = forAllSystems (pkgs: (prelude.lib.evalModule pkgs ./prelude.nix).packages);

In blueprint, a package file does it: packages/prelude-docs.nix, with prelude.nix one directory up.

{ pkgs, inputs, ... }:
(inputs.prelude.lib.evalModule pkgs ../prelude.nix).packages.prelude-docs

Full consumer walkthrough: Your own repo. Command keys and grouping: command conventions. Options: reference.

Themes

prelude.theme selects a palette: prelude, phosphor, minted, amber, solarized, nord, gruvbox, paper (light), mono, apathy. Override tokens with prelude.palette. Preview every theme with nix run .#example-themes.

Contributing

Questions and PRs are welcome via GitHub issues.

nix develop
x go:test
x check

User-visible docs changes: x sync-docs (and x record-docs if media is stale).

Render the README tour with agg docs/recording.cast docs/recording.gif. Preserve the cast's term.theme when replacing the recording so its terminal colors match Prelude instead of the renderer's fallback palette.

License

MIT © 2026 Darkmatter

About

Fancy devshells for your Nix Flakes

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages