ASH is a flexible, command-line tool designed to guide users through a series of steps defined in "recipes." It's perfect for standardizing complex workflows, creating interactive checklists, or automating multi-step processes that require user input or actions at specific points.
- Recipe-Driven Workflow: Define a sequence of steps in simple JSON files.
- Custom Step Display: Each step can have unique title to help user understand step purpose.
- Interactive Steps: Recipes can be simple checklists or can execute custom Python code.
- Dynamic Code Loading: Automatically loads and runs functions from a Python file associated with your recipe.
- User-Friendly Interface: Built with
richfor a clean, modern terminal experience. - GUI Integration: Provides a safe, built-in helper to launch GUI dialogs (like file pickers) from your recipes.
- Recipe Context: Share data between steps using the built-in
recipe_contextsystem. - Version Management: Automatic upgrades from legacy formats with backup preservation.
- Enhanced Error Handling: Detailed error reporting with file names and line numbers.
See INSTALL.md for detailed installation instructions.
Before creating your own recipes, check out the comprehensive examples in the examples/ folder:
# Copy the example to your recipes folder
cp examples/example.* recipes/
python ash_menu.pyThe example recipe demonstrates all major ASH features including user prompts, file dialogs, error handling, and rich formatting.
To run the application, simply execute the ash_menu.py script from your terminal:
python ash_menu.pyThis will display a menu of all available recipes found in the recipes/ directory.
ASH is built around the concept of recipes. A recipe consists of two parts:
- A JSON file (
.json): This is the core of the recipe. It defines the sequence of steps, including the instructions to show the user and (optionally) the name of a Python function to run. - A Python file (
.py): This file is optional. It contains the actual Python functions that are called by the recipe steps. If a recipe is just a simple checklist, you don't need a.pyfile.
When you select a recipe from the menu, the execution engine reads the JSON file and proceeds step-by-step, displaying the instructions and executing any associated code.
To create a new recipe, you'll typically create two files in the recipes/ directory: my_recipe.json and my_recipe.py.
The JSON file uses the modern v1.1 flat format. Simply add your title, description, and step properties directly to the object.
Key fields for a recipe:
version: Always set to "1.1" for the current formattitle: The recipe name displayed in the menudescription: Brief description of what the recipe doescolor(optional): A color name (e.g., "blue"), hex code, or "rainbow" to style the menu panel.color_end(optional): A second color to create a gradient effect withcolor.step1,step2,step3, etc.: Your recipe steps as direct properties
Key fields for a step:
statement: The instruction or information to display to the user for this step.function_name(optional): The name of the function to run from the corresponding.pyfile.prompt_for(optional): A dictionary to prompt the user for input. The key is the function parameter name, and the value is the prompt text.
Example: my_recipe.json
{
"version": "1.1",
"title": "File Processing Example",
"description": "Demonstrates recipe features: text steps, user prompts, and file dialogs",
"color": "cyan",
"color_end": "magenta",
"step1": {
"statement": "Welcome to ASH! This step just displays text - press Enter to continue."
},
"step2": {
"statement": "Now we'll prompt you for some information.",
"function_name": "get_user_info",
"prompt_for": {
"name": "What's your name?",
"project": "What project are you working on?"
}
},
"step3": {
"statement": "Let's open a file dialog to select a file.",
"function_name": "select_file"
},
"step4": {
"statement": "Recipe complete! Check the console output above."
}
}This file contains the functions your recipe will execute.
Example: my_recipe.py
from tkinter import filedialog
def get_user_info(name: str, project: str, console, recipe_context):
"""
Collect user information and store it in recipe context.
"""
console.print(f"[bold green]Hello {name}![/bold green]")
console.print(f"[cyan]Working on project: {project}[/cyan]")
# Store data for later steps
recipe_context['variables']['user_name'] = name
recipe_context['variables']['project_name'] = project
return True
def select_file(console, run_tk_dialog, recipe_context):
"""
Open a file dialog and display the selected file.
"""
# Get user name from previous step
user_name = recipe_context['variables'].get('user_name', 'User')
file_path = run_tk_dialog(
filedialog.askopenfilename,
title=f"Select a file for {user_name}"
)
if file_path:
console.print(f"[bold green]Selected file:[/] [italic white]{file_path}[/italic white]")
recipe_context['variables']['selected_file'] = file_path
return True
else:
console.print("[bold red]No file selected.[/bold red]")
return FalseTo make recipe creation easier and safer, the execution engine can automatically "inject" helpful utilities into your functions if they are listed as parameters:
console: The application'srich.console.Consoleobject for consistent, styled output.run_tk_dialog: A safe wrapper for runningtkinterdialogs. It handles creating and destroying thetkinterroot window and ensures the dialog appears on top of other windows.recipe_context: A shared dictionary for storing data between steps with structured sections:recipe_context['variables']: For storing user data, file paths, and other valuesrecipe_context['metadata']: For recipe information and step tracking
The recipe_context allows you to share data between steps:
def step_one(console, recipe_context):
# Store data for later steps
recipe_context['variables']['user_input'] = "some value"
recipe_context['variables']['file_path'] = "/path/to/file"
def step_two(console, recipe_context):
# Access data from previous steps
user_input = recipe_context['variables'].get('user_input')
file_path = recipe_context['variables'].get('file_path')
console.print(f"Processing {file_path} with {user_input}")ASH supports multiple recipe formats for backward compatibility:
{
"version": "1.1",
"title": "Recipe Name",
"description": "What it does",
"color": "yellow",
"color_end": "green",
"step1": {"statement": "First step"},
"step2": {"statement": "Second step"}
}[
{"title": "Recipe Name", "description": "What it does"},
{"statement": "First step"},
{"statement": "Second step"}
]Automatic Upgrades: Legacy v1.0 recipes are automatically upgraded to v1.1 format when loaded, with backups preserved.
The main ASH repository is designed to be public, but your recipes might be private. The project is set up to support this workflow easily.
The root .gitignore file is configured to ignore the entire /recipes/ directory. This allows you to create a completely separate, private Git repository inside the recipes folder.
- Ignore
/recipes/: The main repo's.gitignorealready does this. - Create a private repo: Create a new private repository on GitHub (e.g.,
my-ash-recipes). - Initialize Git in
recipes/:cd recipes git init git remote add origin <your_private_repo_url> git add . git commit -m "Initial commit" git push -u origin main
Now you can manage your application code and your private recipes in two different repositories, even though they live in the same project folder on your local machine.
