Install
$ agentstack add skill-varnan-tech-opendirectory-domain-expired-opportunity-finder ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ● Network access Used
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →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:
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:
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 LLMAPIKEY 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, orhigh. Controls how aggressively risky candidates are filtered. Default:medium.intended_use(string):rebuild,redirect, oreither. Default:either.
If no candidate_domains are provided: Ask: "Please provide a list of expired domain candidates to evaluate. You can:
- Paste domain names (one per line or comma-separated)
- Provide a file path to a text file with one domain per line
- 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.
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:
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:
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 LLMAPIKEY 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.
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:
- Providing different candidate domains
- Widening seed keywords
- Setting maxrisklevel to 'high' to see borderline candidates
- 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_selectedANDwhy_risky - [ ] No shortlisted domain has a High-severity risk flag AND
high-priority-reviewaction - [ ] Domains with
redirect_mismatchare labeledrebuild-only-review(notreview) - [ ] The guardrails disclaimer is present at the end of output
- [ ] No hype language: no "guaranteed", "easy win", "safe to redirect", "SEO hack"
- [ ]
scoring_modecorrectly reflects whether LLM was used - [ ] Candidates are ranked by
opportunity_scoredescending - [ ] 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
- Source: Varnan-Tech/opendirectory
- License: MIT
- Homepage: https://www.opendirectory.dev
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.