@@ -79,6 +79,9 @@ import { FeedbackManager } from '../feedback/FeedbackManager.js';
7979import { TelemetryManager } from '../telemetry/TelemetryManager.js' ;
8080import { SkillsRegistry } from '../skills/SkillsRegistry.js' ;
8181import { CommunitySkillsClient } from '../skills/CommunitySkillsClient.js' ;
82+ import { CommunitySkillsCache } from '../skills/CommunitySkillsCache.js' ;
83+ import { GitHubRegistryFetcher } from '../skills/GitHubRegistryFetcher.js' ;
84+ import { fetchRegistryWithFallback , installSkillWithSecurity } from '../skills/communityInstaller.js' ;
8285import { McpClientManager } from '../mcp/McpClientManager.js' ;
8386import type { McpServerConfig } from '../mcp/types.js' ;
8487import { AUTOHAND_PATHS } from '../constants.js' ;
@@ -879,6 +882,58 @@ export class AutohandAgent {
879882 const cancelled = this . repeatManager . cancel ( id ) ;
880883 result = cancelled ? `Cancelled schedule ${ id } .` : `No active schedule found with ID "${ id } ".` ;
881884 }
885+ } else if ( action . type === 'install_agent_skill' ) {
886+ const skillName = ( action as { name : string } ) . name ;
887+ if ( ! skillName ) {
888+ result = 'Error: install_agent_skill requires a "name" argument.' ;
889+ } else {
890+ const scope = ( action as { scope ?: 'project' | 'user' } ) . scope ?? 'project' ;
891+ const activate = ( action as { activate ?: boolean } ) . activate !== false ;
892+ const cache = new CommunitySkillsCache ( ) ;
893+ const fetcher = new GitHubRegistryFetcher ( ) ;
894+ const registry = await fetchRegistryWithFallback ( cache , fetcher ) ;
895+ if ( ! registry ) {
896+ result = 'Failed to fetch community skills registry. Please check your internet connection.' ;
897+ } else {
898+ const skill = fetcher . findSkill ( registry . skills , skillName ) ;
899+ if ( ! skill ) {
900+ const similar = fetcher . findSimilarSkills ( registry . skills , skillName , 3 ) ;
901+ let msg = `Skill not found: "${ skillName } ".` ;
902+ if ( similar . length > 0 ) {
903+ msg += `\nDid you mean: ${ similar . map ( ( s ) => s . name ) . join ( ', ' ) } ` ;
904+ }
905+ result = msg ;
906+ } else {
907+ const installResult = await installSkillWithSecurity (
908+ {
909+ skillsRegistry : this . skillsRegistry ,
910+ workspaceRoot : this . runtime . workspaceRoot ,
911+ hookManager : this . hookManager ,
912+ isNonInteractive : true ,
913+ } ,
914+ skill ,
915+ cache ,
916+ fetcher ,
917+ scope ,
918+ ) ;
919+ if ( activate && ! installResult . includes ( 'Failed' ) && ! installResult . includes ( 'Blocked' ) && ! installResult . includes ( 'blocked' ) && ! installResult . includes ( 'Denied' ) ) {
920+ // Try to activate after successful install
921+ try {
922+ const activateResult = this . skillsRegistry . activateSkill ( skill . name ) ;
923+ if ( activateResult ) {
924+ result = `${ installResult } \n\nActivated skill: ${ skill . name } ` ;
925+ } else {
926+ result = `${ installResult } \n\nNote: skill installed but could not be activated automatically.` ;
927+ }
928+ } catch {
929+ result = `${ installResult } \n\nNote: skill installed but activation failed.` ;
930+ }
931+ } else {
932+ result = installResult ;
933+ }
934+ }
935+ }
936+ }
882937 } else if ( McpClientManager . isMcpTool ( action . type ) ) {
883938 // Ensure MCP servers have finished connecting before dispatching
884939 if ( this . mcpReady ) await this . mcpReady ;
0 commit comments