mirror of
https://github.com/iflytek/skillhub.git
synced 2026-10-15 04:18:20 +00:00
121 lines
4.1 KiB
TypeScript
121 lines
4.1 KiB
TypeScript
import { ConfigStore } from '../stores/config-store'
|
|
import { CredentialsStore } from '../stores/credentials-store'
|
|
import { resolveRegistry, resolveToken } from '../services/registry-service'
|
|
import { installSkill } from '../services/install-service'
|
|
import { resolveInstallTargets } from '../agents/resolver'
|
|
import { CliError } from '../shared/errors'
|
|
import { EXIT } from '../shared/constants'
|
|
import { resolveSkillName } from '../shared/skill-name-parser'
|
|
|
|
export interface InstallCommandOptions {
|
|
namespace?: string | undefined
|
|
version?: string | undefined
|
|
agent?: string[] | undefined
|
|
dir?: string | undefined
|
|
scope?: string | undefined
|
|
force?: boolean | undefined
|
|
registry?: string | undefined
|
|
token?: string | undefined
|
|
json?: boolean | undefined
|
|
}
|
|
|
|
export interface InstallCommandDeps {
|
|
promptScope?: () => Promise<'user' | 'project'>
|
|
resolveInstallTargets?: typeof resolveInstallTargets
|
|
installSkill?: typeof installSkill
|
|
isTTY?: () => boolean
|
|
}
|
|
|
|
export function computeStrictIsTTY(env: {
|
|
stdinIsTTY: boolean
|
|
stdoutIsTTY: boolean
|
|
json: boolean
|
|
}): boolean {
|
|
return env.stdinIsTTY && env.stdoutIsTTY && !env.json
|
|
}
|
|
|
|
export async function resolveEffectiveScope(
|
|
options: InstallCommandOptions,
|
|
env: { isTTY: boolean; promptScope: () => Promise<'user' | 'project'> }
|
|
): Promise<'user' | 'project' | undefined> {
|
|
if (options.scope !== undefined && options.scope !== 'user' && options.scope !== 'project') {
|
|
throw new CliError('--scope must be "user" or "project"', EXIT.usage)
|
|
}
|
|
const scope = options.scope as 'user' | 'project' | undefined
|
|
const agentList = options.agent ?? []
|
|
|
|
if (options.dir && scope !== undefined) {
|
|
throw new CliError('--dir cannot be used with --scope', EXIT.usage)
|
|
}
|
|
if (options.dir && agentList.length > 0) {
|
|
throw new CliError('--dir cannot be used with --agent', EXIT.usage)
|
|
}
|
|
|
|
if (scope !== undefined) return scope
|
|
if (options.dir || agentList.length > 0) return undefined
|
|
if (env.isTTY) return await env.promptScope()
|
|
return undefined
|
|
}
|
|
|
|
async function defaultPromptScope(): Promise<'user' | 'project'> {
|
|
const prompts = await import('prompts')
|
|
const { scope } = await prompts.default({
|
|
type: 'select',
|
|
name: 'scope',
|
|
message: 'Install for user or project?',
|
|
choices: [
|
|
{ title: 'User (install to user-level agent directory)', value: 'user' },
|
|
{ title: 'Project (install to project-level agent directory)', value: 'project' }
|
|
]
|
|
})
|
|
if (!scope) {
|
|
throw new CliError('installation cancelled', EXIT.usage)
|
|
}
|
|
return scope
|
|
}
|
|
|
|
export async function installCommand(
|
|
skillNameArg: string,
|
|
options: InstallCommandOptions,
|
|
deps: InstallCommandDeps = {}
|
|
): Promise<string> {
|
|
const isTTYFn = deps.isTTY ?? (() => computeStrictIsTTY({
|
|
stdinIsTTY: process.stdin.isTTY === true,
|
|
stdoutIsTTY: process.stdout.isTTY === true,
|
|
json: Boolean(options.json)
|
|
}))
|
|
const isTTY = isTTYFn()
|
|
|
|
const promptScope = deps.promptScope ?? defaultPromptScope
|
|
const effectiveScope = await resolveEffectiveScope(options, { isTTY, promptScope })
|
|
|
|
const configStore = new ConfigStore()
|
|
const credentialsStore = new CredentialsStore()
|
|
const registry = resolveRegistry(options, process.env, await configStore.read())
|
|
const token = resolveToken(options, process.env, await credentialsStore.getToken(registry))
|
|
|
|
const { namespace, slug } = resolveSkillName(skillNameArg, options.namespace)
|
|
|
|
const resolveTargets = deps.resolveInstallTargets ?? resolveInstallTargets
|
|
const targets = await resolveTargets({
|
|
cwd: process.cwd(),
|
|
scope: effectiveScope,
|
|
dir: options.dir,
|
|
agents: options.agent ?? [],
|
|
json: Boolean(options.json),
|
|
interactive: isTTY
|
|
})
|
|
|
|
const installFn = deps.installSkill ?? installSkill
|
|
const result = await installFn({
|
|
registry, token, namespace, slug,
|
|
version: options.version,
|
|
targets,
|
|
force: Boolean(options.force)
|
|
})
|
|
|
|
if (options.json) {
|
|
return JSON.stringify({ ok: true, namespace, slug, installed: result.installed })
|
|
}
|
|
return result.installed.map(i => `Installed ${namespace}/${slug} -> ${i.dir} (${i.agent})`).join('\n')
|
|
}
|