fix(cli): validate and detect OpenCode Go install settings
Reject invalid --opencode-go values during non-TUI installs and detect existing OpenCode Go usage from the generated oh-my-opencode config so updates preserve the right defaults. Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
@@ -10,17 +10,30 @@ function detectProvidersFromOmoConfig(): {
|
|||||||
hasOpencodeZen: boolean
|
hasOpencodeZen: boolean
|
||||||
hasZaiCodingPlan: boolean
|
hasZaiCodingPlan: boolean
|
||||||
hasKimiForCoding: boolean
|
hasKimiForCoding: boolean
|
||||||
|
hasOpencodeGo: boolean
|
||||||
} {
|
} {
|
||||||
const omoConfigPath = getOmoConfigPath()
|
const omoConfigPath = getOmoConfigPath()
|
||||||
if (!existsSync(omoConfigPath)) {
|
if (!existsSync(omoConfigPath)) {
|
||||||
return { hasOpenAI: true, hasOpencodeZen: true, hasZaiCodingPlan: false, hasKimiForCoding: false }
|
return {
|
||||||
|
hasOpenAI: true,
|
||||||
|
hasOpencodeZen: true,
|
||||||
|
hasZaiCodingPlan: false,
|
||||||
|
hasKimiForCoding: false,
|
||||||
|
hasOpencodeGo: false,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const content = readFileSync(omoConfigPath, "utf-8")
|
const content = readFileSync(omoConfigPath, "utf-8")
|
||||||
const omoConfig = parseJsonc<Record<string, unknown>>(content)
|
const omoConfig = parseJsonc<Record<string, unknown>>(content)
|
||||||
if (!omoConfig || typeof omoConfig !== "object") {
|
if (!omoConfig || typeof omoConfig !== "object") {
|
||||||
return { hasOpenAI: true, hasOpencodeZen: true, hasZaiCodingPlan: false, hasKimiForCoding: false }
|
return {
|
||||||
|
hasOpenAI: true,
|
||||||
|
hasOpencodeZen: true,
|
||||||
|
hasZaiCodingPlan: false,
|
||||||
|
hasKimiForCoding: false,
|
||||||
|
hasOpencodeGo: false,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const configStr = JSON.stringify(omoConfig)
|
const configStr = JSON.stringify(omoConfig)
|
||||||
@@ -28,10 +41,17 @@ function detectProvidersFromOmoConfig(): {
|
|||||||
const hasOpencodeZen = configStr.includes('"opencode/')
|
const hasOpencodeZen = configStr.includes('"opencode/')
|
||||||
const hasZaiCodingPlan = configStr.includes('"zai-coding-plan/')
|
const hasZaiCodingPlan = configStr.includes('"zai-coding-plan/')
|
||||||
const hasKimiForCoding = configStr.includes('"kimi-for-coding/')
|
const hasKimiForCoding = configStr.includes('"kimi-for-coding/')
|
||||||
|
const hasOpencodeGo = configStr.includes('"opencode-go/')
|
||||||
|
|
||||||
return { hasOpenAI, hasOpencodeZen, hasZaiCodingPlan, hasKimiForCoding }
|
return { hasOpenAI, hasOpencodeZen, hasZaiCodingPlan, hasKimiForCoding, hasOpencodeGo }
|
||||||
} catch {
|
} catch {
|
||||||
return { hasOpenAI: true, hasOpencodeZen: true, hasZaiCodingPlan: false, hasKimiForCoding: false }
|
return {
|
||||||
|
hasOpenAI: true,
|
||||||
|
hasOpencodeZen: true,
|
||||||
|
hasZaiCodingPlan: false,
|
||||||
|
hasKimiForCoding: false,
|
||||||
|
hasOpencodeGo: false,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -72,11 +92,12 @@ result.isInstalled = plugins.some((p) => p.startsWith(OLD_PACKAGE_NAME) || p.sta
|
|||||||
const providers = openCodeConfig.provider as Record<string, unknown> | undefined
|
const providers = openCodeConfig.provider as Record<string, unknown> | undefined
|
||||||
result.hasGemini = providers ? "google" in providers : false
|
result.hasGemini = providers ? "google" in providers : false
|
||||||
|
|
||||||
const { hasOpenAI, hasOpencodeZen, hasZaiCodingPlan, hasKimiForCoding } = detectProvidersFromOmoConfig()
|
const { hasOpenAI, hasOpencodeZen, hasZaiCodingPlan, hasKimiForCoding, hasOpencodeGo } = detectProvidersFromOmoConfig()
|
||||||
result.hasOpenAI = hasOpenAI
|
result.hasOpenAI = hasOpenAI
|
||||||
result.hasOpencodeZen = hasOpencodeZen
|
result.hasOpencodeZen = hasOpencodeZen
|
||||||
result.hasZaiCodingPlan = hasZaiCodingPlan
|
result.hasZaiCodingPlan = hasZaiCodingPlan
|
||||||
result.hasKimiForCoding = hasKimiForCoding
|
result.hasKimiForCoding = hasKimiForCoding
|
||||||
|
result.hasOpencodeGo = hasOpencodeGo
|
||||||
|
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,10 +10,12 @@ import { addPluginToOpenCodeConfig } from "./add-plugin-to-opencode-config"
|
|||||||
describe("detectCurrentConfig - dual name detection", () => {
|
describe("detectCurrentConfig - dual name detection", () => {
|
||||||
let testConfigDir = ""
|
let testConfigDir = ""
|
||||||
let testConfigPath = ""
|
let testConfigPath = ""
|
||||||
|
let testOmoConfigPath = ""
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
testConfigDir = join(tmpdir(), `omo-detect-config-${Date.now()}-${Math.random().toString(36).slice(2)}`)
|
testConfigDir = join(tmpdir(), `omo-detect-config-${Date.now()}-${Math.random().toString(36).slice(2)}`)
|
||||||
testConfigPath = join(testConfigDir, "opencode.json")
|
testConfigPath = join(testConfigDir, "opencode.json")
|
||||||
|
testOmoConfigPath = join(testConfigDir, "oh-my-opencode.json")
|
||||||
|
|
||||||
mkdirSync(testConfigDir, { recursive: true })
|
mkdirSync(testConfigDir, { recursive: true })
|
||||||
process.env.OPENCODE_CONFIG_DIR = testConfigDir
|
process.env.OPENCODE_CONFIG_DIR = testConfigDir
|
||||||
@@ -85,6 +87,23 @@ describe("detectCurrentConfig - dual name detection", () => {
|
|||||||
// then
|
// then
|
||||||
expect(result.isInstalled).toBe(false)
|
expect(result.isInstalled).toBe(false)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it("detects OpenCode Go from the existing omo config", () => {
|
||||||
|
// given
|
||||||
|
writeFileSync(testConfigPath, JSON.stringify({ plugin: ["oh-my-openagent"] }, null, 2) + "\n", "utf-8")
|
||||||
|
writeFileSync(
|
||||||
|
testOmoConfigPath,
|
||||||
|
JSON.stringify({ agents: { atlas: { model: "opencode-go/kimi-k2.5" } } }, null, 2) + "\n",
|
||||||
|
"utf-8",
|
||||||
|
)
|
||||||
|
|
||||||
|
// when
|
||||||
|
const result = detectCurrentConfig()
|
||||||
|
|
||||||
|
// then
|
||||||
|
expect(result.isInstalled).toBe(true)
|
||||||
|
expect(result.hasOpencodeGo).toBe(true)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("addPluginToOpenCodeConfig - dual name detection", () => {
|
describe("addPluginToOpenCodeConfig - dual name detection", () => {
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
import { describe, expect, test } from "bun:test"
|
||||||
|
|
||||||
|
import { validateNonTuiArgs } from "./install-validators"
|
||||||
|
import type { InstallArgs } from "./types"
|
||||||
|
|
||||||
|
function createArgs(overrides: Partial<InstallArgs> = {}): InstallArgs {
|
||||||
|
return {
|
||||||
|
tui: false,
|
||||||
|
claude: "no",
|
||||||
|
openai: "no",
|
||||||
|
gemini: "no",
|
||||||
|
copilot: "no",
|
||||||
|
opencodeZen: "no",
|
||||||
|
zaiCodingPlan: "no",
|
||||||
|
kimiForCoding: "no",
|
||||||
|
opencodeGo: "no",
|
||||||
|
skipAuth: false,
|
||||||
|
...overrides,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("validateNonTuiArgs", () => {
|
||||||
|
test("rejects invalid --opencode-go values", () => {
|
||||||
|
// #given
|
||||||
|
const args = createArgs({ opencodeGo: "maybe" as InstallArgs["opencodeGo"] })
|
||||||
|
|
||||||
|
// #when
|
||||||
|
const result = validateNonTuiArgs(args)
|
||||||
|
|
||||||
|
// #then
|
||||||
|
expect(result.valid).toBe(false)
|
||||||
|
expect(result.errors).toContain("Invalid --opencode-go value: maybe (expected: no, yes)")
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -17,6 +17,8 @@ export const SYMBOLS = {
|
|||||||
star: color.yellow("*"),
|
star: color.yellow("*"),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const ANSI_COLOR_PATTERN = new RegExp("\u001b\\[[0-9;]*m", "g")
|
||||||
|
|
||||||
function formatProvider(name: string, enabled: boolean, detail?: string): string {
|
function formatProvider(name: string, enabled: boolean, detail?: string): string {
|
||||||
const status = enabled ? SYMBOLS.check : color.dim("○")
|
const status = enabled ? SYMBOLS.check : color.dim("○")
|
||||||
const label = enabled ? color.white(name) : color.dim(name)
|
const label = enabled ? color.white(name) : color.dim(name)
|
||||||
@@ -83,7 +85,7 @@ export function printBox(content: string, title?: string): void {
|
|||||||
const lines = content.split("\n")
|
const lines = content.split("\n")
|
||||||
const maxWidth =
|
const maxWidth =
|
||||||
Math.max(
|
Math.max(
|
||||||
...lines.map((line) => line.replace(/\x1b\[[0-9;]*m/g, "").length),
|
...lines.map((line) => line.replace(ANSI_COLOR_PATTERN, "").length),
|
||||||
title?.length ?? 0,
|
title?.length ?? 0,
|
||||||
) + 4
|
) + 4
|
||||||
const border = color.dim("─".repeat(maxWidth))
|
const border = color.dim("─".repeat(maxWidth))
|
||||||
@@ -101,7 +103,7 @@ export function printBox(content: string, title?: string): void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (const line of lines) {
|
for (const line of lines) {
|
||||||
const stripped = line.replace(/\x1b\[[0-9;]*m/g, "")
|
const stripped = line.replace(ANSI_COLOR_PATTERN, "")
|
||||||
const padding = maxWidth - stripped.length
|
const padding = maxWidth - stripped.length
|
||||||
console.log(color.dim("│") + ` ${line}${" ".repeat(padding - 1)}` + color.dim("│"))
|
console.log(color.dim("│") + ` ${line}${" ".repeat(padding - 1)}` + color.dim("│"))
|
||||||
}
|
}
|
||||||
@@ -135,6 +137,10 @@ export function validateNonTuiArgs(args: InstallArgs): { valid: boolean; errors:
|
|||||||
errors.push(`Invalid --openai value: ${args.openai} (expected: no, yes)`)
|
errors.push(`Invalid --openai value: ${args.openai} (expected: no, yes)`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (args.opencodeGo !== undefined && !["no", "yes"].includes(args.opencodeGo)) {
|
||||||
|
errors.push(`Invalid --opencode-go value: ${args.opencodeGo} (expected: no, yes)`)
|
||||||
|
}
|
||||||
|
|
||||||
if (args.opencodeZen !== undefined && !["no", "yes"].includes(args.opencodeZen)) {
|
if (args.opencodeZen !== undefined && !["no", "yes"].includes(args.opencodeZen)) {
|
||||||
errors.push(`Invalid --opencode-zen value: ${args.opencodeZen} (expected: no, yes)`)
|
errors.push(`Invalid --opencode-zen value: ${args.opencodeZen} (expected: no, yes)`)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user