$npx -y skills add Varnan-Tech/opendirectory --skill domain-expired-opportunity-finderEvaluates expired domain candidates against a target niche, scores them by topical relevance, historical activity level, and history cleanliness, then outputs a ranked shortlist with explainable reasoning and risk flags.
| 1 | # Expired Domain Opportunity Finder |
| 2 | |
| 3 | Evaluate expired domain candidates for a specific niche. Score them on topical |
| 4 | fit, historical activity level, history cleanliness, and redirect suitability. Output a |
| 5 | conservative, explainable shortlist for human review. |
| 6 | |
| 7 | --- |
| 8 | |
| 9 | **Critical rule:** Every recommendation must include BOTH a positive rationale |
| 10 | (`why_selected`) AND a caution rationale (`why_risky`). Never output a bare |
| 11 | score without explanation. |
| 12 | |
| 13 | **Conservative-by-default rule:** When signals are incomplete or contradictory, |
| 14 | lower the confidence level. Do not surface ambiguous candidates as strong |
| 15 | opportunities. Missing data reduces confidence, never inflates it. |
| 16 | |
| 17 | **Anti-abuse rule:** Never encourage unrelated redirects, PBN construction, or |
| 18 | domain repurposing where the historical topic does not match the target niche. |
| 19 | Read `references/guardrails.md` for the full anti-abuse policy. |
| 20 | |
| 21 | --- |
| 22 | |
| 23 | ## Step 1: Setup Check |
| 24 | |
| 25 | Check the environment before doing anything else. |
| 26 | |
| 27 | Verify that `curl` and `python3` (or `python`) are available: |
| 28 | ```bash |
| 29 | curl --version > /dev/null 2>&1 && echo "curl: available" || echo "curl: MISSING" |
| 30 | python3 --version 2>/dev/null || python --version 2>/dev/null || echo "python: MISSING" |
| 31 | ``` |
| 32 | |
| 33 | Check for an optional LLM API key for enhanced niche-relevance scoring: |
| 34 | ```bash |
| 35 | echo "LLM_API_KEY: ${LLM_API_KEY:+set}" |
| 36 | ``` |
| 37 | |
| 38 | **If `curl` or `python` is missing:** |
| 39 | Stop. Tell the user: "This skill requires curl and Python 3.10+. Please install them and try again." |
| 40 | |
| 41 | **If `LLM_API_KEY` is not set:** |
| 42 | Continue. The skill will use rule-based scoring only (domain string matching, |
| 43 | Wayback title analysis, keyword overlap). Note to the user: "Running in |
| 44 | rule-based-only mode. Set LLM_API_KEY for enhanced niche-relevance scoring." |
| 45 | |
| 46 | **If `LLM_API_KEY` is set:** |
| 47 | The skill will use LLM-enhanced scoring for topical relevance analysis. |
| 48 | This provides deeper contextual assessment of niche fit. |
| 49 | |
| 50 | QA: State the scoring mode (llm-enhanced or rule-based-only) and confirm tools are available. |
| 51 | |
| 52 | --- |
| 53 | |
| 54 | ## Step 2: Input Collection |
| 55 | |
| 56 | Collect the required and optional inputs from the user. |
| 57 | |
| 58 | **Required:** |
| 59 | - `target_niche` (string): The core niche to evaluate against. Examples: "developer tools", "AI SaaS", "cybersecurity", "fintech". |
| 60 | |
| 61 | **Optional (ask only if not provided):** |
| 62 | - `seed_keywords` (array): Keywords to refine topical matching. If not provided, extract 3–5 keywords from the niche name automatically. |
| 63 | - `candidate_domains` (array): Specific domains to evaluate. If not provided, prompt the user. |
| 64 | - `discovery_source` (string): Where candidates came from — `manual`, `expireddomains-net`, `external-feed`. |
| 65 | - `min_snapshots` (integer): Minimum historical snapshot threshold. Default: 10. |
| 66 | - `max_risk_level` (string): `low`, `medium`, or `high`. Controls how aggressively risky candidates are filtered. Default: `medium`. |
| 67 | - `intended_use` (string): `rebuild`, `redirect`, or `either`. Default: `either`. |
| 68 | |
| 69 | **If no `candidate_domains` are provided:** |
| 70 | Ask: "Please provide a list of expired domain candidates to evaluate. You can: |
| 71 | 1. Paste domain names (one per line or comma-separated) |
| 72 | 2. Provide a file path to a text file with one domain per line |
| 73 | 3. Say 'example' to run with a built-in demo set for the 'developer tools' niche" |
| 74 | |
| 75 | **If the user says 'example':** |
| 76 | Use this demo set: |
| 77 | ``` |
| 78 | devtoolsweekly.com |
| 79 | codeshipnews.io |
| 80 | stackforgeapp.com |
| 81 | quickseorank.net |
| 82 | bestcheaphosting247.com |
| 83 | cloudbuildpro.dev |
| 84 | reactwidgetlib.com |
| 85 | megadealsshop.xyz |
| 86 | ``` |
| 87 | |
| 88 | After collecting all inputs, confirm: |
| 89 | "Target niche: [niche]. Evaluating [N] candidate domains. Scoring mode: [mode]. Intended use: [use]." |
| 90 | |
| 91 | --- |
| 92 | |
| 93 | ## Step 3: Candidate Normalization |
| 94 | |
| 95 | Clean and validate the candidate list before scoring. |
| 96 | |
| 97 | ```bash |
| 98 | python3 -c " |
| 99 | import sys, re |
| 100 | |
| 101 | domains = '''CANDIDATE_LIST_HERE'''.strip().split('\n') |
| 102 | seen = set() |
| 103 | valid = [] |
| 104 | invalid = [] |
| 105 | |
| 106 | for d in domains: |
| 107 | d = d.strip().lower() |
| 108 | # Strip protocols and paths |
| 109 | d = re.sub(r'^https?://', '', d) |
| 110 | d = d.split('/')[0] |
| 111 | d = d.strip('.') |
| 112 | |
| 113 | if not d: |
| 114 | continue |
| 115 | |
| 116 | # Basic TLD validation |
| 117 | if '.' not in d or len(d) < 4: |
| 118 | invalid.append(d) |
| 119 | continue |
| 120 | |
| 121 | # Deduplicate |
| 122 | if d in seen: |
| 123 | continue |
| 124 | seen.add(d) |
| 125 | valid.append(d) |
| 126 | |
| 127 | print(f'Valid candidates: {len(valid)}') |
| 128 | print(f'Removed (invalid/duplicate): {len(invalid)}') |
| 129 | for v in valid: |
| 130 | print(f' ✓ {v}') |
| 131 | for i in invalid: |
| 132 | print(f' ✗ {i} (invalid format)') |
| 133 | " |
| 134 | ``` |
| 135 | |
| 136 | Replace `CANDIDATE_LIST_HERE` with the actual domain list from Step 2. |
| 137 | |
| 138 | State: "[N] valid candidates after normalization. [M] removed (invalid/duplicate)." |
| 139 | |
| 140 | If 0 valid candidates remain, stop and tell the user: "No valid |