Browse Source

Fix install script

Alvin Unreal 2 months ago
parent
commit
174e2aa0dd
1 changed files with 103 additions and 69 deletions
  1. 103 69
      src/cli/install.ts

+ 103 - 69
src/cli/install.ts

@@ -1,4 +1,5 @@
 import type { InstallArgs, InstallConfig, BooleanArg, DetectedConfig } from "./types"
+import * as readline from "readline"
 import {
   addPluginToOpenCodeConfig,
   writeLiteConfig,
@@ -9,6 +10,59 @@ import {
   detectCurrentConfig,
 } from "./config-manager"
 
+// Line reader for TUI mode that handles both TTY and piped input
+let lineReader: readline.Interface | null = null
+let lineBuffer: string[] = []
+let lineResolvers: ((line: string) => void)[] = []
+
+function initLineReader(): void {
+  if (lineReader) return
+
+  lineReader = readline.createInterface({
+    input: process.stdin,
+    output: process.stdout,
+    terminal: process.stdin.isTTY ?? false,
+  })
+
+  lineReader.on("line", (line) => {
+    if (lineResolvers.length > 0) {
+      const resolve = lineResolvers.shift()!
+      resolve(line)
+    } else {
+      lineBuffer.push(line)
+    }
+  })
+
+  lineReader.on("close", () => {
+    // Resolve any pending readers with empty string
+    while (lineResolvers.length > 0) {
+      const resolve = lineResolvers.shift()!
+      resolve("")
+    }
+  })
+}
+
+async function readLine(): Promise<string> {
+  initLineReader()
+
+  if (lineBuffer.length > 0) {
+    return lineBuffer.shift()!
+  }
+
+  return new Promise((resolve) => {
+    lineResolvers.push(resolve)
+  })
+}
+
+function closeLineReader(): void {
+  if (lineReader) {
+    lineReader.close()
+    lineReader = null
+    lineBuffer = []
+    lineResolvers = []
+  }
+}
+
 const GREEN = "\x1b[32m"
 const BLUE = "\x1b[34m"
 const YELLOW = "\x1b[33m"
@@ -55,6 +109,30 @@ function printWarning(message: string): void {
   console.log(`${SYMBOLS.warn} ${YELLOW}${message}${RESET}`)
 }
 
+async function checkOpenCodeInstalled(): Promise<{ ok: boolean; version?: string }> {
+  const installed = await isOpenCodeInstalled()
+  if (!installed) {
+    printError("OpenCode is not installed on this system.")
+    printInfo("Install it with:")
+    console.log(`     ${BLUE}curl -fsSL https://opencode.ai/install | bash${RESET}`)
+    return { ok: false }
+  }
+  const version = await getOpenCodeVersion()
+  printSuccess(`OpenCode ${version ?? ""} detected`)
+  return { ok: true, version: version ?? undefined }
+}
+
+type StepResult = { success: boolean; error?: string; configPath?: string }
+
+function handleStepResult(result: StepResult, successMsg: string): boolean {
+  if (!result.success) {
+    printError(`Failed: ${result.error}`)
+    return false
+  }
+  printSuccess(`${successMsg} ${SYMBOLS.arrow} ${DIM}${result.configPath}${RESET}`)
+  return true
+}
+
 function formatConfigSummary(config: InstallConfig): string {
   const lines: string[] = []
   lines.push(`${BOLD}Configuration Summary${RESET}`)
@@ -66,26 +144,13 @@ function formatConfigSummary(config: InstallConfig): string {
 }
 
 function validateNonTuiArgs(args: InstallArgs): { valid: boolean; errors: string[] } {
-  const errors: string[] = []
-
-  if (args.antigravity === undefined) {
-    errors.push("--antigravity is required (values: yes, no)")
-  } else if (!["yes", "no"].includes(args.antigravity)) {
-    errors.push(`Invalid --antigravity value: ${args.antigravity} (expected: yes, no)`)
-  }
-
-  if (args.openai === undefined) {
-    errors.push("--openai is required (values: yes, no)")
-  } else if (!["yes", "no"].includes(args.openai)) {
-    errors.push(`Invalid --openai value: ${args.openai} (expected: yes, no)`)
-  }
-
-  if (args.cerebras === undefined) {
-    errors.push("--cerebras is required (values: yes, no)")
-  } else if (!["yes", "no"].includes(args.cerebras)) {
-    errors.push(`Invalid --cerebras value: ${args.cerebras} (expected: yes, no)`)
-  }
-
+  const requiredArgs = ["antigravity", "openai", "cerebras"] as const
+  const errors = requiredArgs.flatMap((key) => {
+    const value = args[key]
+    if (value === undefined) return [`--${key} is required (values: yes, no)`]
+    if (!["yes", "no"].includes(value)) return [`Invalid --${key} value: ${value} (expected: yes, no)`]
+    return []
+  })
   return { valid: errors.length === 0, errors }
 }
 
@@ -109,17 +174,14 @@ function detectedToInitialValues(detected: DetectedConfig): {
   }
 }
 
-async function askYesNo(prompt: string, defaultValue: BooleanArg = "no"): Promise<BooleanArg> {
+async function askYesNo(promptText: string, defaultValue: BooleanArg = "no"): Promise<BooleanArg> {
   const defaultHint = defaultValue === "yes" ? "[Y/n]" : "[y/N]"
-  process.stdout.write(`${BLUE}${prompt}${RESET} ${defaultHint}: `)
-
-  const reader = Bun.stdin.stream().getReader()
-  const { value } = await reader.read()
-  reader.releaseLock()
+  const fullPrompt = `${BLUE}${promptText}${RESET} ${defaultHint}: `
 
-  const answer = value ? new TextDecoder().decode(value).trim().toLowerCase() : ""
+  process.stdout.write(fullPrompt)
+  const answer = (await readLine()).trim().toLowerCase()
 
-  if (answer === "" || answer === "\n") return defaultValue
+  if (answer === "") return defaultValue
   if (answer === "y" || answer === "yes") return "yes"
   if (answer === "n" || answer === "no") return "no"
   return defaultValue
@@ -143,6 +205,8 @@ async function runTuiMode(detected: DetectedConfig): Promise<InstallConfig | nul
   const cerebras = await askYesNo("Do you have access to Cerebras API?", initial.cerebras)
   console.log()
 
+  closeLineReader()
+
   return {
     hasAntigravity: antigravity === "yes",
     hasOpenAI: openai === "yes",
@@ -150,7 +214,7 @@ async function runTuiMode(detected: DetectedConfig): Promise<InstallConfig | nul
   }
 }
 
-async function runInstall(args: InstallArgs, config: InstallConfig): Promise<number> {
+async function runInstall(config: InstallConfig): Promise<number> {
   const detected = detectCurrentConfig()
   const isUpdate = detected.isInstalled
 
@@ -161,52 +225,29 @@ async function runInstall(args: InstallArgs, config: InstallConfig): Promise<num
 
   // Step 1: Check OpenCode
   printStep(step++, totalSteps, "Checking OpenCode installation...")
-  const installed = await isOpenCodeInstalled()
-  if (!installed) {
-    printError("OpenCode is not installed on this system.")
-    printInfo("Visit https://opencode.ai/docs for installation instructions")
-    return 1
-  }
-
-  const version = await getOpenCodeVersion()
-  printSuccess(`OpenCode ${version ?? ""} detected`)
+  const { ok } = await checkOpenCodeInstalled()
+  if (!ok) return 1
 
   // Step 2: Add plugin
   printStep(step++, totalSteps, "Adding oh-my-opencode-slim plugin...")
   const pluginResult = await addPluginToOpenCodeConfig()
-  if (!pluginResult.success) {
-    printError(`Failed: ${pluginResult.error}`)
-    return 1
-  }
-  printSuccess(`Plugin added ${SYMBOLS.arrow} ${DIM}${pluginResult.configPath}${RESET}`)
+  if (!handleStepResult(pluginResult, "Plugin added")) return 1
 
   // Step 3-4: Auth plugins and provider config (if Antigravity)
   if (config.hasAntigravity) {
     printStep(step++, totalSteps, "Adding auth plugins...")
     const authResult = await addAuthPlugins(config)
-    if (!authResult.success) {
-      printError(`Failed: ${authResult.error}`)
-      return 1
-    }
-    printSuccess(`Auth plugins configured ${SYMBOLS.arrow} ${DIM}${authResult.configPath}${RESET}`)
+    if (!handleStepResult(authResult, "Auth plugins configured")) return 1
 
     printStep(step++, totalSteps, "Adding provider configurations...")
     const providerResult = addProviderConfig(config)
-    if (!providerResult.success) {
-      printError(`Failed: ${providerResult.error}`)
-      return 1
-    }
-    printSuccess(`Providers configured ${SYMBOLS.arrow} ${DIM}${providerResult.configPath}${RESET}`)
+    if (!handleStepResult(providerResult, "Providers configured")) return 1
   }
 
   // Step 5: Write lite config
   printStep(step++, totalSteps, "Writing oh-my-opencode-slim configuration...")
   const liteResult = writeLiteConfig(config)
-  if (!liteResult.success) {
-    printError(`Failed: ${liteResult.error}`)
-    return 1
-  }
-  printSuccess(`Config written ${SYMBOLS.arrow} ${DIM}${liteResult.configPath}${RESET}`)
+  if (!handleStepResult(liteResult, "Config written")) return 1
 
   // Summary
   console.log()
@@ -251,7 +292,7 @@ export async function install(args: InstallArgs): Promise<number> {
     }
 
     const config = argsToConfig(args)
-    return runInstall(args, config)
+    return runInstall(config)
   }
 
   // TUI mode
@@ -260,19 +301,12 @@ export async function install(args: InstallArgs): Promise<number> {
   printHeader(detected.isInstalled)
 
   printStep(1, 1, "Checking OpenCode installation...")
-  const installed = await isOpenCodeInstalled()
-  if (!installed) {
-    printError("OpenCode is not installed on this system.")
-    printInfo("Visit https://opencode.ai/docs for installation instructions")
-    return 1
-  }
-
-  const version = await getOpenCodeVersion()
-  printSuccess(`OpenCode ${version ?? ""} detected`)
+  const { ok } = await checkOpenCodeInstalled()
+  if (!ok) return 1
   console.log()
 
   const config = await runTuiMode(detected)
   if (!config) return 1
 
-  return runInstall(args, config)
+  return runInstall(config)
 }