fix: respect OPENCODE_CONFIG_DIR environment variable across all config paths

Multiple files were hardcoding ~/.config/opencode paths instead of using
getOpenCodeConfigDir() which respects the OPENCODE_CONFIG_DIR env var.

This broke profile isolation features like OCX ghost mode, where users
set OPENCODE_CONFIG_DIR to a custom path but oh-my-opencode.json and
other configs weren't being read from that location.

Changes:
- plugin-config.ts: Use getOpenCodeConfigDir() directly
- cli/doctor/checks: Use getOpenCodeConfigDir() for auth and config checks
- tools/lsp/config.ts: Use getOpenCodeConfigDir() for LSP config paths
- command loaders: Use getOpenCodeConfigDir() for global command dirs
- hooks: Use getOpenCodeConfigDir() for hook config paths
- config-path.ts: Mark getUserConfigDir() as deprecated
- tests: Ensure OPENCODE_CONFIG_DIR is properly isolated in tests
This commit is contained in:
Nguyen Khac Trung Kien
2026-01-22 12:15:09 +07:00
parent 5187d677d2
commit 22a5c8e64d
11 changed files with 36 additions and 68 deletions
+3 -2
View File
@@ -1,12 +1,12 @@
import { existsSync, readdirSync, readFileSync } from "fs"
import { join, basename, dirname } from "path"
import { homedir } from "os"
import {
parseFrontmatter,
resolveCommandsInText,
resolveFileReferencesInText,
sanitizeModelField,
getClaudeConfigDir,
getOpenCodeConfigDir,
} from "../../shared"
import type { CommandFrontmatter } from "../../features/claude-code-command-loader/types"
import { isMarkdownFile } from "../../shared/file-utils"
@@ -101,9 +101,10 @@ export interface ExecutorOptions {
}
async function discoverAllCommands(options?: ExecutorOptions): Promise<CommandInfo[]> {
const configDir = getOpenCodeConfigDir({ binary: "opencode" })
const userCommandsDir = join(getClaudeConfigDir(), "commands")
const projectCommandsDir = join(process.cwd(), ".claude", "commands")
const opencodeGlobalDir = join(homedir(), ".config", "opencode", "command")
const opencodeGlobalDir = join(configDir, "command")
const opencodeProjectDir = join(process.cwd(), ".opencode", "command")
const userCommands = discoverCommandsFromDir(userCommandsDir, "user")
+4 -35
View File
@@ -1,16 +1,12 @@
import * as path from "node:path"
import * as os from "node:os"
import * as fs from "node:fs"
import { getOpenCodeConfigDir } from "../../shared"
export const PACKAGE_NAME = "oh-my-opencode"
export const NPM_REGISTRY_URL = `https://registry.npmjs.org/-/package/${PACKAGE_NAME}/dist-tags`
export const NPM_FETCH_TIMEOUT = 5000
/**
* OpenCode plugin cache directory
* - Linux/macOS: ~/.cache/opencode/
* - Windows: %LOCALAPPDATA%/opencode/
*/
function getCacheDir(): string {
if (process.platform === "win32") {
return path.join(process.env.LOCALAPPDATA ?? os.homedir(), "opencode")
@@ -27,38 +23,11 @@ export const INSTALLED_PACKAGE_JSON = path.join(
"package.json"
)
/**
* OpenCode config file locations (priority order)
* On Windows, checks ~/.config first (cross-platform), then %APPDATA% (fallback)
* This matches shared/config-path.ts behavior for consistency
*/
function getUserConfigDir(): string {
if (process.platform === "win32") {
const crossPlatformDir = path.join(os.homedir(), ".config")
const appdataDir = process.env.APPDATA ?? path.join(os.homedir(), "AppData", "Roaming")
// Check cross-platform path first (~/.config)
const crossPlatformConfig = path.join(crossPlatformDir, "opencode", "opencode.json")
const crossPlatformConfigJsonc = path.join(crossPlatformDir, "opencode", "opencode.jsonc")
if (fs.existsSync(crossPlatformConfig) || fs.existsSync(crossPlatformConfigJsonc)) {
return crossPlatformDir
}
// Fall back to %APPDATA%
return appdataDir
}
return process.env.XDG_CONFIG_HOME ?? path.join(os.homedir(), ".config")
}
/**
* Get the Windows-specific APPDATA directory (for fallback checks)
*/
export function getWindowsAppdataDir(): string | null {
if (process.platform !== "win32") return null
return process.env.APPDATA ?? path.join(os.homedir(), "AppData", "Roaming")
}
export const USER_CONFIG_DIR = getUserConfigDir()
export const USER_OPENCODE_CONFIG = path.join(USER_CONFIG_DIR, "opencode", "opencode.json")
export const USER_OPENCODE_CONFIG_JSONC = path.join(USER_CONFIG_DIR, "opencode", "opencode.jsonc")
export const USER_CONFIG_DIR = getOpenCodeConfigDir({ binary: "opencode" })
export const USER_OPENCODE_CONFIG = path.join(USER_CONFIG_DIR, "opencode.json")
export const USER_OPENCODE_CONFIG_JSONC = path.join(USER_CONFIG_DIR, "opencode.jsonc")
+2 -2
View File
@@ -1,8 +1,8 @@
import { existsSync } from "fs"
import { homedir } from "os"
import { join } from "path"
import type { ClaudeHookEvent } from "./types"
import { log } from "../../shared/logger"
import { getOpenCodeConfigDir } from "../../shared"
export interface DisabledHooksConfig {
Stop?: string[]
@@ -16,7 +16,7 @@ export interface PluginExtendedConfig {
disabledHooks?: DisabledHooksConfig
}
const USER_CONFIG_PATH = join(homedir(), ".config", "opencode", "opencode-cc-plugin.json")
const USER_CONFIG_PATH = join(getOpenCodeConfigDir({ binary: "opencode" }), "opencode-cc-plugin.json")
function getProjectConfigPath(): string {
return join(process.cwd(), ".opencode", "opencode-cc-plugin.json")