diff --git a/__tests__/ui/screens/InstallPluginsScreen.test.tsx b/__tests__/ui/screens/InstallPluginsScreen.test.tsx
index dc6328e..b7b91dd 100644
--- a/__tests__/ui/screens/InstallPluginsScreen.test.tsx
+++ b/__tests__/ui/screens/InstallPluginsScreen.test.tsx
@@ -85,6 +85,25 @@ describe('InstallPluginsScreen', () => {
});
});
+ it('sorts detected IDEs above non-detected ones', async () => {
+ const { detectInstalledPlugins } = await import('../../../src/integrations/skills/plugin.js');
+ vi.mocked(detectInstalledPlugins).mockResolvedValueOnce([
+ { ide: 'claude', via: 'cli' },
+ { ide: 'codex', via: 'cli' },
+ ]);
+
+ using sut = renderScreen(, { screen: ScreenId.InstallPlugins });
+
+ await waitFor(() => {
+ const frame = sut.lastFrame()!;
+ const codexPos = frame.indexOf('Codex');
+ const cursorPos = frame.indexOf('Cursor');
+ expect(codexPos).toBeGreaterThan(-1);
+ expect(cursorPos).toBeGreaterThan(-1);
+ expect(codexPos).toBeLessThan(cursorPos);
+ });
+ });
+
it('shows error and retry option on install failure', async () => {
const { installPlugin } = await import('../../../src/integrations/skills/plugin.js');
vi.mocked(installPlugin).mockRejectedValueOnce(new Error('Installation failed'));
diff --git a/src/ui/tui/screens/install-plugins/InstallPluginsScreen.tsx b/src/ui/tui/screens/install-plugins/InstallPluginsScreen.tsx
index 79f4ef0..6356bdb 100644
--- a/src/ui/tui/screens/install-plugins/InstallPluginsScreen.tsx
+++ b/src/ui/tui/screens/install-plugins/InstallPluginsScreen.tsx
@@ -1,16 +1,11 @@
-import { Box, Text } from 'ink';
-import { Spinner } from '@inkjs/ui';
-import { Colors, Icons } from '../../styles.js';
-import { MainLayout } from '../../components/MainLayout.js';
-import { TaskList } from '../../components/TaskList.js';
-import { buildWizardTasks } from '../../lib/wizard-tasks.js';
-import type { IdeId } from '@shared-kernel/types.js';
import { getIntegrations } from '@integrations/index.js';
-import { PLUGIN_REPO_URL } from '@lib/constants.js';
import { ScreenId } from '@lib/session.js';
+import { MainLayout } from '../../components/MainLayout.js';
+import { TaskList } from '../../components/TaskList.js';
import { useAutoAdvance } from '../../hooks/useAutoAdvance.js';
import { useLogger } from '../../hooks/useLog.js';
import { useNavigation } from '../../hooks/useNavigation.js';
+import { buildWizardTasks } from '../../lib/wizard-tasks.js';
import { $session, store } from '../../store.js';
import { usePluginInstall } from './usePluginInstall.js';
import { track } from '@lib/telemetry.js';
@@ -21,15 +16,10 @@ import {
} from './log-messages.js';
import * as te from './telemetry-events.js';
import { type IdeSelectValue, type DetectedSelectValue, type ErrorAction } from './actions.js';
-import { BottomPrompt } from './components/index.js';
+import { BottomPrompt, MainContent } from './components/index.js';
const ALL_INTEGRATIONS = getIntegrations();
-const IDE_LABELS = Object.fromEntries(ALL_INTEGRATIONS.map((i) => [i.id, i.name])) as Record<
- IdeId,
- string
->;
-
export function InstallPluginsScreen() {
const navigate = useNavigation(ScreenId.InstallPlugins);
const log = useLogger(ScreenId.InstallPlugins);
@@ -55,6 +45,7 @@ export function InstallPluginsScreen() {
function handleDetectedSelect(value: DetectedSelectValue) {
if (value === 'continue') {
if (!preferredIndex) return;
+
store.setIde(preferredIndex);
log(pluginsAlreadyInstalled(detected));
track(te.pluginsAlreadyDetected());
@@ -68,9 +59,9 @@ export function InstallPluginsScreen() {
if (value === 'exit') {
log(pluginExitedAfterError(error));
track(te.pluginExitedAfterError());
- process.exit(1);
- return;
+ return process.exit(1);
}
+
const ide = $session.get().ide;
if (ide) selectIde(ide);
}
@@ -84,67 +75,14 @@ export function InstallPluginsScreen() {
: 'active',
);
- const main = (
-
-
-
- Select agent to set up
-
-
-
-
- Your agent will get Confidence skills for flag management, warehouse setup, migrations,
- and onboarding — so it can help without searching the docs.
-
-
-
- {phase === 'detecting' && }
-
- {phase === 'already-installed' && (
- <>
- Detected Confidence plugins for:
- {detected.map((d) => (
-
- {Icons.check}
- {IDE_LABELS[d] ?? d}
-
- ))}
- >
- )}
-
- {phase === 'installing' && }
-
- {phase === 'installed' && (
-
- Plugin installed successfully. Continuing...
-
- )}
-
- {phase === 'error' && (
-
- Failed to install plugin: {error}
-
-
- You can install manually from: {PLUGIN_REPO_URL}
-
-
-
- )}
-
- );
-
- const preferredLabel = preferredIndex ? IDE_LABELS[preferredIndex] : null;
- const otherIntegrations = ALL_INTEGRATIONS.filter((i) => i.id !== preferredIndex);
-
return (
}
aside={}
prompt={
void;
onDetectedSelect: (value: DetectedSelectValue) => void;
onError: (value: ErrorAction) => void;
@@ -20,8 +22,7 @@ type BottomPromptProps = {
export function BottomPrompt({
phase,
- preferredLabel,
- otherIntegrations,
+ detected,
onIdeSelect,
onDetectedSelect,
onError,
@@ -46,17 +47,21 @@ export function BottomPrompt({
/>
);
case 'already-installed': {
- const otherOptions = otherIntegrations.map((i) => ({
- label: i.name,
- value: i.id,
- }));
+ const detectedSet = new Set(detected);
+ const preferred = ALL_INTEGRATIONS.find((i) => detectedSet.has(i.id));
+ const otherOptions = ALL_INTEGRATIONS.filter((i) => i.id !== preferred?.id)
+ .toSorted((a, b) => Number(detectedSet.has(b.id)) - Number(detectedSet.has(a.id)))
+ .map((i) => ({
+ label: i.name,
+ value: i.id,
+ }));
return (
;
+
+const IDE_LABELS = Object.fromEntries(getIntegrations().map((i) => [i.id, i.name])) as IdeLabels;
+
+type MainContentProps = {
+ phase: PluginPhase;
+ detected: IdeId[];
+ error: string | null;
+};
+
+export function MainContent({ phase, detected, error }: MainContentProps) {
+ return (
+
+
+
+ Select agent to set up
+
+
+
+
+ Your agent will get Confidence skills for flag management, warehouse setup, migrations,
+ and onboarding — so it can help without searching the docs.
+
+
+
+ {phase === 'detecting' && }
+
+ {phase === 'already-installed' && (
+ <>
+ Detected Confidence plugins for:
+ {detected.map((d) => (
+
+ {Icons.check}
+ {IDE_LABELS[d] ?? d}
+
+ ))}
+ >
+ )}
+
+ {phase === 'installing' && }
+
+ {phase === 'installed' && (
+
+ Plugin installed successfully. Continuing...
+
+ )}
+
+ {phase === 'error' && (
+
+ Failed to install plugin: {error}
+
+
+ You can install manually from: {PLUGIN_REPO_URL}
+
+
+
+ )}
+
+ );
+}
diff --git a/src/ui/tui/screens/install-plugins/components/index.ts b/src/ui/tui/screens/install-plugins/components/index.ts
index c9db3ca..8b8b563 100644
--- a/src/ui/tui/screens/install-plugins/components/index.ts
+++ b/src/ui/tui/screens/install-plugins/components/index.ts
@@ -1 +1,2 @@
export { BottomPrompt } from './BottomPrompt.js';
+export { MainContent } from './MainContent.js';