mirror of
https://github.com/iflytek/skillhub.git
synced 2026-09-07 08:26:00 +00:00
fix(web): preserve registry in agent onboarding
Signed-off-by: XiaoSeS <87064762+XiaoSeS@users.noreply.github.com>
This commit is contained in:
parent
400240b5d6
commit
5fea369d60
8 changed files with 103 additions and 10 deletions
|
|
@ -37,16 +37,44 @@ test.describe('Landing Quick Start CLI Tab (Real API)', () => {
|
|||
const humanTab = page.getByRole('button', { name: 'I am Human', exact: true })
|
||||
|
||||
await expect(
|
||||
page.getByText(/Connect SkillHub using .+\/install\/skillhub\.md/),
|
||||
page.getByText(
|
||||
'Connect SkillHub using http://127.0.0.1:3000/install/skillhub.md',
|
||||
{ exact: true },
|
||||
),
|
||||
).toBeVisible()
|
||||
const guideResponse = await page.request.get('/install/skillhub.md')
|
||||
expect(guideResponse.status()).toBe(200)
|
||||
const guide = await guideResponse.text()
|
||||
expect(guide).toContain('http://127.0.0.1:3000')
|
||||
expect(guideResponse.headers()['cache-control']).toContain('no-cache')
|
||||
const legacyGuideResponse = await page.request.get('/registry/skill.md')
|
||||
expect(legacyGuideResponse.status()).toBe(200)
|
||||
expect(await legacyGuideResponse.text()).toBe(guide)
|
||||
const hostileHostResponse = await page.request.get('/install/skillhub.md', {
|
||||
headers: { Host: 'attacker.example' },
|
||||
})
|
||||
expect(hostileHostResponse.status()).toBe(403)
|
||||
const extensionHostResponse = await page.request.get('/install/skillhub.md', {
|
||||
headers: { Host: 'chrome-extension:evil;echo_injected' },
|
||||
})
|
||||
expect(extensionHostResponse.status()).toBe(400)
|
||||
|
||||
await humanTab.click()
|
||||
await expect(humanTab).toHaveAttribute('aria-pressed', 'true')
|
||||
await expect(
|
||||
page.getByText('npx @astron-team/skillhub@latest search <keyword>', { exact: true }),
|
||||
page.getByText(
|
||||
'npx @astron-team/skillhub@latest search <keyword> --registry http://127.0.0.1:3000',
|
||||
{ exact: true },
|
||||
),
|
||||
).toBeVisible()
|
||||
|
||||
await agentTab.click()
|
||||
await expect(agentTab).toHaveAttribute('aria-pressed', 'true')
|
||||
await expect(
|
||||
page.getByText(
|
||||
'Connect SkillHub using http://127.0.0.1:3000/install/skillhub.md',
|
||||
{ exact: true },
|
||||
),
|
||||
).toBeVisible()
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -52,11 +52,23 @@ test.describe('Public Skill Detail Anonymous Access (Real API)', () => {
|
|||
? current.skill.slug
|
||||
: `${current.skill.namespace}--${current.skill.slug}`
|
||||
const skillhubCoordinate = `@${current.skill.namespace}/${current.skill.slug}`
|
||||
const registryUrl = new URL(page.url()).origin
|
||||
|
||||
await expect(page.getByRole('tab', { name: 'SkillHub CLI' })).toHaveAttribute('aria-selected', 'true')
|
||||
await expect(page.getByText(new RegExp(`npx @astron-team/skillhub@latest install ${escapeRegExp(skillhubCoordinate)} --version ${escapeRegExp(current.skill.version)} --registry`))).toBeVisible()
|
||||
await expect(page.getByText(
|
||||
`npx @astron-team/skillhub@latest install ${skillhubCoordinate} --version ${current.skill.version} --registry ${registryUrl}`,
|
||||
{ exact: true },
|
||||
)).toBeVisible()
|
||||
await expect(page.getByRole('button', { name: 'Copy' }).first()).toBeVisible()
|
||||
|
||||
await page.context().grantPermissions(['clipboard-read', 'clipboard-write'], { origin: registryUrl })
|
||||
await page.getByTestId('install-for-agent-button').click()
|
||||
const agentPrompt = await page.evaluate(() => navigator.clipboard.readText())
|
||||
expect(agentPrompt).toContain(`${registryUrl}/install/skillhub.md`)
|
||||
expect(agentPrompt).toContain(skillhubCoordinate)
|
||||
expect(agentPrompt).toContain(current.skill.version)
|
||||
expect(agentPrompt).toContain('ask for my confirmation')
|
||||
|
||||
await page.getByRole('tab', { name: 'ClawHub CLI' }).click()
|
||||
|
||||
await expect(page.getByRole('tab', { name: 'ClawHub CLI' })).toHaveAttribute('aria-selected', 'true')
|
||||
|
|
|
|||
|
|
@ -14,6 +14,9 @@ describe('landing quick start locales', () => {
|
|||
expect(zh.landing.quickStart.agent.commandTemplate).toBe('请根据 {{url}} 接入 SkillHub')
|
||||
expect(en.landing.quickStart.agent.commandTemplate).toBe('Connect SkillHub using {{url}}')
|
||||
expect(ru.landing.quickStart.agent.commandTemplate).toBe('Подключите SkillHub по инструкции {{url}}')
|
||||
expect(zh.landing.quickStart.human.commandTemplate).toContain('--registry {{url}}')
|
||||
expect(en.landing.quickStart.human.commandTemplate).toContain('--registry {{url}}')
|
||||
expect(ru.landing.quickStart.human.commandTemplate).toContain('--registry {{url}}')
|
||||
})
|
||||
|
||||
it('exposes CLI install command in both locales', () => {
|
||||
|
|
|
|||
|
|
@ -127,7 +127,8 @@
|
|||
},
|
||||
"human": {
|
||||
"description": "Use the SkillHub CLI to search for and install Skills",
|
||||
"command": "npx @astron-team/skillhub@latest search <keyword>"
|
||||
"command": "npx @astron-team/skillhub@latest search <keyword> --registry https://www.example.com",
|
||||
"commandTemplate": "npx @astron-team/skillhub@latest search <keyword> --registry {{url}}"
|
||||
},
|
||||
"steps": {
|
||||
"configureEnv": {
|
||||
|
|
|
|||
|
|
@ -127,7 +127,8 @@
|
|||
},
|
||||
"human": {
|
||||
"description": "Используйте SkillHub CLI для поиска и установки Skills",
|
||||
"command": "npx @astron-team/skillhub@latest search <keyword>"
|
||||
"command": "npx @astron-team/skillhub@latest search <keyword> --registry https://www.example.com",
|
||||
"commandTemplate": "npx @astron-team/skillhub@latest search <keyword> --registry {{url}}"
|
||||
},
|
||||
"steps": {
|
||||
"configureEnv": {
|
||||
|
|
|
|||
|
|
@ -127,7 +127,8 @@
|
|||
},
|
||||
"human": {
|
||||
"description": "使用 SkillHub CLI 搜索和安装技能",
|
||||
"command": "npx @astron-team/skillhub@latest search <keyword>"
|
||||
"command": "npx @astron-team/skillhub@latest search <keyword> --registry https://www.example.com",
|
||||
"commandTemplate": "npx @astron-team/skillhub@latest search <keyword> --registry {{url}}"
|
||||
},
|
||||
"steps": {
|
||||
"configureEnv": {
|
||||
|
|
|
|||
|
|
@ -74,6 +74,10 @@ export function LandingQuickStartSection() {
|
|||
defaultValue: t('landing.quickStart.agent.command'),
|
||||
url: `${baseUrl}/install/skillhub.md`,
|
||||
})
|
||||
const humanCommand = t('landing.quickStart.human.commandTemplate', {
|
||||
defaultValue: t('landing.quickStart.human.command'),
|
||||
url: baseUrl,
|
||||
})
|
||||
|
||||
const tabs: LandingQuickStartTab[] = [
|
||||
{
|
||||
|
|
@ -86,7 +90,7 @@ export function LandingQuickStartSection() {
|
|||
id: 'human',
|
||||
label: t('landing.quickStart.tabs.human'),
|
||||
description: t('landing.quickStart.human.description'),
|
||||
command: t('landing.quickStart.human.command'),
|
||||
command: humanCommand,
|
||||
},
|
||||
{
|
||||
id: 'cli',
|
||||
|
|
|
|||
|
|
@ -1,14 +1,57 @@
|
|||
import { defineConfig } from 'vite'
|
||||
import { readFileSync } from 'node:fs'
|
||||
import { defineConfig, type Plugin } from 'vite'
|
||||
import react from '@vitejs/plugin-react'
|
||||
import path from 'path'
|
||||
import { validateBasePath } from './base-path-config'
|
||||
|
||||
const JS_BUILD_TARGET = 'es2020'
|
||||
const LEGACY_BROWSER_TARGETS = ['chrome83', 'edge83', 'firefox78', 'safari14']
|
||||
const basePath = validateBasePath(process.env.VITE_BASE_PATH ?? '/')
|
||||
const guideTemplate = readFileSync(path.resolve(__dirname, 'src/docs/skill.md.template'), 'utf8')
|
||||
const safeHostPattern = /^(?:[A-Za-z0-9.-]+|\[[0-9A-Fa-f:.]+\])(?::[0-9]{1,5})?$/
|
||||
|
||||
function installGuideDevPlugin(): Plugin {
|
||||
const basePrefix = basePath === '/' ? '' : basePath.slice(0, -1)
|
||||
const guidePaths = new Set([
|
||||
`${basePrefix}/install/skillhub.md`,
|
||||
`${basePrefix}/registry/skill.md`,
|
||||
])
|
||||
|
||||
return {
|
||||
name: 'skillhub-install-guide-dev',
|
||||
configureServer(server) {
|
||||
// Install after Vite's built-in Host check so an untrusted Host can never
|
||||
// be reflected into CLI commands. originalUrl survives SPA/base rewrites.
|
||||
return () => {
|
||||
server.middlewares.use((request, response, next) => {
|
||||
const requestPath = new URL(request.originalUrl ?? request.url ?? '/', 'http://localhost').pathname
|
||||
if (!guidePaths.has(requestPath)) {
|
||||
next()
|
||||
return
|
||||
}
|
||||
|
||||
const host = request.headers.host
|
||||
if (!host || !safeHostPattern.test(host)) {
|
||||
response.statusCode = 400
|
||||
response.end('Invalid Host')
|
||||
return
|
||||
}
|
||||
|
||||
const publicBaseUrl = `http://${host}${basePrefix}`
|
||||
const guide = guideTemplate.replaceAll('${SKILLHUB_PUBLIC_BASE_URL}', publicBaseUrl)
|
||||
response.statusCode = 200
|
||||
response.setHeader('Content-Type', 'text/markdown; charset=utf-8')
|
||||
response.setHeader('Cache-Control', 'no-cache, no-store, must-revalidate')
|
||||
response.end(guide)
|
||||
})
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
export default defineConfig({
|
||||
base: validateBasePath(process.env.VITE_BASE_PATH ?? '/'),
|
||||
plugins: [react()],
|
||||
base: basePath,
|
||||
plugins: [installGuideDevPlugin(), react()],
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': path.resolve(__dirname, './src'),
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue