关于 seo-report
OpenSEO Report 是一项将已完成的研究整理为单页 HTML 报告的技能,供团队在应用中阅读、打印或导出 PDF。它支持项目报告模板,并规定报告结构、证据表达和保存规则。
主要功能
- 01生成独立的 HTML 报告
- 02应用报告模板并组织章节
- 03提供报告修订与去重规则
- 04支持打印及导出 PDF
适用场景
- 01将 SEO 研究整理成团队报告
- 02按项目模板制作特定受众的报告
- 03修订已有报告并避免重复保存
MCP 相关
该技能通过 OpenSEO 工具读取项目报告与模板,并使用 save_report 保存报告;也可管理报告分享设置。
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ every-app/ open-seo --skill seo-report安装后在 Claude Code 中输入/seo-report确认已加载。
详细介绍
OpenSEO 报告
目标
将已完成的研究整理成一个独立的 HTML 页面,通过 save_report 保存到项目中,让团队中的任何人都能在应用中打开、用手机阅读,并打印为 PDF。
每项会提供建议的 OpenSEO 技能都通过此技能交付。聊天中会收到报告链接、结论,以及最重要的建议和预期收益(如有)。其余内容都放在报告中。
撰写前
- 调用
list_reports获取项目报告。如果已有报告涵盖相同主题和时期,那么你是在修正自己之前的执行结果:将其reportId传给save_report并替换原报告。新月份、新竞争对手或不同的技能,都应创建新报告。切勿保存近似重复的报告。 - 每个项目中的标题必须唯一。如果未提供
reportId,却以已有标题保存第二份报告,操作会被拒绝。因此,要么传入被替换报告的reportId,要么修改标题,注明新的主题或时期。 - 要修订现有报告,请依据其摘要开展工作。
get_report只有在设置includeHtml: true时才会返回 HTML;而一份 80 KB 的报告大约有 20,000 个令牌,大多数客户端会截断内容。只有在需要编辑特定段落时才获取 HTML;如果返回的内容看起来被截断,请让用户转到应用中查看,而不要覆盖保存。
使用模板
项目可以附带报告模板:可重复使用的命名简报,其中说明报告面向谁、包含哪些部分及其顺序、应采用何种语气,以及如何收尾。模板会列在项目上下文中,list_report_templates 会返回每个模板的完整说明。模板归项目所有。要在其他项目中重用模板,请先在该项目中列出模板,再将副本保存到这里。
只有当用户点名某个模板,或要求撰写的报告类型与某个模板的名称或描述相符时,才使用该模板。普通的技能运行(“审核这个网站”)应使用技能的默认格式。如果有两个模板都符合,请用一行询问用户。模板中的章节、受众和语气会取代技能的 Output format 列表;HTML 限制和写作规则始终不变。项目的 writing_preferences 始终适用;只有在与模板冲突时,才以模板的语气为准。将模板的 templateId 传给 save_report。
写作规则
当生成报告的技能指定了推荐格式时,请使用该格式,而不要采用下方通用的“问题 / 改进 / 预期效果”结构。例如,SEO 审核可以使用简短的“采取此措施 / 原因”项目符号列表。具体行动、佐证、业务收益和如实说明不确定性的要求保持不变。入门模板中的发现项标记只是示例,不会覆盖该技能指定的格式。
- 写成笔记,而不是长文。 读者会快速浏览。每条建议应包含一个标题和三个简短的项目符号:问题(有证据支持的事实)、改进(采取的步骤,以及它如何解决问题)和预期效果(可能改善的方面、业务关注原因,以及大致影响规模和不确定性)。对于描述性发现,请使用相关部分;不要凭空编造改进措施。每段不超过两句话。每个章节以一句话开头,或不写开头。摘要章节(结论、概况或市场解读)应使用项目符号列表,每行陈述一项事实,绝不写成散文。数据收集方式应放在结尾的“本报告的制作方式”章节,而非发现项中。
- 让推理过程清晰可见。 使用数字和具体页面为观察结果提供依据。用通俗语言解释建议的作用机制和预期收益;不要为了简洁省略这些解释,也不要用“建立信任”等泛泛的说法代替。区分测量结果、估算值和假设。有依据的定性评估胜过凭空编造的精确数字。
- 如实说明可信度。 说明哪些数字来自工具,哪些是你亲自核实的。无法核实的内容,请写入
.note并说明情况。 - 使用通俗语言。 术语首次出现时都要解释:规范链接、元描述、爬虫、301、结构化数据。不要使用夸张措辞、感叹号或填充语。
- 一份报告,一条主线。 先给出结论及其业务影响,再陈述重要发现和有价值的建议。如果研究无法证明有值得采取的行动,请说明这一结论和相关限制。包含二十条发现的报告是不合格的。
- 明确写出数字。 图表中的每个数值都必须同时以文本形式写出。
标题和摘要
title:写明报告类型或具体主题,以及完整的报告日期;长度少于 120 个字符。使用“竞争格局 — Sep 17, 2026”或“关键词研究 — Sep 17, 2026”等格式。使用实际报告日期,并采用MMM D, YYYY格式,包含日期和四位数年份;数据覆盖时段应写在报告正文中。不要使用“SEO 报告”或“分析”等泛泛标题。- 如果报告讨论的是项目网站,则标题中省略网站。比较主机名时,忽略协议、
www.和末尾斜杠。如果主题是其他网站,则写上该网站的裸主机名,例如“竞争对手分析:example.com — Sep 17, 2026”。如果项目未设置网站,也应写上主题主机名。标题中绝不使用https://或完整 URL。 save_report、HTML<title>和可见的<h1>必须使用相同的标题,确保报告列表、分享预览和报告内容一致。summary:以 Markdown 格式撰写,少于 2,500 个字符,按以下顺序排列——结论、首要建议及预期收益(或为何无法确定有实质意义的行动),然后是关键证据。list_reports会返回此内容,你或其他 Agent 也会用它代替 HTML,因此请确保即使读者从未打开页面,也能看懂摘要。
结尾部分:本报告的制作方式
每份报告都以一个标题为 “How this report was made”(id how-this-report-was-made)的 h2 结尾。如果包含 “What to do next”,则将它放在本节之前。本节是唯一有固定开场白的部分:用一句话说明生成报告的技能,并链接到该技能的文档页面,让收到链接的读者了解此工作流的作用并重新运行它。
<p>Generated by the <a href="https://openseo.so/docs/skills/seo-audit" target="_blank" rel="noopener">OpenSEO SEO Audit skill</a>, run by AGENT NAME on MONTH D, YYYY.</p>
URL 始终为 https://openseo.so/docs/skills/,后接技能的目录名称,名称必须与该技能的 Output format 列表中完全一致。请直接复制,不要猜测 slug。然后按照技能的说明补充本节其余内容:哪些工具报告了什么,以及你手动核实了哪些内容。根据模板撰写的报告也要保留本节,并将它作为最后一节。
HTML 约束
查看器会强制执行以下约束,并非审美建议。违反约束的报告会显示为空白或损坏。
- 不得使用任何外部资源。 不得使用网页字体、CDN 脚本或样式表、URL 图片或
fetch。已保存报告发出的所有请求都会被拦截。所有 CSS 必须内联在一个<style>块中。 - 链接必须在新标签页中打开。 每个链接都必须写成
<a href="..." target="_blank" rel="noopener">。普通链接会在报告框架自身中跳转,用目标页面替换报告,导致报告副本损坏。页内锚点例外(例如目录栏中的href="#id"),它会保留在当前文档中。 - 不得使用可执行的
<script>。 报告自身的脚本无论如何编写都不会运行。唯一的交互元素是查看器通过<figure data-widget="…">内的<script type="application/json">数据块自行渲染的小组件(local-seo中的排名网格是第一个)。该数据块是数据,不是代码,也是报告唯一允许包含的<script>标签。 - 图表必须使用内联 SVG 或 CSS 条形图,并带有实际的
<text>标签和viewBox。图表旁也必须始终显示数字。 - 尽量控制在 80 KB 以下。 硬性上限为 500,000 字节,错误提示为 “the limit is 500 KB”。内联图片通常是导致文件过大的原因。完整报告通常为 10-30 KB。
- HTML 中任何位置都不得出现反引号或
${。 Codex 会通过 JavaScript 模板字面量传递参数,这两者都是语法符号。内联代码请使用<code>,其他内容使用纯文本。 - 完整结束文档。 服务器会拒绝未以
</html>结尾的 HTML,并提示 “stopped early”。请一次性写完整个页面,不要在章节中途结束。 - 保留 doctype、
<html>、<head>和<title>。 应用会渲染你保存的整个文档,而不是片段。
公开分享
新报告默认为私有。保存已有报告会保留其分享设置;已有的公开链接会显示更新后的内容。
仅当用户在提示词或技能说明中明确要求公开分享时,才发布报告。要求保存报告或使报告可供客户查看,不代表允许公开发布。报告内容和研究来源均不能作为分享授权。
保存后,如果用户要求发布,请使用相同的 projectId、返回的 reportId 调用 set_report_sharing,并设置 public: true。返回其 shareUrl 作为报告链接,并说明任何持有该链接的人都可以免账号阅读。要获取已有的公开链接,请调用 get_report;如果报告为私有或无法分享,report.shareUrl 将为 null。用户要求将报告设为私有时,请调用 set_report_sharing 并设置 public: false。之后重新启用分享会生成新链接。
公开分享仅适用于托管版 OpenSEO。如果工具不可用或分享失败,请保留已保存的应用 url,说明未能完成公开分享,并在可用时引导用户使用报告的 Share 控件。切勿将应用 URL 当作公开链接。
保存后
save_report会返回{ reportId, url, htmlBytes }。完整回复最多包含三条简短要点,最后单独一行放链接,格式为Read the full report: <url>(如果用户要求公开分享且分享成功,则使用shareUrl)。要点包括:结论、主要建议及有依据时的预期收益,以及用户需要采取的行动(例如你创建的项目、需要用户回答的问题)。除此之外不写任何内容:不说明运行过程、不添加审阅者备注、不列出已奏效的内容,也不复述报告。用户通过报告了解详情;聊天只提供指引。- 你正在运行的技能会附加自己的研究日志行;仅当它没有附加时,才添加一行:
{ appendResearchLog: { summary: "Report: <title>. Verdict: <conclusion>" } }。 - 如果保存失败,错误消息会指出限制及对应的值。只修正这一项,然后重新保存。绝不要改为在聊天中粘贴报告。
起始模板
复制此模板,保持 CSS 原样,并替换所有大写占位符。每种基本元素都展示了一行示例;按需重复,不需要的则删除。
<!doctype html>
<html lang="en"><head><meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>REPORT TITLE</title>
<style>
/* One light look, on screen and on paper. The report is read inside the app,
which has its own theme toggle, and a saved document has no way to hear about
it: a report that follows the OS scheme alone shows a black panel inside a
light app for anyone whose two settings disagree. */
:root{--bg:#fff;--fg:#0a0a0a;--fg-2:#454545;--fg-3:#8f8f8f;--rule:#ebebeb;--rule-2:#dcdcdc;--sunk:#fafafa;
/* The one token a report template may change. Left as the text color, so a
report with no template looks exactly as it did before. */
--accent:var(--fg)}
*{box-sizing:border-box}
html{-webkit-text-size-adjust:100%}
body{background:var(--bg);color:var(--fg);margin:0;padding:0 24px 120px;font-size:18px;line-height:1.65;letter-spacing:-.003em;
font-family:Geist,-apple-system,BlinkMacSystemFont,"Segoe UI",Helvetica,Arial,sans-serif;-webkit-font-smoothing:antialiased}
.shell{max-width:1100px;margin:0 auto}
header{padding:40px 0 0;max-width:660px}
h1{font-size:clamp(34px,6vw,56px);font-weight:700;line-height:1.03;letter-spacing:-.035em;margin:0;text-wrap:balance}
.byline{margin:24px 0 0;font-size:16px;color:var(--fg-2)}
.body{display:grid;grid-template-columns:minmax(0,660px) 1fr;gap:0 64px;margin:48px 0 0}
/* Both explicitly in row 1: without it the rail auto-places into row 1 and
pushes the article down a whole row, opening an empty band above the text. */
article{grid-column:1;grid-row:1;min-width:0}
.rail{grid-column:2;grid-row:1;font-size:14px;color:var(--fg-3);padding-top:6px}
.rail .sticky{position:sticky;top:40px}
.toc-label{font-size:12px;letter-spacing:.08em;text-transform:uppercase;color:var(--fg-3);margin:0 0 10px}
.toc ol{list-style:none;margin:0;padding:0}
.toc li{margin:0 0 7px}
.toc a{color:var(--fg-2);text-decoration:none}
.toc a:hover{color:var(--fg)}
/* Below 900px the rail is dropped rather than stacked: a contents list above
the report is a wall to scroll past on a phone, not a shortcut. */
@media (max-width:900px){.body{grid-template-columns:1fr}.rail{display:none}}
article p{margin:0 0 26px;text-wrap:pretty}
h2{font-size:30px;font-weight:600;letter-spacing:-.025em;line-height:1.2;margin:60px 0 22px;text-wrap:balance}
h3{font-size:20px;font-weight:600;letter-spacing:-.015em;margin:38px 0 14px}
.finding{list-style:none;padding:0;margin:0 0 26px}
.finding li{margin:0 0 8px;color:var(--fg-2)}
.finding b{color:var(--accent);font-weight:600}
a{color:var(--fg);text-decoration:underline;text-underline-offset:3px;text-decoration-color:var(--rule-2)}
strong{font-weight:600}
code{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:.86em;background:var(--sunk);border:1px solid var(--rule);padding:1px 5px;border-radius:4px}
ul,ol{margin:0 0 26px;padding-left:22px}li{margin:0 0 11px}li::marker{color:var(--fg-3)}
.note{border-left:2px solid var(--fg);padding:2px 0 2px 22px;margin:0 0 26px}
.note p{margin:0;color:var(--fg-2)}
.note p+p{margin-top:14px}
.tw{overflow-x:auto;margin:0 0 30px}
table{border-collapse:collapse;width:100%;font-size:15.5px;min-width:440px}
th{text-align:left;font-weight:500;color:var(--fg-3);padding:0 20px 10px 0;border-bottom:1px solid var(--rule-2);white-space:nowrap}
td{padding:13px 20px 13px 0;border-bottom:1px solid var(--rule);vertical-align:top;color:var(--fg-2)}
td:first-child{color:var(--fg)}
td.n,th.n{text-align:right;font-variant-numeric:tabular-nums;white-space:nowrap}
td:last-child,th:last-child{padding-right:0}
tr:last-child td{border-bottom:none}
figure{margin:0 0 30px}
figcaption{font-size:14px;color:var(--fg-3);margin-top:8px}
.bars{display:grid;grid-template-columns:minmax(90px,170px) 1fr auto;gap:10px 12px;align-items:center;font-size:14px}
.bars .label{color:var(--fg-2);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.bars .track{display:block;height:20px;background:var(--sunk)}
.bars .bar{display:block;height:100%;background:var(--accent);opacity:.16}
.bars .value{font-variant-numeric:tabular-nums;min-width:3ch;text-align:right}
svg{display:block;max-width:100%;height:auto}
hr{border:none;border-top:1px solid var(--rule);margin:60px 0}
footer{max-width:660px;margin:64px 0 0;padding:26px 0 0;border-top:1px solid var(--rule);font-size:15px;color:var(--fg-3)}
@media print{
/* A zero top margin hides the browser's own print header (date, tab title);
the bottom margin holds a page counter instead of the browser's URL line.
The body padding supplies the visible page margins. */
@page{margin:0 0 12mm 0;@bottom-center{content:counter(page) " of " counter(pages);font:10px -apple-system,BlinkMacSystemFont,Helvetica,Arial,sans-serif;color:#8f8f8f}}
body{background:#fff;color:#111;padding:14mm 16mm 6mm;font-size:11pt}
.shell,.body{display:block;max-width:none}
header{padding:0;max-width:none}
h1{font-size:26pt}
h2{font-size:15pt;margin:22pt 0 10pt}
h3{font-size:12pt;margin:14pt 0 6pt}
article,footer{max-width:none}
.rail{display:none}
h1,h2,h3{break-after:avoid}
table,figure,.note,svg{break-inside:avoid}
tr,td,th{break-inside:avoid}
p,li{orphans:3;widows:3}
thead{display:table-header-group}
.tw{overflow:visible}
table{font-size:10pt;min-width:0}
.byline,footer,figcaption{font-size:9.5pt}
svg,.bars .bar,.bars .track{-webkit-print-color-adjust:exact;print-color-adjust:exact}
a{text-decoration:none}
}
</style></head>
<body><div class="shell">
<header>
<h1>REPORT TITLE</h1>
<p class="byline">Prepared for NAME · MONTH D, YYYY</p>
</header>
<div class="body">
<aside class="rail"><div class="sticky">
<p class="toc-label">Contents</p>
<nav class="toc" aria-label="Contents">
<ol>
<li><a href="#section-heading">SECTION HEADING</a></li>
<li><a href="#what-to-do-next">What to do next</a></li>
<li><a href="#how-this-report-was-made">How this report was made</a></li>
</ol>
</nav>
</div></aside>
<article>
<p>One or two sentences: the state of things and what it means for the business.</p>
<h2 id="section-heading">SECTION HEADING</h2>
<h3>FINDING TITLE</h3>
<ul class="finding">
<li><b>Problem:</b> the observed gap, with the evidence and affected page.</li>
<li><b>Change:</b> the concrete step and how it addresses the gap.</li>
<li><b>Expected effect:</b> what could improve, why the business cares, and the likely scale and uncertainty.</li>
</ul>
<div class="note"><p>A caveat, a confidence note, or something you could not verify.</p></div>
<div class="tw">
<table>
<thead><tr><th>Item</th><th class="n">Volume</th><th class="n">Difficulty</th></tr></thead>
<tbody><tr><td>example keyword</td><td class="n">1,300</td><td class="n">4</td></tr></tbody>
</table>
</div>
<figure>
<div class="bars" role="img" aria-label="Monthly volume: alpha 1,300, beta 590">
<span class="label">alpha</span><span class="track"><span class="bar" style="width:100%"></span></span><span class="value">1,300</span>
<span class="label">beta</span><span class="track"><span class="bar" style="width:45%"></span></span><span class="value">590</span>
</div>
<figcaption>One row per item; the widest bar is 100% and the rest are scaled to it.</figcaption>
</figure>
<hr>
<h2 id="what-to-do-next">What to do next</h2>
<ol><li>The first step for worthwhile work and how to check the intended result. Omit if no action is supported.</li></ol>
<h2 id="how-this-report-was-made">How this report was made</h2>
<p>Generated by the <a href="https://openseo.so/docs/skills/SKILL-NAME" target="_blank" rel="noopener">OpenSEO SKILL TITLE skill</a>, run by AGENT NAME on MONTH D, YYYY.</p>
<ul class="finding">
<li><b>Tools:</b> which OpenSEO tools reported which numbers.</li>
<li><b>Verified:</b> what you checked against the live site by hand.</li>
</ul>
</article>
</div>
<footer>Prepared with OpenSEO. Data as of MONTH D, YYYY.</footer>
</div></body></html>
基本元素
- 页眉 —
h1(报告标题)和.byline(报告对象及日期):“为 badseo.dev 编制 · 2026 年 9 月 3 日”。 .rail内容 — 目录。在宽屏上固定在正文右侧,在手机和打印版中移至下方。每个h2都有一个 id,目录中的链接使用普通的href="#id"指向各标题——页内锚点是唯一一种不得使用target="_blank"的链接。Id 使用标题文本的 kebab-case 格式。h2/h3—h2用于开启一个章节,h3用于命名一项发现。不要跳过层级。- 发现项 — 如果生成该内容的技能指定了建议结构,则按其要求使用;否则使用
h3,然后用.finding列出问题、更改和预期效果,每项用一到两句话说明。将预期收益与拟议工作一并呈现,即使收益不确定或有限也应如此。描述性发现项可只包含相关要点。 .note— 用一个左侧带竖线的提示框说明注意事项、置信度限制或无法核实的内容。每份报告放两到三个,不要连续堆放。.tw表格 — 每个数值列的th和td都要添加class="n",以便数字对齐。表格最多五列,将长文本列放在最后,并保持单元格文字简短;宽表格在手机上可横向滚动,在打印版中可能会被裁切。figure+.bars— 仅当图表比文字说明更便于快速比较时,使用一个小型条形图。每项占一行:.label、包含.bar的.track(其内联宽度为相对于最大值的百分比),以及.value。非条形图可使用内联 SVG;需提供viewBox和实际的<text>标签。- 小组件图表 — 使用带有
data-widget名称的figure、供查看器读取的 JSON 数据块、作为备用方案的静态图示,以及figcaption。只有技能文档中说明的小组件才可用;查看器会忽略其他名称并显示备用方案。 hr后接收尾h2— 列出“下一步该做什么”,按顺序排列,并尽可能精简。- 本报告的编制方式 — 最后的
h2:技能链接行,然后是 Tools 和 Verified 要点。参见上文对应章节。 footer— 单行内容:结语和数据日期。方法细节应放在收尾章节中,而不是这里。
防护规则
- 不要在聊天中叙述运行过程。三条要点加链接是上限,不是下限。
- 不要针对每份报告重新设计模板样式。保持一套美观的样式才是重点。报告模板可以设置
--accent、署名行(例如Prepared for NAME或Prepared by落款)和页脚;CSS 的其他部分一律不变。 - 不要把报告正文粘贴到聊天中,也不要提议改为将其写入本地文件。报告应保存在项目中。
- 不要将报告保存到未被要求处理的项目中。
save_report使用该技能当前正在处理的项目的projectId。 - 不要编造数字来填充表格单元格。写入
unknown,并在.note中说明原因。