refactor: rename remaining oh-my-opencode string literals

This commit is contained in:
YeonGyu-Kim
2026-03-17 13:56:15 +09:00
parent ffa3d43ccb
commit 8efbda9332
49 changed files with 218 additions and 199 deletions
+4 -4
View File
@@ -32,7 +32,7 @@ function supportsAvx2() {
return null; return null;
} }
if (process.env.OH_MY_OPENCODE_FORCE_BASELINE === "1") { if (process.env.OH_MY_OPENAGENT_FORCE_BASELINE === "1" || process.env.OH_MY_OPENCODE_FORCE_BASELINE === "1") {
return false; return false;
} }
@@ -85,7 +85,7 @@ function main() {
preferBaseline: avx2Supported === false, preferBaseline: avx2Supported === false,
}); });
} catch (error) { } catch (error) {
console.error(`\noh-my-opencode: ${error.message}\n`); console.error(`\noh-my-openagent: ${error.message}\n`);
process.exit(1); process.exit(1);
} }
@@ -100,7 +100,7 @@ function main() {
.filter((entry) => entry !== null); .filter((entry) => entry !== null);
if (resolvedBinaries.length === 0) { if (resolvedBinaries.length === 0) {
console.error(`\noh-my-opencode: Platform binary not installed.`); console.error(`\noh-my-openagent: Platform binary not installed.`);
console.error(`\nYour platform: ${platform}-${arch}${libcFamily === "musl" ? "-musl" : ""}`); console.error(`\nYour platform: ${platform}-${arch}${libcFamily === "musl" ? "-musl" : ""}`);
console.error(`Expected packages (in order): ${packageCandidates.join(", ")}`); console.error(`Expected packages (in order): ${packageCandidates.join(", ")}`);
console.error(`\nTo fix, run:`); console.error(`\nTo fix, run:`);
@@ -120,7 +120,7 @@ function main() {
continue; continue;
} }
console.error(`\noh-my-opencode: Failed to execute binary.`); console.error(`\noh-my-openagent: Failed to execute binary.`);
console.error(`Error: ${result.error.message}\n`); console.error(`Error: ${result.error.message}\n`);
process.exit(2); process.exit(2);
} }
+2 -2
View File
@@ -48,9 +48,9 @@ function main() {
); );
} }
console.log(`✓ oh-my-opencode binary installed for ${platform}-${arch} (${resolvedPackage})`); console.log(`✓ oh-my-openagent binary installed for ${platform}-${arch} (${resolvedPackage})`)
} catch (error) { } catch (error) {
console.warn(`⚠ oh-my-opencode: ${error.message}`); console.warn(`⚠ oh-my-openagent: ${error.message}`)
console.warn(` The CLI may not work on this platform.`); console.warn(` The CLI may not work on this platform.`);
// Don't fail installation - let user try anyway // Don't fail installation - let user try anyway
} }
@@ -2,7 +2,7 @@
exports[`generateModelConfig no providers available returns ULTIMATE_FALLBACK for all agents and categories when no providers 1`] = ` exports[`generateModelConfig no providers available returns ULTIMATE_FALLBACK for all agents and categories when no providers 1`] = `
{ {
"$schema": "https://raw.githubusercontent.com/code-yeongyu/oh-my-openagent/dev/assets/oh-my-opencode.schema.json", "$schema": "https://raw.githubusercontent.com/code-yeongyu/oh-my-openagent/dev/assets/oh-my-openagent.schema.json",
"agents": { "agents": {
"atlas": { "atlas": {
"model": "opencode/gpt-5-nano", "model": "opencode/gpt-5-nano",
@@ -66,7 +66,7 @@ exports[`generateModelConfig no providers available returns ULTIMATE_FALLBACK fo
exports[`generateModelConfig single native provider uses Claude models when only Claude is available 1`] = ` exports[`generateModelConfig single native provider uses Claude models when only Claude is available 1`] = `
{ {
"$schema": "https://raw.githubusercontent.com/code-yeongyu/oh-my-openagent/dev/assets/oh-my-opencode.schema.json", "$schema": "https://raw.githubusercontent.com/code-yeongyu/oh-my-openagent/dev/assets/oh-my-openagent.schema.json",
"agents": { "agents": {
"atlas": { "atlas": {
"model": "anthropic/claude-sonnet-4-6", "model": "anthropic/claude-sonnet-4-6",
@@ -128,7 +128,7 @@ exports[`generateModelConfig single native provider uses Claude models when only
exports[`generateModelConfig single native provider uses Claude models with isMax20 flag 1`] = ` exports[`generateModelConfig single native provider uses Claude models with isMax20 flag 1`] = `
{ {
"$schema": "https://raw.githubusercontent.com/code-yeongyu/oh-my-openagent/dev/assets/oh-my-opencode.schema.json", "$schema": "https://raw.githubusercontent.com/code-yeongyu/oh-my-openagent/dev/assets/oh-my-openagent.schema.json",
"agents": { "agents": {
"atlas": { "atlas": {
"model": "anthropic/claude-sonnet-4-6", "model": "anthropic/claude-sonnet-4-6",
@@ -191,7 +191,7 @@ exports[`generateModelConfig single native provider uses Claude models with isMa
exports[`generateModelConfig single native provider uses OpenAI models when only OpenAI is available 1`] = ` exports[`generateModelConfig single native provider uses OpenAI models when only OpenAI is available 1`] = `
{ {
"$schema": "https://raw.githubusercontent.com/code-yeongyu/oh-my-openagent/dev/assets/oh-my-opencode.schema.json", "$schema": "https://raw.githubusercontent.com/code-yeongyu/oh-my-openagent/dev/assets/oh-my-openagent.schema.json",
"agents": { "agents": {
"atlas": { "atlas": {
"model": "openai/gpt-5.4", "model": "openai/gpt-5.4",
@@ -277,7 +277,7 @@ exports[`generateModelConfig single native provider uses OpenAI models when only
exports[`generateModelConfig single native provider uses OpenAI models with isMax20 flag 1`] = ` exports[`generateModelConfig single native provider uses OpenAI models with isMax20 flag 1`] = `
{ {
"$schema": "https://raw.githubusercontent.com/code-yeongyu/oh-my-openagent/dev/assets/oh-my-opencode.schema.json", "$schema": "https://raw.githubusercontent.com/code-yeongyu/oh-my-openagent/dev/assets/oh-my-openagent.schema.json",
"agents": { "agents": {
"atlas": { "atlas": {
"model": "openai/gpt-5.4", "model": "openai/gpt-5.4",
@@ -363,7 +363,7 @@ exports[`generateModelConfig single native provider uses OpenAI models with isMa
exports[`generateModelConfig single native provider uses Gemini models when only Gemini is available 1`] = ` exports[`generateModelConfig single native provider uses Gemini models when only Gemini is available 1`] = `
{ {
"$schema": "https://raw.githubusercontent.com/code-yeongyu/oh-my-openagent/dev/assets/oh-my-opencode.schema.json", "$schema": "https://raw.githubusercontent.com/code-yeongyu/oh-my-openagent/dev/assets/oh-my-openagent.schema.json",
"agents": { "agents": {
"atlas": { "atlas": {
"model": "opencode/gpt-5-nano", "model": "opencode/gpt-5-nano",
@@ -423,7 +423,7 @@ exports[`generateModelConfig single native provider uses Gemini models when only
exports[`generateModelConfig single native provider uses Gemini models with isMax20 flag 1`] = ` exports[`generateModelConfig single native provider uses Gemini models with isMax20 flag 1`] = `
{ {
"$schema": "https://raw.githubusercontent.com/code-yeongyu/oh-my-openagent/dev/assets/oh-my-opencode.schema.json", "$schema": "https://raw.githubusercontent.com/code-yeongyu/oh-my-openagent/dev/assets/oh-my-openagent.schema.json",
"agents": { "agents": {
"atlas": { "atlas": {
"model": "opencode/gpt-5-nano", "model": "opencode/gpt-5-nano",
@@ -483,7 +483,7 @@ exports[`generateModelConfig single native provider uses Gemini models with isMa
exports[`generateModelConfig all native providers uses preferred models from fallback chains when all natives available 1`] = ` exports[`generateModelConfig all native providers uses preferred models from fallback chains when all natives available 1`] = `
{ {
"$schema": "https://raw.githubusercontent.com/code-yeongyu/oh-my-openagent/dev/assets/oh-my-opencode.schema.json", "$schema": "https://raw.githubusercontent.com/code-yeongyu/oh-my-openagent/dev/assets/oh-my-openagent.schema.json",
"agents": { "agents": {
"atlas": { "atlas": {
"model": "anthropic/claude-sonnet-4-6", "model": "anthropic/claude-sonnet-4-6",
@@ -558,7 +558,7 @@ exports[`generateModelConfig all native providers uses preferred models from fal
exports[`generateModelConfig all native providers uses preferred models with isMax20 flag when all natives available 1`] = ` exports[`generateModelConfig all native providers uses preferred models with isMax20 flag when all natives available 1`] = `
{ {
"$schema": "https://raw.githubusercontent.com/code-yeongyu/oh-my-openagent/dev/assets/oh-my-opencode.schema.json", "$schema": "https://raw.githubusercontent.com/code-yeongyu/oh-my-openagent/dev/assets/oh-my-openagent.schema.json",
"agents": { "agents": {
"atlas": { "atlas": {
"model": "anthropic/claude-sonnet-4-6", "model": "anthropic/claude-sonnet-4-6",
@@ -634,7 +634,7 @@ exports[`generateModelConfig all native providers uses preferred models with isM
exports[`generateModelConfig fallback providers uses OpenCode Zen models when only OpenCode Zen is available 1`] = ` exports[`generateModelConfig fallback providers uses OpenCode Zen models when only OpenCode Zen is available 1`] = `
{ {
"$schema": "https://raw.githubusercontent.com/code-yeongyu/oh-my-openagent/dev/assets/oh-my-opencode.schema.json", "$schema": "https://raw.githubusercontent.com/code-yeongyu/oh-my-openagent/dev/assets/oh-my-openagent.schema.json",
"agents": { "agents": {
"atlas": { "atlas": {
"model": "opencode/claude-sonnet-4-6", "model": "opencode/claude-sonnet-4-6",
@@ -709,7 +709,7 @@ exports[`generateModelConfig fallback providers uses OpenCode Zen models when on
exports[`generateModelConfig fallback providers uses OpenCode Zen models with isMax20 flag 1`] = ` exports[`generateModelConfig fallback providers uses OpenCode Zen models with isMax20 flag 1`] = `
{ {
"$schema": "https://raw.githubusercontent.com/code-yeongyu/oh-my-openagent/dev/assets/oh-my-opencode.schema.json", "$schema": "https://raw.githubusercontent.com/code-yeongyu/oh-my-openagent/dev/assets/oh-my-openagent.schema.json",
"agents": { "agents": {
"atlas": { "atlas": {
"model": "opencode/claude-sonnet-4-6", "model": "opencode/claude-sonnet-4-6",
@@ -785,7 +785,7 @@ exports[`generateModelConfig fallback providers uses OpenCode Zen models with is
exports[`generateModelConfig fallback providers uses GitHub Copilot models when only Copilot is available 1`] = ` exports[`generateModelConfig fallback providers uses GitHub Copilot models when only Copilot is available 1`] = `
{ {
"$schema": "https://raw.githubusercontent.com/code-yeongyu/oh-my-openagent/dev/assets/oh-my-opencode.schema.json", "$schema": "https://raw.githubusercontent.com/code-yeongyu/oh-my-openagent/dev/assets/oh-my-openagent.schema.json",
"agents": { "agents": {
"atlas": { "atlas": {
"model": "github-copilot/claude-sonnet-4.6", "model": "github-copilot/claude-sonnet-4.6",
@@ -855,7 +855,7 @@ exports[`generateModelConfig fallback providers uses GitHub Copilot models when
exports[`generateModelConfig fallback providers uses GitHub Copilot models with isMax20 flag 1`] = ` exports[`generateModelConfig fallback providers uses GitHub Copilot models with isMax20 flag 1`] = `
{ {
"$schema": "https://raw.githubusercontent.com/code-yeongyu/oh-my-openagent/dev/assets/oh-my-opencode.schema.json", "$schema": "https://raw.githubusercontent.com/code-yeongyu/oh-my-openagent/dev/assets/oh-my-openagent.schema.json",
"agents": { "agents": {
"atlas": { "atlas": {
"model": "github-copilot/claude-sonnet-4.6", "model": "github-copilot/claude-sonnet-4.6",
@@ -926,7 +926,7 @@ exports[`generateModelConfig fallback providers uses GitHub Copilot models with
exports[`generateModelConfig fallback providers uses ZAI model for librarian when only ZAI is available 1`] = ` exports[`generateModelConfig fallback providers uses ZAI model for librarian when only ZAI is available 1`] = `
{ {
"$schema": "https://raw.githubusercontent.com/code-yeongyu/oh-my-openagent/dev/assets/oh-my-opencode.schema.json", "$schema": "https://raw.githubusercontent.com/code-yeongyu/oh-my-openagent/dev/assets/oh-my-openagent.schema.json",
"agents": { "agents": {
"atlas": { "atlas": {
"model": "opencode/gpt-5-nano", "model": "opencode/gpt-5-nano",
@@ -984,7 +984,7 @@ exports[`generateModelConfig fallback providers uses ZAI model for librarian whe
exports[`generateModelConfig fallback providers uses ZAI model for librarian with isMax20 flag 1`] = ` exports[`generateModelConfig fallback providers uses ZAI model for librarian with isMax20 flag 1`] = `
{ {
"$schema": "https://raw.githubusercontent.com/code-yeongyu/oh-my-openagent/dev/assets/oh-my-opencode.schema.json", "$schema": "https://raw.githubusercontent.com/code-yeongyu/oh-my-openagent/dev/assets/oh-my-openagent.schema.json",
"agents": { "agents": {
"atlas": { "atlas": {
"model": "opencode/gpt-5-nano", "model": "opencode/gpt-5-nano",
@@ -1042,7 +1042,7 @@ exports[`generateModelConfig fallback providers uses ZAI model for librarian wit
exports[`generateModelConfig mixed provider scenarios uses Claude + OpenCode Zen combination 1`] = ` exports[`generateModelConfig mixed provider scenarios uses Claude + OpenCode Zen combination 1`] = `
{ {
"$schema": "https://raw.githubusercontent.com/code-yeongyu/oh-my-openagent/dev/assets/oh-my-opencode.schema.json", "$schema": "https://raw.githubusercontent.com/code-yeongyu/oh-my-openagent/dev/assets/oh-my-openagent.schema.json",
"agents": { "agents": {
"atlas": { "atlas": {
"model": "anthropic/claude-sonnet-4-6", "model": "anthropic/claude-sonnet-4-6",
@@ -1117,7 +1117,7 @@ exports[`generateModelConfig mixed provider scenarios uses Claude + OpenCode Zen
exports[`generateModelConfig mixed provider scenarios uses OpenAI + Copilot combination 1`] = ` exports[`generateModelConfig mixed provider scenarios uses OpenAI + Copilot combination 1`] = `
{ {
"$schema": "https://raw.githubusercontent.com/code-yeongyu/oh-my-openagent/dev/assets/oh-my-opencode.schema.json", "$schema": "https://raw.githubusercontent.com/code-yeongyu/oh-my-openagent/dev/assets/oh-my-openagent.schema.json",
"agents": { "agents": {
"atlas": { "atlas": {
"model": "github-copilot/claude-sonnet-4.6", "model": "github-copilot/claude-sonnet-4.6",
@@ -1192,7 +1192,7 @@ exports[`generateModelConfig mixed provider scenarios uses OpenAI + Copilot comb
exports[`generateModelConfig mixed provider scenarios uses Claude + ZAI combination (librarian uses ZAI) 1`] = ` exports[`generateModelConfig mixed provider scenarios uses Claude + ZAI combination (librarian uses ZAI) 1`] = `
{ {
"$schema": "https://raw.githubusercontent.com/code-yeongyu/oh-my-openagent/dev/assets/oh-my-opencode.schema.json", "$schema": "https://raw.githubusercontent.com/code-yeongyu/oh-my-openagent/dev/assets/oh-my-openagent.schema.json",
"agents": { "agents": {
"atlas": { "atlas": {
"model": "anthropic/claude-sonnet-4-6", "model": "anthropic/claude-sonnet-4-6",
@@ -1256,7 +1256,7 @@ exports[`generateModelConfig mixed provider scenarios uses Claude + ZAI combinat
exports[`generateModelConfig mixed provider scenarios uses Gemini + Claude combination (explore uses Gemini) 1`] = ` exports[`generateModelConfig mixed provider scenarios uses Gemini + Claude combination (explore uses Gemini) 1`] = `
{ {
"$schema": "https://raw.githubusercontent.com/code-yeongyu/oh-my-openagent/dev/assets/oh-my-opencode.schema.json", "$schema": "https://raw.githubusercontent.com/code-yeongyu/oh-my-openagent/dev/assets/oh-my-openagent.schema.json",
"agents": { "agents": {
"atlas": { "atlas": {
"model": "anthropic/claude-sonnet-4-6", "model": "anthropic/claude-sonnet-4-6",
@@ -1322,7 +1322,7 @@ exports[`generateModelConfig mixed provider scenarios uses Gemini + Claude combi
exports[`generateModelConfig mixed provider scenarios uses all fallback providers together 1`] = ` exports[`generateModelConfig mixed provider scenarios uses all fallback providers together 1`] = `
{ {
"$schema": "https://raw.githubusercontent.com/code-yeongyu/oh-my-openagent/dev/assets/oh-my-opencode.schema.json", "$schema": "https://raw.githubusercontent.com/code-yeongyu/oh-my-openagent/dev/assets/oh-my-openagent.schema.json",
"agents": { "agents": {
"atlas": { "atlas": {
"model": "github-copilot/claude-sonnet-4.6", "model": "github-copilot/claude-sonnet-4.6",
@@ -1400,7 +1400,7 @@ exports[`generateModelConfig mixed provider scenarios uses all fallback provider
exports[`generateModelConfig mixed provider scenarios uses all providers together 1`] = ` exports[`generateModelConfig mixed provider scenarios uses all providers together 1`] = `
{ {
"$schema": "https://raw.githubusercontent.com/code-yeongyu/oh-my-openagent/dev/assets/oh-my-opencode.schema.json", "$schema": "https://raw.githubusercontent.com/code-yeongyu/oh-my-openagent/dev/assets/oh-my-openagent.schema.json",
"agents": { "agents": {
"atlas": { "atlas": {
"model": "anthropic/claude-sonnet-4-6", "model": "anthropic/claude-sonnet-4-6",
@@ -1478,7 +1478,7 @@ exports[`generateModelConfig mixed provider scenarios uses all providers togethe
exports[`generateModelConfig mixed provider scenarios uses all providers with isMax20 flag 1`] = ` exports[`generateModelConfig mixed provider scenarios uses all providers with isMax20 flag 1`] = `
{ {
"$schema": "https://raw.githubusercontent.com/code-yeongyu/oh-my-openagent/dev/assets/oh-my-opencode.schema.json", "$schema": "https://raw.githubusercontent.com/code-yeongyu/oh-my-openagent/dev/assets/oh-my-openagent.schema.json",
"agents": { "agents": {
"atlas": { "atlas": {
"model": "anthropic/claude-sonnet-4-6", "model": "anthropic/claude-sonnet-4-6",
+1 -1
View File
@@ -43,7 +43,7 @@ describe("runCliInstaller", () => {
}), }),
spyOn(configManager, "writeOmoConfig").mockReturnValue({ spyOn(configManager, "writeOmoConfig").mockReturnValue({
success: true, success: true,
configPath: "/tmp/oh-my-opencode.jsonc", configPath: "/tmp/oh-my-openagent.jsonc"
}), }),
] ]
+3 -3
View File
@@ -32,7 +32,7 @@ export async function runCliInstaller(args: InstallArgs, version: string): Promi
} }
console.log() console.log()
printInfo( printInfo(
"Usage: bunx oh-my-opencode install --no-tui --claude=<no|yes|max20> --gemini=<no|yes> --copilot=<no|yes>", "Usage: bunx oh-my-openagent install --no-tui --claude=<no|yes|max20> --gemini=<no|yes> --copilot=<no|yes>"
) )
console.log() console.log()
return 1 return 1
@@ -65,7 +65,7 @@ export async function runCliInstaller(args: InstallArgs, version: string): Promi
const config = argsToConfig(args) const config = argsToConfig(args)
printStep(step++, totalSteps, "Adding oh-my-opencode plugin...") printStep(step++, totalSteps, "Adding oh-my-openagent plugin...")
const pluginResult = await addPluginToOpenCodeConfig(version) const pluginResult = await addPluginToOpenCodeConfig(version)
if (!pluginResult.success) { if (!pluginResult.success) {
printError(`Failed: ${pluginResult.error}`) printError(`Failed: ${pluginResult.error}`)
@@ -75,7 +75,7 @@ export async function runCliInstaller(args: InstallArgs, version: string): Promi
`Plugin ${isUpdate ? "verified" : "added"} ${SYMBOLS.arrow} ${color.dim(pluginResult.configPath)}`, `Plugin ${isUpdate ? "verified" : "added"} ${SYMBOLS.arrow} ${color.dim(pluginResult.configPath)}`,
) )
printStep(step++, totalSteps, "Writing oh-my-opencode configuration...") printStep(step++, totalSteps, "Writing oh-my-openagent configuration...")
const omoResult = writeOmoConfig(config) const omoResult = writeOmoConfig(config)
if (!omoResult.success) { if (!omoResult.success) {
printError(`Failed: ${omoResult.error}`) printError(`Failed: ${omoResult.error}`)
+24 -24
View File
@@ -15,14 +15,14 @@ const VERSION = packageJson.version
const program = new Command() const program = new Command()
program program
.name("oh-my-opencode") .name("oh-my-openagent")
.description("The ultimate OpenCode plugin - multi-model orchestration, LSP tools, and more") .description("The ultimate OpenCode plugin - multi-model orchestration, LSP tools, and more")
.version(VERSION, "-v, --version", "Show version number") .version(VERSION, "-v, --version", "Show version number")
.enablePositionalOptions() .enablePositionalOptions()
program program
.command("install") .command("install")
.description("Install and configure oh-my-opencode with interactive setup") .description("Install and configure oh-my-openagent with interactive setup")
.option("--no-tui", "Run in non-interactive mode (requires all options)") .option("--no-tui", "Run in non-interactive mode (requires all options)")
.option("--claude <value>", "Claude subscription: no, yes, max20") .option("--claude <value>", "Claude subscription: no, yes, max20")
.option("--openai <value>", "OpenAI/ChatGPT subscription: no, yes (default: no)") .option("--openai <value>", "OpenAI/ChatGPT subscription: no, yes (default: no)")
@@ -35,9 +35,9 @@ program
.option("--skip-auth", "Skip authentication setup hints") .option("--skip-auth", "Skip authentication setup hints")
.addHelpText("after", ` .addHelpText("after", `
Examples: Examples:
$ bunx oh-my-opencode install $ bunx oh-my-openagent install
$ bunx oh-my-opencode install --no-tui --claude=max20 --openai=yes --gemini=yes --copilot=no $ bunx oh-my-openagent install --no-tui --claude=max20 --openai=yes --gemini=yes --copilot=no
$ bunx oh-my-opencode install --no-tui --claude=no --gemini=no --copilot=yes --opencode-zen=yes $ bunx oh-my-openagent install --no-tui --claude=no --gemini=no --copilot=yes --opencode-zen=yes
Model Providers (Priority: Native > Copilot > OpenCode Zen > Z.ai > Kimi): Model Providers (Priority: Native > Copilot > OpenCode Zen > Z.ai > Kimi):
Claude Native anthropic/ models (Opus, Sonnet, Haiku) Claude Native anthropic/ models (Opus, Sonnet, Haiku)
@@ -82,20 +82,20 @@ program
.option("--session-id <id>", "Resume existing session instead of creating new one") .option("--session-id <id>", "Resume existing session instead of creating new one")
.addHelpText("after", ` .addHelpText("after", `
Examples: Examples:
$ bunx oh-my-opencode run "Fix the bug in index.ts" $ bunx oh-my-openagent run "Fix the bug in index.ts"
$ bunx oh-my-opencode run --agent Sisyphus "Implement feature X" $ bunx oh-my-openagent run --agent Sisyphus "Implement feature X"
$ bunx oh-my-opencode run --port 4321 "Fix the bug" $ bunx oh-my-openagent run --port 4321 "Fix the bug"
$ bunx oh-my-opencode run --attach http://127.0.0.1:4321 "Fix the bug" $ bunx oh-my-openagent run --attach http://127.0.0.1:4321 "Fix the bug"
$ bunx oh-my-opencode run --json "Fix the bug" | jq .sessionId $ bunx oh-my-openagent run --json "Fix the bug" | jq .sessionId
$ bunx oh-my-opencode run --on-complete "notify-send Done" "Fix the bug" $ bunx oh-my-openagent run --on-complete "notify-send Done" "Fix the bug"
$ bunx oh-my-opencode run --session-id ses_abc123 "Continue the work" $ bunx oh-my-openagent run --session-id ses_abc123 "Continue the work"
$ bunx oh-my-opencode run --model anthropic/claude-sonnet-4 "Fix the bug" $ bunx oh-my-openagent run --model anthropic/claude-sonnet-4 "Fix the bug"
$ bunx oh-my-opencode run --agent Sisyphus --model openai/gpt-5.4 "Implement feature X" $ bunx oh-my-openagent run --agent Sisyphus --model openai/gpt-5.4 "Implement feature X"
Agent resolution order: Agent resolution order:
1) --agent flag 1) --agent flag
2) OPENCODE_DEFAULT_AGENT 2) OPENCODE_DEFAULT_AGENT
3) oh-my-opencode.json "default_run_agent" 3) oh-my-openagent.json "default_run_agent"
4) Sisyphus (fallback) 4) Sisyphus (fallback)
Available core agents: Available core agents:
@@ -134,9 +134,9 @@ program
.option("--json", "Output in JSON format for scripting") .option("--json", "Output in JSON format for scripting")
.addHelpText("after", ` .addHelpText("after", `
Examples: Examples:
$ bunx oh-my-opencode get-local-version $ bunx oh-my-openagent get-local-version
$ bunx oh-my-opencode get-local-version --json $ bunx oh-my-openagent get-local-version --json
$ bunx oh-my-opencode get-local-version --directory /path/to/project $ bunx oh-my-openagent get-local-version --directory /path/to/project
This command shows: This command shows:
- Current installed version - Current installed version
@@ -155,16 +155,16 @@ This command shows:
program program
.command("doctor") .command("doctor")
.description("Check oh-my-opencode installation health and diagnose issues") .description("Check oh-my-openagent installation health and diagnose issues")
.option("--status", "Show compact system dashboard") .option("--status", "Show compact system dashboard")
.option("--verbose", "Show detailed diagnostic information") .option("--verbose", "Show detailed diagnostic information")
.option("--json", "Output results in JSON format") .option("--json", "Output results in JSON format")
.addHelpText("after", ` .addHelpText("after", `
Examples: Examples:
$ bunx oh-my-opencode doctor # Show problems only $ bunx oh-my-openagent doctor # Show problems only
$ bunx oh-my-opencode doctor --status # Compact dashboard $ bunx oh-my-openagent doctor --status # Compact dashboard
$ bunx oh-my-opencode doctor --verbose # Deep diagnostics $ bunx oh-my-openagent doctor --verbose # Deep diagnostics
$ bunx oh-my-opencode doctor --json # JSON output $ bunx oh-my-openagent doctor --json # JSON output
`) `)
.action(async (options) => { .action(async (options) => {
const mode = options.status ? "status" : options.verbose ? "verbose" : "default" const mode = options.status ? "status" : options.verbose ? "verbose" : "default"
@@ -180,7 +180,7 @@ program
.command("version") .command("version")
.description("Show version information") .description("Show version information")
.action(() => { .action(() => {
console.log(`oh-my-opencode v${VERSION}`) console.log(`oh-my-openagent v${VERSION}`)
}) })
program.addCommand(createMcpOAuthCommand()) program.addCommand(createMcpOAuthCommand())
+8 -8
View File
@@ -23,7 +23,7 @@ describe("getPluginNameWithVersion", () => {
const result = await getPluginNameWithVersion("2.14.0") const result = await getPluginNameWithVersion("2.14.0")
// #then should use @latest tag // #then should use @latest tag
expect(result).toBe("oh-my-opencode@latest") expect(result).toBe("oh-my-openagent@latest")
}) })
test("returns @beta when current version matches beta tag", async () => { test("returns @beta when current version matches beta tag", async () => {
@@ -39,7 +39,7 @@ describe("getPluginNameWithVersion", () => {
const result = await getPluginNameWithVersion("3.0.0-beta.3") const result = await getPluginNameWithVersion("3.0.0-beta.3")
// #then should use @beta tag // #then should use @beta tag
expect(result).toBe("oh-my-opencode@beta") expect(result).toBe("oh-my-openagent@beta")
}) })
test("returns @next when current version matches next tag", async () => { test("returns @next when current version matches next tag", async () => {
@@ -55,7 +55,7 @@ describe("getPluginNameWithVersion", () => {
const result = await getPluginNameWithVersion("3.1.0-next.1") const result = await getPluginNameWithVersion("3.1.0-next.1")
// #then should use @next tag // #then should use @next tag
expect(result).toBe("oh-my-opencode@next") expect(result).toBe("oh-my-openagent@next")
}) })
test("returns prerelease channel tag when no dist-tag matches prerelease version", async () => { test("returns prerelease channel tag when no dist-tag matches prerelease version", async () => {
@@ -71,7 +71,7 @@ describe("getPluginNameWithVersion", () => {
const result = await getPluginNameWithVersion("3.0.0-beta.2") const result = await getPluginNameWithVersion("3.0.0-beta.2")
// #then should preserve prerelease channel // #then should preserve prerelease channel
expect(result).toBe("oh-my-opencode@beta") expect(result).toBe("oh-my-openagent@beta")
}) })
test("returns prerelease channel tag when fetch fails", async () => { test("returns prerelease channel tag when fetch fails", async () => {
@@ -82,7 +82,7 @@ describe("getPluginNameWithVersion", () => {
const result = await getPluginNameWithVersion("3.0.0-beta.3") const result = await getPluginNameWithVersion("3.0.0-beta.3")
// #then should preserve prerelease channel // #then should preserve prerelease channel
expect(result).toBe("oh-my-opencode@beta") expect(result).toBe("oh-my-openagent@beta")
}) })
test("returns bare package name when npm returns non-ok response for stable version", async () => { test("returns bare package name when npm returns non-ok response for stable version", async () => {
@@ -98,7 +98,7 @@ describe("getPluginNameWithVersion", () => {
const result = await getPluginNameWithVersion("2.14.0") const result = await getPluginNameWithVersion("2.14.0")
// #then should fall back to bare package entry // #then should fall back to bare package entry
expect(result).toBe("oh-my-opencode") expect(result).toBe("oh-my-openagent")
}) })
test("prioritizes latest over other tags when version matches multiple", async () => { test("prioritizes latest over other tags when version matches multiple", async () => {
@@ -114,7 +114,7 @@ describe("getPluginNameWithVersion", () => {
const result = await getPluginNameWithVersion("3.0.0") const result = await getPluginNameWithVersion("3.0.0")
// #then should prioritize @latest // #then should prioritize @latest
expect(result).toBe("oh-my-opencode@latest") expect(result).toBe("oh-my-openagent@latest")
}) })
}) })
@@ -207,7 +207,7 @@ describe("generateOmoConfig - model fallback system", () => {
const result = generateOmoConfig(config) const result = generateOmoConfig(config)
// #then Sisyphus is omitted (requires all fallback providers) // #then Sisyphus is omitted (requires all fallback providers)
expect(result.$schema).toBe("https://raw.githubusercontent.com/code-yeongyu/oh-my-openagent/dev/assets/oh-my-opencode.schema.json") expect(result.$schema).toBe("https://raw.githubusercontent.com/code-yeongyu/oh-my-openagent/dev/assets/oh-my-openagent.schema.json")
expect((result.agents as Record<string, { model: string }>).sisyphus).toBeUndefined() expect((result.agents as Record<string, { model: string }>).sisyphus).toBeUndefined()
}) })
@@ -34,7 +34,7 @@ describe("writeOmoConfig", () => {
beforeEach(() => { beforeEach(() => {
testConfigDir = join(tmpdir(), `omo-write-config-${Date.now()}-${Math.random().toString(36).slice(2)}`) testConfigDir = join(tmpdir(), `omo-write-config-${Date.now()}-${Math.random().toString(36).slice(2)}`)
testConfigPath = join(testConfigDir, "oh-my-opencode.json") testConfigPath = join(testConfigDir, "oh-my-openagent.json")
mkdirSync(testConfigDir, { recursive: true }) mkdirSync(testConfigDir, { recursive: true })
process.env.OPENCODE_CONFIG_DIR = testConfigDir process.env.OPENCODE_CONFIG_DIR = testConfigDir
+1 -1
View File
@@ -61,7 +61,7 @@ export function writeOmoConfig(installConfig: InstallConfig): ConfigMergeResult
return { return {
success: false, success: false,
configPath: omoConfigPath, configPath: omoConfigPath,
error: formatErrorWithSuggestion(err, "write oh-my-opencode config"), error: formatErrorWithSuggestion(err, "write oh-my-openagent config")
} }
} }
} }
@@ -32,7 +32,7 @@ describe("model-resolution check", () => {
}) })
describe("getModelResolutionInfoWithOverrides", () => { describe("getModelResolutionInfoWithOverrides", () => {
// given: User has overrides in oh-my-opencode.json // given: User has overrides in oh-my-openagent.json
// when: Getting resolution info with config // when: Getting resolution info with config
// then: Shows user override in Step 1 position // then: Shows user override in Step 1 position
+6 -6
View File
@@ -5,7 +5,7 @@ const mockGetOpenCodeVersion = mock(async () => "1.0.200")
const mockCompareVersions = mock(() => true) const mockCompareVersions = mock(() => true)
const mockGetPluginInfo = mock(() => ({ const mockGetPluginInfo = mock(() => ({
registered: true, registered: true,
entry: "oh-my-opencode", entry: "oh-my-openagent",
isPinned: false, isPinned: false,
pinnedVersion: null, pinnedVersion: null,
configPath: null, configPath: null,
@@ -14,7 +14,7 @@ const mockGetPluginInfo = mock(() => ({
const mockGetLoadedPluginVersion = mock(() => ({ const mockGetLoadedPluginVersion = mock(() => ({
cacheDir: "/Users/test/Library/Caches/opencode with spaces", cacheDir: "/Users/test/Library/Caches/opencode with spaces",
cachePackagePath: "/tmp/package.json", cachePackagePath: "/tmp/package.json",
installedPackagePath: "/tmp/node_modules/oh-my-opencode/package.json", installedPackagePath: "/tmp/node_modules/oh-my-openagent/package.json",
expectedVersion: "3.0.0", expectedVersion: "3.0.0",
loadedVersion: "3.1.0", loadedVersion: "3.1.0",
})) }))
@@ -51,7 +51,7 @@ describe("system check", () => {
mockCompareVersions.mockReturnValue(true) mockCompareVersions.mockReturnValue(true)
mockGetPluginInfo.mockReturnValue({ mockGetPluginInfo.mockReturnValue({
registered: true, registered: true,
entry: "oh-my-opencode", entry: "oh-my-openagent",
isPinned: false, isPinned: false,
pinnedVersion: null, pinnedVersion: null,
configPath: null, configPath: null,
@@ -60,7 +60,7 @@ describe("system check", () => {
mockGetLoadedPluginVersion.mockReturnValue({ mockGetLoadedPluginVersion.mockReturnValue({
cacheDir: "/Users/test/Library/Caches/opencode with spaces", cacheDir: "/Users/test/Library/Caches/opencode with spaces",
cachePackagePath: "/tmp/package.json", cachePackagePath: "/tmp/package.json",
installedPackagePath: "/tmp/node_modules/oh-my-opencode/package.json", installedPackagePath: "/tmp/node_modules/oh-my-openagent/package.json",
expectedVersion: "3.0.0", expectedVersion: "3.0.0",
loadedVersion: "3.1.0", loadedVersion: "3.1.0",
}) })
@@ -82,7 +82,7 @@ describe("system check", () => {
mockGetLoadedPluginVersion.mockReturnValue({ mockGetLoadedPluginVersion.mockReturnValue({
cacheDir: "/Users/test/Library/Caches/opencode with spaces", cacheDir: "/Users/test/Library/Caches/opencode with spaces",
cachePackagePath: "/tmp/package.json", cachePackagePath: "/tmp/package.json",
installedPackagePath: "/tmp/node_modules/oh-my-opencode/package.json", installedPackagePath: "/tmp/node_modules/oh-my-openagent/package.json",
expectedVersion: "3.0.0-canary.1", expectedVersion: "3.0.0-canary.1",
loadedVersion: "3.0.0-canary.1", loadedVersion: "3.0.0-canary.1",
}) })
@@ -97,7 +97,7 @@ describe("system check", () => {
//#then //#then
const outdatedIssue = result.issues.find((issue) => issue.title === "Loaded plugin is outdated") const outdatedIssue = result.issues.find((issue) => issue.title === "Loaded plugin is outdated")
expect(outdatedIssue?.fix).toBe( expect(outdatedIssue?.fix).toBe(
'Update: cd "/Users/test/Library/Caches/opencode with spaces" && bun add oh-my-opencode@canary' 'Update: cd "/Users/test/Library/Caches/opencode with spaces" && bun add oh-my-openagent@canary'
) )
}) })
}) })
+3 -3
View File
@@ -82,9 +82,9 @@ export async function checkSystem(): Promise<CheckResult> {
if (!pluginInfo.registered) { if (!pluginInfo.registered) {
issues.push({ issues.push({
title: "oh-my-opencode is not registered", title: "oh-my-openagent is not registered",
description: "Plugin entry is missing from OpenCode configuration.", description: "Plugin entry is missing from OpenCode configuration.",
fix: "Run: bunx oh-my-opencode install", fix: "Run: bunx oh-my-openagent install",
severity: "error", severity: "error",
affects: ["all agents"], affects: ["all agents"],
}) })
@@ -108,7 +108,7 @@ export async function checkSystem(): Promise<CheckResult> {
issues.push({ issues.push({
title: "Loaded plugin is outdated", title: "Loaded plugin is outdated",
description: `Loaded ${systemInfo.loadedVersion}, latest ${latestVersion}.`, description: `Loaded ${systemInfo.loadedVersion}, latest ${latestVersion}.`,
fix: `Update: cd "${loadedInfo.cacheDir}" && bun add oh-my-opencode@${installTag}`, fix: `Update: cd "${loadedInfo.cacheDir}" && bun add oh-my-openagent@${installTag}`,
severity: "warning", severity: "warning",
affects: ["plugin features"], affects: ["plugin features"],
}) })
+1 -1
View File
@@ -15,7 +15,7 @@ export function formatDefault(result: DoctorResult): string {
const pluginVer = result.systemInfo.pluginVersion ?? "unknown" const pluginVer = result.systemInfo.pluginVersion ?? "unknown"
lines.push( lines.push(
` ${color.green(SYMBOLS.check)} ${color.green( ` ${color.green(SYMBOLS.check)} ${color.green(
`System OK (opencode ${opencodeVer} · oh-my-opencode ${pluginVer})` `System OK (opencode ${opencodeVer} · oh-my-openagent ${pluginVer})`
)}` )}`
) )
} else { } else {
+1 -1
View File
@@ -12,7 +12,7 @@ export function formatVerbose(result: DoctorResult): string {
lines.push(`${color.bold("System Information")}`) lines.push(`${color.bold("System Information")}`)
lines.push(`${color.dim("\u2500".repeat(40))}`) lines.push(`${color.dim("\u2500".repeat(40))}`)
lines.push(` ${formatStatusSymbol("pass")} opencode ${systemInfo.opencodeVersion ?? "unknown"}`) lines.push(` ${formatStatusSymbol("pass")} opencode ${systemInfo.opencodeVersion ?? "unknown"}`)
lines.push(` ${formatStatusSymbol("pass")} oh-my-opencode ${systemInfo.pluginVersion ?? "unknown"}`) lines.push(` ${formatStatusSymbol("pass")} oh-my-openagent ${systemInfo.pluginVersion ?? "unknown"}`)
if (systemInfo.loadedVersion) { if (systemInfo.loadedVersion) {
lines.push(` ${formatStatusSymbol("pass")} loaded ${systemInfo.loadedVersion}`) lines.push(` ${formatStatusSymbol("pass")} loaded ${systemInfo.loadedVersion}`)
} }
+1 -1
View File
@@ -81,7 +81,7 @@ describe("formatDoctorOutput", () => {
const output = stripAnsi(formatDoctorOutput(result, "default")) const output = stripAnsi(formatDoctorOutput(result, "default"))
//#then //#then
expect(output).toContain("System OK (opencode 1.0.200 · oh-my-opencode 3.4.0)") expect(output).toContain("System OK (opencode 1.0.200 · oh-my-openagent 3.4.0)")
}) })
it("shows issue count and details when issues exist", async () => { it("shows issue count and details when issues exist", async () => {
+2 -2
View File
@@ -15,7 +15,7 @@ export function formatVersionOutput(info: VersionInfo): string {
const lines: string[] = [] const lines: string[] = []
lines.push("") lines.push("")
lines.push(color.bold(color.white("oh-my-opencode Version Information"))) lines.push(color.bold(color.white("oh-my-openagent Version Information")))
lines.push(color.dim("─".repeat(50))) lines.push(color.dim("─".repeat(50)))
lines.push("") lines.push("")
@@ -37,7 +37,7 @@ export function formatVersionOutput(info: VersionInfo): string {
break break
case "outdated": case "outdated":
lines.push(` ${SYMBOLS.warn} ${color.yellow("Update available")}`) lines.push(` ${SYMBOLS.warn} ${color.yellow("Update available")}`)
lines.push(` ${color.dim("Run:")} ${color.cyan("cd ~/.config/opencode && bun update oh-my-opencode")}`) lines.push(` ${color.dim("Run:")} ${color.cyan("cd ~/.config/opencode && bun update oh-my-openagent")}`)
break break
case "local-dev": case "local-dev":
lines.push(` ${SYMBOLS.dev} ${color.cyan("Running in local development mode")}`) lines.push(` ${SYMBOLS.dev} ${color.cyan("Running in local development mode")}`)
+1 -1
View File
@@ -108,7 +108,7 @@ describe("install CLI - binary check behavior", () => {
// then opencode.json should have plugin entry // then opencode.json should have plugin entry
const config = JSON.parse(readFileSync(configPath, "utf-8")) const config = JSON.parse(readFileSync(configPath, "utf-8"))
expect(config.plugin).toBeDefined() expect(config.plugin).toBeDefined()
expect(config.plugin.some((p: string) => p.includes("oh-my-opencode"))).toBe(true) expect(config.plugin.some((p: string) => p.includes("oh-my-openagent"))).toBe(true)
// then exit code should be 0 (success) // then exit code should be 0 (success)
expect(exitCode).toBe(0) expect(exitCode).toBe(0)
+1 -1
View File
@@ -559,7 +559,7 @@ describe("generateModelConfig", () => {
// #then should include correct schema URL // #then should include correct schema URL
expect(result.$schema).toBe( expect(result.$schema).toBe(
"https://raw.githubusercontent.com/code-yeongyu/oh-my-openagent/dev/assets/oh-my-opencode.schema.json" "https://raw.githubusercontent.com/code-yeongyu/oh-my-openagent/dev/assets/oh-my-openagent.schema.json"
) )
}) })
}) })
+1 -1
View File
@@ -20,7 +20,7 @@ export type { GeneratedOmoConfig } from "./model-fallback-types"
const ZAI_MODEL = "zai-coding-plan/glm-4.7" const ZAI_MODEL = "zai-coding-plan/glm-4.7"
const ULTIMATE_FALLBACK = "opencode/gpt-5-nano" const ULTIMATE_FALLBACK = "opencode/gpt-5-nano"
const SCHEMA_URL = "https://raw.githubusercontent.com/code-yeongyu/oh-my-openagent/dev/assets/oh-my-opencode.schema.json" const SCHEMA_URL = "https://raw.githubusercontent.com/code-yeongyu/oh-my-openagent/dev/assets/oh-my-openagent.schema.json"
+2 -2
View File
@@ -86,7 +86,7 @@ describe("resolveSession", () => {
expect(result).toBe("new-session-id") expect(result).toBe("new-session-id")
expect(mockClient.session.create).toHaveBeenCalledWith({ expect(mockClient.session.create).toHaveBeenCalledWith({
body: { body: {
title: "oh-my-opencode run", title: "oh-my-openagent run",
permission: [ permission: [
{ permission: "question", action: "deny", pattern: "*" }, { permission: "question", action: "deny", pattern: "*" },
], ],
@@ -113,7 +113,7 @@ describe("resolveSession", () => {
expect(mockClient.session.create).toHaveBeenCalledTimes(2) expect(mockClient.session.create).toHaveBeenCalledTimes(2)
expect(mockClient.session.create).toHaveBeenCalledWith({ expect(mockClient.session.create).toHaveBeenCalledWith({
body: { body: {
title: "oh-my-opencode run", title: "oh-my-openagent run",
permission: [ permission: [
{ permission: "question", action: "deny", pattern: "*" }, { permission: "question", action: "deny", pattern: "*" },
], ],
+1 -1
View File
@@ -26,7 +26,7 @@ export async function resolveSession(options: {
for (let attempt = 1; attempt <= SESSION_CREATE_MAX_RETRIES; attempt++) { for (let attempt = 1; attempt <= SESSION_CREATE_MAX_RETRIES; attempt++) {
const res = await client.session.create({ const res = await client.session.create({
body: { body: {
title: "oh-my-opencode run", title: "oh-my-openagent run",
// In CLI run mode there's no TUI to answer questions. // In CLI run mode there's no TUI to answer questions.
permission: [ permission: [
{ permission: "question", action: "deny" as const, pattern: "*" }, { permission: "question", action: "deny" as const, pattern: "*" },
+2 -2
View File
@@ -43,7 +43,7 @@ export async function runTuiInstaller(args: InstallArgs, version: string): Promi
const config = await promptInstallConfig(detected) const config = await promptInstallConfig(detected)
if (!config) return 1 if (!config) return 1
spinner.start("Adding oh-my-opencode to OpenCode config") spinner.start("Adding oh-my-openagent to OpenCode config")
const pluginResult = await addPluginToOpenCodeConfig(version) const pluginResult = await addPluginToOpenCodeConfig(version)
if (!pluginResult.success) { if (!pluginResult.success) {
spinner.stop(`Failed to add plugin: ${pluginResult.error}`) spinner.stop(`Failed to add plugin: ${pluginResult.error}`)
@@ -52,7 +52,7 @@ export async function runTuiInstaller(args: InstallArgs, version: string): Promi
} }
spinner.stop(`Plugin added to ${color.cyan(pluginResult.configPath)}`) spinner.stop(`Plugin added to ${color.cyan(pluginResult.configPath)}`)
spinner.start("Writing oh-my-opencode configuration") spinner.start("Writing oh-my-openagent configuration")
const omoResult = writeOmoConfig(config) const omoResult = writeOmoConfig(config)
if (!omoResult.success) { if (!omoResult.success) {
spinner.stop(`Failed to write config: ${omoResult.error}`) spinner.stop(`Failed to write config: ${omoResult.error}`)
+1 -1
View File
@@ -25,7 +25,7 @@ export const OhMyOpenAgentConfigSchema = z.object({
$schema: z.string().optional(), $schema: z.string().optional(),
/** Enable new task system (default: false) */ /** Enable new task system (default: false) */
new_task_system_enabled: z.boolean().optional(), new_task_system_enabled: z.boolean().optional(),
/** Default agent name for `oh-my-opencode run` (env: OPENCODE_DEFAULT_AGENT) */ /** Default agent name for `oh-my-openagent run` (env: OPENCODE_DEFAULT_AGENT) */
default_run_agent: z.string().optional(), default_run_agent: z.string().optional(),
disabled_mcps: z.array(AnyMcpNameSchema).optional(), disabled_mcps: z.array(AnyMcpNameSchema).optional(),
disabled_agents: z.array(z.string()).optional(), disabled_agents: z.array(z.string()).optional(),
@@ -222,11 +222,12 @@ export interface ClaudeSettings {
*/ */
export interface PluginLoaderOptions { export interface PluginLoaderOptions {
/** /**
* Override enabled plugins from oh-my-opencode config. * Override enabled plugins from oh-my-openagent config.
* Key format: "pluginName@marketplace" (e.g., "shell-scripting@claude-code-workflows") * Key format: "pluginName@marketplace" (e.g., "shell-scripting@claude-code-workflows")
* Value: true = enabled, false = disabled * Value: true = enabled, false = disabled
* *
* This takes precedence over ~/.claude/settings.json enabledPlugins * This takes precedence over ~/.claude/settings.json enabledPlugins
*/ */
enabledPluginsOverride?: Record<string, boolean> enabledPluginsOverride?: Record<string, boolean>
} }
+11 -11
View File
@@ -9,7 +9,7 @@ const TEST_USER_CONFIG_DIR = "/tmp/opencode-config"
mock.module("./constants", () => ({ mock.module("./constants", () => ({
CACHE_DIR: TEST_OPENCODE_CACHE_DIR, CACHE_DIR: TEST_OPENCODE_CACHE_DIR,
USER_CONFIG_DIR: TEST_USER_CONFIG_DIR, USER_CONFIG_DIR: TEST_USER_CONFIG_DIR,
PACKAGE_NAME: "oh-my-opencode", PACKAGE_NAME: "oh-my-openagent",
})) }))
mock.module("../../shared/logger", () => ({ mock.module("../../shared/logger", () => ({
@@ -21,10 +21,10 @@ function resetTestCache(): void {
rmSync(TEST_CACHE_DIR, { recursive: true, force: true }) rmSync(TEST_CACHE_DIR, { recursive: true, force: true })
} }
mkdirSync(join(TEST_OPENCODE_CACHE_DIR, "node_modules", "oh-my-opencode"), { recursive: true }) mkdirSync(join(TEST_OPENCODE_CACHE_DIR, "node_modules", "oh-my-openagent"), { recursive: true })
writeFileSync( writeFileSync(
join(TEST_OPENCODE_CACHE_DIR, "package.json"), join(TEST_OPENCODE_CACHE_DIR, "package.json"),
JSON.stringify({ dependencies: { "oh-my-opencode": "latest", other: "1.0.0" } }, null, 2) JSON.stringify({ dependencies: { "oh-my-openagent": "latest", other: "1.0.0" } }, null, 2)
) )
writeFileSync( writeFileSync(
join(TEST_OPENCODE_CACHE_DIR, "bun.lock"), join(TEST_OPENCODE_CACHE_DIR, "bun.lock"),
@@ -32,11 +32,11 @@ function resetTestCache(): void {
{ {
workspaces: { workspaces: {
"": { "": {
dependencies: { "oh-my-opencode": "latest", other: "1.0.0" }, dependencies: { "oh-my-openagent": "latest", other: "1.0.0" },
}, },
}, },
packages: { packages: {
"oh-my-opencode": {}, "oh-my-openagent": {},
other: {}, other: {},
}, },
}, },
@@ -45,8 +45,8 @@ function resetTestCache(): void {
) )
) )
writeFileSync( writeFileSync(
join(TEST_OPENCODE_CACHE_DIR, "node_modules", "oh-my-opencode", "package.json"), join(TEST_OPENCODE_CACHE_DIR, "node_modules", "oh-my-openagent", "package.json"),
'{"name":"oh-my-opencode"}' '{"name":"oh-my-openagent"}'
) )
} }
@@ -67,21 +67,21 @@ describe("invalidatePackage", () => {
const result = invalidatePackage() const result = invalidatePackage()
expect(result).toBe(true) expect(result).toBe(true)
expect(existsSync(join(TEST_OPENCODE_CACHE_DIR, "node_modules", "oh-my-opencode"))).toBe(false) expect(existsSync(join(TEST_OPENCODE_CACHE_DIR, "node_modules", "oh-my-openagent"))).toBe(false)
const packageJson = JSON.parse(readFileSync(join(TEST_OPENCODE_CACHE_DIR, "package.json"), "utf-8")) as { const packageJson = JSON.parse(readFileSync(join(TEST_OPENCODE_CACHE_DIR, "package.json"), "utf-8")) as {
dependencies?: Record<string, string> dependencies?: Record<string, string>
} }
expect(packageJson.dependencies?.["oh-my-opencode"]).toBe("latest") expect(packageJson.dependencies?.["oh-my-openagent"]).toBe("latest")
expect(packageJson.dependencies?.other).toBe("1.0.0") expect(packageJson.dependencies?.other).toBe("1.0.0")
const bunLock = JSON.parse(readFileSync(join(TEST_OPENCODE_CACHE_DIR, "bun.lock"), "utf-8")) as { const bunLock = JSON.parse(readFileSync(join(TEST_OPENCODE_CACHE_DIR, "bun.lock"), "utf-8")) as {
workspaces?: { ""?: { dependencies?: Record<string, string> } } workspaces?: { ""?: { dependencies?: Record<string, string> } }
packages?: Record<string, unknown> packages?: Record<string, unknown>
} }
expect(bunLock.workspaces?.[""]?.dependencies?.["oh-my-opencode"]).toBe("latest") expect(bunLock.workspaces?.[""]?.dependencies?.["oh-my-openagent"]).toBe("latest")
expect(bunLock.workspaces?.[""]?.dependencies?.other).toBe("1.0.0") expect(bunLock.workspaces?.[""]?.dependencies?.other).toBe("1.0.0")
expect(bunLock.packages?.["oh-my-opencode"]).toBeUndefined() expect(bunLock.packages?.["oh-my-openagent"]).toBeUndefined()
expect(bunLock.packages?.other).toEqual({}) expect(bunLock.packages?.other).toEqual({})
}) })
}) })
@@ -21,18 +21,18 @@ describe("pinned-version-updater", () => {
test("updates pinned version in config", () => { test("updates pinned version in config", () => {
//#given //#given
const config = JSON.stringify({ const config = JSON.stringify({
plugin: ["oh-my-opencode@3.1.8"], plugin: ["oh-my-openagent@3.1.8"],
}) })
fs.writeFileSync(configPath, config) fs.writeFileSync(configPath, config)
//#when //#when
const result = updatePinnedVersion(configPath, "oh-my-opencode@3.1.8", "3.4.0") const result = updatePinnedVersion(configPath, "oh-my-openagent@3.1.8", "3.4.0")
//#then //#then
expect(result).toBe(true) expect(result).toBe(true)
const updated = fs.readFileSync(configPath, "utf-8") const updated = fs.readFileSync(configPath, "utf-8")
expect(updated).toContain("oh-my-opencode@3.4.0") expect(updated).toContain("oh-my-openagent@3.4.0")
expect(updated).not.toContain("oh-my-opencode@3.1.8") expect(updated).not.toContain("oh-my-openagent@3.1.8")
}) })
test("returns false when entry not found", () => { test("returns false when entry not found", () => {
@@ -43,7 +43,7 @@ describe("pinned-version-updater", () => {
fs.writeFileSync(configPath, config) fs.writeFileSync(configPath, config)
//#when //#when
const result = updatePinnedVersion(configPath, "oh-my-opencode@3.1.8", "3.4.0") const result = updatePinnedVersion(configPath, "oh-my-openagent@3.1.8", "3.4.0")
//#then //#then
expect(result).toBe(false) expect(result).toBe(false)
@@ -55,7 +55,7 @@ describe("pinned-version-updater", () => {
fs.writeFileSync(configPath, config) fs.writeFileSync(configPath, config)
//#when //#when
const result = updatePinnedVersion(configPath, "oh-my-opencode@3.1.8", "3.4.0") const result = updatePinnedVersion(configPath, "oh-my-openagent@3.1.8", "3.4.0")
//#then //#then
expect(result).toBe(false) expect(result).toBe(false)
@@ -66,46 +66,46 @@ describe("pinned-version-updater", () => {
test("reverts from failed version back to original entry", () => { test("reverts from failed version back to original entry", () => {
//#given //#given
const config = JSON.stringify({ const config = JSON.stringify({
plugin: ["oh-my-opencode@3.4.0"], plugin: ["oh-my-openagent@3.4.0"],
}) })
fs.writeFileSync(configPath, config) fs.writeFileSync(configPath, config)
//#when //#when
const result = revertPinnedVersion(configPath, "3.4.0", "oh-my-opencode@3.1.8") const result = revertPinnedVersion(configPath, "3.4.0", "oh-my-openagent@3.1.8")
//#then //#then
expect(result).toBe(true) expect(result).toBe(true)
const reverted = fs.readFileSync(configPath, "utf-8") const reverted = fs.readFileSync(configPath, "utf-8")
expect(reverted).toContain("oh-my-opencode@3.1.8") expect(reverted).toContain("oh-my-openagent@3.1.8")
expect(reverted).not.toContain("oh-my-opencode@3.4.0") expect(reverted).not.toContain("oh-my-openagent@3.4.0")
}) })
test("reverts to unpinned entry", () => { test("reverts to unpinned entry", () => {
//#given //#given
const config = JSON.stringify({ const config = JSON.stringify({
plugin: ["oh-my-opencode@3.4.0"], plugin: ["oh-my-openagent@3.4.0"],
}) })
fs.writeFileSync(configPath, config) fs.writeFileSync(configPath, config)
//#when //#when
const result = revertPinnedVersion(configPath, "3.4.0", "oh-my-opencode") const result = revertPinnedVersion(configPath, "3.4.0", "oh-my-openagent")
//#then //#then
expect(result).toBe(true) expect(result).toBe(true)
const reverted = fs.readFileSync(configPath, "utf-8") const reverted = fs.readFileSync(configPath, "utf-8")
expect(reverted).toContain('"oh-my-opencode"') expect(reverted).toContain('"oh-my-openagent"')
expect(reverted).not.toContain("oh-my-opencode@3.4.0") expect(reverted).not.toContain("oh-my-openagent@3.4.0")
}) })
test("returns false when failed version not found", () => { test("returns false when failed version not found", () => {
//#given //#given
const config = JSON.stringify({ const config = JSON.stringify({
plugin: ["oh-my-opencode@3.1.8"], plugin: ["oh-my-openagent@3.1.8"],
}) })
fs.writeFileSync(configPath, config) fs.writeFileSync(configPath, config)
//#when //#when
const result = revertPinnedVersion(configPath, "3.4.0", "oh-my-opencode@3.1.8") const result = revertPinnedVersion(configPath, "3.4.0", "oh-my-openagent@3.1.8")
//#then //#then
expect(result).toBe(false) expect(result).toBe(false)
@@ -116,18 +116,18 @@ describe("pinned-version-updater", () => {
test("config returns to original state after update + revert", () => { test("config returns to original state after update + revert", () => {
//#given //#given
const originalConfig = JSON.stringify({ const originalConfig = JSON.stringify({
plugin: ["oh-my-opencode@3.1.8"], plugin: ["oh-my-openagent@3.1.8"],
}) })
fs.writeFileSync(configPath, originalConfig) fs.writeFileSync(configPath, originalConfig)
//#when //#when
updatePinnedVersion(configPath, "oh-my-opencode@3.1.8", "3.4.0") updatePinnedVersion(configPath, "oh-my-openagent@3.1.8", "3.4.0")
revertPinnedVersion(configPath, "3.4.0", "oh-my-opencode@3.1.8") revertPinnedVersion(configPath, "3.4.0", "oh-my-openagent@3.1.8")
//#then //#then
const finalConfig = fs.readFileSync(configPath, "utf-8") const finalConfig = fs.readFileSync(configPath, "utf-8")
expect(finalConfig).toContain("oh-my-opencode@3.1.8") expect(finalConfig).toContain("oh-my-openagent@3.1.8")
expect(finalConfig).not.toContain("oh-my-opencode@3.4.0") expect(finalConfig).not.toContain("oh-my-openagent@3.4.0")
}) })
}) })
}) })
@@ -21,7 +21,7 @@ describe("findPluginEntry", () => {
test("returns unpinned for bare package name", () => { test("returns unpinned for bare package name", () => {
// #given plugin is configured without a tag // #given plugin is configured without a tag
fs.writeFileSync(configPath, JSON.stringify({ plugin: ["oh-my-opencode"] })) fs.writeFileSync(configPath, JSON.stringify({ plugin: ["oh-my-openagent"] }))
// #when plugin entry is detected // #when plugin entry is detected
const pluginInfo = findPluginEntry(temporaryDirectory) const pluginInfo = findPluginEntry(temporaryDirectory)
@@ -34,7 +34,7 @@ describe("findPluginEntry", () => {
test("returns unpinned for latest dist-tag", () => { test("returns unpinned for latest dist-tag", () => {
// #given plugin is configured with latest dist-tag // #given plugin is configured with latest dist-tag
fs.writeFileSync(configPath, JSON.stringify({ plugin: ["oh-my-opencode@latest"] })) fs.writeFileSync(configPath, JSON.stringify({ plugin: ["oh-my-openagent@latest"] }))
// #when plugin entry is detected // #when plugin entry is detected
const pluginInfo = findPluginEntry(temporaryDirectory) const pluginInfo = findPluginEntry(temporaryDirectory)
@@ -47,7 +47,7 @@ describe("findPluginEntry", () => {
test("returns unpinned for beta dist-tag", () => { test("returns unpinned for beta dist-tag", () => {
// #given plugin is configured with beta dist-tag // #given plugin is configured with beta dist-tag
fs.writeFileSync(configPath, JSON.stringify({ plugin: ["oh-my-opencode@beta"] })) fs.writeFileSync(configPath, JSON.stringify({ plugin: ["oh-my-openagent@beta"] }))
// #when plugin entry is detected // #when plugin entry is detected
const pluginInfo = findPluginEntry(temporaryDirectory) const pluginInfo = findPluginEntry(temporaryDirectory)
@@ -60,7 +60,7 @@ describe("findPluginEntry", () => {
test("returns pinned for explicit semver", () => { test("returns pinned for explicit semver", () => {
// #given plugin is configured with explicit version // #given plugin is configured with explicit version
fs.writeFileSync(configPath, JSON.stringify({ plugin: ["oh-my-opencode@3.5.2"] })) fs.writeFileSync(configPath, JSON.stringify({ plugin: ["oh-my-openagent@3.5.2"] }))
// #when plugin entry is detected // #when plugin entry is detected
const pluginInfo = findPluginEntry(temporaryDirectory) const pluginInfo = findPluginEntry(temporaryDirectory)
@@ -7,14 +7,14 @@ const TEST_CACHE_DIR = join(import.meta.dir, "__test-sync-cache__")
mock.module("../constants", () => ({ mock.module("../constants", () => ({
CACHE_DIR: TEST_CACHE_DIR, CACHE_DIR: TEST_CACHE_DIR,
PACKAGE_NAME: "oh-my-opencode", PACKAGE_NAME: "oh-my-openagent",
NPM_REGISTRY_URL: "https://registry.npmjs.org/-/package/oh-my-opencode/dist-tags", NPM_REGISTRY_URL: "https://registry.npmjs.org/-/package/oh-my-openagent/dist-tags",
NPM_FETCH_TIMEOUT: 5000, NPM_FETCH_TIMEOUT: 5000,
VERSION_FILE: join(TEST_CACHE_DIR, "version"), VERSION_FILE: join(TEST_CACHE_DIR, "version"),
USER_CONFIG_DIR: "/tmp/opencode-config", USER_CONFIG_DIR: "/tmp/opencode-config",
USER_OPENCODE_CONFIG: "/tmp/opencode-config/opencode.json", USER_OPENCODE_CONFIG: "/tmp/opencode-config/opencode.json",
USER_OPENCODE_CONFIG_JSONC: "/tmp/opencode-config/opencode.jsonc", USER_OPENCODE_CONFIG_JSONC: "/tmp/opencode-config/opencode.jsonc",
INSTALLED_PACKAGE_JSON: join(TEST_CACHE_DIR, "node_modules", "oh-my-opencode", "package.json"), INSTALLED_PACKAGE_JSON: join(TEST_CACHE_DIR, "node_modules", "oh-my-openagent", "package.json"),
getWindowsAppdataDir: () => null, getWindowsAppdataDir: () => null,
})) }))
@@ -30,7 +30,7 @@ function resetTestCache(currentVersion = "3.10.0"): void {
mkdirSync(TEST_CACHE_DIR, { recursive: true }) mkdirSync(TEST_CACHE_DIR, { recursive: true })
writeFileSync( writeFileSync(
join(TEST_CACHE_DIR, "package.json"), join(TEST_CACHE_DIR, "package.json"),
JSON.stringify({ dependencies: { "oh-my-opencode": currentVersion, other: "1.0.0" } }, null, 2) JSON.stringify({ dependencies: { "oh-my-openagent": currentVersion, other: "1.0.0" } }, null, 2)
) )
} }
@@ -43,7 +43,7 @@ function cleanupTestCache(): void {
function readCachePackageJsonVersion(): string | undefined { function readCachePackageJsonVersion(): string | undefined {
const content = readFileSync(join(TEST_CACHE_DIR, "package.json"), "utf-8") const content = readFileSync(join(TEST_CACHE_DIR, "package.json"), "utf-8")
const pkg = JSON.parse(content) as { dependencies?: Record<string, string> } const pkg = JSON.parse(content) as { dependencies?: Record<string, string> }
return pkg.dependencies?.["oh-my-opencode"] return pkg.dependencies?.["oh-my-openagent"]
} }
describe("syncCachePackageJsonToIntent", () => { describe("syncCachePackageJsonToIntent", () => {
@@ -61,7 +61,7 @@ describe("syncCachePackageJsonToIntent", () => {
const { syncCachePackageJsonToIntent } = await import("./sync-package-json") const { syncCachePackageJsonToIntent } = await import("./sync-package-json")
const pluginInfo: PluginEntryInfo = { const pluginInfo: PluginEntryInfo = {
entry: "oh-my-opencode@latest", entry: "oh-my-openagent@latest",
isPinned: false, isPinned: false,
pinnedVersion: "latest", pinnedVersion: "latest",
configPath: "/tmp/opencode.json", configPath: "/tmp/opencode.json",
@@ -80,7 +80,7 @@ describe("syncCachePackageJsonToIntent", () => {
const { syncCachePackageJsonToIntent } = await import("./sync-package-json") const { syncCachePackageJsonToIntent } = await import("./sync-package-json")
const pluginInfo: PluginEntryInfo = { const pluginInfo: PluginEntryInfo = {
entry: "oh-my-opencode@next", entry: "oh-my-openagent@next",
isPinned: false, isPinned: false,
pinnedVersion: "next", pinnedVersion: "next",
configPath: "/tmp/opencode.json", configPath: "/tmp/opencode.json",
@@ -99,7 +99,7 @@ describe("syncCachePackageJsonToIntent", () => {
const { syncCachePackageJsonToIntent } = await import("./sync-package-json") const { syncCachePackageJsonToIntent } = await import("./sync-package-json")
const pluginInfo: PluginEntryInfo = { const pluginInfo: PluginEntryInfo = {
entry: "oh-my-opencode", entry: "oh-my-openagent",
isPinned: false, isPinned: false,
pinnedVersion: null, pinnedVersion: null,
configPath: "/tmp/opencode.json", configPath: "/tmp/opencode.json",
@@ -120,7 +120,7 @@ describe("syncCachePackageJsonToIntent", () => {
const { syncCachePackageJsonToIntent } = await import("./sync-package-json") const { syncCachePackageJsonToIntent } = await import("./sync-package-json")
const pluginInfo: PluginEntryInfo = { const pluginInfo: PluginEntryInfo = {
entry: "oh-my-opencode@latest", entry: "oh-my-openagent@latest",
isPinned: false, isPinned: false,
pinnedVersion: "latest", pinnedVersion: "latest",
configPath: "/tmp/opencode.json", configPath: "/tmp/opencode.json",
@@ -140,7 +140,7 @@ describe("syncCachePackageJsonToIntent", () => {
const { syncCachePackageJsonToIntent } = await import("./sync-package-json") const { syncCachePackageJsonToIntent } = await import("./sync-package-json")
const pluginInfo: PluginEntryInfo = { const pluginInfo: PluginEntryInfo = {
entry: "oh-my-opencode@latest", entry: "oh-my-openagent@latest",
isPinned: false, isPinned: false,
pinnedVersion: "latest", pinnedVersion: "latest",
configPath: "/tmp/opencode.json", configPath: "/tmp/opencode.json",
@@ -165,7 +165,7 @@ describe("syncCachePackageJsonToIntent", () => {
const { syncCachePackageJsonToIntent } = await import("./sync-package-json") const { syncCachePackageJsonToIntent } = await import("./sync-package-json")
const pluginInfo: PluginEntryInfo = { const pluginInfo: PluginEntryInfo = {
entry: "oh-my-opencode@latest", entry: "oh-my-openagent@latest",
isPinned: false, isPinned: false,
pinnedVersion: "latest", pinnedVersion: "latest",
configPath: "/tmp/opencode.json", configPath: "/tmp/opencode.json",
@@ -184,7 +184,7 @@ describe("syncCachePackageJsonToIntent", () => {
const { syncCachePackageJsonToIntent } = await import("./sync-package-json") const { syncCachePackageJsonToIntent } = await import("./sync-package-json")
const pluginInfo: PluginEntryInfo = { const pluginInfo: PluginEntryInfo = {
entry: "oh-my-opencode@3.10.0", entry: "oh-my-openagent@3.10.0",
isPinned: true, isPinned: true,
pinnedVersion: "3.10.0", pinnedVersion: "3.10.0",
configPath: "/tmp/opencode.json", configPath: "/tmp/opencode.json",
@@ -203,7 +203,7 @@ describe("syncCachePackageJsonToIntent", () => {
const { syncCachePackageJsonToIntent } = await import("./sync-package-json") const { syncCachePackageJsonToIntent } = await import("./sync-package-json")
const pluginInfo: PluginEntryInfo = { const pluginInfo: PluginEntryInfo = {
entry: "oh-my-opencode@latest", entry: "oh-my-openagent@latest",
isPinned: false, isPinned: false,
pinnedVersion: "latest", pinnedVersion: "latest",
configPath: "/tmp/opencode.json", configPath: "/tmp/opencode.json",
@@ -229,7 +229,7 @@ describe("syncCachePackageJsonToIntent", () => {
const { syncCachePackageJsonToIntent } = await import("./sync-package-json") const { syncCachePackageJsonToIntent } = await import("./sync-package-json")
const pluginInfo: PluginEntryInfo = { const pluginInfo: PluginEntryInfo = {
entry: "oh-my-opencode@latest", entry: "oh-my-openagent@latest",
isPinned: false, isPinned: false,
pinnedVersion: "latest", pinnedVersion: "latest",
configPath: "/tmp/opencode.json", configPath: "/tmp/opencode.json",
@@ -248,7 +248,7 @@ describe("syncCachePackageJsonToIntent", () => {
mkdirSync(TEST_CACHE_DIR, { recursive: true }) mkdirSync(TEST_CACHE_DIR, { recursive: true })
writeFileSync( writeFileSync(
join(TEST_CACHE_DIR, "package.json"), join(TEST_CACHE_DIR, "package.json"),
JSON.stringify({ dependencies: { "oh-my-opencode": "3.10.0" } }, null, 2) JSON.stringify({ dependencies: { "oh-my-openagent": "3.10.0" } }, null, 2)
) )
const fs = await import("node:fs") const fs = await import("node:fs")
@@ -267,7 +267,7 @@ describe("syncCachePackageJsonToIntent", () => {
const { syncCachePackageJsonToIntent } = await import("./sync-package-json") const { syncCachePackageJsonToIntent } = await import("./sync-package-json")
const pluginInfo: PluginEntryInfo = { const pluginInfo: PluginEntryInfo = {
entry: "oh-my-opencode@latest", entry: "oh-my-openagent@latest",
isPinned: false, isPinned: false,
pinnedVersion: "latest", pinnedVersion: "latest",
configPath: "/tmp/opencode.json", configPath: "/tmp/opencode.json",
@@ -293,7 +293,7 @@ describe("syncCachePackageJsonToIntent", () => {
mkdirSync(TEST_CACHE_DIR, { recursive: true }) mkdirSync(TEST_CACHE_DIR, { recursive: true })
writeFileSync( writeFileSync(
join(TEST_CACHE_DIR, "package.json"), join(TEST_CACHE_DIR, "package.json"),
JSON.stringify({ dependencies: { "oh-my-opencode": "3.10.0" } }, null, 2) JSON.stringify({ dependencies: { "oh-my-openagent": "3.10.0" } }, null, 2)
) )
const fs = await import("node:fs") const fs = await import("node:fs")
@@ -317,7 +317,7 @@ describe("syncCachePackageJsonToIntent", () => {
const { syncCachePackageJsonToIntent } = await import("./sync-package-json") const { syncCachePackageJsonToIntent } = await import("./sync-package-json")
const pluginInfo: PluginEntryInfo = { const pluginInfo: PluginEntryInfo = {
entry: "oh-my-opencode@latest", entry: "oh-my-openagent@latest",
isPinned: false, isPinned: false,
pinnedVersion: "latest", pinnedVersion: "latest",
configPath: "/tmp/opencode.json", configPath: "/tmp/opencode.json",
+2 -1
View File
@@ -2,8 +2,9 @@ import * as path from "node:path"
import * as os from "node:os" import * as os from "node:os"
import { getOpenCodeCacheDir } from "../../shared/data-path" import { getOpenCodeCacheDir } from "../../shared/data-path"
import { getOpenCodeConfigDir } from "../../shared/opencode-config-dir" import { getOpenCodeConfigDir } from "../../shared/opencode-config-dir"
import { PLUGIN_NAME } from "../../shared/plugin-identity"
export const PACKAGE_NAME = "oh-my-opencode" export const PACKAGE_NAME = PLUGIN_NAME
export const NPM_REGISTRY_URL = `https://registry.npmjs.org/-/package/${PACKAGE_NAME}/dist-tags` export const NPM_REGISTRY_URL = `https://registry.npmjs.org/-/package/${PACKAGE_NAME}/dist-tags`
export const NPM_FETCH_TIMEOUT = 5000 export const NPM_FETCH_TIMEOUT = 5000
@@ -12,7 +12,7 @@ type ToastMessageGetter = (isUpdate: boolean, version?: string) => string
function createPluginEntry(overrides?: Partial<PluginEntry>): PluginEntry { function createPluginEntry(overrides?: Partial<PluginEntry>): PluginEntry {
return { return {
entry: "oh-my-opencode@3.4.0", entry: "oh-my-openagent@3.4.0",
isPinned: false, isPinned: false,
pinnedVersion: null, pinnedVersion: null,
configPath: "/test/opencode.json", configPath: "/test/opencode.json",
@@ -95,7 +95,7 @@ describe("runBackgroundUpdateCheck", () => {
describe("#given no version available", () => { describe("#given no version available", () => {
it("returns early when neither cached nor pinned version exists", async () => { it("returns early when neither cached nor pinned version exists", async () => {
//#given //#given
mockFindPluginEntry.mockReturnValue(createPluginEntry({ entry: "oh-my-opencode" })) mockFindPluginEntry.mockReturnValue(createPluginEntry({ entry: "oh-my-openagent" }))
mockGetCachedVersion.mockReturnValue(null) mockGetCachedVersion.mockReturnValue(null)
//#when //#when
await runBackgroundUpdateCheck(mockCtx, true, getToastMessage) await runBackgroundUpdateCheck(mockCtx, true, getToastMessage)
@@ -12,7 +12,7 @@ export async function showSpinnerToast(ctx: PluginInput, version: string, messag
await ctx.client.tui await ctx.client.tui
.showToast({ .showToast({
body: { body: {
title: `${spinner} OhMyOpenCode ${version}`, title: `${spinner} OhMyOpenAgent ${version}`,
message, message,
variant: "info" as const, variant: "info" as const,
duration: frameInterval + 50, duration: frameInterval + 50,
@@ -9,7 +9,7 @@ export async function showUpdateAvailableToast(
await ctx.client.tui await ctx.client.tui
.showToast({ .showToast({
body: { body: {
title: `OhMyOpenCode ${latestVersion}`, title: `OhMyOpenAgent ${latestVersion}`,
message: getToastMessage(true, latestVersion), message: getToastMessage(true, latestVersion),
variant: "info" as const, variant: "info" as const,
duration: 8000, duration: 8000,
@@ -23,7 +23,7 @@ export async function showAutoUpdatedToast(ctx: PluginInput, oldVersion: string,
await ctx.client.tui await ctx.client.tui
.showToast({ .showToast({
body: { body: {
title: "OhMyOpenCode Updated!", title: "OhMyOpenAgent Updated!",
message: `v${oldVersion} → v${newVersion}\nRestart OpenCode to apply.`, message: `v${oldVersion} → v${newVersion}\nRestart OpenCode to apply.`,
variant: "success" as const, variant: "success" as const,
duration: 8000, duration: 8000,
+7 -7
View File
@@ -40,7 +40,7 @@ const PLATFORM_MAP: Record<string, PlatformInfo> = {
} }
/** /**
* Get the cache directory for oh-my-opencode binaries. * Get the cache directory for oh-my-openagent binaries.
* On Windows: Uses %LOCALAPPDATA% or %APPDATA% (Windows conventions) * On Windows: Uses %LOCALAPPDATA% or %APPDATA% (Windows conventions)
* On Unix: Follows XDG Base Directory Specification * On Unix: Follows XDG Base Directory Specification
*/ */
@@ -48,12 +48,12 @@ export function getCacheDir(): string {
if (process.platform === "win32") { if (process.platform === "win32") {
const localAppData = process.env.LOCALAPPDATA || process.env.APPDATA const localAppData = process.env.LOCALAPPDATA || process.env.APPDATA
const base = localAppData || join(homedir(), "AppData", "Local") const base = localAppData || join(homedir(), "AppData", "Local")
return join(base, "oh-my-opencode", "bin") return join(base, "oh-my-openagent", "bin")
} }
const xdgCache = process.env.XDG_CACHE_HOME const xdgCache = process.env.XDG_CACHE_HOME
const base = xdgCache || join(homedir(), ".cache") const base = xdgCache || join(homedir(), ".cache")
return join(base, "oh-my-opencode", "bin") return join(base, "oh-my-openagent", "bin")
} }
/** /**
@@ -113,7 +113,7 @@ export async function downloadCommentChecker(): Promise<string | null> {
const downloadUrl = `https://github.com/${REPO}/releases/download/v${version}/${assetName}` const downloadUrl = `https://github.com/${REPO}/releases/download/v${version}/${assetName}`
debugLog(`Downloading from: ${downloadUrl}`) debugLog(`Downloading from: ${downloadUrl}`)
log(`[oh-my-opencode] Downloading comment-checker binary...`) log(`[oh-my-openagent] Downloading comment-checker binary...`)
try { try {
// Ensure cache directory exists // Ensure cache directory exists
@@ -139,14 +139,14 @@ export async function downloadCommentChecker(): Promise<string | null> {
ensureExecutable(binaryPath) ensureExecutable(binaryPath)
debugLog(`Successfully downloaded binary to: ${binaryPath}`) debugLog(`Successfully downloaded binary to: ${binaryPath}`)
log(`[oh-my-opencode] comment-checker binary ready.`) log(`[oh-my-openagent] comment-checker binary ready.`)
return binaryPath return binaryPath
} catch (err) { } catch (err) {
debugLog(`Failed to download: ${err}`) debugLog(`Failed to download: ${err}`)
log(`[oh-my-opencode] Failed to download comment-checker: ${err instanceof Error ? err.message : err}`) log(`[oh-my-openagent] Failed to download comment-checker: ${err instanceof Error ? err.message : err}`)
log(`[oh-my-opencode] Comment checking disabled.`) log(`[oh-my-openagent] Comment checking disabled.`)
return null return null
} }
} }
+1 -1
View File
@@ -137,7 +137,7 @@ describe("updateConnectedProvidersCache", () => {
test("does not remove unrelated files in the cache directory", async () => { test("does not remove unrelated files in the cache directory", async () => {
//#given //#given
const realCacheDir = join(fakeUserCacheRoot, "oh-my-opencode") const realCacheDir = join(fakeUserCacheRoot, "oh-my-openagent")
const sentinelPath = join(realCacheDir, "connected-providers-cache.test-sentinel.json") const sentinelPath = join(realCacheDir, "connected-providers-cache.test-sentinel.json")
mkdirSync(realCacheDir, { recursive: true }) mkdirSync(realCacheDir, { recursive: true })
writeFileSync(sentinelPath, JSON.stringify({ keep: true })) writeFileSync(sentinelPath, JSON.stringify({ keep: true }))
+3 -3
View File
@@ -31,11 +31,11 @@ export function getCacheDir(): string {
} }
/** /**
* Returns the oh-my-opencode cache directory. * Returns the oh-my-openagent cache directory.
* All platforms: ~/.cache/oh-my-opencode * All platforms: ~/.cache/oh-my-openagent
*/ */
export function getOmoOpenCodeCacheDir(): string { export function getOmoOpenCodeCacheDir(): string {
return path.join(getCacheDir(), "oh-my-opencode") return path.join(getCacheDir(), "oh-my-openagent")
} }
/** /**
@@ -43,6 +43,24 @@ describe("external-plugin-detector", () => {
expect(result.allPlugins).toContain("oh-my-opencode") expect(result.allPlugins).toContain("oh-my-opencode")
}) })
test("should return detected=false when only oh-my-openagent is configured", () => {
// given - opencode.json with only oh-my-openagent
const opencodeDir = path.join(tempDir, ".opencode")
fs.mkdirSync(opencodeDir, { recursive: true })
fs.writeFileSync(
path.join(opencodeDir, "opencode.json"),
JSON.stringify({ plugin: ["oh-my-openagent"] })
)
// when
const result = detectExternalNotificationPlugin(tempDir)
// then
expect(result.detected).toBe(false)
expect(result.pluginName).toBeNull()
expect(result.allPlugins).toContain("oh-my-openagent")
})
test("should detect opencode-notifier plugin", () => { test("should detect opencode-notifier plugin", () => {
// given - opencode.json with opencode-notifier // given - opencode.json with opencode-notifier
const opencodeDir = path.join(tempDir, ".opencode") const opencodeDir = path.join(tempDir, ".opencode")
+7 -7
View File
@@ -1,5 +1,5 @@
/** /**
* Detects external plugins that may conflict with oh-my-opencode features. * Detects external plugins that may conflict with oh-my-openagent features.
* Used to prevent crashes from concurrent notification plugins. * Used to prevent crashes from concurrent notification plugins.
*/ */
@@ -14,7 +14,7 @@ interface OpencodeConfig {
} }
/** /**
* Known notification plugins that conflict with oh-my-opencode's session-notification. * Known notification plugins that conflict with oh-my-openagent's session-notification.
* Both plugins listen to session.idle and send notifications simultaneously, * Both plugins listen to session.idle and send notifications simultaneously,
* which can cause crashes on Windows due to resource contention. * which can cause crashes on Windows due to resource contention.
*/ */
@@ -124,14 +124,14 @@ export function detectExternalNotificationPlugin(directory: string): ExternalNot
* Generate a warning message for users with conflicting notification plugins. * Generate a warning message for users with conflicting notification plugins.
*/ */
export function getNotificationConflictWarning(pluginName: string): string { export function getNotificationConflictWarning(pluginName: string): string {
return `[oh-my-opencode] External notification plugin detected: ${pluginName} return `[oh-my-openagent] External notification plugin detected: ${pluginName}
Both oh-my-opencode and ${pluginName} listen to session.idle events. Both oh-my-openagent and ${pluginName} listen to session.idle events.
Running both simultaneously can cause crashes on Windows. Running both simultaneously can cause crashes on Windows.
oh-my-opencode's session-notification has been auto-disabled. oh-my-openagent's session-notification has been auto-disabled.
To use oh-my-opencode's notifications instead, either: To use oh-my-openagent's notifications instead, either:
1. Remove ${pluginName} from your opencode.json plugins 1. Remove ${pluginName} from your opencode.json plugins
2. Or set "notification": { "force_enable": true } in oh-my-opencode.json` 2. Or set "notification": { "force_enable": true } in oh-my-openagent.json`
} }
+1 -1
View File
@@ -4,7 +4,7 @@ import * as fs from "fs"
import * as os from "os" import * as os from "os"
import * as path from "path" import * as path from "path"
const logFile = path.join(os.tmpdir(), "oh-my-opencode.log") const logFile = path.join(os.tmpdir(), "oh-my-openagent.log")
export function log(message: string, data?: unknown): void { export function log(message: string, data?: unknown): void {
try { try {
+2 -2
View File
@@ -288,7 +288,7 @@ describe("opencode-config-dir", () => {
expect(paths.configJson).toBe(join(expectedDir, "opencode.json")) expect(paths.configJson).toBe(join(expectedDir, "opencode.json"))
expect(paths.configJsonc).toBe(join(expectedDir, "opencode.jsonc")) expect(paths.configJsonc).toBe(join(expectedDir, "opencode.jsonc"))
expect(paths.packageJson).toBe(join(expectedDir, "package.json")) expect(paths.packageJson).toBe(join(expectedDir, "package.json"))
expect(paths.omoConfig).toBe(join(expectedDir, "oh-my-opencode.json")) expect(paths.omoConfig).toBe(join(expectedDir, "oh-my-openagent.json"))
}) })
test("returns all config paths for desktop binary", () => { test("returns all config paths for desktop binary", () => {
@@ -304,7 +304,7 @@ describe("opencode-config-dir", () => {
expect(paths.configJson).toBe(join(expectedDir, "opencode.json")) expect(paths.configJson).toBe(join(expectedDir, "opencode.json"))
expect(paths.configJsonc).toBe(join(expectedDir, "opencode.jsonc")) expect(paths.configJsonc).toBe(join(expectedDir, "opencode.jsonc"))
expect(paths.packageJson).toBe(join(expectedDir, "package.json")) expect(paths.packageJson).toBe(join(expectedDir, "package.json"))
expect(paths.omoConfig).toBe(join(expectedDir, "oh-my-opencode.json")) expect(paths.omoConfig).toBe(join(expectedDir, "oh-my-openagent.json"))
}) })
}) })
+1 -1
View File
@@ -83,7 +83,7 @@ export function getOpenCodeConfigPaths(options: OpenCodeConfigDirOptions): OpenC
configJson: join(configDir, "opencode.json"), configJson: join(configDir, "opencode.json"),
configJsonc: join(configDir, "opencode.jsonc"), configJsonc: join(configDir, "opencode.jsonc"),
packageJson: join(configDir, "package.json"), packageJson: join(configDir, "package.json"),
omoConfig: join(configDir, "oh-my-opencode.json"), omoConfig: join(configDir, "oh-my-openagent.json")
} }
} }
+5 -5
View File
@@ -1,23 +1,23 @@
/** /**
* Unified system directive prefix for oh-my-opencode internal messages. * Unified system directive prefix for oh-my-openagent internal messages.
* All system-generated messages should use this prefix for consistent filtering. * All system-generated messages should use this prefix for consistent filtering.
* *
* Format: [SYSTEM DIRECTIVE: OH-MY-OPENCODE - {TYPE}] * Format: [SYSTEM DIRECTIVE: OH-MY-OPENAGENT - {TYPE}]
*/ */
export const SYSTEM_DIRECTIVE_PREFIX = "[SYSTEM DIRECTIVE: OH-MY-OPENCODE" export const SYSTEM_DIRECTIVE_PREFIX = "[SYSTEM DIRECTIVE: OH-MY-OPENAGENT"
/** /**
* Creates a system directive header with the given type. * Creates a system directive header with the given type.
* @param type - The directive type (e.g., "TODO CONTINUATION", "RALPH LOOP") * @param type - The directive type (e.g., "TODO CONTINUATION", "RALPH LOOP")
* @returns Formatted directive string like "[SYSTEM DIRECTIVE: OH-MY-OPENCODE - TODO CONTINUATION]" * @returns Formatted directive string like "[SYSTEM DIRECTIVE: OH-MY-OPENAGENT - TODO CONTINUATION]"
*/ */
export function createSystemDirective(type: string): string { export function createSystemDirective(type: string): string {
return `${SYSTEM_DIRECTIVE_PREFIX} - ${type}]` return `${SYSTEM_DIRECTIVE_PREFIX} - ${type}]`
} }
/** /**
* Checks if a message starts with the oh-my-opencode system directive prefix. * Checks if a message starts with the oh-my-openagent system directive prefix.
* Used by keyword-detector and other hooks to skip system-generated messages. * Used by keyword-detector and other hooks to skip system-generated messages.
* @param text - The message text to check * @param text - The message text to check
* @returns true if the message is a system directive * @returns true if the message is a system directive
+6 -8
View File
@@ -47,12 +47,12 @@ export function getCacheDir(): string {
if (process.platform === "win32") { if (process.platform === "win32") {
const localAppData = process.env.LOCALAPPDATA || process.env.APPDATA const localAppData = process.env.LOCALAPPDATA || process.env.APPDATA
const base = localAppData || join(homedir(), "AppData", "Local") const base = localAppData || join(homedir(), "AppData", "Local")
return join(base, "oh-my-opencode", "bin") return join(base, "oh-my-openagent", "bin")
} }
const xdgCache = process.env.XDG_CACHE_HOME const xdgCache = process.env.XDG_CACHE_HOME
const base = xdgCache || join(homedir(), ".cache") const base = xdgCache || join(homedir(), ".cache")
return join(base, "oh-my-opencode", "bin") return join(base, "oh-my-openagent", "bin")
} }
export function getBinaryName(): string { export function getBinaryName(): string {
@@ -63,14 +63,12 @@ export function getCachedBinaryPath(): string | null {
return getCachedBinaryPathShared(getCacheDir(), getBinaryName()) return getCachedBinaryPathShared(getCacheDir(), getBinaryName())
} }
export async function downloadAstGrep(version: string = DEFAULT_VERSION): Promise<string | null> { export async function downloadAstGrep(version: string = DEFAULT_VERSION): Promise<string | null> {
const platformKey = `${process.platform}-${process.arch}` const platformKey = `${process.platform}-${process.arch}`
const platformInfo = PLATFORM_MAP[platformKey] const platformInfo = PLATFORM_MAP[platformKey]
if (!platformInfo) { if (!platformInfo) {
log(`[oh-my-opencode] Unsupported platform for ast-grep: ${platformKey}`) log(`[oh-my-openagent] Unsupported platform for ast-grep: ${platformKey}`)
return null return null
} }
@@ -86,7 +84,7 @@ export async function downloadAstGrep(version: string = DEFAULT_VERSION): Promis
const assetName = `app-${arch}-${os}.zip` const assetName = `app-${arch}-${os}.zip`
const downloadUrl = `https://github.com/${REPO}/releases/download/${version}/${assetName}` const downloadUrl = `https://github.com/${REPO}/releases/download/${version}/${assetName}`
log(`[oh-my-opencode] Downloading ast-grep binary...`) log(`[oh-my-openagent] Downloading ast-grep binary...`)
try { try {
const archivePath = join(cacheDir, assetName) const archivePath = join(cacheDir, assetName)
@@ -96,12 +94,12 @@ export async function downloadAstGrep(version: string = DEFAULT_VERSION): Promis
cleanupArchive(archivePath) cleanupArchive(archivePath)
ensureExecutable(binaryPath) ensureExecutable(binaryPath)
log(`[oh-my-opencode] ast-grep binary ready.`) log(`[oh-my-openagent] ast-grep binary ready.`)
return binaryPath return binaryPath
} catch (err) { } catch (err) {
log( log(
`[oh-my-opencode] Failed to download ast-grep: ${err instanceof Error ? err.message : err}` `[oh-my-openagent] Failed to download ast-grep: ${err instanceof Error ? err.message : err}`
) )
return null return null
} }
+1 -1
View File
@@ -79,7 +79,7 @@ export function createCallOmoAgent(
// Check if agent is disabled // Check if agent is disabled
if (disabledAgents.some((disabled) => disabled.toLowerCase() === normalizedAgent)) { if (disabledAgents.some((disabled) => disabled.toLowerCase() === normalizedAgent)) {
return `Error: Agent "${normalizedAgent}" is disabled via disabled_agents configuration. Remove it from disabled_agents in your oh-my-opencode.json to use it.` return `Error: Agent "${normalizedAgent}" is disabled via disabled_agents configuration. Remove it from disabled_agents in your oh-my-openagent.json to use it.`
} }
const fallbackChain = resolveFallbackChainForCallOmoAgent({ const fallbackChain = resolveFallbackChainForCallOmoAgent({
+2 -2
View File
@@ -62,7 +62,7 @@ export async function resolveCategoryExecution(
To use this category: To use this category:
1. Connect a provider with this model: ${requirement.requiresModel} 1. Connect a provider with this model: ${requirement.requiresModel}
2. Or configure an alternative model in your oh-my-opencode.json for this category 2. Or configure an alternative model in your oh-my-openagent.json for this category
Available categories: ${allCategoryNames}`, Available categories: ${allCategoryNames}`,
} }
@@ -178,7 +178,7 @@ Available categories: ${allCategoryNames}`,
Configure in one of: Configure in one of:
1. OpenCode: Set "model" in opencode.json 1. OpenCode: Set "model" in opencode.json
2. Oh-My-OpenCode: Set category model in oh-my-opencode.json 2. Oh-My-OpenAgent: Set category model in oh-my-openagent.json
3. Provider: Connect a provider with available models 3. Provider: Connect a provider with available models
Current category: ${args.category} Current category: ${args.category}
+1 -1
View File
@@ -39,7 +39,7 @@ function getPlatformKey(): string {
function getInstallDir(): string { function getInstallDir(): string {
const homeDir = process.env.HOME || process.env.USERPROFILE || "." const homeDir = process.env.HOME || process.env.USERPROFILE || "."
return join(homeDir, ".cache", "oh-my-opencode", "bin") return join(homeDir, ".cache", "oh-my-openagent", "bin")
} }
function getRgPath(): string { function getRgPath(): string {
+1 -1
View File
@@ -63,7 +63,7 @@ export function formatServerLookupError(result: Exclude<ServerLookupResult, { st
``, ``,
`Available servers: ${result.availableServers.slice(0, 10).join(", ")}${result.availableServers.length > 10 ? "..." : ""}`, `Available servers: ${result.availableServers.slice(0, 10).join(", ")}${result.availableServers.length > 10 ? "..." : ""}`,
``, ``,
`To add a custom server, configure 'lsp' in oh-my-opencode.json:`, `To add a custom server, configure 'lsp' in oh-my-openagent.json:`,
` {`, ` {`,
` "lsp": {`, ` "lsp": {`,
` "my-server": {`, ` "my-server": {`,
+4 -4
View File
@@ -53,7 +53,7 @@ describe("loadJsonFile", () => {
} }
} }
}` }`
const userPath = join(tempBase, "oh-my-opencode.jsonc") const userPath = join(tempBase, "oh-my-openagent.jsonc")
writeFileSync(userPath, userJsonc, "utf-8") writeFileSync(userPath, userJsonc, "utf-8")
const servers = getMergedServers() const servers = getMergedServers()
@@ -109,7 +109,7 @@ describe("loadJsonFile", () => {
} }
} }
}` }`
const projectPath = join(tempProject, ".opencode", "oh-my-opencode.jsonc") const projectPath = join(tempProject, ".opencode", "oh-my-openagent.jsonc")
writeFileSync(projectPath, projectJsonc, "utf-8") writeFileSync(projectPath, projectJsonc, "utf-8")
process.chdir(tempProject) process.chdir(tempProject)
@@ -146,8 +146,8 @@ describe("loadJsonFile", () => {
} }
} }
}` }`
writeFileSync(join(tempBase, "oh-my-opencode.json"), jsonContent, "utf-8") writeFileSync(join(tempBase, "oh-my-openagent.json"), jsonContent, "utf-8")
writeFileSync(join(tempBase, "oh-my-opencode.jsonc"), jsoncContent, "utf-8") writeFileSync(join(tempBase, "oh-my-openagent.jsonc"), jsoncContent, "utf-8")
const servers = getMergedServers() const servers = getMergedServers()
const found = servers.find(s => s.id === "conflict" && s.source === "user") const found = servers.find(s => s.id === "conflict" && s.source === "user")
+3 -2
View File
@@ -5,6 +5,7 @@ import { BUILTIN_SERVERS } from "./constants"
import type { ResolvedServer } from "./types" import type { ResolvedServer } from "./types"
import { getOpenCodeConfigDir } from "../../shared" import { getOpenCodeConfigDir } from "../../shared"
import { parseJsonc, detectConfigFile } from "../../shared/jsonc-parser" import { parseJsonc, detectConfigFile } from "../../shared/jsonc-parser"
import { CONFIG_BASENAME } from "../../shared/plugin-identity"
interface LspEntry { interface LspEntry {
disabled?: boolean disabled?: boolean
@@ -38,8 +39,8 @@ export function getConfigPaths(): { project: string; user: string; opencode: str
const cwd = process.cwd() const cwd = process.cwd()
const configDir = getOpenCodeConfigDir({ binary: "opencode" }) const configDir = getOpenCodeConfigDir({ binary: "opencode" })
return { return {
project: detectConfigFile(join(cwd, ".opencode", "oh-my-opencode")).path, project: detectConfigFile(join(cwd, ".opencode", CONFIG_BASENAME)).path,
user: detectConfigFile(join(configDir, "oh-my-opencode")).path, user: detectConfigFile(join(configDir, CONFIG_BASENAME)).path,
opencode: detectConfigFile(join(configDir, "opencode")).path, opencode: detectConfigFile(join(configDir, "opencode")).path,
} }
} }
+1 -1
View File
@@ -49,7 +49,7 @@ export function isServerInstalled(command: string[]): boolean {
} }
} }
// Runtime wrappers (bun/node) are always available in oh-my-opencode context // Runtime wrappers (bun/node) are always available in oh-my-openagent context
if (cmd === "bun" || cmd === "node") { if (cmd === "bun" || cmd === "node") {
return true return true
} }