diff --git a/README.md b/README.md index 2df6bdc2..87c2eeb0 100644 --- a/README.md +++ b/README.md @@ -65,6 +65,8 @@ firewall, with the same polish you'd expect from a public registry. ## Quick Start +📖 **[User Documentation →](https://iflytek.github.io/skillhub/)** + Start the full local stack with one of the following commands: Official images: @@ -76,10 +78,22 @@ curl -fsSL https://imageless.oss-cn-beijing.aliyuncs.com/runtime-github.sh | sh The default command pulls the `latest` stable release images. Use `--version edge` if you want the newest build from `main`. +**Configure public URL (recommended for production):** + +```bash +curl -fsSL https://imageless.oss-cn-beijing.aliyuncs.com/runtime-github.sh | sh -s -- up --public-url https://skillhub.your-company.com +``` + +The `--public-url` parameter sets the public access URL for your SkillHub instance. +This ensures: +- CLI install commands show the correct registry URL +- Agent setup instructions display the correct skill.md URL +- OAuth callbacks and device auth links work properly + Aliyun mirror shortcut: ```bash rm -rf /tmp/skillhub-aliyun -curl -fsSL https://imageless.oss-cn-beijing.aliyuncs.com/runtime.sh | sh -s -- up --home /tmp/skillhub-aliyun --aliyun --version latest +curl -fsSL https://imageless.oss-cn-beijing.aliyuncs.com/runtime.sh | sh -s -- up --home /tmp/skillhub-aliyun --aliyun --version latest --public-url https://skillhub.your-company.com ``` If deployment runs into problems, clear the existing runtime home and retry. @@ -170,6 +184,30 @@ This is the supported path for anyone who wants a ready-to-use local environment without building the backend or frontend on their machine. Published images target both `linux/amd64` and `linux/arm64`. +**Quick deployment with curl:** + +```bash +# Official images +curl -fsSL https://imageless.oss-cn-beijing.aliyuncs.com/runtime-github.sh | sh -s -- up --public-url https://skillhub.your-company.com + +# Aliyun mirror (recommended for users in China) +curl -fsSL https://imageless.oss-cn-beijing.aliyuncs.com/runtime.sh | sh -s -- up --aliyun --public-url https://skillhub.your-company.com +``` + +**Deployment parameters:** + +| Parameter | Description | Example | +|-----------|-------------|---------| +| `--public-url ` | Public access URL (recommended) | `--public-url https://skill.example.com` | +| `--version ` | Specific image tag | `--version v0.2.0` | +| `--aliyun` | Use Aliyun mirror (China) | `--aliyun` | +| `--home ` | Runtime directory | `--home /opt/skillhub` | +| `--no-scanner` | Disable security scanner | `--no-scanner` | + +> **Important**: Configure `--public-url` for production deployments to ensure CLI install commands and Agent setup instructions display the correct URLs. + +**Manual deployment:** + 1. Copy the runtime environment template. 2. Pick an image tag. 3. Start the stack with Docker Compose. diff --git a/README_zh.md b/README_zh.md index 41d81ca9..1df6091f 100644 --- a/README_zh.md +++ b/README_zh.md @@ -37,6 +37,8 @@ SkillHub 是一个自托管平台,为团队提供私有的、受治理的智 ## 快速开始 +📖 **[用户使用文档 →](https://iflytek.github.io/skillhub/)** + 使用以下命令之一启动完整的本地环境: 官方镜像: @@ -48,10 +50,21 @@ curl -fsSL https://raw.githubusercontent.com/iflytek/skillhub/main/scripts/runti 默认命令会拉取 `latest` 稳定版镜像;如果你想跟随 `main` 的最新构建,请显式传 `--version edge`。 +**配置公网访问地址(生产环境推荐):** + +```bash +curl -fsSL https://raw.githubusercontent.com/iflytek/skillhub/main/scripts/runtime.sh | sh -s -- up --public-url https://skillhub.your-company.com +``` + +`--public-url` 参数用于设置 SkillHub 实例的公网访问地址。配置后: +- CLI 安装命令会显示正确的注册中心地址 +- Agent 设置指引会显示正确的 skill.md URL +- OAuth 回调和设备认证链接能正常工作 + 阿里云镜像快捷方式: ```bash rm -rf /tmp/skillhub-aliyun -curl -fsSL https://imageless.oss-cn-beijing.aliyuncs.com/runtime.sh | sh -s -- up --home /tmp/skillhub-aliyun --aliyun --version latest +curl -fsSL https://imageless.oss-cn-beijing.aliyuncs.com/runtime.sh | sh -s -- up --home /tmp/skillhub-aliyun --aliyun --version latest --public-url https://skillhub.your-company.com ``` 如果部署遇到问题,请清除现有的运行时目录并重试。 @@ -160,12 +173,24 @@ skillhub/ ```bash # 使用官方镜像 -curl -fsSL https://raw.githubusercontent.com/iflytek/skillhub/main/scripts/runtime.sh | sh -s -- up +curl -fsSL https://raw.githubusercontent.com/iflytek/skillhub/main/scripts/runtime.sh | sh -s -- up --public-url https://skillhub.your-company.com # 使用阿里云镜像 -curl -fsSL https://imageless.oss-cn-beijing.aliyuncs.com/runtime.sh | sh -s -- up --aliyun +curl -fsSL https://imageless.oss-cn-beijing.aliyuncs.com/runtime.sh | sh -s -- up --aliyun --public-url https://skillhub.your-company.com ``` +### 配置参数说明 + +| 参数 | 说明 | 示例 | +|------|------|------| +| `--public-url ` | 公网访问地址(推荐配置) | `--public-url https://skill.example.com` | +| `--version ` | 指定镜像版本 | `--version v0.2.0` | +| `--aliyun` | 使用阿里云镜像(国内推荐) | `--aliyun` | +| `--home ` | 指定运行时目录 | `--home /opt/skillhub` | +| `--no-scanner` | 禁用安全扫描服务 | `--no-scanner` | + +> **重要**:生产环境请务必配置 `--public-url`,确保 CLI 安装命令和 Agent 设置指引显示正确的地址。 + ### 使用 Kubernetes ```bash diff --git a/scripts/runtime.sh b/scripts/runtime.sh old mode 100644 new mode 100755 index f0db3037..4d96eac7 --- a/scripts/runtime.sh +++ b/scripts/runtime.sh @@ -12,6 +12,7 @@ SKILLHUB_REF="${SKILLHUB_REF:-main}" SKILLHUB_HOME_DEFAULT="${TMPDIR:-/tmp}/skillhub-runtime" SKILLHUB_HOME="${SKILLHUB_HOME:-$SKILLHUB_HOME_DEFAULT}" SKILLHUB_VERSION_VALUE="${SKILLHUB_VERSION:-}" +SKILLHUB_PUBLIC_BASE_URL_VALUE="${SKILLHUB_PUBLIC_BASE_URL:-}" SKILLHUB_ALIYUN_REGISTRY="${SKILLHUB_ALIYUN_REGISTRY:-crpi-ptu2rqimrigtq0qx.cn-hangzhou.personal.cr.aliyuncs.com}" SKILLHUB_ALIYUN_NAMESPACE="${SKILLHUB_ALIYUN_NAMESPACE:-skill_hub}" SKILLHUB_MIRROR_REGISTRY_VALUE="${SKILLHUB_MIRROR_REGISTRY:-}" @@ -81,6 +82,11 @@ while [ "$#" -gt 0 ]; do DISABLE_SCANNER=true shift ;; + --public-url) + [ "$#" -ge 2 ] || { echo "Missing value for --public-url" >&2; exit 1; } + SKILLHUB_PUBLIC_BASE_URL_VALUE="$2" + shift 2 + ;; --help|-h) cat < Use mirrored images from / --home Store runtime files in a specific directory --ref Download runtime files from a specific Git ref + --public-url Public access URL (e.g. https://skill.example.com) --server-image Override backend image repository --web-image Override frontend image repository --scanner-image Override scanner image repository @@ -204,6 +211,10 @@ prepare_runtime_files() { if [ -n "$SKILLHUB_SCANNER_IMAGE_VALUE" ]; then set_env_value "SKILLHUB_SCANNER_IMAGE" "$SKILLHUB_SCANNER_IMAGE_VALUE" fi + + if [ -n "$SKILLHUB_PUBLIC_BASE_URL_VALUE" ]; then + set_env_value "SKILLHUB_PUBLIC_BASE_URL" "$SKILLHUB_PUBLIC_BASE_URL_VALUE" + fi } run_compose() { @@ -221,9 +232,10 @@ case "$COMMAND" in else run_compose up -d fi + PUBLIC_URL="${SKILLHUB_PUBLIC_BASE_URL_VALUE:-http://localhost}" cat < /usr/share/nginx/html/runtime-config.js + +# Generate registry/skill.md with actual public URL +envsubst '${SKILLHUB_PUBLIC_BASE_URL}' \ + < /usr/share/nginx/html/registry/skill.md.template \ + > /usr/share/nginx/html/registry/skill.md diff --git a/web/src/docs/skill.md.template b/web/src/docs/skill.md.template new file mode 100644 index 00000000..1d13200f --- /dev/null +++ b/web/src/docs/skill.md.template @@ -0,0 +1,145 @@ +--- +name: skillhub-registry +description: Use this when you need to search, inspect, install, or publish agent skills against a SkillHub registry. SkillHub is a skill registry with a ClawHub-compatible API layer, so prefer the `clawhub` CLI for registry operations instead of making raw HTTP calls. +--- + +# SkillHub Registry + +Use this skill when you need to work with a SkillHub registry: search skills, inspect metadata, install a package, or publish a new version. + +> Important: Prefer the `clawhub` CLI for registry workflows. SkillHub exposes a ClawHub-compatible API surface and a discovery endpoint at `/.well-known/clawhub.json`, so the CLI is the safest path for auth, resolution, and download behavior. Only fall back to raw HTTP when debugging the server itself. + +## What SkillHub Is + +SkillHub is an enterprise-oriented skill registry. It stores versioned skill packages, supports namespace-based skill management, and keeps `SKILL.md` compatibility with OpenSkills-style packages. + +Key facts: + +- Internal coordinates use `@{namespace}/{skill_slug}`. +- If using the clawhub CLI, the compatible format is `{namespace}--{skill_slug}`. +- ClawHub-compatible clients use a `{namespace}--{skill_slug}` slug instead. +- `latest` always means the latest published version, never draft or pending review. +- Public skills in `@global` can be downloaded anonymously. +- If no namespace is specified, it defaults to `@global`. +- `{skill_slug}` can be used instead of `global--{skill_slug}` +- Team namespace skills and non-public skills require authentication. + +## Configure The CLI + +Point `clawhub` at the SkillHub base URL: + +```bash +export CLAWHUB_REGISTRY=${SKILLHUB_PUBLIC_BASE_URL} +``` + +Alternatively, use the `--registry` parameter every time, for example: + +```bash +npx clawhub install my-skill --registry ${SKILLHUB_PUBLIC_BASE_URL} +``` + + +If you need authenticated access, provide an API token: + +```bash +clawhub login --token sk_your_api_token_here +``` + +Optional local check: + +```bash +curl ${SKILLHUB_PUBLIC_BASE_URL}/.well-known/clawhub.json +``` + +Expected response: + +```json +{"apiBase":"/api/v1"} +``` + +## Coordinate Rules - IMPORTANT + +SkillHub has two naming forms: + +| SkillHub coordinate | Canonical slug for `clawhub` | +|---|---| +| `@global/my-skill` | `my-skill` | +| `@team-name/my-skill` | `team-name--my-skill` | + +Rules: + +- `--` is the namespace separator in the compatibility layer. +- If there is no `--`, the skill is treated as `@global/...`. +- `latest` resolves to the latest published version only. + +Examples: + +```bash +npx clawhub install my-skill +npx clawhub install my-skill@1.2.0 +npx clawhub install team-name--my-skill +``` + +## Common Workflows + +### Search + +```bash +npx clawhub search email +``` + +Use an empty query when you want a broad listing: + +```bash +npx clawhub search "" +``` + +### Inspect A Skill + +```bash +npx clawhub info my-skill +npx clawhub info team-name--my-skill +``` + +### Install + +```bash +npx clawhub install my-skill +npx clawhub install my-skill@1.2.0 +npx clawhub install team-name--my-skill +``` + +### Publish + +Prepare a skill package directory, then publish it: + +```bash +npx clawhub publish ./my-skill +``` + +Publishing requires authentication and sufficient permissions in the target namespace. + +## Authentication And Visibility + +Download and search permissions depend on namespace and visibility: + +- `@global` + `PUBLIC`: anonymous search, inspect, and download are allowed. +- Team namespace + `PUBLIC`: authentication required for download. +- `NAMESPACE_ONLY`: authenticated namespace members only. +- `PRIVATE`: owner or explicitly authorized users only. +- Publish, star, and other write operations always require authentication. + +If a request fails with `403`, check: + +- whether the skill belongs to a team namespace, +- whether the skill is `NAMESPACE_ONLY` or `PRIVATE`, +- whether your token is valid, +- whether you have namespace publish permissions. + +## Skill Package Contract + +SkillHub expects OpenSkills-style packages with `SKILL.md` as the entry point. + +## Publishing Guidance + +Just need to follow the OpenSkills-style standards. diff --git a/web/src/features/skill/install-command.test.ts b/web/src/features/skill/install-command.test.ts index 961d46b7..1f4daf50 100644 --- a/web/src/features/skill/install-command.test.ts +++ b/web/src/features/skill/install-command.test.ts @@ -73,6 +73,16 @@ describe('install-command', () => { expect(getBaseUrl()).toBe('https://fallback.example.com') }) + it('falls back to browser origin when app base url is localhost', () => { + setMockWindow('http://localhost') + expect(getBaseUrl()).toBe('https://fallback.example.com') + }) + + it('falls back to browser origin when app base url contains localhost', () => { + setMockWindow('http://localhost:8080') + expect(getBaseUrl()).toBe('https://fallback.example.com') + }) + it('renders the install command in a more compact code block', () => { setMockWindow('http://localhost:3000') diff --git a/web/src/features/skill/install-command.tsx b/web/src/features/skill/install-command.tsx index 63aed99c..f9989abb 100644 --- a/web/src/features/skill/install-command.tsx +++ b/web/src/features/skill/install-command.tsx @@ -19,9 +19,12 @@ export function getBaseUrl(): string { return '' } const runtimeConfig = window.__SKILLHUB_RUNTIME_CONFIG__ - if (runtimeConfig?.appBaseUrl) { - return runtimeConfig.appBaseUrl + const configuredUrl = runtimeConfig?.appBaseUrl + // Use configured URL only if it's set and not localhost + if (configuredUrl && !configuredUrl.includes('localhost')) { + return configuredUrl } + // Fallback to current page origin return `${window.location.protocol}//${window.location.host}` } diff --git a/web/src/i18n/landing-quick-start-locale.test.ts b/web/src/i18n/landing-quick-start-locale.test.ts index 17935629..6a36420b 100644 --- a/web/src/i18n/landing-quick-start-locale.test.ts +++ b/web/src/i18n/landing-quick-start-locale.test.ts @@ -7,4 +7,9 @@ describe('landing quick start locales', () => { expect(zh.landing.quickStart.agent.command).toBe('阅读 https://www.example.com/registry/skill.md,并按照说明完成 SkillHub Skills Registry 的配置') expect(en.landing.quickStart.agent.command).toBe('Read https://www.example.com/registry/skill.md and follow the instructions to setup SkillHub Skills Registry') }) + + it('provides command templates with url placeholder for dynamic rendering', () => { + expect(zh.landing.quickStart.agent.commandTemplate).toBe('阅读 {{url}},并按照说明完成 SkillHub Skills Registry 的配置') + expect(en.landing.quickStart.agent.commandTemplate).toBe('Read {{url}} and follow the instructions to setup SkillHub Skills Registry') + }) }) diff --git a/web/src/i18n/locales/en.json b/web/src/i18n/locales/en.json index d110b0d3..b1e35669 100644 --- a/web/src/i18n/locales/en.json +++ b/web/src/i18n/locales/en.json @@ -107,7 +107,8 @@ }, "agent": { "description": "Send a prompt to your Agent to set up the SkillHub Registry", - "command": "Read https://www.example.com/registry/skill.md and follow the instructions to setup SkillHub Skills Registry" + "command": "Read https://www.example.com/registry/skill.md and follow the instructions to setup SkillHub Skills Registry", + "commandTemplate": "Read {{url}} and follow the instructions to setup SkillHub Skills Registry" }, "human": { "description": "Use the CLI tool to install Skills", diff --git a/web/src/i18n/locales/zh.json b/web/src/i18n/locales/zh.json index dbfbabad..41972c55 100644 --- a/web/src/i18n/locales/zh.json +++ b/web/src/i18n/locales/zh.json @@ -107,7 +107,8 @@ }, "agent": { "description": "发送提示词给你的 Agent,以设置SkillHub Registry", - "command": "阅读 https://www.example.com/registry/skill.md,并按照说明完成 SkillHub Skills Registry 的配置" + "command": "阅读 https://www.example.com/registry/skill.md,并按照说明完成 SkillHub Skills Registry 的配置", + "commandTemplate": "阅读 {{url}},并按照说明完成 SkillHub Skills Registry 的配置" }, "human": { "description": "使用CLI工具安装Skills", diff --git a/web/src/shared/components/landing-quick-start.tsx b/web/src/shared/components/landing-quick-start.tsx index f838d217..cfc7f656 100644 --- a/web/src/shared/components/landing-quick-start.tsx +++ b/web/src/shared/components/landing-quick-start.tsx @@ -1,4 +1,4 @@ -import { useState } from 'react' +import { useState, useMemo } from 'react' import { useTranslation } from 'react-i18next' import { Bot, Check, Copy, UserRound } from 'lucide-react' import { useCopyToClipboard } from '@/shared/lib/clipboard' @@ -12,6 +12,25 @@ interface LandingQuickStartTab { command: string } +/** + * Get the base URL for the application. + * Prefers the runtime config if set and not localhost. + * Falls back to the current page origin. + */ +function getAppBaseUrl(): string { + if (typeof window === 'undefined') { + return '' + } + const runtimeConfig = window.__SKILLHUB_RUNTIME_CONFIG__ + const configuredUrl = runtimeConfig?.appBaseUrl + // Use configured URL only if it's set and not localhost + if (configuredUrl && !configuredUrl.includes('localhost')) { + return configuredUrl + } + // Fallback to current page origin + return `${window.location.protocol}//${window.location.host}` +} + function CompactCopyButton({ text }: { text: string }) { const { t } = useTranslation() const [copied, copy] = useCopyToClipboard() @@ -43,13 +62,20 @@ function CompactCopyButton({ text }: { text: string }) { export function LandingQuickStartSection() { const { t } = useTranslation() const [activeTab, setActiveTab] = useState('agent') + const baseUrl = useMemo(() => getAppBaseUrl(), []) + + // Build dynamic agent command with actual registry URL + const agentCommand = t('landing.quickStart.agent.commandTemplate', { + defaultValue: t('landing.quickStart.agent.command'), + url: `${baseUrl}/registry/skill.md`, + }) const tabs: LandingQuickStartTab[] = [ { id: 'agent', label: t('landing.quickStart.tabs.agent'), description: t('landing.quickStart.agent.description'), - command: t('landing.quickStart.agent.command'), + command: agentCommand, }, { id: 'human', diff --git a/web/src/shared/components/quick-start.tsx b/web/src/shared/components/quick-start.tsx index bc156c75..daedcb05 100644 --- a/web/src/shared/components/quick-start.tsx +++ b/web/src/shared/components/quick-start.tsx @@ -8,9 +8,12 @@ function getAppBaseUrl(): string { return 'https://skill.xfyun.cn' } const runtimeConfig = (window as unknown as Record).__SKILLHUB_RUNTIME_CONFIG__ as { appBaseUrl?: string } | undefined - if (runtimeConfig?.appBaseUrl) { - return runtimeConfig.appBaseUrl + const configuredUrl = runtimeConfig?.appBaseUrl + // Use configured URL only if it's set and not localhost + if (configuredUrl && !configuredUrl.includes('localhost')) { + return configuredUrl } + // Fallback to current page origin return `${window.location.protocol}//${window.location.host}` }