- Document platform-specific config paths in README (en/ko/ja) - Windows: %APPDATA%\opencode\oh-my-opencode.json - macOS/Linux: ~/.config/opencode/oh-my-opencode.json - Show config file path in startup toast - Add config load error warnings when JSON parsing or validation fails - Extract getUserConfigDir to shared/config-path.ts for reuse Fixes #97 🤖 GENERATED WITH ASSISTANCE OF [OhMyOpenCode](https://github.com/code-yeongyu/oh-my-opencode)
This commit is contained in:
@@ -601,7 +601,12 @@ Oh My OpenCode は以下の場所からフックを読み込んで実行しま
|
|||||||
|
|
||||||
設定ファイルの場所(優先順):
|
設定ファイルの場所(優先順):
|
||||||
1. `.opencode/oh-my-opencode.json` (プロジェクト)
|
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` |
|
||||||
|
|
||||||
スキーマ自動補完がサポートされています:
|
スキーマ自動補完がサポートされています:
|
||||||
|
|
||||||
|
|||||||
@@ -595,7 +595,12 @@ Oh My OpenCode는 다음 위치의 훅을 읽고 실행합니다:
|
|||||||
|
|
||||||
설정 파일 위치 (우선순위 순):
|
설정 파일 위치 (우선순위 순):
|
||||||
1. `.opencode/oh-my-opencode.json` (프로젝트)
|
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 자동 완성이 지원됩니다:
|
Schema 자동 완성이 지원됩니다:
|
||||||
|
|
||||||
|
|||||||
@@ -660,7 +660,12 @@ Highly opinionated, but adjustable to taste.
|
|||||||
|
|
||||||
Config file locations (priority order):
|
Config file locations (priority order):
|
||||||
1. `.opencode/oh-my-opencode.json` (project)
|
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:
|
Schema autocomplete supported:
|
||||||
|
|
||||||
|
|||||||
@@ -57,7 +57,9 @@
|
|||||||
"startup-toast",
|
"startup-toast",
|
||||||
"keyword-detector",
|
"keyword-detector",
|
||||||
"agent-usage-reminder",
|
"agent-usage-reminder",
|
||||||
"non-interactive-env"
|
"non-interactive-env",
|
||||||
|
"interactive-bash-session",
|
||||||
|
"empty-message-sanitizer"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ import { checkForUpdate, getCachedVersion, getLocalDevVersion } from "./checker"
|
|||||||
import { invalidatePackage } from "./cache"
|
import { invalidatePackage } from "./cache"
|
||||||
import { PACKAGE_NAME } from "./constants"
|
import { PACKAGE_NAME } from "./constants"
|
||||||
import { log } from "../../shared/logger"
|
import { log } from "../../shared/logger"
|
||||||
|
import { getUserConfigPath } from "../../shared/config-path"
|
||||||
|
import { getConfigLoadErrors, clearConfigLoadErrors } from "../../index"
|
||||||
import type { AutoUpdateCheckerOptions } from "./types"
|
import type { AutoUpdateCheckerOptions } from "./types"
|
||||||
|
|
||||||
export function createAutoUpdateCheckerHook(ctx: PluginInput, options: AutoUpdateCheckerOptions = {}) {
|
export function createAutoUpdateCheckerHook(ctx: PluginInput, options: AutoUpdateCheckerOptions = {}) {
|
||||||
@@ -64,17 +66,40 @@ export function createAutoUpdateCheckerHook(ctx: PluginInput, options: AutoUpdat
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
log("[auto-update-checker] Error during update check:", err)
|
log("[auto-update-checker] Error during update check:", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await showConfigErrorsIfAny(ctx)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function showConfigErrorsIfAny(ctx: PluginInput): Promise<void> {
|
||||||
|
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<void> {
|
async function showVersionToast(ctx: PluginInput, version: string | null): Promise<void> {
|
||||||
const displayVersion = version ?? "unknown"
|
const displayVersion = version ?? "unknown"
|
||||||
|
const configPath = getUserConfigPath()
|
||||||
await ctx.client.tui
|
await ctx.client.tui
|
||||||
.showToast({
|
.showToast({
|
||||||
body: {
|
body: {
|
||||||
title: `OhMyOpenCode ${displayVersion}`,
|
title: `OhMyOpenCode ${displayVersion}`,
|
||||||
message: "OpenCode is now on Steroids. oMoMoMoMo...",
|
message: `OpenCode is now on Steroids. oMoMoMoMo...\nConfig: ${configPath}`,
|
||||||
variant: "info" as const,
|
variant: "info" as const,
|
||||||
duration: 5000,
|
duration: 5000,
|
||||||
},
|
},
|
||||||
|
|||||||
36
src/index.ts
36
src/index.ts
@@ -46,25 +46,10 @@ import { builtinTools, createCallOmoAgent, createBackgroundTools, createLookAt,
|
|||||||
import { BackgroundManager } from "./features/background-agent";
|
import { BackgroundManager } from "./features/background-agent";
|
||||||
import { createBuiltinMcps } from "./mcp";
|
import { createBuiltinMcps } from "./mcp";
|
||||||
import { OhMyOpenCodeConfigSchema, type OhMyOpenCodeConfig, type HookName } from "./config";
|
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 { PLAN_SYSTEM_PROMPT, PLAN_PERMISSION } from "./agents/plan-prompt";
|
||||||
import * as fs from "fs";
|
import * as fs from "fs";
|
||||||
import * as path from "path";
|
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<string, string> = {
|
const AGENT_NAME_MAP: Record<string, string> = {
|
||||||
omo: "OmO",
|
omo: "OmO",
|
||||||
@@ -77,6 +62,21 @@ const AGENT_NAME_MAP: Record<string, string> = {
|
|||||||
"multimodal-looker": "multimodal-looker",
|
"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<string, unknown>): Record<string, unknown> {
|
function normalizeAgentNames(agents: Record<string, unknown>): Record<string, unknown> {
|
||||||
const normalized: Record<string, unknown> = {};
|
const normalized: Record<string, unknown> = {};
|
||||||
for (const [key, value] of Object.entries(agents)) {
|
for (const [key, value] of Object.entries(agents)) {
|
||||||
@@ -99,7 +99,9 @@ function loadConfigFromPath(configPath: string): OhMyOpenCodeConfig | null {
|
|||||||
const result = OhMyOpenCodeConfigSchema.safeParse(rawConfig);
|
const result = OhMyOpenCodeConfigSchema.safeParse(rawConfig);
|
||||||
|
|
||||||
if (!result.success) {
|
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);
|
log(`Config validation error in ${configPath}:`, result.error.issues);
|
||||||
|
configLoadErrors.push({ path: configPath, error: `Validation error: ${errorMsg}` });
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -107,7 +109,9 @@ function loadConfigFromPath(configPath: string): OhMyOpenCodeConfig | null {
|
|||||||
return result.data;
|
return result.data;
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
const errorMsg = err instanceof Error ? err.message : String(err);
|
||||||
log(`Error loading config from ${configPath}:`, err);
|
log(`Error loading config from ${configPath}:`, err);
|
||||||
|
configLoadErrors.push({ path: configPath, error: errorMsg });
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
30
src/shared/config-path.ts
Normal file
30
src/shared/config-path.ts
Normal file
@@ -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")
|
||||||
|
}
|
||||||
@@ -10,3 +10,4 @@ export * from "./hook-disabled"
|
|||||||
export * from "./deep-merge"
|
export * from "./deep-merge"
|
||||||
export * from "./file-utils"
|
export * from "./file-utils"
|
||||||
export * from "./dynamic-truncator"
|
export * from "./dynamic-truncator"
|
||||||
|
export * from "./config-path"
|
||||||
|
|||||||
Reference in New Issue
Block a user