diff --git a/build/add_cmds.py b/build/add_cmds.py
new file mode 100755
index 000000000..94a275bd5
--- /dev/null
+++ b/build/add_cmds.py
@@ -0,0 +1,261 @@
+#!/usr/bin/env python3
+import argparse
+import json
+import logging
+import os
+import sys
+
+from components.syntax import Command
+from components.markdown import Markdown
+
+
+def command_filename(name: str) -> str:
+ """Convert command name to filename format."""
+ return name.lower().replace(' ', '-')
+
+
+def parse_args() -> argparse.Namespace:
+ parser = argparse.ArgumentParser(description='Creates new Redis command pages from JSON input')
+ parser.add_argument('json_file', type=str,
+ help='Path to JSON file containing command definitions')
+ parser.add_argument('--loglevel', type=str,
+ default='INFO',
+ help='Python logging level (overwrites LOGLEVEL env var)')
+ return parser.parse_args()
+
+
+def validate_json_structure(data: dict, filename: str) -> None:
+ """Validate that the JSON has the expected structure for Redis commands."""
+ if not isinstance(data, dict):
+ raise ValueError(f"JSON file {filename} must contain a dictionary at root level")
+
+ for command_name, command_data in data.items():
+ if not isinstance(command_data, dict):
+ raise ValueError(f"Command '{command_name}' must be a dictionary")
+
+ # Check for required fields
+ required_fields = ['summary', 'since', 'group']
+ for field in required_fields:
+ if field not in command_data:
+ logging.warning(f"Command '{command_name}' missing recommended field: {field}")
+
+ # Validate arguments structure if present
+ if 'arguments' in command_data:
+ if not isinstance(command_data['arguments'], list):
+ raise ValueError(f"Command '{command_name}' arguments must be a list")
+
+
+def load_and_validate_json(filepath: str) -> dict:
+ """Load and validate the JSON file containing command definitions."""
+ if not os.path.exists(filepath):
+ raise FileNotFoundError(f"JSON file not found: {filepath}")
+
+ try:
+ with open(filepath, 'r') as f:
+ data = json.load(f)
+ except json.JSONDecodeError as e:
+ raise ValueError(f"Invalid JSON in file {filepath}: {e}")
+
+ validate_json_structure(data, filepath)
+ return data
+
+
+def add_standard_categories(fm_data: dict) -> None:
+ """Add the standard categories from create.sh script."""
+ standard_categories = [
+ 'docs', 'develop', 'stack', 'oss', 'rs', 'rc', 'oss', 'kubernetes', 'clients'
+ ]
+ fm_data['categories'] = standard_categories
+
+
+def get_full_command_name(command_name: str, command_data: dict) -> str:
+ """Get the full command name, handling container commands."""
+ container = command_data.get('container')
+ if container:
+ return f"{container} {command_name}"
+ return command_name
+
+
+def generate_command_frontmatter(command_name: str, command_data: dict, all_commands: dict) -> dict:
+ """Generate complete Hugo frontmatter for a command using existing build infrastructure."""
+ # Get the full command name (handles container commands)
+ full_command_name = get_full_command_name(command_name, command_data)
+
+ # Create Command object to generate syntax using the full command name
+ c = Command(full_command_name, command_data)
+
+ # Start with the command data
+ fm_data = command_data.copy()
+
+ # Add required Hugo frontmatter fields
+ fm_data.update({
+ 'title': full_command_name,
+ 'linkTitle': full_command_name,
+ 'description': command_data.get('summary'),
+ 'syntax_str': str(c),
+ 'syntax_fmt': c.syntax(),
+ 'hidden': False # Default to not hidden
+ })
+
+ # Add the standard categories from create.sh
+ add_standard_categories(fm_data)
+
+ return fm_data
+
+
+def generate_argument_sections(command_data: dict) -> str:
+ """Generate placeholder sections for Required arguments and Optional arguments."""
+ content = ""
+
+ arguments = command_data.get('arguments', [])
+ if not arguments:
+ return content
+
+ required_args = []
+ optional_args = []
+
+ # Categorize arguments
+ for arg in arguments:
+ if arg.get('optional', False):
+ optional_args.append(arg)
+ else:
+ required_args.append(arg)
+
+ # Generate Required arguments section
+ if required_args:
+ content += "## Required arguments\n\n"
+ for arg in required_args:
+ arg_name = arg.get('name', 'unknown')
+ arg_type = arg.get('type', 'unknown')
+ display_text = arg.get('display_text', arg_name)
+
+ content += f"{display_text}
\n\n"
+ content += f"TODO: Add description for {arg_name} ({arg_type})\n\n"
+ content += " \n\n"
+
+ # Generate Optional arguments section
+ if optional_args:
+ content += "## Optional arguments\n\n"
+ for arg in optional_args:
+ arg_name = arg.get('name', 'unknown')
+ arg_type = arg.get('type', 'unknown')
+ display_text = arg.get('display_text', arg_name)
+ token = arg.get('token', '')
+
+ content += f"{token if token else display_text}
\n\n"
+ content += f"TODO: Add description for {arg_name} ({arg_type})\n\n"
+ content += " \n\n"
+
+ return content
+
+
+def generate_return_section() -> str:
+ """Generate placeholder Return information section."""
+ return '''## Return information
+
+{{< multitabs id="return-info"
+ tab1="RESP2"
+ tab2="RESP3" >}}
+
+TODO: Add RESP2 return information
+
+-tab-sep-
+
+TODO: Add RESP3 return information
+
+{{< /multitabs >}}
+
+'''
+
+
+def generate_complete_markdown_content(command_name: str, command_data: dict) -> str:
+ """Generate the complete markdown content for a command page."""
+ content = ""
+
+ # Add command summary as the main description
+ summary = command_data.get('summary', f'TODO: Add summary for {command_name}')
+ content += f"{summary}\n\n"
+
+ # Add argument sections
+ content += generate_argument_sections(command_data)
+
+ # Add return information section
+ content += generate_return_section()
+
+ return content
+
+
+def create_command_file(command_name: str, command_data: dict, all_commands: dict) -> str:
+ """Create a complete command markdown file with frontmatter and content."""
+ # Get the full command name (handles container commands)
+ full_command_name = get_full_command_name(command_name, command_data)
+
+ # Generate the file path using the full command name
+ filename = command_filename(full_command_name)
+ filepath = f'content/commands/{filename}.md'
+
+ # Ensure the directory exists
+ os.makedirs(os.path.dirname(filepath), exist_ok=True)
+
+ # Check if file already exists
+ if os.path.exists(filepath):
+ logging.warning(f"File {filepath} already exists, skipping...")
+ return filepath
+
+ # Generate frontmatter
+ frontmatter_data = generate_command_frontmatter(command_name, command_data, all_commands)
+
+ # Generate content
+ content = generate_complete_markdown_content(command_name, command_data)
+
+ # Create markdown object and set data
+ md = Markdown(filepath)
+ md.fm_data = frontmatter_data
+ md.payload = content
+
+ # Write the file
+ md.persist()
+
+ logging.info(f"Created command file: {filepath}")
+ return filepath
+
+
+if __name__ == '__main__':
+ args = parse_args()
+
+ # Configure logging BEFORE creating objects
+ log_level = getattr(logging, args.loglevel.upper())
+ logging.basicConfig(
+ level=log_level,
+ format='%(message)s %(filename)s:%(lineno)d - %(funcName)s',
+ force=True # Force reconfiguration in case logging was already configured
+ )
+
+ try:
+ # Load and validate JSON data
+ commands_data = load_and_validate_json(args.json_file)
+ logging.info(f"Loaded {len(commands_data)} commands from {args.json_file}")
+
+ # Process each command and generate markdown files
+ created_files = []
+ for command_name in commands_data:
+ try:
+ logging.info(f"Processing command: {command_name}")
+ filepath = create_command_file(command_name, commands_data[command_name], commands_data)
+ created_files.append(filepath)
+ except Exception as e:
+ logging.error(f"Failed to create file for command '{command_name}': {e}")
+ # Continue processing other commands
+ continue
+
+ # Summary
+ logging.info(f"Successfully created {len(created_files)} command files:")
+ for filepath in created_files:
+ logging.info(f" - {filepath}")
+
+ except (FileNotFoundError, ValueError) as e:
+ logging.error(f"Error: {e}")
+ sys.exit(1)
+ except Exception as e:
+ logging.error(f"Unexpected error: {e}")
+ sys.exit(1)