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.
Install
Section titled “Install”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({})Setup options
Section titled “Setup options”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.
Commands
Section titled “Commands”| 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. |
Lua API
Section titled “Lua API”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|autoagent.refresh() -- one-shot re-syncagent.start() -- start live subscriptionagent.stop() -- stop live subscriptionagent.apply_theme("light") -- apply directly inside NeovimEmbed and reuse the client
Section titled “Embed and reuse the client”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.
Implementation and callback threading
Section titled “Implementation and callback threading”- One-shot
statusandsetcalls usevim.systemon Neovim 0.10 and newer, withjobstartas a fallback. - The subscription runs
theme-agent subscribe --jsonunderjobstartand decodes newline-delimited events. theme_changedanddaemon_readycarryeffective. Application is scheduled onto Neovim’s main loop.- A raw
statuscallback is in a libuv fast-event context. Callvim.schedulebefore changing editor state. - Subscription
on_eventcallbacks are already dispatched on the main loop. - Neovim never writes OS appearance. It reads daemon state and changes only editor-local appearance.