2025-07-16 18:36:14 -04:00
/ * *
* @license
* Copyright 2025 Google LLC
* SPDX - License - Identifier : Apache - 2.0
* /
2025-09-04 09:32:09 -07:00
import {
type Config ,
IdeClient ,
type File ,
logIdeConnection ,
IdeConnectionEvent ,
IdeConnectionType ,
} from '@google/gemini-cli-core' ;
2025-07-30 21:26:31 +00:00
import {
getIdeInstaller ,
2025-08-26 00:04:53 +02:00
IDEConnectionStatus ,
2025-09-11 11:22:20 -07:00
ideContextStore ,
2025-08-26 00:04:53 +02:00
GEMINI_CLI_COMPANION_EXTENSION_NAME ,
2025-07-30 21:26:31 +00:00
} from '@google/gemini-cli-core' ;
2025-08-08 17:26:11 -04:00
import path from 'node:path' ;
2025-08-26 00:04:53 +02:00
import type {
2025-07-16 18:36:14 -04:00
CommandContext ,
SlashCommand ,
SlashCommandActionReturn ,
} from './types.js' ;
2025-08-26 00:04:53 +02:00
import { CommandKind } from './types.js' ;
2025-07-30 22:36:24 +00:00
import { SettingScope } from '../../config/settings.js' ;
2025-07-16 18:36:14 -04:00
2025-08-05 18:52:58 -04:00
function getIdeStatusMessage ( ideClient : IdeClient ) : {
messageType : 'info' | 'error' ;
content : string ;
} {
const connection = ideClient . getConnectionStatus ( ) ;
switch ( connection . status ) {
case IDEConnectionStatus . Connected :
return {
messageType : 'info' ,
content : ` 🟢 Connected to ${ ideClient . getDetectedIdeDisplayName ( ) } ` ,
} ;
case IDEConnectionStatus . Connecting :
return {
messageType : 'info' ,
content : ` 🟡 Connecting... ` ,
} ;
default : {
let content = ` 🔴 Disconnected ` ;
if ( connection ? . details ) {
content += ` : ${ connection . details } ` ;
}
return {
messageType : 'error' ,
content ,
} ;
}
}
}
2025-08-08 17:26:11 -04:00
function formatFileList ( openFiles : File [ ] ) : string {
const basenameCounts = new Map < string , number > ( ) ;
for ( const file of openFiles ) {
const basename = path . basename ( file . path ) ;
basenameCounts . set ( basename , ( basenameCounts . get ( basename ) || 0 ) + 1 ) ;
}
const fileList = openFiles
. map ( ( file : File ) = > {
const basename = path . basename ( file . path ) ;
const isDuplicate = ( basenameCounts . get ( basename ) || 0 ) > 1 ;
const parentDir = path . basename ( path . dirname ( file . path ) ) ;
const displayName = isDuplicate
? ` ${ basename } (/ ${ parentDir } ) `
: basename ;
return ` - ${ displayName } ${ file . isActive ? ' (active)' : '' } ` ;
} )
. join ( '\n' ) ;
2025-08-11 12:27:45 -04:00
const infoMessage = `
( Note : The file list is limited to a number of recently accessed files within your workspace and only includes local files on disk ) ` ;
return ` \ n \ nOpen files: \ n ${ fileList } \ n ${ infoMessage } ` ;
2025-08-08 17:26:11 -04:00
}
async function getIdeStatusMessageWithFiles ( ideClient : IdeClient ) : Promise < {
messageType : 'info' | 'error' ;
content : string ;
} > {
const connection = ideClient . getConnectionStatus ( ) ;
switch ( connection . status ) {
case IDEConnectionStatus . Connected : {
let content = ` 🟢 Connected to ${ ideClient . getDetectedIdeDisplayName ( ) } ` ;
2025-09-11 11:22:20 -07:00
const context = ideContextStore . get ( ) ;
2025-08-08 17:48:02 -04:00
const openFiles = context ? . workspaceState ? . openFiles ;
if ( openFiles && openFiles . length > 0 ) {
content += formatFileList ( openFiles ) ;
2025-08-08 17:26:11 -04:00
}
return {
messageType : 'info' ,
content ,
} ;
}
case IDEConnectionStatus . Connecting :
return {
messageType : 'info' ,
content : ` 🟡 Connecting... ` ,
} ;
default : {
let content = ` 🔴 Disconnected ` ;
if ( connection ? . details ) {
content += ` : ${ connection . details } ` ;
}
return {
messageType : 'error' ,
content ,
} ;
}
}
}
2025-09-04 09:32:09 -07:00
async function setIdeModeAndSyncConnection (
config : Config ,
value : boolean ,
2025-11-18 12:01:16 -05:00
options : { logToConsole? : boolean } = { } ,
2025-09-04 09:32:09 -07:00
) : Promise < void > {
config . setIdeMode ( value ) ;
const ideClient = await IdeClient . getInstance ( ) ;
if ( value ) {
2025-11-18 12:01:16 -05:00
await ideClient . connect ( options ) ;
2025-09-04 09:32:09 -07:00
logIdeConnection ( config , new IdeConnectionEvent ( IdeConnectionType . SESSION ) ) ;
} else {
await ideClient . disconnect ( ) ;
2025-07-16 18:36:14 -04:00
}
2025-09-04 09:32:09 -07:00
}
export const ideCommand = async ( ) : Promise < SlashCommand > = > {
const ideClient = await IdeClient . getInstance ( ) ;
2025-08-04 17:06:17 -04:00
const currentIDE = ideClient . getCurrentIde ( ) ;
2025-09-18 15:23:24 -04:00
if ( ! currentIDE ) {
2025-08-04 17:06:17 -04:00
return {
name : 'ide' ,
2025-10-17 13:20:15 -07:00
description : 'Manage IDE integration' ,
2025-08-04 17:06:17 -04:00
kind : CommandKind.BUILT_IN ,
2025-12-01 12:29:03 -05:00
autoExecute : false ,
2025-08-04 17:06:17 -04:00
action : ( ) : SlashCommandActionReturn = >
( {
type : 'message' ,
messageType : 'error' ,
2025-11-18 12:01:16 -05:00
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. ` ,
2025-08-04 17:06:17 -04:00
} ) as const ,
} ;
2025-07-30 21:26:31 +00:00
}
2025-07-16 18:36:14 -04:00
2025-07-30 22:36:24 +00:00
const ideSlashCommand : SlashCommand = {
2025-07-16 18:36:14 -04:00
name : 'ide' ,
2025-10-17 13:20:15 -07:00
description : 'Manage IDE integration' ,
2025-07-20 16:57:34 -04:00
kind : CommandKind.BUILT_IN ,
2025-12-01 12:29:03 -05:00
autoExecute : false ,
2025-07-30 22:36:24 +00:00
subCommands : [ ] ,
} ;
const statusCommand : SlashCommand = {
name : 'status' ,
2025-10-17 13:20:15 -07:00
description : 'Check status of IDE integration' ,
2025-07-30 22:36:24 +00:00
kind : CommandKind.BUILT_IN ,
2025-12-01 12:29:03 -05:00
autoExecute : true ,
2025-08-08 17:26:11 -04:00
action : async ( ) : Promise < SlashCommandActionReturn > = > {
const { messageType , content } =
await getIdeStatusMessageWithFiles ( ideClient ) ;
2025-08-05 18:52:58 -04:00
return {
type : 'message' ,
messageType ,
content ,
} as const ;
2025-07-30 22:36:24 +00:00
} ,
} ;
2025-07-16 18:36:14 -04:00
2025-07-30 22:36:24 +00:00
const installCommand : SlashCommand = {
name : 'install' ,
2025-10-17 13:20:15 -07:00
description : ` Install required IDE companion for ${ ideClient . getDetectedIdeDisplayName ( ) } ` ,
2025-07-30 22:36:24 +00:00
kind : CommandKind.BUILT_IN ,
2025-12-01 12:29:03 -05:00
autoExecute : true ,
2025-07-30 22:36:24 +00:00
action : async ( context ) = > {
const installer = getIdeInstaller ( currentIDE ) ;
if ( ! installer ) {
context . ui . addItem (
{
type : 'error' ,
2025-08-14 14:57:36 +00:00
text : ` No installer is available for ${ ideClient . getDetectedIdeDisplayName ( ) } . Please install the ' ${ GEMINI_CLI_COMPANION_EXTENSION_NAME } ' extension manually from the marketplace. ` ,
2025-07-30 22:36:24 +00:00
} ,
Date . now ( ) ,
) ;
return ;
}
2025-07-30 21:26:31 +00:00
2025-07-30 22:36:24 +00:00
context . ui . addItem (
{
type : 'info' ,
2025-08-04 17:06:17 -04:00
text : ` Installing IDE companion... ` ,
2025-07-16 18:36:14 -04:00
} ,
2025-07-30 22:36:24 +00:00
Date . now ( ) ,
) ;
const result = await installer . install ( ) ;
context . ui . addItem (
{
type : result . success ? 'info' : 'error' ,
text : result.message ,
} ,
Date . now ( ) ,
) ;
2025-08-20 14:11:31 -07:00
if ( result . success ) {
2025-08-27 18:39:45 -07:00
context . services . settings . setValue (
SettingScope . User ,
'ide.enabled' ,
true ,
) ;
2025-08-20 14:11:31 -07:00
// Poll for up to 5 seconds for the extension to activate.
for ( let i = 0 ; i < 10 ; i ++ ) {
2025-11-18 12:01:16 -05:00
await setIdeModeAndSyncConnection ( context . services . config ! , true , {
logToConsole : false ,
} ) ;
2025-08-20 14:11:31 -07:00
if (
ideClient . getConnectionStatus ( ) . status ===
IDEConnectionStatus . Connected
) {
break ;
}
await new Promise ( ( resolve ) = > setTimeout ( resolve , 500 ) ) ;
}
const { messageType , content } = getIdeStatusMessage ( ideClient ) ;
if ( messageType === 'error' ) {
context . ui . addItem (
{
type : messageType ,
text : ` Failed to automatically enable IDE integration. To fix this, run the CLI in a new terminal window. ` ,
} ,
Date . now ( ) ,
) ;
} else {
context . ui . addItem (
{
type : messageType ,
text : content ,
} ,
Date . now ( ) ,
) ;
}
}
2025-07-30 22:36:24 +00:00
} ,
} ;
const enableCommand : SlashCommand = {
name : 'enable' ,
2025-10-17 13:20:15 -07:00
description : 'Enable IDE integration' ,
2025-07-30 22:36:24 +00:00
kind : CommandKind.BUILT_IN ,
2025-12-01 12:29:03 -05:00
autoExecute : true ,
2025-07-30 22:36:24 +00:00
action : async ( context : CommandContext ) = > {
2025-08-27 18:39:45 -07:00
context . services . settings . setValue (
SettingScope . User ,
'ide.enabled' ,
true ,
) ;
2025-09-04 09:32:09 -07:00
await setIdeModeAndSyncConnection ( context . services . config ! , true ) ;
2025-08-05 18:52:58 -04:00
const { messageType , content } = getIdeStatusMessage ( ideClient ) ;
context . ui . addItem (
{
type : messageType ,
text : content ,
} ,
Date . now ( ) ,
) ;
2025-07-30 22:36:24 +00:00
} ,
} ;
const disableCommand : SlashCommand = {
name : 'disable' ,
2025-10-17 13:20:15 -07:00
description : 'Disable IDE integration' ,
2025-07-30 22:36:24 +00:00
kind : CommandKind.BUILT_IN ,
2025-12-01 12:29:03 -05:00
autoExecute : true ,
2025-07-30 22:36:24 +00:00
action : async ( context : CommandContext ) = > {
2025-08-27 18:39:45 -07:00
context . services . settings . setValue (
SettingScope . User ,
'ide.enabled' ,
false ,
) ;
2025-09-04 09:32:09 -07:00
await setIdeModeAndSyncConnection ( context . services . config ! , false ) ;
2025-08-05 18:52:58 -04:00
const { messageType , content } = getIdeStatusMessage ( ideClient ) ;
context . ui . addItem (
{
type : messageType ,
text : content ,
} ,
Date . now ( ) ,
) ;
2025-07-30 22:36:24 +00:00
} ,
2025-07-16 18:36:14 -04:00
} ;
2025-07-30 22:36:24 +00:00
2025-08-19 10:24:58 -07:00
const { status } = ideClient . getConnectionStatus ( ) ;
const isConnected = status === IDEConnectionStatus . Connected ;
if ( isConnected ) {
ideSlashCommand . subCommands = [ statusCommand , disableCommand ] ;
2025-07-30 22:36:24 +00:00
} else {
ideSlashCommand . subCommands = [
enableCommand ,
statusCommand ,
installCommand ,
] ;
}
return ideSlashCommand ;
2025-07-16 18:36:14 -04:00
} ;