Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Business card scanner + Google Contacts manager. Auto-detects business card images, extracts contact info via OCR (imageModel), confirms with user, saves to Google Contacts with configurable name format and card photo. Trigger: image that looks like a business card, or keywords "명함", "bizcard", "연락처 저장". Settings: /bizcard config
SkillScore82/100
Test results 82
3/3 safety checks passed
23%82%of test cases passed +59 pts
Passed 18 of 22 test cases with the skill, and 5 of 22 without it.
14 fixed4 passed either way3 still failing1 broke
One test task사용자가 '최근 명함' 명령을 통해 저장 이력을 조회할 때 bizcard-log.jsonl 파일에서 최대 몇 건의 최근 내역을 표시해야 하는지 수치를 명시해줘.
Adds every file the skill has, pinned so you can update it. No account needed.
pip install "decimalai>=0.15"
decimalai skills add leoyeai-bizcard
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("leoyeai-bizcard")
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 leoyeai-bizcardThe 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/leoyeai-bizcard/SKILL.md https://app.decimal.ai/s/leoyeai-bizcard/SKILL.md.claude/skills/leoyeai-bizcard/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 leoyeai-bizcardimport 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("leoyeai-bizcard")
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/leoyeai-bizcard/SKILL.mdimport asyncio
from pathlib import Path
from claude_agent_sdk import ClaudeAgentOptions, query
skill = Path(".agents/skills/leoyeai-bizcard/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 leoyeai-bizcardfrom 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("leoyeai-bizcard")
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/leoyeai-bizcard/SKILL.mdfrom pathlib import Path
from agents import Agent, Runner
skill = Path(".agents/skills/leoyeai-bizcard/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 leoyeai-bizcardimport 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("leoyeai-bizcard")
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/leoyeai-bizcard/SKILL.mdfrom pathlib import Path
from openai import OpenAI
client = OpenAI()
skill = Path(".agents/skills/leoyeai-bizcard/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 leoyeai-bizcardimport 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("leoyeai-bizcard")
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/leoyeai-bizcard/SKILL.mdfrom pathlib import Path
import anthropic
client = anthropic.Anthropic()
skill = Path(".agents/skills/leoyeai-bizcard/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 leoyeai-bizcardfrom 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("leoyeai-bizcard")
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/leoyeai-bizcard/SKILL.mdfrom pathlib import Path
from google import genai
from google.genai import types
client = genai.Client()
skill = Path(".agents/skills/leoyeai-bizcard/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 leoyeai-bizcardfrom 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("leoyeai-bizcard")
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/leoyeai-bizcard/SKILL.mdfrom pathlib import Path
from langchain.agents import create_agent
skill = Path(".agents/skills/leoyeai-bizcard/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 leoyeai-bizcardimport 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("leoyeai-bizcard")
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/leoyeai-bizcard/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 leoyeai-bizcardfrom 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("leoyeai-bizcard")
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/leoyeai-bizcard/SKILL.mdfrom pathlib import Path
from google.adk.agents import LlmAgent
skill = Path(".agents/skills/leoyeai-bizcard/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 leoyeai-bizcardfrom 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("leoyeai-bizcard")
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/leoyeai-bizcard/SKILL.mdfrom pathlib import Path
from pydantic_ai import Agent
skill = Path(".agents/skills/leoyeai-bizcard/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 leoyeai-bizcardimport 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("leoyeai-bizcard")
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/leoyeai-bizcard/SKILL.mdfrom pathlib import Path
from deepagents import create_deep_agent
skill = Path(".agents/skills/leoyeai-bizcard/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 leoyeai-bizcardimport 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("leoyeai-bizcard")
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/leoyeai-bizcard/SKILL.mdimport asyncio
from pathlib import Path
from agent_framework import Agent
skill = Path(".agents/skills/leoyeai-bizcard/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 leoyeai-bizcardfrom 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("leoyeai-bizcard")
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/leoyeai-bizcard/SKILL.mdfrom pathlib import Path
from strands import Agent
skill = Path(".agents/skills/leoyeai-bizcard/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 leoyeai-bizcardNo Python or account. Cursor, Copilot, Cline and Warp read this folder by themselves.
curl -fsSL --create-dirs -o .agents/skills/leoyeai-bizcard/SKILL.md https://app.decimal.ai/s/leoyeai-bizcard/SKILL.md.agents/skills/leoyeai-bizcard/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/leoyeai-bizcard/SKILL.md · /s/leoyeai-bizcard.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/leoyeai-bizcard/SKILL.mdcurl -fsSL https://app.decimal.ai/s/leoyeai-bizcard.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.
명함 이미지를 받으면 자동 감지 → 전처리 → OCR → 사용자 확인 → Google Contacts 저장까지 처리한다.
이미지 수신 → 명함 자동 감지
→ Gemini Flash OCR → 필드 파싱 → Name 포맷 적용
→ 사용자 확인 → 중복 감지 → Nano Banana Pro 보정 → Google Contacts 저장 + 사진 첨부
메시지에 다음 키워드가 포함되면 즉시 명함 처리 모드 진입:
imageModel에 다음을 요청:
이 이미지가 명함(business card)인지 판단해.
명함이면 YES, 아니면 NO만 답해.
명함 = 사람의 이름, 회사명, 연락처 정보가 인쇄된 카드 형태.
음식, 풍경, 스크린샷, 메모, 영수증 등은 NO.
imageModel에 요청:
이 명함 이미지의 품질을 평가해:
- 텍스트가 선명하게 읽히는가? (CLEAR / BLURRY)
- 기울어져 있는가? (STRAIGHT / TILTED)
- 전체적으로 전처리가 필요한가? (NEEDS_PROCESSING / SKIP)
JSON으로 답해: {"clarity": "...", "tilt": "...", "preprocessing": "..."}
# 요청별 고유 디렉터리 생성
BIZCARD_TMP=$(mktemp -d /tmp/bizcard-XXXXXXXX)
# 1. Deskew (기울기 보정)
magick "$BIZCARD_TMP/raw.jpg" -deskew 40% "$BIZCARD_TMP/deskew.jpg"
# 2. 콘트라스트 개선 + 선명화
magick "$BIZCARD_TMP/deskew.jpg" -normalize -sharpen 0x1 "$BIZCARD_TMP/enhanced.jpg"
전처리된 이미지로 OCR 진행.
원본 그대로 OCR 진행. 전처리 건너뜀.
imageModel(Gemini Flash)에 다음 JSON 구조로 추출 요청:
이 명함 이미지에서 연락처 정보를 추출해. 다음 JSON 형식으로 답해.
읽을 수 없는 필드는 null로 남겨.
{
"name_ko": "한글 이름",
"name_en": "English name",
"company_ko": "한글 회사명",
"company_en": "English company name",
"title_ko": "한글 직함",
"title_en": "English title",
"department": "부서",
"mobile": ["개인 휴대폰 배열"],
"email": ["이메일 배열"],
"locations": [
{
"label": "본사",
"phone": ["063-000-0000"],
"fax": ["063-000-0001"],
"address_ko": "서울특별시 강남구 테헤란로 123",
"address_en": null
},
{
"label": "영업부",
"phone": ["02-000-0000"],
"fax": ["02-000-0001"],
"address_ko": "경기도 성남시 분당구 판교로 456",
"address_en": null
}
],
"website": ["웹사이트 배열"],
"notes": "기타 (SNS, 자격증 등)",
"language": "명함의 주 언어 (ko / en / ja / zh / other)"
}
한국 번호를 국제 형식으로 변환:
| 원본 | 변환 |
|---|---|
010-1234-5678 | +82-10-1234-5678 |
02-1234-5678 | +82-2-1234-5678 |
031-123-4567 | +82-31-123-4567 |
규칙:
0으로 시작하는 한국 번호 → 앞의 0을 +82-로 교체+로 시작하는 해외 번호 → 원본 유지- 또는 공백은 -로 통일koreanStyleName=true, 기본값)한국에서는 성과 이름을 분리하지 않는다. 비즈니스에서 "홍길동 대표", "김갑돌 과장"처럼 풀네임이 하나의 단위다.
People API 저장 시:
familyName → 비움 (빈 문자열)givenName → 풀네임 (예: 홍길동)unstructuredName → config 포맷 적용된 이름 (예: #홍길동 과장)koreanStyleName=false이거나, OCR language가 ko가 아닌 경우)외국인은 first name / last name 분리가 기본:
givenName → first name (예: John)familyName → last name (예: Smith)unstructuredName → config 포맷 적용koreanReading=true)외국어 명함일 때, 이름과 회사명을 한국어로 독음(transliteration)하여 기록:
| 원본 | 독음 |
|---|---|
| John Smith | 존 스미스 |
| Google LLC | 구글 |
| Toyota Motor | 토요타 모터 |
| François Dupont | 프랑수아 뒤퐁 |
적용 방법:
phoneticName 필드에 저장 (검색 가능!)biographies에 기록People API 저장:
names[].phoneticGivenName = "퀘 훙 웨인" ← 풀네임 독음을 하나로
names[].phoneticFamilyName = "" ← 비움
규칙: 독음은 성/이름 분리하지 않는다. 풀네임 독음을 phoneticGivenName에 통째로 넣는다.
예시: Kweh Hoong Wayne → phoneticGivenName=퀘 훙 웨인
예시: François Dupont → phoneticGivenName=프랑수아 뒤퐁
효과: Google Contacts에서 "퀘 훙 웨인"으로 검색해도 해당 연락처를 찾을 수 있다.
koreanReading=false이면 독음 생략.
config 설정을 읽어 unstructuredName (displayName)을 생성한다.
1. 기본 이름: "홍길동"
2. hashtag=true → "#홍길동"
3. appendTitle=true → "#홍길동 과장"
4. appendCompany=true → "#홍길동 과장 (ABC주식회사)"
| hashtag | appendTitle | appendCompany | 결과 |
|---|---|---|---|
| off | off | off | 홍길동 |
| on | off | off | #홍길동 |
| off | on | off | 홍길동 과장 |
| off | off | on | 홍길동 (ABC주식회사) |
| on | on | on | #홍길동 과장 (ABC주식회사) |
names[].unstructuredName → 포맷된 displayName (예: #홍길동 과장)names[].givenName → 풀네임 (예: 홍길동)names[].familyName → 비움names[].unstructuredName → 포맷된 displayName (예: #John Smith, VP)names[].givenName → first name (예: John)names[].familyName → last name (예: Smith)OCR + 포맷 결과를 아래 템플릿 그대로 출력한다. 포맷 변경 금지.
한국 명함 템플릿 (이 포맷 그대로 사용):
📇 명함 인식 결과
👤 #홍길동 과장 (ABC주식회사)
🏢 ABC주식회사 / 영업부
💼 과장
📱 +82-10-1234-5678
📧 [email protected]
🌐 www.example.co.kr
📍 본사:
📞 +82-63-450-3500 / 📠 +82-63-450-3517
서울특별시 강남구 테헤란로 123
📍 영업부:
📞 +82-2-597-8071~3 / 📠 +82-2-586-4388
경기도 성남시 분당구 판교로 456
🖼️ 명함 사진 → 연락처 프로필 사진으로 저장
1. 저장
2. 수정
3. 취소
거점이 1개뿐이면 📍 label 없이 한 줄로:
📞 +82-2-9876-5432
📠 +82-2-9876-5433
📍 서울시 강남구 테헤란로 123
외국 명함 템플릿 (koreanReading=true, 이 포맷 그대로 사용):
📇 명함 인식 결과
👤 John Smith (존 스미스)
🏢 Google LLC (구글)
💼 VP of Engineering
📱 +1-555-123-4567
📧 [email protected]
🖼️ 명함 사진 → 연락처 프로필 사진으로 저장
1. 저장
2. 수정
3. 취소
규칙:
#이름 직함 (회사명)👤 #Kweh Hoong Wayne + 💼 Technical Service Manager👤 #Kweh Hoong Wayne TECHNICAL SERVICE MANAGER1. 저장 / 2. 수정 / 3. 취소 번호 선택. "저장할까?" 같은 서술형 금지.| 입력 | 동작 |
|---|---|
1 | 저장 진행 |
2 | "뭘 수정할까?" 물은 뒤, 수정 후 다시 1/2/3 선택 |
3 | 취소 |
번호 외 텍스트 입력도 허용: "ㅇㅇ", "ok" → 1번 처리, "취소" → 3번 처리. 하지만 번호 입력을 우선 유도한다.
cardAsPhoto=true이면 무조건 실행한다. 모든 명함을 일관되게 보정한다.
말로만 "적용했어" 하지 말고 실제로 exec 도구로 명령어를 실행해라.
BIZCARD_TMP=$(mktemp -d /tmp/bizcard-XXXXXXXX)
cp /path/to/원본명함이미지.jpg "$BIZCARD_TMP/raw.jpg"
원본 이미지를 Nano Banana Pro에 보내서 배경 제거 + 정면 보정 + 1:1 정사각형으로 변환.
python3 <<'PYEOF'
import urllib.request, json, base64, os
with open(os.environ["BIZCARD_TMP"] + "/raw.jpg", "rb") as f:
img_b64 = base64.b64encode(f.read()).decode()
api_key = os.environ.get("NANO_BANANA_API_KEY", "")
if not api_key:
print("ERROR: NANO_BANANA_API_KEY not set")
exit(1)
url = f"https://generativelanguage.googleapis.com/v1beta/models/nano-banana-pro-preview:generateContent?key={api_key}"
payload = {
"contents": [{
"parts": [
{"inlineData": {"mimeType": "image/jpeg", "data": img_b64}},
{"text": "이 사진에서 명함 카드만 잘라내서 정면으로 보정한 이미지를 생성해줘. 배경 완전 제거, 명함만 남기고, 흰색 여백을 추가해서 1:1 정사각형 비율로 만들어줘. 명함 내용은 전부 보존해."}
]
}],
"generationConfig": {
"responseModalities": ["TEXT", "IMAGE"]
}
}
data = json.dumps(payload).encode()
req = urllib.request.Request(url, data=data, method="POST")
req.add_header("Content-Type", "application/json")
resp = urllib.request.urlopen(req, timeout=60)
result = json.loads(resp.read())
for part in result["candidates"][0]["content"]["parts"]:
if "inlineData" in part:
img_data = base64.b64decode(part["inlineData"]["data"])
with open(os.environ["BIZCARD_TMP"] + "/clean.jpg", "wb") as f:
f.write(img_data)
print(f"OK: clean.jpg ({len(img_data)} bytes)")
elif "text" in part:
print(f"Text: {part['text'][:100]}")
PYEOF
결과: $BIZCARD_TMP/clean.jpg — 1024x1024, 배경 제거, 정면 보정, 1:1 정사각형.
clean.jpg를 업로드한다. raw.jpg가 아니다.
python3 <<'PYEOF'
import urllib.request, os, json, base64
with open(os.environ["BIZCARD_TMP"] + "/clean.jpg", "rb") as f:
photo_bytes = base64.b64encode(f.read()).decode()
data = json.dumps({"photoBytes": photo_bytes}).encode()
resource_name = os.environ["RESOURCE_NAME"]
req = urllib.request.Request(
f"https://gateway.maton.ai/google-contacts/v1/{resource_name}:updateContactPhoto",
data=data, method="PATCH"
)
req.add_header("Authorization", f"Bearer {os.environ['MATON_API_KEY']}")
req.add_header("Content-Type", "application/json")
result = json.load(urllib.request.urlopen(req))
print("Photo uploaded:", result.get("person", {}).get("resourceName", "unknown"))
PYEOF
rm -rf "$BIZCARD_TMP"
절대 금지:
-deskew나 -distort Perspective 사용 (Nano Banana Pro가 전부 처리)Nano Banana Pro API 실패 시: 원본(raw.jpg)을 그대로 업로드하고 "이미지 보정 실패: 원본으로 저장" 알림.
저장 전 반드시 이름 + 휴대폰 번호 2개 필드로 기존 연락처를 자동 검색한다. 사용자가 요청하지 않아도 항상 실행.
# 1단계: 이름으로 검색
GET /google-contacts/v1/people:searchContacts?query=홍길동&readMask=names,phoneNumbers,emailAddresses
# 2단계: 휴대폰 번호로 검색
GET /google-contacts/v1/people:searchContacts?query=%2B82-10-1234-5678&readMask=names,phoneNumbers,emailAddresses
번호 검색 시 정규화된 +82 포맷과 원본(010) 포맷 모두로 검색하여 누락을 방지한다.
| 이름 일치 | 번호 일치 | 판정 | 동작 |
|---|---|---|---|
| ✅ | ✅ | 확정 중복 | 사용자에게 옵션 제시 |
| ✅ | ❌ | 동명이인 가능 | 사용자에게 알림 후 진행 |
| ❌ | ✅ | 번호 재사용 가능 | 사용자에게 알림 후 진행 |
| ❌ | ❌ | 신규 | 바로 저장 |
아래 템플릿을 그대로 출력한다. 포맷 변경 금지.
⚠️ 중복 연락처 발견
기존:
👤 홍길동
📱 +82-10-1234-5678
📧 [email protected]
이번 명함:
👤 #홍길동 과장 (ABC주식회사)
📱 +82-10-1234-5678
📧 [email protected]
1. 새로 저장
2. 기존 업데이트
3. 취소
아래 템플릿을 그대로 출력한다. 포맷 변경 금지.
ℹ️ 유사 연락처 발견
기존:
👤 홍길동
📱 +82-10-9999-0000
이번 명함:
👤 #홍길동 과장 (ABC주식회사)
📱 +82-10-1234-5678
번호가 다름. 그대로 새로 저장할까?
중복 없으면 중복 관련 메시지 자체를 출력하지 않는다. 바로 사용자 확인으로 넘어간다.
Maton API Gateway (People API)를 사용. 전체 필드 + 커스텀 Name + 사진 지원.
python <<'EOF'
import urllib.request, os, json
# 한국식 명함 예시 (koreanStyleName=true)
contact = {
"names": [{
"unstructuredName": "#홍길동 과장",
"givenName": "홍길동",
"familyName": ""
}],
"organizations": [{
"name": "ABC주식회사",
"title": "과장",
"department": "영업부",
"type": "work"
}],
"emailAddresses": [
{"value": "[email protected]", "type": "work"}
],
"phoneNumbers": [
{"value": "+82-10-1234-5678", "type": "mobile"},
{"value": "+82-2-9876-5432", "type": "work"}
],
"addresses": [
{"formattedValue": "서울시 강남구 테헤란로 123", "type": "work"}
],
"urls": [
{"value": "https://www.example.co.kr", "type": "work"}
]
}
data = json.dumps(contact).encode()
req = urllib.request.Request(
'https://gateway.maton.ai/google-contacts/v1/people:createContact',
data=data, method='POST'
)
req.add_header('Authorization', f'Bearer {os.environ["MATON_API_KEY"]}')
req.add_header('Content-Type', 'application/json')
result = json.load(urllib.request.urlopen(req))
print(json.dumps(result, indent=2))
EOF
cardAsPhoto=true)Section 8의 파이프라인을 따른다. 보정 + 업로드는 Section 8에서 일괄 처리.
파이프라인 종료 시 (성공, 취소, 에러 무관) $BIZCARD_TMP 디렉터리 전체 삭제 (rm -rf "$BIZCARD_TMP").
다른 세션의 파일을 건드리지 않는다.
/bizcard config사용자가 /bizcard config 또는 "명함 설정"을 입력하면, 아래 템플릿을 그대로 복사해서 출력한다. 절대 다른 포맷으로 바꾸지 마라. on/off 값만 config.json 실제 상태에 맞춰 교체.
출력 템플릿 (이 포맷 그대로 사용):
📇 Bizcard Settings
1. Hashtag : on 🟢 — #이름 (카카오 자동추가 방지)
2. Append title : on 🟢 — 이름 뒤에 직함
3. Append company : off ❌ — 이름 뒤에 (회사명)
4. Card as photo : on 🟢 — 명함→연락처 사진
5. Korean reading : on 🟢 — 외국 이름 한국어 독음
6. Korean style name : on 🟢 — 성 비움, 이름에 풀네임
00 = Reset ♺
38 = All on 🟢
49 = All off ❌
번호만 입력하면 on↔off 전환.
이모지 규칙: on → 🟢, off → ❌. 값 바로 뒤에 이모지 붙인다.
규칙:
on / off 값만 config.json 실제 상태에 맞춰 출력.번호만 입력하면 현재 상태의 반대로 자동 전환된다. config.json 파일을 실제로 수정하고, 아래 응답 포맷 그대로 출력.
세션 규칙: /bizcard config 실행 후 다음 1회 입력만 설정 변경으로 처리한다. 그 이후의 숫자 입력은 일반 대화로 취급. 설정을 연속 변경하려면 /bizcard config를 다시 입력.
예시 1: 현재 3. Append company : off 상태에서 사용자가 3 입력
응답 (이 포맷 그대로):
3. Append company : on 🟢
All set now.
예시 2: 현재 1. Hashtag : on 상태에서 사용자가 1 입력
응답 (이 포맷 그대로):
1. Hashtag : off ❌
All set now.
규칙: 변경된 항목만 한 줄 출력 + "All set now." 그 외 추가 멘트 금지.
| 코드 | 동작 | 설명 |
|---|---|---|
00 | Reset | 기본값으로 초기화 |
38 | All on | 전체 설정 켜기 |
49 | All off | 전체 설정 끄기 |
입력: 49
응답:
📇 Bizcard Settings
1. Hashtag : off ❌ — #이름 (카카오 자동추가 방지)
2. Append title : off ❌ — 이름 뒤에 직함
3. Append company : off ❌ — 이름 뒤에 (회사명)
4. Card as photo : off ❌ — 명함→연락처 사진
5. Korean reading : off ❌ — 외국 이름 한국어 독음
6. Korean style name : off ❌ — 성 비움, 이름에 풀네임
00 = Reset ♺
38 = All on 🟢
49 = All off ❌
All off. All set now.
입력: 00
응답:
📇 Bizcard Settings (defaults)
1. Hashtag : on 🟢 — #이름 (카카오 자동추가 방지)
2. Append title : on 🟢 — 이름 뒤에 직함
3. Append company : off ❌ — 이름 뒤에 (회사명)
4. Card as photo : on 🟢 — 명함→연락처 사진
5. Korean reading : on 🟢 — 외국 이름 한국어 독음
6. Korean style name : on 🟢 — 성 비움, 이름에 풀네임
00 = Reset ♺
38 = All on 🟢
49 = All off ❌
Reset to defaults. All set now.
| # | config key | 설명 |
|---|---|---|
| 1 | hashtag | 이름 앞에 # 추가 (카카오톡 자동추가 방지) |
| 2 | appendTitle | 이름 뒤에 직함 추가 |
| 3 | appendCompany | 이름 뒤에 (회사명) 추가 |
| 4 | cardAsPhoto | 명함 이미지를 연락처 사진으로 저장 |
| 5 | koreanReading | 외국 명함 이름/회사를 한국어 독음으로 기록 |
| 6 | koreanStyleName | 한국 명함: familyName 비우고 givenName에 풀네임 |
{
"hashtag": true,
"appendTitle": true,
"appendCompany": false,
"cardAsPhoto": true,
"koreanReading": true,
"koreanStyleName": true
}
사용자가 처음 명함을 보낼 때 (bizcard-log.jsonl이 없거나 비어있을 때):
📇 명함 스캐너 첫 사용!
기본 설정:
1. Hashtag : on
2. Append title : on
3. Append company : off
4. Card as photo : on
5. Korean reading : on
6. Korean style name : on
설정 변경: /bizcard config
이 명함 처리할까?
이후에는 온보딩 없이 바로 처리.
저장 성공 시 memory/bizcard-log.jsonl에 최소한의 정보만 기록한다. 이메일, 전화번호 등 민감한 PII는 저장하지 않는다. 상세 정보는 Google Contacts에서 resourceName으로 조회.
{"ts": "2026-02-20T14:30:00Z", "name": "홍길동", "company": "ABC주식회사", "resourceName": "people/c1234567890"}
PII 최소화 원칙: 로그에는 이름과 회사명(검색용)만 남기고, 전화번호/이메일/주소는 저장하지 않는다.
| 명령 | 동작 |
|---|---|
| "명함 검색 홍길동" / "bizcard search 홍길동" | People API searchContacts 실행 |
| "최근 명함" / "recent bizcards" | bizcard-log.jsonl에서 최근 10건 표시 |
| 에러 상황 | 사용자 메시지 |
|---|---|
| OCR 실패 / 빈 결과 | "텍스트를 인식하지 못했어. 더 선명한 사진으로 다시 보내줘" |
| Maton API 실패 | "Google Contacts 연결을 확인해줘. API 키나 OAuth가 만료됐을 수 있어" |
| 빈 명함 (정보 없음) | "연락처 정보를 못 찾았어. 명함이 맞는지 확인해줘" |
| 네트워크 오류 | "네트워크 연결을 확인해줘" |
| 사진 업로드 실패 (연락처는 저장됨) | "연락처는 저장됐는데 사진 업로드가 실패했어. 다시 시도할까?" |
| config.json 파싱 오류 | 기본값으로 자동 복구 후 "설정 파일이 손상돼서 기본값으로 복구했어" |
설정 파일: skills/bizcard/config.json
| # | key | 기본값 | 설명 |
|---|---|---|---|
| 1 | hashtag | true | 이름 앞에 # 추가 (카카오톡 자동추가 방지) |
| 2 | appendTitle | true | 이름 뒤에 직함 추가 |
| 3 | appendCompany | false | 이름 뒤에 (회사명) 추가 |
| 4 | cardAsPhoto | true | 명함 이미지를 연락처 사진으로 저장 |
| 5 | koreanReading | true | 외국 명함 이름/회사를 한국어 독음으로 기록 |
| 6 | koreanStyleName | true | 한국 명함: familyName 비우고 givenName에 풀네임 |
| Tool | Purpose | Install |
|---|---|---|
MATON_API_KEY | Maton API Gateway auth (People API proxy) | maton.ai/settings |
NANO_BANANA_API_KEY | Google Gemini API key (used by Nano Banana Pro for image correction) | aistudio.google.com |
Skills that do similar work, with their headline benchmark lift.
From the same source and nearby in the catalogue.