Navigation
Copy page

The programs.kli Module

On this page

kli adds an extension one of two ways. Imperatively, kli install <url> <pin> fetches and verifies one at runtime, per user, with no rebuild. Declaratively, programs.kli compiles a kli image with your extensions and settings baked in, so they are present at every launch. This page is the declarative surface: a Home Manager module and a NixOS module, both from the kli flake. Trust comes from your flake inputs and Nix hashing — there is no per-URL pin and nothing installs at runtime.

Enable the module

Add the kli flake as an input:

nix
{
  inputs.kli.url = "github:kleisli-io/kli";
}

Import the module for your system and turn it on. Home Manager:

nix
{
  imports = [ inputs.kli.homeManagerModules.default ];

  programs.kli.enable = true;
}

NixOS:

nix
{
  imports = [ inputs.kli.nixosModules.default ];

  programs.kli.enable = true;
}

With enable alone the module installs a plain kli image with no extensions baked in. The Home Manager module installs it into home.packages; the NixOS module into environment.systemPackages.

Bake in an extension

List a flake package in extensions and it is compiled into the image. cairn, for instance, is a flake input whose package you name; it needs SQLite on the library path for its FTS5-backed store:

nix
programs.kli = {
  enable = true;
  extensions = [ inputs.cairn.packages.${system}.default ];
  blessedNativeLibs = [ pkgs.sqlite ];
  settings.defaultModel = "claude-opus-4-8";
};

Rebuild and the extension is part of the image. It loads at every launch with no runtime install and no pin, and stays put until you edit the configuration and rebuild.

Options

Option Type Default Effect
enable boolean false Install a kli image built from these options.
extensions list of packages [] Extensions compiled into the image. Each is a Nix package following the extension contract.
settings attribute set {} settings.json keys baked as the lowest settings layer.
registries list [] Registry entries baked into the image's configuration.
blessedNativeLibs list of packages [] Native libraries placed on the image's library search path. See blessedNativeLibs.
dataDir null or string null Project-local data directory. null resolves it from the runtime environment.
sandbox null or submodule null Whole-process bwrap confinement. null leaves the binary unwrapped. See sandbox.
package package image built from the options above The package to install. Set it to install an image of your own. See package.

extensions

An extension you list here is a Nix package, not a URL. kli composes it into the image as a build dependency, so it must be a buildLisp library — the builder kli itself is built with — carrying two passthru fields:

  • passthru.name — the extension id.
  • passthru.manifestSymbol — the package-qualified symbol naming the extension's manifest. The build fails if the compiled image does not export it, so a mislabelled package cannot ship silently.

The list must not include the core kli library; the module asserts against it. As a consumer you take the author's flake as an input and add its package:

nix
programs.kli.extensions = [ inputs.some-extension.packages.${system}.default ];

Producing such a package — the buildLisp library, where buildLisp comes from, and the passthru contract — is the author side, covered in Distribute as a Nix package.

settings

settings bakes the same settings.json keys the on-disk files use, as the lowest-precedence layer. The merge order is baked < global < project, with the active profile overlay applied last, so a user's ~/.config/kli/settings.json always overrides what you bake. Use it to ship image defaults — a default model, theme, or capability set — that a user can still override without editing your configuration. The keys and their merge rules are in settings.json.

blessedNativeLibs

Native shared libraries added to the image's library search path. An extension that binds a C library names it here so the symbol resolves at load: cairn needs pkgs.sqlite for its FTS5 store. Extensions with no native dependency leave this empty.

sandbox

Whole-process confinement via bwrap — confinement the binary cannot switch off. null leaves the binary unwrapped. An attribute set wraps the entrypoint so the working directory is the writable root and the rest of the filesystem is read-only, with the process and IPC namespaces unshared.

Field Type Default Effect
network boolean true Keep network access. false unshares the network namespace, which also cuts the model API — usable only with a local model.
writablePaths list of strings [] Extra paths bound writable, beyond the working directory.
denyRead list of strings [] Paths hidden from the confined process: a file reads as empty, a directory as empty. The at-rest mask for secrets such as credential files.
denyEnv list of strings [] Environment variable names unset before the process starts. The in-environment twin of denyRead.
nestedUserns boolean false Set when the image runs inside an outer unprivileged user namespace whose /proc is masked, such as a systemd-nspawn or OCI container.

The sandbox model, and how it relates to the capability layer, is in Security model and sandboxing.

package

The module builds package from the options above by default. Set it to install an image you built yourself — with the flake's lib.<system>.mkConfiguredKli (below) or any override — and the other build options no longer describe what gets installed.

Without a module

Outside a Home Manager or NixOS configuration, the flake exposes lib.<system>.mkConfiguredKli. It takes the same options and returns the image derivation, for a nix build target or a devShell:

nix
inputs.kli.lib.${system}.mkConfiguredKli {
  extensions = [ inputs.cairn.packages.${system}.default ];
  blessedNativeLibs = [ pkgs.sqlite ];
}

The module's package default is exactly this call over your options.

Declarative or imperative

The two channels coexist, and you can use both in one setup:

  • Declarative — this module. Extensions are compiled into the image at build time, reproducible, trusted through your flake inputs and Nix hashing, present at every launch. Preferred for Nix users.
  • Imperativekli install <url> <pin> at runtime, per user, verified by a git-object pin and an opt-in signature, no rebuild. See Sharing extensions.

Bake the extensions you always want into the image, and install the occasional one at runtime.