{
  inputs = {
    nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
    rust-overlay.url = "github:oxalica/rust-overlay";
    flake-utils.url = "github:numtide/flake-utils";
    servers.url = "github:fulsomenko/servers";
  };

  outputs = {
    self,
    nixpkgs,
    rust-overlay,
    flake-utils,
    servers,
    ...
  }:
    flake-utils.lib.eachDefaultSystem (
      system: let
        overlays = [(import rust-overlay)];
        pkgs = import nixpkgs {
          inherit system overlays;
        };

        rustToolchain = pkgs.rust-bin.stable.latest.default.override {
          extensions = ["rust-src" "rust-analyzer" "clippy" "rustfmt"];
        };

        changeset = pkgs.writeShellApplication {
          name = "changeset";
          runtimeInputs = with pkgs; [coreutils];
          text = builtins.readFile ./scripts/create-changeset.sh;
        };

        bumpVersion = pkgs.writeShellApplication {
          name = "bump-version";
          runtimeInputs = with pkgs; [rustToolchain cargo coreutils gnugrep gnused git findutils];
          text = builtins.readFile ./scripts/bump-version.sh;
        };

        publishCrates = pkgs.writeShellApplication {
          name = "publish-crates";
          runtimeInputs = [rustToolchain pkgs.cargo pkgs.coreutils pkgs.curl pkgs.gnugrep validateRelease listCrates];
          text = builtins.readFile ./scripts/publish-crates.sh;
        };

        aggregateChangelog = pkgs.writeShellApplication {
          name = "aggregate-changelog";
          runtimeInputs = with pkgs; [coreutils gnugrep gnused git findutils];
          text = builtins.readFile ./scripts/aggregate-changelog.sh;
        };

        listCrates = pkgs.writeShellApplication {
          name = "list-crates";
          runtimeInputs = with pkgs; [rustToolchain cargo coreutils jq gnused];
          text = builtins.readFile ./scripts/list-crates.sh;
        };

        validateRelease = pkgs.writeShellApplication {
          name = "validate-release";
          runtimeInputs = with pkgs; [rustToolchain cargo coreutils gnugrep gnused listCrates];
          text = builtins.readFile ./scripts/validate-release.sh;
        };

        checkCrateListSync = pkgs.writeShellApplication {
          name = "check-crate-list-sync";
          runtimeInputs = with pkgs; [coreutils gnugrep findutils diffutils listCrates];
          text = builtins.readFile ./scripts/check-crate-list-sync.sh;
        };

        kanban = pkgs.callPackage ./default.nix { src = self; gitRev = self.rev or null; };
      in {
        devShells.default = import ./shell.nix {
          inherit pkgs rustToolchain;
          inherit changeset aggregateChangelog bumpVersion publishCrates validateRelease listCrates checkCrateListSync;
        };

        devShells.demo = import ./demo/shell.nix { inherit pkgs kanban; };

        packages = let
          kanban-cli = pkgs.callPackage ./default.nix { src = self; gitRev = self.rev or null; withTui = false; };
        in {
          default = kanban;
          kanban-cli = kanban-cli;
          kanban-mcp = pkgs.callPackage ./crates/kanban-mcp/default.nix { src = self; };
          kanban-web = pkgs.callPackage ./web/default.nix {};
          mcp-server-git = servers.packages.${system}.mcp-server-git;
          aggregate-changelog = aggregateChangelog;
          bump-version = bumpVersion;
          publish-crates = publishCrates;
          validate-release = validateRelease;
          list-crates = listCrates;
          check-crate-list-sync = checkCrateListSync;
          changeset = changeset;
        };
      }
    );
}
