Your digital team inside Claude Code: who works on what (programmer, reviewer, security, legal, design, QA), a Watch link to what they build, and what each…

yskills' global Claude Code setup: one command makes every project on a machine use the same rules, skills, agents, hooks and plugins. Built on Everything Claude Code (the Anthropic hackathon winner's setup), curated to this stack so it costs about a fifth of the context the full version does.
Lost? docs/MAP.md says where every part lives, what loads when, and which file is the one home of each rule.
Needs Node 20+ (24 recommended), Git and Claude Code.
Windows (PowerShell; use $HOME, never ~, which PowerShell passes through as a folder named "~"):
git clone https://github.com/yverse-studio/claude-setup $HOME/claude-setup
node $HOME/claude-setup/install.mjs
Mac or Linux:
git clone https://github.com/yverse-studio/claude-setup ~/claude-setup && node ~/claude-setup/install.mjs
Update later (Windows: run the two commands one after the other, with $HOME instead of ~):
git -C ~/claude-setup pull && node ~/claude-setup/install.mjs
Re-running is safe. Your old ~/.claude/CLAUDE.md and settings.json are backed up to ~/.claude/backups/ each time; your own notes in CLAUDE.md outside the managed block, your own skills and agents, and settings keys this setup doesn't touch are kept. Flags: --dry-run, --skip-plugins, --skip-hooks. At the end it lists useful command-line tools that are missing (gh, ffmpeg, docker, python, uv, fnm) with the install command for your OS.
| Piece | What | Where it comes from |
|---|---|---|
CLAUDE.md | How Claude works for you: owns tasks end to end, verifies before pushing, screenshot-reviews UI, your default stack | CLAUDE.md |
| Settings | Auto permission mode, safe allow list, asks before force-push and deploys, denies reading .env and key files (a deny rule is not a sandbox), high effort, status line with context bar | global/settings.json |
| 13 plugins on | superpowers, frontend-design, security-guidance, TypeScript and Python LSP, Context7, Playwright, commit-commands, code-simplifier, feature-dev, claude-md-management, claude-code-setup, skill-creator (/code-review and the claude-api skill are built into Claude Code now) | Anthropic's official marketplace, config/plugins.json |
| 7 plugins per project | Cloudflare, Firebase, Stripe, Sentry, PostHog, Supabase, Vercel: installed but off; a repo turns one on in its .claude/settings.json | same |
| 17 ECC skills | Vue, Nuxt 4, Vite, API design, security review, e2e, SEO, i18n... | .claude/skills, list in config/ecc.json |
| 15 ECC agents | planner, architect, code/TS/Vue/security/database reviewers, build-error-resolver, e2e-runner, a11y, performance, SEO... | .claude/agents |
| ECC rules | common + TypeScript, Vue, Nuxt, web (language rules load only for matching files) | .claude/rules/ecc |
| ECC hooks | session memory across restarts, quality gate after edits, design-quality warning, console.log and secret checks before commits, compaction hints (PC only) | npx ecc-universal@2.2.3 |
| Toolbox | Knows which plugins, APIs and skills any kind of app needs (video, AI, payments, ads, auth, email, analytics, mobile, scraping...), searches all ~315 plugins, 293 ECC skills and skills.sh live (and points cloud threads at the Anthropic Directory), and adds them to that one project | .claude/skills/toolbox |
| Own skills | operator (idea to live app: sizes the job, staffs research/legal/design/build/QA/red team/launch/marketing, acceptance criteria before building, a 5/5 gate on every PR, three ok/no briefs, the gate merges every 5/5 PR), legal (German law for any app, privacy scan of a preview, license and trademark checks), onboard-project (set a repo up for Claude in one PR), scaffold (the exact file list of a new project's skeleton, in order), publish (deploys through Cloudflare Workers Builds with a preview per branch, no keys in GitHub), ship-check (run CI locally, then fresh code-reviewer passes until clean), ui-review (phone/desktop screenshots + critique), sell (checkout, shop and shipping from Germany: keys, tested Stripe-on-Workers code, German shop law, go-live), market (marketing plan in the PRD, launch, channels, German advertising law, weekly report), store (App Store and Google Play: Capacitor, RevenueCat, paywall defaults, review rules, keyless native builds), watch (watch a video or reel: frames plus transcript; vendored from claude-video, MIT), studio (Roblox Studio and Blender through their MCP servers on the PC: setup check, build loop, live frames yskills watches in HQ), roblox (a Roblox game: Rojo and Lune, Robux receipts, Open Cloud publish, Creator Hub steps), game-3d (a 3D game with three.js and Electron for Steam: folder layout, VRM characters and moves, asset licences checked, cloud screenshots and perf budget, Steam checklist) | .claude/skills |
| Own agents | design-critic (reviews screenshots for the "looks AI" problem), evaluator (clicks through a preview and grades a slice against its criteria), legal-reviewer (German must-haves on a preview), red-team (attacks your own preview like an outsider) | .claude/agents |
| Design taste | impeccable (design skill plus a local detect linter for AI-look patterns) and web-interface-guidelines (Vercel's UI checklist); ui-review runs both | .claude/skills (Apache-2.0 and MIT, see THIRD_PARTY.md) |
| Context guard | A hook after every tool call: at 150k tokens of context it tells Claude to work leaner, at 200k to wrap up and hand off, then again every further 50k (re-reading long contexts is most of the bill). Runs on the PC and in cloud threads | global/context-guard.mjs, cloud/setup.sh |
team mod | Your digital team inside Claude Code (terminal or desktop app, not cloud threads): a band above the prompt shows which roles are working (the nine roles: Project Manager, Researcher, Designer, Programmer, Tester, Reviewer, Security, Legal, Marketer) and what the last turn cost; /team opens a pane with each role's task and a Watch link to the dev server or page being tested. Claude's test browser (Playwright) opens as a visible Chrome window on the PC | mods/team |
| HQ dashboard | One live page for all projects (Company XY, https://claude.ai/artifact/TmQ7UpL6EPjXKkpR9S4kJT): level, money, what needs you, who works on what with whom, progress per part, Watch links. Each project's Project Manager writes its rows into it; no project has a page of its own | docs/hq-rows.md (the page lives in yverse-studio/company-xy) |
| Ponytail | Write the least code that works: reuse, standard library, platform, installed dependency, one line, then new code | .claude/skills/ponytail (MIT, see THIRD_PARTY.md) |
Threads in a Claude project run in the cloud, not on your machine. Add this repo to the project (Project settings, Repositories) and every thread loads CLAUDE.md and all skills, agents and rules from .claude/, the same files the installer copies.
Per project, once: install the Claude GitHub App on all repositories so new project repos are reachable, then in Project settings set Thread model Sonnet and Thread effort medium (the default is Opus at high effort), and paste operator/templates/project-instructions.md into Project instructions (steps: docs/USING.md).
Plugins and network need a cloud environment (Project settings > Cloud environment > Add cloud environment). Pick the same one in every project that uses this setup.
cloud/setup.sh. It installs 11 of the 13 global plugins at user scope (Context7 and skill-creator stay on the PC: Context7 asks for a sign-in a thread can't do, and threads have Anthropic's skill-creator built in) and makes built-in subagents default to Sonnet. The result is cached and rebuilt about weekly, so threads don't pay for it each time. Plugin tools connect a few seconds after a thread starts.cdn.jsdelivr.net, unpkg.com (CDN scripts, e.g. GSAP for HyperFrames)*.yskills.workers.dev (your own live sites and branch previews, so threads can open and screenshot what they deployed; blocked otherwise)*.youtube.com, *.googlevideo.com, only if threads work on videomain and gives every other branch its own preview (the publish skill); GitHub only runs verify and holds no keys. Threads never deploy themselves: wrangler deploy through the cloud proxy fails, because the proxy replaces Workers' asset-upload token (401). Optional: a Stripe test key as an API credential for api.stripe.com (Pro/Max plans); the proxy adds it and Claude never sees it.On your PC, global/settings.json lets Claude read the official docs of your stack (Nuxt, Vue, Cloudflare, Stripe, GitHub, MDN, German law texts...) without asking each time. Cloud threads don't get these settings (the setup script installs plugins and one env key, not the permission list), so there a link nobody posted in the chat still asks before it opens; whether adding the list to the setup script would stop that is untested. Threads research with WebSearch instead.
operator skill sizes it, researches, plans and builds it, and sends you three ok/no briefs, and the gate merges every PR at 5/5; toolbox adds the tools to that project only.onboard-project: a short CLAUDE.md, .claude/settings.json with the right per-project plugins, and a verify skill matching CI.ship-check on its own; ask for it any time.ui-review takes the screenshots, design-critic lists what to fix.claude --chrome (needs the Claude in Chrome extension) when a page needs your login; Playwright covers everything headless./feature-dev.CLAUDE.md.config/plugins.json (move one between global and project).config/ecc.json, check out that ECC version, run node scripts/sync-ecc.mjs <ECC checkout>.node scripts/check.mjs validates everything; CI runs it plus a full install into a throwaway home directory.Research notes and sources behind these choices: docs/RESEARCH.md. The roadmap every new project follows: docs/WORKFLOW.md. The practice runs before the first real one: docs/TEST-PROJECTS.md.
hooks/register.tsx 168 lines1import { atom, read, update } from 'claude-code'
2import type { Register } from 'claude-code'
3
4import type { Preview, RoleId, Run, TurnCost } from '../types'
5
6// yskills' digital team, drawn from what Claude Code already does: the session itself is the
7// Project Manager, each subagent it starts is a teammate in a role. A band above the prompt shows who is
8// working; /team opens a pane with each role's task and a Watch link to what is being built.
9const runs = atom({ plugin: 'team', key: 'runs' } as const, [] as Run[])
10const isLeadWorking = atom({ plugin: 'team', key: 'isLeadWorking' } as const, false)
11const preview = atom({ plugin: 'team', key: 'preview' } as const, null as Preview | null)
12const last = atom({ plugin: 'team', key: 'last' } as const, null as TurnCost | null)
13
14const PANE = 'team'
15const KEPT_RUNS = 40
16
17// The nine roles HQ and the thread titles use. The session itself is the Project Manager.
18export const ROLES: { id: RoleId; name: string; agents: RegExp }[] = [
19 { id: 'pm', name: 'Project Manager', agents: /^planner$|^architect$|^plan$/i },
20 { id: 'researcher', name: 'Researcher', agents: /^$/ },
21 { id: 'designer', name: 'Designer', agents: /design-critic|a11y/i },
22 { id: 'programmer', name: 'Programmer', agents: /^$/ },
23 { id: 'tester', name: 'Tester', agents: /evaluator|e2e/i },
24 { id: 'reviewer', name: 'Reviewer', agents: /code-reviewer|typescript-reviewer|vue-reviewer|database-reviewer|silent-failure/i },
25 { id: 'security', name: 'Security', agents: /security|red-team/i },
26 { id: 'legal', name: 'Legal', agents: /legal/i },
27 { id: 'marketer', name: 'Marketer', agents: /seo/i },
28]
29
30const RESEARCH = /^researcher|research|competitor/i
31
32export function roleOf(agent: string, description = ''): RoleId {
33 // Security and legal first so "security-reviewer" and "legal-reviewer" don't land in Reviewer;
34 // anything unknown is a Programmer.
35 for (const id of ['security', 'legal', 'designer', 'tester', 'reviewer', 'marketer', 'pm'] as const) {
36 if (ROLES.find(r => r.id === id)!.agents.test(agent)) return id
37 }
38 return RESEARCH.test(description) ? 'researcher' : 'programmer'
39}
40
41// A dev server, a Worker preview or a page the test browser opened: what the team is building.
42const PREVIEW_URL = /https?:\/\/(?:localhost|127\.0\.0\.1)(?::\d+)?[^\s'"<>)]*|https:\/\/[\w.-]+\.workers\.dev[^\s'"<>)]*/
43export const findPreview = (text: string) => text.match(PREVIEW_URL)?.[0]?.replace(/[.,;:]+$/, '')
44
45const k = (n: number) => `${Math.round(n / 1000)}k`
46// The band and the pane must never hold up Claude's work: a failed write leaves old rows showing.
47const quiet = () => undefined
48
49export const register: Register = on => {
50 on('session.start', async ($, e, next) => {
51 await $.command.register({ name: 'team', description: 'Show your team: who works on what, and a Watch link' })
52 return next(e)
53 })
54
55 on('command.run', { command: 'team' }, async $ => {
56 await $.ui.open({ id: PANE, title: 'Team' })
57 return { text: 'Team pane opened.' }
58 })
59
60 on('prompt.submit', async ($, e, next) => {
61 await update($, runs, list => list.filter(r => !r.isDone)).catch(quiet)
62 await update($, isLeadWorking, () => true).catch(quiet)
63 return next(e)
64 }).catch(($, e, next) => next(e))
65
66 on('agent.spawn', async ($, e, next) => {
67 const result = await next(e)
68 if (result.agentId) {
69 const agent = e.subagentType || 'general-purpose'
70 const run: Run = { id: result.agentId, role: roleOf(agent, e.description), agent, task: e.description, isDone: false }
71 await update($, runs, list => [...list, run].slice(-KEPT_RUNS)).catch(quiet)
72 }
73 return result
74 }).catch(($, e, next) => next(e))
75
76 on('tool.call', async ($, e, next) => {
77 const ran = await next(e)
78 // Only the test browser and a command that starts a server (dev, preview, wrangler) set it,
79 // so a curl or a test log doesn't move the Watch link.
80 const isServer = e.tool === 'Bash' && /\b(dev|preview|serve|start|wrangler)\b/.test(String((e as { command?: unknown }).command ?? ''))
81 const url =
82 e.tool.endsWith('browser_navigate') && typeof (e as { url?: unknown }).url === 'string'
83 ? (e as { url: string }).url
84 : isServer && !ran.deny ? findPreview(ran.text ?? '') : undefined
85 if (url) await update($, preview, () => ({ url, from: e.tool === 'Bash' ? 'dev server' : 'test browser' })).catch(quiet)
86 return ran
87 }).catch(($, e, next) => next(e))
88
89 on('turn.complete', async ($, e, next) => {
90 if (e.agentId) {
91 await update($, runs, list => list.map(r => (r.id === e.agentId ? { ...r, isDone: true } : r))).catch(quiet)
92 const run = (await read($, runs)).find(r => r.id === e.agentId)
93 if (run) $.ui.toast(`${ROLES.find(r => r.id === run.role)?.name}: finished ${run.task}`)
94 } else {
95 await update($, isLeadWorking, () => false).catch(quiet)
96 if (e.usage) {
97 const u = e.usage
98 const cost: TurnCost = {
99 tokens: u.input_tokens + u.cache_read_input_tokens + u.cache_creation_input_tokens + u.output_tokens,
100 cacheRead: u.cache_read_input_tokens,
101 }
102 await update($, last, () => cost).catch(quiet)
103 }
104 }
105 return next(e)
106 })
107
108 on('ui.render', { component: 'AbovePrompt' }, async ($, e, next) => {
109 const list = await read($, runs)
110 const lead = await read($, isLeadWorking)
111 const cost = await read($, last)
112 if (e.props.hasSurvey || (list.length === 0 && cost === null)) return next(e)
113
114 const { Box, Text } = $.ui.resolve(e)
115 const busy = new Set(list.filter(r => !r.isDone).map(r => r.role))
116 if (lead) busy.add('pm')
117 const shown = ROLES.filter(r => busy.has(r.id) || list.some(x => x.role === r.id))
118
119 return (
120 <Box flexDirection="column">
121 <Text>
122 {shown.map(r => `${busy.has(r.id) ? '●' : '○'} ${r.name}`).join(' ')}
123 <Text dimColor> /team for details</Text>
124 </Text>
125 {cost && (
126 <Text dimColor>
127 Last turn: {k(cost.tokens)} tokens, {k(cost.cacheRead)} of them re-read context
128 </Text>
129 )}
130 </Box>
131 )
132 })
133
134 on('ui.render', { component: 'Pane', requestId: PANE }, async ($, e) => {
135 const { Box, Text, Link } = $.ui.resolve(e)
136 const list = await read($, runs)
137 const lead = await read($, isLeadWorking)
138 const live = await read($, preview)
139
140 return (
141 <Box flexDirection="column">
142 {ROLES.map(role => {
143 const mine = list.filter(r => r.role === role.id)
144 const now = mine.filter(r => !r.isDone)
145 const isWorking = role.id === 'pm' ? lead || now.length > 0 : now.length > 0
146 const task = now[0]?.task ?? (role.id === 'pm' ? (lead ? 'Working on your request' : 'Waiting for you') : mine.at(-1)?.task)
147 return (
148 <Box key={role.id} flexDirection="column" marginBottom={1}>
149 <Text bold={isWorking} dimColor={!isWorking}>
150 {isWorking ? '●' : '○'} {role.name}
151 {now.length > 1 ? ` (${now.length})` : ''}
152 </Text>
153 <Text dimColor> {task ?? 'idle'}{!isWorking && mine.length > 0 && role.id !== 'pm' ? ' (done)' : ''}</Text>
154 </Box>
155 )
156 })}
157 {live ? (
158 <Text>
159 Watch ({live.from}): <Link href={live.url} label={live.url} />
160 </Text>
161 ) : (
162 <Text dimColor>Watch: nothing running yet. A dev server or the test browser shows up here.</Text>
163 )}
164 </Box>
165 )
166 })
167}
168types/index.d.ts 11 lines1export type RoleId = 'pm' | 'researcher' | 'designer' | 'programmer' | 'tester' | 'reviewer' | 'security' | 'legal' | 'marketer'
2export type Run = { id: string; role: RoleId; agent: string; task: string; isDone: boolean }
3export type Preview = { url: string; from: string }
4export type TurnCost = { tokens: number; cacheRead: number }
5
6declare module 'claude-code' {
7 interface PluginState {
8 team: { runs: Run[]; isLeadWorking: boolean; preview: Preview | null; last: TurnCost | null }
9 }
10}
11