diff --git a/TODO.md b/TODO.md index 6d60ac2241..89cf8ee42b 100644 --- a/TODO.md +++ b/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* diff --git a/skills/mlops/DESCRIPTION.md b/skills/mlops/DESCRIPTION.md new file mode 100644 index 0000000000..a5c3cf8ee9 --- /dev/null +++ b/skills/mlops/DESCRIPTION.md @@ -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 +--- diff --git a/skills/note-taking/DESCRIPTION.md b/skills/note-taking/DESCRIPTION.md new file mode 100644 index 0000000000..6b828df1a9 --- /dev/null +++ b/skills/note-taking/DESCRIPTION.md @@ -0,0 +1,3 @@ +--- +description: Note taking skills, to save information, assist with research, and collab on multi-session planning and information sharing. +--- diff --git a/skills/note-taking/obsidian/SKILL.md b/skills/note-taking/obsidian/SKILL.md new file mode 100644 index 0000000000..7f5258bc50 --- /dev/null +++ b/skills/note-taking/obsidian/SKILL.md @@ -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. diff --git a/tools/skills_tool.py b/tools/skills_tool.py index eb90902041..258284ad0b 100644 --- a/tools/skills_tool.py +++ b/tools/skills_tool.py @@ -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: