diff --git a/.github/workflows/website.yml b/.github/workflows/website.yml new file mode 100644 index 000000000..597d903fe --- /dev/null +++ b/.github/workflows/website.yml @@ -0,0 +1,26 @@ +name: Website + +on: + pull_request: + paths: + - 'website/**' + - '.github/workflows/website.yml' + push: + branches: [main] + paths: + - 'website/**' + - '.github/workflows/website.yml' + +permissions: + contents: read + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 20 + - run: npm test + working-directory: website diff --git a/website/README.md b/website/README.md new file mode 100644 index 000000000..734ef45ee --- /dev/null +++ b/website/README.md @@ -0,0 +1,20 @@ +# TeamAI website + +Standalone static site for [#1131](https://github.com/Tencent/teamai-cli/issues/1131). Node.js 20+; no dependencies, CLI code, or copied documentation. + +```sh +cd website +npm run build +npm test +python3 -m http.server 4173 --bind 127.0.0.1 --directory dist +``` + +Open `http://127.0.0.1:4173`. Python 3 is only needed for this local preview command; any static file server works. Deploy the contents of `website/dist/` to a static host. Links and assets are relative, so the site also works below a URL path. Hosting, domain, and automatic publication are left for the maintainers to choose. + +## Contribute a real use case + +First share a proposal in [#1131](https://github.com/Tencent/teamai-cli/issues/1131): how your team set TeamAI up and how it uses it. Include the tools you use, a public-safe repo layout, and one real workflow. Do not include private code, credentials, or invented results. + +After agreeing the scope, contribute an HTML page under `website/use-cases/` and link it from the homepage's use-cases section. Use relative asset links (for example `../styles.css`) and accessible headings. The build copies these pages unchanged. If a case is written in another language, set its HTML `lang` appropriately. + +There are no contributed cases yet. Keep `use-cases/` empty until the first real contribution; Git does not track empty directories, so the build creates it if missing. Do not add a placeholder story or `.gitkeep` there. diff --git a/website/README.zh-CN.md b/website/README.zh-CN.md new file mode 100644 index 000000000..0f7944587 --- /dev/null +++ b/website/README.zh-CN.md @@ -0,0 +1,20 @@ +# TeamAI 网站 + +[#1131](https://github.com/Tencent/teamai-cli/issues/1131) 的独立静态站点。需要 Node.js 20+;无额外依赖,不包含 CLI 源码或文档副本。 + +```sh +cd website +npm run build +npm test +python3 -m http.server 4173 --bind 127.0.0.1 --directory dist +``` + +打开 `http://127.0.0.1:4173`。Python 3 只用于上述本地预览命令,也可使用其他静态文件服务器。将 `website/dist/` 内的文件部署到静态托管平台即可。链接和资源使用相对路径,也支持部署到 URL 子路径。托管平台、域名和自动发布方式由维护者确定。 + +## 贡献真实使用案例 + +先在 [#1131](https://github.com/Tencent/teamai-cli/issues/1131) 分享提议:团队如何搭建 TeamAI,以及日常如何使用。包括使用的工具、可公开的仓库结构和一个真实工作流。不要包含私有代码、凭据或编造的结果。 + +范围确定后,在 `website/use-cases/` 下贡献 HTML 页面,并在首页案例区域添加链接。使用相对资源路径(如 `../styles.css`)和易于访问的标题结构。构建时原样复制这些页面。如果案例使用其他语言,请设置对应的 HTML `lang`。 + +目前没有已贡献的案例。首个真实案例到来前保持 `use-cases/` 为空;Git 不跟踪空目录,构建会在缺失时创建它。不要在其中添加虚构案例或 `.gitkeep`。 diff --git a/website/assets/logo.svg b/website/assets/logo.svg new file mode 100644 index 000000000..d2593cd0d --- /dev/null +++ b/website/assets/logo.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/website/build.mjs b/website/build.mjs new file mode 100644 index 000000000..bfcc2cde8 --- /dev/null +++ b/website/build.mjs @@ -0,0 +1,12 @@ +import { cp, mkdir, rm } from 'node:fs/promises'; +import { fileURLToPath } from 'node:url'; + +const root = new URL('./', import.meta.url); +const output = new URL('dist/', root); +await mkdir(new URL('use-cases/', root), { recursive: true }); +await rm(output, { recursive: true, force: true }); +await mkdir(new URL('use-cases/', output), { recursive: true }); +for (const name of ['index.html', 'styles.css', 'assets', 'use-cases']) { + await cp(new URL(name, root), new URL(name, output), { recursive: true }); +} +console.log(`Built website at ${fileURLToPath(output)}`); diff --git a/website/index.html b/website/index.html new file mode 100644 index 000000000..e4325555e --- /dev/null +++ b/website/index.html @@ -0,0 +1,67 @@ + + + + + + + TeamAI — Make Every Team AI Native + + + + + + +
+
+ +
+

OPEN SOURCE / BUILT FOR YOUR TEAM

+

Great teams share
more than code.

+

Your team's skills, rules, and knowledge.
One Git repo. Every AI coding tool.

+ +

npm install -g teamai-cli

+
+
+
ONE REPOSITORY. A SHARED WAY OF WORKING.TEAMAI
+
+

YOUR TEAM'S ASSETS

SkillsReusable expertise
RulesShared standards
AgentsSpecialist roles
+ +

TeamAI

Managed in Git.
Synced to your tools.

your-team / main
+ +

WHERE YOUR TEAM WORKS

Claude Code
Codex
Cursor
CodeBuddy
+
+
Your tools. Your Git host. Your workflow.See capabilities
+
+

Built to work with the tools you already use.

Claude CodeCodexCursorCodeBuddyOpenCode
+
+
+

01 — THE SHARED FOUNDATION

Stop starting
from scratch.

The best prompt, the right review rules, the hard-won context.
Give every teammate a better starting point.

+
+
01

Share what works.

Keep skills, rules, agents, MCP, and model configuration in a team repository. Review changes with the Git workflow you know.

GIT-NATIVE
+
02

Make it everyone's default.

Set teammates up once. Session hooks pull the latest team assets into their AI coding tools, without a daily sync ritual.

SET UP ONCE
+
03

Build on shared experience.

Explore team learnings, codebase context, and usage. Context and improvement capabilities are currently in beta.

CONTEXT & IMPROVEMENT · BETA
+
+
+
+

02 — IN PRACTICE

The next story
starts with
your team.

How did you set TeamAI up?
What does your everyday workflow look like?

COMMUNITY USE CASES

Show us how
you work.

We're collecting the first real team stories. None have been published here yet.

Contribute your use case

Share your setup and one real workflow. Keep private code and credentials out.

+
+

03 — GET STARTED

Your team.
On the same page.

Connect your shared repository and bring
the team's way of working to every agent.

Read the getting started guide
TERMINALNode.js 20+
# Install TeamAI
+$ npm install -g teamai-cli
+
+# Connect your team's repo
+$ teamai init https://github.com/your-org/your-repo

Replace the URL with your team's repository.
Prefer your AI tool? The guide also covers skill-based setup.

+
+ + + diff --git a/website/package.json b/website/package.json new file mode 100644 index 000000000..561be9cac --- /dev/null +++ b/website/package.json @@ -0,0 +1,10 @@ +{ + "name": "teamai-website", + "private": true, + "type": "module", + "engines": { "node": ">=20" }, + "scripts": { + "build": "node build.mjs", + "test": "node --test test.mjs" + } +} diff --git a/website/styles.css b/website/styles.css new file mode 100644 index 000000000..98c4526f8 --- /dev/null +++ b/website/styles.css @@ -0,0 +1,123 @@ +:root { color-scheme: dark; --ink: #f2f3f5; --muted: #969aa6; --paper: #090a0d; --line: #252730; --blue: #7195ff; } +* { box-sizing: border-box; } +html { scroll-behavior: smooth; scroll-padding-top: 32px; } +body { margin: 0; background: var(--paper); color: var(--ink); font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; -webkit-font-smoothing: antialiased; } +a { color: inherit; text-decoration: none; } +a:hover { color: var(--blue); } +pre:focus-visible, a:focus-visible { outline: 2px solid var(--blue); outline-offset: 6px; } +.wrap { width: min(1100px, calc(100% - 80px)); margin-inline: auto; } +.skip-link { position: absolute; top: -100px; left: 16px; padding: 12px; background: white; color: black; z-index: 3; } +.skip-link:focus { top: 10px; } +.site-header { height: 80px; display: flex; align-items: center; justify-content: space-between; gap: 24px; position: relative; z-index: 1; border-bottom: 1px solid var(--line); } +.brand { display: inline-flex; align-items: center; gap: 9px; font-size: 22px; font-weight: 650; letter-spacing: -.7px; } +.brand-label { margin-left: 6px; padding-left: 15px; border-left: 1px solid #34353c; font-size: 11px; font-weight: 400; letter-spacing: 0; color: var(--muted); } +nav a { display: inline-flex; align-items: center; min-height: 44px; } +nav { display: flex; gap: 30px; color: #aeb1ba; font-size: 13px; } +.header-github { min-height: 44px; display: inline-flex; align-items: center; gap: 10px; border: 1px solid #34363f; padding: 9px 12px; border-radius: 6px; font-size: 12px; } +.header-github > span { margin-left: 8px; color: var(--muted); } +.hero { position: relative; overflow: hidden; } +.hero-grid { position: absolute; inset: 0 0 auto; height: 750px; pointer-events: none; background: radial-gradient(ellipse at 50% 80%, #273c8a26, transparent 65%), linear-gradient(#ffffff04 1px, transparent 1px), linear-gradient(90deg, #ffffff04 1px, transparent 1px); background-size: auto, 80px 80px, 80px 80px; mask-image: linear-gradient(black 65%, transparent); } +.hero-copy { position: relative; text-align: center; padding-top: 52px; } +.announcement { display: inline-flex; align-items: center; gap: 9px; font-family: ui-monospace, SFMono-Regular, Consolas, monospace; font-size: 9px; letter-spacing: 1.4px; color: #b4b8c4; margin: 0 0 24px; } +.status-dot { display: inline-block; width: 5px; height: 5px; background: #819cff; border-radius: 50%; flex-shrink: 0; } +.announcement-divider { color: #515563; margin-inline: 6px; } +h1 { font-size: clamp(48px, 6.8vw, 88px); font-weight: 550; line-height: 1.04; letter-spacing: -4.5px; margin: 0; } +h1 > span { color: #8aabff; background: linear-gradient(110deg, #a4bfff 20%, #6286ff 85%); background-clip: text; -webkit-text-fill-color: transparent; } +.intro { font-size: 17px; line-height: 1.75; color: #a3a8b5; margin: 24px 0; } +.actions { display: flex; justify-content: center; flex-wrap: wrap; gap: 12px; } +.button { display: inline-flex; align-items: center; justify-content: space-between; gap: 24px; padding: 14px 20px; border-radius: 6px; font-size: 13px; font-weight: 550; transition: background .15s, border-color .15s; } +.primary { background: #5e7ef0; color: #fff; box-shadow: inset 0 1px #ffffff30, 0 0 24px #476bff20; border: 1px solid #7895ff; } +.primary:hover { background: #7390ff; color: white; } +.secondary { border: 1px solid #343640; background: #171920; color: #dddfe6; } +.secondary:hover { border-color: #717889; color: white; } +.install { font-family: ui-monospace, SFMono-Regular, Consolas, monospace; font-size: 11px; color: #a0a5b2; margin: 22px 0 0; } +.install > span { color: #576075; margin-right: 10px; } +.sync-window { position: relative; margin-top: 36px; border: 1px solid #303443; border-radius: 11px; background: #0e1016; box-shadow: 0 0 90px #3655ac12; overflow: hidden; } +.window-bar { height: 44px; border-bottom: 1px solid var(--line); display: flex; align-items: center; justify-content: space-between; gap: 15px; padding: 0 20px; color: #9298a9; font: 8px ui-monospace, monospace; letter-spacing: 1.6px; background: #14161d; } +.window-dots { display: flex; gap: 6px; } +.window-dots i { width: 6px; height: 6px; background: #3b3f4b; border-radius: 50%; } +.window-label { color: #7d8394; font-size: 8px; } +.sync-map { display: grid; grid-template-columns: 1fr 75px 1fr 75px 1fr; align-items: center; padding: 30px 44px; background: radial-gradient(ellipse at center, #20336c20, transparent 65%); } +.tiny-label { font: 8px ui-monospace, SFMono-Regular, Consolas, monospace; letter-spacing: 1.5px; color: #9299ab; margin: 0 0 18px; } +.asset { display: grid; grid-template-columns: 26px auto; align-items: center; column-gap: 10px; margin-top: 14px; } +.asset-icon { grid-row: span 2; width: 27px; height: 31px; display: grid; place-items: center; color: #94aaff; font: 17px ui-monospace, monospace; background: #1b2032; border: 1px solid #303952; border-radius: 4px; } +.asset strong { font-size: 12px; font-weight: 500; } +.asset > span:last-child { font-size: 10px; color: #838a9a; margin-top: 4px; } +.map-connector { display: flex; align-items: center; color: #53638b; gap: 3px; } +.map-connector span { flex: 1; height: 1px; background: #303b57; } +.map-connector b { font-size: 13px; font-weight: 400; } +.repo-node { text-align: center; } +.repo-halo { display: inline-flex; padding: 10px; border: 1px solid #343d5a; border-radius: 22px; background: #1a2133; box-shadow: 0 0 45px #4c70e52b; } +.repo-halo img { border-radius: 15px; } +.repo-node h2 { font-size: 19px; letter-spacing: -.5px; margin: 12px 0 8px; } +.repo-node p { font-size: 10px; color: #969fb4; line-height: 1.7; margin: 0 0 12px; } +.repo-badge { font: 8px ui-monospace, monospace; color: #a0b0d4; background: #171e2e; border: 1px solid #303d5a; padding: 5px 8px; border-radius: 4px; } +.repo-badge > span { margin-right: 5px; } +.tool { padding: 8px 12px; display: flex; align-items: center; gap: 11px; border: 1px solid #292e3c; background: #161923; border-radius: 5px; margin-top: 7px; } +.tool strong { font-size: 11px; font-weight: 500; } +.tool-icon { width: 17px; font-size: 20px; text-align: center; line-height: 1; } +.claude { color: #dba58c; }.codex { color: #c8c9ce; }.cursor { color: #e1e5f1; }.buddy { color: #859fff; } +.window-bottom { display: flex; align-items: center; justify-content: space-between; padding: 13px 20px; border-top: 1px solid var(--line); color: #979ead; font-size: 9px; } +.window-bottom > span { display: inline-flex; gap: 8px; align-items: center; } +.window-bottom a { color: #b7c6f6; } +.supported { text-align: center; padding-block: 35px 60px; } +.supported p { font-size: 10px; color: #7e8492; margin: 0 0 22px; } +.supported > div { display: flex; justify-content: center; gap: 44px; flex-wrap: wrap; font-size: 15px; font-weight: 550; letter-spacing: -.4px; color: #a5a9b4; } +.principles { padding-block: 85px; border-top: 1px solid var(--line); } +.eyebrow { color: #8197d5; font: 9px ui-monospace, SFMono-Regular, Consolas, monospace; letter-spacing: 1.5px; margin: 0 0 30px; } +h2 { font-size: clamp(34px, 4vw, 50px); font-weight: 500; letter-spacing: -2px; line-height: 1.08; margin: 0; } +.section-heading { position: relative; } +.section-heading > p:last-child { color: var(--muted); font-size: 14px; line-height: 1.8; margin: 24px 0 0; } +.steps { margin-top: 45px; display: grid; grid-template-columns: repeat(3,1fr); border-top: 1px solid var(--line); } +.steps article { padding: 28px 30px 0 0; } +.steps article + article { border-left: 1px solid var(--line); padding-left: 30px; } +.step-number { font: 11px ui-monospace, monospace; color: #7f90bb; } +h3 { font-size: 18px; font-weight: 550; letter-spacing: -.5px; margin: 20px 0 14px; } +.steps p { color: var(--muted); font-size: 12px; line-height: 1.9; margin: 0 0 24px; } +.feature-tag { color: #929bb0; font: 8px ui-monospace, monospace; letter-spacing: 1px; } +.cases-section { color-scheme: light; background: #eeeff2; color: #101218; padding-block: 85px; } +.cases-layout { display: grid; grid-template-columns: 1fr 1fr; gap: 100px; align-items: center; } +.cases-section .eyebrow { color: #58658a; } +.cases-copy h2 { font-size: clamp(40px, 4.6vw, 58px); letter-spacing: -2.5px; } +.cases-copy h2 span { color: #6478b3; } +.cases-copy > p:last-child { color: #5c606d; font-size: 13px; line-height: 1.9; margin-top: 28px; } +.case-invitation { position: relative; border: 1px solid #cbd0da; padding: 35px; border-radius: 7px; background: #f6f7fa; } +.invitation-mark { position: absolute; right: 30px; top: 25px; font-size: 35px; font-weight: 300; color: #697baa; } +.case-invitation .tiny-label { color: #627198; } +.case-invitation h3 { font-size: 34px; letter-spacing: -1.2px; line-height: 1.1; margin: 25px 0 18px; } +.case-invitation > p:not(.tiny-label) { font-size: 12px; color: #5d6371; line-height: 1.8; max-width: 280px; } +.case-button { margin-top: 14px; color: white; background: #161c2c; width: 100%; } +.case-button:hover { color: white; background: #303d60; } +.case-invitation p.case-note { font-size: 9px; max-width: none; margin: 18px 0 0; } +.start { display: grid; grid-template-columns: 1fr 1.1fr; gap: 70px; align-items: center; padding-block: 85px; } +.start > div > p:not(.eyebrow) { color: var(--muted); font-size: 13px; line-height: 1.8; margin: 25px 0; } +.text-link { font-size: 12px; color: #a0b6fc; } +.text-link > span { margin-left: 15px; } +.terminal { min-width: 0; background: #101218; border: 1px solid #2b303d; border-radius: 7px; } +.terminal-header { display: flex; justify-content: space-between; border-bottom: 1px solid var(--line); padding: 15px 20px; color: #8e97ab; font: 8px ui-monospace, monospace; letter-spacing: 1px; } +pre { margin: 0; padding: 24px 20px 12px; overflow-x: auto; font-size: 10px; line-height: 2; } +code { font-family: ui-monospace, SFMono-Regular, Consolas, monospace; } +.comment { color: #7b8497; }.prompt { color: #8aa5ff; } +.terminal > p { margin: 0; padding: 0 20px 22px; color: #8e97a9; font-size: 9px; line-height: 1.9; } +footer { display: flex; justify-content: space-between; align-items: center; gap: 30px; border-top: 1px solid var(--line); padding-block: 35px; } +footer .brand { font-size: 18px; } +footer p { font-size: 10px; color: #888e9b; margin: 12px 0 0; } +.footer-links { display: flex; gap: 30px; font-size: 11px; color: #a4a9b5; } +@media (max-width: 980px) { + .wrap { width: calc(100% - 48px); }.brand-label { display: none; }nav { gap: 22px; }.sync-map { grid-template-columns: 1fr 35px 1fr 35px 1fr; padding: 25px; }.tiny-label { font-size: 7px; letter-spacing: 1px; }.cases-layout { gap: 45px; }.start { gap: 35px; } +} +@media (max-width: 800px) { + .steps { grid-template-columns: 1fr; } + .steps article, .steps article + article { padding: 24px 0; border-left: 0; } + .steps article + article { border-top: 1px solid var(--line); } + .cases-layout, .start { grid-template-columns: 1fr; gap: 32px; } +} +@media (max-width: 650px) { + .wrap { width: calc(100% - 32px); } + .site-header { height: auto; padding-block: 18px; flex-wrap: wrap; gap: 20px; }.brand { font-size: 20px; }.header-github { margin-left: auto; }nav { order: 3; width: 100%; justify-content: space-between; font-size: 13px; gap: 10px; flex-wrap: wrap; }.hero-copy { padding-top: 48px; }.announcement { font-size: 9px; gap: 6px; letter-spacing: 1px; }h1 { font-size: clamp(38px,8.4vw,54px); letter-spacing: -2px; }.intro { font-size: 14px; margin-block: 22px; }.button { font-size: 11px; padding: 13px 16px; gap: 15px; }.install { font-size: 10px; }.sync-window { margin-top: 36px; }.window-bar { padding-inline: 13px; font-size: 6px; letter-spacing: .7px; }.window-label { display: none; }.window-dots { gap: 3px; }.window-dots i { width: 4px; height: 4px; }.sync-map { grid-template-columns: 1fr; padding: 24px; gap: 20px; }.assets-column { display: grid; grid-template-columns: repeat(3,1fr); gap: 8px; }.assets-column .tiny-label { grid-column: 1/-1; margin-bottom: 2px; }.asset { display: flex; flex-direction: column; margin: 0; padding: 12px 5px; border: 1px solid #292e3c; border-radius: 5px; gap: 8px; }.asset strong { font-size: 10px; }.asset > span:last-child { display: none; }.map-connector { display: flex; justify-content: center; }.map-connector span { display: none; }.map-connector b { transform: rotate(90deg); font-size: 20px; }.repo-node { padding-block: 10px; }.repo-halo { padding: 8px; }.repo-halo img { width: 48px; height: 48px; }.repo-node h2 { font-size: 18px; }.repo-node p { font-size: 11px; }.repo-badge { font-size: 9px; }.tools-column { display: grid; grid-template-columns: 1fr 1fr; gap: 7px; }.tools-column .tiny-label { grid-column: 1/-1; margin-bottom: 2px; }.tool { margin: 0; padding: 10px; gap: 8px; }.tool strong { font-size: 12px; }.window-bottom { padding: 12px; font-size: 10px; flex-wrap: wrap; gap: 8px; }.window-bottom a { display: inline-flex; align-items: center; min-height: 44px; font-size: 12px; }.supported { padding-block: 28px 40px; }.supported > div { gap: 16px 22px; font-size: 12px; }.principles { padding-block: 50px; }.eyebrow { font-size: 8px; margin-bottom: 25px; }h2 { font-size: 38px; }.section-heading > p:last-child { font-size: 12px; }.steps { grid-template-columns: 1fr; margin-top: 30px; }.steps article { padding: 25px 0; }.steps article + article { border-left: 0; border-top: 1px solid var(--line); padding: 25px 0; }.steps p { font-size: 13px; margin-bottom: 18px; }h3 { margin-top: 15px; }.cases-section { padding-block: 50px; }.cases-layout { grid-template-columns: 1fr; gap: 30px; }.cases-copy h2 { font-size: 44px; }.case-invitation { padding: 28px; }.start { grid-template-columns: 1fr; padding-block: 50px; gap: 32px; }.start h2 { font-size: 38px; }footer { flex-wrap: wrap; gap: 25px; }.footer-links { gap: 12px 25px; flex-wrap: wrap; }.footer-links a, .text-link { display: inline-flex; align-items: center; min-height: 44px; }.button { min-height: 44px; } +} +@media (max-width: 360px) { + .tools-column { grid-template-columns: 1fr; } + .announcement { flex-wrap: wrap; justify-content: center; letter-spacing: .5px; } +} +@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; }.button { transition: none; } } diff --git a/website/test.mjs b/website/test.mjs new file mode 100644 index 000000000..8cf8d7a23 --- /dev/null +++ b/website/test.mjs @@ -0,0 +1,29 @@ +import assert from 'node:assert/strict'; +import { execFileSync } from 'node:child_process'; +import { readdir, readFile, stat } from 'node:fs/promises'; +import { test } from 'node:test'; + +const root = new URL('./', import.meta.url); +execFileSync(process.execPath, ['build.mjs'], { cwd: root }); +const output = new URL('dist/', root); + +await test('published files exclude project tooling and CLI source', async () => { + const names = await readdir(output); + assert(!names.includes('package.json')); + assert(!names.includes('src')); + assert(!names.includes('README.md')); +}); + +await test('homepage assets and in-page links resolve in the build', async () => { + const html = await readFile(new URL('index.html', output), 'utf8'); + for (const [, value] of html.matchAll(/(?:href|src)="([^"]+)"/g)) { + if (value.startsWith('https://')) continue; + if (value.startsWith('#')) { + assert(html.includes(`id="${value.slice(1)}"`), value); + } else { + await stat(new URL(value, output)); + } + } + assert.match(html, //); + assert.equal([...html.matchAll(/