Quick Start

This commit is contained in:
tww 2026-03-15 21:14:36 +08:00
parent bd83f91648
commit bbd416ceca
7 changed files with 469 additions and 11 deletions

View file

@ -8,6 +8,40 @@ description: ClawHub CLI 协议兼容层
SkillHub 提供 ClawHub CLI 协议兼容层,现有工具可无缝迁移。
## 配置 ClawHub CLI
要让 ClawHub CLI 连接到你的 SkillHub 实例,需要配置以下环境变量:
### 环境变量配置
**Linux/macOS (bash/zsh):**
```bash
# ~/.bashrc 或 ~/.zshrc
export CLAWHUB_SITE=https://skill.xfyun.cn
export CLAWHUB_REGISTRY=https://skill.xfyun.cn
```
**Windows (PowerShell):**
```powershell
# 永久设置(当前用户)
[Environment]::SetEnvironmentVariable('CLAWHUB_SITE', 'https://skill.xfyun.cn', 'User')
[Environment]::SetEnvironmentVariable('CLAWHUB_REGISTRY', 'https://skill.xfyun.cn', 'User')
# 或者临时设置(当前会话)
$env:CLAWHUB_SITE = 'https://skill.xfyun.cn'
$env:CLAWHUB_REGISTRY = 'https://skill.xfyun.cn'
```
### 使用 CLI 标志(单次命令)
```bash
clawhub --site https://skill.xfyun.cn --registry https://skill.xfyun.cn install <skill>
```
### 前端一键复制
SkillHub 网页端的技能详情页会自动显示带有正确环境变量的安装命令,直接复制即可使用。
## Well-known 发现
```http

View file

@ -8,6 +8,40 @@ description: ClawHub CLI protocol compatibility layer
SkillHub provides a ClawHub CLI protocol compatibility layer for seamless migration of existing tools.
## Configuring ClawHub CLI
To connect ClawHub CLI to your SkillHub instance, configure the following environment variables:
### Environment Variable Configuration
**Linux/macOS (bash/zsh):**
```bash
# ~/.bashrc or ~/.zshrc
export CLAWHUB_SITE=https://skill.xfyun.cn
export CLAWHUB_REGISTRY=https://skill.xfyun.cn
```
**Windows (PowerShell):**
```powershell
# Permanent setting (current user)
[Environment]::SetEnvironmentVariable('CLAWHUB_SITE', 'https://skill.xfyun.cn', 'User')
[Environment]::SetEnvironmentVariable('CLAWHUB_REGISTRY', 'https://skill.xfyun.cn', 'User')
# Or temporary setting (current session)
$env:CLAWHUB_SITE = 'https://skill.xfyun.cn'
$env:CLAWHUB_REGISTRY = 'https://skill.xfyun.cn'
```
### Using CLI Flags (Single Command)
```bash
clawhub --site https://skill.xfyun.cn --registry https://skill.xfyun.cn install <skill>
```
### One-click Copy from Web UI
The SkillHub skill detail page automatically displays install commands with the correct environment variables pre-configured. Simply copy and use.
## Well-known Discovery
```http

View file

@ -1,4 +1,4 @@
import { useState } from 'react'
import { useState, useMemo } from 'react'
import { useTranslation } from 'react-i18next'
import { Check, Copy } from 'lucide-react'
import { Button } from '@/shared/ui/button'
@ -9,12 +9,32 @@ interface InstallCommandProps {
version?: string
}
function getAppBaseUrl(): string {
if (typeof window === 'undefined') {
return ''
}
const runtimeConfig = window.__SKILLHUB_RUNTIME_CONFIG__
if (runtimeConfig?.appBaseUrl) {
return runtimeConfig.appBaseUrl
}
return `${window.location.protocol}//${window.location.host}`
}
export function InstallCommand({ namespace, slug, version }: InstallCommandProps) {
const { t } = useTranslation()
const [copied, setCopied] = useState(false)
const command = version
? `clawhub install ${namespace}/${slug}@${version}`
: `clawhub install ${namespace}/${slug}`
const baseUrl = useMemo(() => getAppBaseUrl(), [])
const cleanNamespace = namespace.startsWith('@') ? namespace.slice(1) : namespace
const command = useMemo(() => {
const installCmd = `clawhub install ${slug} `
// 如果是默认的 clawhub.ai 不需要环境变量,否则显示完整配置
if (baseUrl && !baseUrl.includes('clawhub.ai') && !baseUrl.includes('localhost') && !baseUrl.includes('127.0.0.1')) {
return `CLAWHUB_SITE=${baseUrl} CLAWHUB_REGISTRY=${baseUrl} ${installCmd}`
}
return installCmd
}, [cleanNamespace, slug, version, baseUrl])
const handleCopy = async () => {
try {

View file

@ -69,7 +69,29 @@
"footerLicense": "© 2026 SkillHub. Apache License 2.0.",
"footerDocs": "Docs",
"footerGithub": "GitHub",
"footerCommunity": "Community"
"footerCommunity": "Community",
"quickStart": {
"title": "Quick Start",
"subtitle": "快速开始",
"description": "Get started with SkillHub in just a few simple steps",
"tip": "💡 Tip: Visit the skill detail page for one-click install commands with environment variables",
"steps": {
"configureEnv": {
"title": "1. Configure Environment Variables",
"description": "Configure ClawHub CLI to connect to SkillHub"
},
"installSkills": {
"title": "2. Install Skills",
"description": "Search and install the skills you need",
"code": "# Search skills\nclawhub search <keyword>\n\n# Install skill\nclawhub install <skill>"
},
"publishSkills": {
"title": "3. Publish Skills",
"description": "Share your skills with the team",
"code": "# Publish skill\nclawhub publish\n\n# Or use web interface\n# Click \"Publish Skill\""
}
}
}
},
"home": {
"subtitle": "Modern Skill Registry",
@ -80,7 +102,29 @@
"popularDescription": "Most popular skills in the community",
"latestTitle": "Latest Releases",
"latestDescription": "Freshly published skills",
"viewAll": "View All →"
"viewAll": "View All →",
"quickStart": {
"title": "Quick Start",
"subtitle": "快速开始",
"description": "Get started with SkillHub in just a few simple steps",
"tip": "💡 Tip: Visit the skill detail page for one-click install commands with environment variables",
"steps": {
"configureEnv": {
"title": "1. Configure Environment Variables",
"description": "Configure ClawHub CLI to connect to SkillHub"
},
"installSkills": {
"title": "2. Install Skills",
"description": "Search and install the skills you need",
"code": "# Search skills\nclawhub search <keyword>\n\n# Install skill\nclawhub install <skill>"
},
"publishSkills": {
"title": "3. Publish Skills",
"description": "Share your skills with the team",
"code": "# Publish skill\nclawhub publish\n\n# Or use web interface\n# Click \"Publish Skill\""
}
}
}
},
"search": {
"title": "Search Skills",

View file

@ -69,7 +69,29 @@
"footerLicense": "© 2026 SkillHub。Apache License 2.0。",
"footerDocs": "文档",
"footerGithub": "GitHub",
"footerCommunity": "社区"
"footerCommunity": "社区",
"quickStart": {
"title": "快速开始",
"subtitle": "Quick Start",
"description": "只需几个简单步骤,即可开始使用 SkillHub",
"tip": "💡 提示:访问技能详情页可获取带环境变量的一键复制安装命令",
"steps": {
"configureEnv": {
"title": "1. 配置环境变量",
"description": "设置 ClawHub CLI 连接到 SkillHub"
},
"installSkills": {
"title": "2. 安装技能",
"description": "搜索并安装你需要的技能",
"code": "# 搜索技能\nclawhub search <keyword>\n\n# 安装技能\nclawhub install <skill>"
},
"publishSkills": {
"title": "3. 发布技能",
"description": "分享你的技能给团队使用",
"code": "# 发布技能\nclawhub publish\n\n# 或使用网页界面\n# 点击\"发布技能\""
}
}
}
},
"home": {
"subtitle": "现代化的技能注册中心",
@ -80,7 +102,29 @@
"popularDescription": "社区最受欢迎的技能",
"latestTitle": "最新发布",
"latestDescription": "刚刚发布的新技能",
"viewAll": "查看全部 →"
"viewAll": "查看全部 →",
"quickStart": {
"title": "快速开始",
"subtitle": "Quick Start",
"description": "只需几个简单步骤,即可开始使用 SkillHub",
"tip": "💡 提示:访问技能详情页可获取带环境变量的一键复制安装命令",
"steps": {
"configureEnv": {
"title": "1. 配置环境变量",
"description": "设置 ClawHub CLI 连接到 SkillHub"
},
"installSkills": {
"title": "2. 安装技能",
"description": "搜索并安装你需要的技能",
"code": "# 搜索技能\nclawhub search <keyword>\n\n# 安装技能\nclawhub install <skill>"
},
"publishSkills": {
"title": "3. 发布技能",
"description": "分享你的技能给团队使用",
"code": "# 发布技能\nclawhub publish\n\n# 或使用网页界面\n# 点击\"发布技能\""
}
}
}
},
"search": {
"title": "搜索技能",

View file

@ -5,6 +5,144 @@ import { SkillCard } from '@/features/skill/skill-card'
import { SkeletonList } from '@/shared/components/skeleton-loader'
import { useSearchSkills } from '@/shared/hooks/use-skill-queries'
import { Button } from '@/shared/ui/button'
import { Check, Copy, Terminal, Settings, PackageOpen } from 'lucide-react'
import { useState, useMemo } from 'react'
function getAppBaseUrl(): string {
if (typeof window === 'undefined') {
return 'https://skill.xfyun.cn'
}
const runtimeConfig = window.__SKILLHUB_RUNTIME_CONFIG__
if (runtimeConfig?.appBaseUrl) {
return runtimeConfig.appBaseUrl
}
return `${window.location.protocol}//${window.location.host}`
}
function CopyButton({ text }: { text: string }) {
const { t } = useTranslation()
const [copied, setCopied] = useState(false)
const handleCopy = async () => {
try {
await navigator.clipboard.writeText(text)
setCopied(true)
window.setTimeout(() => setCopied(false), 2000)
} catch (err) {
console.error('Failed to copy:', err)
}
}
return (
<Button
type="button"
variant="ghost"
size="icon"
onClick={handleCopy}
title={copied ? t('copyButton.copied') || 'Copied' : t('copyButton.copy') || 'Copy'}
aria-label={copied ? t('copyButton.copied') || 'Copied' : t('copyButton.copy') || 'Copy'}
className="h-8 w-8"
>
{copied ? <Check className="h-4 w-4" /> : <Copy className="h-4 w-4" />}
</Button>
)
}
function CodeBlock({ code }: { code: string }) {
return (
<div className="relative group">
<div className="flex items-center justify-between px-4 py-2 bg-slate-900 border-b border-slate-700 rounded-t-lg">
<span className="text-xs text-slate-400 font-mono">bash</span>
<CopyButton text={code} />
</div>
<pre className="p-4 bg-slate-950 rounded-b-lg overflow-x-auto">
<code className="font-mono text-sm text-slate-200">{code}</code>
</pre>
</div>
)
}
function QuickStartSection() {
const { t } = useTranslation()
const baseUrl = useMemo(() => getAppBaseUrl(), [])
const steps = [
{
icon: <Settings className="h-6 w-6" />,
title: t('home.quickStart.steps.configureEnv.title'),
description: t('home.quickStart.steps.configureEnv.description'),
code: `# Linux/macOS
export CLAWHUB_SITE=${baseUrl}
export CLAWHUB_REGISTRY=${baseUrl}
# Windows PowerShell
$env:CLAWHUB_SITE = '${baseUrl}'
$env:CLAWHUB_REGISTRY = '${baseUrl}'`,
},
{
icon: <Terminal className="h-6 w-6" />,
title: t('home.quickStart.steps.installSkills.title'),
description: t('home.quickStart.steps.installSkills.description'),
code: t('home.quickStart.steps.installSkills.code'),
},
{
icon: <PackageOpen className="h-6 w-6" />,
title: t('home.quickStart.steps.publishSkills.title'),
description: t('home.quickStart.steps.publishSkills.description'),
code: t('home.quickStart.steps.publishSkills.code'),
},
]
return (
<section className="space-y-8 py-12">
<div className="text-center space-y-4">
<h2 className="text-3xl md:text-4xl font-bold font-heading text-foreground">
{t('home.quickStart.title')}
<span className="block text-lg font-normal text-muted-foreground mt-2">
{t('home.quickStart.subtitle')}
</span>
</h2>
<p className="text-muted-foreground max-w-2xl mx-auto">
{t('home.quickStart.description')}
</p>
</div>
<div className="grid md:grid-cols-1 gap-6 max-w-4xl mx-auto">
{steps.map((step, idx) => (
<div
key={idx}
className="relative p-6 rounded-2xl bg-card border border-border hover:border-cyan-500/50 transition-all duration-300"
>
<div className="flex items-start gap-4">
<div className="flex-shrink-0 p-3 rounded-xl bg-cyan-500/10 text-cyan-400">
{step.icon}
</div>
<div className="flex-1 space-y-4">
<div>
<h3 className="text-xl font-bold text-foreground">
{step.title}
</h3>
<p className="text-sm text-muted-foreground">
{step.description}
</p>
</div>
<CodeBlock code={step.code} />
</div>
</div>
</div>
))}
</div>
<div className="text-center pt-4">
<div className="inline-flex items-center gap-2 px-4 py-2 rounded-full bg-yellow-500/10 border border-yellow-500/30">
<span className="text-sm text-yellow-300">
{t('home.quickStart.tip')}
</span>
</div>
</div>
</section>
)
}
export function HomePage() {
const { t } = useTranslation()
@ -59,7 +197,7 @@ export function HomePage() {
</div>
{/* Popular Downloads Section */}
<section className="space-y-6 animate-fade-up delay-3">
<section className="space-y-6 animate-fade-up">
<div className="flex items-center justify-between">
<div>
<h2 className="text-3xl font-bold font-heading text-foreground mb-2">{t('home.popularTitle')}</h2>
@ -89,7 +227,7 @@ export function HomePage() {
</section>
{/* Latest Releases Section */}
<section className="space-y-6 animate-fade-up delay-4">
<section className="space-y-6 animate-fade-up">
<div className="flex items-center justify-between">
<div>
<h2 className="text-3xl font-bold font-heading text-foreground mb-2">{t('home.latestTitle')}</h2>
@ -117,6 +255,9 @@ export function HomePage() {
</div>
)}
</section>
{/* Quick Start Section */}
<QuickStartSection />
</div>
)
}

View file

@ -5,7 +5,145 @@ import { useAuth } from '@/features/auth/use-auth'
import { LanguageSwitcher } from '@/shared/components/language-switcher'
import { UserMenu } from '@/shared/components/user-menu'
import { Button } from '@/shared/ui/button'
import { useEffect, useRef, useState } from 'react'
import { Check, Copy, Terminal, Settings, PackageOpen } from 'lucide-react'
import { useEffect, useRef, useState, useMemo } from 'react'
function getAppBaseUrl(): string {
if (typeof window === 'undefined') {
return 'https://skill.xfyun.cn'
}
const runtimeConfig = window.__SKILLHUB_RUNTIME_CONFIG__
if (runtimeConfig?.appBaseUrl) {
return runtimeConfig.appBaseUrl
}
return `${window.location.protocol}//${window.location.host}`
}
function CopyButton({ text }: { text: string }) {
const { t } = useTranslation()
const [copied, setCopied] = useState(false)
const handleCopy = async () => {
try {
await navigator.clipboard.writeText(text)
setCopied(true)
window.setTimeout(() => setCopied(false), 2000)
} catch (err) {
console.error('Failed to copy:', err)
}
}
return (
<Button
type="button"
variant="ghost"
size="icon"
onClick={handleCopy}
title={copied ? (t('copyButton.copied') || 'Copied') : (t('copyButton.copy') || 'Copy')}
aria-label={copied ? (t('copyButton.copied') || 'Copied') : (t('copyButton.copy') || 'Copy')}
className="h-8 w-8"
>
{copied ? <Check className="h-4 w-4" /> : <Copy className="h-4 w-4" />}
</Button>
)
}
function CodeBlock({ code }: { code: string }) {
return (
<div className="relative group">
<div className="flex items-center justify-between px-4 py-2 bg-slate-900 border-b border-slate-700 rounded-t-lg">
<span className="text-xs text-slate-400 font-mono">bash</span>
<CopyButton text={code} />
</div>
<pre className="p-4 bg-slate-950 rounded-b-lg overflow-x-auto">
<code className="font-mono text-sm text-slate-200">{code}</code>
</pre>
</div>
)
}
function QuickStartSection() {
const { t } = useTranslation()
const baseUrl = useMemo(() => getAppBaseUrl(), [])
const steps = [
{
icon: <Settings className="h-6 w-6" />,
title: t('landing.quickStart.steps.configureEnv.title'),
description: t('landing.quickStart.steps.configureEnv.description'),
code: `# Linux/macOS
export CLAWHUB_SITE=${baseUrl}
export CLAWHUB_REGISTRY=${baseUrl}
# Windows PowerShell
$env:CLAWHUB_SITE = '${baseUrl}'
$env:CLAWHUB_REGISTRY = '${baseUrl}'`,
},
{
icon: <Terminal className="h-6 w-6" />,
title: t('landing.quickStart.steps.installSkills.title'),
description: t('landing.quickStart.steps.installSkills.description'),
code: t('landing.quickStart.steps.installSkills.code'),
},
{
icon: <PackageOpen className="h-6 w-6" />,
title: t('landing.quickStart.steps.publishSkills.title'),
description: t('landing.quickStart.steps.publishSkills.description'),
code: t('landing.quickStart.steps.publishSkills.code'),
},
]
return (
<section className="py-20 space-y-8">
<div className="text-center space-y-4 animate-fade-up">
<h2 className="text-4xl md:text-5xl font-bold text-slate-100">
{t('landing.quickStart.title')}
<span className="block text-lg font-normal text-slate-400 mt-2">
{t('landing.quickStart.subtitle')}
</span>
</h2>
<p className="text-lg text-slate-400 max-w-2xl mx-auto">
{t('landing.quickStart.description')}
</p>
</div>
<div className="grid md:grid-cols-1 gap-6 max-w-4xl mx-auto">
{steps.map((step, idx) => (
<div
key={idx}
className="relative p-6 rounded-2xl bg-slate-800/40 backdrop-blur-sm border border-slate-700/50 hover:border-cyan-500/50 transition-all duration-300 animate-fade-up"
style={{ animationDelay: (idx * 0.1) + 's' }}
>
<div className="flex items-start gap-4">
<div className="flex-shrink-0 p-3 rounded-xl bg-cyan-500/10 text-cyan-400">
{step.icon}
</div>
<div className="flex-1 space-y-4">
<div>
<h3 className="text-xl font-bold text-slate-100">
{step.title}
</h3>
<p className="text-sm text-slate-400">
{step.description}
</p>
</div>
<CodeBlock code={step.code} />
</div>
</div>
</div>
))}
</div>
<div className="text-center pt-4 animate-fade-up">
<div className="inline-flex items-center gap-2 px-4 py-2 rounded-full bg-yellow-500/10 border border-yellow-500/30">
<span className="text-sm text-yellow-300">
{t('landing.quickStart.tip')}
</span>
</div>
</div>
</section>
)
}
export function LandingPage() {
const { t } = useTranslation()
@ -278,6 +416,9 @@ export function LandingPage() {
</div>
</div>
{/* Quick Start Section */}
<QuickStartSection />
</div>
<div className="relative z-10 border-t border-slate-800/50 backdrop-blur-sm">