Initial commit

This commit is contained in:
Zhongwei Li
2025-11-30 09:04:14 +08:00
commit 70c36b5eff
248 changed files with 47482 additions and 0 deletions

View File

@@ -0,0 +1,105 @@
#!/bin/bash
# Helper script to convert argparse CLI to Typer (guidance)
set -euo pipefail
# Colors
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
cat << 'EOF'
Converting argparse to Typer
=============================
This script provides guidance on converting argparse CLIs to Typer.
Common Conversions:
-------------------
1. Argument Parser Setup
argparse: parser = ArgumentParser()
Typer: app = typer.Typer()
2. Positional Arguments
argparse: parser.add_argument('name')
Typer: name: str = typer.Argument(...)
3. Optional Arguments
argparse: parser.add_argument('--flag', '-f')
Typer: flag: bool = typer.Option(False, '--flag', '-f')
4. Required Options
argparse: parser.add_argument('--name', required=True)
Typer: name: str = typer.Option(...)
5. Default Values
argparse: parser.add_argument('--count', default=10)
Typer: count: int = typer.Option(10)
6. Type Conversion
argparse: parser.add_argument('--port', type=int)
Typer: port: int = typer.Option(8000)
7. Choices/Enums
argparse: parser.add_argument('--format', choices=['json', 'yaml'])
Typer: format: Format = typer.Option(Format.json) # Format is Enum
8. File Arguments
argparse: parser.add_argument('--input', type=argparse.FileType('r'))
Typer: input: Path = typer.Option(...)
9. Help Text
argparse: parser.add_argument('--name', help='User name')
Typer: name: str = typer.Option(..., help='User name')
10. Subcommands
argparse: subparsers = parser.add_subparsers()
Typer: sub_app = typer.Typer(); app.add_typer(sub_app, name='sub')
Example Conversion:
-------------------
BEFORE (argparse):
parser = ArgumentParser()
parser.add_argument('input', help='Input file')
parser.add_argument('--output', '-o', help='Output file')
parser.add_argument('--verbose', '-v', action='store_true')
args = parser.parse_args()
AFTER (Typer):
app = typer.Typer()
@app.command()
def main(
input: Path = typer.Argument(..., help='Input file'),
output: Optional[Path] = typer.Option(None, '--output', '-o'),
verbose: bool = typer.Option(False, '--verbose', '-v')
) -> None:
"""Process input file."""
pass
if __name__ == '__main__':
app()
Benefits of Typer:
------------------
✓ Automatic type validation
✓ Better IDE support with type hints
✓ Less boilerplate code
✓ Built-in help generation
✓ Easier testing
✓ Rich formatting support
Next Steps:
-----------
1. Identify all argparse patterns in your CLI
2. Use templates from this skill as reference
3. Convert incrementally, one command at a time
4. Run validation: ./scripts/validate-types.sh
5. Test thoroughly: ./scripts/test-cli.sh
EOF
echo -e "${BLUE}For specific conversion help, provide your argparse CLI code.${NC}"

View File

@@ -0,0 +1,113 @@
#!/bin/bash
# Generate a Typer CLI from template
set -euo pipefail
# Colors
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
# Usage
usage() {
cat << EOF
Usage: $0 <template-name> <output-file> [options]
Templates:
basic - Basic typed command
enum - Enum-based options
subapp - Sub-application structure
factory - Factory pattern
validation - Advanced validation
Options:
--app-name NAME Set application name (default: mycli)
--help Show this help
Example:
$0 basic my_cli.py --app-name myapp
EOF
exit 0
}
# Parse arguments
if [ $# -lt 2 ]; then
usage
fi
TEMPLATE="$1"
OUTPUT="$2"
APP_NAME="mycli"
shift 2
while [ $# -gt 0 ]; do
case "$1" in
--app-name)
APP_NAME="$2"
shift 2
;;
--help)
usage
;;
*)
echo "Unknown option: $1"
usage
;;
esac
done
# Get script directory
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
TEMPLATE_DIR="$(dirname "$SCRIPT_DIR")/templates"
# Map template name to file
case "$TEMPLATE" in
basic)
TEMPLATE_FILE="$TEMPLATE_DIR/basic-typed-command.py"
;;
enum)
TEMPLATE_FILE="$TEMPLATE_DIR/enum-options.py"
;;
subapp)
TEMPLATE_FILE="$TEMPLATE_DIR/sub-app-structure.py"
;;
factory)
TEMPLATE_FILE="$TEMPLATE_DIR/typer-instance.py"
;;
validation)
TEMPLATE_FILE="$TEMPLATE_DIR/advanced-validation.py"
;;
*)
echo "Unknown template: $TEMPLATE"
usage
;;
esac
# Check if template exists
if [ ! -f "$TEMPLATE_FILE" ]; then
echo -e "${YELLOW}✗ Template not found: $TEMPLATE_FILE${NC}"
exit 1
fi
# Copy and customize template
cp "$TEMPLATE_FILE" "$OUTPUT"
# Replace app name if not default
if [ "$APP_NAME" != "mycli" ]; then
sed -i "s/mycli/$APP_NAME/g" "$OUTPUT"
sed -i "s/myapp/$APP_NAME/g" "$OUTPUT"
fi
# Make executable
chmod +x "$OUTPUT"
echo -e "${GREEN}✓ Generated CLI: $OUTPUT${NC}"
echo " Template: $TEMPLATE"
echo " App name: $APP_NAME"
echo ""
echo "Next steps:"
echo " 1. Review and customize the generated file"
echo " 2. Install dependencies: pip install typer"
echo " 3. Run: python $OUTPUT --help"
echo " 4. Validate: ./scripts/validate-types.sh $OUTPUT"

View File

@@ -0,0 +1,138 @@
#!/bin/bash
# Test Typer CLI functionality
set -euo pipefail
# Colors
GREEN='\033[0;32m'
RED='\033[0;31m'
YELLOW='\033[1;33m'
NC='\033[0m'
# Check if file provided
if [ $# -eq 0 ]; then
echo -e "${RED}✗ Usage: $0 <python-file>${NC}"
exit 1
fi
CLI_FILE="$1"
# Check if file exists
if [ ! -f "$CLI_FILE" ]; then
echo -e "${RED}✗ File not found: $CLI_FILE${NC}"
exit 1
fi
echo "Testing Typer CLI: $CLI_FILE"
echo "========================================"
TESTS_PASSED=0
TESTS_FAILED=0
# Test: Help command
echo "Test: Help output"
if python "$CLI_FILE" --help > /dev/null 2>&1; then
echo -e "${GREEN}✓ Help command works${NC}"
((TESTS_PASSED++))
else
echo -e "${RED}✗ Help command failed${NC}"
((TESTS_FAILED++))
fi
# Test: Version flag (if supported)
echo "Test: Version flag"
if python "$CLI_FILE" --version > /dev/null 2>&1; then
echo -e "${GREEN}✓ Version flag works${NC}"
((TESTS_PASSED++))
elif grep -q "version" "$CLI_FILE"; then
echo -e "${YELLOW}⚠ Version defined but flag not working${NC}"
else
echo -e "${YELLOW}⚠ No version flag (optional)${NC}"
fi
# Test: Check for syntax errors
echo "Test: Python syntax"
if python -m py_compile "$CLI_FILE" 2>/dev/null; then
echo -e "${GREEN}✓ No syntax errors${NC}"
((TESTS_PASSED++))
else
echo -e "${RED}✗ Syntax errors detected${NC}"
((TESTS_FAILED++))
fi
# Test: Type checking with mypy (if available)
echo "Test: Type checking"
if command -v mypy &> /dev/null; then
if mypy "$CLI_FILE" --ignore-missing-imports 2>/dev/null; then
echo -e "${GREEN}✓ Type checking passed${NC}"
((TESTS_PASSED++))
else
echo -e "${YELLOW}⚠ Type checking warnings/errors${NC}"
echo " Run: mypy $CLI_FILE --ignore-missing-imports"
fi
else
echo -e "${YELLOW}⚠ mypy not installed (skipping type check)${NC}"
fi
# Test: Linting with ruff (if available)
echo "Test: Code linting"
if command -v ruff &> /dev/null; then
if ruff check "$CLI_FILE" --select E,W,F 2>/dev/null; then
echo -e "${GREEN}✓ Linting passed${NC}"
((TESTS_PASSED++))
else
echo -e "${YELLOW}⚠ Linting warnings/errors${NC}"
echo " Run: ruff check $CLI_FILE"
fi
else
echo -e "${YELLOW}⚠ ruff not installed (skipping linting)${NC}"
fi
# Test: Import check
echo "Test: Import dependencies"
if python -c "import sys; sys.path.insert(0, '.'); exec(open('$CLI_FILE').read().split('if __name__')[0])" 2>/dev/null; then
echo -e "${GREEN}✓ All imports successful${NC}"
((TESTS_PASSED++))
else
echo -e "${RED}✗ Import errors detected${NC}"
echo " Check that all dependencies are installed"
((TESTS_FAILED++))
fi
# Test: Check for common patterns
echo "Test: Typer patterns"
PATTERN_ISSUES=0
if ! grep -q "@app.command()" "$CLI_FILE"; then
echo -e "${YELLOW} ⚠ No @app.command() decorators found${NC}"
((PATTERN_ISSUES++))
fi
if ! grep -q "typer.Typer()" "$CLI_FILE"; then
echo -e "${YELLOW} ⚠ No Typer() instance found${NC}"
((PATTERN_ISSUES++))
fi
if ! grep -q "if __name__ == \"__main__\":" "$CLI_FILE"; then
echo -e "${YELLOW} ⚠ Missing if __name__ == '__main__' guard${NC}"
((PATTERN_ISSUES++))
fi
if [ $PATTERN_ISSUES -eq 0 ]; then
echo -e "${GREEN}✓ Common patterns found${NC}"
((TESTS_PASSED++))
else
echo -e "${YELLOW}⚠ Some patterns missing${NC}"
fi
echo "========================================"
echo "Tests passed: $TESTS_PASSED"
echo "Tests failed: $TESTS_FAILED"
if [ $TESTS_FAILED -eq 0 ]; then
echo -e "${GREEN}✓ All tests passed!${NC}"
exit 0
else
echo -e "${RED}✗ Some tests failed${NC}"
exit 1
fi

View File

@@ -0,0 +1,135 @@
#!/bin/bash
# Validate typer-patterns skill structure
set -euo pipefail
# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
SKILL_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
ERRORS=0
echo "Validating typer-patterns skill..."
echo "========================================"
# Check SKILL.md exists
echo "Checking SKILL.md..."
if [ ! -f "$SKILL_DIR/SKILL.md" ]; then
echo -e "${RED}✗ SKILL.md not found${NC}"
((ERRORS++))
else
echo -e "${GREEN}✓ SKILL.md exists${NC}"
# Check frontmatter starts at line 1
FIRST_LINE=$(head -n 1 "$SKILL_DIR/SKILL.md")
if [ "$FIRST_LINE" != "---" ]; then
echo -e "${RED}✗ SKILL.md frontmatter must start at line 1 (found: $FIRST_LINE)${NC}"
((ERRORS++))
else
echo -e "${GREEN}✓ Frontmatter starts at line 1${NC}"
fi
# Check required frontmatter fields
if grep -q "^name: " "$SKILL_DIR/SKILL.md"; then
echo -e "${GREEN}✓ name field present${NC}"
else
echo -e "${RED}✗ name field missing${NC}"
((ERRORS++))
fi
if grep -q "^description: " "$SKILL_DIR/SKILL.md"; then
echo -e "${GREEN}✓ description field present${NC}"
else
echo -e "${RED}✗ description field missing${NC}"
((ERRORS++))
fi
# Check for "Use when" in description
if grep "^description: " "$SKILL_DIR/SKILL.md" | grep -q "Use when"; then
echo -e "${GREEN}✓ Description contains 'Use when' triggers${NC}"
else
echo -e "${YELLOW}⚠ Description should include 'Use when' trigger contexts${NC}"
fi
fi
# Check templates directory
echo "Checking templates..."
TEMPLATE_COUNT=$(find "$SKILL_DIR/templates" -name "*.py" 2>/dev/null | wc -l)
if [ "$TEMPLATE_COUNT" -ge 4 ]; then
echo -e "${GREEN}✓ Found $TEMPLATE_COUNT templates (minimum 4)${NC}"
else
echo -e "${RED}✗ Found $TEMPLATE_COUNT templates (need at least 4)${NC}"
((ERRORS++))
fi
# Check scripts directory
echo "Checking scripts..."
SCRIPT_COUNT=$(find "$SKILL_DIR/scripts" -name "*.sh" 2>/dev/null | wc -l)
if [ "$SCRIPT_COUNT" -ge 3 ]; then
echo -e "${GREEN}✓ Found $SCRIPT_COUNT scripts (minimum 3)${NC}"
else
echo -e "${RED}✗ Found $SCRIPT_COUNT scripts (need at least 3)${NC}"
((ERRORS++))
fi
# Check scripts are executable
NONEXEC=$(find "$SKILL_DIR/scripts" -name "*.sh" ! -executable 2>/dev/null | wc -l)
if [ "$NONEXEC" -gt 0 ]; then
echo -e "${YELLOW}$NONEXEC scripts are not executable${NC}"
else
echo -e "${GREEN}✓ All scripts are executable${NC}"
fi
# Check examples directory
echo "Checking examples..."
EXAMPLE_COUNT=$(find "$SKILL_DIR/examples" -mindepth 1 -maxdepth 1 -type d 2>/dev/null | wc -l)
if [ "$EXAMPLE_COUNT" -ge 3 ]; then
echo -e "${GREEN}✓ Found $EXAMPLE_COUNT example directories (minimum 3)${NC}"
else
echo -e "${RED}✗ Found $EXAMPLE_COUNT examples (need at least 3)${NC}"
((ERRORS++))
fi
# Check for README files in examples
for example_dir in "$SKILL_DIR/examples"/*; do
if [ -d "$example_dir" ]; then
example_name=$(basename "$example_dir")
if [ -f "$example_dir/README.md" ]; then
echo -e "${GREEN}✓ Example $example_name has README.md${NC}"
else
echo -e "${YELLOW}⚠ Example $example_name missing README.md${NC}"
fi
fi
done
# Check for hardcoded secrets (basic check)
echo "Checking for hardcoded secrets..."
if grep -r "sk-[a-zA-Z0-9]" "$SKILL_DIR" 2>/dev/null | grep -v "validate-skill.sh" | grep -q .; then
echo -e "${RED}✗ Possible API keys detected${NC}"
((ERRORS++))
else
echo -e "${GREEN}✓ No obvious API keys detected${NC}"
fi
# Check SKILL.md length
if [ -f "$SKILL_DIR/SKILL.md" ]; then
LINE_COUNT=$(wc -l < "$SKILL_DIR/SKILL.md")
if [ "$LINE_COUNT" -gt 150 ]; then
echo -e "${YELLOW}⚠ SKILL.md is $LINE_COUNT lines (consider keeping under 150)${NC}"
else
echo -e "${GREEN}✓ SKILL.md length is reasonable ($LINE_COUNT lines)${NC}"
fi
fi
echo "========================================"
if [ $ERRORS -eq 0 ]; then
echo -e "${GREEN}✓ Validation passed!${NC}"
exit 0
else
echo -e "${RED}✗ Validation failed with $ERRORS error(s)${NC}"
exit 1
fi

View File

@@ -0,0 +1,123 @@
#!/bin/bash
# Validate type hints in Typer CLI files
set -euo pipefail
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
# Check if file provided
if [ $# -eq 0 ]; then
echo -e "${RED}✗ Usage: $0 <python-file>${NC}"
exit 1
fi
FILE="$1"
# Check if file exists
if [ ! -f "$FILE" ]; then
echo -e "${RED}✗ File not found: $FILE${NC}"
exit 1
fi
echo "Validating type hints in: $FILE"
echo "----------------------------------------"
ERRORS=0
# Check for type hints on function parameters
echo "Checking function parameter type hints..."
UNTYPED_PARAMS=$(grep -n "def " "$FILE" | while read -r line; do
LINE_NUM=$(echo "$line" | cut -d: -f1)
LINE_CONTENT=$(echo "$line" | cut -d: -f2-)
# Extract parameter list
PARAMS=$(echo "$LINE_CONTENT" | sed -n 's/.*def [^(]*(\(.*\)).*/\1/p')
# Check if any parameter lacks type hint (excluding self, ctx)
if echo "$PARAMS" | grep -qE '[a-zA-Z_][a-zA-Z0-9_]*[[:space:]]*=' | \
grep -vE '[a-zA-Z_][a-zA-Z0-9_]*[[:space:]]*:[[:space:]]*[a-zA-Z]'; then
echo " Line $LINE_NUM: Missing type hint"
((ERRORS++))
fi
done)
if [ -z "$UNTYPED_PARAMS" ]; then
echo -e "${GREEN}✓ All parameters have type hints${NC}"
else
echo -e "${RED}$UNTYPED_PARAMS${NC}"
fi
# Check for return type hints
echo "Checking function return type hints..."
MISSING_RETURN=$(grep -n "def " "$FILE" | grep -v "-> " | while read -r line; do
LINE_NUM=$(echo "$line" | cut -d: -f1)
echo " Line $LINE_NUM: Missing return type hint"
((ERRORS++))
done)
if [ -z "$MISSING_RETURN" ]; then
echo -e "${GREEN}✓ All functions have return type hints${NC}"
else
echo -e "${RED}$MISSING_RETURN${NC}"
fi
# Check for Typer imports
echo "Checking Typer imports..."
if ! grep -q "^import typer" "$FILE" && ! grep -q "^from typer import" "$FILE"; then
echo -e "${RED}✗ Missing typer import${NC}"
((ERRORS++))
else
echo -e "${GREEN}✓ Typer imported${NC}"
fi
# Check for typing imports when using Optional, Union, etc.
echo "Checking typing imports..."
if grep -qE "Optional|Union|List|Dict|Tuple" "$FILE"; then
if ! grep -q "from typing import" "$FILE"; then
echo -e "${YELLOW}⚠ Using typing types but missing typing import${NC}"
((ERRORS++))
else
echo -e "${GREEN}✓ Typing imports present${NC}"
fi
else
echo -e "${YELLOW}⚠ No typing annotations detected${NC}"
fi
# Check for Path usage
echo "Checking Path usage for file parameters..."
if grep -qE "file|path|dir" "$FILE" | grep -i "str.*=.*typer"; then
echo -e "${YELLOW}⚠ Consider using Path type instead of str for file/path parameters${NC}"
else
echo -e "${GREEN}✓ No obvious Path type issues${NC}"
fi
# Check for Enum usage
echo "Checking for Enum patterns..."
if grep -qE "class.*\(str, Enum\)" "$FILE"; then
echo -e "${GREEN}✓ Enum classes found${NC}"
else
echo -e "${YELLOW}⚠ No Enum classes detected (consider for constrained choices)${NC}"
fi
# Check for docstrings
echo "Checking command docstrings..."
MISSING_DOCS=$(grep -A1 "def " "$FILE" | grep -v '"""' | wc -l)
if [ "$MISSING_DOCS" -gt 0 ]; then
echo -e "${YELLOW}⚠ Some functions may be missing docstrings${NC}"
else
echo -e "${GREEN}✓ Docstrings appear present${NC}"
fi
echo "----------------------------------------"
if [ $ERRORS -eq 0 ]; then
echo -e "${GREEN}✓ Validation passed!${NC}"
exit 0
else
echo -e "${RED}✗ Validation failed with $ERRORS error(s)${NC}"
exit 1
fi