
Korean Slang Writing
- 2.6k installs
- 7k repo stars
- Updated August 2, 2026
- nomadamas/k-skill
korean-slang-writing is an agent skill that Use curated Korean trending-slang candidates plus best-effort Namu Wiki lookups to write witty Korean text with up-to-da.
About
사용자가 유행어 섞어서 요즘 말투로 밈스럽게 재치있게 같은 요청을 했을 때 검증된 한국 유행어 후보 를 먼저 뽑고 필요하면 나무위키 원문 요약을 확인해 한국어 문장 홍보문 SNS 게시글 댓글을 유행어 느낌으로 작성한다 스킬은 데이터와 조회 도구 만 제공한다 큐레이션된 시드 인덱스 검색 나무위키 best effort 요약 실제 문장 작성은 에이전트 LLM 가 스킬이 돌려준 후보 중에서 사용자 의도에 맞는 표현을 골라 한다 시드 인덱스 출처는 나무위키 분류 유행어 https namu wiki w EB B6 84 EB A5 98 EC 9C A0 ED 96 89 EC 96 B4 를 기준으로 수작업 큐레이션한다 risky 수준의 혐오 차별 집단 조롱 가능성이 있는 표현은 시드에 넣지 않는다 SNS 홍보 글 유행어 섞어서 써줘 댓글을 요즘 말투로 바꿔줘 밈스럽게 MZ스럽게 재치있게 써줘 이 유행어가 무슨 뜻이야 시드 인덱스 나무위키 lookup 긍정적인 느낌의 유행어 3개 추천해줘 이 상품 카피에 어울리는 유행어 후보 뽑아줘 격식 공문 보도자료 법률 문서 사과문 공식 발표문 등 유행어 사용이 부적절한 문맥 민감한 사회 정치 종교 주제에 풍자 유행어를 끼얹어야 하는 상황 대량 인덱싱 전체 웹 스크래핑 나무위키 약관 Cloudflare 정책 위반
- description: Use curated Korean trending-slang candidates plus best-effort Namu Wiki lookups to write witty Korean text
- 사용자가 "유행어 섞어서", "요즘 말투로", "밈스럽게", "재치있게" 같은 요청을 했을 때, **검증된 한국 유행어 후보**를 먼저 뽑고 필요하면 나무위키 원문 요약을 확인해 한국어 문장·홍보문·SNS 게시글·댓
- - 스킬은 **데이터와 조회 도구**만 제공한다: 큐레이션된 시드 인덱스 검색 + 나무위키 best-effort 요약.
- Follow korean-slang-writing SKILL.md steps and documented constraints.
- Follow korean-slang-writing SKILL.md steps and documented constraints.
Korean Slang Writing by the numbers
- 2,557 all-time installs (skills.sh)
- +241 installs in the week ending Aug 5, 2026 (Skillselion tracking)
- Ranked #337 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Security screen: MEDIUM risk (skills.sh audit)
- Data as of Aug 5, 2026 (Skillselion catalog sync)
korean-slang-writing capabilities & compatibility
- Capabilities
- description: use curated korean trending slang c · 사용자가 "유행어 섞어서", "요즘 말투로", "밈스럽게", "재치있게" 같은 요청을 · 스킬은 **데이터와 조회 도구**만 제공한다: 큐레이션된 시드 인덱스 검색 + 나무 · follow korean slang writing skill.md steps and d
- Use cases
- orchestration
What korean-slang-writing says it does
description: Use curated Korean trending-slang candidates plus best-effort Namu Wiki lookups to write witty Korean text with up-to-date slang, with conservative safety and freshness guardrails.
사용자가 "유행어 섞어서", "요즘 말투로", "밈스럽게", "재치있게" 같은 요청을 했을 때, **검증된 한국 유행어 후보**를 먼저 뽑고 필요하면 나무위키 원문 요약을 확인해 한국어 문장·홍보문·SNS 게시글·댓글을 유행어 느낌으로 작성한다.
- 스킬은 **데이터와 조회 도구**만 제공한다: 큐레이션된 시드 인덱스 검색 + 나무위키 best-effort 요약.
npx skills add https://github.com/nomadamas/k-skill --skill korean-slang-writingAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 2.6k |
|---|---|
| repo stars | ★ 7k |
| Security audit | 2 / 3 scanners passed |
| Last updated | August 2, 2026 |
| Repository | nomadamas/k-skill ↗ |
When should an agent use korean-slang-writing and what problem does it solve?
Use curated Korean trending-slang candidates plus best-effort Namu Wiki lookups to write witty Korean text with up-to-date slang, with conservative safety and freshness guardrails.
Who is it for?
Developers invoking korean-slang-writing as documented in the skill source.
Skip if: Skip when requirements fall outside korean-slang-writing documented scope.
When should I use this skill?
Use curated Korean trending-slang candidates plus best-effort Namu Wiki lookups to write witty Korean text with up-to-date slang, with conservative safety and freshness guardrails.
What you get
Outputs aligned with the korean-slang-writing SKILL.md workflow and stated deliverables.
Files
Korean Slang Writing
What this skill does
사용자가 "유행어 섞어서", "요즘 말투로", "밈스럽게", "재치있게" 같은 요청을 했을 때, 검증된 한국 유행어 후보를 먼저 뽑고 필요하면 나무위키 원문 요약을 확인해 한국어 문장·홍보문·SNS 게시글·댓글을 유행어 느낌으로 작성한다.
- 스킬은 데이터와 조회 도구만 제공한다: 큐레이션된 시드 인덱스 검색 + 나무위키 best-effort 요약.
- 실제 문장 작성은 에이전트(LLM) 가 스킬이 돌려준 후보 중에서 사용자 의도에 맞는 표현을 골라 한다.
- 시드 인덱스 출처는 나무위키 분류:유행어 를 기준으로 수작업 큐레이션한다.
- risky 수준의 혐오·차별·집단 조롱 가능성이 있는 표현은 시드에 넣지 않는다.
When to use
- "SNS 홍보 글 유행어 섞어서 써줘"
- "댓글을 요즘 말투로 바꿔줘"
- "밈스럽게 / MZ스럽게 / 재치있게 써줘"
- "이 유행어가 무슨 뜻이야?" (시드 인덱스 + 나무위키 lookup)
- "긍정적인 느낌의 유행어 3개 추천해줘"
- "이 상품 카피에 어울리는 유행어 후보 뽑아줘"
When NOT to use
- 격식 공문, 보도자료, 법률 문서, 사과문, 공식 발표문 등 유행어 사용이 부적절한 문맥
- 민감한 사회·정치·종교 주제에 풍자 유행어를 끼얹어야 하는 상황
- 대량 인덱싱 / 전체 웹 스크래핑 (나무위키 약관·Cloudflare 정책 위반 소지)
- 불특정 다수 대상의 브랜딩 문구에서 유행어 과다 사용 (저자가 확인·동의하지 않은 경우)
Policy first
- 유행어는 자연스럽게 적게 쓰는 것을 기본값으로 한다. 과다 사용은 오히려 촌스럽다.
- 뜻과 용례를 확인하지 못한 유행어는 사용하지 않는다.
safety가spicy인 항목은 비격식 컨텍스트에서만 쓰고, 격식 자리에는 피한다.still_usable: false표시가 붙은 구형 유행어는 기본 검색 결과에 포함되지 않는다 (--include-deprecated로 의도적 복원 가능).- 나무위키는 best-effort 출처이며 Cloudflare 차단·HTML 구조 변경 시
fetched: false또는warning으로 응답한다. 이 경우 추측하지 말고 시드 인덱스 meaning_short + 링크만 안내한다.
Prerequisites
python33.10+ (argparse,urllib,unittest만 사용 — 추가 의존성 없음)- 스킬 디렉터리의
scripts/*.pyhelper (설치 시 자동 포함) - 나무위키 lookup 에는 인터넷 연결이 필요하지만, 실패해도 시드 인덱스만으로 안전하게 응답할 수 있다.
Response contract
slang_search 결과 스키마 (v1):
{
"query": "중꺾마",
"filters_applied": {
"mood": ["긍정"],
"context": ["SNS"],
"safety": ["safe"],
"intensity": [],
"limit": 10,
"include_deprecated": false
},
"matched_before_limit": 12,
"total_candidates": 5,
"candidates": [
{
"term": "중꺾마",
"aliases": ["중요한 건 꺾이지 않는 마음"],
"meaning_short": "포기하지 않는 불굴의 의지.",
"usage_context": ["격려", "스포츠", "SNS"],
"mood_tags": ["긍정", "의지"],
"intensity": "medium",
"safety": "safe",
"example_usage": ["힘들지만 중꺾마 정신으로 이겨내자!"],
"namuwiki_url": "https://namu.wiki/w/...",
"era": "2022",
"still_usable": true,
"match_reason": "exact"
}
],
"source": "...",
"last_reviewed": "2026-04-22"
}match_reason 우선순위: exact > alias > substring > no-query (필터만 적용한 경우).
slang_lookup 결과 스키마:
{
"input": "중꺾마",
"url": "https://namu.wiki/w/%EC%A4%91%EA%BA%BE%EB%A7%88",
"fetched": true,
"title": "중꺾마",
"summary": "중꺾마는 ...",
"error": null,
"block_reason": null,
"warning": "optional, when HTML parsing failed"
}fetched: false 인 경우 block_reason 이 blocked / not_found / upstream_error 중 하나이며, 에이전트는 추측하지 않고 원문 링크를 제시하거나 다른 출처 확인을 제안한다.
Workflow
1. 사용자 의도 정리
- 글쓰기 목적 (홍보, 댓글, 자기소개, 제목 등)
- 유행어 강도 (살짝 / 적당히 / 많이)
- 톤/무드 (긍정, 부정, 유머, 자조, 감탄 등)
- 문맥 (SNS, 마케팅, 음식, 스포츠, 직장, 학교 등)
2. 시드 인덱스에서 후보 탐색
python3 scripts/slang_search.py --mood "긍정,유머" --context "SNS,마케팅" --safety safe --limit 5 --format json주요 옵션:
| 옵션 | 설명 | 예 |
|---|---|---|
--query | 키워드 매칭 (term/aliases/substring) | --query 중꺾마 |
--mood | 무드 태그 (OR 매칭) | --mood 긍정,유머 |
--context | 문맥 태그 (OR 매칭) | --context SNS,마케팅 |
--safety | 안전 수준 (safe/spicy/risky) | --safety safe,spicy |
--intensity | 강도 (subtle/medium/strong) | --intensity medium,strong |
--limit | 반환 개수 (1~50) | --limit 5 |
--include-deprecated | 구형 유행어 포함 | --include-deprecated |
--index-path | 외부 인덱스 JSON 경로 | --index-path ./my-index.json |
--format | json 또는 text | --format text |
3. (선택) 나무위키 원문 요약 확인
python3 scripts/slang_lookup.py "중꺾마" --format json
python3 scripts/slang_lookup.py "https://namu.wiki/w/%EC%A4%91%EA%BA%BE%EB%A7%88" --max-length 800- 성공 시
title,summary를 돌려준다. - 실패 시
fetched: false+block_reason+error를 돌려준다. 추측하지 말 것.
4. 에이전트가 글 작성
1. 후보 중 문맥에 맞는 1~3개를 선택한다. 많이 넣지 않는다. 2. safety 가 spicy 인 표현은 비격식 컨텍스트에서만 쓰고, 사용 의도를 분명히 한다. 3. 결과물을 1~2문장으로 자연스럽게 녹인다. 4. 필요하면 "사용한 표현: A, B — 의미 확인: [url]" 을 덧붙여 근거 링크를 보여준다. 5. 사용자가 더 과한/덜 과한 버전을 원하면 대안을 함께 제공한다.
CLI examples
# 1) 특정 유행어 뜻/메타 바로 확인
python3 scripts/slang_search.py --query "갓생" --format text
# 2) 긍정 무드의 SNS 마케팅 후보 5개
python3 scripts/slang_search.py --mood "긍정,유머" --context "SNS,마케팅" --safety safe --limit 5
# 3) 이용자가 지정한 커스텀 인덱스로 전환
python3 scripts/slang_search.py --query "중꺾마" --index-path ~/Downloads/my-slang.json
# 4) 나무위키 원문 요약 시도
python3 scripts/slang_lookup.py "럭키비키" --timeout 10 --format textResponse policy
- 응답에는 선택한 유행어, 최종 문장, 근거 링크 세 가지를 포함한다.
- 유행어를 2개 이상 썼다면 각각 왜 썼는지 한 줄 이유를 붙인다.
- 사용자가 명시적으로 요청하지 않은 상황에서는 유행어를 끼얹지 않는다.
- 나무위키 요약이 없을 때는 시드의
meaning_short와 원문 링크로 대체한다. "잘 모르겠다" 를 숨기지 않는다.
Done when
slang_search가 JSON 으로 최소 한 개의 후보를 돌려주었거나, 의도적으로 0건 (no match) 을 명확히 전달했다.- 유행어를 사용한 경우, 각 표현의 뜻/근거 링크가 함께 제시되었다.
safety=risky표현은 시드에 없고,spicy표현을 썼다면 왜 괜찮은 문맥인지 한 줄 설명이 있다.- 나무위키 lookup 실패 시 추측 대신 원문 링크로 대체되었다.
Notes
- 시드 인덱스 위치:
data/seed-slang.json(약 30개 항목). 확장은 PR 로 검토 후 반영한다. last_reviewed필드를 의미 있게 업데이트하지 않는 변경은 받지 않는 편이 안전하다.- 이 스킬은 나무위키 분류:유행어 를 기반으로 하지만 전체 인덱싱하지 않는다. Cloudflare 차단·데이터 선도 변화에 보수적으로 설계된 v1 이다.
- 다음 버전에서는 여러 공개 소스를 통합하거나
last_reviewed기반 자동 만료 로직을 넣을 수 있다.
{
"schema_version": "1.0",
"source": "https://namu.wiki/w/%EB%B6%84%EB%A5%98:%EC%9C%A0%ED%96%89%EC%96%B4 (분류:유행어) + 큐레이션",
"last_reviewed": "2026-04-22",
"notes": "v1 시드 인덱스. 에이전트는 필요 시 slang_lookup.py 로 namuwiki 원문 요약을 확인한다. safety 수준이 risky 인 항목은 포함하지 않는다.",
"entries": [
{
"term": "중꺾마",
"aliases": ["중요한 건 꺾이지 않는 마음", "중꺾그마"],
"meaning_short": "어려운 상황에서도 포기하지 않는 불굴의 의지.",
"usage_context": ["격려", "스포츠", "동기부여", "SNS"],
"mood_tags": ["긍정", "의지", "감동"],
"intensity": "medium",
"safety": "safe",
"example_usage": [
"힘들지만 중꺾마 정신으로 끝까지 간다!",
"중꺾마, 올해도 잘 버텨보자."
],
"namuwiki_url": "https://namu.wiki/w/%EC%A4%91%EA%BA%BE%EB%A7%88",
"era": "2022",
"still_usable": true
},
{
"term": "갓생",
"aliases": ["갓생 살기", "갓생러"],
"meaning_short": "신(God)처럼 부지런하고 생산적인 삶.",
"usage_context": ["동기부여", "일상", "자기계발", "SNS"],
"mood_tags": ["긍정", "의지", "성실"],
"intensity": "medium",
"safety": "safe",
"example_usage": [
"오늘부터 갓생 산다.",
"갓생러들의 아침 루틴 모음."
],
"namuwiki_url": "https://namu.wiki/w/%EA%B0%93%EC%83%9D",
"era": "2021",
"still_usable": true
},
{
"term": "오운완",
"aliases": ["오늘 운동 완료"],
"meaning_short": "오늘의 운동을 마쳤다는 성취 인증.",
"usage_context": ["일상", "운동", "헬스", "SNS"],
"mood_tags": ["긍정", "성취", "뿌듯함"],
"intensity": "subtle",
"safety": "safe",
"example_usage": [
"오운완! 오늘도 해냈다.",
"#오운완 #헬창의일상"
],
"namuwiki_url": "https://namu.wiki/w/%EC%98%A4%EC%9A%B4%EC%99%84",
"era": "2020",
"still_usable": true
},
{
"term": "꾸안꾸",
"aliases": ["꾸민 듯 안 꾸민 듯"],
"meaning_short": "꾸민 티 안 나게 자연스럽게 꾸민 스타일.",
"usage_context": ["패션", "뷰티", "일상"],
"mood_tags": ["중성", "세련"],
"intensity": "subtle",
"safety": "safe",
"example_usage": [
"꾸안꾸 데일리룩 추천해드려요.",
"꾸안꾸가 제일 어렵다."
],
"namuwiki_url": "https://namu.wiki/w/%EA%BE%B8%EC%95%88%EA%BE%B8",
"era": "2019",
"still_usable": true
},
{
"term": "TMI",
"aliases": ["티엠아이", "Too Much Information"],
"meaning_short": "굳이 안 궁금한 너무 많은 정보.",
"usage_context": ["대화", "SNS", "유머"],
"mood_tags": ["중성", "유머"],
"intensity": "subtle",
"safety": "safe",
"example_usage": [
"TMI지만 저 오늘 양말 짝짝이로 신었어요.",
"TMI 주의, 스크롤 내리세요."
],
"namuwiki_url": "https://namu.wiki/w/TMI",
"era": "2018",
"still_usable": true
},
{
"term": "현타",
"aliases": ["현실 자각 타임", "현자타임"],
"meaning_short": "몰두하던 일 끝에 몰려오는 공허함이나 현실 자각.",
"usage_context": ["일상", "감정표현", "유머"],
"mood_tags": ["부정", "허무", "자조"],
"intensity": "medium",
"safety": "safe",
"example_usage": [
"카드값 보고 갑자기 현타 왔다.",
"몰아보기 끝나고 오는 그 현타..."
],
"namuwiki_url": "https://namu.wiki/w/%ED%98%84%ED%83%80",
"era": "2015",
"still_usable": true
},
{
"term": "갑분싸",
"aliases": ["갑자기 분위기 싸해짐"],
"meaning_short": "한순간에 분위기가 어색하거나 썰렁해진 상황.",
"usage_context": ["유머", "대화", "일상"],
"mood_tags": ["부정", "어색", "유머"],
"intensity": "medium",
"safety": "safe",
"example_usage": [
"아재 개그 한 번에 갑분싸 됐어.",
"갑분싸 만들기 장인"
],
"namuwiki_url": "https://namu.wiki/w/%EA%B0%91%EB%B6%84%EC%8B%B8",
"era": "2018",
"still_usable": true
},
{
"term": "워라밸",
"aliases": ["Work-Life Balance", "일과 삶의 균형"],
"meaning_short": "일과 개인 삶 사이의 균형.",
"usage_context": ["직장", "일상", "자기계발"],
"mood_tags": ["중성", "가치관"],
"intensity": "subtle",
"safety": "safe",
"example_usage": [
"이 회사는 워라밸이 확실히 좋다.",
"워라밸 지키면서 일하기."
],
"namuwiki_url": "https://namu.wiki/w/%EC%9B%8C%EB%9D%BC%EB%B0%B8",
"era": "2017",
"still_usable": true
},
{
"term": "존버",
"aliases": ["존나게 버티기"],
"meaning_short": "힘들어도 끝까지 버티는 행동. 비속어 어근이 섞여 있어 격식 상황에서는 주의.",
"usage_context": ["투자", "업무", "의지"],
"mood_tags": ["중성", "의지", "인내"],
"intensity": "strong",
"safety": "spicy",
"example_usage": [
"주가 떨어져도 일단 존버 중.",
"존버는 승리한다."
],
"namuwiki_url": "https://namu.wiki/w/%EC%A1%B4%EB%B2%84",
"era": "2018",
"still_usable": true
},
{
"term": "만반잘부",
"aliases": ["만나서 반가워 잘 부탁해"],
"meaning_short": "새로 만난 사이 캐주얼 인사말.",
"usage_context": ["인사", "자기소개", "SNS"],
"mood_tags": ["긍정", "친근"],
"intensity": "subtle",
"safety": "safe",
"example_usage": [
"저 오늘 새로 왔습니다. 만반잘부!",
"만반잘부 💕"
],
"namuwiki_url": "https://namu.wiki/w/%EB%A7%8C%EB%B0%98%EC%9E%98%EB%B6%80",
"era": "2020",
"still_usable": true
},
{
"term": "억텐",
"aliases": ["억지 텐션"],
"meaning_short": "무리해서 끌어올린 과장된 텐션.",
"usage_context": ["유머", "자조", "SNS"],
"mood_tags": ["유머", "자조"],
"intensity": "medium",
"safety": "safe",
"example_usage": [
"오늘 피곤한데 억텐으로 버틴다.",
"억텐 아니고 찐텐이야."
],
"namuwiki_url": "https://namu.wiki/w/%EC%96%B5%ED%85%90",
"era": "2021",
"still_usable": true
},
{
"term": "찐텐",
"aliases": ["진짜 텐션"],
"meaning_short": "꾸밈없이 진심으로 올라온 텐션.",
"usage_context": ["유머", "SNS", "일상"],
"mood_tags": ["긍정", "진심"],
"intensity": "medium",
"safety": "safe",
"example_usage": [
"이거 찐텐이야, 진짜 웃겨.",
"찐텐 터지는 순간"
],
"namuwiki_url": "https://namu.wiki/w/%EC%B0%90%ED%85%90",
"era": "2021",
"still_usable": true
},
{
"term": "손절",
"aliases": ["손절하다"],
"meaning_short": "인간관계를 단호하게 끊는 행위. 주식 용어에서 전이됨.",
"usage_context": ["관계", "일상", "투자"],
"mood_tags": ["부정", "단호"],
"intensity": "medium",
"safety": "safe",
"example_usage": [
"그런 친구는 그냥 손절해.",
"이 주식 손절할까 고민 중."
],
"namuwiki_url": "https://namu.wiki/w/%EC%86%90%EC%A0%88",
"era": "2018",
"still_usable": true
},
{
"term": "입덕",
"aliases": ["입덕하다"],
"meaning_short": "어떤 대상/스타의 팬이 되어 빠지기 시작하는 것.",
"usage_context": ["취미", "덕질", "SNS"],
"mood_tags": ["긍정", "애정"],
"intensity": "subtle",
"safety": "safe",
"example_usage": [
"이 아이돌 무대 보고 바로 입덕했어.",
"입덕 축하해요!"
],
"namuwiki_url": "https://namu.wiki/w/%EC%9E%85%EB%8D%95",
"era": "2015",
"still_usable": true
},
{
"term": "덕질",
"aliases": ["덕후질"],
"meaning_short": "좋아하는 대상을 열정적으로 파고드는 활동.",
"usage_context": ["취미", "SNS", "일상"],
"mood_tags": ["긍정", "애정", "열정"],
"intensity": "subtle",
"safety": "safe",
"example_usage": [
"오늘도 덕질 중입니다.",
"덕질은 돈이 참 많이 드는 취미야."
],
"namuwiki_url": "https://namu.wiki/w/%EB%8D%95%EC%A7%88",
"era": "2015",
"still_usable": true
},
{
"term": "아아",
"aliases": ["아이스 아메리카노"],
"meaning_short": "아이스 아메리카노. 한국인 카페 주문 공식 음료.",
"usage_context": ["일상", "카페", "음료"],
"mood_tags": ["중성", "일상"],
"intensity": "subtle",
"safety": "safe",
"example_usage": [
"무조건 아아 한 잔.",
"한겨울에도 아아 시키는 한국인."
],
"namuwiki_url": "https://namu.wiki/w/%EC%95%84%EC%9D%B4%EC%8A%A4%20%EC%95%84%EB%A9%94%EB%A6%AC%EC%B9%B4%EB%85%B8",
"era": "2015",
"still_usable": true
},
{
"term": "존맛탱",
"aliases": ["JMT", "존맛"],
"meaning_short": "엄청나게 맛있다는 강조 표현. 비속어 어근 포함으로 격식 상황 주의.",
"usage_context": ["음식", "SNS", "리뷰"],
"mood_tags": ["긍정", "감탄", "맛"],
"intensity": "strong",
"safety": "spicy",
"example_usage": [
"여기 김치찌개 JMT!",
"존맛탱! 다시 올 거야."
],
"namuwiki_url": "https://namu.wiki/w/%EC%A1%B4%EB%A7%9B%ED%83%B1",
"era": "2015",
"still_usable": true
},
{
"term": "노잼",
"aliases": ["노잼이다"],
"meaning_short": "재미없다는 뜻의 부정 평가.",
"usage_context": ["평가", "SNS", "유머"],
"mood_tags": ["부정", "평가"],
"intensity": "medium",
"safety": "safe",
"example_usage": [
"그 영화 노잼이었어.",
"오늘 하루 진짜 노잼."
],
"namuwiki_url": "https://namu.wiki/w/%EB%85%B8%EC%9E%BC",
"era": "2015",
"still_usable": true
},
{
"term": "꿀잼",
"aliases": ["꿀잼이다"],
"meaning_short": "매우 재미있다는 긍정 평가.",
"usage_context": ["평가", "SNS", "유머"],
"mood_tags": ["긍정", "유머"],
"intensity": "medium",
"safety": "safe",
"example_usage": [
"이번 주 예능 꿀잼!",
"이거 생각보다 꿀잼이네."
],
"namuwiki_url": "https://namu.wiki/w/%EA%BF%80%EC%9E%BC",
"era": "2015",
"still_usable": true
},
{
"term": "스불재",
"aliases": ["스스로 불러온 재앙"],
"meaning_short": "자기 탓에 일어난 불행을 유머러스하게 표현한 말.",
"usage_context": ["유머", "자조", "SNS"],
"mood_tags": ["유머", "자조", "부정"],
"intensity": "medium",
"safety": "safe",
"example_usage": [
"시험기간에 드라마 몰아보기… 이거 스불재다.",
"스불재지만 어쩌겠어."
],
"namuwiki_url": "https://namu.wiki/w/%EC%8A%A4%EB%B6%88%EC%9E%AC",
"era": "2019",
"still_usable": true
},
{
"term": "마상",
"aliases": ["마음의 상처"],
"meaning_short": "사소한 한마디에 받은 속상한 감정.",
"usage_context": ["유머", "감정표현", "일상"],
"mood_tags": ["부정", "유머", "소심"],
"intensity": "subtle",
"safety": "safe",
"example_usage": [
"그 말 듣고 마상 왔어.",
"너 그러면 나 마상 입어."
],
"namuwiki_url": "https://namu.wiki/w/%EB%A7%88%EC%83%81",
"era": "2020",
"still_usable": true
},
{
"term": "럭키비키",
"aliases": ["Lucky Vicky"],
"meaning_short": "어떤 일도 긍정적으로 '운 좋았다'로 해석하는 초긍정 태도. 장원영의 인터뷰 밈에서 유래.",
"usage_context": ["긍정", "일상", "SNS", "마케팅"],
"mood_tags": ["긍정", "낙관", "유머"],
"intensity": "medium",
"safety": "safe",
"example_usage": [
"버스 방금 놓쳤는데 다음 거 앉아서 왔네, 럭키비키잖아!",
"이것도 럭키비키야."
],
"namuwiki_url": "https://namu.wiki/w/%EB%9F%AD%ED%82%A4%EB%B9%84%ED%82%A4",
"era": "2024",
"still_usable": true
},
{
"term": "꼰대",
"aliases": [],
"meaning_short": "권위적인 태도로 훈계하거나 가르치려 드는 사람에 대한 비하 표현.",
"usage_context": ["세대", "직장", "관계"],
"mood_tags": ["부정", "풍자"],
"intensity": "medium",
"safety": "spicy",
"example_usage": [
"꼰대 같은 말은 하지 말자.",
"스스로 꼰대가 되지 않기 위해 노력 중."
],
"namuwiki_url": "https://namu.wiki/w/%EA%BC%B0%EB%8C%80",
"era": "2015",
"still_usable": true
},
{
"term": "현생",
"aliases": ["현실 생활"],
"meaning_short": "덕질/게임/몰두하는 취미 밖의 일상적 현실 생활.",
"usage_context": ["일상", "덕질", "SNS"],
"mood_tags": ["중성", "일상"],
"intensity": "subtle",
"safety": "safe",
"example_usage": [
"현생 바빠서 오늘은 덕질 못 해.",
"현생 리셋 시간."
],
"namuwiki_url": "https://namu.wiki/w/%ED%98%84%EC%83%9D",
"era": "2018",
"still_usable": true
},
{
"term": "킹받다",
"aliases": ["킹받네"],
"meaning_short": "'열받다'를 강조한 유행 표현. 짜증이나 약오름을 유머러스하게 나타냄.",
"usage_context": ["유머", "감정표현", "SNS"],
"mood_tags": ["부정", "유머", "짜증"],
"intensity": "medium",
"safety": "safe",
"example_usage": [
"이거 왜 안 되는 거야, 킹받네.",
"진짜 킹받는다 오늘."
],
"namuwiki_url": "https://namu.wiki/w/%ED%82%B9%EB%B0%9B%EB%8B%A4",
"era": "2021",
"still_usable": true
},
{
"term": "어쩔티비",
"aliases": ["어쩔TV", "저쩔티비"],
"meaning_short": "상대의 말에 '어쩌라고'로 응수하는 초딩풍 유행어. 과하면 유치해 보일 수 있음.",
"usage_context": ["유머", "반박"],
"mood_tags": ["유머", "반박"],
"intensity": "strong",
"safety": "spicy",
"example_usage": [
"그래서 어쩔티비.",
"어쩔티비 저쩔티비."
],
"namuwiki_url": "https://namu.wiki/w/%EC%96%B4%EC%A9%94%ED%8B%B0%EB%B9%84",
"era": "2021",
"still_usable": true
},
{
"term": "혼밥",
"aliases": ["혼자 밥 먹기"],
"meaning_short": "혼자 식사하는 것. 이제는 널리 정착된 생활방식 표현.",
"usage_context": ["일상", "음식", "라이프스타일"],
"mood_tags": ["중성", "일상"],
"intensity": "subtle",
"safety": "safe",
"example_usage": [
"오늘은 혼밥 해야지.",
"혼밥 하기 좋은 가게 추천해줘."
],
"namuwiki_url": "https://namu.wiki/w/%ED%98%BC%EB%B0%A5",
"era": "2015",
"still_usable": true
},
{
"term": "인싸",
"aliases": ["인사이더"],
"meaning_short": "모임·무리 안에서 잘 어울려 주도하는 사람. 반대말은 아싸.",
"usage_context": ["사회", "학교", "직장"],
"mood_tags": ["중성"],
"intensity": "subtle",
"safety": "safe",
"example_usage": [
"저 친구는 진짜 인싸야.",
"인싸템 하나 사볼까."
],
"namuwiki_url": "https://namu.wiki/w/%EC%9D%B8%EC%8B%B8",
"era": "2017",
"still_usable": true
},
{
"term": "역대급",
"aliases": [],
"meaning_short": "역사상 최고 수준이라는 감탄. 긍·부정 모두에 쓸 수 있음.",
"usage_context": ["감탄", "리뷰", "SNS"],
"mood_tags": ["감탄", "강조"],
"intensity": "medium",
"safety": "safe",
"example_usage": [
"이번 공연 역대급이었다.",
"역대급 폭염."
],
"namuwiki_url": "https://namu.wiki/w/%EC%97%AD%EB%8C%80%EA%B8%89",
"era": "2015",
"still_usable": true
}
]
}
from __future__ import annotations
import ssl
import urllib.error
import urllib.parse
import urllib.request
NAMUWIKI_BASE = "https://namu.wiki/w/"
_NAMUWIKI_HOSTS = ("namu.wiki", "en.namu.wiki")
_BROWSER_HEADERS = {
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
"Accept-Language": "ko,en-US;q=0.9,en;q=0.8",
"User-Agent": (
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) "
"AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36"
),
}
_ssl_ctx: ssl.SSLContext | None = None
class LookupError(Exception):
pass
class BlockedError(LookupError):
pass
class NotFoundError(LookupError):
pass
class UpstreamError(LookupError):
pass
def _get_ssl_context() -> ssl.SSLContext:
global _ssl_ctx
if _ssl_ctx is None:
_ssl_ctx = ssl.create_default_context()
return _ssl_ctx
def is_namuwiki_url(url: str) -> bool:
parsed = urllib.parse.urlparse(url)
if parsed.scheme not in {"http", "https"}:
return False
host = (parsed.hostname or "").lower()
return host in _NAMUWIKI_HOSTS
def build_namuwiki_url(term_or_url: str) -> str:
value = term_or_url.strip()
if not value:
raise ValueError("term is empty")
if is_namuwiki_url(value):
return value
# Preserve slash so namuwiki subpage titles (e.g. "한국/서울") survive encoding.
quoted = urllib.parse.quote(value, safe="/")
return f"{NAMUWIKI_BASE}{quoted}"
def browser_headers() -> dict[str, str]:
return dict(_BROWSER_HEADERS)
def fetch_html(url: str, timeout: int) -> str:
if not is_namuwiki_url(url):
raise ValueError(f"not a namuwiki URL: {url}")
request = urllib.request.Request(url, headers=browser_headers())
try:
with urllib.request.urlopen(request, timeout=timeout, context=_get_ssl_context()) as response:
body = response.read()
except urllib.error.HTTPError as error:
status = error.code
if status == 404:
raise NotFoundError(f"HTTP 404: {url}") from error
if status in (401, 403, 429):
raise BlockedError(
f"HTTP {status} (possibly Cloudflare / rate-limited) for {url}"
) from error
raise UpstreamError(f"HTTP {status} for {url}") from error
except urllib.error.URLError as error:
raise UpstreamError(f"URL error for {url}: {error.reason}") from error
except TimeoutError as error:
raise UpstreamError(f"timeout after {timeout}s for {url}") from error
return body.decode("utf-8", "ignore")
#!/usr/bin/env python3
from __future__ import annotations
import argparse
import json
import os
import re
import sys
from html import unescape
from typing import Any
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from _slang_http import ( # noqa: E402
BlockedError,
NotFoundError,
UpstreamError,
build_namuwiki_url,
fetch_html,
)
DEFAULT_TIMEOUT = 15
DEFAULT_MAX_LENGTH = 1500
TAG_RE = re.compile(r"<[^>]+>")
SCRIPT_STYLE_RE = re.compile(
r"<(script|style|noscript)[^>]*>.*?</\1>", re.DOTALL | re.IGNORECASE
)
TITLE_RE = re.compile(r"<title[^>]*>(.*?)</title>", re.DOTALL | re.IGNORECASE)
NAMUWIKI_TITLE_SUFFIX_RE = re.compile(r"\s*[-|]?\s*나무위키\s*$")
BLOCK_END_RE = re.compile(r"</(p|div|li|h[1-6])>", re.IGNORECASE)
BR_RE = re.compile(r"<br\s*/?>", re.IGNORECASE)
WHITESPACE_RE = re.compile(r"[ \t]+")
BLANK_LINES_RE = re.compile(r"\n{3,}")
H2_TAG_RE = re.compile(r"<h2\b[^>]*>.*?</h2>", re.DOTALL | re.IGNORECASE)
NUMBERED_H2_INNER_TEXT_RE = re.compile(r"^\s*\d+(?:\.\d+)*\.\s+\S")
SECTION_NUMBER_PREFIX_RE = re.compile(r"^\s*\d+(?:\.\d+)*\.\s+", re.MULTILINE)
EDIT_AFFORDANCE_RE = re.compile(r"\[\s*편집\s*\]")
CATEGORY_NAV_RE = re.compile(r"\[\s*펼치기\s*[·・•]\s*접기\s*\][^\n]*")
DETAILS_PELCHIGI_RE = re.compile(
r"<details\b[^>]*>"
r"\s*<summary\b[^>]*>[^<]*펼치기[^<]*</summary>"
r".*?"
r"</details>",
re.DOTALL | re.IGNORECASE,
)
OG_DESCRIPTION_RE = re.compile(
r'<meta\s+[^>]*property\s*=\s*"og:description"\s+[^>]*content\s*=\s*"([^"]*)"',
re.IGNORECASE,
)
OG_DESCRIPTION_REVERSED_RE = re.compile(
r'<meta\s+[^>]*content\s*=\s*"([^"]*)"\s+[^>]*property\s*=\s*"og:description"',
re.IGNORECASE,
)
MAIN_CONTENT_CLASSES = (
"wiki-paragraph",
"wiki-content",
"namu-wiki-content",
"article-content",
"wiki-body",
"wiki-heading-content",
)
def fetch_page(url: str, timeout: int) -> str:
return fetch_html(url, timeout=timeout)
def extract_title(html: str) -> str:
match = TITLE_RE.search(html)
if not match:
return ""
title = unescape(TAG_RE.sub("", match.group(1))).strip()
title = NAMUWIKI_TITLE_SUFFIX_RE.sub("", title).strip()
return title
def _find_main_content(cleaned_html: str) -> str:
for class_name in MAIN_CONTENT_CLASSES:
pattern = re.compile(
rf'<[a-zA-Z]+[^>]*class="[^"]*\b{re.escape(class_name)}\b[^"]*"[^>]*>',
re.IGNORECASE,
)
match = pattern.search(cleaned_html)
if match:
return cleaned_html[match.start():]
article_match = re.search(r"<article[^>]*>", cleaned_html, re.IGNORECASE)
if article_match:
return cleaned_html[article_match.start():]
return ""
def _h2_inner_text(h2_tag_html: str) -> str:
opening_end = h2_tag_html.index(">") + 1
closing_start = h2_tag_html.rindex("<")
inner = h2_tag_html[opening_end:closing_start]
return unescape(TAG_RE.sub("", inner)).strip()
def _is_numbered_section_h2(h2_tag_html: str) -> bool:
return bool(NUMBERED_H2_INNER_TEXT_RE.match(_h2_inner_text(h2_tag_html)))
def _extract_first_section_between_h2(cleaned_html: str) -> str:
all_matches = list(H2_TAG_RE.finditer(cleaned_html))
numbered = [m for m in all_matches if _is_numbered_section_h2(m.group(0))]
if not numbered:
return ""
start = numbered[0].end()
end = numbered[1].start() if len(numbered) > 1 else len(cleaned_html)
return cleaned_html[start:end]
def _extract_og_description(html: str) -> str:
match = OG_DESCRIPTION_RE.search(html) or OG_DESCRIPTION_REVERSED_RE.search(html)
if not match:
return ""
return unescape(match.group(1)).strip()
def _html_fragment_to_text(fragment: str) -> str:
text = BR_RE.sub("\n", fragment)
text = BLOCK_END_RE.sub("\n", text)
text = TAG_RE.sub("", text)
text = unescape(text)
text = EDIT_AFFORDANCE_RE.sub("", text)
text = CATEGORY_NAV_RE.sub("", text)
text = SECTION_NUMBER_PREFIX_RE.sub("", text)
lines: list[str] = []
for line in text.split("\n"):
stripped = WHITESPACE_RE.sub(" ", line).strip()
if stripped:
lines.append(stripped)
joined = "\n".join(lines)
return BLANK_LINES_RE.sub("\n\n", joined).strip()
def _truncate(text: str, max_length: int) -> str:
if max_length > 0 and len(text) > max_length:
return text[:max_length] + "..."
return text
def extract_summary(html: str, *, max_length: int = DEFAULT_MAX_LENGTH) -> str:
cleaned = SCRIPT_STYLE_RE.sub("", html)
cleaned = DETAILS_PELCHIGI_RE.sub("", cleaned)
h2_section = _extract_first_section_between_h2(cleaned)
if h2_section:
text = _html_fragment_to_text(h2_section)
if text:
return _truncate(text, max_length)
region = _find_main_content(cleaned)
if region:
text = _html_fragment_to_text(region)
if text:
return _truncate(text, max_length)
og_description = _extract_og_description(html)
if og_description:
return _truncate(og_description, max_length)
return ""
def _is_url(value: str) -> bool:
return value.startswith("http://") or value.startswith("https://")
def lookup(
term_or_url: str,
*,
timeout: int = DEFAULT_TIMEOUT,
max_length: int = DEFAULT_MAX_LENGTH,
) -> dict[str, Any]:
input_value = term_or_url.strip()
if not input_value:
raise ValueError("term_or_url is empty")
url = build_namuwiki_url(input_value)
result: dict[str, Any] = {
"input": term_or_url,
"url": url,
"fetched": False,
"title": "",
"summary": "",
"error": None,
"block_reason": None,
}
try:
html = fetch_page(url, timeout=timeout)
except BlockedError as error:
result["error"] = str(error)
result["block_reason"] = "blocked"
return result
except NotFoundError as error:
result["error"] = str(error)
result["block_reason"] = "not_found"
return result
except UpstreamError as error:
result["error"] = str(error)
result["block_reason"] = "upstream_error"
return result
result["fetched"] = True
result["title"] = extract_title(html)
result["summary"] = extract_summary(html, max_length=max_length)
if not result["summary"]:
result["warning"] = (
"Main content region not detected. Namu Wiki HTML layout may have changed; "
"treat this as a hint and verify meaning from seed index or other sources."
)
return result
def parse_args(argv: list[str]) -> argparse.Namespace:
parser = argparse.ArgumentParser(
description=(
"Fetch a Namu Wiki page for a trending slang term and return a best-effort "
"summary. Gracefully reports when the upstream blocks the request."
)
)
parser.add_argument(
"term_or_url",
help="Slang term (e.g. '중꺾마') or full Namu Wiki URL.",
)
parser.add_argument(
"--timeout",
type=int,
default=DEFAULT_TIMEOUT,
help=f"HTTP timeout in seconds. Default: {DEFAULT_TIMEOUT}.",
)
parser.add_argument(
"--max-length",
type=int,
default=DEFAULT_MAX_LENGTH,
help=f"Summary truncation length (0 = unlimited). Default: {DEFAULT_MAX_LENGTH}.",
)
parser.add_argument(
"--format",
choices=["json", "text"],
default="json",
help="Output format.",
)
return parser.parse_args(argv)
def _format_text(result: dict) -> str:
lines: list[str] = []
lines.append(f"URL: {result['url']}")
if result["fetched"]:
lines.append(f"Title: {result['title']}")
lines.append("")
lines.append(result["summary"] or "(summary not extracted)")
else:
lines.append("Fetch failed.")
lines.append(f"Reason: {result.get('block_reason')}")
lines.append(f"Detail: {result.get('error')}")
return "\n".join(lines) + "\n"
def main(argv: list[str] | None = None) -> int:
args = parse_args(argv if argv is not None else sys.argv[1:])
try:
result = lookup(
args.term_or_url,
timeout=args.timeout,
max_length=args.max_length,
)
except ValueError as error:
print(
json.dumps({"error": str(error)}, ensure_ascii=False),
file=sys.stderr,
)
return 1
if args.format == "json":
print(json.dumps(result, ensure_ascii=False, indent=2))
else:
sys.stdout.write(_format_text(result))
return 0 if result["fetched"] else 2
if __name__ == "__main__":
raise SystemExit(main())
#!/usr/bin/env python3
from __future__ import annotations
import argparse
import json
import os
import pathlib
import sys
from typing import Any, Iterable
DEFAULT_LIMIT = 10
MAX_LIMIT = 50
SCRIPT_DIR = pathlib.Path(__file__).resolve().parent
SKILL_ROOT = SCRIPT_DIR.parent
DEFAULT_INDEX_PATH = SKILL_ROOT / "data" / "seed-slang.json"
MATCH_REASON_ORDER = {
"exact": 0,
"alias": 1,
"substring": 2,
"no-query": 3,
}
def load_index(path: str | None = None) -> dict:
target = pathlib.Path(path) if path else DEFAULT_INDEX_PATH
if not target.exists():
raise FileNotFoundError(f"slang index not found at: {target}")
with target.open(encoding="utf-8") as fh:
data = json.load(fh)
if not isinstance(data, dict) or "entries" not in data:
raise ValueError(f"invalid slang index (missing 'entries'): {target}")
return data
def _normalize(text: str) -> str:
return " ".join(text.lower().split())
def _collect_match(entry: dict, query_norm: str) -> str | None:
term_norm = _normalize(entry.get("term", ""))
if not query_norm:
return "no-query"
if term_norm == query_norm:
return "exact"
aliases = entry.get("aliases") or []
alias_norms = [_normalize(a) for a in aliases]
if query_norm in alias_norms:
return "alias"
if query_norm in term_norm:
return "substring"
for alias_norm in alias_norms:
if query_norm and query_norm in alias_norm:
return "substring"
return None
def _ensure_list(value: Any) -> list[str]:
if value is None:
return []
if isinstance(value, str):
return [value]
if isinstance(value, Iterable):
return [str(v) for v in value]
return [str(value)]
def _has_overlap(entry_tags: list[str], requested: list[str]) -> bool:
if not requested:
return True
entry_set = {t.strip().lower() for t in entry_tags}
requested_set = {t.strip().lower() for t in requested}
return bool(entry_set & requested_set)
def _matches_single(value: str | None, allowed: list[str]) -> bool:
if not allowed:
return True
if value is None:
return False
return value.strip().lower() in {a.strip().lower() for a in allowed}
def _era_sort_key(era: str) -> int:
digits = "".join(ch for ch in era if ch.isdigit())
try:
return -int(digits[:4]) if digits else 0
except ValueError:
return 0
def search(
*,
query: str | None = None,
mood: list[str] | None = None,
context: list[str] | None = None,
safety: str | list[str] | None = None,
intensity: str | list[str] | None = None,
limit: int = DEFAULT_LIMIT,
include_deprecated: bool = False,
index: dict | None = None,
index_path: str | None = None,
) -> dict:
if index is None:
index = load_index(index_path)
entries: list[dict] = list(index.get("entries", []))
mood_list = _ensure_list(mood)
context_list = _ensure_list(context)
safety_list = _ensure_list(safety)
intensity_list = _ensure_list(intensity)
query_norm = _normalize(query) if query else ""
clamped_limit = max(1, min(int(limit), MAX_LIMIT))
scored: list[tuple[int, int, str, dict]] = []
for entry in entries:
if not include_deprecated and not entry.get("still_usable", True):
continue
match_reason = _collect_match(entry, query_norm)
if match_reason is None:
continue
if not _has_overlap(entry.get("mood_tags") or [], mood_list):
continue
if not _has_overlap(entry.get("usage_context") or [], context_list):
continue
if not _matches_single(entry.get("safety"), safety_list):
continue
if not _matches_single(entry.get("intensity"), intensity_list):
continue
order = MATCH_REASON_ORDER.get(match_reason, 9)
era_rank = _era_sort_key(str(entry.get("era", "")))
scored.append((order, era_rank, str(entry.get("term", "")), {**entry, "match_reason": match_reason}))
scored.sort(key=lambda item: (item[0], item[1], item[2]))
matched_before_limit = len(scored)
candidates = [row[3] for row in scored[:clamped_limit]]
return {
"query": query,
"filters_applied": {
"mood": mood_list,
"context": context_list,
"safety": safety_list,
"intensity": intensity_list,
"limit": clamped_limit,
"include_deprecated": include_deprecated,
},
"matched_before_limit": matched_before_limit,
"total_candidates": len(candidates),
"candidates": candidates,
"source": index.get("source", ""),
"last_reviewed": index.get("last_reviewed", ""),
}
def _format_text(result: dict) -> str:
if not result["candidates"]:
return "No candidates found.\n"
lines: list[str] = []
query = result.get("query") or "(no query)"
lines.append(f"Query: {query}")
lines.append(
f"Matched: {result['matched_before_limit']} -> showing {result['total_candidates']}"
)
lines.append("")
for idx, entry in enumerate(result["candidates"], start=1):
mood = ", ".join(entry.get("mood_tags") or []) or "-"
context = ", ".join(entry.get("usage_context") or []) or "-"
lines.append(
f"{idx}. {entry['term']} ({entry.get('era', '?')}) "
f"[{entry.get('safety', '?')}, {entry.get('intensity', '?')}]"
)
lines.append(f" mood: {mood}")
lines.append(f" context: {context}")
lines.append(f" meaning: {entry.get('meaning_short', '')}")
examples = entry.get("example_usage") or []
if examples:
lines.append(f" example: {examples[0]}")
lines.append(f" match: {entry.get('match_reason', '?')}")
lines.append(f" url: {entry.get('namuwiki_url', '')}")
lines.append("")
return "\n".join(lines).rstrip() + "\n"
def _split_csv(value: str | None) -> list[str]:
if not value:
return []
return [part.strip() for part in value.split(",") if part.strip()]
def parse_args(argv: list[str]) -> argparse.Namespace:
parser = argparse.ArgumentParser(
description=(
"Search curated Korean trending-slang index. "
"Returns candidates the calling agent can use when writing text with slang."
)
)
parser.add_argument(
"--query", default=None, help="Keyword to match against term/aliases."
)
parser.add_argument(
"--mood",
default="",
help="Comma-separated mood tags (긍정, 부정, 유머, 의지, ...).",
)
parser.add_argument(
"--context",
default="",
help="Comma-separated context tags (SNS, 마케팅, 음식, 스포츠, ...).",
)
parser.add_argument(
"--safety",
default="",
help="Comma-separated safety levels: safe, spicy, risky.",
)
parser.add_argument(
"--intensity",
default="",
help="Comma-separated intensity levels: subtle, medium, strong.",
)
parser.add_argument(
"--limit",
type=int,
default=DEFAULT_LIMIT,
help=f"Max candidates to return (1..{MAX_LIMIT}).",
)
parser.add_argument(
"--include-deprecated",
action="store_true",
help="Include entries marked still_usable=false.",
)
parser.add_argument(
"--index-path",
default=None,
help="Override path to a slang index JSON (defaults to bundled seed).",
)
parser.add_argument(
"--format",
choices=["json", "text"],
default="json",
help="Output format. Default: json.",
)
return parser.parse_args(argv)
def main(argv: list[str] | None = None) -> int:
args = parse_args(argv if argv is not None else sys.argv[1:])
try:
result = search(
query=args.query,
mood=_split_csv(args.mood),
context=_split_csv(args.context),
safety=_split_csv(args.safety),
intensity=_split_csv(args.intensity),
limit=args.limit,
include_deprecated=args.include_deprecated,
index_path=args.index_path,
)
except (FileNotFoundError, ValueError) as error:
print(
json.dumps({"error": str(error)}, ensure_ascii=False),
file=sys.stderr,
)
return 1
if args.format == "json":
print(json.dumps(result, ensure_ascii=False, indent=2))
else:
sys.stdout.write(_format_text(result))
return 0
if __name__ == "__main__":
raise SystemExit(main())
Related skills
FAQ
What is korean-slang-writing?
Use curated Korean trending-slang candidates plus best-effort Namu Wiki lookups to write witty Korean text with up-to-date slang, with conservative safety and freshness guardrails.
When should I use korean-slang-writing?
Use curated Korean trending-slang candidates plus best-effort Namu Wiki lookups to write witty Korean text with up-to-date slang, with conservative safety and freshness guardrails.
Is korean-slang-writing safe to install?
Review the Security Audits panel on this page before production use.