informer

command module
v0.10.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 20, 2026 License: Apache-2.0 Imports: 3 Imported by: 0

README

informer

每天把「日期信息 + 每日鸡汤 + 订阅文章推荐」汇总成一条消息,写入本地 Markdown 日报,并推送到钉钉或飞书机器人。

订阅源支持四种解析方式:标准 feed(RSS/Atom)、正则匹配网页、JSON 接口,以及交给命令行 AI Agent 去找。

推荐使用桌面版:图形化管理分类与订阅、浏览日报、检索文章库、配置参数与机器人。需要在服务器上定时推送时,再用命令行,见 cli-usage.md。两者共用同一个数据目录和 Service 层,可以同时使用。

一、桌面版(推荐)

cmd/informer-ui 是 Wails v3 + Vue 3 + Naive UI 的桌面入口。窗口顶部分为四个 Tab:

Tab 能做什么
订阅 左侧分类树:新增 / 编辑 / 删除分类,用整数「排序值」决定顺序(越小越靠前,相同排序值按分类 ID 排)。右侧按分类筛选的订阅卡片:展示解析类型、抓取状态、所属分类、错误信息,卡片右上角的开关直接启停订阅,底部可「测试抓取」(真实抓取,但不写库、不改订阅状态)、编辑、删除;抓取失败的订阅上还会多出一个「AI 诊断修复」按钮,见下文。测试抓取的抽屉底部有「执行日志」折叠面板,抓取过程中实时输出:请求地址与 HTTP 状态码、正则/JSON 路径的匹配情况、逐条解析出的标题与链接;agent 类型还会显示完整提示词、agent 每一次搜索与网页抓取、以及返回的原文。抓取中面板自动展开(日志就是进度),成功后自动折叠,失败则保持展开。右上角可「导出 / 导入」全部订阅配置,见下文。新建订阅弹窗右上角还有「AI 定义配置」,用对话的方式让 AI 试出一份能用的配置,见下文。
日报 左侧按「年 → 月 → 日」折叠的日期列表,点击某天在右侧全宽渲染当天的 Markdown。单日内容一次性加载,不分页。
文章库 已入库文章的游标翻页列表,可按分类、订阅、关键字筛选,显示通知时间;标题点击后在系统浏览器中打开。
设置 读写 informer.json 的抓取与推荐参数、配置定时推送(仅桌面端,应用打开时生效)、手动触发一次推送、配置机器人地址与 HTTP 代理(写入 informer.json)、执行「重建历史索引」。

窗口右上角的「日志」按钮打开系统运行日志面板,见下文。

正式发布构建会在启动时及之后每 24 小时检查一次 GitHub Releases;若有新版本则后台下载并校验,右上角出现「重启生效新版本」,点击后替换二进制并重启。开发版(version=dev)不会发起检查。

系统运行日志

桌面版从 Finder 或开始菜单启动,stdout 没有人看得到:凌晨定时推送失败,第二天没有任何痕迹可查。所以进程把自己写出的每一行日志同时留一份在内存里(默认最多 2000 行,stdout 照旧输出),窗口右上角的「日志」按钮打开面板即可翻看:

  • 内容:定时任务的触发与结果、每次抓取的请求与解析、推送到机器人的结果、启动失败的原因——即订阅页「测试抓取」里那种执行日志,加上不属于任何一次测试抓取的后台运行记录。
  • 筛选:面板顶部工具条按级别(全部 / 警告与错误 / 仅错误)和关键字过滤;「复制」复制当前筛选出的日志,方便贴进 issue,「清空」丢掉已有的行只看接下来发生的事。
  • 跟随:面板打开时每秒读取一次新增行并滚到底部;向上滚动即暂停跟随,回到底部恢复。也可以关掉「自动刷新」,改用「刷新」按钮手动读。
  • 边界:日志只活在本次运行的内存里,不落盘,重启应用后清空;超过上限时最早的行被覆盖,面板会明确写出「已省略较早的 N 条」而不是假装日志是连续的。启动失败时这个面板照常可用——那正是最需要它的时候。
AI 诊断修复

网页改版之后,订阅配的正则或 JSON 路径就不再匹配,卡片变成「抓取失败」。抓取失败的订阅卡片上有「AI 诊断修复」按钮,点开后由配置好的 agent(同「设置」里那套接口地址 / 密钥 / 模型)读页面、找原因、试配置:

  • AI 看到的就是 informer 看到的:诊断期间 agent 有三个 informer 自己的工具——读订阅配置与失败原因、按 informer 自己的方式抓取页面原文(带上该订阅的 curl 请求头和全局代理)、用候选配置真实试跑一次解析。
  • 探索与判定是分开的:agent 也可以用 WebSearch / WebFetch 去探索——找这个站点的 feed 地址、确认它是不是改版了、查接口文档,一个只能盯着一份文档看的诊断没法形成假设。但判定必须落回 informer 自己的字节:任何结论都要以上面那个抓取工具取到的内容为准,任何候选配置都要真的试跑过。informer 抓取时带着订阅自己的请求头和代理,换一种抓法拿到的字节不一样,据此做的「页面格式变了」的判断就是错的。
  • 可以反复试:试跑不写任何数据,agent 可以「改正则 → 试 → 再改」直到解析出合理的文章列表,这正是正则调试真正花时间的地方。
  • AI 没有保存配置的能力。它只给出结论和建议的改动;informer 拿到建议后自己再复核一次(用建议的配置真跑一遍解析),复核解析出文章才会亮出「应用修复」按钮。agent 自称修好、而复核没通过时,面板会明说这件事并且不提供一键应用。
  • 改动是逐字段展示的:抽屉里列出每个字段的「原值 → 新值」,以及复核解析出的前几条标题(请确认那确实是文章,而不是导航栏或广告)。确认之后点「应用修复」才写库,并立刻重新抓取一次——修好了状态就恢复正常,仍然失败则保留失败状态和新的错误信息。
  • 能改的字段是有限的:只有订阅地址、自定义请求、解析类型及其参数(正则 / 表达式 / JSON 路径 / Agent 提示词)和链接重定向;标题、分类、权重、启用状态不在它的职责范围内。
  • 修不好也会说清楚:站点关闭、需要登录、内容改由 JS 渲染等等,面板会给出结论和建议(换订阅地址、改用 agent 类型抓取、或者干脆删掉这个订阅),而不是硬凑一个「能匹配到东西」的正则。
  • 诊断通常要一到几分钟,抽屉底部的「诊断日志」实时输出每一步:重试当前配置、agent 每一次工具调用、复核结果。同一时间只允许跑一个诊断。
  • 实现方式:informer 把这三个工具做成一个 MCP server,由 agent 命令行以 --strict-mcp-config 加载——也就是说除了这三个工具和只读的 WebSearch / WebFetch 之外,这次会话再碰不到别的东西,尤其够不到数据库(工具进程读的是一份临时快照文件,运行结束即删除)。
AI 定义配置

新建一个订阅,难的从来不是填表,而是要自己判断这个站点该用哪种解析方式、然后把正则或 JSON 路径写对。新建订阅弹窗右上角的「AI 定义配置」把这一步交给同一个 agent:点开之后弹窗变成一个聊天界面,你用大白话说清楚想订阅哪里的什么内容,剩下的它来试。

  • 它按固定顺序选解析方式,不是凭感觉:先试标准 feed(把你给的地址原样试一遍,再去页面 <head> 里找 <link rel="alternate">,再依次探 /feed/rss/atom.xml 等常见路径)——feed 是站点改版后唯一还能继续工作的形态,所以必须先排除;抓到的响应体是 JSON 就用 JSON 路径而不是正则;HTML 列表页才写正则;只有压根没有可抓地址、或内容由 JS 渲染时,才退到 agent 类型(那意味着每次定时抓取都要真跑一次 AI,慢且贵)。
  • 工具和诊断是同一套:按 informer 自己的方式抓页面原文、用完整候选配置真实试跑一次解析,外加只读的 WebSearch / WebFetch 用来探索(例如去找某个博客的 feed 地址)。同样是探索归探索、判定归 informer 的字节。
  • 「解析出东西」不算成功:标题必须是文章标题而不是导航栏、分页或广告;链接必须是每篇文章各自的地址——解析出好几条却都指向同一个地址,会被当场判为失败。
  • AI 依然没有保存的能力:它把配置通过一个专门的工具交给 informer(在回复里用文字描述配置不算数),informer 拿到之后自己再跑一遍解析核对,核对通过才会在聊天里出现「保存并新建订阅」按钮。点了才写库。
  • 提案是逐字段展示的:解析类型、地址、正则/路径逐条列出,下面是复核解析出的前几条标题——请确认那确实是文章。旁边可以选分类(也可以直接输入新分类名),标题用 AI 起的那个,不满意可以改。
  • agent 类型的提案不做自动复核,因为那会在当前 agent 里再启动一个 agent。这种提案会明确标注「未复核」,仍然允许保存,但保存后请用订阅卡片上的「测试抓取」验证一次。
  • 每一轮通常要一到几分钟,聊天下方的「执行日志」实时输出 agent 的每一次抓取和试跑。同一时间只允许跑一个 AI 任务(诊断和这里共用一个闸),一次对话最多 12 轮——每轮都会把整段对话重发一次,太长的对话不如重新开始。
安装

在仓库的 Releases 页面下载最新版本对应平台的安装包:

平台 手动安装 应用内更新使用
macOS informer-ui-<版本>-darwin-universal.dmg(universal,未签名) informer-ui-<版本>-darwin-universal.app.zip
Windows informer-ui-<版本>-windows-amd64-setup.exe(NSIS,内置 WebView2 引导) informer-ui-<版本>-windows-amd64.zip
Linux informer-ui-<版本>-linux-amd64.tar.gz.deb informer-ui-<版本>-linux-amd64.tar.gz

macOS 安装包未做代码签名与公证,首次打开(以及自更新替换后)可能需在「系统设置 → 隐私与安全性」中放行。

从源码运行

需要安装 Wails v3 CLIgo install github.com/wailsapp/wails/v3/cmd/wails3@v3.0.0-beta.8)、Node.js 与 C 编译器(桌面版依赖 CGO:原生 WebView 与 mattn/go-sqlite3):

cd cmd/informer-ui
wails3 dev      # 本地运行
wails3 build    # 出包,产物在 bin/
wails3 package  # 按当前平台打包(macOS .app / Windows NSIS / Linux 等)
导出 / 导入订阅

订阅页右上角的「导出」把全部订阅(不受左侧分类和过滤条件影响)写成一个 JSON 文件,「导入」再把这样的文件合并回来,用于换机器、备份或在两台机器之间同步订阅:

  • 导出的内容:每个订阅的完整配置(标题、URL、curl、解析类型及其参数、Agent 提示词、抓取选项、启用状态),分类按名字导出。数据库自增 ID 和抓取状态(正常 / 失败及错误信息)不导出,它们只属于本机。
  • 唯一键:有 URL 的订阅按 URL 认,没有 URL 的(Agent 订阅本来就没有地址)按标题认。
  • 导入是「追加 + 覆盖」:能对上唯一键的订阅覆盖其配置(本机的抓取状态保留),对不上的追加为新订阅,不会删除任何订阅。因此导入一个只含几条订阅的文件,只会新增和更新这几条。
  • 分类自动补齐:文件里的分类名本机没有时自动创建;分类名为空则归入「未分类」。
  • 单条失败不影响整体:某一条既没有 URL 也没有标题、或 Agent 订阅缺提示词等,会被跳过并在结果里逐条列出,其余照常导入。
  • 文件版本比当前程序新时整体拒绝导入,以免把不认识的字段悄悄丢掉;手写文件也可以直接用一个 JSON 数组(不带外层对象)。
重建历史索引

如果库里有一批文章的通知时间(informed_at)是空的,「设置 → 重建历史索引」可以从已经生成的日报 Markdown 里把这部分信息补回来:

  • 判定依据只有链接:从日报里提取文章链接,与库中文章的 URL 做精确匹配。只有「恰好匹配到一条、且该文章的通知时间为空」 才会补值,补的是该日报当天的本地 00:00(精度到天,因为文件名只能证明到天)。
  • 不覆盖、不编造:已经有通知时间的文章原样保留;库里找不到的链接、匹配到多条文章的链接,一律跳过并保持为空。
  • 可重复执行:结果幂等,第二次运行只会把第一次补好的记录报告为「已有时间」。
  • 执行后会给出扫描天数、链接数,以及成功 / 跳过(分「已有时间」「库中无此链接」「匹配到多条」)/ 失败的数量。
几条与数据安全相关的实现约定
  • 日报 Markdown 以关闭原始 HTML 的方式渲染,并在注入 DOM 前做一次消毒;报告里的链接一律走系统浏览器打开,不会让 WebView 自身跳转。
  • 日期只接受 2006-01-02 这一种写法并由后端校验,界面无法读到数据目录以外的文件。
  • 文章库翻页用文章 ID 作游标而不是 offset,定时任务在翻页期间插入新文章也不会造成重复或漏项;切换筛选条件会回到第一页。
当前不支持

不做拖拽排序、不做全文搜索;配置不迁移到数据库、不提供配置的版本历史或回滚,也不支持多用户 / 多环境配置。

二、数据目录

informer 的所有数据(informer.jsoninformer.secret.jsonfeed.dbdata/<年份>/<日期>.md)都存放在同一个数据主目录中:

INFORMER_HOME 数据主目录
未设置或为空 ~/.informer(Windows 为 %USERPROFILE%\.informer
已设置 该环境变量指向的目录(相对路径会转成绝对路径)

数据目录与可执行文件所在位置无关,因此从 Finder / 桌面快捷方式启动(不继承 shell 环境变量)时,找到的是同一份数据。 如果 INFORMER_HOME 指向的目录无法创建或不可写,informer 会直接报错退出,不会退回到另一份数据继续跑。 注意 crontab 中 ~ 取决于运行该任务的用户,显式写绝对路径更稳妥。

切换到另一个目录时,先把当前数据主目录整体复制过去,再修改 INFORMER_HOME

cp -a ~/.informer /data/informer-home
# 然后把 crontab 里的 INFORMER_HOME 改成 /data/informer-home

informer 不会自动同步多个数据主目录,也不支持同时使用多个 INFORMER_HOME

三、配置文件

informer.json 是一份人类可读的整份 JSON,不入库、不做版本历史。范例见 examples/informer.json

配置项 含义 取值
same_site_max_count 同一站点在一条消息中最多出现几篇 必须大于 0
feed_expire_days 文章多少天之后视为过期,不再推荐 必须大于 0
max_inform_feed_size 一条消息最多推荐多少篇文章 必须大于 0
max_fetch_num 每个订阅默认抓取的文章数上限 填 0 表示不做全局限制

前三项为 0 会让推荐算法选不出任何文章,因此被拒绝;超出合理范围的值同样会被拒绝,且被拒绝的保存不会改动文件。

还有一个顶层字段 webhook,存放钉钉 / 飞书机器人地址(不是敏感凭证):

配置项 含义 取值
webhook 推送用的机器人 webhook URL 字符串;留空或不写表示不推送

还有一个顶层字段 http_proxy,供 URL 抓取、桌面版应用内更新与 Agent 子进程共用:

配置项 含义 取值
http_proxy HTTP(S) 代理地址 http://127.0.0.1:7890;留空或不写表示不使用代理

有配置时,订阅抓取、每日鸡汤、机器人推送与桌面版检查/下载新版本会经共享 HTTP 客户端走代理;Agent(如 claude)子进程会注入 HTTP_PROXY / HTTPS_PROXY / ALL_PROXY。这与 agent.base_url(API 网关地址)无关。

还有一个顶层节 schedule,只被桌面版「定时任务」读取,命令行与系统 crontab 会忽略它(命令行的定时推送仍由 crontab 负责):

配置项 含义 取值
schedule.enabled 是否启用桌面版定时推送 true / false
schedule.time 每日推送时间(本机时区) 24 小时制 "HH:MM",如 "10:00"

桌面版定时仅在应用保持打开时生效,每个自然日最多成功推送一次(记录在数据目录的 informer.schedule-state,重启同一天不会再补推;失败会在下次轮询重试);当天过了设定时间才打开应用,会补推一次。

feed 节外还有一个顶层节 agent,供 agent 类型的订阅使用:

{
  "agent": {
    "provider": "claude",
    "base_url": "",
    "model": "",
    "allowed_tools": "WebSearch,WebFetch",
    "timeout_seconds": 300,
    "command": ""
  }
}
配置项 含义 取值
provider 使用哪个命令行 Agent 目前只实现了 claude(Claude Code);codex 已预留但本版本运行时会报错
base_url Agent 的接口地址 留空表示沿用本机上该 Agent 自己的配置
model 使用的模型 留空表示沿用 Agent 的默认模型
allowed_tools 允许 Agent 使用的工具,逗号分隔 留空使用默认 WebSearch,WebFetch
timeout_seconds 单次运行超时 填 0 表示使用默认 300 秒,合法范围 10 ~ 3600
command Agent 可执行文件 留空时会在 PATH、常见安装目录与登录 Shell 中自动查找 claude/codex,找到后写入配置;也可在设置页手动填写或点「自动查找」

API Key 不写进 informer.json,而是放在同目录的 informer.secret.jsonagent_api_key 字段里。 base_urlagent_api_key 都留空时,informer 直接运行本机 Agent,用的就是该 Agent 自身已登录的凭据—— 也就是说,只要本机 claude(或对应 Agent)能跑,agent 订阅就能跑,不需要额外配置。

桌面版从 Dock / Finder 启动时往往没有终端里的 PATH。command 留空时,informer 会在 PATH、Homebrew / npm / nvm 等常见目录以及登录 Shell 里查找可执行文件,并把绝对路径写回 informer.json;设置页也可以手动填写或点「自动查找」。

前置条件:本机已安装对应命令行 Agent(例如 claude --version 能正常输出)。命令行添加 agent 订阅的方式见 cli-usage.md

无论是命令行手写还是桌面版「设置」页保存,写入都遵守下面几条约定:

  • 保留未知字段:保存只替换 feedagentschedulewebhookhttp_proxy,文件里其它顶层字段(包括这个版本还不认识的字段)连同顺序一起原样保留。
  • 原子替换:先写同目录下的临时文件再 rename 覆盖,所以并发运行的 crontab 读到的要么是旧文件、要么是新文件,不会读到写了一半的内容。
  • 跨进程写锁:写入前会创建 informer.json.lock,两个「读—改—写」不会互相覆盖造成丢失更新。锁有等待上限, 等不到会直接报错而不是一直卡住;进程被杀留下的陈旧锁会在明显过期后被自动接管。
敏感配置文件 informer.secret.json

Agent API Key 不写进 informer.json,而是单独存放在数据主目录下的 informer.secret.json

{
  "agent_api_key": "sk-xxxxx"
}
  • 该文件无论新建还是改写,权限都会被设置并校验为 0600;权限无法落实时保存直接失败,不会以不安全的状态写下去 (Windows 不支持 Unix 权限位,这一步在 Windows 上无法校验)。
  • 桌面版「设置」页只显示「是否已配置」,不回传原文。
  • 机器人 webhook 写在 informer.json 里,不进入这个文件;若旧版本曾把 webhook 写在这里,运行时仍会读取,并在下次保存地址时迁出。

四、命令行

服务器定时推送、或不方便开图形界面时,使用命令行版 informer。安装、订阅管理、crontab 见 cli-usage.md

Release 里的命令行包名为 informer-cli-<版本>-<平台>.tar.gz(Windows 为 .zip),解压后命令为 informer

五、参与开发

make test      # 跑测试并生成覆盖率
make format    # 按 golangci-lint 配置格式化
make check     # 许可证头检查 + golangci-lint

v* 标签后由 .github/workflows/release.yml 在 macOS / Windows / Linux 三个原生 runner 上构建桌面版与命令行版, 并聚合发布到 GitHub Release(含桌面安装包与应用内更新用的 zip/tar.gz,命令行 informer-cli 压缩包,以及固定名 SHA256SUMS)。 当前阶段不做代码签名 / 公证。

许可证:Apache License 2.0

Documentation

Overview

Package main is the thin root entry kept so that `go install github.com/vogo/informer@master` still produces the informer binary. It shares the exact startup function with cmd/informer, the formal command line entry, so the two cannot fork.

Directories

Path Synopsis
cmd
informer command
Command informer is the formal command line entry of informer.
Command informer is the formal command line entry of informer.
agent
Package agent turns a plain language instruction into a list of articles by driving a coding agent command line.
Package agent turns a plain language instruction into a list of articles by driving a coding agent command line.
cli
Package cli parses command arguments, calls the service layer and formats the output.
Package cli parses command arguments, calls the service layer and formats the output.
compose
Package compose works out the configuration of a subscription that does not exist yet, by talking to the person who wants it.
Package compose works out the configuration of a subscription that does not exist yet, by talking to the person who wants it.
configstore
Package configstore reads and writes the human editable json configuration files of informer.
Package configstore reads and writes the human editable json configuration files of informer.
diagnose
Package diagnose repairs a subscription whose parse stopped working.
Package diagnose repairs a subscription whose parse stopped working.
home
Package home resolves the single active data directory of informer and migrates the legacy executable-directory layout into it.
Package home resolves the single active data directory of informer and migrates the legacy executable-directory layout into it.
logbuf
Package logbuf keeps the recent output of the global logger where a window can read it.
Package logbuf keeps the recent output of the global logger where a window can read it.
mcp
Package mcp is the smallest Model Context Protocol server informer needs.
Package mcp is the smallest Model Context Protocol server informer needs.
parsecfg
Package parsecfg is the part of a subscription that decides how its page is turned into articles, and the tools for trying one out.
Package parsecfg is the part of a subscription that decides how its page is turned into articles, and the tools for trying one out.
runlog
Package runlog carries the log of one single run to whoever asked for it.
Package runlog carries the log of one single run to whoever asked for it.
scheduler
Package scheduler fires the desktop app's one daily inform run.
Package scheduler fires the desktop app's one daily inform run.
service
Package service holds the business entry point of informer.
Package service holds the business entry point of informer.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL