Coverage for src / library_skills / scanner.py: 100%
236 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-08-03 20:19 +0000
« prev ^ index » next coverage.py v7.13.5, created at 2026-08-03 20:19 +0000
1import csv 2{b~bac|bbccc}bdcec
2import json 2{b~bac|bbccc}bdcec
3import re 2{b~bac|bbccc}bdcec
4from dataclasses import dataclass, field 2{b~bac|bbccc}bdcec
5from email.parser import Parser 2{b~bac|bbccc}bdcec
6from pathlib import Path, PurePosixPath 2{b~bac|bbccc}bdcec
7from urllib.parse import urlparse 2{b~bac|bbccc}bdcec
8from urllib.request import url2pathname 2{b~bac|bbccc}bdcec
10import yaml 2{b~bac|bbccc}bdcec
13@dataclass(frozen=True) 2{b~bac|bbccc}bdcec
14class Skill: 2{b~bac|bbccc}bdcec
15 """A discovered agent skill."""
17 name: str 2{b|b}b
18 description: str 2{b|b}b
19 path: Path 2{b|b}b
20 package_name: str 2{b|b}b
21 skill_dir: Path 2{b|b}b
22 package_version: str = "" 2{b~bac|bbccc}bdcec
25@dataclass 2{b~bac|bbccc}bdcec
26class ScanResult: 2{b~bac|bbccc}bdcec
27 """Result of scanning for skills."""
29 skills: list[Skill] = field(default_factory=list) 2{b~bac|bbccc}bdcec
30 warnings: list[str] = field(default_factory=list) 2{b~bac|bbccc}bdcec
31 environment_path: Path | None = None 2{b~bac|bbccc}bdcec
34_SKILL_NAME_RE = re.compile(r"^[a-z0-9](?:[a-z0-9-]{0,62}[a-z0-9])?$") 2{b~bac|bbccc}bdcec
37def scan_python_distributions(site_packages: Path) -> ScanResult: 2{b~bac|bbccc}bdcec
38 """Scan target environment distribution metadata for bundled skills."""
39 result = ScanResult() 2N !c#c$cd e O P Q R %cnb'cobS T U pbqb(c)cV W *cX Y +crbsbtbub,cZ a -c0 1 2 3 .c4 5 p @bvbf K wbYcm AcE RbF B v s XbUb0by xc/c1b6 7 8 :c;c=cg h 9 ! # $ ?cxb@cyb% ' ( zbAb[c]c) * ^c+ , _cBbCbDbEb`c- b {c. / : ; |c= ? q [bFbi L GbZcn BcG SbH C w t YbVb2bz yc}c3b@ [ ] ~cadbdj k ^ _ ` { cdHbddIb| } ~ JbKbedfdabbbgdcbdbhdLbMbNbObidebc jdfbgbhbibkdjbkbr ]bPbl M Qb0co CcI TbJ D x u ZbWb4bA zcld5blbmb
40 seen_skill_dirs: set[Path] = set() 2N !c#c$cd e O P Q R %cnb'cobS T U pbqb(c)cV W *cX Y +crbsbtbub,cZ a -c0 1 2 3 .c4 5 p @bvbf K wbYcm AcE RbF B v s XbUb0by xc/c1b6 7 8 :c;c=cg h 9 ! # $ ?cxb@cyb% ' ( zbAb[c]c) * ^c+ , _cBbCbDbEb`c- b {c. / : ; |c= ? q [bFbi L GbZcn BcG SbH C w t YbVb2bz yc}c3b@ [ ] ~cadbdj k ^ _ ` { cdHbddIb| } ~ JbKbedfdabbbgdcbdbhdLbMbNbObidebc jdfbgbhbibkdjbkbr ]bPbl M Qb0co CcI TbJ D x u ZbWb4bA zcld5blbmb
42 if not site_packages.is_dir(): 2N !c#c$cd e O P Q R %cnb'cobS T U pbqb(c)cV W *cX Y +crbsbtbub,cZ a -c0 1 2 3 .c4 5 p @bvbf K wbYcm AcE RbF B v s XbUb0by xc/c1b6 7 8 :c;c=cg h 9 ! # $ ?cxb@cyb% ' ( zbAb[c]c) * ^c+ , _cBbCbDbEb`c- b {c. / : ; |c= ? q [bFbi L GbZcn BcG SbH C w t YbVb2bz yc}c3b@ [ ] ~cadbdj k ^ _ ` { cdHbddIb| } ~ JbKbedfdabbbgdcbdbhdLbMbNbObidebc jdfbgbhbibkdjbkbr ]bPbl M Qb0co CcI TbJ D x u ZbWb4bA zcld5blbmb
43 result.warnings.append(f"Site-packages directory not found: {site_packages}") 2/cWdXd}cYdZdld0d1d
44 return result 2/cWdXd}cYdZdld0d1d
46 for dist_info in sorted(site_packages.glob("*.dist-info")): 2N !c#c$cd e O P Q R %cnb'cobS T U pbqb(c)cV W *cX Y +crbsbtbub,cZ a -c0 1 2 3 .c4 5 p @bvbf K wbYcm AcE RbF B v s XbUb0by xc1b6 7 8 :c;c=cg h 9 ! # $ ?cxb@cyb% ' ( zbAb[c]c) * ^c+ , _cBbCbDbEb`c- b {c. / : ; |c= ? q [bFbi L GbZcn BcG SbH C w t YbVb2bz yc3b@ [ ] ~cadbdj k ^ _ ` { cdHbddIb| } ~ JbKbedfdabbbgdcbdbhdLbMbNbObidebc jdfbgbhbibkdjbkbr ]bPbl M Qb0co CcI TbJ D x u ZbWb4bA zc5blbmb
47 if not dist_info.is_dir(): 2N d e O P Q R nbobS T U pbqbV W X Y rbsbtbubZ a 0 1 2 3 4 5 p @bvbf K wbYcm AcE RbF B v s XbUb0by xc1b6 7 8 g h 9 ! # $ xbyb% ' ( zbAb) * + , BbCbDbEb- b . / : ; = ? q [bFbi L GbZcn BcG SbH C w t YbVb2bz yc3b@ [ ] j k ^ _ ` { HbIb| } ~ JbKbabbbcbdbLbMbNbObebc fbgbhbibjbkbr ]bPbl M Qb0co CcI TbJ D x u ZbWb4bA zc5blbmb
48 continue 2Yc-d.dZc/d:d0c;d=d
50 dist = _read_distribution_info(dist_info) 2N d e O P Q R nbobS T U pbqbV W X Y rbsbtbubZ a 0 1 2 3 4 5 p @bvbf K wbm AcE RbF B v s XbUb0by xc1b6 7 8 g h 9 ! # $ xbyb% ' ( zbAb) * + , BbCbDbEb- b . / : ; = ? q [bFbi L Gbn BcG SbH C w t YbVb2bz yc3b@ [ ] j k ^ _ ` { HbIb| } ~ JbKbabbbcbdbLbMbNbObebc fbgbhbibjbkbr ]bPbl M Qbo CcI TbJ D x u ZbWb4bA zc5blbmb
51 if dist is None: 2N d e O P Q R nbobS T U pbqbV W X Y rbsbtbubZ a 0 1 2 3 4 5 p @bvbf K wbm AcE RbF B v s XbUb0by xc1b6 7 8 g h 9 ! # $ xbyb% ' ( zbAb) * + , BbCbDbEb- b . / : ; = ? q [bFbi L Gbn BcG SbH C w t YbVb2bz yc3b@ [ ] j k ^ _ ` { HbIb| } ~ JbKbabbbcbdbLbMbNbObebc fbgbhbibjbkbr ]bPbl M Qbo CcI TbJ D x u ZbWb4bA zc5blbmb
52 result.warnings.append( 2AcxcpdqdBcycrdsdCczctdud
53 f"Skipping invalid distribution metadata: {dist_info}"
54 )
55 continue 2AcxcpdqdBcycrdsdCczctdud
57 found = _scan_distribution_records( 2N d e O P Q R nbobS T U pbqbV W X Y rbsbtbubZ a 0 1 2 3 4 5 p @bvbf K wbm E RbF B v s XbUb0by 1b6 7 8 g h 9 ! # $ xbyb% ' ( zbAb) * + , BbCbDbEb- b . / : ; = ? q [bFbi L Gbn G SbH C w t YbVb2bz 3b@ [ ] j k ^ _ ` { HbIb| } ~ JbKbabbbcbdbLbMbNbObebc fbgbhbibjbkbr ]bPbl M Qbo I TbJ D x u ZbWb4bA 5blbmb
58 site_packages=site_packages,
59 dist_info=dist_info,
60 package_name=dist.name,
61 package_version=dist.version,
62 seen_skill_dirs=seen_skill_dirs,
63 )
64 result.skills.extend(found.skills) 2N d e O P Q R nbobS T U pbqbV W X Y rbsbtbubZ a 0 1 2 3 4 5 p @bvbf K wbm E RbF B v s XbUb0by 1b6 7 8 g h 9 ! # $ xbyb% ' ( zbAb) * + , BbCbDbEb- b . / : ; = ? q [bFbi L Gbn G SbH C w t YbVb2bz 3b@ [ ] j k ^ _ ` { HbIb| } ~ JbKbabbbcbdbLbMbNbObebc fbgbhbibjbkbr ]bPbl M Qbo I TbJ D x u ZbWb4bA 5blbmb
65 result.warnings.extend(found.warnings) 2N d e O P Q R nbobS T U pbqbV W X Y rbsbtbubZ a 0 1 2 3 4 5 p @bvbf K wbm E RbF B v s XbUb0by 1b6 7 8 g h 9 ! # $ xbyb% ' ( zbAb) * + , BbCbDbEb- b . / : ; = ? q [bFbi L Gbn G SbH C w t YbVb2bz 3b@ [ ] j k ^ _ ` { HbIb| } ~ JbKbabbbcbdbLbMbNbObebc fbgbhbibjbkbr ]bPbl M Qbo I TbJ D x u ZbWb4bA 5blbmb
67 if not found.skills: 2N d e O P Q R nbobS T U pbqbV W X Y rbsbtbubZ a 0 1 2 3 4 5 p @bvbf K wbm E RbF B v s XbUb0by 1b6 7 8 g h 9 ! # $ xbyb% ' ( zbAb) * + , BbCbDbEb- b . / : ; = ? q [bFbi L Gbn G SbH C w t YbVb2bz 3b@ [ ] j k ^ _ ` { HbIb| } ~ JbKbabbbcbdbLbMbNbObebc fbgbhbibjbkbr ]bPbl M Qbo I TbJ D x u ZbWb4bA 5blbmb
68 fallback = _scan_editable_direct_url( 2p @bf m E RbF B v s XbUb0by 1bDcEcq [bi n G SbH C w t YbVb2bz 3bFcGcr ]bl o I TbJ D x u ZbWb4bA 5bHcIc
69 dist_info=dist_info,
70 site_packages=site_packages,
71 package_name=dist.name,
72 package_version=dist.version,
73 seen_skill_dirs=seen_skill_dirs,
74 )
75 result.skills.extend(fallback.skills) 2p @bf m E RbF B v s XbUb0by 1bDcEcq [bi n G SbH C w t YbVb2bz 3bFcGcr ]bl o I TbJ D x u ZbWb4bA 5bHcIc
76 result.warnings.extend(fallback.warnings) 2p @bf m E RbF B v s XbUb0by 1bDcEcq [bi n G SbH C w t YbVb2bz 3bFcGcr ]bl o I TbJ D x u ZbWb4bA 5bHcIc
78 return result 2N !c#c$cd e O P Q R %cnb'cobS T U pbqb(c)cV W *cX Y +crbsbtbub,cZ a -c0 1 2 3 .c4 5 p @bvbf K wbYcm AcE RbF B v s XbUb0by xc1b6 7 8 :c;c=cg h 9 ! # $ ?cxb@cyb% ' ( zbAb[c]c) * ^c+ , _cBbCbDbEb`c- b {c. / : ; |c= ? q [bFbi L GbZcn BcG SbH C w t YbVb2bz yc3b@ [ ] ~cadbdj k ^ _ ` { cdHbddIb| } ~ JbKbedfdabbbgdcbdbhdLbMbNbObidebc jdfbgbhbibkdjbkbr ]bPbl M Qb0co CcI TbJ D x u ZbWb4bA zc5blbmb
81def scan_node_packages(node_modules: Path) -> ScanResult: 2{b~bac|bbccc}bdcec
82 """Scan Node.js packages in node_modules for bundled skills."""
83 result = ScanResult() 2d e 6b7b8b9b!ba ^b.b;bfcicjcg h #b$b%b'b(bb _b/b=bgckclcj k )b*b+b,b-bc `b:b?bhcmcnc
84 seen_skill_dirs: set[Path] = set() 2d e 6b7b8b9b!ba ^b.b;bfcicjcg h #b$b%b'b(bb _b/b=bgckclcj k )b*b+b,b-bc `b:b?bhcmcnc
86 if not node_modules.is_dir(): 2d e 6b7b8b9b!ba ^b.b;bfcicjcg h #b$b%b'b(bb _b/b=bgckclcj k )b*b+b,b-bc `b:b?bhcmcnc
87 result.warnings.append(f"node_modules directory not found: {node_modules}") 2fcPcQcgcRcSchcTcUc
88 return result 2fcPcQcgcRcSchcTcUc
90 for package_root in _iter_node_package_roots(node_modules): 2d e 6b7b8b9b!ba ^b.b;bfcicjcg h #b$b%b'b(bb _b/b=bgckclcj k )b*b+b,b-bc `b:b?bhcmcnc
91 package_info = _read_node_package_info(package_root) 2d e 6b7b8b9b!ba ^b.b;bfcicjcg h #b$b%b'b(bb _b/b=bgckclcj k )b*b+b,b-bc `b:b?bhcmcnc
92 if package_info is None: 2d e 6b7b8b9b!ba ^b.b;bfcicjcg h #b$b%b'b(bb _b/b=bgckclcj k )b*b+b,b-bc `b:b?bhcmcnc
93 result.warnings.append(f"Skipping invalid package metadata: {package_root}") 2fcPcQcgcRcSchcTcUc
94 continue 2fcPcQcgcRcSchcTcUc
96 for skill_md in sorted(package_root.glob(".agents/skills/*/SKILL.md")): 2d e 6b7b8b9b!ba ^b.b;bicjcg h #b$b%b'b(bb _b/b=bkclcj k )b*b+b,b-bc `b:b?bmcnc
97 skill_dir = skill_md.parent.resolve() 2d e 6b7b8b9b!ba ^b.b;bicjcg h #b$b%b'b(bb _b/b=bkclcj k )b*b+b,b-bc `b:b?bmcnc
98 if skill_dir in seen_skill_dirs: 2d e 6b7b8b9b!ba ^b.b;bicjcg h #b$b%b'b(bb _b/b=bkclcj k )b*b+b,b-bc `b:b?bmcnc
99 continue 2^b?d@d_b[d]d`b^d_d
100 seen_skill_dirs.add(skill_dir) 2d e 6b7b8b9b!ba ^b.b;bicjcg h #b$b%b'b(bb _b/b=bkclcj k )b*b+b,b-bc `b:b?bmcnc
102 skill, warning = _load_skill( 2d e 6b7b8b9b!ba ^b.b;bicjcg h #b$b%b'b(bb _b/b=bkclcj k )b*b+b,b-bc `b:b?bmcnc
103 skill_dir=skill_dir,
104 skill_md=skill_md.resolve(),
105 package_name=package_info.name,
106 package_version=package_info.version,
107 )
108 if skill: 2d e 6b7b8b9b!ba ^b.b;bicjcg h #b$b%b'b(bb _b/b=bkclcj k )b*b+b,b-bc `b:b?bmcnc
109 result.skills.append(skill) 2d e 6b7b8b9b!ba ^b.bicjcg h #b$b%b'b(bb _b/bkclcj k )b*b+b,b-bc `b:bmcnc
110 elif warning: 2;bvdwd=bxdyd?bzdAd
111 result.warnings.append(warning) 2;bvdwd=bxdyd?bzdAd
113 return result 2d e 6b7b8b9b!ba ^b.b;bfcicjcg h #b$b%b'b(bb _b/b=bgckclcj k )b*b+b,b-bc `b:b?bhcmcnc
116def _iter_node_package_roots(node_modules: Path) -> list[Path]: 2{b~bac|bbccc}bdcec
117 package_roots: list[Path] = [] 2d e 6b7b8b9b!ba .b;bfcicjcg h #b$b%b'b(bb /b=bgckclcj k )b*b+b,b-bc :b?bhcmcnc
118 for entry in sorted(node_modules.iterdir()): 2d e 6b7b8b9b!ba .b;bfcicjcg h #b$b%b'b(bb /b=bgckclcj k )b*b+b,b-bc :b?bhcmcnc
119 if not entry.is_dir(): 2d e 6b7b8b9b!ba .b;bfcicjcg h #b$b%b'b(bb /b=bgckclcj k )b*b+b,b-bc :b?bhcmcnc
120 continue 2fcPcQcgcRcSchcTcUc
121 if entry.name.startswith("."): 2d e 6b7b8b9b!ba .b;bfcicjcg h #b$b%b'b(bb /b=bgckclcj k )b*b+b,b-bc :b?bhcmcnc
122 continue 2fcPcQcgcRcSchcTcUc
123 if entry.name.startswith("@"): 2d e 6b7b8b9b!ba .b;bfcicjcg h #b$b%b'b(bb /b=bgckclcj k )b*b+b,b-bc :b?bhcmcnc
124 package_roots.extend( 2a .b`d{db /b|d}dc :b~dae
125 child for child in sorted(entry.iterdir()) if child.is_dir()
126 )
127 else:
128 package_roots.append(entry) 2d e 6b7b8b9b!ba ;bfcicjcg h #b$b%b'b(bb =bgckclcj k )b*b+b,b-bc ?bhcmcnc
129 return package_roots 2d e 6b7b8b9b!ba .b;bfcicjcg h #b$b%b'b(bb /b=bgckclcj k )b*b+b,b-bc :b?bhcmcnc
132@dataclass(frozen=True) 2{b~bac|bbccc}bdcec
133class _DistributionInfo: 2{b~bac|bbccc}bdcec
134 name: str 2{b|b}b
135 version: str 2{b|b}b
138_PackageInfo = _DistributionInfo 2{b~bac|bbccc}bdcec
141def _read_node_package_info(package_root: Path) -> _PackageInfo | None: 2{b~bac|bbccc}bdcec
142 package_json = package_root / "package.json" 2d e 6b7b8b9b!ba ^b.b;bfcicjcg h #b$b%b'b(bb _b/b=bgckclcj k )b*b+b,b-bc `b:b?bhcmcnc
143 try: 2d e 6b7b8b9b!ba ^b.b;bfcicjcg h #b$b%b'b(bb _b/b=bgckclcj k )b*b+b,b-bc `b:b?bhcmcnc
144 data = json.loads(package_json.read_text(encoding="utf-8")) 2d e 6b7b8b9b!ba ^b.b;bfcicjcg h #b$b%b'b(bb _b/b=bgckclcj k )b*b+b,b-bc `b:b?bhcmcnc
145 except (OSError, json.JSONDecodeError): 2fcPcQcgcRcSchcTcUc
146 return None 2fcPcQcgcRcSchcTcUc
148 if not isinstance(data, dict): 2d e 6b7b8b9b!ba ^b.b;bfcicjcg h #b$b%b'b(bb _b/b=bgckclcj k )b*b+b,b-bc `b:b?bhcmcnc
149 return None 2fcPcQcgcRcSchcTcUc
150 name = data.get("name") 2d e 6b7b8b9b!ba ^b.b;bfcicjcg h #b$b%b'b(bb _b/b=bgckclcj k )b*b+b,b-bc `b:b?bhcmcnc
151 if not isinstance(name, str) or not name: 2d e 6b7b8b9b!ba ^b.b;bfcicjcg h #b$b%b'b(bb _b/b=bgckclcj k )b*b+b,b-bc `b:b?bhcmcnc
152 return None 2fcPcQcgcRcSchcTcUc
153 version = data.get("version") 2d e 6b7b8b9b!ba ^b.b;bicjcg h #b$b%b'b(bb _b/b=bkclcj k )b*b+b,b-bc `b:b?bmcnc
154 return _PackageInfo(name=name, version=version if isinstance(version, str) else "") 2d e 6b7b8b9b!ba ^b.b;bicjcg h #b$b%b'b(bb _b/b=bkclcj k )b*b+b,b-bc `b:b?bmcnc
157def _read_distribution_info(dist_info: Path) -> _DistributionInfo | None: 2{b~bac|bbccc}bdcec
158 metadata_path = dist_info / "METADATA" 2N d e O P Q R nbobS T U pbqbV W X Y rbsbtbubZ a 0 1 2 3 4 5 p @bvbf K wbm AcE RbF B v s XbUb0by xc1b6 7 8 g h 9 ! # $ xbyb% ' ( zbAb) * + , BbCbDbEb- b . / : ; = ? q [bFbi L Gbn BcG SbH C w t YbVb2bz yc3b@ [ ] j k ^ _ ` { HbIb| } ~ JbKbabbbcbdbLbMbNbObebc fbgbhbibjbkbr ]bPbl M Qbo CcI TbJ D x u ZbWb4bA zc5blbmb
159 try: 2N d e O P Q R nbobS T U pbqbV W X Y rbsbtbubZ a 0 1 2 3 4 5 p @bvbf K wbm AcE RbF B v s XbUb0by xc1b6 7 8 g h 9 ! # $ xbyb% ' ( zbAb) * + , BbCbDbEb- b . / : ; = ? q [bFbi L Gbn BcG SbH C w t YbVb2bz yc3b@ [ ] j k ^ _ ` { HbIb| } ~ JbKbabbbcbdbLbMbNbObebc fbgbhbibjbkbr ]bPbl M Qbo CcI TbJ D x u ZbWb4bA zc5blbmb
160 metadata_text = metadata_path.read_text(encoding="utf-8") 2N d e O P Q R nbobS T U pbqbV W X Y rbsbtbubZ a 0 1 2 3 4 5 p @bvbf K wbm AcE RbF B v s XbUb0by xc1b6 7 8 g h 9 ! # $ xbyb% ' ( zbAb) * + , BbCbDbEb- b . / : ; = ? q [bFbi L Gbn BcG SbH C w t YbVb2bz yc3b@ [ ] j k ^ _ ` { HbIb| } ~ JbKbabbbcbdbLbMbNbObebc fbgbhbibjbkbr ]bPbl M Qbo CcI TbJ D x u ZbWb4bA zc5blbmb
161 except OSError: 2AcpdqdBcrdsdCctdud
162 return None 2AcpdqdBcrdsdCctdud
164 metadata = Parser().parsestr(metadata_text) 2N d e O P Q R nbobS T U pbqbV W X Y rbsbtbubZ a 0 1 2 3 4 5 p @bvbf K wbm E RbF B v s XbUb0by xc1b6 7 8 g h 9 ! # $ xbyb% ' ( zbAb) * + , BbCbDbEb- b . / : ; = ? q [bFbi L Gbn G SbH C w t YbVb2bz yc3b@ [ ] j k ^ _ ` { HbIb| } ~ JbKbabbbcbdbLbMbNbObebc fbgbhbibjbkbr ]bPbl M Qbo I TbJ D x u ZbWb4bA zc5blbmb
165 name = metadata.get("Name") 2N d e O P Q R nbobS T U pbqbV W X Y rbsbtbubZ a 0 1 2 3 4 5 p @bvbf K wbm E RbF B v s XbUb0by xc1b6 7 8 g h 9 ! # $ xbyb% ' ( zbAb) * + , BbCbDbEb- b . / : ; = ? q [bFbi L Gbn G SbH C w t YbVb2bz yc3b@ [ ] j k ^ _ ` { HbIb| } ~ JbKbabbbcbdbLbMbNbObebc fbgbhbibjbkbr ]bPbl M Qbo I TbJ D x u ZbWb4bA zc5blbmb
166 if not name: 2N d e O P Q R nbobS T U pbqbV W X Y rbsbtbubZ a 0 1 2 3 4 5 p @bvbf K wbm E RbF B v s XbUb0by xc1b6 7 8 g h 9 ! # $ xbyb% ' ( zbAb) * + , BbCbDbEb- b . / : ; = ? q [bFbi L Gbn G SbH C w t YbVb2bz yc3b@ [ ] j k ^ _ ` { HbIb| } ~ JbKbabbbcbdbLbMbNbObebc fbgbhbibjbkbr ]bPbl M Qbo I TbJ D x u ZbWb4bA zc5blbmb
167 return None 2xcbeceycdeeezcfege
168 return _DistributionInfo(name=name, version=metadata.get("Version", "")) 2N d e O P Q R nbobS T U pbqbV W X Y rbsbtbubZ a 0 1 2 3 4 5 p @bvbf K wbm E RbF B v s XbUb0by 1b6 7 8 g h 9 ! # $ xbyb% ' ( zbAb) * + , BbCbDbEb- b . / : ; = ? q [bFbi L Gbn G SbH C w t YbVb2bz 3b@ [ ] j k ^ _ ` { HbIb| } ~ JbKbabbbcbdbLbMbNbObebc fbgbhbibjbkbr ]bPbl M Qbo I TbJ D x u ZbWb4bA 5blbmb
171def _scan_distribution_records( 2{b~bac|bbccc}bdcec
172 *,
173 site_packages: Path,
174 dist_info: Path,
175 package_name: str,
176 package_version: str,
177 seen_skill_dirs: set[Path],
178) -> ScanResult:
179 result = ScanResult() 2N d e O P Q R nbobS T U pbqbV W X Y rbsbtbubZ a 0 1 2 3 4 5 p @bvbf K wbm E RbF B v s XbUb0by 1b6 7 8 g h 9 ! # $ xbyb% ' ( zbAb) * + , BbCbDbEb- b . / : ; = ? q [bFbi L Gbn G SbH C w t YbVb2bz 3b@ [ ] j k ^ _ ` { HbIb| } ~ JbKbabbbcbdbLbMbNbObebc fbgbhbibjbkbr ]bPbl M Qbo I TbJ D x u ZbWb4bA 5blbmb
180 record_path = dist_info / "RECORD" 2N d e O P Q R nbobS T U pbqbV W X Y rbsbtbubZ a 0 1 2 3 4 5 p @bvbf K wbm E RbF B v s XbUb0by 1b6 7 8 g h 9 ! # $ xbyb% ' ( zbAb) * + , BbCbDbEb- b . / : ; = ? q [bFbi L Gbn G SbH C w t YbVb2bz 3b@ [ ] j k ^ _ ` { HbIb| } ~ JbKbabbbcbdbLbMbNbObebc fbgbhbibjbkbr ]bPbl M Qbo I TbJ D x u ZbWb4bA 5blbmb
181 try: 2N d e O P Q R nbobS T U pbqbV W X Y rbsbtbubZ a 0 1 2 3 4 5 p @bvbf K wbm E RbF B v s XbUb0by 1b6 7 8 g h 9 ! # $ xbyb% ' ( zbAb) * + , BbCbDbEb- b . / : ; = ? q [bFbi L Gbn G SbH C w t YbVb2bz 3b@ [ ] j k ^ _ ` { HbIb| } ~ JbKbabbbcbdbLbMbNbObebc fbgbhbibjbkbr ]bPbl M Qbo I TbJ D x u ZbWb4bA 5blbmb
182 record_file = record_path.open(encoding="utf-8", newline="") 2N d e O P Q R nbobS T U pbqbV W X Y rbsbtbubZ a 0 1 2 3 4 5 p @bvbf K wbm E RbF B v s XbUb0by 1b6 7 8 g h 9 ! # $ xbyb% ' ( zbAb) * + , BbCbDbEb- b . / : ; = ? q [bFbi L Gbn G SbH C w t YbVb2bz 3b@ [ ] j k ^ _ ` { HbIb| } ~ JbKbabbbcbdbLbMbNbObebc fbgbhbibjbkbr ]bPbl M Qbo I TbJ D x u ZbWb4bA 5blbmb
183 except OSError: 2p @bf m ocpcq [bi n qcrcr ]bl o sctc
184 return result 2p @bf m ocpcq [bi n qcrcr ]bl o sctc
186 with record_file: 2N d e O P Q R nbobS T U pbqbV W X Y rbsbtbubZ a 0 1 2 3 4 5 vbf K wbE RbF B v s XbUb0by 1b6 7 8 g h 9 ! # $ xbyb% ' ( zbAb) * + , BbCbDbEb- b . / : ; = ? Fbi L GbG SbH C w t YbVb2bz 3b@ [ ] j k ^ _ ` { HbIb| } ~ JbKbabbbcbdbLbMbNbObebc fbgbhbibjbkbPbl M QbI TbJ D x u ZbWb4bA 5blbmb
187 for row in csv.reader(record_file): 2N d e O P Q R nbobS T U pbqbV W X Y rbsbtbubZ a 0 1 2 3 4 5 vbf K wbE RbF B v s XbUb0by 1b6 7 8 g h 9 ! # $ xbyb% ' ( zbAb) * + , BbCbDbEb- b . / : ; = ? Fbi L GbG SbH C w t YbVb2bz 3b@ [ ] j k ^ _ ` { HbIb| } ~ JbKbabbbcbdbLbMbNbObebc fbgbhbibjbkbPbl M QbI TbJ D x u ZbWb4bA 5blbmb
188 if not row: 2N d e O P Q R nbobS T U pbqbV W X Y rbsbtbubZ a 0 1 2 3 4 5 vbf K wbE RbF B v s XbUb0by 1b6 7 8 g h 9 ! # $ xbyb% ' ( zbAb) * + , BbCbDbEb- b . / : ; = ? Fbi L GbG SbH C w t YbVb2bz 3b@ [ ] j k ^ _ ` { HbIb| } ~ JbKbabbbcbdbLbMbNbObebc fbgbhbibjbkbPbl M QbI TbJ D x u ZbWb4bA 5blbmb
189 continue 2K BdCdL DdEdM FdGd
190 installed_path = row[0] 2N d e O P Q R nbobS T U pbqbV W X Y rbsbtbubZ a 0 1 2 3 4 5 vbf K wbE RbF B v s XbUb0by 1b6 7 8 g h 9 ! # $ xbyb% ' ( zbAb) * + , BbCbDbEb- b . / : ; = ? Fbi L GbG SbH C w t YbVb2bz 3b@ [ ] j k ^ _ ` { HbIb| } ~ JbKbabbbcbdbLbMbNbObebc fbgbhbibjbkbPbl M QbI TbJ D x u ZbWb4bA 5blbmb
191 if not _is_skill_file_record(installed_path): 2N d e O P Q R nbobS T U pbqbV W X Y rbsbtbubZ a 0 1 2 3 4 5 vbf K wbE RbF B v s XbUb0by 1b6 7 8 g h 9 ! # $ xbyb% ' ( zbAb) * + , BbCbDbEb- b . / : ; = ? Fbi L GbG SbH C w t YbVb2bz 3b@ [ ] j k ^ _ ` { HbIb| } ~ JbKbabbbcbdbLbMbNbObebc fbgbhbibjbkbPbl M QbI TbJ D x u ZbWb4bA 5blbmb
192 continue 2K BdCdL DdEdM FdGd
194 skill_md = (site_packages / installed_path).resolve() 2N d e O P Q R nbobS T U pbqbV W X Y rbsbtbubZ a 0 1 2 3 4 5 vbf K wbE RbF B v s XbUb0by 1b6 7 8 g h 9 ! # $ xbyb% ' ( zbAb) * + , BbCbDbEb- b . / : ; = ? Fbi L GbG SbH C w t YbVb2bz 3b@ [ ] j k ^ _ ` { HbIb| } ~ JbKbabbbcbdbLbMbNbObebc fbgbhbibjbkbPbl M QbI TbJ D x u ZbWb4bA 5blbmb
195 skill_dir = skill_md.parent 2N d e O P Q R nbobS T U pbqbV W X Y rbsbtbubZ a 0 1 2 3 4 5 vbf K wbE RbF B v s XbUb0by 1b6 7 8 g h 9 ! # $ xbyb% ' ( zbAb) * + , BbCbDbEb- b . / : ; = ? Fbi L GbG SbH C w t YbVb2bz 3b@ [ ] j k ^ _ ` { HbIb| } ~ JbKbabbbcbdbLbMbNbObebc fbgbhbibjbkbPbl M QbI TbJ D x u ZbWb4bA 5blbmb
196 if skill_dir in seen_skill_dirs: 2N d e O P Q R nbobS T U pbqbV W X Y rbsbtbubZ a 0 1 2 3 4 5 vbf K wbE RbF B v s XbUb0by 1b6 7 8 g h 9 ! # $ xbyb% ' ( zbAb) * + , BbCbDbEb- b . / : ; = ? Fbi L GbG SbH C w t YbVb2bz 3b@ [ ] j k ^ _ ` { HbIb| } ~ JbKbabbbcbdbLbMbNbObebc fbgbhbibjbkbPbl M QbI TbJ D x u ZbWb4bA 5blbmb
197 continue 2K BdCdL DdEdM FdGd
198 seen_skill_dirs.add(skill_dir) 2N d e O P Q R nbobS T U pbqbV W X Y rbsbtbubZ a 0 1 2 3 4 5 vbf K wbE RbF B v s XbUb0by 1b6 7 8 g h 9 ! # $ xbyb% ' ( zbAb) * + , BbCbDbEb- b . / : ; = ? Fbi L GbG SbH C w t YbVb2bz 3b@ [ ] j k ^ _ ` { HbIb| } ~ JbKbabbbcbdbLbMbNbObebc fbgbhbibjbkbPbl M QbI TbJ D x u ZbWb4bA 5blbmb
200 skill, warning = _load_skill( 2N d e O P Q R nbobS T U pbqbV W X Y rbsbtbubZ a 0 1 2 3 4 5 vbf K wbE RbF B v s XbUb0by 1b6 7 8 g h 9 ! # $ xbyb% ' ( zbAb) * + , BbCbDbEb- b . / : ; = ? Fbi L GbG SbH C w t YbVb2bz 3b@ [ ] j k ^ _ ` { HbIb| } ~ JbKbabbbcbdbLbMbNbObebc fbgbhbibjbkbPbl M QbI TbJ D x u ZbWb4bA 5blbmb
201 skill_dir=skill_dir,
202 skill_md=skill_md,
203 package_name=package_name,
204 package_version=package_version,
205 )
206 if skill: 2N d e O P Q R nbobS T U pbqbV W X Y rbsbtbubZ a 0 1 2 3 4 5 vbf K wbE RbF B v s XbUb0by 1b6 7 8 g h 9 ! # $ xbyb% ' ( zbAb) * + , BbCbDbEb- b . / : ; = ? Fbi L GbG SbH C w t YbVb2bz 3b@ [ ] j k ^ _ ` { HbIb| } ~ JbKbabbbcbdbLbMbNbObebc fbgbhbibjbkbPbl M QbI TbJ D x u ZbWb4bA 5blbmb
207 result.skills.append(skill) 2N d e O P Q R nbobS T U pbqbV W X Y rbsbtbubZ a 0 1 2 3 4 5 vbf K wb6 7 8 g h 9 ! # $ xbyb% ' ( zbAb) * + , BbCbDbEb- b . / : ; = ? Fbi L Gb@ [ ] j k ^ _ ` { HbIb| } ~ JbKbabbbcbdbLbMbNbObebc fbgbhbibjbkbPbl M Qblbmb
208 elif warning: 2E RbF B v s XbUb0by 1bDcEcG SbH C w t YbVb2bz 3bFcGcI TbJ D x u ZbWb4bA 5bHcIc
209 result.warnings.append(warning) 2E RbF B v s XbUb0by 1bDcEcG SbH C w t YbVb2bz 3bFcGcI TbJ D x u ZbWb4bA 5bHcIc
211 return result 2N d e O P Q R nbobS T U pbqbV W X Y rbsbtbubZ a 0 1 2 3 4 5 vbf K wbE RbF B v s XbUb0by 1b6 7 8 g h 9 ! # $ xbyb% ' ( zbAb) * + , BbCbDbEb- b . / : ; = ? Fbi L GbG SbH C w t YbVb2bz 3b@ [ ] j k ^ _ ` { HbIb| } ~ JbKbabbbcbdbLbMbNbObebc fbgbhbibjbkbPbl M QbI TbJ D x u ZbWb4bA 5blbmb
214def _is_skill_file_record(installed_path: str) -> bool: 2{b~bac|bbccc}bdcec
215 parts = PurePosixPath(installed_path).parts 2N d e O P Q R nbobS T U pbqbV W X Y rbsbtbubZ a 0 1 2 3 4 5 vbf K wbE RbF B v s XbUb0by 1b6 7 8 g h 9 ! # $ xbyb% ' ( zbAb) * + , BbCbDbEb- b . / : ; = ? Fbi L GbG SbH C w t YbVb2bz 3b@ [ ] j k ^ _ ` { HbIb| } ~ JbKbabbbcbdbLbMbNbObebc fbgbhbibjbkbPbl M QbI TbJ D x u ZbWb4bA 5blbmb
216 for index, part in enumerate(parts): 2N d e O P Q R nbobS T U pbqbV W X Y rbsbtbubZ a 0 1 2 3 4 5 vbf K wbE RbF B v s XbUb0by 1b6 7 8 g h 9 ! # $ xbyb% ' ( zbAb) * + , BbCbDbEb- b . / : ; = ? Fbi L GbG SbH C w t YbVb2bz 3b@ [ ] j k ^ _ ` { HbIb| } ~ JbKbabbbcbdbLbMbNbObebc fbgbhbibjbkbPbl M QbI TbJ D x u ZbWb4bA 5blbmb
217 if part != ".agents": 2N d e O P Q R nbobS T U pbqbV W X Y rbsbtbubZ a 0 1 2 3 4 5 vbf K wbE RbF B v s XbUb0by 1b6 7 8 g h 9 ! # $ xbyb% ' ( zbAb) * + , BbCbDbEb- b . / : ; = ? Fbi L GbG SbH C w t YbVb2bz 3b@ [ ] j k ^ _ ` { HbIb| } ~ JbKbabbbcbdbLbMbNbObebc fbgbhbibjbkbPbl M QbI TbJ D x u ZbWb4bA 5blbmb
218 continue 2N d e O P Q R nbobS T U pbqbV W X Y rbsbtbubZ a 0 1 2 3 4 5 vbf K wbE RbF B v s XbUb0by 1b6 7 8 g h 9 ! # $ xbyb% ' ( zbAb) * + , BbCbDbEb- b . / : ; = ? Fbi L GbG SbH C w t YbVb2bz 3b@ [ ] j k ^ _ ` { HbIb| } ~ JbKbabbbcbdbLbMbNbObebc fbgbhbibjbkbPbl M QbI TbJ D x u ZbWb4bA 5blbmb
219 if (
220 len(parts) > index + 3
221 and parts[index + 1] == "skills"
222 and parts[-1] == "SKILL.md"
223 ):
224 return True 2N d e O P Q R nbobS T U pbqbV W X Y rbsbtbubZ a 0 1 2 3 4 5 vbf K wbE RbF B v s XbUb0by 1b6 7 8 g h 9 ! # $ xbyb% ' ( zbAb) * + , BbCbDbEb- b . / : ; = ? Fbi L GbG SbH C w t YbVb2bz 3b@ [ ] j k ^ _ ` { HbIb| } ~ JbKbabbbcbdbLbMbNbObebc fbgbhbibjbkbPbl M QbI TbJ D x u ZbWb4bA 5blbmb
225 return False 2K BdCdL DdEdM FdGd
228def _scan_editable_direct_url( 2{b~bac|bbccc}bdcec
229 *,
230 dist_info: Path,
231 site_packages: Path | None = None,
232 package_name: str,
233 package_version: str,
234 seen_skill_dirs: set[Path],
235) -> ScanResult:
236 result = ScanResult() 2p uc@bf m E RbF B v s XbUb0by 1bDcEcq vc[bi n G SbH C w t YbVb2bz 3bFcGcr wc]bl o I TbJ D x u ZbWb4bA 5bHcIc
238 source_root = _read_editable_source_root(dist_info) 2p uc@bf m E RbF B v s XbUb0by 1bDcEcq vc[bi n G SbH C w t YbVb2bz 3bFcGcr wc]bl o I TbJ D x u ZbWb4bA 5bHcIc
239 if source_root is None: 2p uc@bf m E RbF B v s XbUb0by 1bDcEcq vc[bi n G SbH C w t YbVb2bz 3bFcGcr wc]bl o I TbJ D x u ZbWb4bA 5bHcIc
240 return result 2E RbF B v s XbUb0by 1bDcEcG SbH C w t YbVb2bz 3bFcGcI TbJ D x u ZbWb4bA 5bHcIc
242 for skill_md in sorted(source_root.rglob(".agents/skills/*/SKILL.md")): 2p uc@bf m ocpcq vc[bi n qcrcr wc]bl o sctc
243 resolved_skill_md = skill_md.resolve() 2p uc@bf m ocpcq vc[bi n qcrcr wc]bl o sctc
244 if not _is_relative_to(resolved_skill_md, source_root): 2p uc@bf m ocpcq vc[bi n qcrcr wc]bl o sctc
245 continue 2@bheie[bjeke]bleme
246 if site_packages is not None and _is_relative_to( 2p ucf m ocpcq vci n qcrcr wcl o sctc
247 resolved_skill_md, site_packages
248 ):
249 continue 2f neoei peqel rese
251 skill_dir = resolved_skill_md.parent 2p ucm ocpcq vcn qcrcr wco sctc
252 if skill_dir in seen_skill_dirs: 2p ucm ocpcq vcn qcrcr wco sctc
253 continue 2ucteuevcvewewcxeye
254 seen_skill_dirs.add(skill_dir) 2p m ocpcq n qcrcr o sctc
256 skill, warning = _load_skill( 2p m ocpcq n qcrcr o sctc
257 skill_dir=skill_dir,
258 skill_md=resolved_skill_md,
259 package_name=package_name,
260 package_version=package_version,
261 )
262 if skill: 2p m ocpcq n qcrcr o sctc
263 result.skills.append(skill) 2m ocpcn qcrco sctc
264 elif warning: 2p 2d3dq 4d5dr 6d7d
265 result.warnings.append(warning) 2p 2d3dq 4d5dr 6d7d
267 return result 2p uc@bf m ocpcq vc[bi n qcrcr wc]bl o sctc
270def _read_editable_source_root(dist_info: Path) -> Path | None: 2{b~bac|bbccc}bdcec
271 direct_url_path = dist_info / "direct_url.json" 2md1cVcMcJcp uc@bf m E RbF B v s XbUb0by 1bDcEcnd2cWcNcKcq vc[bi n G SbH C w t YbVb2bz 3bFcGcod3cXcOcLcr wc]bl o I TbJ D x u ZbWb4bA 5bHcIc
272 try: 2md1cVcMcJcp uc@bf m E RbF B v s XbUb0by 1bDcEcnd2cWcNcKcq vc[bi n G SbH C w t YbVb2bz 3bFcGcod3cXcOcLcr wc]bl o I TbJ D x u ZbWb4bA 5bHcIc
273 data = json.loads(direct_url_path.read_text(encoding="utf-8")) 2md1cVcMcJcp uc@bf m E RbF B v s XbUb0by 1bDcEcnd2cWcNcKcq vc[bi n G SbH C w t YbVb2bz 3bFcGcod3cXcOcLcr wc]bl o I TbJ D x u ZbWb4bA 5bHcIc
274 except (OSError, json.JSONDecodeError): 2E RbF B v s XbUb0by 1bDcEcG SbH C w t YbVb2bz 3bFcGcI TbJ D x u ZbWb4bA 5bHcIc
275 return None 2E RbF B v s XbUb0by 1bDcEcG SbH C w t YbVb2bz 3bFcGcI TbJ D x u ZbWb4bA 5bHcIc
277 if not isinstance(data, dict): 2md1cVcMcJcp uc@bf m ocpcnd2cWcNcKcq vc[bi n qcrcod3cXcOcLcr wc]bl o sctc
278 return None 2mdzeAendBeCeodDeEe
280 dir_info = data.get("dir_info") 21cVcMcJcp uc@bf m ocpc2cWcNcKcq vc[bi n qcrc3cXcOcLcr wc]bl o sctc
281 if not isinstance(dir_info, dict) or dir_info.get("editable") is not True: 21cVcMcJcp uc@bf m ocpc2cWcNcKcq vc[bi n qcrc3cXcOcLcr wc]bl o sctc
282 return None 21cHdId2cJdKd3cLdMd
284 url = data.get("url") 2VcMcJcp uc@bf m ocpcWcNcKcq vc[bi n qcrcXcOcLcr wc]bl o sctc
285 if not isinstance(url, str): 2VcMcJcp uc@bf m ocpcWcNcKcq vc[bi n qcrcXcOcLcr wc]bl o sctc
286 return None 2VcHdIdWcJdKdXcLdMd
288 parsed = urlparse(url) 2McJcp uc@bf m ocpcNcKcq vc[bi n qcrcOcLcr wc]bl o sctc
289 if parsed.scheme != "file": 2McJcp uc@bf m ocpcNcKcq vc[bi n qcrcOcLcr wc]bl o sctc
290 return None 2McHdIdNcJdKdOcLdMd
292 # Windows file URLs use /C:/... paths; url2pathname converts them to C:\...
293 source_root = Path(url2pathname(parsed.path)).resolve() 2Jcp uc@bf m ocpcKcq vc[bi n qcrcLcr wc]bl o sctc
294 if source_root.is_dir(): 2Jcp uc@bf m ocpcKcq vc[bi n qcrcLcr wc]bl o sctc
295 return source_root 2p uc@bf m ocpcq vc[bi n qcrcr wc]bl o sctc
296 return None 2JcHdIdKcJdKdLcLdMd
299def _is_relative_to(path: Path, parent: Path) -> bool: 2{b~bac|bbccc}bdcec
300 try: 2Ndp uc@bf m ocpcOdq vc[bi n qcrcPdr wc]bl o sctc
301 path.relative_to(parent.resolve()) 2Ndp uc@bf m ocpcOdq vc[bi n qcrcPdr wc]bl o sctc
302 except ValueError: 2Ndp @bm ocpcOdq [bn qcrcPdr ]bo sctc
303 return False 2Ndp @bm ocpcOdq [bn qcrcPdr ]bo sctc
304 return True 2p ucf m ocpcq vci n qcrcr wcl o sctc
307def _load_skill( 2{b~bac|bbccc}bdcec
308 *,
309 skill_dir: Path,
310 skill_md: Path,
311 package_name: str,
312 package_version: str,
313) -> tuple[Skill | None, str | None]:
314 metadata, warning = _parse_skill_frontmatter(skill_md) 2N d e O P Q R nbobS T U pbqbV W X Y 6b7b8b9b!brbsbtbubZ a 0 1 2 3 4 5 p ^b.b;bvbf K wbm E RbF B v s XbUb0by 1b6 7 8 g h 9 ! # $ xbyb% ' ( zbAb) * + , #b$b%b'b(bBbCbDbEb- b . / : ; = ? q _b/b=bFbi L Gbn G SbH C w t YbVb2bz 3b@ [ ] j k ^ _ ` { HbIb| } ~ JbKbabbbcbdb)b*b+b,b-bLbMbNbObebc fbgbhbibjbkbr `b:b?bPbl M Qbo I TbJ D x u ZbWb4bA 5blbmb
315 if warning: 2N d e O P Q R nbobS T U pbqbV W X Y 6b7b8b9b!brbsbtbubZ a 0 1 2 3 4 5 p ^b.b;bvbf K wbm E RbF B v s XbUb0by 1b6 7 8 g h 9 ! # $ xbyb% ' ( zbAb) * + , #b$b%b'b(bBbCbDbEb- b . / : ; = ? q _b/b=bFbi L Gbn G SbH C w t YbVb2bz 3b@ [ ] j k ^ _ ` { HbIb| } ~ JbKbabbbcbdb)b*b+b,b-bLbMbNbObebc fbgbhbibjbkbr `b:b?bPbl M Qbo I TbJ D x u ZbWb4bA 5blbmb
316 return None, f"{skill_md}: {warning}" 2RbXbUb0b1b8d9dSbYbVb2b3b!d#dTbZbWb4b5b$d%d
318 name = metadata.get("name", "") 2N d e O P Q R nbobS T U pbqbV W X Y 6b7b8b9b!brbsbtbubZ a 0 1 2 3 4 5 p ^b.b;bvbf K wbm E F B v s y 6 7 8 g h 9 ! # $ xbyb% ' ( zbAb) * + , #b$b%b'b(bBbCbDbEb- b . / : ; = ? q _b/b=bFbi L Gbn G H C w t z @ [ ] j k ^ _ ` { HbIb| } ~ JbKbabbbcbdb)b*b+b,b-bLbMbNbObebc fbgbhbibjbkbr `b:b?bPbl M Qbo I J D x u A lbmb
319 description = metadata.get("description", "") 2N d e O P Q R nbobS T U pbqbV W X Y 6b7b8b9b!brbsbtbubZ a 0 1 2 3 4 5 p ^b.b;bvbf K wbm E F B v s y 6 7 8 g h 9 ! # $ xbyb% ' ( zbAb) * + , #b$b%b'b(bBbCbDbEb- b . / : ; = ? q _b/b=bFbi L Gbn G H C w t z @ [ ] j k ^ _ ` { HbIb| } ~ JbKbabbbcbdb)b*b+b,b-bLbMbNbObebc fbgbhbibjbkbr `b:b?bPbl M Qbo I J D x u A lbmb
320 validation_error = _validate_skill_metadata( 2N d e O P Q R nbobS T U pbqbV W X Y 6b7b8b9b!brbsbtbubZ a 0 1 2 3 4 5 p ^b.b;bvbf K wbm E F B v s y 6 7 8 g h 9 ! # $ xbyb% ' ( zbAb) * + , #b$b%b'b(bBbCbDbEb- b . / : ; = ? q _b/b=bFbi L Gbn G H C w t z @ [ ] j k ^ _ ` { HbIb| } ~ JbKbabbbcbdb)b*b+b,b-bLbMbNbObebc fbgbhbibjbkbr `b:b?bPbl M Qbo I J D x u A lbmb
321 name=name,
322 description=description,
323 parent_dir_name=skill_dir.name,
324 )
325 if validation_error: 2N d e O P Q R nbobS T U pbqbV W X Y 6b7b8b9b!brbsbtbubZ a 0 1 2 3 4 5 p ^b.b;bvbf K wbm E F B v s y 6 7 8 g h 9 ! # $ xbyb% ' ( zbAb) * + , #b$b%b'b(bBbCbDbEb- b . / : ; = ? q _b/b=bFbi L Gbn G H C w t z @ [ ] j k ^ _ ` { HbIb| } ~ JbKbabbbcbdb)b*b+b,b-bLbMbNbObebc fbgbhbibjbkbr `b:b?bPbl M Qbo I J D x u A lbmb
326 return None, f"{skill_md}: {validation_error}" 2p ;bE F B v s y vdwdq =bG H C w t z xdydr ?bI J D x u A zdAd
328 return ( 2N d e O P Q R nbobS T U pbqbV W X Y 6b7b8b9b!brbsbtbubZ a 0 1 2 3 4 5 ^b.bvbf K wbm 6 7 8 g h 9 ! # $ xbyb% ' ( zbAb) * + , #b$b%b'b(bBbCbDbEb- b . / : ; = ? _b/bFbi L Gbn @ [ ] j k ^ _ ` { HbIb| } ~ JbKbabbbcbdb)b*b+b,b-bLbMbNbObebc fbgbhbibjbkb`b:bPbl M Qbo lbmb
329 Skill(
330 name=name,
331 description=description,
332 path=skill_md,
333 package_name=package_name,
334 package_version=package_version,
335 skill_dir=skill_dir,
336 ),
337 None,
338 )
341def _parse_skill_frontmatter(skill_md: Path) -> tuple[dict[str, str], str | None]: 2{b~bac|bbccc}bdcec
342 try: 2N d e O P Q R nbobS T U pbqbV W X Y 6b7b8b9b!brbsbtbubZ a 0 1 2 3 4 5 p ^b.b;bvbf K wbm E RbF B v s XbUb0by 1b6 7 8 g h 9 ! # $ xbyb% ' ( zbAb) * + , #b$b%b'b(bBbCbDbEb- b . / : ; = ? q _b/b=bFbi L Gbn G SbH C w t YbVb2bz 3b@ [ ] j k ^ _ ` { HbIb| } ~ JbKbabbbcbdb)b*b+b,b-bLbMbNbObebc fbgbhbibjbkbr `b:b?bPbl M Qbo I TbJ D x u ZbWb4bA 5blbmb
343 text = skill_md.read_text(encoding="utf-8") 2N d e O P Q R nbobS T U pbqbV W X Y 6b7b8b9b!brbsbtbubZ a 0 1 2 3 4 5 p ^b.b;bvbf K wbm E RbF B v s XbUb0by 1b6 7 8 g h 9 ! # $ xbyb% ' ( zbAb) * + , #b$b%b'b(bBbCbDbEb- b . / : ; = ? q _b/b=bFbi L Gbn G SbH C w t YbVb2bz 3b@ [ ] j k ^ _ ` { HbIb| } ~ JbKbabbbcbdb)b*b+b,b-bLbMbNbObebc fbgbhbibjbkbr `b:b?bPbl M Qbo I TbJ D x u ZbWb4bA 5blbmb
344 except (OSError, UnicodeDecodeError) as e: 21bQdRd3bSdTd5bUdVd
345 return {}, f"could not read SKILL.md ({e})" 21bQdRd3bSdTd5bUdVd
347 if not text.startswith("---"): 2N d e O P Q R nbobS T U pbqbV W X Y 6b7b8b9b!brbsbtbubZ a 0 1 2 3 4 5 p ^b.b;bvbf K wbm E RbF B v s XbUb0by 6 7 8 g h 9 ! # $ xbyb% ' ( zbAb) * + , #b$b%b'b(bBbCbDbEb- b . / : ; = ? q _b/b=bFbi L Gbn G SbH C w t YbVb2bz @ [ ] j k ^ _ ` { HbIb| } ~ JbKbabbbcbdb)b*b+b,b-bLbMbNbObebc fbgbhbibjbkbr `b:b?bPbl M Qbo I TbJ D x u ZbWb4bA lbmb
348 return {}, "missing YAML frontmatter" 20b8d9d2b!d#d4b$d%d
350 end = text.find("\n---", 3) 2N d e O P Q R nbobS T U pbqbV W X Y 6b7b8b9b!brbsbtbubZ a 0 1 2 3 4 5 p ^b.b;bvbf K wbm E RbF B v s XbUby 6 7 8 g h 9 ! # $ xbyb% ' ( zbAb) * + , #b$b%b'b(bBbCbDbEb- b . / : ; = ? q _b/b=bFbi L Gbn G SbH C w t YbVbz @ [ ] j k ^ _ ` { HbIb| } ~ JbKbabbbcbdb)b*b+b,b-bLbMbNbObebc fbgbhbibjbkbr `b:b?bPbl M Qbo I TbJ D x u ZbWbA lbmb
351 if end == -1: 2N d e O P Q R nbobS T U pbqbV W X Y 6b7b8b9b!brbsbtbubZ a 0 1 2 3 4 5 p ^b.b;bvbf K wbm E RbF B v s XbUby 6 7 8 g h 9 ! # $ xbyb% ' ( zbAb) * + , #b$b%b'b(bBbCbDbEb- b . / : ; = ? q _b/b=bFbi L Gbn G SbH C w t YbVbz @ [ ] j k ^ _ ` { HbIb| } ~ JbKbabbbcbdb)b*b+b,b-bLbMbNbObebc fbgbhbibjbkbr `b:b?bPbl M Qbo I TbJ D x u ZbWbA lbmb
352 return {}, "unterminated YAML frontmatter" 2Xb4c5cYb6c7cZb8c9c
354 frontmatter = text[3:end] 2N d e O P Q R nbobS T U pbqbV W X Y 6b7b8b9b!brbsbtbubZ a 0 1 2 3 4 5 p ^b.b;bvbf K wbm E RbF B v s Uby 6 7 8 g h 9 ! # $ xbyb% ' ( zbAb) * + , #b$b%b'b(bBbCbDbEb- b . / : ; = ? q _b/b=bFbi L Gbn G SbH C w t Vbz @ [ ] j k ^ _ ` { HbIb| } ~ JbKbabbbcbdb)b*b+b,b-bLbMbNbObebc fbgbhbibjbkbr `b:b?bPbl M Qbo I TbJ D x u WbA lbmb
355 try: 2N d e O P Q R nbobS T U pbqbV W X Y 6b7b8b9b!brbsbtbubZ a 0 1 2 3 4 5 p ^b.b;bvbf K wbm E RbF B v s Uby 6 7 8 g h 9 ! # $ xbyb% ' ( zbAb) * + , #b$b%b'b(bBbCbDbEb- b . / : ; = ? q _b/b=bFbi L Gbn G SbH C w t Vbz @ [ ] j k ^ _ ` { HbIb| } ~ JbKbabbbcbdb)b*b+b,b-bLbMbNbObebc fbgbhbibjbkbr `b:b?bPbl M Qbo I TbJ D x u WbA lbmb
356 parsed = yaml.safe_load(frontmatter) 2N d e O P Q R nbobS T U pbqbV W X Y 6b7b8b9b!brbsbtbubZ a 0 1 2 3 4 5 p ^b.b;bvbf K wbm E RbF B v s Uby 6 7 8 g h 9 ! # $ xbyb% ' ( zbAb) * + , #b$b%b'b(bBbCbDbEb- b . / : ; = ? q _b/b=bFbi L Gbn G SbH C w t Vbz @ [ ] j k ^ _ ` { HbIb| } ~ JbKbabbbcbdb)b*b+b,b-bLbMbNbObebc fbgbhbibjbkbr `b:b?bPbl M Qbo I TbJ D x u WbA lbmb
357 except yaml.YAMLError as e: 2Ub4c5cVb6c7cWb8c9c
358 return {}, f"invalid YAML frontmatter ({e})" 2Ub4c5cVb6c7cWb8c9c
360 if parsed is None: 2N d e O P Q R nbobS T U pbqbV W X Y 6b7b8b9b!brbsbtbubZ a 0 1 2 3 4 5 p ^b.b;bvbf K wbm E RbF B v s y 6 7 8 g h 9 ! # $ xbyb% ' ( zbAb) * + , #b$b%b'b(bBbCbDbEb- b . / : ; = ? q _b/b=bFbi L Gbn G SbH C w t z @ [ ] j k ^ _ ` { HbIb| } ~ JbKbabbbcbdb)b*b+b,b-bLbMbNbObebc fbgbhbibjbkbr `b:b?bPbl M Qbo I TbJ D x u A lbmb
361 parsed = {} 2E 'd(dG )d*dI +d,d
363 if not isinstance(parsed, dict): 2N d e O P Q R nbobS T U pbqbV W X Y 6b7b8b9b!brbsbtbubZ a 0 1 2 3 4 5 p ^b.b;bvbf K wbm E RbF B v s y 6 7 8 g h 9 ! # $ xbyb% ' ( zbAb) * + , #b$b%b'b(bBbCbDbEb- b . / : ; = ? q _b/b=bFbi L Gbn G SbH C w t z @ [ ] j k ^ _ ` { HbIb| } ~ JbKbabbbcbdb)b*b+b,b-bLbMbNbObebc fbgbhbibjbkbr `b:b?bPbl M Qbo I TbJ D x u A lbmb
364 return {}, "YAML frontmatter must be a mapping" 2Rb'd(dSb)d*dTb+d,d
366 metadata: dict[str, str] = {} 2N d e O P Q R nbobS T U pbqbV W X Y 6b7b8b9b!brbsbtbubZ a 0 1 2 3 4 5 p ^b.b;bvbf K wbm E F B v s y 6 7 8 g h 9 ! # $ xbyb% ' ( zbAb) * + , #b$b%b'b(bBbCbDbEb- b . / : ; = ? q _b/b=bFbi L Gbn G H C w t z @ [ ] j k ^ _ ` { HbIb| } ~ JbKbabbbcbdb)b*b+b,b-bLbMbNbObebc fbgbhbibjbkbr `b:b?bPbl M Qbo I J D x u A lbmb
367 for key in ("name", "description"): 2N d e O P Q R nbobS T U pbqbV W X Y 6b7b8b9b!brbsbtbubZ a 0 1 2 3 4 5 p ^b.b;bvbf K wbm E F B v s y 6 7 8 g h 9 ! # $ xbyb% ' ( zbAb) * + , #b$b%b'b(bBbCbDbEb- b . / : ; = ? q _b/b=bFbi L Gbn G H C w t z @ [ ] j k ^ _ ` { HbIb| } ~ JbKbabbbcbdb)b*b+b,b-bLbMbNbObebc fbgbhbibjbkbr `b:b?bPbl M Qbo I J D x u A lbmb
368 value = parsed.get(key) 2N d e O P Q R nbobS T U pbqbV W X Y 6b7b8b9b!brbsbtbubZ a 0 1 2 3 4 5 p ^b.b;bvbf K wbm E F B v s y 6 7 8 g h 9 ! # $ xbyb% ' ( zbAb) * + , #b$b%b'b(bBbCbDbEb- b . / : ; = ? q _b/b=bFbi L Gbn G H C w t z @ [ ] j k ^ _ ` { HbIb| } ~ JbKbabbbcbdb)b*b+b,b-bLbMbNbObebc fbgbhbibjbkbr `b:b?bPbl M Qbo I J D x u A lbmb
369 if isinstance(value, str): 2N d e O P Q R nbobS T U pbqbV W X Y 6b7b8b9b!brbsbtbubZ a 0 1 2 3 4 5 p ^b.b;bvbf K wbm E F B v s y 6 7 8 g h 9 ! # $ xbyb% ' ( zbAb) * + , #b$b%b'b(bBbCbDbEb- b . / : ; = ? q _b/b=bFbi L Gbn G H C w t z @ [ ] j k ^ _ ` { HbIb| } ~ JbKbabbbcbdb)b*b+b,b-bLbMbNbObebc fbgbhbibjbkbr `b:b?bPbl M Qbo I J D x u A lbmb
370 metadata[key] = value.strip() 2N d e O P Q R nbobS T U pbqbV W X Y 6b7b8b9b!brbsbtbubZ a 0 1 2 3 4 5 p ^b.b;bvbf K wbm F B v s y 6 7 8 g h 9 ! # $ xbyb% ' ( zbAb) * + , #b$b%b'b(bBbCbDbEb- b . / : ; = ? q _b/b=bFbi L Gbn H C w t z @ [ ] j k ^ _ ` { HbIb| } ~ JbKbabbbcbdb)b*b+b,b-bLbMbNbObebc fbgbhbibjbkbr `b:b?bPbl M Qbo J D x u A lbmb
372 return metadata, None 2N d e O P Q R nbobS T U pbqbV W X Y 6b7b8b9b!brbsbtbubZ a 0 1 2 3 4 5 p ^b.b;bvbf K wbm E F B v s y 6 7 8 g h 9 ! # $ xbyb% ' ( zbAb) * + , #b$b%b'b(bBbCbDbEb- b . / : ; = ? q _b/b=bFbi L Gbn G H C w t z @ [ ] j k ^ _ ` { HbIb| } ~ JbKbabbbcbdb)b*b+b,b-bLbMbNbObebc fbgbhbibjbkbr `b:b?bPbl M Qbo I J D x u A lbmb
375def _validate_skill_metadata( 2{b~bac|bbccc}bdcec
376 *,
377 name: str,
378 description: str,
379 parent_dir_name: str,
380) -> str | None:
381 if not name: 2N d e O P Q R nbobS T U pbqbV W X Y 6b7b8b9b!brbsbtbubZ a 0 1 2 3 4 5 p ^b.b;bvbf K wbm E F B v s y 6 7 8 g h 9 ! # $ xbyb% ' ( zbAb) * + , #b$b%b'b(bBbCbDbEb- b . / : ; = ? q _b/b=bFbi L Gbn G H C w t z @ [ ] j k ^ _ ` { HbIb| } ~ JbKbabbbcbdb)b*b+b,b-bLbMbNbObebc fbgbhbibjbkbr `b:b?bPbl M Qbo I J D x u A lbmb
382 return "missing required 'name' field" 2E F 4c5cG H 6c7cI J 8c9c
383 if not _SKILL_NAME_RE.fullmatch(name) or "--" in name: 2N d e O P Q R nbobS T U pbqbV W X Y 6b7b8b9b!brbsbtbubZ a 0 1 2 3 4 5 p ^b.b;bvbf K wbm B v s y 6 7 8 g h 9 ! # $ xbyb% ' ( zbAb) * + , #b$b%b'b(bBbCbDbEb- b . / : ; = ? q _b/b=bFbi L Gbn C w t z @ [ ] j k ^ _ ` { HbIb| } ~ JbKbabbbcbdb)b*b+b,b-bLbMbNbObebc fbgbhbibjbkbr `b:b?bPbl M Qbo D x u A lbmb
384 return "invalid 'name' field; use lowercase letters, numbers, and hyphens only" 2B 4c5cC 6c7cD 8c9c
385 if name != parent_dir_name: 2N d e O P Q R nbobS T U pbqbV W X Y 6b7b8b9b!brbsbtbubZ a 0 1 2 3 4 5 p ^b.b;bvbf K wbm v s y 6 7 8 g h 9 ! # $ xbyb% ' ( zbAb) * + , #b$b%b'b(bBbCbDbEb- b . / : ; = ? q _b/b=bFbi L Gbn w t z @ [ ] j k ^ _ ` { HbIb| } ~ JbKbabbbcbdb)b*b+b,b-bLbMbNbObebc fbgbhbibjbkbr `b:b?bPbl M Qbo x u A lbmb
386 return f"'name' field must match parent directory name ({parent_dir_name})" 2p ;by vdwdq =bz xdydr ?bA zdAd
387 if not description: 2N d e O P Q R nbobS T U pbqbV W X Y 6b7b8b9b!brbsbtbubZ a 0 1 2 3 4 5 ^b.bvbf K wbm v s 6 7 8 g h 9 ! # $ xbyb% ' ( zbAb) * + , #b$b%b'b(bBbCbDbEb- b . / : ; = ? _b/bFbi L Gbn w t @ [ ] j k ^ _ ` { HbIb| } ~ JbKbabbbcbdb)b*b+b,b-bLbMbNbObebc fbgbhbibjbkb`b:bPbl M Qbo x u lbmb
388 return "missing required 'description' field" 2v 4c5cw 6c7cx 8c9c
389 if len(description) > 1024: 2N d e O P Q R nbobS T U pbqbV W X Y 6b7b8b9b!brbsbtbubZ a 0 1 2 3 4 5 ^b.bvbf K wbm s 6 7 8 g h 9 ! # $ xbyb% ' ( zbAb) * + , #b$b%b'b(bBbCbDbEb- b . / : ; = ? _b/bFbi L Gbn t @ [ ] j k ^ _ ` { HbIb| } ~ JbKbabbbcbdb)b*b+b,b-bLbMbNbObebc fbgbhbibjbkb`b:bPbl M Qbo u lbmb
390 return "'description' field must be at most 1024 characters" 2s QdRdt SdTdu UdVd
391 return None 2N d e O P Q R nbobS T U pbqbV W X Y 6b7b8b9b!brbsbtbubZ a 0 1 2 3 4 5 ^b.bvbf K wbm 6 7 8 g h 9 ! # $ xbyb% ' ( zbAb) * + , #b$b%b'b(bBbCbDbEb- b . / : ; = ? _b/bFbi L Gbn @ [ ] j k ^ _ ` { HbIb| } ~ JbKbabbbcbdb)b*b+b,b-bLbMbNbObebc fbgbhbibjbkb`b:bPbl M Qbo lbmb
394def _normalize_package_name(name: str) -> str: 2{b~bac|bbccc}bdcec
395 """Normalize a Python package name (PEP 503)."""
396 return re.sub(r"[-_.]+", "-", name).lower() 2N d e O P Q R S T U V W X Y 6b7b8b9b!bZ a 0 1 2 3 4 5 FeGeHeIeJe6 7 8 g h 9 ! # $ % ' ( ) * + , #b$b%b'b(b- b . / : ; = ? KeLeMeNeOe@ [ ] j k ^ _ ` { | } ~ abbbcbdb)b*b+b,b-bebc fbgbhbibjbkbPeQeReSeTelbmb