{"id":"d86e6241-02ec-4962-b793-ae4398d83d30","entityType":"agent","slug":"clawhub-z-zihan-fe-cli","name":"Fe Cli","canonicalUrl":"https://www.xpersona.co/agent/clawhub-z-zihan-fe-cli","canonicalPath":"/agent/clawhub-z-zihan-fe-cli","generatedAt":"2026-10-10T10:43:17.987Z","source":"CLAWHUB","claimStatus":"UNCLAIMED","verificationTier":"NONE","summary":{"evidence":{"source":"editorial-content","verified":true,"confidence":"high","updatedAt":"2026-10-10T08:12:40.692Z","emptyReason":null},"description":"前端项目脚手架 CLI。支持 React/Vue/Next.js/Nuxt.js/Astro/React Native + shadcn/Ant Design/MUI/Tailwind + Zustand/Redux + i18n，初始化标准化前端项目。支持 9 种项目类型：Web SPA、后台管理、移动 H5、... Skill: Fe Cli Owner: z-zihan Summary: 前端项目脚手架 CLI。支持 React/Vue/Next.js/Nuxt.js/Astro/React Native + shadcn/Ant Design/MUI/Tailwind + Zustand/Redux + i18n，初始化标准化前端项目。支持 9 种项目类型：Web SPA、后台管理、移动 H5、... Tags: latest:3.1.1 Version history: v3.1.1 | 2026-05-23T19:27:39.296Z | user Auto-publish from commit 265fd1e38f8cebfbc499cb8d2ae54f2fce9cf2e4 v3.1.0 | 2026-05-23T19:12:37.955Z | user Auto-publish from commit deb04d5ca6ff","descriptionLabel":"Technical summary","evidenceSummary":"Capability contract not published. No trust telemetry is available yet. 1.6K downloads reported by the source. Last updated 10/10/2026.","installCommand":"clawhub skill install s17bsrqjkb5zv8sm90kdv3zawn83g42h:fe-cli","sourceUrl":"https://clawhub.ai/z-zihan/fe-cli","homepage":"https://clawhub.ai/z-zihan/skills/fe-cli","primaryLinks":[{"label":"View on ClawHub","url":"https://clawhub.ai/z-zihan/fe-cli","kind":"source"},{"label":"Homepage","url":"https://clawhub.ai/z-zihan/skills/fe-cli","kind":"homepage"}],"safetyScore":84,"overallRank":62,"popularityScore":64,"trustScore":null,"claimedByName":null,"isOwner":false,"seoDescription":"前端项目脚手架 CLI。支持 React/Vue/Next.js/Nuxt.js/Astro/React Native + shadcn/Ant Design/MUI/Tailwind + Zustand/Redux + i18n，初始化标准化前端项目。支持 9 种项目类型：Web SPA、后台管理、移动 H5、..."},"coverage":{"evidence":{"source":"public-profile","verified":false,"confidence":"medium","updatedAt":"2026-10-10T08:12:40.692Z","emptyReason":null},"protocols":[{"protocol":"OPENCLEW","label":"OpenClaw","status":"self-declared","notes":"Declared in the public agent profile."}],"capabilities":[],"verifiedCount":0,"selfDeclaredCount":1,"capabilityMatrix":{"rows":[{"key":"OPENCLEW","type":"protocol","support":"unknown","confidenceSource":"profile","notes":"Listed on profile"}],"flattenedTokens":"protocol:OPENCLEW|unknown|profile"}},"adoption":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"medium","updatedAt":"2026-10-10T08:12:40.692Z","emptyReason":null},"stars":null,"forks":null,"downloads":1566,"packageName":null,"latestVersion":"3.1.1","tractionLabel":"1.6K downloads"},"release":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"medium","updatedAt":"2026-10-10T08:12:40.692Z","emptyReason":null},"lastUpdatedAt":"2026-10-10T08:12:40.692Z","lastCrawledAt":"2026-10-10T08:12:40.692Z","lastIndexedAt":null,"nextCrawlAt":"2026-10-11T08:12:40.692Z","lastVerifiedAt":null,"highlights":[{"version":"3.1.1","createdAt":"2026-05-23T19:27:39.296Z","changelog":"Auto-publish from commit 265fd1e38f8cebfbc499cb8d2ae54f2fce9cf2e4","fileCount":16,"zipByteSize":67342},{"version":"3.1.0","createdAt":"2026-05-23T19:12:37.955Z","changelog":"Auto-publish from commit deb04d5ca6ff235b0af79b0c9b3e95f51cc57c73","fileCount":15,"zipByteSize":65407},{"version":"3.0.0","createdAt":"2026-05-21T10:46:24.734Z","changelog":"Auto-publish from commit 1554c716f32f3e405b34576510db4f3167f0d361","fileCount":14,"zipByteSize":52036},{"version":"2.0.0","createdAt":"2026-05-18T12:47:46.207Z","changelog":"Auto-publish from commit dc4421fe7970ce27a9e172af29c59ab38d8373a3","fileCount":11,"zipByteSize":41635},{"version":"0.3.0","createdAt":"2026-05-18T08:10:54.345Z","changelog":"Auto-publish from commit 5b27ac4957173e2b02bfd6ba2b7bec399aaa54be","fileCount":11,"zipByteSize":41635},{"version":"1.2.1","createdAt":"2026-05-18T07:45:26.906Z","changelog":"fe-cli 1.2.1 No file changes detected for this version. - No updates or changes to code or documentation in this release. - The current functionality, features, and behavior remain unchanged from version 1.2.0.","fileCount":11,"zipByteSize":41635},{"version":"1.2.0","createdAt":"2026-05-16T13:35:12.128Z","changelog":"fe-cli 1.2.0 - 更新了主技能文档和各类型子技能文档，提升了内容一致性和清晰度 - references/shared-base.md、web/SKILL.md、miniapp/SKILL.md 等文档内容同步更新，与新版主技能规范保持一致 - 无功能变更，仅文档调整，便于用户快速理解不同项目类型和共享规则","fileCount":11,"zipByteSize":41635},{"version":"1.1.0","createdAt":"2026-05-16T11:54:28.672Z","changelog":"Bilingual restructure","fileCount":11,"zipByteSize":41533}]},"execution":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"low","updatedAt":null,"emptyReason":"No published capability contract is available yet."},"installCommand":"clawhub skill install s17bsrqjkb5zv8sm90kdv3zawn83g42h:fe-cli","setupComplexity":"low","setupSteps":["Setup complexity is classified as HIGH. You must provision dedicated cloud infrastructure or an isolated VM. Do not run this directly on your local workstation.","Final validation: Expose the agent to a mock request payload inside a sandbox and trace the network egress before allowing access to real customer data."],"contract":{"contractStatus":"missing","authModes":[],"requires":[],"forbidden":[],"supportsMcp":false,"supportsA2a":false,"supportsStreaming":false,"inputSchemaRef":null,"outputSchemaRef":null,"dataRegion":null,"contractUpdatedAt":null,"sourceUpdatedAt":null,"freshnessSeconds":null},"invocationGuide":{"preferredApi":{"snapshotUrl":"https://www.xpersona.co/api/v1/agents/clawhub-z-zihan-fe-cli/snapshot","contractUrl":"https://www.xpersona.co/api/v1/agents/clawhub-z-zihan-fe-cli/contract","trustUrl":"https://www.xpersona.co/api/v1/agents/clawhub-z-zihan-fe-cli/trust"},"curlExamples":["curl -s \"https://www.xpersona.co/api/v1/agents/clawhub-z-zihan-fe-cli/snapshot\"","curl -s \"https://www.xpersona.co/api/v1/agents/clawhub-z-zihan-fe-cli/contract\"","curl -s \"https://www.xpersona.co/api/v1/agents/clawhub-z-zihan-fe-cli/trust\""],"jsonRequestTemplate":{"query":"summarize this repo","constraints":{"maxLatencyMs":2000,"protocolPreference":["OPENCLEW"]}},"jsonResponseTemplate":{"ok":true,"result":{"summary":"...","confidence":0.9},"meta":{"source":"CLAWHUB","generatedAt":"2026-10-10T10:43:17.985Z"}},"retryPolicy":{"maxAttempts":3,"backoffMs":[500,1500,3500],"retryableConditions":["HTTP_429","HTTP_503","NETWORK_TIMEOUT"]}},"endpoints":{"dossierUrl":"https://www.xpersona.co/api/v1/agents/clawhub-z-zihan-fe-cli/dossier","snapshotUrl":"https://www.xpersona.co/api/v1/agents/clawhub-z-zihan-fe-cli/snapshot","contractUrl":"https://www.xpersona.co/api/v1/agents/clawhub-z-zihan-fe-cli/contract","trustUrl":"https://www.xpersona.co/api/v1/agents/clawhub-z-zihan-fe-cli/trust"}},"reliability":{"evidence":{"source":"runtime-metrics","verified":false,"confidence":"low","updatedAt":null,"emptyReason":"No trust, reliability, or runtime telemetry is available."},"trust":{"status":"unavailable","handshakeStatus":"UNKNOWN","verificationFreshnessHours":null,"reputationScore":null,"p95LatencyMs":null,"successRate30d":null,"fallbackRate":null,"attempts30d":null,"trustUpdatedAt":null,"trustConfidence":"unknown","sourceUpdatedAt":null,"freshnessSeconds":null},"decisionGuardrails":{"doNotUseIf":["Contract metadata is missing or unavailable for deterministic execution."],"safeUseWhen":[],"riskFlags":["missing_or_unavailable_contract","trust_data_unavailable","schema_references_missing"],"operationalConfidence":"low"},"executionMetrics":{"observedLatencyMsP50":null,"observedLatencyMsP95":null,"estimatedCostUsd":null,"uptime30d":null,"rateLimitRpm":null,"rateLimitBurst":null,"lastVerifiedAt":null,"verificationSource":null},"runtimeMetrics":{"successRate":null,"avgLatencyMs":null,"avgCostUsd":null,"hallucinationRate":null,"retryRate":null,"disputeRate":null,"p50Latency":null,"p95Latency":null,"lastUpdated":null}},"benchmarks":{"evidence":{"source":"no-benchmark-data","verified":false,"confidence":"low","updatedAt":null,"emptyReason":"No benchmark suites or observed failure patterns are available."},"suites":[],"failurePatterns":[]},"artifacts":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"high","updatedAt":"2026-10-10T08:12:40.692Z","emptyReason":null},"readme":"Skill: Fe Cli\n\nOwner: z-zihan\n\nSummary: 前端项目脚手架 CLI。支持 React/Vue/Next.js/Nuxt.js/Astro/React Native + shadcn/Ant Design/MUI/Tailwind + Zustand/Redux + i18n，初始化标准化前端项目。支持 9 种项目类型：Web SPA、后台管理、移动 H5、...\n\nTags: latest:3.1.1\n\nVersion history:\n\nv3.1.1 | 2026-05-23T19:27:39.296Z | user\n\nAuto-publish from commit 265fd1e38f8cebfbc499cb8d2ae54f2fce9cf2e4\n\nv3.1.0 | 2026-05-23T19:12:37.955Z | user\n\nAuto-publish from commit deb04d5ca6ff235b0af79b0c9b3e95f51cc57c73\n\nv3.0.0 | 2026-05-21T10:46:24.734Z | user\n\nAuto-publish from commit 1554c716f32f3e405b34576510db4f3167f0d361\n\nv2.0.0 | 2026-05-18T12:47:46.207Z | user\n\nAuto-publish from commit dc4421fe7970ce27a9e172af29c59ab38d8373a3\n\nv0.3.0 | 2026-05-18T08:10:54.345Z | user\n\nAuto-publish from commit 5b27ac4957173e2b02bfd6ba2b7bec399aaa54be\n\nv1.2.1 | 2026-05-18T07:45:26.906Z | auto\n\nfe-cli 1.2.1\n\nNo file changes detected for this version.\n\n- No updates or changes to code or documentation in this release.\n- The current functionality, features, and behavior remain unchanged from version 1.2.0.\n\nv1.2.0 | 2026-05-16T13:35:12.128Z | auto\n\nfe-cli 1.2.0\n\n- 更新了主技能文档和各类型子技能文档，提升了内容一致性和清晰度\n- references/shared-base.md、web/SKILL.md、miniapp/SKILL.md 等文档内容同步更新，与新版主技能规范保持一致\n- 无功能变更，仅文档调整，便于用户快速理解不同项目类型和共享规则\n\nv1.1.0 | 2026-05-16T11:54:28.672Z | user\n\nBilingual restructure\n\nv0.1.99 | 2026-05-16T03:23:20.493Z | user\n\nAuto-publish from commit 926041209e8cad0642bea27605a45317279cea93\n\nv0.1.91 | 2026-05-15T12:23:05.561Z | user\n\nAuto-publish from commit 604552c799ede974a669458e3178eaa1183f4b64\n\nv0.1.16 | 2026-05-13T08:38:36.042Z | user\n\nAuto-publish from commit ab4c08dbf27766afc28cc96c70f33fb57cd3ab0f\n\nv0.1.15 | 2026-05-13T08:34:51.783Z | user\n\nAuto-publish from commit a820cbbfef21254fde92463c892723b17525ea7d\n\nv0.1.14 | 2026-05-13T08:26:54.787Z | user\n\nAuto-publish from commit e89b017b44d4aecc7f3fcec49984326f8fb13b68\n\nv0.1.13 | 2026-05-13T08:21:26.545Z | user\n\nAuto-publish from commit 156bbd703a6695cc497d46b47995beeed7c270af\n\nv0.1.12 | 2026-05-13T08:18:30.704Z | user\n\nAuto-publish from commit 17c37ea9460b513eb588fd0e695ad04f2a3094d9\n\nv0.1.11 | 2026-05-13T07:58:36.893Z | user\n\nAuto-publish from commit cafe488469633ddc923de0af49ee309433a36210\n\nv0.1.0 | 2026-05-13T07:40:36.387Z | user\n\nAuto-publish from commit a2b54a9f3725f5e46f05a60b9da50e8fa6649432\n\nv0.1.9 | 2026-05-13T07:38:02.237Z | user\n\nAuto-publish from commit a2b54a9f3725f5e46f05a60b9da50e8fa6649432\n\nv0.1.8 | 2026-05-13T07:37:14.527Z | user\n\nAuto-publish from commit 5b23c3f741c7cbd93eea641ceffac859794cd51d\n\nv0.1.7 | 2026-05-13T07:26:08.891Z | user\n\nAuto-publish from commit 87b9b9b6150f5e87fad9f23e4328bdac8a477900\n\nv0.0.2 | 2026-05-13T03:27:43.349Z | user\n\nAuto-publish from commit 8ed7c3368b1d64ba80173d60f10671370750ae64\n\nv0.0.1 | 2026-05-13T02:51:10.910Z | user\n\nAuto-publish from commit 7b1724dfc3fcf2e7df4531c11f6e957d9cf59e37\n\nArchive index:\n\nArchive v3.1.1: 16 files, 67342 bytes\n\nFiles: admin/SKILL.md (17263b), astro/SKILL.md (6971b), electron/SKILL.md (8809b), h5/SKILL.md (7869b), miniapp/SKILL.md (10254b), references/ai-project-md.md (7691b), references/shared-base.md (25813b), references/shared-config.md (5072b), references/shared-infrastructure.md (34645b), rn/SKILL.md (7722b), skill-card.md (2490b), SKILL.md (15570b), ssr/SKILL.md (10949b), tauri/SKILL.md (7425b), web/SKILL.md (8487b), _meta.json (125b)\n\nFile v3.1.1:admin/SKILL.md\n\n---\nname: fe-cli-admin\ndescription: >\n  Scaffold an Admin Dashboard frontend project. For data management backends, CRUD systems,\n  and internal tools. Features permission-based routing, login page, Ant Design Pro layout.\n  Triggered as a sub-skill of fe-cli when user wants an admin/后台管理 project type.\n---\n\n# fe-cli-admin — Admin Dashboard Scaffolding\n\n## Workflow\n\n### Step 1: Gather Options\n\nSkip questions already answered in user's request.\n\n1. **Framework**: React 19 / Vue 3 (React recommended for admin, Ant Design is React-only; Vue admin 推荐 Element Plus)\n2. **Styling/UI**: Ant Design (default React) / shadcn/ui + Lucide (React) / Element Plus (default Vue) / shadcn-vue + Vue Bits (Vue) / Tailwind CSS / MUI\n3. **CSS Preprocessor**: Sass / Less (default: Sass)\n4. **State Management**: Zustand / Redux Toolkit / Pinia (Vue) / None\n5. **Charts**: Recharts (lightweight) / ECharts (heavy) / None\n6. **i18n**: react-i18next / vue-i18n / None\n7. **Testing**: Vitest / None\n8. **Pre-commit hooks**: husky + lint-staged + commitlint? (default: No)\n9. **Project name**: string (required)\n\n### Step 2: Scaffold Project\n\n```\nbun create vite <project-name> --template react-ts\n```\n\nFor Vue: `bun create vite <project-name> --template vue-ts`\n\n(Use `pnpm create vite` if bun unavailable)\n\n### Step 3: Install Dependencies\n\n**React (Ant Design):**\n```\nbun add antd @ant-design/icons @ant-design/v5-patch-for-react-19 react-router-dom dayjs\n```\n\n**React (shadcn/ui):**\n```\nbun add react-router-dom dayjs lucide-react && npx shadcn@latest init\n```\n\n**Vue (Element Plus):**\n```\nbun add element-plus @element-plus/icons-vue vue-router dayjs\n```\n\n**Vue (shadcn-vue):**\n```\nbun add vue-router dayjs lucide-vue-next && npx shadcn-vue@latest init\n```\n\nPlus extras based on selections (zustand, pinia, i18n, charts, etc.)\n\n**Charts:**\n- Recharts: `bun add recharts`\n- ECharts: `bun add echarts echarts-for-react` (React) / `bun add echarts vue-echarts` (Vue)\n\nFor date handling: `pnpm add dayjs` (recommended; moment is deprecated and not recommended).\n\n### Step 4: Generate Admin-Specific Files\n\n```\nsrc/\n├── components/                 # Global components (from shared infrastructure)\n│   ├── AppProvider.tsx\n│   ├── AuthGuard.tsx\n│   ├── GlobalLoading.tsx\n│   ├── ErrorBoundary.tsx\n│   ├── PageLoading.tsx\n│   ├── EmptyState.tsx\n│   └── ErrorState.tsx\n├── layouts/\n│   └── AdminLayout.tsx         # Sidebar + Header + Content layout (admin-specific)\n├── pages/\n│   ├── Login/\n│   │   └── index.tsx           # Login page with form\n│   ├── Dashboard/\n│   │   └── index.tsx           # Dashboard with stats cards\n│   ├── List/\n│   │   └── index.tsx           # Table list page template\n│   ├── Form/\n│   │   └── index.tsx           # Form page template\n│   └── NotFound.tsx\n├── store/                      # State management (from shared infrastructure)\n│   └── useUserStore.ts         # Auth state (token, user info, login/logout)\n├── config/\n│   └── routes.tsx              # Route config with permission guard (from shared infrastructure)\n├── hooks/                      # Common hooks (from shared infrastructure)\n├── theme/                      # Theme system (from shared infrastructure)\n├── App.tsx                     # Router + AuthProvider\n└── main.tsx\n```\n\n### Step 5: Key Templates\n\n**`layouts/AdminLayout.tsx`** — Classic admin layout:\n> **Note:** Inline styles below are for quick scaffolding. **Production environments should replace them with SCSS modules** (e.g., `import styles from './AdminLayout.module.scss'`).\n```tsx\nimport { useState } from 'react';\nimport { Outlet, useNavigate, useLocation } from 'react-router-dom';\nimport { Layout, Menu, Button, Dropdown, Avatar, theme } from 'antd';\nimport {\n  DashboardOutlined, FileTextOutlined, FormOutlined,\n  MenuFoldOutlined, MenuUnfoldOutlined, UserOutlined, LogoutOutlined,\n} from '@ant-design/icons';\n\nconst { Header, Sider, Content } = Layout;\n\n// Menu items - extend based on project needs\nconst menuItems = [\n  { key: '/dashboard', icon: <DashboardOutlined />, label: '仪表盘' },\n  { key: '/list', icon: <FileTextOutlined />, label: '列表页' },\n  { key: '/form', icon: <FormOutlined />, label: '表单页' },\n];\n\nexport default function AdminLayout() {\n  const [collapsed, setCollapsed] = useState(false);\n  const navigate = useNavigate();\n  const location = useLocation();\n  const { token: { colorBgContainer, borderRadiusLG } } = theme.useToken();\n\n  return (\n    <Layout style={{ minHeight: '100vh' }}>\n      <Sider trigger={null} collapsible collapsed={collapsed}>\n        <div style={{ height: 32, margin: 16, background: 'rgba(255,255,255,0.2)', borderRadius: 6 }} />\n        <Menu\n          theme=\"dark\"\n          mode=\"inline\"\n          selectedKeys={[location.pathname]}\n          items={menuItems}\n          onClick={({ key }) => navigate(key)}\n        />\n      </Sider>\n      <Layout>\n        <Header style={{ padding: '0 24px', background: colorBgContainer, display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>\n          <Button type=\"text\" icon={collapsed ? <MenuUnfoldOutlined /> : <MenuFoldOutlined />} onClick={() => setCollapsed(!collapsed)} />\n          <Dropdown menu={{ items: [{ key: 'logout', icon: <LogoutOutlined />, label: '退出登录' }] }}>\n            <Avatar icon={<UserOutlined />} style={{ cursor: 'pointer' }} />\n          </Dropdown>\n        </Header>\n        <Content style={{ margin: 24, padding: 24, background: colorBgContainer, borderRadius: borderRadiusLG, minHeight: 280 }}>\n          <Outlet />\n        </Content>\n      </Layout>\n    </Layout>\n  );\n}\n```\n\n**`pages/Login/index.tsx`** — Login page:\n> **Note:** Inline styles below are for quick scaffolding. **Production environments should replace them with SCSS modules**.\n```tsx\nimport { useState, type FormEvent } from 'react';\nimport { useNavigate } from 'react-router-dom';\nimport { Form, Input, Button, Card, message } from 'antd';\nimport { UserOutlined, LockOutlined } from '@ant-design/icons';\n\nexport default function Login() {\n  const [loading, setLoading] = useState(false);\n  const navigate = useNavigate();\n\n  const onFinish = async (values: { username: string; password: string }) => {\n    setLoading(true);\n    try {\n      // Replace with actual API call\n      // const { token } = await userApi.login(values);\n      // authStore.login(token);\n      message.success('登录成功');\n      navigate('/dashboard', { replace: true });\n    } catch {\n      message.error('登录失败');\n    } finally {\n      setLoading(false);\n    }\n  };\n\n  return (\n    <div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', minHeight: '100vh', background: '#f0f2f5' }}>\n      <Card title=\"系统登录\" style={{ width: 400 }}>\n        <Form onFinish={onFinish} size=\"large\">\n          <Form.Item name=\"username\" rules={[{ required: true, message: '请输入用户名' }]}>\n            <Input prefix={<UserOutlined />} placeholder=\"用户名\" />\n          </Form.Item>\n          <Form.Item name=\"password\" rules={[{ required: true, message: '请输入密码' }]}>\n            <Input.Password prefix={<LockOutlined />} placeholder=\"密码\" />\n          </Form.Item>\n          <Form.Item>\n            <Button type=\"primary\" htmlType=\"submit\" loading={loading} block>登录</Button>\n          </Form.Item>\n        </Form>\n      </Card>\n    </div>\n  );\n}\n```\n\n**`pages/Dashboard/index.tsx`** — Dashboard with stats:\n```tsx\nimport { Row, Col, Card, Statistic } from 'antd';\nimport { UserOutlined, ShoppingCartOutlined, RiseOutlined, FileTextOutlined } from '@ant-design/icons';\n\nexport default function Dashboard() {\n  return (\n    <div>\n      <Row gutter={[16, 16]}>\n        <Col xs={24} sm={12} lg={6}>\n          <Card><Statistic title=\"用户数\" value={1893} prefix={<UserOutlined />} /></Card>\n        </Col>\n        <Col xs={24} sm={12} lg={6}>\n          <Card><Statistic title=\"订单数\" value={568} prefix={<ShoppingCartOutlined />} /></Card>\n        </Col>\n        <Col xs={24} sm={12} lg={6}>\n          <Card><Statistic title=\"销售额\" value={93456} prefix={<RiseOutlined />} suffix=\"元\" /></Card>\n        </Col>\n        <Col xs={24} sm={12} lg={6}>\n          <Card><Statistic title=\"文章数\" value={128} prefix={<FileTextOutlined />} /></Card>\n        </Col>\n      </Row>\n    </div>\n  );\n}\n```\n\n**`pages/List/index.tsx`** — Table list template:\n> **Note:** Inline styles below are for quick scaffolding. **Production environments should replace them with SCSS modules**.\n```tsx\nimport { useState } from 'react';\nimport { Table, Button, Input, Space, Tag, type TableColumnsType } from 'antd';\nimport { PlusOutlined, SearchOutlined } from '@ant-design/icons';\n\ninterface DataItem { id: number; name: string; status: 'active' | 'inactive'; createdAt: string; }\n\nexport default function List() {\n  const [data] = useState<DataItem[]>([]);\n  const columns: TableColumnsType<DataItem> = [\n    { title: 'ID', dataIndex: 'id', key: 'id', width: 80 },\n    { title: '名称', dataIndex: 'name', key: 'name' },\n    { title: '状态', dataIndex: 'status', key: 'status', render: (s) => <Tag color={s === 'active' ? 'green' : 'red'}>{s}</Tag> },\n    { title: '创建时间', dataIndex: 'createdAt', key: 'createdAt' },\n    { title: '操作', key: 'action', render: () => <Space><Button type=\"link\" size=\"small\">编辑</Button><Button type=\"link\" size=\"small\" danger>删除</Button></Space> },\n  ];\n  return (\n    <div>\n      <div style={{ marginBottom: 16, display: 'flex', justifyContent: 'space-between' }}>\n        <Space><Input placeholder=\"搜索\" prefix={<SearchOutlined />} /><Button type=\"primary\">搜索</Button></Space>\n        <Button type=\"primary\" icon={<PlusOutlined />}>新建</Button>\n      </div>\n      <Table rowKey=\"id\" columns={columns} dataSource={data} pagination={{ pageSize: 10 }} />\n    </div>\n  );\n}\n```\n\n**`pages/Form/index.tsx`** — Form page template:\n```tsx\nimport { Form, Input, Button, Select, DatePicker, Card, message } from 'antd';\n\nexport default function FormPage() {\n  const [form] = Form.useForm();\n  const onFinish = (values: unknown) => { console.log(values); message.success('提交成功'); };\n  return (\n    <Card title=\"表单页\">\n      <Form form={form} layout=\"vertical\" onFinish={onFinish} style={{ maxWidth: 600 }}>\n        <Form.Item label=\"名称\" name=\"name\" rules={[{ required: true }]}><Input /></Form.Item>\n        <Form.Item label=\"类型\" name=\"type\" rules={[{ required: true }]}><Select options={[{ label: '类型A', value: 'a' }, { label: '类型B', value: 'b' }]} /></Form.Item>\n        <Form.Item label=\"日期\" name=\"date\"><DatePicker style={{ width: '100%' }} /></Form.Item>\n        <Form.Item><Button type=\"primary\" htmlType=\"submit\">提交</Button></Form.Item>\n      </Form>\n    </Card>\n  );\n}\n```\n\n**`App.tsx`:**\n```tsx\nimport { useRoutes } from 'react-router-dom';\nimport { BrowserRouter } from 'react-router-dom';\nimport { ConfigProvider } from 'antd';\nimport zhCN from 'antd/locale/zh_CN';\nimport AppProvider from '@/components/AppProvider';\nimport { routes } from '@/config/routes';\n\nfunction AppRoutes() { return useRoutes(routes); }\n\nexport default function App() {\n  return (\n    <AppProvider>\n      <ConfigProvider locale={zhCN}>\n        <BrowserRouter>\n          <AppRoutes />\n        </BrowserRouter>\n      </ConfigProvider>\n    </AppProvider>\n  );\n}\n```\n\n> **Note:** Auth state (`useUserStore`) and route guard (`AuthGuard`) are generated by the shared infrastructure layer (Step 7). The `config/routes.tsx` uses `AuthGuard` from `@/components/AuthGuard` and `useUserStore` from `@/store`. Do NOT create separate `stores/authStore.ts` or `routes/index.tsx` — the infrastructure layer handles this.\n\n### Step 6: Vue Admin Templates (if Vue + Element Plus selected)\n\n**Vue file structure:**\n```\nsrc/\n├── components/                 # Global components (from shared infrastructure)\n│   ├── AppProvider.vue\n│   ├── AuthGuard.vue\n│   ├── GlobalLoading.vue\n│   ├── ErrorBoundary.vue\n│   ├── PageLoading.vue\n│   ├── EmptyState.vue\n│   └── ErrorState.vue\n├── layouts/\n│   └── AdminLayout.vue        # Element Plus sidebar layout\n├── pages/\n│   ├── Login.vue              # Login page\n│   ├── Dashboard.vue          # Dashboard with stats\n│   ├── List.vue               # Table list page\n│   ├── Form.vue               # Form page\n│   └── NotFound.vue\n├── store/                      # State management (from shared infrastructure)\n│   └── useUserStore.ts         # Auth state (Pinia)\n├── config/\n│   └── routes.ts               # Vue Router with permission guard (from shared infrastructure)\n├── hooks/                      # Common hooks (from shared infrastructure)\n├── theme/                      # Theme system (from shared infrastructure)\n├── App.vue\n└── main.ts\n```\n\n**`main.ts`:**\n```typescript\nimport { createApp } from 'vue';\nimport { createPinia } from 'pinia';\nimport ElementPlus from 'element-plus';\nimport zhCn from 'element-plus/es/locale/lang/zh-cn';\nimport 'element-plus/dist/index.css';\nimport App from './App.vue';\nimport router from '@/config/routes';\nimport '@/locales';  // if i18n selected\n\nconst app = createApp(App);\napp.use(createPinia());\napp.use(router);\napp.use(ElementPlus, { locale: zhCn });\napp.mount('#app');\n```\n\n> **Note:** Auth state (`useUserStore`) and route guard are generated by the shared infrastructure layer. Vue router config goes in `config/routes.ts` (not `router/index.ts`). See `../references/shared-infrastructure.md` for Pinia store templates.\n\n**`layouts/AdminLayout.vue`:**\n```vue\n<template>\n  <el-container style=\"min-height: 100vh\">\n    <el-aside :width=\"isCollapsed ? '64px' : '220px'\">\n      <div class=\"logo-placeholder\" />\n      <el-menu\n        :default-active=\"route.path\"\n        :collapse=\"isCollapsed\"\n        background-color=\"#001529\"\n        text-color=\"#ffffffa6\"\n        active-text-color=\"#fff\"\n        @select=\"(key: string) => router.push(key)\"\n      >\n        <el-menu-item index=\"/dashboard\"><el-icon><Odometer /></el-icon><span>仪表盘</span></el-menu-item>\n        <el-menu-item index=\"/list\"><el-icon><Document /></el-icon><span>列表页</span></el-menu-item>\n        <el-menu-item index=\"/form\"><el-icon><EditPen /></el-icon><span>表单页</span></el-menu-item>\n      </el-menu>\n    </el-aside>\n    <el-container>\n      <el-header class=\"admin-header\">\n        <el-icon class=\"collapse-btn\" @click=\"isCollapsed = !isCollapsed\">\n          <component :is=\"isCollapsed ? 'Expand' : 'Fold'\" />\n        </el-icon>\n        <el-dropdown @command=\"handleCommand\">\n          <el-avatar :size=\"32\"><el-icon><User /></el-icon></el-avatar>\n          <template #dropdown>\n            <el-dropdown-menu>\n              <el-dropdown-item command=\"logout\">退出登录</el-dropdown-item>\n            </el-dropdown-menu>\n          </template>\n        </el-dropdown>\n      </el-header>\n      <el-main><router-view /></el-main>\n    </el-container>\n  </el-container>\n</template>\n\n<script setup lang=\"ts\">\nimport { ref } from 'vue';\nimport { useRoute, useRouter } from 'vue-router';\nimport { Odometer, Document, EditPen, User, Expand, Fold } from '@element-plus/icons-vue';\nimport { useUserStore } from '@/store';\n\nconst route = useRoute();\nconst router = useRouter();\nconst userStore = useUserStore();\nconst isCollapsed = ref(false);\n\nfunction handleCommand(command: string) {\n  if (command === 'logout') {\n    userStore.logout();\n    router.push('/login');\n  }\n}\n</script>\n\n<style scoped lang=\"scss\">\n/* Production: replace with SCSS module */\n.logo-placeholder {\n  height: 32px;\n  margin: 16px;\n  background: rgba(255, 255, 255, 0.2);\n  border-radius: 6px;\n}\n.admin-header {\n  display: flex;\n  align-items: center;\n  justify-content: space-between;\n  padding: 0 24px;\n}\n.collapse-btn {\n  font-size: 20px;\n  cursor: pointer;\n}\n</style>\n```\n\n### Step 7: Generate Shared Layer\n\nRead the following reference files and generate all shared code:\n1. `../references/shared-base.md` — services, utils, styles, types, env files\n2. `../references/shared-config.md` — vite, tsconfig, eslint, prettier configs\n3. `../references/shared-infrastructure.md` — store, theme, i18n, hooks, layouts, auth guard, config/constants\n\n**Conditional generation** (only if user selected the corresponding option):\n- Selected Zustand/Redux Toolkit/Pinia → generate `src/store/`\n- Selected i18n → generate `src/locales/`\n- **Always generate**: `src/hooks/`, `src/components/AppProvider.tsx`, `src/components/AuthGuard.tsx`, `src/components/GlobalLoading.tsx`, `src/config/`, `src/theme/`\n\n### Step 8: Final Setup\n\n```bash\ncd <project-name>\nbun install  # or: pnpm install\nbun dev      # or: pnpm dev\n```\n\nAnnounce completion with admin-specific info: login page at /login, dashboard at /dashboard, etc.\n\n**Vercel deployment** (recommended):\n```bash\nbun add -g vercel\nvercel\n```\nOr connect GitHub repo at vercel.com for auto-deploy.\n\nFile v3.1.1:astro/SKILL.md\n\n---\nname: fe-cli-astro\ndescription: >\n  Scaffold a static site / content site project. Supports Astro, Gatsby (React), and VitePress (Vue).\n  For blogs, documentation sites, portfolios, landing pages with SSG/SSR.\n  Triggered as a sub-skill of fe-cli when user wants Astro/Gatsby/建站/博客/文档站/VitePress project.\n---\n\n# fe-cli-astro — Static Site Scaffolding\n\n## Workflow\n\n### Step 1: Gather Options\n\n1. **Framework**: Astro / Gatsby (React) / VitePress (Vue)\n2. **UI Integration** (Astro only): React / Vue / Svelte / Preact / None (Astro components only)\n3. **Styling**: Tailwind CSS / shadcn/ui (Astro+React) / 纯 SCSS\n4. **Content**: MDX (Astro/Gatsby) / Markdown (all) / CMS integration (Contentful/Sanity) / None\n5. **i18n**: Yes / No\n6. **Deployment**: Vercel (default) / Netlify / Cloudflare Pages\n7. **Project name**: string\n\n**Quick guide:**\n- **Blog / portfolio / content site** → Astro (islands architecture, partial hydration, fast)\n- **React-heavy content site** → Gatsby (GraphQL data layer, rich plugin ecosystem)\n- **Vue documentation site** → VitePress (Vue-powered static site, markdown-first)\n\n### Step 2: Scaffold\n\n**Astro:**\n```\nbun create astro@latest <project-name>\n```\nInteractive prompt: template (blog/docs/portfolio/empty), TypeScript, install dependencies.\n\nOr with flags:\n```\nbunx create-astro <project-name> --template blog --typescript strict --install\n```\n\n**Gatsby:**\n```\nbunx gatsby new <project-name>\n```\n\n**VitePress:**\n```\nmkdir <project-name> && cd <project-name>\nbun init\nbun add -D vitepress vue\nmkdir docs && echo '# Hello VitePress' > docs/index.md\n```\n\n### Step 3: Install Additional Dependencies\n\n| Selection | Packages |\n|---|---|\n| **Astro** | |\n| Tailwind CSS | `@astrojs/tailwind tailwindcss` (Astro 4+) or `tailwindcss @tailwindcss/vite` |\n| React integration | `@astrojs/react react react-dom` |\n| Vue integration | `@astrojs/vue vue` |\n| MDX | `@astrojs/mdx` |\n| i18n | `astro-i18next` / manual routing |\n| shadcn/ui | `npx shadcn@latest init` (if React integration) + `lucide-react` |\n| **Gatsby** | |\n| Tailwind CSS | `tailwindcss postcss autoprefixer` + `gatsby-plugin-postcss` |\n| MDX | `gatsby-plugin-mdx @mdx-js/react` |\n| CMS | `gatsby-source-contentful` / `gatsby-source-sanity` |\n| **VitePress** | |\n| Tailwind CSS | `tailwindcss @tailwindcss/vite` |\n| Vue components | `vue` (already included) |\n| Search | `vitepress-plugin-search` / Algolia |\n\n### Step 4: Project Structure\n\n**Astro:**\n```\nproject-name/\n├── src/\n│   ├── components/\n│   │   ├── BaseHead.astro       # <head> meta tags, fonts, styles\n│   │   ├── Header.astro         # Navigation header\n│   │   ├── Footer.astro         # Site footer\n│   │   └── Card.astro           # Content card component\n│   ├── content/\n│   │   ├── config.ts            # Content collection schema\n│   │   └── blog/                # Blog posts (MDX/Markdown)\n│   │       └── first-post.mdx\n│   ├── layouts/\n│   │   ├── BaseLayout.astro     # Page layout wrapper\n│   │   └── BlogPost.astro       # Blog post layout\n│   ├── pages/\n│   │   ├── index.astro          # Home page\n│   │   ├── about.astro          # About page\n│   │   └── blog/\n│   │       ├── index.astro      # Blog listing\n│   │       └── [...slug].astro  # Dynamic blog post\n│   └── styles/\n│       └── global.scss          # Global styles\n├── public/                      # Static assets\n├── astro.config.mjs             # Astro configuration\n├── tsconfig.json\n└── package.json\n```\n\n**Gatsby:**\n```\nproject-name/\n├── src/\n│   ├── components/\n│   ├── pages/\n│   │   ├── index.tsx\n│   │   └── blog.tsx\n│   ├── templates/\n│   │   └── blog-post.tsx        # GraphQL template\n│   └── styles/\n├── gatsby-config.ts             # Plugins & metadata\n├── gatsby-node.ts               # Programmatic page creation\n└── package.json\n```\n\n**VitePress:**\n```\nproject-name/\n├── docs/\n│   ├── .vitepress/\n│   │   ├── config.ts            # VitePress config (nav, sidebar, theme)\n│   │   └── theme/\n│   │       └── index.ts         # Custom theme extensions\n│   ├── index.md                 # Home page\n│   ├── guide/                   # Guide pages\n│   │   ├── getting-started.md\n│   │   └── configuration.md\n│   └── blog/                    # Blog pages\n│       └── first-post.md\n├── package.json\n└── tsconfig.json\n```\n\n### Step 5: Key Templates\n\n**`astro.config.mjs`:**\n```javascript\nimport { defineConfig } from 'astro/config';\nimport react from '@astrojs/react';        // if React integration\nimport mdx from '@astrojs/mdx';            // if MDX\nimport tailwindcss from '@tailwindcss/vite';\n\nexport default defineConfig({\n  site: 'https://example.com',\n  integrations: [\n    react(),     // if selected\n    mdx(),       // if selected\n  ],\n  vite: {\n    plugins: [tailwindcss()],\n  },\n});\n```\n\n**`src/content/config.ts`** (Astro content collections):\n```typescript\nimport { defineCollection, z } from 'astro:content';\n\nconst blog = defineCollection({\n  type: 'content',\n  schema: z.object({\n    title: z.string(),\n    description: z.string(),\n    pubDate: z.coerce.date(),\n    updatedDate: z.coerce.date().optional(),\n    heroImage: z.string().optional(),\n    tags: z.array(z.string()).default([]),\n  }),\n});\n\nexport const collections = { blog };\n```\n\n**`docs/.vitepress/config.ts`** (VitePress):\n```typescript\nimport { defineConfig } from 'vitepress';\n\nexport default defineConfig({\n  title: 'My Docs',\n  description: 'Documentation site',\n  themeConfig: {\n    nav: [\n      { text: 'Guide', link: '/guide/getting-started' },\n      { text: 'Blog', link: '/blog/' },\n    ],\n    sidebar: {\n      '/guide/': [\n        {\n          text: 'Getting Started',\n          items: [\n            { text: 'Introduction', link: '/guide/getting-started' },\n            { text: 'Configuration', link: '/guide/configuration' },\n          ],\n        },\n      ],\n    },\n    socialLinks: [\n      { icon: 'github', link: 'https://github.com/your-org/your-repo' },\n    ],\n  },\n});\n```\n\n### Step 6: Final Setup\n\n```bash\ncd <project-name>\nbun install\nbun dev      # Astro: http://localhost:4321 | VitePress: http://localhost:5173\n```\n\n**Deployment:**\n\nVercel (recommended, zero-config):\n```bash\nbun add -g vercel\nvercel\n```\n\nNetlify:\n```bash\nbun add -g netlify-cli\nnetlify deploy --prod\n```\n\nCloudflare Pages:\n```bash\nbunx wrangler pages deploy dist/\n```\n\nAstro adapter for SSR (if needed):\n```\nbun add @astrojs/vercel    # or @astrojs/netlify / @astrojs/cloudflare\n```\nThen add to `astro.config.mjs`: `output: 'server'` + adapter import.\n\nAnnounce: Static site ready, framework, content setup, deploy target.\n\nFile v3.1.1:electron/SKILL.md\n\n---\nname: fe-cli-electron\ndescription: >\n  Scaffold an Electron desktop application. For macOS/Windows desktop apps using React/Vue.\n  Features auto-update (electron-updater), IPC communication, system tray, native menus.\n  For lighter desktop apps, consider Tauri (fe-cli-tauri) instead.\n  Triggered as a sub-skill of fe-cli when user wants Electron/桌面端 project.\n---\n\n# fe-cli-electron — Electron Desktop App Scaffolding\n\n## Workflow\n\n### Step 1: Gather Options\n\n1. **Framework**: React 19 / Vue 3\n2. **Styling**: Tailwind CSS / Ant Design / MUI / 纯 SCSS\n3. **CSS Preprocessor**: Sass / Less\n4. **State Management**: Zustand / Redux Toolkit / None\n5. **Router**: React Router / Vue Router\n6. **i18n**: Yes / No\n7. **Auto Update**: Yes (default) / No\n8. **System Tray**: Yes / No\n9. **Testing**: Vitest / None\n10. **Pre-commit**: Yes / No\n11. **Project name**: string\n\n### Step 2: Scaffold\n\n```\npnpm create vite <project-name> --template react-ts\ncd <project-name>\npnpm add -D electron electron-builder concurrently wait-on\npnpm add electron-updater electron-store  # if auto-update selected\n```\n\n### Step 3: Project Structure\n\n```\nproject-name/\n├── electron/\n│   ├── main.ts              # Main process entry\n│   ├── preload.ts            # Preload script (contextBridge)\n│   └── updater.ts            # Auto-update logic (if selected)\n├── src/                      # Renderer process (React/Vue)\n│   ├── components/           # Global components (from shared infrastructure)\n│   ├── pages/                # Page components\n│   ├── hooks/                # Common hooks (from shared infrastructure)\n│   ├── store/                # State management (from shared infrastructure)\n│   ├── theme/                # Theme system (from shared infrastructure)\n│   ├── config/               # App constants + routes (from shared infrastructure)\n│   ├── services/             # API layer (from shared base)\n│   ├── utils/                # Utilities (from shared base)\n│   ├── styles/               # Global styles (from shared base)\n│   ├── electron.d.ts         # IPC type declarations\n│   ├── App.tsx\n│   └── main.tsx\n├── package.json\n├── vite.config.ts\n├── electron-builder.yml      # Build config\n└── resources/                # App icons\n```\n\n### Step 4: Key Templates\n\n**`electron/main.ts`:**\n```typescript\nimport { app, BrowserWindow, ipcMain, Tray, Menu, nativeImage } from 'electron';\nimport path from 'path';\nimport { setupUpdater } from './updater';  // if auto-update\n\nlet mainWindow: BrowserWindow | null = null;\nlet tray: Tray | null = null;\n\nconst isDev = !app.isPackaged;\n\nfunction createWindow() {\n  mainWindow = new BrowserWindow({\n    width: 1200,\n    height: 800,\n    minWidth: 800,\n    minHeight: 600,\n    webPreferences: {\n      preload: path.join(__dirname, 'preload.js'),\n      contextIsolation: true,\n      nodeIntegration: false,\n    },\n    titleBarStyle: 'hiddenInset',  // macOS native title bar\n  });\n\n  if (isDev) {\n    mainWindow.loadURL('http://localhost:5173');\n    mainWindow.webContents.openDevTools();\n  } else {\n    mainWindow.loadFile(path.join(__dirname, '../dist/index.html'));\n  }\n\n  // System tray (if selected)\n  if (process.env.NODE_ENV !== 'linux') {\n    const icon = nativeImage.createFromPath(path.join(__dirname, '../resources/icon.png'));\n    tray = new Tray(icon.resize({ width: 16, height: 16 }));\n    tray.setToolTip('Your App');\n    tray.setContextMenu(Menu.buildFromTemplate([\n      { label: 'Show', click: () => mainWindow?.show() },\n      { type: 'separator' },\n      { label: 'Quit', click: () => app.quit() },\n    ]));\n  }\n}\n\n// IPC handlers\nipcMain.handle('get-app-version', () => app.getVersion());\nipcMain.handle('get-platform', () => process.platform);\n\napp.whenReady().then(() => {\n  createWindow();\n  if (process.env.AUTO_UPDATE !== 'false') setupUpdater(mainWindow!);\n  app.on('activate', () => { if (!mainWindow) createWindow(); });\n});\n\napp.on('window-all-closed', () => { if (process.platform !== 'darwin') app.quit(); });\n```\n\n**`electron/preload.ts`:**\n```typescript\nimport { contextBridge, ipcRenderer } from 'electron';\n\ncontextBridge.exposeInMainWorld('electronAPI', {\n  getAppVersion: () => ipcRenderer.invoke('get-app-version'),\n  getPlatform: () => ipcRenderer.invoke('get-platform'),\n  onUpdateAvailable: (cb: (info: unknown) => void) => ipcRenderer.on('update-available', (_, info) => cb(info)),\n  onUpdateDownloaded: (cb: () => void) => ipcRenderer.on('update-downloaded', () => cb()),\n  installUpdate: () => ipcRenderer.send('install-update'),\n  minimize: () => ipcRenderer.send('window-minimize'),\n  maximize: () => ipcRenderer.send('window-maximize'),\n  close: () => ipcRenderer.send('window-close'),\n});\n```\n\n**`electron/updater.ts`** (if auto-update):\n```typescript\nimport { autoUpdater } from 'electron-updater';\nimport { BrowserWindow } from 'electron';\n\nexport function setupUpdater(mainWindow: BrowserWindow) {\n  autoUpdater.autoDownload = false;\n  autoUpdater.autoInstallOnAppQuit = true;\n\n  autoUpdater.on('update-available', (info) => {\n    mainWindow.webContents.send('update-available', info);\n  });\n\n  autoUpdater.on('update-downloaded', () => {\n    mainWindow.webContents.send('update-downloaded');\n  });\n\n  autoUpdater.on('error', (err) => {\n    console.error('Update error:', err);\n  });\n\n  // Check for updates every 4 hours\n  autoUpdater.checkForUpdatesAndNotify();\n  setInterval(() => autoUpdater.checkForUpdatesAndNotify(), 4 * 60 * 60 * 1000);\n}\n```\n\n**`src/electron.d.ts`:**\n```typescript\nexport {};\n\ndeclare global {\n  interface Window {\n    electronAPI: {\n      getAppVersion: () => Promise<string>;\n      getPlatform: () => Promise<string>;\n      onUpdateAvailable: (cb: (info: unknown) => void) => void;\n      onUpdateDownloaded: (cb: () => void) => void;\n      installUpdate: () => void;\n      minimize: () => void;\n      maximize: () => void;\n      close: () => void;\n    };\n  }\n}\n```\n\n**`electron-builder.yml`:**\n```yaml\nappId: <YOUR_APP_ID>           # e.g. com.yourcompany.yourapp — replace with actual app ID (see parameterization rule in main SKILL.md)\nproductName: <YOUR_APP_NAME>   # e.g. My App — replace with actual app name\ndirectories:\n  output: release\nfiles:\n  - dist/**/*\n  - electron/**/*\n  - package.json\nmac:\n  target:\n    - dmg\n    - zip\n  artifactName: ${productName}-${version}-mac-${arch}.${ext}\n  hardenedRuntime: true\n  gatekeeperAssess: false\n  entitlements: build/entitlements.mac.plist\n  entitlementsInherit: build/entitlements.mac.plist\nwin:\n  target:\n    - nsis\n  artifactName: ${productName}-${version}-win-${arch}.${ext}\nnsis:\n  oneClick: false\n  allowToChangeInstallationDirectory: true\npublish:\n  - provider: generic\n    url: <YOUR_UPDATE_URL>     # e.g. https://update.yourcompany.com — replace with actual update server URL\n```\n\n**`package.json` scripts:**\n```json\n{\n  \"main\": \"electron/main.js\",\n  \"scripts\": {\n    \"dev\": \"concurrently \\\"vite\\\" \\\"wait-on http://localhost:5173 && electron .\\\"\",\n    \"dev:renderer\": \"vite\",\n    \"dev:electron\": \"electron .\",\n    \"build\": \"vite build && tsc -p tsconfig.electron.json\",\n    \"build:prod\": \"vite build && tsc -p tsconfig.electron.json && electron-builder\",\n    \"preview\": \"vite preview\",\n    \"pack\": \"electron-builder --dir\",\n    \"dist\": \"electron-builder\"\n  }\n}\n```\n\n### Step 5: Vite Config\n\n```typescript\nimport { defineConfig } from 'vite';\nimport react from '@vitejs/plugin-react';\nimport path from 'path';\n\nexport default defineConfig({\n  plugins: [react()],\n  resolve: { alias: { '@': path.resolve(__dirname, 'src') } },\n  base: './',  // Electron loads from file:// so relative paths\n  build: { outDir: 'dist' },\n  server: { port: 5173 },\n});\n```\n\n### Step 6: Shared Layer + Infrastructure + Final\n\nRead the following reference files and generate all shared code:\n1. `../references/shared-base.md` — services, utils, styles, types, env files\n2. `../references/shared-config.md` — vite, tsconfig, eslint, prettier configs\n3. `../references/shared-infrastructure.md` — store, theme, i18n, hooks, layouts, auth guard, config/constants\n\nOnly generate env files for VITE_ variables (no API proxy needed — Electron uses direct connection).\n\n**Conditional generation** (only if user selected the corresponding option):\n- Selected Zustand/Redux Toolkit/Pinia → generate `src/store/`\n- Selected i18n → generate `src/locales/`\n- **Always generate**: `src/hooks/`, `src/components/AppProvider.tsx`, `src/components/AuthGuard.tsx`, `src/components/GlobalLoading.tsx`, `src/config/`, `src/theme/`\n\nAfter setup:\n```bash\ncd <project-name>\npnpm install\npnpm dev\n```\n\nAnnounce: project with Electron + auto-update config, dev starts both Vite and Electron.\n\nFile v3.1.1:h5/SKILL.md\n\n---\nname: fe-cli-h5\ndescription: >\n  Scaffold a Mobile H5 frontend project. For mobile web pages, WeChat H5 pages,\n  and mobile-first web apps. Features vw/vh responsive, touch gestures, safe area,\n  and WeChat JSSDK integration. Triggered as a sub-skill of fe-cli.\n---\n\n# fe-cli-h5 — Mobile H5 Scaffolding\n\n## Workflow\n\n### Step 1: Gather Options\n\n1. **Framework**: React 19 / Vue 3\n2. **Styling**: Tailwind CSS (default, mobile-first) / Ant Design Mobile / 纯 SCSS\n3. **CSS Preprocessor**: Sass (default) / Less\n4. **State Management**: Zustand / Redux Toolkit / Pinia / None\n5. **Router**: React Router / Vue Router\n6. **i18n**: react-i18next / vue-i18n / None\n7. **WeChat JSSDK**: Yes / No (if yes, add `weixin-js-sdk`)\n8. **Testing**: Vitest / None\n9. **Pre-commit**: husky + lint-staged? (default: No)\n10. **Project name**: string\n\n### Step 2: Scaffold\n\n```\npnpm create vite <project-name> --template react-ts\n```\n\n### Step 3: Dependencies\n\n```\npnpm add react-router-dom\npnpm add -D postcss-px-to-viewport-8-plugin amfe-flexible\npnpm add -D sass\n```\n\nIf WeChat JSSDK: `pnpm add weixin-js-sdk`\nIf Vue: replace react-router-dom with vue-router\n\n### Step 4: H5-Specific Configuration\n\n**`postcss.config.cjs`:**\n```javascript\nmodule.exports = {\n  plugins: {\n    'postcss-px-to-viewport-8-plugin': {\n      viewportWidth: 375,  // Design width\n      unitPrecision: 5,\n      viewportUnit: 'vw',\n      selectorBlackList: ['.ignore-vw'],\n      minPixelValue: 1,\n      mediaQuery: false,\n    },\n  },\n};\n```\n\n**`vite.config.ts`** — add mobile-specific config:\n```typescript\nimport { defineConfig } from 'vite';\nimport react from '@vitejs/plugin-react';\nimport path from 'path';\n\nexport default defineConfig(({ mode }) => ({\n  plugins: [react()],\n  resolve: { alias: { '@': path.resolve(__dirname, 'src') } },\n  css: {\n    postcss: './postcss.config.cjs',\n    preprocessorOptions: {\n      scss: { additionalData: '@use \"@/styles/variables\" as *;' },\n    },\n  },\n  server: {\n    port: 5173,\n    host: '0.0.0.0',  // Allow LAN access for mobile testing\n    proxy: { '/api': { target: 'http://localhost:3000', changeOrigin: true } },\n  },\n}));\n```\n\n**`index.html`** — add viewport meta:\n```html\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover\">\n```\n\n### Step 5: H5-Specific Files\n\n```\nsrc/\n├── components/\n│   ├── ErrorBoundary.tsx\n│   ├── PageLoading.tsx\n│   ├── EmptyState.tsx\n│   ├── ErrorState.tsx\n│   └── SafeArea.tsx            # Safe area padding for notch phones\n├── pages/\n│   ├── Home/\n│   │   └── index.tsx\n│   ├── Detail/\n│   │   └── index.tsx\n│   └── Mine/\n│       └── index.tsx\n├── layouts/\n│   └── MobileLayout.tsx        # Bottom tab bar\n├── hooks/                      # Common hooks (generated by shared layer)\n│   ├── useRequest.ts           # Data fetching (generated by shared layer)\n│   └── useWechat.ts            # WeChat JSSDK hook (if selected)\n├── store/                      # State management (generated by shared infrastructure)\n├── theme/                      # Theme system (generated by shared infrastructure)\n├── config/                     # App constants + routes (generated by shared infrastructure)\n├── locales/                    # i18n (generated by shared infrastructure, if selected)\n├── App.tsx\n└── main.tsx\n```\n\n**`components/SafeArea.tsx`:**\n> **Note:** Inline styles below are for quick scaffolding. **Production environments should replace them with SCSS modules**.\n```tsx\n// Safe area padding for iPhone notch / Android punch-hole\nexport default function SafeArea({ children }: { children: React.ReactNode }) {\n  return (\n    <div style={{\n      paddingTop: 'env(safe-area-inset-top)',\n      paddingBottom: 'env(safe-area-inset-bottom)',\n      paddingLeft: 'env(safe-area-inset-left)',\n      paddingRight: 'env(safe-area-inset-right)',\n    }}>{children}</div>\n  );\n}\n```\n\n**`layouts/MobileLayout.tsx`:**\n> **Note:** Inline styles below are for quick scaffolding. **Production environments should replace them with SCSS modules** (e.g., `import styles from './MobileLayout.module.scss'`).\n```tsx\nimport { Outlet, useNavigate, useLocation } from 'react-router-dom';\nimport { HomeOutlined, AppstoreOutlined, UserOutlined } from '@ant-design/icons';\nimport styles from './MobileLayout.module.scss';  // or Tailwind classes\n\nconst tabs = [\n  { key: '/home', icon: <HomeOutlined />, label: '首页' },\n  { key: '/category', icon: <AppstoreOutlined />, label: '分类' },\n  { key: '/mine', icon: <UserOutlined />, label: '我的' },\n];\n\nexport default function MobileLayout() {\n  const navigate = useNavigate();\n  const location = useLocation();\n\n  return (\n    <div style={{ display: 'flex', flexDirection: 'column', minHeight: '100vh' }}>\n      <main style={{ flex: 1, overflow: 'auto' }}>\n        <Outlet />\n      </main>\n      <nav style={{\n        display: 'flex', borderTop: '1px solid #eee', paddingBottom: 'env(safe-area-inset-bottom)',\n        background: '#fff', height: 50,\n      }}>\n        {tabs.map((tab) => (\n          <div key={tab.key} onClick={() => navigate(tab.key)}\n            style={{ flex: 1, display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center',\n              color: location.pathname === tab.key ? '#1677ff' : '#999', fontSize: 12 }}>\n            {tab.icon}\n            <span>{tab.label}</span>\n          </div>\n        ))}\n      </nav>\n    </div>\n  );\n}\n```\n\n**`hooks/useWechat.ts`** (if WeChat selected):\n```typescript\nimport { useEffect, useState } from 'react';\nimport wx from 'weixin-js-sdk';\n\ninterface WxConfig {\n  appId: string;\n  timestamp: number;\n  nonceStr: string;\n  signature: string;\n}\n\nexport function useWechat(config?: WxConfig) {\n  const [ready, setReady] = useState(false);\n\n  useEffect(() => {\n    if (!config) return;\n    wx.config({ debug: false, ...config, jsApiList: ['updateAppMessageShareData', 'updateTimelineShareData'] });\n    wx.ready(() => setReady(true));\n  }, [config]);\n\n  const share = (title: string, desc: string, link: string, imgUrl: string) => {\n    if (!ready) return;\n    wx.updateAppMessageShareData({ title, desc, link, imgUrl, success: () => {} });\n    wx.updateTimelineShareData({ title, link, imgUrl, success: () => {} });\n  };\n\n  return { ready, share };\n}\n```\n\n### Step 6: Global Styles\n\nIn `global.scss`, add mobile-specific styles:\n```scss\nhtml, body, #root {\n  height: 100%;\n  overflow-x: hidden;\n}\n\n// Safe area\nbody {\n  padding: env(safe-area-inset-top) env(safe-area-inset-right) env(safe-area-inset-bottom) env(safe-area-inset-left);\n}\n\n// 1px border on retina\n.hairline {\n  position: relative;\n  &::after {\n    content: '';\n    position: absolute;\n    left: 0; bottom: 0;\n    width: 100%; height: 1px;\n    background: #e5e5e5;\n    transform: scaleY(0.5);\n  }\n}\n\n// No scroll body when modal open\nbody.no-scroll { overflow: hidden; }\n```\n\n### Step 7: Shared Layer + Infrastructure + Final\n\nRead the following reference files and generate all shared code:\n1. `../references/shared-base.md` — services, utils, styles, types, env files\n2. `../references/shared-config.md` — vite, tsconfig, eslint, prettier configs\n3. `../references/shared-infrastructure.md` — store, theme, i18n, hooks, layouts, auth guard, config/constants\n\n**Conditional generation** (only if user selected the corresponding option):\n- Selected Zustand/Redux Toolkit/Pinia → generate `src/store/`\n- Selected i18n → generate `src/locales/`\n- **Always generate** (regardless of options): `src/hooks/` (common hooks), `src/components/AppProvider.tsx`, `src/components/AuthGuard.tsx`, `src/components/GlobalLoading.tsx`, `src/config/`, `src/theme/`\n\nThen: `pnpm install && pnpm dev`.\n\nFile v3.1.1:miniapp/SKILL.md\n\n---\nname: fe-cli-miniapp\ndescription: >\n  Scaffold a WeChat Mini Program project using Taro or uni-app framework.\n  Features subpackage configuration, native API wrappers, subscription messages,\n  and multi-platform build (WeChat, Alipay, etc.). Triggered as a sub-skill of fe-cli\n  when user wants 小程序/微信小程序/Mini Program project.\n---\n\n# fe-cli-miniapp — Mini Program Scaffolding\n\n## Workflow\n\n### Step 1: Gather Options\n\n1. **Framework**: Taro (React) / Taro (Vue) / uni-app (Vue)\n2. **Styling**: Tailwind (via weapp-tailwindcss) / SCSS / Less\n3. **CSS Preprocessor**: Sass / Less\n4. **State Management**: Zustand / Redux Toolkit / Pinia (Vue) / None\n5. **i18n**: Yes (Taro: @tarojs/plugin-i18n) / No\n6. **Subpackages**: Yes (分包) / No\n7. **Target Platforms**: 微信 / 支付宝 / 字节 / 百度 / 全部\n8. **Testing**: Vitest / None\n9. **Pre-commit**: Yes / No\n10. **Project name**: string\n\n### Step 2: Scaffold\n\n**Taro (React):**\n```\npnpm create @tarojs/app <project-name> --template react-ts --css sass --compiler vite\n```\n\n**Taro (Vue):**\n```\npnpm create @tarojs/app <project-name> --template vue3-ts --css sass --compiler vite\n```\n\n**uni-app:**\n```\npnpm create uni-app <project-name> --template vue3-ts\n```\n\n### Step 3: Dependencies\n\n| Need | Packages |\n|---|---|\n| Zustand | `zustand` |\n| Redux | `@reduxjs/toolkit react-redux` |\n| Pinia (uni-app) | Pinia comes with uni-app template |\n| Taro i18n | `@tarojs/plugin-i18n` |\n| Tailwind | `weapp-tailwindcss` (Taro plugin) |\n| Icons | `@taroify/icons` (icon components for Taro) |\n\n### Step 4: Project Structure (Taro)\n\n```\nsrc/\n├── pages/\n│   ├── index/\n│   │   ├── index.tsx         # Home page\n│   │   ├── index.config.ts   # Page config (title, etc.)\n│   │   └── index.scss\n│   ├── detail/\n│   │   └── ...\n│   └── mine/\n│       └── ...\n├── components/\n│   ├── NavBar.tsx             # Custom navigation bar\n│   ├── EmptyState.tsx\n│   ├── ErrorState.tsx\n│   └── PageLoading.tsx\n├── services/\n│   ├── request.ts             # Taro.request wrapper\n│   └── api/\n│       └── index.ts\n├── utils/\n│   ├── index.ts               # General utils\n│   ├── storage.ts             # Taro Storage wrapper\n│   ├── format.ts\n│   └── validate.ts\n├── hooks/\n│   └── useRequest.ts\n├── stores/                    # State management\n├── styles/\n│   └── variables.scss\n├── app.config.ts              # Global app config (pages, window, subpackages)\n├── app.tsx                    # App entry\n└── app.scss                   # Global styles\n```\n\n### Step 5: Subpackage Config (if selected)\n\nIn `app.config.ts`:\n```typescript\nexport default defineAppConfig({\n  pages: ['pages/index/index', 'pages/mine/mine'],\n  subPackages: [\n    {\n      root: 'packageA',\n      name: 'packageA',\n      pages: ['pages/detail/detail', 'pages/list/list'],\n    },\n    {\n      root: 'packageB',\n      name: 'packageB',\n      pages: ['pages/settings/settings'],\n    },\n  ],\n  window: {\n    backgroundTextStyle: 'light',\n    navigationBarBackgroundColor: '#fff',\n    navigationBarTitleText: 'WeChat',\n    navigationBarTextStyle: 'black',\n  },\n  tabBar: {\n    color: '#999',\n    selectedColor: '#1677ff',\n    list: [\n      { pagePath: 'pages/index/index', text: '首页', iconPath: 'assets/tab/home.png', selectedIconPath: 'assets/tab/home-active.png' },\n      { pagePath: 'packageA/pages/list/list', text: '列表', iconPath: 'assets/tab/list.png', selectedIconPath: 'assets/tab/list-active.png' },\n      { pagePath: 'pages/mine/mine', text: '我的', iconPath: 'assets/tab/mine.png', selectedIconPath: 'assets/tab/mine-active.png' },\n    ],\n  },\n});\n```\n\n### Step 6: Key Templates\n\n**`services/request.ts`** — Taro request wrapper:\n```typescript\nimport Taro from '@tarojs/taro';\n\nconst BASE_URL = process.env.TARO_APP_API_URL || '';\n\ninterface RequestOptions extends Omit<Taro.request.Option, 'url' | 'success' | 'fail'> {\n  params?: Record<string, string>;\n}\n\nclass RequestError extends Error {\n  constructor(public code: number, message: string, public data?: unknown) {\n    super(message);\n    this.name = 'RequestError';\n  }\n}\n\n// Token management\nconst TOKEN_KEY = 'auth_token';\nfunction getToken(): string {\n  return Taro.getStorageSync(TOKEN_KEY) || '';\n}\n\nasync function request<T = unknown>(url: string, options: RequestOptions = {}): Promise<T> {\n  const { params, ...rest } = options;\n  const token = getToken();\n\n  // Build URL\n  let fullUrl = `${BASE_URL}${url}`;\n  if (params) {\n    const qs = Object.entries(params)\n      .filter(([, v]) => v != null)\n      .map(([k, v]) => `${k}=${encodeURIComponent(String(v))}`)\n      .join('&');\n    if (qs) fullUrl += `?${qs}`;\n  }\n\n  const header: Record<string, string> = {\n    ...(rest.header as Record<string, string> || {}),\n    ...(token ? { Authorization: `Bearer ${token}` } : {}),\n  };\n\n  try {\n    const res = await Taro.request({ url: fullUrl, header, ...rest });\n    if (res.statusCode === 401) { Taro.removeStorageSync(TOKEN_KEY); Taro.reLaunch({ url: '/pages/index/index' }); throw new RequestError(401, 'Unauthorized'); }\n    if (res.statusCode >= 400) { const data = res.data as Record<string, unknown>; throw new RequestError(res.statusCode, (data?.message as string) || `HTTP ${res.statusCode}`, data); }\n    return res.data as T;\n  } catch (error) {\n    if (error instanceof RequestError) throw error;\n    throw new RequestError(0, 'Network error');\n  }\n}\n\nexport const http = {\n  get: <T>(url: string, options?: RequestOptions) => request<T>(url, { ...options, method: 'GET' }),\n  post: <T>(url: string, data?: unknown, options?: RequestOptions) => request<T>(url, { ...options, method: 'POST', data }),\n  put: <T>(url: string, data?: unknown, options?: RequestOptions) => request<T>(url, { ...options, method: 'PUT', data }),\n  delete: <T>(url: string, options?: RequestOptions) => request<T>(url, { ...options, method: 'DELETE' }),\n};\n```\n\n**`utils/storage.ts`** — Taro Storage wrapper:\n```typescript\nimport Taro from '@tarojs/taro';\n\nconst PREFIX = 'app_';\n\nexport const storage = {\n  get<T = unknown>(key: string, defaultValue?: T): T | undefined {\n    try {\n      const raw = Taro.getStorageSync(`${PREFIX}${key}`);\n      return raw !== '' ? (JSON.parse(raw) as T) : defaultValue;\n    } catch { return defaultValue; }\n  },\n  set(key: string, value: unknown): void {\n    Taro.setStorageSync(`${PREFIX}${key}`, JSON.stringify(value));\n  },\n  remove(key: string): void { Taro.removeStorageSync(`${PREFIX}${key}`); },\n  clear(): void {\n    const { keys } = Taro.getStorageInfoSync();\n    keys.filter((k) => k.startsWith(PREFIX)).forEach((k) => Taro.removeStorageSync(k));\n  },\n};\n```\n\n**`components/NavBar.tsx`** — Custom navigation bar:\n```tsx\nimport { View, Text } from '@tarojs/components';\nimport Taro from '@tarojs/taro';\nimport { ArrowLeft } from '@taroify/icons'; // 需安装: pnpm add @taroify/icons\n\ninterface Props { title?: string; showBack?: boolean; }\nexport default function NavBar({ title = '', showBack = true }: Props) {\n  const statusBarHeight = Taro.getWindowInfo?.()?.statusBarHeight || 20;\n  const navHeight = 44;\n\n  return (\n    <View style={{ paddingTop: `${statusBarHeight}px`, background: '#fff' }}>\n      <View style={{ height: `${navHeight}px`, display: 'flex', alignItems: 'center', justifyContent: 'center', position: 'relative' }}>\n        {showBack && (\n          <View onClick={() => Taro.navigateBack()} style={{ position: 'absolute', left: 12, display: 'flex', alignItems: 'center' }}>\n            <ArrowLeft size={20} />\n            <Text>返回</Text>\n          </View>\n        )}\n        <Text style={{ fontSize: 17, fontWeight: 500 }}>{title}</Text>\n      </View>\n    </View>\n  );\n}\n```\n\n**`hooks/useRequest.ts`** — Data fetching hook for Taro:\n```typescript\nimport { useState, useEffect, useCallback, useRef } from 'react';\nimport Taro from '@tarojs/taro';\n\nexport function useRequest<T>(fetcher: () => Promise<T>, options: { manual?: boolean } = {}) {\n  const { manual = false } = options;\n  const [data, setData] = useState<T>();\n  const [loading, setLoading] = useState(!manual);\n  const [error, setError] = useState<Error>();\n  const fetcherRef = useRef(fetcher);\n\n  const run = useCallback(async () => {\n    setLoading(true);\n    setError(undefined);\n    try {\n      const result = await fetcherRef.current();\n      setData(result);\n    } catch (e) {\n      setError(e as Error);\n      if ((e as Error).message !== 'Unauthorized') {\n        Taro.showToast({ title: (e as Error).message, icon: 'none' });\n      }\n    } finally {\n      setLoading(false);\n    }\n  }, []);\n\n  useEffect(() => { if (!manual) run(); }, [manual, run]);\n\n  return { data, loading, error, run, refresh: run };\n}\n```\n\n### Step 7: Shared Layer + Infrastructure + Final\n\nRead the following reference files and adapt for mini-program environment:\n1. `../references/shared-base.md` — services, utils, styles, types, env files\n2. `../references/shared-config.md` — tsconfig, eslint configs (skip vite configs)\n3. `../references/shared-infrastructure.md` — store, i18n, hooks, config/constants\n\n**Mini-program adaptations:**\n- `request.ts` uses Taro.request instead of fetch\n- `storage.ts` uses Taro Storage API\n- `format.ts` — same as shared, works in Taro\n- `validate.ts` — same as shared\n- Theme: skip `theme/` directory (mini-programs don't support CSS custom properties widely; use Taro theme config instead)\n- Layout components: skip (mini-programs use native tab bar + page stack)\n- AuthGuard: adapt to Taro page lifecycle (use `useDidShow` + redirect)\n- Store (Zustand): works, but use `taro-persistent-store` or manual `Taro.setStorageSync` for persistence\n- CSS variables — same as shared (Taro supports SCSS)\n\nDo NOT generate:\n- vite.config.ts — Taro has its own build system\n- Env files — use Taro's config/env system instead\n- React Router / Vue Router — Taro has its own routing via page configs\n\n```bash\ncd <project-name>\npnpm install\npnpm dev:weapp    # or dev:alipay for Alipay\n```\n\nAnnounce: Mini program project with selected platforms and features.\n\nFile v3.1.1:rn/SKILL.md\n\n---\nname: fe-cli-rn\ndescription: >\n  Scaffold a React Native mobile application. For iOS/Android native mobile apps.\n  Uses Expo (recommended) or React Native CLI. Features navigation, state management, theming.\n  Triggered as a sub-skill of fe-cli when user wants React Native/RN/原生移动/App project.\n---\n\n# fe-cli-rn — React Native Mobile App Scaffolding\n\n## Workflow\n\n### Step 1: Gather Options\n\n1. **Approach**: Expo (recommended, faster setup) / React Native CLI (more native control)\n2. **Navigation**: Expo Router (file-based, recommended) / React Navigation (component-based)\n3. **Styling**: NativeWind (Tailwind for RN) / StyleSheet / Tamagui / React Native Paper\n4. **State Management**: Zustand / Redux Toolkit / None\n5. **i18n**: i18next + react-i18next / None\n6. **Testing**: Jest + React Native Testing Library / None\n7. **Pre-commit**: husky + lint-staged? (default: No)\n8. **Project name**: string\n\n**Expo vs CLI:**\n- **Expo**: Faster dev, OTA updates, easy build (EAS), limited native modules (improving)\n- **CLI**: Full native control, custom native modules, more complex setup\n\n### Step 2: Scaffold\n\n**Expo (recommended):**\n```\nbunx create-expo-app <project-name> --template tabs\n```\n\nTemplates: `blank` / `tabs` / `minimal` / `expo-template-blank-typescript`\n\n**React Native CLI:**\n```\nbunx react-native init <project-name> --template react-native-template-typescript\n```\n\n### Step 3: Install Additional Dependencies\n\n| Selection | Packages |\n|---|---|\n| Expo Router | `npx expo install expo-router react-native-safe-area-context react-native-screens expo-linking expo-constants expo-status-bar` |\n| React Navigation | `@react-navigation/native @react-navigation/native-stack @react-navigation/bottom-tabs react-native-screens react-native-safe-area-context` |\n| NativeWind | `npx expo install nativewind tailwindcss` + `tailwind.config.js` |\n| Tamagui | `@tamagui/core @tamagui/config` |\n| React Native Paper | `react-native-paper` |\n| Zustand | `zustand` |\n| Redux Toolkit | `@reduxjs/toolkit react-redux` |\n| i18n | `i18next react-i18next` |\n| Animations | `react-native-reanimated` |\n| Gesture | `react-native-gesture-handler` |\n| Charts | `react-native-svg victor-native` / `react-native-chart-kit` |\n| Storage | `@react-native-async-storage/async-storage` |\n| Image | `expo-image` |\n| Font | `expo-font` |\n\n### Step 4: Project Structure\n\n**Expo + Expo Router:**\n```\nproject-name/\n├── app/                        # File-based routing (Expo Router)\n│   ├── _layout.tsx             # Root layout\n│   ├── index.tsx               # Home screen\n│   ├── (tabs)/                 # Tab navigation group\n│   │   ├── _layout.tsx         # Tab layout\n│   │   ├── index.tsx           # Tab: Home\n│   │   ├── explore.tsx         # Tab: Explore\n│   │   └── profile.tsx         # Tab: Profile\n│   └── modal.tsx               # Modal screen\n├── components/\n│   ├── ThemedView.tsx          # Themed container\n│   ├── ThemedText.tsx          # Themed text\n│   └── LoadingScreen.tsx       # Loading state\n├── hooks/\n│   ├── useTheme.ts             # Theme hook\n│   └── useRequest.ts           # Data fetching\n├── stores/\n│   └── appStore.ts             # Zustand store\n├── services/\n│   ├── request.ts              # API client\n│   └── api/\n│       └── index.ts            # API endpoints\n├── constants/\n│   └── theme.ts                # Colors, spacing, typography\n├── assets/                     # Images, fonts\n├── app.json                    # Expo config\n├── app.config.ts               # Expo config (TypeScript)\n├── tsconfig.json\n└── package.json\n```\n\n**React Native CLI + React Navigation:**\n```\nproject-name/\n├── src/\n│   ├── screens/\n│   │   ├── HomeScreen.tsx\n│   │   ├── DetailScreen.tsx\n│   │   └── ProfileScreen.tsx\n│   ├── navigation/\n│   │   ├── AppNavigator.tsx     # Root navigator\n│   │   ├── TabNavigator.tsx     # Bottom tabs\n│   │   └── types.ts             # Navigation types\n│   ├── components/\n│   ├── hooks/\n│   ├── stores/\n│   ├── services/\n│   └── constants/\n├── android/                     # Native Android\n├── ios/                         # Native iOS\n├── index.js                     # Entry point\n├── App.tsx                      # Root component\n└── package.json\n```\n\n### Step 5: Key Templates\n\n**`app/_layout.tsx`** (Expo Router root):\n```tsx\nimport { Stack } from 'expo-router';\n\nexport default function RootLayout() {\n  return (\n    <Stack>\n      <Stack.Screen name=\"(tabs)\" options={{ headerShown: false }} />\n      <Stack.Screen name=\"modal\" options={{ presentation: 'modal' }} />\n    </Stack>\n  );\n}\n```\n\n**`app/(tabs)/_layout.tsx`** (Tab layout):\n```tsx\nimport { Tabs } from 'expo-router';\nimport { Ionicons } from '@expo/vector-icons';\n\nexport default function TabLayout() {\n  return (\n    <Tabs screenOptions={{ tabBarActiveTintColor: '#1677ff' }}>\n      <Tabs.Screen\n        name=\"index\"\n        options={{ title: 'Home', tabBarIcon: ({ color, size }) => <Ionicons name=\"home\" size={size} color={color} /> }}\n      />\n      <Tabs.Screen\n        name=\"explore\"\n        options={{ title: 'Explore', tabBarIcon: ({ color, size }) => <Ionicons name=\"search\" size={size} color={color} /> }}\n      />\n      <Tabs.Screen\n        name=\"profile\"\n        options={{ title: 'Profile', tabBarIcon: ({ color, size }) => <Ionicons name=\"person\" size={size} color={color} /> }}\n      />\n    </Tabs>\n  );\n}\n```\n\n**`constants/theme.ts`:**\n```typescript\nexport const Colors = {\n  light: {\n    primary: '#1677ff',\n    background: '#ffffff',\n    text: '#1a1a1a',\n    textSecondary: '#666666',\n    border: '#e8e8e8',\n    card: '#f5f5f5',\n    error: '#ff4d4f',\n    success: '#52c41a',\n  },\n  dark: {\n    primary: '#4096ff',\n    background: '#1a1a1a',\n    text: '#ffffff',\n    textSecondary: '#999999',\n    border: '#333333',\n    card: '#2a2a2a',\n    error: '#ff7875',\n    success: '#73d13d',\n  },\n};\n\nexport const Spacing = {\n  xs: 4,\n  sm: 8,\n  md: 16,\n  lg: 24,\n  xl: 32,\n};\n\nexport const Typography = {\n  h1: { fontSize: 28, fontWeight: '700' as const },\n  h2: { fontSize: 22, fontWeight: '600' as const },\n  h3: { fontSize: 18, fontWeight: '600' as const },\n  body: { fontSize: 16, fontWeight: '400' as const },\n  caption: { fontSize: 13, fontWeight: '400' as const },\n};\n```\n\n**`app.config.ts`** (Expo config):\n```typescript\nimport { ExpoConfig, ConfigContext } from 'expo/config';\n\nexport default ({ config }: ConfigContext): ExpoConfig => ({\n  ...config,\n  name: 'Your App',\n  slug: 'your-app',\n  version: '1.0.0',\n  orientation: 'portrait',\n  scheme: 'yourapp',\n  userInterfaceStyle: 'automatic',\n  newArchEnabled: true,\n  ios: {\n    supportsTablet: true,\n    bundleIdentifier: 'com.example.app',\n  },\n  android: {\n    adaptiveIcon: { backgroundColor: '#ffffff' },\n    package: 'com.example.app',\n  },\n  plugins: ['expo-router', 'expo-font'],\n});\n```\n\n### Step 6: Final Setup\n\n```bash\ncd <project-name>\nbun install\nbun start      # Expo: starts Metro bundler\n# Press 'i' for iOS simulator, 'a' for Android emulator\n```\n\n**EAS Build** (Expo cloud build):\n```bash\nbun add -g eas-cli\neas login\neas build:configure\neas build --platform ios     # or android / all\n```\n\n**Local build:**\n```bash\n# iOS (requires macOS + Xcode)\nnpx expo run:ios\n\n# Android (requires Android Studio)\nnpx expo run:android\n```\n\nAnnounce: React Native project ready with Expo, navigation, and theming. Dev server at Expo Go or simulator.\n\nFile v3.1.1:SKILL.md\n\n---\nname: fe-cli\nversion: \"3.1.1\"\nhomepage: https://github.com/z-Zihan/awesome-skills\ndescription: >\n  前端项目脚手架 CLI。支持 React/Vue/Next.js/Nuxt.js/Astro/React Native + shadcn/Ant Design/MUI/Tailwind\n  + Zustand/Redux + i18n，初始化标准化前端项目。支持 9 种项目类型：Web SPA、后台管理、移动 H5、\n  Electron 桌面端、Tauri 桌面端、SSR (Next.js/Nuxt)、小程序 (微信/Taro)、React Native 移动端、\n  静态建站 (Astro/Gatsby/VitePress)。\n  触发：用户要求创建新前端项目、初始化项目、搭建脚手架，\n  或提到\"新建前端项目\"、\"初始化项目\"、\"脚手架\"、\"创建 React/Vue 项目\"。\n  也可用于检查/审查已有前端项目的规范性。\n---\n\n# fe-cli — 前端项目脚手架\n\n## 语言规则\n\n**检测用户使用的语言，全程使用同一语言输出。** 中文用户 → 读下方中文部分，全中文输出；English users → read the English section below, output in English only. 技术术语（React、Vite、pnpm 等）保留原文即可。子技能文件为英文，AI 执行时根据用户语言输出对应语言的交互文本。\n\n---\n\n# 中文版\n\n所有前端项目类型的统一入口。路由到类型专属子技能，并生成共享公共层（请求工具、全局样式、多环境配置等）。\n\n## 快速开始\n\n### 一句话启动（跳过所有问题）\n\n```\n用户: \"初始化一个 React + Tailwind + Zustand 后台项目，叫 my-admin\"\n→ 识别类型=admin → 委派给 fe-cli-admin 快速模式\n```\n\n### 交互模式\n\n```\n用户: \"新建前端项目\"\n→ 询问: \"什么类型？\" → 路由到子技能 → 子技能询问详细问题\n```\n\n## 项目类型检测\n\n从用户请求中识别类型：\n\n| 关键词 | 类型 | 子技能 |\n|---|---|---|\n| 后台/管理/Admin/Dashboard/CRUD | admin | fe-cli-admin |\n| 移动/H5/手机/微信H5/Mobile | h5 | fe-cli-h5 |\n| 桌面/Electron/客户端/Desktop | electron | fe-cli-electron |\n| Tauri/轻量桌面/Rust桌面 | tauri | fe-cli-tauri |\n| SSR/Next.js/Nuxt/服务端渲染/SEO | ssr | fe-cli-ssr |\n| 小程序/微信小程序/MiniApp/WeChat | miniapp | fe-cli-miniapp |\n| React Native/RN/原生移动/App | rn | fe-cli-rn |\n| Astro/Gatsby/建站/博客/文档站/VitePress | astro | fe-cli-astro |\n| (默认/官网/Web/SPA/网页) | web | fe-cli-web |\n\n如果不确定，询问：\"这是什么类型的项目？Web SPA / 后台管理 / 移动H5 / Electron桌面 / Tauri桌面 / SSR / 小程序 / React Native / 静态建站\"\n\n### 输入澄清\n\n当用户请求模糊时，**必须先澄清再执行**：\n- **模糊输入**（如\"帮我搞一下\"、\"新建项目\"）→ 询问项目名称 + 类型 + 技术栈偏好\n- **矛盾请求**（如\"用 React 和 Vue\"）→ 指出矛盾，请求确认使用哪一个\n- **越界请求**（如\"帮我部署到服务器\"）→ 拒绝并说明：\"fe-cli 只负责本地项目初始化，部署请使用其他工具\"\n- **非前端请求**（如\"写一个 Python 后端\"）→ 拒绝并重定向：\"fe-cli 仅支持前端项目脚手架\"\n\n### 降级策略\n\n- `bun create vite` / `pnpm create vite` 失败 → 提示用户手动创建，或检查网络/Node/Bun 版本\n- 依赖安装失败 → 提示检查网络代理、npm registry 配置，提供淘宝镜像命令\n- 目标目录已存在 → 询问：\"目录已存在，覆盖 / 合并 / 取消？\"\n- 模板中引用的包版本不存在 → 提示用户手动指定版本，不阻塞流程\n\n## 路由到子技能\n\n类型识别后，读取对应子技能的 SKILL.md：\n\n```\nRead: ./<type>/SKILL.md (relative to fe-cli/)\n```\n\n每个子技能负责：\n1. 类型专属问题（框架、UI 库、状态管理、i18n、pre-commit）\n2. 类型专属文件生成（布局、页面、路由）\n3. 安装和后续配置\n\n## 共享公共层\n\n子技能生成类型专属文件后，在项目中生成以下共享文件。\nRead `references/shared-base.md` and `references/shared-config.md` for code templates.\n\n### 要生成的目录结构\n\n```\nproject-name/\n├── src/\n│   ├── components/\n│   │   ├── AppProvider.tsx      # 根级 Context Provider 包裹层\n│   │   ├── AuthGuard.tsx        # 路由鉴权守卫（角色控制）\n│   │   ├── ErrorBoundary.tsx    # React 错误边界\n│   │   ├── GlobalLoading.tsx    # 全屏 Loading 遮罩\n│   │   ├── PageLoading.tsx      # 页面级 Loading\n│   │   ├── EmptyState.tsx       # 空状态占位\n│   │   └── ErrorState.tsx       # 错误状态（含重试）\n│   ├── config/\n│   │   ├── index.ts             # 应用常量 + 环境配置\n│   │   └── routes.tsx           # 集中式路由定义\n│   ├── hooks/\n│   │   ├── index.ts             # Re-export all hooks\n│   │   ├── useRequest.ts        # 数据请求\n│   │   ├── useDebounce.ts       # 防抖\n│   │   ├── useLocalStorage.ts   # 类型安全的 localStorage\n│   │   ├── useMediaQuery.ts     # CSS 媒体查询响应式\n│   │   ├── useClickAway.ts      # 点击外部检测\n│   │   ├── useToggle.ts         # 布尔切换\n│   │   ├── usePrevious.ts       # 上一次值追踪\n│   │   └── useUpdateEffect.ts   # 跳过首次渲染的 effect\n│   ├── layouts/\n│   │   ├── AppLayout.tsx        # 主布局（侧边栏 + 头部 + 内容）\n│   │   ├── Header.tsx           # 顶栏（主题切换、用户菜单）\n│   │   └── Sidebar.tsx          # 侧边导航\n│   ├── locales/                 # （仅 i18n 选中时生成）\n│   │   ├── index.ts             # i18n 初始化\n│   │   ├── zh-CN.json           # 中文翻译\n│   │   ├── en-US.json           # 英文翻译\n│   │   └── useLanguage.ts       # 语言切换 hook\n│   ├── store/                   # （仅状态管理选中时生成）\n│   │   ├── index.ts             # Re-export stores\n│   │   ├── useUserStore.ts      # 用户认证 & 信息\n│   │   ├── useAppStore.ts       # 应用级状态（主题、语言、侧边栏）\n│   │   └── middleware.ts        # 自定义中间件（logger、persist）\n│   ├── theme/\n│   │   ├── index.ts             # ThemeProvider + 主题逻辑\n│   │   ├── tokens.ts            # 亮色 + 暗色设计令牌\n│   │   └── useTheme.ts          # 主题 hook（读取/切换/主题感知类名）\n│   ├── services/\n│   │   ├── request.ts           # fetch 请求封装（含拦截器）\n│   │   ├── logger.ts            # 结构化日志（分级、轮转、持久化）\n│   │   ├── log-export.ts        # 日志导出（下载 .log/.json）+ 上报（待定）\n│   │   └── api/\n│   │       └── index.ts         # API 接口定义\n│   ├── styles/\n│   │   ├── global.scss          # CSS 变量 + 全局样式\n│   │   ├── reset.scss           # CSS reset\n│   │   └── variables.scss       # 设计令牌（颜色、间距、断点）\n│   ├── utils/\n│   │   ├── index.ts             # 通用工具函数（防抖、深拷贝等）\n│   │   ├── storage.ts           # localStorage/sessionStorage 封装\n│   │   ├── format.ts            # 日期/数字格式化\n│   │   └── validate.ts          # 表单校验工具\n│   └── types/\n│       └── global.d.ts          # 全局类型声明\n├── .env                       # 公共环境变量\n├── .env.development           # 开发环境\n├── .env.test                  # 测试环境\n├── .env.production            # 生产环境\n└── package.json               # Scripts 配置\n```\n\n### Package.json scripts\n\n```json\n{\n  \"scripts\": {\n    \"dev\": \"vite --mode development\",\n    \"dev:test\": \"vite --mode test\",\n    \"build:test\": \"vite build --mode test\",\n    \"build:prod\": \"vite build --mode production\",\n    \"preview\": \"vite preview\",\n    \"lint\": \"eslint src --ext .ts,.tsx\",\n    \"lint:fix\": \"eslint src --ext .ts,.tsx --fix\",\n    \"typecheck\": \"tsc --noEmit\"\n  }\n}\n```\n\n### 共享代码模板\n\n基础层模板见 `references/shared-base.md`（含 logger.ts 和 log-export.ts）。\n构建配置模板见 `references/shared-config.md`。\n基础设施层模板见 `references/shared-infrastructure.md`（状态管理、主题系统、i18n、通用 Hooks、全局组件、布局、路由守卫、常量配置）。\n\n**条件生成**：\n- 用户选了 Zustand/Redux Toolkit/Pinia → 生成 `src/store/`\n- 用户选了 i18n → 生成 `src/locales/`\n- **无论选什么都生成**：`src/hooks/`（通用 hooks）、`src/components/AppProvider.tsx`、`src/components/AuthGuard.tsx`、`src/components/GlobalLoading.tsx`、`src/config/`、`src/theme/`\n\n## AI 可读项目文档\n\n生成所有项目文件后，在项目根目录生成 `.ai/PROJECT.md`。\nThis file is for AI agents to quickly understand the project.\n\n### .ai/PROJECT.md 目标\n\n- **完整目录树**，每个文件列出并有一行说明\n- **技术栈摘要**（框架、UI 库、状态管理、CSS 等）\n- **架构约定**（路径别名、导入顺序、命名规则）\n- **命令**（dev、build、test、lint、typecheck）\n- **关键模式**（路由、API 调用、状态管理）\n\n### 模板\n\n完整模板见 `references/ai-project-md.md`，根据具体项目类型调整目录树。\n\n### 规则\n\n- 目录使用 `tree` 命令格式（├── 和 └──），文件用一行说明\n- 每个文件必须有说明其用途的注释\n- 按目录分组，与实际文件系统顺序一致\n- 项目结构变化时更新\n- 保持简洁——每个文件一行，不是段落\n\n## 关键规则\n\n- **包管理器**：默认使用 **bun**（更快），备选 pnpm。检测方式：`bun --version` 存在则用 bun，否则用 pnpm\n- **运行时**：默认 Node.js，bun 也可作为运行时（`bun run` 替代 `node`）\n- **CSS 预处理器**：默认 Sass（SCSS 语法）\n- **路径别名**：在 tsconfig 和 vite config 中配置 `@/` → `src/`\n- **响应式断点**：Mobile < 768px < Tablet < 1024px < Desktop\n- **网络库**：原生 fetch 封装（不用 axios），见 references/shared-base.md\n- **环境文件**：始终生成 `.env`、`.env.development`、`.env.test`、`.env.production`\n- **日志**：始终生成 `services/logger.ts` + `services/log-export.ts`。使用 `Logger.child(\"Module\")` 模式。最大存储 5MB 自动清理。仅开发模式控制台输出。上报端点为占位符（待定）。\n- **Node 版本**：目标 Node 18+\n- **部署**：默认推荐 Vercel 部署（零配置），在项目生成后提供 `vercel` 初始化命令\n- **图表库**：轻量场景推荐 Recharts，重场景用 ECharts\n- **pnpm 构建脚本**：在 package.json 中使用 `pnpm.onlyBuiltDependencies` 自动批准原生构建（如 `@parcel/watcher`）。Vite 渲染层构建命令（`build:prod`、`build:test`）不应包含 `tsc -b`——Vite 处理 TS 转译；类型检查是独立的 `typecheck` 脚本。**Electron/Tauri 主进程例外**：主进程代码在 Node 环境运行，需要 `tsc -p tsconfig.electron.json` 编译。\n\n## 模板占位符参数化\n\n生成项目时，AI 应主动将模板中的占位值替换为用户提供的实际值，包括但不限于：\n- `com.example.app` → 用户指定的应用 ID\n- `https://api.example.com` → 用户指定的 API 地址\n- `My App` / `Your App` → 用户指定的应用名称\n- `https://update.example.com` → 用户指定的更新服务地址\n\n完成后提示用户确认：\"项目已生成，请确认以下占位值是否正确：[列出替换后的值]\"\n\n## 已有项目审查\n\n当用户要求检查已有项目时：\"检查这个前端项目\"、\"审查项目规范性\"：\n\n### 审查步骤\n\n1. 读取 `package.json` → 检查必要依赖和脚本\n2. 检查 vite.config / tsconfig / eslint / prettier 配置\n3. 对照上述标准检查 src 目录结构\n4. 输出审查报告\n\n### 审查报告格式\n\n```markdown\n## 项目审查报告\n\n### ✅ 符合项\n- [列出符合标准的条目]\n\n### ❌ 缺失项\n- [缺失项]：描述 + 建议修复方案\n- ...\n\n### ⚠️ 建议项\n- [可选改进]：描述\n```\n\n### 审查检查项\n\n| 检查项 | 标准 |\n|---|---|\n| 包管理器 | 使用 bun（优先）或 pnpm |\n| 路径别名 | tsconfig + vite.config 中 `@/` → `src/` |\n| CSS 预处理器 | 配置 Sass (SCSS) |\n| 环境文件 | 存在 `.env` / `.env.development` / `.env.test` / `.env.production` |\n| 请求封装 | `services/request.ts` 使用 fetch wrapper（非 axios） |\n| 日志 | `services/logger.ts` 存在，使用 `Logger.child(\"Module\")` 模式 |\n| 全局样式 | `styles/global.scss` + `reset.scss` + `variables.scss` |\n| 主题系统 | `theme/` 目录存在，支持亮/暗模式切换 |\n| 状态管理 | 若选了状态管理 → `store/` 目录存在，含 userStore + appStore |\n| 多语言 | 若选了 i18n → `locales/` 目录存在，含 zh-CN + en-US |\n| 通用 Hooks | `hooks/` 目录存在，含 useDebounce、useLocalStorage、useMediaQuery 等 |\n| 全局组件 | `components/AppProvider.tsx` + `AuthGuard.tsx` + `GlobalLoading.tsx` |\n| 布局组件 | `layouts/` 目录存在，含 AppLayout + Header + Sidebar |\n| 路由配置 | `config/routes.tsx` 集中定义，非分散在各页面 |\n| 常量配置 | `config/index.ts` 含路由路径、HTTP 状态码、分页默认值等 |\n| 工具函数 | `utils/index.ts` + `storage.ts` + `format.ts` + `validate.ts` |\n| 类型声明 | `types/global.d.ts` 含 ImportMetaEnv |\n| Scripts | 含 dev / build:prod / build:test / lint / typecheck |\n| .ai/PROJECT.md | 存在且与实际项目结构一致 |\n\n---\n---\n\n# English Version\n\n**fe-cli** is a unified entry point for scaffolding frontend projects. It detects the project type from user input and routes to one of 9 sub-skills: **web** (SPA), **admin** (dashboard/CRUD), **h5** (mobile), **electron** (desktop), **tauri** (lightweight desktop), **ssr** (Next.js/Nuxt), **miniapp** (WeChat Mini Program), **rn** (React Native), or **astro** (Astro/Gatsby/VitePress static site). Each sub-skill handles type-specific questions, file generation, and setup.\n\n**Key rules:** Default package manager is **bun** (fallback: pnpm); default CSS preprocessor is **Sass (SCSS)**; path alias `@/` → `src/`; native **fetch** wrapper (no axios); always generate `.env` / `.env.development` / `.env.test` / `.env.production`; always generate `services/logger.ts` + `services/log-export.ts`; target Node 18+; Vite handles TS transpilation (separate `typecheck` script), except Electron/Tauri main process needs `tsc -p tsconfig.electron.json`; default deployment: **Vercel**; chart libs: **Recharts** (lightweight) / **ECharts** (heavy); new UI libs: **shadcn/ui + Lucide** (React), **shadcn-vue + Vue Bits** (Vue), **Chakra UI**, **React Bits**.\n\nAfter type-specific files, generate the **shared common layer** (request utils, global styles, multi-env config, utilities, type declarations). Finally, generate `.ai/PROJECT.md` for AI-readability. Supports **existing project audit** against the same standards.\n\n**Template placeholder rule:** When generating projects, AI should replace placeholders (`com.example.app`, `https://api.example.com`, etc.) with actual user-provided values and prompt the user to confirm.\n\nSub-skill files are in English; AI should output interaction text in the user's language during execution.\n\nFile v3.1.1:ssr/SKILL.md\n\n---\nname: fe-cli-ssr\ndescription: >\n  Scaffold an SSR (Server-Side Rendering) frontend project using Next.js or Nuxt.\n  Features SSR/SSG/ISR, SEO optimization, API routes, middleware, and image optimization.\n  Triggered as a sub-skill of fe-cli when user wants SSR/Next.js/Nuxt/服务端渲染 project.\n---\n\n# fe-cli-ssr — SSR Project Scaffolding\n\n## Workflow\n\n### Step 1: Gather Options\n\n1. **Framework**: Next.js (React) / Nuxt (Vue)\n2. **Styling/UI**: Tailwind CSS / shadcn/ui + Lucide (Next.js) / shadcn-vue + Vue Bits (Nuxt) / Ant Design (Next.js) / Element Plus (Nuxt) / MUI (Next.js) / 纯 SCSS\n3. **CSS Preprocessor**: Sass / Less\n4. **State Management**: Zustand / Redux Toolkit / Pinia (Nuxt) / None\n5. **Charts**: Recharts (lightweight) / ECharts (heavy) / None\n6. **i18n**: next-intl (Next.js) / @nuxtjs/i18n (Nuxt) / None\n7. **Database/ORM**: Prisma / Drizzle / None\n8. **Auth**: NextAuth.js / Lucia / None\n9. **Testing**: Vitest / Playwright / None\n10. **Pre-commit**: husky + lint-staged? (default: No)\n11. **Project name**: string\n\n### Step 2: Scaffold\n\n**Next.js:**\n```\nbun create next-app <project-name> --typescript --tailwind --eslint --app --src-dir --import-alias \"@/*\"\n```\nNote: `--tailwind` flag may not exist in all Next.js versions. If not, manually install tailwind.\n\n**Nuxt:**\n```\nbunx nuxi init <project-name> --packageManager bun\n```\n\n(Use `pnpm dlx` if bun unavailable)\n\n### Step 3: Install Dependencies\n\n| Selection | Packages |\n|---|---|\n| Sass | `sass` |\n| Less | `less` |\n| Zustand | `zustand` |\n| Redux Toolkit | `@reduxjs/toolkit react-redux` |\n| Ant Design | `antd @ant-design/icons @ant-design/v5-patch-for-react-19` |\n| shadcn/ui (Next.js) | `npx shadcn@latest init` + `lucide-react` (requires Tailwind) |\n| shadcn-vue (Nuxt) | `npx shadcn-vue@latest init` + `lucide-vue-next` (requires Tailwind) |\n| Recharts | `recharts` |\n| ECharts | `echarts echarts-for-react` (Next.js) / `echarts vue-echarts` (Nuxt) |\n| Next.js i18n | `next-intl` |\n| Nuxt i18n | `@nuxtjs/i18n` |\n| Prisma | `prisma @prisma/client` → `bunx prisma init` |\n| NextAuth | `next-auth@beta` |\n| Playwright | `@playwright/test` |\n\n### Step 4: Next.js-Specific Structure\n\n```\nsrc/\n├── app/\n│   ├── layout.tsx            # Root layout with metadata\n│   ├── page.tsx              # Home page (SSG by default)\n│   ├── loading.tsx           # Loading UI\n│   ├── error.tsx             # Error boundary\n│   ├── not-found.tsx         # 404 page\n│   ├── globals.scss          # Global styles\n│   └── (routes)/             # Route groups\n├── components/\n│   ├── ErrorBoundary.tsx     # Client-side error boundary\n│   ├── PageLoading.tsx\n│   ├── EmptyState.tsx\n│   └── ErrorState.tsx\n├── lib/\n│   ├── request.ts            # Server-side fetch + client-side fetch\n│   └── utils.ts              # Shared utils\n├── services/\n│   └── api.ts                # API calls (works on server & client)\n├── hooks/\n│   └── useRequest.ts         # Client-side data fetching\n├── stores/                   # State management (client)\n├── types/\n│   └── global.d.ts\n└── middleware.ts             # Next.js middleware (auth, redirects, i18n)\n```\n\n### Step 5: Key Templates\n\n**`app/layout.tsx`:**\n```tsx\nimport type { Metadata } from 'next';\nimport './globals.scss';\n\nexport const metadata: Metadata = {\n  title: { template: '%s | My App', default: 'My App' },\n  description: 'Your app description',\n  openGraph: {\n    title: 'My App',\n    description: 'Your app description',\n    url: 'https://example.com',\n    siteName: 'My App',\n    locale: 'zh_CN',\n    type: 'website',\n  },\n  robots: { index: true, follow: true },\n};\n\nexport default function RootLayout({ children }: { children: React.ReactNode }) {\n  return (\n    <html lang=\"zh-CN\">\n      <body>{children}</body>\n    </html>\n  );\n}\n```\n\n**`lib/request.ts`** — Universal fetch (works server + client):\n```typescript\ntype RequestOptions = {\n  method?: string;\n  headers?: Record<string, string>;\n  body?: unknown;\n  cache?: RequestCache;\n  next?: { revalidate?: number; tags?: string[] };\n};\n\nclass RequestError extends Error {\n  constructor(public status: number, message: string, public data?: unknown) {\n    super(message);\n    this.name = 'RequestError';\n  }\n}\n\nconst BASE_URL = process.env.NEXT_PUBLIC_API_URL || process.env.API_URL || '';\n\nexport async function request<T = unknown>(url: string, options: RequestOptions = {}): Promise<T> {\n  const { body, headers: customHeaders, ...rest } = options;\n  const isServer = typeof window === 'undefined';\n\n  const headers: Record<string, string> = {\n    ...(body && !(body instanceof FormData) ? { 'Content-Type': 'application/json' } : {}),\n    ...(isServer ? {} : {}),  // Client-side token injection\n    ...customHeaders,\n  };\n\n  const res = await fetch(`${BASE_URL}${url}`, {\n    ...rest,\n    headers,\n    body: body instanceof FormData ? body : body ? JSON.stringify(body) : undefined,\n  });\n\n  if (!res.ok) {\n    const errData = await res.json().catch(() => null);\n    throw new RequestError(res.status, errData?.message || `HTTP ${res.status}`, errData);\n  }\n\n  return res.json();\n}\n```\n\n**`app/page.tsx`** — Home with SSG:\n> **Note:** Inline styles below are for quick scaffolding. **Production environments should replace them with SCSS modules or CSS Modules**.\n```tsx\nexport default async function Home() {\n  // Server-side data fetch (SSG by default)\n  // const data = await request('/api/some-endpoint', { next: { revalidate: 60 } });\n\n  return (\n    <main style={{ maxWidth: 1200, margin: '0 auto', padding: '2rem' }}>\n      <h1>Welcome</h1>\n    </main>\n  );\n}\n```\n\n**`app/loading.tsx`:**\n> **Note:** Inline styles below are for quick scaffolding. **Production environments should replace them with SCSS modules or CSS Modules**.\n```tsx\nexport default function Loading() {\n  return (\n    <div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', minHeight: '100vh' }}>\n      Loading...\n    </div>\n  );\n}\n```\n\n**`app/error.tsx`:**\n> **Note:** Inline styles below are for quick scaffolding. **Production environments should replace them with SCSS modules or CSS Modules**.\n```tsx\n'use client';\nexport default function Error({ error, reset }: { error: Error; reset: () => void }) {\n  return (\n    <div style={{ textAlign: 'center', padding: '4rem' }}>\n      <h2>Something went wrong!</h2>\n      <p>{error.message}</p>\n      <button onClick={reset}>Try again</button>\n    </div>\n  );\n}\n```\n\n**`app/not-found.tsx`:**\n> **Note:** Inline styles below are for quick scaffolding. **Production environments should replace them with SCSS modules or CSS Modules**.\n```tsx\nimport Link from 'next/link';\nexport default function NotFound() {\n  return (\n    <div style={{ textAlign: 'center', padding: '4rem' }}>\n      <h2>404 - Page Not Found</h2>\n      <Link href=\"/\">Go Home</Link>\n    </div>\n  );\n}\n```\n\n**`middleware.ts`** (if auth/i18n selected):\n```typescript\nimport { NextResponse } from 'next/server';\nimport type { NextRequest } from 'next/server';\n\nexport function middleware(request: NextRequest) {\n  // Example: redirect unauthenticated users\n  // const token = request.cookies.get('token');\n  // if (!token && !request.nextUrl.pathname.startsWith('/login')) {\n  //   return NextResponse.redirect(new URL('/login', request.url));\n  // }\n  return NextResponse.next();\n}\n\nexport const config = {\n  matcher: ['/((?!api|_next/static|_next/image|favicon.ico).*)'],\n};\n```\n\n**`env files`:**\n```\n.env.local            # Local overrides (gitignored)\n.env.development      # NEXT_PUBLIC_API_URL=http://localhost:3000\n.env.production       # NEXT_PUBLIC_API_URL=https://api.example.com\n```\n\n### Step 6: Nuxt-Specific\n\nFor Nuxt, use Nuxt modules and composables instead of Next.js patterns.\n\n**`nuxt.config.ts`:**\n```typescript\nexport default defineNuxtConfig({\n  devtools: { enabled: true },\n  modules: [\n    '@nuxtjs/tailwindcss',   // if Tailwind selected\n    '@nuxtjs/i18n',           // if i18n selected\n  ],\n  runtimeConfig: {\n    public: {\n      apiBase: process.env.NUXT_PUBLIC_API_URL || 'http://localhost:3000',\n    },\n  },\n  css: ['~/assets/styles/main.scss'],\n});\n```\n\n**`app.vue`:**\n```vue\n<template>\n  <NuxtPage />\n</template>\n```\n\nKey file mapping (Nuxt → concept):\n- `nuxt.config.ts` — Module registration, runtime config\n- `app.vue` — Root app component\n- `pages/` — File-based routing (auto-import)\n- `server/api/` — API routes (Nitro)\n- `composables/` — Shared composables (useFetch wrapper, useAuth, etc.)\n- `middleware/` — Route middleware (auth redirect, i18n)\n- `layouts/` — Layout components (default, auth, etc.)\n\n**`composables/useRequest.ts`** (Nuxt equivalent of React useRequest):\n```typescript\nimport { ref } from 'vue';\n\nexport function useRequest<T>(fetcher: () => Promise<T>) {\n  const data = ref<T>();\n  const loading = ref(true);\n  const error = ref<Error>();\n\n  async function run() {\n    loading.value = true;\n    error.value = undefined;\n    try {\n      data.value = await fetcher();\n    } catch (e) {\n      error.value = e as Error;\n    } finally {\n      loading.value = false;\n    }\n  }\n\n  return { data, loading, error, run };\n}\n```\n\n### Step 7: Shared Layer + Infrastructure + Final\n\nRead the following reference files and adapt for SSR context:\n1. `../references/shared-base.md` — services, utils, styles, types, env files\n2. `../references/shared-config.md` — tsconfig, eslint, prettier configs (skip vite configs)\n3. `../references/shared-infrastructure.md` — store, theme, i18n, hooks, layouts, auth guard, config/constants\n\n**SSR-specific adaptations:**\n- Use `lib/` instead of `src/services/` for universal code\n- Use `components/` instead of `src/components/`\n- `hooks/` become client-only (add `'use client'` directive)\n- Store (Zustand) must handle SSR hydration (use `useStore` with `useSyncExternalStore` or `skipHydration`)\n- Theme must handle server-side rendering (avoid flash of wrong theme)\n- i18n should use `next-intl` for Next.js or `@nuxtjs/i18n` for Nuxt instead of react-i18next/vue-i18n\n- Skip `vite.config.ts` and `tsconfig.node.json` (Next.js/Nuxt have their own build systems)\n- Only generate files that make sense for SSR context\n\n**Conditional generation** (only if user selected the corresponding option):\n- Selected state management → generate `src/store/` (with SSR hydration)\n- Selected i18n → generate i18n config (platform-specific)\n- **Always generate**: `src/hooks/`, global components, `src/config/`, `src/theme/`\n\n```bash\ncd <project-name>\nbun install  # or: pnpm install\nbun dev      # or: pnpm dev\n```\n\nAnnounce: SSR project with Next.js/Nuxt, URLs, and key features enabled.\n\n**Vercel deployment** (recommended, zero-config for Next.js/Nuxt):\n```bash\nbun add -g vercel\nvercel\n```\nNext.js has native Vercel support. Nuxt also works with `vercel` CLI.\n\nFile v3.1.1:tauri/SKILL.md\n\n---\nname: fe-cli-tauri\ndescription: >\n  Scaffold a Tauri desktop application. Lightweight alternative to Electron (Rust backend, smaller bundle).\n  For macOS/Windows/Linux desktop apps using React/Vue frontend + Rust backend.\n  Features auto-update, system tray, native menus, IPC, file system access.\n  Triggered as a sub-skill of fe-cli when user wants Tauri/轻量桌面/Rust桌面 project.\n---\n\n# fe-cli-tauri — Tauri Desktop App Scaffolding\n\n**Why Tauri over Electron?** Smaller bundle (~5MB vs ~150MB), lower memory usage, Rust backend for performance-critical tasks, native webview (no Chromium bundled).\n\n## Prerequisites\n\n- Rust toolchain: `curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh`\n- System dependencies: https://v2.tauri.app/start/prerequisites/\n\n## Workflow\n\n### Step 1: Gather Options\n\n1. **Framework**: React 19 / Vue 3 / Svelte / Vanilla\n2. **Styling/UI**: Tailwind CSS / shadcn/ui + Lucide (React) / shadcn-vue + Vue Bits (Vue) / Ant Design / Element Plus / 纯 SCSS\n3. **State Management**: Zustand / Pinia (Vue) / None\n4. **i18n**: Yes / No\n5. **System Tray**: Yes / No\n6. **Auto Update**: Yes (default) / No\n7. **Sidecar** (bundled external binary): Yes / No\n8. **Project name**: string\n\n### Step 2: Scaffold\n\n```\nbun create tauri-app <project-name>\n```\n\nInteractive prompt will ask for:\n- Frontend language (TypeScript recommended)\n- Package manager (bun)\n- UI template (React/Vue/Svelte/Vanilla)\n\nOr use flags:\n```\nbun create tauri-app <project-name> --template react-ts --manager bun\n```\n\n### Step 3: Install Additional Dependencies\n\n| Selection | Packages |\n|---|---|\n| Tailwind CSS | `tailwindcss @tailwindcss/vite` |\n| shadcn/ui (React) | `npx shadcn@latest init` + `lucide-react` |\n| shadcn-vue (Vue) | `npx shadcn-vue@latest init` + `lucide-vue-next` |\n| Zustand | `zustand` |\n| Ant Design | `antd @ant-design/icons` |\n| Element Plus | `element-plus @element-plus/icons-vue` |\n| i18n (React) | `react-i18next i18next` |\n| i18n (Vue) | `vue-i18n` |\n\n### Step 4: Project Structure\n\n```\nproject-name/\n├── src/                        # Frontend (React/Vue)\n│   ├── components/\n│   │   ├── ErrorBoundary.tsx\n│   │   ├── PageLoading.tsx\n│   │   ├── EmptyState.tsx\n│   │   └── ErrorState.tsx\n│   ├── pages/\n│   │   ├── Home/\n│   │   │   └── index.tsx\n│   │   └── NotFound.tsx\n│   ├── hooks/\n│   │   ├── useRequest.ts       # Data fetching hook (from shared layer)\n│   │   └── useTauri.ts         # Tauri IPC hooks\n│   ├── store/                  # State management (from shared infrastructure)\n│   ├── theme/                  # Theme system (from shared infrastructure)\n│   ├── config/                 # App constants + routes (from shared infrastructure)\n│   ├── App.tsx\n│   └── main.tsx\n├── src-tauri/                  # Rust backend\n│   ├── src/\n│   │   ├── main.rs             # Entry point\n│   │   ├── lib.rs              # Core logic + IPC commands\n│   │   └── commands/           # Custom Tauri commands\n│   │       └── mod.rs\n│   ├── Cargo.toml              # Rust dependencies\n│   ├── tauri.conf.json         # Tauri config (window, permissions, etc.)\n│   ├── icons/                  # App icons\n│   └── capabilities/\n│       └── default.json        # Permission capabilities\n├── package.json\n├── vite.config.ts\n└── .env / .env.development / .env.production\n```\n\n### Step 5: Key Templates\n\n**`src-tauri/tauri.conf.json`** (key sections):\n```json\n{\n  \"productName\": \"Your App\",\n  \"version\": \"0.1.0\",\n  \"identifier\": \"com.example.app\",\n  \"app\": {\n    \"windows\": [\n      {\n        \"title\": \"Your App\",\n        \"width\": 1200,\n        \"height\": 800,\n        \"minWidth\": 800,\n        \"minHeight\": 600,\n        \"center\": true,\n        \"decorations\": true\n      }\n    ],\n    \"trayIcon\": {\n      \"iconPath\": \"icons/icon.png\",\n      \"iconAsTemplate\": true\n    }\n  },\n  \"plugins\": {\n    \"updater\": {\n      \"active\": true,\n      \"endpoints\": [\"https://update.example.com/{{target}}/{{arch}}/{{current_version}}\"],\n      \"pubkey\": \"YOUR_PUBLIC_KEY_HERE\"\n    }\n  }\n}\n```\n\n**`src-tauri/src/lib.rs`** — IPC commands:\n```rust\nuse tauri::Manager;\n\n#[tauri::command]\nfn greet(name: &str) -> String {\n    format!(\"Hello, {}! Welcome from Rust.\", name)\n}\n\n#[tauri::command]\nfn get_system_info() -> Result<String, String> {\n    Ok(format!(\"OS: {}\", std::env::consts::OS))\n}\n\n#[cfg_attr(mobile, tauri::mobile_entry_point)]\npub fn run() {\n    tauri::Builder::default()\n        .plugin(tauri_plugin_shell::init())\n        .plugin(tauri_plugin_updater::Builder::new().build())\n        .plugin(tauri_plugin_dialog::init())\n        .invoke_handler(tauri::generate_handler![greet, get_system_info])\n        .run(tauri::generate_context!())\n        .expect(\"error while running tauri application\");\n}\n```\n\n**`src/hooks/useTauri.ts`** — Tauri IPC hooks:\n```typescript\nimport { invoke } from '@tauri-apps/api/core';\nimport { useState, useCallback } from 'react';\n\nexport function useTauriCommand<T>(command: string) {\n  const [data, setData] = useState<T>();\n  const [loading, setLoading] = useState(false);\n  const [error, setError] = useState<Error>();\n\n  const execute = useCallback(async (...args: Record<string, unknown>[]) => {\n    setLoading(true);\n    setError(undefined);\n    try {\n      const result = await invoke<T>(command, args[0] || {});\n      setData(result);\n      return result;\n    } catch (e) {\n      setError(e as Error);\n      throw e;\n    } finally {\n      setLoading(false);\n    }\n  }, [command]);\n\n  return { data, loading, error, execute };\n}\n```\n\n**`src-tauri/Cargo.toml`** (key deps):\n```toml\n[dependencies]\ntauri = { version = \"2\", features = [\"tray-icon\"] }\ntauri-plugin-shell = \"2\"\ntauri-plugin-updater = \"2\"\ntauri-plugin-dialog = \"2\"\nserde = { version = \"1\", features = [\"derive\"] }\nserde_json = \"1\"\n\n[build-dependencies]\ntauri-build = { version = \"2\" }\n```\n\n**`package.json` scripts:**\n```json\n{\n  \"scripts\": {\n    \"dev\": \"tauri dev\",\n    \"build\": \"tauri build\",\n    \"dev:frontend\": \"vite\",\n    \"build:frontend\": \"vite build\",\n    \"preview\": \"vite preview\",\n    \"lint\": \"eslint src --ext .ts,.tsx\",\n    \"typecheck\": \"tsc --noEmit\"\n  }\n}\n```\n\n### Step 6: Shared Layer + Infrastructure + Final\n\nRead the following reference files and generate all shared code:\n1. `../references/shared-base.md` — services, utils, styles, types, env files\n2. `../references/shared-config.md` — vite, tsconfig, eslint, prettier configs\n3. `../references/shared-infrastructure.md` — store, theme, i18n, hooks, layouts, auth guard, config/constants\n\nSkip vite.config.ts modifications for env proxy — Tauri uses direct connections.\n\n**Conditional generation** (only if user selected the corresponding option):\n- Selected Zustand/Redux Toolkit/Pinia → generate `src/store/`\n- Selected i18n → generate `src/locales/`\n- **Always generate**: `src/hooks/`, `src/components/AppProvider.tsx`, `src/components/AuthGuard.tsx`, `src/components/GlobalLoading.tsx`, `src/config/`, `src/theme/`\n\n```bash\ncd <project-name>\nbun install\nbun dev      # Starts Vite + Rust backend concurrently\n```\n\nAnnounce: Tauri project ready, dev starts both frontend and Rust backend. Bundle size will be ~5-15MB vs Electron's ~150MB.\n\nFile v3.1.1:web/SKILL.md\n\n---\nname: fe-cli-web\ndescription: >\n  Scaffold a Web SPA frontend project. For websites, landing pages, and standard single-page apps.\n  Triggered as a sub-skill of fe-cli when user wants a web/SPA project type.\n---\n\n# fe-cli-web — Web SPA Scaffolding\n\nHandles project scaffolding for standard Web Single Page Applications.\n\n## Workflow\n\n### Step 1: Gather Options\n\nAsk these questions. Skip questions whose answer is implied by the user's initial request.\n\n**Quick mode:** If user provides all options in one message (e.g. \"React + Tailwind + Zustand + i18n，叫 my-site\"),\nskip ALL questions and proceed directly to Step 2.\n\n1. **Framework**: React 19 / Vue 3\n2. **Styling/UI**: Tailwind CSS / shadcn/ui + Lucide (React) / shadcn-vue + Vue Bits (Vue) / Ant Design (React) / Element Plus (Vue) / MUI (React) / Chakra UI (React) / React Bits (React) / 纯 CSS (SCSS)\n3. **CSS Preprocessor**: Sass / Less / None (default: Sass if using SCSS approach)\n4. **State Management**: Zustand / Redux Toolkit / Pinia (for Vue) / None\n5. **Router**: React Router / Vue Router / None\n6. **Charts**: Recharts (lightweight) / ECharts (heavy) / None\n7. **i18n**: react-i18next / vue-i18n / None\n8. **Testing**: Vitest / None\n9. **Pre-commit hooks**: husky + lint-staged + commitlint? (default: No)\n10. **Project name**: string (required)\n\n### Step 2: Scaffold Project\n\nUse `bun create vite` (or `pnpm create vite` if bun unavailable) as the base:\n\n```\nbun create vite <project-name> --template react-ts   (for React)\nbun create vite <project-name> --template vue-ts     (for Vue)\n```\n\nThen `cd <project-name>` and install additional dependencies based on selections.\n\n### Step 3: Install Dependencies\n\n| Selection | Packages |\n|---|---|\n| Tailwind CSS | `tailwindcss @tailwindcss/vite` |\n| shadcn/ui (React) | `npx shadcn@latest init` → adds components on demand. Requires Tailwind CSS. Include `lucide-react` |\n| shadcn-vue (Vue) | `npx shadcn-vue@latest init` → adds components on demand. Requires Tailwind CSS. Include `lucide-vue-next` |\n| Ant Design | `antd @ant-design/icons @ant-design/v5-patch-for-react-19` |\n| Element Plus | `element-plus @element-plus/icons-vue` |\n| MUI | `@mui/material @emotion/react @emotion/styled @mui/icons-material` |\n| Chakra UI | `@chakra-ui/react @emotion/react @emotion/styled framer-motion react-icons` |\n| React Bits | `react-bits` (animated UI components) |\n| Vue Bits | `vue-bits` (animated UI components for Vue) |\n| Zustand | `zustand` |\n| Redux Toolkit | `@reduxjs/toolkit react-redux` |\n| React Router | `react-router-dom` |\n| Vue Router | `vue-router` |\n| Recharts | `recharts` |\n| ECharts | `echarts echarts-for-react` (React) / `vue-echarts` (Vue) |\n| i18n (React) | `react-i18next i18next i18next-browser-languagedetector` |\n| i18n (Vue) | `vue-i18n` |\n| Vitest | `vitest @testing-library/react @testing-library/jest-dom jsdom` (React) |\n| Sass | `sass` (built-in Vite support) |\n| Less | `less` |\n| pre-commit | `husky lint-staged @commitlint/cli @commitlint/config-conventional` |\n\n### Step 4: Configure Vite\n\nGenerate `vite.config.ts` based on selections. Use the template from `../references/shared-config.md`.\nAdd Tailwind plugin if selected: `import tailwindcss from '@tailwindcss/vite'` → `plugins: [react(), tailwindcss()]`\n\n### Step 5: Generate Type-Specific Files\n\n**React projects:**\n\n```\nsrc/\n├── components/\n│   ├── ErrorBoundary.tsx     # React error boundary with fallback UI\n│   ├── PageLoading.tsx       # Full-page loading spinner\n│   ├── EmptyState.tsx        # Empty state placeholder\n│   └── ErrorState.tsx        # Error state with retry button\n├── pages/\n│   ├── Home/\n│   │   └── index.tsx         # Home page\n│   └── NotFound.tsx          # 404 page\n├── hooks/                    # Common hooks (generated by shared layer)\n├── layouts/                  # Layout components (generated by shared infrastructure)\n├── store/                    # State management (generated by shared infrastructure)\n├── theme/                    # Theme system (generated by shared infrastructure)\n├── config/                   # App constants + routes (generated by shared infrastructure)\n├── locales/                  # i18n (generated by shared infrastructure, if selected)\n├── App.tsx                   # App with router setup + layout\n└── main.tsx                  # Entry point\n```\n\n`App.tsx` template:\n```tsx\nimport { BrowserRouter, Routes, Route } from 'react-router-dom';\nimport Home from '@/pages/Home';\nimport NotFound from '@/pages/NotFound';\n\nexport default function App() {\n  return (\n    <BrowserRouter>\n      <Routes>\n        <Route path=\"/\" element={<Home />} />\n        <Route path=\"*\" element={<NotFound />} />\n      </Routes>\n    </BrowserRouter>\n  );\n}\n```\n\n`components/ErrorBoundary.tsx`:\n```tsx\nimport { Component, type ReactNode } from 'react';\nimport ErrorState from './ErrorState';\n\ninterface Props { children: ReactNode; }\ninterface State { hasError: boolean; error?: Error; }\n\nexport default class ErrorBoundary extends Component<Props, State> {\n  state: State = { hasError: false };\n  static getDerivedStateFromError(error: Error) { return { hasError: true, error }; }\n  render() {\n    if (this.state.hasError) return <ErrorState error={this.state.error} />;\n    return this.props.children;\n  }\n}\n```\n\n`components/PageLoading.tsx`:\n> **Note:** Inline styles below are for quick scaffolding. **Production environments should replace them with SCSS modules**.\n```tsx\n// Pure CSS spinner — works regardless of UI library choice\nexport default function PageLoading() {\n  return (\n    <div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', height: '100vh' }}>\n      <div style={{\n        width: 32, height: 32, border: '3px solid #e8e8e8', borderTopColor: '#1677ff',\n        borderRadius: '50%', animation: 'spin 0.8s linear infinite',\n      }} />\n      <style>{`@keyframes spin { to { transform: rotate(360deg) } }`}</style>\n    </div>\n  );\n}\n```\n\n`components/EmptyState.tsx`:\n> **Note:** Inline styles below are for quick scaffolding. **Production environments should replace them with SCSS modules**.\n```tsx\ninterface Props { description?: string; }\nexport default function EmptyState({ description = '暂无数据' }: Props) {\n  return <div style={{ textAlign: 'center', padding: '48px 0', color: '#999' }}>{description}</div>;\n}\n```\n\n`components/ErrorState.tsx`:\n> **Note:** Inline styles below are for quick scaffolding. **Production environments should replace them with SCSS modules**.\n```tsx\ninterface Props { error?: Error; onRetry?: () => void; }\nexport default function ErrorState({ error, onRetry }: Props) {\n  return (\n    <div style={{ textAlign: 'center', padding: '48px 0' }}>\n      <p style={{ color: '#ff4d4f', marginBottom: 16 }}>{error?.message || '页面出错了'}</p>\n      {onRetry && <button onClick={onRetry}>重试</button>}\n    </div>\n  );\n}\n```\n\n> **Note:** `hooks/useRequest.ts` and other common hooks are generated by the shared infrastructure layer (Step 6).\n> See `../references/shared-base.md` for `useRequest` template and `../references/shared-infrastructure.md` for all other hooks.\n\n**Vue projects:** Adapt same patterns to Vue 3 Composition API (defineComponent, ref, reactive, etc.)\n\n### Step 6: Generate Shared Layer\n\nAfter type-specific files, read the following reference files and generate all shared code:\n1. `../references/shared-base.md` — services, utils, styles, types, env files\n2. `../references/shared-config.md` — vite, tsconfig, eslint, prettier configs\n3. `../references/shared-infrastructure.md` — store, theme, i18n, hooks, layouts, auth guard, config/constants\n\n**Conditional generation** (only if user selected the corresponding option):\n- Selected Zustand/Redux Toolkit/Pinia → generate `src/store/`\n- Selected i18n → generate `src/locales/`\n- **Always generate** (regardless of options): `src/hooks/` (common hooks), `src/components/AppProvider.tsx`, `src/components/AuthGuard.tsx`, `src/components/GlobalLoading.tsx`, `src/config/`, `src/theme/`\n\n### Step 7: Final Setup\n\n```bash\ncd <project-name>\nbun install  # or: pnpm install\nbun dev      # or: pnpm dev\n```\n\nAnnounce completion: project name, framework, key dependencies, dev server URL (http://localhost:5173).\n\n**Vercel deployment** (recommended):\n```bash\nbun add -g vercel\nvercel\n```\nOr connect GitHub repo at vercel.com for auto-deploy.\n\nFile v3.1.1:_meta.json\n\n{\n  \"ownerId\": \"kn76af6ccjftr7hsds21j60xnn82q1qd\",\n  \"slug\": \"fe-cli\",\n  \"version\": \"3.1.1\",\n  \"publishedAt\": 1779564459296\n}\n\nFile v3.1.1:references/ai-project-md.md\n\n# .ai/PROJECT.md Template\n\nGenerated at the root of every scaffolded project. Adapt based on actual project type and choices.\n\n---\n\n# {ProjectName}\n\n## Tech Stack\n\n- **Framework**: {React 18 / Vue 3 / Next.js 14 / Nuxt 3}\n- **Language**: TypeScript\n- **Build**: Vite 5\n- **CSS**: Sass (SCSS) + {Tailwind CSS / Ant Design / MUI}\n- **State**: {Zustand / Pinia / Redux Toolkit}\n- **Router**: {React Router v6 / Vue Router 4 / Next.js App Router}\n- **Package Manager**: pnpm\n- **Node**: 18+\n\n## Commands\n\n```bash\npnpm dev          # Start dev server (development mode)\npnpm dev:test     # Start dev server (test mode)\npnpm build:test   # Build for test env (Vite handles TS transpilation)\npnpm build:prod   # Build for production (Vite handles TS transpilation)\npnpm preview      # Preview production build\npnpm lint         # Check code style\npnpm lint:fix     # Auto-fix code style\npnpm typecheck    # TypeScript type check\n```\n\n## Directory Structure\n\n```\n{project-name}/\n│\n├── .ai/\n│   └── PROJECT.md              # This file — AI reference doc\n│\n├── public/\n│   └── favicon.ico             # Site favicon\n│\n├── src/\n│   ├── assets/                 # Static assets (images, fonts, etc.)\n│   │\n│   ├── components/             # Shared/reusable UI components\n│   │   ├── AppProvider.tsx     # Root context provider wrapper\n│   │   ├── AuthGuard.tsx       # Route authentication guard\n│   │   ├── GlobalLoading.tsx   # Full-page loading overlay\n│   │   ├── ErrorBoundary.tsx   # React error boundary\n│   │   ├── PageLoading.tsx     # Page-level loading spinner\n│   │   ├── EmptyState.tsx      # Empty state placeholder\n│   │   └── ErrorState.tsx      # Error state with retry\n│   │\n│   ├── config/                 # App-level constants and route config\n│   │   ├── index.ts            # Constants, env config, route paths\n│   │   └── routes.tsx          # Centralized route definitions\n│   │\n│   ├── hooks/                  # Custom React/Vue hooks\n│   │   ├── index.ts            # Re-export all hooks\n│   │   ├── useRequest.ts       # Data fetching\n│   │   ├── useDebounce.ts      # Debounce value\n│   │   ├── useLocalStorage.ts  # Typed localStorage\n│   │   ├── useMediaQuery.ts    # CSS media query reactive\n│   │   ├── useClickAway.ts     # Click outside detector\n│   │   ├── useToggle.ts        # Boolean toggle\n│   │   ├── usePrevious.ts      # Previous value tracker\n│   │   └── useUpdateEffect.ts  # Skip-first-render effect\n│   │\n│   ├── layouts/                # Page layout components\n│   │   ├── AppLayout.tsx       # Main layout (sidebar + header + content)\n│   │   ├── Header.tsx          # Top bar (theme toggle, user menu)\n│   │   └── Sidebar.tsx         # Side navigation\n│   │\n│   ├── locales/                # i18n translations (if i18n selected)\n│   │   ├── index.ts            # i18n initialization\n│   │   ├── zh-CN.json          # Chinese translations\n│   │   ├── en-US.json          # English translations\n│   │   └── useLanguage.ts      # Language switch hook\n│   │\n│   ├── store/                  # State management (if selected)\n│   │   ├── index.ts            # Re-export stores\n│   │   ├── useUserStore.ts     # User auth & profile\n│   │   ├── useAppStore.ts      # App-level state (theme, locale, sidebar)\n│   │   └── middleware.ts       # Custom middleware (logger, persist)\n│   │\n│   ├── theme/                  # Theme system\n│   │   ├── index.ts            # ThemeProvider + theme logic\n│   │   ├── tokens.ts           # Light + dark design tokens\n│   │   └── useTheme.ts         # Theme hook\n│   │\n│   ├── pages/                  # Page-level components (one per route)\n│   │\n│   ├── services/               # API layer\n│   │   ├── request.ts          # fetch wrapper — interceptors, error handling, auth header\n│   │   ├── logger.ts           # Structured logger — levels, rotation, file persistence\n│   │   ├── log-export.ts        # Log export (download) + submit (endpoint TBD)\n│   │   └── api/\n│   │       └── index.ts        # API endpoint functions\n│   │\n│   ├── styles/                 # Global styles\n│   │   ├── global.scss         # CSS custom properties + body/html defaults\n│   │   ├── reset.scss          # CSS reset / normalize\n│   │   └── variables.scss      # Design tokens (colors, spacing, breakpoints, font-sizes)\n│   │\n│   ├── types/                  # TypeScript type declarations\n│   │   └── global.d.ts         # Global types, module declarations\n│   │\n│   ├── utils/                  # Pure utility functions\n│   │   ├── index.ts            # Common helpers (debounce, throttle, deepClone, etc.)\n│   │   ├── storage.ts          # Typed localStorage/sessionStorage wrapper\n│   │   ├── format.ts           # Date, number, currency formatters\n│   │   └── validate.ts         # Form validation rules\n│   │\n│   ├── App.{tsx|vue}           # Root component — layout + router outlet\n│   ├── main.{tsx|ts}           # App entry — mount, global providers, styles\n│   └── vite-env.d.ts           # Vite client type reference\n│\n├── .env                         # Common env (loaded in all modes)\n├── .env.development             # Dev env overrides\n├── .env.test                    # Test env overrides\n├── .env.production              # Prod env overrides\n│\n├── index.html                   # Vite HTML entry point\n├── package.json                 # Dependencies + scripts\n├── pnpm-lock.yaml               # Locked dependency versions\n├── tsconfig.json                # TypeScript config (paths: @/ → src/)\n├── tsconfig.node.json           # TypeScript config for Vite config file\n├── vite.config.ts               # Vite config (aliases, plugins, env prefix)\n├── .eslintrc.cjs                # ESLint config\n└── .prettierrc                  # Prettier config\n```\n\n## Conventions\n\n- **Path alias**: `@/` maps to `src/`\n- **Import order**: React/Vue → third-party libs → `@/` aliases → relative imports\n- **Component naming**: PascalCase for components, camelCase for utilities\n- **CSS modules**: Enabled by default (`.module.scss`)\n- **Responsive breakpoints**: Mobile (< 768px) → Tablet (768-1023px) → Desktop (≥ 1024px)\n- **API calls**: Use `src/services/request.ts` wrapper; never call `fetch()` directly\n- **Logging**: Use `logger.child(\"Module\")` from `services/logger.ts`; never use raw `console.log` in production\n- **State**: Server state in API layer; UI state in stores; form state local to component\n\n## Environment Variables\n\n| Variable | Dev | Test | Prod | Description |\n|---|---|---|---|---|\n| `VITE_API_BASE_URL` | `http://localhost:3000` | `https://test-api.example.com` | `https://api.example.com` | API base URL |\n| `VITE_APP_TITLE` | `{ProjectName} (Dev)` | `{ProjectName} (Test)` | `{ProjectName}` | App title |\n| `VITE_APP_ENV` | `development` | `test` | `production` | Current environment |\n| `VITE_LOG_LEVEL` | `info` | `info` | `warn` | Min log level (debug/info/warn/error) |\n| `VITE_LOG_SUBMIT_URL` | — | — | — | Log submit endpoint (TBD) |\n\nArchive v3.1.0: 15 files, 65407 bytes\n\nFiles: admin/SKILL.md (19281b), astro/SKILL.md (6971b), electron/SKILL.md (7884b), h5/SKILL.md (7372b), miniapp/SKILL.md (10254b), references/ai-project-md.md (5437b), references/shared-base.md (25813b), references/shared-config.md (5072b), references/shared-infrastructure.md (31845b), rn/SKILL.md (7722b), SKILL.md (15559b), ssr/SKILL.md (10949b), tauri/SKILL.md (7221b), web/SKILL.md (8986b), _meta.json (125b)\n\nFile v3.1.0:admin/SKILL.md\n\n---\nname: fe-cli-admin\ndescription: >\n  Scaffold an Admin Dashboard frontend project. For data management backends, CRUD systems,\n  and internal tools. Features permission-based routing, login page, Ant Design Pro layout.\n  Triggered as a sub-skill of fe-cli when user wants an admin/后台管理 project type.\n---\n\n# fe-cli-admin — Admin Dashboard Scaffolding\n\n## Workflow\n\n### Step 1: Gather Options\n\nSkip questions already answered in user's request.\n\n1. **Framework**: React 19 / Vue 3 (React recommended for admin, Ant Design is React-only; Vue admin 推荐 Element Plus)\n2. **Styling/UI**: Ant Design (default React) / shadcn/ui + Lucide (React) / Element Plus (default Vue) / shadcn-vue + Vue Bits (Vue) / Tailwind CSS / MUI\n3. **CSS Preprocessor**: Sass / Less (default: Sass)\n4. **State Management**: Zustand / Redux Toolkit / Pinia (Vue) / None\n5. **Charts**: Recharts (lightweight) / ECharts (heavy) / None\n6. **i18n**: react-i18next / vue-i18n / None\n7. **Testing**: Vitest / None\n8. **Pre-commit hooks**: husky + lint-staged + commitlint? (default: No)\n9. **Project name**: string (required)\n\n### Step 2: Scaffold Project\n\n```\nbun create vite <project-name> --template react-ts\n```\n\nFor Vue: `bun create vite <project-name> --template vue-ts`\n\n(Use `pnpm create vite` if bun unavailable)\n\n### Step 3: Install Dependencies\n\n**React (Ant Design):**\n```\nbun add antd @ant-design/icons @ant-design/v5-patch-for-react-19 react-router-dom dayjs\n```\n\n**React (shadcn/ui):**\n```\nbun add react-router-dom dayjs lucide-react && npx shadcn@latest init\n```\n\n**Vue (Element Plus):**\n```\nbun add element-plus @element-plus/icons-vue vue-router dayjs\n```\n\n**Vue (shadcn-vue):**\n```\nbun add vue-router dayjs lucide-vue-next && npx shadcn-vue@latest init\n```\n\nPlus extras based on selections (zustand, pinia, i18n, charts, etc.)\n\n**Charts:**\n- Recharts: `bun add recharts`\n- ECharts: `bun add echarts echarts-for-react` (React) / `bun add echarts vue-echarts` (Vue)\n\nFor date handling: `pnpm add dayjs` (recommended; moment is deprecated and not recommended).\n\n### Step 4: Generate Admin-Specific Files\n\n```\nsrc/\n├── components/\n│   ├── ErrorBoundary.tsx\n│   ├── PageLoading.tsx\n│   ├── EmptyState.tsx\n│   └── ErrorState.tsx          # (same templates as fe-cli-web)\n├── layouts/\n│   └── AdminLayout.tsx         # Sidebar + Header + Content layout\n├── pages/\n│   ├── Login/\n│   │   └── index.tsx           # Login page with form\n│   ├── Dashboard/\n│   │   └── index.tsx           # Dashboard with stats cards\n│   ├── List/\n│   │   └── index.tsx           # Table list page template\n│   ├── Form/\n│   │   └── index.tsx           # Form page template\n│   └── NotFound.tsx\n├── hooks/\n│   └── useRequest.ts           # (same as fe-cli-web)\n├── stores/\n│   └── authStore.ts            # Auth state (token, user info, login/logout)\n├── routes/\n│   └── index.tsx               # Route config with permission guard\n├── App.tsx                     # Router + AuthProvider\n└── main.tsx\n```\n\n### Step 5: Key Templates\n\n**`layouts/AdminLayout.tsx`** — Classic admin layout:\n> **Note:** Inline styles below are for quick scaffolding. **Production environments should replace them with SCSS modules** (e.g., `import styles from './AdminLayout.module.scss'`).\n```tsx\nimport { useState } from 'react';\nimport { Outlet, useNavigate, useLocation } from 'react-router-dom';\nimport { Layout, Menu, Button, Dropdown, Avatar, theme } from 'antd';\nimport {\n  DashboardOutlined, FileTextOutlined, FormOutlined,\n  MenuFoldOutlined, MenuUnfoldOutlined, UserOutlined, LogoutOutlined,\n} from '@ant-design/icons';\n\nconst { Header, Sider, Content } = Layout;\n\n// Menu items - extend based on project needs\nconst menuItems = [\n  { key: '/dashboard', icon: <DashboardOutlined />, label: '仪表盘' },\n  { key: '/list', icon: <FileTextOutlined />, label: '列表页' },\n  { key: '/form', icon: <FormOutlined />, label: '表单页' },\n];\n\nexport default function AdminLayout() {\n  const [collapsed, setCollapsed] = useState(false);\n  const navigate = useNavigate();\n  const location = useLocation();\n  const { token: { colorBgContainer, borderRadiusLG } } = theme.useToken();\n\n  return (\n    <Layout style={{ minHeight: '100vh' }}>\n      <Sider trigger={null} collapsible collapsed={collapsed}>\n        <div style={{ height: 32, margin: 16, background: 'rgba(255,255,255,0.2)', borderRadius: 6 }} />\n        <Menu\n          theme=\"dark\"\n          mode=\"inline\"\n          selectedKeys={[location.pathname]}\n          items={menuItems}\n          onClick={({ key }) => navigate(key)}\n        />\n      </Sider>\n      <Layout>\n        <Header style={{ padding: '0 24px', background: colorBgContainer, display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>\n          <Button type=\"text\" icon={collapsed ? <MenuUnfoldOutlined /> : <MenuFoldOutlined />} onClick={() => setCollapsed(!collapsed)} />\n          <Dropdown menu={{ items: [{ key: 'logout', icon: <LogoutOutlined />, label: '退出登录' }] }}>\n            <Avatar icon={<UserOutlined />} style={{ cursor: 'pointer' }} />\n          </Dropdown>\n        </Header>\n        <Content style={{ margin: 24, padding: 24, background: colorBgContainer, borderRadius: borderRadiusLG, minHeight: 280 }}>\n          <Outlet />\n        </Content>\n      </Layout>\n    </Layout>\n  );\n}\n```\n\n**`pages/Login/index.tsx`** — Login page:\n> **Note:** Inline styles below are for quick scaffolding. **Production environments should replace them with SCSS modules**.\n```tsx\nimport { useState, type FormEvent } from 'react';\nimport { useNavigate } from 'react-router-dom';\nimport { Form, Input, Button, Card, message } from 'antd';\nimport { UserOutlined, LockOutlined } from '@ant-design/icons';\n\nexport default function Login() {\n  const [loading, setLoading] = useState(false);\n  const navigate = useNavigate();\n\n  const onFinish = async (values: { username: string; password: string }) => {\n    setLoading(true);\n    try {\n      // Replace with actual API call\n      // const { token } = await userApi.login(values);\n      // authStore.login(token);\n      message.success('登录成功');\n      navigate('/dashboard', { replace: true });\n    } catch {\n      message.error('登录失败');\n    } finally {\n      setLoading(false);\n    }\n  };\n\n  return (\n    <div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', minHeight: '100vh', background: '#f0f2f5' }}>\n      <Card title=\"系统登录\" style={{ width: 400 }}>\n        <Form onFinish={onFinish} size=\"large\">\n          <Form.Item name=\"username\" rules={[{ required: true, message: '请输入用户名' }]}>\n            <Input prefix={<UserOutlined />} placeholder=\"用户名\" />\n          </Form.Item>\n          <Form.Item name=\"password\" rules={[{ required: true, message: '请输入密码' }]}>\n            <Input.Password prefix={<LockOutlined />} placeholder=\"密码\" />\n          </Form.Item>\n          <Form.Item>\n            <Button type=\"primary\" htmlType=\"submit\" loading={loading} block>登录</Button>\n          </Form.Item>\n        </Form>\n      </Card>\n    </div>\n  );\n}\n```\n\n**`pages/Dashboard/index.tsx`** — Dashboard with stats:\n```tsx\nimport { Row, Col, Card, Statistic } from 'antd';\nimport { UserOutlined, ShoppingCartOutlined, RiseOutlined, FileTextOutlined } from '@ant-design/icons';\n\nexport default function Dashboard() {\n  return (\n    <div>\n      <Row gutter={[16, 16]}>\n        <Col xs={24} sm={12} lg={6}>\n          <Card><Statistic title=\"用户数\" value={1893} prefix={<UserOutlined />} /></Card>\n        </Col>\n        <Col xs={24} sm={12} lg={6}>\n          <Card><Statistic title=\"订单数\" value={568} prefix={<ShoppingCartOutlined />} /></Card>\n        </Col>\n        <Col xs={24} sm={12} lg={6}>\n          <Card><Statistic title=\"销售额\" value={93456} prefix={<RiseOutlined />} suffix=\"元\" /></Card>\n        </Col>\n        <Col xs={24} sm={12} lg={6}>\n          <Card><Statistic title=\"文章数\" value={128} prefix={<FileTextOutlined />} /></Card>\n        </Col>\n      </Row>\n    </div>\n  );\n}\n```\n\n**`pages/List/index.tsx`** — Table list template:\n> **Note:** Inline styles below are for quick scaffolding. **Production environments should replace them with SCSS modules**.\n```tsx\nimport { useState } from 'react';\nimport { Table, Button, Input, Space, Tag, type TableColumnsType } from 'antd';\nimport { PlusOutlined, SearchOutlined } from '@ant-design/icons';\n\ninterface DataItem { id: number; name: string; status: 'active' | 'inactive'; createdAt: string; }\n\nexport default function List() {\n  const [data] = useState<DataItem[]>([]);\n  const columns: TableColumnsType<DataItem> = [\n    { title: 'ID', dataIndex: 'id', key: 'id', width: 80 },\n    { title: '名称', dataIndex: 'name', key: 'name' },\n    { title: '状态', dataIndex: 'status', key: 'status', render: (s) => <Tag color={s === 'active' ? 'green' : 'red'}>{s}</Tag> },\n    { title: '创建时间', dataIndex: 'createdAt', key: 'createdAt' },\n    { title: '操作', key: 'action', render: () => <Space><Button type=\"link\" size=\"small\">编辑</Button><Button type=\"link\" size=\"small\" danger>删除</Button></Space> },\n  ];\n  return (\n    <div>\n      <div style={{ marginBottom: 16, display: 'flex', justifyContent: 'space-between' }}>\n        <Space><Input placeholder=\"搜索\" prefix={<SearchOutlined />} /><Button type=\"primary\">搜索</Button></Space>\n        <Button type=\"primary\" icon={<PlusOutlined />}>新建</Button>\n      </div>\n      <Table rowKey=\"id\" columns={columns} dataSource={data} pagination={{ pageSize: 10 }} />\n    </div>\n  );\n}\n```\n\n**`pages/Form/index.tsx`** — Form page template:\n```tsx\nimport { Form, Input, Button, Select, DatePicker, Card, message } from 'antd';\n\nexport default function FormPage() {\n  const [form] = Form.useForm();\n  const onFinish = (values: unknown) => { console.log(values); message.success('提交成功'); };\n  return (\n    <Card title=\"表单页\">\n      <Form form={form} layout=\"vertical\" onFinish={onFinish} style={{ maxWidth: 600 }}>\n        <Form.Item label=\"名称\" name=\"name\" rules={[{ required: true }]}><Input /></Form.Item>\n        <Form.Item label=\"类型\" name=\"type\" rules={[{ required: true }]}><Select options={[{ label: '类型A', value: 'a' }, { label: '类型B', value: 'b' }]} /></Form.Item>\n        <Form.Item label=\"日期\" name=\"date\"><DatePicker style={{ width: '100%' }} /></Form.Item>\n        <Form.Item><Button type=\"primary\" htmlType=\"submit\">提交</Button></Form.Item>\n      </Form>\n    </Card>\n  );\n}\n```\n\n**`stores/authStore.ts`** (Zustand):\n```tsx\nimport { create } from 'zustand';\nimport { storage } from '@/utils/storage';\n\ninterface AuthState {\n  token: string | null;\n  user: { id: string; name: string; avatar?: string } | null;\n  login: (token: string, user: AuthState['user']) => void;\n  logout: () => void;\n  isLoggedIn: () => boolean;\n}\n\nexport const useAuthStore = create<AuthState>((set, get) => ({\n  token: storage.get('token'),\n  user: storage.get('user'),\n  login: (token, user) => { storage.set('token', token); storage.set('user', user); set({ token, user }); },\n  logout: () => { storage.remove('token'); storage.remove('user'); set({ token: null, user: null }); },\n  isLoggedIn: () => !!get().token,\n}));\n```\n\n**`routes/index.tsx`** — Permission route config:\n```tsx\nimport { Navigate, type RouteObject } from 'react-router-dom';\nimport AdminLayout from '@/layouts/AdminLayout';\nimport Login from '@/pages/Login';\nimport Dashboard from '@/pages/Dashboard';\nimport List from '@/pages/List';\nimport FormPage from '@/pages/Form';\nimport NotFound from '@/pages/NotFound';\n\n// Auth guard component\nimport { useAuthStore } from '@/stores/authStore';\n\nfunction AuthGuard({ children }: { children: React.ReactNode }) {\n  const token = useAuthStore((s) => s.token);\n  if (!token) return <Navigate to=\"/login\" replace />;\n  return <>{children}</>;\n}\n\nexport const routes: RouteObject[] = [\n  { path: '/login', element: <Login /> },\n  {\n    path: '/',\n    element: <AuthGuard><AdminLayout /></AuthGuard>,\n    children: [\n      { index: true, element: <Navigate to=\"/dashboard\" replace /> },\n      { path: 'dashboard', element: <Dashboard /> },\n      { path: 'list', element: <List /> },\n      { path: 'form', element: <FormPage /> },\n    ],\n  },\n  { path: '*', element: <NotFound /> },\n];\n```\n\n**`App.tsx`:**\n```tsx\nimport { useRoutes } from 'react-router-dom';\nimport { BrowserRouter } from 'react-router-dom';\nimport { ConfigProvider } from 'antd';\nimport zhCN from 'antd/locale/zh_CN';\nimport { routes } from '@/routes';\n\nfunction AppRoutes() { return useRoutes(routes); }\n\nexport default function App() {\n  return (\n    <ConfigProvider locale={zhCN}>\n      <BrowserRouter>\n        <AppRoutes />\n      </BrowserRouter>\n    </ConfigProvider>\n  );\n}\n```\n\n### Step 6: Vue Admin Templates (if Vue + Element Plus selected)\n\n**Vue file structure:**\n```\nsrc/\n├── components/\n│   ├── ErrorBoundary.vue\n│   ├── PageLoading.vue\n│   ├── EmptyState.vue\n│   └── ErrorState.vue\n├── layouts/\n│   └── AdminLayout.vue        # Element Plus sidebar layout\n├── pages/\n│   ├── Login.vue              # Login page\n│   ├── Dashboard.vue          # Dashboard with stats\n│   ├── List.vue               # Table list page\n│   ├── Form.vue               # Form page\n│   └── NotFound.vue\n├── stores/\n│   └── authStore.ts           # Pinia auth store\n├── router/\n│   └── index.ts               # Vue Router with permission guard\n├── App.vue\n└── main.ts\n```\n\n**`main.ts`:**\n```typescript\nimport { createApp } from 'vue';\nimport { createPinia } from 'pinia';\nimport ElementPlus from 'element-plus';\nimport zhCn from 'element-plus/es/locale/lang/zh-cn';\nimport 'element-plus/dist/index.css';\nimport App from './App.vue';\nimport router from './router';\n\nconst app = createApp(App);\napp.use(createPinia());\napp.use(router);\napp.use(ElementPlus, { locale: zhCn });\napp.mount('#app');\n```\n\n**`router/index.ts`:**\n```typescript\nimport { createRouter, createWebHistory } from 'vue-router';\nimport { useAuthStore } from '@/stores/authStore';\n\nconst router = createRouter({\n  history: createWebHistory(),\n  routes: [\n    { path: '/login', component: () => import('@/pages/Login.vue') },\n    {\n      path: '/',\n      component: () => import('@/layouts/AdminLayout.vue'),\n      meta: { requiresAuth: true },\n      children: [\n        { path: '', redirect: '/dashboard' },\n        { path: 'dashboard', component: () => import('@/pages/Dashboard.vue') },\n        { path: 'list', component: () => import('@/pages/List.vue') },\n        { path: 'form', component: () => import('@/pages/Form.vue') },\n      ],\n    },\n    { path: '/:pathMatch(.*)*', component: () => import('@/pages/NotFound.vue') },\n  ],\n});\n\nrouter.beforeEach((to) => {\n  const authStore = useAuthStore();\n  if (to.meta.requiresAuth && !authStore.token) return '/login';\n});\n\nexport default router;\n```\n\n**`stores/authStore.ts` (Pinia):**\n```typescript\nimport { defineStore } from 'pinia';\nimport { storage } from '@/utils/storage';\n\nexport const useAuthStore = defineStore('auth', {\n  state: () => ({\n    token: storage.get<string>('token') || null,\n    user: storage.get<{ id: string; name: string; avatar?: string }>('user') || null,\n  }),\n  actions: {\n    login(token: string, user: this['user']) {\n      this.token = token;\n      this.user = user;\n      storage.set('token', token);\n      storage.set('user', user);\n    },\n    logout() {\n      this.token = null;\n      this.user = null;\n      storage.remove('token');\n      storage.remove('user');\n    },\n  },\n  getters: {\n    isLoggedIn: (state) => !!state.token,\n  },\n});\n```\n\n**`layouts/AdminLayout.vue`:**\n```vue\n<template>\n  <el-container style=\"min-height: 100vh\">\n    <el-aside :width=\"isCollapsed ? '64px' : '220px'\">\n      <div class=\"logo-placeholder\" />\n      <el-menu\n        :default-active=\"route.path\"\n        :collapse=\"isCollapsed\"\n        background-color=\"#001529\"\n        text-color=\"#ffffffa6\"\n        active-text-color=\"#fff\"\n        @select=\"(key: string) => router.push(key)\"\n      >\n        <el-menu-item index=\"/dashboard\"><el-icon><Odometer /></el-icon><span>仪表盘</span></el-menu-item>\n        <el-menu-item index=\"/list\"><el-icon><Document /></el-icon><span>列表页</span></el-menu-item>\n        <el-menu-item index=\"/form\"><el-icon><EditPen /></el-icon><span>表单页</span></el-menu-item>\n      </el-menu>\n    </el-aside>\n    <el-container>\n      <el-header class=\"admin-header\">\n        <el-icon class=\"collapse-btn\" @click=\"isCollapsed = !isCollapsed\">\n          <component :is=\"isCollapsed ? 'Expand' : 'Fold'\" />\n        </el-icon>\n        <el-dropdown @command=\"handleCommand\">\n          <el-avatar :size=\"32\"><el-icon><User /></el-icon></el-avatar>\n          <template #dropdown>\n            <el-dropdown-menu>\n              <el-dropdown-item command=\"logout\">退出登录</el-dropdown-item>\n            </el-dropdown-menu>\n          </template>\n        </el-dropdown>\n      </el-header>\n      <el-main><router-view /></el-main>\n    </el-container>\n  </el-container>\n</template>\n\n<script setup lang=\"ts\">\nimport { ref } from 'vue';\nimport { useRoute, useRouter } from 'vue-router';\nimport { Odometer, Document, EditPen, User, Expand, Fold } from '@element-plus/icons-vue';\nimport { useAuthStore } from '@/stores/authStore';\n\nconst route = useRoute();\nconst router = useRouter();\nconst authStore = useAuthStore();\nconst isCollapsed = ref(false);\n\nfunction handleCommand(command: string) {\n  if (command === 'logout') {\n    authStore.logout();\n    router.push('/login');\n  }\n}\n</script>\n\n<style scoped lang=\"scss\">\n/* Production: replace with SCSS module */\n.logo-placeholder {\n  height: 32px;\n  margin: 16px;\n  background: rgba(255, 255, 255, 0.2);\n  border-radius: 6px;\n}\n.admin-header {\n  display: flex;\n  align-items: center;\n  justify-content: space-between;\n  padding: 0 24px;\n}\n.collapse-btn {\n  font-size: 20px;\n  cursor: pointer;\n}\n</style>\n```\n\n### Step 7: Generate Shared Layer\n\nRead the following reference files and generate all shared code:\n1. `../references/shared-base.md` — services, utils, styles, types, env files\n2. `../references/shared-config.md` — vite, tsconfig, eslint, prettier configs\n3. `../references/shared-infrastructure.md` — store, theme, i18n, hooks, layouts, auth guard, config/constants\n\n**Conditional generation** (only if user selected the corresponding option):\n- Selected Zustand/Redux Toolkit/Pinia → generate `src/store/`\n- Selected i18n → generate `src/locales/`\n- **Always generate**: `src/hooks/`, `src/components/AppProvider.tsx`, `src/components/AuthGuard.tsx`, `src/components/GlobalLoading.tsx`, `src/config/`, `src/theme/`\n\n### Step 8: Final Setup\n\n```bash\ncd <project-name>\nbun install  # or: pnpm install\nbun dev      # or: pnpm dev\n```\n\nAnnounce completion with admin-specific info: login page at /login, dashboard at /dashboard, etc.\n\n**Vercel deployment** (recommended):\n```bash\nbun add -g vercel\nvercel\n```\nOr connect GitHub repo at vercel.com for auto-deploy.\n\nFile v3.1.0:astro/SKILL.md\n\n---\nname: fe-cli-astro\ndescription: >\n  Scaffold a static site / content site project. Supports Astro, Gatsby (React), and VitePress (Vue).\n  For blogs, documentation sites, portfolios, landing pages with SSG/SSR.\n  Triggered as a sub-skill of fe-cli when user wants Astro/Gatsby/建站/博客/文档站/VitePress project.\n---\n\n# fe-cli-astro — Static Site Scaffolding\n\n## Workflow\n\n### Step 1: Gather Options\n\n1. **Framework**: Astro / Gatsby (React) / VitePress (Vue)\n2. **UI Integration** (Astro only): React / Vue / Svelte / Preact / None (Astro components only)\n3. **Styling**: Tailwind CSS / shadcn/ui (Astro+React) / 纯 SCSS\n4. **Content**: MDX (Astro/Gatsby) / Markdown (all) / CMS integration (Contentful/Sanity) / None\n5. **i18n**: Yes / No\n6. **Deployment**: Vercel (default) / Netlify / Cloudflare Pages\n7. **Project name**: string\n\n**Quick guide:**\n- **Blog / portfolio / content site** → Astro (islands architecture, partial hydration, fast)\n- **React-heavy content site** → Gatsby (GraphQL data layer, rich plugin ecosystem)\n- **Vue documentation site** → VitePress (Vue-powered static site, markdown-first)\n\n### Step 2: Scaffold\n\n**Astro:**\n```\nbun create astro@latest <project-name>\n```\nInteractive prompt: template (blog/docs/portfolio/empty), TypeScript, install dependencies.\n\nOr with flags:\n```\nbunx create-astro <project-name> --template blog --typescript strict --install\n```\n\n**Gatsby:**\n```\nbunx gatsby new <project-name>\n```\n\n**VitePress:**\n```\nmkdir <project-name> && cd <project-name>\nbun init\nbun add -D vitepress vue\nmkdir docs && echo '# Hello VitePress' > docs/index.md\n```\n\n### Step 3: Install Additional Dependencies\n\n| Selection | Packages |\n|---|---|\n| **Astro** | |\n| Tailwind CSS | `@astrojs/tailwind tailwindcss` (Astro 4+) or `tailwindcss @tailwindcss/vite` |\n| React integration | `@astrojs/react react react-dom` |\n| Vue integration | `@astrojs/vue vue` |\n| MDX | `@astrojs/mdx` |\n| i18n | `astro-i18next` / manual routing |\n| shadcn/ui | `npx shadcn@latest init` (if React integration) + `lucide-react` |\n| **Gatsby** | |\n| Tailwind CSS | `tailwindcss postcss autoprefixer` + `gatsby-plugin-postcss` |\n| MDX | `gatsby-plugin-mdx @mdx-js/react` |\n| CMS | `gatsby-source-contentful` / `gatsby-source-sanity` |\n| **VitePress** | |\n| Tailwind CSS | `tailwindcss @tailwindcss/vite` |\n| Vue components | `vue` (already included) |\n| Search | `vitepress-plugin-search` / Algolia |\n\n### Step 4: Project Structure\n\n**Astro:**\n```\nproject-name/\n├── src/\n│   ├── components/\n│   │   ├── BaseHead.astro       # <head> meta tags, fonts, styles\n│   │   ├── Header.astro         # Navigation header\n│   │   ├── Footer.astro         # Site footer\n│   │   └── Card.astro           # Content card component\n│   ├── content/\n│   │   ├── config.ts            # Content collection schema\n│   │   └── blog/                # Blog posts (MDX/Markdown)\n│   │       └── first-post.mdx\n│   ├── layouts/\n│   │   ├── BaseLayout.astro     # Page layout wrapper\n│   │   └── BlogPost.astro       # Blog post layout\n│   ├── pages/\n│   │   ├── index.astro          # Home page\n│   │   ├── about.astro          # About page\n│   │   └── blog/\n│   │       ├── index.astro      # Blog listing\n│   │       └── [...slug].astro  # Dynamic blog post\n│   └── styles/\n│       └── global.scss          # Global styles\n├── public/                      # Static assets\n├── astro.config.mjs             # Astro configuration\n├── tsconfig.json\n└── package.json\n```\n\n**Gatsby:**\n```\nproject-name/\n├── src/\n│   ├── components/\n│   ├── pages/\n│   │   ├── index.tsx\n│   │   └── blog.tsx\n│   ├── templates/\n│   │   └── blog-post.tsx        # GraphQL template\n│   └── styles/\n├── gatsby-config.ts             # Plugins & metadata\n├── gatsby-node.ts               # Programmatic page creation\n└── package.json\n```\n\n**VitePress:**\n```\nproject-name/\n├── docs/\n│   ├── .vitepress/\n│   │   ├── config.ts            # VitePress config (nav, sidebar, theme)\n│   │   └── theme/\n│   │       └── index.ts         # Custom theme extensions\n│   ├── index.md                 # Home page\n│   ├── guide/                   # Guide pages\n│   │   ├── getting-started.md\n│   │   └── configuration.md\n│   └── blog/                    # Blog pages\n│       └── first-post.md\n├── package.json\n└── tsconfig.json\n```\n\n### Step 5: Key Templates\n\n**`astro.config.mjs`:**\n```javascript\nimport { defineConfig } from 'astro/config';\nimport react from '@astrojs/react';        // if React integration\nimport mdx from '@astrojs/mdx';            // if MDX\nimport tailwindcss from '@tailwindcss/vite';\n\nexport default defineConfig({\n  site: 'https://example.com',\n  integrations: [\n    react(),     // if selected\n    mdx(),       // if selected\n  ],\n  vite: {\n    plugins: [tailwindcss()],\n  },\n});\n```\n\n**`src/content/config.ts`** (Astro content collections):\n```typescript\nimport { defineCollection, z } from 'astro:content';\n\nconst blog = defineCollection({\n  type: 'content',\n  schema: z.object({\n    title: z.string(),\n    description: z.string(),\n    pubDate: z.coerce.date(),\n    updatedDate: z.coerce.date().optional(),\n    heroImage: z.string().optional(),\n    tags: z.array(z.string()).default([]),\n  }),\n});\n\nexport const collections = { blog };\n```\n\n**`docs/.vitepress/config.ts`** (VitePress):\n```typescript\nimport { defineConfig } from 'vitepress';\n\nexport default defineConfig({\n  title: 'My Docs',\n  description: 'Documentation site',\n  themeConfig: {\n    nav: [\n      { text: 'Guide', link: '/guide/getting-started' },\n      { text: 'Blog', link: '/blog/' },\n    ],\n    sidebar: {\n      '/guide/': [\n        {\n          text: 'Getting Started',\n          items: [\n            { text: 'Introduction', link: '/guide/getting-started' },\n            { text: 'Configuration', link: '/guide/configuration' },\n          ],\n        },\n      ],\n    },\n    socialLinks: [\n      { icon: 'github', link: 'https://github.com/your-org/your-repo' },\n    ],\n  },\n});\n```\n\n### Step 6: Final Setup\n\n```bash\ncd <project-name>\nbun install\nbun dev      # Astro: http://localhost:4321 | VitePress: http://localhost:5173\n```\n\n**Deployment:**\n\nVercel (recommended, zero-config):\n```bash\nbun add -g vercel\nvercel\n```\n\nNetlify:\n```bash\nbun add -g netlify-cli\nnetlify deploy --prod\n```\n\nCloudflare Pages:\n```bash\nbunx wrangler pages deploy dist/\n```\n\nAstro adapter for SSR (if needed):\n```\nbun add @astrojs/vercel    # or @astrojs/netlify / @astrojs/cloudflare\n```\nThen add to `astro.config.mjs`: `output: 'server'` + adapter import.\n\nAnnounce: Static site ready, framework, content setup, deploy target.\n\nFile v3.1.0:electron/SKILL.md\n\n---\nname: fe-cli-electron\ndescription: >\n  Scaffold an Electron desktop application. For macOS/Windows desktop apps using React/Vue.\n  Features auto-update (electron-updater), IPC communication, system tray, native menus.\n  For lighter desktop apps, consider Tauri (fe-cli-tauri) instead.\n  Triggered as a sub-skill of fe-cli when user wants Electron/桌面端 project.\n---\n\n# fe-cli-electron — Electron Desktop App Scaffolding\n\n## Workflow\n\n### Step 1: Gather Options\n\n1. **Framework**: React 19 / Vue 3\n2. **Styling**: Tailwind CSS / Ant Design / MUI / 纯 SCSS\n3. **CSS Preprocessor**: Sass / Less\n4. **State Management**: Zustand / Redux Toolkit / None\n5. **Router**: React Router / Vue Router\n6. **i18n**: Yes / No\n7. **Auto Update**: Yes (default) / No\n8. **System Tray**: Yes / No\n9. **Testing**: Vitest / None\n10. **Pre-commit**: Yes / No\n11. **Project name**: string\n\n### Step 2: Scaffold\n\n```\npnpm create vite <project-name> --template react-ts\ncd <project-name>\npnpm add -D electron electron-builder concurrently wait-on\npnpm add electron-updater electron-store  # if auto-update selected\n```\n\n### Step 3: Project Structure\n\n```\nproject-name/\n├── electron/\n│   ├── main.ts              # Main process entry\n│   ├── preload.ts            # Preload script (contextBridge)\n│   └── updater.ts            # Auto-update logic (if selected)\n├── src/                      # Renderer process (React/Vue)\n│   ├── ...                   # Same as Web SPA structure\n│   └── electron.d.ts         # IPC type declarations\n├── package.json\n├── vite.config.ts\n├── electron-builder.yml      # Build config\n└── resources/                # App icons\n```\n\n### Step 4: Key Templates\n\n**`electron/main.ts`:**\n```typescript\nimport { app, BrowserWindow, ipcMain, Tray, Menu, nativeImage } from 'electron';\nimport path from 'path';\nimport { setupUpdater } from './updater';  // if auto-update\n\nlet mainWindow: BrowserWindow | null = null;\nlet tray: Tray | null = null;\n\nconst isDev = !app.isPackaged;\n\nfunction createWindow() {\n  mainWindow = new BrowserWindow({\n    width: 1200,\n    height: 800,\n    minWidth: 800,\n    minHeight: 600,\n    webPreferences: {\n      preload: path.join(__dirname, 'preload.js'),\n      contextIsolation: true,\n      nodeIntegration: false,\n    },\n    titleBarStyle: 'hiddenInset',  // macOS native title bar\n  });\n\n  if (isDev) {\n    mainWindow.loadURL('http://localhost:5173');\n    mainWindow.webContents.openDevTools();\n  } else {\n    mainWindow.loadFile(path.join(__dirname, '../dist/index.html'));\n  }\n\n  // System tray (if selected)\n  if (process.env.NODE_ENV !== 'linux') {\n    const icon = nativeImage.createFromPath(path.join(__dirname, '../resources/icon.png'));\n    tray = new Tray(icon.resize({ width: 16, height: 16 }));\n    tray.setToolTip('Your App');\n    tray.setContextMenu(Menu.buildFromTemplate([\n      { label: 'Show', click: () => mainWindow?.show() },\n      { type: 'separator' },\n      { label: 'Quit', click: () => app.quit() },\n    ]));\n  }\n}\n\n// IPC handlers\nipcMain.handle('get-app-version', () => app.getVersion());\nipcMain.handle('get-platform', () => process.platform);\n\napp.whenReady().then(() => {\n  createWindow();\n  if (process.env.AUTO_UPDATE !== 'false') setupUpdater(mainWindow!);\n  app.on('activate', () => { if (!mainWindow) createWindow(); });\n});\n\napp.on('window-all-closed', () => { if (process.platform !== 'darwin') app.quit(); });\n```\n\n**`electron/preload.ts`:**\n```typescript\nimport { contextBridge, ipcRenderer } from 'electron';\n\ncontextBridge.exposeInMainWorld('electronAPI', {\n  getAppVersion: () => ipcRenderer.invoke('get-app-version'),\n  getPlatform: () => ipcRenderer.invoke('get-platform'),\n  onUpdateAvailable: (cb: (info: unknown) => void) => ipcRenderer.on('update-available', (_, info) => cb(info)),\n  onUpdateDownloaded: (cb: () => void) => ipcRenderer.on('update-downloaded', () => cb()),\n  installUpdate: () => ipcRenderer.send('install-update'),\n  minimize: () => ipcRenderer.send('window-minimize'),\n  maximize: () => ipcRenderer.send('window-maximize'),\n  close: () => ipcRenderer.send('window-close'),\n});\n```\n\n**`electron/updater.ts`** (if auto-update):\n```typescript\nimport { autoUpdater } from 'electron-updater';\nimport { BrowserWindow } from 'electron';\n\nexport function setupUpdater(mainWindow: BrowserWindow) {\n  autoUpdater.autoDownload = false;\n  autoUpdater.autoInstallOnAppQuit = true;\n\n  autoUpdater.on('update-available', (info) => {\n    mainWindow.webContents.send('update-available', info);\n  });\n\n  autoUpdater.on('update-downloaded', () => {\n    mainWindow.webContents.send('update-downloaded');\n  });\n\n  autoUpdater.on('error', (err) => {\n    console.error('Update error:', err);\n  });\n\n  // Check for updates every 4 hours\n  autoUpdater.checkForUpdatesAndNotify();\n  setInterval(() => autoUpdater.checkForUpdatesAndNotify(), 4 * 60 * 60 * 1000);\n}\n```\n\n**`src/electron.d.ts`:**\n```typescript\nexport {};\n\ndeclare global {\n  interface Window {\n    electronAPI: {\n      getAppVersion: () => Promise<string>;\n      getPlatform: () => Promise<string>;\n      onUpdateAvailable: (cb: (info: unknown) => void) => void;\n      onUpdateDownloaded: (cb: () => void) => void;\n      installUpdate: () => void;\n      minimize: () => void;\n      maximize: () => void;\n      close: () => void;\n    };\n  }\n}\n```\n\n**`electron-builder.yml`:**\n```yaml\nappId: com.example.app\nproductName: Your App\ndirectories:\n  output: release\nfiles:\n  - dist/**/*\n  - electron/**/*\n  - package.json\nmac:\n  target:\n    - dmg\n    - zip\n  artifactName: ${productName}-${version}-mac-${arch}.${ext}\n  hardenedRuntime: true\n  gatekeeperAssess: false\n  entitlements: build/entitlements.mac.plist\n  entitlementsInherit: build/entitlements.mac.plist\nwin:\n  target:\n    - nsis\n  artifactName: ${productName}-${version}-win-${arch}.${ext}\nnsis:\n  oneClick: false\n  allowToChangeInstallationDirectory: true\npublish:\n  - provider: generic\n    url: https://update.example.com\n```\n\n**`package.json` scripts:**\n```json\n{\n  \"main\": \"electron/main.js\",\n  \"scripts\": {\n    \"dev\": \"concurrently \\\"vite\\\" \\\"wait-on http://localhost:5173 && electron .\\\"\",\n    \"dev:renderer\": \"vite\",\n    \"dev:electron\": \"electron .\",\n    \"build\": \"vite build && tsc -p tsconfig.electron.json\",\n    \"build:prod\": \"vite build && tsc -p tsconfig.electron.json && electron-builder\",\n    \"preview\": \"vite preview\",\n    \"pack\": \"electron-builder --dir\",\n    \"dist\": \"electron-builder\"\n  }\n}\n```\n\n### Step 5: Vite Config\n\n```typescript\nimport { defineConfig } from 'vite';\nimport react from '@vitejs/plugin-react';\nimport path from 'path';\n\nexport default defineConfig({\n  plugins: [react()],\n  resolve: { alias: { '@': path.resolve(__dirname, 'src') } },\n  base: './',  // Electron loads from file:// so relative paths\n  build: { outDir: 'dist' },\n  server: { port: 5173 },\n});\n```\n\n### Step 6: Shared Layer + Infrastructure + Final\n\nRead the following reference files and generate all shared code:\n1. `../references/shared-base.md` — services, utils, styles, types, env files\n2. `../references/shared-config.md` — vite, tsconfig, eslint, prettier configs\n3. `../references/shared-infrastructure.md` — store, theme, i18n, hooks, layouts, auth guard, config/constants\n\nOnly generate env files for VITE_ variables (no API proxy needed — Electron uses direct connection).\n\n**Conditional generation** (only if user selected the corresponding option):\n- Selected Zustand/Redux Toolkit/Pinia → generate `src/store/`\n- Selected i18n → generate `src/locales/`\n- **Always generate**: `src/hooks/`, `src/components/AppProvider.tsx`, `src/components/AuthGuard.tsx`, `src/components/GlobalLoading.tsx`, `src/config/`, `src/theme/`\n\nAfter setup:\n```bash\ncd <project-name>\npnpm install\npnpm dev\n```\n\nAnnounce: project with Electron + auto-update config, dev starts both Vite and Electron.\n\nFile v3.1.0:h5/SKILL.md\n\n---\nname: fe-cli-h5\ndescription: >\n  Scaffold a Mobile H5 frontend project. For mobile web pages, WeChat H5 pages,\n  and mobile-first web apps. Features vw/vh responsive, touch gestures, safe area,\n  and WeChat JSSDK integration. Triggered as a sub-skill of fe-cli.\n---\n\n# fe-cli-h5 — Mobile H5 Scaffolding\n\n## Workflow\n\n### Step 1: Gather Options\n\n1. **Framework**: React 19 / Vue 3\n2. **Styling**: Tailwind CSS (default, mobile-first) / Ant Design Mobile / 纯 SCSS\n3. **CSS Preprocessor**: Sass (default) / Less\n4. **State Management**: Zustand / Redux Toolkit / Pinia / None\n5. **Router**: React Router / Vue Router\n6. **i18n**: react-i18next / vue-i18n / None\n7. **WeChat JSSDK**: Yes / No (if yes, add `weixin-js-sdk`)\n8. **Testing**: Vitest / None\n9. **Pre-commit**: husky + lint-staged? (default: No)\n10. **Project name**: string\n\n### Step 2: Scaffold\n\n```\npnpm create vite <project-name> --template react-ts\n```\n\n### Step 3: Dependencies\n\n```\npnpm add react-router-dom\npnpm add -D postcss-px-to-viewport-8-plugin amfe-flexible\npnpm add -D sass\n```\n\nIf WeChat JSSDK: `pnpm add weixin-js-sdk`\nIf Vue: replace react-router-dom with vue-router\n\n### Step 4: H5-Specific Configuration\n\n**`postcss.config.cjs`:**\n```javascript\nmodule.exports = {\n  plugins: {\n    'postcss-px-to-viewport-8-plugin': {\n      viewportWidth: 375,  // Design width\n      unitPrecision: 5,\n      viewportUnit: 'vw',\n      selectorBlackList: ['.ignore-vw'],\n      minPixelValue: 1,\n      mediaQuery: false,\n    },\n  },\n};\n```\n\n**`vite.config.ts`** — add mobile-specific config:\n```typescript\nimport { defineConfig } from 'vite';\nimport react from '@vitejs/plugin-react';\nimport path from 'path';\n\nexport default defineConfig(({ mode }) => ({\n  plugins: [react()],\n  resolve: { alias: { '@': path.resolve(__dirname, 'src') } },\n  css: {\n    postcss: './postcss.config.cjs',\n    preprocessorOptions: {\n      scss: { additionalData: '@use \"@/styles/variables\" as *;' },\n    },\n  },\n  server: {\n    port: 5173,\n    host: '0.0.0.0',  // Allow LAN access for mobile testing\n    proxy: { '/api': { target: 'http://localhost:3000', changeOrigin: true } },\n  },\n}));\n```\n\n**`index.html`** — add viewport meta:\n```html\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover\">\n```\n\n### Step 5: H5-Specific Files\n\n```\nsrc/\n├── components/\n│   ├── ErrorBoundary.tsx\n│   ├── PageLoading.tsx\n│   ├── EmptyState.tsx\n│   ├── ErrorState.tsx\n│   └── SafeArea.tsx            # Safe area padding for notch phones\n├── pages/\n│   ├── Home/\n│   │   └── index.tsx\n│   ├── Detail/\n│   │   └── index.tsx\n│   └── Mine/\n│       └── index.tsx\n├── layouts/\n│   └── MobileLayout.tsx        # Bottom tab bar\n├── hooks/\n│   ├── useRequest.ts\n│   └── useWechat.ts            # WeChat JSSDK hook (if selected)\n├── App.tsx\n└── main.tsx\n```\n\n**`components/SafeArea.tsx`:**\n> **Note:** Inline styles below are for quick scaffolding. **Production environments should replace them with SCSS modules**.\n```tsx\n// Safe area padding for iPhone notch / Android punch-hole\nexport default function SafeArea({ children }: { children: React.ReactNode }) {\n  return (\n    <div style={{\n      paddingTop: 'env(safe-area-inset-top)',\n      paddingBottom: 'env(safe-area-inset-bottom)',\n      paddingLeft: 'env(safe-area-inset-left)',\n      paddingRight: 'env(safe-area-inset-right)',\n    }}>{children}</div>\n  );\n}\n```\n\n**`layouts/MobileLayout.tsx`:**\n> **Note:** Inline styles below are for quick scaffolding. **Production environments should replace them with SCSS modules** (e.g., `import styles from './MobileLayout.module.scss'`).\n```tsx\nimport { Outlet, useNavigate, useLocation } from 'react-router-dom';\nimport { HomeOutlined, AppstoreOutlined, UserOutlined } from '@ant-design/icons';\nimport styles from './MobileLayout.module.scss';  // or Tailwind classes\n\nconst tabs = [\n  { key: '/home', icon: <HomeOutlined />, label: '首页' },\n  { key: '/category', icon: <AppstoreOutlined />, label: '分类' },\n  { key: '/mine', icon: <UserOutlined />, label: '我的' },\n];\n\nexport default function MobileLayout() {\n  const navigate = useNavigate();\n  const location = useLocation();\n\n  return (\n    <div style={{ display: 'flex', flexDirection: 'column', minHeight: '100vh' }}>\n      <main style={{ flex: 1, overflow: 'auto' }}>\n        <Outlet />\n      </main>\n      <nav style={{\n        display: 'flex', borderTop: '1px solid #eee', paddingBottom: 'env(safe-area-inset-bottom)',\n        background: '#fff', height: 50,\n      }}>\n        {tabs.map((tab) => (\n          <div key={tab.key} onClick={() => navigate(tab.key)}\n            style={{ flex: 1, display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center',\n              color: location.pathname === tab.key ? '#1677ff' : '#999', fontSize: 12 }}>\n            {tab.icon}\n            <span>{tab.label}</span>\n          </div>\n        ))}\n      </nav>\n    </div>\n  );\n}\n```\n\n**`hooks/useWechat.ts`** (if WeChat selected):\n```typescript\nimport { useEffect, useState } from 'react';\nimport wx from 'weixin-js-sdk';\n\ninterface WxConfig {\n  appId: string;\n  timestamp: number;\n  nonceStr: string;\n  signature: string;\n}\n\nexport function useWechat(config?: WxConfig) {\n  const [ready, setReady] = useState(false);\n\n  useEffect(() => {\n    if (!config) return;\n    wx.config({ debug: false, ...config, jsApiList: ['updateAppMessageShareData', 'updateTimelineShareData'] });\n    wx.ready(() => setReady(true));\n  }, [config]);\n\n  const share = (title: string, desc: string, link: string, imgUrl: string) => {\n    if (!ready) return;\n    wx.updateAppMessageShareData({ title, desc, link, imgUrl, success: () => {} });\n    wx.updateTimelineShareData({ title, link, imgUrl, success: () => {} });\n  };\n\n  return { ready, share };\n}\n```\n\n### Step 6: Global Styles\n\nIn `global.scss`, add mobile-specific styles:\n```scss\nhtml, body, #root {\n  height: 100%;\n  overflow-x: hidden;\n}\n\n// Safe area\nbody {\n  padding: env(safe-area-inset-top) env(safe-area-inset-right) env(safe-area-inset-bottom) env(safe-area-inset-left);\n}\n\n// 1px border on retina\n.hairline {\n  position: relative;\n  &::after {\n    content: '';\n    position: absolute;\n    left: 0; bottom: 0;\n    width: 100%; height: 1px;\n    background: #e5e5e5;\n    transform: scaleY(0.5);\n  }\n}\n\n// No scroll body when modal open\nbody.no-scroll { overflow: hidden; }\n```\n\n### Step 7: Shared Layer + Infrastructure + Final\n\nRead the following reference files and generate all shared code:\n1. `../\n\nArchive v3.0.0: 14 files, 52036 bytes\n\nFiles: admin/SKILL.md (18728b), astro/SKILL.md (6971b), electron/SKILL.md (7220b), h5/SKILL.md (6663b), miniapp/SKILL.md (9560b), references/ai-project-md.md (5437b), references/shared-base.md (25813b), references/shared-config.md (5072b), rn/SKILL.md (7722b), SKILL.md (11863b), ssr/SKILL.md (10012b), tauri/SKILL.md (6597b), web/SKILL.md (8398b), _meta.json (125b)\n\nArchive v2.0.0: 11 files, 41635 bytes\n\nFiles: admin/SKILL.md (12069b), electron/SKILL.md (7153b), h5/SKILL.md (6353b), miniapp/SKILL.md (9504b), references/ai-project-md.md (5437b), references/shared-base.md (25763b), references/shared-config.md (4362b), SKILL.md (17190b), ssr/SKILL.md (8743b), web/SKILL.md (6989b), _meta.json (125b)\n\nArchive v0.3.0: 11 files, 41635 bytes\n\nFiles: admin/SKILL.md (12069b), electron/SKILL.md (7153b), h5/SKILL.md (6353b), miniapp/SKILL.md (9504b), references/ai-project-md.md (5437b), references/shared-base.md (25763b), references/shared-config.md (4362b), SKILL.md (17190b), ssr/SKILL.md (8743b), web/SKILL.md (6989b), _meta.json (125b)\n\nArchive v1.2.1: 11 files, 41635 bytes\n\nFiles: admin/SKILL.md (12069b), electron/SKILL.md (7153b), h5/SKILL.md (6353b), miniapp/SKILL.md (9504b), references/ai-project-md.md (5437b), references/shared-base.md (25763b), references/shared-config.md (4362b), SKILL.md (17190b), ssr/SKILL.md (8743b), web/SKILL.md (6989b), _meta.json (125b)\n\nArchive v1.2.0: 11 files, 41635 bytes\n\nFiles: admin/SKILL.md (12069b), electron/SKILL.md (7153b), h5/SKILL.md (6353b), miniapp/SKILL.md (9504b), references/ai-project-md.md (5437b), references/shared-base.md (25763b), references/shared-config.md (4362b), SKILL.md (17190b), ssr/SKILL.md (8743b), web/SKILL.md (6989b), _meta.json (125b)\n\nArchive v1.1.0: 11 files, 41533 bytes\n\nFiles: admin/SKILL.md (12069b), electron/SKILL.md (7153b), h5/SKILL.md (6353b), miniapp/SKILL.md (9484b), references/ai-project-md.md (5437b), references/shared-base.md (25710b), references/shared-config.md (4362b), SKILL.md (17190b), ssr/SKILL.md (8743b), web/SKILL.md (6837b), _meta.json (125b)\n\nArchive v0.1.99: 11 files, 39495 bytes\n\nFiles: admin/SKILL.md (12069b), electron/SKILL.md (7153b), h5/SKILL.md (6353b), miniapp/SKILL.md (9484b), references/ai-project-md.md (5437b), references/shared-base.md (25710b), references/shared-config.md (4362b), SKILL.md (10121b), ssr/SKILL.md (8743b), web/SKILL.md (6837b), _meta.json (126b)\n\nArchive v0.1.91: 11 files, 39480 bytes\n\nFiles: admin/SKILL.md (12069b), electron/SKILL.md (7153b), h5/SKILL.md (6353b), miniapp/SKILL.md (9484b), references/ai-project-md.md (5437b), references/shared-base.md (25710b), references/shared-config.md (4362b), SKILL.md (10104b), ssr/SKILL.md (8743b), web/SKILL.md (6837b), _meta.json (126b)","readmeExcerpt":"Skill: Fe Cli Owner: z-zihan Summary: 前端项目脚手架 CLI。支持 React/Vue/Next.js/Nuxt.js/Astro/React Native + shadcn/Ant Design/MUI/Tailwind + Zustand/Redux + i18n，初始化标准化前端项目。支持 9 种项目类型：Web SPA、后台管理、移动 H5、... Tags: latest:3.1.1 Version history: v3.1.1 | 2026-05-23T19:27:39.296Z | user Auto-publish from commit 265fd1e38f8cebfbc499cb8d2ae54f2fce9cf2e4 v3.1.0 | 2026-05-23T19:12:37.955Z | user Auto-publish from commit deb04d5ca6ff","codeSnippets":[],"executableExamples":[{"language":"text","snippet":"bun create vite <project-name> --template react-ts"},{"language":"text","snippet":"bun add antd @ant-design/icons @ant-design/v5-patch-for-react-19 react-router-dom dayjs"},{"language":"text","snippet":"bun add react-router-dom dayjs lucide-react && npx shadcn@latest init"},{"language":"text","snippet":"bun add element-plus @element-plus/icons-vue vue-router dayjs"},{"language":"text","snippet":"bun add vue-router dayjs lucide-vue-next && npx shadcn-vue@latest init"},{"language":"text","snippet":"src/\n├── components/                 # Global components (from shared infrastructure)\n│   ├── AppProvider.tsx\n│   ├── AuthGuard.tsx\n│   ├── GlobalLoading.tsx\n│   ├── ErrorBoundary.tsx\n│   ├── PageLoading.tsx\n│   ├── EmptyState.tsx\n│   └── ErrorState.tsx\n├── layouts/\n│   └── AdminLayout.tsx         # Sidebar + Header + Content layout (admin-specific)\n├── pages/\n│   ├── Login/\n│   │   └── index.tsx           # Login page with form\n│   ├── Dashboard/\n│   │   └── index.tsx           # Dashboard with stats cards\n│   ├── List/\n│   │   └── index.tsx           # Table list page template\n│   ├── Form/\n│   │   └── index.tsx           # Form page template\n│   └── NotFound.tsx\n├── store/                      # State management (from shared infrastructure)\n│   └── useUserStore.ts         # Auth state (token, user info, login/logout)\n├── config/\n│   └── routes.tsx              # Route config with permission guard (from shared infrastructure)\n├── hooks/                      # Common hooks (from shared infrastructure)\n├── theme/                      # Theme system (from shared infrastructure)\n├── App.tsx                     # Router + AuthProvider\n└── main.tsx"}],"parameters":null,"dependencies":[],"permissions":[],"extractedFiles":[{"path":"admin/SKILL.md","content":"---\nname: fe-cli-admin\ndescription: >\n  Scaffold an Admin Dashboard frontend project. For data management backends, CRUD systems,\n  and internal tools. Features permission-based routing, login page, Ant Design Pro layout.\n  Triggered as a sub-skill of fe-cli when user wants an admin/后台管理 project type.\n---\n\n# fe-cli-admin — Admin Dashboard Scaffolding\n\n## Workflow\n\n### Step 1: Gather Options\n\nSkip questions already answered in user's request.\n\n1. **Framework**: React 19 / Vue 3 (React recommended for admin, Ant Design is React-only; Vue admin 推荐 Element Plus)\n2. **Styling/UI**: Ant Design (default React) / shadcn/ui + Lucide (React) / Element Plus (default Vue) / shadcn-vue + Vue Bits (Vue) / Tailwind CSS / MUI\n3. **CSS Preprocessor**: Sass / Less (default: Sass)\n4. **State Management**: Zustand / Redux Toolkit / Pinia (Vue) / None\n5. **Charts**: Recharts (lightweight) / ECharts (heavy) / None\n6. **i18n**: react-i18next / vue-i18n / None\n7. **Testing**: Vitest / None\n8. **Pre-commit hooks**: husky + lint-staged + commitlint? (default: No)\n9. **Project name**: string (required)\n\n### Step 2: Scaffold Project\n\n```\nbun create vite <project-name> --template react-ts\n```\n\nFor Vue: `bun create vite <project-name> --template vue-ts`\n\n(Use `pnpm create vite` if bun unavailable)\n\n### Step 3: Install Dependencies\n\n**React (Ant Design):**\n```\nbun add antd @ant-design/icons @ant-design/v5-patch-for-react-19 react-router-dom dayjs\n```\n\n**React (shadcn/ui):**\n```\nbun add react-router-dom dayjs lucide-react && npx shadcn@latest init\n```\n\n**Vue (Element Plus):**\n```\nbun add element-plus @element-plus/icons-vue vue-router dayjs\n```\n\n**Vue (shadcn-vue):**\n```\nbun add vue-router dayjs lucide-vue-next && npx shadcn-vue@latest init\n```\n\nPlus extras based on selections (zustand, pinia, i18n, charts, etc.)\n\n**Charts:**\n- Recharts: `bun add recharts`\n- ECharts: `bun add echarts echarts-for-react` (React) / `bun add echarts vue-echarts` (Vue)\n\nFor date handling: `pnpm add dayjs` (recommended; moment is deprecated and not recommended).\n\n### Step 4: Generate Admin-Specific Files\n\n```\nsrc/\n├── components/                 # Global components (from shared infrastructure)\n│   ├── AppProvider.tsx\n│   ├── AuthGuard.tsx\n│   ├── GlobalLoading.tsx\n│   ├── ErrorBoundary.tsx\n│   ├── PageLoading.tsx\n│   ├── EmptyState.tsx\n│   └── ErrorState.tsx\n├── layouts/\n│   └── AdminLayout.tsx         # Sidebar + Header + Content layout (admin-specific)\n├── pages/\n│   ├── Login/\n│   │   └── index.tsx           # Login page with form\n│   ├── Dashboard/\n│   │   └── index.tsx           # Dashboard with stats cards\n│   ├── List/\n│   │   └── index.tsx           # Table list page template\n│   ├── Form/\n│   │   └── index.tsx           # Form page template\n│   └── NotFound.tsx\n├── store/                      # State management (from shared infrastructure)\n│   └── useUserStore.ts         # Auth state (token, user info, login/logout)\n├── config/\n│   └── routes.tsx              # Route config with permission g"},{"path":"astro/SKILL.md","content":"---\nname: fe-cli-astro\ndescription: >\n  Scaffold a static site / content site project. Supports Astro, Gatsby (React), and VitePress (Vue).\n  For blogs, documentation sites, portfolios, landing pages with SSG/SSR.\n  Triggered as a sub-skill of fe-cli when user wants Astro/Gatsby/建站/博客/文档站/VitePress project.\n---\n\n# fe-cli-astro — Static Site Scaffolding\n\n## Workflow\n\n### Step 1: Gather Options\n\n1. **Framework**: Astro / Gatsby (React) / VitePress (Vue)\n2. **UI Integration** (Astro only): React / Vue / Svelte / Preact / None (Astro components only)\n3. **Styling**: Tailwind CSS / shadcn/ui (Astro+React) / 纯 SCSS\n4. **Content**: MDX (Astro/Gatsby) / Markdown (all) / CMS integration (Contentful/Sanity) / None\n5. **i18n**: Yes / No\n6. **Deployment**: Vercel (default) / Netlify / Cloudflare Pages\n7. **Project name**: string\n\n**Quick guide:**\n- **Blog / portfolio / content site** → Astro (islands architecture, partial hydration, fast)\n- **React-heavy content site** → Gatsby (GraphQL data layer, rich plugin ecosystem)\n- **Vue documentation site** → VitePress (Vue-powered static site, markdown-first)\n\n### Step 2: Scaffold\n\n**Astro:**\n```\nbun create astro@latest <project-name>\n```\nInteractive prompt: template (blog/docs/portfolio/empty), TypeScript, install dependencies.\n\nOr with flags:\n```\nbunx create-astro <project-name> --template blog --typescript strict --install\n```\n\n**Gatsby:**\n```\nbunx gatsby new <project-name>\n```\n\n**VitePress:**\n```\nmkdir <project-name> && cd <project-name>\nbun init\nbun add -D vitepress vue\nmkdir docs && echo '# Hello VitePress' > docs/index.md\n```\n\n### Step 3: Install Additional Dependencies\n\n| Selection | Packages |\n|---|---|\n| **Astro** | |\n| Tailwind CSS | `@astrojs/tailwind tailwindcss` (Astro 4+) or `tailwindcss @tailwindcss/vite` |\n| React integration | `@astrojs/react react react-dom` |\n| Vue integration | `@astrojs/vue vue` |\n| MDX | `@astrojs/mdx` |\n| i18n | `astro-i18next` / manual routing |\n| shadcn/ui | `npx shadcn@latest init` (if React integration) + `lucide-react` |\n| **Gatsby** | |\n| Tailwind CSS | `tailwindcss postcss autoprefixer` + `gatsby-plugin-postcss` |\n| MDX | `gatsby-plugin-mdx @mdx-js/react` |\n| CMS | `gatsby-source-contentful` / `gatsby-source-sanity` |\n| **VitePress** | |\n| Tailwind CSS | `tailwindcss @tailwindcss/vite` |\n| Vue components | `vue` (already included) |\n| Search | `vitepress-plugin-search` / Algolia |\n\n### Step 4: Project Structure\n\n**Astro:**\n```\nproject-name/\n├── src/\n│   ├── components/\n│   │   ├── BaseHead.astro       # <head> meta tags, fonts, styles\n│   │   ├── Header.astro         # Navigation header\n│   │   ├── Footer.astro         # Site footer\n│   │   └── Card.astro           # Content card component\n│   ├── content/\n│   │   ├── config.ts            # Content collection schema\n│   │   └── blog/                # Blog posts (MDX/Markdown)\n│   │       └── first-post.mdx\n│   ├── layouts/\n│   │   ├── BaseLayout.astro     # Page layout wrapper\n│   │   └── BlogPost.astro       # Blog p"},{"path":"electron/SKILL.md","content":"---\nname: fe-cli-electron\ndescription: >\n  Scaffold an Electron desktop application. For macOS/Windows desktop apps using React/Vue.\n  Features auto-update (electron-updater), IPC communication, system tray, native menus.\n  For lighter desktop apps, consider Tauri (fe-cli-tauri) instead.\n  Triggered as a sub-skill of fe-cli when user wants Electron/桌面端 project.\n---\n\n# fe-cli-electron — Electron Desktop App Scaffolding\n\n## Workflow\n\n### Step 1: Gather Options\n\n1. **Framework**: React 19 / Vue 3\n2. **Styling**: Tailwind CSS / Ant Design / MUI / 纯 SCSS\n3. **CSS Preprocessor**: Sass / Less\n4. **State Management**: Zustand / Redux Toolkit / None\n5. **Router**: React Router / Vue Router\n6. **i18n**: Yes / No\n7. **Auto Update**: Yes (default) / No\n8. **System Tray**: Yes / No\n9. **Testing**: Vitest / None\n10. **Pre-commit**: Yes / No\n11. **Project name**: string\n\n### Step 2: Scaffold\n\n```\npnpm create vite <project-name> --template react-ts\ncd <project-name>\npnpm add -D electron electron-builder concurrently wait-on\npnpm add electron-updater electron-store  # if auto-update selected\n```\n\n### Step 3: Project Structure\n\n```\nproject-name/\n├── electron/\n│   ├── main.ts              # Main process entry\n│   ├── preload.ts            # Preload script (contextBridge)\n│   └── updater.ts            # Auto-update logic (if selected)\n├── src/                      # Renderer process (React/Vue)\n│   ├── components/           # Global components (from shared infrastructure)\n│   ├── pages/                # Page components\n│   ├── hooks/                # Common hooks (from shared infrastructure)\n│   ├── store/                # State management (from shared infrastructure)\n│   ├── theme/                # Theme system (from shared infrastructure)\n│   ├── config/               # App constants + routes (from shared infrastructure)\n│   ├── services/             # API layer (from shared base)\n│   ├── utils/                # Utilities (from shared base)\n│   ├── styles/               # Global styles (from shared base)\n│   ├── electron.d.ts         # IPC type declarations\n│   ├── App.tsx\n│   └── main.tsx\n├── package.json\n├── vite.config.ts\n├── electron-builder.yml      # Build config\n└── resources/                # App icons\n```\n\n### Step 4: Key Templates\n\n**`electron/main.ts`:**\n```typescript\nimport { app, BrowserWindow, ipcMain, Tray, Menu, nativeImage } from 'electron';\nimport path from 'path';\nimport { setupUpdater } from './updater';  // if auto-update\n\nlet mainWindow: BrowserWindow | null = null;\nlet tray: Tray | null = null;\n\nconst isDev = !app.isPackaged;\n\nfunction createWindow() {\n  mainWindow = new BrowserWindow({\n    width: 1200,\n    height: 800,\n    minWidth: 800,\n    minHeight: 600,\n    webPreferences: {\n      preload: path.join(__dirname, 'preload.js'),\n      contextIsolation: true,\n      nodeIntegration: false,\n    },\n    titleBarStyle: 'hiddenInset',  // macOS native title bar\n  });\n\n  if (isDev) {\n    mainWindow.loadURL('http://localhost:5173');\n    mainWindow.w"},{"path":"h5/SKILL.md","content":"---\nname: fe-cli-h5\ndescription: >\n  Scaffold a Mobile H5 frontend project. For mobile web pages, WeChat H5 pages,\n  and mobile-first web apps. Features vw/vh responsive, touch gestures, safe area,\n  and WeChat JSSDK integration. Triggered as a sub-skill of fe-cli.\n---\n\n# fe-cli-h5 — Mobile H5 Scaffolding\n\n## Workflow\n\n### Step 1: Gather Options\n\n1. **Framework**: React 19 / Vue 3\n2. **Styling**: Tailwind CSS (default, mobile-first) / Ant Design Mobile / 纯 SCSS\n3. **CSS Preprocessor**: Sass (default) / Less\n4. **State Management**: Zustand / Redux Toolkit / Pinia / None\n5. **Router**: React Router / Vue Router\n6. **i18n**: react-i18next / vue-i18n / None\n7. **WeChat JSSDK**: Yes / No (if yes, add `weixin-js-sdk`)\n8. **Testing**: Vitest / None\n9. **Pre-commit**: husky + lint-staged? (default: No)\n10. **Project name**: string\n\n### Step 2: Scaffold\n\n```\npnpm create vite <project-name> --template react-ts\n```\n\n### Step 3: Dependencies\n\n```\npnpm add react-router-dom\npnpm add -D postcss-px-to-viewport-8-plugin amfe-flexible\npnpm add -D sass\n```\n\nIf WeChat JSSDK: `pnpm add weixin-js-sdk`\nIf Vue: replace react-router-dom with vue-router\n\n### Step 4: H5-Specific Configuration\n\n**`postcss.config.cjs`:**\n```javascript\nmodule.exports = {\n  plugins: {\n    'postcss-px-to-viewport-8-plugin': {\n      viewportWidth: 375,  // Design width\n      unitPrecision: 5,\n      viewportUnit: 'vw',\n      selectorBlackList: ['.ignore-vw'],\n      minPixelValue: 1,\n      mediaQuery: false,\n    },\n  },\n};\n```\n\n**`vite.config.ts`** — add mobile-specific config:\n```typescript\nimport { defineConfig } from 'vite';\nimport react from '@vitejs/plugin-react';\nimport path from 'path';\n\nexport default defineConfig(({ mode }) => ({\n  plugins: [react()],\n  resolve: { alias: { '@': path.resolve(__dirname, 'src') } },\n  css: {\n    postcss: './postcss.config.cjs',\n    preprocessorOptions: {\n      scss: { additionalData: '@use \"@/styles/variables\" as *;' },\n    },\n  },\n  server: {\n    port: 5173,\n    host: '0.0.0.0',  // Allow LAN access for mobile testing\n    proxy: { '/api': { target: 'http://localhost:3000', changeOrigin: true } },\n  },\n}));\n```\n\n**`index.html`** — add viewport meta:\n```html\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover\">\n```\n\n### Step 5: H5-Specific Files\n\n```\nsrc/\n├── components/\n│   ├── ErrorBoundary.tsx\n│   ├── PageLoading.tsx\n│   ├── EmptyState.tsx\n│   ├── ErrorState.tsx\n│   └── SafeArea.tsx            # Safe area padding for notch phones\n├── pages/\n│   ├── Home/\n│   │   └── index.tsx\n│   ├── Detail/\n│   │   └── index.tsx\n│   └── Mine/\n│       └── index.tsx\n├── layouts/\n│   └── MobileLayout.tsx        # Bottom tab bar\n├── hooks/                      # Common hooks (generated by shared layer)\n│   ├── useRequest.ts           # Data fetching (generated by shared layer)\n│   └── useWechat.ts            # WeChat JSSDK hook (if selected)\n├── store/                      # State management (generated"},{"path":"miniapp/SKILL.md","content":"---\nname: fe-cli-miniapp\ndescription: >\n  Scaffold a WeChat Mini Program project using Taro or uni-app framework.\n  Features subpackage configuration, native API wrappers, subscription messages,\n  and multi-platform build (WeChat, Alipay, etc.). Triggered as a sub-skill of fe-cli\n  when user wants 小程序/微信小程序/Mini Program project.\n---\n\n# fe-cli-miniapp — Mini Program Scaffolding\n\n## Workflow\n\n### Step 1: Gather Options\n\n1. **Framework**: Taro (React) / Taro (Vue) / uni-app (Vue)\n2. **Styling**: Tailwind (via weapp-tailwindcss) / SCSS / Less\n3. **CSS Preprocessor**: Sass / Less\n4. **State Management**: Zustand / Redux Toolkit / Pinia (Vue) / None\n5. **i18n**: Yes (Taro: @tarojs/plugin-i18n) / No\n6. **Subpackages**: Yes (分包) / No\n7. **Target Platforms**: 微信 / 支付宝 / 字节 / 百度 / 全部\n8. **Testing**: Vitest / None\n9. **Pre-commit**: Yes / No\n10. **Project name**: string\n\n### Step 2: Scaffold\n\n**Taro (React):**\n```\npnpm create @tarojs/app <project-name> --template react-ts --css sass --compiler vite\n```\n\n**Taro (Vue):**\n```\npnpm create @tarojs/app <project-name> --template vue3-ts --css sass --compiler vite\n```\n\n**uni-app:**\n```\npnpm create uni-app <project-name> --template vue3-ts\n```\n\n### Step 3: Dependencies\n\n| Need | Packages |\n|---|---|\n| Zustand | `zustand` |\n| Redux | `@reduxjs/toolkit react-redux` |\n| Pinia (uni-app) | Pinia comes with uni-app template |\n| Taro i18n | `@tarojs/plugin-i18n` |\n| Tailwind | `weapp-tailwindcss` (Taro plugin) |\n| Icons | `@taroify/icons` (icon components for Taro) |\n\n### Step 4: Project Structure (Taro)\n\n```\nsrc/\n├── pages/\n│   ├── index/\n│   │   ├── index.tsx         # Home page\n│   │   ├── index.config.ts   # Page config (title, etc.)\n│   │   └── index.scss\n│   ├── detail/\n│   │   └── ...\n│   └── mine/\n│       └── ...\n├── components/\n│   ├── NavBar.tsx             # Custom navigation bar\n│   ├── EmptyState.tsx\n│   ├── ErrorState.tsx\n│   └── PageLoading.tsx\n├── services/\n│   ├── request.ts             # Taro.request wrapper\n│   └── api/\n│       └── index.ts\n├── utils/\n│   ├── index.ts               # General utils\n│   ├── storage.ts             # Taro Storage wrapper\n│   ├── format.ts\n│   └── validate.ts\n├── hooks/\n│   └── useRequest.ts\n├── stores/                    # State management\n├── styles/\n│   └── variables.scss\n├── app.config.ts              # Global app config (pages, window, subpackages)\n├── app.tsx                    # App entry\n└── app.scss                   # Global styles\n```\n\n### Step 5: Subpackage Config (if selected)\n\nIn `app.config.ts`:\n```typescript\nexport default defineAppConfig({\n  pages: ['pages/index/index', 'pages/mine/mine'],\n  subPackages: [\n    {\n      root: 'packageA',\n      name: 'packageA',\n      pages: ['pages/detail/detail', 'pages/list/list'],\n    },\n    {\n      root: 'packageB',\n      name: 'packageB',\n      pages: ['pages/settings/settings'],\n    },\n  ],\n  window: {\n    backgroundTextStyle: 'light',\n    navigationBarBackgroundColor: '#fff',\n    navigationBarTitleText: 'WeChat',\n    nav"}],"languages":[],"docsSourceLabel":"CLAWHUB","editorialOverview":"前端项目脚手架 CLI。支持 React/Vue/Next.js/Nuxt.js/Astro/React Native + shadcn/Ant Design/MUI/Tailwind + Zustand/Redux + i18n，初始化标准化前端项目。支持 9 种项目类型：Web SPA、后台管理、移动 H5、... Skill: Fe Cli Owner: z-zihan Summary: 前端项目脚手架 CLI。支持 React/Vue/Next.js/Nuxt.js/Astro/React Native + shadcn/Ant Design/MUI/Tailwind + Zustand/Redux + i18n，初始化标准化前端项目。支持 9 种项目类型：Web SPA、后台管理、移动 H5、... Tags: latest:3.1.1 Version history: v3.1.1 | 2026-05-23T19:27:39.296Z | user Auto-publish from commit 265fd1e38f8cebfbc499cb8d2ae54f2fce9cf2e4 v3.1.0 | 2026-05-23T19:12:37.955Z | user Auto-publish from commit deb04d5ca6ff","editorialQuality":{"score":100,"threshold":65,"status":"ready","wordCount":1277,"uniquenessScore":49,"reasons":[]}},"media":{"evidence":{"source":"no-media","verified":false,"confidence":"low","updatedAt":"2026-10-10T08:12:40.692Z","emptyReason":"No screenshots, media assets, or demo links are available."},"primaryImageUrl":null,"mediaAssetCount":0,"assets":[],"demoUrl":null},"ownerResources":{"evidence":{"source":"unclaimed","verified":false,"confidence":"low","updatedAt":"2026-10-10T08:12:40.692Z","emptyReason":"This page has not been claimed by the agent owner."},"hasCustomPage":false,"customPageUpdatedAt":null,"customLinks":[],"structuredLinks":{"docsUrl":null,"demoUrl":null,"supportUrl":null,"pricingUrl":null,"statusUrl":null},"customPage":null},"relatedAgents":{"evidence":{"source":"protocol-neighbors","verified":false,"confidence":"medium","updatedAt":"2026-10-10T10:43:17.987Z","emptyReason":null},"items":[{"id":"8ebccd8e-3863-4187-8355-c3f14e1f9edf","entityType":"agent","canonicalPath":"/agent/iofficeai-aionui","slug":"iofficeai-aionui","name":"AionUi","description":"Free, local, open-source 24/7 Cowork app and OpenClaw for Gemini CLI, Claude Code, Codex, OpenCode, Qwen Code, Goose CLI, Auggie, and more | 🌟 Star if you like it!","url":"https://github.com/iOfficeAI/AionUi","homepage":"https://www.aionui.com","source":"GITHUB_REPOS","protocols":["MCP","OPENCLAW"],"capabilities":[],"safetyScore":100,"overallRank":70,"updatedAt":"2026-10-09T19:11:12.944Z","createdAt":"2026-02-25T03:38:16.584Z","downloads":null},{"id":"b917f68a-ebff-438e-84f8-3f4b2494c0bc","entityType":"agent","canonicalPath":"/agent/activepieces-activepieces","slug":"activepieces-activepieces","name":"activepieces","description":"AI Agents & MCPs & AI Workflow Automation • (~400 MCP servers for AI agents) • AI Automation / AI Agent with MCPs • AI Workflows & AI Agents • MCPs for AI Agents","url":"https://github.com/activepieces/activepieces","homepage":"https://www.activepieces.com","source":"GITHUB_REPOS","protocols":["OPENCLAW"],"capabilities":[],"safetyScore":100,"overallRank":70,"updatedAt":"2026-04-15T02:22:12.426Z","createdAt":"2026-02-25T03:38:12.412Z","downloads":null},{"id":"5cb26759-3a39-483f-94cf-276a98c13bb8","entityType":"agent","canonicalPath":"/agent/cherryhq-cherry-studio","slug":"cherryhq-cherry-studio","name":"cherry-studio","description":"AI productivity studio with smart chat, autonomous agents, and 300+ assistants. Unified access to frontier LLMs","url":"https://github.com/CherryHQ/cherry-studio","homepage":"https://cherry-ai.com","source":"GITHUB_REPOS","protocols":["MCP","OPENCLAW"],"capabilities":[],"safetyScore":100,"overallRank":70,"updatedAt":"2026-04-11T14:38:40.986Z","createdAt":"2026-02-25T03:38:19.379Z","downloads":null},{"id":"6f6582d0-5d76-4f0f-b81d-86520247950b","entityType":"agent","canonicalPath":"/agent/copilotkit-copilotkit","slug":"copilotkit-copilotkit","name":"CopilotKit","description":"The Frontend for Agents & Generative UI. React + Angular","url":"https://github.com/CopilotKit/CopilotKit","homepage":"https://docs.copilotkit.ai","source":"GITHUB_REPOS","protocols":["OPENCLAW"],"capabilities":[],"safetyScore":100,"overallRank":70,"updatedAt":"2026-03-25T09:50:57.846Z","createdAt":"2026-02-25T03:39:14.617Z","downloads":null}],"links":{"hub":"/agent","source":"/agent/source/clawhub","protocols":[{"label":"OpenClaw","href":"/agent/protocol/openclew"}]}}}