@@ -13,7 +13,7 @@ import {
1313} from '../loader/utils'
1414import type { SkillDefinition , SkillResourceDescriptor } from '../types'
1515import { createImportSkill } from './importSkill'
16- import type { SkillStorage } from './types'
16+ import type { SkillStorage , SkillSummary } from './types'
1717
1818/** 一个标准 skill 目录集合的文件系统 storage。 */
1919export interface FsSkillStorageOptions {
@@ -97,7 +97,17 @@ export class FsSkillStorage implements SkillStorage<SkillLoadOptions> {
9797 }
9898
9999 async has ( name : string ) {
100- return Boolean ( await this . get ( name ) )
100+ const entryPath = join ( this . getSkillDirectory ( name ) , entryFile )
101+
102+ try {
103+ return ( await stat ( entryPath ) ) . isFile ( )
104+ } catch ( error ) {
105+ if ( isFileNotFoundError ( error ) ) {
106+ return false
107+ }
108+
109+ throw error
110+ }
101111 }
102112
103113 async delete ( name : string ) {
@@ -130,17 +140,33 @@ export class FsSkillStorage implements SkillStorage<SkillLoadOptions> {
130140 const summaries = await Promise . all (
131141 entries
132142 . filter ( ( entry ) => entry . isDirectory ( ) && ! entry . name . startsWith ( '.' ) )
133- . map ( async ( entry ) => this . get ( entry . name ) ) ,
143+ . map ( async ( entry ) : Promise < SkillSummary | undefined > => {
144+ const directory = this . getSkillDirectory ( entry . name )
145+ const skill = await this . readSkillEntry ( directory ) . catch ( ( error : unknown ) => {
146+ if ( isFileNotFoundError ( error ) ) {
147+ return undefined
148+ }
149+
150+ throw error
151+ } )
152+
153+ if ( ! skill ) {
154+ return undefined
155+ }
156+
157+ const resourceCount = ( await this . readResourceFiles ( directory ) ) . length
158+
159+ return {
160+ name : skill . name ,
161+ description : skill . description ,
162+ resourceCount,
163+ metadata : skill . metadata ,
164+ }
165+ } ) ,
134166 )
135167
136168 return summaries
137- . filter ( ( skill ) : skill is SkillDefinition => Boolean ( skill ) )
138- . map ( ( skill ) => ( {
139- name : skill . name ,
140- description : skill . description ,
141- resourceCount : skill . resources ?. length ?? 0 ,
142- metadata : skill . metadata ,
143- } ) )
169+ . filter ( ( summary ) : summary is SkillSummary => Boolean ( summary ) )
144170 . sort ( ( a , b ) => a . name . localeCompare ( b . name ) )
145171 }
146172
@@ -162,6 +188,16 @@ export class FsSkillStorage implements SkillStorage<SkillLoadOptions> {
162188 }
163189
164190 private async readSkillDirectory ( directory : string ) : Promise < SkillDefinition > {
191+ const skill = await this . readSkillEntry ( directory )
192+ const resources = await this . readResourceDescriptors ( directory )
193+
194+ return {
195+ ...skill ,
196+ resources : resources . length ? resources : undefined ,
197+ }
198+ }
199+
200+ private async readSkillEntry ( directory : string ) {
165201 const entryPath = join ( directory , entryFile )
166202 const entryContent = await readFile ( entryPath , 'utf8' )
167203 const { frontmatter, body } = parseMarkdownFrontmatter ( entryContent )
@@ -171,13 +207,10 @@ export class FsSkillStorage implements SkillStorage<SkillLoadOptions> {
171207 throw new Error ( `Skill entry file "${ entryFile } " must contain instructions.` )
172208 }
173209
174- const resources = await this . readResourceDescriptors ( directory )
175-
176210 return {
177211 name : getString ( frontmatter . name ) || directory . split ( / [ \\ / ] / ) . at ( - 1 ) || '' ,
178212 description : getString ( frontmatter . description ) || '' ,
179213 instructions,
180- resources : resources . length ? resources : undefined ,
181214 metadata : {
182215 ...getRecord ( frontmatter . metadata ) ,
183216 ...( getString ( frontmatter . homepage ) ? { homepage : getString ( frontmatter . homepage ) } : { } ) ,
@@ -188,6 +221,40 @@ export class FsSkillStorage implements SkillStorage<SkillLoadOptions> {
188221 private async readResourceDescriptors ( directory : string ) {
189222 const resources : SkillResourceDescriptor [ ] = [ ]
190223
224+ for ( const { fullPath, path } of await this . readResourceFiles ( directory ) ) {
225+ const fileStat = await stat ( fullPath )
226+ const kind = isTextSkillFilePath ( path ) ? 'text' : 'binary'
227+ const base = {
228+ path,
229+ kind,
230+ resourceId : path ,
231+ size : fileStat . size ,
232+ lastModified : fileStat . mtimeMs ,
233+ }
234+
235+ resources . push (
236+ kind === 'text'
237+ ? {
238+ ...base ,
239+ kind,
240+ readText : async ( ) => readFile ( fullPath , 'utf8' ) ,
241+ readBinary : async ( ) => new Uint8Array ( await readFile ( fullPath ) ) ,
242+ }
243+ : {
244+ ...base ,
245+ kind,
246+ readBinary : async ( ) => new Uint8Array ( await readFile ( fullPath ) ) ,
247+ readText : async ( ) => new TextDecoder ( ) . decode ( await readFile ( fullPath ) ) ,
248+ } ,
249+ )
250+ }
251+
252+ return resources
253+ }
254+
255+ private async readResourceFiles ( directory : string ) {
256+ const files : Array < { fullPath : string ; path : string } > = [ ]
257+
191258 const walk = async ( currentDirectory : string ) => {
192259 const entries = await readdir ( currentDirectory , {
193260 withFileTypes : true ,
@@ -214,36 +281,12 @@ export class FsSkillStorage implements SkillStorage<SkillLoadOptions> {
214281 continue
215282 }
216283
217- const fileStat = await stat ( fullPath )
218- const kind = isTextSkillFilePath ( path ) ? 'text' : 'binary'
219- const base = {
220- path,
221- kind,
222- resourceId : path ,
223- size : fileStat . size ,
224- lastModified : fileStat . mtimeMs ,
225- }
226-
227- resources . push (
228- kind === 'text'
229- ? {
230- ...base ,
231- kind,
232- readText : async ( ) => readFile ( fullPath , 'utf8' ) ,
233- readBinary : async ( ) => new Uint8Array ( await readFile ( fullPath ) ) ,
234- }
235- : {
236- ...base ,
237- kind,
238- readBinary : async ( ) => new Uint8Array ( await readFile ( fullPath ) ) ,
239- readText : async ( ) => new TextDecoder ( ) . decode ( await readFile ( fullPath ) ) ,
240- } ,
241- )
284+ files . push ( { fullPath, path } )
242285 }
243286 }
244287
245288 await walk ( directory )
246- return resources . sort ( ( a , b ) => a . path . localeCompare ( b . path ) )
289+ return files . sort ( ( a , b ) => a . path . localeCompare ( b . path ) )
247290 }
248291
249292 private async writeResource ( directory : string , resource : SkillResourceDescriptor ) {
0 commit comments