1- import fs from "node:fs" ;
21import { homedir } from "node:os" ;
32import path from "node:path" ;
4- import { agents , isUniversalAgent , detectInstalledAgents , CANONICAL_SKILLS_DIR } from "./agents.js" ;
5- import { createSymlinkSafe } from "./utils/create-symlink-safe.js" ;
3+ import {
4+ CANONICAL_SKILLS_DIR ,
5+ detectInstalledSkillAgents ,
6+ installSkillsFromSource ,
7+ isSkillAgentType ,
8+ } from "agent-install" ;
9+ import type { InstallMode , SkillAgentType } from "agent-install" ;
610
711const SKILL_NAME = "debug-agent" ;
812
9- const getSkillContent = ( ) : string => {
10- const skillPath = path . resolve ( import . meta. dirname , ".." , "skill" , "SKILL.md" ) ;
11- return fs . readFileSync ( skillPath , "utf-8" ) ;
12- } ;
13+ const getSkillSourceDirectory = ( ) : string => path . resolve ( import . meta. dirname , ".." , "skill" ) ;
1314
1415export interface InitOptions {
1516 global ?: boolean ;
@@ -24,73 +25,79 @@ export interface InitResult {
2425 errors : string [ ] ;
2526}
2627
27- const ensureRealDirectory = ( directory : string ) => {
28- const segments = directory . split ( path . sep ) ;
29- for ( let index = 1 ; index <= segments . length ; index ++ ) {
30- const partial = segments . slice ( 0 , index ) . join ( path . sep ) || path . sep ;
31- try {
32- const stat = fs . lstatSync ( partial ) ;
33- if ( stat . isSymbolicLink ( ) ) {
34- fs . unlinkSync ( partial ) ;
35- fs . mkdirSync ( partial , { recursive : true } ) ;
36- }
37- } catch {
38- break ;
28+ interface ResolvedAgents {
29+ agents : SkillAgentType [ ] ;
30+ userIncludedUniversal : boolean ;
31+ }
32+
33+ const resolveRequestedAgents = async (
34+ requestedAgents : string [ ] | undefined ,
35+ errors : string [ ] ,
36+ ) : Promise < ResolvedAgents > => {
37+ if ( requestedAgents === undefined ) {
38+ return {
39+ agents : await detectInstalledSkillAgents ( ) ,
40+ userIncludedUniversal : false ,
41+ } ;
42+ }
43+
44+ const validAgents : SkillAgentType [ ] = [ ] ;
45+ let userIncludedUniversal = false ;
46+
47+ for ( const agentName of requestedAgents ) {
48+ if ( isSkillAgentType ( agentName ) ) {
49+ if ( agentName === "universal" ) userIncludedUniversal = true ;
50+ validAgents . push ( agentName ) ;
51+ } else {
52+ errors . push ( `Unknown agent: ${ agentName } ` ) ;
3953 }
4054 }
41- fs . mkdirSync ( directory , { recursive : true } ) ;
42- } ;
4355
44- const writeSkillToDirectory = ( directory : string , content : string ) => {
45- ensureRealDirectory ( directory ) ;
46- fs . writeFileSync ( path . join ( directory , "SKILL.md" ) , content ) ;
56+ return { agents : validAgents , userIncludedUniversal } ;
4757} ;
4858
49- export const init = ( options : InitOptions = { } ) : InitResult => {
50- const workingDirectory = options . cwd || process . cwd ( ) ;
59+ export const init = async ( options : InitOptions = { } ) : Promise < InitResult > => {
5160 const isGlobal = options . global ?? false ;
52- const useSymlinks = ! ( options . copy ?? false ) ;
53-
54- const baseDirectory = isGlobal ? homedir ( ) : workingDirectory ;
55- const canonicalDirectory = path . join ( baseDirectory , CANONICAL_SKILLS_DIR , SKILL_NAME ) ;
56-
57- const result : InitResult = {
58- canonicalPath : canonicalDirectory ,
59- linkedAgents : [ ] ,
60- errors : [ ] ,
61- } ;
62-
63- const skillContent = getSkillContent ( ) ;
64-
65- writeSkillToDirectory ( canonicalDirectory , skillContent ) ;
66-
67- const agentsToInstall = options . agent
68- ? options . agent
69- . map ( ( agentName ) => [ agentName , agents [ agentName ] ] as const )
70- . filter ( ( [ agentName , agentDefinition ] ) => {
71- if ( ! agentDefinition ) {
72- result . errors . push ( `Unknown agent: ${ agentName } ` ) ;
73- return false ;
74- }
75- return true ;
76- } )
77- : detectInstalledAgents ( ) ;
78-
79- for ( const [ agentName , agentDefinition ] of agentsToInstall ) {
80- if ( ! isUniversalAgent ( agentDefinition ) ) {
81- const agentSkillsDirectory = isGlobal
82- ? agentDefinition . globalSkillsDir
83- : path . join ( workingDirectory , agentDefinition . skillsDir ) ;
84- const agentSkillDirectory = path . join ( agentSkillsDirectory , SKILL_NAME ) ;
85-
86- const didLink = useSymlinks && createSymlinkSafe ( canonicalDirectory , agentSkillDirectory ) ;
87- if ( ! didLink ) {
88- writeSkillToDirectory ( agentSkillDirectory , skillContent ) ;
89- }
90- }
61+ const workingDirectory = options . cwd || process . cwd ( ) ;
62+ const installMode : InstallMode = options . copy ? "copy" : "symlink" ;
63+
64+ const canonicalPath = path . join (
65+ isGlobal ? homedir ( ) : workingDirectory ,
66+ CANONICAL_SKILLS_DIR ,
67+ SKILL_NAME ,
68+ ) ;
69+
70+ const errors : string [ ] = [ ] ;
71+ const { agents : requestedAgents , userIncludedUniversal } = await resolveRequestedAgents (
72+ options . agent ,
73+ errors ,
74+ ) ;
75+
76+ const agentsToInstall : SkillAgentType [ ] = requestedAgents . includes ( "universal" )
77+ ? requestedAgents
78+ : [ ...requestedAgents , "universal" ] ;
79+
80+ const installResult = await installSkillsFromSource ( {
81+ source : getSkillSourceDirectory ( ) ,
82+ agents : agentsToInstall ,
83+ cwd : workingDirectory ,
84+ global : isGlobal ,
85+ mode : installMode ,
86+ } ) ;
87+
88+ for ( const failure of installResult . failed ) {
89+ errors . push ( `${ failure . agent } : ${ failure . error } ` ) ;
90+ }
9191
92- result . linkedAgents . push ( agentName ) ;
92+ const linkedAgentSet = new Set < string > ( ) ;
93+ for ( const installed of installResult . installed ) {
94+ if ( ! userIncludedUniversal && installed . agent === "universal" ) continue ;
95+ linkedAgentSet . add ( installed . agent ) ;
9396 }
9497
95- return result ;
98+ return {
99+ canonicalPath,
100+ linkedAgents : [ ...linkedAgentSet ] ,
101+ errors,
102+ } ;
96103} ;
0 commit comments