Initial commit
This commit is contained in:
151
skills/cli-demo-generator/scripts/auto_generate_demo.py
Executable file
151
skills/cli-demo-generator/scripts/auto_generate_demo.py
Executable file
@@ -0,0 +1,151 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Auto-generate CLI demos from command descriptions.
|
||||
|
||||
This script creates VHS tape files and generates GIF demos automatically.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import List, Optional
|
||||
|
||||
|
||||
def create_tape_file(
|
||||
commands: List[str],
|
||||
output_gif: str,
|
||||
title: Optional[str] = None,
|
||||
theme: str = "Dracula",
|
||||
font_size: int = 16,
|
||||
width: int = 1400,
|
||||
height: int = 700,
|
||||
padding: int = 20,
|
||||
) -> str:
|
||||
"""Generate a VHS tape file from commands."""
|
||||
|
||||
tape_lines = [
|
||||
f'Output {output_gif}',
|
||||
'',
|
||||
f'Set FontSize {font_size}',
|
||||
f'Set Width {width}',
|
||||
f'Set Height {height}',
|
||||
f'Set Theme "{theme}"',
|
||||
f'Set Padding {padding}',
|
||||
'',
|
||||
]
|
||||
|
||||
# Add title if provided
|
||||
if title:
|
||||
tape_lines.extend([
|
||||
f'Type "# {title}" Sleep 500ms Enter',
|
||||
'Sleep 1s',
|
||||
'',
|
||||
])
|
||||
|
||||
# Add commands with smart timing
|
||||
for i, cmd in enumerate(commands, 1):
|
||||
# Type the command
|
||||
tape_lines.append(f'Type "{cmd}" Sleep 500ms')
|
||||
tape_lines.append('Enter')
|
||||
|
||||
# Smart sleep based on command complexity
|
||||
if any(keyword in cmd.lower() for keyword in ['install', 'build', 'test', 'deploy']):
|
||||
sleep_time = '3s'
|
||||
elif any(keyword in cmd.lower() for keyword in ['ls', 'pwd', 'echo', 'cat']):
|
||||
sleep_time = '1s'
|
||||
else:
|
||||
sleep_time = '2s'
|
||||
|
||||
tape_lines.append(f'Sleep {sleep_time}')
|
||||
|
||||
# Add spacing between commands
|
||||
if i < len(commands):
|
||||
tape_lines.append('')
|
||||
|
||||
return '\n'.join(tape_lines)
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description='Auto-generate CLI demos from commands',
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
epilog='''
|
||||
Examples:
|
||||
# Generate demo from single command
|
||||
%(prog)s -c "npm install" -o demo.gif
|
||||
|
||||
# Generate demo with multiple commands
|
||||
%(prog)s -c "git clone repo" -c "cd repo" -c "npm install" -o setup.gif
|
||||
|
||||
# Custom theme and size
|
||||
%(prog)s -c "ls -la" -o demo.gif --theme Monokai --width 1200
|
||||
|
||||
# With title
|
||||
%(prog)s -c "echo Hello" -o demo.gif --title "My Demo"
|
||||
'''
|
||||
)
|
||||
|
||||
parser.add_argument('-c', '--command', action='append', required=True,
|
||||
help='Command to include in demo (can be specified multiple times)')
|
||||
parser.add_argument('-o', '--output', required=True,
|
||||
help='Output GIF file path')
|
||||
parser.add_argument('--title', help='Demo title (optional)')
|
||||
parser.add_argument('--theme', default='Dracula',
|
||||
help='VHS theme (default: Dracula)')
|
||||
parser.add_argument('--font-size', type=int, default=16,
|
||||
help='Font size (default: 16)')
|
||||
parser.add_argument('--width', type=int, default=1400,
|
||||
help='Terminal width (default: 1400)')
|
||||
parser.add_argument('--height', type=int, default=700,
|
||||
help='Terminal height (default: 700)')
|
||||
parser.add_argument('--no-execute', action='store_true',
|
||||
help='Generate tape file only, do not execute VHS')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
# Generate tape file content
|
||||
tape_content = create_tape_file(
|
||||
commands=args.command,
|
||||
output_gif=args.output,
|
||||
title=args.title,
|
||||
theme=args.theme,
|
||||
font_size=args.font_size,
|
||||
width=args.width,
|
||||
height=args.height,
|
||||
)
|
||||
|
||||
# Write tape file
|
||||
output_path = Path(args.output)
|
||||
tape_file = output_path.with_suffix('.tape')
|
||||
|
||||
with open(tape_file, 'w') as f:
|
||||
f.write(tape_content)
|
||||
|
||||
print(f"✓ Generated tape file: {tape_file}")
|
||||
|
||||
if not args.no_execute:
|
||||
# Check if VHS is installed
|
||||
try:
|
||||
subprocess.run(['vhs', '--version'], capture_output=True, check=True)
|
||||
except (subprocess.CalledProcessError, FileNotFoundError):
|
||||
print("✗ VHS is not installed!", file=sys.stderr)
|
||||
print("Install it with: brew install vhs", file=sys.stderr)
|
||||
print(f"✓ You can manually run: vhs < {tape_file}", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
# Execute VHS
|
||||
print(f"Generating GIF: {args.output}")
|
||||
try:
|
||||
subprocess.run(['vhs', str(tape_file)], check=True)
|
||||
print(f"✓ Demo generated: {args.output}")
|
||||
print(f" Size: {output_path.stat().st_size / 1024:.1f} KB")
|
||||
except subprocess.CalledProcessError as e:
|
||||
print(f"✗ VHS execution failed: {e}", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main())
|
||||
175
skills/cli-demo-generator/scripts/batch_generate.py
Executable file
175
skills/cli-demo-generator/scripts/batch_generate.py
Executable file
@@ -0,0 +1,175 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Batch generate multiple CLI demos from a configuration file.
|
||||
|
||||
Supports YAML and JSON formats for defining multiple demos.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import Dict, List
|
||||
|
||||
try:
|
||||
import yaml
|
||||
YAML_AVAILABLE = True
|
||||
except ImportError:
|
||||
YAML_AVAILABLE = False
|
||||
|
||||
|
||||
def load_config(config_file: Path) -> Dict:
|
||||
"""Load demo configuration from YAML or JSON file."""
|
||||
suffix = config_file.suffix.lower()
|
||||
|
||||
with open(config_file) as f:
|
||||
if suffix in ['.yaml', '.yml']:
|
||||
if not YAML_AVAILABLE:
|
||||
print("Error: PyYAML not installed. Install with: pip install pyyaml", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
return yaml.safe_load(f)
|
||||
elif suffix == '.json':
|
||||
return json.load(f)
|
||||
else:
|
||||
print(f"Error: Unsupported config format: {suffix}", file=sys.stderr)
|
||||
print("Supported formats: .yaml, .yml, .json", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def generate_demo(demo_config: Dict, base_path: Path, script_path: Path) -> bool:
|
||||
"""Generate a single demo from configuration."""
|
||||
name = demo_config.get('name', 'unnamed')
|
||||
output = demo_config.get('output')
|
||||
commands = demo_config.get('commands', [])
|
||||
|
||||
if not output or not commands:
|
||||
print(f"✗ Skipping '{name}': missing output or commands", file=sys.stderr)
|
||||
return False
|
||||
|
||||
# Build command
|
||||
cmd = [sys.executable, str(script_path)]
|
||||
|
||||
for command in commands:
|
||||
cmd.extend(['-c', command])
|
||||
|
||||
cmd.extend(['-o', str(base_path / output)])
|
||||
|
||||
# Optional parameters
|
||||
if 'title' in demo_config:
|
||||
cmd.extend(['--title', demo_config['title']])
|
||||
if 'theme' in demo_config:
|
||||
cmd.extend(['--theme', demo_config['theme']])
|
||||
if 'width' in demo_config:
|
||||
cmd.extend(['--width', str(demo_config['width'])])
|
||||
if 'height' in demo_config:
|
||||
cmd.extend(['--height', str(demo_config['height'])])
|
||||
|
||||
print(f"\n{'='*60}")
|
||||
print(f"Generating: {name}")
|
||||
print(f"Output: {output}")
|
||||
print(f"Commands: {len(commands)}")
|
||||
print(f"{'='*60}")
|
||||
|
||||
try:
|
||||
subprocess.run(cmd, check=True)
|
||||
return True
|
||||
except subprocess.CalledProcessError as e:
|
||||
print(f"✗ Failed to generate '{name}': {e}", file=sys.stderr)
|
||||
return False
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description='Batch generate CLI demos from configuration file',
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
epilog='''
|
||||
Configuration file format (YAML):
|
||||
demos:
|
||||
- name: "Install Demo"
|
||||
output: "install.gif"
|
||||
title: "Installation"
|
||||
theme: "Dracula"
|
||||
commands:
|
||||
- "npm install my-package"
|
||||
- "npm run build"
|
||||
|
||||
- name: "Usage Demo"
|
||||
output: "usage.gif"
|
||||
commands:
|
||||
- "my-package --help"
|
||||
- "my-package run"
|
||||
|
||||
Configuration file format (JSON):
|
||||
{
|
||||
"demos": [
|
||||
{
|
||||
"name": "Install Demo",
|
||||
"output": "install.gif",
|
||||
"commands": ["npm install"]
|
||||
}
|
||||
]
|
||||
}
|
||||
'''
|
||||
)
|
||||
|
||||
parser.add_argument('config', type=Path,
|
||||
help='Configuration file (.yaml, .yml, or .json)')
|
||||
parser.add_argument('--output-dir', type=Path, default=Path.cwd(),
|
||||
help='Output directory for generated demos')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if not args.config.exists():
|
||||
print(f"Error: Config file not found: {args.config}", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
# Load configuration
|
||||
config = load_config(args.config)
|
||||
demos = config.get('demos', [])
|
||||
|
||||
if not demos:
|
||||
print("Error: No demos defined in configuration", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
# Create output directory
|
||||
args.output_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# Find auto_generate_demo.py script
|
||||
script_path = Path(__file__).parent / 'auto_generate_demo.py'
|
||||
if not script_path.exists():
|
||||
print(f"Error: auto_generate_demo.py not found at {script_path}", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
# Generate demos
|
||||
total = len(demos)
|
||||
successful = 0
|
||||
failed = 0
|
||||
|
||||
print(f"\n{'='*60}")
|
||||
print(f"Starting batch generation: {total} demos")
|
||||
print(f"Output directory: {args.output_dir}")
|
||||
print(f"{'='*60}\n")
|
||||
|
||||
for i, demo in enumerate(demos, 1):
|
||||
print(f"\n[{i}/{total}] Processing: {demo.get('name', 'unnamed')}")
|
||||
if generate_demo(demo, args.output_dir, script_path):
|
||||
successful += 1
|
||||
else:
|
||||
failed += 1
|
||||
|
||||
# Summary
|
||||
print(f"\n{'='*60}")
|
||||
print(f"Batch generation complete!")
|
||||
print(f"{'='*60}")
|
||||
print(f"✓ Successful: {successful}")
|
||||
if failed > 0:
|
||||
print(f"✗ Failed: {failed}")
|
||||
print(f"Total: {total}")
|
||||
print(f"{'='*60}\n")
|
||||
|
||||
return 0 if failed == 0 else 1
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main())
|
||||
136
skills/cli-demo-generator/scripts/record_interactive.sh
Executable file
136
skills/cli-demo-generator/scripts/record_interactive.sh
Executable file
@@ -0,0 +1,136 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# Record interactive CLI demos using asciinema and convert to GIF
|
||||
#
|
||||
# Usage:
|
||||
# record_interactive.sh output.gif
|
||||
# record_interactive.sh output.gif --theme Dracula --width 1200
|
||||
#
|
||||
|
||||
set -e
|
||||
|
||||
# Colors
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m'
|
||||
|
||||
# Default values
|
||||
OUTPUT=""
|
||||
THEME="Dracula"
|
||||
WIDTH=1400
|
||||
HEIGHT=700
|
||||
FONT_SIZE=16
|
||||
|
||||
# Parse arguments
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case $1 in
|
||||
--theme)
|
||||
THEME="$2"
|
||||
shift 2
|
||||
;;
|
||||
--width)
|
||||
WIDTH="$2"
|
||||
shift 2
|
||||
;;
|
||||
--height)
|
||||
HEIGHT="$2"
|
||||
shift 2
|
||||
;;
|
||||
--font-size)
|
||||
FONT_SIZE="$2"
|
||||
shift 2
|
||||
;;
|
||||
*)
|
||||
OUTPUT="$1"
|
||||
shift
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [ -z "$OUTPUT" ]; then
|
||||
echo -e "${RED}Error: Output file required${NC}" >&2
|
||||
echo "Usage: $0 output.gif [--theme Theme] [--width 1200] [--height 700]"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check dependencies
|
||||
if ! command -v asciinema &> /dev/null; then
|
||||
echo -e "${RED}Error: asciinema not installed${NC}" >&2
|
||||
echo "Install it with:"
|
||||
echo " macOS: brew install asciinema"
|
||||
echo " Linux: sudo apt install asciinema"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! command -v vhs &> /dev/null; then
|
||||
echo -e "${RED}Error: VHS not installed${NC}" >&2
|
||||
echo "Install it with: brew install vhs"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Generate temp files
|
||||
CAST_FILE="${OUTPUT%.gif}.cast"
|
||||
TAPE_FILE="${OUTPUT%.gif}.tape"
|
||||
|
||||
echo -e "${GREEN}===========================================================${NC}"
|
||||
echo -e "${GREEN}Interactive Demo Recording${NC}"
|
||||
echo -e "${GREEN}===========================================================${NC}"
|
||||
echo ""
|
||||
echo -e "${YELLOW}Instructions:${NC}"
|
||||
echo "1. Type your commands naturally"
|
||||
echo "2. Press ENTER after each command"
|
||||
echo "3. Press Ctrl+D when finished"
|
||||
echo ""
|
||||
echo -e "${YELLOW}Output:${NC} $OUTPUT"
|
||||
echo -e "${YELLOW}Theme:${NC} $THEME"
|
||||
echo -e "${YELLOW}Size:${NC} ${WIDTH}x${HEIGHT}"
|
||||
echo ""
|
||||
echo -e "${GREEN}Starting recording in 3 seconds...${NC}"
|
||||
sleep 3
|
||||
echo ""
|
||||
|
||||
# Record with asciinema
|
||||
asciinema rec "$CAST_FILE"
|
||||
|
||||
echo ""
|
||||
echo -e "${GREEN}✓ Recording saved to: $CAST_FILE${NC}"
|
||||
echo ""
|
||||
echo -e "${YELLOW}Converting to GIF...${NC}"
|
||||
|
||||
# Convert asciinema cast to VHS tape format
|
||||
cat > "$TAPE_FILE" << EOF
|
||||
Output $OUTPUT
|
||||
|
||||
Set FontSize $FONT_SIZE
|
||||
Set Width $WIDTH
|
||||
Set Height $HEIGHT
|
||||
Set Theme "$THEME"
|
||||
Set Padding 20
|
||||
|
||||
Play $CAST_FILE
|
||||
EOF
|
||||
|
||||
echo -e "${GREEN}✓ Generated tape file: $TAPE_FILE${NC}"
|
||||
|
||||
# Generate GIF with VHS
|
||||
vhs < "$TAPE_FILE"
|
||||
|
||||
if [ -f "$OUTPUT" ]; then
|
||||
FILE_SIZE=$(du -h "$OUTPUT" | cut -f1)
|
||||
echo ""
|
||||
echo -e "${GREEN}===========================================================${NC}"
|
||||
echo -e "${GREEN}✓ Demo generated successfully!${NC}"
|
||||
echo -e "${GREEN}===========================================================${NC}"
|
||||
echo -e "${YELLOW}Output:${NC} $OUTPUT"
|
||||
echo -e "${YELLOW}Size:${NC} $FILE_SIZE"
|
||||
echo ""
|
||||
echo "Generated files:"
|
||||
echo " - $CAST_FILE (asciinema recording)"
|
||||
echo " - $TAPE_FILE (VHS tape file)"
|
||||
echo " - $OUTPUT (GIF demo)"
|
||||
echo ""
|
||||
else
|
||||
echo -e "${RED}✗ Failed to generate GIF${NC}" >&2
|
||||
exit 1
|
||||
fi
|
||||
Reference in New Issue
Block a user