@@ -6,111 +6,111 @@ import { AppFileSystem } from "@opencode-ai/shared/filesystem"
66import { Global } from "../global"
77import { Log } from "../util"
88
9- export namespace Discovery {
10- const skillConcurrency = 4
11- const fileConcurrency = 8
12-
13- class IndexSkill extends Schema . Class < IndexSkill > ( "IndexSkill" ) ( {
14- name : Schema . String ,
15- files : Schema . Array ( Schema . String ) ,
16- } ) { }
17-
18- class Index extends Schema . Class < Index > ( "Index" ) ( {
19- skills : Schema . Array ( IndexSkill ) ,
20- } ) { }
21-
22- export interface Interface {
23- readonly pull : ( url : string ) => Effect . Effect < string [ ] >
24- }
25-
26- export class Service extends Context . Service < Service , Interface > ( ) ( "@opencode/SkillDiscovery" ) { }
27-
28- export const layer : Layer . Layer < Service , never , AppFileSystem . Service | Path . Path | HttpClient . HttpClient > =
29- Layer . effect (
30- Service ,
31- Effect . gen ( function * ( ) {
32- const log = Log . create ( { service : "skill-discovery" } )
33- const fs = yield * AppFileSystem . Service
34- const path = yield * Path . Path
35- const http = HttpClient . filterStatusOk ( withTransientReadRetry ( yield * HttpClient . HttpClient ) )
36- const cache = path . join ( Global . Path . cache , "skills" )
37-
38- const download = Effect . fn ( "Discovery.download" ) ( function * ( url : string , dest : string ) {
39- if ( yield * fs . exists ( dest ) . pipe ( Effect . orDie ) ) return true
40-
41- return yield * HttpClientRequest . get ( url ) . pipe (
42- http . execute ,
43- Effect . flatMap ( ( res ) => res . arrayBuffer ) ,
44- Effect . flatMap ( ( body ) => fs . writeWithDirs ( dest , new Uint8Array ( body ) ) ) ,
45- Effect . as ( true ) ,
46- Effect . catch ( ( err ) =>
47- Effect . sync ( ( ) => {
48- log . error ( "failed to download" , { url, err } )
49- return false
50- } ) ,
51- ) ,
52- )
53- } )
9+ const skillConcurrency = 4
10+ const fileConcurrency = 8
11+
12+ class IndexSkill extends Schema . Class < IndexSkill > ( "IndexSkill" ) ( {
13+ name : Schema . String ,
14+ files : Schema . Array ( Schema . String ) ,
15+ } ) { }
16+
17+ class Index extends Schema . Class < Index > ( "Index" ) ( {
18+ skills : Schema . Array ( IndexSkill ) ,
19+ } ) { }
20+
21+ export interface Interface {
22+ readonly pull : ( url : string ) => Effect . Effect < string [ ] >
23+ }
5424
55- const pull = Effect . fn ( "Discovery.pull" ) ( function * ( url : string ) {
56- const base = url . endsWith ( "/" ) ? url : `${ url } /`
57- const index = new URL ( "index.json" , base ) . href
58- const host = base . slice ( 0 , - 1 )
59-
60- log . info ( "fetching index" , { url : index } )
61-
62- const data = yield * HttpClientRequest . get ( index ) . pipe (
63- HttpClientRequest . acceptJson ,
64- http . execute ,
65- Effect . flatMap ( HttpClientResponse . schemaBodyJson ( Index ) ) ,
66- Effect . catch ( ( err ) =>
67- Effect . sync ( ( ) => {
68- log . error ( "failed to fetch index" , { url : index , err } )
69- return null
70- } ) ,
71- ) ,
72- )
73-
74- if ( ! data ) return [ ]
75-
76- const list = data . skills . filter ( ( skill ) => {
77- if ( ! skill . files . includes ( "SKILL.md" ) ) {
78- log . warn ( "skill entry missing SKILL.md" , { url : index , skill : skill . name } )
25+ export class Service extends Context . Service < Service , Interface > ( ) ( "@opencode/SkillDiscovery" ) { }
26+
27+ export const layer : Layer . Layer < Service , never , AppFileSystem . Service | Path . Path | HttpClient . HttpClient > =
28+ Layer . effect (
29+ Service ,
30+ Effect . gen ( function * ( ) {
31+ const log = Log . create ( { service : "skill-discovery" } )
32+ const fs = yield * AppFileSystem . Service
33+ const path = yield * Path . Path
34+ const http = HttpClient . filterStatusOk ( withTransientReadRetry ( yield * HttpClient . HttpClient ) )
35+ const cache = path . join ( Global . Path . cache , "skills" )
36+
37+ const download = Effect . fn ( "Discovery.download" ) ( function * ( url : string , dest : string ) {
38+ if ( yield * fs . exists ( dest ) . pipe ( Effect . orDie ) ) return true
39+
40+ return yield * HttpClientRequest . get ( url ) . pipe (
41+ http . execute ,
42+ Effect . flatMap ( ( res ) => res . arrayBuffer ) ,
43+ Effect . flatMap ( ( body ) => fs . writeWithDirs ( dest , new Uint8Array ( body ) ) ) ,
44+ Effect . as ( true ) ,
45+ Effect . catch ( ( err ) =>
46+ Effect . sync ( ( ) => {
47+ log . error ( "failed to download" , { url, err } )
7948 return false
80- }
81- return true
82- } )
83-
84- const dirs = yield * Effect . forEach (
85- list ,
86- ( skill ) =>
87- Effect . gen ( function * ( ) {
88- const root = path . join ( cache , skill . name )
89-
90- yield * Effect . forEach (
91- skill . files ,
92- ( file ) => download ( new URL ( file , `${ host } /${ skill . name } /` ) . href , path . join ( root , file ) ) ,
93- {
94- concurrency : fileConcurrency ,
95- } ,
96- )
97-
98- const md = path . join ( root , "SKILL.md" )
99- return ( yield * fs . exists ( md ) . pipe ( Effect . orDie ) ) ? root : null
100- } ) ,
101- { concurrency : skillConcurrency } ,
102- )
103-
104- return dirs . filter ( ( dir ) : dir is string => dir !== null )
49+ } ) ,
50+ ) ,
51+ )
52+ } )
53+
54+ const pull = Effect . fn ( "Discovery.pull" ) ( function * ( url : string ) {
55+ const base = url . endsWith ( "/" ) ? url : `${ url } /`
56+ const index = new URL ( "index.json" , base ) . href
57+ const host = base . slice ( 0 , - 1 )
58+
59+ log . info ( "fetching index" , { url : index } )
60+
61+ const data = yield * HttpClientRequest . get ( index ) . pipe (
62+ HttpClientRequest . acceptJson ,
63+ http . execute ,
64+ Effect . flatMap ( HttpClientResponse . schemaBodyJson ( Index ) ) ,
65+ Effect . catch ( ( err ) =>
66+ Effect . sync ( ( ) => {
67+ log . error ( "failed to fetch index" , { url : index , err } )
68+ return null
69+ } ) ,
70+ ) ,
71+ )
72+
73+ if ( ! data ) return [ ]
74+
75+ const list = data . skills . filter ( ( skill ) => {
76+ if ( ! skill . files . includes ( "SKILL.md" ) ) {
77+ log . warn ( "skill entry missing SKILL.md" , { url : index , skill : skill . name } )
78+ return false
79+ }
80+ return true
10581 } )
10682
107- return Service . of ( { pull } )
108- } ) ,
109- )
110-
111- export const defaultLayer : Layer . Layer < Service > = layer . pipe (
112- Layer . provide ( FetchHttpClient . layer ) ,
113- Layer . provide ( AppFileSystem . defaultLayer ) ,
114- Layer . provide ( NodePath . layer ) ,
83+ const dirs = yield * Effect . forEach (
84+ list ,
85+ ( skill ) =>
86+ Effect . gen ( function * ( ) {
87+ const root = path . join ( cache , skill . name )
88+
89+ yield * Effect . forEach (
90+ skill . files ,
91+ ( file ) => download ( new URL ( file , `${ host } /${ skill . name } /` ) . href , path . join ( root , file ) ) ,
92+ {
93+ concurrency : fileConcurrency ,
94+ } ,
95+ )
96+
97+ const md = path . join ( root , "SKILL.md" )
98+ return ( yield * fs . exists ( md ) . pipe ( Effect . orDie ) ) ? root : null
99+ } ) ,
100+ { concurrency : skillConcurrency } ,
101+ )
102+
103+ return dirs . filter ( ( dir ) : dir is string => dir !== null )
104+ } )
105+
106+ return Service . of ( { pull } )
107+ } ) ,
115108 )
116- }
109+
110+ export const defaultLayer : Layer . Layer < Service > = layer . pipe (
111+ Layer . provide ( FetchHttpClient . layer ) ,
112+ Layer . provide ( AppFileSystem . defaultLayer ) ,
113+ Layer . provide ( NodePath . layer ) ,
114+ )
115+
116+ export * as Discovery from "./discovery"
0 commit comments