mirror of
https://github.com/iflytek/skillhub.git
synced 2026-09-24 00:55:35 +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)
57 lines
1.8 KiB
TypeScript
57 lines
1.8 KiB
TypeScript
import { stat } from 'node:fs/promises'
|
|
import { ConfigStore } from '../stores/config-store'
|
|
import { InventoryStore } from '../stores/inventory-store'
|
|
import { resolveRegistry } from '../services/registry-service'
|
|
|
|
export interface ListCommandOptions {
|
|
agent?: string[] | undefined
|
|
dir?: string | undefined
|
|
registry?: string | undefined
|
|
json?: boolean | undefined
|
|
}
|
|
|
|
export async function listCommand(options: ListCommandOptions): Promise<string> {
|
|
const configStore = new ConfigStore()
|
|
const registry = resolveRegistry(options, process.env, await configStore.read())
|
|
const store = new InventoryStore()
|
|
const inventory = await store.read()
|
|
|
|
// Flatten targets
|
|
type FlatTarget = { namespace: string; slug: string; version: string; agent: string; installDir: string; installedAt: string; status: string }
|
|
const flat: FlatTarget[] = []
|
|
|
|
for (const item of inventory.items) {
|
|
if (item.registry !== registry) continue
|
|
for (const target of item.targets) {
|
|
if (options.agent?.length && !options.agent.includes(target.agent)) continue
|
|
if (options.dir && !target.installDir.startsWith(options.dir)) continue
|
|
|
|
let status = 'ok'
|
|
try {
|
|
await stat(target.installDir)
|
|
} catch {
|
|
status = 'missing'
|
|
}
|
|
|
|
flat.push({
|
|
namespace: item.namespace,
|
|
slug: item.slug,
|
|
version: item.version,
|
|
agent: target.agent,
|
|
installDir: target.installDir,
|
|
installedAt: target.installedAt,
|
|
status
|
|
})
|
|
}
|
|
}
|
|
|
|
if (options.json) {
|
|
return JSON.stringify({ ok: true, items: flat })
|
|
}
|
|
|
|
if (flat.length === 0) return 'No skills installed.'
|
|
|
|
return flat.map(t =>
|
|
`${t.namespace}/${t.slug}@${t.version} ${t.agent} ${t.installDir} ${t.installedAt} ${t.status}`
|
|
).join('\n')
|
|
}
|