mirror of
https://github.com/zhayujie/chatgpt-on-wechat.git
synced 2026-02-19 09:07:02 +08:00
173 lines
5.2 KiB
Python
173 lines
5.2 KiB
Python
"""
|
|
Frontmatter parsing for skills.
|
|
"""
|
|
|
|
import re
|
|
import json
|
|
from typing import Dict, Any, Optional, List
|
|
from agent.skills.types import SkillMetadata, SkillInstallSpec
|
|
|
|
|
|
def parse_frontmatter(content: str) -> Dict[str, Any]:
|
|
"""
|
|
Parse YAML-style frontmatter from markdown content.
|
|
|
|
Returns a dictionary of frontmatter fields.
|
|
"""
|
|
frontmatter = {}
|
|
|
|
# Match frontmatter block between --- markers
|
|
match = re.match(r'^---\s*\n(.*?)\n---\s*\n', content, re.DOTALL)
|
|
if not match:
|
|
return frontmatter
|
|
|
|
frontmatter_text = match.group(1)
|
|
|
|
# Try to use PyYAML for proper YAML parsing
|
|
try:
|
|
import yaml
|
|
frontmatter = yaml.safe_load(frontmatter_text)
|
|
if not isinstance(frontmatter, dict):
|
|
frontmatter = {}
|
|
return frontmatter
|
|
except ImportError:
|
|
# Fallback to simple parsing if PyYAML not available
|
|
pass
|
|
except Exception:
|
|
# If YAML parsing fails, fall back to simple parsing
|
|
pass
|
|
|
|
# Simple YAML-like parsing (supports key: value format only)
|
|
# This is a fallback for when PyYAML is not available
|
|
for line in frontmatter_text.split('\n'):
|
|
line = line.strip()
|
|
if not line or line.startswith('#'):
|
|
continue
|
|
|
|
if ':' in line:
|
|
key, value = line.split(':', 1)
|
|
key = key.strip()
|
|
value = value.strip()
|
|
|
|
# Try to parse as JSON if it looks like JSON
|
|
if value.startswith('{') or value.startswith('['):
|
|
try:
|
|
value = json.loads(value)
|
|
except json.JSONDecodeError:
|
|
pass
|
|
# Parse boolean values
|
|
elif value.lower() in ('true', 'false'):
|
|
value = value.lower() == 'true'
|
|
# Parse numbers
|
|
elif value.isdigit():
|
|
value = int(value)
|
|
|
|
frontmatter[key] = value
|
|
|
|
return frontmatter
|
|
|
|
|
|
def parse_metadata(frontmatter: Dict[str, Any]) -> Optional[SkillMetadata]:
|
|
"""
|
|
Parse skill metadata from frontmatter.
|
|
|
|
Looks for 'metadata' field containing JSON with skill configuration.
|
|
"""
|
|
metadata_raw = frontmatter.get('metadata')
|
|
if not metadata_raw:
|
|
return None
|
|
|
|
# If it's a string, try to parse as JSON
|
|
if isinstance(metadata_raw, str):
|
|
try:
|
|
metadata_raw = json.loads(metadata_raw)
|
|
except json.JSONDecodeError:
|
|
return None
|
|
|
|
if not isinstance(metadata_raw, dict):
|
|
return None
|
|
|
|
# Use metadata_raw directly (COW format)
|
|
meta_obj = metadata_raw
|
|
|
|
# Parse install specs
|
|
install_specs = []
|
|
install_raw = meta_obj.get('install', [])
|
|
if isinstance(install_raw, list):
|
|
for spec_raw in install_raw:
|
|
if not isinstance(spec_raw, dict):
|
|
continue
|
|
|
|
kind = spec_raw.get('kind', spec_raw.get('type', '')).lower()
|
|
if not kind:
|
|
continue
|
|
|
|
spec = SkillInstallSpec(
|
|
kind=kind,
|
|
id=spec_raw.get('id'),
|
|
label=spec_raw.get('label'),
|
|
bins=_normalize_string_list(spec_raw.get('bins')),
|
|
os=_normalize_string_list(spec_raw.get('os')),
|
|
formula=spec_raw.get('formula'),
|
|
package=spec_raw.get('package'),
|
|
module=spec_raw.get('module'),
|
|
url=spec_raw.get('url'),
|
|
archive=spec_raw.get('archive'),
|
|
extract=spec_raw.get('extract', False),
|
|
strip_components=spec_raw.get('stripComponents'),
|
|
target_dir=spec_raw.get('targetDir'),
|
|
)
|
|
install_specs.append(spec)
|
|
|
|
# Parse requires
|
|
requires = {}
|
|
requires_raw = meta_obj.get('requires', {})
|
|
if isinstance(requires_raw, dict):
|
|
for key, value in requires_raw.items():
|
|
requires[key] = _normalize_string_list(value)
|
|
|
|
return SkillMetadata(
|
|
always=meta_obj.get('always', False),
|
|
skill_key=meta_obj.get('skillKey'),
|
|
primary_env=meta_obj.get('primaryEnv'),
|
|
emoji=meta_obj.get('emoji'),
|
|
homepage=meta_obj.get('homepage'),
|
|
os=_normalize_string_list(meta_obj.get('os')),
|
|
requires=requires,
|
|
install=install_specs,
|
|
)
|
|
|
|
|
|
def _normalize_string_list(value: Any) -> List[str]:
|
|
"""Normalize a value to a list of strings."""
|
|
if not value:
|
|
return []
|
|
|
|
if isinstance(value, list):
|
|
return [str(v).strip() for v in value if v]
|
|
|
|
if isinstance(value, str):
|
|
return [v.strip() for v in value.split(',') if v.strip()]
|
|
|
|
return []
|
|
|
|
|
|
def parse_boolean_value(value: Optional[str], default: bool = False) -> bool:
|
|
"""Parse a boolean value from frontmatter."""
|
|
if value is None:
|
|
return default
|
|
|
|
if isinstance(value, bool):
|
|
return value
|
|
|
|
if isinstance(value, str):
|
|
return value.lower() in ('true', '1', 'yes', 'on')
|
|
|
|
return default
|
|
|
|
|
|
def get_frontmatter_value(frontmatter: Dict[str, Any], key: str) -> Optional[str]:
|
|
"""Get a frontmatter value as a string."""
|
|
value = frontmatter.get(key)
|
|
return str(value) if value is not None else None
|