diff --git a/packages/cli/src/ui/App.tsx b/packages/cli/src/ui/App.tsx
index e952d6b2..ab30b730 100644
--- a/packages/cli/src/ui/App.tsx
+++ b/packages/cli/src/ui/App.tsx
@@ -576,14 +576,18 @@ const App = ({ config, settings, startupWarnings = [], version }: AppProps) => {
const handleIdePromptComplete = useCallback(
(result: IdeIntegrationNudgeResult) => {
- if (result === 'yes') {
- handleSlashCommand('/ide install');
+ if (result.userSelection === 'yes') {
+ if (result.isExtensionPreInstalled) {
+ handleSlashCommand('/ide enable');
+ } else {
+ handleSlashCommand('/ide install');
+ }
settings.setValue(
SettingScope.User,
'hasSeenIdeIntegrationNudge',
true,
);
- } else if (result === 'dismiss') {
+ } else if (result.userSelection === 'dismiss') {
settings.setValue(
SettingScope.User,
'hasSeenIdeIntegrationNudge',
@@ -942,9 +946,9 @@ const App = ({ config, settings, startupWarnings = [], version }: AppProps) => {
)}
- {shouldShowIdePrompt ? (
+ {shouldShowIdePrompt && currentIDE ? (
) : isFolderTrustDialogOpen ? (
diff --git a/packages/cli/src/ui/IdeIntegrationNudge.tsx b/packages/cli/src/ui/IdeIntegrationNudge.tsx
index f0c6172d..13f70a75 100644
--- a/packages/cli/src/ui/IdeIntegrationNudge.tsx
+++ b/packages/cli/src/ui/IdeIntegrationNudge.tsx
@@ -4,44 +4,74 @@
* SPDX-License-Identifier: Apache-2.0
*/
+import { DetectedIde, getIdeInfo } from '@google/gemini-cli-core';
import { Box, Text, useInput } from 'ink';
import {
RadioButtonSelect,
RadioSelectItem,
} from './components/shared/RadioButtonSelect.js';
-export type IdeIntegrationNudgeResult = 'yes' | 'no' | 'dismiss';
+export type IdeIntegrationNudgeResult = {
+ userSelection: 'yes' | 'no' | 'dismiss';
+ isExtensionPreInstalled: boolean;
+};
interface IdeIntegrationNudgeProps {
- ideName?: string;
+ ide: DetectedIde;
onComplete: (result: IdeIntegrationNudgeResult) => void;
}
export function IdeIntegrationNudge({
- ideName,
+ ide,
onComplete,
}: IdeIntegrationNudgeProps) {
useInput((_input, key) => {
if (key.escape) {
- onComplete('no');
+ onComplete({
+ userSelection: 'no',
+ isExtensionPreInstalled: false,
+ });
}
});
+ const { displayName: ideName } = getIdeInfo(ide);
+ // Assume extension is already installed if the env variables are set.
+ const isExtensionPreInstalled =
+ !!process.env.GEMINI_CLI_IDE_SERVER_PORT &&
+ !!process.env.GEMINI_CLI_IDE_WORKSPACE_PATH;
+
const OPTIONS: Array> = [
{
label: 'Yes',
- value: 'yes',
+ value: {
+ userSelection: 'yes',
+ isExtensionPreInstalled,
+ },
},
{
label: 'No (esc)',
- value: 'no',
+ value: {
+ userSelection: 'no',
+ isExtensionPreInstalled,
+ },
},
{
label: "No, don't ask again",
- value: 'dismiss',
+ value: {
+ userSelection: 'dismiss',
+ isExtensionPreInstalled,
+ },
},
];
+ const installText = isExtensionPreInstalled
+ ? `If you select Yes, the CLI will have access to your open files and display diffs directly in ${
+ ideName ?? 'your editor'
+ }.`
+ : `If you select Yes, we'll install an extension that allows the CLI to access your open files and display diffs directly in ${
+ ideName ?? 'your editor'
+ }.`;
+
return (
{'> '}
- {`Do you want to connect your ${ideName ?? 'your'} editor to Gemini CLI?`}
+ {`Do you want to connect ${ideName ?? 'your'} editor to Gemini CLI?`}
- {`If you select Yes, we'll install an extension that allows the CLI to access your open files and display diffs directly in ${ideName ?? 'your editor'}.`}
+ {installText}
{
content: `IDE integration is not supported in your current environment. To use this feature, run Gemini CLI in one of these supported IDEs: ${Object.values(
DetectedIde,
)
- .map((ide) => getIdeDisplayName(ide))
+ .map((ide) => getIdeInfo(ide).displayName)
.join(', ')}`,
}) as const,
};
diff --git a/packages/core/src/ide/detect-ide.ts b/packages/core/src/ide/detect-ide.ts
index 759c4103..ef07994c 100644
--- a/packages/core/src/ide/detect-ide.ts
+++ b/packages/core/src/ide/detect-ide.ts
@@ -6,33 +6,43 @@
export enum DetectedIde {
VSCode = 'vscode',
- VSCodium = 'vscodium',
Cursor = 'cursor',
CloudShell = 'cloudshell',
Codespaces = 'codespaces',
- Windsurf = 'windsurf',
FirebaseStudio = 'firebasestudio',
Trae = 'trae',
}
-export function getIdeDisplayName(ide: DetectedIde): string {
+export interface IdeInfo {
+ displayName: string;
+}
+
+export function getIdeInfo(ide: DetectedIde): IdeInfo {
switch (ide) {
case DetectedIde.VSCode:
- return 'VS Code';
- case DetectedIde.VSCodium:
- return 'VSCodium';
+ return {
+ displayName: 'VS Code',
+ };
case DetectedIde.Cursor:
- return 'Cursor';
+ return {
+ displayName: 'Cursor',
+ };
case DetectedIde.CloudShell:
- return 'Cloud Shell';
+ return {
+ displayName: 'Cloud Shell',
+ };
case DetectedIde.Codespaces:
- return 'GitHub Codespaces';
- case DetectedIde.Windsurf:
- return 'Windsurf';
+ return {
+ displayName: 'GitHub Codespaces',
+ };
case DetectedIde.FirebaseStudio:
- return 'Firebase Studio';
+ return {
+ displayName: 'Firebase Studio',
+ };
case DetectedIde.Trae:
- return 'Trae';
+ return {
+ displayName: 'Trae',
+ };
default: {
// This ensures that if a new IDE is added to the enum, we get a compile-time error.
const exhaustiveCheck: never = ide;
diff --git a/packages/core/src/ide/ide-client.ts b/packages/core/src/ide/ide-client.ts
index 74d0df74..fe605eb2 100644
--- a/packages/core/src/ide/ide-client.ts
+++ b/packages/core/src/ide/ide-client.ts
@@ -6,11 +6,7 @@
import * as fs from 'node:fs';
import * as path from 'node:path';
-import {
- detectIde,
- DetectedIde,
- getIdeDisplayName,
-} from '../ide/detect-ide.js';
+import { detectIde, DetectedIde, getIdeInfo } from '../ide/detect-ide.js';
import {
ideContext,
IdeContextNotificationSchema,
@@ -68,7 +64,7 @@ export class IdeClient {
private constructor() {
this.currentIde = detectIde();
if (this.currentIde) {
- this.currentIdeDisplayName = getIdeDisplayName(this.currentIde);
+ this.currentIdeDisplayName = getIdeInfo(this.currentIde).displayName;
}
}
@@ -86,7 +82,7 @@ export class IdeClient {
`IDE integration is not supported in your current environment. To use this feature, run Gemini CLI in one of these supported IDEs: ${Object.values(
DetectedIde,
)
- .map((ide) => getIdeDisplayName(ide))
+ .map((ide) => getIdeInfo(ide).displayName)
.join(', ')}`,
false,
);
diff --git a/packages/core/src/ide/ide-installer.test.ts b/packages/core/src/ide/ide-installer.test.ts
index 1afd7a36..e43e1b34 100644
--- a/packages/core/src/ide/ide-installer.test.ts
+++ b/packages/core/src/ide/ide-installer.test.ts
@@ -23,19 +23,6 @@ describe('ide-installer', () => {
// A more specific check might be needed if we export the class
expect(installer).toBeInstanceOf(Object);
});
-
- it('should return an OpenVSXInstaller for "vscodium"', () => {
- const installer = getIdeInstaller(DetectedIde.VSCodium);
- expect(installer).not.toBeNull();
- expect(installer).toBeInstanceOf(Object);
- });
-
- it('should return a DefaultIDEInstaller for an unknown IDE', () => {
- const installer = getIdeInstaller('unknown' as DetectedIde);
- // Assuming DefaultIDEInstaller is the fallback
- expect(installer).not.toBeNull();
- expect(installer).toBeInstanceOf(Object);
- });
});
describe('VsCodeInstaller', () => {
@@ -67,44 +54,4 @@ describe('ide-installer', () => {
});
});
});
-
- describe('OpenVSXInstaller', () => {
- let installer: IdeInstaller;
-
- beforeEach(() => {
- installer = getIdeInstaller(DetectedIde.VSCodium)!;
- });
-
- afterEach(() => {
- vi.restoreAllMocks();
- });
-
- describe('install', () => {
- it('should call execSync with the correct command and return success', async () => {
- const execSyncSpy = vi
- .spyOn(child_process, 'execSync')
- .mockImplementation(() => '');
- const result = await installer.install();
- expect(execSyncSpy).toHaveBeenCalledWith(
- 'npx ovsx get google.gemini-cli-vscode-ide-companion',
- { stdio: 'pipe' },
- );
- expect(result.success).toBe(true);
- expect(result.message).toContain(
- 'VS Code companion extension was installed successfully from OpenVSX',
- );
- });
-
- it('should return a failure message on failed installation', async () => {
- vi.spyOn(child_process, 'execSync').mockImplementation(() => {
- throw new Error('Command failed');
- });
- const result = await installer.install();
- expect(result.success).toBe(false);
- expect(result.message).toContain(
- 'Failed to install VS Code companion extension from OpenVSX',
- );
- });
- });
- });
});
diff --git a/packages/core/src/ide/ide-installer.ts b/packages/core/src/ide/ide-installer.ts
index e6192bfa..7db8e2d2 100644
--- a/packages/core/src/ide/ide-installer.ts
+++ b/packages/core/src/ide/ide-installer.ts
@@ -147,31 +147,11 @@ class VsCodeInstaller implements IdeInstaller {
}
}
-class OpenVSXInstaller implements IdeInstaller {
- async install(): Promise {
- // TODO: Use the correct extension path.
- const command = `npx ovsx get google.gemini-cli-vscode-ide-companion`;
- try {
- child_process.execSync(command, { stdio: 'pipe' });
- return {
- success: true,
- message:
- 'VS Code companion extension was installed successfully from OpenVSX. Please restart your terminal to complete the setup.',
- };
- } catch (_error) {
- return {
- success: false,
- message: `Failed to install VS Code companion extension from OpenVSX. Please try installing it manually.`,
- };
- }
- }
-}
-
export function getIdeInstaller(ide: DetectedIde): IdeInstaller | null {
switch (ide) {
case DetectedIde.VSCode:
return new VsCodeInstaller();
default:
- return new OpenVSXInstaller();
+ return null;
}
}
diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts
index e60bd048..791446e3 100644
--- a/packages/core/src/index.ts
+++ b/packages/core/src/index.ts
@@ -50,7 +50,7 @@ export * from './services/gitService.js';
export * from './ide/ide-client.js';
export * from './ide/ideContext.js';
export * from './ide/ide-installer.js';
-export { getIdeDisplayName, DetectedIde } from './ide/detect-ide.js';
+export { getIdeInfo, DetectedIde, IdeInfo } from './ide/detect-ide.js';
// Export Shell Execution Service
export * from './services/shellExecutionService.js';