Spaces:
Paused
Paused
| import { useEffect, useRef } from "react"; | |
| import logo from "../../assets/icon.png"; | |
| const IconClaude = () => ( | |
| <svg viewBox="0 0 16 16" width="18" height="18" fill="currentColor"> | |
| <path d="m3.127 10.604 3.135-1.76.053-.153-.053-.085H6.11l-.525-.032-1.791-.048-1.554-.065-1.505-.08-.38-.081L0 7.832l.036-.234.32-.214.455.04 1.009.069 1.513.105 1.097.064 1.626.17h.259l.036-.105-.089-.065-.068-.064-1.566-1.062-1.695-1.121-.887-.646-.48-.327-.243-.306-.104-.67.435-.48.585.04.15.04.593.456 1.267.981 1.654 1.218.242.202.097-.068.012-.049-.109-.181-.9-1.626-.96-1.655-.428-.686-.113-.411a2 2 0 0 1-.068-.484l.496-.674L4.446 0l.662.089.279.242.411.94.666 1.48 1.033 2.014.302.597.162.553.06.17h.105v-.097l.085-1.134.157-1.392.154-1.792.052-.504.25-.605.497-.327.387.186.319.456-.045.294-.19 1.23-.37 1.93-.243 1.29h.142l.161-.16.654-.868 1.097-1.372.484-.545.565-.601.363-.287h.686l.505.751-.226.775-.707.895-.585.759-.839 1.13-.524.904.048.072.125-.012 1.897-.403 1.024-.186 1.223-.21.553.258.06.263-.218.536-1.307.323-1.533.307-2.284.54-.028.02.032.04 1.029.098.44.024h1.077l2.005.15.525.346.315.424-.053.323-.807.411-3.631-.863-.872-.218h-.12v.073l.726.71 1.331 1.202 1.667 1.55.084.383-.214.302-.226-.032-1.464-1.101-.565-.497-1.28-1.077h-.084v.113l.295.432 1.557 2.34.08.718-.112.234-.404.141-.444-.08-.911-1.28-.94-1.44-.759-1.291-.093.053-.448 4.821-.21.246-.484.186-.403-.307-.214-.496.214-.98.258-1.28.21-1.016.19-1.263.112-.42-.008-.028-.092.012-.953 1.307-1.448 1.957-1.146 1.227-.274.109-.477-.247.045-.44.266-.39 1.586-2.018.956-1.25.617-.723-.004-.105h-.036l-4.212 2.736-.75.096-.324-.302.04-.496.154-.162 1.267-.871z"/> | |
| </svg> | |
| ); | |
| const IconCodex = () => ( | |
| <svg viewBox="0 0 16 16" width="18" height="18" fill="currentColor"> | |
| <path d="M14.949 6.547a3.94 3.94 0 0 0-.348-3.273 4.11 4.11 0 0 0-4.4-1.934 4.1 4.1 0 0 0-1.778-.613 4.15 4.15 0 0 0-2.118-.114 4.1 4.1 0 0 0-1.891.948 4.04 4.04 0 0 0-1.158 1.753 4.1 4.1 0 0 0-1.563.679 4 4 0 0 0-1.14 1.254 3.99 3.99 0 0 0 .502 4.731 3.94 3.94 0 0 0 .346 3.274 4.11 4.11 0 0 0 4.402 1.933c.382.425.852.764 1.377.995.526.231 1.095.35 1.67.346 1.78.002 3.358-1.132 3.901-2.804a4.1 4.1 0 0 0 1.563-.68 4 4 0 0 0 1.14-1.253 3.99 3.99 0 0 0-.506-4.716m-6.097 8.406a3.05 3.05 0 0 1-1.945-.694l.096-.054 3.23-1.838a.53.53 0 0 0 .265-.455v-4.49l1.366.778q.02.011.025.035v3.722c-.003 1.653-1.361 2.992-3.037 2.996m-6.53-2.75a2.95 2.95 0 0 1-.36-2.01l.095.057L5.29 12.09a.53.53 0 0 0 .527 0l3.949-2.246v1.555a.05.05 0 0 1-.022.041L6.473 13.3c-1.454.826-3.311.335-4.15-1.098m-.85-6.94A3.02 3.02 0 0 1 3.07 3.949v3.785a.51.51 0 0 0 .262.451l3.93 2.237-1.366.779a.05.05 0 0 1-.048 0L2.585 9.342a2.98 2.98 0 0 1-1.113-4.094zm11.216 2.571L8.747 5.576l1.362-.776a.05.05 0 0 1 .048 0l3.265 1.86a3 3 0 0 1 1.173 1.207 2.96 2.96 0 0 1-.27 3.2 3.05 3.05 0 0 1-1.36.997V8.279a.52.52 0 0 0-.276-.445m1.36-2.015-.097-.057-3.226-1.855a.53.53 0 0 0-.53 0L6.249 6.153V4.598a.04.04 0 0 1 .019-.04L9.533 2.7a3.07 3.07 0 0 1 3.257.139c.474.325.843.778 1.066 1.303.223.526.289 1.103.191 1.664zM5.503 8.575 4.139 7.8a.05.05 0 0 1-.026-.037V4.049c0-.57.166-1.127.476-1.607s.752-.864 1.275-1.105a3.08 3.08 0 0 1 3.234.41l-.096.054-3.23 1.838a.53.53 0 0 0-.265.455zm.742-1.577 1.758-1 1.762 1v2l-1.755 1-1.762-1z"/> | |
| </svg> | |
| ); | |
| const IconOpenClaw = () => ( | |
| <svg viewBox="0 0 20 20" width="18" height="18" fill="none" stroke="currentColor" strokeWidth="1.3" strokeLinecap="round" strokeLinejoin="round"> | |
| <path d="M8 9C6.5 7 4.5 5 3.5 4.5C2.5 4 2 5 3 6C4 7 6 8 7.5 9"/><path d="M12 9C13.5 7 15.5 5 16.5 4.5C17.5 4 18 5 17 6C16 7 14 8 12.5 9"/> | |
| <path d="M7.5 9C7 11 7.5 14.5 10 17.5C12.5 14.5 13 11 12.5 9L10 8Z"/> | |
| <line x1="8.5" y1="4" x2="7" y2="1.5"/><line x1="11.5" y1="4" x2="13" y2="1.5"/> | |
| </svg> | |
| ); | |
| const IconNanobot = () => ( | |
| <svg viewBox="0 0 20 20" width="18" height="18" fill="none" stroke="currentColor" strokeWidth="1.3" strokeLinecap="round" strokeLinejoin="round"> | |
| <path d="M4.5 8.5L3 3L7.5 7"/><path d="M15.5 8.5L17 3L12.5 7"/> | |
| <circle cx="10" cy="12.5" r="5.5"/> | |
| <circle cx="7.8" cy="11.5" r="1.2" fill="currentColor" stroke="none"/><circle cx="12.2" cy="11.5" r="1.2" fill="currentColor" stroke="none"/> | |
| <path d="M9.2 14L10 14.8L10.8 14"/> | |
| </svg> | |
| ); | |
| const IconCLI = () => ( | |
| <svg viewBox="0 0 20 20" width="18" height="18" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"> | |
| <path d="M5 7L9 10L5 13"/><line x1="11" y1="14" x2="16" y2="14"/> | |
| </svg> | |
| ); | |
| const clientIcons = { claude: IconClaude, codex: IconCodex, openclaw: IconOpenClaw, nanobot: IconNanobot, any: IconCLI }; | |
| const clients = [ | |
| { key: "claude", label: "Claude Code" }, | |
| { key: "codex", label: "Codex" }, | |
| { key: "openclaw", label: "OpenClaw" }, | |
| { key: "nanobot", label: "nanobot" }, | |
| { key: "any", label: "Any CLI" } | |
| ]; | |
| const orbitClients = clients; | |
| const features = [ | |
| { title: "Shared task graph", body: "One board for blockers, priority, ownership, and progress across all agents." }, | |
| { title: "Persistent coordination", body: "Handoffs, aliases, and notes stay visible after prompts scroll away." }, | |
| { title: "Git-aware execution", body: "Real repos, worktrees, diffs, and mergeable state keep the swarm grounded." } | |
| ]; | |
| const steps = [ | |
| { num: "01", title: "Install", body: "Get the CLI. Add P2P transport when the team needs it.", code: 'pip install clawteam\npip install "clawteam[p2p]"' }, | |
| { num: "02", title: "Model the work", body: "Create a team and define tasks so the board tracks the project.", code: 'clawteam team spawn-team my-team -d "Docs + engineering"\nclawteam task create my-team "Build landing page" --priority urgent' }, | |
| { num: "03", title: "Spawn agents", body: "Run any terminal-native client from the same surface.", code: "clawteam spawn tmux claude-code --team my-team --agent-name builder\nclawteam spawn tmux codex --team my-team --agent-name reviewer" } | |
| ]; | |
| const docs = [ | |
| { title: "Quick Start", body: "Install to first running swarm.", href: "https://github.com/HKUDS/ClawTeam#-quick-start" }, | |
| { title: "Skill Guide", body: "Agent-facing operating guide.", href: "skills/clawteam/SKILL.md" }, | |
| { title: "CLI Reference", body: "Commands, flags, and runtime details.", href: "skills/clawteam/references/cli-reference.md" }, | |
| { title: "Workflows", body: "Practical patterns for real teams.", href: "skills/clawteam/references/workflows.md" } | |
| ]; | |
| function HalfGlobe() { | |
| const ref = useRef(null); | |
| useEffect(() => { | |
| const canvas = ref.current; | |
| const ctx = canvas.getContext("2d"); | |
| let w = 0, h = 0, dpr = 1, R = 0, rot = 0, raf = 0; | |
| let tilt = -0.45; | |
| const agents = [ | |
| // upper arc (south pole region, appears at top) | |
| { theta: 0.3, phi: 2.6, r: 249, g: 115, b: 22 }, | |
| { theta: 1.5, phi: 2.5, r: 129, g: 140, b: 248 }, | |
| { theta: 2.7, phi: 2.7, r: 34, g: 197, b: 94 }, | |
| { theta: 3.8, phi: 2.55, r: 249, g: 115, b: 22 }, | |
| { theta: 5.0, phi: 2.65, r: 129, g: 140, b: 248 }, | |
| // middle band | |
| { theta: 0.8, phi: 2.1, r: 34, g: 197, b: 94 }, | |
| { theta: 1.9, phi: 2.2, r: 249, g: 115, b: 22 }, | |
| { theta: 3.2, phi: 2.0, r: 129, g: 140, b: 248 }, | |
| { theta: 4.4, phi: 2.15, r: 34, g: 197, b: 94 }, | |
| { theta: 5.6, phi: 2.1, r: 249, g: 115, b: 22 }, | |
| // lower band (equator, appears near bottom edge) | |
| { theta: 0.5, phi: 1.6, r: 129, g: 140, b: 248 }, | |
| { theta: 1.7, phi: 1.5, r: 249, g: 115, b: 22 }, | |
| { theta: 3.0, phi: 1.55, r: 34, g: 197, b: 94 }, | |
| { theta: 4.2, phi: 1.65, r: 129, g: 140, b: 248 }, | |
| { theta: 5.4, phi: 1.5, r: 249, g: 115, b: 22 } | |
| ]; | |
| const conns = [[0,1],[1,2],[2,3],[3,4],[4,0],[0,5],[1,6],[2,7],[3,8],[4,9],[5,6],[6,7],[7,8],[8,9],[9,5],[5,10],[6,11],[7,12],[8,13],[9,14],[10,11],[11,12],[12,13],[13,14],[14,10]]; | |
| const sph = (t, p) => ({ x: Math.sin(p)*Math.cos(t), y: Math.cos(p), z: Math.sin(p)*Math.sin(t) }); | |
| const rX = (p, a) => { const c=Math.cos(a),s=Math.sin(a); return {x:p.x, y:p.y*c-p.z*s, z:p.y*s+p.z*c}; }; | |
| const rY = (p, a) => { const c=Math.cos(a),s=Math.sin(a); return {x:p.x*c-p.z*s, y:p.y, z:p.x*s+p.z*c}; }; | |
| const xform = (t, p) => rX(rY(sph(t, p), rot), tilt); | |
| const cx = () => w * 0.5; | |
| const cy = () => h + R * 0.08; | |
| const proj = (p) => { const d=3.2, sc=d/(d-p.z); return {x:cx()+p.x*R*sc, y:cy()+p.y*R*sc, z:p.z, scale:sc}; }; | |
| const resize = () => { | |
| const rc = canvas.getBoundingClientRect(); | |
| dpr = Math.min(window.devicePixelRatio||1, 2); | |
| w = rc.width; h = rc.height; | |
| R = w * 0.3; | |
| canvas.width = Math.round(w*dpr); canvas.height = Math.round(h*dpr); | |
| ctx.setTransform(dpr,0,0,dpr,0,0); | |
| }; | |
| const onScroll = () => { | |
| const rect = canvas.getBoundingClientRect(); | |
| const vh = window.innerHeight; | |
| const center = rect.top + rect.height / 2; | |
| const progress = Math.max(0, Math.min(1, 1 - center / vh)); | |
| tilt = -1.4 + progress * 2.8; | |
| }; | |
| const drawGrid = () => { | |
| const glow = ctx.createRadialGradient(cx(),cy(),0, cx(),cy(),R*1.15); | |
| glow.addColorStop(0,"rgba(99,102,241,0.05)"); glow.addColorStop(0.4,"rgba(249,115,22,0.025)"); glow.addColorStop(1,"transparent"); | |
| ctx.beginPath(); ctx.arc(cx(),cy(),R*1.15,0,Math.PI*2); ctx.fillStyle=glow; ctx.fill(); | |
| ctx.lineWidth=1; | |
| for(let i=0;i<13;i++){const phi=((i+1)/14)*Math.PI; ctx.beginPath(); ctx.strokeStyle="rgba(148,163,184,0.16)"; let on=false; | |
| for(let j=0;j<=100;j++){const p=proj(xform((j/100)*Math.PI*2,phi)); if(p.z<-0.35||p.y>h+5){on=false;continue;} if(!on){ctx.moveTo(p.x,p.y);on=true;}else ctx.lineTo(p.x,p.y);} ctx.stroke();} | |
| for(let i=0;i<20;i++){const t=(i/20)*Math.PI*2; ctx.beginPath(); ctx.strokeStyle="rgba(148,163,184,0.16)"; let on=false; | |
| for(let j=0;j<=100;j++){const p=proj(xform(t,(j/100)*Math.PI)); if(p.z<-0.35||p.y>h+5){on=false;continue;} if(!on){ctx.moveTo(p.x,p.y);on=true;}else ctx.lineTo(p.x,p.y);} ctx.stroke();} | |
| ctx.beginPath(); ctx.arc(cx(),cy(),R*1.08,0,Math.PI*2); | |
| ctx.setLineDash([6,12]); ctx.strokeStyle="rgba(148,163,184,0.1)"; ctx.lineWidth=1; ctx.stroke(); ctx.setLineDash([]); | |
| }; | |
| const drawConns = () => { | |
| conns.forEach(([i,j])=>{ | |
| const a=proj(xform(agents[i].theta,agents[i].phi)), b=proj(xform(agents[j].theta,agents[j].phi)); | |
| if(a.z<-0.5||b.z<-0.5||a.y>h||b.y>h) return; | |
| const fade=Math.min((a.z+0.5)/0.8,(b.z+0.5)/0.8), dist=Math.hypot(b.x-a.x,b.y-a.y); | |
| if(dist<2) return; | |
| const mx=(a.x+b.x)/2, my=(a.y+b.y)/2, nx=-(b.y-a.y)/dist, ny=(b.x-a.x)/dist; | |
| ctx.beginPath(); ctx.moveTo(a.x,a.y); ctx.quadraticCurveTo(mx+nx*dist*0.12,my+ny*dist*0.12,b.x,b.y); | |
| ctx.strokeStyle=`rgba(148,163,184,${0.13*fade})`; ctx.lineWidth=0.8; ctx.stroke(); | |
| }); | |
| }; | |
| const drawAgents = (time) => { | |
| agents.forEach(agent=>{ | |
| const p=proj(xform(agent.theta,agent.phi)); | |
| if(p.z<-0.5||p.y>h) return; | |
| const fade=Math.min(1,(p.z+0.5)/0.8), pulse=1+0.2*Math.sin(time*0.002+agent.theta*5); | |
| const {r,g,b}=agent, gr=26*p.scale*pulse; | |
| const grd=ctx.createRadialGradient(p.x,p.y,0,p.x,p.y,gr); | |
| grd.addColorStop(0,`rgba(${r},${g},${b},${0.4*fade})`); grd.addColorStop(1,"transparent"); | |
| ctx.beginPath(); ctx.arc(p.x,p.y,gr,0,Math.PI*2); ctx.fillStyle=grd; ctx.fill(); | |
| ctx.beginPath(); ctx.arc(p.x,p.y,4.5*p.scale*pulse,0,Math.PI*2); ctx.fillStyle=`rgba(${r},${g},${b},${0.95*fade})`; ctx.fill(); | |
| ctx.beginPath(); ctx.arc(p.x,p.y,10*p.scale*pulse,0,Math.PI*2); ctx.strokeStyle=`rgba(${r},${g},${b},${0.2*fade})`; ctx.lineWidth=1; ctx.stroke(); | |
| }); | |
| }; | |
| const draw = (time) => { ctx.clearRect(0,0,w,h); drawGrid(); drawConns(); drawAgents(time); rot+=0.0018; raf=requestAnimationFrame(draw); }; | |
| resize(); onScroll(); draw(0); | |
| window.addEventListener("resize",resize); window.addEventListener("scroll",onScroll,{passive:true}); | |
| return ()=>{ cancelAnimationFrame(raf); window.removeEventListener("resize",resize); window.removeEventListener("scroll",onScroll); }; | |
| },[]); | |
| return ( | |
| <div className="globe-section"> | |
| <div className="globe-glow" aria-hidden="true" /> | |
| <canvas ref={ref} className="globe-canvas" aria-hidden="true" /> | |
| {orbitClients.map(c=>{ const Icon=clientIcons[c.key]; return <span key={c.key} className={`orbit-label orbit-${c.key}`}><Icon/>{c.label}</span>; })} | |
| <div className="globe-fade-top" aria-hidden="true" /> | |
| <div className="globe-fade-bottom" aria-hidden="true" /> | |
| </div> | |
| ); | |
| } | |
| function TerminalMockup() { | |
| return ( | |
| <div className="terminal"> | |
| <div className="terminal-bar"><span className="terminal-dot"/><span className="terminal-dot"/><span className="terminal-dot"/><span className="terminal-title">clawteam</span></div> | |
| <div className="terminal-body"> | |
| <div className="terminal-line"><span className="t-prompt">$</span> clawteam team spawn-team docs-sprint</div> | |
| <div className="terminal-line t-output"><span className="t-success">{"\u2713"}</span> Team "docs-sprint" created</div><br/> | |
| <div className="terminal-line"><span className="t-prompt">$</span> clawteam spawn tmux claude-code --agent-name builder</div> | |
| <div className="terminal-line t-output"><span className="t-active">{"\u25cf"}</span> Agent "builder" spawned in tmux</div><br/> | |
| <div className="terminal-line"><span className="t-prompt">$</span> clawteam team status docs-sprint</div> | |
| <div className="terminal-output-block"> | |
| <div className="t-status-header">docs-sprint <span className="t-dim">3 agents active</span></div> | |
| <div className="t-status-row"><span className="t-success">{"\u25cf"}</span> T-001 Build landing page <span className="t-badge t-done">done</span></div> | |
| <div className="t-status-row"><span className="t-active">{"\u25cf"}</span> T-002 Write API docs <span className="t-badge t-progress">active</span></div> | |
| <div className="t-status-row"><span className="t-dim">{"\u25cb"}</span> T-003 Review & merge <span className="t-badge t-blocked">blocked</span></div> | |
| </div> | |
| <div className="terminal-line" style={{marginTop:10}}><span className="t-prompt">$</span> <span className="terminal-cursor"/></div> | |
| </div> | |
| </div> | |
| ); | |
| } | |
| function App() { | |
| return ( | |
| <div className="page"> | |
| <div className="bg-gradient" aria-hidden="true"/> | |
| <header className="header"> | |
| <div className="shell header-inner"> | |
| <a className="logo" href="#top"><img src={logo} alt="ClawTeam"/><strong>ClawTeam</strong></a> | |
| <nav className="nav"><a href="#features">Features</a><a href="#workflow">How it works</a><a href="#docs">Docs</a></nav> | |
| <a className="btn-primary" href="https://github.com/HKUDS/ClawTeam" target="_blank" rel="noreferrer">GitHub</a> | |
| </div> | |
| </header> | |
| <main> | |
| <section className="hero shell" id="top"> | |
| <div className="hero-content"> | |
| <p className="badge">Agent swarm orchestration</p> | |
| <h1>Coordinate any coding agent from one CLI</h1> | |
| <p className="hero-sub">ClawTeam is the coordination layer for Claude Code, Codex, OpenClaw, nanobot, and any terminal-native client that needs to plan, delegate, and ship together.</p> | |
| <div className="hero-cta"> | |
| <a className="btn-primary" href="https://github.com/HKUDS/ClawTeam#-quick-start" target="_blank" rel="noreferrer">Get started</a> | |
| <a className="btn-ghost" href="skills/clawteam/references/cli-reference.md">CLI Reference</a> | |
| </div> | |
| </div> | |
| <div className="hero-visual"><TerminalMockup/></div> | |
| </section> | |
| <HalfGlobe/> | |
| <section className="clients shell"> | |
| <span className="clients-label">Works with</span> | |
| <div className="clients-list">{clients.map(c=>{ const Icon=clientIcons[c.key]; return <span key={c.key}><Icon/>{c.label}</span>; })}</div> | |
| </section> | |
| <section className="features shell" id="features"> | |
| <div className="section-header"><p className="section-label">Core capabilities</p><h2>Built for agent teams, not isolated sessions</h2></div> | |
| <div className="features-grid">{features.map(f=><article className="feature-card" key={f.title}><h3>{f.title}</h3><p>{f.body}</p></article>)}</div> | |
| </section> | |
| <section className="workflow shell" id="workflow"> | |
| <div className="section-header"><p className="section-label">How it works</p><h2>Three steps to a running swarm</h2></div> | |
| <div className="steps">{steps.map(s=><div className="step" key={s.num}><div className="step-info"><span className="step-num">{s.num}</span><h3>{s.title}</h3><p>{s.body}</p></div><pre className="step-code"><code>{s.code}</code></pre></div>)}</div> | |
| </section> | |
| <section className="docs shell" id="docs"> | |
| <div className="section-header"><p className="section-label">Documentation</p><h2>Learn more</h2></div> | |
| <div className="docs-grid">{docs.map(d=><a className="doc-card" key={d.title} href={d.href} target={d.href.startsWith("http")?"_blank":undefined} rel={d.href.startsWith("http")?"noreferrer":undefined}><strong>{d.title}</strong><span>{d.body}</span><span className="doc-arrow">{"\u2192"}</span></a>)}</div> | |
| </section> | |
| </main> | |
| <footer className="footer shell"> | |
| <span>ClawTeam</span> | |
| <div className="footer-links"><a href="https://github.com/HKUDS/ClawTeam">GitHub</a><a href="skills/clawteam/SKILL.md">Skill</a><a href="skills/clawteam/references/cli-reference.md">CLI Reference</a></div> | |
| </footer> | |
| </div> | |
| ); | |
| } | |
| export default App; | |