Skill: chloepark85/juso-address-cli (version 0.1.0)

--- name: juso-address-cli description: Search and resolve Korean road addresses (도로명주소) via the official juso.go.kr OpenAPI. Keyword search with zip codes, English address lookup, and address-to-coordinate conversion. Use when a task needs a form-ready Korean address with 우편번호, 영문주소, or lat/lng for a specific address string — e.g. checkout forms, shipping, invoicing, 사업자 등록 forms, real-estate intake. Prefer this over Kakao/Naver geocoders when the requirement is a government-validated road address rather than a map pin. version: 0.1.0 license: MIT --- # juso-address-cli Minimal command-line wrapper for the **행정안전부 주소기반산업지원서비스** (juso.go.kr) OpenAPI. Three operations: 1. `search` — keyword search for 도로명주소 / 지번주소 with 우편번호 (KR). 2. `eng` — English road-address search (for non-Korean-speaking users). 3. `coord` — address → WGS84-ish 좌표 (entrance-coordinate API, grade-B key required). All output is compact JSON, one object per line (JSONL) for easy piping into `jq` or agent post-processing. ## When to use this skill Use when the task is **Korean-address-shaped**: - "Find the full 도로명주소 and 우편번호 for 강남대로 123" - "Normalize this user-entered address against the government registry" - "Give me the 영문주소 for 서울특별시 종로구 세종대로 209" - "Get the entrance lat/lng of this address so I can drop a pin" Do **not** use this skill to: - Do a free-form *place* search (use `kakao-local-cli` keyword search instead). - Reverse-geocode coordinates (juso.go.kr does not publish a reverse endpoint — use `kakao-local-cli coord2addr`). ## Prerequisites 1. Request a **확인키 (confmKey)** at <https://business.juso.go.kr/addrlink/openApi/apiExprn.do>. Grade-A (개발용) is granted instantly; grade-B (운영용) is granted within 1–2 business days and is required for `coord`. 2. Export the key: ```bash export JUSO_CONFM_KEY="devU01TX0FVVEgyMDI1..." # for search/eng export JUSO_CONFM_KEY_COORD="U01TX0FVVEgyMDI1..." # grade-B, for coord ``` 3. Dependencies: `bash`, `curl`, `jq`, `python3` (all default on macOS/Linux). ## Commands ```bash # 1) Keyword search (도로명 or 지번) scripts/search.sh "강남대로 123" scripts/search.sh "세종대로 209" --per-page 20 --page 1 --history Y # 2) English address search scripts/eng.sh "Sejongdae-ro 209" # 3) Address → entrance coordinates (grade-B key required) scripts/coord.sh --admcode 1111000000 \ --rnmgmcd 111104166021 --udrtyn 0 \ --buldmnnm 209 --buldslno 0 # Helper: resolve a raw address string to coordinates in one call. # Internally: search → pick first result → coord. scripts/resolve.sh "서울특별시 종로구 세종대로 209" ``` Every script prints one JSON object per line on stdout; errors go to stderr with a non-zero exit code. ## Example JSONL output ```jsonl {"roadAddr":"서울특별시 종로구 세종대로 209","jibunAddr":"서울특별시 종로구 세종로 55-1 정부서울청사","zipNo":"03171","bdNm":"정부서울청사","siNm":"서울특별시","sggNm":"종로구","emdNm":"세종로","rn":"세종대로","buldMnnm":"209","buldSlno":"0","admCd":"1111051500","rnMgtSn":"111104166021","bdMgtSn":"1111010100100550001000001","udrtYn":"0"} ``` ## Rate limits - juso.go.kr caps free keys at **10 requests/sec** and **30,000 requests/day** per key (grade-A) / **100,000/day** (grade-B). The scripts **do not** retry on 429 — back off in the caller. ## Notes for agents - Always pipe `search.sh` through `jq -s '.[0]'` (or `head -n1`) when you only need the best match. - `resolve.sh` is the one-shot convenience: it silently picks the first `search` hit and then hits `coord`, so it requires both keys. Fail back to `search.sh` alone when `JUSO_CONFM_KEY_COORD` is absent. - The 우편번호 (`zipNo`) returned here is the **5-digit 기초구역번호** (post-2015), not the legacy 6-digit code. - Input may mix 도로명 and 지번; juso.go.kr accepts both and returns a normalized `roadAddr`. - English search requires the street stem in Revised Romanization (e.g. "Sejongdae-ro", not "Sejong-daero"). ## Reference - Official API docs: <https://business.juso.go.kr/addrlink/openApi/apiExprn.do> - Error-code list: <https://business.juso.go.kr/addrlink/devCenterEventBoard/devBoardList.do?cPath=99MD> - License: MIT (this CLI wrapper). The underlying data is public under 공공데이터 이용 약관.

path: README.md
content: # juso-address-cli

Command-line wrapper for the **행정안전부 주소기반산업지원서비스** ([juso.go.kr](https://business.juso.go.kr)) OpenAPI — search and resolve Korean road addresses (도로명주소) from the terminal or an AI agent.

## Why

Every Korean web form, checkout, shipping-label pipeline, invoice, or 사업자 등록 flow needs a **government-validated** road address with a 5-digit 우편번호. Kakao / Naver geocoders are great for map pins but return place data, not form-ready registry addresses. This CLI talks to the official source.

## What you get

- `search.sh` — keyword search (도로명/지번) returning normalized `roadAddr`, `jibunAddr`, `zipNo`, 건물관리번호 etc.
- `eng.sh` — English road-address search.
- `coord.sh` — entrance lat/lng for a resolved address (grade-B 확인키 필요).
- `resolve.sh` — one-shot: raw string → best match → coords.

Output is JSONL — one object per line — so it plays nicely with `jq`, `fx`, or an LLM tool-call.

## Setup

1. Grab a 확인키 at <https://business.juso.go.kr/addrlink/openApi/apiExprn.do>.
2. `export JUSO_CONFM_KEY=...` (and optionally `JUSO_CONFM_KEY_COORD=...` for coordinates).
3. Make scripts executable:
   ```bash
   chmod +x scripts/*.sh
   ```

## Example

```bash
./scripts/search.sh "세종대로 209" --per-page 3 | jq .
./scripts/eng.sh "Sejongdae-ro 209"
./scripts/resolve.sh "서울특별시 종로구 세종대로 209"
```

## Install as a skill

```bash
clawhub install juso-address-cli
```

Or clone this repo into any skills directory — the `SKILL.md` tells agent harnesses how to invoke the scripts.

## License

MIT for the wrapper. The underlying juso.go.kr data is published under 공공데이터 이용 약관.


path: scripts/coord.sh
content: #!/usr/bin/env bash
# juso-address-cli / coord.sh
# Entrance-coordinate lookup (addrCoordApi) — grade-B (운영용) 확인키 required.
# Prints a single JSON object with entX/entY (EPSG:5179-like grid by default).
#
# Usage:
#   coord.sh --admcode <code> --rnmgmcd <rnMgtSn> --udrtyn <0|1> \
#            --buldmnnm <N> --buldslno <N> [--srchwrd <raw>]
#
# Env: JUSO_CONFM_KEY_COORD (required, grade-B)

set -euo pipefail

admcode=""
rnmgmcd=""
udrtyn="0"
buldmnnm=""
buldslno="0"
srchwrd=""

while [[ $# -gt 0 ]]; do
  case "$1" in
    --admcode)  admcode="$2"; shift 2;;
    --rnmgmcd)  rnmgmcd="$2"; shift 2;;
    --udrtyn)   udrtyn="$2"; shift 2;;
    --buldmnnm) buldmnnm="$2"; shift 2;;
    --buldslno) buldslno="$2"; shift 2;;
    --srchwrd)  srchwrd="$2"; shift 2;;
    *) echo "unknown flag: $1" >&2; exit 64;;
  esac
done

if [[ -z "$admcode" || -z "$rnmgmcd" || -z "$buldmnnm" ]]; then
  echo "usage: coord.sh --admcode <code> --rnmgmcd <rnMgtSn> --udrtyn <0|1> --buldmnnm <N> --buldslno <N> [--srchwrd <raw>]" >&2
  echo "hint: run scripts/search.sh first to get admCd, rnMgtSn, udrtYn, buldMnnm, buldSlno" >&2
  exit 64
fi

: "${JUSO_CONFM_KEY_COORD:?JUSO_CONFM_KEY_COORD is not set — grade-B key required for coord lookup}"

resp=$(curl -sS --get \
  --data-urlencode "confmKey=${JUSO_CONFM_KEY_COORD}" \
  --data-urlencode "admCd=${admcode}" \
  --data-urlencode "rnMgtSn=${rnmgmcd}" \
  --data-urlencode "udrtYn=${udrtyn}" \
  --data-urlencode "buldMnnm=${buldmnnm}" \
  --data-urlencode "buldSlno=${buldslno}" \
  --data-urlencode "srchwrd=${srchwrd}" \
  --data-urlencode "resultType=json" \
  "https://business.juso.go.kr/addrlink/addrCoordApi.do")

code=$(printf '%s' "$resp" | jq -r '.results.common.errorCode // empty')
if [[ -n "$code" && "$code" != "0" ]]; then
  msg=$(printf '%s' "$resp" | jq -r '.results.common.errorMessage // "unknown"')
  echo "juso API error ${code}: ${msg}" >&2
  exit 1
fi

printf '%s' "$resp" | jq -c '.results.juso[0] // empty'


path: scripts/eng.sh
content: #!/usr/bin/env bash
# juso-address-cli / eng.sh
# English road-address search (addrEngLinkApi).
# Prints one JSON object per matching English address (JSONL).
#
# Usage:
#   eng.sh <keyword> [--per-page N] [--page N]
#
# Env: JUSO_CONFM_KEY (required)

set -euo pipefail

if [[ $# -lt 1 ]]; then
  echo "usage: eng.sh <keyword> [--per-page N] [--page N]" >&2
  exit 64
fi

keyword="$1"; shift
per_page="10"
page="1"

while [[ $# -gt 0 ]]; do
  case "$1" in
    --per-page) per_page="$2"; shift 2;;
    --page)     page="$2"; shift 2;;
    *) echo "unknown flag: $1" >&2; exit 64;;
  esac
done

: "${JUSO_CONFM_KEY:?JUSO_CONFM_KEY is not set — get a key at https://business.juso.go.kr/addrlink/openApi/apiExprn.do}"

resp=$(curl -sS --get \
  --data-urlencode "confmKey=${JUSO_CONFM_KEY}" \
  --data-urlencode "currentPage=${page}" \
  --data-urlencode "countPerPage=${per_page}" \
  --data-urlencode "keyword=${keyword}" \
  --data-urlencode "resultType=json" \
  "https://business.juso.go.kr/addrlink/addrEngLinkApi.do")

code=$(printf '%s' "$resp" | jq -r '.results.common.errorCode // empty')
if [[ -n "$code" && "$code" != "0" ]]; then
  msg=$(printf '%s' "$resp" | jq -r '.results.common.errorMessage // "unknown"')
  echo "juso API error ${code}: ${msg}" >&2
  exit 1
fi

printf '%s' "$resp" | jq -c '.results.juso[]? // empty'


path: scripts/resolve.sh
content: #!/usr/bin/env bash
# juso-address-cli / resolve.sh
# Convenience: raw address string → top match → entrance coordinates.
# Emits one JSON object combining search fields + entX/entY.
#
# Usage:
#   resolve.sh <raw-address>
#
# Env:
#   JUSO_CONFM_KEY        (required)   grade-A or grade-B, for search
#   JUSO_CONFM_KEY_COORD  (required)   grade-B, for coord

set -euo pipefail

if [[ $# -lt 1 ]]; then
  echo "usage: resolve.sh <raw-address>" >&2
  exit 64
fi

raw="$1"
here="$(cd "$(dirname "$0")" && pwd)"

: "${JUSO_CONFM_KEY:?JUSO_CONFM_KEY is not set}"

match=$("${here}/search.sh" "$raw" --per-page 1 | head -n 1)
if [[ -z "$match" ]]; then
  echo "no match for: $raw" >&2
  exit 2
fi

if [[ -z "${JUSO_CONFM_KEY_COORD:-}" ]]; then
  # No coord key — emit search match alone so the caller still gets structured data.
  printf '%s\n' "$match"
  exit 0
fi

admcode=$(printf '%s' "$match" | jq -r '.admCd')
rnmgmcd=$(printf '%s' "$match" | jq -r '.rnMgtSn')
udrtyn=$(printf '%s' "$match" | jq -r '.udrtYn')
buldmnnm=$(printf '%s' "$match" | jq -r '.buldMnnm')
buldslno=$(printf '%s' "$match" | jq -r '.buldSlno')

coord=$("${here}/coord.sh" \
  --admcode "$admcode" \
  --rnmgmcd "$rnmgmcd" \
  --udrtyn "$udrtyn" \
  --buldmnnm "$buldmnnm" \
  --buldslno "$buldslno" \
  --srchwrd "$raw" || true)

if [[ -z "$coord" ]]; then
  printf '%s\n' "$match"
  exit 0
fi

jq -c -n --argjson a "$match" --argjson b "$coord" '$a + {entX:$b.entX, entY:$b.entY, bdNm:($b.bdNm // $a.bdNm)}'


path: scripts/search.sh
content: #!/usr/bin/env bash
# juso-address-cli / search.sh
# 도로명주소 / 지번주소 keyword search against juso.go.kr addrLinkApi.
# Prints one JSON object per matching address (JSONL) on stdout.
#
# Usage:
#   search.sh <keyword> [--per-page N] [--page N] [--history Y|N] [--sort road|location|none]
#
# Env: JUSO_CONFM_KEY (required, grade-A or grade-B)

set -euo pipefail

if [[ $# -lt 1 ]]; then
  echo "usage: search.sh <keyword> [--per-page N] [--page N] [--history Y|N] [--sort road|location|none]" >&2
  exit 64
fi

keyword="$1"; shift
per_page="10"
page="1"
history="N"
sort="none"

while [[ $# -gt 0 ]]; do
  case "$1" in
    --per-page) per_page="$2"; shift 2;;
    --page)     page="$2"; shift 2;;
    --history)  history="$2"; shift 2;;
    --sort)     sort="$2"; shift 2;;
    *) echo "unknown flag: $1" >&2; exit 64;;
  esac
done

: "${JUSO_CONFM_KEY:?JUSO_CONFM_KEY is not set — get a key at https://business.juso.go.kr/addrlink/openApi/apiExprn.do}"

resp=$(curl -sS --get \
  --data-urlencode "confmKey=${JUSO_CONFM_KEY}" \
  --data-urlencode "currentPage=${page}" \
  --data-urlencode "countPerPage=${per_page}" \
  --data-urlencode "keyword=${keyword}" \
  --data-urlencode "resultType=json" \
  --data-urlencode "hstryYn=${history}" \
  --data-urlencode "firstSort=${sort}" \
  "https://business.juso.go.kr/addrlink/addrLinkApi.do")

code=$(printf '%s' "$resp" | jq -r '.results.common.errorCode // empty')
if [[ -n "$code" && "$code" != "0" ]]; then
  msg=$(printf '%s' "$resp" | jq -r '.results.common.errorMessage // "unknown"')
  echo "juso API error ${code}: ${msg}" >&2
  exit 1
fi

printf '%s' "$resp" | jq -c '.results.juso[]? // empty'


Security scan verdicts — ClawScan: suspicious | static analysis: clean | VirusTotal: clean | SkillSpector: clean (LOW)