@@ -8,6 +8,7 @@ import { CloudinaryTreeDataProvider } from "../tree/treeDataProvider";
88import { createWebviewDocument , getScriptUri } from "./webviewUtils" ;
99import { escapeHtml } from "./utils/helpers" ;
1010import {
11+ PlatformId ,
1112 SkillInfo ,
1213 MCP_SERVERS ,
1314 detectEditor ,
@@ -17,9 +18,11 @@ import {
1718 readInstalledSkillDirNames ,
1819 readConfiguredMcpServerKeys ,
1920 installForClaudeCode ,
20- installForCursor ,
2121 installForCopilot ,
22+ installForUniversal ,
23+ installForWindsurf ,
2224 installMcpServers ,
25+ detectActivePlatforms ,
2326} from "../aiToolsService" ;
2427
2528export class HomescreenViewProvider implements vscode . WebviewViewProvider {
@@ -64,7 +67,7 @@ export class HomescreenViewProvider implements vscode.WebviewViewProvider {
6467 } ) ;
6568
6669 webviewView . webview . onDidReceiveMessage (
67- async ( message : { command : string ; skills ?: string [ ] ; ideTarget ?: string ; mcpServers ?: string [ ] } ) => {
70+ async ( message : { command : string ; skills ?: string [ ] ; platforms ?: string [ ] ; mcpServers ?: string [ ] } ) => {
6871 switch ( message . command ) {
6972 case "openGlobalConfig" :
7073 vscode . commands . executeCommand ( "cloudinary.openGlobalConfig" ) ;
@@ -84,7 +87,7 @@ export class HomescreenViewProvider implements vscode.WebviewViewProvider {
8487 case "installAiTools" :
8588 await this . _handleInstallAiTools (
8689 message . skills ?? [ ] ,
87- message . ideTarget ?? "Claude Code" ,
90+ message . platforms ?? [ ] ,
8891 message . mcpServers ?? [ ]
8992 ) ;
9093 break ;
@@ -461,50 +464,6 @@ export class HomescreenViewProvider implements vscode.WebviewViewProvider {
461464 background: var(--vscode-panel-border, rgba(128,128,128,0.14));
462465 }
463466
464- /* IDE segmented control */
465- .hs-ai-ide {
466- position: relative;
467- display: flex;
468- background: rgba(255,255,255,0.04);
469- border: 1px solid var(--vscode-panel-border, rgba(128,128,128,0.14));
470- border-radius: 5px;
471- padding: 2px;
472- margin-bottom: 7px;
473- }
474- .hs-ai-ide-pill {
475- position: absolute;
476- top: 2px;
477- height: calc(100% - 4px);
478- background: rgba(52,72,197,0.35);
479- border: 1px solid rgba(52,72,197,0.5);
480- border-radius: 3px;
481- transition:
482- left 0.15s cubic-bezier(0.4,0,0.2,1),
483- width 0.15s cubic-bezier(0.4,0,0.2,1);
484- pointer-events: none;
485- }
486- .hs-ai-ide-btn {
487- flex: 1;
488- padding: 3px 4px;
489- font-size: 9px;
490- font-weight: 600;
491- letter-spacing: 0.2px;
492- text-align: center;
493- text-transform: uppercase;
494- background: none;
495- border: none;
496- border-radius: 3px;
497- color: var(--vscode-descriptionForeground);
498- cursor: pointer;
499- font-family: var(--vscode-font-family);
500- position: relative;
501- z-index: 1;
502- transition: color 0.15s;
503- white-space: nowrap;
504- }
505- .hs-ai-ide-btn.active { color: var(--vscode-foreground); }
506- .hs-ai-ide-btn:hover:not(.active) { color: var(--vscode-foreground); opacity: 0.7; }
507-
508467 /* Checklist items */
509468 .hs-ai-item {
510469 display: flex;
@@ -590,6 +549,14 @@ export class HomescreenViewProvider implements vscode.WebviewViewProvider {
590549 }
591550 .hs-ai-item-status--ok::before { background: #4ade80; }
592551 .hs-ai-item-status--none::before { background: rgba(255,255,255,0.15); }
552+ .hs-ai-item-status--partial::before { background: rgba(250,204,21,0.7); }
553+ .hs-ai-platform-sub {
554+ display: block;
555+ font-size: 9px;
556+ font-weight: 400;
557+ color: var(--vscode-descriptionForeground);
558+ margin-top: 1px;
559+ }
593560
594561 /* Progress tick */
595562 .hs-ai-item-tick {
@@ -737,13 +704,9 @@ export class HomescreenViewProvider implements vscode.WebviewViewProvider {
737704 <div class="hs-ai-panel-inner hidden" id="hs-ai-state-ready">
738705 <div>
739706 <div class="hs-ai-section-head">Skills</div>
740- <div class="hs-ai-ide" id="hs-ai-ide" role="group" aria-label="Target IDE">
741- <div class="hs-ai-ide-pill" id="hs-ai-ide-pill"></div>
742- <button class="hs-ai-ide-btn active" data-ide="Claude Code">Claude Code</button>
743- <button class="hs-ai-ide-btn" data-ide="Cursor">Cursor</button>
744- <button class="hs-ai-ide-btn" data-ide="VS Code (Copilot)">VS Code</button>
745- </div>
746707 <div id="hs-ai-skills-list"></div>
708+ <div class="hs-ai-section-head" style="margin-top:8px">Install for</div>
709+ <div id="hs-ai-platform-list"></div>
747710 </div>
748711 <div>
749712 <div class="hs-ai-section-head">MCP Servers</div>
@@ -792,41 +755,34 @@ export class HomescreenViewProvider implements vscode.WebviewViewProvider {
792755 const rootUri = workspaceFolders [ 0 ] . uri ;
793756
794757 try {
795- // Fetch skills once; cache for subsequent opens
796758 if ( ! this . _cachedSkills ) {
797759 this . _cachedSkills = await fetchSkillList ( ) ;
798760 }
799761 const skills = this . _cachedSkills ;
800762
801- const ideLabels : string [ ] = [ "Claude Code" , "Cursor" , "VS Code (Copilot)" ] ;
802-
803- // Pre-compute installed status for all 3 IDEs
804- const installedByIde : Record < string , string [ ] > = { } ;
763+ const platformIds : PlatformId [ ] = [ "universal" , "claude-code" , "vscode-copilot" , "windsurf" ] ;
764+ const installedByPlatform : Record < string , string [ ] > = { } ;
805765 await Promise . all (
806- ideLabels . map ( async ( label ) => {
807- const installedSet = await readInstalledSkillDirNames ( rootUri , label , skills ) ;
808- installedByIde [ label ] = [ ...installedSet ] ;
766+ platformIds . map ( async ( pid ) => {
767+ const set = await readInstalledSkillDirNames ( rootUri , pid , skills ) ;
768+ installedByPlatform [ pid ] = [ ...set ] ;
809769 } )
810770 ) ;
811771
812- // MCP servers — use detected editor for the config file path
772+ const activePlatforms = await detectActivePlatforms ( rootUri ) ;
773+
813774 const editor = detectEditor ( ) ;
814775 const mcpFilePath = getMcpFilePath ( editor ) ;
815776 const rootKey = editor === "vscode" ? "servers" : "mcpServers" ;
816777 const configuredMcpSet = await readConfiguredMcpServerKeys ( rootUri , mcpFilePath , rootKey ) ;
817778
818- const detectedIde =
819- editor === "cursor" ? "Cursor" :
820- editor === "vscode" ? "VS Code (Copilot)" :
821- "Claude Code" ;
822-
823779 view . webview . postMessage ( {
824780 command : "aiToolsData" ,
825781 skills : skills . map ( ( s ) => ( { name : s . name , description : s . description , dirName : s . dirName } ) ) ,
826- installedByIde,
782+ installedByPlatform,
783+ activePlatforms,
827784 mcpServers : MCP_SERVERS . map ( ( s ) => ( { key : s . key , label : s . label , description : s . description } ) ) ,
828785 configuredMcpKeys : [ ...configuredMcpSet ] ,
829- detectedIde,
830786 } ) ;
831787 } catch ( err : any ) {
832788 view . webview . postMessage ( {
@@ -838,7 +794,7 @@ export class HomescreenViewProvider implements vscode.WebviewViewProvider {
838794
839795 private async _handleInstallAiTools (
840796 skills : string [ ] ,
841- ideTarget : string ,
797+ platforms : string [ ] ,
842798 mcpServers : string [ ]
843799 ) : Promise < void > {
844800 const view = this . _webviewView ;
@@ -851,9 +807,8 @@ export class HomescreenViewProvider implements vscode.WebviewViewProvider {
851807 }
852808 const rootUri = workspaceFolders [ 0 ] . uri ;
853809 const errors : string [ ] = [ ] ;
854-
855- // Install skills
856810 const cachedSkills = this . _cachedSkills ?? [ ] ;
811+
857812 for ( const dirName of skills ) {
858813 const skillInfo = cachedSkills . find ( ( s ) => s . dirName === dirName ) ;
859814 if ( ! skillInfo ) { continue ; }
@@ -868,22 +823,30 @@ export class HomescreenViewProvider implements vscode.WebviewViewProvider {
868823 }
869824
870825 const createdFiles : string [ ] = [ ] ;
871- try {
872- if ( ideTarget === "Claude Code" ) {
873- await installForClaudeCode ( rootUri , dirName , content , createdFiles , errors ) ;
874- } else if ( ideTarget === "Cursor" ) {
875- await installForCursor ( rootUri , dirName , content , createdFiles ) ;
876- } else {
877- await installForCopilot ( rootUri , skillInfo . name , content , createdFiles ) ;
878- }
879- view . webview . postMessage ( { command : "aiToolsProgress" , item : dirName , status : "done" } ) ;
880- } catch ( err : any ) {
881- errors . push ( `${ dirName } : ${ err . message } ` ) ;
882- view . webview . postMessage ( { command : "aiToolsProgress" , item : dirName , status : "error" } ) ;
826+ let anyError = false ;
827+ for ( const platform of platforms ) {
828+ try {
829+ if ( platform === "claude-code" ) {
830+ await installForClaudeCode ( rootUri , dirName , content , createdFiles , errors ) ;
831+ } else if ( platform === "universal" ) {
832+ await installForUniversal ( rootUri , dirName , content , createdFiles , errors ) ;
833+ } else if ( platform === "windsurf" ) {
834+ await installForWindsurf ( rootUri , dirName , content , createdFiles , errors ) ;
835+ } else if ( platform === "vscode-copilot" ) {
836+ await installForCopilot ( rootUri , skillInfo . name , content , createdFiles ) ;
837+ }
838+ } catch ( err : any ) {
839+ errors . push ( `${ dirName } (${ platform } ): ${ err . message } ` ) ;
840+ anyError = true ;
841+ }
883842 }
843+ view . webview . postMessage ( {
844+ command : "aiToolsProgress" ,
845+ item : dirName ,
846+ status : anyError ? "error" : "done" ,
847+ } ) ;
884848 }
885849
886- // Install MCP servers
887850 if ( mcpServers . length > 0 ) {
888851 const editor = detectEditor ( ) ;
889852 const createdFiles : string [ ] = [ ] ;
@@ -900,9 +863,7 @@ export class HomescreenViewProvider implements vscode.WebviewViewProvider {
900863 }
901864 }
902865
903- // Invalidate cached skills so next open re-reads disk
904866 this . _cachedSkills = undefined ;
905-
906867 view . webview . postMessage ( { command : "aiToolsResult" , errors } ) ;
907868 }
908869}
0 commit comments