diff --git a/README.ja.md b/README.ja.md index f25943b..79d5b17 100644 --- a/README.ja.md +++ b/README.ja.md @@ -601,7 +601,12 @@ Oh My OpenCode は以下の場所からフックを読み込んで実行しま 設定ファイルの場所(優先順): 1. `.opencode/oh-my-opencode.json` (プロジェクト) -2. `~/.config/opencode/oh-my-opencode.json` (ユーザー) +2. ユーザー設定(プラットフォーム別): + +| プラットフォーム | ユーザー設定パス | +|------------------|------------------| +| **Windows** | `%APPDATA%\opencode\oh-my-opencode.json` | +| **macOS/Linux** | `~/.config/opencode/oh-my-opencode.json` | スキーマ自動補完がサポートされています: diff --git a/README.ko.md b/README.ko.md index d828efa..39629dc 100644 --- a/README.ko.md +++ b/README.ko.md @@ -595,7 +595,12 @@ Oh My OpenCode는 다음 위치의 훅을 읽고 실행합니다: 설정 파일 위치 (우선순위 순): 1. `.opencode/oh-my-opencode.json` (프로젝트) -2. `~/.config/opencode/oh-my-opencode.json` (사용자) +2. 사용자 설정 (플랫폼별): + +| 플랫폼 | 사용자 설정 경로 | +|--------|------------------| +| **Windows** | `%APPDATA%\opencode\oh-my-opencode.json` | +| **macOS/Linux** | `~/.config/opencode/oh-my-opencode.json` | Schema 자동 완성이 지원됩니다: diff --git a/README.md b/README.md index 7d07204..baa19f9 100644 --- a/README.md +++ b/README.md @@ -660,7 +660,12 @@ Highly opinionated, but adjustable to taste. Config file locations (priority order): 1. `.opencode/oh-my-opencode.json` (project) -2. `~/.config/opencode/oh-my-opencode.json` (user) +2. User config (platform-specific): + +| Platform | User Config Path | +|----------|------------------| +| **Windows** | `%APPDATA%\opencode\oh-my-opencode.json` | +| **macOS/Linux** | `~/.config/opencode/oh-my-opencode.json` | Schema autocomplete supported: diff --git a/assets/oh-my-opencode.schema.json b/assets/oh-my-opencode.schema.json index dd54cd5..bfe8981 100644 --- a/assets/oh-my-opencode.schema.json +++ b/assets/oh-my-opencode.schema.json @@ -57,7 +57,9 @@ "startup-toast", "keyword-detector", "agent-usage-reminder", - "non-interactive-env" + "non-interactive-env", + "interactive-bash-session", + "empty-message-sanitizer" ] } }, diff --git a/src/hooks/auto-update-checker/index.ts b/src/hooks/auto-update-checker/index.ts index 2ad05e5..a720260 100644 --- a/src/hooks/auto-update-checker/index.ts +++ b/src/hooks/auto-update-checker/index.ts @@ -3,6 +3,8 @@ import { checkForUpdate, getCachedVersion, getLocalDevVersion } from "./checker" import { invalidatePackage } from "./cache" import { PACKAGE_NAME } from "./constants" import { log } from "../../shared/logger" +import { getUserConfigPath } from "../../shared/config-path" +import { getConfigLoadErrors, clearConfigLoadErrors } from "../../index" import type { AutoUpdateCheckerOptions } from "./types" export function createAutoUpdateCheckerHook(ctx: PluginInput, options: AutoUpdateCheckerOptions = {}) { @@ -64,17 +66,40 @@ export function createAutoUpdateCheckerHook(ctx: PluginInput, options: AutoUpdat } catch (err) { log("[auto-update-checker] Error during update check:", err) } + + await showConfigErrorsIfAny(ctx) }, } } +async function showConfigErrorsIfAny(ctx: PluginInput): Promise { + const errors = getConfigLoadErrors() + if (errors.length === 0) return + + const errorMessages = errors.map(e => `${e.path}: ${e.error}`).join("\n") + await ctx.client.tui + .showToast({ + body: { + title: "Config Load Error", + message: `Failed to load config:\n${errorMessages}`, + variant: "error" as const, + duration: 10000, + }, + }) + .catch(() => {}) + + log(`[auto-update-checker] Config load errors shown: ${errors.length} error(s)`) + clearConfigLoadErrors() +} + async function showVersionToast(ctx: PluginInput, version: string | null): Promise { const displayVersion = version ?? "unknown" + const configPath = getUserConfigPath() await ctx.client.tui .showToast({ body: { title: `OhMyOpenCode ${displayVersion}`, - message: "OpenCode is now on Steroids. oMoMoMoMo...", + message: `OpenCode is now on Steroids. oMoMoMoMo...\nConfig: ${configPath}`, variant: "info" as const, duration: 5000, }, diff --git a/src/index.ts b/src/index.ts index 72fc913..2ca036b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -46,25 +46,10 @@ import { builtinTools, createCallOmoAgent, createBackgroundTools, createLookAt, import { BackgroundManager } from "./features/background-agent"; import { createBuiltinMcps } from "./mcp"; import { OhMyOpenCodeConfigSchema, type OhMyOpenCodeConfig, type HookName } from "./config"; -import { log, deepMerge } from "./shared"; +import { log, deepMerge, getUserConfigDir } from "./shared"; import { PLAN_SYSTEM_PROMPT, PLAN_PERMISSION } from "./agents/plan-prompt"; import * as fs from "fs"; import * as path from "path"; -import * as os from "os"; - -/** - * Returns the user-level config directory based on the OS. - * - Linux/macOS: XDG_CONFIG_HOME or ~/.config - * - Windows: %APPDATA% - */ -function getUserConfigDir(): string { - if (process.platform === "win32") { - return process.env.APPDATA || path.join(os.homedir(), "AppData", "Roaming"); - } - - // Linux, macOS, and other Unix-like systems: respect XDG_CONFIG_HOME - return process.env.XDG_CONFIG_HOME || path.join(os.homedir(), ".config"); -} const AGENT_NAME_MAP: Record = { omo: "OmO", @@ -77,6 +62,21 @@ const AGENT_NAME_MAP: Record = { "multimodal-looker": "multimodal-looker", }; +export type ConfigLoadError = { + path: string; + error: string; +}; + +let configLoadErrors: ConfigLoadError[] = []; + +export function getConfigLoadErrors(): ConfigLoadError[] { + return configLoadErrors; +} + +export function clearConfigLoadErrors(): void { + configLoadErrors = []; +} + function normalizeAgentNames(agents: Record): Record { const normalized: Record = {}; for (const [key, value] of Object.entries(agents)) { @@ -99,7 +99,9 @@ function loadConfigFromPath(configPath: string): OhMyOpenCodeConfig | null { const result = OhMyOpenCodeConfigSchema.safeParse(rawConfig); if (!result.success) { + const errorMsg = result.error.issues.map(i => `${i.path.join(".")}: ${i.message}`).join(", "); log(`Config validation error in ${configPath}:`, result.error.issues); + configLoadErrors.push({ path: configPath, error: `Validation error: ${errorMsg}` }); return null; } @@ -107,7 +109,9 @@ function loadConfigFromPath(configPath: string): OhMyOpenCodeConfig | null { return result.data; } } catch (err) { + const errorMsg = err instanceof Error ? err.message : String(err); log(`Error loading config from ${configPath}:`, err); + configLoadErrors.push({ path: configPath, error: errorMsg }); } return null; } diff --git a/src/shared/config-path.ts b/src/shared/config-path.ts new file mode 100644 index 0000000..05606cb --- /dev/null +++ b/src/shared/config-path.ts @@ -0,0 +1,30 @@ +import * as path from "path" +import * as os from "os" + +/** + * Returns the user-level config directory based on the OS. + * - Linux/macOS: XDG_CONFIG_HOME or ~/.config + * - Windows: %APPDATA% + */ +export function getUserConfigDir(): string { + if (process.platform === "win32") { + return process.env.APPDATA || path.join(os.homedir(), "AppData", "Roaming") + } + + // Linux, macOS, and other Unix-like systems: respect XDG_CONFIG_HOME + return process.env.XDG_CONFIG_HOME || path.join(os.homedir(), ".config") +} + +/** + * Returns the full path to the user-level oh-my-opencode config file. + */ +export function getUserConfigPath(): string { + return path.join(getUserConfigDir(), "opencode", "oh-my-opencode.json") +} + +/** + * Returns the full path to the project-level oh-my-opencode config file. + */ +export function getProjectConfigPath(directory: string): string { + return path.join(directory, ".opencode", "oh-my-opencode.json") +} diff --git a/src/shared/index.ts b/src/shared/index.ts index e06b064..9a371b7 100644 --- a/src/shared/index.ts +++ b/src/shared/index.ts @@ -10,3 +10,4 @@ export * from "./hook-disabled" export * from "./deep-merge" export * from "./file-utils" export * from "./dynamic-truncator" +export * from "./config-path"