feat: implement tweaks plan - modals, smart shell, spinner, /models, debug log, skills
Phase 1: Permission modal dialog, session resume modal, HistoryInput with up/down arrow cycling, remove "You:" echo from chat log, LLM client cleanup on unmount. Phase 2: Smart shell auto-approve using allowed/denied command lists from ToolsConfig, animated thinking spinner with live token count in status bar. Phase 3: /models slash command (list + switch), CLI directory positional argument, JSONL debug logger with rotation. Phase 4: Skills system with SkillsManager, load_skill LLM tool, /skills listing, skill invocation via slash commands, system prompt integration. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
60
app/tools/skills.py
Normal file
60
app/tools/skills.py
Normal file
@@ -0,0 +1,60 @@
|
||||
"""Load skill tool — allows the LLM to load skill instructions on demand."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
from typing import Any, ClassVar
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from app.models.config import AppConfig
|
||||
from app.models.tool_call import ToolResult, ToolResultStatus
|
||||
from app.services.skills import SkillsManager
|
||||
from app.tools.base import BaseTool
|
||||
|
||||
|
||||
class LoadSkillParams(BaseModel):
|
||||
"""Parameters for the load_skill tool."""
|
||||
|
||||
name: str = Field(description="Name of the skill to load")
|
||||
|
||||
|
||||
class LoadSkillTool(BaseTool):
|
||||
"""Load a skill's full instructions by name.
|
||||
|
||||
Use when a skill is relevant to the current task.
|
||||
"""
|
||||
|
||||
name: ClassVar[str] = "load_skill"
|
||||
description: ClassVar[str] = (
|
||||
"Load a skill's full instructions by name. "
|
||||
"Use when a skill is relevant to the current task."
|
||||
)
|
||||
params_model: ClassVar[type[BaseModel]] = LoadSkillParams
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
workspace_root: Path,
|
||||
config: AppConfig,
|
||||
skills_manager: SkillsManager,
|
||||
) -> None:
|
||||
super().__init__(workspace_root, config)
|
||||
self._skills = skills_manager
|
||||
|
||||
def execute(self, *, tool_call_id: str, **kwargs: Any) -> ToolResult:
|
||||
skill_name: str = kwargs["name"]
|
||||
content = self._skills.load_skill(skill_name)
|
||||
if content is None:
|
||||
available = [s.name for s in self._skills.list_skills()]
|
||||
return ToolResult(
|
||||
tool_call_id=tool_call_id,
|
||||
tool_name=self.name,
|
||||
status=ToolResultStatus.ERROR,
|
||||
error=f"Unknown skill '{skill_name}'. Available: {available}",
|
||||
)
|
||||
return ToolResult(
|
||||
tool_call_id=tool_call_id,
|
||||
tool_name=self.name,
|
||||
status=ToolResultStatus.SUCCESS,
|
||||
output=content,
|
||||
)
|
||||
Reference in New Issue
Block a user