Skip to main content

agentshield/adapter/
openclaw.rs

1use std::path::Path;
2
3use crate::error::Result;
4use crate::ir::*;
5use crate::parser;
6
7/// OpenClaw Skills adapter.
8///
9/// Detects by presence of `SKILL.md` file.
10pub struct OpenClawAdapter;
11
12impl super::Adapter for OpenClawAdapter {
13    fn framework(&self) -> Framework {
14        Framework::OpenClaw
15    }
16
17    fn detect(&self, root: &Path) -> bool {
18        root.join("SKILL.md").exists()
19    }
20
21    fn load(&self, root: &Path) -> Result<Vec<ScanTarget>> {
22        let name = root
23            .file_name()
24            .map(|n| n.to_string_lossy().to_string())
25            .unwrap_or_else(|| "openclaw-skill".into());
26
27        let mut source_files = Vec::new();
28        let mut execution = execution_surface::ExecutionSurface::default();
29
30        // Collect source files (Python and Shell scripts)
31        let walker = ignore::WalkBuilder::new(root)
32            .hidden(true)
33            .git_ignore(true)
34            .max_depth(Some(3))
35            .build();
36
37        for entry in walker.flatten() {
38            let path = entry.path();
39            if !path.is_file() {
40                continue;
41            }
42            let ext = path
43                .extension()
44                .map(|e| e.to_string_lossy().to_string())
45                .unwrap_or_default();
46            let lang = Language::from_extension(&ext);
47
48            if !matches!(
49                lang,
50                Language::Python | Language::Shell | Language::Markdown
51            ) {
52                continue;
53            }
54
55            let metadata = std::fs::metadata(path)?;
56            if metadata.len() > 1_048_576 {
57                continue;
58            }
59
60            if let Ok(content) = std::fs::read_to_string(path) {
61                use sha2::Digest;
62                let hash = format!(
63                    "{:x}",
64                    sha2::Sha256::new()
65                        .chain_update(content.as_bytes())
66                        .finalize()
67                );
68                source_files.push(SourceFile {
69                    path: path.to_path_buf(),
70                    language: lang,
71                    size_bytes: metadata.len(),
72                    content_hash: hash,
73                    content,
74                });
75            }
76        }
77
78        // Parse source files
79        for sf in &source_files {
80            if let Some(parser) = parser::parser_for_language(sf.language) {
81                if let Ok(parsed) = parser.parse_file(&sf.path, &sf.content) {
82                    execution.commands.extend(parsed.commands);
83                    execution.file_operations.extend(parsed.file_operations);
84                    execution
85                        .network_operations
86                        .extend(parsed.network_operations);
87                    execution.env_accesses.extend(parsed.env_accesses);
88                    execution.dynamic_exec.extend(parsed.dynamic_exec);
89                }
90            }
91        }
92
93        Ok(vec![ScanTarget {
94            name,
95            framework: Framework::OpenClaw,
96            root_path: root.to_path_buf(),
97            tools: vec![],
98            execution,
99            data: Default::default(),
100            dependencies: Default::default(),
101            provenance: Default::default(),
102            source_files,
103        }])
104    }
105}