@@ -23,7 +23,10 @@ type openclawSkillListItem struct {
2323 Source string `json:"source"`
2424 Bundled bool `json:"bundled"`
2525 Disabled bool `json:"disabled"`
26- SkillKey string `json:"skillKey"`
26+ }
27+
28+ type openclawSkillInfo struct {
29+ SkillKey string `json:"skillKey"`
2730}
2831
2932type skillhubSearchPayload struct {
@@ -32,6 +35,7 @@ type skillhubSearchPayload struct {
3235}
3336
3437var clawhubSearchLinePattern = regexp .MustCompile (`^(\S+)\s+(.+?)\s+\(([\d.]+)\)$` )
38+ var ansiEscapePattern = regexp .MustCompile (`\x1b\[[0-9;?]*[ -/]*[@-~]` )
3539
3640func (a AgentService ) ListSkills (req dto.AgentIDReq ) ([]dto.AgentSkillItem , error ) {
3741 _ , install , err := a .loadOpenclawAgentAndInstall (req .AgentID )
@@ -41,7 +45,11 @@ func (a AgentService) ListSkills(req dto.AgentIDReq) ([]dto.AgentSkillItem, erro
4145 if err := ensureContainerRunning (install .ContainerName ); err != nil {
4246 return nil , err
4347 }
44- output , err := loadOpenclawSkillsStatus (install .ContainerName )
48+ output , err := cmd .RunDefaultWithStdoutBashCfAndTimeOut (
49+ "docker exec %s openclaw skills list --json 2>&1" ,
50+ 30 * time .Second ,
51+ install .ContainerName ,
52+ )
4553 if err != nil {
4654 return nil , err
4755 }
@@ -86,7 +94,7 @@ func (a AgentService) UpdateSkill(req dto.AgentSkillUpdateReq) error {
8694 if err != nil {
8795 return err
8896 }
89- skillKey , err := getOpenclawSkillKeyFromStatus (install .ContainerName , req .Name )
97+ skillKey , err := getOpenclawSkillKey (install .ContainerName , req .Name )
9098 if err != nil {
9199 return err
92100 }
@@ -119,12 +127,15 @@ func (a AgentService) InstallSkill(req dto.AgentSkillInstallReq) error {
119127}
120128
121129func parseOpenclawSkillsList (output string ) ([]dto.AgentSkillItem , error ) {
122- trimmed := strings .TrimSpace (output )
123- if trimmed == "" {
130+ payloadBytes , err := extractEmbeddedJSON (output )
131+ if err != nil {
132+ return nil , err
133+ }
134+ if len (payloadBytes ) == 0 {
124135 return nil , nil
125136 }
126137 var payload openclawSkillsList
127- if err := json .Unmarshal ([] byte ( trimmed ) , & payload ); err != nil {
138+ if err := json .Unmarshal (payloadBytes , & payload ); err != nil {
128139 return nil , err
129140 }
130141 items := make ([]dto.AgentSkillItem , 0 , len (payload .Skills ))
@@ -140,14 +151,6 @@ func parseOpenclawSkillsList(output string) ([]dto.AgentSkillItem, error) {
140151 return items , nil
141152}
142153
143- func loadOpenclawSkillsStatus (containerName string ) (string , error ) {
144- return cmd .RunDefaultWithStdoutBashCfAndTimeOut (
145- "docker exec %s openclaw gateway call skills.status --json 2>&1" ,
146- 30 * time .Second ,
147- containerName ,
148- )
149- }
150-
151154func loadOpenclawSkillSearchOutput (containerName , source , keyword string ) (string , error ) {
152155 switch source {
153156 case "skillhub" :
@@ -229,32 +232,50 @@ func buildOpenclawSkillInstallCommand(source, slug string) string {
229232 }
230233}
231234
232- func getOpenclawSkillKeyFromStatus (containerName , name string ) (string , error ) {
233- output , err := loadOpenclawSkillsStatus (containerName )
235+ func getOpenclawSkillKey (containerName , name string ) (string , error ) {
236+ output , err := cmd .RunDefaultWithStdoutBashCfAndTimeOut (
237+ "docker exec %s openclaw skills info %q --json 2>&1" ,
238+ 30 * time .Second ,
239+ containerName ,
240+ name ,
241+ )
234242 if err != nil {
235243 return "" , err
236244 }
237- return parseOpenclawSkillKeyFromStatus (name , output )
245+ return parseOpenclawSkillKey (name , output )
238246}
239247
240- func parseOpenclawSkillKeyFromStatus (name , output string ) (string , error ) {
241- trimmed := strings . TrimSpace (output )
242- if trimmed == "" {
243- return "" , fmt . Errorf ( "skill %s does not have a skillKey" , name )
248+ func parseOpenclawSkillKey (name , output string ) (string , error ) {
249+ payloadBytes , err := extractEmbeddedJSON (output )
250+ if err != nil {
251+ return "" , err
244252 }
245- var payload openclawSkillsList
246- if err := json .Unmarshal ([] byte ( trimmed ) , & payload ); err != nil {
253+ var payload openclawSkillInfo
254+ if err := json .Unmarshal (payloadBytes , & payload ); err != nil {
247255 return "" , err
248256 }
249- for _ , item := range payload .Skills {
250- if item .Name == name {
251- if item .SkillKey == "" {
252- return "" , fmt .Errorf ("skill %s does not have a skillKey" , name )
253- }
254- return item .SkillKey , nil
257+ if payload .SkillKey == "" {
258+ return "" , fmt .Errorf ("skill %s does not have a skillKey" , name )
259+ }
260+ return payload .SkillKey , nil
261+ }
262+
263+ func extractEmbeddedJSON (output string ) ([]byte , error ) {
264+ trimmed := strings .TrimSpace (ansiEscapePattern .ReplaceAllString (output , "" ))
265+ if trimmed == "" {
266+ return nil , nil
267+ }
268+ for i := 0 ; i < len (trimmed ); i ++ {
269+ if trimmed [i ] != '{' && trimmed [i ] != '[' {
270+ continue
271+ }
272+ decoder := json .NewDecoder (strings .NewReader (trimmed [i :]))
273+ var raw json.RawMessage
274+ if err := decoder .Decode (& raw ); err == nil {
275+ return raw , nil
255276 }
256277 }
257- return "" , fmt .Errorf ("skill %s not found" , name )
278+ return nil , fmt .Errorf ("json payload not found" )
258279}
259280
260281func setOpenclawSkillEnabled (conf map [string ]interface {}, skillKey string , enabled bool ) {
0 commit comments