kat automatically invokes manifest generators like helm or kustomize, and provides a persistent, navigable view of rendered resources, with support for live reloading, integrated validation, and more.
It is made of two main components, which can be used together or independently:
- A rule-based engine for automatically rendering and validating manifests
 - A terminal UI for browsing and debugging rendered Kubernetes manifests
 
Together, these deliver a seamless development experience that maintains context and focus while iterating on Helm charts, Kustomize overlays, and other manifest generators.
β€οΈ Made with bubble tea, chroma, and other amazing libraries.
ποΈ Manifest browsing
- Navigate hundreds of resources with fuzzy search and filtering
 - View individual resources in your terminal with syntax highlighting
 
β‘οΈ Live reload
- Monitor source files with 
--watchfor automatic re-rendering - Maintain your current context between reloads so you don't lose your place
 - Highlight changes with diff visualization between renders
 
π Error handling
- Surface rendering and validation errors as overlays
 - Works with reload; fix source files and watch errors disappear instantly
 
π§ͺ Tool integration
- Define profiles for any manifest generator (Helm, Kustomize, CUE, KCL, Jsonnet, etc.)
 - Run tools like 
kubeconformorkyvernoautomatically on rendered manifests - Chain multiple tools together with pre and post-render hooks
 
π― Project detection
- Select your defined profiles automatically using CEL expressions
 - Match projects based on file contents, structure, or naming patterns
 
π Plugin system
- Add custom keybind-triggered commands for your specific workflows
 - Execute dry-runs, deployments, or any custom tooling without leaving kat
 
π€ MCP server (Experimental)
- Limit access to irrelevant context, improving performance and reducing cost
 - Force your AI to always follow the same rendering and validation pipeline
 - Enable iterative testing without handing over cluster or command-line access
 
π¨ Fully customizable
- Choose from any available Chroma themes, or define your own
 - Remap any keybinding to match your preferences
 
Homebrew
You can install kat using my tap.
With brew:
brew install macropower/tap/kat --caskWith your Brewfile:
tap "macropower/tap"
cask "kat"Go
go install github.com/macropower/kat/cmd/kat@latestDocker
Docker images are published to ghcr.io/macropower.
All images are configured with WORKDIR=/data, so you can mount your current directory there to run kat against your local files.
Run the latest alpine image:
docker run -it -v .:/data -e TERM=$TERM ghcr.io/macropower/kat:latest-alpineThe default config is located at /config/kat/config.yaml, and you can override it by mounting your own configuration file at that path.
There is also a scratch image that contains only the kat binary, which is useful when you want to build your own image (which I generally recommend doing):
FROM alpine:latest
COPY --from=ghcr.io/macropower/kat:latest /kat /usr/local/bin/kat
# Add whatever customization you need here.
ENTRYPOINT ["/usr/local/bin/kat"]Nix (NUR)
You can install kat using my NUR.
With nix-env:
nix-env -iA kat -f https://github.com/macropower/nur-packages/archive/main.tar.gzWith nix-shell:
nix-shell -A kat https://github.com/macropower/nur-packages/archive/main.tar.gzWith your flake.nix:
{
  inputs = {
    macropower.url = "github:macropower/nur-packages";
  };
  # Reference the package as `inputs.macropower.packages.<system>.kat`
}With devbox:
devbox add github:macropower/nur-packages#katGitHub CLI
gh release download -R macropower/kat -p "kat_$(uname -s)_$(uname -m).tar.gz" -O - | tar -xzAnd then move kat to a directory in your PATH.
Curl
curl -s https://api.github.com/repos/macropower/kat/releases/latest | \
  jq -r ".assets[] |
    select(.name | test(\"kat_$(uname -s)_$(uname -m).tar.gz\")) |
    .browser_download_url" | \
  xargs curl -L | tar -xzAnd then move kat to a directory in your PATH.
Or, download a binary from releases.
Note: The default
katconfiguration contains references tohelm,kustomize, andyq. If you want to usekatwith these tools, you will need to install them separately.
You can verify the authenticity and integrity of kat releases.
See verification for more details.
Show help:
kat --helpRender a project in the current directory:
katRender a project and enable watch (live reloading):
kat -wRender a project in a specific directory:
kat ./example/helmRender a project in a specific directory using the ks profile:
kat ./example/kustomize ksRender a project with custom profile arguments:
kat ./example/helm -- -g -f prod-values.yaml
kat ./example/kustomize ks -- --enable-helmRender a project with command passthrough:
kat ./example/helm task -- helm:renderRead from stdin (disables rendering engine):
cat ./example/kustomize/resources.yaml | kat -Send output to a file (disables TUI):
kat ./example/helm > manifest.yamlYou can optionally start kat with an MCP server by using the --serve-mcp flag:
kat --serve-mcp :50165See MCP Server for more details.
When you first run kat, it will attempt to add default configuration files to $XDG_CONFIG_HOME/kat/ (or ~/.config/kat/). This configuration allows you to customize the behavior of kat, such as the UI style, keybindings, rules for project detection, and profiles for rendering different types of projects.
Note that JSON schemas are also included in the configuration directory, which can be used by your editor's YAML language server.
Some of the default behavior around loading configuration can be overridden with command line flags or environment variables. See
kat --helpfor details.
Over time, the default configuration may change, and the schema is currently still evolving. If you want to reset your configuration to the latest defaults, you can use kat --write-config, which will move your existing configuration to a backup file and generate a new default configuration.
You can find the default configuration file as well as JSON schemas in pkg/config.
You can customize how kat detects and renders different types of projects using rules and profiles in the configuration file. This system uses CEL (Common Expression Language) expressions to provide flexible file matching and processing.
Rules determine which profile should be used. Each rule contains:
match(required): A CEL expression that returnstrueif the rule should be appliedprofile(required): The name of the profile to use when this rule matches
Rules use boolean CEL expressions with access to:
files(list): All file paths in the directorydir(string): The directory path being processed
rules:
  - # Select the Helm profile if any Helm chart files exist
    match: >-
      files.exists(f, pathBase(f) in ["Chart.yaml", "Chart.yml"])
    profile: helm
  - # Select the Kustomize profile if any Kustomization files exist
    match: >-
      files.exists(f, pathBase(f) in ["kustomization.yaml", "kustomization.yml"])
    profile: ks
  - # Fallback: select the YAML profile if any YAML files exist
    match: >-
      files.exists(f, pathExt(f) in [".yaml", ".yml"])
    profile: yamlProfiles define how to render projects. They can be automatically selected by rules, or manually specified when kat is invoked. Each profile contains:
command(required): The command to executeargs: Arguments to pass to the commandextraArgs: Arguments that can be overridden from the CLIenv: List of environment variables for the commandenvFrom: List of sources for environment variablessource: Define which files to watch for changes (when watch is enabled)reload: Define conditions for when events should trigger a reloadui: UI configuration overrideshooks: Initialization and rendering hooksinithooks are executed once whenkatis initializedpreRenderhooks are executed before the profile's command is runpostRenderhooks are executed after the profile's command has run, and are provided the rendered output via stdin
plugins: Custom commands that can be executed on-demand with keybindsdescription(required): Human-readable description of what the plugin doeskeys(required): Array of key bindings that trigger the plugincommand(required): The command to executeargs: Arguments to pass to the command
profiles:
  helm:
    command: helm
    args: [template, .]
    extraArgs: [-g]
    source: >-
      files.filter(f, pathExt(f) in [".yaml", ".yml", ".tpl"])
    reload: >-
      fs.event.has(fs.WRITE, fs.CREATE, fs.REMOVE)
    envFrom:
      - callerRef:
          pattern: "^HELM_.+"
    ui:
      theme: dracula
    hooks:
      init:
        - command: helm
          args: [version, --short]
      preRender:
        - command: helm
          args: [dependency, build]
          envFrom:
            - callerRef:
                pattern: "^HELM_.+"
      postRender:
        # Pass the rendered manifest via stdin to `kubeconform`.
        - command: kubeconform
          args: [-strict, -summary]
    plugins:
      dry-run:
        command: helm
        args: [install, ., -g, --dry-run]
        envFrom:
          - callerRef:
              pattern: "^HELM_.+"
        description: invoke helm dry-run
        keys:
          - code: ctrl+r
            alias: βr
  ks:
    command: kustomize
    args: [build, .]
    source: >-
      files.filter(f, pathExt(f) in [".yaml", ".yml"])
    reload: >-
      fs.event.has(fs.WRITE, fs.CREATE, fs.REMOVE)
    env:
      - name: KUSTOMIZE_ENABLE_ALPHA_COMMANDS
        value: "true"
    ui:
      compact: true
      theme: tokyonight-storm
    hooks:
      init:
        - command: kustomize
          args: [version]kat provides custom CEL functions for use in rules and profiles:
Path Functions:
pathBase(string): Returns the filename (e.g.,"Chart.yaml")pathExt(string): Returns the file extension (e.g.,".yaml")pathDir(string): Returns the directory path
YAML Functions:
yamlPath(file, path): Reads a YAML file and extracts a value using a JSONPath expression
Event Functions:
has(event, flag...): Checks if a file system event contains specific flags
File System Constants:
fs.CREATE,fs.WRITE,fs.REMOVE,fs.RENAME,fs.CHMOD: File system event types
Render Status Constants:
render.STAGE_NONE,render.STAGE_PRE_RENDER,render.STAGE_RENDER,render.STAGE_POST_RENDER: Render stagesrender.RESULT_NONE,render.RESULT_OK,render.RESULT_ERROR,render.RESULT_CANCEL: Render results
You can combine these with CEL's built-in functions like exists(), filter(), in, contains(), matches(), and logical operators.
Example:
rules:
  - match: >-
      files.exists(f,
        pathBase(f) == "Chart.yaml" &&
        yamlPath(f, "$.apiVersion") == "v2")
    profile: helm
profiles:
  helm:
    command: helm
    args: [template, .]
    extraArgs: [-g]
    source: >-
      files.filter(f,
        pathExt(f) in [".yaml", ".yml", ".tpl"])
    reload: >-
      fs.event.has(fs.WRITE, fs.RENAME) &&
      render.result != render.RESULT_CANCELFor more details on CEL expressions and examples, see the CEL documentation.
The kat configuration supports YAML anchor nodes, alias nodes, and merge keys. You can define common settings once and reuse them across the configuration.
profiles:
  ks: &ks
    command: kustomize
    args: [build, .]
    source: >-
      files.filter(f, pathExt(f) in [".yaml", ".yml"])
    hooks:
      postRender:
        - &kubeconform
          command: kubeconform
          args: [-strict, -summary]
  ks-helm:
    <<: *ks
    args: [build, ., --enable-helm]
  helm:
    command: helm
    args: [template, .]
    extraArgs: [-g]
    source: >-
      files.filter(f, pathExt(f) in [".yaml", ".yml", ".tpl"])
    envFrom:
      - callerRef:
          pattern: "^HELM_.+"
    hooks:
      postRender:
        - *kubeconformβ€οΈ Thanks to goccy/go-yaml.
Default config - By default, kat includes a configuration that supports helm, kustomize, and generic YAML files. This is a great starting point for writing your own custom config:
- See 
pkg/config/config.yamlfor the default configuration. 
Support for custom tools - You can add support for other languages/tools like kcl, jsonnet, flux-local, cue, and so on:
rules:
  - match: >-
      files.exists(f, pathExt(f) == ".k")
    profile: kcl
profiles:
  kcl:
    command: kcl
    args: [run, .]
    source: >-
      files.filter(f, pathExt(f) == ".k")
    envFrom:
      - callerRef:
          pattern: "^KCL_.+"Content-based detection - Match based on file content, not just names:
rules:
  - # Match Helm v3 specifically
    match: >-
      files.exists(f,
        pathBase(f) == "Chart.yaml" &&
        yamlPath(f, "$.apiVersion") == "v2")
    profile: helm-v3
  - # Match Kubernetes resources with specific API versions
    match: >-
      files.exists(f,
        pathExt(f) in [".yaml", ".yml"] &&
        yamlPath(f, "$.apiVersion") in ["apps/v1", "v1"])
    profile: yamlReload filtering - Control when file changes trigger reloads using CEL expressions:
profiles:
  helm:
    command: helm
    args: [template, .]
    source: >-
      files.filter(f, pathExt(f) in [".yaml", ".yml", ".tpl"])
    reload: >-
      fs.event.has(fs.WRITE, fs.RENAME) &&
      render.result != render.RESULT_CANCELUsing Task - If you use task, you can use your tasks in the kat config:
rules:
  - match: >-
      files.exists(f, pathBase(f) in ["Taskfile.yml", "Taskfile.yaml"])
    profile: task
profiles:
  task:
    command: task
    args: [render]
    source: >-
      files.filter(f, pathExt(f) in [".yaml", ".yml"])
    hooks:
      postRender:
        - command: task
          args: [validate]Note that you should write your
taskto:
- Output the rendered manifest to stdout, and anything else to stderr.
 - Tolerate being called from any directory in the project.
 
- E.g., instead of
 ./folder, use{{joinPath .ROOT_DIR "folder"}}.- Not require any additional arguments to run.
 
- You can reference
 {{.USER_WORKING_DIR}}to obtain the path that the user invokedkatfrom/with.- E.g.,
 vars: { PATH: "{{.PATH | default .USER_WORKING_DIR}}" }If you are concerned about safety (i.e. accidentally calling a task defined by someone else), you can consider not including a rule for
taskand only allowing it to be invoked manually via the CLI args, or you could write a more narrow match expression (e.g.f.contains("/my-org/")).
Configure a theme with --ui-theme, KAT_UI_THEME, or via config:
ui:
  theme: "dracula"You can optionally set different themes for different profiles:
profiles:
  helm:
    ui:
      theme: "dracula"
      # ...
  ks:
    ui:
      theme: "tokyonight-storm"
      # ...We use Chroma for theming, so you can use any styles from the Chroma Style Gallery.
You can also add your own themes in the config:
ui:
  theme: "my-custom-theme"
  themes:
    my-custom-theme:
      styles:
        background: "#abb2bf bg:#282c34"
        punctuation: "#abb2bf"
        keyword: "#c678dd"
        name: "bold #e06c75"
        comment: "italic #8b949e"
        commentSpecial: "bold italic #8b949e"
        # ...Chroma uses the same syntax as Pygments. Define ui.themes.[name].styles as a map of Pygments Tokens to Styles. You can then reference any theme in ui.theme (or by using the corresponding flag / env var).
These projects provided a lot of inspiration (and snippets) for kat:
- k9s - A terminal UI to interact with your Kubernetes clusters.
 - bat - A 
cat(1)clone with wings. - task - A task runner for Go.
 - glow - Render markdown on the CLI, with pizzazz!
 - soft-serve - The mighty, self-hostable Git server for the command line.
 - wishlist - The SSH directory.
 - viddy - A modern 
watchcommand. 
kat is built on top of a number of libraries. Here are some of its key dependencies:
- charmbracelet/bubbletea - A powerful TUI framework for Go.
- ...plus many other fantastic libraries from charm
 
 - alecthomas/chroma - A general-purpose syntax highlighter in pure Go.
 - google/cel-go - A fast, portable, and safe expression evaluation engine.
 - goccy/go-yaml - YAML support for Go.
 - fsnotify - Cross-platform filesystem notifications.
 - invopop/jsonschema - JSON Schema generation.
 - santhosh-tekuri/jsonschema - JSON Schema validation.
 - And more.
 

