Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions apps/web/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ npm run check:seo

构建同时生成双语 Markdown、`api-reference.json` 和带语言对应关系的 sitemap。`public/llms.txt` 提供辅助入口,`robots.txt` 允许 OAI-SearchBot;这些文件与结构化数据不保证收录、排名或模型推荐。ChatGPT 实测证据保存在仓库 `docs/verification/seo-aeo/`,区分上线前基线、搜索引用和显式提供 URL 的读取测试。

面向 Agent 的完整知识入口由构建脚本生成:`agent-guide.zh-CN.md`、`agent-guide.en.md`、`skills.md` 和 `llms-full.txt`。官网可见区域必须链接这些文件,`llms.txt` 负责轻量导航,`llms-full.txt` 提供一次读取所需的完整上下文。更新任务数量、安全边界或 Skills 时,需要同步生成内容和 `check:seo` 断言,避免人类页面与 AI 文档漂移。

### ChatGPT 读取兼容性

2026-10-07 的实际测试发现:ChatGPT 可以读取中英文 HTML 页面,但读取 `overview.zh-CN.md` 时报告 `Unsupported content-type: text/markdown`。因此 `overview.zh-CN.md` 和 `overview.en.md` 保留 Markdown 内容和文件名,通过 `public/_headers` 以 `text/plain; charset=utf-8` 返回。变更后需要实际读回响应头,并在 ChatGPT 中重测,不能仅凭文件上传成功认定可读取。
Expand Down
16 changes: 16 additions & 0 deletions apps/web/public/_headers
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,22 @@
Cache-Control: public, max-age=300
Content-Type: text/plain; charset=utf-8

/llms-full.txt
Cache-Control: public, max-age=300
Content-Type: text/plain; charset=utf-8

/agent-guide.zh-CN.md
Cache-Control: public, max-age=300
Content-Type: text/plain; charset=utf-8

/agent-guide.en.md
Cache-Control: public, max-age=300
Content-Type: text/plain; charset=utf-8

/skills.md
Cache-Control: public, max-age=300
Content-Type: text/plain; charset=utf-8

/overview.zh-CN.md
Content-Type: text/plain; charset=utf-8
Cache-Control: public, max-age=300
Expand Down
4 changes: 4 additions & 0 deletions apps/web/public/llms.txt
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ The registry contains 156 entries across 13 API families. Coverage means registe
- [English website](https://agccli.app/en/): English product overview, installation, FAQ and endpoint browser.
- [中文完整介绍](https://agccli.app/overview.zh-CN.md): Chinese plain-text overview and answers.
- [English overview](https://agccli.app/overview.en.md): English plain-text overview and answers.
- [中文 Agent 使用指南](https://agccli.app/agent-guide.zh-CN.md): Task selection, safe execution, verification, and machine-readable resources in Chinese.
- [English Agent usage guide](https://agccli.app/agent-guide.en.md): Task selection, safe execution, verification, and machine-readable resources in English.
- [Agent Skills index](https://agccli.app/skills.md): All six task-oriented Skills with install commands and direct source links.
- [Complete AI context](https://agccli.app/llms-full.txt): Combined project overview, Agent guide, safety boundaries, and Skills index for one-pass retrieval.
- [Endpoint contracts](https://agccli.app/api-reference.json): All registered endpoints, methods, paths, parameters and reference URLs.
- [Source repository](https://github.com/Createitv/agc-cli): Code, MIT license and issues.
- [中文使用指南](https://github.com/Createitv/agc-cli/blob/main/docs/CLI_USAGE.md): CLI installation and usage.
Expand Down
12 changes: 11 additions & 1 deletion apps/web/scripts/check-seo.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { JSDOM } from 'jsdom';
const registry = JSON.parse(await readFile('dist/api-reference.json','utf8')).data;
const families = [...new Set(registry.map(e=>e.familyId))];
const sitemap = await readFile('dist/sitemap.xml','utf8');
assert.ok(sitemap.includes('<lastmod>2026-10-13</lastmod>'));
let checked=0;
for (const language of ['zh','en']) for (const family of [undefined,...families]) {
const path=`${language==='en'?'/en/':'/'}${family?`reference/${family}/`:''}`;
Expand All @@ -19,6 +20,11 @@ for (const language of ['zh','en']) for (const family of [undefined,...families]
for (const lang of ['zh-CN','en','x-default']) assert.ok(doc.querySelector(`link[hreflang="${lang}"]`));
const graph=JSON.parse(doc.querySelector('#seo-schema').textContent)['@graph'];
const faq=graph.find(e=>e['@type']==='FAQPage');
const software=graph.find(e=>e['@type']==='SoftwareApplication');
const webpage=graph.find(e=>e['@type']==='WebPage');
assert.ok(software.isAccessibleForFree);
assert.ok(software.featureList.includes('Task-oriented Agent Skills'));
assert.equal(webpage.hasPart.length,4);
const visibleFaq=[...doc.querySelectorAll('.projectFaq details')];
assert.equal(visibleFaq.length,faq.mainEntity.length);
for (const [i,element] of visibleFaq.entries()) {
Expand All @@ -35,5 +41,9 @@ for (const language of ['zh','en']) for (const family of [undefined,...families]
}
assert.ok((await readFile('dist/robots.txt','utf8')).includes('User-agent: OAI-SearchBot\nAllow: /'));
const headers=await readFile('dist/_headers','utf8');
for (const file of ['overview.zh-CN.md','overview.en.md']) assert.ok(headers.includes(`/${file}\n Content-Type: text/plain; charset=utf-8`),`${file} must remain readable by clients that reject text/markdown`);
for (const file of ['overview.zh-CN.md','overview.en.md','agent-guide.zh-CN.md','agent-guide.en.md','skills.md']) assert.ok(headers.includes(`/${file}\n Cache-Control: public, max-age=300\n Content-Type: text/plain; charset=utf-8`) || headers.includes(`/${file}\n Content-Type: text/plain; charset=utf-8`),`${file} must remain readable by clients that reject text/markdown`);
for (const file of ['llms-full.txt','agent-guide.zh-CN.md','agent-guide.en.md','skills.md']) {
const body=await readFile(`dist/${file}`,'utf8');
assert.ok(body.length>500,`${file} must contain substantial agent-readable guidance`);
}
console.log(`SEO checks passed: ${checked} pages, reciprocal locales, visible FAQ/schema parity, ${registry.length} endpoints.`);
7 changes: 5 additions & 2 deletions apps/web/scripts/prerender.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { resolve } from 'node:path';

const server = await createServer({ server: { middlewareMode: true }, appType: 'custom' });
try {
const { renderPage, markdown } = await server.ssrLoadModule('/src/prerender.tsx');
const { renderPage, markdown, agentGuide, skillsMarkdown, llmsFull, contentUpdated } = await server.ssrLoadModule('/src/prerender.tsx');
const entries = JSON.parse(await readFile('src/endpoint-registry.json','utf8'));
const families = [...new Set(entries.map(e => e.familyId))];
const template = await readFile('dist/index.html','utf8');
Expand All @@ -18,11 +18,14 @@ try {
paths.push(path);
}
await writeFile(`dist/overview.${language === 'zh'?'zh-CN':'en'}.md`, markdown(language));
await writeFile(`dist/agent-guide.${language === 'zh'?'zh-CN':'en'}.md`, agentGuide(language));
}
await writeFile('dist/skills.md', skillsMarkdown());
await writeFile('dist/llms-full.txt', llmsFull());
await writeFile('dist/api-reference.json',JSON.stringify({data:entries},null,2)+'\n');
const sitemap = `<?xml version="1.0" encoding="UTF-8"?>\n<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:xhtml="http://www.w3.org/1999/xhtml">\n${paths.map(path=>{
const rest=path.replace(/^\/en\//,'/');
return `<url><loc>https://agccli.app${path}</loc><lastmod>2026-10-07</lastmod><xhtml:link rel="alternate" hreflang="zh-CN" href="https://agccli.app${rest}"/><xhtml:link rel="alternate" hreflang="en" href="https://agccli.app/en${rest}"/><xhtml:link rel="alternate" hreflang="x-default" href="https://agccli.app${rest}"/></url>`;
return `<url><loc>https://agccli.app${path}</loc><lastmod>${contentUpdated}</lastmod><xhtml:link rel="alternate" hreflang="zh-CN" href="https://agccli.app${rest}"/><xhtml:link rel="alternate" hreflang="en" href="https://agccli.app/en${rest}"/><xhtml:link rel="alternate" hreflang="x-default" href="https://agccli.app${rest}"/></url>`;
}).join('\n')}\n</urlset>\n`;
await writeFile('dist/sitemap.xml',sitemap);
console.log(`Prerendered ${paths.length} bilingual pages and ${entries.length} endpoint contracts.`);
Expand Down
5 changes: 5 additions & 0 deletions apps/web/src/App.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,11 @@ describe('App', () => {
expect(screen.getAllByText(/agc skills install --name/)).toHaveLength(6);
expect(screen.getAllByRole('link', { name: 'Download SKILL.md' })).toHaveLength(6);
expect(screen.getByRole('link', { name: /Contribute a Skill/ })).toHaveAttribute('href', 'https://github.com/Createitv/agc-cli/blob/main/CONTRIBUTING.md');
expect(screen.getByRole('heading', { name: 'Agent-readable documentation' })).toBeInTheDocument();
expect(screen.getByRole('link', { name: /Agent usage guide/ })).toHaveAttribute('href', '/agent-guide.en.md');
expect(screen.getByRole('link', { name: /Skills index/ })).toHaveAttribute('href', '/skills.md');
expect(screen.getByRole('link', { name: /Full AI context/ })).toHaveAttribute('href', '/llms-full.txt');
expect(screen.getByRole('link', { name: /API contract JSON/ })).toHaveAttribute('href', '/api-reference.json');
});

it('shows the complete selected family without a local API server', async () => {
Expand Down
12 changes: 12 additions & 0 deletions apps/web/src/ProjectAnswers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,18 @@ export function ProjectAnswers({ language, families }: { language: SiteLanguage;
return <section className="section projectAnswers" id="faq">
<h2>{c.heading}</h2><p className="projectDefinition">{c.definition}</p>
<p className="projectSources">{c.source}: <a href="https://github.com/Createitv/agc-cli">GitHub</a> · <a href={`https://github.com/Createitv/agc-cli/blob/main/docs/CLI_USAGE${language === 'en' ? '.en' : ''}.md`}>{language === 'zh' ? '使用指南' : 'Usage guide'}</a> · <a href="https://github.com/Createitv/agc-cli/issues">Issues</a></p>
<div className="agentDocs" aria-labelledby="agent-docs-title">
<div className="agentDocsIntro">
<p className="eyebrow">GEO · AEO · LLMO</p>
<h3 id="agent-docs-title">{c.agentDocs.heading}</h3>
<p>{c.agentDocs.intro}</p>
</div>
<div className="agentDocsGrid">
{c.agentDocs.links.map(({ title, description, href }) => <a key={href} href={href}>
<span>{title}</span><p>{description}</p><code>{href}</code>
</a>)}
</div>
</div>
<h3>{c.faq}</h3>
<div className="projectFaq">{c.faqs.map(({q,a}) => <details key={q}><summary>{q}</summary><p>{a}</p></details>)}</div>
<h3>{language === 'zh' ? '按 API 家族浏览完整参考' : 'Browse the complete API reference by family'}</h3>
Expand Down
24 changes: 23 additions & 1 deletion apps/web/src/prerender.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { renderToString } from 'react-dom/server';
import { App } from './App';
import { content, pageMeta, structuredData, pagePath, siteOrigin, type SiteLanguage } from './siteContent';
import { agentSkillNames, content, pageMeta, structuredData, pagePath, siteOrigin, updated, type SiteLanguage } from './siteContent';

export const contentUpdated = updated;

const escape = (value: string) => value.replace(/[&<>"']/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c]!));
export function renderPage(language: SiteLanguage, family?: string) {
Expand Down Expand Up @@ -30,3 +32,23 @@ export function markdown(language: SiteLanguage) {
const c=content[language];
return `# ${c.title}\n\n${c.definition}\n\n${c.faqs.map(({q,a})=>`## ${q}\n\n${a}`).join('\n\n')}\n\n## Links\n\n- Website: ${siteOrigin + pagePath(language)}\n- Source: https://github.com/Createitv/agc-cli\n- API contracts: ${siteOrigin}/api-reference.json\n`;
}

export function agentGuide(language: SiteLanguage) {
const zh = language === 'zh';
const title = zh ? 'agc-cli Agent 使用指南' : 'agc-cli Agent Usage Guide';
const intro = zh
? '本文档帮助 Codex、Claude 和其他 Coding Agent 在不猜测命令或泄露凭据的前提下使用 agc-cli。agc-cli 是 Createitv 维护的独立开源项目,不是华为官方产品。'
: 'This guide helps Codex, Claude, and other coding agents use agc-cli without guessing commands or exposing credentials. agc-cli is an independent open-source project maintained by Createitv; it is not an official Huawei product.';
const tasks = zh
? [['检查身份与权限', 'agc auth whoami --pretty'], ['发现能力', 'agc capabilities --pretty'], ['查看接口', 'agc <family> endpoints --output table'], ['读取接口文档', 'agc <family> <endpoint> --docs --pretty'], ['预演请求', 'agc <family> <endpoint> --invoke ...'], ['明确执行', '在核对目标、凭据和参数后增加 --dry-run=false']]
: [['Check identity and access', 'agc auth whoami --pretty'], ['Discover capabilities', 'agc capabilities --pretty'], ['List endpoints', 'agc <family> endpoints --output table'], ['Read endpoint docs', 'agc <family> <endpoint> --docs --pretty'], ['Preview a request', 'agc <family> <endpoint> --invoke ...'], ['Invoke explicitly', 'Add --dry-run=false only after checking the target, profile, and parameters']];
return `# ${title}\n\nLast updated: ${updated}\n\n${intro}\n\n## ${zh ? '推荐工作流' : 'Recommended workflow'}\n\n${tasks.map(([label,command],index)=>`${index+1}. **${label}** — \`${command}\``).join('\n')}\n\n## ${zh ? '安全边界' : 'Safety boundaries'}\n\n- ${zh ? '默认 dry-run 只构建请求,不发送远程变更;真实调用需要显式设置 `--dry-run=false`。' : 'The default dry run builds a request without sending a remote change; real invocation requires an explicit `--dry-run=false`.'}\n- ${zh ? '不要把 Service Account 私钥、OAuth 密钥、凭据响应或 `~/.agc/credentials.json` 提交到 Git。' : 'Never commit Service Account private keys, OAuth secrets, credential responses, or `~/.agc/credentials.json` to Git.'}\n- ${zh ? '注册表覆盖不等于每个接口已经完成生产验证;权限、状态和完整模型以接口文档链接为准。' : 'Registry coverage does not mean every endpoint is production-verified; follow the linked endpoint references for permissions, state, and complete models.'}\n- ${zh ? '上传、提交审核、发布和删除类操作必须由用户明确授权,并在执行后读取远程状态。' : 'Uploads, review submissions, releases, and deletion operations require explicit user authorization and remote-state verification.'}\n\n## Agent Skills\n\n${agentSkillNames.map(name=>`- [${name}](https://github.com/Createitv/agc-cli/tree/main/skills/${name})`).join('\n')}\n\n${zh ? '安装全部 Skills' : 'Install all Skills'}: \`agc skills install --agent codex --global\`\n\n## ${zh ? '机器可读资源' : 'Machine-readable resources'}\n\n- [llms.txt](${siteOrigin}/llms.txt)\n- [llms-full.txt](${siteOrigin}/llms-full.txt)\n- [Skills index](${siteOrigin}/skills.md)\n- [API contracts](${siteOrigin}/api-reference.json)\n- [Open-source repository](https://github.com/Createitv/agc-cli)\n`;
}

export function skillsMarkdown() {
return `# agc-cli Agent Skills\n\nLast updated: ${updated}\n\nSix task-oriented Skills help coding agents use agc-cli safely and consistently. Install all Skills with \`agc skills install --agent codex --global\`, or select one with \`agc skills install --name <skill> --agent codex --global\`.\n\n${agentSkillNames.map(name=>`## ${name}\n\n- Source: https://github.com/Createitv/agc-cli/tree/main/skills/${name}\n- Direct SKILL.md: https://raw.githubusercontent.com/Createitv/agc-cli/main/skills/${name}/SKILL.md\n- Install: \`agc skills install --name ${name} --agent codex --global\``).join('\n\n')}\n\nThe Skills describe workflows and safety checks. They do not grant Huawei permissions, supply credentials, or prove that a remote action succeeded.\n`;
}

export function llmsFull() {
return `${markdown('en')}\n\n---\n\n${agentGuide('en')}\n\n---\n\n${skillsMarkdown()}\n\n---\n\n${markdown('zh')}\n\n---\n\n${agentGuide('zh')}\n`;
}
Loading
Loading