@@ -7,6 +7,20 @@ import * as vscode from "vscode";
77import { CloudinaryTreeDataProvider } from "../tree/treeDataProvider" ;
88import { createWebviewDocument , getScriptUri } from "./webviewUtils" ;
99import { escapeHtml } from "./utils/helpers" ;
10+ import {
11+ SkillInfo ,
12+ MCP_SERVERS ,
13+ detectEditor ,
14+ getMcpFilePath ,
15+ fetchSkillList ,
16+ fetchSkillContent ,
17+ readInstalledSkillDirNames ,
18+ readConfiguredMcpServerKeys ,
19+ installForClaudeCode ,
20+ installForCursor ,
21+ installForCopilot ,
22+ installMcpServers ,
23+ } from "../aiToolsService" ;
1024
1125export class HomescreenViewProvider implements vscode . WebviewViewProvider {
1226 public static readonly viewType = "cloudinaryHomescreen" ;
@@ -17,6 +31,7 @@ export class HomescreenViewProvider implements vscode.WebviewViewProvider {
1731 ) { }
1832
1933 private _webviewView : vscode . WebviewView | undefined ;
34+ private _cachedSkills : SkillInfo [ ] | undefined ;
2035
2136 resolveWebviewView (
2237 webviewView : vscode . WebviewView ,
@@ -49,7 +64,7 @@ export class HomescreenViewProvider implements vscode.WebviewViewProvider {
4964 } ) ;
5065
5166 webviewView . webview . onDidReceiveMessage (
52- ( message : { command : string } ) => {
67+ ( message : { command : string ; skills ?: string [ ] ; ideTarget ?: string ; mcpServers ?: string [ ] } ) => {
5368 switch ( message . command ) {
5469 case "openGlobalConfig" :
5570 vscode . commands . executeCommand ( "cloudinary.openGlobalConfig" ) ;
@@ -63,8 +78,15 @@ export class HomescreenViewProvider implements vscode.WebviewViewProvider {
6378 case "openWelcomeScreen" :
6479 vscode . commands . executeCommand ( "cloudinary.openWelcomeScreen" ) ;
6580 break ;
66- case "configureAiTools" :
67- vscode . commands . executeCommand ( "cloudinary.configureAiTools" ) ;
81+ case "aiToolsExpanded" :
82+ this . _handleAiToolsExpanded ( ) ;
83+ break ;
84+ case "installAiTools" :
85+ this . _handleInstallAiTools (
86+ message . skills ?? [ ] ,
87+ message . ideTarget ?? "Claude Code" ,
88+ message . mcpServers ?? [ ]
89+ ) ;
6890 break ;
6991 }
7092 }
@@ -752,4 +774,133 @@ export class HomescreenViewProvider implements vscode.WebviewViewProvider {
752774 </div>
753775 ` ;
754776 }
777+
778+ private async _handleAiToolsExpanded ( ) : Promise < void > {
779+ const view = this . _webviewView ;
780+ if ( ! view ) { return ; }
781+
782+ const workspaceFolders = vscode . workspace . workspaceFolders ;
783+ if ( ! workspaceFolders || workspaceFolders . length === 0 ) {
784+ view . webview . postMessage ( {
785+ command : "aiToolsData" ,
786+ error : "Please open a workspace folder first." ,
787+ } ) ;
788+ return ;
789+ }
790+ const rootUri = workspaceFolders [ 0 ] . uri ;
791+
792+ try {
793+ // Fetch skills once; cache for subsequent opens
794+ if ( ! this . _cachedSkills ) {
795+ this . _cachedSkills = await fetchSkillList ( ) ;
796+ }
797+ const skills = this . _cachedSkills ;
798+
799+ const ideLabels : string [ ] = [ "Claude Code" , "Cursor" , "VS Code (Copilot)" ] ;
800+
801+ // Pre-compute installed status for all 3 IDEs
802+ const installedByIde : Record < string , string [ ] > = { } ;
803+ await Promise . all (
804+ ideLabels . map ( async ( label ) => {
805+ const installedSet = await readInstalledSkillDirNames ( rootUri , label , skills ) ;
806+ installedByIde [ label ] = [ ...installedSet ] ;
807+ } )
808+ ) ;
809+
810+ // MCP servers — use detected editor for the config file path
811+ const editor = detectEditor ( ) ;
812+ const mcpFilePath = getMcpFilePath ( editor ) ;
813+ const rootKey = editor === "vscode" ? "servers" : "mcpServers" ;
814+ const configuredMcpSet = await readConfiguredMcpServerKeys ( rootUri , mcpFilePath , rootKey ) ;
815+
816+ const detectedIde =
817+ editor === "cursor" ? "Cursor" :
818+ editor === "vscode" ? "VS Code (Copilot)" :
819+ "Claude Code" ;
820+
821+ view . webview . postMessage ( {
822+ command : "aiToolsData" ,
823+ skills : skills . map ( ( s ) => ( { name : s . name , description : s . description , dirName : s . dirName } ) ) ,
824+ installedByIde,
825+ mcpServers : MCP_SERVERS . map ( ( s ) => ( { key : s . key , label : s . label , description : s . description } ) ) ,
826+ configuredMcpKeys : [ ...configuredMcpSet ] ,
827+ detectedIde,
828+ } ) ;
829+ } catch ( err : any ) {
830+ view . webview . postMessage ( {
831+ command : "aiToolsData" ,
832+ error : err . message ?? String ( err ) ,
833+ } ) ;
834+ }
835+ }
836+
837+ private async _handleInstallAiTools (
838+ skills : string [ ] ,
839+ ideTarget : string ,
840+ mcpServers : string [ ]
841+ ) : Promise < void > {
842+ const view = this . _webviewView ;
843+ if ( ! view ) { return ; }
844+
845+ const workspaceFolders = vscode . workspace . workspaceFolders ;
846+ if ( ! workspaceFolders || workspaceFolders . length === 0 ) {
847+ view . webview . postMessage ( { command : "aiToolsResult" , errors : [ "No workspace folder open." ] } ) ;
848+ return ;
849+ }
850+ const rootUri = workspaceFolders [ 0 ] . uri ;
851+ const errors : string [ ] = [ ] ;
852+
853+ // Install skills
854+ const cachedSkills = this . _cachedSkills ?? [ ] ;
855+ for ( const dirName of skills ) {
856+ const skillInfo = cachedSkills . find ( ( s ) => s . dirName === dirName ) ;
857+ if ( ! skillInfo ) { continue ; }
858+
859+ let content : string ;
860+ try {
861+ content = await fetchSkillContent ( dirName ) ;
862+ } catch ( err : any ) {
863+ errors . push ( `${ dirName } : ${ err . message } ` ) ;
864+ view . webview . postMessage ( { command : "aiToolsProgress" , item : dirName , status : "error" } ) ;
865+ continue ;
866+ }
867+
868+ const createdFiles : string [ ] = [ ] ;
869+ try {
870+ if ( ideTarget === "Claude Code" ) {
871+ await installForClaudeCode ( rootUri , dirName , content , createdFiles , errors ) ;
872+ } else if ( ideTarget === "Cursor" ) {
873+ await installForCursor ( rootUri , dirName , content , createdFiles ) ;
874+ } else {
875+ await installForCopilot ( rootUri , skillInfo . name , content , createdFiles ) ;
876+ }
877+ view . webview . postMessage ( { command : "aiToolsProgress" , item : dirName , status : "done" } ) ;
878+ } catch ( err : any ) {
879+ errors . push ( `${ dirName } : ${ err . message } ` ) ;
880+ view . webview . postMessage ( { command : "aiToolsProgress" , item : dirName , status : "error" } ) ;
881+ }
882+ }
883+
884+ // Install MCP servers
885+ if ( mcpServers . length > 0 ) {
886+ const editor = detectEditor ( ) ;
887+ const createdFiles : string [ ] = [ ] ;
888+ try {
889+ await installMcpServers ( rootUri , editor , mcpServers , createdFiles ) ;
890+ for ( const key of mcpServers ) {
891+ view . webview . postMessage ( { command : "aiToolsProgress" , item : key , status : "done" } ) ;
892+ }
893+ } catch ( err : any ) {
894+ errors . push ( `MCP: ${ err . message } ` ) ;
895+ for ( const key of mcpServers ) {
896+ view . webview . postMessage ( { command : "aiToolsProgress" , item : key , status : "error" } ) ;
897+ }
898+ }
899+ }
900+
901+ // Invalidate cached skills so next open re-reads disk
902+ this . _cachedSkills = undefined ;
903+
904+ view . webview . postMessage ( { command : "aiToolsResult" , errors } ) ;
905+ }
755906}
0 commit comments