feat: sync full workspace including web modules, docs, and configurations to Gitea
Optimized the root .gitignore to exclude virtual environments, node modules, and temp folders to ensure clean and lightweight version tracking. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
182
axhub-make/skills/third-party/research/scripts/research.sh
vendored
Normal file
182
axhub-make/skills/third-party/research/scripts/research.sh
vendored
Normal file
@@ -0,0 +1,182 @@
|
||||
#!/bin/bash
|
||||
# Tavily Research API script
|
||||
# Usage: ./research.sh '{"input": "your research query", ...}' [output_file]
|
||||
# Example: ./research.sh '{"input": "quantum computing trends", "model": "pro"}' results.md
|
||||
|
||||
set -e
|
||||
|
||||
# Function to decode JWT payload
|
||||
decode_jwt_payload() {
|
||||
local token="$1"
|
||||
local payload=$(echo "$token" | cut -d'.' -f2)
|
||||
local padded_payload="$payload"
|
||||
case $((${#payload} % 4)) in
|
||||
2) padded_payload="${payload}==" ;;
|
||||
3) padded_payload="${payload}=" ;;
|
||||
esac
|
||||
echo "$padded_payload" | base64 -d 2>/dev/null
|
||||
}
|
||||
|
||||
# Function to check if a JWT is valid for Tavily (not expired and correct issuer)
|
||||
is_valid_tavily_token() {
|
||||
local token="$1"
|
||||
local payload=$(decode_jwt_payload "$token")
|
||||
|
||||
# Check if it's a Tavily token (exact issuer match for security)
|
||||
local iss=$(echo "$payload" | jq -r '.iss // empty' 2>/dev/null)
|
||||
if [ "$iss" != "https://mcp.tavily.com/" ]; then
|
||||
return 1 # Not a valid Tavily token
|
||||
fi
|
||||
|
||||
# Check if expired
|
||||
local exp=$(echo "$payload" | jq -r '.exp // empty' 2>/dev/null)
|
||||
if [ -n "$exp" ] && [ "$exp" != "null" ]; then
|
||||
local current_time=$(date +%s)
|
||||
if [ "$current_time" -ge "$exp" ]; then
|
||||
return 1 # Expired
|
||||
fi
|
||||
fi
|
||||
|
||||
return 0 # Valid Tavily token
|
||||
}
|
||||
|
||||
# Function to find token from MCP auth cache
|
||||
get_mcp_token() {
|
||||
MCP_AUTH_DIR="$HOME/.mcp-auth"
|
||||
if [ -d "$MCP_AUTH_DIR" ]; then
|
||||
# Search recursively for *_tokens.json files
|
||||
while IFS= read -r token_file; do
|
||||
if [ -f "$token_file" ]; then
|
||||
token=$(jq -r '.access_token // empty' "$token_file" 2>/dev/null)
|
||||
if [ -n "$token" ] && [ "$token" != "null" ]; then
|
||||
# Check if valid Tavily token (correct issuer and not expired)
|
||||
if ! is_valid_tavily_token "$token"; then
|
||||
continue # Skip invalid/non-Tavily/expired tokens
|
||||
fi
|
||||
echo "$token"
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
done < <(find "$MCP_AUTH_DIR" -name "*_tokens.json" 2>/dev/null)
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
|
||||
# Try to load OAuth token from MCP if TAVILY_API_KEY is not set
|
||||
if [ -z "$TAVILY_API_KEY" ]; then
|
||||
token=$(get_mcp_token) || true
|
||||
if [ -n "$token" ]; then
|
||||
export TAVILY_API_KEY="$token"
|
||||
fi
|
||||
fi
|
||||
|
||||
JSON_INPUT="$1"
|
||||
OUTPUT_FILE="$2"
|
||||
|
||||
if [ -z "$JSON_INPUT" ]; then
|
||||
echo "Usage: ./research.sh '<json>' [output_file]"
|
||||
echo ""
|
||||
echo "Required:"
|
||||
echo " input: string - The research topic or question"
|
||||
echo ""
|
||||
echo "Optional:"
|
||||
echo " model: \"mini\" (default), \"pro\", \"auto\""
|
||||
echo " - mini: Targeted, efficient research for narrow questions"
|
||||
echo " - pro: Comprehensive, multi-agent research for complex topics"
|
||||
echo " - auto: Automatically selects based on query complexity"
|
||||
echo ""
|
||||
echo "Arguments:"
|
||||
echo " output_file: optional file to save results"
|
||||
echo ""
|
||||
echo "Example:"
|
||||
echo " ./research.sh '{\"input\": \"AI agent frameworks comparison\", \"model\": \"pro\"}' report.md"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# If no token found, run MCP OAuth flow
|
||||
if [ -z "$TAVILY_API_KEY" ]; then
|
||||
set +e
|
||||
echo "No Tavily token found. Initiating OAuth flow..." >&2
|
||||
echo "Please complete authentication in your browser..." >&2
|
||||
npx -y mcp-remote https://mcp.tavily.com/mcp </dev/null >/dev/null 2>&1 &
|
||||
MCP_PID=$!
|
||||
|
||||
TIMEOUT=120
|
||||
ELAPSED=0
|
||||
while [ $ELAPSED -lt $TIMEOUT ]; do
|
||||
sleep 3
|
||||
ELAPSED=$((ELAPSED + 3))
|
||||
|
||||
token=$(get_mcp_token) || true
|
||||
if [ -n "$token" ]; then
|
||||
export TAVILY_API_KEY="$token"
|
||||
echo "Authentication successful!" >&2
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
kill $MCP_PID 2>/dev/null || true
|
||||
wait $MCP_PID 2>/dev/null || true
|
||||
set -e
|
||||
fi
|
||||
|
||||
if [ -z "$TAVILY_API_KEY" ]; then
|
||||
echo "Error: Failed to obtain Tavily API token"
|
||||
echo "Note: The OAuth flow requires an existing Tavily account — account creation is not supported through this flow."
|
||||
echo "Please sign up at https://tavily.com first, then retry, or set TAVILY_API_KEY manually."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Validate JSON
|
||||
if ! echo "$JSON_INPUT" | jq empty 2>/dev/null; then
|
||||
echo "Error: Invalid JSON input"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check for required input field
|
||||
if ! echo "$JSON_INPUT" | jq -e '.input' >/dev/null 2>&1; then
|
||||
echo "Error: 'input' field is required"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
INPUT=$(echo "$JSON_INPUT" | jq -r '.input')
|
||||
MODEL=$(echo "$JSON_INPUT" | jq -r '.model // "mini"')
|
||||
|
||||
echo "Researching: $INPUT (model: $MODEL)"
|
||||
echo "This may take 30-120 seconds..."
|
||||
|
||||
# Build MCP JSON-RPC request
|
||||
MCP_REQUEST=$(jq -n --argjson args "$JSON_INPUT" '{
|
||||
"jsonrpc": "2.0",
|
||||
"id": 1,
|
||||
"method": "tools/call",
|
||||
"params": {
|
||||
"name": "tavily_research",
|
||||
"arguments": $args
|
||||
}
|
||||
}')
|
||||
|
||||
# Call Tavily MCP server via HTTPS (SSE response)
|
||||
RESPONSE=$(curl -s --request POST \
|
||||
--url "https://mcp.tavily.com/mcp" \
|
||||
--header "Authorization: Bearer $TAVILY_API_KEY" \
|
||||
--header 'Content-Type: application/json' \
|
||||
--header 'Accept: application/json, text/event-stream' \
|
||||
--header 'x-client-source: claude-code-skill' \
|
||||
--data "$MCP_REQUEST")
|
||||
|
||||
# Parse SSE response and extract the JSON result
|
||||
JSON_DATA=$(echo "$RESPONSE" | grep '^data:' | sed 's/^data://' | head -1)
|
||||
|
||||
if [ -z "$JSON_DATA" ]; then
|
||||
RESULT="$RESPONSE"
|
||||
else
|
||||
RESULT=$(echo "$JSON_DATA" | jq '.result.structuredContent // .result.content[0].text // .error // .' 2>/dev/null || echo "$JSON_DATA")
|
||||
fi
|
||||
|
||||
if [ -n "$OUTPUT_FILE" ]; then
|
||||
echo "$RESULT" > "$OUTPUT_FILE"
|
||||
echo "Results saved to: $OUTPUT_FILE"
|
||||
else
|
||||
echo "$RESULT"
|
||||
fi
|
||||
Reference in New Issue
Block a user