tasox/ti-agent
GitHub: tasox/ti-agent
一款 AI 驱动的威胁情报简报工具,自动抓取并分析安全 RSS 订阅源,借助 Claude 生成带可溯源引用的结构化分析报告。
Stars: 0 | Forks: 0
# TI Agent — AI 驱动的威胁情报简报工具
TI Agent 是一个全栈本地应用程序,它从 RSS/Atom 订阅源中获取真实的安全文章,将其传递给 Claude 进行分析,并生成包含内联引用的结构化威胁情报简报,引用可直接链接到源文章。
与那些仅仅将订阅源 URL 提供给 LLM 并祈祷好运的工具不同,TI Agent 会在服务端实际获取并解析订阅源,根据您选择的日期窗口过滤文章,并将真实内容发送给 Claude。报告中的每一个声明都可以追溯到特定的文章,并且每个 `[N]` 引用都是指向该文章 URL 的可点击链接。

## 功能
- **97 个内置安全订阅源**,分为 6 个类别:IOC 订阅源、漏洞、威胁情报、红|蓝|紫队、安全新闻、CTF 与学习
- **真正的 RSS/Atom 获取** — 在服务端获取并解析文章,并在 Claude 处理之前根据您的日期窗口进行过滤
- **内联引用** — 报告中的每一个事实性声明后面都带有指向源文章的 `[N]` 徽章
- **权威参考文献部分** — 生成后从实际的源映射中重建,确保完整性且无重复
- **4 种导出格式** — Markdown(带有超链接引用)、HTML、PDF(深色主题,可直接打印)、DOCX(标准的基于 ZIP 的 `.docx`)
- **成本追踪与预测** — 单次运行的 token 用量、实际与预估成本的准确性、30 天预算预测
- **完全持久化** — 所有报告、配置和自定义订阅源均保存至 SQLite;在服务端重启后依然存在
- **自定义订阅源** — 添加任何带有名称和类别的 RSS/Atom 订阅源 URL
- **订阅源管理** — 启用、禁用或永久删除任何订阅源(内置或自定义)
- **配置导出/导入** — 以 JSON 格式快照并恢复您的整个配置
- **4 种 Claude 模型** — Haiku 4.5(默认)、Sonnet 4.5、Sonnet 4.6、Opus 4.5
## 工作原理
```
┌─────────────────────────────────────────────────────────┐
│ Browser (React) │
│ CONFIG tab → select feeds, date window, model, focus │
│ │ │
│ Click "Generate Briefing" │
└────────────────────────┬────────────────────────────────┘
│ POST /api/fetch-feeds
▼
┌─────────────────────────────────────────────────────────┐
│ Express Server :3001 │
│ │
│ 1. Fetch all selected feeds in parallel (8s timeout) │
│ 2. Parse RSS 2.0 / Atom 1.0 XML │
│ 3. Filter articles to date window │
│ 4. Cap at 15 articles per feed │
│ 5. Return { articles[], errors[] } │
└────────────────────────┬────────────────────────────────┘
│ articles arrive in browser
│ POST /api/analyze
▼
┌─────────────────────────────────────────────────────────┐
│ Express Server :3001 │
│ │
│ Proxy to Anthropic API with prompt containing: │
│ - Numbered article list [1]..[N] with title/URL/date/ │
│ summary for each article │
│ - Citation rule: cite [N] after every claim │
│ - Structured report sections │
└────────────────────────┬────────────────────────────────┘
│ Claude response
▼
┌─────────────────────────────────────────────────────────┐
│ Browser (React) │
│ │
│ - rebuildReferences() strips Claude's References │
│ section and rebuilds it from the source map │
│ - renderMarkdown() renders [N] as clickable links │
│ - Report saved to SQLite with source_map JSON │
└─────────────────────────────────────────────────────────┘
```
### 报告部分
每份报告都遵循以下结构:
| # | 部分 | 描述 |
|---|---------|-------------|
| 1 | Data Window | 涵盖的日期范围和文章数量 |
| 2 | Executive Summary | 3-4 句 CISO 级别的摘要及引用 |
| 3 | Top Threats | 最多 5 个排名的威胁 (CRITICAL / HIGH / MEDIUM) |
| 4 | IOC Highlights | 仅包含文章中真实的 IOC — IP、域名、哈希值 |
| 5 | CVE Watch | 以表格格式明确提及的 CVE |
| 6 | Threat Actor Spotlight | 具名的攻击者及其 TTP 和 MITRE ID |
| 7 | Analyst Recommendations | 3 个可操作的步骤 |
| 8 | MITRE ATT&CK Coverage | 表格格式的技术列表 |
| 9 | References | 完整且去重的引用文章列表 |
## 要求
| 要求 | 版本 | 备注 |
|-------------|---------|-------|
| Node.js | **22.x** | Express 4 在 Node 22 上会崩溃 — 必须使用 Node 22 |
| macOS | 12+ | 已在 macOS 上测试;兼容 Linux;Windows 未测试 |
| Anthropic API key | — | `sk-ant-api03-...` 格式 |
| Xcode CLI tools | — | 编译 `better-sqlite3` 本地构建所需 |
## 依赖项
### 运行时 (npm)
| 包 | 版本 | 用途 |
|---------|---------|---------|
| `express` | `^5.x` | HTTP 服务器和路由。**必须是 v5 版本** — Express 4 与 Node 22 内置的 `path-to-regexp` 不兼容 |
| `cors` | `^2.x` | 跨域标头,以便运行在 :3000 上的 React 开发服务器可以调用 :3001 |
| `node-fetch` | `@2.x` | 用于获取 RSS 订阅源和代理至 Anthropic 的 HTTP 客户端。**必须是 v2 版本** — v3 仅支持 ESM,与 CommonJS 的 `require()` 不兼容 |
| `better-sqlite3` | `^9.x` | 同步 SQLite 驱动程序。需要本地构建工具(macOS 上的 Xcode CLI) |
### 内置 (Node.js — 无需安装)
| 模块 | 用于 |
|--------|---------|
| `path` | 解析相对于 `server.js` 的数据库文件路径 |
| `fs` | 启动时数据库文件状态检查 |
| `zlib` | `deflateRawSync` 用于构建有效的 `.docx` ZIP 归档 |
### 前端 (包含在 Create React App 中)
| 包 | 用途 |
|---------|---------|
| `react` | UI 框架 |
| `react-dom` | DOM 渲染 |
| `react-scripts` | CRA 构建工具链、开发服务器、Babel |
### 外部服务
| 服务 | 用于 |
|---------|---------|
| Anthropic API | Claude 推理 (`/v1/messages`) — 通过本地服务器代理,API key 绝不存储在磁盘上 |
| Google Fonts(可选) | HTML/PDF 导出中的 IBM Plex Mono 字体 — 离线时回退到 Courier New |
## 安装说明
### 1. 前置条件
```
# macOS — 安装 Xcode command line tools(better-sqlite3 必需)
xcode-select --install
# 如果尚未安装,通过 Homebrew 安装 Node 22
brew install node
node --version # should print v22.x.x
```
### 2. 克隆仓库
```
git clone https://github.com//ti-agent.git
cd ti-agent
```
### 3. 安装依赖
该仓库附带了一个现成的 `package.json`,涵盖了前端和后端的所有依赖:
```
npm install
```
这一步将一次性安装 `react`、`react-dom`、`react-scripts`、`express@5`、`cors`、`node-fetch@2` 和 `better-sqlite3`。
### 4. 项目位置
**重要提示:** 请勿将项目放在 iCloud Drive (`~/Library/Mobile Documents/`) 内。iCloud 的同步机制会干扰 SQLite 的 WAL 日志文件,并可能导致数据库损坏或数据库文件不可见。请将其克隆到本地路径,例如 `~/Projects/ti-agent`。
## 运行说明

在项目目录中打开两个终端窗口:
```
# Terminal 1 — backend(端口 3001)
node server.js
```
您应该会看到:
```
📂 DB path: /Users/you/Downloads/ti-agent/ti-agent.db
📦 DB file size: 32768 bytes
📦 Database ready at /Users/you/Downloads/ti-agent/ti-agent.db
✅ TI Agent server → http://localhost:3001
```

```
# Terminal 2 — frontend(端口 3000)
npm start
```

在浏览器中打开 **http://localhost:3000**。
## 首次运行
1. 转到 **⚙ CONFIG** 标签页
2. 输入您的 Anthropic API key — 它仅保存在内存中,并在每次页面刷新时清除,绝不写入磁盘
3. 选择您的订阅源和日期窗口
4. 点击 **⚡ GENERATE BRIEFING**
按钮标签将循环显示两个阶段:
- `◌ FETCHING N FEEDS…` — 正在下载并解析 RSS/Atom 订阅源
- `◌ ANALYZING WITH AI…` — Claude 正在处理文章
## 数据库
SQLite 数据库 (`ti-agent.db`) 会在首次运行时自动在 `server.js` 所在的目录中创建。它包含三个表:
### `config`
用于存储所有 UI 偏好设置的键/值存储。值均已进行 JSON 序列化。
| 键 | 类型 | 描述 |
|-----|------|-------------|
| `selectedIds` | `string[]` | 选择用于分析的订阅源 ID |
| `selectedModelId` | `string` | 当前生效的 Claude 模型 |
| `query` | `string` | 分析焦点 prompt |
| `schedule` | `string` | 计划任务偏好标签 |
| `maxTokens` | `number` | Claude 最大输出 token 数量 |
| `datePreset` | `number` | 日期预设数组的索引 |
| `customFrom` / `customTo` | `string` | 自定义日期范围 (YYYY-MM-DD) |
| `disabledIds` | `string[]` | 暂时禁用的订阅源 |
| `deletedIds` | `string[]` | 永久删除的订阅源 |
### `custom_feeds`
用户添加的 RSS/Atom 订阅源。
| 列 | 类型 | 描述 |
|--------|------|-------------|
| `id` | TEXT PK | 唯一标识符 |
| `name` | TEXT | 显示名称 |
| `xmlUrl` | TEXT | RSS/Atom 订阅源 URL |
| `url` | TEXT | 人类可读的网站 URL |
| `category` | TEXT | 订阅源类别 |
| `color` | TEXT | UI 的十六进制颜色代码 |
| `created_at` | TEXT | ISO 时间戳 |
### `reports`
生成的简报及其完整元数据。
| 列 | 类型 | 描述 |
|--------|------|-------------|
| `id` | INTEGER PK | 自动递增 |
| `timestamp` | TEXT | 人类可读的生成时间 |
| `query` | TEXT | 所使用的分析焦点 |
| `date_from` / `date_to` | TEXT | 日期窗口 |
| `sources` | INTEGER | 选择的订阅源数量 |
| `model_id` / `model_name` | TEXT | 所使用的 Claude 模型 |
| `input_tokens` / `output_tokens` | INTEGER | 实际 token 使用量 |
| `input_cost` / `output_cost` / `total_cost` | REAL | 实际花费(美元) |
| `est_input_tok` / `est_output_tok` / `est_total_cost` | REAL | 执行前预估 |
| `body` | TEXT | 完整的 Markdown 报告 |
| `source_map` | TEXT | 用于引用的 JSON 映射 `{"1": {name, url}, ...}` |
| `created_at` | TEXT | ISO 时间戳 |
### 数据迁移
服务器在启动时运行非破坏性迁移。向 schema 添加列不会破坏现有数据库 — `ALTER TABLE` 语句被包裹在 `try/catch` 中,因此已存在的列会被静默跳过。
## API 参考
所有 endpoint 均位于 `http://localhost:3001`。
| 方法 | 路径 | 描述 |
|--------|------|-------------|
| `GET` | `/health` | 服务器健康检查,返回数据库路径 |
| `POST` | `/api/fetch-feeds` | 获取并解析特定日期窗口的 RSS/Atom 订阅源 |
| `POST` | `/api/analyze` | 代理至 Anthropic `/v1/messages` |
| `GET` | `/api/config` | 加载所有已保存的配置 |
| `PUT` | `/api/config` | 保存配置(始终排除 API key) |
| `GET` | `/api/config/export` | 以 JSON 格式下载完整的配置及自定义订阅源 |
| `POST` | `/api/config/import` | 从导出的 JSON 恢复配置 |
| `GET` | `/api/feeds/custom` | 列出自定义订阅源 |
| `POST` | `/api/feeds/custom` | 添加自定义订阅源 |
| `DELETE` | `/api/feeds/custom/:id` | 移除自定义订阅源 |
| `GET` | `/api/reports` | 列出所有报告(不含 body 字段) |
| `GET` | `/api/reports/:id` | 获取包含 body 的单份报告 |
| `POST` | `/api/reports` | 保存生成的报告 |
| `DELETE` | `/api/reports/:id` | 删除报告 |
| `GET` | `/api/costs` | 获取成本台账记录 |
| `GET` | `/api/reports/:id/export?format=` | 导出报告 (md / html / pdf / docx) |
### `POST /api/fetch-feeds`
请求 body:
```
{
"feeds": [
{ "id": "talos", "name": "Cisco Talos", "xmlUrl": "https://blog.talosintelligence.com/feed", "url": "https://blog.talosintelligence.com" }
],
"dateFrom": "2025-01-01",
"dateTo": "2025-01-07"
}
```
响应:
```
{
"articles": [
{
"title": "Article title",
"url": "https://...",
"date": "2025-01-03",
"summary": "First 800 chars of article text...",
"feedName": "Cisco Talos",
"feedIndex": 1
}
],
"errors": [
{ "feed": "Some Feed", "error": "Timeout" }
]
}
```
订阅源获取器行为:
- 使用 `Promise.allSettled` 并行获取所有订阅源
- 每个订阅源有 8 秒的超时限制 (`AbortController`)
- 在发送给 Claude 之前,每个订阅源最多限制 15 篇文章
- 不在日期窗口内的文章将被过滤掉
- 失败的订阅源(超时、HTTP 错误、解析错误)会出现在 `errors[]` 中,并在报告中予以注明
## 导出格式
| 格式 | 引用 | 表格 | 备注 |
|--------|-----------|--------|-------|
| `.md` | `[N](url)` Markdown 超链接 | GFM 竖线表格 | 兼容 Obsidian、GitHub、Typora |
| `.html` | `` 上标链接 | 带有深色 CSS 的 `` | 独立存在,深色主题与应用匹配 |
| `.pdf` | `` 上标链接 | 带有深色 CSS 的 `` | 打开打印对话框 — 在 Chrome 中启用“背景图形” |
| `.docx` 蓝色上标 `[N]` 文本 | `