Add Claude plugin sync alongside VS Code integration

This commit is contained in:
Jason
2025-10-01 21:23:55 +08:00
parent fcfa9574e8
commit f74d641f86
9 changed files with 319 additions and 9 deletions

View File

@@ -102,6 +102,10 @@ function App() {
if (data.appType === "codex" && isAutoSyncEnabled) {
await syncCodexToVSCode(data.providerId, true);
}
if (data.appType === "claude") {
await syncClaudePlugin(data.providerId, true);
}
});
} catch (error) {
console.error(t("console.setupListenerFailed"), error);
@@ -240,6 +244,32 @@ function App() {
}
};
// 同步 Claude 插件配置(写入/移除固定 JSON
const syncClaudePlugin = async (providerId: string, silent = false) => {
try {
const provider = providers[providerId];
if (!provider) return;
const isOfficial = provider.category === "official";
await window.api.applyClaudePluginConfig({ official: isOfficial });
if (!silent) {
showNotification(
isOfficial
? t("notifications.removedFromClaudePlugin")
: t("notifications.appliedToClaudePlugin"),
"success",
2000,
);
}
} catch (error: any) {
console.error("同步 Claude 插件失败:", error);
if (!silent) {
const message =
error?.message || t("notifications.syncClaudePluginFailed");
showNotification(message, "error", 5000);
}
}
};
const handleSwitchProvider = async (id: string) => {
const success = await window.api.switchProvider(id, activeApp);
if (success) {
@@ -258,6 +288,10 @@ function App() {
if (activeApp === "codex" && isAutoSyncEnabled) {
await syncCodexToVSCode(id, true); // silent模式不显示通知
}
if (activeApp === "claude") {
await syncClaudePlugin(id, true);
}
} else {
showNotification(t("notifications.switchFailed"), "error");
}

View File

@@ -70,6 +70,7 @@ const ProviderList: React.FC<ProviderListProps> = ({
// VS Code 按钮:仅在 Codex + 当前供应商显示;按钮文案根据是否"已应用"变化
const [vscodeAppliedFor, setVscodeAppliedFor] = useState<string | null>(null);
const { enableAutoSync, disableAutoSync } = useVSCodeAutoSync();
const [claudeApplied, setClaudeApplied] = useState<boolean>(false);
// 当当前供应商或 appType 变化时,尝试读取 VS Code settings 并检测状态
useEffect(() => {
@@ -104,6 +105,24 @@ const ProviderList: React.FC<ProviderListProps> = ({
check();
}, [appType, currentProviderId, providers]);
// 检查 Claude 插件配置是否已应用
useEffect(() => {
const checkClaude = async () => {
if (appType !== "claude" || !currentProviderId) {
setClaudeApplied(false);
return;
}
try {
const applied = await window.api.isClaudePluginApplied();
setClaudeApplied(applied);
} catch (error) {
console.error("检测 Claude 插件配置失败:", error);
setClaudeApplied(false);
}
};
checkClaude();
}, [appType, currentProviderId, providers]);
const handleApplyToVSCode = async (provider: Provider) => {
try {
const status = await window.api.getVSCodeSettingsStatus();
@@ -181,6 +200,36 @@ const ProviderList: React.FC<ProviderListProps> = ({
}
};
const handleApplyToClaudePlugin = async () => {
try {
await window.api.applyClaudePluginConfig({ official: false });
onNotify?.(t("notifications.appliedToClaudePlugin"), "success", 3000);
setClaudeApplied(true);
} catch (error: any) {
console.error(error);
const msg =
error && error.message
? error.message
: t("notifications.syncClaudePluginFailed");
onNotify?.(msg, "error", 5000);
}
};
const handleRemoveFromClaudePlugin = async () => {
try {
await window.api.applyClaudePluginConfig({ official: true });
onNotify?.(t("notifications.removedFromClaudePlugin"), "success", 3000);
setClaudeApplied(false);
} catch (error: any) {
console.error(error);
const msg =
error && error.message
? error.message
: t("notifications.syncClaudePluginFailed");
onNotify?.(msg, "error", 5000);
}
};
// 对供应商列表进行排序
const sortedProviders = Object.values(providers).sort((a, b) => {
// 按添加时间排序
@@ -272,9 +321,10 @@ const ProviderList: React.FC<ProviderListProps> = ({
<div className="flex items-center gap-2 ml-4">
{/* VS Code 按钮占位容器 - 始终保持空间,避免布局跳动 */}
{appType === "codex" ? (
<div className="w-[130px]">
{provider.category !== "official" && isCurrent && (
<div className="w-[130px]">
{appType === "codex" &&
provider.category !== "official" &&
isCurrent && (
<button
onClick={() =>
vscodeAppliedFor === provider.id
@@ -298,8 +348,34 @@ const ProviderList: React.FC<ProviderListProps> = ({
: t("provider.applyToVSCode")}
</button>
)}
</div>
) : null}
{appType === "claude" &&
provider.category !== "official" &&
isCurrent && (
<button
onClick={() =>
claudeApplied
? handleRemoveFromClaudePlugin()
: handleApplyToClaudePlugin()
}
className={cn(
"inline-flex items-center gap-1 px-3 py-1.5 text-sm font-medium rounded-md transition-colors w-full whitespace-nowrap justify-center",
claudeApplied
? "border border-gray-300 text-gray-600 hover:border-red-300 hover:text-red-600 hover:bg-red-50 dark:border-gray-600 dark:text-gray-400 dark:hover:border-red-800 dark:hover:text-red-400 dark:hover:bg-red-900/20"
: "border border-gray-300 text-gray-700 hover:border-green-300 hover:text-green-600 hover:bg-green-50 dark:border-gray-600 dark:text-gray-300 dark:hover:border-green-700 dark:hover:text-green-400 dark:hover:bg-green-900/20"
)}
title={
claudeApplied
? t("provider.removeFromClaudePlugin")
: t("provider.applyToClaudePlugin")
}
>
{claudeApplied
? t("provider.removeFromClaudePlugin")
: t("provider.applyToClaudePlugin")}
</button>
)}
</div>
<button
onClick={() => onSwitch(provider.id)}
disabled={isCurrent}

View File

@@ -39,7 +39,9 @@
"configError": "Configuration Error",
"notConfigured": "Not configured for official website",
"applyToVSCode": "Apply to VS Code",
"removeFromVSCode": "Remove from VS Code"
"removeFromVSCode": "Remove from VS Code",
"applyToClaudePlugin": "Apply to Claude plugin",
"removeFromClaudePlugin": "Remove from Claude plugin"
},
"notifications": {
"providerSaved": "Provider configuration saved",
@@ -54,7 +56,10 @@
"missingBaseUrl": "Current configuration missing base_url, cannot write to VS Code",
"saveFailed": "Save failed: {{error}}",
"saveFailedGeneric": "Save failed, please try again",
"syncVSCodeFailed": "Sync to VS Code failed"
"syncVSCodeFailed": "Sync to VS Code failed",
"appliedToClaudePlugin": "Applied to Claude plugin",
"removedFromClaudePlugin": "Removed from Claude plugin",
"syncClaudePluginFailed": "Sync Claude plugin failed"
},
"confirm": {
"deleteProvider": "Delete Provider",

View File

@@ -39,7 +39,9 @@
"configError": "配置错误",
"notConfigured": "未配置官网地址",
"applyToVSCode": "应用到 VS Code",
"removeFromVSCode": "从 VS Code 移除"
"removeFromVSCode": "从 VS Code 移除",
"applyToClaudePlugin": "应用到 Claude 插件",
"removeFromClaudePlugin": "从 Claude 插件移除"
},
"notifications": {
"providerSaved": "供应商配置已保存",
@@ -54,7 +56,10 @@
"missingBaseUrl": "当前配置缺少 base_url无法写入 VS Code",
"saveFailed": "保存失败:{{error}}",
"saveFailedGeneric": "保存失败,请重试",
"syncVSCodeFailed": "同步 VS Code 失败"
"syncVSCodeFailed": "同步 VS Code 失败",
"appliedToClaudePlugin": "已应用到 Claude 插件",
"removedFromClaudePlugin": "已从 Claude 插件移除",
"syncClaudePluginFailed": "同步 Claude 插件失败"
},
"confirm": {
"deleteProvider": "删除供应商",

View File

@@ -306,6 +306,46 @@ export const tauriAPI = {
throw new Error(`写入 VS Code 设置失败: ${String(error)}`);
}
},
// Claude 插件:获取 ~/.claude/config.json 状态
getClaudePluginStatus: async (): Promise<ConfigStatus> => {
try {
return await invoke<ConfigStatus>("get_claude_plugin_status");
} catch (error) {
console.error("获取 Claude 插件状态失败:", error);
return { exists: false, path: "", error: String(error) };
}
},
// Claude 插件:读取配置内容
readClaudePluginConfig: async (): Promise<string | null> => {
try {
return await invoke<string | null>("read_claude_plugin_config");
} catch (error) {
throw new Error(`读取 Claude 插件配置失败: ${String(error)}`);
}
},
// Claude 插件:应用或移除固定配置
applyClaudePluginConfig: async (options: {
official: boolean;
}): Promise<boolean> => {
const { official } = options;
try {
return await invoke<boolean>("apply_claude_plugin_config", { official });
} catch (error) {
throw new Error(`写入 Claude 插件配置失败: ${String(error)}`);
}
},
// Claude 插件:检测是否已应用目标配置
isClaudePluginApplied: async (): Promise<boolean> => {
try {
return await invoke<boolean>("is_claude_plugin_applied");
} catch (error) {
throw new Error(`检测 Claude 插件配置失败: ${String(error)}`);
}
},
};
// 创建全局 API 对象,兼容现有代码

7
src/vite-env.d.ts vendored
View File

@@ -46,6 +46,13 @@ declare global {
getVSCodeSettingsStatus: () => Promise<ConfigStatus>;
readVSCodeSettings: () => Promise<string>;
writeVSCodeSettings: (content: string) => Promise<boolean>;
// Claude 插件配置能力
getClaudePluginStatus: () => Promise<ConfigStatus>;
readClaudePluginConfig: () => Promise<string | null>;
applyClaudePluginConfig: (options: {
official: boolean;
}) => Promise<boolean>;
isClaudePluginApplied: () => Promise<boolean>;
};
platform: {
isMac: boolean;