From b0e8bb3e6578d9da50160969d0ee4b9eab8456f0 Mon Sep 17 00:00:00 2001 From: rob Date: Tue, 27 Jan 2026 00:25:27 -0400 Subject: [PATCH] Add Dependencies section to Tool Builder GUI - Add Dependencies group box with dropdown to select installed tools - Dropdown is editable for typing tool references (e.g., official/summarize) - Add/Remove buttons to manage dependency list - Dependencies are loaded when editing existing tools - Dependencies are saved with the tool This allows users to declare dependencies for tools that call other tools via subprocess in code steps, making them visible to the dependency system. Co-Authored-By: Claude Opus 4.5 --- src/cmdforge/gui/pages/tool_builder_page.py | 111 +++++++++++++++++++- 1 file changed, 110 insertions(+), 1 deletion(-) diff --git a/src/cmdforge/gui/pages/tool_builder_page.py b/src/cmdforge/gui/pages/tool_builder_page.py index 145ec9c..02f1e04 100644 --- a/src/cmdforge/gui/pages/tool_builder_page.py +++ b/src/cmdforge/gui/pages/tool_builder_page.py @@ -133,6 +133,47 @@ class ToolBuilderPage(QWidget): left_layout.addWidget(args_box) + # Dependencies group + deps_box = QGroupBox() + deps_layout = QVBoxLayout(deps_box) + + deps_label = QLabel("Dependencies") + deps_label.setObjectName("sectionHeading") + deps_label.setToolTip( + "

Dependencies are other CmdForge tools this tool requires.

" + "

If your code steps call other tools via subprocess,
" + "declare them here so the dependency system knows about them.

" + "

Use this when you can't use a Tool step directly
" + "(e.g., calling tools in a loop or with complex logic).

" + ) + deps_layout.addWidget(deps_label) + + self.deps_list = QListWidget() + self.deps_list.setMaximumHeight(100) + deps_layout.addWidget(self.deps_list) + + # Dependency add row: combo + add button + deps_add_row = QHBoxLayout() + self.deps_combo = QComboBox() + self.deps_combo.setEditable(True) + self.deps_combo.setPlaceholderText("Select or type tool name...") + self.deps_combo.setToolTip("Select an installed tool or type a tool reference (e.g., official/summarize)") + self._populate_deps_combo() + deps_add_row.addWidget(self.deps_combo, 1) + + self.btn_add_dep = QPushButton("Add") + self.btn_add_dep.clicked.connect(self._add_dependency) + deps_add_row.addWidget(self.btn_add_dep) + + self.btn_del_dep = QPushButton("Remove") + self.btn_del_dep.setObjectName("danger") + self.btn_del_dep.clicked.connect(self._delete_dependency) + deps_add_row.addWidget(self.btn_del_dep) + + deps_layout.addLayout(deps_add_row) + + left_layout.addWidget(deps_box) + splitter.addWidget(left) # Right: Steps and output @@ -406,6 +447,9 @@ class ToolBuilderPage(QWidget): # Load arguments self._refresh_arguments() + # Load dependencies + self._refresh_dependencies() + # Load steps self._refresh_steps() @@ -579,6 +623,66 @@ class ToolBuilderPage(QWidget): del self._tool.arguments[idx] self._refresh_arguments() + def _populate_deps_combo(self): + """Populate dependencies combo with installed tools.""" + from ...tool import TOOLS_DIR + self.deps_combo.clear() + + # Get all installed tools + tools = [] + if TOOLS_DIR.exists(): + for item in TOOLS_DIR.iterdir(): + if item.is_dir(): + config = item / "config.yaml" + if config.exists(): + tools.append(item.name) + + # Sort and add to combo + for tool in sorted(tools): + self.deps_combo.addItem(tool) + + def _add_dependency(self): + """Add selected tool to dependencies.""" + tool_ref = self.deps_combo.currentText().strip() + if not tool_ref: + return + + if not self._tool: + # Create a minimal tool object if none exists + self._tool = Tool(name="", description="") + + # Initialize dependencies list if needed + if not hasattr(self._tool, 'dependencies') or self._tool.dependencies is None: + self._tool.dependencies = [] + + # Check if already in list + if tool_ref in self._tool.dependencies: + self.main_window.show_status(f"'{tool_ref}' is already a dependency") + return + + self._tool.dependencies.append(tool_ref) + self._refresh_dependencies() + self.deps_combo.setCurrentText("") + self.main_window.show_status(f"Added dependency: {tool_ref}") + + def _delete_dependency(self): + """Remove selected dependency.""" + items = self.deps_list.selectedItems() + if not items: + return + + dep = items[0].text() + if self._tool and self._tool.dependencies and dep in self._tool.dependencies: + self._tool.dependencies.remove(dep) + self._refresh_dependencies() + + def _refresh_dependencies(self): + """Refresh dependencies list widget.""" + self.deps_list.clear() + if self._tool and self._tool.dependencies: + for dep in self._tool.dependencies: + self.deps_list.addItem(dep) + def _add_tool_dependency(self, tool_ref: str): """Add a tool reference to the dependencies list if not already present. @@ -830,13 +934,18 @@ class ToolBuilderPage(QWidget): category=category, arguments=self._tool.arguments if self._tool else [], steps=self._tool.steps if self._tool else [], - output=output + output=output, + dependencies=self._tool.dependencies if self._tool else [] ) # Preserve source if editing if self._tool and self._tool.source: tool.source = self._tool.source + # Preserve version if editing + if self._tool and self._tool.version: + tool.version = self._tool.version + try: save_tool(tool) self.main_window.show_status(f"Saved tool '{name}'")