triage
Triage FreshService ticket and create GitHub issue
What this skill does
# FreshService Ticket Triage
You are a support engineer who triages bug reports from FreshService and creates well-structured GitHub issues. You extract all relevant information from FreshService tickets and automatically create comprehensive issues for development teams.
**Ticket ID:** $ARGUMENTS
## Workflow
### Phase 1: Configuration Validation & Ticket Fetch
Validate credentials, fetch the ticket, and format the data:
```bash
# Validate and sanitize ticket ID
TICKET_ID="$ARGUMENTS"
TICKET_ID="${TICKET_ID//[^0-9]/}" # Remove all non-numeric characters
if [ -z "$TICKET_ID" ] || ! [[ "$TICKET_ID" =~ ^[0-9]+$ ]]; then
echo "Invalid ticket ID"
echo "Usage: /triage <ticket-id>"
exit 1
fi
# Load FreshService credentials (check multiple locations)
# Priority: env vars (shell profile) > ~/.config/psd-productivity/.env > ~/.claude/freshservice.env
if [ -n "$FRESHSERVICE_API_KEY" ] && [ -n "$FRESHSERVICE_DOMAIN" ]; then
echo "Using FreshService credentials from environment..."
elif [ -f ~/.config/psd-productivity/.env ]; then
echo "Loading from ~/.config/psd-productivity/.env..."
set -a
source ~/.config/psd-productivity/.env
set +a
elif [ -f ~/.claude/freshservice.env ]; then
echo "Loading from ~/.claude/freshservice.env..."
source ~/.claude/freshservice.env
else
echo "FreshService configuration not found!"
echo ""
echo "Set credentials using one of these methods:"
echo ""
echo " Option A - Shell profile (~/.zshrc):"
echo " export FRESHSERVICE_API_KEY=your_api_key_here"
echo " export FRESHSERVICE_DOMAIN=psd401"
echo ""
echo " Option B - Config file (~/.config/psd-productivity/.env):"
echo " mkdir -p ~/.config/psd-productivity"
echo " Add: FRESHSERVICE_API_KEY=your_api_key_here"
echo " Add: FRESHSERVICE_DOMAIN=psd401"
echo ""
exit 1
fi
# Validate required variables
if [ -z "$FRESHSERVICE_API_KEY" ] || [ -z "$FRESHSERVICE_DOMAIN" ]; then
echo "Missing required environment variables!"
echo "Required: FRESHSERVICE_API_KEY, FRESHSERVICE_DOMAIN"
exit 1
fi
# Validate domain format (alphanumeric and hyphens only, prevents SSRF)
if ! [[ "$FRESHSERVICE_DOMAIN" =~ ^[a-zA-Z0-9-]+$ ]]; then
echo "Invalid FRESHSERVICE_DOMAIN format"
echo "Domain must contain only alphanumeric characters and hyphens"
echo "Example: 'psd401' (not 'psd401.freshservice.com')"
exit 1
fi
# Validate API key format (basic sanity check)
if [ ${#FRESHSERVICE_API_KEY} -lt 20 ]; then
echo "Warning: API key appears too short. Please verify your configuration."
fi
echo "Configuration validated"
echo "Domain: $FRESHSERVICE_DOMAIN"
echo ""
# API configuration
API_BASE_URL="https://${FRESHSERVICE_DOMAIN}.freshservice.com/api/v2"
TICKET_ENDPOINT="${API_BASE_URL}/tickets/${TICKET_ID}"
# Temporary files for API responses
TICKET_JSON="/tmp/fs-ticket-${TICKET_ID}.json"
CONVERSATIONS_JSON="/tmp/fs-conversations-${TICKET_ID}.json"
# Cleanup function
cleanup() {
rm -f "$TICKET_JSON" "$CONVERSATIONS_JSON"
}
trap cleanup EXIT
echo "=== Fetching FreshService Ticket #${TICKET_ID} ==="
echo ""
# Function to make API request with retry logic
api_request() {
local url="$1"
local output_file="$2"
local max_retries=3
local retry_delay=2
local attempt=1
while [ $attempt -le $max_retries ]; do
# Make request and capture HTTP status code
http_code=$(curl -s -w "%{http_code}" -u "${FRESHSERVICE_API_KEY}:X" \
-H "Content-Type: application/json" \
-X GET "$url" \
-o "$output_file" \
--max-time 30)
# Check for rate limiting (HTTP 429)
if [ "$http_code" = "429" ]; then
echo "Error: Rate limit exceeded. Please wait before retrying."
echo "FreshService API has rate limits (typically 1000 requests/hour)."
return 1
fi
# Success (HTTP 200)
if [ "$http_code" = "200" ]; then
return 0
fi
# Unauthorized (HTTP 401)
if [ "$http_code" = "401" ]; then
echo "Error: Authentication failed. Please check your API key."
return 1
fi
# Not found (HTTP 404)
if [ "$http_code" = "404" ]; then
echo "Error: Ticket not found. Please verify the ticket ID."
return 1
fi
# Retry on server errors (5xx)
if [ $attempt -lt $max_retries ]; then
echo "Warning: API request failed with HTTP $http_code (attempt $attempt/$max_retries), retrying in ${retry_delay}s..."
sleep $retry_delay
retry_delay=$((retry_delay * 2)) # Exponential backoff
fi
attempt=$((attempt + 1))
done
echo "Error: API request failed after $max_retries attempts (last HTTP code: $http_code)"
return 1
}
# Fetch ticket with embedded fields
echo "Fetching ticket #${TICKET_ID}..."
if ! api_request "${TICKET_ENDPOINT}?include=requester,stats" "$TICKET_JSON"; then
echo ""
echo "Failed to retrieve ticket from FreshService"
echo "Please verify:"
echo " - Ticket ID $TICKET_ID exists"
echo " - API key is valid"
echo " - Domain is correct ($FRESHSERVICE_DOMAIN)"
exit 1
fi
# Fetch conversations (comments)
echo "Fetching ticket conversations..."
if ! api_request "${TICKET_ENDPOINT}/conversations" "$CONVERSATIONS_JSON"; then
echo "Warning: Failed to fetch conversations, continuing without them..."
echo '{"conversations":[]}' > "$CONVERSATIONS_JSON"
fi
echo "Ticket retrieved successfully"
echo ""
# Check if jq is available for JSON parsing
if ! command -v jq &> /dev/null; then
echo "Warning: jq not found, using basic parsing"
echo "Install jq for full functionality: brew install jq (macOS) or apt-get install jq (Linux)"
JQ_AVAILABLE=false
else
JQ_AVAILABLE=true
fi
# Extract ticket fields
if [ "$JQ_AVAILABLE" = true ]; then
SUBJECT=$(jq -r '.ticket.subject // "No subject"' "$TICKET_JSON")
DESCRIPTION=$(jq -r '.ticket.description_text // .ticket.description // "No description"' "$TICKET_JSON")
PRIORITY=$(jq -r '.ticket.priority // 0' "$TICKET_JSON")
STATUS=$(jq -r '.ticket.status // 0' "$TICKET_JSON")
CREATED_AT=$(jq -r '.ticket.created_at // "Unknown"' "$TICKET_JSON")
REQUESTER_NAME=$(jq -r '.ticket.requester.name // "Unknown"' "$TICKET_JSON" 2>/dev/null || echo "Unknown")
CATEGORY=$(jq -r '.ticket.category // "Uncategorized"' "$TICKET_JSON")
URGENCY=$(jq -r '.ticket.urgency // 0' "$TICKET_JSON")
# Extract custom fields if present
CUSTOM_FIELDS=$(jq -r '.ticket.custom_fields // {}' "$TICKET_JSON")
# Extract attachments if present
ATTACHMENTS=$(jq -r '.ticket.attachments // []' "$TICKET_JSON")
HAS_ATTACHMENTS=$(echo "$ATTACHMENTS" | jq '. | length > 0')
# Extract conversations
CONVERSATIONS=$(jq -r '.conversations // []' "$CONVERSATIONS_JSON")
CONVERSATION_COUNT=$(echo "$CONVERSATIONS" | jq '. | length')
else
# Fallback to basic grep/sed parsing
SUBJECT=$(grep -o '"subject":"[^"]*"' "$TICKET_JSON" | head -1 | sed 's/"subject":"//;s/"$//' || echo "No subject")
DESCRIPTION=$(grep -o '"description_text":"[^"]*"' "$TICKET_JSON" | head -1 | sed 's/"description_text":"//;s/"$//' || echo "No description")
PRIORITY="0"
STATUS="0"
CREATED_AT="Unknown"
REQUESTER_NAME="Unknown"
CATEGORY="Unknown"
URGENCY="0"
HAS_ATTACHMENTS="false"
CONVERSATION_COUNT="0"
fi
# Map priority codes to human-readable strings
case "$PRIORITY" in
1) PRIORITY_STR="Low" ;;
2) PRIORITY_STR="Medium" ;;
3) PRIORITY_STR="High" ;;
4) PRIORITY_STR="Urgent" ;;
*) PRIORITY_STR="Unknown" ;;
esac
# Map urgency codes
case "$URGENCY" in
1) URGENCY_STR="Low" ;;
2) URGENCY_STR="Medium" ;;
3) URGENCY_STR="High" ;;
*) URGENCY_STR="Unknown" ;;
esac
# Map status codes
case "$STATUS" in
2) STATUS_STR="Open" ;;
3) STATUS_STR="Pending" ;;
4) STATUS_STR="Resolved" ;;
5) STATUS_STR="Closed" ;;
*) STATUS_STR="Unknown" ;;
esac
# Format the issue description
ISSUE_DESCRIPTION="Bug report from FreshService Ticket #${TICKET_ID}
## Summary
${SUBJECT}
## Description
${DESCRIPTION}
## Ticket Information
- **FreshService Ticket**: #${TICKET_ID}
- **Status**: ${STARelated in General
modeling-omnistudio-epc-catalog
IncludedSalesforce Industries CME EPC product-modeling skill for Product2-based catalog creation. Use when creating EPC products, configuring product attributes, building offer bundles with Product Child Items, or reviewing EPC DataPack JSON metadata for product catalog changes. TRIGGER when: user creates or updates Product2 EPC records, AttributeAssignment payloads, AttributeMetadata/AttributeDefaultValues, Offer bundles, or ProductChildItem relationships. DO NOT TRIGGER when: designing OmniScripts/FlexCards/Integration Procedures (use building-omnistudio-omniscript, building-omnistudio-flexcard, or building-omnistudio-integration-procedure), implementing Apex business logic (use generating-apex), or troubleshooting deployment pipelines (use deploying-metadata).
relationship-science-coach
IncludedUse this skill for direct, practical adult relationship coaching: couples conflict, repair, trust, marriage, dating, flirting, attachment patterns, emotional connection, sex, desire differences, eroticism, kink negotiation, affection, love languages, breakups, and long-term passion. Draw on Gottman, EFT and Hold Me Tight, attachment science, modern sex research, Perel, Nagoski, Kerner, Schnarch, Love and Stosny, and flexible love-language tools. Be concrete and low-hedge. Redirect only for imminent danger, abuse, coercive control, minors, non-consent, self-harm, stalking, or medical/legal/psychiatric decisions.
building-sf-integrations
IncludedSalesforce integration architecture and runtime plumbing with 120-point scoring. Use this skill to set up Named Credentials, External Credentials, External Services, REST/SOAP callout patterns, Platform Events, and Change Data Capture. TRIGGER when: user sets up Named Credentials, External Services, REST/SOAP callouts, Platform Events, CDC, or touches .namedCredential-meta.xml files. DO NOT TRIGGER when: Connected App/OAuth config (use configuring-connected-apps), Apex-only logic (use generating-apex), or data import/export (use handling-sf-data).
venue-templates
IncludedAccess comprehensive LaTeX templates, formatting requirements, and submission guidelines for major scientific publication venues (Nature, Science, PLOS, IEEE, ACM), academic conferences (NeurIPS, ICML, CVPR, CHI), research posters, and grant proposals (NSF, NIH, DOE, DARPA). This skill should be used when preparing manuscripts for journal submission, conference papers, research posters, or grant proposals and need venue-specific formatting requirements and templates.
let-fate-decide
IncludedDraws the 12 Houses of the Zodiac Tarot spread to inject entropy into planning when prompts are vague, ambiguous, or casually delegated. Interprets the spread to guide next steps. Use when the user says 'let fate decide', 'YOLO', 'whatever', 'idk', or other nonchalant phrases, makes Yu-Gi-Oh references, or when you are about to arbitrarily pick between multiple reasonable approaches. Prefer over ask-questions-if-underspecified when the user's tone is casual or playful rather than precision-seeking.
net-ops
IncludedCross-platform network troubleshooting (Windows, macOS, Linux) via local or remote shell. Use for: DNS broken, can't resolve hostnames, nslookup/dig works but apps fail, NRPT, WFP, scutil, /etc/resolver, systemd-resolved, /etc/resolv.conf, NetworkManager, VPN DNS leak residue (ProtonVPN/Mullvad/WireGuard/AnyConnect), AV/firewall blocking DNS or DoH, Tailscale DNS interaction, intermittent connectivity, remote diagnostics over SSH.