关于 sift
sift 是面向开发者的 MCP 服务和命令行工具,可运行命令并筛选日志、构建与测试输出。它保留完整原始内容,只展示模型选中的行,并可随时查看遗漏部分。
主要功能
- 01运行命令并保存原始输出
- 02筛选重要行并标记省略内容
- 03查看捕获内容的任意范围
- 04摘要日志、JSON 和源代码结构
- 05后台运行并分段跟进输出
- 06脱敏后请求模型选择行号
适用场景
- 01从冗长测试输出中定位失败
- 02检查保存的 CI 日志或崩溃转储
- 03逐步查看持续运行的构建输出
- 04浏览源文件声明而不显示函数体
MCP 相关
作为 MCP 服务,sift 可接收客户端命令并处理其输出;其 hook 也可在客户端执行 shell 命令前进行路由。
配置
本地服务的配置在各客户端中通用,复制后粘贴到客户端的 MCP 配置文件即可。
uvx sift-cliNVIDIA_API_KEY密钥A free NVIDIA NIM key. Optional: without one every tool still runs the command, still keeps every byte, and still returns the exit code -- what is lost is the model's choice of lines, not the output. Also read from ~/.config/nvidia/api_key.本地服务需要在你的电脑上安装运行环境(如 Node.js 18+)。各客户端的配置文件位置不同,请参考客户端文档。
工具
7收录的工具列表 · 更新于 3 周前
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| cwd | any | 否 | |
| keep | any | 否 | |
| budget | any | 否 | |
| command | string | 是 | |
| timeout | any | 否 | |
| background | boolean | 否 |
详细介绍
sift
运行你的命令,然后只把重要的行交给模型。
测试套件输出了 4,000 行,其中 11 行是失败信息。构建过程输出的进度条重绘了 900 次。安装过程列出了它处理过的每个软件包。所有内容都会进入上下文窗口,而且——这才是代价所在——之后的每一轮都会完整重新发送这些内容。
sift 是一个 MCP 服务和命令行工具,用来解决这个问题。它自行运行命令,将每个字节都保存在磁盘上,并向模型提供一份视图:失败信息、摘要,以及读者真正会停下来看的行。其余内容会被标记,而不是删除。
两个数字,都经过实测,也都能在此仓库中复现。运行本项目自带的测试套件会输出 652 行——按模型自己的分词器计算,共 21,392 个 token。最终返回的是 8 行、208 个 token:减少 99%(python test/kazanc.py)。去掉几乎所有内容只是容易的那一半。在一个包含 22 组真实构建和测试输出的语料中,默认预算保留了读者不可或缺的 140 行中的 138 行(python test/budget.py)。
$ sift run -- pytest
FAILED test/test_auth.py::test_expired_token - assert 401 == 200
...
─ 3,914 lines not shown · sift peek 9f2c41ab for any of them ─
=== 1 failed, 212 passed in 18.4s ===
对于你没有生成的文件——日志、已保存的 CI 记录或崩溃转储——无需运行,也能进行同样的处理:
$ sift digest ci-run-8812.log
2026-09-02T04:11:07 Building 214 targets
─ 38,904 lines not shown · sift peek ci-run-8812.log for any of them ─
ERROR: //src/parser:parse_test failed in 4.1s
─ 1,022 lines not shown · sift peek ci-run-8812.log for any of them ─
FAILED: 1 of 214 targets
列表不是按行读取的。为机器编写的 JSON 数组通常完全没有换行符,而且其中某一行也没有值得挑选的内容,因此会以记录为单位,并遵循同样的规则:
$ sift digest export.json
{"id": 3, "status": "failed", "error": "connection refused"}
─ 412 records not shown · sift peek export.json for the text they came from ─
{"id": 416, "status": "ok"}
如果把同样的问题用在源代码上,得到的就是目录:
$ sift outline src/parser.rs
pub struct Parser {
pub fn parse(input: &str) -> Result<Ast, Error> {
─ 34 lines not shown · sift peek src/parser.rs for any of them ─
impl Iterator for Tokens {
对于不会结束的命令——开发服务器、日志跟踪,或你想让它持续运行的构建——则先启动它,再一次读取一段:
$ sift run --background -- cargo build --release
9f2c41ab
$ sift follow 9f2c41ab
warning: unused import: `std::fmt`
─ 212 lines not shown · sift peek 9f2c41ab for any of them ─
error[E0308]: mismatched types
sift 9f2c41ab · running · new lines 1-247 · 2 shown · nemotron
$ sift follow 9f2c41ab # only what has arrived since
$ sift follow --all --wait 30 # every run at once, holding for something new
$ sift stop 9f2c41ab # ends it, and everything it started
内容不会重复显示,行号也取自这次运行:只要捕获内容还在,sift peek 中的第 247 行就始终是第 247 行。
由谁决定
由免费模型决定。sift 会将带编号的行发送给它,并只问一个问题:哪些编号重要? 它只返回编号,其他回复一概不用——你在上面看到的文本,都是从本地捕获文件中逐字节打印出来的。
这就是全部诀窍,也正因如此,它不是摘要器。摘要器可能会误述某一行的内容;sift 不会,因为它从不编写任何行,只负责挑选。
这也意味着,语言覆盖范围不必列成清单。模型能读懂土耳其语、日语、阿拉伯语和印地语;也能读懂 Rust、COBOL、Mojo,甚至上周刚发布的语言。这里没有逐一列举它们,因此也不会漏掉任何一种。
三条规则
显示的内容绝不凭空捏造。 判断器返回行号。文本始终来自本地文件。
内容绝不丢弃。 sift peek <handle> 会返回未经更改的原始捕获内容。视图中的每个间隔都会标明其中包含多少行。
命令绝不会因此出故障。 无 API 密钥、无网络、端点过载、回复毫无意义、提炼器出现错误,或因决定不让任何内容离开这台机器而无法使用——以上任何一种情况出现时,都会回退到不依赖这些条件的规则。命令照常运行;你仍能获取其输出和退出代码。
离开本机的内容
只有一样:问题文本。发送之前,所有看起来像凭据的内容都会被替换——带有已知前缀的令牌、JWT、授权标头、连接字符串中的密码,以及 PEM 块的正文。
你可以放心阅读这些内容。模型只会被要求提供行号,而这些行会从你自己的文件中打印出来,因此,即使某一行在发出时被屏蔽,你仍能看到它的完整内容。
有三个开关:
SIFT_MAX_CAPTURE=0 # keep everything a command writes, however much that is
SIFT_BASE_URL=... # ask your own endpoint instead, and no key is wanted
SIFT_MODELS=a,b # which models to ask there, best first
SIFT_NO_MODEL=1 # never send anything; use the deterministic view
SIFT_MASK=0 # send unmasked
SIFT_CACHE=0 # ask again, even about text already answered
SIFT_EFFORT=low # let the model think less, and lose some of what matters
SIFT_PATIENCE=0 # one quick pass only; do not wait out a busy hour
屏蔽并不完整,也不声称能做到完整:看起来不像任何特定内容的裸密钥仍可能漏过。只有 SIFT_NO_MODEL 能提供保证。
第四项是一种经过衡量的取舍,默认关闭。要判断一份 404 行的构建日志中哪些行重要,模型会写出约 900 个令牌的推理内容,才得出一个 12 个令牌的答案,而你要等 15 秒。设置为 SIFT_EFFORT=low 时,同一个问题只需 2.4 秒——但在整个语料中,会漏掉 7.8% 读者离不开的行,恰好出现在这个工具存在的原因所在:大型机作业的返回码、崩溃循环的诊断信息。可以选择速度,但它不是默认选项,代价也已明示。
第三项是为了避免重复付费。对于完全相同的字节和完全相同的问题,已经给出的答案会被再次使用,而不是再次购买——保存下来的只有数字,不会保存视图,因此文本仍会从你自己的文件中呈现,间隔标记仍会指向你自己的捕获内容。不需要请求的视图会显示 (remembered),而不是模型名称。
捕获的字节永远不会离开 $SIFT_HOME(默认值为 ~/.cache/sift)。不会上传任何内容,也不会在其他地方记录任何内容;删除捕获目录,就会删除其中曾保存的所有内容。
每次捕获最多保留 1 GB。这远超任何实际构建日志,以及命令陷入循环时几秒钟产生的内容——而后者正是此工具存在的原因:内容不会被丢弃,也不会占满他人需要的磁盘。读取永远不会停止——如果管道中的内容无人读取,命令就会停止,而这是本工具绝不会做的事——因此命令会正常完成,退出码由命令自身决定,页脚会显示 kept the first 1,073,741,824 bytes of it,而不是让你误以为拿到了完整运行记录。对于宁愿占用磁盘也要保留全部内容的人,可以设置 SIFT_MAX_CAPTURE=0 关闭上限。
它们也不会自行消失。本工具不会在后台清理、过期或整理内容:只有 sift gc [DAYS] 会删除捕获内容,而且只会在你输入命令时删除,不会提前删除。删除后会为每个句柄留下一行记录——删除时间和大小,而不是命令内容——这样,两周后查看间隔标记时,会看到 "removed on the 8th",而不是看到它对一个虚构句柄给出的答案。
命令
sift run [--timeout SECONDS] [--shell] [--background] [--cwd DIR]
[--budget LINES] [--keep PATTERN] [--] COMMAND...
sift follow [HANDLE] [--all] [--wait N]
what a background run has said since you last looked
sift stop [HANDLE] end it, and everything it started
sift outline PATH|- what a file declares, without its bodies
sift digest PATH...|- what is in files somebody else produced
sift peek HANDLE|PATH [FIRST] [LAST]
sift hook answer one shell-command event on stdin
sift mcp speak the protocol on stdin, for a client
sift tools which dense tools this machine has
sift tool NAME [ARGS...] run one of them, distilled
sift memory [TERM] what has been run here before, and how it went
sift list [COUNT] what is running, and what has been run
sift stats [COUNT] what the shortening cost, and what it saved
sift gc [DAYS] remove captures older than that, and say what went
将 - 作为路径会读取标准输入,这是接收他人输出的另一种方式:
$ journalctl -u nginx --since yesterday | sift digest -
Sep 08 04:11:07 nginx[2114]: worker process 2119 exited on signal 11
─ 8,204 lines not shown · sift peek 7c1a04e9 for any of them ─
Sep 09 01:02:55 nginx[2114]: signal 15 (SIGTERM) received, exiting
管道传来的内容没有可供他人再次输入的路径,因此会单独保存为一份捕获内容,间隙标记也会指向它。第二条规则正是原因:如果视图省略了若干行,却指向一个临时文件,那么等别人读取时,这个文件早已不复存在。
同时查询传给 digest 的多个路径,而 --all 会一次性跟进所有正在运行的命令。这两者表达的是同一个想法:等待是成本,所以只等一次。--wait N 会一直等到某次运行确实有了结果,而不是回答说目前还没有发生任何事。
sift hook 是捕获客户端自行运行的 Shell 命令的方式,无需代理:将客户端的工具前置钩子指向它,每条 Shell 命令都会先经过这里。所有内容都会被路由——这里不会猜哪些命令值得捕获,因为这样的命令清单无异于一份工具清单,而且命令会输出多少内容,在运行前无法得知。路由所有内容没有成本:十二行视图就是十二行。它会在出错时放行,因此其中的 Bug 不会改变你的 Shell 原样,而 SIFT_HOOK=0 可以将其关闭。
这是最有用的部分,也是唯一需要你启用的部分,因此它会主动提供自身,而不是等着被发现:
sift hook --install # says what it gives and what it costs, then asks
sift hook --uninstall # and takes it back out
它会在客户端的设置中写入一行,保留原文件副本,并且不触碰其中的其他内容。sift run 只会提到它一次,然后就不再提了。
sift tool 会运行三个程序之一,它们可以在不打开文件的情况下回答问题——sg(ast-grep)用于结构化搜索,diff(difftastic)用于区分重新缩进与内容更改的差异比较,loc(scc)用于统计代码树的大小。它们的输出天生就很大,这正是它们适合放在这里的原因。此软件包不附带任何二进制文件:sift tools 会说明这台机器上有哪些工具及其名称,是否安装则由你决定。
sift memory 完全不调用模型。它要回答的是计数问题——出现了多少次、结果如何、哪个命令在这里一次也没成功过——而让模型计数速度更慢、需要一次请求,而且有时还会出错。模型只负责判断无法计算的内容,除此之外一概不管。
sift stats 会说明缩短内容节省了什么、付出了什么代价,并将两者分开,因为它们不是同一类数字。比例是此工具根据其持有的字节自行计算的,因此是精确值。成本则是端点对其自身 Token 数量的统计,因此是测量值。旁边现在还会显示真正需要关注的数字——输出在发送时由同一分词器计算出的 Token 数量:
$ sift stats
handle captured shown part asks weighed cost command
9f2c41ab 62,003 B 450 B 0.7% 1 21,392 5,120 pytest -v
1 run · 62,003 B captured · 450 B shown · 0.7% of it
cost 5,120 tokens, as the endpoint counted them
the output put to a model weighed 21,392 tokens -- numbering and question included
最后这一行比单纯的捕获内容多了一点,因为这些行发出时带有编号和前置问题,它会如实说明这一点,而不是减去一个猜测值。它也会让报告不那么好看:对于一个小型命令,提问的成本可能超过输出本身,这时你就会看到这一点。端点未统计的运行会以短横线显示,并从两个总数中排除,而不是用字节数除以四来填补。
--keep PATTERN 会显示所有匹配的行,不受其他选择和预算限制。模式由你提供,而不是由此工具猜测——这是这里唯一由模式决定任何内容的地方,而且只有在你输入后才会起作用。--budget LINES 是单个视图的上限。
在 Agent 中
相同的答案也可通过 MCP 获取,而这正是它们最能发挥价值的地方:工具 结果会在之后的每一轮中重新发送,因此,不放进对话记录的构建日志 会始终留在记录之外。
uv tool install "sift-cli[mcp]" # or pipx; see Installing
claude mcp add --scope user sift -- sift-mcp
密钥从 ~/.config/nvidia/api_key 读取,因此无需将其放在
环境变量中,也无需粘贴到任何地方。--scope user 会将服务配置到
所有项目中,而不是只配置到你当前所在的项目。
然后重启客户端。MCP 服务会在会话开始时连接,因此你运行该命令的会话 看不到这个服务。
没有密钥时,服务仍会启动,而所有需要模型的工具都会说明原因并拒绝执行, 因此 Agent 会改用自己的 shell,而不是得到一个它无法分辨好坏的更差答案。
任何支持 stdio 的客户端都可以使用。命令是 sift-mcp,而 sift mcp
会启动同一个服务——这是为更愿意按软件包自身名称运行它的客户端准备的
命令(uvx --from "sift-cli[mcp]" sift-cli mcp,MCP
注册表条目中列出的就是这个命令)。它提供
run、follow、outline、digest、digest_many、tool 和 peek。list
和 stats 则刻意不予提供:它们会把这台机器上最近运行的所有命令交给模型,
包括模型从未询问过的命令;而在终端前的人本来就有权访问这些信息,连接到套接字的模型却没有。
由于客户端看不到 stderr,每个结果的最后一行都会说明你看到的内容:句柄、 命令如何结束,以及模型是否选出了行,或是没有找到可用的模型。
安装
uv tool install sift-cli # the command line, no dependencies at all
uv tool install "sift-cli[mcp]" # and the MCP server
pipx install 也能完成同样的操作。两种方式都会将 sift 和 sift-mcp 放入各自独立的环境,并添加到
PATH 中,这正是命令行工具所需要的:这里的任何内容都不应放进你用于构建的 Python 环境。
在已激活的虚拟环境中,直接运行 pip install sift-cli 也可以。
但它不能用于 Debian、Ubuntu 或 WSL 上的系统 Python——这些系统带有
EXTERNALLY-MANAGED 标记,因此 pip 会按设计拒绝安装:
error: externally-managed-environment
× This environment is externally managed
这种拒绝是正确的,解决办法不是 --break-system-packages。请使用 uv tool 或 pipx。
软件包名是 sift-cli,命令是 sift 和 sift-mcp。名称不同是因为
PyPI 上的 sift-mcp 属于其他项目——一个与此无关、用于授权 Agent 操作的 MCP 服务。这里的内容不属于他们,
那边的内容也不是这里的内容。
需要 Python 3.12 或更新版本,命令行工具没有依赖项。
你需要一个模型——密钥,或自己的模型
sift 会询问模型哪些行很重要。有两种方式可以提供模型,你只需要其中一种。
免费密钥
**不随软件提供,也不可共享。**密钥必须属于你。它是免费的,获取只需一分钟:
- 在 https://build.nvidia.com 获取密钥
- 将其放在
sift会查找的位置:
export SIFT_API_KEY=nvapi-... # or NVIDIA_API_KEY
# or, once and for good:
mkdir -p ~/.config/nvidia && echo 'nvapi-...' > ~/.config/nvidia/api_key
或者使用自己的模型,完全不需要密钥
指定 sift 使用某个地址,它就会向该地址发起请求。问题本身不会改变;请求会按常规的 OpenAI 格式发送到端点,而没有密钥可填时,也不会发送
Authorization 标头。
export SIFT_BASE_URL=http://localhost:11434/v1 # Ollama's own port
export SIFT_MODELS=the-model-you-pulled # whatever that server calls it
同样的两行配置适用于 llama.cpp(--api)、vLLM、LM Studio、LocalAI、公司网关,或任何支持
POST /v1/chat/completions 的其他端点。SIFT_MODELS 接受以逗号分隔的模型列表,并按顺序尝试。
你输入的地址会被视为一个决定:如果缺少密钥,不会收到任何警告,MCP 服务也不会拒绝请求。该端点需要什么凭据,由你和它自行决定。
上面故意没有列出模型名称。这里测试的是这种模式,而不是模型列表:测试套件证明会向你自己的端点发起请求,而且 不带密钥。这里尚未衡量哪个本地模型回答得好,贸然推荐某个模型也名不副实——你可以用
test/budget.py中的语料库,针对自己实际运行的输出进行评估,得出结论。
两者都没有时
我们有意对两种调用方作出不同的回应:
- 在终端中,一切照常运行——命令、字节、退出码、第三条规则——同时会显示醒目的横幅,说明这些内容并非由模型挑选,并提醒你看到的是输出的两端。
- 通过 MCP,工具会拒绝调用并说明原因,还会告诉 Agent 改用自己的 shell。人可以看到降级后的视图并自行判断;模型拿到的则是一段短文本,附带的页脚没有理由让它怀疑,而悄然变差正是这套机制绝不会对无法核查的读者做的事。
如果你想在没有模型的情况下运行,请设置 SIFT_NO_MODEL=1。这是主动选择,而非疏忽;一切都能正常工作,也不会有任何说教。
贡献与问题报告
CONTRIBUTING.md 是指南:有意采用两种语言、三条不可妥协的规则,以及针对每条规则的变异测试。SECURITY.md 说明了哪些内容会运行、什么会离开这台机器,以及如何私下提交安全问题。
构建过程
共二十四个阶段,每个阶段都在下一个阶段开始前完成;notlar/ 中的说明记录了每个阶段的决策及其代价。notlar/00-PLAN.md 概述了整个过程,包括那些我们有意不构建的内容及其原因。
测试位于 test/。旁边还有 test/mutations.py,它会逐一破坏代码所遵循的每条规则——共 162 条——并检查测试套件是否能发现问题。测试套件全绿,只能说明测试没有反对这个版本的代码,并不代表它们能发现更糟糕的版本。
文档还说明了测试电池中断时会发生什么,因为它确实中断过:磁盘上遗留的一处断点躲过了所有常规测试运行,在有人查看之前让机器瘫痪了六次。test/conftest.py 现在会修复其中一个问题;而它破坏的规则也有双重保障,因此任何单次编辑都无法把 sift stop 变成向你拥有的一切发送信号的命令。详见 notlar/19。
许可证
MIT。
评论
一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。