这是提交至Hacktoberfest 开源 AI 挑战赛第 4 周:机器中的幽灵
TL;DR:我在 DEV 首页信息流的大多数标签页里反复看到同样的帖子:
Discover,Following以及Curated。想通过实现一个简单的标记为已读浏览器扩展来修复它。然后事情一件接一件,最后我做出了三个项目:一个已被放弃,以及两个仍在进行中(Feedgrab和Ghostify)。
给用户的说明:这仍在开发中,所以如果你看到错误,请查找更新:https://github.com/SweaDev/ghostify/releases
给评委的说明: v1.0.0是在提交期内完成的;之后的版本是更新。
背景故事
这些天,我在 DEV 上花了太多时间。
- 删掉了我的旧 X/Twitter 账号,因为它让我头疼!只新建了一个作为占位。
- 停用了我的 Facebook 账号,因为它……嗯,那是另一个故事了!
所以我上网的大部分时间都花在 DEV 和 YouTube 上。
但 DEV(以及 YouTube)的问题是,我在信息流里总是看到同样的帖子!
早上打开 DEV:又是那个帖子。午饭时打开:它又在那儿。晚上打开:猜猜谁回来了!我读过了。我互动过了。我甚至可能评论过了。它还在那儿,盯着我,好像从没被介绍过一样。
于是在 10 月 26 日,我做了任何理智的开发者都会做的事:我打开编辑器,而不是关掉标签页。
尝试 #1:myDEV(安息 🪦)
计划很简单:
- 在每张帖子卡片上、紧挨着 Bookmark 放一个 ✓ 按钮。
- 点击它,帖子就从信息流中消失,并进入一个新的myDEV标签页。
- 搞定。回去继续阅读。
它奏效了!大约一个小时。然后我打开 YouTube,这周第 14 次看到同一个视频,才意识到 myDEV 只会读取一个网站,而且选择器是我手工硬编码的。一旦 DEV 改了类名,它就死了。而对于 YouTube,我得从头再来。
我花了几天才承认,但今天我最终把它归档了。安息吧,myDEV。你对这个世界来说太具体了。
尝试 #2:Feedgrab
我真正需要的不是一个 DEV 插件。我需要的是能查看任何页面并说:“这是一个信息流,这些是条目,而这一个是你之前见过的同一个条目,即使无限滚动重新渲染过它。”
那变成了Feedgrab,一个 TypeScript 库,它可以:
- 检测页面上的信息流(结构启发式,可选由 LLM 精炼),或采用你输入的 CSS/XPath 选择器;
- 让你确认或修正它的猜测,在 Shadow DOM 覆盖层中;
- 把信息流变成一个去重后的流,其中的条目能跟上无限滚动;
- 除非你要求,否则绝不删除任何内容。
它运行在浏览器扩展、无头浏览器和服务器上,使用相同的 API。
尝试 #3:Ghostify
然后,终于,我最初想要的东西。在 Feedgrab 之上。带着一个幽灵。👻
我构建了什么
Ghostify是一个浏览器扩展(Chrome + Firefox),用于那些不断向你展示相同帖子的信息流。
悬停在一个帖子上,点击幽灵,它就从信息流中消失了。永久消失。刷新、滚动、明天再来:它依然消失。
但它不是被删除。它变成了一个幽灵。Ghostify 保留一份副本,这样你可以再次找到它、稍后阅读,或把它推迟到周末。
这是我对机器中的幽灵的理解:你已经看过的帖子不会消亡,它们只是不再纠缠你的信息流,而是移入机器中。安静地。礼貌地。直到你召唤它们回来。
它的功能:
- 幽灵按钮会出现在你指针下的帖子上。点击它,帖子消失,一个撤销提示条给你 5 秒的后悔时间。
- Ghostify 页面把所有内容保存在三个标签页中:已幽灵化、稍后和已推迟。
- 推迟一个帖子到稍后今天、明天、周末、下周或任何日期。时间到了,它会出现在从推迟中返回中。
- 搜索你的幽灵,按关键词,或开启语义并直接描述你记得的内容(“那篇关于 JavaScript 添加对象和数组的帖子”)。
- 自动幽灵化规则用平实的英语,按站点设置:“我已经读过的每周重复讨论帖”。一个本地决策模型检查新的信息流条目是否匹配它们,并幽灵化匹配项。如果它弄错了一个,不是这个会把帖子带回来,并且该规则再也不会碰它。
- 添加任何站点。在任何有信息流的页面上,点击添加此站点。Feedgrab 会提议信息流,你在它的覆盖层中确认它们,并选择它们是用于已登录还是未登录视图。
- 新标签页风味,如果你想让幽灵出现在每个新标签页上。
演示
还没有商店上架(暂时!),所以演示就是:安装它,打开 DEV,开始幽灵化。大约需要一分钟:
- 从以下位置下载 zipReleases:
ghostify-<version>-chrome.zip或ghostify-<version>-firefox.zip。 -
Chrome:解压,打开
chrome://extensions,开启开发者模式,点击加载已解压的扩展程序。Firefox:打开about:debugging#/runtime/this-firefox,点击加载临时附加组件…并选择该 zip。 - 打开https://dev.to/处于登出状态,悬停在一篇帖子上,点击幽灵。
这里有一个快速导览。(截图展示的是用户会看到的内容。)
工具栏弹窗列出了你最近的幽灵。点击 👁 取消某个幽灵,它就会重新出现在信息流中:
点击一个幽灵查看其详情、AI 摘要和标签:
用通俗英语按站点编写自动幽灵化规则:
并为每项任务选择模型:
代码
幽灵化
Ghostify 是一个浏览器扩展(Chrome 和 Firefox),用于那些总是向你展示相同帖子的信息流。悬停在一篇帖子上,点击幽灵,它就会从该站点的信息流中永久消失。Ghostify 会保留一份副本,以便你之后再次找到它。
v1.0.0。Ghostify 开箱即用于 dev.to 首页信息流,你也可以添加任何站点。它有一个完整的 Ghostify 页面,支持暂停和搜索,以及可选的 AI,可通过 Ollama 在本地运行,或使用你自己的 OpenRouter、Gemini 或 OpenAI 密钥。
功能
-
dev.to 登出状态的首页信息流(相关
/、最新/latest、热门/top/*)会被自动识别,通过feedgrab。 -
幽灵按钮会显示在你指针下的帖子上。点击它会隐藏该帖子并保存,一个撤销提示条可让你撤回。
-
被幽灵化的帖子会保持隐藏当你重新加载时,以及当它们再次出现时……
Feedgrab
一个 TypeScript 库,可查找并描述内容信息流在网页中(帖子时间线、搜索结果、产品网格、文章列表),并将它们转换为一个去重后的单一流,包含带有排除报告的
- 检测自动检测信息流(结构启发式,可选由 LLM 优化),通过检查元素,或通过你输入的 CSS/XPath 选择器。
- 确认/编辑在 Shadow-DOM 覆盖层中查看提案,或通过无头辅助工具以编程方式操作。
-
迭代一个统一的信息流,可跟上无限滚动(
MutationObserver)或分页ingest),并带有每项报告:kept,excluded(以及原因),或pending。 - 排除按特异性排序的选择器规则、内置广告规则或自然语言 LLM 条件来筛选条目。除非你调用移除辅助函数,否则不会移除任何内容。
它运行在浏览器扩展(Chromium + Firefox,MV3)、无头浏览器(Playwright/Puppeteer 注入的 bundle)以及服务端(Bun/Node 配合解析后的 DOM)中,各处使用同一套 API。
安装
…
我是如何构建它的
开源 AI 部分
AI 是可选的,且默认关闭。Ghostify 完全可以只是一个简单的“隐藏这条帖子”按钮。开启 AI 后,它会承担三项工作,每项都有自己的模型:
| 任务 | 默认(本地,通过 Ollama) | 用于 |
|---|---|---|
| 嵌入 | embeddinggemma-2 |
对你的 ghost 进行语义搜索 |
| 通用 | gemma4:e2b |
为每条被 ghost 的帖子生成摘要和标签 |
| 决策 | d3:2b |
用你的自动 ghostify 规则检查新的信息流条目 |
这三者都是开源权重模型,小到可以在笔记本电脑上运行。启动 Ollama 使其接受来自扩展的请求,就这些:
ollama pull embeddinggemma-2
ollama pull gemma4:e2b
ollama pull d3:2b
OLLAMA_ORIGINS="chrome-extension://*,moz-extension://*" ollama serve
如果你更想用托管模型,每项工作都可以切换到OpenRouter、Gemini或OpenAI,使用你自己的密钥。底层管道是AI SDK,所以切换提供商只是一个下拉菜单,而不是重写。
有几件事让我很满意:
- 处处都有回退。如果决策模型不可用,通用模型会处理规则检查。如果嵌入失败,搜索会回退到关键词。不会因为 Ollama 没运行就出问题。
- 嵌入会标记生成它们的模型。更换嵌入模型后,Ghostify 会忽略旧向量,而不是把它们混在一起。重建索引会填补空缺。
-
一个坦诚的缺口:
d3的响应格式(还)没有文档。Ghostify 会读取它认识的形状,把其他任何内容视为“不匹配”,并记录下来。宁可漏掉一个 ghost,也不隐藏你想看的内容!
扩展部分
- WXT用于一套代码库构建 Chrome MV3 和 Firefox,外加新标签页版本。
- React 19 + Tailwind v4用于弹窗、Ghostify 页面和设置。
- Dexie(IndexedDB)用于存储 ghost、嵌入和密钥。一切都在你的浏览器里。没有账户,没有同步,没有服务器。
-
Feedgrab用于查找信息流和条目,以及隐藏它们。Ghostify 从不直接触碰信息流节点;它请求 Feedgrab 的
removeItem来隐藏它,而它自己的 UI 位于单独的 Shadow DOM 中。
安全性方面有点像个无底洞:
- 内容脚本永远看不到 API 密钥。它们甚至不打开 IndexedDB。一切都通过类型化消息传给后台脚本,由后台与模型通信。
- 密钥也不在
storage.local里,因为内容脚本可以读取它。它们存放在扩展自身源上的 Dexie 表中。 - 在内容脚本中,Feedgrab 拿到的是一个代理而不是模型,这也让打包体积从380 KB 缩小到 71 KB。不错的副作用!
“你怎么在一周内做出这一切”的部分
大部分代码不是我手写的。我用了一个编码智能体。
但我也没有只说一句“给我做个扩展”。我一直在用一个小型的规范驱动流程,我称之为specx:
- 规范:我手写了规范。它只有 17 行。字面上就是一段话加一个功能列表。
- 计划:智能体把它变成了一个计划,包含技术栈、架构和一份决策日志。
- 任务:然后拆成23 个任务,分布在 4 个阶段,每个任务都标注了它需要的规范/计划行范围、它涉及的文件,以及一个完成检查。
- 构建循环:取第一个未被阻塞的任务,只读它引用的内容,实现它,运行它的完成检查加上 lint + 类型检查 + 测试,勾选复选框,提交。重复。
- 阶段检查:每个阶段结束时,从干净状态按照 README 操作,构建,在浏览器中加载,修复坏掉的地方,打一个发布标签。
规则放在AGENTS.md里,所以任何智能体都能接手。每个判断决定都写进计划的决策日志,这样我能看到为什么某件事要那样做。而且每个任务一个提交,所以git log读起来就像变更日志。
阶段 1 是“在 DEV 上幽灵化一篇帖子”。阶段 4 是“用本地 AI 自动幽灵化”。每个阶段结束时都有我能真正加载和使用的东西,而不是一堆半接线的模块。
为什么开放创新很重要?
因为Ghostify 读取的东西是我的浏览记录。
想想一个信息流扩展里的 AI 功能实际能看到什么:你滚过的每个信息流里的每篇帖子、你隐藏了什么、你稍后保存了什么、你搜索了什么。那是一个相当详细的人物画像。我不想它默认流向任何人的 API。连我自己的都不想。
有了开源权重模型,就不必如此:
- Gemma 4 E2B、EmbeddingGemma 2 和 d3:2b 在我的笔记本电脑上运行。摘要、标签、语义搜索和规则检查全都在本地完成。用 Ollama 的话,什么都不会离开这台机器。
- 每条帖子零成本。自动幽灵化检查信息流中的每一个新条目。用付费 API 的话,我每滚动一次就是在烧表。本地运行,只是我的风扇多转一会儿。
- 它一直能用。没有速率限制,没有弃用邮件,没有“此模型将于……退役”。
- 一个小型专用模型就够了。我不需要一个巨型模型来回答“这是不是每周讨论帖?”一个 2B 的决策模型对这个任务来说大小正合适,而我能选它,只因为它是开放的。
而且不只是模型。Ghostify 自下而上都构建在开源之上:WXT、React、Dexie、AI SDK、Biome、Vitest……还有 Feedgrab,我把它拆分出来是因为我希望它能被复用。如果有人想为 YouTube 做一个“隐藏重播”扩展,或者为一个没有 RSS 的网站做信息流阅读器,最难的部分已经做好了。MIT 许可。放手去做吧。
闭源 API 也许能给我更聪明的摘要。开源模型给了我一个我真正愿意用在我读的所有内容上的工具。
我的智能体会话
整个项目都是在智能体会话中构建的,遵循上面的 specx 流程。查看它们的最好方式是提交历史:每个任务一个提交,从Scaffold WXT React project到Add auto-ghostify rules with rescue。
它的精神
我一直在想这个名字。
大多数“隐藏这个”功能实际上是“删除这个”。你一点,它没了,你再也找不到它。这一直让我觉得有点不对劲。我确实读过那篇帖子。其中有些还不错!我只是不需要每次打开主页都看到它们。
所以在 Ghostify 里,没有东西会死。它会变成幽灵。它留在机器里,带着标题、作者、一小段摘要和几个标签,等你把它召唤回来。有点像帖子的亡灵节:不在这里了,但也没被遗忘。
而在某个归档的仓库里,myDEV 正安息着,知道这一切都是由它开始的。🕯️
奖项类别
-
Gemma 最佳应用:Ghostify 使用Gemma 4 E2B(
gemma4:e2b)做摘要和标签,使用EmbeddingGemma 2(embeddinggemma-2)做语义搜索,两者默认都通过 Ollama 在本地运行。
感谢阅读!如果你的一篇帖子从我的信息流里消失了,别往心里去。这只说明我可能已经读过它、看过它,或者以后会读它。👻





