mirror of
https://github.com/iflytek/skillhub.git
synced 2026-08-28 11:25:00 +00:00
Implement complete CLI tool for SkillHub with 12 commands, 7 backend API endpoints, and comprehensive documentation. CLI Commands: - help, version: Basic information - login, logout, whoami: Authentication management - search: Discover published skills - install: Install skills to agent directories (14 Tier 1 agents supported) - list, remove, doctor: Local skill management - publish: Publish skill packages - update: Self-update mechanism Backend API: - Add /api/cli/v1 endpoints for auth, search, resolve, download, delete, publish - Implement CliAuthController and CliSkillController - Add security policies for CLI routes - Full test coverage (19 backend tests) CLI Implementation: - TypeScript with strict mode, Bun runtime - Pure JS zip handling (fflate) for cross-platform compatibility - 15 agent profiles (14 Tier 1 + generic fallback) - Secure token storage (0600 permissions) - Path safety validation for remove operations - Comprehensive error handling (404/403/network distinction) - 41 unit and integration tests Documentation: - CLI user guide (Chinese and English) - README updates with quick start - GitHub Actions workflow for cross-platform CI Quality: - lint: 0 errors - typecheck: pass - test: 41/41 pass - build: 0.30 MB (target=node for npm/npx compatibility)
92 lines
3 KiB
TypeScript
92 lines
3 KiB
TypeScript
import { describe, expect, test } from 'bun:test'
|
|
import { resolveInstallTargets } from '../../../src/agents/resolver'
|
|
|
|
describe('resolveInstallTargets', () => {
|
|
test('rejects dir and agent together before filesystem writes', async () => {
|
|
await expect(resolveInstallTargets({
|
|
cwd: '/repo',
|
|
dir: '/tmp/skills',
|
|
agents: ['codex'],
|
|
json: false,
|
|
interactive: false
|
|
})).rejects.toThrow('--dir cannot be used with --agent')
|
|
})
|
|
|
|
test('falls back to cwd .agents skills when nothing detected', async () => {
|
|
const targets = await resolveInstallTargets({
|
|
cwd: '/repo',
|
|
agents: [],
|
|
json: false,
|
|
interactive: false,
|
|
detected: []
|
|
})
|
|
expect(targets).toEqual([{ agent: 'generic', rootDir: '/repo/.agents/skills', scope: 'project', source: 'fallback' }])
|
|
})
|
|
|
|
test('uses explicit dir when provided', async () => {
|
|
const targets = await resolveInstallTargets({
|
|
cwd: '/repo',
|
|
dir: '/tmp/my-skills',
|
|
json: false,
|
|
interactive: false
|
|
})
|
|
expect(targets).toEqual([{ agent: 'custom', rootDir: '/tmp/my-skills', scope: 'user', source: 'explicit' }])
|
|
})
|
|
|
|
test('explicit agent resolves the profile user root by default', async () => {
|
|
const targets = await resolveInstallTargets({
|
|
cwd: '/repo',
|
|
home: '/home/u',
|
|
agents: ['codex'],
|
|
json: false,
|
|
interactive: false
|
|
})
|
|
expect(targets).toEqual([{ agent: 'codex', rootDir: '/home/u/.codex/skills', scope: 'user', source: 'explicit' }])
|
|
})
|
|
|
|
test('deduplicates repeated explicit agents by target root', async () => {
|
|
const targets = await resolveInstallTargets({
|
|
cwd: '/repo',
|
|
home: '/home/u',
|
|
agents: ['codex', 'codex'],
|
|
json: false,
|
|
interactive: false
|
|
})
|
|
expect(targets).toHaveLength(1)
|
|
expect(targets[0]!.rootDir).toBe('/home/u/.codex/skills')
|
|
})
|
|
|
|
test('returns single detected target directly', async () => {
|
|
const targets = await resolveInstallTargets({
|
|
cwd: '/repo',
|
|
agents: [],
|
|
json: false,
|
|
interactive: false,
|
|
detected: [{ agent: 'codex', rootDir: '/repo/.codex/skills', scope: 'project', source: 'detected' }]
|
|
})
|
|
expect(targets).toHaveLength(1)
|
|
expect(targets[0]!.agent).toBe('codex')
|
|
})
|
|
|
|
test('rejects multiple detected targets in non-interactive mode', async () => {
|
|
await expect(resolveInstallTargets({
|
|
cwd: '/repo',
|
|
agents: [],
|
|
json: false,
|
|
interactive: false,
|
|
detected: [
|
|
{ agent: 'codex', rootDir: '/repo/.codex/skills', scope: 'project', source: 'detected' },
|
|
{ agent: 'claude-code', rootDir: '/repo/.claude/skills', scope: 'project', source: 'detected' }
|
|
]
|
|
})).rejects.toThrow('multiple install targets detected')
|
|
})
|
|
|
|
test('rejects unknown agent', async () => {
|
|
await expect(resolveInstallTargets({
|
|
cwd: '/repo',
|
|
agents: ['unknown-agent'],
|
|
json: false,
|
|
interactive: false
|
|
})).rejects.toThrow('unknown agent: unknown-agent')
|
|
})
|
|
})
|