11import { spawn } from "node:child_process" ;
22import { existsSync } from "node:fs" ;
3- import { dirname , join , resolve } from "node:path" ;
3+ import { dirname , join } from "node:path" ;
4+ import { fileURLToPath } from "node:url" ;
45
56import { c } from "./utils/colors.ts" ;
67import { readSkillsConfig } from "./config.ts" ;
@@ -13,27 +14,34 @@ export interface InstallSkillsOptions {
1314 yes ?: boolean ;
1415}
1516
16- export async function findSkillsBinary ( cwd : string = process . cwd ( ) ) : Promise < string | undefined > {
17- let dir = resolve ( cwd ) ;
18- const root = dirname ( dir ) ;
17+ let _skillsBinaryCache : string | undefined | null = null ;
1918
20- while ( dir !== root ) {
19+ export function findSkillsBinary ( options ?: { cache ?: boolean } ) : string | undefined {
20+ const useCache = options ?. cache !== false ;
21+ if ( useCache && _skillsBinaryCache !== null ) {
22+ return _skillsBinaryCache ;
23+ }
24+
25+ let dir = dirname ( fileURLToPath ( import . meta. url ) ) ;
26+
27+ while ( true ) {
2128 const candidate = join ( dir , "node_modules" , ".bin" , "skills" ) ;
2229 if ( existsSync ( candidate ) ) {
30+ _skillsBinaryCache = candidate ;
2331 return candidate ;
2432 }
2533 const parent = dirname ( dir ) ;
2634 if ( parent === dir ) break ;
2735 dir = parent ;
2836 }
2937
38+ _skillsBinaryCache = undefined ;
3039 return undefined ;
3140}
3241
3342export async function installSkills ( options : InstallSkillsOptions = { } ) : Promise < void > {
3443 const { config, path : configPath } = await readSkillsConfig ( { cwd : options . cwd } ) ;
3544 const configDir = dirname ( configPath ) ;
36- const skillsBinary = await findSkillsBinary ( options . cwd ) ;
3745
3846 // Ensure .agents is in .gitignore
3947 await addGitignoreEntry ( ".agents" , { cwd : configDir } ) ;
@@ -45,47 +53,62 @@ export async function installSkills(options: InstallSkillsOptions = {}): Promise
4553 let i = 0 ;
4654 for ( const entry of config . skills ) {
4755 i ++ ;
48- const skillList =
49- ( entry . skills ?. length || 0 ) > 0 ? ` ${ c . dim } (${ entry . skills ! . join ( ", " ) } )${ c . reset } ` : "" ;
50- console . log ( `${ c . cyan } ◐${ c . reset } [${ i } /${ total } ] Installing ${ entry . source } ${ skillList } ` ) ;
51-
52- const [ command , args ] = skillsBinary
53- ? [ skillsBinary , [ "add" , entry . source ] ]
54- : [ "npx" , [ "skills" , "add" , entry . source ] ] ;
55-
56- if ( ( entry . skills ?. length || 0 ) > 0 ) {
57- args . push ( "--skill" , ...entry . skills ! ) ;
58- } else {
59- args . push ( "--skill" , "*" ) ;
60- }
56+ await installSkillSource ( entry , { ...options , prefix : `[${ i } /${ total } ] ` } ) ;
57+ }
6158
62- if ( options . agents && options . agents . length > 0 ) {
63- args . push ( "--agent" , ...options . agents ) ;
64- }
59+ const totalDuration = formatDuration ( performance . now ( ) - totalStart ) ;
60+ console . log (
61+ `🎉 Done! ${ total } skill${ total === 1 ? "" : "s" } installed in ${ c . green } ${ totalDuration } ${ c . reset } .` ,
62+ ) ;
63+ }
6564
66- if ( options . global ) {
67- args . push ( "--global" ) ;
68- }
65+ export interface InstallSkillSourceOptions extends InstallSkillsOptions {
66+ prefix ?: string ;
67+ }
6968
70- if ( options . yes ) {
71- args . push ( "--yes" ) ;
72- }
69+ export async function installSkillSource (
70+ entry : { source : string ; skills ?: string [ ] } ,
71+ options : InstallSkillSourceOptions ,
72+ ) : Promise < void > {
73+ const skillsBinary = findSkillsBinary ( ) ;
7374
74- if ( process . env . DEBUG ) {
75- console . log ( `${ c . dim } $ ${ [ "skills" , ...args ] . join ( " " ) } ${ c . reset } \n` ) ;
76- }
75+ const skillList =
76+ ( entry . skills ?. length || 0 ) > 0 ? ` ${ c . dim } (${ entry . skills ! . join ( ", " ) } )${ c . reset } ` : "" ;
77+ console . log ( `${ c . cyan } ◐${ c . reset } ${ options . prefix || "" } Installing ${ entry . source } ${ skillList } ` ) ;
78+
79+ const [ command , args ] = skillsBinary
80+ ? [ skillsBinary , [ "add" , entry . source ] ]
81+ : [ "npx" , [ "skills" , "add" , entry . source ] ] ;
82+
83+ if ( ( entry . skills ?. length || 0 ) > 0 ) {
84+ args . push ( "--skill" , ...entry . skills ! ) ;
85+ } else {
86+ args . push ( "--skill" , "*" ) ;
87+ }
88+
89+ if ( options . agents && options . agents . length > 0 ) {
90+ args . push ( "--agent" , ...options . agents ) ;
91+ }
7792
78- const skillStart = performance . now ( ) ;
79- await runCommand ( command , args ) ;
80- const skillDuration = formatDuration ( performance . now ( ) - skillStart ) ;
93+ if ( options . global ) {
94+ args . push ( "--global" ) ;
95+ }
96+
97+ if ( options . yes ) {
98+ args . push ( "--yes" ) ;
99+ }
100+
101+ if ( process . env . DEBUG ) {
81102 console . log (
82- `${ c . green } ✔ ${ c . reset } Installed ${ entry . source } ${ c . dim } ( ${ skillDuration } ) ${ c . reset } \n` ,
103+ `${ c . dim } $ ${ [ command . replace ( process . cwd ( ) , "." ) , ... args ] . join ( " " ) } ${ c . reset } \n` ,
83104 ) ;
84105 }
85106
86- const totalDuration = formatDuration ( performance . now ( ) - totalStart ) ;
107+ const skillStart = performance . now ( ) ;
108+ await runCommand ( command , args ) ;
109+ const skillDuration = formatDuration ( performance . now ( ) - skillStart ) ;
87110 console . log (
88- `🎉 Done! ${ total } skill ${ total === 1 ? "" : "s" } installed in ${ c . green } ${ totalDuration } ${ c . reset } . ` ,
111+ `${ c . green } ✔ ${ c . reset } Installed ${ entry . source } ${ c . dim } ( ${ skillDuration } ) ${ c . reset } \n ` ,
89112 ) ;
90113}
91114
0 commit comments