# Domain Expired Opportunity Finder

> Evaluates 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.

- **Type:** Skill
- **Install:** `agentstack add skill-varnan-tech-opendirectory-domain-expired-opportunity-finder`
- **Verified:** Yes — security-reviewed for prompt injection and unsafe behavior
- **Seller:** [Varnan-Tech](https://agentstack.voostack.com/s/varnan-tech)
- **Installs:** 0
- **Category:** [Agent Skills](https://agentstack.voostack.com/c/agent-skills)
- **Latest version:** 0.1.0
- **License:** MIT
- **Upstream author:** [Varnan-Tech](https://github.com/Varnan-Tech)
- **Source:** https://github.com/Varnan-Tech/opendirectory/tree/main/skills/domain-expired-opportunity-finder
- **Website:** https://www.opendirectory.dev

## Install

```sh
agentstack add skill-varnan-tech-opendirectory-domain-expired-opportunity-finder
```

Requires the [AgentStack CLI](https://agentstack.voostack.com/docs/cli). Works with Claude Code, Cursor, and any MCP-compatible agent.

## About

# Expired Domain Opportunity Finder

Evaluate expired domain candidates for a specific niche. Score them on topical
fit, historical activity level, history cleanliness, and redirect suitability. Output a
conservative, explainable shortlist for human review.

---

**Critical rule:** Every recommendation must include BOTH a positive rationale
(`why_selected`) AND a caution rationale (`why_risky`). Never output a bare
score without explanation.

**Conservative-by-default rule:** When signals are incomplete or contradictory,
lower the confidence level. Do not surface ambiguous candidates as strong
opportunities. Missing data reduces confidence, never inflates it.

**Anti-abuse rule:** Never encourage unrelated redirects, PBN construction, or
domain repurposing where the historical topic does not match the target niche.
Read `references/guardrails.md` for the full anti-abuse policy.

---

## Step 1: Setup Check

Check the environment before doing anything else.

Verify that `curl` and `python3` (or `python`) are available:
```bash
curl --version > /dev/null 2>&1 && echo "curl: available" || echo "curl: MISSING"
python3 --version 2>/dev/null || python --version 2>/dev/null || echo "python: MISSING"
```

Check for an optional LLM API key for enhanced niche-relevance scoring:
```bash
echo "LLM_API_KEY: ${LLM_API_KEY:+set}"
```

**If `curl` or `python` is missing:**
Stop. Tell the user: "This skill requires curl and Python 3.10+. Please install them and try again."

**If `LLM_API_KEY` is not set:**
Continue. The skill will use rule-based scoring only (domain string matching,
Wayback title analysis, keyword overlap). Note to the user: "Running in
rule-based-only mode. Set LLM_API_KEY for enhanced niche-relevance scoring."

**If `LLM_API_KEY` is set:**
The skill will use LLM-enhanced scoring for topical relevance analysis.
This provides deeper contextual assessment of niche fit.

QA: State the scoring mode (llm-enhanced or rule-based-only) and confirm tools are available.

---

## Step 2: Input Collection

Collect the required and optional inputs from the user.

**Required:**
- `target_niche` (string): The core niche to evaluate against. Examples: "developer tools", "AI SaaS", "cybersecurity", "fintech".

**Optional (ask only if not provided):**
- `seed_keywords` (array): Keywords to refine topical matching. If not provided, extract 3–5 keywords from the niche name automatically.
- `candidate_domains` (array): Specific domains to evaluate. If not provided, prompt the user.
- `discovery_source` (string): Where candidates came from — `manual`, `expireddomains-net`, `external-feed`.
- `min_snapshots` (integer): Minimum historical snapshot threshold. Default: 10.
- `max_risk_level` (string): `low`, `medium`, or `high`. Controls how aggressively risky candidates are filtered. Default: `medium`.
- `intended_use` (string): `rebuild`, `redirect`, or `either`. Default: `either`.

**If no `candidate_domains` are provided:**
Ask: "Please provide a list of expired domain candidates to evaluate. You can:
1. Paste domain names (one per line or comma-separated)
2. Provide a file path to a text file with one domain per line
3. Say 'example' to run with a built-in demo set for the 'developer tools' niche"

**If the user says 'example':**
Use this demo set:
```
devtoolsweekly.com
codeshipnews.io
stackforgeapp.com
quickseorank.net
bestcheaphosting247.com
cloudbuildpro.dev
reactwidgetlib.com
megadealsshop.xyz
```

After collecting all inputs, confirm:
"Target niche: [niche]. Evaluating [N] candidate domains. Scoring mode: [mode]. Intended use: [use]."

---

## Step 3: Candidate Normalization

Clean and validate the candidate list before scoring.

```bash
python3 -c "
import sys, re

domains = '''CANDIDATE_LIST_HERE'''.strip().split('\n')
seen = set()
valid = []
invalid = []

for d in domains:
    d = d.strip().lower()
    # Strip protocols and paths
    d = re.sub(r'^https?://', '', d)
    d = d.split('/')[0]
    d = d.strip('.')

    if not d:
        continue

    # Basic TLD validation
    if '.' not in d or len(d)  0 snapshots, fetch the most recent snapshot to extract
the page title (used for topical relevance scoring):

```bash
curl -s -L "https://web.archive.org/web/LATEST_TIMESTAMP/http://DOMAIN_HERE" \
  | python3 -c "
import sys, re
html = sys.stdin.read()[:50000]
title_match = re.search(r']*>(.*?)', html, re.IGNORECASE | re.DOTALL)
title = title_match.group(1).strip() if title_match else 'no title found'
# Extract meta description too
meta_match = re.search(r']*name=[\"']description[\"'][^>]*content=[\"'](.*?)[\"']', html, re.IGNORECASE)
desc = meta_match.group(1).strip() if meta_match else 'no description found'
print(f'Title: {title}')
print(f'Description: {desc}')
"
```

Replace `LATEST_TIMESTAMP` with the most recent timestamp from Step 4a.

### 4c: RDAP Lookup — Registration Status

Use the cross-platform HTTP-based RDAP standard (replaces OS-dependent WHOIS).
An HTTP 404 from RDAP means the domain is not registered (i.e. it is genuinely
available or untracked) — that is distinct from a network failure. Handle both
cases explicitly:

```bash
python3 -c "
import urllib.request, urllib.error, json

domain = 'DOMAIN_HERE'
try:
    req = urllib.request.Request(
        f'https://rdap.org/domain/{domain}',
        headers={'User-Agent': 'Mozilla/5.0'}
    )
    with urllib.request.urlopen(req, timeout=10) as response:
        data = json.loads(response.read().decode())

    registrar = 'unknown'
    created = 'unknown'

    for entity in data.get('entities', []):
        if 'registrar' in entity.get('roles', []):
            try:
                registrar = entity.get('vcardArray', [[]])[1][0][3]
            except Exception:
                pass

    for event in data.get('events', []):
        if event.get('eventAction') == 'registration':
            created = event.get('eventDate', 'unknown')

    print(json.dumps({
        'domain': domain,
        'status': 'registered',
        'registrar': registrar,
        'created': created
    }))
except urllib.error.HTTPError as e:
    if e.code == 404:
        # Domain has no RDAP object — likely unregistered or not in RDAP coverage
        print(json.dumps({'domain': domain, 'status': 'unregistered_or_no_rdap_object'}))
    else:
        print(json.dumps({'domain': domain, 'error': f'rdap_http_error_{e.code}'}))
except Exception:
    print(json.dumps({'domain': domain, 'error': 'rdap_lookup_failed'}))
"
```

### 4d: Domain String Analysis — Keyword Matching

Score keyword overlap between the domain name and the target niche / seed keywords:

```bash
python3 -c "
import re, json

domain = 'DOMAIN_HERE'
niche = 'NICHE_HERE'
seeds = SEEDS_JSON_HERE  # e.g., ['devops', 'ci/cd', 'code editor']

# Extract words from domain
domain_base = domain.rsplit('.', 1)[0]  # remove TLD
domain_words = re.split(r'[-_.]', domain_base.lower())

# Check niche words
niche_words = niche.lower().split()
all_keywords = set(niche_words + [s.lower() for s in seeds])

matches = [w for w in domain_words if any(kw in w or w in kw for kw in all_keywords)]
match_ratio = len(matches) / max(len(domain_words), 1)

print(json.dumps({
    'domain': domain,
    'domain_words': domain_words,
    'keyword_matches': matches,
    'match_ratio': round(match_ratio, 2)
}))
"
```

### 4e: Gemini LLM Niche-Relevance Assessment (if LLM_API_KEY is set)

If the LLM API key is configured, batch all candidates with their collected
signals and ask for a contextual niche-relevance assessment.

**Note:** The request/response format below uses the **Gemini API** (`generateContent`
format). It is not compatible with OpenAI-style endpoints without modification.
If you use a different provider, you must adapt the JSON body and response parsing.

```bash
cat > /tmp/domain-relevance-request.json  "$OUTFILE" << 'EOF'
JSON_OUTPUT_HERE
EOF
echo "Saved to $OUTFILE"
```

**If 0 candidates pass the shortlist:**
"No candidates met the shortlist criteria for the '[niche]' niche with the
current risk tolerance. This is a normal outcome — it means the evaluated
domains were not strong enough matches. Try:
1. Providing different candidate domains
2. Widening seed keywords
3. Setting max_risk_level to 'high' to see borderline candidates
4. Running in audit mode to see why candidates were rejected"

---

## Self-QA Checklist

Run every check before presenting output:

- [ ] Every shortlisted domain has both `why_selected` AND `why_risky`
- [ ] No shortlisted domain has a High-severity risk flag AND `high-priority-review` action
- [ ] Domains with `redirect_mismatch` are labeled `rebuild-only-review` (not `review`)
- [ ] The guardrails disclaimer is present at the end of output
- [ ] No hype language: no "guaranteed", "easy win", "safe to redirect", "SEO hack"
- [ ] `scoring_mode` correctly reflects whether LLM was used
- [ ] Candidates are ranked by `opportunity_score` descending
- [ ] JSON output saved to `docs/expired-domain-intel/YYYY-MM-DD.json`
- [ ] All Wayback API calls were rate-limited (2s between calls)

Fix any violation before presenting.

---

## What Good Output Looks Like

- Every domain has a score, confidence, action, and risk assessment
- Summaries are 1–2 sentences each, specific to the candidate (not generic)
- Risk flags are present and explained in `why_risky`
- The shortlist is small (quality over quantity) — typically 2–5 domains from a batch of 10–20
- Conservative: when in doubt, reject or lower confidence
- The user can understand exactly why each domain was selected or rejected

## What Bad Output Looks Like

- Bare scores without explanation
- Generic summaries like "this domain has good metrics" (must be specific)
- High-priority recommendations for domains with serious risk flags
- Redirect recommendations for topic-mismatched domains
- No disclaimer at the end
- Hype language promising SEO outcomes
- Too many shortlisted domains (the skill should be selective, not permissive)

## Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [Varnan-Tech](https://github.com/Varnan-Tech)
- **Source:** [Varnan-Tech/opendirectory](https://github.com/Varnan-Tech/opendirectory)
- **License:** MIT
- **Homepage:** https://www.opendirectory.dev

Install and usage instructions live in the source repository linked above.

## Pricing

- **Free** — Free

## Security capabilities

Automated source analysis of v0.1.0 — what this tool can access:

- **Network access:** yes
- **Filesystem access:** no
- **Shell / process execution:** no
- **Environment & secrets:** no
- **Dynamic code execution:** no

*"Yes" means the capability is present in the source — more access means more to trust, not that it is unsafe.*


## Versions

- **0.1.0** — security scan: passed — Imported from the upstream source.

## Links

- Listing page: https://agentstack.voostack.com/l/skill-varnan-tech-opendirectory-domain-expired-opportunity-finder
- Seller: https://agentstack.voostack.com/s/varnan-tech
- Browse the marketplace: https://agentstack.voostack.com/browse

---
Listed on AgentStack — the marketplace for AI agent skills and MCP servers. Every listing is security-reviewed. Creators keep 70%.
