Install any skill in seconds. Free to start, no credit card required.
Get Started Free →선택된 reference brand의 라이브 사이트에서 디자인 컨텍스트(토큰·구조·visual reference)를 캡쳐. brand homepage 패칭, 컴퓨티드 스타일 inspect, 로고/스크린샷을 assets/_reference/<id>/ 로 가져와 attribution.md + LICENSE-NOTE.md와 함께 저장. '뱅크샐러드 에셋 가져와줘', 'X 사이트 패칭', 'X reference 캡쳐', 'X 라이브 스타일 추출', '브랜드 자료 받아와' 류 요청에 트리거. omd:init 직후 또는 omd:harness 중간에 호출 가능. DESIGN.md는 이 스킬이 만들지 않음 (omd:init 책임).
SkillScore74/100
Test results 74
3/3 safety checks passed
22%74%of test cases passed +52 pts
Passed 17 of 23 test cases with the skill, and 5 of 23 without it.
13 fixed4 passed either way5 still failing1 broke
This benchmark measured an earlier version of the skill.
A newer attempt on gemini-3.8-flash on 11/2/2026 did not produce a publishable comparison (graded differently). The snapshot above is the previously measured run.
One test taskWhat command should be run prior to downloading live site paths to check automated crawling policy?
Adds every file the skill has, pinned so you can update it. No account needed.
pip install "decimalai>=0.15"
decimalai skills add kwakseongjae-omd-reference-capture
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("kwakseongjae-omd-reference-capture")
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 kwakseongjae-omd-reference-captureThe 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/kwakseongjae-omd-reference-capture/SKILL.md https://app.decimal.ai/s/kwakseongjae-omd-reference-capture/SKILL.md.claude/skills/kwakseongjae-omd-reference-capture/SKILL.mdThis gets SKILL.md only. The skill's 3 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 kwakseongjae-omd-reference-captureimport 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("kwakseongjae-omd-reference-capture")
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/kwakseongjae-omd-reference-capture/SKILL.mdimport asyncio
from pathlib import Path
from claude_agent_sdk import ClaudeAgentOptions, query
skill = Path(".agents/skills/kwakseongjae-omd-reference-capture/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 3 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 kwakseongjae-omd-reference-capturefrom 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("kwakseongjae-omd-reference-capture")
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/kwakseongjae-omd-reference-capture/SKILL.mdfrom pathlib import Path
from agents import Agent, Runner
skill = Path(".agents/skills/kwakseongjae-omd-reference-capture/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 3 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 kwakseongjae-omd-reference-captureimport 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("kwakseongjae-omd-reference-capture")
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/kwakseongjae-omd-reference-capture/SKILL.mdfrom pathlib import Path
from openai import OpenAI
client = OpenAI()
skill = Path(".agents/skills/kwakseongjae-omd-reference-capture/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 3 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 kwakseongjae-omd-reference-captureimport 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("kwakseongjae-omd-reference-capture")
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/kwakseongjae-omd-reference-capture/SKILL.mdfrom pathlib import Path
import anthropic
client = anthropic.Anthropic()
skill = Path(".agents/skills/kwakseongjae-omd-reference-capture/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 3 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 kwakseongjae-omd-reference-capturefrom 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("kwakseongjae-omd-reference-capture")
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/kwakseongjae-omd-reference-capture/SKILL.mdfrom pathlib import Path
from google import genai
from google.genai import types
client = genai.Client()
skill = Path(".agents/skills/kwakseongjae-omd-reference-capture/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 3 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 kwakseongjae-omd-reference-capturefrom 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("kwakseongjae-omd-reference-capture")
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/kwakseongjae-omd-reference-capture/SKILL.mdfrom pathlib import Path
from langchain.agents import create_agent
skill = Path(".agents/skills/kwakseongjae-omd-reference-capture/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 3 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 kwakseongjae-omd-reference-captureimport 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("kwakseongjae-omd-reference-capture")
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/kwakseongjae-omd-reference-capture/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 3 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 kwakseongjae-omd-reference-capturefrom 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("kwakseongjae-omd-reference-capture")
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/kwakseongjae-omd-reference-capture/SKILL.mdfrom pathlib import Path
from google.adk.agents import LlmAgent
skill = Path(".agents/skills/kwakseongjae-omd-reference-capture/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 3 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 kwakseongjae-omd-reference-capturefrom 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("kwakseongjae-omd-reference-capture")
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/kwakseongjae-omd-reference-capture/SKILL.mdfrom pathlib import Path
from pydantic_ai import Agent
skill = Path(".agents/skills/kwakseongjae-omd-reference-capture/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 3 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 kwakseongjae-omd-reference-captureimport 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("kwakseongjae-omd-reference-capture")
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/kwakseongjae-omd-reference-capture/SKILL.mdfrom pathlib import Path
from deepagents import create_deep_agent
skill = Path(".agents/skills/kwakseongjae-omd-reference-capture/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 3 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 kwakseongjae-omd-reference-captureimport 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("kwakseongjae-omd-reference-capture")
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/kwakseongjae-omd-reference-capture/SKILL.mdimport asyncio
from pathlib import Path
from agent_framework import Agent
skill = Path(".agents/skills/kwakseongjae-omd-reference-capture/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 3 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 kwakseongjae-omd-reference-capturefrom 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("kwakseongjae-omd-reference-capture")
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/kwakseongjae-omd-reference-capture/SKILL.mdfrom pathlib import Path
from strands import Agent
skill = Path(".agents/skills/kwakseongjae-omd-reference-capture/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 3 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 kwakseongjae-omd-reference-captureNo Python or account. Cursor, Copilot, Cline and Warp read this folder by themselves.
curl -fsSL --create-dirs -o .agents/skills/kwakseongjae-omd-reference-capture/SKILL.md https://app.decimal.ai/s/kwakseongjae-omd-reference-capture/SKILL.md.agents/skills/kwakseongjae-omd-reference-capture/SKILL.mdThis gets SKILL.md only. The skill's 3 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 3 other files are not included.
Raw files: /s/kwakseongjae-omd-reference-capture/SKILL.md · /s/kwakseongjae-omd-reference-capture.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/kwakseongjae-omd-reference-capture/SKILL.mdcurl -fsSL https://app.decimal.ai/s/kwakseongjae-omd-reference-capture.jsonThis gets SKILL.md only. The skill's 3 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.
선택된 reference brand의 라이브 사이트에서 디자인 evidence를 가져온다.
산출물은 assets/_reference/<id>/ 디렉토리에 모이며 그 자체로 프로젝트
디자인 권한이 되지 않는다.
이 스킬은 dev/디자인 reference 캡쳐용이다. brand IP를 사용자 product에 그대로 ship하는 도구가 아니다.
Evidence-only authority boundary
DESIGN.md, .omd/system/graph.json, manifest 또는
project token을 쓰거나 수정하지 않는다.evidence.json, tokens.json, live_overrides, fonts.json, screenshots,
logo는 관찰 evidence다. omd:apply/omd:harness가 자동 적용·동기화하거나
fallback으로 사용할 수 없다.graph.json에 명시적으로 admit하고, profile: portable-core manifest가 graph와 DESIGN.md의 exact SHA-256을 검증할 때만
그 graph path가 프로젝트 authority가 된다. capture 파일 자체는
admission 뒤에도 evidence로 남는다.experience, foundations,
typography-assets, components-states, layout-platforms,
content-locales, governance로 읽는다. anchor가 전혀 없는 reference만
legacy meaning-based fallback으로 읽고 숫자 section을 새 citation에
복사하지 않는다.Facts vs. Content 구분
저작권 표시 의무
assets/_reference/<id>/LICENSE-NOTE.md를 가장 먼저 작성.attribution.md에 source URL + 캡쳐 일자 + 추정 권리자 기록.사용자 product 생성 시 분리
robots.txt / TOS 우선
curl -sI <site>/robots.txt로 기본 정책 확인.scope 한정
이전 버전(v1.3.2)은 clone vs inspired 두 mode를 제공했으나 v1.3.3에서 폐기. 시각적 동일성은 brand creative work을 사용자 product에 reproduce해야 가능하고 그건 IP 영역. 단일 mode 흐름으로 통일 — brand 토큰·구조·voice는 가져오되, brand 자체 자산(mascot·로고·마케팅 사진)은 reference로만 보존하고 사용자 product에는 자체 자산 자리([YOUR LOGO] placeholder 등)를 둠. 결과물의 시각 polish는 무료 라이선스 자산 라이브러리(Open Peeps / Lucide / Heroicons 등 CC0/MIT/SIL OFL)로 채움 — 자세한 카탈로그는 skills/omd-harness/SKILL.md Step 4 master prompt rule 6 참조.
다음 모든 Phase는 단일 흐름. (구버전 Phase 0 - clone/inspired ask 제거됨.)
과거 clone|inspired 값이 .omd/init-context.json에 남아 있어도 이 스킬은
동작을 분기하지 않는다. 두 값 모두 동일한 evidence-only capture를 수행하며
logo/font/screenshot/token을 product에 자동 삽입하지 않는다. legacy mode를
Core graph admission 또는 사용자 권한으로 해석하지 않는다.
Phase 1: 입력 검증 — brand id 확정
Phase 2: 라이브 URL 수집 (homepage, logo, docs)
Phase 3: 디렉토리 + LICENSE-NOTE 사전 작성 (CRITICAL — 다운로드보다 먼저)
Phase 4: 토큰 캡쳐 (facts) — playwright computed styles
Phase 5: 시각 reference 캡쳐 (screenshot + 로고)
Phase 6: attribution.md 작성
Phase 7: 사용자 요약 + 다음 단계 안내
사용자 요청에서 brand id 추출:
reference-fingerprints.json의 items[].id 매칭:
.codex/data/reference-fingerprints.json.claude/data/reference-fingerprints.json.opencode/data/reference-fingerprints.jsonnode_modules/oh-my-design-cli/data/reference-fingerprints.jsondata/reference-fingerprints.json (개발 레포)id가 카탈로그에 없으면 종료 + "X는 reference 카탈로그에 없어요. omd:init으로 추가 가능합니다."
reference 자료 경로 <refdir> 는 reference DESIGN.md 위치 기준으로 resolve (먼저 존재하는 것 사용 — omd:init Phase 4.1과 동일한 카탈로그 resolution order):
.codex/data/references/<id>/DESIGN.md (Codex installer copy; 디렉토리에는 DESIGN.md만 보장).claude/data/references/<id>/DESIGN.md (Claude Code / Cursor installer copy; 디렉토리에는 DESIGN.md만 보장).opencode/data/references/<id>/DESIGN.md (OpenCode installer copy; 디렉토리에는 DESIGN.md만 보장)node_modules/oh-my-design-cli/web/references/<id>/DESIGN.md (로컬 npm 설치 직접 경로 — 디렉토리에 _promo.json/_research.md 포함)web/references/<id>/DESIGN.md (개발 레포)https://oh-my-design.kr/<id>/design.md 를 fetch (WebFetch 또는 curl -fsSL) — 1~5 로컬 경로가 전부 없을 때. 200이면 본문이 곧 reference DESIGN.md. 가져온 내용은 활성 채널의 첫 writable data dir (.codex/data, .claude/data, .opencode/data) 아래 references/<id>/DESIGN.md에 저장한다. 채널을 판별할 수 없으면 1→3 중 먼저 존재하고 쓸 수 있는 dir을 사용하고, 모두 없으면 활성 host 채널 dir을 생성한다.<refdir> = resolve된 DESIGN.md가 있는 디렉토리 (tier 6 fetch면 위 규칙으로 정한 활성 채널 cache dir). _promo.json/_research.md는 installer copy나 network cache에 없을 수 있으니, 없으면 (4)/(5)로 폴백하고 그래도 없으면 fingerprints 기반 추론으로 진행.
다음을 순서대로 시도:
homepage URL:
<refdir>/_promo.json의 logo_url이 brand site면 거기서 도메인 추출<refdir>/_research.md에서 Tier 1 source URL grepreference-fingerprints.json의 items[].category_raw 기반으로 추론 — 마지막 수단logo URL (_promo.json 우선):
_promo.json.logo_url → 그대로 사용apple-touch-icon / og:image / favicon-256 추출공식 DS docs URL (있으면):
<refdir>/DESIGN.md의 governance anchor와 인접한
hash-listed provenance artifact에서 찾는다. Core anchor가 전혀 없는
legacy reference만 footer 또는 의미 heading Verified Sources에서 찾는다.
찾지 못하면 URL을 추론하지 않는다.수집한 URL 후보를 사용자에게 보여주고 확인:
다음 자료를 가져오려고 합니다:
- homepage: https://www.banksalad.com
- logo: https://blog.banksalad.com/static/img/logo-banksalad.svg
- 공식 DS: (없음)
저작권 안내: 이 자료는 디자인 reference 용도로만 사용됩니다. 사용자 product에 그대로 ship하지 마세요.
진행하시겠어요? (yes/no/edit-urls)
다운로드 시작 전에 다음을 먼저 작성:
mkdir -p "assets/_reference/<id>/screenshots"
assets/_reference/<id>/LICENSE-NOTE.md:
# License Note — <id> Reference Capture
This directory contains design-reference materials captured from
**<id>**'s public website on **<ISO-date>** for the purpose of
informing UI design work in this project.
## What's here is for REFERENCE, not REDISTRIBUTION
- **Observed design values** (colors, fonts, spacing) — `tokens.json`.
These are evidence about the captured surface, not project tokens.
- **Logo file** — captured for visual recognition during development.
This is <brand>'s trademark. Do not use in your own product, your
own marketing, or any redistribution. Replace with your own brand
mark before shipping.
- **Screenshots** — captured for visual reference during development.
Do not embed in your product, your blog, or social posts beyond
fair-use commentary.
## What you should NOT do
- Embed `logo.*` or `screenshots/*.png` in your own product UI as if
they belong to you.
- Copy or adapt `tokens.json` values into your product before an explicit
Core graph-authoring checkpoint admits the decision with provenance.
- Copy any marketing text from screenshots into your own product
verbatim — voice/tone is fact, but specific phrasing is creative work.
## What you SHOULD do
- Review the **design language** (token relationships, component
patterns, voice register) as evidence during a Core graph-authoring
checkpoint; only admitted graph decisions become project authority.
- Reference the screenshots during design reviews to align with the
visual target.
- Replace all brand-identifying assets with your own before any
external sharing.
## Attribution
See `attribution.md` in this directory for source URLs and capture
timestamps.
---
This note is generated by `omd:reference-capture`. Do not edit by hand
— rerun the skill to refresh.
캡쳐된 brand가 web font(Pretendard / BM JUA / Inter / Noto Sans KR 등 시스템 기본이 아닌 폰트)를 쓰면, 토큰만 잡아도 생성기가 폰트를 로드 안 하면 결과가 시스템 fallback으로 바뀐다 (가장 흔한 증상: macOS 시스템 폰트가 둥글둥글하게 렌더되는 mismatch).
assets/_reference/<id>/fonts.json:
{
"captured_at": "<ISO-8601>",
"fonts": [
{
"family": "Pretendard",
"license": "SIL OFL 1.1 (open-source, free for commercial use)",
"cdn_url": "https://cdn.jsdelivr.net/gh/orioncactus/pretendard/dist/web/static/pretendard.css",
"html_link": "<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/gh/orioncactus/pretendard/dist/web/static/pretendard.css\" />",
"live_observed": true,
"role": "body + heading"
},
{
"family": "Apple SD Gothic Neo",
"license": "system (macOS/iOS)",
"cdn_url": null,
"html_link": null,
"live_observed": false,
"role": "system fallback"
}
]
}
body / h1-h3 / 주요 buttons의 font-family 첫 항목 추출 (fallback chain 무시, 첫 토큰만)| 추출된 family | license | CDN URL |
|---|---|---|
Pretendard | SIL OFL 1.1 | https://cdn.jsdelivr.net/gh/orioncactus/pretendard/dist/web/static/pretendard.css |
Pretendard Variable | SIL OFL 1.1 | https://cdn.jsdelivr.net/gh/orioncactus/pretendard/dist/web/variable/pretendardvariable.css |
Noto Sans KR | SIL OFL 1.1 | https://fonts.googleapis.com/css2?family=Noto+Sans+KR:wght@300;400;500;700;900&display=swap |
Inter | SIL OFL 1.1 | https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap |
Wanted Sans | SIL OFL 1.1 | https://cdn.jsdelivr.net/gh/wanteddev/wanted-sans@latest/packages/wanted-sans/fonts/webfonts/static/complete/WantedSans.css |
BM JUA (배민 주아체) | 배달의민족 폰트 라이선스 (개인·기업 무료, 재배포·판매 금지) | (CDN 미공식 — html_link: null 처리, 사용자에게 "BM JUA는 라이브에서 관측 안 됨; 로컬 fallback" 알림) |
Apple SD Gothic Neo, Malgun Gothic, system-ui, -apple-system | system | html_link: null |
registry에 없는 family는 html_link: null + notes에 "CDN 미확인, 수동 로드 필요" 기록.
reference DESIGN.md가 "BM JUA를 landing/promo accent로"라고 적어뒀더라도,
라이브 inspect에서 실제 BM JUA 사용이 관측 안 되면
live_observed: false로 기록한다. 이는 admission 검토용 반증 evidence일 뿐
reference나 project authority를 자동 교체하지 않는다. 후속 graph-authoring
checkpoint가 채택/기각을 결정한다.
폐기된 clone metadata가 남아 있어도 capture만으로 HTML <head>에 font URL을
자동 삽입하지 않는다. source/license를 검토해 Core graph의
typography_assets에 명시적으로 admitted된 font만 후속 writer가 로드한다.
라이브 캡쳐의 정본은 버전된 ReferenceEvidenceBundle이다. 에이전트가 매번 다른 evaluate 스니펫을 만들지 않는다.
개발 저장소 안에서는:
npm --prefix web run capture:reference -- <id-or-url> \
--max-routes 3 \
--out "$PWD/assets/_reference/<id>/evidence.json"
npm으로 설치된 스킬에서는:
# 현재 읽고 있는 SKILL.md와 같은 디렉토리의 bundled collector를 사용한다.
# 예: .agents/skills/omd-reference-capture/scripts/capture-reference-evidence.mjs
SKILL_DIR="<directory-containing-this-SKILL.md>"
COLLECTOR="$SKILL_DIR/scripts/capture-reference-evidence.mjs"
node "$COLLECTOR" "<homepage-url>" --id "<id>" \
--max-routes 3 \
--out "$PWD/assets/_reference/<id>/evidence.json"
설치된 collector는 Node 18에서 실행되는 단일 ESM 파일이며 playwright-core까지 내부에 포함한다. 따라서 프로젝트 node_modules, npm cache, TypeScript runtime에 의존하지 않고 system Chrome을 직접 사용한다. Chrome 경로가 표준 위치가 아니면 OMD_CHROME_PATH만 지정한다. MCP server, MCP SDK, 별도 API key는 필요 없다. 설치형 collector에는 항상 Phase 2에서 확정한 homepage URL과 --id를 함께 전달한다. 이 방식은 npm package가 일회성 npx cache에서 사라진 뒤에도 동작한다.
현재 SKILL.md의 위치를 직접 알 수 없는 shell 환경에서는 아래 순서에서 첫 실행 파일을 찾는다:
for candidate in \
"$PWD/.claude/skills/omd-reference-capture/scripts/capture-reference-evidence.mjs" \
"$PWD/.agents/skills/omd-reference-capture/scripts/capture-reference-evidence.mjs" \
"$PWD/.opencode/skills/omd-reference-capture/scripts/capture-reference-evidence.mjs" \
"$HOME/.claude/skills/omd-reference-capture/scripts/capture-reference-evidence.mjs" \
"$HOME/.agents/skills/omd-reference-capture/scripts/capture-reference-evidence.mjs" \
"$HOME/.config/opencode/skills/omd-reference-capture/scripts/capture-reference-evidence.mjs"
do
if [ -f "$candidate" ]; then COLLECTOR="$candidate"; break; fi
done
test -n "${COLLECTOR:-}" || { echo "capture_blocked: bundled collector not found"; exit 1; }
필수 산출물은 evidence.json 하나이며 schema version, surface URL, selector, computed style, FontFaceSet, @font-face source, interaction state, component fingerprint와 coverage를 포함한다. hover/focus/pressed는 state-specific computed snapshot으로 남고, 안전하게 열 수 있는 menu/dialog/form-error/tab/toast는 interactions[]의 trigger/target/state로 남는다. tokens.json, .live-inspect-proof.json, structure.json은 이 bundle의 하위 projection으로만 만든다.
shutil.which("browser-harness") 또는 command -v browser-harness로 detect. 있고 browser-harness --doctor 가 [ok ] chrome running 띄우면 fast-path:
--remote-debugging-port=9222 또는 chrome://inspect#remote-debugging 활성.BU_CDP_URL="http://localhost:9222" browser-harness <<'PY'
new_tab("https://<brand-domain>")
wait_for_load()
import time; time.sleep(1.5) # SPA hydration
result = js("""
(() => {
const body = getComputedStyle(document.body);
const ctas = [...document.querySelectorAll('button, a[role="button"], [class*="button"]')]
.filter(el => {
const r = el.getBoundingClientRect();
return r.height >= 32 && r.height <= 80 && r.top < 1200;
})
.slice(0, 8).map(el => {
const cs = getComputedStyle(el);
return { bg: cs.backgroundColor, color: cs.color, radius: cs.borderRadius,
padding: cs.padding, fontWeight: cs.fontWeight, h: Math.round(el.getBoundingClientRect().height) };
});
return JSON.stringify({
bodyFont: body.fontFamily.split(',')[0].replace(/['"]/g,'').trim(),
ctas, scrollH: document.documentElement.scrollHeight
});
})()
""")
print(result)
capture_screenshot(path="<assets_dir>/screenshots/hero-desktop.png", full=False)
PY
evidence.json의 surface/state evidence와 동일한 필드로 reconcile한다. screenshot은 보조 자료이며 토큰 근거를 대신하지 않는다.packaged collector가 없고 browser-harness만 있으면 위 fast-path를 사용한다. 둘 다 없으면 live 검증을 했다고 주장하지 말고 capture_blocked를 기록한다. Playwright MCP로 fallback하지 않는다.
if [ -f "web/scripts/capture-reference-evidence.ts" ]; then
echo "MODE=local-collector"
elif [ -n "${COLLECTOR:-}" ] && [ -f "$COLLECTOR" ]; then
echo "MODE=packaged-collector"
elif command -v browser-harness >/dev/null && browser-harness --doctor 2>&1 | grep -q "ok.*chrome running"; then
echo "MODE=harness-supplement"
else
echo "MODE=capture-blocked"
fi
skill 진입 시 사용자에게 한 줄로 알림:
"MCP 없이 evidence collector로 N개 surface를 캡쳐합니다." 또는 "collector가 없어 live capture가 막혔습니다."
이 안내는 informational only, 사용자 action 요구 X.
Proof gate (위반 = regression): 이 Phase는 실제 navigation + computed style 추출이 일어났음을 evidence.json으로 증명해야 한다. 사람이 쓴 "라이브 inspect 했다" 문장은 증거가 아니다.
.live-inspect-proof.json 작성 (REQUIRED, 없으면 candidate evidence 폐기)collector의 evidence.json에서 raw computed style 5개 이상을 projection해 assets/_reference/<id>/.live-inspect-proof.json에 저장한다:
{
"navigated_at": "<ISO-8601 with seconds>",
"source_url": "https://www.banksalad.com",
"viewport": "1280x800",
"tool_used": "reference_evidence_collector_v1",
"raw_samples": [
{ "element_selector": "body", "font-family": "Pretendard, ...", "color": "rgb(0, 0, 0)", "background": "rgba(0, 0, 0, 0)" },
{ "element_selector": "header nav a:first-child", "font-size": "14px", "font-weight": "500" },
{ "element_selector": "button:contains('앱 다운로드')", "background-color": "rgb(19, 189, 126)", "border-radius": "41px" },
{ "element_selector": ".hero-section section", "background-color": "rgba(0, 0, 0, 0)" },
{ "element_selector": "footer", "background-color": "rgb(255, 255, 255)" }
],
"evidence_schema_version": 1,
"evidence_bundle": "./evidence.json"
}
후속 graph-authoring 단계는 evidence.json의 schemaVersion === 1,
surfaces.length >= 1, raw element ≥5를 먼저 확인한다. 이 검사는 evidence
admissibility만 판단한다. 통과 자체가 project token admission은 아니다. legacy
proof 파일만 있고 bundle이 없으면 capture 값을 승격하지 않는다.
tokens.json#live_overrides라는 legacy key가 남아 있어도 각 값은 bundle의
surfaceId + selector + style property로 역추적 가능해야 한다. 이 key 이름은
우선순위나 override 권한을 부여하지 않는다. provenance가 없으면 candidate를
삭제하고, provenance가 있어도 graph checkpoint 전에는 evidence-only다.
evidence.json의 aggregate와 representative raw element를 reconcile한다. 결과는 assets/_reference/<id>/tokens.json:
{
"captured_at": "<ISO-8601>",
"source_url": "https://www.banksalad.com",
"tokens": {
"colors": {
"background": "#ffffff",
"text_primary": "#2b2b2b",
"text_body": "#434444",
"interaction_primary": "#04c584",
"interaction_hover": "#10df99"
},
"typography": {
"body_font_family": "Pretendard, ...",
"body_font_weight": "400",
"heading_font_weight": "700",
"base_size_px": 16
},
"shape": {
"default_radius_px": 2,
"card_radius_px": 2,
"pill_radius_px": 16
},
"shadow": {
"default": "0 2px 5px rgba(0,0,0,0.12)"
}
},
"samples": [
{ "element": "primary button (hero CTA)", "raw_computed_style": { /* getComputedStyle slice */ } },
{ "element": "card", "raw_computed_style": { /* ... */ } }
]
}
최소 샘플은 body / heading / hero CTA / card 또는 form / footer이며, 각 샘플은 bundle selector를 보존한다. 캡쳐하려는 brand가 카탈로그에 없으면 URL을 직접 collector에 전달한다. 카탈로그에 신규 brand를 추가하는 워크플로우(omd-add-reference)는 dev 레포에만 존재한다.
Tokens는 색·radius·font 같은 원자 값이고, structure는 페이지가 어떻게 짜였는지의 관측 가능한 facts다. 토큰만 보면 같은 brand도 surface마다 다른 결과물이 나오는 mismatch가 생긴다 (예: app surface의 2px-radius advisor 톤 vs marketing landing의 41px-pill 일러스트 톤). structure.json은 후속 생성기가 실제 라이브 사이트의 composition idiom을 따라가도록 한다.
assets/_reference/<id>/structure.json:
{
"captured_at": "<ISO-8601>",
"source_url": "https://www.banksalad.com",
"viewport": "1280x800",
"hero": {
"type": "illustration | photo | video | data-card | text-only | mixed",
"has_carousel": false,
"carousel_dot_count": 0,
"primary_visual_position": "right | left | center | full-width | background",
"background_ornament": "none | gradient-blob | geometric | radial-tint | photo-bleed",
"approx_height_vh": 75
},
"cta": {
"dominant_shape": "pill | rectangular | ghost-only | text-link",
"primary_radius_px_observed": 41,
"secondary_style": "ghost-outline | underline | none",
"vertical_stack": true
},
"nav": {
"structure": "horizontal-categories | mega-menu | hamburger-mobile | single-row-utility | top-tabs",
"approx_item_count": 11,
"has_search": false,
"has_locale_switch": false
},
"page": {
"viewport_heights": 3.9,
"section_count_observed": 8,
"predominant_alignment": "center | left | grid-asymmetric",
"image_density": "high | medium | low | none"
},
"section_rhythm": [
"hero",
"social-proof",
"feature-grid",
"testimonial",
"footer"
],
"notes": [
"라이브 hero는 carousel — 3-5 슬라이드로 product 카테고리 순환",
"section_rhythm은 위에서 아래로 관측 순서"
]
}
playwright로 homepage navigate 후 다음 JS 실행 (facts derivation only):
() => {
// hero 탐색 — 보통 첫 800px 이내 viewport에서 가장 큰 visual element
const heroCandidates = [...document.querySelectorAll('section, header + div, [class*="hero" i], [class*="Hero"]')]
.filter(el => {
const r = el.getBoundingClientRect();
return r.top < 200 && r.height > 300 && r.width > 800;
});
const heroEl = heroCandidates[0];
// type 판정
const heroImgCount = heroEl ? heroEl.querySelectorAll('img, svg, picture').length : 0;
const heroVideoCount = heroEl ? heroEl.querySelectorAll('video').length : 0;
const heroHasCarouselDots = heroEl ? !!heroEl.querySelector('[class*="dot" i], [class*="pagination" i], [class*="indicator" i]') : false;
// CTA 탐색 — viewport 위 절반의 button/cta
const ctas = [...document.querySelectorAll('button, a[role="button"], [class*="button" i] a')]
.filter(el => el.getBoundingClientRect().top < 800)
.map(el => {
const cs = getComputedStyle(el);
return { radius: cs.borderRadius, bg: cs.backgroundColor };
});
const dominantRadii = ctas.map(c => parseInt(c.radius)).filter(n => !isNaN(n));
const maxRadius = Math.max(...dominantRadii, 0);
// nav
const navEl = document.querySelector('header nav, [role="navigation"]');
const navItemCount = navEl ? navEl.querySelectorAll('a, button').length : 0;
return {
hero_img_count: heroImgCount,
hero_video_count: heroVideoCount,
hero_has_carousel_dots: heroHasCarouselDots,
cta_max_radius_observed: maxRadius,
cta_sample_count: ctas.length,
nav_item_count: navItemCount,
total_scroll_height_px: document.documentElement.scrollHeight,
viewport_height_px: window.innerHeight,
};
}
위 raw 측정값을 structure.json schema의 enum 값으로 매핑 (예: hero_img_count >= 1 && hero_video_count === 0 → hero.type: "illustration", cta_max_radius_observed >= 40 → cta.dominant_shape: "pill").
structure.json에는 텍스트·이미지·copy·brand statement 일체 X. 오로지 structural facts (개수, radius, viewport-relative position). 저작권 영역 아님.
notes 필드의 자유 텍스트는 본인 관찰만 — 사이트의 마케팅 카피 인용 금지.
curl -sSL -o "assets/_reference/<id>/logo.<ext>" "<logo_url>"
evidence collector와 같은 로컬 Playwright context 또는 browser-harness로 homepage 위 fold만 캡쳐(MCP 불필요):
assets/_reference/<id>/screenshots/hero-desktop.png모바일 viewport (375×812)도 1장: screenshots/hero-mobile.png.
대용량 페이지 전체 screenshot은 default skip. 사용자가 명시적으로 요청 시만.
.mp4, .webm)이런 자원은 attribution.md에 URL만 기록.
assets/_reference/<id>/attribution.md:
# Attribution — <id> Reference Capture
Captured: **<ISO-8601>** via `omd:reference-capture`
## Sources
| File | Source URL | Captured | Notes |
|---|---|---|---|
| `tokens.json` | `<homepage_url>` | <date> | Computed styles via playwright, factual analysis |
| `structure.json` | `<homepage_url>` | <date> | Observable composition facts (hero type, CTA shape, nav structure, page rhythm) |
| `logo.<ext>` | `<logo_url>` | <date> | Brand trademark — do not redistribute |
| `screenshots/hero-desktop.png` | `<homepage_url>` (1280×800) | <date> | Reference for design alignment |
| `screenshots/hero-mobile.png` | `<homepage_url>` (375×812) | <date> | Reference for design alignment |
## Skipped (URL recorded for manual review)
- (none) OR <list of URLs skipped due to robots.txt / size / type>
## Rights
These materials are owned by <id> and its respective rights holders.
Captured under fair-use principles for the purpose of design analysis
and development reference. See `LICENSE-NOTE.md` for usage boundaries.
## Refresh
Rerun `omd:reference-capture <id>` to recapture (overwrites this
directory). Captured tokens reflect the live site at capture time and
may drift as the brand evolves.
CLONE-MODE.md, replace-checklist.md, product scaffold, font <link>,
reference logo 사용 같은 과거 clone 산출은 생성하지 않는다. 이 스킬의 write
scope는 assets/_reference/<id>/ evidence와 attribution/license 기록뿐이다.
프로젝트 적용은 별도 Core graph-authoring checkpoint 이후의 writer가 소유한다.
prose로:
✓ <id> reference captured into assets/_reference/<id>/
- tokens.json — <N> design tokens extracted
- logo.<ext> — <size>
- screenshots/ — <N> images
⚠ 저작권 안내: 이 자료는 디자인 reference 용도. 사용자 product에 brand 로고/스크린샷을 직접 embed하지 마세요. 자세한 사용 경계는 LICENSE-NOTE.md 참고.
다음 단계:
- Core graph-authoring checkpoint에서 채택할 evidence를 검토
- admitted graph가 valid하게 hash-bound된 뒤 omd:apply로 컴포넌트 작업
- omd:harness에서 이 자료를 evidence/reference로 검토
- 토큰 시각 확인: open assets/_reference/<id>/screenshots/hero-desktop.png
이 스킬이 만든 assets/_reference/<id>/는 후속 skill의 evidence input이다:
UI 생성 시 logo 사용 규칙 (omd:apply / omd:harness 둘 다 적용):
[YOUR LOGO] 자리만 잡고 사용자에게 자체 로고 요청tokens.json#live_overrides, fonts.json, screenshot을 graph admission 없이
omd:apply/omd:harness의 prescriptive authority나 fallback으로 사용assets/_reference/<id>/
├── LICENSE-NOTE.md (사전 작성)
├── attribution.md (마지막 작성)
├── tokens.json (facts — atomic values)
├── structure.json (facts — composition cues for surface idiom)
├── logo.<svg|png|ico> (brand mark — reference only)
└── screenshots/
├── hero-desktop.png (1280×800, above fold)
└── hero-mobile.png (375×812, above fold)
assets/_reference/ 디렉토리는 사용자 자체 자산이 아닌 reference 자료이므로, 사용자 git repo의 정책에 따라 .gitignore 권장:
# Reference capture (regeneratable via omd:reference-capture)
assets/_reference/
이 스킬은 .gitignore를 자동 수정하지 않음 — 사용자에게 알림만.
Skills that do similar work, with their headline benchmark lift.
From the same source and nearby in the catalogue.