439 lines
13 KiB
Python
Executable File
439 lines
13 KiB
Python
Executable File
#!/usr/bin/env python3
|
||
"""
|
||
Go Script Generator for Claude Code Skills
|
||
|
||
Analyzes operation requirements and generates efficient Go scripts for
|
||
deterministic operations that don't require agent interaction.
|
||
|
||
Usage:
|
||
generate_go_script.py --name <script-name> --description <desc> \
|
||
--input <input-desc> --output <output-desc> \
|
||
--logic <logic-desc> --skill-path <path>
|
||
|
||
Example:
|
||
generate_go_script.py \
|
||
--name pdf-to-images \
|
||
--description "Convert PDF pages to PNG images" \
|
||
--input "PDF file path" \
|
||
--output "Directory of PNG files" \
|
||
--logic "Extract each page as separate image at 300 DPI" \
|
||
--skill-path ./my-skill
|
||
"""
|
||
|
||
import argparse
|
||
import sys
|
||
from pathlib import Path
|
||
from textwrap import dedent
|
||
|
||
# Go script template with best practices
|
||
GO_SCRIPT_TEMPLATE = '''package main
|
||
|
||
import (
|
||
"flag"
|
||
"fmt"
|
||
"log"
|
||
"os"
|
||
{extra_imports}
|
||
)
|
||
|
||
// {description}
|
||
// Generated by meta-skill-generator for Claude Code skills
|
||
|
||
var (
|
||
verbose = flag.Bool("verbose", false, "Enable verbose logging")
|
||
help = flag.Bool("help", false, "Show this help message")
|
||
)
|
||
|
||
func main() {{
|
||
flag.Usage = usage
|
||
flag.Parse()
|
||
|
||
if *help {{
|
||
usage()
|
||
os.Exit(0)
|
||
}}
|
||
|
||
if *verbose {{
|
||
log.SetFlags(log.Ldate | log.Ltime | log.Lshortfile)
|
||
}} else {{
|
||
log.SetFlags(0)
|
||
}}
|
||
|
||
// Validate command line arguments
|
||
if err := validateArgs(); err != nil {{
|
||
fmt.Fprintf(os.Stderr, "Error: %v\\n", err)
|
||
usage()
|
||
os.Exit(2)
|
||
}}
|
||
|
||
// Execute main logic
|
||
if err := run(); err != nil {{
|
||
fmt.Fprintf(os.Stderr, "Error: %v\\n", err)
|
||
os.Exit(1)
|
||
}}
|
||
}}
|
||
|
||
func usage() {{
|
||
fmt.Fprintf(os.Stderr, "Usage: %s [options] {usage_args}\\n", os.Args[0])
|
||
fmt.Fprintf(os.Stderr, "\\n{description}\\n\\n")
|
||
fmt.Fprintf(os.Stderr, "Input: {input_desc}\\n")
|
||
fmt.Fprintf(os.Stderr, "Output: {output_desc}\\n\\n")
|
||
fmt.Fprintf(os.Stderr, "Options:\\n")
|
||
flag.PrintDefaults()
|
||
}}
|
||
|
||
func validateArgs() error {{
|
||
{validation_logic}
|
||
return nil
|
||
}}
|
||
|
||
func run() error {{
|
||
if *verbose {{
|
||
log.Println("Starting {name}...")
|
||
}}
|
||
|
||
{run_logic}
|
||
|
||
if *verbose {{
|
||
log.Println("Completed successfully")
|
||
}}
|
||
return nil
|
||
}}
|
||
|
||
{helper_functions}
|
||
'''
|
||
|
||
def infer_imports(logic_desc):
|
||
"""Infer required Go imports from operation description."""
|
||
imports = []
|
||
logic_lower = logic_desc.lower()
|
||
|
||
if any(word in logic_lower for word in ['file', 'directory', 'path', 'copy', 'move']):
|
||
imports.append('"io"')
|
||
imports.append('"path/filepath"')
|
||
|
||
if any(word in logic_lower for word in ['json', 'parse']):
|
||
imports.append('"encoding/json"')
|
||
|
||
if any(word in logic_lower for word in ['csv', 'comma']):
|
||
imports.append('"encoding/csv"')
|
||
|
||
if any(word in logic_lower for word in ['http', 'api', 'request']):
|
||
imports.append('"net/http"')
|
||
|
||
if any(word in logic_lower for word in ['string', 'text', 'replace']):
|
||
imports.append('"strings"')
|
||
|
||
if any(word in logic_lower for word in ['regex', 'pattern', 'match']):
|
||
imports.append('"regexp"')
|
||
|
||
if any(word in logic_lower for word in ['time', 'date', 'duration']):
|
||
imports.append('"time"')
|
||
|
||
if any(word in logic_lower for word in ['concurrent', 'parallel', 'goroutine']):
|
||
imports.append('"sync"')
|
||
|
||
if any(word in logic_lower for word in ['buffer', 'bytes']):
|
||
imports.append('"bytes"')
|
||
|
||
return imports
|
||
|
||
def generate_validation_logic(input_desc):
|
||
"""Generate input validation logic."""
|
||
validations = []
|
||
|
||
if 'file' in input_desc.lower():
|
||
validations.append(dedent('''
|
||
if flag.NArg() < 1 {
|
||
return fmt.Errorf("input file required")
|
||
}
|
||
inputFile := flag.Arg(0)
|
||
if _, err := os.Stat(inputFile); os.IsNotExist(err) {
|
||
return fmt.Errorf("input file does not exist: %s", inputFile)
|
||
}
|
||
''').strip())
|
||
|
||
if 'directory' in input_desc.lower():
|
||
validations.append(dedent('''
|
||
if flag.NArg() < 1 {
|
||
return fmt.Errorf("input directory required")
|
||
}
|
||
inputDir := flag.Arg(0)
|
||
if info, err := os.Stat(inputDir); os.IsNotExist(err) || !info.IsDir() {
|
||
return fmt.Errorf("input directory does not exist: %s", inputDir)
|
||
}
|
||
''').strip())
|
||
|
||
return '\n\t'.join(validations) if validations else '// No validation needed'
|
||
|
||
def generate_run_logic(logic_desc, input_desc, output_desc):
|
||
"""Generate main execution logic with placeholder."""
|
||
logic_lower = logic_desc.lower()
|
||
|
||
# Start with input handling
|
||
logic = []
|
||
|
||
if 'file' in input_desc.lower():
|
||
logic.append('inputFile := flag.Arg(0)')
|
||
logic.append('')
|
||
|
||
if 'directory' in input_desc.lower():
|
||
logic.append('inputDir := flag.Arg(0)')
|
||
logic.append('')
|
||
|
||
# Add operation-specific logic template
|
||
logic.append('// TODO: Implement the following logic:')
|
||
logic.append(f'// {logic_desc}')
|
||
logic.append('')
|
||
|
||
if any(word in logic_lower for word in ['convert', 'transform', 'process']):
|
||
logic.append(dedent('''
|
||
// 1. Read input
|
||
// 2. Process/transform data
|
||
// 3. Write output
|
||
''').strip())
|
||
|
||
if 'parallel' in logic_lower or 'concurrent' in logic_lower:
|
||
logic.append(dedent('''
|
||
// Consider using goroutines for parallel processing:
|
||
// var wg sync.WaitGroup
|
||
// for _, item := range items {
|
||
// wg.Add(1)
|
||
// go func(item Item) {
|
||
// defer wg.Done()
|
||
// // Process item
|
||
// }(item)
|
||
// }
|
||
// wg.Wait()
|
||
''').strip())
|
||
|
||
# Add output handling
|
||
if 'stdout' in output_desc.lower():
|
||
logic.append('')
|
||
logic.append('// Write results to stdout')
|
||
logic.append('fmt.Println(result)')
|
||
elif 'file' in output_desc.lower():
|
||
logic.append('')
|
||
logic.append('outputFile := "output.txt" // TODO: Make configurable')
|
||
logic.append('if err := os.WriteFile(outputFile, []byte(result), 0644); err != nil {')
|
||
logic.append(' return fmt.Errorf("failed to write output: %w", err)')
|
||
logic.append('}')
|
||
|
||
logic.append('')
|
||
logic.append('return nil')
|
||
|
||
return '\n\t'.join(logic)
|
||
|
||
def generate_helper_functions(logic_desc):
|
||
"""Generate helper function templates."""
|
||
helpers = []
|
||
logic_lower = logic_desc.lower()
|
||
|
||
if 'progress' in logic_lower or 'batch' in logic_lower:
|
||
helpers.append(dedent('''
|
||
func showProgress(current, total int) {
|
||
if total > 0 {
|
||
percent := float64(current) / float64(total) * 100
|
||
fmt.Fprintf(os.Stderr, "\\rProgress: %.1f%% (%d/%d)", percent, current, total)
|
||
if current == total {
|
||
fmt.Fprintln(os.Stderr)
|
||
}
|
||
}
|
||
}
|
||
''').strip())
|
||
|
||
if 'validate' in logic_lower or 'check' in logic_lower:
|
||
helpers.append(dedent('''
|
||
func validateInput(data interface{}) error {
|
||
// TODO: Implement validation logic
|
||
return nil
|
||
}
|
||
''').strip())
|
||
|
||
return '\n\n'.join(helpers) if helpers else '// No helper functions needed'
|
||
|
||
def determine_usage_args(input_desc):
|
||
"""Determine usage string from input description."""
|
||
if 'file' in input_desc.lower():
|
||
return '<input-file>'
|
||
elif 'directory' in input_desc.lower():
|
||
return '<input-dir>'
|
||
else:
|
||
return '<input>'
|
||
|
||
def generate_go_script(name, description, input_desc, output_desc, logic_desc, skill_path):
|
||
"""Generate a complete Go script."""
|
||
|
||
# Infer what imports we need
|
||
extra_imports = '\n\t'.join(infer_imports(logic_desc))
|
||
|
||
# Generate different sections
|
||
validation_logic = generate_validation_logic(input_desc)
|
||
run_logic = generate_run_logic(logic_desc, input_desc, output_desc)
|
||
helper_functions = generate_helper_functions(logic_desc)
|
||
usage_args = determine_usage_args(input_desc)
|
||
|
||
# Fill in template
|
||
script_content = GO_SCRIPT_TEMPLATE.format(
|
||
description=description,
|
||
name=name,
|
||
input_desc=input_desc,
|
||
output_desc=output_desc,
|
||
usage_args=usage_args,
|
||
extra_imports=extra_imports,
|
||
validation_logic=validation_logic,
|
||
run_logic=run_logic,
|
||
helper_functions=helper_functions
|
||
)
|
||
|
||
return script_content
|
||
|
||
def create_build_script(skill_path, script_name):
|
||
"""Create a build script to compile the Go binary."""
|
||
build_script = f'''#!/bin/bash
|
||
# Build script for {script_name}
|
||
|
||
set -e
|
||
|
||
SCRIPT_DIR="$(cd "$(dirname "${{BASH_SOURCE[0]}}")" && pwd)"
|
||
BIN_DIR="$SCRIPT_DIR/bin"
|
||
|
||
mkdir -p "$BIN_DIR"
|
||
|
||
echo "Building {script_name}..."
|
||
cd "$SCRIPT_DIR"
|
||
go build -o "$BIN_DIR/{script_name}" {script_name}.go
|
||
|
||
echo "✅ Built: $BIN_DIR/{script_name}"
|
||
echo "Run with: $BIN_DIR/{script_name} --help"
|
||
'''
|
||
|
||
build_path = skill_path / 'scripts' / f'build_{script_name}.sh'
|
||
build_path.write_text(build_script)
|
||
build_path.chmod(0o755)
|
||
|
||
return build_path
|
||
|
||
def update_skill_md(skill_path, script_name, description, input_desc, output_desc):
|
||
"""Add usage information to SKILL.md."""
|
||
skill_md = skill_path / 'SKILL.md'
|
||
|
||
if not skill_md.exists():
|
||
print(f"⚠️ Warning: SKILL.md not found at {skill_md}")
|
||
return
|
||
|
||
content = skill_md.read_text()
|
||
|
||
# Add script documentation if not already present
|
||
script_section = f'''
|
||
### {script_name}
|
||
|
||
{description}
|
||
|
||
**Input:** {input_desc}
|
||
**Output:** {output_desc}
|
||
|
||
**Usage:**
|
||
```bash
|
||
scripts/bin/{script_name} [options] <input>
|
||
scripts/bin/{script_name} --help # For detailed options
|
||
```
|
||
|
||
**Example:**
|
||
```bash
|
||
scripts/bin/{script_name} input.txt
|
||
```
|
||
'''
|
||
|
||
if script_name not in content:
|
||
# Try to add after "## Resources" or at the end
|
||
if '## Resources' in content:
|
||
content = content.replace('## Resources', script_section + '\n## Resources')
|
||
else:
|
||
content += '\n' + script_section
|
||
|
||
skill_md.write_text(content)
|
||
print(f"✅ Updated SKILL.md with {script_name} documentation")
|
||
else:
|
||
print(f"ℹ️ {script_name} already documented in SKILL.md")
|
||
|
||
def main():
|
||
parser = argparse.ArgumentParser(
|
||
description='Generate Go scripts for Claude Code skills',
|
||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||
epilog=dedent('''
|
||
Examples:
|
||
generate_go_script.py \\
|
||
--name pdf-extract-text \\
|
||
--description "Extract text from PDF files" \\
|
||
--input "PDF file path" \\
|
||
--output "Text content to stdout" \\
|
||
--logic "Parse PDF and extract all text content" \\
|
||
--skill-path ./my-pdf-skill
|
||
|
||
generate_go_script.py \\
|
||
--name csv-to-json \\
|
||
--description "Convert CSV files to JSON" \\
|
||
--input "CSV file path" \\
|
||
--output "JSON file" \\
|
||
--logic "Parse CSV rows and convert to JSON array" \\
|
||
--skill-path ./data-tools
|
||
''')
|
||
)
|
||
|
||
parser.add_argument('--name', required=True, help='Name of the script (e.g., pdf-to-images)')
|
||
parser.add_argument('--description', required=True, help='What the script does')
|
||
parser.add_argument('--input', required=True, help='Description of input')
|
||
parser.add_argument('--output', required=True, help='Description of output')
|
||
parser.add_argument('--logic', required=True, help='Description of the transformation logic')
|
||
parser.add_argument('--skill-path', required=True, help='Path to the skill directory')
|
||
parser.add_argument('--no-build', action='store_true', help='Skip creating build script')
|
||
parser.add_argument('--no-update-md', action='store_true', help='Skip updating SKILL.md')
|
||
|
||
args = parser.parse_args()
|
||
|
||
# Validate skill path
|
||
skill_path = Path(args.skill_path).resolve()
|
||
if not skill_path.exists():
|
||
print(f"❌ Error: Skill path does not exist: {skill_path}")
|
||
sys.exit(1)
|
||
|
||
# Create scripts directory if needed
|
||
scripts_dir = skill_path / 'scripts'
|
||
scripts_dir.mkdir(exist_ok=True)
|
||
|
||
# Generate Go script
|
||
print(f"🚀 Generating Go script: {args.name}")
|
||
script_content = generate_go_script(
|
||
args.name,
|
||
args.description,
|
||
args.input,
|
||
args.output,
|
||
args.logic,
|
||
skill_path
|
||
)
|
||
|
||
# Write Go file
|
||
go_file = scripts_dir / f'{args.name}.go'
|
||
go_file.write_text(script_content)
|
||
print(f"✅ Created: {go_file}")
|
||
|
||
# Create build script
|
||
if not args.no_build:
|
||
build_script = create_build_script(skill_path, args.name)
|
||
print(f"✅ Created build script: {build_script}")
|
||
print(f" Run: ./scripts/build_{args.name}.sh")
|
||
|
||
# Update SKILL.md
|
||
if not args.no_update_md:
|
||
update_skill_md(skill_path, args.name, args.description, args.input, args.output)
|
||
|
||
print(f"\n✅ Go script '{args.name}' generated successfully")
|
||
print("\nNext steps:")
|
||
print(f"1. Review and customize: {go_file}")
|
||
print(f"2. Build the binary: ./scripts/build_{args.name}.sh")
|
||
print(f"3. Test: ./scripts/bin/{args.name} --help")
|
||
|
||
if __name__ == '__main__':
|
||
main()
|