feat: Add tool categories for organization
- Add category field to Tool dataclass (Text, Developer, Data, Other) - Display tools grouped by category in main UI with section headers - Add category dropdown selector in tool builder dialog - Update example tools with appropriate categories - Document category feature in README Categories help organize tools in the UI for easier navigation. Tools without a category default to "Other". 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
cff3b674ac
commit
541d49b7f9
12
README.md
12
README.md
|
|
@ -204,6 +204,7 @@ A tool is a YAML config file in `~/.smarttools/<name>/config.yaml`:
|
|||
```yaml
|
||||
name: summarize
|
||||
description: Condense documents to key points
|
||||
category: Text # Optional: Text, Developer, Data, or Other
|
||||
arguments:
|
||||
- flag: --length
|
||||
variable: length
|
||||
|
|
@ -219,6 +220,17 @@ steps:
|
|||
output: "{response}"
|
||||
```
|
||||
|
||||
### Categories
|
||||
|
||||
Tools can be organized into categories for easier navigation in the UI:
|
||||
|
||||
| Category | Description |
|
||||
|----------|-------------|
|
||||
| `Text` | Text processing (summarize, translate, grammar) |
|
||||
| `Developer` | Dev tools (explain-error, gen-tests, commit-msg) |
|
||||
| `Data` | Data extraction and conversion (json-extract, csv) |
|
||||
| `Other` | Default for uncategorized tools |
|
||||
|
||||
### Multi-Step Tools
|
||||
|
||||
Chain AI prompts with Python code for powerful workflows:
|
||||
|
|
|
|||
|
|
@ -100,11 +100,16 @@ class CodeStep:
|
|||
Step = PromptStep | CodeStep
|
||||
|
||||
|
||||
# Default categories for organizing tools
|
||||
DEFAULT_CATEGORIES = ["Text", "Developer", "Data", "Other"]
|
||||
|
||||
|
||||
@dataclass
|
||||
class Tool:
|
||||
"""A SmartTools tool definition."""
|
||||
name: str
|
||||
description: str = ""
|
||||
category: str = "Other" # Tool category for organization
|
||||
arguments: List[ToolArgument] = field(default_factory=list)
|
||||
steps: List[Step] = field(default_factory=list)
|
||||
output: str = "{input}" # Output template
|
||||
|
|
@ -125,19 +130,24 @@ class Tool:
|
|||
return cls(
|
||||
name=data["name"],
|
||||
description=data.get("description", ""),
|
||||
category=data.get("category", "Other"),
|
||||
arguments=arguments,
|
||||
steps=steps,
|
||||
output=data.get("output", "{input}")
|
||||
)
|
||||
|
||||
def to_dict(self) -> dict:
|
||||
return {
|
||||
d = {
|
||||
"name": self.name,
|
||||
"description": self.description,
|
||||
"arguments": [arg.to_dict() for arg in self.arguments],
|
||||
"steps": [step.to_dict() for step in self.steps],
|
||||
"output": self.output
|
||||
}
|
||||
# Only include category if it's not the default
|
||||
if self.category and self.category != "Other":
|
||||
d["category"] = self.category
|
||||
d["arguments"] = [arg.to_dict() for arg in self.arguments]
|
||||
d["steps"] = [step.to_dict() for step in self.steps]
|
||||
d["output"] = self.output
|
||||
return d
|
||||
|
||||
def get_available_variables(self) -> List[str]:
|
||||
"""Get all variables available for use in templates."""
|
||||
|
|
|
|||
|
|
@ -863,12 +863,39 @@ class SmartToolsUI:
|
|||
|
||||
def _refresh_main_menu(self):
|
||||
"""Refresh the main menu display."""
|
||||
from collections import defaultdict
|
||||
from .tool import DEFAULT_CATEGORIES
|
||||
|
||||
tools = list_tools()
|
||||
self._tools_list = tools
|
||||
|
||||
# Tool list - arrows navigate within, doesn't pass focus out
|
||||
tool_items = []
|
||||
# Group tools by category
|
||||
tools_by_category = defaultdict(list)
|
||||
for name in tools:
|
||||
tool = load_tool(name)
|
||||
category = tool.category if tool else "Other"
|
||||
tools_by_category[category].append(name)
|
||||
|
||||
# Build tool list with category headers
|
||||
tool_items = []
|
||||
|
||||
# Show categories in defined order, then any custom ones
|
||||
all_categories = list(DEFAULT_CATEGORIES)
|
||||
for cat in tools_by_category:
|
||||
if cat not in all_categories:
|
||||
all_categories.append(cat)
|
||||
|
||||
for category in all_categories:
|
||||
if category in tools_by_category and tools_by_category[category]:
|
||||
# Category header (non-selectable)
|
||||
header = urwid.AttrMap(
|
||||
urwid.Text(f"─── {category} ───"),
|
||||
'label'
|
||||
)
|
||||
tool_items.append(header)
|
||||
|
||||
# Tools in this category
|
||||
for name in sorted(tools_by_category[category]):
|
||||
item = SelectableToolItem(name, on_select=self._on_tool_select)
|
||||
tool_items.append(item)
|
||||
|
||||
|
|
@ -1089,6 +1116,8 @@ class SmartToolsUI:
|
|||
|
||||
def _show_tool_builder(self):
|
||||
"""Render the tool builder screen."""
|
||||
from .tool import DEFAULT_CATEGORIES
|
||||
|
||||
tool = self._current_tool
|
||||
|
||||
# Create edit widgets
|
||||
|
|
@ -1101,12 +1130,50 @@ class SmartToolsUI:
|
|||
self._desc_edit = urwid.AttrMap(urwid.Edit(('label', "Desc: "), tool.description), 'edit', 'edit_focus')
|
||||
self._output_edit = urwid.AttrMap(urwid.Edit(('label', "Output: "), tool.output), 'edit', 'edit_focus')
|
||||
|
||||
# Category selector
|
||||
self._selected_category = [tool.category or "Other"]
|
||||
category_btn_text = urwid.Text(self._selected_category[0])
|
||||
category_btn = urwid.AttrMap(
|
||||
urwid.Padding(category_btn_text, left=1, right=1),
|
||||
'edit', 'edit_focus'
|
||||
)
|
||||
|
||||
def show_category_dropdown(_):
|
||||
"""Show category selection popup."""
|
||||
def select_category(cat):
|
||||
def callback(_):
|
||||
self._selected_category[0] = cat
|
||||
category_btn_text.set_text(cat)
|
||||
tool.category = cat
|
||||
self.close_overlay()
|
||||
return callback
|
||||
|
||||
items = []
|
||||
for cat in DEFAULT_CATEGORIES:
|
||||
btn = urwid.Button(cat, on_press=select_category(cat))
|
||||
items.append(urwid.AttrMap(btn, 'button', 'button_focus'))
|
||||
|
||||
listbox = urwid.ListBox(urwid.SimpleFocusListWalker(items))
|
||||
popup = Dialog("Select Category", listbox, [])
|
||||
self.show_overlay(popup, width=30, height=len(DEFAULT_CATEGORIES) + 4)
|
||||
|
||||
category_select_btn = Button3DCompact("▼", on_press=show_category_dropdown)
|
||||
|
||||
category_row = urwid.Columns([
|
||||
('pack', urwid.Text(('label', "Category: "))),
|
||||
('weight', 1, category_btn),
|
||||
('pack', urwid.Text(" ")),
|
||||
('pack', category_select_btn),
|
||||
])
|
||||
|
||||
# Left column - fields
|
||||
left_pile = urwid.Pile([
|
||||
('pack', name_widget),
|
||||
('pack', urwid.Divider()),
|
||||
('pack', self._desc_edit),
|
||||
('pack', urwid.Divider()),
|
||||
('pack', category_row),
|
||||
('pack', urwid.Divider()),
|
||||
('pack', self._output_edit),
|
||||
])
|
||||
left_box = urwid.LineBox(urwid.Filler(left_pile, valign='top'), title='Tool Info')
|
||||
|
|
|
|||
Loading…
Reference in New Issue