Install pipeline: - cli-installer.ts, tui-installer.ts, tui-install-prompts.ts - install-validators.ts Model fallback: - model-fallback-types.ts, fallback-chain-resolution.ts - provider-availability.ts, provider-model-id-transform.ts
190 lines
6.2 KiB
TypeScript
190 lines
6.2 KiB
TypeScript
import color from "picocolors"
|
|
import type {
|
|
BooleanArg,
|
|
ClaudeSubscription,
|
|
DetectedConfig,
|
|
InstallArgs,
|
|
InstallConfig,
|
|
} from "./types"
|
|
|
|
export const SYMBOLS = {
|
|
check: color.green("[OK]"),
|
|
cross: color.red("[X]"),
|
|
arrow: color.cyan("->"),
|
|
bullet: color.dim("*"),
|
|
info: color.blue("[i]"),
|
|
warn: color.yellow("[!]"),
|
|
star: color.yellow("*"),
|
|
}
|
|
|
|
function formatProvider(name: string, enabled: boolean, detail?: string): string {
|
|
const status = enabled ? SYMBOLS.check : color.dim("○")
|
|
const label = enabled ? color.white(name) : color.dim(name)
|
|
const suffix = detail ? color.dim(` (${detail})`) : ""
|
|
return ` ${status} ${label}${suffix}`
|
|
}
|
|
|
|
export function formatConfigSummary(config: InstallConfig): string {
|
|
const lines: string[] = []
|
|
|
|
lines.push(color.bold(color.white("Configuration Summary")))
|
|
lines.push("")
|
|
|
|
const claudeDetail = config.hasClaude ? (config.isMax20 ? "max20" : "standard") : undefined
|
|
lines.push(formatProvider("Claude", config.hasClaude, claudeDetail))
|
|
lines.push(formatProvider("OpenAI/ChatGPT", config.hasOpenAI, "GPT-5.2 for Oracle"))
|
|
lines.push(formatProvider("Gemini", config.hasGemini))
|
|
lines.push(formatProvider("GitHub Copilot", config.hasCopilot, "fallback"))
|
|
lines.push(formatProvider("OpenCode Zen", config.hasOpencodeZen, "opencode/ models"))
|
|
lines.push(formatProvider("Z.ai Coding Plan", config.hasZaiCodingPlan, "Librarian/Multimodal"))
|
|
lines.push(formatProvider("Kimi For Coding", config.hasKimiForCoding, "Sisyphus/Prometheus fallback"))
|
|
|
|
lines.push("")
|
|
lines.push(color.dim("─".repeat(40)))
|
|
lines.push("")
|
|
|
|
lines.push(color.bold(color.white("Model Assignment")))
|
|
lines.push("")
|
|
lines.push(` ${SYMBOLS.info} Models auto-configured based on provider priority`)
|
|
lines.push(` ${SYMBOLS.bullet} Priority: Native > Copilot > OpenCode Zen > Z.ai`)
|
|
|
|
return lines.join("\n")
|
|
}
|
|
|
|
export function printHeader(isUpdate: boolean): void {
|
|
const mode = isUpdate ? "Update" : "Install"
|
|
console.log()
|
|
console.log(color.bgMagenta(color.white(` oMoMoMoMo... ${mode} `)))
|
|
console.log()
|
|
}
|
|
|
|
export function printStep(step: number, total: number, message: string): void {
|
|
const progress = color.dim(`[${step}/${total}]`)
|
|
console.log(`${progress} ${message}`)
|
|
}
|
|
|
|
export function printSuccess(message: string): void {
|
|
console.log(`${SYMBOLS.check} ${message}`)
|
|
}
|
|
|
|
export function printError(message: string): void {
|
|
console.log(`${SYMBOLS.cross} ${color.red(message)}`)
|
|
}
|
|
|
|
export function printInfo(message: string): void {
|
|
console.log(`${SYMBOLS.info} ${message}`)
|
|
}
|
|
|
|
export function printWarning(message: string): void {
|
|
console.log(`${SYMBOLS.warn} ${color.yellow(message)}`)
|
|
}
|
|
|
|
export function printBox(content: string, title?: string): void {
|
|
const lines = content.split("\n")
|
|
const maxWidth =
|
|
Math.max(
|
|
...lines.map((line) => line.replace(/\x1b\[[0-9;]*m/g, "").length),
|
|
title?.length ?? 0,
|
|
) + 4
|
|
const border = color.dim("─".repeat(maxWidth))
|
|
|
|
console.log()
|
|
if (title) {
|
|
console.log(
|
|
color.dim("┌─") +
|
|
color.bold(` ${title} `) +
|
|
color.dim("─".repeat(maxWidth - title.length - 4)) +
|
|
color.dim("┐"),
|
|
)
|
|
} else {
|
|
console.log(color.dim("┌") + border + color.dim("┐"))
|
|
}
|
|
|
|
for (const line of lines) {
|
|
const stripped = line.replace(/\x1b\[[0-9;]*m/g, "")
|
|
const padding = maxWidth - stripped.length
|
|
console.log(color.dim("│") + ` ${line}${" ".repeat(padding - 1)}` + color.dim("│"))
|
|
}
|
|
|
|
console.log(color.dim("└") + border + color.dim("┘"))
|
|
console.log()
|
|
}
|
|
|
|
export function validateNonTuiArgs(args: InstallArgs): { valid: boolean; errors: string[] } {
|
|
const errors: string[] = []
|
|
|
|
if (args.claude === undefined) {
|
|
errors.push("--claude is required (values: no, yes, max20)")
|
|
} else if (!["no", "yes", "max20"].includes(args.claude)) {
|
|
errors.push(`Invalid --claude value: ${args.claude} (expected: no, yes, max20)`)
|
|
}
|
|
|
|
if (args.gemini === undefined) {
|
|
errors.push("--gemini is required (values: no, yes)")
|
|
} else if (!["no", "yes"].includes(args.gemini)) {
|
|
errors.push(`Invalid --gemini value: ${args.gemini} (expected: no, yes)`)
|
|
}
|
|
|
|
if (args.copilot === undefined) {
|
|
errors.push("--copilot is required (values: no, yes)")
|
|
} else if (!["no", "yes"].includes(args.copilot)) {
|
|
errors.push(`Invalid --copilot value: ${args.copilot} (expected: no, yes)`)
|
|
}
|
|
|
|
if (args.openai !== undefined && !["no", "yes"].includes(args.openai)) {
|
|
errors.push(`Invalid --openai value: ${args.openai} (expected: no, yes)`)
|
|
}
|
|
|
|
if (args.opencodeZen !== undefined && !["no", "yes"].includes(args.opencodeZen)) {
|
|
errors.push(`Invalid --opencode-zen value: ${args.opencodeZen} (expected: no, yes)`)
|
|
}
|
|
|
|
if (args.zaiCodingPlan !== undefined && !["no", "yes"].includes(args.zaiCodingPlan)) {
|
|
errors.push(`Invalid --zai-coding-plan value: ${args.zaiCodingPlan} (expected: no, yes)`)
|
|
}
|
|
|
|
if (args.kimiForCoding !== undefined && !["no", "yes"].includes(args.kimiForCoding)) {
|
|
errors.push(`Invalid --kimi-for-coding value: ${args.kimiForCoding} (expected: no, yes)`)
|
|
}
|
|
|
|
return { valid: errors.length === 0, errors }
|
|
}
|
|
|
|
export function argsToConfig(args: InstallArgs): InstallConfig {
|
|
return {
|
|
hasClaude: args.claude !== "no",
|
|
isMax20: args.claude === "max20",
|
|
hasOpenAI: args.openai === "yes",
|
|
hasGemini: args.gemini === "yes",
|
|
hasCopilot: args.copilot === "yes",
|
|
hasOpencodeZen: args.opencodeZen === "yes",
|
|
hasZaiCodingPlan: args.zaiCodingPlan === "yes",
|
|
hasKimiForCoding: args.kimiForCoding === "yes",
|
|
}
|
|
}
|
|
|
|
export function detectedToInitialValues(detected: DetectedConfig): {
|
|
claude: ClaudeSubscription
|
|
openai: BooleanArg
|
|
gemini: BooleanArg
|
|
copilot: BooleanArg
|
|
opencodeZen: BooleanArg
|
|
zaiCodingPlan: BooleanArg
|
|
kimiForCoding: BooleanArg
|
|
} {
|
|
let claude: ClaudeSubscription = "no"
|
|
if (detected.hasClaude) {
|
|
claude = detected.isMax20 ? "max20" : "yes"
|
|
}
|
|
|
|
return {
|
|
claude,
|
|
openai: detected.hasOpenAI ? "yes" : "no",
|
|
gemini: detected.hasGemini ? "yes" : "no",
|
|
copilot: detected.hasCopilot ? "yes" : "no",
|
|
opencodeZen: detected.hasOpencodeZen ? "yes" : "no",
|
|
zaiCodingPlan: detected.hasZaiCodingPlan ? "yes" : "no",
|
|
kimiForCoding: detected.hasKimiForCoding ? "yes" : "no",
|
|
}
|
|
}
|