flake-verbs
Enables analyzing Nix flakes to identify which inputs are actually used and to detect locked-input drift, including unportable or orphaned inputs, through MCP tools.
README
flake-verbs
Flake-structure verbs, authored once as typed verbspec specs and projected to CLI, MCP, Anthropic tool-use and OpenAPI.
Two questions this answers, both of which used to require reading a flake by hand:
| verb | question |
|---|---|
inputs |
Which of my inputs does anything actually use? |
input-drift |
How far has each locked input drifted from its upstream — and can it still be fetched at all? |
Run
bun install
bun run src/cli.ts inputs ~/.config/home-manager
bun run src/cli.ts input-drift ~/.config/home-manager --problemsOnly
bun run src/cli.ts --mcp-tools # the agent-facing surface, derived
Both verbs take the flake root as a positional (default .) and emit JSON;
the CLI renders a text view, every other surface consumes output.
inputs — use vs. mention
The naive version of this is grep <input-name> *.nix, and it reports that
every input is used. Three things mention an input without depending on it:
- the
outputs = { a, b, … }:parameter list, which names every input - comments
- path and string literals —
./programs/lnzr.nix,"lnzr/.keep"
String interpolations are the exception and must survive the filter, because
"${dx-compose}/path/to/module" is a genuine reference. That asymmetry is why
this can't be a grep.
The scan also follows _module.args aliases. A flake that binds
synoptic = config._module.args.synoptic-github uses the input under a
different word entirely, and missing that is how a live input gets pruned.
The three verdicts
referenced and unused mean what they say. module-import-only is the
interesting one, and it is deliberately not called "unused": importing a Home
Manager module can be entirely load-bearing (the module sets config itself) or
a complete no-op (every option defaults false and nothing enables them).
With --deep (default on) the verb settles the case where it can: it resolves
the input to its store path via nix flake archive, reads the imported module,
recovers the option namespace it declares, and checks whether anything in the
consuming flake sets it. That turns a shrug into a fact:
DEAD dx-compose github:bdelanghe/dx-compose
imports a module declaring programs.devcontainers-cli, which nothing in this flake sets
Where it can't resolve a namespace it stays module-import-only rather than
guessing. A false unused is the expensive mistake — it deletes a working
input — so every step degrades toward "a human should look".
input-drift — the one that pays for itself
behind is ordinary and not a failure. Two statuses are:
unportable— apath:input. Nix re-reads path inputs from disk at evaluation time, so a lock pointing at a directory that exists only on the authoring machine makes the flake unevaluable anywhere else. This is a bootstrap dead-end that stays completely invisible until you try to build on a second machine.orphaned— the locked revision carries commits that are not on the remote's default branch, or the remote has never heard of it. The commit exists only in a local checkout: unpushed work that a machine rebuild discards silently.
Both exit non-zero, so this gates CI. behind does not.
Upstream queries go through gh api, so private inputs resolve without this
tool ever handling a token.
Why it exists
Written after a real loss. A home-manager flake carried four path: inputs
pointing under ~/.local; on a fresh machine the flake could not evaluate at
all. Repointing them at GitHub revealed that one, lnzr, had a local checkout
three months ahead of its remote main — work that existed nowhere else and
went with the old disk.
The lock had recorded both facts the whole time. Nothing could ask it.
Limits worth knowing
- The reference scan is textual, not an evaluation. It reads
.nixsources; it does not evaluate the flake, so an input reached only through generated or dynamically-constructed attribute paths can still be missed. --deeprecognises thecfg = config.<ns>andoptions.<ns>idioms. A module written some other way falls back tomodule-import-only.input-driftonly checksgithub:inputs against their upstream.git:andtarball:inputs reportunknownrather than a wrong answer.- Only direct inputs are examined. A transitive input is someone else's choice, to audit in their own repo.
推荐服务器
Baidu Map
百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。
Playwright MCP Server
一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。
Magic Component Platform (MCP)
一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。
Audiense Insights MCP Server
通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。
VeyraX
一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。
graphlit-mcp-server
模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。
Kagi MCP Server
一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。
e2b-mcp-server
使用 MCP 通过 e2b 运行代码。
Neon MCP Server
用于与 Neon 管理 API 和数据库交互的 MCP 服务器
Exa MCP Server
模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。