Add Help menu and contextual tooltips to GUI

- Add Help menu with quick guides: Getting Started, Create Tool,
  Install Tools, Publish Tools, Keyboard Shortcuts, About
- F1 shortcut opens Getting Started guide
- Add tooltips to Tool Builder section headings (Arguments, Steps,
  Output Template) that appear on hover over the label text
- Add tooltips to Registry page controls (search, filters, pagination)
- Enhance sidebar tooltips with keyboard shortcuts

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
rob 2026-01-17 06:27:23 -04:00
parent 3f259449d4
commit 9baddeef18
6 changed files with 540 additions and 16 deletions

View File

@ -35,6 +35,18 @@ All notable changes to CmdForge will be documented in this file.
- Automatically clears invalid/revoked tokens
- Shows status bar message when connection is cleared
- Prevents confusing errors when trying to publish with stale credentials
- **Help menu and quick guides**: Added Help menu with documentation guides
- Getting Started guide with quick start steps and keyboard shortcuts
- How to Create a Tool guide with step-by-step instructions
- How to Install Tools guide for registry browsing
- How to Publish Tools guide for sharing tools
- Keyboard Shortcuts reference
- About CmdForge dialog
- F1 shortcut opens Getting Started guide
- **Enhanced tooltips**: Added contextual tooltips throughout the GUI
- Section headings in Tool Builder (Arguments, Steps, Output Template)
- Registry page controls (search, filters, pagination, install)
- Sidebar navigation items with keyboard shortcuts
#### CLI Features
- `cmdforge config disconnect` - Clear registry token from local configuration

View File

@ -0,0 +1,363 @@
"""Help dialogs for CmdForge GUI."""
from PySide6.QtWidgets import (
QDialog, QVBoxLayout, QHBoxLayout, QLabel, QPushButton,
QTextBrowser
)
from PySide6.QtCore import Qt
# Help content for quick guides
HELP_CONTENT = {
"getting_started": {
"title": "Getting Started with CmdForge",
"content": """
<h3>Welcome to CmdForge!</h3>
<p>CmdForge lets you create AI-powered command-line tools that work like any Unix command.</p>
<h4>Quick Start Steps:</h4>
<ol>
<li><strong>Configure a Provider</strong> - Go to <em>Providers</em> in the sidebar and add an AI backend (like Claude or GPT). This is required before running AI tools.</li>
<li><strong>Browse the Registry</strong> - Check out the <em>Registry</em> to discover tools shared by the community.</li>
<li><strong>Install Tools</strong> - Click on any tool and hit <em>Install</em> to add it to your system.</li>
<li><strong>Create Your Own</strong> - Use the <em>Tool Builder</em> to create custom AI-powered commands.</li>
<li><strong>Use in Terminal</strong> - Run your tools like any command: <code>my-tool &lt; input.txt</code></li>
</ol>
<h4>Key Concepts:</h4>
<ul>
<li><strong>Steps</strong> - Tools are made of steps: Prompt (AI call), Code (Python), or Tool (chain another tool)</li>
<li><strong>Variables</strong> - Use <code>{variable}</code> to pass data between steps</li>
<li><strong>Arguments</strong> - Add flags like <code>--format</code> that users can set when running the tool</li>
</ul>
<h4>Keyboard Shortcuts:</h4>
<table>
<tr><td><code>Ctrl+N</code></td><td>Create new tool</td></tr>
<tr><td><code>Ctrl+S</code></td><td>Save current tool</td></tr>
<tr><td><code>Ctrl+R</code></td><td>Refresh current page</td></tr>
<tr><td><code>Ctrl+1-4</code></td><td>Navigate to pages</td></tr>
<tr><td><code>Ctrl+Q</code></td><td>Quit</td></tr>
<tr><td><code>Escape</code></td><td>Close tool builder</td></tr>
</table>
"""
},
"create_tool": {
"title": "How to Create a Tool",
"content": """
<h3>Creating a New Tool</h3>
<h4>Step 1: Start the Tool Builder</h4>
<p>Click <em>Create Tool</em> on the Welcome page, or press <code>Ctrl+N</code> from anywhere.</p>
<h4>Step 2: Fill in Basic Info</h4>
<ul>
<li><strong>Name</strong> - Use lowercase letters, numbers, and hyphens (e.g., <code>summarize-text</code>)</li>
<li><strong>Description</strong> - Brief explanation shown in listings</li>
<li><strong>Category</strong> - Pick from Text, Developer, Data, or add your own</li>
</ul>
<h4>Step 3: Add Arguments (Optional)</h4>
<p>Arguments let users customize tool behavior:</p>
<ul>
<li><strong>Flag</strong> - The command-line flag (e.g., <code>--max</code>)</li>
<li><strong>Variable</strong> - Name to use in templates (e.g., <code>max</code>)</li>
<li><strong>Default</strong> - Value when flag isn't provided</li>
</ul>
<h4>Step 4: Add Steps</h4>
<p>Steps define what your tool does:</p>
<ul>
<li><strong>Prompt Step</strong> - Calls an AI provider with your template. Use <code>{input}</code> for stdin content.</li>
<li><strong>Code Step</strong> - Runs Python code. Variables are available directly (e.g., <code>input</code>, <code>response</code>).</li>
<li><strong>Tool Step</strong> - Calls another CmdForge tool, enabling composition.</li>
</ul>
<h4>Step 5: Set Output Template</h4>
<p>The output template defines what your tool prints. Use <code>{variable}</code> to include step outputs.</p>
<p>Example: <code>{response}</code> prints the last AI response.</p>
<h4>Step 6: Save</h4>
<p>Press <code>Ctrl+S</code> or click <em>Save</em>. Your tool is now available as a command!</p>
<h4>Example: Simple Summarizer</h4>
<pre>
Name: summarize
Steps:
1. Prompt [claude] -> response
"Summarize this text: {input}"
Output: {response}
</pre>
<p>Run it: <code>echo "Long text..." | summarize</code></p>
"""
},
"install_tools": {
"title": "How to Install Tools",
"content": """
<h3>Installing Tools from the Registry</h3>
<h4>Step 1: Open the Registry</h4>
<p>Click <em>Registry</em> in the sidebar to browse community tools.</p>
<h4>Step 2: Find a Tool</h4>
<ul>
<li><strong>Search</strong> - Type keywords in the search box</li>
<li><strong>Filter by Category</strong> - Use the dropdown to narrow results</li>
<li><strong>Sort</strong> - Order by popularity, newest, or name</li>
<li><strong>Tags</strong> - Click tags on tools to filter by tag</li>
</ul>
<h4>Step 3: Review Tool Details</h4>
<p>Click on a tool to see:</p>
<ul>
<li>Description and usage information</li>
<li>Rating and download count</li>
<li>Publisher reputation</li>
<li>Available versions</li>
</ul>
<h4>Step 4: Install</h4>
<ul>
<li>Select a version (or use <em>Latest</em>)</li>
<li>Click <em>Install</em></li>
<li>Wait for confirmation</li>
</ul>
<h4>Step 5: Use the Tool</h4>
<p>Installed tools are available immediately in your terminal:</p>
<pre>
echo "text" | tool-name
cat file.txt | tool-name --flag value
tool-name --help
</pre>
<h4>Updates</h4>
<p>Tools with available updates show a green arrow (<span style="color: #48bb78;"></span>) in the list. Click <em>Update</em> to get the latest version.</p>
<h4>Managing Installed Tools</h4>
<p>Go to <em>Tools</em> in the sidebar to see all your installed tools, edit them, or delete ones you no longer need.</p>
"""
},
"publish_tools": {
"title": "How to Publish Tools",
"content": """
<h3>Publishing Your Tools</h3>
<h4>Prerequisites</h4>
<ul>
<li>A tool you've created and tested</li>
<li>A registry account (sign up at cmdforge.brrd.tech)</li>
</ul>
<h4>Step 1: Connect Your Account</h4>
<ol>
<li>Go to the <em>Providers</em> page</li>
<li>Click <em>Connect to Registry</em></li>
<li>Log in or register</li>
</ol>
<h4>Step 2: Prepare Your Tool</h4>
<ul>
<li>Give it a clear, descriptive name</li>
<li>Write a helpful description</li>
<li>Choose the right category</li>
<li>Test it thoroughly</li>
</ul>
<h4>Step 3: Publish</h4>
<ol>
<li>Go to the <em>Tools</em> page</li>
<li>Select the tool you want to publish</li>
<li>Click <em>Publish</em></li>
<li>Add tags to help users find it</li>
<li>Set the initial version number</li>
<li>Click <em>Submit</em></li>
</ol>
<h4>Versioning</h4>
<p>When you update a published tool:</p>
<ul>
<li><strong>Patch</strong> (1.0.0 1.0.1) - Bug fixes</li>
<li><strong>Minor</strong> (1.0.0 1.1.0) - New features, backward compatible</li>
<li><strong>Major</strong> (1.0.0 2.0.0) - Breaking changes</li>
</ul>
<h4>Best Practices</h4>
<ul>
<li>Write clear descriptions and examples</li>
<li>Use semantic versioning</li>
<li>Test before publishing updates</li>
<li>Respond to user feedback</li>
</ul>
"""
},
"keyboard_shortcuts": {
"title": "Keyboard Shortcuts",
"content": """
<h3>Keyboard Shortcuts</h3>
<h4>Global</h4>
<table style="width: 100%;">
<tr><td style="width: 40%;"><code>Ctrl+N</code></td><td>Create new tool</td></tr>
<tr><td><code>Ctrl+S</code></td><td>Save current tool</td></tr>
<tr><td><code>Ctrl+R</code></td><td>Refresh current page</td></tr>
<tr><td><code>Ctrl+Q</code></td><td>Quit application</td></tr>
<tr><td><code>F1</code></td><td>Show help</td></tr>
</table>
<h4>Navigation</h4>
<table style="width: 100%;">
<tr><td style="width: 40%;"><code>Ctrl+1</code></td><td>Go to Welcome page</td></tr>
<tr><td><code>Ctrl+2</code></td><td>Go to Tools page</td></tr>
<tr><td><code>Ctrl+3</code></td><td>Go to Registry page</td></tr>
<tr><td><code>Ctrl+4</code></td><td>Go to Providers page</td></tr>
</table>
<h4>Tool Builder</h4>
<table style="width: 100%;">
<tr><td style="width: 40%;"><code>Escape</code></td><td>Close builder / cancel</td></tr>
<tr><td><code>Ctrl+S</code></td><td>Save tool</td></tr>
</table>
<h4>Lists and Tables</h4>
<table style="width: 100%;">
<tr><td style="width: 40%;"><code>Double-click</code></td><td>Edit selected item</td></tr>
<tr><td><code>Drag & Drop</code></td><td>Reorder steps</td></tr>
</table>
"""
}
}
class QuickGuideDialog(QDialog):
"""Dialog showing a quick guide on a topic."""
def __init__(self, guide_key: str, parent=None):
super().__init__(parent)
guide = HELP_CONTENT.get(guide_key, {})
self.setWindowTitle(guide.get("title", "Help"))
self.setMinimumSize(550, 450)
self.resize(600, 500)
self.setModal(True)
self._setup_ui(guide)
def _setup_ui(self, guide: dict):
"""Set up the dialog UI."""
layout = QVBoxLayout(self)
layout.setContentsMargins(24, 24, 24, 24)
layout.setSpacing(16)
# Content browser with styled HTML
browser = QTextBrowser()
browser.setOpenExternalLinks(True)
browser.setStyleSheet("""
QTextBrowser {
border: none;
background-color: transparent;
font-size: 13px;
}
""")
# Build styled content
content = f"""
<style>
h3 {{ color: #2d3748; margin-bottom: 8px; }}
h4 {{ color: #4a5568; margin-top: 16px; margin-bottom: 8px; }}
p {{ color: #4a5568; margin-bottom: 8px; line-height: 1.5; }}
ul, ol {{ color: #4a5568; margin-bottom: 8px; }}
li {{ margin-bottom: 4px; }}
code {{ background: #edf2f7; padding: 2px 6px; border-radius: 3px; font-family: monospace; }}
pre {{ background: #edf2f7; padding: 12px; border-radius: 6px; font-family: monospace; overflow-x: auto; }}
table {{ border-collapse: collapse; margin: 8px 0; }}
td {{ padding: 6px 12px; }}
em {{ color: #667eea; }}
</style>
{guide.get("content", "")}
"""
browser.setHtml(content)
layout.addWidget(browser, 1)
# Close button
btn_layout = QHBoxLayout()
btn_layout.addStretch()
btn_close = QPushButton("Close")
btn_close.setMinimumWidth(100)
btn_close.clicked.connect(self.accept)
btn_layout.addWidget(btn_close)
layout.addLayout(btn_layout)
class AboutDialog(QDialog):
"""About CmdForge dialog."""
def __init__(self, parent=None):
super().__init__(parent)
self.setWindowTitle("About CmdForge")
self.setFixedSize(400, 300)
self.setModal(True)
self._setup_ui()
def _setup_ui(self):
"""Set up the dialog UI."""
layout = QVBoxLayout(self)
layout.setContentsMargins(32, 32, 32, 32)
layout.setSpacing(16)
# Logo/title
title = QLabel("CmdForge")
title.setStyleSheet("""
font-size: 32px;
font-weight: bold;
color: #667eea;
""")
title.setAlignment(Qt.AlignCenter)
layout.addWidget(title)
# Tagline
tagline = QLabel("AI-Powered CLI Tool Builder")
tagline.setStyleSheet("font-size: 14px; color: #718096;")
tagline.setAlignment(Qt.AlignCenter)
layout.addWidget(tagline)
layout.addSpacing(16)
# Version info
try:
from cmdforge import __version__
version = __version__
except ImportError:
version = "unknown"
version_label = QLabel(f"Version {version}")
version_label.setStyleSheet("color: #4a5568;")
version_label.setAlignment(Qt.AlignCenter)
layout.addWidget(version_label)
# Description
desc = QLabel(
"Create custom terminal commands that call AI providers,\n"
"chain prompts with Python code, and use them like\n"
"any Unix pipe command."
)
desc.setStyleSheet("color: #718096; font-size: 12px;")
desc.setAlignment(Qt.AlignCenter)
layout.addWidget(desc)
layout.addStretch()
# Links
links = QLabel(
'<a href="https://cmdforge.brrd.tech" style="color: #667eea;">Website</a>'
)
links.setOpenExternalLinks(True)
links.setAlignment(Qt.AlignCenter)
layout.addWidget(links)
# Close button
btn_close = QPushButton("Close")
btn_close.clicked.connect(self.accept)
layout.addWidget(btn_close)

View File

@ -3,10 +3,10 @@
from PySide6.QtWidgets import (
QMainWindow, QWidget, QHBoxLayout, QVBoxLayout,
QListWidget, QListWidgetItem, QStackedWidget,
QStatusBar, QLabel, QSplitter
QStatusBar, QLabel, QSplitter, QMenuBar, QMenu
)
from PySide6.QtCore import Qt, QSize, QSettings, QTimer
from PySide6.QtGui import QIcon, QFont, QShortcut, QKeySequence
from PySide6.QtGui import QIcon, QFont, QShortcut, QKeySequence, QAction
from .styles import STYLESHEET
@ -65,6 +65,9 @@ class MainWindow(QMainWindow):
# Setup keyboard shortcuts
self._setup_shortcuts()
# Setup menu bar
self._setup_menu_bar()
# Restore window geometry
self._restore_geometry()
@ -98,11 +101,11 @@ class MainWindow(QMainWindow):
def _setup_sidebar(self):
"""Set up sidebar navigation items."""
items = [
("CmdForge", "Welcome to CmdForge"),
("Tools", "Manage your tools"),
("Registry", "Browse and install tools"),
("Providers", "Configure AI providers"),
("Profiles", "AI persona profiles"),
("CmdForge", "Welcome page - Quick actions and getting started (Ctrl+1)"),
("Tools", "View, edit, delete, and test your local tools (Ctrl+2)"),
("Registry", "Browse and install tools from the community (Ctrl+3)"),
("Providers", "Configure AI backends like Claude, GPT, etc. (Ctrl+4)"),
("Profiles", "AI persona profiles for customizing tool behavior"),
]
font = QFont()
@ -228,6 +231,88 @@ class MainWindow(QMainWindow):
shortcut_quit = QShortcut(QKeySequence("Ctrl+Q"), self)
shortcut_quit.activated.connect(self.close)
# F1: Help
shortcut_help = QShortcut(QKeySequence("F1"), self)
shortcut_help.activated.connect(self._show_getting_started)
def _setup_menu_bar(self):
"""Set up the menu bar with Help menu."""
menubar = self.menuBar()
# Help menu
help_menu = menubar.addMenu("&Help")
# Getting Started
action_getting_started = QAction("&Getting Started", self)
action_getting_started.setShortcut("F1")
action_getting_started.triggered.connect(self._show_getting_started)
help_menu.addAction(action_getting_started)
help_menu.addSeparator()
# Quick Guides
action_create_tool = QAction("How to &Create a Tool", self)
action_create_tool.triggered.connect(self._show_create_tool_guide)
help_menu.addAction(action_create_tool)
action_install_tools = QAction("How to &Install Tools", self)
action_install_tools.triggered.connect(self._show_install_tools_guide)
help_menu.addAction(action_install_tools)
action_publish = QAction("How to &Publish Tools", self)
action_publish.triggered.connect(self._show_publish_guide)
help_menu.addAction(action_publish)
help_menu.addSeparator()
# Keyboard Shortcuts
action_shortcuts = QAction("&Keyboard Shortcuts", self)
action_shortcuts.triggered.connect(self._show_keyboard_shortcuts)
help_menu.addAction(action_shortcuts)
help_menu.addSeparator()
# About
action_about = QAction("&About CmdForge", self)
action_about.triggered.connect(self._show_about)
help_menu.addAction(action_about)
def _show_getting_started(self):
"""Show Getting Started guide."""
from .dialogs.help_dialogs import QuickGuideDialog
dialog = QuickGuideDialog("getting_started", self)
dialog.exec()
def _show_create_tool_guide(self):
"""Show Create Tool guide."""
from .dialogs.help_dialogs import QuickGuideDialog
dialog = QuickGuideDialog("create_tool", self)
dialog.exec()
def _show_install_tools_guide(self):
"""Show Install Tools guide."""
from .dialogs.help_dialogs import QuickGuideDialog
dialog = QuickGuideDialog("install_tools", self)
dialog.exec()
def _show_publish_guide(self):
"""Show Publish Tools guide."""
from .dialogs.help_dialogs import QuickGuideDialog
dialog = QuickGuideDialog("publish_tools", self)
dialog.exec()
def _show_keyboard_shortcuts(self):
"""Show Keyboard Shortcuts guide."""
from .dialogs.help_dialogs import QuickGuideDialog
dialog = QuickGuideDialog("keyboard_shortcuts", self)
dialog.exec()
def _show_about(self):
"""Show About dialog."""
from .dialogs.help_dialogs import AboutDialog
dialog = AboutDialog(self)
dialog.exec()
def _shortcut_new_tool(self):
"""Handle Ctrl+N: Create new tool."""
self.open_tool_builder()

View File

@ -129,6 +129,7 @@ class RegistryPage(QWidget):
# Browse all button
self.btn_browse = QPushButton("Browse All")
self.btn_browse.setToolTip("Clear filters and show all available tools")
self.btn_browse.clicked.connect(self._browse_all)
header.addWidget(self.btn_browse)
@ -143,6 +144,7 @@ class RegistryPage(QWidget):
# Search input
self.search_input = QLineEdit()
self.search_input.setPlaceholderText("Search tools...")
self.search_input.setToolTip("Search by name, description, or keywords (press Enter)")
self.search_input.returnPressed.connect(self._do_search)
filters_layout.addWidget(self.search_input, 2)
@ -152,6 +154,7 @@ class RegistryPage(QWidget):
self.category_combo = QComboBox()
self.category_combo.addItems(["All", "Text", "Developer", "Data", "Other"])
self.category_combo.setMinimumWidth(100)
self.category_combo.setToolTip("Filter by tool category")
self.category_combo.currentTextChanged.connect(self._on_filter_changed)
filters_layout.addWidget(self.category_combo)
@ -163,11 +166,13 @@ class RegistryPage(QWidget):
self.sort_combo.addItem("Newest", "published_at")
self.sort_combo.addItem("Name (A-Z)", "name")
self.sort_combo.setMinimumWidth(120)
self.sort_combo.setToolTip("Change sort order of results")
self.sort_combo.currentIndexChanged.connect(self._on_filter_changed)
filters_layout.addWidget(self.sort_combo)
# Search button
self.btn_search = QPushButton("Search")
self.btn_search.setToolTip("Search with current filters")
self.btn_search.clicked.connect(self._do_search)
filters_layout.addWidget(self.btn_search)
@ -196,6 +201,7 @@ class RegistryPage(QWidget):
self.results_table = QTableWidget()
self.results_table.setColumnCount(6)
self.results_table.setHorizontalHeaderLabels(["", "Name", "Owner", "Rating", "Downloads", "Version"])
self.results_table.setToolTip("Click a tool to see details. ✓ = installed, ↑ = update available")
self.results_table.horizontalHeader().setSectionResizeMode(0, QHeaderView.Fixed)
self.results_table.setColumnWidth(0, 30) # Installed indicator
self.results_table.horizontalHeader().setSectionResizeMode(1, QHeaderView.Stretch)
@ -215,6 +221,7 @@ class RegistryPage(QWidget):
pag_layout.setContentsMargins(0, 8, 0, 0)
self.btn_prev = QPushButton("← Previous")
self.btn_prev.setToolTip("Go to previous page of results")
self.btn_prev.clicked.connect(self._prev_page)
self.btn_prev.setEnabled(False)
pag_layout.addWidget(self.btn_prev)
@ -228,6 +235,7 @@ class RegistryPage(QWidget):
pag_layout.addStretch()
self.btn_next = QPushButton("Next →")
self.btn_next.setToolTip("Go to next page of results")
self.btn_next.clicked.connect(self._next_page)
self.btn_next.setEnabled(False)
pag_layout.addWidget(self.btn_next)
@ -263,6 +271,7 @@ class RegistryPage(QWidget):
self.version_combo.setMinimumWidth(120)
self.version_combo.addItem("Latest", None)
self.version_combo.setEnabled(False)
self.version_combo.setToolTip("Select which version to install")
version_row.addWidget(self.version_combo)
version_row.addStretch()
@ -272,11 +281,13 @@ class RegistryPage(QWidget):
actions = QHBoxLayout()
self.btn_install = QPushButton("Install")
self.btn_install.setToolTip("Download and install this tool locally")
self.btn_install.clicked.connect(self._install_tool)
self.btn_install.setEnabled(False)
actions.addWidget(self.btn_install)
self.btn_update = QPushButton("Update Available")
self.btn_update.setToolTip("Update to the latest version from the registry")
self.btn_update.clicked.connect(self._install_tool)
self.btn_update.setEnabled(False)
self.btn_update.setStyleSheet("background-color: #48bb78; color: white;")

View File

@ -51,10 +51,12 @@ class ToolBuilderPage(QWidget):
self.btn_cancel = QPushButton("Cancel")
self.btn_cancel.setObjectName("secondary")
self.btn_cancel.setToolTip("Cancel and return to tools page (Escape)")
self.btn_cancel.clicked.connect(self._cancel)
header_layout.addWidget(self.btn_cancel)
self.btn_save = QPushButton("Save")
self.btn_save.setToolTip("Save this tool (Ctrl+S)")
self.btn_save.clicked.connect(self._save)
header_layout.addWidget(self.btn_save)
@ -76,14 +78,17 @@ class ToolBuilderPage(QWidget):
self.name_input = QLineEdit()
self.name_input.setPlaceholderText("my-tool")
self.name_input.setToolTip("Unique tool name using lowercase letters, numbers, and hyphens")
info_layout.addRow("Name:", self.name_input)
self.desc_input = QLineEdit()
self.desc_input.setPlaceholderText("A brief description of what this tool does")
self.desc_input.setToolTip("Brief description shown in tool listings and help text")
info_layout.addRow("Description:", self.desc_input)
self.category_combo = QComboBox()
self.category_combo.setEditable(True)
self.category_combo.setToolTip("Category for organizing tools (select or type custom)")
for cat in DEFAULT_CATEGORIES:
self.category_combo.addItem(cat)
info_layout.addRow("Category:", self.category_combo)
@ -91,9 +96,19 @@ class ToolBuilderPage(QWidget):
left_layout.addWidget(info_box)
# Arguments group
args_box = QGroupBox("Arguments")
args_box = QGroupBox()
args_layout = QVBoxLayout(args_box)
args_label = QLabel("Arguments")
args_label.setObjectName("sectionHeading")
args_label.setToolTip(
"<p>Arguments are command-line flags users can pass to your tool.</p>"
"<p>Example: Adding --max with variable 'max' lets users run:<br>"
"<code>my-tool --max 100 &lt; input.txt</code></p>"
"<p>Use {max} in your prompts to reference the value.</p>"
)
args_layout.addWidget(args_label)
self.args_list = QListWidget()
self.args_list.itemDoubleClicked.connect(self._edit_argument)
args_layout.addWidget(self.args_list)
@ -127,21 +142,37 @@ class ToolBuilderPage(QWidget):
right_layout.setSpacing(16)
# Steps group with view toggle
steps_box = QGroupBox("Steps")
steps_box = QGroupBox()
steps_layout = QVBoxLayout(steps_box)
# View toggle header
view_header = QHBoxLayout()
# Steps header with label and view toggle
steps_header = QHBoxLayout()
steps_label = QLabel("Steps")
steps_label.setObjectName("sectionHeading")
steps_label.setToolTip(
"<p>Steps define what your tool does, executed in order.</p>"
"<p><b>Step types:</b><br>"
"- Prompt: Call an AI provider with a template<br>"
"- Code: Run Python code to process data<br>"
"- Tool: Call another CmdForge tool</p>"
"<p>Use {variable} syntax to pass data between steps.<br>"
"Built-in: {input} contains stdin content.</p>"
)
steps_header.addWidget(steps_label)
steps_header.addStretch()
# View toggle buttons
self.btn_list_view = QPushButton("List")
self.btn_list_view.setCheckable(True)
self.btn_list_view.setChecked(True)
self.btn_list_view.setObjectName("viewToggle")
self.btn_list_view.setToolTip("View steps as a list (drag to reorder)")
self.btn_list_view.clicked.connect(lambda: self._set_view_mode(0))
self.btn_flow_view = QPushButton("Flow")
self.btn_flow_view.setCheckable(True)
self.btn_flow_view.setObjectName("viewToggle")
self.btn_flow_view.setToolTip("View steps as a visual flow graph")
self.btn_flow_view.clicked.connect(lambda: self._set_view_mode(1))
# Button group for mutual exclusivity
@ -149,10 +180,9 @@ class ToolBuilderPage(QWidget):
self.view_group.addButton(self.btn_list_view, 0)
self.view_group.addButton(self.btn_flow_view, 1)
view_header.addWidget(self.btn_list_view)
view_header.addWidget(self.btn_flow_view)
view_header.addStretch()
steps_layout.addLayout(view_header)
steps_header.addWidget(self.btn_list_view)
steps_header.addWidget(self.btn_flow_view)
steps_layout.addLayout(steps_header)
# Stacked widget for list/flow views
self.steps_stack = QStackedWidget()
@ -183,24 +213,29 @@ class ToolBuilderPage(QWidget):
# Step action buttons
steps_btns = QHBoxLayout()
self.btn_add_prompt = QPushButton("Add Prompt")
self.btn_add_prompt.setToolTip("Add an AI prompt step - calls your provider with a template")
self.btn_add_prompt.clicked.connect(self._add_prompt_step)
steps_btns.addWidget(self.btn_add_prompt)
self.btn_add_code = QPushButton("Add Code")
self.btn_add_code.setToolTip("Add a Python code step - process or transform data")
self.btn_add_code.clicked.connect(self._add_code_step)
steps_btns.addWidget(self.btn_add_code)
self.btn_add_tool = QPushButton("Add Tool")
self.btn_add_tool.setToolTip("Add a tool step - chain another CmdForge tool")
self.btn_add_tool.clicked.connect(self._add_tool_step)
steps_btns.addWidget(self.btn_add_tool)
self.btn_edit_step = QPushButton("Edit")
self.btn_edit_step.setObjectName("secondary")
self.btn_edit_step.setToolTip("Edit the selected step (or double-click)")
self.btn_edit_step.clicked.connect(self._edit_step)
steps_btns.addWidget(self.btn_edit_step)
self.btn_del_step = QPushButton("Delete")
self.btn_del_step.setObjectName("danger")
self.btn_del_step.setToolTip("Delete the selected step")
self.btn_del_step.clicked.connect(self._delete_step)
steps_btns.addWidget(self.btn_del_step)
@ -210,9 +245,21 @@ class ToolBuilderPage(QWidget):
right_layout.addWidget(steps_box, 1) # Steps box gets stretch priority
# Output group
output_box = QGroupBox("Output Template")
output_box = QGroupBox()
output_layout = QVBoxLayout(output_box)
output_label = QLabel("Output Template")
output_label.setObjectName("sectionHeading")
output_label.setToolTip(
"<p>The output template defines what your tool prints.</p>"
"<p><b>Use {variable} to include step outputs:</b><br>"
"- {response} - typical AI response variable<br>"
"- {result} - common code step output<br>"
"- Any output_var you defined in steps</p>"
"<p>Example: <code>{response}</code> prints just the AI response.</p>"
)
output_layout.addWidget(output_label)
self.output_input = QTextEdit()
self.output_input.setPlaceholderText("Use {variable} to reference step outputs, e.g. {response}")
self.output_input.setPlainText("{response}") # Default value

View File

@ -225,6 +225,12 @@ QLabel#label {
color: #4a5568;
}
QLabel#sectionHeading {
font-weight: 600;
font-size: 13px;
color: #2d3748;
}
/* Scrollbars */
QScrollBar:vertical {
background-color: #f7fafc;