Files
openchamber/packages/ui/src/components/sections/integrations/thirdPartyPlugins.ts
T
Serhii DziupinandSerhii Dziupin 53ab67ea35 refactor(integrations): drop unused logos, brand icons, and card wrapper
Keep only the Settings Integrations page wiring: three plugins, i18n,
search/metadata, and the plugins-store registry boolean needed for
failure status. Remove ProviderLogo fallbacks, SVG assets, custom sprite
icons, and the separate IntegrationCard layer.

Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>
2026-08-14 15:19:25 +00:00

162 lines
5.2 KiB
TypeScript

import type { IconName } from '@/components/icon/icons';
import type { I18nKey } from '@/lib/i18n';
import type { PluginEntry, RegistryResult } from '@/stores/usePluginsStore';
export interface ThirdPartyPluginDefinition {
id: string;
packageName: string;
providerId: string;
icon: IconName;
nameKey: I18nKey;
descriptionKey: I18nKey;
homepage: string;
}
export const THIRD_PARTY_PLUGINS: readonly ThirdPartyPluginDefinition[] = [
{
id: 'opencode-claude',
packageName: '@otto-assistant/opencode-claude',
providerId: 'claude-code',
icon: 'sparkling',
nameKey: 'settings.integrations.thirdParty.opencodeClaude.name',
descriptionKey: 'settings.integrations.thirdParty.opencodeClaude.description',
homepage: 'https://github.com/otto-assistant/opencode-claude',
},
{
id: 'opencode-commandcode',
packageName: '@otto-assistant/opencode-commandcode',
providerId: 'command-code',
icon: 'terminal-box',
nameKey: 'settings.integrations.thirdParty.opencodeCommandcode.name',
descriptionKey: 'settings.integrations.thirdParty.opencodeCommandcode.description',
homepage: 'https://github.com/otto-assistant/opencode-commandcode',
},
{
id: 'opencode-cursor-oauth',
packageName: '@otto-assistant/opencode-cursor-oauth',
providerId: 'cursor',
icon: 'cursor',
nameKey: 'settings.integrations.thirdParty.opencodeCursorOauth.name',
descriptionKey: 'settings.integrations.thirdParty.opencodeCursorOauth.description',
homepage: 'https://github.com/otto-assistant/opencode-cursor',
},
] as const;
export interface CatalogPluginState {
userEntry: PluginEntry | null;
userEntryIsAmbiguous: boolean;
projectEntries: PluginEntry[];
registry: RegistryResult | null;
}
export type CatalogPluginPrimaryAction = 'install' | 'update' | 'setup' | 'manage';
type CatalogPluginPresentationStatus =
| 'not-installed'
| 'installed'
| 'installed-version'
| 'update-available'
| 'unpinned'
| 'ambiguous'
| 'restart-required'
| 'registry-unavailable'
| 'provider-unavailable';
interface CatalogPluginPresentationOptions {
registryUnavailable?: boolean;
restartRequired?: boolean;
providerUnavailable?: boolean;
}
interface CatalogPluginPresentation {
status: CatalogPluginPresentationStatus;
latestVersion: string | null;
}
export const specMatchesPackage = (spec: string, packageName: string): boolean =>
spec === packageName || spec.startsWith(`${packageName}@`);
export function getCatalogPluginState(
entries: PluginEntry[],
packageName: string,
registryInfo: Record<string, RegistryResult>,
): CatalogPluginState {
const matchingEntries = entries.filter((entry) => specMatchesPackage(entry.spec, packageName));
const userEntries = matchingEntries.filter((entry) => entry.scope === 'user');
const projectEntries = matchingEntries.filter((entry) => entry.scope === 'project');
const userEntry = userEntries.length === 1 ? userEntries[0] : null;
const registry = registryInfo[userEntry?.spec ?? packageName] ?? registryInfo[packageName] ?? null;
return {
userEntry,
userEntryIsAmbiguous: userEntries.length > 1,
projectEntries,
registry,
};
}
export function getLatestNpmSpec(
packageName: string,
registry: RegistryResult | null | undefined,
): string | null {
if (registry?.kind !== 'npm-ok' || registry.name !== packageName || !registry.latestVersion) {
return null;
}
return `${packageName}@${registry.latestVersion}`;
}
export function getCatalogPluginPrimaryAction(
state: CatalogPluginState,
packageName: string,
): CatalogPluginPrimaryAction {
if (state.userEntryIsAmbiguous) {
return 'manage';
}
if (!state.userEntry) {
return 'install';
}
const latestSpec = getLatestNpmSpec(packageName, state.registry);
return latestSpec && latestSpec !== state.userEntry.spec ? 'update' : 'setup';
}
/**
* Converts catalog and temporary mutation state into the one compact-card
* status. Transient states intentionally outrank installed/version metadata.
*/
export function getCatalogPluginPresentation(
state: CatalogPluginState,
options: CatalogPluginPresentationOptions = {},
): CatalogPluginPresentation {
const latestVersion = state.registry?.kind === 'npm-ok'
? state.registry.latestVersion
: null;
if (state.userEntryIsAmbiguous) {
return { status: 'ambiguous', latestVersion };
}
if (options.restartRequired) {
return { status: 'restart-required', latestVersion };
}
if (options.providerUnavailable) {
return { status: 'provider-unavailable', latestVersion };
}
if (options.registryUnavailable) {
return { status: 'registry-unavailable', latestVersion };
}
if (!state.userEntry) {
return { status: 'not-installed', latestVersion };
}
if (state.registry?.kind === 'npm-ok' && state.registry.currentVersion === state.registry.latestVersion) {
return { status: 'installed-version', latestVersion };
}
if (state.registry?.kind === 'npm-ok' && state.registry.currentVersion === null) {
return { status: 'unpinned', latestVersion };
}
if (state.registry?.kind === 'npm-ok' && latestVersion) {
return { status: 'update-available', latestVersion };
}
return { status: 'installed', latestVersion };
}