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
|
```yaml
|
||||||
name: summarize
|
name: summarize
|
||||||
description: Condense documents to key points
|
description: Condense documents to key points
|
||||||
|
category: Text # Optional: Text, Developer, Data, or Other
|
||||||
arguments:
|
arguments:
|
||||||
- flag: --length
|
- flag: --length
|
||||||
variable: length
|
variable: length
|
||||||
|
|
@ -219,6 +220,17 @@ steps:
|
||||||
output: "{response}"
|
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
|
### Multi-Step Tools
|
||||||
|
|
||||||
Chain AI prompts with Python code for powerful workflows:
|
Chain AI prompts with Python code for powerful workflows:
|
||||||
|
|
|
||||||
|
|
@ -100,11 +100,16 @@ class CodeStep:
|
||||||
Step = PromptStep | CodeStep
|
Step = PromptStep | CodeStep
|
||||||
|
|
||||||
|
|
||||||
|
# Default categories for organizing tools
|
||||||
|
DEFAULT_CATEGORIES = ["Text", "Developer", "Data", "Other"]
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class Tool:
|
class Tool:
|
||||||
"""A SmartTools tool definition."""
|
"""A SmartTools tool definition."""
|
||||||
name: str
|
name: str
|
||||||
description: str = ""
|
description: str = ""
|
||||||
|
category: str = "Other" # Tool category for organization
|
||||||
arguments: List[ToolArgument] = field(default_factory=list)
|
arguments: List[ToolArgument] = field(default_factory=list)
|
||||||
steps: List[Step] = field(default_factory=list)
|
steps: List[Step] = field(default_factory=list)
|
||||||
output: str = "{input}" # Output template
|
output: str = "{input}" # Output template
|
||||||
|
|
@ -125,19 +130,24 @@ class Tool:
|
||||||
return cls(
|
return cls(
|
||||||
name=data["name"],
|
name=data["name"],
|
||||||
description=data.get("description", ""),
|
description=data.get("description", ""),
|
||||||
|
category=data.get("category", "Other"),
|
||||||
arguments=arguments,
|
arguments=arguments,
|
||||||
steps=steps,
|
steps=steps,
|
||||||
output=data.get("output", "{input}")
|
output=data.get("output", "{input}")
|
||||||
)
|
)
|
||||||
|
|
||||||
def to_dict(self) -> dict:
|
def to_dict(self) -> dict:
|
||||||
return {
|
d = {
|
||||||
"name": self.name,
|
"name": self.name,
|
||||||
"description": self.description,
|
"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]:
|
def get_available_variables(self) -> List[str]:
|
||||||
"""Get all variables available for use in templates."""
|
"""Get all variables available for use in templates."""
|
||||||
|
|
|
||||||
|
|
@ -863,14 +863,41 @@ class SmartToolsUI:
|
||||||
|
|
||||||
def _refresh_main_menu(self):
|
def _refresh_main_menu(self):
|
||||||
"""Refresh the main menu display."""
|
"""Refresh the main menu display."""
|
||||||
|
from collections import defaultdict
|
||||||
|
from .tool import DEFAULT_CATEGORIES
|
||||||
|
|
||||||
tools = list_tools()
|
tools = list_tools()
|
||||||
self._tools_list = tools
|
self._tools_list = tools
|
||||||
|
|
||||||
# Tool list - arrows navigate within, doesn't pass focus out
|
# Group tools by category
|
||||||
tool_items = []
|
tools_by_category = defaultdict(list)
|
||||||
for name in tools:
|
for name in tools:
|
||||||
item = SelectableToolItem(name, on_select=self._on_tool_select)
|
tool = load_tool(name)
|
||||||
tool_items.append(item)
|
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)
|
||||||
|
|
||||||
if not tools:
|
if not tools:
|
||||||
tool_items.append(urwid.Text(('label', " (no tools - click Create to add one) ")))
|
tool_items.append(urwid.Text(('label', " (no tools - click Create to add one) ")))
|
||||||
|
|
@ -1089,6 +1116,8 @@ class SmartToolsUI:
|
||||||
|
|
||||||
def _show_tool_builder(self):
|
def _show_tool_builder(self):
|
||||||
"""Render the tool builder screen."""
|
"""Render the tool builder screen."""
|
||||||
|
from .tool import DEFAULT_CATEGORIES
|
||||||
|
|
||||||
tool = self._current_tool
|
tool = self._current_tool
|
||||||
|
|
||||||
# Create edit widgets
|
# Create edit widgets
|
||||||
|
|
@ -1101,12 +1130,50 @@ class SmartToolsUI:
|
||||||
self._desc_edit = urwid.AttrMap(urwid.Edit(('label', "Desc: "), tool.description), 'edit', 'edit_focus')
|
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')
|
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 column - fields
|
||||||
left_pile = urwid.Pile([
|
left_pile = urwid.Pile([
|
||||||
('pack', name_widget),
|
('pack', name_widget),
|
||||||
('pack', urwid.Divider()),
|
('pack', urwid.Divider()),
|
||||||
('pack', self._desc_edit),
|
('pack', self._desc_edit),
|
||||||
('pack', urwid.Divider()),
|
('pack', urwid.Divider()),
|
||||||
|
('pack', category_row),
|
||||||
|
('pack', urwid.Divider()),
|
||||||
('pack', self._output_edit),
|
('pack', self._output_edit),
|
||||||
])
|
])
|
||||||
left_box = urwid.LineBox(urwid.Filler(left_pile, valign='top'), title='Tool Info')
|
left_box = urwid.LineBox(urwid.Filler(left_pile, valign='top'), title='Tool Info')
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue