707 lines
28 KiB
Python
707 lines
28 KiB
Python
"""BIOS-style TUI for CmdForge using snack (python3-newt)."""
|
|
|
|
import sys
|
|
# Ensure system packages are accessible
|
|
if '/usr/lib/python3/dist-packages' not in sys.path:
|
|
sys.path.insert(0, '/usr/lib/python3/dist-packages')
|
|
|
|
import snack
|
|
from typing import Optional, List, Tuple
|
|
|
|
from .tool import (
|
|
Tool, ToolArgument, PromptStep, CodeStep, Step,
|
|
list_tools, load_tool, save_tool, delete_tool, tool_exists
|
|
)
|
|
from .providers import Provider, load_providers, add_provider, delete_provider, get_provider
|
|
|
|
|
|
class CmdForgeUI:
|
|
"""BIOS-style UI for CmdForge."""
|
|
|
|
def __init__(self):
|
|
self.screen = None
|
|
|
|
def run(self):
|
|
"""Run the UI."""
|
|
self.screen = snack.SnackScreen()
|
|
# Enable mouse support
|
|
self.screen.pushHelpLine(" Tab/Arrow:Navigate | Enter:Select | Mouse:Click | Esc:Back ")
|
|
try:
|
|
# Enable mouse - newt supports GPM and xterm mouse
|
|
import os
|
|
os.environ.setdefault('NEWT_MONO', '0')
|
|
self.main_menu()
|
|
finally:
|
|
self.screen.finish()
|
|
|
|
def main_menu(self):
|
|
"""Show the main menu."""
|
|
while True:
|
|
items = [
|
|
("Create New Tool", "create"),
|
|
("Edit Tool", "edit"),
|
|
("Delete Tool", "delete"),
|
|
("List Tools", "list"),
|
|
("Test Tool", "test"),
|
|
("Manage Providers", "providers"),
|
|
("Exit", "exit"),
|
|
]
|
|
|
|
listbox = snack.Listbox(height=7, width=30, returnExit=1)
|
|
for label, value in items:
|
|
listbox.append(label, value)
|
|
|
|
grid = snack.GridForm(self.screen, "CmdForge Manager", 1, 1)
|
|
grid.add(listbox, 0, 0)
|
|
|
|
result = grid.runOnce()
|
|
selected = listbox.current()
|
|
|
|
if selected == "exit" or result == "ESC":
|
|
break
|
|
elif selected == "create":
|
|
self.tool_builder(None)
|
|
elif selected == "edit":
|
|
self.select_and_edit_tool()
|
|
elif selected == "delete":
|
|
self.select_and_delete_tool()
|
|
elif selected == "list":
|
|
self.show_tools_list()
|
|
elif selected == "test":
|
|
self.select_and_test_tool()
|
|
elif selected == "providers":
|
|
self.manage_providers()
|
|
|
|
def message_box(self, title: str, message: str):
|
|
"""Show a message box."""
|
|
snack.ButtonChoiceWindow(self.screen, title, message, ["OK"])
|
|
|
|
def yes_no(self, title: str, message: str) -> bool:
|
|
"""Show a yes/no dialog."""
|
|
result = snack.ButtonChoiceWindow(self.screen, title, message, ["Yes", "No"])
|
|
return result == "yes"
|
|
|
|
def input_box(self, title: str, prompt: str, initial: str = "", width: int = 40) -> Optional[str]:
|
|
"""Show an input dialog."""
|
|
entry = snack.Entry(width, initial)
|
|
grid = snack.GridForm(self.screen, title, 1, 3)
|
|
grid.add(snack.Label(prompt), 0, 0)
|
|
grid.add(entry, 0, 1, padding=(0, 1, 0, 1))
|
|
|
|
buttons = snack.ButtonBar(self.screen, [("OK", "ok"), ("Cancel", "cancel")])
|
|
grid.add(buttons, 0, 2)
|
|
|
|
result = grid.runOnce()
|
|
|
|
if buttons.buttonPressed(result) == "ok":
|
|
return entry.value()
|
|
return None
|
|
|
|
def text_edit(self, title: str, initial: str = "", width: int = 60, height: int = 10) -> Optional[str]:
|
|
"""Show a multi-line text editor."""
|
|
text = snack.Textbox(width, height, initial, scroll=1, wrap=1)
|
|
|
|
# snack doesn't have a true multi-line editor, so we use Entry for now
|
|
# For multi-line, we'll use a workaround with a simple entry
|
|
entry = snack.Entry(width, initial, scroll=1)
|
|
|
|
grid = snack.GridForm(self.screen, title, 1, 2)
|
|
grid.add(entry, 0, 0, padding=(0, 0, 0, 1))
|
|
|
|
buttons = snack.ButtonBar(self.screen, [("OK", "ok"), ("Cancel", "cancel")])
|
|
grid.add(buttons, 0, 1)
|
|
|
|
result = grid.runOnce()
|
|
|
|
if buttons.buttonPressed(result) == "ok":
|
|
return entry.value()
|
|
return None
|
|
|
|
def select_provider(self) -> Optional[str]:
|
|
"""Show provider selection dialog."""
|
|
providers = load_providers()
|
|
|
|
listbox = snack.Listbox(height=min(len(providers) + 1, 8), width=50, returnExit=1)
|
|
for p in providers:
|
|
listbox.append(f"{p.name}: {p.command}", p.name)
|
|
listbox.append("[ + Add New Provider ]", "__new__")
|
|
|
|
grid = snack.GridForm(self.screen, "Select Provider", 1, 2)
|
|
grid.add(listbox, 0, 0)
|
|
|
|
buttons = snack.ButtonBar(self.screen, [("Select", "ok"), ("Cancel", "cancel")])
|
|
grid.add(buttons, 0, 1)
|
|
|
|
result = grid.runOnce()
|
|
|
|
if buttons.buttonPressed(result) == "cancel" or result == "ESC":
|
|
return None
|
|
|
|
selected = listbox.current()
|
|
|
|
if selected == "__new__":
|
|
provider = self.add_provider_dialog()
|
|
if provider:
|
|
add_provider(provider)
|
|
return provider.name
|
|
return None
|
|
|
|
return selected
|
|
|
|
def add_provider_dialog(self) -> Optional[Provider]:
|
|
"""Dialog to add a new provider."""
|
|
name_entry = snack.Entry(30, "")
|
|
cmd_entry = snack.Entry(40, "")
|
|
desc_entry = snack.Entry(40, "")
|
|
|
|
grid = snack.GridForm(self.screen, "Add Provider", 2, 4)
|
|
grid.add(snack.Label("Name:"), 0, 0, anchorLeft=1)
|
|
grid.add(name_entry, 1, 0, padding=(1, 0, 0, 0))
|
|
grid.add(snack.Label("Command:"), 0, 1, anchorLeft=1)
|
|
grid.add(cmd_entry, 1, 1, padding=(1, 0, 0, 0))
|
|
grid.add(snack.Label("Description:"), 0, 2, anchorLeft=1)
|
|
grid.add(desc_entry, 1, 2, padding=(1, 0, 0, 0))
|
|
|
|
buttons = snack.ButtonBar(self.screen, [("OK", "ok"), ("Cancel", "cancel")])
|
|
grid.add(buttons, 0, 3, growx=1, growy=1)
|
|
|
|
result = grid.runOnce()
|
|
|
|
if buttons.buttonPressed(result) == "ok":
|
|
name = name_entry.value().strip()
|
|
cmd = cmd_entry.value().strip()
|
|
if name and cmd:
|
|
return Provider(name=name, command=cmd, description=desc_entry.value().strip())
|
|
self.message_box("Error", "Name and command are required.")
|
|
|
|
return None
|
|
|
|
def add_argument_dialog(self, existing: Optional[ToolArgument] = None) -> Optional[ToolArgument]:
|
|
"""Dialog to add/edit an argument."""
|
|
flag_entry = snack.Entry(20, existing.flag if existing else "--")
|
|
var_entry = snack.Entry(20, existing.variable if existing else "")
|
|
default_entry = snack.Entry(20, existing.default or "" if existing else "")
|
|
desc_entry = snack.Entry(40, existing.description if existing else "")
|
|
|
|
title = "Edit Argument" if existing else "Add Argument"
|
|
grid = snack.GridForm(self.screen, title, 2, 5)
|
|
grid.add(snack.Label("Flag:"), 0, 0, anchorLeft=1)
|
|
grid.add(flag_entry, 1, 0, padding=(1, 0, 0, 0))
|
|
grid.add(snack.Label("Variable:"), 0, 1, anchorLeft=1)
|
|
grid.add(var_entry, 1, 1, padding=(1, 0, 0, 0))
|
|
grid.add(snack.Label("Default:"), 0, 2, anchorLeft=1)
|
|
grid.add(default_entry, 1, 2, padding=(1, 0, 0, 0))
|
|
grid.add(snack.Label("Description:"), 0, 3, anchorLeft=1)
|
|
grid.add(desc_entry, 1, 3, padding=(1, 0, 0, 0))
|
|
|
|
buttons = snack.ButtonBar(self.screen, [("OK", "ok"), ("Cancel", "cancel")])
|
|
grid.add(buttons, 0, 4, growx=1)
|
|
|
|
result = grid.runOnce()
|
|
|
|
if buttons.buttonPressed(result) == "ok":
|
|
flag = flag_entry.value().strip()
|
|
var = var_entry.value().strip()
|
|
|
|
if not flag:
|
|
self.message_box("Error", "Flag is required.")
|
|
return None
|
|
|
|
if not var:
|
|
var = flag.lstrip("-").replace("-", "_")
|
|
|
|
return ToolArgument(
|
|
flag=flag,
|
|
variable=var,
|
|
default=default_entry.value().strip() or None,
|
|
description=desc_entry.value().strip()
|
|
)
|
|
|
|
return None
|
|
|
|
def add_step_dialog(self, tool: Tool, existing_step: Optional[Step] = None, step_idx: int = -1) -> Optional[Step]:
|
|
"""Dialog to choose and add a step."""
|
|
if existing_step:
|
|
# Edit existing step
|
|
if isinstance(existing_step, PromptStep):
|
|
return self.add_prompt_dialog(tool, existing_step, step_idx)
|
|
else:
|
|
return self.add_code_dialog(tool, existing_step, step_idx)
|
|
|
|
# Choose step type
|
|
listbox = snack.Listbox(height=2, width=30, returnExit=1)
|
|
listbox.append("Prompt (AI call)", "prompt")
|
|
listbox.append("Code (Python)", "code")
|
|
|
|
grid = snack.GridForm(self.screen, "Add Step", 1, 2)
|
|
grid.add(listbox, 0, 0)
|
|
|
|
buttons = snack.ButtonBar(self.screen, [("Select", "ok"), ("Cancel", "cancel")])
|
|
grid.add(buttons, 0, 1)
|
|
|
|
result = grid.runOnce()
|
|
|
|
if buttons.buttonPressed(result) == "cancel" or result == "ESC":
|
|
return None
|
|
|
|
step_type = listbox.current()
|
|
|
|
if step_type == "prompt":
|
|
return self.add_prompt_dialog(tool, None, step_idx)
|
|
else:
|
|
return self.add_code_dialog(tool, None, step_idx)
|
|
|
|
def get_available_variables(self, tool: Tool, up_to_step: int = -1) -> List[str]:
|
|
"""Get available variables at a point in the tool."""
|
|
variables = ["input"]
|
|
for arg in tool.arguments:
|
|
variables.append(arg.variable)
|
|
if up_to_step == -1:
|
|
up_to_step = len(tool.steps)
|
|
for i, step in enumerate(tool.steps):
|
|
if i >= up_to_step:
|
|
break
|
|
variables.append(step.output_var)
|
|
return variables
|
|
|
|
def add_prompt_dialog(self, tool: Tool, existing: Optional[PromptStep] = None, step_idx: int = -1) -> Optional[PromptStep]:
|
|
"""Dialog to add/edit a prompt step."""
|
|
available = self.get_available_variables(tool, step_idx if step_idx >= 0 else -1)
|
|
var_help = "Variables: " + ", ".join(f"{{{v}}}" for v in available)
|
|
|
|
# Provider selection first
|
|
provider = self.select_provider()
|
|
if not provider:
|
|
provider = existing.provider if existing else "mock"
|
|
|
|
prompt_entry = snack.Entry(60, existing.prompt if existing else f"Process this:\n\n{{input}}", scroll=1)
|
|
output_entry = snack.Entry(20, existing.output_var if existing else "result")
|
|
|
|
title = "Edit Prompt Step" if existing else "Add Prompt Step"
|
|
grid = snack.GridForm(self.screen, title, 2, 4)
|
|
|
|
grid.add(snack.Label(f"Provider: {provider}"), 0, 0, anchorLeft=1, growx=1)
|
|
grid.add(snack.Label(""), 1, 0)
|
|
|
|
grid.add(snack.Label(f"Prompt ({var_help}):"), 0, 1, anchorLeft=1, growx=1)
|
|
grid.add(snack.Label(""), 1, 1)
|
|
|
|
grid.add(prompt_entry, 0, 2, growx=1)
|
|
grid.add(snack.Label(""), 1, 2)
|
|
|
|
sub_grid = snack.Grid(2, 1)
|
|
sub_grid.setField(snack.Label("Output var:"), 0, 0, anchorLeft=1)
|
|
sub_grid.setField(output_entry, 1, 0, padding=(1, 0, 0, 0))
|
|
grid.add(sub_grid, 0, 3, anchorLeft=1)
|
|
|
|
buttons = snack.ButtonBar(self.screen, [("OK", "ok"), ("Cancel", "cancel")])
|
|
grid.add(buttons, 1, 3)
|
|
|
|
result = grid.runOnce()
|
|
|
|
if buttons.buttonPressed(result) == "ok":
|
|
prompt = prompt_entry.value().strip()
|
|
output_var = output_entry.value().strip()
|
|
|
|
if not prompt:
|
|
self.message_box("Error", "Prompt is required.")
|
|
return None
|
|
if not output_var:
|
|
output_var = "result"
|
|
|
|
return PromptStep(prompt=prompt, provider=provider, output_var=output_var)
|
|
|
|
return None
|
|
|
|
def add_code_dialog(self, tool: Tool, existing: Optional[CodeStep] = None, step_idx: int = -1) -> Optional[CodeStep]:
|
|
"""Dialog to add/edit a code step."""
|
|
available = self.get_available_variables(tool, step_idx if step_idx >= 0 else -1)
|
|
var_help = "Variables: " + ", ".join(available)
|
|
|
|
default_code = existing.code if existing else "# Set 'result' for output\nresult = input.upper()"
|
|
code_entry = snack.Entry(60, default_code, scroll=1)
|
|
output_entry = snack.Entry(20, existing.output_var if existing else "processed")
|
|
|
|
title = "Edit Code Step" if existing else "Add Code Step"
|
|
grid = snack.GridForm(self.screen, title, 2, 4)
|
|
|
|
grid.add(snack.Label(f"Python Code ({var_help}):"), 0, 0, anchorLeft=1, growx=1)
|
|
grid.add(snack.Label("Set 'result' variable for output"), 1, 0)
|
|
|
|
grid.add(code_entry, 0, 1, growx=1)
|
|
grid.add(snack.Label(""), 1, 1)
|
|
|
|
sub_grid = snack.Grid(2, 1)
|
|
sub_grid.setField(snack.Label("Output var:"), 0, 0, anchorLeft=1)
|
|
sub_grid.setField(output_entry, 1, 0, padding=(1, 0, 0, 0))
|
|
grid.add(sub_grid, 0, 2, anchorLeft=1)
|
|
|
|
buttons = snack.ButtonBar(self.screen, [("OK", "ok"), ("Cancel", "cancel")])
|
|
grid.add(buttons, 1, 2)
|
|
|
|
result = grid.runOnce()
|
|
|
|
if buttons.buttonPressed(result) == "ok":
|
|
code = code_entry.value().strip()
|
|
output_var = output_entry.value().strip()
|
|
|
|
if not code:
|
|
self.message_box("Error", "Code is required.")
|
|
return None
|
|
if not output_var:
|
|
output_var = "processed"
|
|
|
|
return CodeStep(code=code, output_var=output_var)
|
|
|
|
return None
|
|
|
|
def tool_builder(self, existing: Optional[Tool] = None) -> Optional[Tool]:
|
|
"""Main tool builder - BIOS-style unified form."""
|
|
is_edit = existing is not None
|
|
|
|
# Initialize tool
|
|
if existing:
|
|
tool = Tool(
|
|
name=existing.name,
|
|
description=existing.description,
|
|
arguments=list(existing.arguments),
|
|
steps=list(existing.steps),
|
|
output=existing.output
|
|
)
|
|
else:
|
|
tool = Tool(name="", description="", arguments=[], steps=[], output="{input}")
|
|
|
|
while True:
|
|
# Create form elements
|
|
name_entry = snack.Entry(25, tool.name, scroll=0)
|
|
desc_entry = snack.Entry(25, tool.description, scroll=1)
|
|
output_entry = snack.Entry(25, tool.output, scroll=1)
|
|
|
|
# Arguments listbox
|
|
args_listbox = snack.Listbox(height=4, width=35, returnExit=0, scroll=1)
|
|
for i, arg in enumerate(tool.arguments):
|
|
args_listbox.append(f"{arg.flag} -> {{{arg.variable}}}", i)
|
|
args_listbox.append("[ + Add ]", "add")
|
|
|
|
# Steps listbox
|
|
steps_listbox = snack.Listbox(height=5, width=35, returnExit=0, scroll=1)
|
|
for i, step in enumerate(tool.steps):
|
|
if isinstance(step, PromptStep):
|
|
steps_listbox.append(f"P:{step.provider} -> {{{step.output_var}}}", i)
|
|
else:
|
|
steps_listbox.append(f"C: -> {{{step.output_var}}}", i)
|
|
steps_listbox.append("[ + Add ]", "add")
|
|
|
|
# Build the grid layout using nested grids for better control
|
|
title = f"Edit Tool: {tool.name}" if is_edit and tool.name else "New Tool"
|
|
|
|
# Left column grid: Name, Description, Output
|
|
left_grid = snack.Grid(1, 6)
|
|
left_grid.setField(snack.Label("Name:"), 0, 0, anchorLeft=1)
|
|
left_grid.setField(name_entry, 0, 1, anchorLeft=1)
|
|
left_grid.setField(snack.Label("Description:"), 0, 2, anchorLeft=1, padding=(0, 1, 0, 0))
|
|
left_grid.setField(desc_entry, 0, 3, anchorLeft=1)
|
|
left_grid.setField(snack.Label("Output:"), 0, 4, anchorLeft=1, padding=(0, 1, 0, 0))
|
|
left_grid.setField(output_entry, 0, 5, anchorLeft=1)
|
|
|
|
# Right column grid: Arguments and Steps
|
|
right_grid = snack.Grid(1, 4)
|
|
right_grid.setField(snack.Label("Arguments:"), 0, 0, anchorLeft=1)
|
|
right_grid.setField(args_listbox, 0, 1, anchorLeft=1)
|
|
right_grid.setField(snack.Label("Execution Steps:"), 0, 2, anchorLeft=1, padding=(0, 1, 0, 0))
|
|
right_grid.setField(steps_listbox, 0, 3, anchorLeft=1)
|
|
|
|
# Main grid
|
|
grid = snack.GridForm(self.screen, title, 2, 2)
|
|
grid.add(left_grid, 0, 0, anchorTop=1, padding=(0, 0, 2, 0))
|
|
grid.add(right_grid, 1, 0, anchorTop=1)
|
|
|
|
# Buttons at bottom
|
|
buttons = snack.ButtonBar(self.screen, [("Save", "save"), ("Cancel", "cancel")])
|
|
grid.add(buttons, 0, 1, growx=1)
|
|
|
|
# Handle hotkeys for listbox interaction
|
|
form = grid.form
|
|
|
|
while True:
|
|
result = form.run()
|
|
|
|
# Update tool from entries
|
|
if not is_edit:
|
|
tool.name = name_entry.value().strip()
|
|
tool.description = desc_entry.value().strip()
|
|
tool.output = output_entry.value().strip()
|
|
|
|
# Check what was activated
|
|
if result == args_listbox:
|
|
selected = args_listbox.current()
|
|
if selected == "add":
|
|
new_arg = self.add_argument_dialog()
|
|
if new_arg:
|
|
tool.arguments.append(new_arg)
|
|
break # Refresh form
|
|
elif isinstance(selected, int):
|
|
# Edit/delete existing argument
|
|
action = self.arg_action_menu(tool.arguments[selected])
|
|
if action == "edit":
|
|
updated = self.add_argument_dialog(tool.arguments[selected])
|
|
if updated:
|
|
tool.arguments[selected] = updated
|
|
elif action == "delete":
|
|
tool.arguments.pop(selected)
|
|
break # Refresh form
|
|
|
|
elif result == steps_listbox:
|
|
selected = steps_listbox.current()
|
|
if selected == "add":
|
|
new_step = self.add_step_dialog(tool)
|
|
if new_step:
|
|
tool.steps.append(new_step)
|
|
break # Refresh form
|
|
elif isinstance(selected, int):
|
|
# Edit/delete existing step
|
|
action = self.step_action_menu(tool.steps[selected], selected, len(tool.steps))
|
|
if action == "edit":
|
|
updated = self.add_step_dialog(tool, tool.steps[selected], selected)
|
|
if updated:
|
|
tool.steps[selected] = updated
|
|
elif action == "delete":
|
|
tool.steps.pop(selected)
|
|
elif action == "move_up" and selected > 0:
|
|
tool.steps[selected], tool.steps[selected-1] = tool.steps[selected-1], tool.steps[selected]
|
|
elif action == "move_down" and selected < len(tool.steps) - 1:
|
|
tool.steps[selected], tool.steps[selected+1] = tool.steps[selected+1], tool.steps[selected]
|
|
break # Refresh form
|
|
|
|
elif buttons.buttonPressed(result) == "save":
|
|
if not tool.name:
|
|
self.message_box("Error", "Tool name is required.")
|
|
break
|
|
if not is_edit and tool_exists(tool.name):
|
|
if not self.yes_no("Overwrite?", f"Tool '{tool.name}' exists. Overwrite?"):
|
|
break
|
|
self.screen.popWindow()
|
|
save_tool(tool)
|
|
self.message_box("Success", f"Tool '{tool.name}' saved!")
|
|
return tool
|
|
|
|
elif buttons.buttonPressed(result) == "cancel" or result == "ESC":
|
|
self.screen.popWindow()
|
|
return None
|
|
|
|
else:
|
|
# Tab between fields, continue
|
|
continue
|
|
|
|
self.screen.popWindow()
|
|
|
|
def arg_action_menu(self, arg: ToolArgument) -> Optional[str]:
|
|
"""Show action menu for an argument."""
|
|
listbox = snack.Listbox(height=3, width=20, returnExit=1)
|
|
listbox.append("Edit", "edit")
|
|
listbox.append("Delete", "delete")
|
|
listbox.append("Cancel", "cancel")
|
|
|
|
grid = snack.GridForm(self.screen, f"Argument: {arg.flag}", 1, 1)
|
|
grid.add(listbox, 0, 0)
|
|
|
|
grid.runOnce()
|
|
return listbox.current() if listbox.current() != "cancel" else None
|
|
|
|
def step_action_menu(self, step: Step, idx: int, total: int) -> Optional[str]:
|
|
"""Show action menu for a step."""
|
|
step_type = "Prompt" if isinstance(step, PromptStep) else "Code"
|
|
|
|
items = [("Edit", "edit")]
|
|
if idx > 0:
|
|
items.append(("Move Up", "move_up"))
|
|
if idx < total - 1:
|
|
items.append(("Move Down", "move_down"))
|
|
items.append(("Delete", "delete"))
|
|
items.append(("Cancel", "cancel"))
|
|
|
|
listbox = snack.Listbox(height=len(items), width=20, returnExit=1)
|
|
for label, value in items:
|
|
listbox.append(label, value)
|
|
|
|
grid = snack.GridForm(self.screen, f"Step {idx+1}: {step_type}", 1, 1)
|
|
grid.add(listbox, 0, 0)
|
|
|
|
grid.runOnce()
|
|
return listbox.current() if listbox.current() != "cancel" else None
|
|
|
|
def select_and_edit_tool(self):
|
|
"""Select a tool to edit."""
|
|
tools = list_tools()
|
|
if not tools:
|
|
self.message_box("Edit Tool", "No tools found.")
|
|
return
|
|
|
|
listbox = snack.Listbox(height=min(len(tools), 10), width=40, returnExit=1, scroll=1)
|
|
for name in tools:
|
|
tool = load_tool(name)
|
|
desc = tool.description[:30] if tool and tool.description else "No description"
|
|
listbox.append(f"{name}: {desc}", name)
|
|
|
|
grid = snack.GridForm(self.screen, "Select Tool to Edit", 1, 2)
|
|
grid.add(listbox, 0, 0)
|
|
buttons = snack.ButtonBar(self.screen, [("Select", "ok"), ("Cancel", "cancel")])
|
|
grid.add(buttons, 0, 1)
|
|
|
|
result = grid.runOnce()
|
|
|
|
if buttons.buttonPressed(result) == "ok":
|
|
selected = listbox.current()
|
|
tool = load_tool(selected)
|
|
if tool:
|
|
self.tool_builder(tool)
|
|
|
|
def select_and_delete_tool(self):
|
|
"""Select a tool to delete."""
|
|
tools = list_tools()
|
|
if not tools:
|
|
self.message_box("Delete Tool", "No tools found.")
|
|
return
|
|
|
|
listbox = snack.Listbox(height=min(len(tools), 10), width=40, returnExit=1, scroll=1)
|
|
for name in tools:
|
|
listbox.append(name, name)
|
|
|
|
grid = snack.GridForm(self.screen, "Select Tool to Delete", 1, 2)
|
|
grid.add(listbox, 0, 0)
|
|
buttons = snack.ButtonBar(self.screen, [("Delete", "ok"), ("Cancel", "cancel")])
|
|
grid.add(buttons, 0, 1)
|
|
|
|
result = grid.runOnce()
|
|
|
|
if buttons.buttonPressed(result) == "ok":
|
|
selected = listbox.current()
|
|
if self.yes_no("Confirm", f"Delete tool '{selected}'?"):
|
|
if delete_tool(selected):
|
|
self.message_box("Deleted", f"Tool '{selected}' deleted.")
|
|
|
|
def show_tools_list(self):
|
|
"""Show list of all tools."""
|
|
tools = list_tools()
|
|
if not tools:
|
|
self.message_box("Tools", "No tools found.\n\nCreate one from the main menu.")
|
|
return
|
|
|
|
text = ""
|
|
for name in tools:
|
|
tool = load_tool(name)
|
|
if tool:
|
|
text += f"{name}\n"
|
|
text += f" {tool.description or 'No description'}\n"
|
|
if tool.arguments:
|
|
args = ", ".join(a.flag for a in tool.arguments)
|
|
text += f" Args: {args}\n"
|
|
if tool.steps:
|
|
steps = []
|
|
for s in tool.steps:
|
|
if isinstance(s, PromptStep):
|
|
steps.append(f"P:{s.provider}")
|
|
else:
|
|
steps.append("C")
|
|
text += f" Steps: {' -> '.join(steps)}\n"
|
|
text += "\n"
|
|
|
|
snack.ButtonChoiceWindow(self.screen, "Available Tools", text.strip(), ["OK"])
|
|
|
|
def select_and_test_tool(self):
|
|
"""Select a tool to test."""
|
|
tools = list_tools()
|
|
if not tools:
|
|
self.message_box("Test Tool", "No tools found.")
|
|
return
|
|
|
|
listbox = snack.Listbox(height=min(len(tools), 10), width=40, returnExit=1, scroll=1)
|
|
for name in tools:
|
|
listbox.append(name, name)
|
|
|
|
grid = snack.GridForm(self.screen, "Select Tool to Test", 1, 2)
|
|
grid.add(listbox, 0, 0)
|
|
buttons = snack.ButtonBar(self.screen, [("Test", "ok"), ("Cancel", "cancel")])
|
|
grid.add(buttons, 0, 1)
|
|
|
|
result = grid.runOnce()
|
|
|
|
if buttons.buttonPressed(result) == "ok":
|
|
selected = listbox.current()
|
|
tool = load_tool(selected)
|
|
if tool:
|
|
test_input = self.input_box("Test Input", "Enter test input:", "Hello world")
|
|
if test_input:
|
|
from .runner import run_tool
|
|
output, code = run_tool(
|
|
tool=tool,
|
|
input_text=test_input,
|
|
custom_args={},
|
|
provider_override="mock",
|
|
dry_run=False,
|
|
show_prompt=False,
|
|
verbose=False
|
|
)
|
|
result_text = f"Exit code: {code}\n\nOutput:\n{output[:500]}"
|
|
self.message_box("Test Result", result_text)
|
|
|
|
def manage_providers(self):
|
|
"""Manage providers menu."""
|
|
while True:
|
|
providers = load_providers()
|
|
|
|
listbox = snack.Listbox(height=min(len(providers) + 2, 10), width=50, returnExit=1, scroll=1)
|
|
for p in providers:
|
|
listbox.append(f"{p.name}: {p.command}", p.name)
|
|
listbox.append("[ + Add Provider ]", "__add__")
|
|
listbox.append("[ <- Back ]", "__back__")
|
|
|
|
grid = snack.GridForm(self.screen, "Manage Providers", 1, 1)
|
|
grid.add(listbox, 0, 0)
|
|
|
|
grid.runOnce()
|
|
selected = listbox.current()
|
|
|
|
if selected == "__back__":
|
|
break
|
|
elif selected == "__add__":
|
|
provider = self.add_provider_dialog()
|
|
if provider:
|
|
add_provider(provider)
|
|
self.message_box("Success", f"Provider '{provider.name}' added.")
|
|
else:
|
|
# Edit or delete
|
|
provider = get_provider(selected)
|
|
if provider:
|
|
action = self.provider_action_menu(provider)
|
|
if action == "edit":
|
|
updated = self.add_provider_dialog() # TODO: pass existing
|
|
if updated:
|
|
add_provider(updated)
|
|
elif action == "delete":
|
|
if self.yes_no("Confirm", f"Delete provider '{selected}'?"):
|
|
delete_provider(selected)
|
|
|
|
def provider_action_menu(self, provider: Provider) -> Optional[str]:
|
|
"""Show action menu for a provider."""
|
|
listbox = snack.Listbox(height=3, width=20, returnExit=1)
|
|
listbox.append("Edit", "edit")
|
|
listbox.append("Delete", "delete")
|
|
listbox.append("Cancel", "cancel")
|
|
|
|
grid = snack.GridForm(self.screen, f"Provider: {provider.name}", 1, 1)
|
|
grid.add(listbox, 0, 0)
|
|
|
|
grid.runOnce()
|
|
return listbox.current() if listbox.current() != "cancel" else None
|
|
|
|
|
|
def run_ui():
|
|
"""Entry point for the snack UI."""
|
|
ui = CmdForgeUI()
|
|
ui.run()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
run_ui()
|