{"repo": "aditya060806/maestro", "files": {".agents/scripts/model-registry-helper.sh": {"9810e84435a3cce44d75d0d30d68a54bd8faabe0": {"t": 1786067520.8241692, "n": 46292, "h": "b2b9526ee3053dd0", "c": "#!/usr/bin/env bash\n# SPDX-License-Identifier: MIT\n# SPDX-FileCopyrightText: 2025-2026 Aditya Pandey and Harvest\n\n# Model Registry Helper - Provider/Model Registry with Periodic Sync\n# Maintains a SQLite registry of AI models from configured providers,\n# compares against local subagent definitions, flags deprecated/renamed\n# models, and suggests new models worth adding.\n#\n# Usage: model-registry-helper.sh [command] [options]\n#\n# Commands:\n# sync Sync registry from all sources (subagents, embedded data, APIs)\n# list List all models in the registry\n# status Show registry health and staleness\n# check Check configured models against live provider APIs\n# suggest Suggest new models worth adding to subagent definitions\n# deprecations Show deprecated/renamed/unavailable models\n# diff Show differences between registry and local config\n# export Export registry as JSON\n# help Show this help\n#\n# Options:\n# --json Output in JSON format (where supported)\n# --quiet Suppress informational output\n# --force Force full resync even if cache is fresh\n#\n# Runs on: maestro update, optionally via cron\n# Storage: ~/.maestro/.agent-workspace/model-registry.db\n#\n# Author: Maestro Framework\n# Version: 1.0.0\n\nSCRIPT_DIR=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" && pwd)\" || exit\nsource \"${SCRIPT_DIR}/shared-constants.sh\"\n\nset -euo pipefail\n\ninit_log_file\n\n# =============================================================================\n# Configuration\n# =============================================================================\n\nreadonly REGISTRY_DIR=\"${HOME}/.maestro/.agent-workspace\"\nreadonly REGISTRY_DB=\"${REGISTRY_DIR}/model-registry.db\"\nreadonly SYNC_INTERVAL=86400 # 24 hours in seconds\nreadonly AGENTS_DIR=\"${SCRIPT_DIR}/..\"\nreadonly MODELS_DIR=\"${AGENTS_DIR}/tools/ai-assistants/models\"\n\n# =============================================================================\n# Database Setup\n# =============================================================================\n\ninit_db() {\n\tmkdir -p \"$REGISTRY_DIR\" 2>/dev/null || true\n\n\tsqlite3 \"$REGISTRY_DB\" \"\n CREATE TABLE IF NOT EXISTS models (\n model_id TEXT NOT NULL,\n provider TEXT NOT NULL,\n display_name TEXT DEFAULT '',\n normalized_name TEXT DEFAULT '',\n context_window INTEGER DEFAULT 0,\n input_price REAL DEFAULT 0.0,\n output_price REAL DEFAULT 0.0,\n tier TEXT DEFAULT '',\n capabilities TEXT DEFAULT '',\n best_for TEXT DEFAULT '',\n source TEXT DEFAULT 'embedded',\n status TEXT DEFAULT 'active',\n first_seen TEXT DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')),\n last_seen TEXT DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')),\n last_verified TEXT DEFAULT '',\n deprecated INTEGER DEFAULT 0,\n deprecation_note TEXT DEFAULT '',\n PRIMARY KEY (model_id, provider)\n );\n\n CREATE TABLE IF NOT EXISTS subagent_models (\n tier TEXT PRIMARY KEY,\n model_id TEXT NOT NULL,\n model_full_id TEXT DEFAULT '',\n normalized_name TEXT DEFAULT '',\n fallback_id TEXT DEFAULT '',\n subagent_file TEXT NOT NULL,\n last_synced TEXT DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ', 'now'))\n );\n\n CREATE TABLE IF NOT EXISTS provider_models (\n model_id TEXT NOT NULL,\n provider TEXT NOT NULL,\n discovered_at TEXT DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')),\n in_registry INTEGER DEFAULT 0,\n in_subagents INTEGER DEFAULT 0,\n PRIMARY KEY (model_id, provider)\n );\n\n CREATE TABLE IF NOT EXISTS sync_log (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n sync_type TEXT NOT NULL,\n source TEXT NOT NULL,\n models_added INTEGER DEFAULT 0,\n models_updated INTEGER DEFAULT 0,\n models_removed INTEGER DEFAULT 0,\n timestamp TEXT DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')),\n details TEXT DEFAULT ''\n );\n \" 2>/dev/null || {\n\t\tprint_error \"Failed to initialize registry database\"\n\t\treturn 1\n\t}\n\treturn 0\n}\n\n# =============================================================================\n# Model Name Normalization\n# =============================================================================\n# Normalizes model names for fuzzy matching across different naming conventions.\n# Example: provider/version variants normalize to a stable family label.\n\nnormalize_model_name() {\n\tlocal name=\"$1\"\n\t# Strip provider prefix\n\tname=\"${name#*/}\"\n\t# Strip date suffixes (e.g., -20250514, -20241022)\n\tname=\"${name%-[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]}\"\n\t# Strip preview suffixes\n\tname=$(echo \"$name\" | sed -E 's/-preview-[0-9]{2}-[0-9]{2}$//')\n\t# Strip version numbers (e.g., -3-5, -3.5, -4, -2.5, -2.0)\n\tname=$(echo \"$name\" | sed -E 's/-[0-9]+(\\.[0-9]+)?//g')\n\t# Lowercase\n\techo \"$name\" | tr '[:upper:]' '[:lower:]'\n\treturn 0\n}\n\n# =============================================================================\n# SQL Helpers\n# =============================================================================\n\nsql_escape() {\n\tlocal val=\"$1\"\n\techo \"${val//\\'/\\'\\'}\"\n\treturn 0\n}\n\ndb_query() {\n\tlocal query=\"$1\"\n\tsqlite3 -cmd \".timeout 5000\" \"$REGISTRY_DB\" \"$query\" 2>/dev/null\n\treturn $?\n}\n\ndb_query_csv() {\n\tlocal query=\"$1\"\n\tsqlite3 -cmd \".timeout 5000\" -csv -header \"$REGISTRY_DB\" \"$query\" 2>/dev/null\n\treturn $?\n}\n\ndb_query_json() {\n\tlocal query=\"$1\"\n\tsqlite3 -cmd \".timeout 5000\" -json \"$REGISTRY_DB\" \"$query\" 2>/dev/null\n\treturn $?\n}\n\n# =============================================================================\n# Sync: Subagent Frontmatter\n# =============================================================================\n\n# Parse model frontmatter fields from a single markdown file.\n# Outputs: model_full, model_tier, model_fallback (one per line as key=value).\n# Returns 1 if the file has no valid model frontmatter.\n_parse_subagent_frontmatter() {\n\tlocal md_file=\"$1\"\n\tlocal model_full=\"\" model_tier=\"\" model_fallback=\"\"\n\tlocal in_frontmatter=false\n\tlocal line_num=0\n\n\twhile IFS= read -r line; do\n\t\tline_num=$((line_num + 1))\n\t\tif [[ $line_num -eq 1 && \"$line\" == \"---\" ]]; then\n\t\t\tin_frontmatter=true\n\t\t\tcontinue\n\t\tfi\n\t\tif [[ \"$in_frontmatter\" == \"true\" && \"$line\" == \"---\" ]]; then\n\t\t\tbreak\n\t\tfi\n\t\tif [[ \"$in_frontmatter\" == \"true\" ]]; then\n\t\t\tcase \"$line\" in\n\t\t\tmodel:*)\n\t\t\t\tmodel_full=\"${line#model:}\"\n\t\t\t\tmodel_full=\"${model_full#\"${model_full%%[![:space:]]*}\"}\"\n\t\t\t\t;;\n\t\t\tmodel-tier:*)\n\t\t\t\tmodel_tier=\"${line#model-tier:}\"\n\t\t\t\tmodel_tier=\"${model_tier#\"${model_tier%%[![:space:]]*}\"}\"\n\t\t\t\t;;\n\t\t\tmodel-fallback:*)\n\t\t\t\tmodel_fallback=\"${line#model-fallback:}\"\n\t\t\t\tmodel_fallback=\"${model_fallback#\"${model_fallback%%[![:space:]]*}\"}\"\n\t\t\t\t;;\n\t\t\tesac\n\t\tfi\n\tdone <\"$md_file\"\n\n\tif [[ -z \"$model_tier\" || -z \"$model_full\" ]]; then\n\t\treturn 1\n\tfi\n\n\tprintf 'model_full=%s\\nmodel_tier=%s\\nmodel_fallback=%s\\n' \\\n\t\t\"$model_full\" \"$model_tier\" \"$model_fallback\"\n\treturn 0\n}\n\n# Upsert a single subagent model file into the subagent_models table.\n# Arguments: md_file basename_file updated_ref\n# Increments the named counter variable via eval on success.\n_sync_subagent_file() {\n\tlocal md_file=\"$1\"\n\tlocal basename_file=\"$2\"\n\tlocal updated_ref=\"$3\"\n\n\tlocal frontmatter\n\tfrontmatter=$(_parse_subagent_frontmatter \"$md_file\") || return 0\n\n\tlocal model_full model_tier model_fallback\n\tmodel_full=$(echo \"$frontmatter\" | grep '^model_full=' | cut -d= -f2-)\n\tmodel_tier=$(echo \"$frontmatter\" | grep '^model_tier=' | cut -d= -f2-)\n\tmodel_fallback=$(echo \"$frontmatter\" | grep '^model_fallback=' | cut -d= -f2-)\n\n\t# Extract short model_id from full ID (e.g., anthropic/claude-sonnet-4-6 -> claude-sonnet-4-6)\n\tlocal model_short\n\tmodel_short=\"${model_full#*/}\"\n\t# Strip trailing date suffix (e.g., -20250514)\n\tmodel_short=\"${model_short%-[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]}\"\n\n\tlocal norm_name\n\tnorm_name=$(normalize_model_name \"$model_full\")\n\n\tdb_query \"\n INSERT INTO subagent_models (tier, model_id, model_full_id, normalized_name, fallback_id, subagent_file, last_synced)\n VALUES (\n '$(sql_escape \"$model_tier\")',\n '$(sql_escape \"$model_short\")',\n '$(sql_escape \"$model_full\")',\n '$(sql_escape \"$norm_name\")',\n '$(sql_escape \"$model_fallback\")',\n '$(sql_escape \"$basename_file\")',\n strftime('%Y-%m-%dT%H:%M:%SZ', 'now')\n )\n ON CONFLICT(tier) DO UPDATE SET\n model_id = excluded.model_id,\n model_full_id = excluded.model_full_id,\n normalized_name = excluded.normalized_name,\n fallback_id = excluded.fallback_id,\n subagent_file = excluded.subagent_file,\n last_synced = excluded.last_synced;\n \" && eval \"${updated_ref}=\\$(( ${updated_ref} + 1 ))\"\n\treturn 0\n}\n\nsync_subagents() {\n\tlocal added=0\n\tlocal updated=0\n\n\tif [[ ! -d \"$MODELS_DIR\" ]]; then\n\t\tprint_warning \"Models directory not found: $MODELS_DIR\"\n\t\treturn 0\n\tfi\n\n\tlocal md_file\n\tfor md_file in \"$MODELS_DIR\"/*.md; do\n\t\t[[ ! -f \"$md_file\" ]] && continue\n\t\tlocal basename_file\n\t\tbasename_file=$(basename \"$md_file\")\n\n\t\t# Skip README and non-model files\n\t\t[[ \"$basename_file\" == \"README.md\" ]] && continue\n\t\t[[ \"$basename_file\" == *\"-reviewer.md\" ]] && continue\n\n\t\t_sync_subagent_file \"$md_file\" \"$basename_file\" updated\n\tdone\n\n\t# Log sync\n\tdb_query \"\n INSERT INTO sync_log (sync_type, source, models_added, models_updated, details)\n VALUES ('subagents', 'frontmatter', $added, $updated, 'Synced from $MODELS_DIR');\n \"\n\n\tprint_info \"Subagent sync: $updated tiers updated from frontmatter\"\n\treturn 0\n}\n\n# =============================================================================\n# Sync: Embedded Model Data (from compare-models-helper.sh)\n# =============================================================================\n\n# Extract the MODEL_DATA block from compare-models-helper.sh.\n# Outputs the raw pipe-delimited lines to stdout; returns 1 if not found.\n_extract_model_data() {\n\tlocal compare_helper=\"$1\"\n\tlocal model_data=\"\"\n\tlocal in_model_data=false\n\twhile IFS= read -r line; do\n\t\tif [[ \"$line\" == 'readonly MODEL_DATA=\"'* ]]; then\n\t\t\tin_model_data=true\n\t\t\tmodel_data=\"${line#*=\\\"}\"\n\t\t\tif [[ \"$model_data\" == *'\"' ]]; then\n\t\t\t\tmodel_data=\"${model_data%\\\"}\"\n\t\t\t\tbreak\n\t\t\tfi\n\t\t\tcontinue\n\t\tfi\n\t\tif [[ \"$in_model_data\" == \"true\" ]]; then\n\t\t\tif [[ \"$line\" == *'\"' ]]; then\n\t\t\t\tmodel_data=\"${model_data}\n${line%\\\"}\"\n\t\t\t\tbreak\n\t\t\tfi\n\t\t\tmodel_data=\"${model_data}\n${line}\"\n\t\tfi\n\tdone <\"$compare_helper\"\n\n\tif [[ -z \"$model_data\" ]]; then\n\t\treturn 1\n\tfi\n\tprintf '%s\\n' \"$model_data\"\n\treturn 0\n}\n\n# Upsert a single pipe-delimited model line into the models table.\n# Arguments: \n# Increments the named counter variables via eval.\n_upsert_embedded_model() {\n\tlocal line=\"$1\"\n\tlocal added_ref=\"$2\"\n\tlocal updated_ref=\"$3\"\n\n\tlocal model_id provider display_name ctx input output tier caps best_for\n\tmodel_id=$(echo \"$line\" | cut -d'|' -f1)\n\tprovider=$(echo \"$line\" | cut -d'|' -f2)\n\tdisplay_name=$(echo \"$line\" | cut -d'|' -f3)\n\tctx=$(echo \"$line\" | cut -d'|' -f4)\n\tinput=$(echo \"$line\" | cut -d'|' -f5)\n\toutput=$(echo \"$line\" | cut -d'|' -f6)\n\ttier=$(echo \"$line\" | cut -d'|' -f7)\n\tcaps=$(echo \"$line\" | cut -d'|' -f8)\n\tbest_for=$(echo \"$line\" | cut -d'|' -f9)\n\n\tlocal norm_name\n\tnorm_name=$(normalize_model_name \"$model_id\")\n\n\tlocal existing\n\texisting=$(db_query \"SELECT model_id FROM models WHERE model_id='$(sql_escape \"$model_id\")' AND provider='$(sql_escape \"$provider\")';\")\n\n\tif [[ -n \"$existing\" ]]; then\n\t\tdb_query \"\n UPDATE models SET\n display_name = '$(sql_escape \"$display_name\")',\n normalized_name = '$(sql_escape \"$norm_name\")',\n context_window = $ctx,\n input_price = $input,\n output_price = $output,\n tier = '$(sql_escape \"$tier\")',\n capabilities = '$(sql_escape \"$caps\")',\n best_for = '$(sql_escape \"$best_for\")',\n last_seen = strftime('%Y-%m-%dT%H:%M:%SZ', 'now'),\n source = 'embedded'\n WHERE model_id = '$(sql_escape \"$model_id\")' AND provider = '$(sql_escape \"$provider\")';\n \"\n\t\teval \"${updated_ref}=\\$(( ${updated_ref} + 1 ))\"\n\telse\n\t\tdb_query \"\n INSERT INTO models (model_id, provider, display_name, normalized_name, context_window, input_price, output_price, tier, capabilities, best_for, source)\n VALUES (\n '$(sql_escape \"$model_id\")',\n '$(sql_escape \"$provider\")',\n '$(sql_escape \"$display_name\")',\n '$(sql_escape \"$norm_name\")',\n $ctx, $input, $output,\n '$(sql_escape \"$tier\")',\n '$(sql_escape \"$caps\")',\n '$(sql_escape \"$best_for\")',\n 'embedded'\n );\n \"\n\t\teval \"${added_ref}=\\$(( ${added_ref} + 1 ))\"\n\tfi\n\treturn 0\n}\n\nsync_embedded() {\n\tlocal added=0\n\tlocal updated=0\n\n\tlocal compare_helper=\"${SCRIPT_DIR}/compare-models-helper.sh\"\n\tif [[ ! -f \"$compare_helper\" ]]; then\n\t\tprint_warning \"compare-models-helper.sh not found\"\n\t\treturn 0\n\tfi\n\n\tlocal model_data\n\tif ! model_data=$(_extract_model_data \"$compare_helper\"); then\n\t\tprint_warning \"Could not extract MODEL_DATA from compare-models-helper.sh\"\n\t\treturn 0\n\tfi\n\n\twhile IFS= read -r line; do\n\t\t[[ -z \"$line\" ]] && continue\n\t\t_upsert_embedded_model \"$line\" added updated\n\tdone <<<\"$model_data\"\n\n\tdb_query \"\n INSERT INTO sync_log (sync_type, source, models_added, models_updated, details)\n VALUES ('embedded', 'compare-models-helper.sh', $added, $updated, 'Parsed MODEL_DATA');\n \"\n\n\tprint_info \"Embedded sync: $added added, $updated updated from compare-models-helper.sh\"\n\treturn 0\n}\n\n# =============================================================================\n# Sync: OpenCode Models (preferred — uses opencode CLI model registry)\n# =============================================================================\n# OpenCode maintains a model registry sourced from models.dev that includes\n# all providers the user has configured. This is faster and more reliable than\n# probing individual provider APIs directly, and works even without direct\n# API keys (OpenCode routes through its gateway for opencode/* models).\n\nsync_opencode() {\n\tlocal added=0\n\n\t# Check if opencode CLI is available\n\tif ! command -v opencode &>/dev/null; then\n\t\tprint_info \"OpenCode CLI not found, skipping opencode model sync\"\n\t\treturn 0\n\tfi\n\n\t# Try the cached models file first (instant, no network)\n\tlocal cache_file=\"${HOME}/.cache/opencode/models.json\"\n\tlocal model_ids=\"\"\n\n\t# Only extract models from providers we track (avoids processing 2500+ models\n\t# from the full models.dev registry — we only need ~100 from relevant providers)\n\tlocal relevant_providers='[\"anthropic\",\"openai\",\"google\",\"openrouter\",\"groq\",\"deepseek\",\"opencode\"]'\n\n\tif [[ -f \"$cache_file\" && -s \"$cache_file\" ]]; then\n\t\tmodel_ids=$(jq -r --argjson providers \"$relevant_providers\" '\n to_entries[] |\n select(.key as $k | $providers | index($k)) |\n .key as $provider |\n .value.models // {} |\n keys[] |\n \"\\($provider)/\\(.)\"\n ' \"$cache_file\" 2>/dev/null) || true\n\tfi\n\n\t# Fallback: use opencode models CLI if cache is empty or missing\n\tif [[ -z \"$model_ids\" ]]; then\n\t\tprint_info \" Cache miss, querying opencode models CLI...\"\n\t\tmodel_ids=$(timeout \"$DEFAULT_TIMEOUT\" opencode models 2>/dev/null |\n\t\t\tgrep -E '^(anthropic|openai|google|openrouter|groq|deepseek|opencode)/' | sort) || true\n\tfi\n\n\tif [[ -z \"$model_ids\" ]]; then\n\t\tprint_warning \"No models discovered from OpenCode\"\n\t\treturn 0\n\tfi\n\n\t# Build batch SQL for all models (much faster than individual queries)\n\tlocal sql_batch=\"BEGIN TRANSACTION;\"\n\twhile IFS= read -r full_id; do\n\t\t[[ -z \"$full_id\" ]] && continue\n\t\t# Skip non-text models (embeddings, tts, whisper, image-only)\n\t\tcase \"$full_id\" in\n\t\t*embed* | *tts* | *whisper* | *dall-e* | *moderation* | *image*) continue ;;\n\t\tesac\n\n\t\tlocal provider\n\t\tprovider=\"${full_id%%/*}\"\n\n\t\t# Normalize provider name to match our conventions\n\t\tlocal norm_provider\n\t\tcase \"$provider\" in\n\t\tanthropic) norm_provider=\"Anthropic\" ;;\n\t\topenai) norm_provider=\"OpenAI\" ;;\n\t\tgoogle) norm_provider=\"Google\" ;;\n\t\topenrouter) norm_provider=\"OpenRouter\" ;;\n\t\tgroq) norm_provider=\"Groq\" ;;\n\t\tdeepseek) norm_provider=\"DeepSeek\" ;;\n\t\topencode) norm_provider=\"OpenCode\" ;;\n\t\t*) norm_provider=\"$provider\" ;;\n\t\tesac\n\n\t\tsql_batch=\"${sql_batch}\n INSERT INTO provider_models (model_id, provider, in_registry, in_subagents, discovered_at)\n VALUES (\n '$(sql_escape \"$full_id\")',\n '$(sql_escape \"$norm_provider\")',\n 0, 0,\n strftime('%Y-%m-%dT%H:%M:%SZ', 'now')\n )\n ON CONFLICT(model_id, provider) DO UPDATE SET\n discovered_at = excluded.discovered_at;\"\n\t\tadded=$((added + 1))\n\tdone <<<\"$model_ids\"\n\tsql_batch=\"${sql_batch} COMMIT;\"\n\n\t# Execute batch insert\n\tdb_query \"$sql_batch\"\n\n\tdb_query \"\n INSERT INTO sync_log (sync_type, source, models_added, models_updated, details)\n VALUES ('opencode', 'opencode-models', $added, 0, 'Discovered from OpenCode model registry');\n \"\n\n\tprint_info \"OpenCode sync: $added models discovered\"\n\treturn 0\n}\n\n# =============================================================================\n# Sync: Provider APIs (fallback — direct API key probing)\n# =============================================================================\n# Falls back to direct API probing when OpenCode CLI is not available.\n# Requires individual provider API keys in environment.\n\n# Fetch models JSON from a single provider API using the given key.\n# Outputs newline-separated model IDs to stdout; returns 1 on failure.\n_fetch_provider_models() {\n\tlocal provider=\"$1\"\n\tlocal key_value=\"$2\"\n\n\tlocal models_json=\"\"\n\tcase \"$provider\" in\n\tAnthropic)\n\t\tmodels_json=$(curl -s --max-time \"$DEFAULT_TIMEOUT\" \\\n\t\t\t-H \"x-api-key: ${key_value}\" \\\n\t\t\t-H \"anthropic-version: 2023-06-01\" \\\n\t\t\t\"https://api.anthropic.com/v1/models\" 2>/dev/null) || return 1\n\t\t;;\n\tOpenAI)\n\t\tmodels_json=$(curl -s --max-time \"$DEFAULT_TIMEOUT\" \\\n\t\t\t-H \"Authorization: Bearer ${key_value}\" \\\n\t\t\t\"https://api.openai.com/v1/models\" 2>/dev/null) || return 1\n\t\t;;\n\tGoogle)\n\t\tmodels_json=$(curl -s --max-time \"$DEFAULT_TIMEOUT\" \\\n\t\t\t\"https://generativelanguage.googleapis.com/v1beta/models?key=${key_value}\" 2>/dev/null) || return 1\n\t\t;;\n\tOpenRouter)\n\t\tmodels_json=$(curl -s --max-time \"$DEFAULT_TIMEOUT\" \\\n\t\t\t-H \"Authorization: Bearer ${key_value}\" \\\n\t\t\t\"https://openrouter.ai/api/v1/models\" 2>/dev/null) || return 1\n\t\t;;\n\tGroq)\n\t\tmodels_json=$(curl -s --max-time \"$DEFAULT_TIMEOUT\" \\\n\t\t\t-H \"Authorization: Bearer ${key_value}\" \\\n\t\t\t\"https://api.groq.com/openai/v1/models\" 2>/dev/null) || return 1\n\t\t;;\n\tDeepSeek)\n\t\tmodels_json=$(curl -s --max-time \"$DEFAULT_TIMEOUT\" \\\n\t\t\t-H \"Authorization: Bearer ${key_value}\" \\\n\t\t\t\"https://api.deepseek.com/v1/models\" 2>/dev/null) || return 1\n\t\t;;\n\t*)\n\t\treturn 1\n\t\t;;\n\tesac\n\n\t[[ -z \"$models_json\" ]] && return 1\n\n\tcase \"$provider\" in\n\tGoogle)\n\t\techo \"$models_json\" | jq -r '.models[].name // empty' 2>/dev/null | sed 's|^models/||' | sort || return 1\n\t\t;;\n\t*)\n\t\techo \"$models_json\" | jq -r '.data[].id // empty' 2>/dev/null | sort || return 1\n\t\t;;\n\tesac\n\treturn 0\n}\n\n# Upsert a single model ID into provider_models for the given provider.\n_upsert_provider_model() {\n\tlocal mid=\"$1\"\n\tlocal provider=\"$2\"\n\n\tlocal in_registry\n\tin_registry=$(db_query \"SELECT COUNT(*) FROM models WHERE model_id LIKE '%$(sql_escape \"$mid\")%' AND provider='$(sql_escape \"$provider\")';\")\n\tlocal in_reg_flag=0\n\t[[ \"$in_registry\" -gt 0 ]] && in_reg_flag=1\n\n\tlocal in_subagents\n\tin_subagents=$(db_query \"SELECT COUNT(*) FROM subagent_models WHERE model_full_id LIKE '%$(sql_escape \"$mid\")%';\")\n\tlocal in_sub_flag=0\n\t[[ \"$in_subagents\" -gt 0 ]] && in_sub_flag=1\n\n\tdb_query \"\n INSERT INTO provider_models (model_id, provider, in_registry, in_subagents, discovered_at)\n VALUES (\n '$(sql_escape \"$mid\")',\n '$(sql_escape \"$provider\")',\n $in_reg_flag,\n $in_sub_flag,\n strftime('%Y-%m-%dT%H:%M:%SZ', 'now')\n )\n ON CONFLICT(model_id, provider) DO UPDATE SET\n in_registry = excluded.in_registry,\n in_subagents = excluded.in_subagents,\n discovered_at = excluded.discovered_at;\n \"\n\treturn 0\n}\n\nsync_providers() {\n\tlocal added=0\n\n\t# Skip if opencode sync already ran successfully (check sync_log)\n\tlocal opencode_synced\n\topencode_synced=$(db_query \"\n SELECT models_added FROM sync_log\n WHERE sync_type = 'opencode'\n AND (julianday('now') - julianday(timestamp)) * 86400 < 60\n ORDER BY id DESC LIMIT 1;\n \" 2>/dev/null || echo \"\")\n\n\tif [[ -n \"$opencode_synced\" && \"$opencode_synced\" -gt 0 ]]; then\n\t\tprint_info \"Skipping direct API probing (OpenCode sync already discovered $opencode_synced models)\"\n\t\treturn 0\n\tfi\n\n\tlocal provider_env_keys=\"Anthropic|ANTHROPIC_API_KEY\nOpenAI|OPENAI_API_KEY\nGoogle|GOOGLE_API_KEY,GEMINI_API_KEY\nOpenRouter|OPENROUTER_API_KEY\nGroq|GROQ_API_KEY\nDeepSeek|DEEPSEEK_API_KEY\"\n\n\twhile IFS= read -r pline; do\n\t\tlocal provider key_names\n\t\tprovider=$(echo \"$pline\" | cut -d'|' -f1)\n\t\tkey_names=$(echo \"$pline\" | cut -d'|' -f2)\n\n\t\tlocal key_value=\"\"\n\t\tlocal -a keys\n\t\tIFS=',' read -ra keys <<<\"$key_names\"\n\t\tfor key_name in \"${keys[@]}\"; do\n\t\t\tif [[ -n \"${!key_name:-}\" ]]; then\n\t\t\t\tkey_value=\"${!key_name}\"\n\t\t\t\tbreak\n\t\t\tfi\n\t\tdone\n\n\t\t[[ -z \"$key_value\" ]] && continue\n\n\t\tlocal model_ids=\"\"\n\t\tmodel_ids=$(_fetch_provider_models \"$provider\" \"$key_value\") || continue\n\t\t[[ -z \"$model_ids\" ]] && continue\n\n\t\tlocal provider_count=0\n\t\twhile IFS= read -r mid; do\n\t\t\t[[ -z \"$mid\" ]] && continue\n\t\t\t_upsert_provider_model \"$mid\" \"$provider\"\n\t\t\tprovider_count=$((provider_count + 1))\n\t\tdone <<<\"$model_ids\"\n\n\t\tadded=$((added + provider_count))\n\t\tprint_info \" $provider: $provider_count models discovered\"\n\tdone <<<\"$provider_env_keys\"\n\n\tdb_query \"\n INSERT INTO sync_log (sync_type, source, models_added, models_updated, details)\n VALUES ('provider_api', 'live', $added, 0, 'Discovered from provider APIs');\n \"\n\n\tprint_info \"Provider sync: $added total models discovered from APIs\"\n\treturn 0\n}\n\n# =============================================================================\n# Commands\n# =============================================================================\n\ncmd_sync() {\n\tlocal force=false\n\tlocal quiet=false\n\n\twhile [[ $# -gt 0 ]]; do\n\t\tcase \"$1\" in\n\t\t--force)\n\t\t\tforce=true\n\t\t\tshift\n\t\t\t;;\n\t\t--quiet)\n\t\t\tquiet=true\n\t\t\tshift\n\t\t\t;;\n\t\t*) shift ;;\n\t\tesac\n\tdone\n\n\t# Check if sync is needed\n\tif [[ \"$force\" != \"true\" ]]; then\n\t\tlocal last_sync\n\t\tlast_sync=$(db_query \"SELECT timestamp FROM sync_log ORDER BY id DESC LIMIT 1;\" 2>/dev/null || echo \"\")\n\t\tif [[ -n \"$last_sync\" ]]; then\n\t\t\tlocal last_epoch now_epoch\n\t\t\tif [[ \"$(uname)\" == \"Darwin\" ]]; then\n\t\t\t\tlast_epoch=$(TZ=UTC date -j -f \"%Y-%m-%dT%H:%M:%SZ\" \"$last_sync\" \"+%s\" 2>/dev/null || echo \"0\")\n\t\t\telse\n\t\t\t\tlast_epoch=$(date -d \"$last_sync\" \"+%s\" 2>/dev/null || echo \"0\")\n\t\t\tfi\n\t\t\tnow_epoch=$(date \"+%s\")\n\t\t\tlocal age=$((now_epoch - last_epoch))\n\t\t\tif [[ $age -lt $SYNC_INTERVAL ]]; then\n\t\t\t\tlocal hours_ago=$((age / 3600))\n\t\t\t\t[[ \"$quiet\" != \"true\" ]] && print_info \"Registry synced ${hours_ago}h ago (interval: $((SYNC_INTERVAL / 3600))h). Use --force to resync.\"\n\t\t\t\treturn 0\n\t\t\tfi\n\t\tfi\n\tfi\n\n\t[[ \"$quiet\" != \"true\" ]] && echo \"\"\n\t[[ \"$quiet\" != \"true\" ]] && echo \"Model Registry Sync\"\n\t[[ \"$quiet\" != \"true\" ]] && echo \"===================\"\n\t[[ \"$quiet\" != \"true\" ]] && echo \"\"\n\n\t# Backup before sync\n\tif [[ -f \"$REGISTRY_DB\" ]]; then\n\t\tbackup_sqlite_db \"$REGISTRY_DB\" \"pre-sync\" >/dev/null 2>&1 || true\n\tfi\n\n\t# Phase 1: Sync subagent frontmatter\n\t[[ \"$quiet\" != \"true\" ]] && print_info \"Phase 1: Syncing subagent frontmatter...\"\n\tsync_subagents\n\n\t# Phase 2: Sync embedded model data\n\t[[ \"$quiet\" != \"true\" ]] && print_info \"Phase 2: Syncing embedded model data...\"\n\tsync_embedded\n\n\t# Phase 3: Sync from OpenCode model registry (preferred — fast, no API keys needed)\n\t[[ \"$quiet\" != \"true\" ]] && print_info \"Phase 3: Discovering models from OpenCode registry...\"\n\tsync_opencode\n\n\t# Phase 4: Fallback to direct provider API probing (skipped if Phase 3 succeeded)\n\t[[ \"$quiet\" != \"true\" ]] && print_info \"Phase 4: Direct provider API discovery (fallback)...\"\n\tsync_providers\n\n\t# Cleanup old backups\n\tcleanup_sqlite_backups \"$REGISTRY_DB\" 3\n\n\t[[ \"$quiet\" != \"true\" ]] && echo \"\"\n\t[[ \"$quiet\" != \"true\" ]] && print_success \"Registry sync complete\"\n\treturn 0\n}\n\ncmd_list() {\n\tlocal json_flag=false\n\tlocal filter_provider=\"\"\n\tlocal filter_tier=\"\"\n\n\twhile [[ $# -gt 0 ]]; do\n\t\tcase \"$1\" in\n\t\t--json)\n\t\t\tjson_flag=true\n\t\t\tshift\n\t\t\t;;\n\t\t--provider)\n\t\t\tfilter_provider=\"${2:-}\"\n\t\t\tshift 2\n\t\t\t;;\n\t\t--tier)\n\t\t\tfilter_tier=\"${2:-}\"\n\t\t\tshift 2\n\t\t\t;;\n\t\t*) shift ;;\n\t\tesac\n\tdone\n\n\tlocal where_clause=\"WHERE 1=1\"\n\t[[ -n \"$filter_provider\" ]] && where_clause=\"$where_clause AND provider='$(sql_escape \"$filter_provider\")'\"\n\t[[ -n \"$filter_tier\" ]] && where_clause=\"$where_clause AND tier='$(sql_escape \"$filter_tier\")'\"\n\n\tif [[ \"$json_flag\" == \"true\" ]]; then\n\t\tdb_query_json \"\n SELECT model_id, provider, display_name, context_window, input_price, output_price,\n tier, capabilities, status, deprecated, last_seen\n FROM models $where_clause\n ORDER BY provider, input_price;\n \"\n\t\treturn $?\n\tfi\n\n\techo \"\"\n\techo \"Model Registry\"\n\techo \"==============\"\n\techo \"\"\n\n\tlocal count\n\tcount=$(db_query \"SELECT COUNT(*) FROM models $where_clause;\")\n\n\tif [[ \"$count\" -eq 0 ]]; then\n\t\tprint_warning \"Registry is empty. Run 'model-registry-helper.sh sync' first.\"\n\t\treturn 0\n\tfi\n\n\tprintf \"%-24s %-10s %-8s %-10s %-10s %-7s %-8s\\n\" \\\n\t\t\"Model\" \"Provider\" \"Context\" \"In/1M\" \"Out/1M\" \"Tier\" \"Status\"\n\tprintf \"%-24s %-10s %-8s %-10s %-10s %-7s %-8s\\n\" \\\n\t\t\"-----\" \"--------\" \"-------\" \"-----\" \"------\" \"----\" \"------\"\n\n\tdb_query \"\n SELECT model_id, provider, context_window, input_price, output_price, tier, status, deprecated\n FROM models $where_clause\n ORDER BY provider, input_price;\n \" | while IFS='|' read -r mid prov ctx inp outp tier stat dep; do\n\t\tlocal ctx_fmt\n\t\tif [[ \"$ctx\" -ge 1000000 ]]; then\n\t\t\tctx_fmt=\"1M\"\n\t\telif [[ \"$ctx\" -ge 500000 ]]; then\n\t\t\tctx_fmt=\"512K\"\n\t\telif [[ \"$ctx\" -ge 200000 ]]; then\n\t\t\tctx_fmt=\"200K\"\n\t\telif [[ \"$ctx\" -ge 128000 ]]; then\n\t\t\tctx_fmt=\"128K\"\n\t\telse\n\t\t\tctx_fmt=\"${ctx}\"\n\t\tfi\n\n\t\tlocal status_display=\"$stat\"\n\t\t[[ \"$dep\" == \"1\" ]] && status_display=\"DEPR\"\n\n\t\tprintf \"%-24s %-10s %-8s \\$%-9s \\$%-9s %-7s %-8s\\n\" \\\n\t\t\t\"$mid\" \"$prov\" \"$ctx_fmt\" \"$inp\" \"$outp\" \"$tier\" \"$status_display\"\n\tdone\n\n\techo \"\"\n\techo \"Total: $count models\"\n\treturn 0\n}\n\ncmd_status() {\n\tlocal json_flag=false\n\n\twhile [[ $# -gt 0 ]]; do\n\t\tcase \"$1\" in\n\t\t--json)\n\t\t\tjson_flag=true\n\t\t\tshift\n\t\t\t;;\n\t\t*) shift ;;\n\t\tesac\n\tdone\n\n\tif [[ ! -f \"$REGISTRY_DB\" ]]; then\n\t\tprint_warning \"Registry not initialized. Run 'model-registry-helper.sh sync' first.\"\n\t\treturn 0\n\tfi\n\n\tlocal total_models total_providers total_subagents total_provider_models\n\tlocal last_sync deprecated_count\n\n\ttotal_models=$(db_query \"SELECT COUNT(*) FROM models;\")\n\ttotal_providers=$(db_query \"SELECT COUNT(DISTINCT provider) FROM models;\")\n\ttotal_subagents=$(db_query \"SELECT COUNT(*) FROM subagent_models;\")\n\ttotal_provider_models=$(db_query \"SELECT COUNT(*) FROM provider_models;\")\n\tdeprecated_count=$(db_query \"SELECT COUNT(*) FROM models WHERE deprecated=1;\")\n\tlast_sync=$(db_query \"SELECT timestamp FROM sync_log ORDER BY id DESC LIMIT 1;\" || echo \"never\")\n\n\t# Calculate staleness\n\tlocal staleness=\"unknown\"\n\tif [[ -n \"$last_sync\" && \"$last_sync\" != \"never\" ]]; then\n\t\tlocal last_epoch now_epoch\n\t\tif [[ \"$(uname)\" == \"Darwin\" ]]; then\n\t\t\tlast_epoch=$(TZ=UTC date -j -f \"%Y-%m-%dT%H:%M:%SZ\" \"$last_sync\" \"+%s\" 2>/dev/null || echo \"0\")\n\t\telse\n\t\t\tlast_epoch=$(date -d \"$last_sync\" \"+%s\" 2>/dev/null || echo \"0\")\n\t\tfi\n\t\tnow_epoch=$(date \"+%s\")\n\t\tlocal age=$((now_epoch - last_epoch))\n\t\tif [[ $age -lt 3600 ]]; then\n\t\t\tstaleness=\"fresh ($((age / 60))m ago)\"\n\t\telif [[ $age -lt 86400 ]]; then\n\t\t\tstaleness=\"recent ($((age / 3600))h ago)\"\n\t\telse\n\t\t\tstaleness=\"stale ($((age / 86400))d ago)\"\n\t\tfi\n\tfi\n\n\tif [[ \"$json_flag\" == \"true\" ]]; then\n\t\techo \"{\\\"total_models\\\":$total_models,\\\"total_providers\\\":$total_providers,\\\"subagent_tiers\\\":$total_subagents,\\\"provider_models_discovered\\\":$total_provider_models,\\\"deprecated\\\":$deprecated_count,\\\"last_sync\\\":\\\"$last_sync\\\",\\\"staleness\\\":\\\"$staleness\\\"}\"\n\t\treturn 0\n\tfi\n\n\techo \"\"\n\techo \"Model Registry Status\"\n\techo \"=====================\"\n\techo \"\"\n\techo \" Registry models: $total_models\"\n\techo \" Providers: $total_providers\"\n\techo \" Subagent tiers: $total_subagents\"\n\techo \" API-discovered: $total_provider_models\"\n\techo \" Deprecated: $deprecated_count\"\n\techo \" Last sync: ${last_sync:-never}\"\n\techo \" Staleness: $staleness\"\n\techo \"\"\n\n\t# Show subagent tier mapping\n\techo \"Subagent Tier Mapping:\"\n\techo \"\"\n\tprintf \" %-8s %-24s %-36s %-24s\\n\" \"Tier\" \"Model\" \"Full ID\" \"Fallback\"\n\tprintf \" %-8s %-24s %-36s %-24s\\n\" \"----\" \"-----\" \"-------\" \"--------\"\n\n\tdb_query \"SELECT tier, model_id, model_full_id, fallback_id FROM subagent_models ORDER BY tier;\" |\n\t\twhile IFS='|' read -r tier mid full_id fallback; do\n\t\t\tprintf \" %-8s %-24s %-36s %-24s\\n\" \"$tier\" \"$mid\" \"$full_id\" \"$fallback\"\n\t\tdone\n\n\techo \"\"\n\n\t# Show recent sync log\n\techo \"Recent Sync History:\"\n\techo \"\"\n\tdb_query \"\n SELECT timestamp, sync_type, source, models_added, models_updated\n FROM sync_log ORDER BY id DESC LIMIT 5;\n \" | while IFS='|' read -r ts stype src added updated; do\n\t\techo \" $ts $stype ($src): +$added ~$updated\"\n\tdone\n\n\techo \"\"\n\treturn 0\n}\n\ncmd_check() {\n\tlocal json_flag=false\n\n\twhile [[ $# -gt 0 ]]; do\n\t\tcase \"$1\" in\n\t\t--json)\n\t\t\tjson_flag=true\n\t\t\tshift\n\t\t\t;;\n\t\t*) shift ;;\n\t\tesac\n\tdone\n\n\techo \"\"\n\techo \"Model Availability Check\"\n\techo \"========================\"\n\techo \"\"\n\n\t# Check each subagent model against provider APIs\n\tlocal issues=0\n\n\tdb_query \"SELECT tier, model_id, model_full_id, fallback_id FROM subagent_models;\" |\n\t\twhile IFS='|' read -r tier mid full_id fallback; do\n\t\t\tlocal provider\n\t\t\tprovider=$(echo \"$full_id\" | cut -d'/' -f1)\n\n\t\t\t# Get normalized name for this subagent model\n\t\t\tlocal norm_name\n\t\t\tnorm_name=$(db_query \"SELECT normalized_name FROM subagent_models WHERE tier='$(sql_escape \"$tier\")';\" 2>/dev/null || echo \"\")\n\t\t\t[[ -z \"$norm_name\" ]] && norm_name=$(normalize_model_name \"$full_id\")\n\n\t\t\t# Check if model exists in provider_models (try both directions for fuzzy match)\n\t\t\tlocal found\n\t\t\tfound=$(db_query \"\n SELECT COUNT(*) FROM provider_models\n WHERE (model_id LIKE '%$(sql_escape \"$mid\")%'\n OR '$(sql_escape \"$mid\")' LIKE '%' || model_id || '%')\n AND provider LIKE '%$(sql_escape \"$provider\")%';\n \" 2>/dev/null || echo \"0\")\n\n\t\t\tlocal status_icon=\"Y\"\n\t\t\tlocal status_text=\"available\"\n\n\t\t\tif [[ \"$found\" -eq 0 ]]; then\n\t\t\t\t# Not found in API discovery - check embedded registry via normalized name\n\t\t\t\tlocal in_embedded\n\t\t\t\tin_embedded=$(db_query \"\n SELECT COUNT(*) FROM models\n WHERE normalized_name = '$(sql_escape \"$norm_name\")'\n OR model_id LIKE '%$(sql_escape \"$mid\")%'\n OR '$(sql_escape \"$mid\")' LIKE '%' || model_id || '%';\n \" 2>/dev/null || echo \"0\")\n\n\t\t\t\tif [[ \"$in_embedded\" -gt 0 ]]; then\n\t\t\t\t\tstatus_icon=\"?\"\n\t\t\t\t\tstatus_text=\"in registry, not verified via API\"\n\t\t\t\telse\n\t\t\t\t\tstatus_icon=\"!\"\n\t\t\t\t\tstatus_text=\"NOT FOUND in registry or API\"\n\t\t\t\t\tissues=$((issues + 1))\n\t\t\t\tfi\n\t\t\tfi\n\n\t\t\tprintf \" %s %-8s %-36s %s\\n\" \"$status_icon\" \"$tier\" \"$full_id\" \"$status_text\"\n\t\tdone\n\n\techo \"\"\n\tif [[ $issues -gt 0 ]]; then\n\t\tprint_warning \"$issues model(s) have availability issues\"\n\telse\n\t\tprint_success \"All configured models appear available\"\n\tfi\n\techo \"\"\n\treturn 0\n}\n\ncmd_suggest() {\n\tlocal json_flag=false\n\n\twhile [[ $# -gt 0 ]]; do\n\t\tcase \"$1\" in\n\t\t--json)\n\t\t\tjson_flag=true\n\t\t\tshift\n\t\t\t;;\n\t\t*) shift ;;\n\t\tesac\n\tdone\n\n\techo \"\"\n\techo \"Model Suggestions\"\n\techo \"=================\"\n\techo \"\"\n\techo \"Models discovered from provider APIs but not in the registry:\"\n\techo \"\"\n\n\tlocal count=0\n\tdb_query \"\n SELECT pm.model_id, pm.provider, pm.discovered_at\n FROM provider_models pm\n WHERE pm.in_registry = 0\n AND pm.in_subagents = 0\n ORDER BY pm.provider, pm.model_id;\n \" | while IFS='|' read -r mid prov discovered; do\n\t\t# Filter to interesting models (skip internal/deprecated-looking ones)\n\t\tcase \"$mid\" in\n\t\t*embed* | *tts* | *whisper* | *dall-e* | *moderation* | *babbage* | *davinci-00* | *search* | *instruct* | *realtime*)\n\t\t\tcontinue\n\t\t\t;;\n\t\tesac\n\t\techo \" [$prov] $mid (discovered: $discovered)\"\n\t\tcount=$((count + 1))\n\tdone\n\n\techo \"\"\n\tif [[ $count -eq 0 ]]; then\n\t\tprint_info \"No new model suggestions. Registry is up to date.\"\n\telse\n\t\techo \" $count potential models found.\"\n\t\techo \" Review and add relevant ones to compare-models-helper.sh MODEL_DATA\"\n\t\techo \" and create subagent files in $MODELS_DIR/ as needed.\"\n\tfi\n\techo \"\"\n\treturn 0\n}\n\ncmd_deprecations() {\n\tlocal json_flag=false\n\n\twhile [[ $# -gt 0 ]]; do\n\t\tcase \"$1\" in\n\t\t--json)\n\t\t\tjson_flag=true\n\t\t\tshift\n\t\t\t;;\n\t\t*) shift ;;\n\t\tesac\n\tdone\n\n\techo \"\"\n\techo \"Deprecated/Unavailable Models\"\n\techo \"=============================\"\n\techo \"\"\n\n\tlocal dep_count\n\tdep_count=$(db_query \"SELECT COUNT(*) FROM models WHERE deprecated=1;\")\n\n\tif [[ \"$dep_count\" -eq 0 ]]; then\n\t\tprint_info \"No deprecated models found in registry.\"\n\t\techo \"\"\n\n\t\t# Check for models in subagents that aren't in provider APIs\n\t\techo \"Models in subagents not confirmed by provider APIs:\"\n\t\techo \"\"\n\t\tlocal unconfirmed=0\n\t\tdb_query \"SELECT tier, model_id, model_full_id FROM subagent_models;\" |\n\t\t\twhile IFS='|' read -r tier mid full_id; do\n\t\t\t\tlocal short_id\n\t\t\t\tshort_id=\"${full_id#*/}\"\n\t\t\t\tlocal api_found\n\t\t\t\tapi_found=$(db_query \"\n SELECT COUNT(*) FROM provider_models\n WHERE model_id = '$(sql_escape \"$short_id\")';\n \" 2>/dev/null || echo \"0\")\n\n\t\t\t\tif [[ \"$api_found\" -eq 0 ]]; then\n\t\t\t\t\techo \" ! [$tier] $full_id - not found in API discovery\"\n\t\t\t\t\tunconfirmed=$((unconfirmed + 1))\n\t\t\t\tfi\n\t\t\tdone\n\n\t\tif [[ $unconfirmed -eq 0 ]]; then\n\t\t\tprint_info \" All subagent models confirmed via API discovery.\"\n\t\tfi\n\telse\n\t\tdb_query \"\n SELECT model_id, provider, deprecation_note, last_seen\n FROM models WHERE deprecated=1\n ORDER BY provider, model_id;\n \" | while IFS='|' read -r mid prov note last; do\n\t\t\techo \" ! $mid ($prov)\"\n\t\t\t[[ -n \"$note\" ]] && echo \" Note: $note\"\n\t\t\techo \" Last seen: $last\"\n\t\tdone\n\tfi\n\n\techo \"\"\n\treturn 0\n}\n\ncmd_diff() {\n\tlocal json_flag=false\n\n\twhile [[ $# -gt 0 ]]; do\n\t\tcase \"$1\" in\n\t\t--json)\n\t\t\tjson_flag=true\n\t\t\tshift\n\t\t\t;;\n\t\t*) shift ;;\n\t\tesac\n\tdone\n\n\techo \"\"\n\techo \"Registry vs Local Config Diff\"\n\techo \"==============================\"\n\techo \"\"\n\n\t# Compare subagent models with registry\n\techo \"Subagent Models vs Registry:\"\n\techo \"\"\n\n\tdb_query \"SELECT tier, model_id, model_full_id, normalized_name FROM subagent_models;\" |\n\t\twhile IFS='|' read -r tier mid full_id norm_name; do\n\t\t\t[[ -z \"$norm_name\" ]] && norm_name=$(normalize_model_name \"$full_id\")\n\t\t\tlocal reg_match\n\t\t\treg_match=$(db_query \"\n SELECT model_id, input_price, output_price\n FROM models\n WHERE normalized_name = '$(sql_escape \"$norm_name\")'\n OR model_id LIKE '%$(sql_escape \"$mid\")%'\n OR '$(sql_escape \"$mid\")' LIKE '%' || model_id || '%'\n LIMIT 1;\n \" 2>/dev/null || echo \"\")\n\n\t\t\tif [[ -n \"$reg_match\" ]]; then\n\t\t\t\tlocal reg_id reg_input reg_output\n\t\t\t\treg_id=$(echo \"$reg_match\" | cut -d'|' -f1)\n\t\t\t\treg_input=$(echo \"$reg_match\" | cut -d'|' -f2)\n\t\t\t\treg_output=$(echo \"$reg_match\" | cut -d'|' -f3)\n\t\t\t\tprintf \" = %-8s %-24s (registry: %s, \\$%s/\\$%s)\\n\" \\\n\t\t\t\t\t\"$tier\" \"$full_id\" \"$reg_id\" \"$reg_input\" \"$reg_output\"\n\t\t\telse\n\t\t\t\tprintf \" ! %-8s %-24s (NOT in registry)\\n\" \"$tier\" \"$full_id\"\n\t\t\tfi\n\t\tdone\n\n\techo \"\"\n\n\t# Show registry models not in subagents\n\techo \"Registry Models Not in Subagents:\"\n\techo \"\"\n\n\tlocal orphan_count=0\n\tdb_query \"\n SELECT m.model_id, m.provider, m.tier\n FROM models m\n WHERE NOT EXISTS (\n SELECT 1 FROM subagent_models s\n WHERE m.normalized_name = s.normalized_name\n OR m.model_id LIKE '%' || s.model_id || '%'\n OR s.model_id LIKE '%' || m.model_id || '%'\n )\n ORDER BY m.provider, m.model_id;\n \" | while IFS='|' read -r mid prov tier; do\n\t\tprintf \" + %-24s %-10s %-7s\\n\" \"$mid\" \"$prov\" \"$tier\"\n\t\torphan_count=$((orphan_count + 1))\n\tdone\n\n\techo \"\"\n\treturn 0\n}\n\ncmd_export() {\n\tlocal format=\"json\"\n\n\twhile [[ $# -gt 0 ]]; do\n\t\tcase \"$1\" in\n\t\t--csv)\n\t\t\tformat=\"csv\"\n\t\t\tshift\n\t\t\t;;\n\t\t*) shift ;;\n\t\tesac\n\tdone\n\n\tcase \"$format\" in\n\tcsv)\n\t\tdb_query_csv \"\n SELECT model_id, provider, display_name, context_window,\n input_price, output_price, tier, capabilities,\n best_for, status, deprecated, last_seen\n FROM models ORDER BY provider, model_id;\n \"\n\t\t;;\n\t*)\n\t\tdb_query_json \"\n SELECT model_id, provider, display_name, context_window,\n input_price, output_price, tier, capabilities,\n best_for, status, deprecated, last_seen\n FROM models ORDER BY provider, model_id;\n \"\n\t\t;;\n\tesac\n\treturn $?\n}\n\n# Classify a task description into a model tier.\n# Sets variables tier, reason, cost in the caller's scope via eval.\n# Arguments: \n_route_classify_tier() {\n\tlocal desc_lower=\"$1\"\n\tlocal out_tier=\"$2\"\n\tlocal out_reason=\"$3\"\n\tlocal out_cost=\"$4\"\n\n\tlocal tier=\"sonnet\"\n\tlocal reason=\"Default tier for general development tasks\"\n\tlocal cost=\"1x\"\n\n\t# Opus: architecture, design, security audit, novel, trade-off, evaluate\n\tif echo \"$desc_lower\" | grep -qE 'architect|system.design|security.audit|novel|trade.?off|evaluat|complex.*(plan|design|decision)|from.scratch'; then\n\t\ttier=\"opus\"\n\t\treason=\"Complex reasoning, architecture, or novel problem-solving\"\n\t\tcost=\"3x\"\n\t# Haiku: rename, format, classify, triage, commit message, simple\n\telif echo \"$desc_lower\" | grep -qE 'rename|reformat|classify|triage|commit.message|simple.*(text|transform)|extract.field|sort|prioriti[sz]e|route|tag|label'; then\n\t\ttier=\"haiku\"\n\t\treason=\"Simple classification, formatting, or text transform\"\n\t\tcost=\"0.25x\"\n\t# Flash: summarize, large context, bulk, read, scan\n\telif echo \"$desc_lower\" | grep -qE 'summari[sz]e|large.*(file|context|document|pdf)|bulk|scan.*files|read.*all|200.page|overview|skim'; then\n\t\ttier=\"flash\"\n\t\treason=\"Large context processing or summarization\"\n\t\tcost=\"0.20x\"\n\t# Pro: large codebase, many files, refactor across\n\telif echo \"$desc_lower\" | grep -qE 'large.codebase|500.file|many.files|refactor.across|entire.project|full.repo|cross.file'; then\n\t\ttier=\"pro\"\n\t\treason=\"Large codebase analysis requiring both context and reasoning\"\n\t\tcost=\"1.5x\"\n\tfi\n\n\teval \"${out_tier}=\\\"\\${tier}\\\"\"\n\teval \"${out_reason}=\\\"\\${reason}\\\"\"\n\teval \"${out_cost}=\\\"\\${cost}\\\"\"\n\treturn 0\n}\n\n# Look up primary and fallback models for a tier from the registry.\n# Falls back to hardcoded defaults when the registry is empty.\n# Sets primary_model and fallback_model in the caller's scope via eval.\n_route_lookup_models() {\n\tlocal tier=\"$1\"\n\tlocal out_primary=\"$2\"\n\tlocal out_fallback=\"$3\"\n\n\tlocal primary_model=\"\"\n\tlocal fallback_model=\"\"\n\tprimary_model=$(db_query \"SELECT model_id FROM models WHERE tier = '$tier' AND is_primary = 1 LIMIT 1;\" 2>/dev/null || echo \"\")\n\tfallback_model=$(db_query \"SELECT model_id FROM models WHERE tier = '$tier' AND is_fallback = 1 LIMIT 1;\" 2>/dev/null || echo \"\")\n\n\tcase \"$tier\" in\n\thaiku)\n\t\tprimary_model=\"${primary_model:-claude-haiku-4-5}\"\n\t\tfallback_model=\"${fallback_model:-gemini-2.5-flash}\"\n\t\t;;\n\tflash)\n\t\tprimary_model=\"${primary_model:-gemini-2.5-flash}\"\n\t\tfallback_model=\"${fallback_model:-gpt-4.1-mini}\"\n\t\t;;\n\tsonnet)\n\t\tprimary_model=\"${primary_model:-claude-sonnet-4-6}\"\n\t\tfallback_model=\"${fallback_model:-gpt-4.1}\"\n\t\t;;\n\tpro)\n\t\tprimary_model=\"${primary_model:-gemini-2.5-pro}\"\n\t\tfallback_model=\"${fallback_model:-claude-sonnet-4-6}\"\n\t\t;;\n\topus)\n\t\tprimary_model=\"${primary_model:-claude-opus-4-6}\"\n\t\tfallback_model=\"${fallback_model:-o3}\"\n\t\t;;\n\tesac\n\n\teval \"${out_primary}=\\\"\\${primary_model}\\\"\"\n\teval \"${out_fallback}=\\\"\\${fallback_model}\\\"\"\n\treturn 0\n}\n\n# Print the routing result in human-readable or JSON format.\n_route_output() {\n\tlocal json_flag=\"$1\"\n\tlocal description=\"$2\"\n\tlocal tier=\"$3\"\n\tlocal primary_model=\"$4\"\n\tlocal fallback_model=\"$5\"\n\tlocal cost=\"$6\"\n\tlocal reason=\"$7\"\n\n\tif [[ \"$json_flag\" == \"true\" ]]; then\n\t\tprintf '{\"tier\":\"%s\",\"model\":\"%s\",\"fallback\":\"%s\",\"cost\":\"%s\",\"reason\":\"%s\"}\\n' \\\n\t\t\t\"$tier\" \"$primary_model\" \"$fallback_model\" \"$cost\" \"$reason\"\n\telse\n\t\techo \"\"\n\t\techo \"Task: $description\"\n\t\techo \"\"\n\t\techo \" Recommended tier: $tier\"\n\t\techo \" Primary model: $primary_model\"\n\t\techo \" Fallback model: $fallback_model\"\n\t\techo \" Relative cost: $cost\"\n\t\techo \" Reason: $reason\"\n\t\techo \"\"\n\tfi\n\treturn 0\n}\n\ncmd_route() {\n\tlocal description=\"$*\"\n\tlocal json_flag=false\n\n\tlocal args=()\n\twhile [[ $# -gt 0 ]]; do\n\t\tcase \"$1\" in\n\t\t--json)\n\t\t\tjson_flag=true\n\t\t\tshift\n\t\t\t;;\n\t\t*)\n\t\t\targs+=(\"$1\")\n\t\t\tshift\n\t\t\t;;\n\t\tesac\n\tdone\n\tdescription=\"${args[*]}\"\n\n\tif [[ -z \"$description\" ]]; then\n\t\techo \"\"\n\t\techo \"Usage: model-registry-helper.sh route \"\n\t\techo \"\"\n\t\techo \"Examples:\"\n\t\techo \" model-registry-helper.sh route 'rename variable X to Y'\"\n\t\techo \" model-registry-helper.sh route 'design auth system architecture'\"\n\t\techo \" model-registry-helper.sh route 'summarize this 200-page PDF'\"\n\t\techo \"\"\n\t\treturn 1\n\tfi\n\n\tlocal desc_lower\n\tdesc_lower=$(echo \"$description\" | tr '[:upper:]' '[:lower:]')\n\n\tlocal tier reason cost\n\t_route_classify_tier \"$desc_lower\" tier reason cost\n\n\tlocal primary_model fallback_model\n\t_route_lookup_models \"$tier\" primary_model fallback_model\n\n\t_route_output \"$json_flag\" \"$description\" \"$tier\" \"$primary_model\" \"$fallback_model\" \"$cost\" \"$reason\"\n\treturn 0\n}\n\ncmd_help() {\n\techo \"\"\n\techo \"Model Registry Helper - Provider/Model Registry with Periodic Sync\"\n\techo \"===================================================================\"\n\techo \"\"\n\techo \"Usage: model-registry-helper.sh [command] [options]\"\n\techo \"\"\n\techo \"Commands:\"\n\techo \" sync Sync registry from all sources (subagents, embedded data, APIs)\"\n\techo \" list List all models in the registry\"\n\techo \" status Show registry health, staleness, and tier mapping\"\n\techo \" check Check configured subagent models against registry/APIs\"\n\techo \" suggest Suggest new models discovered from APIs but not tracked\"\n\techo \" deprecations Show deprecated/renamed/unavailable models\"\n\techo \" diff Show differences between registry and local config\"\n\techo \" route Recommend optimal model tier for a task description\"\n\techo \" export Export registry data (JSON or CSV)\"\n\techo \" help Show this help\"\n\techo \"\"\n\techo \"Options:\"\n\techo \" --json Output in JSON format (list, status, export)\"\n\techo \" --quiet Suppress informational output (sync)\"\n\techo \" --force Force full resync even if cache is fresh (sync)\"\n\techo \" --provider X Filter by provider name (list)\"\n\techo \" --tier X Filter by tier (list)\"\n\techo \" --csv Export as CSV instead of JSON (export)\"\n\techo \"\"\n\techo \"Examples:\"\n\techo \" model-registry-helper.sh sync # Full sync\"\n\techo \" model-registry-helper.sh sync --force # Force resync\"\n\techo \" model-registry-helper.sh list # List all models\"\n\techo \" model-registry-helper.sh list --provider Google # Google models only\"\n\techo \" model-registry-helper.sh status # Registry health\"\n\techo \" model-registry-helper.sh check # Verify subagent models\"\n\techo \" model-registry-helper.sh suggest # New model suggestions\"\n\techo \" model-registry-helper.sh diff # Config vs registry\"\n\techo \" model-registry-helper.sh route 'fix React bug' # Recommend model tier\"\n\techo \" model-registry-helper.sh export --json # Export as JSON\"\n\techo \"\"\n\techo \"Integration:\"\n\techo \" - Runs automatically on 'maestro update'\"\n\techo \" - Can be added to cron for periodic sync\"\n\techo \" - Storage: $REGISTRY_DB\"\n\techo \"\"\n\techo \"Data Sources:\"\n\techo \" 1. Subagent frontmatter ($MODELS_DIR/*.md)\"\n\techo \" 2. Embedded data (compare-models-helper.sh MODEL_DATA)\"\n\techo \" 3. OpenCode model registry (opencode models — preferred, from models.dev)\"\n\techo \" 4. Provider APIs (Anthropic, OpenAI, Google, OpenRouter, Groq, DeepSeek)\"\n\techo \"\"\n\treturn 0\n}\n\n# =============================================================================\n# Main\n# =============================================================================\n\nmain() {\n\tlocal command=\"${1:-help}\"\n\tshift || true\n\n\t# Initialize DB for all commands except help\n\tif [[ \"$command\" != \"help\" && \"$command\" != \"--help\" && \"$command\" != \"-h\" ]]; then\n\t\tinit_db || return 1\n\tfi\n\n\tcase \"$command\" in\n\tsync)\n\t\tcmd_sync \"$@\"\n\t\t;;\n\tlist)\n\t\tcmd_list \"$@\"\n\t\t;;\n\tstatus)\n\t\tcmd_status \"$@\"\n\t\t;;\n\tcheck)\n\t\tcmd_check \"$@\"\n\t\t;;\n\tsuggest)\n\t\tcmd_suggest \"$@\"\n\t\t;;\n\tdeprecations | deprecated)\n\t\tcmd_deprecations \"$@\"\n\t\t;;\n\tdiff)\n\t\tcmd_diff \"$@\"\n\t\t;;\n\troute)\n\t\tcmd_route \"$@\"\n\t\t;;\n\texport)\n\t\tcmd_export \"$@\"\n\t\t;;\n\thelp | --help | -h)\n\t\tcmd_help\n\t\t;;\n\t*)\n\t\tprint_error \"Unknown command: $command\"\n\t\tcmd_help\n\t\treturn 1\n\t\t;;\n\tesac\n\treturn $?\n}\n\nmain \"$@\"\n"}}, ".agents/scripts/crawl4ai-helper.sh": {"9810e84435a3cce44d75d0d30d68a54bd8faabe0": {"t": 1786068353.691683, "n": 32736, "h": "4df4734176d4d7dd", "c": "#!/usr/bin/env bash\n# SPDX-License-Identifier: MIT\n# SPDX-FileCopyrightText: 2025-2026 Aditya Pandey and Harvest\n# shellcheck disable=SC2086,SC2154\nset -euo pipefail\n\n# Crawl4AI Helper Script\n# AI-powered web crawler and scraper for LLM-friendly data extraction\n#\n# This script provides comprehensive management for Crawl4AI including:\n# - Docker deployment with monitoring dashboard\n# - Python package installation and setup\n# - MCP server integration for AI assistants\n# - Web scraping and data extraction operations\n# - CapSolver integration for CAPTCHA solving and anti-bot bypass\n#\n# Usage: ./crawl4ai-helper.sh [command] [options]\n# Commands:\n# install - Install Crawl4AI Python package\n# docker-setup - Setup Docker deployment with monitoring\n# docker-start - Start Docker container\n# docker-stop - Stop Docker container\n# mcp-setup - Setup MCP server integration\n# capsolver-setup - Setup CapSolver integration for CAPTCHA solving\n# crawl - Perform web crawling operation\n# extract - Extract structured data from URL\n# captcha-crawl - Crawl with CAPTCHA solving capabilities\n# status - Check Crawl4AI service status\n# help - Show this help message\n#\n# Author: Maestro Framework\n# Version: 1.0.0\n# License: MIT\n\n# Constants\nSCRIPT_DIR=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" && pwd)\" || exit\nsource \"${SCRIPT_DIR}/shared-constants.sh\"\n\nreadonly SCRIPT_DIR\nreadonly CONFIG_DIR=\"$SCRIPT_DIR/../configs\"\nreadonly DOCKER_IMAGE=\"unclecode/crawl4ai:latest\"\nreadonly DOCKER_CONTAINER=\"crawl4ai\"\nreadonly DOCKER_PORT=\"11235\"\nreadonly MCP_PORT=\"3009\"\nreadonly CRAWL4AI_STARTUP_WAIT=\"${CRAWL4AI_STARTUP_WAIT:-5}\" # seconds to wait for container readiness\nreadonly HELP_SHOW_MESSAGE=\"Show this help message\"\n\n# Print functions\nprint_header() {\n\tlocal message=\"$1\"\n\techo -e \"${PURPLE}🚀 $message${NC}\"\n\treturn 0\n}\n\n# Check if Docker is available\ncheck_docker() {\n\tif ! command -v docker &>/dev/null; then\n\t\tprint_error \"Docker is not installed. Please install Docker first.\"\n\t\treturn 1\n\tfi\n\n\tif ! docker info &>/dev/null; then\n\t\tprint_error \"Docker daemon is not running. Please start Docker.\"\n\t\treturn 1\n\tfi\n\n\treturn 0\n}\n\n# Check if Python is available\ncheck_python() {\n\tif ! command -v python3 &>/dev/null; then\n\t\tprint_error \"Python 3 is not installed. Please install Python 3.8+ first.\"\n\t\treturn 1\n\tfi\n\n\tlocal python_version python_major python_minor\n\tpython_version=$(python3 -c \"import sys; print(f'{sys.version_info.major}.{sys.version_info.minor}')\")\n\tpython_major=${python_version%.*}\n\tpython_minor=${python_version#*.}\n\n\tif [[ \"$python_major\" -lt 3 ]] || { [[ \"$python_major\" -eq 3 ]] && [[ \"$python_minor\" -lt 8 ]]; }; then\n\t\tprint_error \"Python 3.8+ is required. Current version: $python_version\"\n\t\treturn 1\n\tfi\n\n\treturn 0\n}\n\n# Install Crawl4AI Python package\ninstall_crawl4ai() {\n\tprint_header \"Installing Crawl4AI Python Package\"\n\n\tif ! check_python; then\n\t\treturn 1\n\tfi\n\n\tprint_info \"Installing Crawl4AI with pip...\"\n\tif pip3 install -U crawl4ai; then\n\t\tprint_success \"Crawl4AI installed successfully\"\n\telse\n\t\tprint_error \"Failed to install Crawl4AI\"\n\t\treturn 1\n\tfi\n\n\tprint_info \"Running post-installation setup...\"\n\tif crawl4ai-setup; then\n\t\tprint_success \"Crawl4AI setup completed\"\n\telse\n\t\tprint_warning \"Setup completed with warnings. Run 'crawl4ai-doctor' to check.\"\n\tfi\n\n\tprint_info \"Verifying installation...\"\n\tif crawl4ai-doctor; then\n\t\tprint_success \"Crawl4AI installation verified\"\n\telse\n\t\tprint_warning \"Installation verification completed with warnings\"\n\tfi\n\n\treturn 0\n}\n\n# Setup Docker deployment\ndocker_setup() {\n\tprint_header \"Setting up Crawl4AI Docker Deployment\"\n\n\tif ! check_docker; then\n\t\treturn 1\n\tfi\n\n\tprint_info \"Pulling Crawl4AI Docker image...\"\n\tif docker pull \"$DOCKER_IMAGE\"; then\n\t\tprint_success \"Docker image pulled successfully\"\n\telse\n\t\tprint_error \"Failed to pull Docker image\"\n\t\treturn 1\n\tfi\n\n\t# Create environment file if it doesn't exist\n\tlocal env_file=\"$CONFIG_DIR/.crawl4ai.env\"\n\tif [[ ! -f \"$env_file\" ]]; then\n\t\tprint_info \"Creating environment configuration...\"\n\t\tcat >\"$env_file\" <<'EOF'\n# Crawl4AI Environment Configuration\n# Add your API keys here for LLM integration\n\n# OpenAI\n# OPENAI_API_KEY=sk-your-key\n\n# Anthropic\n# ANTHROPIC_API_KEY=your-anthropic-key\n\n# Other providers\n# DEEPSEEK_API_KEY=your-deepseek-key\n# GROQ_API_KEY=your-groq-key\n# TOGETHER_API_KEY=your-together-key\n# MISTRAL_API_KEY=your-mistral-key\n# GEMINI_API_TOKEN=your-gemini-token\n\n# Global LLM settings\n# LLM_PROVIDER=openai/gpt-4o-mini\n# LLM_TEMPERATURE=0.7\nEOF\n\t\tprint_success \"Environment file created at $env_file\"\n\t\tprint_warning \"Please edit $env_file to add your API keys\"\n\tfi\n\n\treturn 0\n}\n\n# Start Docker container\ndocker_start() {\n\tprint_header \"Starting Crawl4AI Docker Container\"\n\n\tif ! check_docker; then\n\t\treturn 1\n\tfi\n\n\t# Stop existing container if running\n\tif docker ps -q -f name=\"$DOCKER_CONTAINER\" | grep -q .; then\n\t\tprint_info \"Stopping existing container...\"\n\t\tdocker stop \"$DOCKER_CONTAINER\" >/dev/null 2>&1\n\t\tdocker rm \"$DOCKER_CONTAINER\" >/dev/null 2>&1\n\tfi\n\n\tlocal env_file=\"$CONFIG_DIR/.crawl4ai.env\"\n\tlocal docker_args=(\n\t\t\"-d\"\n\t\t\"-p\" \"$DOCKER_PORT:$DOCKER_PORT\"\n\t\t\"--name\" \"$DOCKER_CONTAINER\"\n\t\t\"--shm-size=1g\"\n\t)\n\n\tif [[ -f \"$env_file\" ]]; then\n\t\tdocker_args+=(\"--env-file\" \"$env_file\")\n\tfi\n\n\tdocker_args+=(\"$DOCKER_IMAGE\")\n\n\tprint_info \"Starting Docker container...\"\n\tif docker run \"${docker_args[@]}\"; then\n\t\tprint_success \"Crawl4AI container started successfully\"\n\t\tprint_info \"Dashboard: http://localhost:$DOCKER_PORT/dashboard\"\n\t\tprint_info \"Playground: http://localhost:$DOCKER_PORT/playground\"\n\t\tprint_info \"API: http://localhost:$DOCKER_PORT\"\n\telse\n\t\tprint_error \"Failed to start Docker container\"\n\t\treturn 1\n\tfi\n\n\treturn 0\n}\n\n# Stop Docker container\ndocker_stop() {\n\tprint_header \"Stopping Crawl4AI Docker Container\"\n\n\tif ! check_docker; then\n\t\treturn 1\n\tfi\n\n\tif docker ps -q -f name=\"$DOCKER_CONTAINER\" | grep -q .; then\n\t\tprint_info \"Stopping container...\"\n\t\tif docker stop \"$DOCKER_CONTAINER\" && docker rm \"$DOCKER_CONTAINER\"; then\n\t\t\tprint_success \"Container stopped and removed\"\n\t\telse\n\t\t\tprint_error \"Failed to stop container\"\n\t\t\treturn 1\n\t\tfi\n\telse\n\t\tprint_warning \"Container is not running\"\n\tfi\n\n\treturn 0\n}\n\n# Setup MCP server integration\nmcp_setup() {\n\tprint_header \"Setting up Crawl4AI MCP Server Integration\"\n\n\tlocal mcp_config=\"$CONFIG_DIR/crawl4ai-mcp-config.json\"\n\n\tprint_info \"Creating MCP server configuration...\"\n\tcat >\"$mcp_config\" <\"$config_file\" <<'EOF'\n{\n \"provider\": \"capsolver\",\n \"description\": \"CapSolver configuration for automated CAPTCHA solving with Crawl4AI\",\n \"service_type\": \"captcha_solver\",\n \"version\": \"latest\",\n \"api\": {\n \"base_url\": \"https://api.capsolver.com\",\n \"endpoints\": {\n \"create_task\": \"/createTask\",\n \"get_task_result\": \"/getTaskResult\",\n \"get_balance\": \"/getBalance\"\n },\n \"authentication\": {\n \"type\": \"api_key\",\n \"header\": \"clientKey\"\n }\n },\nEOF\n\treturn 0\n}\n\n# Write CapSolver config: reCAPTCHA type definitions\n_capsolver_config_recaptcha_types() {\n\tlocal config_file=\"$1\"\n\n\tcat >>\"$config_file\" <<'EOF'\n \"supported_captcha_types\": {\n \"recaptcha_v2\": {\n \"type\": \"ReCaptchaV2TaskProxyLess\",\n \"description\": \"reCAPTCHA v2 checkbox solving\",\n \"response_field\": \"gRecaptchaResponse\",\n \"injection_target\": \"g-recaptcha-response\",\n \"pricing\": \"$0.5/1000 requests\",\n \"avg_solve_time\": \"< 9 seconds\"\n },\n \"recaptcha_v3\": {\n \"type\": \"ReCaptchaV3TaskProxyLess\",\n \"description\": \"reCAPTCHA v3 invisible solving with score >=0.7\",\n \"response_field\": \"gRecaptchaResponse\",\n \"injection_method\": \"fetch_hook\",\n \"pricing\": \"$0.5/1000 requests\",\n \"avg_solve_time\": \"< 3 seconds\"\n },\n \"recaptcha_v2_enterprise\": {\n \"type\": \"ReCaptchaV2EnterpriseTaskProxyLess\",\n \"description\": \"reCAPTCHA v2 Enterprise solving\",\n \"response_field\": \"gRecaptchaResponse\",\n \"pricing\": \"$1/1000 requests\",\n \"avg_solve_time\": \"< 9 seconds\"\n },\n \"recaptcha_v3_enterprise\": {\n \"type\": \"ReCaptchaV3EnterpriseTaskProxyLess\",\n \"description\": \"reCAPTCHA v3 Enterprise solving with score >=0.9\",\n \"response_field\": \"gRecaptchaResponse\",\n \"pricing\": \"$3/1000 requests\",\n \"avg_solve_time\": \"< 3 seconds\"\n },\nEOF\n\treturn 0\n}\n\n# Write CapSolver config: non-reCAPTCHA type definitions\n_capsolver_config_other_types() {\n\tlocal config_file=\"$1\"\n\tcat >>\"$config_file\" <<'EOF'\n \"cloudflare_turnstile\": {\n \"type\": \"AntiTurnstileTaskProxyLess\",\n \"description\": \"Cloudflare Turnstile CAPTCHA solving\",\n \"response_field\": \"token\",\n \"injection_target\": \"cf-turnstile-response\",\n \"pricing\": \"$3/1000 requests\",\n \"avg_solve_time\": \"< 3 seconds\"\n },\n \"cloudflare_challenge\": {\n \"type\": \"AntiCloudflareTask\",\n \"description\": \"Cloudflare Challenge (5s shield) solving\",\n \"response_field\": \"cookies\",\n \"requires_proxy\": true,\n \"pricing\": \"Contact for pricing\",\n \"avg_solve_time\": \"< 10 seconds\"\n },\n \"aws_waf\": {\n \"type\": \"AntiAwsWafTaskProxyLess\",\n \"description\": \"AWS WAF CAPTCHA solving\",\n \"response_field\": \"cookie\",\n \"injection_method\": \"cookie_set\",\n \"pricing\": \"Contact for pricing\",\n \"avg_solve_time\": \"< 5 seconds\"\n },\n \"geetest_v3\": {\n \"type\": \"GeeTestTaskProxyLess\",\n \"description\": \"GeeTest v3 CAPTCHA solving\",\n \"response_field\": \"challenge\",\n \"pricing\": \"$0.5/1000 requests\",\n \"avg_solve_time\": \"< 5 seconds\"\n },\n \"geetest_v4\": {\n \"type\": \"GeeTestV4TaskProxyLess\",\n \"description\": \"GeeTest v4 CAPTCHA solving\",\n \"response_field\": \"captcha_output\",\n \"pricing\": \"$0.5/1000 requests\",\n \"avg_solve_time\": \"< 5 seconds\"\n },\nEOF\n\treturn 0\n}\n\n# Write CapSolver config: remaining types, integration methods, SDK, and pricing\n_capsolver_config_footer() {\n\tlocal config_file=\"$1\"\n\tcat >>\"$config_file\" <<'EOF'\n \"image_to_text\": {\n \"type\": \"ImageToTextTask\",\n \"description\": \"OCR image CAPTCHA solving\",\n \"response_field\": \"text\",\n \"pricing\": \"$0.4/1000 requests\",\n \"avg_solve_time\": \"< 1 second\"\n }\n },\n \"integration_methods\": {\n \"api_integration\": {\n \"description\": \"Direct API integration with Python capsolver SDK\",\n \"advantages\": [\"More flexible\", \"Precise control\", \"Better error handling\"],\n \"recommended\": true\n },\n \"browser_extension\": {\n \"description\": \"CapSolver browser extension integration\",\n \"advantages\": [\"Easy setup\", \"Automatic detection\", \"No coding required\"],\n \"extension_url\": \"https://chrome.google.com/webstore/detail/capsolver/pgojnojmmhpofjgdmaebadhbocahppod\"\n }\n },\n \"python_sdk\": {\n \"installation\": \"pip install capsolver\",\n \"import\": \"import capsolver\",\n \"usage\": \"capsolver.api_key = 'CAP-xxxxxxxxxxxxxxxxxxxxx'\"\n },\n \"pricing\": {\n \"pay_per_usage\": \"Standard pricing per request\",\n \"package_discounts\": \"Up to 60% savings with packages\",\n \"developer_plan\": \"Contact for better pricing\",\n \"balance_check\": \"GET /getBalance endpoint\"\n }\n}\nEOF\n\treturn 0\n}\n\n# Write CapSolver JSON configuration file (orchestrator)\n_capsolver_write_config() {\n\tlocal capsolver_config=\"$1\"\n\n\tprint_info \"Creating CapSolver configuration...\"\n\t_capsolver_config_header \"$capsolver_config\"\n\t_capsolver_config_recaptcha_types \"$capsolver_config\"\n\t_capsolver_config_other_types \"$capsolver_config\"\n\t_capsolver_config_footer \"$capsolver_config\"\n\n\tprint_success \"CapSolver configuration created at $capsolver_config\"\n\treturn 0\n}\n\n# Write CapSolver example: imports and setup section\n_capsolver_example_header() {\n\tlocal script_file=\"$1\"\n\n\tcat >\"$script_file\" <<'EOF'\n#!/usr/bin/env python3\n\"\"\"\nCapSolver + Crawl4AI Integration Example\nDemonstrates CAPTCHA solving with various types\n\"\"\"\n\nimport asyncio\nimport capsolver\nfrom crawl4ai import AsyncWebCrawler, BrowserConfig, CrawlerRunConfig, CacheMode\n\n# TODO: Set your CapSolver API key\n# Get your API key from: https://dashboard.capsolver.com/dashboard/overview\nCAPSOLVER_API_KEY = \"CAP-xxxxxxxxxxxxxxxxxxxxx\"\ncapsolver.api_key = CAPSOLVER_API_KEY\n\nEOF\n\treturn 0\n}\n\n# Write CapSolver example: reCAPTCHA v2 solve function\n_capsolver_example_recaptcha_v2() {\n\tlocal script_file=\"$1\"\n\n\tcat >>\"$script_file\" <<'EOF'\nasync def solve_recaptcha_v2_example():\n \"\"\"Example: Solving reCAPTCHA v2 checkbox\"\"\"\n site_url = \"https://recaptcha-demo.appspot.com/recaptcha-v2-checkbox.php\"\n site_key = \"6LfW6wATAAAAAHLqO2pb8bDBahxlMxNdo9g947u9\"\n\n browser_config = BrowserConfig(\n verbose=True,\n headless=False,\n use_persistent_context=True,\n )\n\n async with AsyncWebCrawler(config=browser_config) as crawler:\n await crawler.arun(\n url=site_url,\n cache_mode=CacheMode.BYPASS,\n session_id=\"captcha_session\"\n )\n\n print(\"Solving reCAPTCHA v2...\")\n solution = capsolver.solve({\n \"type\": \"ReCaptchaV2TaskProxyLess\",\n \"websiteURL\": site_url,\n \"websiteKey\": site_key,\n })\n token = solution[\"gRecaptchaResponse\"]\n print(f\"Token obtained: {token[:50]}...\")\n\n js_code = f\"\"\"\n const textarea = document.getElementById('g-recaptcha-response');\n if (textarea) {{\n textarea.value = '{token}';\n document.querySelector('button.form-field[type=\"submit\"]').click();\n }}\n \"\"\"\n\n wait_condition = \"\"\"() => {\n const items = document.querySelectorAll('h2');\n return items.length > 1;\n }\"\"\"\n\n run_config = CrawlerRunConfig(\n cache_mode=CacheMode.BYPASS,\n session_id=\"captcha_session\",\n js_code=js_code,\n js_only=True,\n wait_for=f\"js:{wait_condition}\"\n )\n\n result = await crawler.arun(url=site_url, config=run_config)\n print(\"CAPTCHA solved successfully!\")\n return result.markdown\n\nEOF\n\treturn 0\n}\n\n# Write CapSolver example: Cloudflare Turnstile solve function\n_capsolver_example_turnstile() {\n\tlocal script_file=\"$1\"\n\tcat >>\"$script_file\" <<'EOF'\nasync def solve_cloudflare_turnstile_example():\n \"\"\"Example: Solving Cloudflare Turnstile\"\"\"\n site_url = \"https://clifford.io/demo/cloudflare-turnstile\"\n site_key = \"0x4AAAAAAAGlwMzq_9z6S9Mh\"\n browser_config = BrowserConfig(\n verbose=True, headless=False, use_persistent_context=True,\n )\n async with AsyncWebCrawler(config=browser_config) as crawler:\n await crawler.arun(\n url=site_url, cache_mode=CacheMode.BYPASS,\n session_id=\"turnstile_session\"\n )\n print(\"Solving Cloudflare Turnstile...\")\n solution = capsolver.solve({\n \"type\": \"AntiTurnstileTaskProxyLess\",\n \"websiteURL\": site_url,\n \"websiteKey\": site_key,\n })\n token = solution[\"token\"]\n print(f\"Token obtained: {token[:50]}...\")\n js_code = f\"\"\"\n document.querySelector('input[name=\"cf-turnstile-response\"]').value = '{token}';\n document.querySelector('button[type=\"submit\"]').click();\n \"\"\"\n wait_condition = \"\"\"() => {\n const items = document.querySelectorAll('h1');\n return items.length === 0;\n }\"\"\"\n run_config = CrawlerRunConfig(\n cache_mode=CacheMode.BYPASS,\n session_id=\"turnstile_session\",\n js_code=js_code, js_only=True,\n wait_for=f\"js:{wait_condition}\"\n )\n result = await crawler.arun(url=site_url, config=run_config)\n print(\"Turnstile solved successfully!\")\n return result.markdown\n\nEOF\n\treturn 0\n}\n\n# Write CapSolver example: main entry point\n_capsolver_example_main() {\n\tlocal script_file=\"$1\"\n\n\tcat >>\"$script_file\" <<'EOF'\nasync def main():\n \"\"\"Main function to run examples\"\"\"\n print(\"CapSolver + Crawl4AI Integration Examples\")\n print(\"=\" * 50)\n\n try:\n print(\"\\nExample 1: reCAPTCHA v2\")\n result1 = await solve_recaptcha_v2_example()\n\n print(\"\\nExample 2: Cloudflare Turnstile\")\n result2 = await solve_cloudflare_turnstile_example()\n\n print(\"\\nAll examples completed successfully!\")\n\n except Exception as e:\n print(f\"Error: {e}\")\n print(\"Make sure to set your CapSolver API key!\")\n\nif __name__ == \"__main__\":\n asyncio.run(main())\nEOF\n\treturn 0\n}\n\n# Write CapSolver Python example script (orchestrator)\n_capsolver_write_example() {\n\tlocal example_script=\"$1\"\n\n\t_capsolver_example_header \"$example_script\"\n\t_capsolver_example_recaptcha_v2 \"$example_script\"\n\t_capsolver_example_turnstile \"$example_script\"\n\t_capsolver_example_main \"$example_script\"\n\n\tchmod +x \"$example_script\"\n\tprint_success \"Python example script created at $example_script\"\n\treturn 0\n}\n\n# Print CapSolver next-steps instructions\n_capsolver_print_instructions() {\n\tlocal example_script=\"$1\"\n\n\tprint_info \"CapSolver Integration Setup Complete!\"\n\tprint_info \"\"\n\tprint_info \"📋 Next Steps:\"\n\tprint_info \"1. Get API key: https://dashboard.capsolver.com/dashboard/overview\"\n\tprint_info \"2. Install Python SDK: pip install capsolver\"\n\tprint_info \"3. Set API key in example script: $example_script\"\n\tprint_info \"4. Run example: python3 $example_script\"\n\tprint_info \"\"\n\tprint_info \"📚 Supported CAPTCHA Types:\"\n\tprint_info \"• reCAPTCHA v2/v3 (including Enterprise)\"\n\tprint_info \"• Cloudflare Turnstile & Challenge\"\n\tprint_info \"• AWS WAF\"\n\tprint_info \"• GeeTest v3/v4\"\n\tprint_info \"• Image-to-Text OCR\"\n\tprint_info \"\"\n\tprint_info \"💰 Pricing: Starting from $0.4/1000 requests\"\n\tprint_info \"🔗 Documentation: https://docs.capsolver.com/\"\n\treturn 0\n}\n\n# Setup CapSolver integration for CAPTCHA solving\ncapsolver_setup() {\n\tprint_header \"Setting up CapSolver Integration for CAPTCHA Solving\"\n\n\tlocal capsolver_config=\"$CONFIG_DIR/capsolver-config.json\"\n\tlocal example_script=\"$CONFIG_DIR/capsolver-example.py\"\n\n\t_capsolver_write_config \"$capsolver_config\"\n\t_capsolver_write_example \"$example_script\"\n\t_capsolver_print_instructions \"$example_script\"\n\n\treturn 0\n}\n\n# Perform web crawling operation\ncrawl_url() {\n\tlocal url=\"$1\"\n\tlocal output_file=\"$3\"\n\n\tif [[ -z \"$url\" ]]; then\n\t\tprint_error \"URL is required\"\n\t\treturn 1\n\tfi\n\n\tprint_header \"Crawling URL: $url\"\n\n\t# Check if Docker container is running\n\tif ! docker ps -q -f name=\"$DOCKER_CONTAINER\" | grep -q .; then\n\t\tprint_warning \"Docker container is not running. Starting it...\"\n\t\tif ! docker_start; then\n\t\t\treturn 1\n\t\tfi\n\t\tsleep \"$CRAWL4AI_STARTUP_WAIT\"\n\tfi\n\n\tlocal api_url=\"http://localhost:$DOCKER_PORT/crawl\"\n\tlocal payload\n\tpayload=$(\n\t\tcat <\"$output_file\"\n\t\t\tprint_success \"Results saved to $output_file\"\n\t\telse\n\t\t\techo \"$response\" | jq '.'\n\t\tfi\n\n\t\tprint_success \"Crawl completed successfully\"\n\telse\n\t\tprint_error \"Failed to crawl URL\"\n\t\treturn 1\n\tfi\n\n\treturn 0\n}\n\n# Extract structured data\nextract_structured() {\n\tlocal url=\"$1\"\n\tlocal schema=\"$2\"\n\tlocal output_file=\"$3\"\n\n\tif [[ -z \"$url\" || -z \"$schema\" ]]; then\n\t\tprint_error \"URL and schema are required\"\n\t\treturn 1\n\tfi\n\n\tprint_header \"Extracting structured data from: $url\"\n\n\t# Check if Docker container is running\n\tif ! docker ps -q -f name=\"$DOCKER_CONTAINER\" | grep -q .; then\n\t\tprint_warning \"Docker container is not running. Starting it...\"\n\t\tif ! docker_start; then\n\t\t\treturn 1\n\t\tfi\n\t\tsleep \"$CRAWL4AI_STARTUP_WAIT\"\n\tfi\n\n\tlocal api_url=\"http://localhost:$DOCKER_PORT/crawl\"\n\tlocal payload\n\tpayload=$(\n\t\tcat <\"$output_file\"\n\t\t\tprint_success \"Results saved to $output_file\"\n\t\telse\n\t\t\techo \"$response\" | jq '.results[0].extracted_content'\n\t\tfi\n\n\t\tprint_success \"Extraction completed successfully\"\n\telse\n\t\tprint_error \"Failed to extract data\"\n\t\treturn 1\n\tfi\n\n\treturn 0\n}\n\n# Write CAPTCHA script: imports, API key setup, and function signature\n_captcha_script_header() {\n\tlocal script_file=\"$1\"\n\tlocal url=\"$2\"\n\tlocal captcha_type=\"$3\"\n\tlocal site_key=\"$4\"\n\n\tcat >\"$script_file\" <>\"$script_file\" <<'PYEOF'\n if captcha_type == \"recaptcha_v2\":\n if not site_key:\n print(\"Error: site_key required for reCAPTCHA v2\")\n return\n print(\"Solving reCAPTCHA v2...\")\n solution = capsolver.solve({\n \"type\": \"ReCaptchaV2TaskProxyLess\",\n \"websiteURL\": url,\n \"websiteKey\": site_key,\n })\n token = solution[\"gRecaptchaResponse\"]\n js_code = f'''\n const textarea = document.getElementById('g-recaptcha-response');\n if (textarea) {{\n textarea.value = '{token}';\n console.log('reCAPTCHA v2 token injected');\n }}\n '''\n\n elif captcha_type == \"recaptcha_v3\":\n if not site_key:\n print(\"Error: site_key required for reCAPTCHA v3\")\n return\n print(\"Solving reCAPTCHA v3...\")\n solution = capsolver.solve({\n \"type\": \"ReCaptchaV3TaskProxyLess\",\n \"websiteURL\": url,\n \"websiteKey\": site_key,\n \"pageAction\": \"submit\",\n })\n token = solution[\"gRecaptchaResponse\"]\n js_code = f'''\n const originalFetch = window.fetch;\n window.fetch = function(...args) {{\n if (typeof args[0] === 'string' && args[0].includes('recaptcha')) {{\n console.log('Hooking reCAPTCHA v3 request');\n }}\n return originalFetch.apply(this, args);\n }};\n console.log('reCAPTCHA v3 hook installed');\n '''\n\nPYEOF\n\treturn 0\n}\n\n# Write CAPTCHA script: turnstile, AWS WAF, and fallback handlers\n_captcha_script_other_handlers() {\n\tlocal script_file=\"$1\"\n\n\tcat >>\"$script_file\" <<'PYEOF'\n elif captcha_type == \"turnstile\":\n if not site_key:\n print(\"Error: site_key required for Cloudflare Turnstile\")\n return\n print(\"Solving Cloudflare Turnstile...\")\n solution = capsolver.solve({\n \"type\": \"AntiTurnstileTaskProxyLess\",\n \"websiteURL\": url,\n \"websiteKey\": site_key,\n })\n token = solution[\"token\"]\n js_code = f'''\n const input = document.querySelector('input[name=\"cf-turnstile-response\"]');\n if (input) {{\n input.value = '{token}';\n console.log('Turnstile token injected');\n }}\n '''\n\n elif captcha_type == \"aws_waf\":\n print(\"Solving AWS WAF...\")\n solution = capsolver.solve({\n \"type\": \"AntiAwsWafTaskProxyLess\",\n \"websiteURL\": url,\n })\n cookie = solution[\"cookie\"]\n js_code = f'''\n document.cookie = 'aws-waf-token={cookie};path=/';\n console.log('AWS WAF cookie set');\n location.reload();\n '''\n\n else:\n print(f\"Error: Unsupported CAPTCHA type: {captcha_type}\")\n return\n\nPYEOF\n\treturn 0\n}\n\n# Write CAPTCHA script: execute JS, return result, and main entry point\n_captcha_script_runner() {\n\tlocal script_file=\"$1\"\n\n\tcat >>\"$script_file\" <<'PYEOF'\n run_config = CrawlerRunConfig(\n cache_mode=CacheMode.BYPASS,\n session_id=\"captcha_crawl_session\",\n js_code=js_code,\n js_only=True,\n )\n\n result = await crawler.arun(url=url, config=run_config)\n print(\"CAPTCHA solved and page crawled successfully!\")\n\n return result.markdown\n\nif __name__ == \"__main__\":\n result = asyncio.run(crawl_with_captcha())\n if result:\n print(\"Crawled content:\")\n print(result[:500] + \"...\" if len(result) > 500 else result)\nPYEOF\n\treturn 0\n}\n\n# Write the Python CAPTCHA-crawl temp script to a file (orchestrator)\n_captcha_write_script() {\n\tlocal temp_script=\"$1\"\n\tlocal url=\"$2\"\n\tlocal captcha_type=\"$3\"\n\tlocal site_key=\"$4\"\n\n\t_captcha_script_header \"$temp_script\" \"$url\" \"$captcha_type\" \"$site_key\"\n\t_captcha_script_recaptcha_handlers \"$temp_script\"\n\t_captcha_script_other_handlers \"$temp_script\"\n\t_captcha_script_runner \"$temp_script\"\n\treturn 0\n}\n\n# Validate env, run the CAPTCHA Python script, and save optional output\n_captcha_run_script() {\n\tlocal temp_script=\"$1\"\n\tlocal output_file=\"$2\"\n\n\t# Check if CapSolver API key is set\n\tif [[ -z \"$CAPSOLVER_API_KEY\" ]]; then\n\t\tprint_error \"CAPSOLVER_API_KEY environment variable not set\"\n\t\tprint_info \"Set it with: export CAPSOLVER_API_KEY='CAP-xxxxxxxxxxxxxxxxxxxxx'\"\n\t\tprint_info \"Get your API key from: https://dashboard.capsolver.com/dashboard/overview\"\n\t\trm -f \"$temp_script\"\n\t\treturn 1\n\tfi\n\n\tprint_info \"Running CAPTCHA-enabled crawl...\"\n\tif python3 \"$temp_script\"; then\n\t\tprint_success \"CAPTCHA crawl completed successfully\"\n\t\tif [[ -n \"$output_file\" ]]; then\n\t\t\tpython3 \"$temp_script\" >\"$output_file\" 2>&1\n\t\t\tprint_info \"Results saved to: $output_file\"\n\t\tfi\n\telse\n\t\tprint_error \"CAPTCHA crawl failed\"\n\t\trm -f \"$temp_script\"\n\t\treturn 1\n\tfi\n\n\trm -f \"$temp_script\"\n\treturn 0\n}\n\n# Crawl with CAPTCHA solving capabilities\ncaptcha_crawl() {\n\tlocal url=\"$1\"\n\tlocal captcha_type=\"$2\"\n\tlocal site_key=\"$3\"\n\tlocal output_file=\"$4\"\n\n\tif [[ -z \"$url\" || -z \"$captcha_type\" ]]; then\n\t\tprint_error \"URL and CAPTCHA type are required\"\n\t\tprint_info \"Usage: captcha-crawl [site_key] [output_file]\"\n\t\tprint_info \"CAPTCHA types: recaptcha_v2, recaptcha_v3, turnstile, aws_waf\"\n\t\treturn 1\n\tfi\n\n\tprint_header \"Crawling with CAPTCHA Solving: $url\"\n\tprint_info \"CAPTCHA Type: $captcha_type\"\n\n\t# Check if Docker container is running\n\tif ! docker ps -q -f name=\"$DOCKER_CONTAINER\" | grep -q .; then\n\t\tprint_warning \"Docker container is not running. Starting it...\"\n\t\tif ! docker_start; then\n\t\t\treturn 1\n\t\tfi\n\t\tsleep \"$CRAWL4AI_STARTUP_WAIT\"\n\tfi\n\n\tlocal temp_script=\"/tmp/captcha_crawl_$$.py\"\n\t_captcha_write_script \"$temp_script\" \"$url\" \"$captcha_type\" \"$site_key\"\n\t_captcha_run_script \"$temp_script\" \"$output_file\"\n\treturn $?\n}\n\n# Check service status\ncheck_status() {\n\tprint_header \"Checking Crawl4AI Service Status\"\n\n\t# Check Python package\n\tif command -v crawl4ai-doctor &>/dev/null; then\n\t\tprint_info \"Python package: Installed\"\n\t\tif crawl4ai-doctor &>/dev/null; then\n\t\t\tprint_success \"Python package: Working\"\n\t\telse\n\t\t\tprint_warning \"Python package: Issues detected\"\n\t\tfi\n\telse\n\t\tprint_warning \"Python package: Not installed\"\n\tfi\n\n\t# Check Docker container\n\tif check_docker; then\n\t\tif docker ps -q -f name=\"$DOCKER_CONTAINER\" | grep -q .; then\n\t\t\tprint_success \"Docker container: Running\"\n\n\t\t\t# Check API health\n\t\t\tlocal health_url=\"http://localhost:$DOCKER_PORT/health\"\n\t\t\tif curl -s \"$health_url\" &>/dev/null; then\n\t\t\t\tprint_success \"API endpoint: Healthy\"\n\t\t\t\tprint_info \"Dashboard: http://localhost:$DOCKER_PORT/dashboard\"\n\t\t\t\tprint_info \"Playground: http://localhost:$DOCKER_PORT/playground\"\n\t\t\telse\n\t\t\t\tprint_warning \"API endpoint: Not responding\"\n\t\t\tfi\n\t\telse\n\t\t\tprint_warning \"Docker container: Not running\"\n\t\tfi\n\telse\n\t\tprint_warning \"Docker: Not available\"\n\tfi\n\n\t# Check MCP configuration\n\tlocal mcp_config=\"$CONFIG_DIR/crawl4ai-mcp-config.json\"\n\tif [[ -f \"$mcp_config\" ]]; then\n\t\tprint_success \"MCP configuration: Available\"\n\telse\n\t\tprint_warning \"MCP configuration: Not setup\"\n\tfi\n\n\treturn 0\n}\n\n# Show help\nshow_help() {\n\techo \"Crawl4AI Helper Script\"\n\techo \"Usage: $0 [command] [options]\"\n\techo \"\"\n\techo \"Commands:\"\n\techo \" install - Install Crawl4AI Python package\"\n\techo \" docker-setup - Setup Docker deployment with monitoring\"\n\techo \" docker-start - Start Docker container\"\n\techo \" docker-stop - Stop Docker container\"\n\techo \" mcp-setup - Setup MCP server integration\"\n\techo \" capsolver-setup - Setup CapSolver CAPTCHA solving integration\"\n\techo \" crawl [url] [format] [file] - Crawl URL and extract content\"\n\techo \" extract [url] [schema] [file] - Extract structured data\"\n\techo \" captcha-crawl [url] [type] [key] [file] - Crawl with CAPTCHA solving\"\n\techo \" status - Check Crawl4AI service status\"\n\techo \" help - $HELP_SHOW_MESSAGE\"\n\techo \"\"\n\techo \"Examples:\"\n\techo \" $0 install\"\n\techo \" $0 docker-setup\"\n\techo \" $0 docker-start\"\n\techo \" $0 crawl https://example.com markdown output.json\"\n\techo \" $0 extract https://example.com '{\\\"title\\\":\\\"h1\\\"}' data.json\"\n\techo \" $0 captcha-crawl https://example.com recaptcha_v2 6LfW6wATAAAAAHLqO2pb8bDBahxlMxNdo9g947u9\"\n\techo \" $0 status\"\n\techo \"\"\n\techo \"Documentation:\"\n\techo \" GitHub: https://github.com/unclecode/crawl4ai\"\n\techo \" Docs: https://docs.crawl4ai.com/\"\n\techo \" Framework docs: .agents/CRAWL4AI.md\"\n\treturn 0\n}\n\n# Main function\nmain() {\n\t# Assign positional parameters to local variables\n\tlocal command=\"${1:-help}\"\n\tlocal param2=\"$2\"\n\tlocal param3=\"$3\"\n\tlocal param4=\"$4\"\n\tlocal param5=\"$5\"\n\n\t# Main command handler\n\tcase \"$command\" in\n\t\"install\")\n\t\tinstall_crawl4ai\n\t\t;;\n\t\"docker-setup\")\n\t\tdocker_setup\n\t\t;;\n\t\"docker-start\")\n\t\tdocker_start\n\t\t;;\n\t\"docker-stop\")\n\t\tdocker_stop\n\t\t;;\n\t\"mcp-setup\")\n\t\tmcp_setup\n\t\t;;\n\t\"capsolver-setup\")\n\t\tcapsolver_setup\n\t\t;;\n\t\"crawl\")\n\t\tcrawl_url \"$param2\" \"$param3\" \"$param4\"\n\t\t;;\n\t\"extract\")\n\t\textract_structured \"$param2\" \"$param3\" \"$param4\"\n\t\t;;\n\t\"captcha-crawl\")\n\t\tcaptcha_crawl \"$param2\" \"$param3\" \"$param4\" \"$param5\"\n\t\t;;\n\t\"status\")\n\t\tcheck_status\n\t\t;;\n\t\"help\" | \"-h\" | \"--help\" | \"\")\n\t\tshow_help\n\t\t;;\n\t*)\n\t\tprint_error \"$ERROR_UNKNOWN_COMMAND $command\"\n\t\tshow_help\n\t\treturn 1\n\t\t;;\n\tesac\n\treturn 0\n}\n\nmain \"$@\"\n\nexit 0\n"}}}}