mirror of
https://github.com/iflytek/skillhub.git
synced 2026-09-22 00:31:21 +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)
75 lines
2.7 KiB
TypeScript
75 lines
2.7 KiB
TypeScript
import { ConfigStore } from '../stores/config-store'
|
|
import { CredentialsStore } from '../stores/credentials-store'
|
|
import { SkillHubClient } from '../clients/skillhub-client'
|
|
import { resolveRegistry, resolveToken } from '../services/registry-service'
|
|
import { removeLocalSkill } from '../services/remove-service'
|
|
import { CliError } from '../shared/errors'
|
|
import { EXIT } from '../shared/constants'
|
|
|
|
export interface RemoveCommandOptions {
|
|
agent?: string[] | undefined
|
|
all?: boolean | undefined
|
|
remote?: boolean | undefined
|
|
hard?: boolean | undefined
|
|
namespace?: string | undefined
|
|
registry?: string | undefined
|
|
token?: string | undefined
|
|
json?: boolean | undefined
|
|
}
|
|
|
|
export async function removeCommand(slug: string, options: RemoveCommandOptions): Promise<string> {
|
|
if (options.all && options.agent?.length) {
|
|
throw new CliError('--all cannot be used with --agent', EXIT.usage)
|
|
}
|
|
if (options.remote && (options.agent?.length || options.all)) {
|
|
throw new CliError('--remote cannot be used with --agent or --all', EXIT.usage)
|
|
}
|
|
|
|
const configStore = new ConfigStore()
|
|
const credentialsStore = new CredentialsStore()
|
|
const registry = resolveRegistry(options, process.env, await configStore.read())
|
|
|
|
if (options.remote) {
|
|
const token = resolveToken(options, process.env, await credentialsStore.getToken(registry))
|
|
const namespace = options.namespace ?? 'global'
|
|
|
|
if (!options.hard && process.stdout.isTTY) {
|
|
const prompts = await import('prompts')
|
|
const { confirm } = await prompts.default({
|
|
type: 'confirm',
|
|
name: 'confirm',
|
|
message: `Delete remote skill ${namespace}/${slug}?`,
|
|
initial: false
|
|
})
|
|
if (!confirm) {
|
|
throw new CliError('remote delete cancelled', EXIT.generic)
|
|
}
|
|
} else if (!options.hard && !process.stdout.isTTY) {
|
|
throw new CliError('non-interactive remote delete requires --hard', EXIT.usage)
|
|
}
|
|
|
|
const client = new SkillHubClient(registry, token)
|
|
await client.deleteRemote(namespace, slug)
|
|
|
|
if (options.json) {
|
|
return JSON.stringify({ ok: true, scope: 'remote', action: 'hard-delete', namespace, slug })
|
|
}
|
|
return `Removed remote skill: ${namespace}/${slug}\nAction: remote-hard-delete`
|
|
}
|
|
|
|
// Local remove
|
|
const result = await removeLocalSkill({
|
|
registry, slug,
|
|
agents: options.agent,
|
|
all: options.all
|
|
})
|
|
|
|
if (options.json) {
|
|
return JSON.stringify({ ok: true, scope: 'local', removed: result.removed })
|
|
}
|
|
return result.removed.map(r =>
|
|
r.existed
|
|
? `Removed ${r.namespace}/${slug} from ${r.dir} (${r.agent})`
|
|
: `Cleaned stale record for ${r.namespace}/${slug} at ${r.dir} (${r.agent}, directory already missing)`
|
|
).join('\n')
|
|
}
|