2025-07-16 18:36:14 -04:00
/ * *
* @license
* Copyright 2025 Google LLC
* SPDX - License - Identifier : Apache - 2.0
* /
2025-07-30 21:26:31 +00:00
import {
Config ,
2025-08-04 17:06:17 -04:00
DetectedIde ,
2025-07-30 22:36:24 +00:00
IDEConnectionStatus ,
2025-07-30 21:26:31 +00:00
getIdeDisplayName ,
getIdeInstaller ,
2025-08-05 18:52:58 -04:00
IdeClient ,
2025-08-08 17:26:11 -04:00
type File ,
ideContext ,
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-07-16 18:36:14 -04:00
import {
CommandContext ,
SlashCommand ,
SlashCommandActionReturn ,
2025-07-20 16:57:34 -04:00
CommandKind ,
2025-07-16 18:36:14 -04:00
} 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' ) ;
return ` \ n \ nOpen files: \ n ${ fileList } ` ;
}
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-08-08 17:48:02 -04:00
const context = ideContext . getIdeContext ( ) ;
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-07-16 18:36:14 -04:00
export const ideCommand = ( config : Config | null ) : SlashCommand | null = > {
2025-08-04 17:06:17 -04:00
if ( ! config || ! config . getIdeModeFeature ( ) ) {
2025-07-16 18:36:14 -04:00
return null ;
}
2025-08-04 17:06:17 -04:00
const ideClient = config . getIdeClient ( ) ;
const currentIDE = ideClient . getCurrentIde ( ) ;
if ( ! currentIDE || ! ideClient . getDetectedIdeDisplayName ( ) ) {
return {
name : 'ide' ,
description : 'manage IDE integration' ,
kind : CommandKind.BUILT_IN ,
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: ${ Object . values (
DetectedIde ,
2025-08-08 17:26:11 -04:00
) . map ( ( ide ) = > getIdeDisplayName ( ide ) ) }
2025-08-04 17:06:17 -04:00
. join ( ', ' ) } ` ,
} ) 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' ,
description : 'manage IDE integration' ,
2025-07-20 16:57:34 -04:00
kind : CommandKind.BUILT_IN ,
2025-07-30 22:36:24 +00:00
subCommands : [ ] ,
} ;
const statusCommand : SlashCommand = {
name : 'status' ,
description : 'check status of IDE integration' ,
kind : CommandKind.BUILT_IN ,
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-08-04 17:06:17 -04:00
description : ` install required IDE companion for ${ ideClient . getDetectedIdeDisplayName ( ) } ` ,
2025-07-30 22:36:24 +00:00
kind : CommandKind.BUILT_IN ,
action : async ( context ) = > {
const installer = getIdeInstaller ( currentIDE ) ;
if ( ! installer ) {
context . ui . addItem (
{
type : 'error' ,
2025-08-04 17:06:17 -04:00
text : ` No installer is available for ${ ideClient . getDetectedIdeDisplayName ( ) } . Please install the IDE companion manually from its 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 ( ) ;
2025-08-05 18:52:58 -04:00
if ( result . success ) {
config . setIdeMode ( true ) ;
context . services . settings . setValue ( SettingScope . User , 'ideMode' , true ) ;
}
2025-07-30 22:36:24 +00:00
context . ui . addItem (
{
type : result . success ? 'info' : 'error' ,
text : result.message ,
} ,
Date . now ( ) ,
) ;
} ,
} ;
const enableCommand : SlashCommand = {
name : 'enable' ,
description : 'enable IDE integration' ,
kind : CommandKind.BUILT_IN ,
action : async ( context : CommandContext ) = > {
context . services . settings . setValue ( SettingScope . User , 'ideMode' , true ) ;
2025-08-05 18:52:58 -04:00
await config . setIdeModeAndSyncConnection ( true ) ;
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' ,
description : 'disable IDE integration' ,
kind : CommandKind.BUILT_IN ,
action : async ( context : CommandContext ) = > {
context . services . settings . setValue ( SettingScope . User , 'ideMode' , false ) ;
2025-08-05 18:52:58 -04:00
await config . setIdeModeAndSyncConnection ( false ) ;
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
const ideModeEnabled = config . getIdeMode ( ) ;
if ( ideModeEnabled ) {
ideSlashCommand . subCommands = [
disableCommand ,
statusCommand ,
installCommand ,
] ;
} else {
ideSlashCommand . subCommands = [
enableCommand ,
statusCommand ,
installCommand ,
] ;
}
return ideSlashCommand ;
2025-07-16 18:36:14 -04:00
} ;