feat(cli): support both oh-my-opencode and oh-my-openagent package names

Update CLI config manager to detect and handle both legacy (oh-my-opencode)
and new (oh-my-openagent) package names during installation. Migration
will automatically replace old plugin entries with the new name.

🤖 Generated with assistance of OhMyOpenCode
This commit is contained in:
YeonGyu-Kim
2026-03-14 12:43:54 +09:00
parent 6aeda598b9
commit 0dcfcd372b
4 changed files with 222 additions and 12 deletions
@@ -7,7 +7,8 @@ import { detectConfigFormat } from "./opencode-config-format"
import { parseOpenCodeConfigFileWithError, type OpenCodeConfig } from "./parse-opencode-config-file"
import { getPluginNameWithVersion } from "./plugin-name-with-version"
const PACKAGE_NAME = "oh-my-opencode"
const OLD_PACKAGE_NAME = "oh-my-opencode"
const NEW_PACKAGE_NAME = "oh-my-openagent"
export async function addPluginToOpenCodeConfig(currentVersion: string): Promise<ConfigMergeResult> {
try {
@@ -21,7 +22,7 @@ export async function addPluginToOpenCodeConfig(currentVersion: string): Promise
}
const { format, path } = detectConfigFormat()
const pluginEntry = await getPluginNameWithVersion(currentVersion)
const pluginEntry = await getPluginNameWithVersion(currentVersion, NEW_PACKAGE_NAME)
try {
if (format === "none") {
@@ -41,7 +42,13 @@ export async function addPluginToOpenCodeConfig(currentVersion: string): Promise
const config = parseResult.config
const plugins = config.plugin ?? []
const existingIndex = plugins.findIndex((p) => p === PACKAGE_NAME || p.startsWith(`${PACKAGE_NAME}@`))
const existingIndex = plugins.findIndex(
(p) =>
p === OLD_PACKAGE_NAME ||
p.startsWith(`${OLD_PACKAGE_NAME}@`) ||
p === NEW_PACKAGE_NAME ||
p.startsWith(`${NEW_PACKAGE_NAME}@`)
)
if (existingIndex !== -1) {
if (plugins[existingIndex] === pluginEntry) {
@@ -61,7 +61,9 @@ hasKimiForCoding: false,
const openCodeConfig = parseResult.config
const plugins = openCodeConfig.plugin ?? []
result.isInstalled = plugins.some((p) => p.startsWith("oh-my-opencode"))
const OLD_PACKAGE_NAME = "oh-my-opencode"
const NEW_PACKAGE_NAME = "oh-my-openagent"
result.isInstalled = plugins.some((p) => p.startsWith(OLD_PACKAGE_NAME) || p.startsWith(NEW_PACKAGE_NAME))
if (!result.isInstalled) {
return result
@@ -0,0 +1,196 @@
import { afterEach, beforeEach, describe, expect, it } from "bun:test"
import { mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs"
import { tmpdir } from "node:os"
import { join } from "node:path"
import { resetConfigContext } from "./config-context"
import { detectCurrentConfig } from "./detect-current-config"
import { addPluginToOpenCodeConfig } from "./add-plugin-to-opencode-config"
describe("detectCurrentConfig - dual name detection", () => {
let testConfigDir = ""
let testConfigPath = ""
beforeEach(() => {
testConfigDir = join(tmpdir(), `omo-detect-config-${Date.now()}-${Math.random().toString(36).slice(2)}`)
testConfigPath = join(testConfigDir, "opencode.json")
mkdirSync(testConfigDir, { recursive: true })
process.env.OPENCODE_CONFIG_DIR = testConfigDir
resetConfigContext()
})
afterEach(() => {
rmSync(testConfigDir, { recursive: true, force: true })
resetConfigContext()
delete process.env.OPENCODE_CONFIG_DIR
})
it("detects oh-my-opencode in plugin array", () => {
// given
const config = { plugin: ["oh-my-opencode"] }
writeFileSync(testConfigPath, JSON.stringify(config, null, 2) + "\n", "utf-8")
// when
const result = detectCurrentConfig()
// then
expect(result.isInstalled).toBe(true)
})
it("detects oh-my-openagent in plugin array", () => {
// given
const config = { plugin: ["oh-my-openagent"] }
writeFileSync(testConfigPath, JSON.stringify(config, null, 2) + "\n", "utf-8")
// when
const result = detectCurrentConfig()
// then
expect(result.isInstalled).toBe(true)
})
it("detects oh-my-opencode with version pin", () => {
// given
const config = { plugin: ["oh-my-opencode@3.11.0"] }
writeFileSync(testConfigPath, JSON.stringify(config, null, 2) + "\n", "utf-8")
// when
const result = detectCurrentConfig()
// then
expect(result.isInstalled).toBe(true)
})
it("detects oh-my-openagent with version pin", () => {
// given
const config = { plugin: ["oh-my-openagent@3.12.0"] }
writeFileSync(testConfigPath, JSON.stringify(config, null, 2) + "\n", "utf-8")
// when
const result = detectCurrentConfig()
// then
expect(result.isInstalled).toBe(true)
})
it("returns false when plugin not present", () => {
// given
const config = { plugin: ["some-other-plugin"] }
writeFileSync(testConfigPath, JSON.stringify(config, null, 2) + "\n", "utf-8")
// when
const result = detectCurrentConfig()
// then
expect(result.isInstalled).toBe(false)
})
})
describe("addPluginToOpenCodeConfig - dual name detection", () => {
let testConfigDir = ""
let testConfigPath = ""
beforeEach(() => {
testConfigDir = join(tmpdir(), `omo-add-plugin-${Date.now()}-${Math.random().toString(36).slice(2)}`)
testConfigPath = join(testConfigDir, "opencode.json")
mkdirSync(testConfigDir, { recursive: true })
process.env.OPENCODE_CONFIG_DIR = testConfigDir
resetConfigContext()
})
afterEach(() => {
rmSync(testConfigDir, { recursive: true, force: true })
resetConfigContext()
delete process.env.OPENCODE_CONFIG_DIR
})
it("finds and replaces old oh-my-opencode with new name", async () => {
// given
const config = { plugin: ["oh-my-opencode"] }
writeFileSync(testConfigPath, JSON.stringify(config, null, 2) + "\n", "utf-8")
// when
const result = await addPluginToOpenCodeConfig("3.11.0")
// then
expect(result.success).toBe(true)
const savedConfig = JSON.parse(readFileSync(testConfigPath, "utf-8"))
expect(savedConfig.plugin).toContain("oh-my-openagent")
expect(savedConfig.plugin).not.toContain("oh-my-opencode")
})
it("finds and replaces oh-my-openagent with new name", async () => {
// given
const config = { plugin: ["oh-my-openagent"] }
writeFileSync(testConfigPath, JSON.stringify(config, null, 2) + "\n", "utf-8")
// when
const result = await addPluginToOpenCodeConfig("3.11.0")
// then
expect(result.success).toBe(true)
const savedConfig = JSON.parse(readFileSync(testConfigPath, "utf-8"))
expect(savedConfig.plugin).toContain("oh-my-openagent")
expect(savedConfig.plugin).not.toContain("oh-my-opencode")
})
it("finds and replaces version-pinned oh-my-opencode@X.Y.Z", async () => {
// given
const config = { plugin: ["oh-my-opencode@3.10.0"] }
writeFileSync(testConfigPath, JSON.stringify(config, null, 2) + "\n", "utf-8")
// when
const result = await addPluginToOpenCodeConfig("3.11.0")
// then
expect(result.success).toBe(true)
const savedConfig = JSON.parse(readFileSync(testConfigPath, "utf-8"))
expect(savedConfig.plugin).toContain("oh-my-openagent")
expect(savedConfig.plugin).not.toContain("oh-my-opencode@3.10.0")
})
it("finds and replaces version-pinned oh-my-openagent@X.Y.Z", async () => {
// given
const config = { plugin: ["oh-my-openagent@3.10.0"] }
writeFileSync(testConfigPath, JSON.stringify(config, null, 2) + "\n", "utf-8")
// when
const result = await addPluginToOpenCodeConfig("3.11.0")
// then
expect(result.success).toBe(true)
const savedConfig = JSON.parse(readFileSync(testConfigPath, "utf-8"))
expect(savedConfig.plugin).toContain("oh-my-openagent")
expect(savedConfig.plugin).not.toContain("oh-my-openagent@3.10.0")
})
it("adds new plugin when none exists", async () => {
// given - no plugin array
const config = {}
writeFileSync(testConfigPath, JSON.stringify(config, null, 2) + "\n", "utf-8")
// when
const result = await addPluginToOpenCodeConfig("3.11.0")
// then
expect(result.success).toBe(true)
const savedConfig = JSON.parse(readFileSync(testConfigPath, "utf-8"))
expect(savedConfig.plugin).toContain("oh-my-openagent")
})
it("adds plugin when plugin array is empty", async () => {
// given - empty plugin array
const config = { plugin: [] }
writeFileSync(testConfigPath, JSON.stringify(config, null, 2) + "\n", "utf-8")
// when
const result = await addPluginToOpenCodeConfig("3.11.0")
// then
expect(result.success).toBe(true)
const savedConfig = JSON.parse(readFileSync(testConfigPath, "utf-8"))
expect(savedConfig.plugin).toContain("oh-my-openagent")
})
})
@@ -1,28 +1,33 @@
import { fetchNpmDistTags } from "./npm-dist-tags"
const PACKAGE_NAME = "oh-my-opencode"
const DEFAULT_PACKAGE_NAME = "oh-my-opencode"
const NEW_PACKAGE_NAME = "oh-my-openagent"
const PRIORITIZED_TAGS = ["latest", "beta", "next"] as const
function getFallbackEntry(version: string): string {
function getFallbackEntry(version: string, packageName: string): string {
const prereleaseMatch = version.match(/-([a-zA-Z][a-zA-Z0-9-]*)(?:\.|$)/)
if (prereleaseMatch) {
return `${PACKAGE_NAME}@${prereleaseMatch[1]}`
return `${packageName}@${prereleaseMatch[1]}`
}
return PACKAGE_NAME
return packageName
}
export async function getPluginNameWithVersion(currentVersion: string): Promise<string> {
const distTags = await fetchNpmDistTags(PACKAGE_NAME)
export async function getPluginNameWithVersion(
currentVersion: string,
packageName: string = DEFAULT_PACKAGE_NAME
): Promise<string> {
const distTags = await fetchNpmDistTags(NEW_PACKAGE_NAME)
if (distTags) {
const allTags = new Set([...PRIORITIZED_TAGS, ...Object.keys(distTags)])
for (const tag of allTags) {
if (distTags[tag] === currentVersion) {
return `${PACKAGE_NAME}@${tag}`
return `${packageName}@${tag}`
}
}
}
return getFallbackEntry(currentVersion)
return getFallbackEntry(currentVersion, packageName)
}