lazycomd

Configuration

Global config: ~/.config/lazycomd/config.yaml ($XDG_CONFIG_HOME and LAZYCOMD_CONFIG override it).

listen: ""                 # empty = unix socket only; ":7777" = loopback TCP
token_file: ""             # required when listen is set; must be mode 0600
projects:
  - ~/coding/scraper       # each holds a lazycomd.yaml
commands:
  proxy:
    cmd: ["cloudflared", "tunnel", "--url", "localhost:3000"]
    cwd: ~                 # default: your home dir, or the file's directory
    env: {LOG: debug}
    shell: false           # true wraps cmd in sh -c
    restart: on-failure    # no | on-failure | always
    autostart: false       # start when the daemon starts
    log: true              # also tee output to ~/.local/state/lazycomd/logs/
    size: 262144           # ring buffer bytes
    depends_on: []         # started first, in order
    group: backend         # a label: commands sharing one are listed together
    port: 3000             # the port this command means to bind
    health: http://localhost:3000/healthz   # probed every 10s while running

health: is polled every 10s while the command is running: a GET with a 2s timeout, any 2xx is healthy, redirects are not followed. It is a display signal only — depends_on still uses spawned-plus-200ms readiness.

port: declares the port the command means to bind, so the dashboard can tell you when something else is holding it. With health: set and port: unset, the port is taken from the health URL.

Keeping commands with the code

A command can live in the repository it runs, rather than in the global config. List the directory, and its lazycomd.yaml is read alongside:

projects:
  - ~/code/app
lazycomd files add ~/code/app   # writes the entry, seeds lazycomd.yaml
lazycomd files                  # what is being read

Commands in such a file are named and grouped exactly like any others — the file is only where they are kept. Two files cannot both hold a command of the same name, since the name is how everything else refers to it, and a command there defaults to running in its own directory.

Editing a command rewrites it in the file it came from. A new one goes to the global config, which is the only file the daemon knows without being told.

Metrics

The daemon records each command’s cpu and memory as it samples them, so the charts in the TUI are full when you open it and an agent can ask what a command has been doing.

metrics:
  enabled: true     # default; false stops recording and frees the windows
  window: 30m       # how much history to keep per command
  every: 2s         # sample interval, and what one chart column is worth

A command opts out on its own:

commands:
  chatty:
    cmd: ["./worker"]
    metrics: false

The window is the whole memory budget: one sample is two numbers and a flag, so 30 minutes at 2s is ~900 samples, about 21KB per command. Nothing is written to disk — a daemon restart starts the window again.

metrics: is global-only, like listen: and projects:: the sampler runs one loop for every command, so an interval belongs to that loop rather than to any one command.

What a command inherits

A command runs with the daemon’s environment, plus whatever its own env: adds. That matters most for PATH.

Started at login, the daemon inherits a minimal environment: launchd gives it /usr/bin:/bin:/usr/sbin:/sbin, and a systemd user unit is nearly as sparse. Neither holds Homebrew, nvm, rustup, pyenv or ~/go/bin — so a command that runs in your terminal fails under the daemon, often wearing another program’s name:

go run ./cmd/console
make: go: No such file or directory
make: *** [run] Error 1

So at startup the daemon asks your login shell ($SHELL -lc) what PATH it uses, and appends the entries it is missing. Nothing is removed, the shell is given five seconds to answer, and it happens once — the log line says what was added. lazycomd doctor checks every command’s program against the PATH the daemon actually ended up with.

Set LAZYCOMD_NO_LOGIN_PATH=1 to skip that and keep the environment exactly as the service manager handed it over. Then give each command what it needs:

    env:
      PATH: /opt/homebrew/bin:/usr/bin:/bin

A command’s own env: PATH always wins, whichever way the daemon started.

Environment

Variable Meaning
LAZYCOMD_ADDR unix:///path/to.sock or http://host:port
LAZYCOMD_TOKEN Bearer token for a TCP address
LAZYCOMD_CONFIG Override the config path
LAZYCOMD_NO_LOGIN_PATH Set to skip widening PATH from the login shell
XDG_CONFIG_HOME, XDG_STATE_HOME Standard overrides