mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-04-25 00:51:20 +00:00
Add new skills descriptions and enhance skills tool functionality
- Added detailed descriptions for new skills categories: Machine Learning Operations and Note Taking. - Introduced a new Obsidian skill with commands for reading, listing, searching, creating, and appending notes. - Enhanced the skills tool to load and display category descriptions from DESCRIPTION.md files, improving user guidance and discovery of available skills.
This commit is contained in:
parent
32254d3010
commit
8f5f99c22a
5 changed files with 149 additions and 7 deletions
20
TODO.md
20
TODO.md
|
|
@ -276,6 +276,26 @@ These items need to be addressed ASAP:
|
|||
|
||||
### Medium-Impact
|
||||
|
||||
- [ ] **Canvas / Visual Workspace** 🖼️
|
||||
- Agent-controlled visual panel for rendering interactive UI
|
||||
- Inspired by OpenClaw's Canvas feature
|
||||
- **Capabilities:**
|
||||
- `present` / `hide` - Show/hide the canvas panel
|
||||
- `navigate` - Load HTML files or URLs into the canvas
|
||||
- `eval` - Execute JavaScript in the canvas context
|
||||
- `snapshot` - Capture the rendered UI as an image
|
||||
- **Use cases:**
|
||||
- Display generated HTML/CSS/JS previews
|
||||
- Show interactive data visualizations (charts, graphs)
|
||||
- Render diagrams (Mermaid → rendered output)
|
||||
- Present structured information in rich format
|
||||
- A2UI-style component system for structured agent UI
|
||||
- **Implementation options:**
|
||||
- Electron-based panel for CLI
|
||||
- WebSocket-connected web app
|
||||
- VS Code webview extension
|
||||
- *Would let agent "show" things rather than just describe them*
|
||||
|
||||
- [ ] **Document Generation** 📄
|
||||
- Create styled PDFs, Word docs, presentations
|
||||
- *Can do basic PDF via terminal tools, but limited*
|
||||
|
|
|
|||
3
skills/mlops/DESCRIPTION.md
Normal file
3
skills/mlops/DESCRIPTION.md
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
---
|
||||
description: Knowledge and Tools for Machine Learning Operations - tools and frameworks for training, fine-tuning, deploying, and optimizing ML/AI models
|
||||
---
|
||||
3
skills/note-taking/DESCRIPTION.md
Normal file
3
skills/note-taking/DESCRIPTION.md
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
---
|
||||
description: Note taking skills, to save information, assist with research, and collab on multi-session planning and information sharing.
|
||||
---
|
||||
57
skills/note-taking/obsidian/SKILL.md
Normal file
57
skills/note-taking/obsidian/SKILL.md
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
---
|
||||
name: obsidian
|
||||
description: Read, search, and create notes in the Obsidian vault.
|
||||
---
|
||||
|
||||
# Obsidian Vault
|
||||
|
||||
**Location:** `/home/teknium/Documents/Primary Vault`
|
||||
|
||||
Note: Path contains a space - always quote it.
|
||||
|
||||
## Read a note
|
||||
|
||||
```bash
|
||||
cat "/home/teknium/Documents/Primary Vault/Note Name.md"
|
||||
```
|
||||
|
||||
## List notes
|
||||
|
||||
```bash
|
||||
# All notes
|
||||
find "/home/teknium/Documents/Primary Vault" -name "*.md" -type f
|
||||
|
||||
# In a specific folder
|
||||
ls "/home/teknium/Documents/Primary Vault/AI Research/"
|
||||
```
|
||||
|
||||
## Search
|
||||
|
||||
```bash
|
||||
# By filename
|
||||
find "/home/teknium/Documents/Primary Vault" -name "*.md" -iname "*keyword*"
|
||||
|
||||
# By content
|
||||
grep -rli "keyword" "/home/teknium/Documents/Primary Vault" --include="*.md"
|
||||
```
|
||||
|
||||
## Create a note
|
||||
|
||||
```bash
|
||||
cat > "/home/teknium/Documents/Primary Vault/New Note.md" << 'ENDNOTE'
|
||||
# Title
|
||||
|
||||
Content here.
|
||||
ENDNOTE
|
||||
```
|
||||
|
||||
## Append to a note
|
||||
|
||||
```bash
|
||||
echo "
|
||||
New content here." >> "/home/teknium/Documents/Primary Vault/Existing Note.md"
|
||||
```
|
||||
|
||||
## Wikilinks
|
||||
|
||||
Obsidian links notes with `[[Note Name]]` syntax. When creating notes, use these to link related content.
|
||||
|
|
@ -312,17 +312,56 @@ def _find_all_skills() -> List[Dict[str, Any]]:
|
|||
return skills
|
||||
|
||||
|
||||
def _load_category_description(category_dir: Path) -> Optional[str]:
|
||||
"""
|
||||
Load category description from DESCRIPTION.md if it exists.
|
||||
|
||||
Args:
|
||||
category_dir: Path to the category directory
|
||||
|
||||
Returns:
|
||||
Description string or None if not found
|
||||
"""
|
||||
desc_file = category_dir / "DESCRIPTION.md"
|
||||
if not desc_file.exists():
|
||||
return None
|
||||
|
||||
try:
|
||||
content = desc_file.read_text(encoding='utf-8')
|
||||
# Parse frontmatter if present
|
||||
frontmatter, body = _parse_frontmatter(content)
|
||||
|
||||
# Prefer frontmatter description, fall back to first non-header line
|
||||
description = frontmatter.get('description', '')
|
||||
if not description:
|
||||
for line in body.strip().split('\n'):
|
||||
line = line.strip()
|
||||
if line and not line.startswith('#'):
|
||||
description = line
|
||||
break
|
||||
|
||||
# Truncate to reasonable length
|
||||
if len(description) > MAX_DESCRIPTION_LENGTH:
|
||||
description = description[:MAX_DESCRIPTION_LENGTH - 3] + "..."
|
||||
|
||||
return description if description else None
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
|
||||
def skills_categories(task_id: str = None) -> str:
|
||||
"""
|
||||
List available skill categories (progressive disclosure tier 0).
|
||||
List available skill categories with descriptions (progressive disclosure tier 0).
|
||||
|
||||
Returns just category names for efficient discovery before filtering.
|
||||
Returns category names and descriptions for efficient discovery before drilling down.
|
||||
Categories can have a DESCRIPTION.md file with a description frontmatter field
|
||||
or first paragraph to explain what skills are in that category.
|
||||
|
||||
Args:
|
||||
task_id: Optional task identifier (unused, for API consistency)
|
||||
|
||||
Returns:
|
||||
JSON string with list of category names
|
||||
JSON string with list of categories and their descriptions
|
||||
"""
|
||||
try:
|
||||
if not SKILLS_DIR.exists():
|
||||
|
|
@ -333,16 +372,36 @@ def skills_categories(task_id: str = None) -> str:
|
|||
}, ensure_ascii=False)
|
||||
|
||||
# Scan for categories (top-level directories containing skills)
|
||||
categories = set()
|
||||
category_dirs = {}
|
||||
for skill_md in SKILLS_DIR.rglob("SKILL.md"):
|
||||
category = _get_category_from_path(skill_md)
|
||||
if category:
|
||||
categories.add(category)
|
||||
category_dir = SKILLS_DIR / category
|
||||
if category not in category_dirs:
|
||||
category_dirs[category] = category_dir
|
||||
|
||||
# Build category list with descriptions
|
||||
categories = []
|
||||
for name in sorted(category_dirs.keys()):
|
||||
category_dir = category_dirs[name]
|
||||
description = _load_category_description(category_dir)
|
||||
|
||||
# Count skills in this category
|
||||
skill_count = sum(1 for _ in category_dir.rglob("SKILL.md"))
|
||||
|
||||
cat_entry = {
|
||||
"name": name,
|
||||
"skill_count": skill_count
|
||||
}
|
||||
if description:
|
||||
cat_entry["description"] = description
|
||||
|
||||
categories.append(cat_entry)
|
||||
|
||||
return json.dumps({
|
||||
"success": True,
|
||||
"categories": sorted(categories),
|
||||
"hint": "Use skills_list(category) to see skills in a category"
|
||||
"categories": categories,
|
||||
"hint": "If a category is relevant to your task, use skills_list with that category to see available skills"
|
||||
}, ensure_ascii=False)
|
||||
|
||||
except Exception as e:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue