Browse Source

feat: Add Opencode Zen support (#30)

* refactor: simplify provider setup

- Remove cerebras from interactive prompts and CLI args
- Zen free models now enabled by default (no prompt needed)
- Updated priority: antigravity > openai > zen (fallback)
- Users can still manually configure cerebras in config file if needed

* refactor: remove --zen flag, always enable free models

Address PR review comments:
- Remove --zen CLI flag since free models are always available
- hasOpencodeZen is now always true by default
- Users can manually configure premium models in their config
- Simplify non-interactive mode to require only 3 flags

* chore: restore comments deleted from upstream/master

Restore section comments:
- // Colors
- // TODO: tmux has a bug (in runInteractiveMode)
- // Calculate total steps dynamically
- // Summary
- // Interactive mode
Abhideep Maity 2 months ago
parent
commit
66cfe931fa
4 changed files with 27 additions and 33 deletions
  1. 14 11
      src/cli/config-manager.ts
  2. 1 4
      src/cli/index.ts
  3. 10 15
      src/cli/install.ts
  4. 2 3
      src/cli/types.ts

+ 14 - 11
src/cli/config-manager.ts

@@ -177,6 +177,7 @@ export async function addPluginToOpenCodeConfig(): Promise<ConfigMergeResult> {
     let config = parseConfig(configPath) ?? {}
     let config = parseConfig(configPath) ?? {}
     const plugins = config.plugin ?? []
     const plugins = config.plugin ?? []
 
 
+
     // Remove existing oh-my-opencode-slim entries
     // Remove existing oh-my-opencode-slim entries
     const filteredPlugins = plugins.filter(
     const filteredPlugins = plugins.filter(
       (p) => p !== PACKAGE_NAME && !p.startsWith(`${PACKAGE_NAME}@`)
       (p) => p !== PACKAGE_NAME && !p.startsWith(`${PACKAGE_NAME}@`)
@@ -341,17 +342,24 @@ const MODEL_MAPPINGS = {
     explorer: "cerebras/zai-glm-4.7",
     explorer: "cerebras/zai-glm-4.7",
     designer: "cerebras/zai-glm-4.7",
     designer: "cerebras/zai-glm-4.7",
   },
   },
+  opencode: {
+    orchestrator: "opencode/glm-4.7-free",
+    oracle: "opencode/glm-4.7-free",
+    librarian: "opencode/glm-4.7-free",
+    explorer: "opencode/glm-4.7-free",
+    designer: "opencode/glm-4.7-free",
+  },
 } as const;
 } as const;
 
 
 export function generateLiteConfig(installConfig: InstallConfig): Record<string, unknown> {
 export function generateLiteConfig(installConfig: InstallConfig): Record<string, unknown> {
-  // Determine base provider
+  // Priority: antigravity > openai > opencode (Zen free models)
   const baseProvider = installConfig.hasAntigravity
   const baseProvider = installConfig.hasAntigravity
     ? "antigravity"
     ? "antigravity"
     : installConfig.hasOpenAI
     : installConfig.hasOpenAI
       ? "openai"
       ? "openai"
-      : installConfig.hasCerebras
-        ? "cerebras"
-        : null;
+      : installConfig.hasOpencodeZen
+        ? "opencode"
+        : "opencode"; // Default to Zen free models
 
 
   const config: Record<string, unknown> = { agents: {} };
   const config: Record<string, unknown> = { agents: {} };
 
 
@@ -369,11 +377,6 @@ export function generateLiteConfig(installConfig: InstallConfig): Record<string,
       if (installConfig.hasOpenAI) {
       if (installConfig.hasOpenAI) {
         agents["oracle"] = { model: "openai/gpt-5.2-codex", skills: DEFAULT_AGENT_SKILLS["oracle"] ?? [] };
         agents["oracle"] = { model: "openai/gpt-5.2-codex", skills: DEFAULT_AGENT_SKILLS["oracle"] ?? [] };
       }
       }
-      if (installConfig.hasCerebras) {
-        agents["explorer"] = { model: "cerebras/zai-glm-4.7", skills: DEFAULT_AGENT_SKILLS["explorer"] ?? [] };
-      }
-    } else if (installConfig.hasOpenAI && installConfig.hasCerebras) {
-      agents["explorer"] = { model: "cerebras/zai-glm-4.7", skills: DEFAULT_AGENT_SKILLS["explorer"] ?? [] };
     }
     }
     config.agents = agents;
     config.agents = agents;
   }
   }
@@ -437,7 +440,7 @@ export function detectCurrentConfig(): DetectedConfig {
     isInstalled: false,
     isInstalled: false,
     hasAntigravity: false,
     hasAntigravity: false,
     hasOpenAI: false,
     hasOpenAI: false,
-    hasCerebras: false,
+    hasOpencodeZen: false,
     hasTmux: false,
     hasTmux: false,
   }
   }
 
 
@@ -459,7 +462,7 @@ export function detectCurrentConfig(): DetectedConfig {
         .map((a) => a?.model)
         .map((a) => a?.model)
         .filter(Boolean)
         .filter(Boolean)
       result.hasOpenAI = models.some((m) => m?.startsWith("openai/"))
       result.hasOpenAI = models.some((m) => m?.startsWith("openai/"))
-      result.hasCerebras = models.some((m) => m?.startsWith("cerebras/"))
+      result.hasOpencodeZen = models.some((m) => m?.startsWith("opencode/"))
     }
     }
 
 
     if (configObj.tmux && typeof configObj.tmux === "object") {
     if (configObj.tmux && typeof configObj.tmux === "object") {

+ 1 - 4
src/cli/index.ts

@@ -16,8 +16,6 @@ function parseArgs(args: string[]): InstallArgs {
       result.antigravity = arg.split("=")[1] as BooleanArg
       result.antigravity = arg.split("=")[1] as BooleanArg
     } else if (arg.startsWith("--openai=")) {
     } else if (arg.startsWith("--openai=")) {
       result.openai = arg.split("=")[1] as BooleanArg
       result.openai = arg.split("=")[1] as BooleanArg
-    } else if (arg.startsWith("--cerebras=")) {
-      result.cerebras = arg.split("=")[1] as BooleanArg
     } else if (arg.startsWith("--tmux=")) {
     } else if (arg.startsWith("--tmux=")) {
       result.tmux = arg.split("=")[1] as BooleanArg
       result.tmux = arg.split("=")[1] as BooleanArg
     } else if (arg === "-h" || arg === "--help") {
     } else if (arg === "-h" || arg === "--help") {
@@ -38,7 +36,6 @@ Usage: bunx oh-my-opencode-slim install [OPTIONS]
 Options:
 Options:
   --antigravity=yes|no   Antigravity subscription (yes/no)
   --antigravity=yes|no   Antigravity subscription (yes/no)
   --openai=yes|no        OpenAI API access (yes/no)
   --openai=yes|no        OpenAI API access (yes/no)
-  --cerebras=yes|no      Cerebras API access (yes/no)
   --tmux=yes|no          Enable tmux integration (yes/no)
   --tmux=yes|no          Enable tmux integration (yes/no)
   --no-tui               Non-interactive mode (requires all flags)
   --no-tui               Non-interactive mode (requires all flags)
   --skip-auth            Skip authentication reminder
   --skip-auth            Skip authentication reminder
@@ -46,7 +43,7 @@ Options:
 
 
 Examples:
 Examples:
   bunx oh-my-opencode-slim install
   bunx oh-my-opencode-slim install
-  bunx oh-my-opencode-slim install --no-tui --antigravity=yes --openai=yes --cerebras=no --tmux=yes
+  bunx oh-my-opencode-slim install --no-tui --antigravity=yes --openai=yes --tmux=no
 `)
 `)
 }
 }
 
 

+ 10 - 15
src/cli/install.ts

@@ -90,7 +90,7 @@ function formatConfigSummary(config: InstallConfig): string {
   lines.push("")
   lines.push("")
   lines.push(`  ${config.hasAntigravity ? SYMBOLS.check : DIM + "○" + RESET} Antigravity`)
   lines.push(`  ${config.hasAntigravity ? SYMBOLS.check : DIM + "○" + RESET} Antigravity`)
   lines.push(`  ${config.hasOpenAI ? SYMBOLS.check : DIM + "○" + RESET} OpenAI`)
   lines.push(`  ${config.hasOpenAI ? SYMBOLS.check : DIM + "○" + RESET} OpenAI`)
-  lines.push(`  ${config.hasCerebras ? SYMBOLS.check : DIM + "○" + RESET} Cerebras`)
+  lines.push(`  ${SYMBOLS.check} Opencode Zen (free models)`) // Always enabled
   lines.push(`  ${config.hasTmux ? SYMBOLS.check : DIM + "○" + RESET} Tmux Integration`)
   lines.push(`  ${config.hasTmux ? SYMBOLS.check : DIM + "○" + RESET} Tmux Integration`)
   return lines.join("\n")
   return lines.join("\n")
 }
 }
@@ -118,7 +118,7 @@ function argsToConfig(args: InstallArgs): InstallConfig {
   return {
   return {
     hasAntigravity: args.antigravity === "yes",
     hasAntigravity: args.antigravity === "yes",
     hasOpenAI: args.openai === "yes",
     hasOpenAI: args.openai === "yes",
-    hasCerebras: args.cerebras === "yes",
+    hasOpencodeZen: true, // Always enabled - free models available to all users
     hasTmux: args.tmux === "yes",
     hasTmux: args.tmux === "yes",
   }
   }
 }
 }
@@ -141,8 +141,8 @@ async function runInteractiveMode(detected: DetectedConfig): Promise<InstallConf
   const rl = readline.createInterface({ input: process.stdin, output: process.stdout })
   const rl = readline.createInterface({ input: process.stdin, output: process.stdout })
   // TODO: tmux has a bug, disabled for now
   // TODO: tmux has a bug, disabled for now
   // const tmuxInstalled = await isTmuxInstalled()
   // const tmuxInstalled = await isTmuxInstalled()
-  // const totalQuestions = tmuxInstalled ? 4 : 3
-  const totalQuestions = 3
+  // const totalQuestions = tmuxInstalled ? 3 : 2
+  const totalQuestions = 2
 
 
   try {
   try {
     console.log(`${BOLD}Question 1/${totalQuestions}:${RESET}`)
     console.log(`${BOLD}Question 1/${totalQuestions}:${RESET}`)
@@ -154,14 +154,10 @@ async function runInteractiveMode(detected: DetectedConfig): Promise<InstallConf
     const openai = await askYesNo(rl, "Do you have access to OpenAI API?", detected.hasOpenAI ? "yes" : "no")
     const openai = await askYesNo(rl, "Do you have access to OpenAI API?", detected.hasOpenAI ? "yes" : "no")
     console.log()
     console.log()
 
 
-    console.log(`${BOLD}Question 3/${totalQuestions}:${RESET}`)
-    const cerebras = await askYesNo(rl, "Do you have access to Cerebras API?", detected.hasCerebras ? "yes" : "no")
-    console.log()
-
     // TODO: tmux has a bug, disabled for now
     // TODO: tmux has a bug, disabled for now
     // let tmux: BooleanArg = "no"
     // let tmux: BooleanArg = "no"
     // if (tmuxInstalled) {
     // if (tmuxInstalled) {
-    //   console.log(`${BOLD}Question 4/4:${RESET}`)
+    //   console.log(`${BOLD}Question 3/3:${RESET}`)
     //   printInfo(`${BOLD}Tmux detected!${RESET} We can enable tmux integration for you.`)
     //   printInfo(`${BOLD}Tmux detected!${RESET} We can enable tmux integration for you.`)
     //   printInfo("This will spawn new panes for sub-agents, letting you watch them work in real-time.")
     //   printInfo("This will spawn new panes for sub-agents, letting you watch them work in real-time.")
     //   tmux = await askYesNo(rl, "Enable tmux integration?", detected.hasTmux ? "yes" : "no")
     //   tmux = await askYesNo(rl, "Enable tmux integration?", detected.hasTmux ? "yes" : "no")
@@ -171,7 +167,7 @@ async function runInteractiveMode(detected: DetectedConfig): Promise<InstallConf
     return {
     return {
       hasAntigravity: antigravity === "yes",
       hasAntigravity: antigravity === "yes",
       hasOpenAI: openai === "yes",
       hasOpenAI: openai === "yes",
-      hasCerebras: cerebras === "yes",
+      hasOpencodeZen: true,
       hasTmux: false,
       hasTmux: false,
     }
     }
   } finally {
   } finally {
@@ -233,9 +229,8 @@ async function runInstall(config: InstallConfig): Promise<number> {
 
 
   printAgentModels(config)
   printAgentModels(config)
 
 
-  if (!config.hasAntigravity && !config.hasOpenAI && !config.hasCerebras) {
-    printWarning("No providers configured. At least one provider is required.")
-    return 1
+  if (!config.hasAntigravity && !config.hasOpenAI) {
+    printWarning("No providers configured. Zen free models will be used as fallback.")
   }
   }
 
 
   console.log(`${SYMBOLS.star} ${BOLD}${GREEN}${isUpdate ? "Configuration updated!" : "Installation complete!"}${RESET}`)
   console.log(`${SYMBOLS.star} ${BOLD}${GREEN}${isUpdate ? "Configuration updated!" : "Installation complete!"}${RESET}`)
@@ -265,7 +260,7 @@ async function runInstall(config: InstallConfig): Promise<number> {
 export async function install(args: InstallArgs): Promise<number> {
 export async function install(args: InstallArgs): Promise<number> {
   // Non-interactive mode: all args must be provided
   // Non-interactive mode: all args must be provided
   if (!args.tui) {
   if (!args.tui) {
-    const requiredArgs = ["antigravity", "openai", "cerebras", "tmux"] as const
+    const requiredArgs = ["antigravity", "openai", "tmux"] as const
     const errors = requiredArgs.filter((key) => {
     const errors = requiredArgs.filter((key) => {
       const value = args[key]
       const value = args[key]
       return value === undefined || !["yes", "no"].includes(value)
       return value === undefined || !["yes", "no"].includes(value)
@@ -278,7 +273,7 @@ export async function install(args: InstallArgs): Promise<number> {
         console.log(`  ${SYMBOLS.bullet} --${key}=<yes|no>`)
         console.log(`  ${SYMBOLS.bullet} --${key}=<yes|no>`)
       }
       }
       console.log()
       console.log()
-      printInfo("Usage: bunx oh-my-opencode-slim install --no-tui --antigravity=<yes|no> --openai=<yes|no> --cerebras=<yes|no> --tmux=<yes|no>")
+      printInfo("Usage: bunx oh-my-opencode-slim install --no-tui --antigravity=<yes|no> --openai=<yes|no> --tmux=<yes|no>")
       console.log()
       console.log()
       return 1
       return 1
     }
     }

+ 2 - 3
src/cli/types.ts

@@ -4,7 +4,6 @@ export interface InstallArgs {
   tui: boolean
   tui: boolean
   antigravity?: BooleanArg
   antigravity?: BooleanArg
   openai?: BooleanArg
   openai?: BooleanArg
-  cerebras?: BooleanArg
   tmux?: BooleanArg
   tmux?: BooleanArg
   skipAuth?: boolean
   skipAuth?: boolean
 }
 }
@@ -12,7 +11,7 @@ export interface InstallArgs {
 export interface InstallConfig {
 export interface InstallConfig {
   hasAntigravity: boolean
   hasAntigravity: boolean
   hasOpenAI: boolean
   hasOpenAI: boolean
-  hasCerebras: boolean
+  hasOpencodeZen: boolean
   hasTmux: boolean
   hasTmux: boolean
 }
 }
 
 
@@ -26,6 +25,6 @@ export interface DetectedConfig {
   isInstalled: boolean
   isInstalled: boolean
   hasAntigravity: boolean
   hasAntigravity: boolean
   hasOpenAI: boolean
   hasOpenAI: boolean
-  hasCerebras: boolean
+  hasOpencodeZen: boolean
   hasTmux: boolean
   hasTmux: boolean
 }
 }