Install any skill in seconds. Free to start, no credit card required.
Get Started Free →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.
SkillScore68/100
Test results 68
3/3 safety checks passed
36%68%of test cases passed +32 pts
Passed 15 of 22 test cases with the skill, and 8 of 22 without it.
9 fixed6 passed either way5 still failing2 broke
One test taskIn the 6-dimension scoring framework, what are the maximum point allocations for Topical Relevance and Historical Activity Level?
Adds every file the skill has, pinned so you can update it. No account needed.
pip install "decimalai>=0.15"
decimalai skills add varnan-tech-domain-expired-opportunity-finder
claude --plugin-dir "$(git rev-parse --show-toplevel 2>/dev/null || pwd)/.agents"The same install from Python, for a notebook or a setup script.
pip install "decimalai>=0.15"from decimalai import skillpkg
# Writes .agents/skills and .decimal/skills.toml. Safe to run twice.
result = skillpkg.add("varnan-tech-domain-expired-opportunity-finder")
if result.exit_code:
raise SystemExit(result.problems)
print("Added. Start Claude Code with the command below.")claude --plugin-dir "$(git rev-parse --show-toplevel 2>/dev/null || pwd)/.agents"Type these in a Claude Code session. No account or API key. It writes the skill and any files it bundles, and asks first when its safety rating isn't Passed.
/plugin marketplace add decimal-labs/decimalai-claude-plugins
/plugin install decimalai@decimalai/decimalai:install varnan-tech-domain-expired-opportunity-finderThe plugin needs python3 and curl on your machine.
No Python or account. Claude Code picks it up in its next session.
curl -fsSL --create-dirs -o .claude/skills/varnan-tech-domain-expired-opportunity-finder/SKILL.md https://app.decimal.ai/s/varnan-tech-domain-expired-opportunity-finder/SKILL.md.claude/skills/varnan-tech-domain-expired-opportunity-finder/SKILL.mdThis gets SKILL.md only. The skill's 4 other files are not included.
agent_config() hands your agent the skill. With no DecimalAI key it reads your project's files and sends nothing. Needs SDK 0.27 or newer.
pip install "decimalai[claude-agent-sdk]"
decimalai skills add varnan-tech-domain-expired-opportunity-finderimport asyncio
from claude_agent_sdk import ClaudeAgentOptions
from decimalai.claude_agent_sdk import agent_config
# With no DECIMAL_API_KEY this reads the skills your project lists, from disk.
support = agent_config("support", fallback="You are a helpful agent.")
async def main():
question = "Hello!"
async with support.run(question) as run: # one user message, one run
async for message in run.query(options=ClaudeAgentOptions(max_turns=6)):
print(message)
# In a notebook or async code: await main()
asyncio.run(main())Prefer its own loader? ClaudeAgentOptions(plugins=[skills_plugin()], skills="all") reads the same folder. Use one or the other: a skill delivered both ways reaches the prompt twice.
The same install from Python, for a notebook or a setup script. agent_config() hands your agent the skill. With no DecimalAI key it reads your project's files and sends nothing. Needs SDK 0.27 or newer.
pip install "decimalai[claude-agent-sdk]"from decimalai import skillpkg
# Writes .agents/skills and .decimal/skills.toml. Safe to run twice.
result = skillpkg.add("varnan-tech-domain-expired-opportunity-finder")
if result.exit_code:
raise SystemExit(result.problems)
import asyncio
from claude_agent_sdk import ClaudeAgentOptions
from decimalai.claude_agent_sdk import agent_config
# With no DECIMAL_API_KEY this reads the skills your project lists, from disk.
support = agent_config("support", fallback="You are a helpful agent.")
async def main():
question = "Hello!"
async with support.run(question) as run: # one user message, one run
async for message in run.query(options=ClaudeAgentOptions(max_turns=6)):
print(message)
# In a notebook or async code: await main()
asyncio.run(main())Prefer its own loader? ClaudeAgentOptions(plugins=[skills_plugin()], skills="all") reads the same folder. Use one or the other: a skill delivered both ways reaches the prompt twice.
No DecimalAI package. The whole skill goes in every prompt, and nothing pins or updates it.
.agents/skills/varnan-tech-domain-expired-opportunity-finder/SKILL.mdimport asyncio
from pathlib import Path
from claude_agent_sdk import ClaudeAgentOptions, query
skill = Path(".agents/skills/varnan-tech-domain-expired-opportunity-finder/SKILL.md").read_text()
options = ClaudeAgentOptions(system_prompt=f"You are a helpful agent.\n\n{skill}", max_turns=6)
async def main():
async for message in query(prompt="Hello!", options=options):
print(message)
# In a notebook or async code: await main()
asyncio.run(main())This gets SKILL.md only. The skill's 4 other files are not included.
agent_config() hands your agent the skill. With no DecimalAI key it reads your project's files and sends nothing. Needs SDK 0.27 or newer.
pip install "decimalai[openai-agents]"
decimalai skills add varnan-tech-domain-expired-opportunity-finderfrom agents import Agent, Runner
from decimalai.openai_agents import agent_config
# With no DECIMAL_API_KEY this reads the skills your project lists, from disk.
support = agent_config("support", fallback="You are a helpful agent.")
agent = Agent(
name=support.name,
instructions=support.instructions, # your prompt, then the skill list
tools=[*support.tools], # your tools, then load_skill
)
# In a notebook or async code: result = await Runner.run(agent, "Hello!")
result = Runner.run_sync(agent, "Hello!")Using SandboxAgent? capabilities=[Shell(), decimalai.openai_agents.sandbox_skills()] points its own loader at the same folder.
The same install from Python, for a notebook or a setup script. agent_config() hands your agent the skill. With no DecimalAI key it reads your project's files and sends nothing. Needs SDK 0.27 or newer.
pip install "decimalai[openai-agents]"from decimalai import skillpkg
# Writes .agents/skills and .decimal/skills.toml. Safe to run twice.
result = skillpkg.add("varnan-tech-domain-expired-opportunity-finder")
if result.exit_code:
raise SystemExit(result.problems)
from agents import Agent, Runner
from decimalai.openai_agents import agent_config
# With no DECIMAL_API_KEY this reads the skills your project lists, from disk.
support = agent_config("support", fallback="You are a helpful agent.")
agent = Agent(
name=support.name,
instructions=support.instructions, # your prompt, then the skill list
tools=[*support.tools], # your tools, then load_skill
)
# In a notebook or async code: result = await Runner.run(agent, "Hello!")
result = Runner.run_sync(agent, "Hello!")Using SandboxAgent? capabilities=[Shell(), decimalai.openai_agents.sandbox_skills()] points its own loader at the same folder.
No DecimalAI package. The whole skill goes in every prompt, and nothing pins or updates it.
.agents/skills/varnan-tech-domain-expired-opportunity-finder/SKILL.mdfrom pathlib import Path
from agents import Agent, Runner
skill = Path(".agents/skills/varnan-tech-domain-expired-opportunity-finder/SKILL.md").read_text()
agent = Agent(name="support", instructions=f"You are a helpful agent.\n\n{skill}")
# In a notebook or async code: result = await Runner.run(agent, "Hello!")
result = Runner.run_sync(agent, "Hello!")This gets SKILL.md only. The skill's 4 other files are not included.
agent_config() hands your agent the skill. With no DecimalAI key it reads your project's files and sends nothing. Needs SDK 0.27 or newer.
pip install "decimalai[openai]"
decimalai skills add varnan-tech-domain-expired-opportunity-finderimport json
from openai import OpenAI
from decimalai.openai import agent_config
client = OpenAI()
# With no DECIMAL_API_KEY this reads the skills your project lists, from disk.
support = agent_config("support", fallback="You are a helpful agent.")
def lookup_order(order_id: str) -> str:
return f"Order {order_id} shipped yesterday."
# Your tools, as Chat Completions takes them, and what answers each.
TOOLS = [{"type": "function", "function": {
"name": "lookup_order",
"description": "Look up the status of an order.",
"parameters": {"type": "object", "properties": {"order_id": {"type": "string"}},
"required": ["order_id"]},
}}]
FUNCTIONS = {"lookup_order": lookup_order}
question = "Where is order A1001?"
with support.run(question) as run: # one user message, one run
# run.system is your prompt, then the skill list.
messages = [{"role": "system", "content": run.system}, run.message]
while True:
reply = client.chat.completions.create(
model="gpt-5.4-mini",
messages=messages,
tools=[*TOOLS, *run.tools], # your tools, then load_skill
).choices[0].message
messages.append(reply)
if not reply.tool_calls:
break
for call in reply.tool_calls:
# load_skill is answered by the run; your own tools by FUNCTIONS.
function = FUNCTIONS.get(call.function.name)
messages.append(run.tool_result(call) or {
"role": "tool", "tool_call_id": call.id,
"content": function(**json.loads(call.function.arguments)) if function
else f"There is no tool named {call.function.name!r}.",
})
print(reply.content)
# The Responses API takes run.response_tools instead of run.tools.The same install from Python, for a notebook or a setup script. agent_config() hands your agent the skill. With no DecimalAI key it reads your project's files and sends nothing. Needs SDK 0.27 or newer.
pip install "decimalai[openai]"from decimalai import skillpkg
# Writes .agents/skills and .decimal/skills.toml. Safe to run twice.
result = skillpkg.add("varnan-tech-domain-expired-opportunity-finder")
if result.exit_code:
raise SystemExit(result.problems)
import json
from openai import OpenAI
from decimalai.openai import agent_config
client = OpenAI()
# With no DECIMAL_API_KEY this reads the skills your project lists, from disk.
support = agent_config("support", fallback="You are a helpful agent.")
def lookup_order(order_id: str) -> str:
return f"Order {order_id} shipped yesterday."
# Your tools, as Chat Completions takes them, and what answers each.
TOOLS = [{"type": "function", "function": {
"name": "lookup_order",
"description": "Look up the status of an order.",
"parameters": {"type": "object", "properties": {"order_id": {"type": "string"}},
"required": ["order_id"]},
}}]
FUNCTIONS = {"lookup_order": lookup_order}
question = "Where is order A1001?"
with support.run(question) as run: # one user message, one run
# run.system is your prompt, then the skill list.
messages = [{"role": "system", "content": run.system}, run.message]
while True:
reply = client.chat.completions.create(
model="gpt-5.4-mini",
messages=messages,
tools=[*TOOLS, *run.tools], # your tools, then load_skill
).choices[0].message
messages.append(reply)
if not reply.tool_calls:
break
for call in reply.tool_calls:
# load_skill is answered by the run; your own tools by FUNCTIONS.
function = FUNCTIONS.get(call.function.name)
messages.append(run.tool_result(call) or {
"role": "tool", "tool_call_id": call.id,
"content": function(**json.loads(call.function.arguments)) if function
else f"There is no tool named {call.function.name!r}.",
})
print(reply.content)
# The Responses API takes run.response_tools instead of run.tools.No DecimalAI package. The whole skill goes in every prompt, and nothing pins or updates it.
.agents/skills/varnan-tech-domain-expired-opportunity-finder/SKILL.mdfrom pathlib import Path
from openai import OpenAI
client = OpenAI()
skill = Path(".agents/skills/varnan-tech-domain-expired-opportunity-finder/SKILL.md").read_text()
reply = client.chat.completions.create(
model="gpt-5.4-mini",
messages=[
{"role": "system", "content": f"You are a helpful agent.\n\n{skill}"},
{"role": "user", "content": "Hello!"},
],
).choices[0].messageThis gets SKILL.md only. The skill's 4 other files are not included.
agent_config() hands your agent the skill. With no DecimalAI key it reads your project's files and sends nothing. Needs SDK 0.27 or newer.
pip install "decimalai[anthropic]"
decimalai skills add varnan-tech-domain-expired-opportunity-finderimport anthropic
from decimalai.anthropic import agent_config
client = anthropic.Anthropic()
# With no DECIMAL_API_KEY this reads the skills your project lists, from disk.
support = agent_config("support", fallback="You are a helpful agent.")
question = "Hello!"
with support.run(question) as run: # one user message, one run
answer = client.beta.messages.tool_runner(
model="claude-haiku-5-5",
max_tokens=4096,
system=run.system, # your prompt, then the skill list
tools=[*run.tools], # your tools, then load_skill
messages=[{"role": "user", "content": question}],
).until_done()The same install from Python, for a notebook or a setup script. agent_config() hands your agent the skill. With no DecimalAI key it reads your project's files and sends nothing. Needs SDK 0.27 or newer.
pip install "decimalai[anthropic]"from decimalai import skillpkg
# Writes .agents/skills and .decimal/skills.toml. Safe to run twice.
result = skillpkg.add("varnan-tech-domain-expired-opportunity-finder")
if result.exit_code:
raise SystemExit(result.problems)
import anthropic
from decimalai.anthropic import agent_config
client = anthropic.Anthropic()
# With no DECIMAL_API_KEY this reads the skills your project lists, from disk.
support = agent_config("support", fallback="You are a helpful agent.")
question = "Hello!"
with support.run(question) as run: # one user message, one run
answer = client.beta.messages.tool_runner(
model="claude-haiku-5-5",
max_tokens=4096,
system=run.system, # your prompt, then the skill list
tools=[*run.tools], # your tools, then load_skill
messages=[{"role": "user", "content": question}],
).until_done()No DecimalAI package. The whole skill goes in every prompt, and nothing pins or updates it.
.agents/skills/varnan-tech-domain-expired-opportunity-finder/SKILL.mdfrom pathlib import Path
import anthropic
client = anthropic.Anthropic()
skill = Path(".agents/skills/varnan-tech-domain-expired-opportunity-finder/SKILL.md").read_text()
reply = client.messages.create(
model="claude-haiku-5-5",
max_tokens=4096,
system=f"You are a helpful agent.\n\n{skill}",
messages=[{"role": "user", "content": "Hello!"}],
)This gets SKILL.md only. The skill's 4 other files are not included.
agent_config() hands your agent the skill. With no DecimalAI key it reads your project's files and sends nothing. Needs SDK 0.27 or newer.
pip install "decimalai[google-genai]"
decimalai skills add varnan-tech-domain-expired-opportunity-finderfrom google import genai
from google.genai import types
from decimalai.google_genai import agent_config
client = genai.Client()
# With no DECIMAL_API_KEY this reads the skills your project lists, from disk.
support = agent_config("support", fallback="You are a helpful agent.")
def lookup_order(order_id: str) -> str:
return f"Order {order_id} shipped yesterday."
# Your functions, as declarations for the model, and what answers each.
TOOLS = [{"function_declarations": [{
"name": "lookup_order",
"description": "Look up the status of an order.",
"parameters_json_schema": {"type": "object", "properties": {"order_id": {"type": "string"}},
"required": ["order_id"]},
}]}]
FUNCTIONS = {"lookup_order": lookup_order}
question = "Where is order A1001?"
with support.run(question) as run: # one user message, one run
config = types.GenerateContentConfig(
system_instruction=run.system, # your prompt, then the skill list
tools=[*TOOLS, *run.tool_params], # your functions, then load_skill
)
contents = [{"role": "user", "parts": [{"text": run.message}]}]
while True:
response = client.models.generate_content(
model="gemini-3.5-flash", contents=contents, config=config,
)
if not response.function_calls:
break
contents.append(response.candidates[0].content)
parts = []
for call in response.function_calls:
# load_skill is answered by the run; your own functions by FUNCTIONS.
# A loop of your own rather than automatic function calling, which
# raises KeyError when the model asks for a function you did not pass.
function = FUNCTIONS.get(call.name)
parts.append(run.tool_result(call) or {"function_response": {
"name": call.name, "id": call.id,
"response": {"result": function(**(call.args or {})) if function
else f"There is no function named {call.name!r}."},
}})
contents.append({"role": "user", "parts": parts})
print(response.text)The same install from Python, for a notebook or a setup script. agent_config() hands your agent the skill. With no DecimalAI key it reads your project's files and sends nothing. Needs SDK 0.27 or newer.
pip install "decimalai[google-genai]"from decimalai import skillpkg
# Writes .agents/skills and .decimal/skills.toml. Safe to run twice.
result = skillpkg.add("varnan-tech-domain-expired-opportunity-finder")
if result.exit_code:
raise SystemExit(result.problems)
from google import genai
from google.genai import types
from decimalai.google_genai import agent_config
client = genai.Client()
# With no DECIMAL_API_KEY this reads the skills your project lists, from disk.
support = agent_config("support", fallback="You are a helpful agent.")
def lookup_order(order_id: str) -> str:
return f"Order {order_id} shipped yesterday."
# Your functions, as declarations for the model, and what answers each.
TOOLS = [{"function_declarations": [{
"name": "lookup_order",
"description": "Look up the status of an order.",
"parameters_json_schema": {"type": "object", "properties": {"order_id": {"type": "string"}},
"required": ["order_id"]},
}]}]
FUNCTIONS = {"lookup_order": lookup_order}
question = "Where is order A1001?"
with support.run(question) as run: # one user message, one run
config = types.GenerateContentConfig(
system_instruction=run.system, # your prompt, then the skill list
tools=[*TOOLS, *run.tool_params], # your functions, then load_skill
)
contents = [{"role": "user", "parts": [{"text": run.message}]}]
while True:
response = client.models.generate_content(
model="gemini-3.5-flash", contents=contents, config=config,
)
if not response.function_calls:
break
contents.append(response.candidates[0].content)
parts = []
for call in response.function_calls:
# load_skill is answered by the run; your own functions by FUNCTIONS.
# A loop of your own rather than automatic function calling, which
# raises KeyError when the model asks for a function you did not pass.
function = FUNCTIONS.get(call.name)
parts.append(run.tool_result(call) or {"function_response": {
"name": call.name, "id": call.id,
"response": {"result": function(**(call.args or {})) if function
else f"There is no function named {call.name!r}."},
}})
contents.append({"role": "user", "parts": parts})
print(response.text)No DecimalAI package. The whole skill goes in every prompt, and nothing pins or updates it.
.agents/skills/varnan-tech-domain-expired-opportunity-finder/SKILL.mdfrom pathlib import Path
from google import genai
from google.genai import types
client = genai.Client()
skill = Path(".agents/skills/varnan-tech-domain-expired-opportunity-finder/SKILL.md").read_text()
response = client.models.generate_content(
model="gemini-3.5-flash",
contents="Hello!",
config=types.GenerateContentConfig(system_instruction=f"You are a helpful agent.\n\n{skill}"),
)This gets SKILL.md only. The skill's 4 other files are not included.
agent_config() hands your agent the skill. With no DecimalAI key it reads your project's files and sends nothing. Needs SDK 0.27 or newer.
pip install "decimalai[langchain]" langchain-openai
decimalai skills add varnan-tech-domain-expired-opportunity-finderfrom langchain.agents import create_agent
from decimalai.langchain import agent_config
# With no DECIMAL_API_KEY this reads the skills your project lists, from disk.
support = agent_config("support", fallback="You are a helpful agent.")
agent = create_agent(
"openai:gpt-5.4-mini",
tools=[], # your tools; the middleware adds load_skill
middleware=[support.middleware], # your prompt, the skill list and load_skill
)
result = agent.invoke({"messages": [{"role": "user", "content": "Hello!"}]})The same install from Python, for a notebook or a setup script. agent_config() hands your agent the skill. With no DecimalAI key it reads your project's files and sends nothing. Needs SDK 0.27 or newer.
pip install "decimalai[langchain]" langchain-openaifrom decimalai import skillpkg
# Writes .agents/skills and .decimal/skills.toml. Safe to run twice.
result = skillpkg.add("varnan-tech-domain-expired-opportunity-finder")
if result.exit_code:
raise SystemExit(result.problems)
from langchain.agents import create_agent
from decimalai.langchain import agent_config
# With no DECIMAL_API_KEY this reads the skills your project lists, from disk.
support = agent_config("support", fallback="You are a helpful agent.")
agent = create_agent(
"openai:gpt-5.4-mini",
tools=[], # your tools; the middleware adds load_skill
middleware=[support.middleware], # your prompt, the skill list and load_skill
)
result = agent.invoke({"messages": [{"role": "user", "content": "Hello!"}]})No DecimalAI package. The whole skill goes in every prompt, and nothing pins or updates it.
.agents/skills/varnan-tech-domain-expired-opportunity-finder/SKILL.mdfrom pathlib import Path
from langchain.agents import create_agent
skill = Path(".agents/skills/varnan-tech-domain-expired-opportunity-finder/SKILL.md").read_text()
agent = create_agent("openai:gpt-5.4-mini", tools=[], system_prompt=f"You are a helpful agent.\n\n{skill}")
result = agent.invoke({"messages": [{"role": "user", "content": "Hello!"}]})This gets SKILL.md only. The skill's 4 other files are not included.
CrewAI's own skill loader reads the installed folder. No DecimalAI account needed.
pip install "decimalai>=0.16" "crewai>=1.15.27"
decimalai skills add varnan-tech-domain-expired-opportunity-finderimport decimalai.crewai
from crewai import Agent, Crew, Task
agent = Agent(
role="Support", goal="Help the customer", backstory="A support agent.",
llm=llm, # your own CrewAI LLM
skills=decimalai.crewai.skills(), # the installed folder
tools=decimalai.crewai.skill_tools(), # reads a skill's files, runs its scripts
)
task = Task(description="Hello!", expected_output="an answer", agent=agent)
Crew(agents=[agent], tasks=[task]).kickoff()The same install from Python, for a notebook or a setup script. CrewAI's own skill loader reads the installed folder. No DecimalAI account needed.
pip install "decimalai>=0.16" "crewai>=1.15.27"from decimalai import skillpkg
# Writes .agents/skills and .decimal/skills.toml. Safe to run twice.
result = skillpkg.add("varnan-tech-domain-expired-opportunity-finder")
if result.exit_code:
raise SystemExit(result.problems)
import decimalai.crewai
from crewai import Agent, Crew, Task
agent = Agent(
role="Support", goal="Help the customer", backstory="A support agent.",
llm=llm, # your own CrewAI LLM
skills=decimalai.crewai.skills(), # the installed folder
tools=decimalai.crewai.skill_tools(), # reads a skill's files, runs its scripts
)
task = Task(description="Hello!", expected_output="an answer", agent=agent)
Crew(agents=[agent], tasks=[task]).kickoff()No DecimalAI package. CrewAI's own loader reads the folder, and nothing pins or updates it.
.agents/skills/varnan-tech-domain-expired-opportunity-finder/SKILL.mdfrom pathlib import Path
from crewai import Agent, Crew, Task
agent = Agent(
role="Support", goal="Help the customer", backstory="A support agent.",
llm=llm, # your own CrewAI LLM
skills=[Path(".agents/skills")],
)
task = Task(description="Hello!", expected_output="an answer", agent=agent)
Crew(agents=[agent], tasks=[task]).kickoff()This gets SKILL.md only. The skill's 4 other files are not included.
agent_config() hands your agent the skill. With no DecimalAI key it reads your project's files and sends nothing. Needs SDK 0.27 or newer.
pip install "decimalai[adk]"
decimalai skills add varnan-tech-domain-expired-opportunity-finderfrom decimalai.adk import DecimalAIPlugin, agent_config
from google.adk.agents import LlmAgent
from google.adk.apps import App
# With no DECIMAL_API_KEY this reads the skills your project lists, from disk.
support = agent_config("support", fallback="You are a helpful agent.")
agent = LlmAgent(
name="support", # ADK needs a Python identifier
model="gemini-3.5-flash",
instruction=support.instruction, # your prompt, then the skill list
tools=[*support.tools], # your tools, then load_skill
)
# DecimalAIPlugin makes each invocation of runners built from this App one trace.
app = App(name="support", root_agent=agent, plugins=[DecimalAIPlugin()])Prefer ADK's own loader? SkillToolset(skills=decimalai.adk.skills(), ...) reads the same folder. Use one or the other: a skill delivered both ways reaches the prompt twice.
The same install from Python, for a notebook or a setup script. agent_config() hands your agent the skill. With no DecimalAI key it reads your project's files and sends nothing. Needs SDK 0.27 or newer.
pip install "decimalai[adk]"from decimalai import skillpkg
# Writes .agents/skills and .decimal/skills.toml. Safe to run twice.
result = skillpkg.add("varnan-tech-domain-expired-opportunity-finder")
if result.exit_code:
raise SystemExit(result.problems)
from decimalai.adk import DecimalAIPlugin, agent_config
from google.adk.agents import LlmAgent
from google.adk.apps import App
# With no DECIMAL_API_KEY this reads the skills your project lists, from disk.
support = agent_config("support", fallback="You are a helpful agent.")
agent = LlmAgent(
name="support", # ADK needs a Python identifier
model="gemini-3.5-flash",
instruction=support.instruction, # your prompt, then the skill list
tools=[*support.tools], # your tools, then load_skill
)
# DecimalAIPlugin makes each invocation of runners built from this App one trace.
app = App(name="support", root_agent=agent, plugins=[DecimalAIPlugin()])Prefer ADK's own loader? SkillToolset(skills=decimalai.adk.skills(), ...) reads the same folder. Use one or the other: a skill delivered both ways reaches the prompt twice.
No DecimalAI package. The whole skill goes in every prompt, and nothing pins or updates it.
.agents/skills/varnan-tech-domain-expired-opportunity-finder/SKILL.mdfrom pathlib import Path
from google.adk.agents import LlmAgent
skill = Path(".agents/skills/varnan-tech-domain-expired-opportunity-finder/SKILL.md").read_text()
agent = LlmAgent(
name="support",
model="gemini-3.5-flash",
instruction=lambda context: f"You are a helpful agent.\n\n{skill}",
)This gets SKILL.md only. The skill's 4 other files are not included.
agent_config() hands your agent the skill. With no DecimalAI key it reads your project's files and sends nothing. Needs SDK 0.27 or newer.
pip install "decimalai[pydantic-ai]"
decimalai skills add varnan-tech-domain-expired-opportunity-finderfrom pydantic_ai import Agent
from decimalai.pydantic_ai import agent_config
# With no DECIMAL_API_KEY this reads the skills your project lists, from disk.
support = agent_config("support", fallback="You are a helpful agent.")
agent = Agent(
"openai:gpt-4o-mini",
name=support.name,
tools=[], # your tools; the capability adds load_skill
capabilities=[support], # your prompt, the skill list and load_skill
)
# In a notebook or async code: result = await agent.run("Hello!")
result = agent.run_sync("Hello!")On the Pydantic AI harness, decimalai.pydantic_ai_harness.skills(project) uses its own loader instead.
The same install from Python, for a notebook or a setup script. agent_config() hands your agent the skill. With no DecimalAI key it reads your project's files and sends nothing. Needs SDK 0.27 or newer.
pip install "decimalai[pydantic-ai]"from decimalai import skillpkg
# Writes .agents/skills and .decimal/skills.toml. Safe to run twice.
result = skillpkg.add("varnan-tech-domain-expired-opportunity-finder")
if result.exit_code:
raise SystemExit(result.problems)
from pydantic_ai import Agent
from decimalai.pydantic_ai import agent_config
# With no DECIMAL_API_KEY this reads the skills your project lists, from disk.
support = agent_config("support", fallback="You are a helpful agent.")
agent = Agent(
"openai:gpt-4o-mini",
name=support.name,
tools=[], # your tools; the capability adds load_skill
capabilities=[support], # your prompt, the skill list and load_skill
)
# In a notebook or async code: result = await agent.run("Hello!")
result = agent.run_sync("Hello!")On the Pydantic AI harness, decimalai.pydantic_ai_harness.skills(project) uses its own loader instead.
No DecimalAI package. The whole skill goes in every prompt, and nothing pins or updates it.
.agents/skills/varnan-tech-domain-expired-opportunity-finder/SKILL.mdfrom pathlib import Path
from pydantic_ai import Agent
skill = Path(".agents/skills/varnan-tech-domain-expired-opportunity-finder/SKILL.md").read_text()
agent = Agent("openai:gpt-4o-mini", instructions=f"You are a helpful agent.\n\n{skill}")
# In a notebook or async code: result = await agent.run("Hello!")
result = agent.run_sync("Hello!")This gets SKILL.md only. The skill's 4 other files are not included.
Deep Agents' own skill loader reads the installed folder. No DecimalAI account needed.
pip install "decimalai>=0.16" deepagents
decimalai skills add varnan-tech-domain-expired-opportunity-finderimport decimalai.deepagents
from deepagents import create_deep_agent
agent = create_deep_agent(model=model, **decimalai.deepagents.skills())
agent.invoke({"messages": [{"role": "user", "content": "Hello!"}]})The default backend reads files but has no shell. To run a skill's scripts, pass skills(backend=LocalShellBackend(...)).
The same install from Python, for a notebook or a setup script. Deep Agents' own skill loader reads the installed folder. No DecimalAI account needed.
pip install "decimalai>=0.16" deepagentsfrom decimalai import skillpkg
# Writes .agents/skills and .decimal/skills.toml. Safe to run twice.
result = skillpkg.add("varnan-tech-domain-expired-opportunity-finder")
if result.exit_code:
raise SystemExit(result.problems)
import decimalai.deepagents
from deepagents import create_deep_agent
agent = create_deep_agent(model=model, **decimalai.deepagents.skills())
agent.invoke({"messages": [{"role": "user", "content": "Hello!"}]})The default backend reads files but has no shell. To run a skill's scripts, pass skills(backend=LocalShellBackend(...)).
No DecimalAI package. The whole skill goes in every prompt, and nothing pins or updates it.
.agents/skills/varnan-tech-domain-expired-opportunity-finder/SKILL.mdfrom pathlib import Path
from deepagents import create_deep_agent
skill = Path(".agents/skills/varnan-tech-domain-expired-opportunity-finder/SKILL.md").read_text()
agent = create_deep_agent(model=model, system_prompt=f"You are a helpful agent.\n\n{skill}")
agent.invoke({"messages": [{"role": "user", "content": "Hello!"}]})This gets SKILL.md only. The skill's 4 other files are not included.
Its own skill loader reads the installed folder. No DecimalAI account needed.
pip install "decimalai>=0.16" agent-framework
decimalai skills add varnan-tech-domain-expired-opportunity-finderimport asyncio
from agent_framework import Agent
from decimalai.agent_framework import skills
agent = Agent(client=client, instructions="You are a helpful agent.", context_providers=[skills()])
asyncio.run(agent.run("Hello!"))It caches skills by default, so restart the process after decimalai skills update.
The same install from Python, for a notebook or a setup script. Its own skill loader reads the installed folder. No DecimalAI account needed.
pip install "decimalai>=0.16" agent-frameworkfrom decimalai import skillpkg
# Writes .agents/skills and .decimal/skills.toml. Safe to run twice.
result = skillpkg.add("varnan-tech-domain-expired-opportunity-finder")
if result.exit_code:
raise SystemExit(result.problems)
import asyncio
from agent_framework import Agent
from decimalai.agent_framework import skills
agent = Agent(client=client, instructions="You are a helpful agent.", context_providers=[skills()])
asyncio.run(agent.run("Hello!"))It caches skills by default, so restart the process after decimalai skills update.
No DecimalAI package. The whole skill goes in every prompt, and nothing pins or updates it.
.agents/skills/varnan-tech-domain-expired-opportunity-finder/SKILL.mdimport asyncio
from pathlib import Path
from agent_framework import Agent
skill = Path(".agents/skills/varnan-tech-domain-expired-opportunity-finder/SKILL.md").read_text()
agent = Agent(client=client, instructions=f"You are a helpful agent.\n\n{skill}")
asyncio.run(agent.run("Hello!"))This gets SKILL.md only. The skill's 4 other files are not included.
Strands' own skill loader reads the installed folder. No DecimalAI account needed.
pip install "decimalai>=0.16" strands-agents
decimalai skills add varnan-tech-domain-expired-opportunity-finderfrom decimalai.strands import skills
from strands import Agent
from strands.vended_tools import file_editor, shell
agent = Agent(
model=model, # always pass one: the default is Amazon Bedrock
system_prompt="You are a helpful agent.",
tools=[file_editor, shell],
plugins=[skills()],
)
agent("Hello!")Strands loads an agent's skills once, so build the agent again after decimalai skills update.
The same install from Python, for a notebook or a setup script. Strands' own skill loader reads the installed folder. No DecimalAI account needed.
pip install "decimalai>=0.16" strands-agentsfrom decimalai import skillpkg
# Writes .agents/skills and .decimal/skills.toml. Safe to run twice.
result = skillpkg.add("varnan-tech-domain-expired-opportunity-finder")
if result.exit_code:
raise SystemExit(result.problems)
from decimalai.strands import skills
from strands import Agent
from strands.vended_tools import file_editor, shell
agent = Agent(
model=model, # always pass one: the default is Amazon Bedrock
system_prompt="You are a helpful agent.",
tools=[file_editor, shell],
plugins=[skills()],
)
agent("Hello!")Strands loads an agent's skills once, so build the agent again after decimalai skills update.
No DecimalAI package. The whole skill goes in every prompt, and nothing pins or updates it.
.agents/skills/varnan-tech-domain-expired-opportunity-finder/SKILL.mdfrom pathlib import Path
from strands import Agent
skill = Path(".agents/skills/varnan-tech-domain-expired-opportunity-finder/SKILL.md").read_text()
agent = Agent(model=model, system_prompt=f"You are a helpful agent.\n\n{skill}")
agent("Hello!")This gets SKILL.md only. The skill's 4 other files are not included.
Adds every file the skill has, pinned so you can update it. Cursor, Copilot, Cline and Warp read this folder by themselves.
pip install "decimalai>=0.15"
decimalai skills add varnan-tech-domain-expired-opportunity-finderNo Python or account. Cursor, Copilot, Cline and Warp read this folder by themselves.
curl -fsSL --create-dirs -o .agents/skills/varnan-tech-domain-expired-opportunity-finder/SKILL.md https://app.decimal.ai/s/varnan-tech-domain-expired-opportunity-finder/SKILL.md.agents/skills/varnan-tech-domain-expired-opportunity-finder/SKILL.mdThis gets SKILL.md only. The skill's 4 other files are not included.
Give its text to your model as instructions. No account or package needed.
This gets SKILL.md only. The skill's 4 other files are not included.
Raw files: /s/varnan-tech-domain-expired-opportunity-finder/SKILL.md · /s/varnan-tech-domain-expired-opportunity-finder.json
Fetch the file and give its text to your model as instructions. No account or package needed.
curl -fsSL https://app.decimal.ai/s/varnan-tech-domain-expired-opportunity-finder/SKILL.mdcurl -fsSL https://app.decimal.ai/s/varnan-tech-domain-expired-opportunity-finder.jsonThis gets SKILL.md only. The skill's 4 other files are not included.
You don't need a DecimalAI account.
Saved prompts and skills, pinned versions, and traces of this skill in your runs. DecimalAI does not run your agent.
Claude Code reads files from your project, so DecimalAI config does not apply there.
On Claude Agent SDK: save the skill to an agent here, set DECIMAL_API_KEY, and the same code uses it with no command to run. Changes you save reach the next run with no redeploy.
On OpenAI Agents: save the skill to an agent here, set DECIMAL_API_KEY, and the same code uses it with no command to run. Changes you save reach the next run with no redeploy.
On OpenAI SDK: save the skill to an agent here, set DECIMAL_API_KEY, and the same code uses it with no command to run. Changes you save reach the next run with no redeploy.
On Anthropic: save the skill to an agent here, set DECIMAL_API_KEY, and the same code uses it with no command to run. Changes you save reach the next run with no redeploy.
On Gemini API: save the skill to an agent here, set DECIMAL_API_KEY, and the same code uses it with no command to run. Changes you save reach the next run with no redeploy.
On LangChain: save the skill to an agent here, set DECIMAL_API_KEY, and the same code uses it with no command to run. Changes you save reach the next run with no redeploy.
On CrewAI: set DECIMAL_API_KEY and add decimalai.init(crewai=True) to record runs and skill use. The skill version stays in your repo.
On Google ADK: save the skill to an agent here, set DECIMAL_API_KEY, and the same code uses it with no command to run. Changes you save reach the next run with no redeploy.
On Pydantic AI: save the skill to an agent here, set DECIMAL_API_KEY, and the same code uses it with no command to run. Changes you save reach the next run with no redeploy.
On Deep Agents: set DECIMAL_API_KEY and add decimalai.init(langchain=True) to record runs and skill use. The skill version stays in your repo.
On Microsoft Agent Framework: set DECIMAL_API_KEY and add decimalai.init(otel=True, agent_name="support") to record runs and skill use. The skill version stays in your repo.
On Strands: set DECIMAL_API_KEY and add decimalai.init(otel=True, agent_name="support") to record runs and skill use. The skill version stays in your repo.
Editors read files from your project, so DecimalAI config does not apply there.
Anywhere else: record a use with decimalai.log_skill_activation() inside a trace.
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.
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 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.
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:
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]."
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) < 4:
invalid.append(d)
continue
# Deduplicate
if d in seen:
continue
seen.add(d)
valid.append(d)
print(f'Valid candidates: {len(valid)}')
print(f'Removed (invalid/duplicate): {len(invalid)}')
for v in valid:
print(f' ✓ {v}')
for i in invalid:
print(f' ✗ {i} (invalid format)')
"
Replace CANDIDATE_LIST_HERE with the actual domain list from Step 2.
State: "[N] valid candidates after normalization. [M] removed (invalid/duplicate)."
If 0 valid candidates remain, stop and tell the user: "No valid domain candidates found. Please provide domain names in the format 'example.com'."
For each valid candidate, collect signals from free public sources. Run these checks sequentially per domain.
Query the Wayback Machine for all historical snapshots. We use limit=100000
and explicit from/to parameters are intentionally omitted so that CDX
returns snapshots from the full lifetime of the domain. The results are sorted
ascending by timestamp (oldest first) so first_capture and last_capture
are accurate:
curl -s "https://web.archive.org/cdx/search/cdx?url=DOMAIN_HERE&output=json&fl=timestamp,statuscode&collapse=timestamp:6&limit=100000" \
| python3 -c "
import sys, json
try:
data = json.load(sys.stdin)
if len(data) <= 1:
print(json.dumps({'domain': 'DOMAIN_HERE', 'snapshots': 0, 'first_capture': None, 'last_capture': None, 'status_codes': {}, 'years_active': 0}))
else:
rows = data[1:] # skip header row
timestamps = [r[0] for r in rows] # already ascending (oldest first)
statuses = [r[1] for r in rows]
status_counts = {}
for s in statuses:
status_counts[s] = status_counts.get(s, 0) + 1
first_year = int(timestamps[0][:4])
last_year = int(timestamps[-1][:4])
print(json.dumps({
'domain': 'DOMAIN_HERE',
'snapshots': len(rows),
'first_capture': timestamps[0],
'last_capture': timestamps[-1],
'status_codes': status_counts,
'years_active': last_year - first_year + 1
}))
except:
print(json.dumps({'domain': 'DOMAIN_HERE', 'snapshots': 0, 'error': 'wayback_api_failed'}))
"
Rate limiting: Wait 2 seconds between Wayback API calls to be polite to the service.
For candidates with > 0 snapshots, fetch the most recent snapshot to extract the page title (used for topical relevance scoring):
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'<title[^>]*>(.*?)</title>', 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'<meta[^>]*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.
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'}))
"
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)
}))
"
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 << 'ENDJSON'
{
"system_instruction": {
"parts": [{
"text": "You are an SEO research analyst. For each expired domain candidate provided, assess its topical relevance to the specified target niche. Consider the domain name, historical page title, and meta description. For each domain, output a JSON object with: domain (string), relevance_score (integer 1-10), relevance_rationale (one sentence explaining the score), redirect_plausibility (integer 1-10), redirect_rationale (one sentence). Output only a JSON array. No commentary before or after."
}]
},
"contents": [{
"parts": [{
"text": "DOMAIN_SIGNALS_AND_NICHE_CONTEXT_HERE"
}]
}],
"generationConfig": {
"temperature": 0.2,
"maxOutputTokens": 2048
}
}
ENDJSON
Replace DOMAIN_SIGNALS_AND_NICHE_CONTEXT_HERE with:
Send the request to the Gemini API:
curl -s -X POST \
"${LLM_API_ENDPOINT:-https://generativelanguage.googleapis.com/v1beta}/models/${LLM_MODEL:-gemini-2.0-flash}:generateContent?key=$LLM_API_KEY" \
-H "Content-Type: application/json" \
-d @/tmp/domain-relevance-request.json \
| python3 -c "
import sys, json
try:
d = json.load(sys.stdin)
text = d['candidates'][0]['content']['parts'][0]['text']
print(text)
except (KeyError, IndexError, json.JSONDecodeError) as e:
print(json.dumps({'error': 'llm_response_parse_failed', 'detail': str(e)}))
"
If the LLM call or response parsing fails, log the error and continue with rule-based scoring only. Do not stop the workflow.
After all signal collection, state: "Signal collection complete for [N] candidates. [M] Wayback hits, [K] RDAP lookups succeeded."
Read references/scoring-model.md for the full scoring framework.
For each candidate, compute scores across the 6 dimensions:
Topical Relevance (0–30): Combine domain keyword match ratio, historical title/description analysis, and LLM relevance score (if available). Without LLM: use keyword match ratio × 15 + title keyword overlap × 15. With LLM: use LLM relevance_score × 3.
Historical Activity Level (0–25): Based on Wayback snapshot diversity and frequency. More snapshots consistently captured across multiple years indicates higher sustained activity and inferred legitimacy.
Historical Content Quality (0–15): Derived from historical page title and meta description analysis, checking for natural phrasing versus keyword stuffing. Without LLM: base score of 8/15 adjusted by exact-match density.
History Cleanliness (0–15): Based on Wayback snapshot count, years active, status code consistency, and absence of parking page indicators.
Redirect Suitability (0–10): Based on topic continuity between historical content and target niche. Use LLM redirect_plausibility score if available; otherwise use keyword overlap ratio.
Signal Completeness (0–5): Count how many data sources returned usable data (Wayback, RDAP, domain analysis, LLM if configured).
Compute:
opportunity_score = sum of all dimension scores (0–100)confidence = based on how many dimensions have strong data (see scoring-model.md)recommended_action = based on score + confidence + risk flags (see Step 6)Read references/risk-flags.md for the complete flag definitions.
Apply risk flags to each candidate:
| Check | Flag Applied |
|---|---|
| Historical topic overlap < 30% with target niche | topic_mismatch |
| Domain active < 1 year before expiry | short_history |
| < 3 Wayback snapshots or all parking pages | unclear_history |
| Sudden Wayback drop-off after years of activity | possible_deindex |
Snapshot count below min_snapshots | weak_historical_activity |
| Redirect suitability < 4/10 | redirect_mismatch |
Apply recommendation logic:
| Score + Flags | Recommendation |
|---|---|
Score ≥ 75 AND confidence high AND no High-severity flags | high-priority-review |
Score ≥ 55 AND confidence ≥ medium | review |
| Score ≥ 55 BUT redirect_suitability < 4/10 | rebuild-only-review |
| Score < 55 OR any critical High-severity flag | reject |
Apply max_risk_level filter:
max_risk_level = low: exclude any candidate with Medium or High flagsmax_risk_level = medium: exclude candidates with High flags onlymax_risk_level = high: include all candidates (no filter)Read references/output-format.md for the exact JSON schema.
Read references/guardrails.md for the required disclaimer text.
Default: Shortlist mode. Show only candidates with recommended_action
of high-priority-review, review, or rebuild-only-review.
If the user requested audit mode, show ALL candidates with full dimension breakdowns including rejection reasons.
## Expired Domain Opportunity Finder — [YYYY-MM-DD]
**Target niche:** [niche]
**Seed keywords:** [keywords]
**Intended use:** [rebuild/redirect/either]
**Candidates evaluated:** [N]
**Shortlisted:** [M]
**Rejected:** [K]
**Scoring mode:** [llm-enhanced / rule-based-only]
---
### 1. [domain.com] — Score: [N]/100 | Confidence: [level] | Action: [recommendation]
**Topical fit:** [summary]
**Activity level:** [summary]
**Content quality:** [summary]
**History:** [summary]
**Redirect suitability:** [level]
**Risk flags:** [flags or "none"]
**Why selected:** [rationale]
**Why risky:** [rationale]
---
[repeat for each shortlisted domain, ranked by opportunity_score descending]
---
**Disclaimer:** These results are research recommendations, not guarantees
of SEO value. Redirect analysis should only be considered when strong
topic continuity exists between the expired domain and your target site.
Search engine algorithms change frequently. Always perform manual due
diligence — including checking current index status, reviewing the full
backlink profile with a commercial tool, and verifying domain history —
before making any acquisition decision. This skill does not endorse or
facilitate manipulative SEO practices.
Save the structured JSON output:
mkdir -p docs/expired-domain-intel
OUTFILE="docs/expired-domain-intel/$(date +%Y-%m-%d).json"
cat > "$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:
Run every check before presenting output:
why_selected AND why_riskyhigh-priority-review actionredirect_mismatch are labeled rebuild-only-review (not review)scoring_mode correctly reflects whether LLM was usedopportunity_score descendingdocs/expired-domain-intel/YYYY-MM-DD.jsonFix any violation before presenting.
why_riskySkills that do similar work, with their headline benchmark lift.
From the same source and nearby in the catalogue.