feat(settings): add third-party integrations dashboard

Add a Settings → Integrations page for installing and managing the three
supported OpenCode provider plugins (Claude Code, Command Code, Cursor),
with search, i18n, and plugin-registry status wiring.

Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>
This commit is contained in:
Serhii Dziupin
2026-08-14 15:19:25 +00:00
committed by Cursor Agent
co-authored by Serhii Dziupin
parent 3f266232f9
commit af380081c2
33 changed files with 1562 additions and 7 deletions
@@ -34,6 +34,7 @@ import { MagicPromptsPage } from '@/components/sections/magic-prompts/MagicPromp
import { SnippetsSidebar } from '@/components/sections/snippets/SnippetsSidebar';
import { SnippetsPage } from '@/components/sections/snippets/SnippetsPage';
import { GitPage } from '@/components/sections/git-identities/GitPage';
import { IntegrationsPage } from '@/components/sections/integrations/IntegrationsPage';
import type { OpenChamberSection } from '@/components/sections/openchamber/types';
import { OpenChamberPage } from '@/components/sections/openchamber/OpenChamberPage';
import { AboutSettings } from '@/components/sections/openchamber/AboutSettings';
@@ -94,6 +95,7 @@ const pageOrder: SettingsPageSlug[] = [
'sessions',
'shortcuts',
'voice',
'integrations',
'usage',
'about',
// 'projects' group — Workspace
@@ -298,6 +300,24 @@ export const SettingsView: React.FC<SettingsViewProps> = ({ onClose, forceMobile
setMobileStage(def.kind === 'split' ? 'page-sidebar' : 'page-content');
}, [isMobile, setSettingsPage]);
const openThirdPartyProviderSetup = React.useCallback(async (providerId: string): Promise<boolean> => {
const configStore = useConfigStore.getState();
await configStore.loadProviders({ source: 'settings:third-party-provider-setup' });
const providerAvailable = useConfigStore.getState().providers.some(
(provider) => provider.id === providerId,
);
if (!providerAvailable) {
return false;
}
configStore.setSelectedProvider(providerId);
openPage('providers');
if (isMobile) {
setMobileStage('page-content');
}
return true;
}, [isMobile, openPage]);
const activePageMeta = React.useMemo(() => {
return getSettingsPageMeta(settingsSlug);
}, [settingsSlug]);
@@ -343,6 +363,8 @@ export const SettingsView: React.FC<SettingsViewProps> = ({ onClose, forceMobile
return t('settings.page.skillsCatalog.title');
case 'git':
return t('settings.page.git.title');
case 'integrations':
return t('settings.page.integrations.title');
case 'appearance':
return t('settings.page.appearance.title');
case 'chat':
@@ -646,6 +668,13 @@ export const SettingsView: React.FC<SettingsViewProps> = ({ onClose, forceMobile
return <SnippetsPage />;
case 'git':
return <GitPage />;
case 'integrations':
return (
<IntegrationsPage
onOpenProviderSetup={openThirdPartyProviderSetup}
onOpenPluginManager={() => openPage('plugins')}
/>
);
case 'general':
case 'appearance':
case 'chat':
@@ -661,7 +690,7 @@ export const SettingsView: React.FC<SettingsViewProps> = ({ onClose, forceMobile
default:
return null;
}
}, [openChamberSectionBySlug, renderUnavailable, runtimeCtx, t]);
}, [openChamberSectionBySlug, openPage, openThirdPartyProviderSetup, renderUnavailable, runtimeCtx, t]);
// Mobile: if opened via deep-link / palette to a non-home page, jump into it once.
React.useEffect(() => {