[简体中文](recipes.zh-CN.md)

# Recipe System Guide (AI-First)

## Overview

This guide demonstrates how **Claude Code AI Agent** uses the new Recipe system. Design philosophy: AI is the primary user, humans can execute manually (secondary scenario).

**AI Core Capabilities**:
1. Discover and select appropriate Recipes through metadata
2. Choose output destinations based on task requirements (stdout/file/clipboard)
3. Automatically generate orchestrated Workflow Recipes
4. Understand structured errors and adopt response strategies

---

## AI Usage Scenarios Quick Start

### Scenario 1: AI Discovers and Executes Recipe

**User Intent**: "Help me extract subtitles from this YouTube video and save as a file"

**AI Execution Flow**:

#### Step 1: Query Available Recipes (JSON Format)

```bash
# AI invokes Bash tool
uv run frago recipe list --format json
```

**JSON Response Received by AI**:
```json
[
  {
    "name": "youtube_extract_video_transcript",
    "type": "atomic",
    "runtime": "chrome-js",
    "description": "Extract complete subtitle content from YouTube video page",
    "use_cases": [
      "Get subtitles for translation",
      "Create subtitle files",
      "Analyze video content"
    ],
    "tags": ["web-scraping", "youtube", "transcript"],
    "output_targets": ["stdout", "file"],
    "version": "1.2.0",
    "source": "Example"
  },
  {
    "name": "upwork_extract_job_details_as_markdown",
    "type": "atomic",
    "runtime": "chrome-js",
    "description": "Extract complete information from Upwork job details page and format as Markdown",
    "use_cases": [
      "Analyze job market demands",
      "Batch collect job information"
    ],
    "tags": ["web-scraping", "upwork"],
    "output_targets": ["stdout", "file"],
    "version": "1.0.0",
    "source": "Example"
  }
]
```

#### Step 2: AI Analyzes and Selects Recipe

AI thinking process:
1. User needs to extract YouTube subtitles → Matches `youtube_extract_video_transcript`
2. Check `use_cases`: Contains "Get subtitles for translation" ✓
3. Check `output_targets`: Supports `file` ✓
4. Decision: Use this Recipe, output to file

#### Step 3: AI Executes Recipe

```bash
# AI invokes Bash tool
uv run frago recipe run youtube_extract_video_transcript \
  --params '{"url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ"}' \
  --output-file transcript.json
```

**Successful Execution Response**:
```json
{
  "success": true,
  "data": {
    "title": "Rick Astley - Never Gonna Give You Up",
    "transcript": "We're no strangers to love...",
    "language": "en",
    "duration": 213
  },
  "execution_time": 3.21,
  "recipe_name": "youtube_extract_video_transcript",
  "runtime": "chrome-js"
}
```

**File Also Saved**: `transcript.json`

---

### Scenario 2: AI Handles Errors and Retries

**Failed Execution Response**:
```json
{
  "success": false,
  "error": {
    "type": "RecipeExecutionError",
    "message": "Recipe 'youtube_extract_video_transcript' execution failed",
    "recipe_name": "youtube_extract_video_transcript",
    "runtime": "chrome-js",
    "exit_code": 1,
    "stdout": "Navigated to https://youtube.com/watch?v=...",
    "stderr": "Error: Element not found: .transcript-button"
  },
  "execution_time": 1.5,
  "recipe_name": "youtube_extract_video_transcript",
  "runtime": "chrome-js"
}
```

**AI Analyzes Error**:
1. `error.type`: RecipeExecutionError → Execution failed
2. `error.stderr`: "Element not found: .transcript-button" → Page structure changed or no subtitles
3. **AI Strategy**: Report to user "This video may not have subtitles or YouTube page structure has been updated"

---

### Scenario 3: AI Automatically Generates Workflow Recipe

**User Intent**: "Batch extract 10 Upwork jobs and save as JSON file"

**AI Execution Flow**:

#### Step 1: AI Invokes `/frago.recipe` Command

```
/frago.recipe create workflow "Batch extract 10 Upwork jobs and save as JSON"
```

#### Step 2: AI Automatically Generates Workflow Script

AI-generated file: `~/.frago/recipes/workflows/upwork_batch_extract.py`

```python
#!/usr/bin/env python3
"""
Workflow: Batch Extract Upwork Jobs
Generated: 2025-11-20
Auto-generated by Claude Code
"""
import sys, json
from frago.recipes import RecipeRunner

def main():
    params = json.loads(sys.argv[1] if len(sys.argv) > 1 else '{}')
    urls = params.get('urls', [])

    if not urls:
        print(json.dumps({"error": "Missing 'urls' parameter"}), file=sys.stderr)
        sys.exit(1)

    runner = RecipeRunner()
    results = []

    for i, url in enumerate(urls[:10], 1):
        try:
            print(f"Processing job {i}/10...", file=sys.stderr)
            result = runner.run('upwork_extract_job_details_as_markdown', {'url': url})
            results.append(result['data'])
        except Exception as e:
            results.append({"url": url, "error": str(e)})

    print(json.dumps({"success": True, "jobs": results}, ensure_ascii=False))

if __name__ == '__main__':
    main()
```

#### Step 3: AI Generates Metadata File

`~/.frago/recipes/workflows/upwork_batch_extract.md`:

```yaml
---
name: upwork_batch_extract
type: workflow
runtime: python
description: "Batch extract multiple Upwork jobs and save as JSON"
use_cases:
  - "Batch collect job data"
  - "Market analysis"
tags:
  - upwork
  - batch-processing
output_targets:
  - stdout
  - file
dependencies:
  - upwork_extract_job_details_as_markdown
version: 1.0.0
---
```

#### Step 4: AI Executes Workflow

```bash
uv run frago recipe run upwork_batch_extract \
  --params '{"urls": ["https://...", "https://...", ...]}' \
  --output-file jobs.json
```

---

## Manual Human Usage (Secondary Scenario)

### Scenario: Create a Simple Clipboard Read Recipe

#### 1. Create Script File

```bash
# Create Python script
cat > ~/.frago/recipes/atomic/system/clipboard_read.py <<'EOF'
#!/usr/bin/env python3
import sys
import json
import subprocess

# Read input from command line arguments
params = json.loads(sys.argv[1] if len(sys.argv) > 1 else '{}')

# Read clipboard content (using xclip)
result = subprocess.run(['xclip', '-selection', 'clipboard', '-o'],
                        capture_output=True, text=True, check=True)

# Output JSON result to stdout
output = {
    "content": result.stdout,
    "length": len(result.stdout)
}
print(json.dumps(output, ensure_ascii=False))
EOF

# Add execution permission (not mandatory for Python scripts, but recommended)
chmod +x ~/.frago/recipes/atomic/system/clipboard_read.py
```

#### 2. Create Metadata File

```bash
cat > ~/.frago/recipes/atomic/system/clipboard_read.md <<'EOF'
---
name: clipboard_read
type: atomic
runtime: python
inputs: {}
outputs:
  content: string
  length: number
version: 1.0.0
dependencies: []
description: "Read system clipboard content"
---

# Clipboard Read Recipe

## Function Description

Read current content from Linux system clipboard (using xclip tool).

## Prerequisites

- Install xclip: `sudo apt install xclip`
- Running in X11 environment (Wayland needs adjustment)

## Usage

```bash
uv run frago recipe run clipboard_read
```

## Expected Output

```json
{
  "success": true,
  "data": {
    "content": "Copied text content",
    "length": 10
  }
}
```

## Notes

- If clipboard is empty, `content` will be an empty string
- Only supports text content, not binary data like images
EOF
```

#### 3. Verify and Execute

```bash
# View Recipe information
uv run frago recipe info clipboard_read

# Execute Recipe
uv run frago recipe run clipboard_read
```

---

## Creating Orchestrated Recipes (Workflow)

### Scenario: Batch Extract Multiple YouTube Video Subtitles

#### 1. Create Workflow Script

```bash
cat > ~/.frago/recipes/workflows/youtube_batch_extract.py <<'EOF'
#!/usr/bin/env python3
import sys
import json
from pathlib import Path

# Import RecipeRunner (assuming implemented)
# from frago.recipes import RecipeRunner

def main():
    # Read input from command line arguments
    params = json.loads(sys.argv[1] if len(sys.argv) > 1 else '{}')
    video_urls = params.get('urls', [])

    if not video_urls:
        print(json.dumps({"error": "Missing 'urls' parameter"}), file=sys.stderr)
        sys.exit(1)

    # Initialize RecipeRunner
    # runner = RecipeRunner()

    results = []
    for url in video_urls:
        try:
            # Call atomic Recipe
            # result = runner.run('youtube_extract_video_transcript', {'url': url})
            # results.append(result)

            # Temporary simulation (remove after actual implementation)
            results.append({
                "url": url,
                "title": f"Video from {url}",
                "transcript": "..."
            })
        except Exception as e:
            results.append({
                "url": url,
                "error": str(e)
            })

    # Output JSON result
    output = {
        "success": True,
        "videos": results,
        "total": len(video_urls),
        "success_count": sum(1 for r in results if 'error' not in r)
    }
    print(json.dumps(output, ensure_ascii=False))

if __name__ == '__main__':
    main()
EOF

chmod +x ~/.frago/recipes/workflows/youtube_batch_extract.py
```

#### 2. Create Metadata File

```bash
cat > ~/.frago/recipes/workflows/youtube_batch_extract.md <<'EOF'
---
name: youtube_batch_extract
type: workflow
runtime: python
inputs:
  urls:
    type: array
    required: true
    description: "List of YouTube video URLs"
outputs:
  videos: array
  total: number
  success_count: number
version: 1.0.0
dependencies:
  - youtube_extract_video_transcript
description: "Batch extract subtitles from multiple YouTube videos"
---

# YouTube Batch Extract Workflow

## Function Description

Batch call `youtube_extract_video_transcript` Recipe to extract subtitles from multiple YouTube videos.

## Usage

```bash
uv run frago recipe run youtube_batch_extract \
  --params '{"urls": ["https://youtube.com/watch?v=...", "..."]}'
```

## Expected Output

```json
{
  "success": true,
  "videos": [
    {"url": "...", "title": "...", "transcript": "..."},
    {"url": "...", "title": "...", "transcript": "..."}
  ],
  "total": 2,
  "success_count": 2
}
```
EOF
```

#### 3. Execute Workflow

```bash
uv run frago recipe run youtube_batch_extract \
  --params '{
    "urls": [
      "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
      "https://www.youtube.com/watch?v=oHg5SJYRHA0"
    ]
  }'
```

---

## Project-Level Recipes (Optional)

### Scenario: Use Project-Specific Recipes in a Specific Project

#### 1. Create Recipe Directory in Project Root

```bash
# Navigate to project directory
cd /path/to/your/project

# Create project-level Recipe directory
mkdir -p .frago/recipes/workflows

# Create project-specific Workflow
cat > .frago/recipes/workflows/project_specific_task.py <<'EOF'
#!/usr/bin/env python3
import sys, json

params = json.loads(sys.argv[1] if len(sys.argv) > 1 else '{}')

# Project-specific logic
output = {"message": "This is a project-specific workflow"}
print(json.dumps(output))
EOF

chmod +x .frago/recipes/workflows/project_specific_task.py
```

#### 2. Create Metadata File

```bash
cat > .frago/recipes/workflows/project_specific_task.md <<'EOF'
---
name: project_specific_task
type: workflow
runtime: python
version: 1.0.0
description: "Project-specific automation task"
---

# Project-Specific Task

Workflow only used in current project.
EOF
```

#### 3. Execute (Automatically Prioritizes Project-Level)

```bash
# Execute in project directory
uv run frago recipe run project_specific_task

# List all Recipes (project-level will be marked [Project])
uv run frago recipe list
```

**Output**:
```text
SOURCE   TYPE      NAME                       RUNTIME  VERSION
────────────────────────────────────────────────────────────────
Project  workflow  project_specific_task      python   1.0.0
User     atomic    clipboard_read             python   1.0.0
Example  atomic    youtube_extract_video...   chrome-js 1.2.0
```

---

## Common Tasks

### View Recipe Detailed Information

```bash
uv run frago recipe info <recipe_name>
```

### Use Parameter File

```bash
# Create parameter file
cat > params.json <<EOF
{
  "url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ"
}
EOF

# Use parameter file
uv run frago recipe run youtube_extract_video_transcript \
  --params-file params.json
```

### Save Result to File

```bash
uv run frago recipe run youtube_extract_video_transcript \
  --params '{"url": "..."}' \
  --output-file result.json
```

### Debug Recipe Execution

```bash
# Enable debug mode
uv run frago --debug recipe run <recipe_name> --params '{...}'
```

**Output**:
```text
[DEBUG] RecipeRegistry: Scanning path /home/user/.frago/recipes
[DEBUG] RecipeRegistry: Found 5 Recipes
[DEBUG] RecipeRunner: Selected executor ChromeJSExecutor
[DEBUG] ChromeJSExecutor: Executing command: frago chrome exec-js ...
[DEBUG] ChromeJSExecutor: Exit code: 0
{
  "success": true,
  "data": { ... }
}
```

---

## Best Practices

### 1. Recipe Naming Conventions

- Use lowercase letters, numbers, underscores, hyphens
- Atomic Recipe: `<platform>_<action>` (e.g., `youtube_extract_transcript`)
- Workflow Recipe: `<platform>_batch_<action>` or `<workflow_name>`

### 2. Parameter Validation

Validate required parameters in Recipe scripts:

```python
import sys, json

params = json.loads(sys.argv[1] if len(sys.argv) > 1 else '{}')

# Validate required parameters
if 'url' not in params:
    error = {"error": "Missing required parameter: url"}
    print(json.dumps(error), file=sys.stderr)
    sys.exit(1)
```

### 3. Error Handling

Unified error output format:

```python
try:
    # Recipe logic
    result = do_something()
    print(json.dumps({"success": True, "data": result}))
except Exception as e:
    error = {
        "success": False,
        "error": {
            "type": type(e).__name__,
            "message": str(e)
        }
    }
    print(json.dumps(error), file=sys.stderr)
    sys.exit(1)
```

### 4. Version Management

Use semantic versioning in metadata:

```yaml
version: "1.0.0"  # major.minor.patch
```

- Major version: Incompatible API changes
- Minor version: New features (backward compatible)
- Patch: Bug fixes

### 5. Dependency Declaration

Declare dependencies in Workflow metadata:

```yaml
dependencies:
  - youtube_extract_video_transcript
  - clipboard_read
```

System will check dependency existence before execution.

---

## Environment Variables Support

The Recipe system supports environment variables for managing API keys, runtime configurations, and other sensitive or mutable information.

### Design Principles

- **Full System Environment Inheritance**: Recipes inherit all system environment variables (PATH, HOME, etc.) during execution
- **Three-Level Configuration Priority**: Project > User > System Environment
- **Declarative Definition**: Declare required environment variables in Recipe metadata
- **Workflow Context Sharing**: Share environment variables across multiple Recipes

### Declaring Environment Variables in Recipe

Declare required environment variables in the metadata `env` field:

```yaml
---
name: openai_chat
type: atomic
runtime: python
description: "Call OpenAI API for conversation"
use_cases:
  - "AI conversation generation"
  - "Text processing"
output_targets:
  - stdout
env:
  OPENAI_API_KEY:
    required: true
    description: "OpenAI API key"
  MODEL_NAME:
    required: false
    default: "gpt-4o"
    description: "Model name to use"
  MAX_TOKENS:
    required: false
    default: "1000"
    description: "Maximum token count"
version: "1.0.0"
---
```

**Field Description**:

| Field | Type | Description |
|-------|------|-------------|
| `required` | boolean | Whether required (error if missing) |
| `default` | string | Default value (used when not provided) |
| `description` | string | Environment variable description |

### Configuring Environment Variables

#### Method 1: Configuration Files (Recommended)

Create `.env` files to store environment variables:

**User-level configuration** (applies to all projects):
```bash
# ~/.frago/.env
OPENAI_API_KEY=sk-your-api-key
DEEPSEEK_API_KEY=sk-your-deepseek-key
DEFAULT_MODEL=gpt-4o
```

**Project-level configuration** (current project only):
```bash
# .frago/.env
MODEL_NAME=gpt-4o-mini
MAX_TOKENS=2000
DEBUG=true
```

#### Method 2: CLI Parameter Override

Use `--env` or `-e` parameter to override at execution time:

```bash
# Single environment variable
uv run frago recipe run openai_chat \
  --params '{"prompt": "Hello"}' \
  -e OPENAI_API_KEY=sk-xxx

# Multiple environment variables
uv run frago recipe run openai_chat \
  --params '{"prompt": "Hello"}' \
  -e OPENAI_API_KEY=sk-xxx \
  -e MODEL_NAME=gpt-4 \
  -e MAX_TOKENS=500
```

#### Method 3: System Environment Variables

Export directly in shell:

```bash
export OPENAI_API_KEY=sk-your-api-key
uv run frago recipe run openai_chat --params '{"prompt": "Hello"}'
```

### Priority Rules

Environment variables are resolved in the following priority (high to low):

![Environment Variable Priority](images/frago-env-priority.jpg)

### Using Environment Variables in Scripts

**Python Recipe**:
```python
#!/usr/bin/env python3
import os
import sys
import json

# Environment variables are auto-injected, use os.environ directly
api_key = os.environ.get('OPENAI_API_KEY')
model = os.environ.get('MODEL_NAME', 'gpt-4o')

if not api_key:
    print(json.dumps({"error": "OPENAI_API_KEY not set"}), file=sys.stderr)
    sys.exit(1)

# Use environment variables...
result = {"model": model, "status": "ready"}
print(json.dumps(result))
```

**Shell Recipe**:
```bash
#!/bin/bash
# Environment variables are auto-injected, use directly
echo "Using model: $MODEL_NAME"
echo "API Key present: $([ -n \"$OPENAI_API_KEY\" ] && echo 'yes' || echo 'no')"

# Output JSON
cat <<EOF
{
  "model": "$MODEL_NAME",
  "configured": true
}
EOF
```

### Workflow Context Sharing

In Workflows, multiple Recipes can share environment variables:

```python
#!/usr/bin/env python3
"""Workflow: Batch Processing Task"""
import sys
import json
from frago.recipes import RecipeRunner, WorkflowContext

def main():
    params = json.loads(sys.argv[1] if len(sys.argv) > 1 else '{}')

    # Create shared context
    context = WorkflowContext()

    # Set shared environment variables
    context.set("BATCH_ID", "batch_001")
    context.set("TOTAL_ITEMS", str(len(params.get('items', []))))

    runner = RecipeRunner()
    results = []

    for i, item in enumerate(params.get('items', [])):
        # Update current progress
        context.set("CURRENT_INDEX", str(i + 1))

        # Execute Recipe with shared context
        result = runner.run(
            'process_item',
            params={'item': item},
            workflow_context=context
        )
        results.append(result['data'])

    print(json.dumps({"success": True, "results": results}))

if __name__ == '__main__':
    main()
```

Called Recipes can access shared variables via `os.environ`:

```python
#!/usr/bin/env python3
"""Atomic Recipe: Process Single Item"""
import os
import sys
import json

# Get Workflow shared context
batch_id = os.environ.get('BATCH_ID', 'unknown')
current = os.environ.get('CURRENT_INDEX', '0')
total = os.environ.get('TOTAL_ITEMS', '0')

params = json.loads(sys.argv[1] if len(sys.argv) > 1 else '{}')
item = params.get('item')

print(f"Processing {current}/{total} in batch {batch_id}", file=sys.stderr)

result = {"item": item, "batch_id": batch_id, "processed": True}
print(json.dumps(result))
```

### Viewing Recipe Environment Variable Definitions

```bash
# View Recipe details including env definitions
uv run frago recipe info openai_chat

# JSON format output (includes env field)
uv run frago recipe info openai_chat --format json
```

**Output Example**:
```
Recipe: openai_chat
==================================================

Basic Info
──────────────────────────────────────────────────
Name:     openai_chat
Type:     atomic
Runtime:  python
Version:  1.0.0
Source:   User

Environment Variables
──────────────────────────────────────────────────
• OPENAI_API_KEY (required): OpenAI API key
• MODEL_NAME (optional, default: gpt-4o): Model name to use
• MAX_TOKENS (optional, default: 1000): Maximum token count

Dependencies
──────────────────────────────────────────────────
None
```

### Security Best Practices

1. **Don't hardcode sensitive information**: Store API keys in `.env` files
2. **Add `.env` to `.gitignore`**: Avoid committing sensitive info to version control
3. **Use `required: true`**: Ensure critical environment variables exist
4. **Provide reasonable defaults**: Use `default` field for non-sensitive configs

```bash
# .gitignore
.frago/.env
```

---

## Migrating Existing Recipes

### Migrating from Old Method to New System

#### Old Method (Direct `exec-js` Call)

```bash
frago chrome exec-js src/frago/recipes/upwork_extract_job.js
```

#### New Method

1. **Migrate script to new location**:
   ```bash
   # Script automatically migrated to examples/atomic/chrome/
   uv run frago recipe copy upwork_extract_job_details_as_markdown
   ```

2. **Use new command**:
   ```bash
   uv run frago recipe run upwork_extract_job_details_as_markdown \
     --params '{"url": "..."}'
   ```

### Old vs New Method Comparison

| Feature | Old Method (`exec-js`) | New Method (`recipe run`) |
|---------|----------------------|------------------------|
| Parameter Passing | Command line arguments | JSON format (unified) |
| Metadata | None | YAML frontmatter |
| Search Path | Fixed path | Three-level lookup (project/user/example) |
| Error Handling | Raw output | Structured JSON errors |
| Dependency Management | None | Automatic dependency checking |
| Orchestration Capability | None | Workflow support |

---

## Troubleshooting

### Recipe Not Found

**Problem**: `Error: Recipe 'xxx' not found`

**Solution**:
1. Check Recipe name spelling
2. Run `uv run frago recipe list` to view available Recipes
3. Confirm metadata file `.md` exists

### Parameter Format Error

**Problem**: `Error: Invalid parameter format`

**Solution**:
- Use double quotes (JSON standard): `{"url": "..."}` not `{'url': '...'}`
- Escape special characters: `"{\"key\": \"value\"}"`
- Or use parameter file: `--params-file params.json`

### Dependency Recipe Not Found

**Problem**: Workflow execution failed, prompts missing dependency

**Solution**:
```bash
# Copy dependent example Recipe
uv run frago recipe copy <dependency_name>

# Or create custom dependent Recipe
```

### Recipe Execution Timeout

**Problem**: `Error: Recipe execution timeout`

**Solution**:
- Increase timeout: `--timeout 600`
- Check for infinite loops in Recipe script
- Check network connection (Chrome CDP operations)

---

---

## AI-First Design Summary

### Core Flow for AI Using Recipe System

```text
1. User proposes task
   ↓
2. AI invokes: uv run frago recipe list --format json
   ↓
3. AI analyzes metadata:
   - description: Quick understand functionality
   - use_cases: Determine if applicable to current task
   - output_targets: Choose output method (stdout/file/clipboard)
   ↓
4. AI decision:
   - Use existing Recipe → Execute
   - No suitable Recipe → Invoke /frago.recipe to generate new Recipe
   ↓
5. AI executes: uv run frago recipe run <name> --params '{...}' [--output-file/--output-clipboard]
   ↓
6. AI processes result:
   - success: true → Report to user
   - success: false → Analyze error.stderr, adopt strategy (retry/report)
```

### Key Design Principles

1. **Metadata-Driven**: AI understands Recipe capabilities through semantic fields without reading script code
2. **Structured Output**: All CLI commands support `--format json` for AI parsing
3. **Output Format Declaration**: Recipe explicitly declares supported output destinations, AI can plan based on task
4. **AI Generates Workflow**: `/frago.recipe` command enables AI to automatically create orchestrated Recipes
5. **Understandable Errors**: Structured errors allow AI to analyze failure reasons and automatically respond

### AI vs Human Usage Comparison

| Feature | AI Usage | Human Usage |
|---------|----------|-------------|
| **Discover Recipe** | `recipe list --format json` + semantic analysis | `recipe list` (table) or `recipe info` |
| **Create Recipe** | `/frago.recipe create` auto-generate | Manually write script and metadata |
| **Execute Recipe** | Bash tool invocation, auto-select output method | Manually type command, manually specify --output-file |
| **Error Handling** | Parse JSON error, automatic response strategy | Read error message, manually troubleshoot |
| **Task Orchestration** | Auto-generate Workflow Python script | Manually combine multiple Recipes |

---

## Next Steps

- **Read complete documentation**: [spec.md](./spec.md)
- **View data models**: [data-model.md](./data-model.md)
- **CLI command reference**: [contracts/cli-commands.md](./contracts/cli-commands.md)
- **Technical research**: [research.md](./research.md)

---

## Feedback

Encountered problems or have improvement suggestions? Please submit an Issue in the project repository.
