@@ -14,11 +14,19 @@ interface McpServerInfo {
1414 description : string ;
1515}
1616
17+ interface AdditionalPlatform {
18+ id : string ;
19+ label : string ;
20+ sublabel ?: string ;
21+ locked : boolean ;
22+ }
23+
1724interface AiToolsDataMessage {
1825 command : "aiToolsData" ;
1926 skills : SkillInfo [ ] ;
20- installedByPlatform : Record < string , string [ ] > ; // platformId → array of dirNames
21- activePlatforms : string [ ] ;
27+ primaryPlatform : string ;
28+ installedOnPrimary : string [ ] ;
29+ additionalPlatforms : AdditionalPlatform [ ] ;
2230 mcpServers : McpServerInfo [ ] ;
2331 configuredMcpKeys : string [ ] ;
2432 error ?: string ;
@@ -78,87 +86,44 @@ function showPanelState(state: "loading" | "ready" | "done" | "error"): void {
7886
7987// ── Helper functions ──────────────────────────────────────────────────────────
8088
81- function getCheckedPlatforms ( ) : string [ ] {
82- return [ ...document . querySelectorAll < HTMLInputElement > ( ".hs-ai-platform-cb" ) ]
83- . filter ( ( c ) => c . checked )
84- . map ( ( c ) => c . dataset . platform ! ) ;
85- }
86-
87- function computeSkillStatus (
88- dirName : string ,
89- installedByPlatform : Record < string , string [ ] > ,
90- checkedPlatforms : string [ ]
91- ) : "installed" | "partial" | "none" {
92- if ( checkedPlatforms . length === 0 ) { return "none" ; }
93- const installedCount = checkedPlatforms . filter (
94- ( pid ) => ( installedByPlatform [ pid ] ?? [ ] ) . includes ( dirName )
95- ) . length ;
96- if ( installedCount === checkedPlatforms . length ) { return "installed" ; }
97- if ( installedCount > 0 ) { return "partial" ; }
98- return "none" ;
99- }
100-
101- // ── Platform definitions and rendering ───────────────────────────────────────
89+ // ── Platform rendering ────────────────────────────────────────────────────────
10290
103- const PLATFORM_DEFS = [
104- { id : "universal" , label : "Universal" , sublabel : "Cursor, Codex, Amp, Warp + more" } ,
105- { id : "claude-code" , label : "Claude Code" } ,
106- { id : "vscode-copilot" , label : "VS Code (Copilot)" } ,
107- { id : "windsurf" , label : "Windsurf" } ,
108- ] ;
91+ const PLATFORM_LABELS : Record < string , string > = {
92+ "universal" : "Universal" ,
93+ "claude-code" : "Claude Code" ,
94+ "vscode-copilot" : "VS Code (Copilot)" ,
95+ "windsurf" : "Windsurf" ,
96+ } ;
10997
110- function renderPlatformRows (
111- activePlatforms : string [ ] ,
112- installedByPlatform : Record < string , string [ ] >
113- ) : void {
98+ function renderAdditionalPlatformRows ( platforms : AdditionalPlatform [ ] ) : void {
11499 const list = el ( "hs-ai-platform-list" ) ;
115100 if ( ! list ) { return ; }
116- const activeSet = new Set ( activePlatforms ) ;
117- list . innerHTML = PLATFORM_DEFS . map ( ( p ) => {
118- const hasInstalls = ( installedByPlatform [ p . id ] ?? [ ] ) . length > 0 ;
119- const checked = activeSet . has ( p . id ) ? "checked" : "" ;
120- const disabled = hasInstalls ? "disabled" : "" ;
101+ list . innerHTML = platforms . map ( ( p ) => {
121102 const sublabel = p . sublabel
122103 ? `<span class="hs-ai-platform-sub">${ escapeHtml ( p . sublabel ) } </span>`
123104 : "" ;
124105 return `<label class="hs-ai-item hs-ai-platform-row">
125- <input type="checkbox" class="hs-ai-cb hs-ai-platform-cb" data-platform="${ escapeHtml ( p . id ) } " ${ checked } ${ disabled } >
106+ <input type="checkbox" class="hs-ai-cb hs-ai-platform-cb" data-platform="${ escapeHtml ( p . id ) } " ${ p . locked ? " checked disabled" : "" } >
126107 <span class="hs-ai-item-name">${ escapeHtml ( p . label ) } ${ sublabel } </span>
127108 </label>` ;
128109 } ) . join ( "" ) ;
129110 list . querySelectorAll < HTMLInputElement > ( ".hs-ai-platform-cb" ) . forEach ( ( cb ) => {
130- cb . addEventListener ( "change" , onPlatformChange ) ;
111+ cb . addEventListener ( "change" , updateApplyButton ) ;
131112 } ) ;
132113}
133114
134- function onPlatformChange ( ) : void {
135- if ( ! _cachedData ) { return ; }
136- const checkedPlatforms = getCheckedPlatforms ( ) ;
137- renderSkillRows ( _cachedData . skills , _cachedData . installedByPlatform , checkedPlatforms ) ;
138- updateApplyButton ( ) ;
139- }
140-
141115// ── Checklist rendering ───────────────────────────────────────────────────────
142116
143- function renderSkillRows (
144- skills : SkillInfo [ ] ,
145- installedByPlatform : Record < string , string [ ] > ,
146- checkedPlatforms : string [ ]
147- ) : void {
117+ function renderSkillRows ( skills : SkillInfo [ ] , installedOnPrimary : string [ ] ) : void {
148118 const list = el ( "hs-ai-skills-list" ) ;
149119 if ( ! list ) { return ; }
120+ const installedSet = new Set ( installedOnPrimary ) ;
150121 list . innerHTML = skills . map ( ( s ) => {
151- const status = computeSkillStatus ( s . dirName , installedByPlatform , checkedPlatforms ) ;
152- const statusClass = status === "installed" ? "hs-ai-item-status--ok"
153- : status === "partial" ? "hs-ai-item-status--partial"
154- : "hs-ai-item-status--none" ;
155- const statusText = status === "installed" ? "installed"
156- : status === "partial" ? "partial"
157- : "—" ;
158- const checked = "checked" ;
159- const disabled = status === "installed" ? "disabled" : "" ;
122+ const isInstalled = installedSet . has ( s . dirName ) ;
123+ const statusClass = isInstalled ? "hs-ai-item-status--ok" : "hs-ai-item-status--none" ;
124+ const statusText = isInstalled ? "installed" : "—" ;
160125 return `<label class="hs-ai-item">
161- <input type="checkbox" class="hs-ai-cb" data-skill="${ escapeHtml ( s . dirName ) } " ${ checked } ${ disabled } >
126+ <input type="checkbox" class="hs-ai-cb" data-skill="${ escapeHtml ( s . dirName ) } " ${ isInstalled ? " checked disabled" : "" } >
162127 <span class="hs-ai-item-name" title="${ escapeHtml ( s . description ) } ">${ escapeHtml ( s . name ) } </span>
163128 <span class="hs-ai-item-status ${ statusClass } ">${ statusText } </span>
164129 </label>` ;
@@ -195,10 +160,9 @@ function renderMcpRows(
195160function updateApplyButton ( ) : void {
196161 const applyBtn = el < HTMLButtonElement > ( "hs-ai-apply" ) ;
197162 if ( ! applyBtn ) { return ; }
198- const anyItemChecked = [ ...document . querySelectorAll < HTMLInputElement > ( ".hs-ai-cb[data-skill], .hs-ai-cb[data-mcp] " ) ]
163+ const anyActionable = [ ...document . querySelectorAll < HTMLInputElement > ( ".hs-ai-cb" ) ]
199164 . some ( ( c ) => c . checked && ! c . disabled ) ;
200- const anyPlatformChecked = getCheckedPlatforms ( ) . length > 0 ;
201- applyBtn . disabled = ! ( anyItemChecked && anyPlatformChecked ) ;
165+ applyBtn . disabled = ! anyActionable ;
202166}
203167
204168// ── Accordion toggle ──────────────────────────────────────────────────────────
@@ -230,9 +194,12 @@ function handleApply(): void {
230194 const skillCheckboxes = document . querySelectorAll < HTMLInputElement > ( ".hs-ai-cb[data-skill]" ) ;
231195 const mcpCheckboxes = document . querySelectorAll < HTMLInputElement > ( ".hs-ai-cb[data-mcp]" ) ;
232196
197+ const platformCheckboxes = document . querySelectorAll < HTMLInputElement > ( ".hs-ai-cb[data-platform]" ) ;
198+
233199 const selectedSkills = [ ...skillCheckboxes ] . filter ( ( c ) => c . checked ) . map ( ( c ) => c . dataset . skill ! ) ;
234200 const selectedMcpKeys = [ ...mcpCheckboxes ] . filter ( ( c ) => c . checked ) . map ( ( c ) => c . dataset . mcp ! ) ;
235- const selectedPlatforms = getCheckedPlatforms ( ) ;
201+ const additionalPlatforms = [ ...platformCheckboxes ] . filter ( ( c ) => c . checked ) . map ( ( c ) => c . dataset . platform ! ) ;
202+ const platforms = [ _cachedData . primaryPlatform , ...additionalPlatforms ] ;
236203
237204 document . querySelectorAll < HTMLInputElement > ( ".hs-ai-cb" ) . forEach ( ( c ) => { c . disabled = true ; } ) ;
238205 const applyBtn = el < HTMLButtonElement > ( "hs-ai-apply" ) ;
@@ -244,7 +211,7 @@ function handleApply(): void {
244211 getVSCode ( ) ?. postMessage ( {
245212 command : "installAiTools" ,
246213 skills : selectedSkills ,
247- platforms : selectedPlatforms ,
214+ platforms,
248215 mcpServers : selectedMcpKeys ,
249216 } ) ;
250217}
@@ -261,15 +228,18 @@ function handleAiToolsData(msg: AiToolsDataMessage): void {
261228
262229 _cachedData = {
263230 skills : msg . skills ,
264- installedByPlatform : msg . installedByPlatform ,
265- activePlatforms : msg . activePlatforms ,
231+ primaryPlatform : msg . primaryPlatform ,
232+ installedOnPrimary : msg . installedOnPrimary ,
233+ additionalPlatforms : msg . additionalPlatforms ,
266234 mcpServers : msg . mcpServers ,
267235 configuredMcpKeys : msg . configuredMcpKeys ,
268236 } ;
269237
270- const checkedPlatforms = msg . activePlatforms ;
271- renderPlatformRows ( checkedPlatforms , msg . installedByPlatform ) ;
272- renderSkillRows ( msg . skills , msg . installedByPlatform , checkedPlatforms ) ;
238+ const platformLabel = el ( "hs-ai-skills-platform" ) ;
239+ if ( platformLabel ) { platformLabel . textContent = `(${ PLATFORM_LABELS [ msg . primaryPlatform ] ?? msg . primaryPlatform } )` ; }
240+
241+ renderAdditionalPlatformRows ( msg . additionalPlatforms ) ;
242+ renderSkillRows ( msg . skills , msg . installedOnPrimary ) ;
273243 renderMcpRows ( msg . mcpServers , msg . configuredMcpKeys ) ;
274244 showPanelState ( "ready" ) ;
275245 updateApplyButton ( ) ;
0 commit comments