Skip to content

Neovim integration

The bundled theme-agent.nvim plugin mirrors the daemon’s effective theme into vim.o.background, optionally selects a colorscheme, changes daemon modes, and follows live subscription events.

The repository root is directly loadable as a plugin because it contains lua/ and plugin/. The theme-agent binary must be on PATH, or command must be an absolute path. Install the binary first. A stopped daemon is not fatal; the plugin degrades to a one-time warning.

With lazy.nvim:

{
"Ajaymamtora/theme-agent",
main = "theme_agent",
event = "VeryLazy",
cmd = { "ThemeAgentStatus", "ThemeAgentSet", "ThemeAgentSubscribe" },
opts = {
-- colorschemes = { light = "dawnfox", dark = "nightfox" },
},
}

With packer or a manual runtime-path install:

require("theme_agent").setup({})

Defaults are shown below:

require("theme_agent").setup({
command = "theme-agent", -- binary name or absolute path
auto_subscribe = true, -- follow live changes via `subscribe`
set_background = true, -- drive vim.o.background
colorschemes = {}, -- { light = "<scheme>", dark = "<scheme>" }
on_change = nil, -- function(theme, info): custom hook
notify = true, -- vim.notify on errors / daemon-down
})

After each apply, on_change(theme, info) receives "light" or "dark" and the originating event or status table, including fields such as info.mode and info.source.

Command Action
:ThemeAgentStatus Echo the current mode and effective theme.
:ThemeAgentSet {light|dark|system|sun|auto} Set the daemon mode, with tab completion.
:ThemeAgentSubscribe Start the live subscription.
:ThemeAgentUnsubscribe Stop the live subscription.
:ThemeAgentReload Re-sync the editor from daemon state.
local agent = require("theme_agent")
agent.setup(opts) -- configure + initial sync (+ subscribe)
agent.status(function(state) -- async; nil when the daemon is down
print(vim.inspect(state))
end)
agent.set_mode("dark") -- light|dark|system|sun|auto
agent.refresh() -- one-shot re-sync
agent.start() -- start live subscription
agent.stop() -- stop live subscription
agent.apply_theme("light") -- apply directly inside Neovim

Other plugins can use the daemon without calling setup(). Reuse these read-only building blocks instead of creating another OS detector or subscription.

-- One-shot effective state. Runs in a libuv fast event context.
require("theme_agent").status(function(state)
-- Hop to the main loop before touching Vim options or vim.env.
vim.schedule(function()
if state then vim.o.background = state.effective end
end)
end)
-- Live NDJSON stream. on_event already runs on the main loop.
local sub = require("theme_agent.subscribe")
sub.start({
command = "theme-agent",
on_event = function(ev) --[[ ev.effective == "light"/"dark" ]] end,
on_exit = function() --[[ reconnect with backoff if desired ]] end,
})
sub.stop()

theme_agent.config.command is initialized when the module loads, so both APIs work before or without setup(). auto_theme.nvim uses this model in its agent mode: one subscription re-emits User AutoThemeChanged, while the daemon remains the sole source of system theme state.

  • One-shot status and set calls use vim.system on Neovim 0.10 and newer, with jobstart as a fallback.
  • The subscription runs theme-agent subscribe --json under jobstart and decodes newline-delimited events.
  • theme_changed and daemon_ready carry effective. Application is scheduled onto Neovim’s main loop.
  • A raw status callback is in a libuv fast-event context. Call vim.schedule before changing editor state.
  • Subscription on_event callbacks are already dispatched on the main loop.
  • Neovim never writes OS appearance. It reads daemon state and changes only editor-local appearance.