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 - Automatically clears invalid/revoked tokens
- Shows status bar message when connection is cleared - Shows status bar message when connection is cleared
- Prevents confusing errors when trying to publish with stale credentials - 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 #### CLI Features
- `cmdforge config disconnect` - Clear registry token from local configuration - `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 ( from PySide6.QtWidgets import (
QMainWindow, QWidget, QHBoxLayout, QVBoxLayout, QMainWindow, QWidget, QHBoxLayout, QVBoxLayout,
QListWidget, QListWidgetItem, QStackedWidget, QListWidget, QListWidgetItem, QStackedWidget,
QStatusBar, QLabel, QSplitter QStatusBar, QLabel, QSplitter, QMenuBar, QMenu
) )
from PySide6.QtCore import Qt, QSize, QSettings, QTimer 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 from .styles import STYLESHEET
@ -65,6 +65,9 @@ class MainWindow(QMainWindow):
# Setup keyboard shortcuts # Setup keyboard shortcuts
self._setup_shortcuts() self._setup_shortcuts()
# Setup menu bar
self._setup_menu_bar()
# Restore window geometry # Restore window geometry
self._restore_geometry() self._restore_geometry()
@ -98,11 +101,11 @@ class MainWindow(QMainWindow):
def _setup_sidebar(self): def _setup_sidebar(self):
"""Set up sidebar navigation items.""" """Set up sidebar navigation items."""
items = [ items = [
("CmdForge", "Welcome to CmdForge"), ("CmdForge", "Welcome page - Quick actions and getting started (Ctrl+1)"),
("Tools", "Manage your tools"), ("Tools", "View, edit, delete, and test your local tools (Ctrl+2)"),
("Registry", "Browse and install tools"), ("Registry", "Browse and install tools from the community (Ctrl+3)"),
("Providers", "Configure AI providers"), ("Providers", "Configure AI backends like Claude, GPT, etc. (Ctrl+4)"),
("Profiles", "AI persona profiles"), ("Profiles", "AI persona profiles for customizing tool behavior"),
] ]
font = QFont() font = QFont()
@ -228,6 +231,88 @@ class MainWindow(QMainWindow):
shortcut_quit = QShortcut(QKeySequence("Ctrl+Q"), self) shortcut_quit = QShortcut(QKeySequence("Ctrl+Q"), self)
shortcut_quit.activated.connect(self.close) 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): def _shortcut_new_tool(self):
"""Handle Ctrl+N: Create new tool.""" """Handle Ctrl+N: Create new tool."""
self.open_tool_builder() self.open_tool_builder()

View File

@ -129,6 +129,7 @@ class RegistryPage(QWidget):
# Browse all button # Browse all button
self.btn_browse = QPushButton("Browse All") 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) self.btn_browse.clicked.connect(self._browse_all)
header.addWidget(self.btn_browse) header.addWidget(self.btn_browse)
@ -143,6 +144,7 @@ class RegistryPage(QWidget):
# Search input # Search input
self.search_input = QLineEdit() self.search_input = QLineEdit()
self.search_input.setPlaceholderText("Search tools...") 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) self.search_input.returnPressed.connect(self._do_search)
filters_layout.addWidget(self.search_input, 2) filters_layout.addWidget(self.search_input, 2)
@ -152,6 +154,7 @@ class RegistryPage(QWidget):
self.category_combo = QComboBox() self.category_combo = QComboBox()
self.category_combo.addItems(["All", "Text", "Developer", "Data", "Other"]) self.category_combo.addItems(["All", "Text", "Developer", "Data", "Other"])
self.category_combo.setMinimumWidth(100) self.category_combo.setMinimumWidth(100)
self.category_combo.setToolTip("Filter by tool category")
self.category_combo.currentTextChanged.connect(self._on_filter_changed) self.category_combo.currentTextChanged.connect(self._on_filter_changed)
filters_layout.addWidget(self.category_combo) filters_layout.addWidget(self.category_combo)
@ -163,11 +166,13 @@ class RegistryPage(QWidget):
self.sort_combo.addItem("Newest", "published_at") self.sort_combo.addItem("Newest", "published_at")
self.sort_combo.addItem("Name (A-Z)", "name") self.sort_combo.addItem("Name (A-Z)", "name")
self.sort_combo.setMinimumWidth(120) self.sort_combo.setMinimumWidth(120)
self.sort_combo.setToolTip("Change sort order of results")
self.sort_combo.currentIndexChanged.connect(self._on_filter_changed) self.sort_combo.currentIndexChanged.connect(self._on_filter_changed)
filters_layout.addWidget(self.sort_combo) filters_layout.addWidget(self.sort_combo)
# Search button # Search button
self.btn_search = QPushButton("Search") self.btn_search = QPushButton("Search")
self.btn_search.setToolTip("Search with current filters")
self.btn_search.clicked.connect(self._do_search) self.btn_search.clicked.connect(self._do_search)
filters_layout.addWidget(self.btn_search) filters_layout.addWidget(self.btn_search)
@ -196,6 +201,7 @@ class RegistryPage(QWidget):
self.results_table = QTableWidget() self.results_table = QTableWidget()
self.results_table.setColumnCount(6) self.results_table.setColumnCount(6)
self.results_table.setHorizontalHeaderLabels(["", "Name", "Owner", "Rating", "Downloads", "Version"]) 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.horizontalHeader().setSectionResizeMode(0, QHeaderView.Fixed)
self.results_table.setColumnWidth(0, 30) # Installed indicator self.results_table.setColumnWidth(0, 30) # Installed indicator
self.results_table.horizontalHeader().setSectionResizeMode(1, QHeaderView.Stretch) self.results_table.horizontalHeader().setSectionResizeMode(1, QHeaderView.Stretch)
@ -215,6 +221,7 @@ class RegistryPage(QWidget):
pag_layout.setContentsMargins(0, 8, 0, 0) pag_layout.setContentsMargins(0, 8, 0, 0)
self.btn_prev = QPushButton("← Previous") 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.clicked.connect(self._prev_page)
self.btn_prev.setEnabled(False) self.btn_prev.setEnabled(False)
pag_layout.addWidget(self.btn_prev) pag_layout.addWidget(self.btn_prev)
@ -228,6 +235,7 @@ class RegistryPage(QWidget):
pag_layout.addStretch() pag_layout.addStretch()
self.btn_next = QPushButton("Next →") 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.clicked.connect(self._next_page)
self.btn_next.setEnabled(False) self.btn_next.setEnabled(False)
pag_layout.addWidget(self.btn_next) pag_layout.addWidget(self.btn_next)
@ -263,6 +271,7 @@ class RegistryPage(QWidget):
self.version_combo.setMinimumWidth(120) self.version_combo.setMinimumWidth(120)
self.version_combo.addItem("Latest", None) self.version_combo.addItem("Latest", None)
self.version_combo.setEnabled(False) self.version_combo.setEnabled(False)
self.version_combo.setToolTip("Select which version to install")
version_row.addWidget(self.version_combo) version_row.addWidget(self.version_combo)
version_row.addStretch() version_row.addStretch()
@ -272,11 +281,13 @@ class RegistryPage(QWidget):
actions = QHBoxLayout() actions = QHBoxLayout()
self.btn_install = QPushButton("Install") 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.clicked.connect(self._install_tool)
self.btn_install.setEnabled(False) self.btn_install.setEnabled(False)
actions.addWidget(self.btn_install) actions.addWidget(self.btn_install)
self.btn_update = QPushButton("Update Available") 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.clicked.connect(self._install_tool)
self.btn_update.setEnabled(False) self.btn_update.setEnabled(False)
self.btn_update.setStyleSheet("background-color: #48bb78; color: white;") 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 = QPushButton("Cancel")
self.btn_cancel.setObjectName("secondary") self.btn_cancel.setObjectName("secondary")
self.btn_cancel.setToolTip("Cancel and return to tools page (Escape)")
self.btn_cancel.clicked.connect(self._cancel) self.btn_cancel.clicked.connect(self._cancel)
header_layout.addWidget(self.btn_cancel) header_layout.addWidget(self.btn_cancel)
self.btn_save = QPushButton("Save") self.btn_save = QPushButton("Save")
self.btn_save.setToolTip("Save this tool (Ctrl+S)")
self.btn_save.clicked.connect(self._save) self.btn_save.clicked.connect(self._save)
header_layout.addWidget(self.btn_save) header_layout.addWidget(self.btn_save)
@ -76,14 +78,17 @@ class ToolBuilderPage(QWidget):
self.name_input = QLineEdit() self.name_input = QLineEdit()
self.name_input.setPlaceholderText("my-tool") 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) info_layout.addRow("Name:", self.name_input)
self.desc_input = QLineEdit() self.desc_input = QLineEdit()
self.desc_input.setPlaceholderText("A brief description of what this tool does") 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) info_layout.addRow("Description:", self.desc_input)
self.category_combo = QComboBox() self.category_combo = QComboBox()
self.category_combo.setEditable(True) self.category_combo.setEditable(True)
self.category_combo.setToolTip("Category for organizing tools (select or type custom)")
for cat in DEFAULT_CATEGORIES: for cat in DEFAULT_CATEGORIES:
self.category_combo.addItem(cat) self.category_combo.addItem(cat)
info_layout.addRow("Category:", self.category_combo) info_layout.addRow("Category:", self.category_combo)
@ -91,9 +96,19 @@ class ToolBuilderPage(QWidget):
left_layout.addWidget(info_box) left_layout.addWidget(info_box)
# Arguments group # Arguments group
args_box = QGroupBox("Arguments") args_box = QGroupBox()
args_layout = QVBoxLayout(args_box) 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 = QListWidget()
self.args_list.itemDoubleClicked.connect(self._edit_argument) self.args_list.itemDoubleClicked.connect(self._edit_argument)
args_layout.addWidget(self.args_list) args_layout.addWidget(self.args_list)
@ -127,21 +142,37 @@ class ToolBuilderPage(QWidget):
right_layout.setSpacing(16) right_layout.setSpacing(16)
# Steps group with view toggle # Steps group with view toggle
steps_box = QGroupBox("Steps") steps_box = QGroupBox()
steps_layout = QVBoxLayout(steps_box) steps_layout = QVBoxLayout(steps_box)
# View toggle header # Steps header with label and view toggle
view_header = QHBoxLayout() 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 = QPushButton("List")
self.btn_list_view.setCheckable(True) self.btn_list_view.setCheckable(True)
self.btn_list_view.setChecked(True) self.btn_list_view.setChecked(True)
self.btn_list_view.setObjectName("viewToggle") 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_list_view.clicked.connect(lambda: self._set_view_mode(0))
self.btn_flow_view = QPushButton("Flow") self.btn_flow_view = QPushButton("Flow")
self.btn_flow_view.setCheckable(True) self.btn_flow_view.setCheckable(True)
self.btn_flow_view.setObjectName("viewToggle") 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)) self.btn_flow_view.clicked.connect(lambda: self._set_view_mode(1))
# Button group for mutual exclusivity # 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_list_view, 0)
self.view_group.addButton(self.btn_flow_view, 1) self.view_group.addButton(self.btn_flow_view, 1)
view_header.addWidget(self.btn_list_view) steps_header.addWidget(self.btn_list_view)
view_header.addWidget(self.btn_flow_view) steps_header.addWidget(self.btn_flow_view)
view_header.addStretch() steps_layout.addLayout(steps_header)
steps_layout.addLayout(view_header)
# Stacked widget for list/flow views # Stacked widget for list/flow views
self.steps_stack = QStackedWidget() self.steps_stack = QStackedWidget()
@ -183,24 +213,29 @@ class ToolBuilderPage(QWidget):
# Step action buttons # Step action buttons
steps_btns = QHBoxLayout() steps_btns = QHBoxLayout()
self.btn_add_prompt = QPushButton("Add Prompt") 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) self.btn_add_prompt.clicked.connect(self._add_prompt_step)
steps_btns.addWidget(self.btn_add_prompt) steps_btns.addWidget(self.btn_add_prompt)
self.btn_add_code = QPushButton("Add Code") 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) self.btn_add_code.clicked.connect(self._add_code_step)
steps_btns.addWidget(self.btn_add_code) steps_btns.addWidget(self.btn_add_code)
self.btn_add_tool = QPushButton("Add Tool") 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) self.btn_add_tool.clicked.connect(self._add_tool_step)
steps_btns.addWidget(self.btn_add_tool) steps_btns.addWidget(self.btn_add_tool)
self.btn_edit_step = QPushButton("Edit") self.btn_edit_step = QPushButton("Edit")
self.btn_edit_step.setObjectName("secondary") 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) self.btn_edit_step.clicked.connect(self._edit_step)
steps_btns.addWidget(self.btn_edit_step) steps_btns.addWidget(self.btn_edit_step)
self.btn_del_step = QPushButton("Delete") self.btn_del_step = QPushButton("Delete")
self.btn_del_step.setObjectName("danger") self.btn_del_step.setObjectName("danger")
self.btn_del_step.setToolTip("Delete the selected step")
self.btn_del_step.clicked.connect(self._delete_step) self.btn_del_step.clicked.connect(self._delete_step)
steps_btns.addWidget(self.btn_del_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 right_layout.addWidget(steps_box, 1) # Steps box gets stretch priority
# Output group # Output group
output_box = QGroupBox("Output Template") output_box = QGroupBox()
output_layout = QVBoxLayout(output_box) 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 = QTextEdit()
self.output_input.setPlaceholderText("Use {variable} to reference step outputs, e.g. {response}") self.output_input.setPlaceholderText("Use {variable} to reference step outputs, e.g. {response}")
self.output_input.setPlainText("{response}") # Default value self.output_input.setPlainText("{response}") # Default value

View File

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