Add OpenClaw config directory settings (#1518)

Co-authored-by: 张斌 <zhangbin25@xiaomi.com>
This commit is contained in:
Franklin
2026-04-20 08:44:38 +08:00
committed by GitHub
parent 8ba92b5470
commit 34349a2743
11 changed files with 124 additions and 12 deletions
@@ -16,6 +16,7 @@ interface DirectorySettingsProps {
codexDir?: string;
geminiDir?: string;
opencodeDir?: string;
openclawDir?: string;
onDirectoryChange: (app: AppId, value?: string) => void;
onBrowseDirectory: (app: AppId) => Promise<void>;
onResetDirectory: (app: AppId) => Promise<void>;
@@ -31,6 +32,7 @@ export function DirectorySettings({
codexDir,
geminiDir,
opencodeDir,
openclawDir,
onDirectoryChange,
onBrowseDirectory,
onResetDirectory,
@@ -130,6 +132,17 @@ export function DirectorySettings({
onBrowse={() => onBrowseDirectory("opencode")}
onReset={() => onResetDirectory("opencode")}
/>
<DirectoryInput
label={t("settings.openclawConfigDir")}
description={undefined}
value={openclawDir}
resolvedValue={resolvedDirs.openclaw}
placeholder={t("settings.browsePlaceholderOpenclaw")}
onChange={(val) => onDirectoryChange("openclaw", val)}
onBrowse={() => onBrowseDirectory("openclaw")}
onReset={() => onResetDirectory("openclaw")}
/>
</section>
</div>
);
+1
View File
@@ -316,6 +316,7 @@ export function SettingsPage({
codexDir={settings.codexConfigDir}
geminiDir={settings.geminiConfigDir}
opencodeDir={settings.opencodeConfigDir}
openclawDir={settings.openclawConfigDir}
onDirectoryChange={updateDirectory}
onBrowseDirectory={browseDirectory}
onResetDirectory={resetDirectory}
+37 -7
View File
@@ -5,7 +5,13 @@ import { homeDir, join } from "@tauri-apps/api/path";
import { settingsApi, type AppId } from "@/lib/api";
import type { SettingsFormState } from "./useSettingsForm";
type DirectoryKey = "appConfig" | "claude" | "codex" | "gemini" | "opencode";
type DirectoryKey =
| "appConfig"
| "claude"
| "codex"
| "gemini"
| "opencode"
| "openclaw";
export interface ResolvedDirectories {
appConfig: string;
@@ -13,6 +19,7 @@ export interface ResolvedDirectories {
codex: string;
gemini: string;
opencode: string;
openclaw: string;
}
const sanitizeDir = (value?: string | null): string | undefined => {
@@ -46,7 +53,9 @@ const computeDefaultConfigDir = async (
? ".codex"
: app === "gemini"
? ".gemini"
: ".config/opencode";
: app === "opencode"
? ".config/opencode"
: ".openclaw";
return await join(home, folder);
} catch (error) {
console.error(
@@ -78,6 +87,7 @@ export interface UseDirectorySettingsResult {
codexDir?: string,
geminiDir?: string,
opencodeDir?: string,
openclawDir?: string,
) => void;
}
@@ -105,6 +115,7 @@ export function useDirectorySettings({
codex: "",
gemini: "",
opencode: "",
openclaw: "",
});
const [isLoading, setIsLoading] = useState(true);
@@ -114,6 +125,7 @@ export function useDirectorySettings({
codex: "",
gemini: "",
opencode: "",
openclaw: "",
});
const initialAppConfigDirRef = useRef<string | undefined>(undefined);
@@ -130,22 +142,26 @@ export function useDirectorySettings({
codexDir,
geminiDir,
opencodeDir,
openclawDir,
defaultAppConfig,
defaultClaudeDir,
defaultCodexDir,
defaultGeminiDir,
defaultOpencodeDir,
defaultOpenclawDir,
] = await Promise.all([
settingsApi.getAppConfigDirOverride(),
settingsApi.getConfigDir("claude"),
settingsApi.getConfigDir("codex"),
settingsApi.getConfigDir("gemini"),
settingsApi.getConfigDir("opencode"),
settingsApi.getConfigDir("openclaw"),
computeDefaultAppConfigDir(),
computeDefaultConfigDir("claude"),
computeDefaultConfigDir("codex"),
computeDefaultConfigDir("gemini"),
computeDefaultConfigDir("opencode"),
computeDefaultConfigDir("openclaw"),
]);
if (!active) return;
@@ -158,6 +174,7 @@ export function useDirectorySettings({
codex: defaultCodexDir ?? "",
gemini: defaultGeminiDir ?? "",
opencode: defaultOpencodeDir ?? "",
openclaw: defaultOpenclawDir ?? "",
};
setAppConfigDir(normalizedOverride);
@@ -169,6 +186,7 @@ export function useDirectorySettings({
codex: codexDir || defaultsRef.current.codex,
gemini: geminiDir || defaultsRef.current.gemini,
opencode: opencodeDir || defaultsRef.current.opencode,
openclaw: openclawDir || defaultsRef.current.openclaw,
});
} catch (error) {
console.error(
@@ -201,7 +219,9 @@ export function useDirectorySettings({
? { codexConfigDir: sanitized }
: key === "gemini"
? { geminiConfigDir: sanitized }
: { opencodeConfigDir: sanitized },
: key === "opencode"
? { opencodeConfigDir: sanitized }
: { openclawConfigDir: sanitized },
);
}
@@ -229,7 +249,9 @@ export function useDirectorySettings({
? "codex"
: app === "gemini"
? "gemini"
: "opencode",
: app === "opencode"
? "opencode"
: "openclaw",
value,
);
},
@@ -245,7 +267,9 @@ export function useDirectorySettings({
? "codex"
: app === "gemini"
? "gemini"
: "opencode";
: app === "opencode"
? "opencode"
: "openclaw";
const currentValue =
key === "claude"
? (settings?.claudeConfigDir ?? resolvedDirs.claude)
@@ -253,7 +277,9 @@ export function useDirectorySettings({
? (settings?.codexConfigDir ?? resolvedDirs.codex)
: key === "gemini"
? (settings?.geminiConfigDir ?? resolvedDirs.gemini)
: (settings?.opencodeConfigDir ?? resolvedDirs.opencode);
: key === "opencode"
? (settings?.opencodeConfigDir ?? resolvedDirs.opencode)
: (settings?.openclawConfigDir ?? resolvedDirs.openclaw);
try {
const picked = await settingsApi.selectConfigDirectory(currentValue);
@@ -301,7 +327,9 @@ export function useDirectorySettings({
? "codex"
: app === "gemini"
? "gemini"
: "opencode";
: app === "opencode"
? "opencode"
: "openclaw";
if (!defaultsRef.current[key]) {
const fallback = await computeDefaultConfigDir(app);
if (fallback) {
@@ -335,6 +363,7 @@ export function useDirectorySettings({
codexDir?: string,
geminiDir?: string,
opencodeDir?: string,
openclawDir?: string,
) => {
setAppConfigDir(initialAppConfigDirRef.current);
setResolvedDirs({
@@ -344,6 +373,7 @@ export function useDirectorySettings({
codex: codexDir ?? defaultsRef.current.codex,
gemini: geminiDir ?? defaultsRef.current.gemini,
opencode: opencodeDir ?? defaultsRef.current.opencode,
openclaw: openclawDir ?? defaultsRef.current.openclaw,
});
},
[],
+15 -2
View File
@@ -110,6 +110,7 @@ export function useSettings(): UseSettingsResult {
sanitizeDir(data?.codexConfigDir),
sanitizeDir(data?.geminiConfigDir),
sanitizeDir(data?.opencodeConfigDir),
sanitizeDir(data?.openclawConfigDir),
);
setRequiresRestart(false);
}, [
@@ -135,6 +136,9 @@ export function useSettings(): UseSettingsResult {
const sanitizedOpencodeDir = sanitizeDir(
mergedSettings.opencodeConfigDir,
);
const sanitizedOpenclawDir = sanitizeDir(
mergedSettings.openclawConfigDir,
);
const { webdavSync: _ignoredWebdavSync, ...restSettings } =
mergedSettings;
@@ -144,6 +148,7 @@ export function useSettings(): UseSettingsResult {
codexConfigDir: sanitizedCodexDir,
geminiConfigDir: sanitizedGeminiDir,
opencodeConfigDir: sanitizedOpencodeDir,
openclawConfigDir: sanitizedOpenclawDir,
language: mergedSettings.language,
};
@@ -248,11 +253,15 @@ export function useSettings(): UseSettingsResult {
const sanitizedOpencodeDir = sanitizeDir(
mergedSettings.opencodeConfigDir,
);
const sanitizedOpenclawDir = sanitizeDir(
mergedSettings.openclawConfigDir,
);
const previousAppDir = initialAppConfigDir;
const previousClaudeDir = sanitizeDir(data?.claudeConfigDir);
const previousCodexDir = sanitizeDir(data?.codexConfigDir);
const previousGeminiDir = sanitizeDir(data?.geminiConfigDir);
const previousOpencodeDir = sanitizeDir(data?.opencodeConfigDir);
const previousOpenclawDir = sanitizeDir(data?.openclawConfigDir);
const { webdavSync: _ignoredWebdavSync, ...restSettings } =
mergedSettings;
@@ -262,6 +271,7 @@ export function useSettings(): UseSettingsResult {
codexConfigDir: sanitizedCodexDir,
geminiConfigDir: sanitizedGeminiDir,
opencodeConfigDir: sanitizedOpencodeDir,
openclawConfigDir: sanitizedOpenclawDir,
language: mergedSettings.language,
};
@@ -358,16 +368,19 @@ export function useSettings(): UseSettingsResult {
console.warn("[useSettings] Failed to refresh tray menu", error);
}
// 如果 Claude/Codex/Gemini/OpenCode 的目录覆盖发生变化,则立即将"当前使用的供应商"写回对应应用的 live 配置
// 如果 Claude/Codex/Gemini/OpenCode/OpenClaw 的目录覆盖发生变化,则立即将"当前使用的供应商"写回对应应用的 live 配置
const claudeDirChanged = sanitizedClaudeDir !== previousClaudeDir;
const codexDirChanged = sanitizedCodexDir !== previousCodexDir;
const geminiDirChanged = sanitizedGeminiDir !== previousGeminiDir;
const opencodeDirChanged = sanitizedOpencodeDir !== previousOpencodeDir;
const openclawDirChanged =
sanitizedOpenclawDir !== previousOpenclawDir;
if (
claudeDirChanged ||
codexDirChanged ||
geminiDirChanged ||
opencodeDirChanged
opencodeDirChanged ||
openclawDirChanged
) {
const syncResult = await syncCurrentProvidersLiveSafe();
if (!syncResult.ok) {
+2
View File
@@ -90,6 +90,7 @@ export function useSettingsForm(): UseSettingsFormResult {
codexConfigDir: sanitizeDir(data.codexConfigDir),
geminiConfigDir: sanitizeDir(data.geminiConfigDir),
opencodeConfigDir: sanitizeDir(data.opencodeConfigDir),
openclawConfigDir: sanitizeDir(data.openclawConfigDir),
language: normalizedLanguage,
};
@@ -150,6 +151,7 @@ export function useSettingsForm(): UseSettingsFormResult {
codexConfigDir: sanitizeDir(serverData.codexConfigDir),
geminiConfigDir: sanitizeDir(serverData.geminiConfigDir),
opencodeConfigDir: sanitizeDir(serverData.opencodeConfigDir),
openclawConfigDir: sanitizeDir(serverData.openclawConfigDir),
language: normalizedLanguage,
};
+3
View File
@@ -577,10 +577,13 @@
"geminiConfigDirDescription": "Override Gemini configuration directory (.env).",
"opencodeConfigDir": "OpenCode Configuration Directory",
"opencodeConfigDirDescription": "Override OpenCode configuration directory (opencode.json).",
"openclawConfigDir": "OpenClaw Configuration Directory",
"openclawConfigDirDescription": "Override OpenClaw configuration directory (openclaw.json).",
"browsePlaceholderClaude": "e.g., /home/<your-username>/.claude",
"browsePlaceholderCodex": "e.g., /home/<your-username>/.codex",
"browsePlaceholderGemini": "e.g., /home/<your-username>/.gemini",
"browsePlaceholderOpencode": "e.g., /home/<your-username>/.config/opencode",
"browsePlaceholderOpenclaw": "e.g., /home/<your-username>/.openclaw",
"browseDirectory": "Browse Directory",
"resetDefault": "Reset to default directory (takes effect after saving)",
"checkForUpdates": "Check for Updates",
+3
View File
@@ -577,10 +577,13 @@
"geminiConfigDirDescription": "Gemini の設定ディレクトリ(.env)を上書きします。",
"opencodeConfigDir": "OpenCode 設定ディレクトリ",
"opencodeConfigDirDescription": "OpenCode の設定ディレクトリ(opencode.json)を上書きします。",
"openclawConfigDir": "OpenClaw 設定ディレクトリ",
"openclawConfigDirDescription": "OpenClaw の設定ディレクトリ(openclaw.json)を上書きします。",
"browsePlaceholderClaude": "例: /home/<your-username>/.claude",
"browsePlaceholderCodex": "例: /home/<your-username>/.codex",
"browsePlaceholderGemini": "例: /home/<your-username>/.gemini",
"browsePlaceholderOpencode": "例: /home/<your-username>/.config/opencode",
"browsePlaceholderOpenclaw": "例: /home/<your-username>/.openclaw",
"browseDirectory": "ディレクトリを選択",
"resetDefault": "デフォルトに戻す(保存後に反映)",
"checkForUpdates": "アップデートを確認",
+3
View File
@@ -577,10 +577,13 @@
"geminiConfigDirDescription": "覆盖 Gemini 配置目录 (.env)。",
"opencodeConfigDir": "OpenCode 配置目录",
"opencodeConfigDirDescription": "覆盖 OpenCode 配置目录 (opencode.json)。",
"openclawConfigDir": "OpenClaw 配置目录",
"openclawConfigDirDescription": "覆盖 OpenClaw 配置目录 (openclaw.json)。",
"browsePlaceholderClaude": "例如:/home/<你的用户名>/.claude",
"browsePlaceholderCodex": "例如:/home/<你的用户名>/.codex",
"browsePlaceholderGemini": "例如:/home/<你的用户名>/.gemini",
"browsePlaceholderOpencode": "例如:/home/<你的用户名>/.config/opencode",
"browsePlaceholderOpenclaw": "例如:/home/<你的用户名>/.openclaw",
"browseDirectory": "浏览目录",
"resetDefault": "恢复默认目录(需保存后生效)",
"checkForUpdates": "检查更新",
+2
View File
@@ -21,6 +21,8 @@ export const settingsSchema = z.object({
claudeConfigDir: directorySchema.nullable().optional(),
codexConfigDir: directorySchema.nullable().optional(),
geminiConfigDir: directorySchema.nullable().optional(),
opencodeConfigDir: directorySchema.nullable().optional(),
openclawConfigDir: directorySchema.nullable().optional(),
// 当前供应商 ID(设备级)
currentProviderClaude: z.string().optional(),