mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-03-17 01:21:10 -07:00
init
This commit is contained in:
@@ -13,6 +13,8 @@ import {
|
||||
StartSessionEvent,
|
||||
logCliConfiguration,
|
||||
startupProfiler,
|
||||
type ConnectionConfig,
|
||||
type IdeInfo,
|
||||
} from '@google/gemini-cli-core';
|
||||
import { type LoadedSettings } from '../config/settings.js';
|
||||
import { performInitialAuth } from './auth.js';
|
||||
@@ -23,6 +25,9 @@ export interface InitializationResult {
|
||||
themeError: string | null;
|
||||
shouldOpenAuthDialog: boolean;
|
||||
geminiMdFileCount: number;
|
||||
availableIdeConnections?: Array<
|
||||
ConnectionConfig & { workspacePath?: string; ideInfo?: IdeInfo }
|
||||
>;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -52,10 +57,30 @@ export async function initializeApp(
|
||||
new StartSessionEvent(config, config.getToolRegistry()),
|
||||
);
|
||||
|
||||
let availableIdeConnections:
|
||||
| Array<ConnectionConfig & { workspacePath?: string; ideInfo?: IdeInfo }>
|
||||
| undefined;
|
||||
|
||||
if (config.getIdeMode()) {
|
||||
const ideClient = await IdeClient.getInstance();
|
||||
await ideClient.connect();
|
||||
logIdeConnection(config, new IdeConnectionEvent(IdeConnectionType.START));
|
||||
// Try to auto-connect if possible (legacy behavior or single match)
|
||||
// We attempt to connect. If it requires selection, we get a list back?
|
||||
// No, I need to implement the logic here.
|
||||
const connections = await ideClient.discoverAvailableConnections();
|
||||
|
||||
// Heuristic: If we have a PID match, prioritize it.
|
||||
// Ideally IdeClient.getInstance() already did some detection but didn't connect.
|
||||
// Actually IdeClient.connect() (without args) tries to find "the one" config.
|
||||
// If I want to support multiple, I should check here.
|
||||
|
||||
if (connections.length > 1) {
|
||||
// Multiple connections found, let the UI handle selection
|
||||
availableIdeConnections = connections;
|
||||
} else {
|
||||
// 0 or 1 connection, or let connect() handle the "best guess" fallback
|
||||
await ideClient.connect();
|
||||
logIdeConnection(config, new IdeConnectionEvent(IdeConnectionType.START));
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -63,5 +88,6 @@ export async function initializeApp(
|
||||
themeError,
|
||||
shouldOpenAuthDialog,
|
||||
geminiMdFileCount: config.getGeminiMdFileCount(),
|
||||
availableIdeConnections,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -65,7 +65,12 @@ import {
|
||||
generateSummary,
|
||||
type AgentsDiscoveredPayload,
|
||||
ChangeAuthRequestedError,
|
||||
IdeConnectionEvent,
|
||||
IdeConnectionType,
|
||||
logIdeConnection,
|
||||
type ConnectionConfig,
|
||||
} from '@google/gemini-cli-core';
|
||||
import { IdeConnectionSelector } from './components/IdeConnectionSelector.js';
|
||||
import { validateAuthMethod } from '../config/auth.js';
|
||||
import process from 'node:process';
|
||||
import { useHistory } from './hooks/useHistoryManager.js';
|
||||
@@ -750,7 +755,40 @@ Logging in with Google... Restarting Gemini CLI to continue.
|
||||
dispatchExtensionStateUpdate,
|
||||
addConfirmUpdateExtensionRequest,
|
||||
setText: (text: string) => buffer.setText(text),
|
||||
promptIdeConnection: async () => {
|
||||
const ideClient = await IdeClient.getInstance();
|
||||
await ideClient.disconnect();
|
||||
const connections = await ideClient.discoverAvailableConnections();
|
||||
|
||||
if (connections.length > 1) {
|
||||
setAvailableIdeConnections(connections);
|
||||
} else if (connections.length === 1) {
|
||||
await ideClient.connect();
|
||||
logIdeConnection(
|
||||
config,
|
||||
new IdeConnectionEvent(IdeConnectionType.START),
|
||||
);
|
||||
// Show success message
|
||||
historyManager.addItem(
|
||||
{
|
||||
type: MessageType.INFO,
|
||||
text: `Connected to IDE: ${connections[0].ideInfo?.displayName || 'Unknown IDE'}`,
|
||||
},
|
||||
Date.now(),
|
||||
);
|
||||
} else {
|
||||
// Show error message
|
||||
historyManager.addItem(
|
||||
{
|
||||
type: MessageType.ERROR,
|
||||
text: 'No IDE connections found.',
|
||||
},
|
||||
Date.now(),
|
||||
);
|
||||
}
|
||||
},
|
||||
}),
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[
|
||||
setAuthState,
|
||||
openThemeDialog,
|
||||
@@ -1965,6 +2003,43 @@ Logging in with Google... Restarting Gemini CLI to continue.
|
||||
],
|
||||
);
|
||||
|
||||
// ... (existing imports)
|
||||
|
||||
// Inside AppContainer function:
|
||||
|
||||
// ... (existing state)
|
||||
const [availableIdeConnections, setAvailableIdeConnections] = useState<
|
||||
| Array<ConnectionConfig & { workspacePath?: string; ideInfo?: IdeInfo }>
|
||||
| undefined
|
||||
>(initializationResult.availableIdeConnections);
|
||||
|
||||
// ... (existing effects/hooks)
|
||||
|
||||
if (availableIdeConnections && availableIdeConnections.length > 0) {
|
||||
return (
|
||||
<IdeConnectionSelector
|
||||
connections={availableIdeConnections}
|
||||
onSelect={async (
|
||||
conn: ConnectionConfig & {
|
||||
workspacePath?: string;
|
||||
ideInfo?: IdeInfo;
|
||||
},
|
||||
) => {
|
||||
const ideClient = await IdeClient.getInstance();
|
||||
await ideClient.connect({ connectionConfig: conn });
|
||||
logIdeConnection(
|
||||
config,
|
||||
new IdeConnectionEvent(IdeConnectionType.START),
|
||||
);
|
||||
setAvailableIdeConnections(undefined);
|
||||
}}
|
||||
onCancel={() => {
|
||||
setAvailableIdeConnections(undefined);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (authState === AuthState.AwaitingGoogleLoginRestart) {
|
||||
return (
|
||||
<LoginWithGoogleRestartDialog
|
||||
|
||||
@@ -136,20 +136,6 @@ async function setIdeModeAndSyncConnection(
|
||||
export const ideCommand = async (): Promise<SlashCommand> => {
|
||||
const ideClient = await IdeClient.getInstance();
|
||||
const currentIDE = ideClient.getCurrentIde();
|
||||
if (!currentIDE) {
|
||||
return {
|
||||
name: 'ide',
|
||||
description: 'Manage IDE integration',
|
||||
kind: CommandKind.BUILT_IN,
|
||||
autoExecute: false,
|
||||
action: (): SlashCommandActionReturn =>
|
||||
({
|
||||
type: 'message',
|
||||
messageType: 'error',
|
||||
content: `IDE integration is not supported in your current environment. To use this feature, run Gemini CLI in one of these supported IDEs: Antigravity, VS Code, or VS Code forks.`,
|
||||
}) as const,
|
||||
};
|
||||
}
|
||||
|
||||
const ideSlashCommand: SlashCommand = {
|
||||
name: 'ide',
|
||||
@@ -181,6 +167,16 @@ export const ideCommand = async (): Promise<SlashCommand> => {
|
||||
kind: CommandKind.BUILT_IN,
|
||||
autoExecute: true,
|
||||
action: async (context) => {
|
||||
if (!currentIDE) {
|
||||
context.ui.addItem(
|
||||
{
|
||||
type: 'error',
|
||||
text: 'No IDE detected. Cannot run installer.',
|
||||
},
|
||||
Date.now(),
|
||||
);
|
||||
return;
|
||||
}
|
||||
const installer = getIdeInstaller(currentIDE);
|
||||
if (!installer) {
|
||||
context.ui.addItem(
|
||||
@@ -297,15 +293,30 @@ export const ideCommand = async (): Promise<SlashCommand> => {
|
||||
},
|
||||
};
|
||||
|
||||
const switchCommand: SlashCommand = {
|
||||
name: 'switch',
|
||||
description: 'Switch to a different IDE connection',
|
||||
kind: CommandKind.BUILT_IN,
|
||||
autoExecute: true,
|
||||
action: async (context: CommandContext) => {
|
||||
await context.ui.promptIdeConnection();
|
||||
},
|
||||
};
|
||||
|
||||
const { status } = ideClient.getConnectionStatus();
|
||||
const isConnected = status === IDEConnectionStatus.Connected;
|
||||
|
||||
if (isConnected) {
|
||||
ideSlashCommand.subCommands = [statusCommand, disableCommand];
|
||||
ideSlashCommand.subCommands = [
|
||||
statusCommand,
|
||||
switchCommand,
|
||||
disableCommand,
|
||||
];
|
||||
} else {
|
||||
ideSlashCommand.subCommands = [
|
||||
enableCommand,
|
||||
statusCommand,
|
||||
switchCommand,
|
||||
installCommand,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -83,7 +83,9 @@ export interface CommandContext {
|
||||
extensionsUpdateState: Map<string, ExtensionUpdateStatus>;
|
||||
dispatchExtensionStateUpdate: (action: ExtensionUpdateAction) => void;
|
||||
addConfirmUpdateExtensionRequest: (value: ConfirmationRequest) => void;
|
||||
|
||||
removeComponent: () => void;
|
||||
promptIdeConnection: () => Promise<void>;
|
||||
};
|
||||
// Session-specific data
|
||||
session: {
|
||||
|
||||
74
packages/cli/src/ui/components/IdeConnectionSelector.tsx
Normal file
74
packages/cli/src/ui/components/IdeConnectionSelector.tsx
Normal file
@@ -0,0 +1,74 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2025 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { Box, Text } from 'ink';
|
||||
import { type ConnectionConfig, type IdeInfo } from '@google/gemini-cli-core';
|
||||
import {
|
||||
RadioButtonSelect,
|
||||
type RadioSelectItem,
|
||||
} from './shared/RadioButtonSelect.js';
|
||||
|
||||
interface IdeConnectionSelectorProps {
|
||||
connections: Array<
|
||||
ConnectionConfig & { workspacePath?: string; ideInfo?: IdeInfo }
|
||||
>;
|
||||
onSelect: (
|
||||
connection: ConnectionConfig & {
|
||||
workspacePath?: string;
|
||||
ideInfo?: IdeInfo;
|
||||
},
|
||||
) => void;
|
||||
onCancel: () => void;
|
||||
}
|
||||
|
||||
export const IdeConnectionSelector = ({
|
||||
connections,
|
||||
onSelect,
|
||||
onCancel,
|
||||
}: IdeConnectionSelectorProps) => {
|
||||
const items: Array<RadioSelectItem<number>> = connections.map(
|
||||
(conn, index) => {
|
||||
const label = `${conn.ideInfo?.displayName || 'Unknown IDE'} (${conn.workspacePath || 'No workspace'})`;
|
||||
return {
|
||||
label,
|
||||
value: index,
|
||||
key: index.toString(),
|
||||
};
|
||||
},
|
||||
);
|
||||
|
||||
// Add an option to skip/cancel
|
||||
items.push({
|
||||
label: 'Do not connect to an IDE',
|
||||
value: -1,
|
||||
key: 'cancel',
|
||||
});
|
||||
|
||||
return (
|
||||
<Box
|
||||
flexDirection="column"
|
||||
padding={1}
|
||||
borderStyle="round"
|
||||
borderColor="cyan"
|
||||
>
|
||||
<Text bold color="cyan">
|
||||
Multiple IDE connections found. Please select one:
|
||||
</Text>
|
||||
<Box marginTop={1}>
|
||||
<RadioButtonSelect
|
||||
items={items}
|
||||
onSelect={(value: number) => {
|
||||
if (value === -1) {
|
||||
onCancel();
|
||||
} else {
|
||||
onSelect(connections[value]);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
@@ -213,7 +213,9 @@ describe('useSlashCommandProcessor', () => {
|
||||
toggleDebugProfiler: vi.fn(),
|
||||
dispatchExtensionStateUpdate: vi.fn(),
|
||||
addConfirmUpdateExtensionRequest: vi.fn(),
|
||||
|
||||
setText: vi.fn(),
|
||||
promptIdeConnection: vi.fn(),
|
||||
},
|
||||
new Map(), // extensionsUpdateState
|
||||
true, // isConfigInitialized
|
||||
|
||||
@@ -83,6 +83,7 @@ interface SlashCommandProcessorActions {
|
||||
dispatchExtensionStateUpdate: (action: ExtensionUpdateAction) => void;
|
||||
addConfirmUpdateExtensionRequest: (request: ConfirmationRequest) => void;
|
||||
setText: (text: string) => void;
|
||||
promptIdeConnection: () => Promise<void>;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -237,6 +238,7 @@ export const useSlashCommandProcessor = (
|
||||
addConfirmUpdateExtensionRequest:
|
||||
actions.addConfirmUpdateExtensionRequest,
|
||||
removeComponent: () => setCustomDialog(null),
|
||||
promptIdeConnection: actions.promptIdeConnection,
|
||||
},
|
||||
session: {
|
||||
stats: session.stats,
|
||||
|
||||
@@ -29,5 +29,6 @@ export function createNonInteractiveUI(): CommandContext['ui'] {
|
||||
dispatchExtensionStateUpdate: (_action: ExtensionUpdateAction) => {},
|
||||
addConfirmUpdateExtensionRequest: (_request) => {},
|
||||
removeComponent: () => {},
|
||||
promptIdeConnection: async () => {},
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user